086c5c6573
- 修复 N+1 查询: users/devices/agents 批量 GROUP BY 替代循环查询 - 添加分页: cards/finance/agents/devices API - Redis 初始化根据安装配置 redis.enabled 决定是否连接 - 修复 DefaultVal 解析错误: 使用 sql.NullString 处理 NULL 值 - Dashboard 优化: 替换 ECharts 世界地图为 Chart.js 环形饼图 - 适配前端 cards 页面新 API 响应格式 - 添加数据库索引优化查询性能 - 实现可配置的数据清理定时任务
863 lines
24 KiB
Go
863 lines
24 KiB
Go
package extension
|
|
|
|
import (
|
|
"crypto/hmac"
|
|
"crypto/sha256"
|
|
"encoding/hex"
|
|
"fmt"
|
|
"io"
|
|
"strconv"
|
|
"strings"
|
|
"time"
|
|
|
|
"verification-platform-backend/internal/database"
|
|
"verification-platform-backend/internal/model"
|
|
"verification-platform-backend/internal/service"
|
|
"verification-platform-backend/pkg/response"
|
|
|
|
"github.com/gin-gonic/gin"
|
|
"gorm.io/gorm"
|
|
)
|
|
|
|
func SetupRoutes(r *gin.RouterGroup) {
|
|
ext := r.Group("/ext")
|
|
ext.Use(ExtensionAuthMiddleware())
|
|
{
|
|
// 用户相关
|
|
ext.GET("/user/:userId", handleGetUser)
|
|
ext.GET("/users", handleGetUsers)
|
|
ext.POST("/user/:userId/recharge", handleRechargeUser)
|
|
ext.POST("/user/:userId/deduct", handleDeductUser)
|
|
|
|
// 用户变量相关
|
|
ext.GET("/user/:userId/variables", handleGetUserVariables)
|
|
ext.POST("/user/:userId/variables", handleUpdateUserVariables)
|
|
|
|
// 卡密相关
|
|
ext.GET("/cards", handleGetCards)
|
|
ext.POST("/cards/generate", handleGenerateCards)
|
|
ext.GET("/card/:cardId", handleGetCard)
|
|
|
|
// 通知相关
|
|
ext.POST("/notification", handleSendNotification)
|
|
ext.POST("/notification/batch", handleSendBatchNotification)
|
|
|
|
// 应用信息
|
|
ext.GET("/app/info", handleGetAppInfo)
|
|
ext.GET("/app/stats", handleGetAppStats)
|
|
|
|
// 应用变量相关
|
|
ext.GET("/app/variables", handleGetAppVariables)
|
|
ext.POST("/app/variables", handleUpdateAppVariables)
|
|
}
|
|
}
|
|
|
|
func ExtensionAuthMiddleware() gin.HandlerFunc {
|
|
return func(c *gin.Context) {
|
|
accessKey := c.GetHeader("X-Access-Key")
|
|
signature := c.GetHeader("X-Signature")
|
|
timestamp := c.GetHeader("X-Timestamp")
|
|
|
|
if accessKey == "" || signature == "" || timestamp == "" {
|
|
response.Error(c, 401, "缺少认证信息")
|
|
c.Abort()
|
|
return
|
|
}
|
|
|
|
ts, err := strconv.ParseInt(timestamp, 10, 64)
|
|
if err != nil {
|
|
response.Error(c, 401, "时间戳格式错误")
|
|
c.Abort()
|
|
return
|
|
}
|
|
|
|
if time.Now().Unix()-ts > 300 {
|
|
response.Error(c, 401, "请求已过期")
|
|
c.Abort()
|
|
return
|
|
}
|
|
|
|
var apiKey model.ExtensionAPIKey
|
|
if err := database.DB.Where("access_key = ? AND status = ?", accessKey, "active").
|
|
Preload("Application").First(&apiKey).Error; err != nil {
|
|
response.Error(c, 401, "API密钥无效")
|
|
c.Abort()
|
|
return
|
|
}
|
|
|
|
if apiKey.ExpiresAt != nil && apiKey.ExpiresAt.Before(time.Now()) {
|
|
response.Error(c, 401, "API密钥已过期")
|
|
c.Abort()
|
|
return
|
|
}
|
|
|
|
bodyBytes, _ := io.ReadAll(c.Request.Body)
|
|
c.Set("requestBody", bodyBytes)
|
|
c.Request.Body = io.NopCloser(strings.NewReader(string(bodyBytes)))
|
|
|
|
stringToSign := fmt.Sprintf("%s%s%s%s", c.Request.Method, c.Request.URL.Path, timestamp, string(bodyBytes))
|
|
expectedSignature := generateSignature(apiKey.SecretKey, stringToSign)
|
|
|
|
if !hmac.Equal([]byte(signature), []byte(expectedSignature)) {
|
|
response.Error(c, 401, "签名验证失败")
|
|
c.Abort()
|
|
return
|
|
}
|
|
|
|
c.Set("apiKey", apiKey)
|
|
c.Set("applicationID", apiKey.ApplicationID)
|
|
|
|
var app model.Application
|
|
if err := database.DB.First(&app, apiKey.ApplicationID).Error; err == nil {
|
|
c.Set("user_id", app.UserID)
|
|
c.Set("app_id", apiKey.ApplicationID)
|
|
|
|
var user model.User
|
|
if err := database.DB.Preload("CurrentPackage").First(&user, app.UserID).Error; err == nil {
|
|
if user.CurrentPackageID != nil {
|
|
var permission model.PackagePermission
|
|
if err := database.DB.Where("package_id = ?", user.CurrentPackageID).First(&permission).Error; err == nil {
|
|
now := time.Now()
|
|
if user.ApiCallsResetAt == nil || now.Sub(*user.ApiCallsResetAt) >= 24*time.Hour {
|
|
user.ApiCallsUsed = 0
|
|
user.ApiCallsResetAt = &now
|
|
database.DB.Save(&user)
|
|
}
|
|
|
|
if user.ApiCallsUsed >= permission.MaxApiCalls {
|
|
response.Error(c, 403, fmt.Sprintf("API调用次数已达上限(%d次/天),请升级套餐", permission.MaxApiCalls))
|
|
c.Abort()
|
|
return
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
now := time.Now()
|
|
database.DB.Model(&apiKey).Update("last_used_at", now)
|
|
|
|
c.Next()
|
|
|
|
if _, exists := c.Get("app_id"); exists {
|
|
if userID, exists := c.Get("user_id"); exists {
|
|
var user model.User
|
|
if err := database.DB.First(&user, userID).Error; err == nil {
|
|
user.ApiCallsUsed++
|
|
database.DB.Save(&user)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
func generateSignature(secretKey, data string) string {
|
|
h := hmac.New(sha256.New, []byte(secretKey))
|
|
h.Write([]byte(data))
|
|
return hex.EncodeToString(h.Sum(nil))
|
|
}
|
|
|
|
func handleGetUser(c *gin.Context) {
|
|
appID := c.GetUint("applicationID")
|
|
userID := c.Param("userId")
|
|
|
|
var user model.AppUser
|
|
if err := database.DB.Where("id = ? AND application_id = ?", userID, appID).First(&user).Error; err != nil {
|
|
response.Error(c, 404, "用户不存在")
|
|
return
|
|
}
|
|
|
|
response.Success(c, gin.H{
|
|
"id": user.ID,
|
|
"username": user.Username,
|
|
"email": user.Email,
|
|
"status": user.Status,
|
|
"balance": user.Balance,
|
|
"expiry_at": user.ExpiryAt,
|
|
"lastLoginAt": user.LastLoginAt,
|
|
"lastHeartbeatAt": user.LastHeartbeatAt,
|
|
"isTrialUser": user.IsTrialUser,
|
|
"createdAt": user.CreatedAt,
|
|
})
|
|
}
|
|
|
|
func handleGetUsers(c *gin.Context) {
|
|
appID := c.GetUint("applicationID")
|
|
page, _ := strconv.Atoi(c.DefaultQuery("page", "1"))
|
|
pageSize, _ := strconv.Atoi(c.DefaultQuery("page_size", "20"))
|
|
status := c.Query("status")
|
|
search := c.Query("search")
|
|
|
|
var users []model.AppUser
|
|
var total int64
|
|
|
|
query := database.DB.Model(&model.AppUser{}).Where("application_id = ?", appID)
|
|
|
|
if status != "" {
|
|
query = query.Where("status = ?", status)
|
|
}
|
|
|
|
if search != "" {
|
|
query = query.Where("username LIKE ? OR email LIKE ?", "%"+search+"%", "%"+search+"%")
|
|
}
|
|
|
|
query.Count(&total)
|
|
|
|
offset := (page - 1) * pageSize
|
|
if err := query.Order("created_at DESC").Offset(offset).Limit(pageSize).Find(&users).Error; err != nil {
|
|
response.Error(c, 500, "获取用户列表失败")
|
|
return
|
|
}
|
|
|
|
response.Success(c, gin.H{
|
|
"users": users,
|
|
"total": total,
|
|
"page": page,
|
|
"page_size": pageSize,
|
|
"total_page": (total + int64(pageSize) - 1) / int64(pageSize),
|
|
})
|
|
}
|
|
|
|
func handleRechargeUser(c *gin.Context) {
|
|
appID := c.GetUint("applicationID")
|
|
userID := c.Param("userId")
|
|
|
|
var req struct {
|
|
Amount int `json:"amount" binding:"required"`
|
|
Type string `json:"type" binding:"required"` // balance, days
|
|
Description string `json:"description"`
|
|
}
|
|
|
|
if err := c.ShouldBindJSON(&req); err != nil {
|
|
response.Error(c, 400, "参数错误: "+err.Error())
|
|
return
|
|
}
|
|
|
|
var app model.Application
|
|
if err := database.DB.First(&app, appID).Error; err != nil {
|
|
response.Error(c, 404, "应用不存在")
|
|
return
|
|
}
|
|
|
|
var user model.AppUser
|
|
if err := database.DB.Where("id = ? AND application_id = ?", userID, appID).First(&user).Error; err != nil {
|
|
service.LogVerification(c, &app.ID, nil, "extension_recharge_failed", fmt.Sprintf("扩展API充值失败: 用户不存在 - %s", userID), "", fmt.Errorf("用户不存在"))
|
|
response.Error(c, 404, "用户不存在")
|
|
return
|
|
}
|
|
|
|
if user.Balance == -1 {
|
|
service.LogVerification(c, &app.ID, &user.ID, "extension_recharge_failed", fmt.Sprintf("扩展API充值失败: 用户已是永久会员 - %s", user.Username), "", fmt.Errorf("该用户为永久会员,无需充值"))
|
|
response.Error(c, 400, "该用户为永久会员,无需充值")
|
|
return
|
|
}
|
|
|
|
now := time.Now()
|
|
|
|
switch req.Type {
|
|
case "days":
|
|
var baseTime time.Time
|
|
if user.ExpiryAt != nil && user.ExpiryAt.After(now) {
|
|
baseTime = *user.ExpiryAt
|
|
} else {
|
|
baseTime = now
|
|
}
|
|
duration := time.Duration(req.Amount) * 24 * time.Hour
|
|
newExpiry := baseTime.Add(duration)
|
|
user.ExpiryAt = &newExpiry
|
|
case "balance":
|
|
tx := database.DB.Begin()
|
|
defer func() {
|
|
if r := recover(); r != nil {
|
|
tx.Rollback()
|
|
}
|
|
}()
|
|
|
|
if err := tx.Model(&model.AppUser{}).Where("id = ?", user.ID).
|
|
Update("balance", gorm.Expr("balance + ?", float64(req.Amount))).Error; err != nil {
|
|
tx.Rollback()
|
|
service.LogVerification(c, &app.ID, &user.ID, "extension_recharge_failed", fmt.Sprintf("扩展API充值失败: 保存失败 - %s", user.Username), "", err)
|
|
response.Error(c, 500, "充值失败")
|
|
return
|
|
}
|
|
|
|
record := model.RechargeRecord{
|
|
UserID: user.ID,
|
|
OrderNo: fmt.Sprintf("EXT%d%d", time.Now().Unix(), user.ID),
|
|
Amount: float64(req.Amount),
|
|
Status: "success",
|
|
PaymentType: "extension_api",
|
|
Remark: req.Description,
|
|
}
|
|
if err := tx.Create(&record).Error; err != nil {
|
|
tx.Rollback()
|
|
response.Error(c, 500, "充值失败")
|
|
return
|
|
}
|
|
|
|
tx.Commit()
|
|
database.DB.Where("id = ?", user.ID).First(&user)
|
|
|
|
service.LogVerification(c, &app.ID, &user.ID, "extension_recharge", fmt.Sprintf("扩展API充值: 用户%s, 类型:%s, 数量:%d", user.Username, req.Type, req.Amount), "", nil)
|
|
|
|
response.Success(c, gin.H{
|
|
"message": "充值成功",
|
|
"user": user,
|
|
})
|
|
return
|
|
default:
|
|
service.LogVerification(c, &app.ID, &user.ID, "extension_recharge_failed", fmt.Sprintf("扩展API充值失败: 类型无效 - %s", req.Type), "", fmt.Errorf("充值类型无效"))
|
|
response.Error(c, 400, "充值类型无效,仅支持days或balance类型")
|
|
return
|
|
}
|
|
|
|
tx := database.DB.Begin()
|
|
defer func() {
|
|
if r := recover(); r != nil {
|
|
tx.Rollback()
|
|
}
|
|
}()
|
|
|
|
if err := tx.Save(&user).Error; err != nil {
|
|
tx.Rollback()
|
|
service.LogVerification(c, &app.ID, &user.ID, "extension_recharge_failed", fmt.Sprintf("扩展API充值失败: 保存失败 - %s", user.Username), "", err)
|
|
response.Error(c, 500, "充值失败")
|
|
return
|
|
}
|
|
|
|
record := model.RechargeRecord{
|
|
UserID: user.ID,
|
|
OrderNo: fmt.Sprintf("EXT%d%d", time.Now().Unix(), user.ID),
|
|
Amount: float64(req.Amount),
|
|
Status: "success",
|
|
PaymentType: "extension_api",
|
|
Remark: req.Description,
|
|
}
|
|
if err := tx.Create(&record).Error; err != nil {
|
|
tx.Rollback()
|
|
response.Error(c, 500, "充值失败")
|
|
return
|
|
}
|
|
|
|
tx.Commit()
|
|
|
|
service.LogVerification(c, &app.ID, &user.ID, "extension_recharge", fmt.Sprintf("扩展API充值: 用户%s, 类型:%s, 数量:%d", user.Username, req.Type, req.Amount), "", nil)
|
|
|
|
response.Success(c, gin.H{
|
|
"message": "充值成功",
|
|
"user": user,
|
|
})
|
|
}
|
|
|
|
func handleDeductUser(c *gin.Context) {
|
|
appID := c.GetUint("applicationID")
|
|
userID := c.Param("userId")
|
|
|
|
var req struct {
|
|
Amount int `json:"amount" binding:"required"`
|
|
Type string `json:"type" binding:"required"` // balance, days
|
|
Description string `json:"description"`
|
|
}
|
|
|
|
if err := c.ShouldBindJSON(&req); err != nil {
|
|
response.Error(c, 400, "参数错误: "+err.Error())
|
|
return
|
|
}
|
|
|
|
var app model.Application
|
|
database.DB.First(&app, appID)
|
|
|
|
var user model.AppUser
|
|
if err := database.DB.Where("id = ? AND application_id = ?", userID, appID).First(&user).Error; err != nil {
|
|
service.LogVerification(c, &app.ID, nil, "extension_deduct_failed", fmt.Sprintf("扩展API扣费失败: 用户不存在 - %s", userID), "", fmt.Errorf("用户不存在"))
|
|
response.Error(c, 404, "用户不存在")
|
|
return
|
|
}
|
|
|
|
if user.Balance == -1 {
|
|
service.LogVerification(c, &app.ID, &user.ID, "extension_deduct_failed", fmt.Sprintf("扩展API扣费失败: 用户已是永久会员 - %s", user.Username), "", fmt.Errorf("该用户为永久会员,无法扣除"))
|
|
response.Error(c, 400, "该用户为永久会员,无法扣除")
|
|
return
|
|
}
|
|
|
|
now := time.Now()
|
|
|
|
switch req.Type {
|
|
case "days":
|
|
if user.ExpiryAt == nil || user.ExpiryAt.Before(now) {
|
|
service.LogVerification(c, &app.ID, &user.ID, "extension_deduct_failed", fmt.Sprintf("扩展API扣费失败: 用户订阅已过期 - %s", user.Username), "", fmt.Errorf("用户订阅已过期"))
|
|
response.Error(c, 400, "用户订阅已过期")
|
|
return
|
|
}
|
|
duration := time.Duration(req.Amount) * 24 * time.Hour
|
|
newExpiry := user.ExpiryAt.Add(-duration)
|
|
if newExpiry.Before(now) {
|
|
newExpiry = now
|
|
}
|
|
user.ExpiryAt = &newExpiry
|
|
case "balance":
|
|
if user.Balance < float64(req.Amount) {
|
|
service.LogVerification(c, &app.ID, &user.ID, "extension_deduct_failed", fmt.Sprintf("扩展API扣费失败: 余额不足 - %s", user.Username), "", fmt.Errorf("余额不足"))
|
|
response.Error(c, 400, "余额不足")
|
|
return
|
|
}
|
|
|
|
tx := database.DB.Begin()
|
|
defer func() {
|
|
if r := recover(); r != nil {
|
|
tx.Rollback()
|
|
}
|
|
}()
|
|
|
|
result := tx.Model(&model.AppUser{}).Where("id = ? AND balance >= ?", user.ID, float64(req.Amount)).
|
|
Update("balance", gorm.Expr("balance - ?", float64(req.Amount)))
|
|
if result.Error != nil {
|
|
tx.Rollback()
|
|
service.LogVerification(c, &app.ID, &user.ID, "extension_deduct_failed", fmt.Sprintf("扩展API扣费失败: 保存失败 - %s", user.Username), "", result.Error)
|
|
response.Error(c, 500, "扣除失败")
|
|
return
|
|
}
|
|
if result.RowsAffected == 0 {
|
|
tx.Rollback()
|
|
response.Error(c, 400, "余额不足")
|
|
return
|
|
}
|
|
|
|
record := model.ConsumptionRecord{
|
|
UserID: user.ID,
|
|
OrderNo: fmt.Sprintf("EXT%d%d", time.Now().Unix(), user.ID),
|
|
Type: req.Type,
|
|
Amount: float64(req.Amount),
|
|
Status: "success",
|
|
PaymentType: "extension_api",
|
|
Remark: req.Description,
|
|
}
|
|
if err := tx.Create(&record).Error; err != nil {
|
|
tx.Rollback()
|
|
response.Error(c, 500, "扣除失败")
|
|
return
|
|
}
|
|
|
|
tx.Commit()
|
|
database.DB.Where("id = ?", user.ID).First(&user)
|
|
|
|
service.LogVerification(c, &app.ID, &user.ID, "extension_deduct", fmt.Sprintf("扩展API扣费: 用户%s, 类型:%s, 数量:%d", user.Username, req.Type, req.Amount), "", nil)
|
|
|
|
response.Success(c, gin.H{
|
|
"message": "扣除成功",
|
|
"user": user,
|
|
})
|
|
return
|
|
default:
|
|
service.LogVerification(c, &app.ID, &user.ID, "extension_deduct_failed", fmt.Sprintf("扩展API扣费失败: 类型无效 - %s", req.Type), "", fmt.Errorf("扣除类型无效"))
|
|
response.Error(c, 400, "扣除类型无效,仅支持days或balance类型")
|
|
return
|
|
}
|
|
|
|
tx := database.DB.Begin()
|
|
defer func() {
|
|
if r := recover(); r != nil {
|
|
tx.Rollback()
|
|
}
|
|
}()
|
|
|
|
if err := tx.Save(&user).Error; err != nil {
|
|
tx.Rollback()
|
|
service.LogVerification(c, &app.ID, &user.ID, "extension_deduct_failed", fmt.Sprintf("扩展API扣费失败: 保存失败 - %s", user.Username), "", err)
|
|
response.Error(c, 500, "扣除失败")
|
|
return
|
|
}
|
|
|
|
record := model.ConsumptionRecord{
|
|
UserID: user.ID,
|
|
OrderNo: fmt.Sprintf("EXT%d%d", time.Now().Unix(), user.ID),
|
|
Type: req.Type,
|
|
Amount: float64(req.Amount),
|
|
Status: "success",
|
|
PaymentType: "extension_api",
|
|
Remark: req.Description,
|
|
}
|
|
if err := tx.Create(&record).Error; err != nil {
|
|
tx.Rollback()
|
|
response.Error(c, 500, "扣除失败")
|
|
return
|
|
}
|
|
|
|
tx.Commit()
|
|
|
|
service.LogVerification(c, &app.ID, &user.ID, "extension_deduct", fmt.Sprintf("扩展API扣费: 用户%s, 类型:%s, 数量:%d", user.Username, req.Type, req.Amount), "", nil)
|
|
|
|
response.Success(c, gin.H{
|
|
"message": "扣除成功",
|
|
"user": user,
|
|
})
|
|
}
|
|
|
|
func handleGetUserVariables(c *gin.Context) {
|
|
appID := c.GetUint("applicationID")
|
|
userID := c.Param("userId")
|
|
|
|
var user model.AppUser
|
|
if err := database.DB.Where("id = ? AND application_id = ?", userID, appID).First(&user).Error; err != nil {
|
|
response.Error(c, 404, "用户不存在")
|
|
return
|
|
}
|
|
|
|
var variables []model.CloudVariable
|
|
if err := database.DB.Where("app_id = ?", appID).Find(&variables).Error; err != nil {
|
|
response.Error(c, 500, "获取变量定义失败")
|
|
return
|
|
}
|
|
|
|
var userVariables []model.UserVariable
|
|
if err := database.DB.Where("user_id = ? AND app_id = ?", userID, appID).Find(&userVariables).Error; err != nil {
|
|
response.Error(c, 500, "获取用户变量失败")
|
|
return
|
|
}
|
|
|
|
userVarMap := make(map[string]string)
|
|
for _, uv := range userVariables {
|
|
userVarMap[uv.VarName] = uv.VarValue
|
|
}
|
|
|
|
result := make(map[string]interface{})
|
|
for _, v := range variables {
|
|
if v.Scope == "app" {
|
|
result[v.Key] = gin.H{
|
|
"value": v.DefaultValue,
|
|
"scope": "app",
|
|
}
|
|
} else {
|
|
value := v.DefaultValue
|
|
if uv, ok := userVarMap[v.Key]; ok {
|
|
value = uv
|
|
}
|
|
result[v.Key] = gin.H{
|
|
"value": value,
|
|
"scope": "user",
|
|
}
|
|
}
|
|
}
|
|
|
|
response.Success(c, result)
|
|
}
|
|
|
|
func handleUpdateUserVariables(c *gin.Context) {
|
|
appID := c.GetUint("applicationID")
|
|
userID := c.Param("userId")
|
|
|
|
var user model.AppUser
|
|
if err := database.DB.Where("id = ? AND application_id = ?", userID, appID).First(&user).Error; err != nil {
|
|
response.Error(c, 404, "用户不存在")
|
|
return
|
|
}
|
|
|
|
var req struct {
|
|
Variables map[string]string `json:"variables"`
|
|
}
|
|
if err := c.ShouldBindJSON(&req); err != nil {
|
|
response.Error(c, 400, "参数错误")
|
|
return
|
|
}
|
|
|
|
var variables []model.CloudVariable
|
|
if err := database.DB.Where("app_id = ?", appID).Find(&variables).Error; err != nil {
|
|
response.Error(c, 500, "获取变量定义失败")
|
|
return
|
|
}
|
|
|
|
varMap := make(map[string]model.CloudVariable)
|
|
for _, v := range variables {
|
|
varMap[v.Key] = v
|
|
}
|
|
|
|
for key, value := range req.Variables {
|
|
variable, exists := varMap[key]
|
|
if !exists {
|
|
continue
|
|
}
|
|
|
|
if variable.Scope == "app" {
|
|
variable.DefaultValue = value
|
|
database.DB.Save(&variable)
|
|
} else {
|
|
var userVar model.UserVariable
|
|
if err := database.DB.Where("user_id = ? AND app_id = ? AND var_name = ?", userID, appID, key).First(&userVar).Error; err != nil {
|
|
userVar = model.UserVariable{
|
|
UserID: user.ID,
|
|
AppID: appID,
|
|
VarName: key,
|
|
VarValue: value,
|
|
VarType: variable.VarType,
|
|
}
|
|
database.DB.Create(&userVar)
|
|
} else {
|
|
userVar.VarValue = value
|
|
database.DB.Save(&userVar)
|
|
}
|
|
}
|
|
}
|
|
|
|
response.Success(c, gin.H{
|
|
"message": "更新成功",
|
|
})
|
|
}
|
|
|
|
func handleGetAppVariables(c *gin.Context) {
|
|
appID := c.GetUint("applicationID")
|
|
|
|
var variables []model.CloudVariable
|
|
if err := database.DB.Where("app_id = ? AND scope = ?", appID, "app").Find(&variables).Error; err != nil {
|
|
response.Error(c, 500, "获取应用变量失败")
|
|
return
|
|
}
|
|
|
|
result := make(map[string]string)
|
|
for _, v := range variables {
|
|
result[v.Key] = v.DefaultValue
|
|
}
|
|
|
|
response.Success(c, result)
|
|
}
|
|
|
|
func handleUpdateAppVariables(c *gin.Context) {
|
|
appID := c.GetUint("applicationID")
|
|
|
|
var req struct {
|
|
Variables map[string]string `json:"variables"`
|
|
}
|
|
if err := c.ShouldBindJSON(&req); err != nil {
|
|
response.Error(c, 400, "参数错误")
|
|
return
|
|
}
|
|
|
|
for key, value := range req.Variables {
|
|
var variable model.CloudVariable
|
|
if err := database.DB.Where("app_id = ? AND key = ? AND scope = ?", appID, key, "app").First(&variable).Error; err == nil {
|
|
variable.DefaultValue = value
|
|
database.DB.Save(&variable)
|
|
}
|
|
}
|
|
|
|
response.Success(c, gin.H{
|
|
"message": "更新成功",
|
|
})
|
|
}
|
|
|
|
func handleGetCards(c *gin.Context) {
|
|
appID := c.GetUint("applicationID")
|
|
page, _ := strconv.Atoi(c.DefaultQuery("page", "1"))
|
|
pageSize, _ := strconv.Atoi(c.DefaultQuery("page_size", "20"))
|
|
status := c.Query("status")
|
|
cardTypeID := c.Query("card_type_id")
|
|
|
|
var cards []model.Card
|
|
var total int64
|
|
|
|
query := database.DB.Model(&model.Card{}).Where("application_id = ?", appID)
|
|
|
|
if status != "" {
|
|
query = query.Where("status = ?", status)
|
|
}
|
|
|
|
if cardTypeID != "" {
|
|
query = query.Where("card_type_id = ?", cardTypeID)
|
|
}
|
|
|
|
query.Count(&total)
|
|
|
|
offset := (page - 1) * pageSize
|
|
if err := query.Order("created_at DESC").Offset(offset).Limit(pageSize).Find(&cards).Error; err != nil {
|
|
response.Error(c, 500, "获取卡密列表失败")
|
|
return
|
|
}
|
|
|
|
response.Success(c, gin.H{
|
|
"cards": cards,
|
|
"total": total,
|
|
"page": page,
|
|
"page_size": pageSize,
|
|
"total_page": (total + int64(pageSize) - 1) / int64(pageSize),
|
|
})
|
|
}
|
|
|
|
func handleGenerateCards(c *gin.Context) {
|
|
appID := c.GetUint("applicationID")
|
|
|
|
var req struct {
|
|
CardTypeID uint `json:"card_type_id" binding:"required"`
|
|
Count int `json:"count" binding:"required"`
|
|
}
|
|
|
|
if err := c.ShouldBindJSON(&req); err != nil {
|
|
response.Error(c, 400, "参数错误: "+err.Error())
|
|
return
|
|
}
|
|
|
|
var cardType model.CardType
|
|
if err := database.DB.Where("id = ? AND application_id = ?", req.CardTypeID, appID).First(&cardType).Error; err != nil {
|
|
response.Error(c, 404, "卡密类型不存在")
|
|
return
|
|
}
|
|
|
|
cards := make([]model.Card, req.Count)
|
|
for i := 0; i < req.Count; i++ {
|
|
cardKey := generateCardKey()
|
|
cards[i] = model.Card{
|
|
ApplicationID: appID,
|
|
CardTypeID: req.CardTypeID,
|
|
CardKey: cardKey,
|
|
Status: "unused",
|
|
}
|
|
}
|
|
|
|
if err := database.DB.Create(&cards).Error; err != nil {
|
|
response.Error(c, 500, "生成卡密失败")
|
|
return
|
|
}
|
|
|
|
response.Success(c, gin.H{
|
|
"message": "生成成功",
|
|
"count": req.Count,
|
|
"cards": cards,
|
|
})
|
|
}
|
|
|
|
func handleGetCard(c *gin.Context) {
|
|
appID := c.GetUint("applicationID")
|
|
cardID := c.Param("cardId")
|
|
|
|
var card model.Card
|
|
if err := database.DB.Where("id = ? AND application_id = ?", cardID, appID).First(&card).Error; err != nil {
|
|
response.Error(c, 404, "卡密不存在")
|
|
return
|
|
}
|
|
|
|
response.Success(c, card)
|
|
}
|
|
|
|
func handleSendNotification(c *gin.Context) {
|
|
appID := c.GetUint("applicationID")
|
|
|
|
var req struct {
|
|
UserID uint `json:"user_id" binding:"required"`
|
|
Title string `json:"title" binding:"required"`
|
|
Content string `json:"content" binding:"required"`
|
|
Type string `json:"type"`
|
|
}
|
|
|
|
if err := c.ShouldBindJSON(&req); err != nil {
|
|
response.Error(c, 400, "参数错误: "+err.Error())
|
|
return
|
|
}
|
|
|
|
var user model.AppUser
|
|
if err := database.DB.Where("id = ? AND application_id = ?", req.UserID, appID).First(&user).Error; err != nil {
|
|
response.Error(c, 404, "用户不存在")
|
|
return
|
|
}
|
|
|
|
notification := model.Announcement{
|
|
ApplicationID: appID,
|
|
Title: req.Title,
|
|
Content: req.Content,
|
|
Type: req.Type,
|
|
Status: "active",
|
|
}
|
|
|
|
if err := database.DB.Create(¬ification).Error; err != nil {
|
|
response.Error(c, 500, "发送通知失败")
|
|
return
|
|
}
|
|
|
|
response.Success(c, gin.H{
|
|
"message": "发送成功",
|
|
"notification": notification,
|
|
})
|
|
}
|
|
|
|
func handleSendBatchNotification(c *gin.Context) {
|
|
appID := c.GetUint("applicationID")
|
|
|
|
var req struct {
|
|
Title string `json:"title" binding:"required"`
|
|
Content string `json:"content" binding:"required"`
|
|
Type string `json:"type"`
|
|
}
|
|
|
|
if err := c.ShouldBindJSON(&req); err != nil {
|
|
response.Error(c, 400, "参数错误: "+err.Error())
|
|
return
|
|
}
|
|
|
|
notification := model.Announcement{
|
|
ApplicationID: appID,
|
|
Title: req.Title,
|
|
Content: req.Content,
|
|
Type: req.Type,
|
|
Status: "active",
|
|
}
|
|
|
|
if err := database.DB.Create(¬ification).Error; err != nil {
|
|
response.Error(c, 500, "发送通知失败")
|
|
return
|
|
}
|
|
|
|
response.Success(c, gin.H{
|
|
"message": "发送成功",
|
|
"notification": notification,
|
|
})
|
|
}
|
|
|
|
func handleGetAppInfo(c *gin.Context) {
|
|
appID := c.GetUint("applicationID")
|
|
|
|
var app model.Application
|
|
if err := database.DB.First(&app, appID).Error; err != nil {
|
|
response.Error(c, 404, "应用不存在")
|
|
return
|
|
}
|
|
|
|
response.Success(c, gin.H{
|
|
"id": app.ID,
|
|
"name": app.Name,
|
|
"description": app.Description,
|
|
"billing_type": app.BillingType,
|
|
"encrypt_type": app.EncryptType,
|
|
"bind_type": app.BindType,
|
|
"max_devices": app.MaxDevices,
|
|
"multi_open_mode": app.MultiOpenMode,
|
|
"enable_trial": app.EnableTrial,
|
|
"trial_balance": app.TrialBalance,
|
|
"status": app.Status,
|
|
"created_at": app.CreatedAt,
|
|
})
|
|
}
|
|
|
|
func handleGetAppStats(c *gin.Context) {
|
|
appID := c.GetUint("applicationID")
|
|
|
|
var userCount, activeUserCount, cardCount, usedCardCount int64
|
|
|
|
database.DB.Model(&model.AppUser{}).Where("application_id = ?", appID).Count(&userCount)
|
|
database.DB.Model(&model.AppUser{}).Where("application_id = ? AND status = ?", appID, "active").Count(&activeUserCount)
|
|
database.DB.Model(&model.Card{}).Where("application_id = ?", appID).Count(&cardCount)
|
|
database.DB.Model(&model.Card{}).Where("application_id = ? AND status = ?", appID, "used").Count(&usedCardCount)
|
|
|
|
response.Success(c, gin.H{
|
|
"user_count": userCount,
|
|
"active_user_count": activeUserCount,
|
|
"card_count": cardCount,
|
|
"used_card_count": usedCardCount,
|
|
})
|
|
}
|
|
|
|
func generateCardKey() string {
|
|
const charset = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
|
|
b := make([]byte, 16)
|
|
for i := range b {
|
|
b[i] = charset[i%len(charset)]
|
|
}
|
|
return string(b)
|
|
}
|