package agent import ( "fmt" "math/rand" "strconv" "strings" "time" "verification-platform-backend/internal/database" "verification-platform-backend/internal/model" "verification-platform-backend/pkg/response" "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.GET("/finance", handleGetFinance) r.GET("/profile", handleGetProfile) r.PUT("/profile", handleUpdateProfile) r.GET("/cards/export", handleExportCards) } 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 disabledCount int64 database.DB.Model(&model.Card{}).Where("("+baseCondition+") AND status = ?", userID, userID, "disabled").Count(&disabledCount) 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, "disabled_count": disabledCount, }) } 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") 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{ "users": []interface{}{}, "total": 0, }) return } page := c.DefaultQuery("page", "1") pageSize := c.DefaultQuery("page_size", "20") var total int64 database.DB.Model(&model.AppUser{}).Where("application_id IN ?", appIDs).Count(&total) 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("application_id IN ?", appIDs).Order("created_at DESC").Limit(limit).Offset(offset).Find(&users) response.Success(c, gin.H{ "users": users, "total": total, }) } 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 } var records []model.RechargeRecord database.DB.Where("user_id = ?", userID).Order("created_at DESC").Limit(20).Find(&records) response.Success(c, gin.H{ "balance": user.Balance, "records": records, }) } 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, }) } 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": "已过期", "disabled": "已禁用"} 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 }