d0ef52ab4f
- 优化设备管理、版本管理、用户管理、在线实例、公告管理页面的分页功能 - 后端添加筛选参数支持(application_id, status, type, search等) - 前端移除冗余的前端筛选逻辑,改为服务端筛选 - 修复分页总数与筛选数据不匹配的问题 - 优化云端变量记录页面的时间筛选和搜索功能 - 修复DateTimePicker组件关闭后不自动更新的问题
888 lines
24 KiB
Go
888 lines
24 KiB
Go
package admin
|
|
|
|
import (
|
|
"fmt"
|
|
"log"
|
|
"strconv"
|
|
"time"
|
|
"verification-platform-backend/internal/database"
|
|
"verification-platform-backend/internal/model"
|
|
"verification-platform-backend/internal/service"
|
|
"verification-platform-backend/pkg/response"
|
|
"verification-platform-backend/pkg/utils"
|
|
|
|
"github.com/gin-gonic/gin"
|
|
"gorm.io/gorm"
|
|
)
|
|
|
|
type UserWithStatus struct {
|
|
model.AppUser
|
|
OnlineStatus string `json:"online_status"`
|
|
DeviceCount int `json:"device_count"`
|
|
}
|
|
|
|
func getUserOnlineStatus(user model.AppUser, heartbeatTimeout int) string {
|
|
if user.Status == "banned" {
|
|
return "banned"
|
|
}
|
|
|
|
if user.LastHeartbeatAt == nil {
|
|
return "offline"
|
|
}
|
|
|
|
offlineThreshold := time.Duration(heartbeatTimeout) * time.Second
|
|
if time.Since(*user.LastHeartbeatAt) > offlineThreshold {
|
|
return "offline"
|
|
}
|
|
|
|
return "online"
|
|
}
|
|
|
|
func SetupUserRoutes(r *gin.RouterGroup) {
|
|
appUsers := r.Group("/app-users")
|
|
{
|
|
appUsers.GET("", handleGetUsers)
|
|
appUsers.POST("", handleCreateUser)
|
|
appUsers.GET("/:id", handleGetUser)
|
|
appUsers.PUT("/:id", handleUpdateUser)
|
|
appUsers.DELETE("/:id", handleDeleteUser)
|
|
appUsers.GET("/:id/devices", handleGetUserDevices)
|
|
appUsers.DELETE("/:id/devices/:deviceId", handleUnbindDevice)
|
|
appUsers.PUT("/:id/expiry", handleUpdateExpiry)
|
|
appUsers.POST("/batch/status", handleBatchUpdateStatus)
|
|
appUsers.DELETE("/batch", handleBatchDelete)
|
|
}
|
|
}
|
|
|
|
func handleGetUsers(c *gin.Context) {
|
|
userID := c.GetUint("user_id")
|
|
log.Printf("[DEBUG] handleGetUsers called, userID: %d", userID)
|
|
|
|
page, _ := strconv.Atoi(c.DefaultQuery("page", "1"))
|
|
pageSize, _ := strconv.Atoi(c.DefaultQuery("page_size", "20"))
|
|
|
|
applicationID := c.Query("application_id")
|
|
statusFilter := c.Query("status")
|
|
|
|
var users []model.AppUser
|
|
var appHeartbeatTimeoutMap map[uint]int
|
|
if applicationID != "" {
|
|
var appID uint
|
|
if _, err := fmt.Sscanf(applicationID, "%d", &appID); err != nil {
|
|
response.Error(c, 400, "应用ID格式错误")
|
|
return
|
|
}
|
|
|
|
var app model.Application
|
|
if err := database.DB.First(&app, appID).Error; err != nil {
|
|
response.Error(c, 404, "应用不存在")
|
|
return
|
|
}
|
|
|
|
appHeartbeatTimeoutMap = make(map[uint]int)
|
|
timeout := app.HeartbeatTimeout
|
|
if timeout == 0 {
|
|
timeout = 300
|
|
}
|
|
appHeartbeatTimeoutMap[app.ID] = timeout
|
|
|
|
query := database.DB.Preload("Application").Where("application_id = ?", appID)
|
|
if statusFilter != "" {
|
|
query = query.Where("status = ?", statusFilter)
|
|
}
|
|
|
|
if app.UserID == userID {
|
|
if err := query.Find(&users).Error; err != nil {
|
|
response.Error(c, 500, "获取用户列表失败")
|
|
return
|
|
}
|
|
} else {
|
|
var agentApp model.AgentApplication
|
|
if err := database.DB.Where("application_id = ? AND agent_id = ? AND is_received = ?", appID, userID, true).First(&agentApp).Error; err != nil {
|
|
response.Error(c, 403, "无权限查看该应用的用户")
|
|
return
|
|
}
|
|
|
|
var cardUserIDs []uint
|
|
if err := database.DB.Model(&model.Card{}).
|
|
Where("creator_id = ? AND application_id = ? AND app_user_id IS NOT NULL", userID, appID).
|
|
Pluck("app_user_id", &cardUserIDs).Error; err != nil {
|
|
response.Error(c, 500, "获取用户列表失败")
|
|
return
|
|
}
|
|
|
|
if len(cardUserIDs) > 0 {
|
|
query = database.DB.Preload("Application").Where("id IN ?", cardUserIDs)
|
|
if statusFilter != "" {
|
|
query = query.Where("status = ?", statusFilter)
|
|
}
|
|
if err := query.Find(&users).Error; err != nil {
|
|
response.Error(c, 500, "获取用户列表失败")
|
|
return
|
|
}
|
|
}
|
|
}
|
|
} else {
|
|
var ownApps []model.Application
|
|
if err := database.DB.Where("user_id = ?", userID).Find(&ownApps).Error; err != nil {
|
|
response.Error(c, 500, "获取应用列表失败")
|
|
return
|
|
}
|
|
|
|
var agentApps []model.AgentApplication
|
|
if err := database.DB.Where("agent_id = ? AND is_received = ?", userID, true).Find(&agentApps).Error; err != nil {
|
|
log.Printf("[DEBUG] Failed to get agent apps: %v", err)
|
|
response.Error(c, 500, "获取授权列表失败")
|
|
return
|
|
}
|
|
|
|
log.Printf("[DEBUG] Found %d agent apps for user %d", len(agentApps), userID)
|
|
|
|
appHeartbeatTimeoutMap = make(map[uint]int)
|
|
|
|
ownAppIDs := make([]uint, len(ownApps))
|
|
for i, app := range ownApps {
|
|
ownAppIDs[i] = app.ID
|
|
timeout := app.HeartbeatTimeout
|
|
if timeout == 0 {
|
|
timeout = 300
|
|
}
|
|
appHeartbeatTimeoutMap[app.ID] = timeout
|
|
}
|
|
|
|
if len(ownAppIDs) > 0 {
|
|
query := database.DB.Preload("Application").Where("application_id IN ?", ownAppIDs)
|
|
if statusFilter != "" {
|
|
query = query.Where("status = ?", statusFilter)
|
|
}
|
|
if err := query.Find(&users).Error; err != nil {
|
|
response.Error(c, 500, "获取用户列表失败")
|
|
return
|
|
}
|
|
}
|
|
|
|
agentAppIDs := make([]uint, len(agentApps))
|
|
for i, agentApp := range agentApps {
|
|
agentAppIDs[i] = agentApp.ApplicationID
|
|
}
|
|
|
|
var agentAppModels []model.Application
|
|
if len(agentAppIDs) > 0 {
|
|
database.DB.Where("id IN ?", agentAppIDs).Find(&agentAppModels)
|
|
}
|
|
|
|
agentAppMap := make(map[uint]model.Application)
|
|
for _, app := range agentAppModels {
|
|
agentAppMap[app.ID] = app
|
|
timeout := app.HeartbeatTimeout
|
|
if timeout == 0 {
|
|
timeout = 300
|
|
}
|
|
appHeartbeatTimeoutMap[app.ID] = timeout
|
|
}
|
|
|
|
var allCardUserIDs []uint
|
|
for _, agentApp := range agentApps {
|
|
app, exists := agentAppMap[agentApp.ApplicationID]
|
|
if !exists {
|
|
continue
|
|
}
|
|
log.Printf("[DEBUG] Processing agent app: ApplicationID=%d, AppName=%s", app.ID, app.Name)
|
|
|
|
var cardUserIDs []uint
|
|
database.DB.Model(&model.Card{}).
|
|
Where("creator_id = ? AND application_id = ? AND app_user_id IS NOT NULL", userID, app.ID).
|
|
Pluck("app_user_id", &cardUserIDs)
|
|
allCardUserIDs = append(allCardUserIDs, cardUserIDs...)
|
|
}
|
|
|
|
if len(allCardUserIDs) > 0 {
|
|
query := database.DB.Preload("Application").Where("id IN ?", allCardUserIDs)
|
|
if statusFilter != "" {
|
|
query = query.Where("status = ?", statusFilter)
|
|
}
|
|
var appUsers []model.AppUser
|
|
if err := query.Find(&appUsers).Error; err != nil {
|
|
log.Printf("[DEBUG] Failed to get users: %v", err)
|
|
} else {
|
|
users = append(users, appUsers...)
|
|
}
|
|
}
|
|
}
|
|
|
|
totalCount := len(users)
|
|
onlineCount := 0
|
|
offlineCount := 0
|
|
bannedCount := 0
|
|
|
|
userIDs := make([]uint, len(users))
|
|
for i, u := range users {
|
|
userIDs[i] = u.ID
|
|
}
|
|
|
|
deviceCountMap := make(map[uint]int)
|
|
if len(userIDs) > 0 {
|
|
type DeviceCountResult struct {
|
|
UserID uint
|
|
Count int
|
|
}
|
|
var deviceCounts []DeviceCountResult
|
|
database.DB.Model(&model.UserDevice{}).
|
|
Select("user_id, COUNT(*) as count").
|
|
Where("user_id IN ?", userIDs).
|
|
Group("user_id").
|
|
Find(&deviceCounts)
|
|
for _, dc := range deviceCounts {
|
|
deviceCountMap[dc.UserID] = dc.Count
|
|
}
|
|
}
|
|
|
|
usersWithStatus := make([]UserWithStatus, 0, len(users))
|
|
for _, user := range users {
|
|
log.Printf("[DEBUG] User ID=%d, Username=%s, LastLoginAt=%v, LastHeartbeatAt=%v", user.ID, user.Username, user.LastLoginAt, user.LastHeartbeatAt)
|
|
|
|
heartbeatTimeout := 300
|
|
if applicationID != "" {
|
|
heartbeatTimeout = appHeartbeatTimeoutMap[user.ApplicationID]
|
|
} else if appHeartbeatTimeoutMap != nil {
|
|
heartbeatTimeout = appHeartbeatTimeoutMap[user.ApplicationID]
|
|
}
|
|
|
|
onlineStatus := getUserOnlineStatus(user, heartbeatTimeout)
|
|
|
|
switch onlineStatus {
|
|
case "online":
|
|
onlineCount++
|
|
case "offline":
|
|
offlineCount++
|
|
case "banned":
|
|
bannedCount++
|
|
}
|
|
|
|
log.Printf("[DEBUG] 用户 %s (ID=%d) 余额: %f", user.Username, user.ID, user.Balance)
|
|
|
|
usersWithStatus = append(usersWithStatus, UserWithStatus{
|
|
AppUser: user,
|
|
OnlineStatus: onlineStatus,
|
|
DeviceCount: deviceCountMap[user.ID],
|
|
})
|
|
}
|
|
|
|
offset := (page - 1) * pageSize
|
|
end := offset + pageSize
|
|
if offset > len(usersWithStatus) {
|
|
offset = len(usersWithStatus)
|
|
}
|
|
if end > len(usersWithStatus) {
|
|
end = len(usersWithStatus)
|
|
}
|
|
paginatedUsers := usersWithStatus[offset:end]
|
|
|
|
responseData := gin.H{
|
|
"users": paginatedUsers,
|
|
"total": totalCount,
|
|
"page": page,
|
|
"page_size": pageSize,
|
|
"online_count": onlineCount,
|
|
"offline_count": offlineCount,
|
|
"banned_count": bannedCount,
|
|
}
|
|
log.Printf("[DEBUG] Response data: %+v", responseData)
|
|
response.Success(c, responseData)
|
|
}
|
|
|
|
func handleCreateUser(c *gin.Context) {
|
|
userID := c.GetUint("user_id")
|
|
var req struct {
|
|
Username string `json:"username"`
|
|
Email string `json:"email"`
|
|
Password string `json:"password"`
|
|
ApplicationID uint `json:"application_id"`
|
|
CardTypeID *uint `json:"card_type_id"`
|
|
CardQuantity int `json:"card_quantity"`
|
|
}
|
|
if err := c.ShouldBindJSON(&req); err != nil {
|
|
response.Error(c, 400, "参数错误")
|
|
return
|
|
}
|
|
|
|
if req.Username == "" {
|
|
response.Error(c, 400, "用户名不能为空")
|
|
return
|
|
}
|
|
|
|
if req.Password == "" {
|
|
response.Error(c, 400, "密码不能为空")
|
|
return
|
|
}
|
|
|
|
if req.ApplicationID == 0 {
|
|
response.Error(c, 400, "所属应用不能为空")
|
|
return
|
|
}
|
|
|
|
if req.CardQuantity < 1 {
|
|
req.CardQuantity = 1
|
|
}
|
|
if req.CardQuantity > 100 {
|
|
response.Error(c, 400, "卡密数量不能超过100")
|
|
return
|
|
}
|
|
|
|
var app model.Application
|
|
if err := database.DB.First(&app, req.ApplicationID).Error; err != nil {
|
|
response.Error(c, 404, "应用不存在")
|
|
return
|
|
}
|
|
|
|
if app.UserID != userID {
|
|
var agentApp model.AgentApplication
|
|
if err := database.DB.Where("application_id = ? AND agent_id = ? AND is_received = ?", req.ApplicationID, userID, true).First(&agentApp).Error; err != nil {
|
|
response.Error(c, 403, "无权限在该应用下创建用户")
|
|
return
|
|
}
|
|
}
|
|
|
|
var existingUser model.AppUser
|
|
if err := database.DB.Where("username = ? AND application_id = ?", req.Username, app.ID).First(&existingUser).Error; err == nil {
|
|
response.Error(c, 400, "用户已存在")
|
|
return
|
|
}
|
|
|
|
var cardType *model.CardType
|
|
if req.CardTypeID != nil && *req.CardTypeID > 0 {
|
|
var ct model.CardType
|
|
if err := database.DB.Where("id = ? AND application_id = ?", *req.CardTypeID, req.ApplicationID).First(&ct).Error; err != nil {
|
|
response.Error(c, 400, "卡密类型不存在或不属于该应用")
|
|
return
|
|
}
|
|
cardType = &ct
|
|
}
|
|
|
|
tx := database.DB.Begin()
|
|
|
|
user := model.AppUser{
|
|
Username: req.Username,
|
|
Email: req.Email,
|
|
Password: req.Password,
|
|
Avatar: "",
|
|
Status: "active",
|
|
ApplicationID: app.ID,
|
|
}
|
|
|
|
if err := tx.Create(&user).Error; err != nil {
|
|
tx.Rollback()
|
|
response.Error(c, 500, "创建用户失败")
|
|
return
|
|
}
|
|
|
|
var cards []model.Card
|
|
if cardType != nil {
|
|
now := time.Now()
|
|
|
|
for i := 0; i < req.CardQuantity; i++ {
|
|
cardKey := "CK" + utils.GenerateRandomString(16)
|
|
card := model.Card{
|
|
ApplicationID: req.ApplicationID,
|
|
CardTypeID: cardType.ID,
|
|
CardKey: cardKey,
|
|
CreatorID: userID,
|
|
AppUserID: &user.ID,
|
|
Status: "used",
|
|
}
|
|
card.UsedAt = &now
|
|
|
|
if err := tx.Create(&card).Error; err != nil {
|
|
tx.Rollback()
|
|
response.Error(c, 500, "生成卡密失败")
|
|
return
|
|
}
|
|
|
|
user.IsTrialUser = false
|
|
|
|
if cardType.Value == -1 {
|
|
if cardType.RechargeType == "subscription" {
|
|
permanentExpiry := time.Date(9999, 12, 31, 23, 59, 59, 0, time.UTC)
|
|
user.ExpiryAt = &permanentExpiry
|
|
user.Balance = -1
|
|
} else {
|
|
user.Balance = -1
|
|
user.ExpiryAt = nil
|
|
}
|
|
} else {
|
|
switch cardType.RechargeType {
|
|
case "subscription":
|
|
var baseTime time.Time
|
|
if user.ExpiryAt != nil && user.ExpiryAt.After(now) {
|
|
baseTime = *user.ExpiryAt
|
|
} else {
|
|
baseTime = now
|
|
}
|
|
var duration time.Duration
|
|
switch cardType.ValueUnit {
|
|
case "minute":
|
|
duration = time.Duration(cardType.Value) * time.Minute
|
|
case "hour":
|
|
duration = time.Duration(cardType.Value) * time.Hour
|
|
case "day":
|
|
duration = time.Duration(cardType.Value) * 24 * time.Hour
|
|
case "month":
|
|
duration = time.Duration(cardType.Value) * 30 * 24 * time.Hour
|
|
case "year":
|
|
duration = time.Duration(cardType.Value) * 365 * 24 * time.Hour
|
|
default:
|
|
duration = time.Duration(cardType.Value) * time.Second
|
|
}
|
|
newExpiry := baseTime.Add(duration)
|
|
user.ExpiryAt = &newExpiry
|
|
case "balance":
|
|
fallthrough
|
|
default:
|
|
user.Balance += cardType.Value
|
|
}
|
|
}
|
|
|
|
rechargeRecord := model.RechargeRecord{
|
|
UserID: user.ID,
|
|
OrderNo: generateUserOrderNo("R"),
|
|
CardID: &card.ID,
|
|
CardCode: card.CardKey,
|
|
Amount: cardType.Price,
|
|
Status: "success",
|
|
PaymentType: "card",
|
|
Remark: "创建用户充值 - " + cardType.Name,
|
|
}
|
|
|
|
if err := tx.Create(&rechargeRecord).Error; err != nil {
|
|
tx.Rollback()
|
|
response.Error(c, 500, "创建充值记录失败")
|
|
return
|
|
}
|
|
|
|
cards = append(cards, card)
|
|
}
|
|
|
|
if err := tx.Save(&user).Error; err != nil {
|
|
tx.Rollback()
|
|
response.Error(c, 500, "充值失败")
|
|
return
|
|
}
|
|
}
|
|
|
|
if err := tx.Commit().Error; err != nil {
|
|
response.Error(c, 500, "创建用户失败")
|
|
return
|
|
}
|
|
|
|
logDesc := fmt.Sprintf("创建用户: %s (应用: %s)", user.Username, app.Name)
|
|
if cardType != nil {
|
|
logDesc += fmt.Sprintf(",充值卡密: %s x%d", cardType.Name, req.CardQuantity)
|
|
}
|
|
service.LogOperation(c, "create", "app_user", &user.ID, logDesc, nil)
|
|
|
|
result := gin.H{
|
|
"user": user,
|
|
}
|
|
if len(cards) > 0 {
|
|
result["cards"] = cards
|
|
}
|
|
|
|
response.Success(c, result)
|
|
}
|
|
|
|
func generateUserOrderNo(prefix string) string {
|
|
return prefix + time.Now().Format("20060102150405") + utils.GenerateRandomString(6)
|
|
}
|
|
|
|
func handleGetUser(c *gin.Context) {
|
|
userID := c.GetUint("user_id")
|
|
id := c.Param("id")
|
|
var user model.AppUser
|
|
if err := database.DB.Preload("Application").First(&user, id).Error; err != nil {
|
|
response.Error(c, 404, "用户不存在")
|
|
return
|
|
}
|
|
|
|
var app model.Application
|
|
if err := database.DB.First(&app, user.ApplicationID).Error; err != nil {
|
|
response.Error(c, 404, "应用不存在")
|
|
return
|
|
}
|
|
|
|
if app.UserID != userID {
|
|
var agentApp model.AgentApplication
|
|
if err := database.DB.Where("application_id = ? AND agent_id = ? AND is_received = ?", app.ID, userID, true).First(&agentApp).Error; err != nil {
|
|
response.Error(c, 403, "无权限查看该用户")
|
|
return
|
|
}
|
|
|
|
var card model.Card
|
|
if err := database.DB.Where("creator_id = ? AND app_user_id = ?", userID, user.ID).First(&card).Error; err != nil {
|
|
response.Error(c, 403, "无权限查看该用户")
|
|
return
|
|
}
|
|
}
|
|
|
|
response.Success(c, gin.H{
|
|
"user": user,
|
|
})
|
|
}
|
|
|
|
func handleUpdateUser(c *gin.Context) {
|
|
userID := c.GetUint("user_id")
|
|
id := c.Param("id")
|
|
var req struct {
|
|
Username string `json:"username"`
|
|
Email string `json:"email"`
|
|
Password string `json:"password"`
|
|
Status string `json:"status"`
|
|
ApplicationID uint `json:"application_id"`
|
|
}
|
|
if err := c.ShouldBindJSON(&req); err != nil {
|
|
response.Error(c, 400, "参数错误")
|
|
return
|
|
}
|
|
|
|
var user model.AppUser
|
|
if err := database.DB.First(&user, id).Error; err != nil {
|
|
response.Error(c, 404, "用户不存在")
|
|
return
|
|
}
|
|
|
|
var app model.Application
|
|
if err := database.DB.First(&app, user.ApplicationID).Error; err != nil {
|
|
response.Error(c, 404, "应用不存在")
|
|
return
|
|
}
|
|
|
|
if app.UserID != userID {
|
|
var agentApp model.AgentApplication
|
|
if err := database.DB.Where("application_id = ? AND agent_id = ? AND is_received = ?", app.ID, userID, true).First(&agentApp).Error; err != nil {
|
|
response.Error(c, 403, "无权限修改该用户")
|
|
return
|
|
}
|
|
|
|
var card model.Card
|
|
if err := database.DB.Where("creator_id = ? AND app_user_id = ?", userID, user.ID).First(&card).Error; err != nil {
|
|
response.Error(c, 403, "无权限修改该用户")
|
|
return
|
|
}
|
|
}
|
|
|
|
if req.Username != "" {
|
|
user.Username = req.Username
|
|
}
|
|
if req.Email != "" {
|
|
user.Email = req.Email
|
|
}
|
|
if req.Password != "" {
|
|
user.Password = req.Password
|
|
}
|
|
if req.Status != "" {
|
|
user.Status = req.Status
|
|
}
|
|
if req.ApplicationID != 0 {
|
|
var newApp model.Application
|
|
if err := database.DB.First(&newApp, req.ApplicationID).Error; err != nil {
|
|
response.Error(c, 404, "目标应用不存在")
|
|
return
|
|
}
|
|
if newApp.UserID != userID {
|
|
var agentApp model.AgentApplication
|
|
if err := database.DB.Where("application_id = ? AND agent_id = ? AND is_received = ?", req.ApplicationID, userID, true).First(&agentApp).Error; err != nil {
|
|
response.Error(c, 403, "无权限将用户转移到该应用")
|
|
return
|
|
}
|
|
}
|
|
user.ApplicationID = req.ApplicationID
|
|
}
|
|
|
|
if err := database.DB.Save(&user).Error; err != nil {
|
|
response.Error(c, 500, "更新用户失败")
|
|
return
|
|
}
|
|
|
|
service.LogOperation(c, "update", "app_user", &user.ID, fmt.Sprintf("更新用户: %s", user.Username), nil)
|
|
|
|
response.Success(c, user)
|
|
}
|
|
|
|
func handleDeleteUser(c *gin.Context) {
|
|
userID := c.GetUint("user_id")
|
|
id := c.Param("id")
|
|
|
|
var user model.AppUser
|
|
if err := database.DB.First(&user, id).Error; err != nil {
|
|
response.Error(c, 404, "用户不存在")
|
|
return
|
|
}
|
|
|
|
var app model.Application
|
|
if err := database.DB.First(&app, user.ApplicationID).Error; err != nil {
|
|
response.Error(c, 404, "应用不存在")
|
|
return
|
|
}
|
|
|
|
if app.UserID != userID {
|
|
var agentApp model.AgentApplication
|
|
if err := database.DB.Where("application_id = ? AND agent_id = ? AND is_received = ?", app.ID, userID, true).First(&agentApp).Error; err != nil {
|
|
response.Error(c, 403, "无权限删除该用户")
|
|
return
|
|
}
|
|
|
|
var card model.Card
|
|
if err := database.DB.Where("creator_id = ? AND app_user_id = ?", userID, user.ID).First(&card).Error; err != nil {
|
|
response.Error(c, 403, "无权限删除该用户")
|
|
return
|
|
}
|
|
}
|
|
|
|
if err := database.DB.Delete(&user).Error; err != nil {
|
|
response.Error(c, 500, "删除用户失败")
|
|
return
|
|
}
|
|
|
|
service.LogOperation(c, "delete", "app_user", &user.ID, fmt.Sprintf("删除用户: %s (应用: %s)", user.Username, app.Name), nil)
|
|
|
|
response.Success(c, nil)
|
|
}
|
|
|
|
func handleGetUserDevices(c *gin.Context) {
|
|
userID := c.GetUint("user_id")
|
|
id := c.Param("id")
|
|
|
|
var user model.AppUser
|
|
if err := database.DB.First(&user, id).Error; err != nil {
|
|
response.Error(c, 404, "用户不存在")
|
|
return
|
|
}
|
|
|
|
var app model.Application
|
|
if err := database.DB.First(&app, user.ApplicationID).Error; err != nil {
|
|
response.Error(c, 404, "应用不存在")
|
|
return
|
|
}
|
|
|
|
if app.UserID != userID {
|
|
var agentApp model.AgentApplication
|
|
if err := database.DB.Where("application_id = ? AND agent_id = ? AND is_received = ?", app.ID, userID, true).First(&agentApp).Error; err != nil {
|
|
response.Error(c, 403, "无权限查看该用户设备")
|
|
return
|
|
}
|
|
}
|
|
|
|
var devices []model.UserDevice
|
|
if err := database.DB.Where("user_id = ? AND application_id = ?", user.ID, app.ID).Find(&devices).Error; err != nil {
|
|
response.Error(c, 500, "获取设备列表失败")
|
|
return
|
|
}
|
|
|
|
response.Success(c, devices)
|
|
}
|
|
|
|
func handleUpdateExpiry(c *gin.Context) {
|
|
userID := c.GetUint("user_id")
|
|
id := c.Param("id")
|
|
var req struct {
|
|
Amount float64 `json:"amount"`
|
|
Type string `json:"type"`
|
|
Field string `json:"field"`
|
|
}
|
|
if err := c.ShouldBindJSON(&req); err != nil {
|
|
response.Error(c, 400, "参数错误")
|
|
return
|
|
}
|
|
|
|
var appUser model.AppUser
|
|
if err := database.DB.First(&appUser, id).Error; err != nil {
|
|
response.Error(c, 404, "用户不存在")
|
|
return
|
|
}
|
|
|
|
var app model.Application
|
|
if err := database.DB.First(&app, appUser.ApplicationID).Error; err != nil {
|
|
response.Error(c, 404, "应用不存在")
|
|
return
|
|
}
|
|
|
|
if app.UserID != userID {
|
|
var agentApp model.AgentApplication
|
|
if err := database.DB.Where("application_id = ? AND agent_id = ? AND is_received = ?", app.ID, userID, true).First(&agentApp).Error; err != nil {
|
|
response.Error(c, 403, "无权限修改该用户")
|
|
return
|
|
}
|
|
|
|
var card model.Card
|
|
if err := database.DB.Where("creator_id = ? AND app_user_id = ?", userID, appUser.ID).First(&card).Error; err != nil {
|
|
response.Error(c, 403, "无权限修改该用户")
|
|
return
|
|
}
|
|
}
|
|
|
|
if req.Amount == 0 {
|
|
response.Error(c, 400, "数值不能为0")
|
|
return
|
|
}
|
|
|
|
if appUser.Balance == -1 {
|
|
response.Error(c, 400, "该用户为永久会员,无需操作")
|
|
return
|
|
}
|
|
|
|
now := time.Now()
|
|
|
|
if app.BillingType == "subscription" || req.Field == "days" {
|
|
if req.Type == "recharge" {
|
|
var baseTime time.Time
|
|
if appUser.ExpiryAt != nil && appUser.ExpiryAt.After(now) {
|
|
baseTime = *appUser.ExpiryAt
|
|
} else {
|
|
baseTime = now
|
|
}
|
|
duration := time.Duration(req.Amount) * 24 * time.Hour
|
|
newExpiry := baseTime.Add(duration)
|
|
appUser.ExpiryAt = &newExpiry
|
|
} else if req.Type == "deduct" {
|
|
if appUser.ExpiryAt == nil || appUser.ExpiryAt.Before(now) {
|
|
response.Error(c, 400, "用户订阅已过期")
|
|
return
|
|
}
|
|
duration := time.Duration(req.Amount) * 24 * time.Hour
|
|
newExpiry := appUser.ExpiryAt.Add(-duration)
|
|
if newExpiry.Before(now) {
|
|
newExpiry = now
|
|
}
|
|
appUser.ExpiryAt = &newExpiry
|
|
} else {
|
|
response.Error(c, 400, "操作类型错误")
|
|
return
|
|
}
|
|
} else {
|
|
if req.Type == "recharge" {
|
|
if err := database.DB.Model(&model.AppUser{}).Where("id = ?", appUser.ID).
|
|
Update("balance", gorm.Expr("balance + ?", req.Amount)).Error; err != nil {
|
|
response.Error(c, 500, "充值失败")
|
|
return
|
|
}
|
|
} else if req.Type == "deduct" {
|
|
result := database.DB.Model(&model.AppUser{}).Where("id = ? AND balance >= ?", appUser.ID, req.Amount).
|
|
Update("balance", gorm.Expr("balance - ?", req.Amount))
|
|
if result.Error != nil {
|
|
response.Error(c, 500, "扣费失败")
|
|
return
|
|
}
|
|
if result.RowsAffected == 0 {
|
|
response.Error(c, 400, "余额不足")
|
|
return
|
|
}
|
|
} else {
|
|
response.Error(c, 400, "操作类型错误")
|
|
return
|
|
}
|
|
}
|
|
|
|
if app.BillingType == "balance" {
|
|
database.DB.Where("id = ?", appUser.ID).First(&appUser)
|
|
} else {
|
|
if err := database.DB.Save(&appUser).Error; err != nil {
|
|
response.Error(c, 500, "更新失败")
|
|
return
|
|
}
|
|
}
|
|
|
|
response.Success(c, appUser)
|
|
}
|
|
|
|
func handleBatchUpdateStatus(c *gin.Context) {
|
|
userID := c.GetUint("user_id")
|
|
var req struct {
|
|
UserIDs []uint `json:"user_ids"`
|
|
Status string `json:"status"`
|
|
}
|
|
if err := c.ShouldBindJSON(&req); err != nil {
|
|
response.Error(c, 400, "参数错误")
|
|
return
|
|
}
|
|
|
|
var users []model.AppUser
|
|
if err := database.DB.Where("id IN ?", req.UserIDs).Find(&users).Error; err != nil {
|
|
response.Error(c, 500, "获取用户列表失败")
|
|
return
|
|
}
|
|
|
|
var validUserIDs []uint
|
|
for _, user := range users {
|
|
var app model.Application
|
|
if err := database.DB.First(&app, user.ApplicationID).Error; err != nil {
|
|
continue
|
|
}
|
|
|
|
if app.UserID == userID {
|
|
validUserIDs = append(validUserIDs, user.ID)
|
|
} else {
|
|
var agentApp model.AgentApplication
|
|
if err := database.DB.Where("application_id = ? AND agent_id = ? AND is_received = ?", app.ID, userID, true).First(&agentApp).Error; err == nil {
|
|
var card model.Card
|
|
if err := database.DB.Where("creator_id = ? AND app_user_id = ?", userID, user.ID).First(&card).Error; err == nil {
|
|
validUserIDs = append(validUserIDs, user.ID)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if len(validUserIDs) > 0 {
|
|
if err := database.DB.Model(&model.AppUser{}).Where("id IN ?", validUserIDs).Update("status", req.Status).Error; err != nil {
|
|
response.Error(c, 500, "批量更新状态失败")
|
|
return
|
|
}
|
|
}
|
|
|
|
response.Success(c, nil)
|
|
}
|
|
|
|
func handleBatchDelete(c *gin.Context) {
|
|
userID := c.GetUint("user_id")
|
|
var req struct {
|
|
UserIDs []uint `json:"user_ids"`
|
|
}
|
|
if err := c.ShouldBindJSON(&req); err != nil {
|
|
response.Error(c, 400, "参数错误")
|
|
return
|
|
}
|
|
|
|
var users []model.AppUser
|
|
if err := database.DB.Where("id IN ?", req.UserIDs).Find(&users).Error; err != nil {
|
|
response.Error(c, 500, "获取用户列表失败")
|
|
return
|
|
}
|
|
|
|
var validUserIDs []uint
|
|
for _, user := range users {
|
|
var app model.Application
|
|
if err := database.DB.First(&app, user.ApplicationID).Error; err != nil {
|
|
continue
|
|
}
|
|
|
|
if app.UserID == userID {
|
|
validUserIDs = append(validUserIDs, user.ID)
|
|
} else {
|
|
var agentApp model.AgentApplication
|
|
if err := database.DB.Where("application_id = ? AND agent_id = ? AND is_received = ?", app.ID, userID, true).First(&agentApp).Error; err == nil {
|
|
var card model.Card
|
|
if err := database.DB.Where("creator_id = ? AND app_user_id = ?", userID, user.ID).First(&card).Error; err == nil {
|
|
validUserIDs = append(validUserIDs, user.ID)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if len(validUserIDs) > 0 {
|
|
if err := database.DB.Where("id IN ?", validUserIDs).Delete(&model.AppUser{}).Error; err != nil {
|
|
response.Error(c, 500, "批量删除失败")
|
|
return
|
|
}
|
|
}
|
|
|
|
response.Success(c, nil)
|
|
}
|