Files
verify/backend/internal/router/agent/agent.go
T
admin d83e7dfac9 fix: 添加代理生成卡密余额检查和扣费逻辑
- 检查代理余额是否足够
- 检查并限制生成数量(1-100)
- 使用事务确保数据一致性
- 扣除代理余额
- 记录消费记录

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-10 02:20:45 +08:00

451 lines
12 KiB
Go

package agent
import (
"fmt"
"math/rand"
"strconv"
"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)
}
func handleGetStats(c *gin.Context) {
userID := c.GetUint("user_id")
var totalApps int64
database.DB.Model(&model.AgentApplication{}).Where("agent_id = ?", userID).Count(&totalApps)
var totalCards int64
database.DB.Model(&model.Card{}).Where("agent_id = ?", 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 = ?", 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 = ? AND DATE(created_at) = ?", userID, today).Count(&todayCards)
var todayRevenue float64
database.DB.Model(&model.Card{}).Where("agent_id = ? AND DATE(created_at) = ?", 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")
var total int64
database.DB.Model(&model.Card{}).Where("agent_id = ?", userID).Count(&total)
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
}
database.DB.Where("agent_id = ?", userID).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,
})
}
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)
}