package agent import ( "encoding/json" "fmt" "math/rand" "sort" "strconv" "strings" "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" ) func SetupAgentRoutes(r *gin.RouterGroup) { r.GET("/stats", handleGetStats) r.GET("/apps", handleGetApps) r.GET("/apps/:id", handleGetAppDetail) r.GET("/cards", handleGetCards) r.POST("/cards/generate", handleGenerateCards) r.GET("/users", handleGetUsers) r.POST("/users", handleCreateUser) r.PUT("/users/:id/status", handleUpdateUserStatus) r.GET("/finance", handleGetFinance) r.GET("/profile", handleGetProfile) r.PUT("/profile", handleUpdateProfile) r.GET("/cards/export", handleExportCards) r.GET("/cloud-variables", handleGetCloudVariables) r.GET("/cloud-variables/:id/records", handleGetCloudVariableRecords) } func handleGetStats(c *gin.Context) { userID := c.GetUint("user_id") var totalApps int64 database.DB.Model(&model.AgentApplication{}).Where("agent_id = ?", userID).Count(&totalApps) // 统计卡密数:包括 agent_id 或 creator_id 等于当前用户的卡密 var totalCards int64 database.DB.Model(&model.Card{}).Where("agent_id = ? OR (agent_id IS NULL AND creator_id = ?)", userID, userID).Count(&totalCards) var totalUsers int64 var agentApps []model.AgentApplication database.DB.Where("agent_id = ?", userID).Find(&agentApps) appIDs := make([]uint, 0) for _, app := range agentApps { appIDs = append(appIDs, app.ApplicationID) } if len(appIDs) > 0 { database.DB.Model(&model.AppUser{}).Where("application_id IN ?", appIDs).Count(&totalUsers) } var totalRevenue float64 database.DB.Model(&model.Card{}).Where("agent_id = ? OR (agent_id IS NULL AND creator_id = ?)", userID, userID).Select("COALESCE(SUM(price), 0)").Scan(&totalRevenue) today := time.Now().Format("2006-01-02") var todayCards int64 database.DB.Model(&model.Card{}).Where("(agent_id = ? OR (agent_id IS NULL AND creator_id = ?)) AND DATE(created_at) = ?", userID, userID, today).Count(&todayCards) var todayRevenue float64 database.DB.Model(&model.Card{}).Where("(agent_id = ? OR (agent_id IS NULL AND creator_id = ?)) AND DATE(created_at) = ?", userID, userID, today).Select("COALESCE(SUM(price), 0)").Scan(&todayRevenue) response.Success(c, gin.H{ "totalApps": totalApps, "totalCards": totalCards, "totalUsers": totalUsers, "totalRevenue": totalRevenue, "todayCards": todayCards, "todayRevenue": todayRevenue, }) } func handleGetApps(c *gin.Context) { userID := c.GetUint("user_id") var agentApps []model.AgentApplication if err := database.DB.Where("agent_id = ?", userID).Preload("Application").Find(&agentApps).Error; err != nil { response.Error(c, 500, "获取应用列表失败") return } apps := make([]gin.H, 0) for _, aa := range agentApps { if aa.Application.ID > 0 { apps = append(apps, gin.H{ "id": aa.Application.ID, "name": aa.Application.Name, "description": aa.Application.Description, "status": aa.Application.Status, "authorized_at": aa.CreatedAt, }) } } response.Success(c, gin.H{ "apps": apps, }) } func handleGetAppDetail(c *gin.Context) { userID := c.GetUint("user_id") appID := c.Param("id") // 检查代理是否有该应用的授权 var agentApp model.AgentApplication if err := database.DB.Where("agent_id = ? AND application_id = ?", userID, appID).Preload("Application").First(&agentApp).Error; err != nil { response.Error(c, 404, "应用不存在或无权访问") return } // 获取该应用可用的卡类 var cardTypes []model.CardType if err := database.DB.Where("application_id = ?", appID).Find(&cardTypes).Error; err != nil { response.Error(c, 500, "获取卡类列表失败") return } // 获取代理对该应用的卡类权限和价格 var agentCardTypes []model.AgentApplicationCardType database.DB.Where("agent_application_id = ?", agentApp.ID).Find(&agentCardTypes) // 构建卡类列表,包含代理权限信息 cardTypesList := make([]gin.H, 0) for _, ct := range cardTypes { // 查找代理是否有该卡类的权限 var agentPrice float64 var canGenerate bool for _, act := range agentCardTypes { if act.CardTypeID == ct.ID { agentPrice = act.Price canGenerate = act.CanGenerate break } } // 只返回代理有权限的卡类 if canGenerate { cardTypesList = append(cardTypesList, gin.H{ "id": ct.ID, "name": ct.Name, "billing_type": ct.RechargeType, "price": agentPrice, "value": ct.Value, "duration_days": ct.Value, }) } } response.Success(c, gin.H{ "app": gin.H{ "id": agentApp.Application.ID, "name": agentApp.Application.Name, "description": agentApp.Application.Description, "status": agentApp.Application.Status, }, "cardTypes": cardTypesList, }) } func handleGetCards(c *gin.Context) { userID := c.GetUint("user_id") page := c.DefaultQuery("page", "1") pageSize := c.DefaultQuery("page_size", "20") applicationID := c.Query("application_id") cardTypeID := c.Query("card_type_id") status := c.Query("status") search := c.Query("search") startDate := c.Query("start_date") endDate := c.Query("end_date") baseCondition := "agent_id = ? OR (agent_id IS NULL AND creator_id = ?)" var total int64 database.DB.Model(&model.Card{}).Where(baseCondition, userID, userID).Count(&total) var unusedCount int64 database.DB.Model(&model.Card{}).Where("("+baseCondition+") AND status = ?", userID, userID, "unused").Count(&unusedCount) var usedCount int64 database.DB.Model(&model.Card{}).Where("("+baseCondition+") AND status = ?", userID, userID, "used").Count(&usedCount) var expiredCount int64 database.DB.Model(&model.Card{}).Where("("+baseCondition+") AND status = ?", userID, userID, "expired").Count(&expiredCount) var bannedCount int64 database.DB.Model(&model.Card{}).Where("("+baseCondition+") AND status = ?", userID, userID, "banned").Count(&bannedCount) query := database.DB.Model(&model.Card{}).Where(baseCondition, userID, userID) if applicationID != "" { appID, err := strconv.ParseUint(applicationID, 10, 32) if err == nil { query = query.Where("application_id = ?", uint(appID)) } } if cardTypeID != "" { ctID, err := strconv.ParseUint(cardTypeID, 10, 32) if err == nil { query = query.Where("card_type_id = ?", uint(ctID)) } } if status != "" { query = query.Where("status = ?", status) } if search != "" { searchPattern := "%" + search + "%" query = query.Where("card_key LIKE ?", searchPattern) } if startDate != "" { query = query.Where("created_at >= ?", startDate+" 00:00:00") } if endDate != "" { query = query.Where("created_at <= ?", endDate+" 23:59:59") } var filteredTotal int64 query.Count(&filteredTotal) var cards []model.Card offset := 0 if pageInt, err := strconv.Atoi(page); err == nil && pageInt > 1 { offset = (pageInt - 1) * 20 } limit := 20 if pageSizeInt, err := strconv.Atoi(pageSize); err == nil && pageSizeInt > 0 { limit = pageSizeInt } query.Order("created_at DESC").Limit(limit).Offset(offset).Find(&cards) // 获取关联数据 cardTypeIDs := make([]uint, 0) appIDs := make([]uint, 0) for _, card := range cards { cardTypeIDs = append(cardTypeIDs, card.CardTypeID) appIDs = append(appIDs, card.ApplicationID) } cardTypeMap := make(map[uint]model.CardType) if len(cardTypeIDs) > 0 { var cardTypes []model.CardType database.DB.Where("id IN ?", cardTypeIDs).Find(&cardTypes) for _, ct := range cardTypes { cardTypeMap[ct.ID] = ct } } appMap := make(map[uint]model.Application) if len(appIDs) > 0 { var apps []model.Application database.DB.Where("id IN ?", appIDs).Find(&apps) for _, app := range apps { appMap[app.ID] = app } } // 构建返回数据 cardList := make([]gin.H, 0) for _, card := range cards { ct := cardTypeMap[card.CardTypeID] app := appMap[card.ApplicationID] cardList = append(cardList, gin.H{ "id": card.ID, "code": card.CardKey, "app_name": app.Name, "card_type_name": ct.Name, "status": card.Status, "created_at": card.CreatedAt, "used_at": card.UsedAt, }) } response.Success(c, gin.H{ "cards": cardList, "total": total, "filtered_total": filteredTotal, "unused_count": unusedCount, "used_count": usedCount, "expired_count": expiredCount, "banned_count": bannedCount, }) } func handleGenerateCards(c *gin.Context) { userID := c.GetUint("user_id") var req struct { ApplicationID uint `json:"application_id"` AppID uint `json:"app_id"` // 兼容前端传的app_id CardTypeID uint `json:"card_type_id"` Quantity int `json:"quantity"` } if err := c.ShouldBindJSON(&req); err != nil { response.Error(c, 400, "参数错误") return } // 限制最大生成数量 if req.Quantity <= 0 || req.Quantity > 100 { response.Error(c, 400, "生成数量必须在1-100之间") return } // 兼容 app_id 和 application_id appID := req.ApplicationID if appID == 0 { appID = req.AppID } var agentApp model.AgentApplication if err := database.DB.Where("agent_id = ? AND application_id = ?", userID, appID).First(&agentApp).Error; err != nil { response.Error(c, 403, "无权操作该应用") return } // 检查代理是否有该卡类的生成权限 var agentCardType model.AgentApplicationCardType if err := database.DB.Where("agent_application_id = ? AND card_type_id = ? AND can_generate = ?", agentApp.ID, req.CardTypeID, true).First(&agentCardType).Error; err != nil { response.Error(c, 403, "无权生成该卡类") return } // 计算总价格 totalPrice := agentCardType.Price * float64(req.Quantity) // 获取代理余额并检查是否足够 var user model.User if err := database.DB.First(&user, userID).Error; err != nil { response.Error(c, 404, "用户不存在") return } if user.Balance < totalPrice { response.Error(c, 400, fmt.Sprintf("余额不足,当前余额: %.2f,需要: %.2f", user.Balance, totalPrice)) return } // 使用事务确保数据一致性 tx := database.DB.Begin() cards := make([]model.Card, req.Quantity) for i := 0; i < req.Quantity; i++ { cards[i] = model.Card{ ApplicationID: appID, CardTypeID: req.CardTypeID, CardKey: generateCardCode(), CreatorID: userID, AgentID: &userID, Status: "unused", } } if err := tx.Create(&cards).Error; err != nil { tx.Rollback() response.Error(c, 500, "生成卡密失败") return } // 扣除余额 if err := tx.Model(&user).Update("balance", user.Balance-totalPrice).Error; err != nil { tx.Rollback() response.Error(c, 500, "扣除余额失败") return } // 记录消费记录 consumeRecord := model.RechargeRecord{ UserID: userID, OrderNo: fmt.Sprintf("CARD%d%d", userID, time.Now().UnixNano()), Amount: -totalPrice, Status: "success", PaymentType: "balance", Remark: fmt.Sprintf("生成卡密 %d 张", req.Quantity), } if err := tx.Create(&consumeRecord).Error; err != nil { tx.Rollback() response.Error(c, 500, "记录消费失败") return } tx.Commit() // 返回生成的卡号 codes := make([]string, req.Quantity) for i, card := range cards { codes[i] = card.CardKey } response.Success(c, gin.H{ "count": req.Quantity, "codes": codes, "totalPrice": totalPrice, "balance": user.Balance - totalPrice, }) } func generateCardCode() string { const charset = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789" b := make([]byte, 16) for i := range b { b[i] = charset[rand.Intn(len(charset))] } return string(b) } func handleGetUsers(c *gin.Context) { userID := c.GetUint("user_id") agentIDs := []uint{userID} var childAgents []model.User database.DB.Where("parent_agent_id = ? AND role = ?", userID, "agent").Find(&childAgents) for _, child := range childAgents { agentIDs = append(agentIDs, child.ID) } var cardIDs []uint database.DB.Model(&model.Card{}).Where("agent_id IN ?", agentIDs).Pluck("id", &cardIDs) if len(cardIDs) == 0 { response.Success(c, gin.H{ "users": []interface{}{}, "total": 0, "active_count": 0, "disabled_count": 0, "banned_count": 0, }) return } var appUserIDs []uint database.DB.Model(&model.RechargeRecord{}). Where("card_id IN ? AND status = ?", cardIDs, "success"). Distinct("user_id"). Pluck("user_id", &appUserIDs) if len(appUserIDs) == 0 { response.Success(c, gin.H{ "users": []interface{}{}, "total": 0, "active_count": 0, "disabled_count": 0, "banned_count": 0, }) return } page := c.DefaultQuery("page", "1") pageSize := c.DefaultQuery("page_size", "20") var total int64 database.DB.Model(&model.AppUser{}).Where("id IN ?", appUserIDs).Count(&total) var activeCount, disabledCount, bannedCount int64 database.DB.Model(&model.AppUser{}).Where("id IN ? AND status = ?", appUserIDs, "active").Count(&activeCount) database.DB.Model(&model.AppUser{}).Where("id IN ? AND status = ?", appUserIDs, "disabled").Count(&disabledCount) database.DB.Model(&model.AppUser{}).Where("id IN ? AND status = ?", appUserIDs, "banned").Count(&bannedCount) var users []model.AppUser offset := 0 if pageInt, err := strconv.Atoi(page); err == nil && pageInt > 1 { offset = (pageInt - 1) * 20 } limit := 20 if pageSizeInt, err := strconv.Atoi(pageSize); err == nil && pageSizeInt > 0 { limit = pageSizeInt } database.DB.Where("id IN ?", appUserIDs).Order("created_at DESC").Limit(limit).Offset(offset).Find(&users) response.Success(c, gin.H{ "users": users, "total": total, "active_count": activeCount, "disabled_count": disabledCount, "banned_count": bannedCount, }) } func handleGetFinance(c *gin.Context) { userID := c.GetUint("user_id") var user model.User if err := database.DB.First(&user, userID).Error; err != nil { response.Error(c, 404, "用户不存在") return } typeFilter := c.Query("type") page, _ := strconv.Atoi(c.DefaultQuery("page", "1")) pageSize, _ := strconv.Atoi(c.DefaultQuery("page_size", "20")) type TransactionItem struct { ID uint `json:"id"` Type string `json:"type"` Amount float64 `json:"amount"` Description string `json:"description"` Balance float64 `json:"balance"` CreatedAt string `json:"created_at"` } var allTransactions []TransactionItem var rechargeRecords []model.RechargeRecord database.DB.Where("user_id = ? AND status = ?", userID, "success").Order("created_at DESC").Find(&rechargeRecords) for _, r := range rechargeRecords { txType := "recharge" if r.PaymentType == "refund" || r.Remark != "" && strings.Contains(strings.ToLower(r.Remark), "refund") { txType = "refund" } desc := r.Remark if desc == "" { desc = "充值" } allTransactions = append(allTransactions, TransactionItem{ ID: r.ID, Type: txType, Amount: r.Amount, Description: desc, Balance: user.Balance, CreatedAt: r.CreatedAt.Format("2006-01-02T15:04:05Z07:00"), }) } var consumptionRecords []model.ConsumptionRecord database.DB.Where("user_id = ?", userID).Order("created_at DESC").Find(&consumptionRecords) for _, r := range consumptionRecords { desc := r.Description if desc == "" { desc = r.Content } if desc == "" { desc = "消费" } allTransactions = append(allTransactions, TransactionItem{ ID: r.ID, Type: "consume", Amount: r.Amount, Description: desc, Balance: r.BalanceAfter, CreatedAt: r.CreatedAt.Format("2006-01-02T15:04:05Z07:00"), }) } sort.Slice(allTransactions, func(i, j int) bool { return allTransactions[i].CreatedAt > allTransactions[j].CreatedAt }) var filtered []TransactionItem if typeFilter != "" { for _, tx := range allTransactions { if tx.Type == typeFilter { filtered = append(filtered, tx) } } } else { filtered = allTransactions } total := len(filtered) rechargeCount := 0 consumeCount := 0 var totalRecharge, totalConsume float64 for _, tx := range allTransactions { if tx.Type == "recharge" || tx.Type == "refund" { rechargeCount++ totalRecharge += tx.Amount } else if tx.Type == "consume" { consumeCount++ totalConsume += tx.Amount } } offset := (page - 1) * pageSize end := offset + pageSize if offset > total { offset = total } if end > total { end = total } paginated := filtered[offset:end] response.Success(c, gin.H{ "balance": user.Balance, "transactions": paginated, "total": total, "page": page, "page_size": pageSize, "recharge_count": rechargeCount, "consume_count": consumeCount, "total_recharge": totalRecharge, "total_consume": totalConsume, }) } func handleGetProfile(c *gin.Context) { userID := c.GetUint("user_id") var user model.User if err := database.DB.First(&user, userID).Error; err != nil { response.Error(c, 404, "用户不存在") return } response.Success(c, gin.H{ "id": user.ID, "username": user.Username, "email": user.Email, "avatar": user.Avatar, "balance": user.Balance, "can_create_agent": user.CanCreateAgent, "can_view_cloud_data": user.CanViewCloudData, }) } func handleUpdateProfile(c *gin.Context) { userID := c.GetUint("user_id") var req struct { Email string `json:"email"` } if err := c.ShouldBindJSON(&req); err != nil { response.Error(c, 400, "参数错误") return } var user model.User if err := database.DB.First(&user, userID).Error; err != nil { response.Error(c, 404, "用户不存在") return } if req.Email != "" { user.Email = &req.Email } database.DB.Save(&user) response.Success(c, nil) } func handleExportCards(c *gin.Context) { userID := c.GetUint("user_id") token := c.Query("token") if token == "" { response.Error(c, 401, "未授权") return } baseCondition := "agent_id = ? OR (agent_id IS NULL AND creator_id = ?)" query := database.DB.Model(&model.Card{}).Where(baseCondition, userID, userID) if applicationID := c.Query("application_id"); applicationID != "" { appID, err := strconv.ParseUint(applicationID, 10, 32) if err == nil { query = query.Where("application_id = ?", uint(appID)) } } if cardTypeID := c.Query("card_type_id"); cardTypeID != "" { ctID, err := strconv.ParseUint(cardTypeID, 10, 32) if err == nil { query = query.Where("card_type_id = ?", uint(ctID)) } } if status := c.Query("status"); status != "" { query = query.Where("status = ?", status) } if startDate := c.Query("start_date"); startDate != "" { query = query.Where("created_at >= ?", startDate+" 00:00:00") } if endDate := c.Query("end_date"); endDate != "" { query = query.Where("created_at <= ?", endDate+" 23:59:59") } if ids := c.Query("ids"); ids != "" { idList := []uint{} for _, idStr := range splitIDs(ids) { if id, err := strconv.ParseUint(idStr, 10, 32); err == nil { idList = append(idList, uint(id)) } } if len(idList) > 0 { query = database.DB.Model(&model.Card{}).Where("id IN ? AND ("+baseCondition+")", idList, userID, userID) } } var cards []model.Card query.Order("created_at DESC").Find(&cards) cardTypeIDs := make([]uint, 0) appIDs := make([]uint, 0) for _, card := range cards { cardTypeIDs = append(cardTypeIDs, card.CardTypeID) appIDs = append(appIDs, card.ApplicationID) } cardTypeMap := make(map[uint]model.CardType) if len(cardTypeIDs) > 0 { var cardTypes []model.CardType database.DB.Where("id IN ?", cardTypeIDs).Find(&cardTypes) for _, ct := range cardTypes { cardTypeMap[ct.ID] = ct } } appMap := make(map[uint]model.Application) if len(appIDs) > 0 { var apps []model.Application database.DB.Where("id IN ?", appIDs).Find(&apps) for _, app := range apps { appMap[app.ID] = app } } c.Header("Content-Type", "text/csv; charset=utf-8") c.Header("Content-Disposition", "attachment; filename=cards_export.csv") c.Writer.Write([]byte("\xEF\xBB\xBF")) c.Writer.Write([]byte("卡号,应用,卡类,状态,创建时间,使用时间\n")) for _, card := range cards { ct := cardTypeMap[card.CardTypeID] app := appMap[card.ApplicationID] statusMap := map[string]string{"unused": "未使用", "used": "已使用", "expired": "已过期", "banned": "已禁用"} statusText := statusMap[card.Status] if statusText == "" { statusText = card.Status } usedAt := "" if card.UsedAt != nil { usedAt = card.UsedAt.Format("2006-01-02 15:04:05") } line := fmt.Sprintf("%s,%s,%s,%s,%s,%s\n", card.CardKey, app.Name, ct.Name, statusText, card.CreatedAt.Format("2006-01-02 15:04:05"), usedAt, ) c.Writer.Write([]byte(line)) } } func splitIDs(ids string) []string { result := []string{} for _, id := range strings.Split(ids, ",") { id = strings.TrimSpace(id) if id != "" { result = append(result, id) } } return result } func handleGetCloudVariables(c *gin.Context) { userID := c.GetUint("user_id") var agent model.User if err := database.DB.First(&agent, userID).Error; err != nil { response.Error(c, 404, "代理不存在") return } if !agent.CanViewCloudData { response.Error(c, 403, "无权查看云端变量") return } var agentApps []model.AgentApplication database.DB.Where("agent_id = ?", userID).Find(&agentApps) appIDs := make([]uint, 0) for _, app := range agentApps { appIDs = append(appIDs, app.ApplicationID) } if len(appIDs) == 0 { response.Success(c, gin.H{ "variables": []interface{}{}, "total": 0, }) return } appIDFilter := c.Query("app_id") var variables []model.CloudVariable query := database.DB.Where("app_id IN ?", appIDs) if appIDFilter != "" { if aid, err := strconv.ParseUint(appIDFilter, 10, 32); err == nil { query = database.DB.Where("app_id = ?", uint(aid)) } } query.Find(&variables) appMap := make(map[uint]string) var apps []model.Application database.DB.Where("id IN ?", appIDs).Find(&apps) for _, app := range apps { appMap[app.ID] = app.Name } result := make([]gin.H, 0) for _, v := range variables { appName := "" if v.AppID != nil { appName = appMap[*v.AppID] } result = append(result, gin.H{ "id": v.ID, "key": v.Key, "app_id": v.AppID, "application_name": appName, "default_value": v.DefaultValue, "var_type": v.VarType, "max_records": v.MaxRecords, "scope": v.Scope, "write_permission": v.WritePermission, "status": v.Status, "description": v.Description, "created_at": v.CreatedAt.Format("2006-01-02T15:04:05Z07:00"), }) } response.Success(c, gin.H{ "variables": result, "total": len(result), }) } func handleGetCloudVariableRecords(c *gin.Context) { userID := c.GetUint("user_id") var agent model.User if err := database.DB.First(&agent, userID).Error; err != nil { response.Error(c, 404, "代理不存在") return } if !agent.CanViewCloudData { response.Error(c, 403, "无权查看云端变量") return } variableID := c.Param("id") var variable model.CloudVariable if err := database.DB.First(&variable, variableID).Error; err != nil { response.Error(c, 404, "云端变量不存在") return } var agentApps []model.AgentApplication database.DB.Where("agent_id = ?", userID).Find(&agentApps) appIDs := make(map[uint]bool) for _, app := range agentApps { appIDs[app.ApplicationID] = true } if variable.AppID == nil || !appIDs[*variable.AppID] { response.Error(c, 403, "无权查看该变量的记录") return } if variable.VarType != "stream" { response.Error(c, 400, "该变量不是记录类型") return } page, _ := strconv.Atoi(c.DefaultQuery("page", "1")) pageSize, _ := strconv.Atoi(c.DefaultQuery("page_size", "20")) if page < 1 { page = 1 } if pageSize < 1 || pageSize > 100 { pageSize = 20 } startDate := c.Query("start_date") endDate := c.Query("end_date") var total int64 query := database.DB.Model(&model.CloudVariableRecord{}).Where("cloud_variable_id = ?", variable.ID) if startDate != "" { query = query.Where("created_at >= ?", startDate+" 00:00:00") } if endDate != "" { query = query.Where("created_at <= ?", endDate+" 23:59:59") } query.Count(&total) var records []model.CloudVariableRecord offset := (page - 1) * pageSize if err := query.Order("created_at DESC").Limit(pageSize).Offset(offset).Find(&records).Error; err != nil { response.Error(c, 500, "获取记录失败") return } result := make([]gin.H, len(records)) for i, r := range records { var parsedData interface{} if r.Data != "" { json.Unmarshal([]byte(r.Data), &parsedData) } if parsedData == nil && r.Data != "" { parsedData = r.Data } record := gin.H{ "id": r.ID, "data": parsedData, "created_at": r.CreatedAt, } if r.AppUserID != nil { record["user_id"] = r.AppUserID var appUser model.AppUser if err := database.DB.Select("id, username").First(&appUser, *r.AppUserID).Error; err == nil { record["user"] = gin.H{ "id": appUser.ID, "username": appUser.Username, } } } result[i] = record } response.Success(c, gin.H{ "records": result, "total": total, "page": page, "page_size": pageSize, "total_pages": (total + int64(pageSize) - 1) / int64(pageSize), }) } 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 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 app model.Application if err := database.DB.First(&app, req.ApplicationID).Error; err != nil { response.Error(c, 404, "应用不存在") 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, AgentID: &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: generateAgentOrderNo("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 generateAgentOrderNo(prefix string) string { return prefix + time.Now().Format("20060102150405") + utils.GenerateRandomString(6) } func handleUpdateUserStatus(c *gin.Context) { userID := c.GetUint("user_id") id := c.Param("id") var req struct { Status string `json:"status"` } if err := c.ShouldBindJSON(&req); err != nil { response.Error(c, 400, "参数错误") return } if req.Status != "active" && req.Status != "banned" { response.Error(c, 400, "状态值无效,仅支持 active 或 banned") return } var user model.AppUser if err := database.DB.First(&user, id).Error; err != nil { response.Error(c, 404, "用户不存在") return } agentIDs := []uint{userID} var childAgents []model.User database.DB.Where("parent_agent_id = ? AND role = ?", userID, "agent").Find(&childAgents) for _, child := range childAgents { agentIDs = append(agentIDs, child.ID) } var cardIDs []uint database.DB.Model(&model.Card{}).Where("agent_id IN ?", agentIDs).Pluck("id", &cardIDs) if len(cardIDs) == 0 { response.Error(c, 403, "无权限操作该用户") return } var count int64 database.DB.Model(&model.RechargeRecord{}). Where("user_id = ? AND card_id IN ? AND status = ?", user.ID, cardIDs, "success"). Count(&count) if count == 0 { response.Error(c, 403, "无权限操作该用户") return } user.Status = req.Status if err := database.DB.Save(&user).Error; err != nil { response.Error(c, 500, "更新用户状态失败") return } logDesc := fmt.Sprintf("代理更新用户状态: %s -> %s", user.Username, req.Status) service.LogOperation(c, "update", "app_user", &user.ID, logDesc, nil) response.Success(c, user) }