feat: add user creation and ban/unban for agent backend
- Backend: POST /agent/users - create user with optional card recharge - Backend: PUT /agent/users/:id/status - ban/unban user - Frontend: add create user page for agent (select app, card type) - Frontend: add ban/unban action in user list with confirm dialog - Frontend: add actions dropdown column in user data table - Fix: cloud variables records page i18n key (agent.finance.to -> agent.cloudVariables.records.to) - Add i18n keys for agent user management (create, ban, unban)
This commit is contained in:
@@ -10,7 +10,9 @@ import (
|
||||
"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"
|
||||
)
|
||||
@@ -22,6 +24,8 @@ func SetupAgentRoutes(r *gin.RouterGroup) {
|
||||
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)
|
||||
@@ -945,3 +949,261 @@ func handleGetCloudVariableRecords(c *gin.Context) {
|
||||
"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)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user