Initial commit: 网络验证平台

This commit is contained in:
Admin
2026-04-27 17:22:56 +08:00
commit afe67d704e
780 changed files with 88960 additions and 0 deletions
+185
View File
@@ -0,0 +1,185 @@
package app
import (
"log"
"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"
)
func SetupAccountRoutes(r *gin.RouterGroup) {
r.GET("/account", handleAppGetAccount)
r.POST("/heartbeat", handleAppHeartbeat)
}
func handleAppGetAccount(c *gin.Context) {
appKey := c.Param("appKey")
var app model.Application
if err := database.DB.Where("app_key = ?", appKey).First(&app).Error; err != nil {
response.Error(c, 404, "应用不存在")
return
}
if service.GetApplicationDisabledStatus(app.ID) {
response.Error(c, 403, "该应用已被禁用")
return
}
var req struct {
UserID uint `json:"user_id"`
}
if err := c.ShouldBindJSON(&req); err != nil {
response.Error(c, 400, "参数错误")
return
}
var user model.AppUser
if err := database.DB.First(&user, req.UserID).Error; err != nil {
response.Error(c, 404, "用户不存在")
return
}
response.Success(c, gin.H{
"user_id": user.ID,
"username": user.Username,
"balance": user.Balance,
"status": user.Status,
})
}
func handleAppHeartbeat(c *gin.Context) {
appKey := c.Param("appKey")
var app model.Application
if err := database.DB.Where("app_key = ?", appKey).First(&app).Error; err != nil {
response.Error(c, 404, "应用不存在")
return
}
if service.GetApplicationDisabledStatus(app.ID) {
response.Error(c, 403, "该应用已被禁用")
return
}
var req struct {
UserID uint `json:"user_id"`
DeviceID string `json:"device_id"`
InstanceID string `json:"instance_id"`
}
if err := c.ShouldBindJSON(&req); err != nil {
response.Error(c, 400, "参数错误")
return
}
var user model.AppUser
if err := database.DB.First(&user, req.UserID).Error; err != nil {
response.Error(c, 404, "用户不存在")
return
}
if blocked, reason := checkRiskControl(c, app.ID, req.DeviceID, user.Username); blocked {
log.Printf("[DEBUG] Heartbeat blocked by risk control: %s", reason)
response.Error(c, 403, reason)
return
}
now := time.Now()
user.LastHeartbeatAt = &now
if app.BillingType != "free" && app.DeductionMode == "auto" && app.DeductionAmount > 0 {
shouldDeduct := false
switch app.DeductionType {
case "timer":
if user.LastHeartbeatAt != nil {
elapsed := now.Sub(*user.LastHeartbeatAt)
var interval time.Duration
switch app.DeductionUnit {
case "minute":
interval = time.Duration(app.DeductionInterval) * time.Minute
case "hour":
interval = time.Duration(app.DeductionInterval) * time.Hour
case "day":
interval = time.Duration(app.DeductionInterval) * 24 * time.Hour
default:
interval = time.Duration(app.DeductionInterval) * time.Minute
}
if elapsed >= interval {
shouldDeduct = true
}
}
case "login":
shouldDeduct = true
}
if shouldDeduct {
if user.Balance >= app.DeductionAmount {
user.Balance -= app.DeductionAmount
log.Printf("[DEBUG] Deducted %.2f from user %d, new balance: %.2f", app.DeductionAmount, user.ID, user.Balance)
} else {
log.Printf("[DEBUG] User %d has insufficient balance: %.2f < %.2f", user.ID, user.Balance, app.DeductionAmount)
response.Error(c, 403, "余额不足")
return
}
}
}
if err := database.DB.Save(&user).Error; err != nil {
response.Error(c, 500, "更新心跳失败")
return
}
if req.DeviceID != "" {
var device model.UserDevice
err := database.DB.Where("user_id = ? AND application_id = ? AND device_id = ?", user.ID, app.ID, req.DeviceID).First(&device).Error
if err != nil {
device = model.UserDevice{
UserID: user.ID,
ApplicationID: app.ID,
DeviceID: req.DeviceID,
DeviceName: req.DeviceID,
Status: "active",
}
if err := database.DB.Create(&device).Error; err != nil {
log.Printf("[DEBUG] Failed to create device: %v", err)
}
}
instanceID := req.InstanceID
if instanceID == "" {
instanceID = req.DeviceID
}
if device.ID > 0 {
var session model.DeviceSession
sessionErr := database.DB.Where("device_id = ? AND instance_id = ?", device.ID, instanceID).First(&session).Error
if sessionErr != nil {
session = model.DeviceSession{
DeviceID: device.ID,
UserID: user.ID,
ApplicationID: app.ID,
InstanceID: instanceID,
LastHeartbeat: &now,
}
if err := database.DB.Create(&session).Error; err != nil {
log.Printf("[DEBUG] Failed to create session: %v", err)
}
} else {
session.LastHeartbeat = &now
if err := database.DB.Save(&session).Error; err != nil {
log.Printf("[DEBUG] Failed to update session: %v", err)
}
}
}
}
response.SuccessWithMessage(c, "心跳成功", gin.H{
"message": "心跳成功",
"balance": user.Balance,
})
}
+15
View File
@@ -0,0 +1,15 @@
package app
import (
"github.com/gin-gonic/gin"
)
func SetupRoutes(r *gin.RouterGroup) {
SetupPaymentRoutes(r)
SetupAccountRoutes(r)
SetupDynamicRoutes(r)
}
func SetupAuthRoutes(r *gin.RouterGroup) {
SetupDeviceRoutes(r)
}
@@ -0,0 +1,100 @@
package app
import (
"fmt"
"verification-platform-backend/internal/database"
"verification-platform-backend/internal/model"
"verification-platform-backend/pkg/crypto"
"github.com/gin-gonic/gin"
)
func AppCryptoMiddleware() gin.HandlerFunc {
return func(c *gin.Context) {
fmt.Printf("[AppCrypto] Middleware called\n")
appKey := c.Param("appKey")
fmt.Printf("[AppCrypto] AppKey from param: %s\n", appKey)
if appKey == "" {
fmt.Printf("[AppCrypto] AppKey is empty, skipping\n")
c.Next()
return
}
var app model.Application
if err := database.DB.Where("app_key = ?", appKey).First(&app).Error; err != nil {
fmt.Printf("[AppCrypto] Database error: %v\n", err)
c.Next()
return
}
fmt.Printf("[AppCrypto] App found: %+v\n", app)
var encryptType crypto.EncryptType
switch app.EncryptType {
case "aes":
encryptType = crypto.EncryptTypeAES
case "rc4":
encryptType = crypto.EncryptTypeRC4
default:
encryptType = crypto.EncryptTypeNone
}
shouldEncrypt := encryptType != crypto.EncryptTypeNone
fmt.Printf("[AppCrypto] AppKey: %s, EncryptType: %s, SecretKey: %s, ShouldEncrypt: %v\n",
appKey, app.EncryptType, app.SecretKey, shouldEncrypt)
c.Set("should_encrypt_response", shouldEncrypt)
c.Set("crypto_manager", crypto.NewCryptoManager(encryptType, app.SecretKey))
c.Next()
}
}
func getAppCryptoManager(appKey string) (*crypto.CryptoManager, error) {
var app model.Application
if err := database.DB.Where("app_key = ?", appKey).First(&app).Error; err != nil {
return nil, err
}
var encryptType crypto.EncryptType
switch app.EncryptType {
case "aes":
encryptType = crypto.EncryptTypeAES
case "rc4":
encryptType = crypto.EncryptTypeRC4
default:
encryptType = crypto.EncryptTypeNone
}
return crypto.NewCryptoManager(encryptType, app.SecretKey), nil
}
func decryptRequest(c *gin.Context, appKey string) ([]byte, error) {
encryptedData := c.GetHeader("X-Encrypted-Data")
if encryptedData == "" {
return nil, nil
}
cryptoManager, err := getAppCryptoManager(appKey)
if err != nil {
return nil, err
}
decrypted, err := cryptoManager.Decrypt(encryptedData)
if err != nil {
return nil, err
}
return []byte(decrypted), nil
}
func encryptResponse(c *gin.Context, appKey string, data []byte) (string, error) {
cryptoManager, err := getAppCryptoManager(appKey)
if err != nil {
return "", err
}
return cryptoManager.Encrypt(string(data))
}
+905
View File
@@ -0,0 +1,905 @@
package app
import (
"encoding/json"
"fmt"
"log"
"math/rand"
"strings"
"time"
"verification-platform-backend/internal/database"
"verification-platform-backend/internal/model"
"verification-platform-backend/internal/service"
"verification-platform-backend/pkg/jwt"
"verification-platform-backend/pkg/response"
"github.com/gin-gonic/gin"
"golang.org/x/crypto/bcrypt"
)
func getDeviceType(c *gin.Context, clientDeviceType string) string {
validTypes := map[string]bool{
"android": true,
"ios": true,
"windows": true,
"mac": true,
"linux": true,
"web": true,
}
if clientDeviceType != "" && validTypes[clientDeviceType] {
return clientDeviceType
}
userAgent := c.GetHeader("User-Agent")
return parseDeviceTypeFromUserAgent(userAgent)
}
func parseDeviceTypeFromUserAgent(userAgent string) string {
if userAgent == "" {
return "unknown"
}
ua := strings.ToLower(userAgent)
switch {
case strings.Contains(ua, "android"):
return "android"
case strings.Contains(ua, "iphone") || strings.Contains(ua, "ipad") || strings.Contains(ua, "ipod"):
return "ios"
case strings.Contains(ua, "windows"):
return "windows"
case strings.Contains(ua, "macintosh") || strings.Contains(ua, "mac os x"):
return "mac"
case strings.Contains(ua, "linux"):
return "linux"
case strings.Contains(ua, "mozilla") || strings.Contains(ua, "webkit") || strings.Contains(ua, "chrome") || strings.Contains(ua, "safari"):
return "web"
default:
return "unknown"
}
}
func SetupAuthUserRoutes(r *gin.RouterGroup) {
r.POST("/register", handleAppRegister)
r.POST("/login", handleAppLogin)
r.POST("/send-email-code", handleAppSendEmailCode)
r.POST("/reset-password", handleAppResetPassword)
}
func handleAppSendEmailCode(c *gin.Context) {
appKey := c.Param("appKey")
var app model.Application
if err := database.DB.Where("app_key = ?", appKey).First(&app).Error; err != nil {
response.Error(c, 404, "应用不存在")
return
}
if service.GetApplicationDisabledStatus(app.ID) {
response.Error(c, 403, "该应用已被禁用")
return
}
var req struct {
Email string `json:"email" binding:"required,email"`
Purpose string `json:"purpose"`
}
if err := c.ShouldBindJSON(&req); err != nil {
response.Error(c, 400, "请输入有效的邮箱地址")
return
}
if req.Purpose == "" {
req.Purpose = "register"
}
if req.Purpose == "register" {
if !app.EnableEmailVerify {
response.Error(c, 400, "该应用未启用邮箱验证")
return
}
} else if req.Purpose == "reset_password" {
if !app.EnablePasswordReset {
response.Error(c, 400, "该应用未启用密码重置功能")
return
}
}
var smtpConfig model.AppSMTPConfig
if err := database.DB.Where("application_id = ?", app.ID).First(&smtpConfig).Error; err != nil {
response.Error(c, 400, "应用未配置SMTP")
return
}
var template model.EmailTemplate
database.DB.Where("application_id = ? AND type = ? AND status = ?", app.ID, req.Purpose, "active").
Order("is_default DESC").First(&template)
code := generateVerifyCode()
expireAt := time.Now().Add(15 * time.Minute)
verifyCode := model.EmailVerifyCode{
ApplicationID: app.ID,
Email: req.Email,
Code: code,
Purpose: req.Purpose,
ExpiresAt: expireAt,
}
database.DB.Create(&verifyCode)
emailService := service.NewEmailService()
config := service.EmailConfig{
Host: smtpConfig.Host,
Port: smtpConfig.Port,
User: smtpConfig.User,
Password: smtpConfig.Password,
FromName: smtpConfig.FromName,
FromEmail: smtpConfig.FromEmail,
UseSSL: smtpConfig.UseSSL,
}
subject := fmt.Sprintf("验证码 - %s", app.Name)
content := fmt.Sprintf(`
<!DOCTYPE html>
<html>
<head><meta charset="UTF-8"></head>
<body style="font-family: Arial, sans-serif; padding: 20px; background-color: #f5f5f5;">
<div style="max-width: 600px; margin: 0 auto; background: white; padding: 30px; border-radius: 8px; box-shadow: 0 2px 4px rgba(0,0,0,0.1);">
<h2 style="color: #333; margin-bottom: 20px;">邮箱验证</h2>
<p style="color: #666; line-height: 1.6;">您的验证码是:<strong style="font-size: 24px; color: #1890ff;">%s</strong></p>
<p style="color: #999; font-size: 12px;">验证码有效期为15分钟,请尽快使用。</p>
<hr style="border: none; border-top: 1px solid #eee; margin: 20px 0;">
<p style="color: #999; font-size: 12px;">此邮件由 %s 系统自动发送,请勿回复。</p>
</div>
</body>
</html>
`, code, app.Name)
if template.ID > 0 {
subject = template.Subject
content = template.Content
}
if err := emailService.SendEmail(config, req.Email, subject, content); err != nil {
response.Error(c, 500, fmt.Sprintf("发送失败: %v", err))
return
}
response.Success(c, gin.H{
"message": "验证码已发送",
})
}
func generateVerifyCode() string {
r := rand.New(rand.NewSource(time.Now().UnixNano()))
return fmt.Sprintf("%06d", r.Intn(1000000))
}
func handleAppRegister(c *gin.Context) {
appKey := c.Param("appKey")
log.Printf("[DEBUG] Starting registration for appKey: %s", appKey)
app, exists := c.Get("app")
if !exists {
var appModel model.Application
if err := database.DB.Where("app_key = ?", appKey).First(&appModel).Error; err != nil {
log.Printf("[DEBUG] Application not found for appKey: %s, error: %v", appKey, err)
response.Error(c, 404, "应用不存在")
return
}
app = &appModel
}
appModel := app.(*model.Application)
log.Printf("[DEBUG] Found application: ID=%d, Status=%s", appModel.ID, appModel.Status)
if service.GetApplicationDisabledStatus(appModel.ID) {
response.Error(c, 403, "该应用已被禁用")
return
}
var req struct {
Username string `json:"username"`
Email string `json:"email"`
EmailCode string `json:"email_code"`
Password string `json:"password"`
DeviceID string `json:"device_id"`
DeviceName string `json:"device_name"`
DeviceType string `json:"device_type"`
InstanceID string `json:"instance_id"`
}
if err := c.ShouldBindJSON(&req); err != nil {
log.Printf("[DEBUG] Invalid request parameters: %v", err)
response.Error(c, 400, "参数错误")
return
}
log.Printf("[DEBUG] Registration request for username: %s", req.Username)
if blocked, reason := checkRiskControl(c, appModel.ID, req.DeviceID, req.Username); blocked {
log.Printf("[DEBUG] Registration blocked by risk control: %s", reason)
service.LogVerification(c, &appModel.ID, nil, "register_blocked", "注册被拦截: "+reason, req.DeviceID, fmt.Errorf(reason))
response.Error(c, 403, reason)
return
}
if !appModel.AllowRegister {
log.Printf("[DEBUG] Registration disabled for app: %s", appKey)
response.Error(c, 403, "注册功能已关闭")
return
}
var allowedMethods []string
if appModel.RegisterMethods != "" {
if err := json.Unmarshal([]byte(appModel.RegisterMethods), &allowedMethods); err != nil {
log.Printf("[DEBUG] Failed to parse register methods: %v", err)
allowedMethods = []string{"username"}
}
} else {
allowedMethods = []string{"username"}
}
isEmailRegister := req.Email != "" && req.Username == ""
isUsernameRegister := req.Username != "" && req.Email == ""
isPhoneRegister := false
if isEmailRegister && !containsString(allowedMethods, "email") {
response.Error(c, 403, "邮箱注册方式未开放")
return
}
if isUsernameRegister && !containsString(allowedMethods, "username") {
response.Error(c, 403, "用户名注册方式未开放")
return
}
if isPhoneRegister && !containsString(allowedMethods, "phone") {
response.Error(c, 403, "手机号注册方式未开放")
return
}
if appModel.EnableEmailVerify && appModel.RequireEmailVerify {
if req.Email == "" {
response.Error(c, 400, "请输入邮箱地址")
return
}
if req.EmailCode == "" {
response.Error(c, 400, "请输入邮箱验证码")
return
}
var verifyCode model.EmailVerifyCode
if err := database.DB.Where(
"application_id = ? AND email = ? AND code = ? AND purpose = ? AND used = ?",
appModel.ID, req.Email, req.EmailCode, "register", false,
).First(&verifyCode).Error; err != nil {
response.Error(c, 400, "验证码错误或已过期")
return
}
if verifyCode.ExpiresAt.Before(time.Now()) {
response.Error(c, 400, "验证码已过期")
return
}
verifyCode.Used = true
database.DB.Save(&verifyCode)
}
var user model.AppUser
if err := database.DB.Where("username = ? AND application_id = ?", req.Username, appModel.ID).First(&user).Error; err == nil {
log.Printf("[DEBUG] User already exists: %s", req.Username)
service.LogVerification(c, &appModel.ID, nil, "register_failed", "注册失败: 用户已存在 - "+req.Username, req.DeviceID, fmt.Errorf("用户已存在"))
response.Error(c, 400, "用户已存在")
return
}
user = model.AppUser{
Username: req.Username,
Email: req.Email,
Password: req.Password,
DeviceID: req.DeviceID,
Avatar: "",
Status: "active",
ApplicationID: appModel.ID,
IsTrialUser: appModel.EnableTrial,
}
if appModel.EnableTrial {
now := time.Now()
user.TrialStartAt = &now
if appModel.TrialBalance > 0 {
user.Balance = appModel.TrialBalance
}
}
if err := database.DB.Create(&user).Error; err != nil {
log.Printf("[DEBUG] Failed to create user: %v", err)
response.Error(c, 500, "注册失败")
return
}
log.Printf("[DEBUG] Successfully created user with ID: %d", user.ID)
if req.DeviceID != "" {
now := time.Now()
deviceType := getDeviceType(c, req.DeviceType)
deviceName := req.DeviceName
if deviceName == "" {
deviceName = req.DeviceID
}
var device model.UserDevice
if err := database.DB.Where("user_id = ? AND application_id = ? AND device_id = ?", user.ID, appModel.ID, req.DeviceID).First(&device).Error; err != nil {
device = model.UserDevice{
UserID: user.ID,
ApplicationID: appModel.ID,
DeviceID: req.DeviceID,
DeviceName: deviceName,
DeviceType: deviceType,
Status: "active",
}
if err := database.DB.Create(&device).Error; err != nil {
log.Printf("[DEBUG] Failed to create device: %v", err)
} else {
log.Printf("[DEBUG] Successfully created device with ID: %d, type: %s", device.ID, deviceType)
}
}
instanceID := req.InstanceID
if instanceID == "" {
instanceID = req.DeviceID
}
if device.ID > 0 {
var session model.DeviceSession
sessionErr := database.DB.Where("device_id = ? AND instance_id = ?", device.ID, instanceID).First(&session).Error
if sessionErr != nil {
session = model.DeviceSession{
DeviceID: device.ID,
UserID: user.ID,
ApplicationID: appModel.ID,
InstanceID: instanceID,
LastHeartbeat: &now,
}
if err := database.DB.Create(&session).Error; err != nil {
log.Printf("[DEBUG] Failed to create session: %v", err)
}
} else {
session.LastHeartbeat = &now
if err := database.DB.Save(&session).Error; err != nil {
log.Printf("[DEBUG] Failed to update session: %v", err)
}
}
}
}
service.LogVerification(c, &appModel.ID, &user.ID, "register", "用户注册: "+req.Username, req.DeviceID, nil)
response.SuccessWithMessage(c, "注册成功", gin.H{
"user_id": user.ID,
})
}
func handleAppLogin(c *gin.Context) {
appKey := c.Param("appKey")
log.Printf("[DEBUG] Login attempt for appKey: %s", appKey)
app, exists := c.Get("app")
if !exists {
var appModel model.Application
if err := database.DB.Where("app_key = ?", appKey).First(&appModel).Error; err != nil {
log.Printf("[DEBUG] Application not found for appKey: %s, error: %v", appKey, err)
response.Error(c, 404, "应用不存在")
return
}
app = &appModel
}
appModel := app.(*model.Application)
log.Printf("[DEBUG] Found application: ID=%d, Status=%s", appModel.ID, appModel.Status)
var req struct {
Username string `json:"username"`
Password string `json:"password"`
DeviceID string `json:"device_id"`
DeviceName string `json:"device_name"`
DeviceType string `json:"device_type"`
InstanceID string `json:"instance_id"`
}
if err := c.ShouldBindJSON(&req); err != nil {
log.Printf("[DEBUG] Invalid request parameters: %v", err)
response.Error(c, 400, "参数错误")
return
}
log.Printf("[DEBUG] Login request for username: %s", req.Username)
if req.DeviceID == "" {
response.Error(c, 400, "设备ID不能为空")
return
}
var user model.AppUser
if err := database.DB.Where("username = ? AND application_id = ?", req.Username, appModel.ID).First(&user).Error; err != nil {
log.Printf("[DEBUG] User not found: %v", err)
service.LogVerification(c, &appModel.ID, nil, "login_failed", "登录失败: 用户不存在 - "+req.Username, req.DeviceID, fmt.Errorf("用户不存在"))
response.Error(c, 404, "用户不存在")
return
}
if user.Password != req.Password {
log.Printf("[DEBUG] Password mismatch for user: %s", req.Username)
service.LogVerification(c, &appModel.ID, &user.ID, "login_failed", "登录失败: 密码错误 - "+req.Username, req.DeviceID, fmt.Errorf("密码错误"))
response.Error(c, 400, "密码错误")
return
}
if blocked, reason := checkRiskControl(c, appModel.ID, req.DeviceID, req.Username); blocked {
log.Printf("[DEBUG] Login blocked by risk control: %s", reason)
service.LogVerification(c, &appModel.ID, &user.ID, "login_blocked", "登录被拦截: "+reason, req.DeviceID, fmt.Errorf(reason))
response.Error(c, 403, reason)
return
}
if appModel.LoginPolicy == "strict" {
log.Printf("[DEBUG] LoginPolicy is strict, checking free period and trial")
isInFreePeriod := false
if appModel.EnableFreePeriod {
isInFreePeriod = checkFreePeriod(appModel)
}
log.Printf("[DEBUG] isInFreePeriod=%v", isInFreePeriod)
if !isInFreePeriod {
log.Printf("[DEBUG] Not in free period, checking trial")
isTrialValid := false
if user.IsTrialUser && user.TrialEndAt != nil && user.TrialEndAt.After(time.Now()) {
isTrialValid = true
}
log.Printf("[DEBUG] isTrialValid=%v, IsTrialUser=%v, TrialEndAt=%v, Balance=%f", isTrialValid, user.IsTrialUser, user.TrialEndAt, user.Balance)
if !isTrialValid {
if appModel.BillingType != "free" && user.Balance <= 0 {
log.Printf("[DEBUG] User %d has no balance remaining", user.ID)
service.LogVerification(c, &appModel.ID, &user.ID, "login_failed", "登录失败: 余额不足 - "+req.Username, req.DeviceID, fmt.Errorf("余额不足,请充值后继续使用"))
response.Error(c, 403, "余额不足,请充值后继续使用")
return
}
}
} else {
log.Printf("[DEBUG] User is in free period, allowing login")
}
}
if req.DeviceID != "" {
heartbeatTimeout := time.Duration(appModel.HeartbeatTimeout) * time.Second
timeoutThreshold := time.Now().Add(-heartbeatTimeout)
database.DB.Where("user_id = ? AND application_id = ? AND last_heartbeat < ?", user.ID, appModel.ID, timeoutThreshold).Delete(&model.DeviceSession{})
now := time.Now()
deviceType := getDeviceType(c, req.DeviceType)
deviceName := req.DeviceName
if deviceName == "" {
deviceName = req.DeviceID
}
clientIP := c.ClientIP()
if appModel.BindType == "ip" || appModel.BindType == "mixed" {
var userIP model.UserIP
ipErr := database.DB.Where("user_id = ? AND application_id = ? AND ip_address = ?", user.ID, appModel.ID, clientIP).First(&userIP).Error
if ipErr != nil {
var ipCount int64
database.DB.Model(&model.UserIP{}).
Where("user_id = ? AND application_id = ?", user.ID, appModel.ID).
Count(&ipCount)
if appModel.MaxDevices > 0 && ipCount >= int64(appModel.MaxDevices) {
log.Printf("[DEBUG] IP limit exceeded for user %d: %d/%d", user.ID, ipCount, appModel.MaxDevices)
var userIPs []model.UserIP
database.DB.Where("user_id = ? AND application_id = ?", user.ID, appModel.ID).Order("created_at DESC").Find(&userIPs)
ipList := make([]gin.H, 0)
for _, ip := range userIPs {
ipList = append(ipList, gin.H{
"id": ip.ID,
"ip_address": ip.IPAddress,
"status": ip.Status,
"created_at": ip.CreatedAt,
})
}
response.ErrorWithData(c, 403, "IP绑定数量已达上限,请解绑后再试", gin.H{
"error_code": "IP_LIMIT_EXCEEDED",
"max_ips": appModel.MaxDevices,
"ip_count": ipCount,
"ips": ipList,
})
service.LogVerification(c, &appModel.ID, &user.ID, "login_failed", "登录失败: IP绑定数量已达上限 - "+req.Username, req.DeviceID, fmt.Errorf("IP绑定数量已达上限"))
return
}
userIP = model.UserIP{
UserID: user.ID,
ApplicationID: appModel.ID,
IPAddress: clientIP,
Status: "active",
}
if err := database.DB.Create(&userIP).Error; err != nil {
log.Printf("[DEBUG] Failed to create user IP: %v", err)
} else {
log.Printf("[DEBUG] Successfully created user IP with ID: %d, IP: %s", userIP.ID, clientIP)
}
}
}
var device model.UserDevice
deviceErr := database.DB.Where("user_id = ? AND application_id = ? AND device_id = ?", user.ID, appModel.ID, req.DeviceID).First(&device).Error
if deviceErr != nil {
if (appModel.BindType == "device" || appModel.BindType == "mixed") && appModel.MaxDevices > 0 {
var deviceCount int64
database.DB.Model(&model.UserDevice{}).
Where("user_id = ? AND application_id = ?", user.ID, appModel.ID).
Count(&deviceCount)
if deviceCount >= int64(appModel.MaxDevices) {
log.Printf("[DEBUG] Device limit exceeded for user %d: %d/%d", user.ID, deviceCount, appModel.MaxDevices)
var devices []model.UserDevice
database.DB.Where("user_id = ? AND application_id = ?", user.ID, appModel.ID).Order("created_at DESC").Find(&devices)
deviceList := make([]gin.H, 0)
for _, d := range devices {
var onlineSessions []model.DeviceSession
database.DB.Where("device_id = ? AND last_heartbeat > ?", d.ID, timeoutThreshold).Find(&onlineSessions)
deviceList = append(deviceList, gin.H{
"id": d.ID,
"device_id": d.DeviceID,
"device_name": d.DeviceName,
"device_type": d.DeviceType,
"online_count": len(onlineSessions),
"created_at": d.CreatedAt,
})
}
response.ErrorWithData(c, 403, "设备绑定数量已达上限,请解绑后再试", gin.H{
"error_code": "DEVICE_LIMIT_EXCEEDED",
"max_devices": appModel.MaxDevices,
"device_count": deviceCount,
"devices": deviceList,
})
service.LogVerification(c, &appModel.ID, &user.ID, "login_failed", "登录失败: 设备绑定数量已达上限 - "+req.Username, req.DeviceID, fmt.Errorf("设备绑定数量已达上限"))
return
}
}
device = model.UserDevice{
UserID: user.ID,
ApplicationID: appModel.ID,
DeviceID: req.DeviceID,
DeviceName: deviceName,
DeviceType: deviceType,
Status: "active",
}
if err := database.DB.Create(&device).Error; err != nil {
log.Printf("[DEBUG] Failed to create device: %v", err)
} else {
log.Printf("[DEBUG] Successfully created device with ID: %d, type: %s", device.ID, deviceType)
}
}
instanceID := req.InstanceID
if instanceID == "" {
instanceID = req.DeviceID
}
if device.ID > 0 {
if appModel.MultiOpen && appModel.MaxInstances > 0 {
var sessionCount int64
database.DB.Model(&model.DeviceSession{}).
Where("device_id = ? AND instance_id != ? AND last_heartbeat > ?", device.ID, instanceID, timeoutThreshold).
Count(&sessionCount)
if sessionCount >= int64(appModel.MaxInstances) {
if appModel.MultiOpenMode == "forbidden" {
log.Printf("[DEBUG] Multi-instance limit exceeded for user %d device %s: %d/%d", user.ID, req.DeviceID, sessionCount, appModel.MaxInstances)
var sessions []model.DeviceSession
database.DB.Where("device_id = ? AND instance_id != ?", device.ID, instanceID).Order("last_heartbeat DESC").Find(&sessions)
sessionList := make([]gin.H, 0)
for _, s := range sessions {
isOnline := s.LastHeartbeat != nil && s.LastHeartbeat.After(timeoutThreshold)
sessionList = append(sessionList, gin.H{
"id": s.ID,
"instance_id": s.InstanceID,
"is_online": isOnline,
"last_heartbeat": s.LastHeartbeat,
"created_at": s.CreatedAt,
})
}
response.ErrorWithData(c, 403, "多开数量已达上限", gin.H{
"error_code": "MULTI_INSTANCE_LIMIT_EXCEEDED",
"max_instances": appModel.MaxInstances,
"instance_count": sessionCount,
"instances": sessionList,
})
service.LogVerification(c, &appModel.ID, &user.ID, "login_failed", "登录失败: 多开数量已达上限 - "+req.Username, req.DeviceID, fmt.Errorf("多开数量已达上限"))
return
}
}
}
var session model.DeviceSession
if err := database.DB.Where("device_id = ? AND instance_id = ?", device.ID, instanceID).First(&session).Error; err != nil {
session = model.DeviceSession{
DeviceID: device.ID,
UserID: user.ID,
ApplicationID: appModel.ID,
InstanceID: instanceID,
LastHeartbeat: &now,
}
if err := database.DB.Create(&session).Error; err != nil {
log.Printf("[DEBUG] Failed to create session: %v", err)
} else {
log.Printf("[DEBUG] Successfully created session with ID: %d, instance: %s", session.ID, instanceID)
}
} else {
session.LastHeartbeat = &now
if err := database.DB.Save(&session).Error; err != nil {
log.Printf("[DEBUG] Failed to update session: %v", err)
} else {
log.Printf("[DEBUG] Successfully updated session %d last_heartbeat", session.ID)
}
}
}
}
token, err := jwt.GenerateToken(user.ID, user.Username, "app_user")
if err != nil {
log.Printf("[DEBUG] Failed to generate token: %v", err)
}
now := time.Now()
database.DB.Model(&user).Updates(map[string]interface{}{
"last_login_at": now,
"last_heartbeat_at": now,
})
service.LogVerification(c, &appModel.ID, &user.ID, "login", "用户登录: "+req.Username, req.DeviceID, nil)
response.SuccessWithMessage(c, "登录成功", gin.H{
"user_id": user.ID,
"token": token,
})
}
func checkFreePeriod(app *model.Application) bool {
now := time.Now()
log.Printf("[DEBUG] checkFreePeriod: EnableFreePeriod=%v, FreePeriodType=%s, FreePeriodStart=%s, FreePeriodEnd=%s, FreePeriodWeekdays=%s, FreePeriodStartTime=%s, FreePeriodEndTime=%s",
app.EnableFreePeriod, app.FreePeriodType, app.FreePeriodStart, app.FreePeriodEnd, app.FreePeriodWeekdays, app.FreePeriodStartTime, app.FreePeriodEndTime)
localLocation := time.Local
if app.FreePeriodType == "range" {
if app.FreePeriodStart == "" || app.FreePeriodEnd == "" {
log.Printf("[DEBUG] checkFreePeriod: range type but missing start or end")
return false
}
var startDate, endDate time.Time
var err error
if len(app.FreePeriodStart) > 10 {
startDate, err = time.ParseInLocation("2006-01-02T15:04", app.FreePeriodStart, localLocation)
} else {
startDate, err = time.ParseInLocation("2006-01-02", app.FreePeriodStart, localLocation)
}
if err != nil {
log.Printf("[DEBUG] checkFreePeriod: failed to parse start date: %v", err)
return false
}
if len(app.FreePeriodEnd) > 10 {
endDate, err = time.ParseInLocation("2006-01-02T15:04", app.FreePeriodEnd, localLocation)
} else {
endDate, err = time.ParseInLocation("2006-01-02", app.FreePeriodEnd, localLocation)
if err == nil {
endDate = endDate.Add(24 * time.Hour)
}
}
if err != nil {
log.Printf("[DEBUG] checkFreePeriod: failed to parse end date: %v", err)
return false
}
result := now.After(startDate) && now.Before(endDate)
log.Printf("[DEBUG] checkFreePeriod: range check result=%v, now=%v, start=%v, end=%v", result, now, startDate, endDate)
return result
} else if app.FreePeriodType == "weekdays" {
if app.FreePeriodWeekdays == "" {
log.Printf("[DEBUG] checkFreePeriod: weekdays type but missing weekdays")
return false
}
weekday := int(now.Weekday())
if weekday == 0 {
weekday = 7
}
weekdayStr := string(rune('0' + weekday))
if !contains(app.FreePeriodWeekdays, weekdayStr) {
log.Printf("[DEBUG] checkFreePeriod: weekday %s not in %s", weekdayStr, app.FreePeriodWeekdays)
return false
}
if app.FreePeriodStartTime != "" && app.FreePeriodEndTime != "" {
currentTime := now.Format("15:04")
result := currentTime >= app.FreePeriodStartTime && currentTime <= app.FreePeriodEndTime
log.Printf("[DEBUG] checkFreePeriod: weekday time check result=%v, currentTime=%s, startTime=%s, endTime=%s", result, currentTime, app.FreePeriodStartTime, app.FreePeriodEndTime)
return result
}
log.Printf("[DEBUG] checkFreePeriod: weekday check passed")
return true
}
log.Printf("[DEBUG] checkFreePeriod: unknown type %s", app.FreePeriodType)
return false
}
func checkRiskControl(c *gin.Context, appID uint, deviceID string, username string) (bool, string) {
clientIP := c.ClientIP()
var globalRules []model.RiskControlRule
database.DB.Where("application_id IS NULL AND status = ?", "active").Find(&globalRules)
var appRules []model.RiskControlRule
database.DB.Where("application_id = ? AND status = ?", appID, "active").Find(&appRules)
now := time.Now()
for _, rule := range globalRules {
if rule.ExpiresAt != nil && rule.ExpiresAt.Before(now) {
continue
}
switch rule.Type {
case "device":
if deviceID != "" && rule.Value == deviceID {
return true, "设备已被全局封禁: " + rule.Reason
}
case "ip":
if rule.Value == clientIP {
return true, "IP已被全局封禁: " + rule.Reason
}
case "region":
}
}
for _, rule := range appRules {
if rule.ExpiresAt != nil && rule.ExpiresAt.Before(now) {
continue
}
switch rule.Type {
case "device":
if deviceID != "" && rule.Value == deviceID {
return true, "设备已被封禁: " + rule.Reason
}
case "ip":
if rule.Value == clientIP {
return true, "IP已被封禁: " + rule.Reason
}
case "user":
if username != "" && rule.Value == username {
return true, "账号已被封禁: " + rule.Reason
}
case "region":
}
}
return false, ""
}
func contains(s string, substr string) bool {
for i := 0; i < len(s); i++ {
if s[i:i+1] == substr {
return true
}
}
return false
}
func containsString(slice []string, str string) bool {
for _, v := range slice {
if v == str {
return true
}
}
return false
}
func checkAppEmailPermission(userID uint, permission *model.PackagePermission) bool {
var userPackage model.UserPackage
if err := database.DB.Where("user_id = ? AND status = ?", userID, "active").
Preload("Package").First(&userPackage).Error; err != nil {
return false
}
if userPackage.ExpiredAt != nil && userPackage.ExpiredAt.Before(time.Now()) {
return false
}
if err := database.DB.Where("package_id = ?", userPackage.PackageID).First(permission).Error; err != nil {
return false
}
return true
}
func handleAppResetPassword(c *gin.Context) {
appKey := c.Param("appKey")
var app model.Application
if err := database.DB.Where("app_key = ?", appKey).First(&app).Error; err != nil {
response.Error(c, 404, "应用不存在")
return
}
if service.GetApplicationDisabledStatus(app.ID) {
response.Error(c, 403, "该应用已被禁用")
return
}
if !app.EnablePasswordReset {
response.Error(c, 400, "该应用未启用密码重置功能")
return
}
var req struct {
Email string `json:"email" binding:"required,email"`
Code string `json:"code" binding:"required"`
Password string `json:"password" binding:"required,min=6"`
}
if err := c.ShouldBindJSON(&req); err != nil {
response.Error(c, 400, "参数错误")
return
}
var verifyCode model.EmailVerifyCode
if err := database.DB.Where(
"application_id = ? AND email = ? AND code = ? AND purpose = ? AND used = ?",
app.ID, req.Email, req.Code, "reset_password", false,
).First(&verifyCode).Error; err != nil {
response.Error(c, 400, "验证码无效或已过期")
return
}
if verifyCode.ExpiresAt.Before(time.Now()) {
response.Error(c, 400, "验证码已过期")
return
}
var user model.AppUser
if err := database.DB.Where("email = ? AND application_id = ?", req.Email, app.ID).First(&user).Error; err != nil {
response.Error(c, 404, "用户不存在")
return
}
hashedPassword, err := bcrypt.GenerateFromPassword([]byte(req.Password), bcrypt.DefaultCost)
if err != nil {
response.Error(c, 500, "密码加密失败")
return
}
user.Password = string(hashedPassword)
if err := database.DB.Save(&user).Error; err != nil {
response.Error(c, 500, "密码重置失败")
return
}
verifyCode.Used = true
database.DB.Save(&verifyCode)
response.Success(c, gin.H{
"message": "密码重置成功",
})
}
+918
View File
@@ -0,0 +1,918 @@
package app
import (
"crypto/md5"
"encoding/hex"
"encoding/json"
"fmt"
"io"
"net/url"
"os"
"path/filepath"
"strconv"
"strings"
"time"
"verification-platform-backend/internal/database"
"verification-platform-backend/internal/middleware"
"verification-platform-backend/internal/model"
"verification-platform-backend/internal/service"
"verification-platform-backend/pkg/response"
"github.com/gin-gonic/gin"
)
func SetupCloudRoutes(r *gin.RouterGroup) {
r.GET("/constants", handleAppGetConstants)
r.GET("/constants/:key", handleAppGetConstantByKey)
r.GET("/constants/:key/download", handleAppDownloadConstant)
r.GET("/variables", handleAppGetVariables)
r.GET("/variables/:key", handleAppGetVariableByKey)
r.GET("/variables/:key/download", handleAppDownloadVariable)
r.POST("/variables/:key/upload", handleAppUploadVariableBinary)
r.POST("/variables", handleAppUpdateVariables)
r.POST("/variables/:key/records", handleAppCreateVariableRecord)
r.GET("/variables/:key/records", handleAppGetVariableRecords)
r.DELETE("/variables/:key/records/:record_id", handleAppDeleteVariableRecord)
r.POST("/call-function", handleAppCallFunction)
}
func handleAppGetConstants(c *gin.Context) {
appKey := c.Param("appKey")
userID, _ := c.Get("user_id")
var app model.Application
if err := database.DB.Where("app_key = ?", appKey).First(&app).Error; err != nil {
response.Error(c, 404, "应用不存在")
return
}
if service.GetApplicationDisabledStatus(app.ID) {
response.Error(c, 403, "该应用已被禁用")
return
}
var appUser model.AppUser
if err := database.DB.Where("id = ? AND application_id = ?", userID, app.ID).First(&appUser).Error; err != nil {
response.Error(c, 403, "无权访问该应用的云端常量")
return
}
var constants []model.CloudConstant
if err := database.DB.Where("user_id = ? AND app_id = ? AND status = ?", app.UserID, app.ID, "active").Find(&constants).Error; err != nil {
response.Error(c, 500, "获取云端常量失败")
return
}
result := make(map[string]interface{})
for _, constant := range constants {
if constant.VarType == "binary" {
result[constant.Key] = gin.H{
"type": "binary",
"value": "/api/v1/app/" + appKey + "/constants/" + constant.Key + "/download",
"file_name": constant.OriginalName,
"file_size": constant.FileSize,
"md5": constant.FileMD5,
"mime_type": constant.MimeType,
}
} else {
result[constant.Key] = gin.H{
"type": constant.VarType,
"value": constant.Value,
}
}
}
response.Success(c, result)
}
func handleAppGetConstantByKey(c *gin.Context) {
appKey := c.Param("appKey")
key := c.Param("key")
userID, _ := c.Get("user_id")
var app model.Application
if err := database.DB.Where("app_key = ?", appKey).First(&app).Error; err != nil {
response.Error(c, 404, "应用不存在")
return
}
if service.GetApplicationDisabledStatus(app.ID) {
response.Error(c, 403, "该应用已被禁用")
return
}
var appUser model.AppUser
if err := database.DB.Where("id = ? AND application_id = ?", userID, app.ID).First(&appUser).Error; err != nil {
response.Error(c, 403, "无权访问该应用的云端常量")
return
}
var constant model.CloudConstant
if err := database.DB.Where("user_id = ? AND app_id = ? AND key = ? AND status = ?", app.UserID, app.ID, key, "active").First(&constant).Error; err != nil {
response.Error(c, 404, "云端常量不存在")
return
}
if constant.VarType == "binary" {
response.Success(c, gin.H{
"key": constant.Key,
"type": "binary",
"value": "/api/v1/app/" + appKey + "/constants/" + constant.Key + "/download",
"file_name": constant.OriginalName,
"file_size": constant.FileSize,
"md5": constant.FileMD5,
"mime_type": constant.MimeType,
})
} else {
response.Success(c, gin.H{
"key": constant.Key,
"type": constant.VarType,
"value": constant.Value,
})
}
}
func handleAppDownloadConstant(c *gin.Context) {
appKey := c.Param("appKey")
key := c.Param("key")
userID, _ := c.Get("user_id")
var app model.Application
if err := database.DB.Where("app_key = ?", appKey).First(&app).Error; err != nil {
response.Error(c, 404, "应用不存在")
return
}
if service.GetApplicationDisabledStatus(app.ID) {
response.Error(c, 403, "该应用已被禁用")
return
}
var appUser model.AppUser
if err := database.DB.Where("id = ? AND application_id = ?", userID, app.ID).First(&appUser).Error; err != nil {
response.Error(c, 403, "无权访问该应用的云端常量")
return
}
var constant model.CloudConstant
if err := database.DB.Where("user_id = ? AND app_id = ? AND key = ? AND status = ?", app.UserID, app.ID, key, "active").First(&constant).Error; err != nil {
response.Error(c, 404, "云端常量不存在")
return
}
if constant.VarType != "binary" || constant.FilePath == "" {
response.Error(c, 400, "该常量不是文件类型")
return
}
filePath := constant.FilePath
if strings.HasPrefix(filePath, "/") {
filePath = filePath[1:]
}
if _, err := os.Stat(filePath); os.IsNotExist(err) {
response.Error(c, 404, "文件不存在")
return
}
encodedFilename := url.QueryEscape(constant.OriginalName)
c.Header("Content-Description", "File Transfer")
c.Header("Content-Type", "application/octet-stream")
c.Header("Content-Disposition", "attachment; filename*=UTF-8''"+encodedFilename)
c.Header("Content-Transfer-Encoding", "binary")
c.Header("Expires", "0")
c.Header("Cache-Control", "must-revalidate")
c.Header("Pragma", "public")
c.FileAttachment(filePath, constant.OriginalName)
}
func handleAppGetVariables(c *gin.Context) {
appKey := c.Param("appKey")
userID, _ := c.Get("user_id")
var app model.Application
if err := database.DB.Where("app_key = ?", appKey).First(&app).Error; err != nil {
response.Error(c, 404, "应用不存在")
return
}
if service.GetApplicationDisabledStatus(app.ID) {
response.Error(c, 403, "该应用已被禁用")
return
}
var appUser model.AppUser
if err := database.DB.Where("id = ? AND application_id = ?", userID, app.ID).First(&appUser).Error; err != nil {
response.Error(c, 403, "无权访问该应用的云端变量")
return
}
var variables []model.CloudVariable
if err := database.DB.Where("user_id = ? AND app_id = ? AND status = ?", app.UserID, app.ID, "active").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, app.ID).Find(&userVariables).Error; err != nil {
response.Error(c, 500, "获取用户变量失败")
return
}
userVarMap := make(map[string]model.UserVariable)
for _, uv := range userVariables {
userVarMap[uv.VarName] = uv
}
result := make(map[string]interface{})
for _, v := range variables {
if v.VarType == "binary" {
if v.Scope == "user" {
if uv, ok := userVarMap[v.Key]; ok && uv.FilePath != "" {
result[v.Key] = gin.H{
"type": "binary",
"value": "/api/v1/app/" + appKey + "/variables/" + v.Key + "/download",
"file_name": uv.OriginalName,
"file_size": uv.FileSize,
"md5": uv.FileMD5,
"mime_type": uv.MimeType,
}
} else {
result[v.Key] = gin.H{
"type": "binary",
"value": "/api/v1/app/" + appKey + "/variables/" + v.Key + "/download",
"file_name": v.OriginalName,
"file_size": v.FileSize,
"md5": v.FileMD5,
"mime_type": v.MimeType,
}
}
} else {
result[v.Key] = gin.H{
"type": "binary",
"value": "/api/v1/app/" + appKey + "/variables/" + v.Key + "/download",
"file_name": v.OriginalName,
"file_size": v.FileSize,
"md5": v.FileMD5,
"mime_type": v.MimeType,
}
}
} else {
if v.Scope == "app" {
result[v.Key] = gin.H{
"type": v.VarType,
"value": v.DefaultValue,
}
} else {
value := v.DefaultValue
if uv, ok := userVarMap[v.Key]; ok {
value = uv.VarValue
}
result[v.Key] = gin.H{
"type": v.VarType,
"value": value,
}
}
}
}
response.Success(c, result)
}
func handleAppGetVariableByKey(c *gin.Context) {
appKey := c.Param("appKey")
key := c.Param("key")
userID, _ := c.Get("user_id")
var app model.Application
if err := database.DB.Where("app_key = ?", appKey).First(&app).Error; err != nil {
response.Error(c, 404, "应用不存在")
return
}
if service.GetApplicationDisabledStatus(app.ID) {
response.Error(c, 403, "该应用已被禁用")
return
}
var appUser model.AppUser
if err := database.DB.Where("id = ? AND application_id = ?", userID, app.ID).First(&appUser).Error; err != nil {
response.Error(c, 403, "无权访问该应用的云端变量")
return
}
var variable model.CloudVariable
if err := database.DB.Where("user_id = ? AND app_id = ? AND key = ? AND status = ?", app.UserID, app.ID, key, "active").First(&variable).Error; err != nil {
response.Error(c, 404, "云端变量不存在")
return
}
if variable.VarType == "binary" {
if variable.Scope == "user" {
var userVar model.UserVariable
if err := database.DB.Where("user_id = ? AND app_id = ? AND var_name = ?", userID, app.ID, key).First(&userVar).Error; err == nil && userVar.FilePath != "" {
response.Success(c, gin.H{
"key": variable.Key,
"type": "binary",
"value": "/api/v1/app/" + appKey + "/variables/" + variable.Key + "/download",
"file_name": userVar.OriginalName,
"file_size": userVar.FileSize,
"md5": userVar.FileMD5,
"mime_type": userVar.MimeType,
})
return
}
}
response.Success(c, gin.H{
"key": variable.Key,
"type": "binary",
"value": "/api/v1/app/" + appKey + "/variables/" + variable.Key + "/download",
"file_name": variable.OriginalName,
"file_size": variable.FileSize,
"md5": variable.FileMD5,
"mime_type": variable.MimeType,
})
return
}
if variable.Scope == "app" {
response.Success(c, gin.H{
"key": variable.Key,
"type": variable.VarType,
"value": variable.DefaultValue,
})
return
}
var userVar model.UserVariable
value := variable.DefaultValue
if err := database.DB.Where("user_id = ? AND app_id = ? AND var_name = ?", userID, app.ID, key).First(&userVar).Error; err == nil {
value = userVar.VarValue
}
response.Success(c, gin.H{
"key": variable.Key,
"type": variable.VarType,
"value": value,
})
}
func handleAppDownloadVariable(c *gin.Context) {
appKey := c.Param("appKey")
key := c.Param("key")
userID, _ := c.Get("user_id")
var app model.Application
if err := database.DB.Where("app_key = ?", appKey).First(&app).Error; err != nil {
response.Error(c, 404, "应用不存在")
return
}
if service.GetApplicationDisabledStatus(app.ID) {
response.Error(c, 403, "该应用已被禁用")
return
}
var appUser model.AppUser
if err := database.DB.Where("id = ? AND application_id = ?", userID, app.ID).First(&appUser).Error; err != nil {
response.Error(c, 403, "无权访问该应用的云端变量")
return
}
var variable model.CloudVariable
if err := database.DB.Where("user_id = ? AND app_id = ? AND key = ? AND status = ?", app.UserID, app.ID, key, "active").First(&variable).Error; err != nil {
response.Error(c, 404, "云端变量不存在")
return
}
if variable.VarType != "binary" {
response.Error(c, 400, "该变量不是二进制类型")
return
}
var filePath string
var originalName string
if variable.Scope == "user" {
var userVar model.UserVariable
if err := database.DB.Where("user_id = ? AND app_id = ? AND var_name = ?", userID, app.ID, key).First(&userVar).Error; err == nil && userVar.FilePath != "" {
filePath = userVar.FilePath
originalName = userVar.OriginalName
} else {
filePath = variable.FilePath
originalName = variable.OriginalName
}
} else {
filePath = variable.FilePath
originalName = variable.OriginalName
}
if filePath == "" {
response.Error(c, 400, "该变量没有关联文件")
return
}
if strings.HasPrefix(filePath, "/") {
filePath = filePath[1:]
}
if _, err := os.Stat(filePath); os.IsNotExist(err) {
response.Error(c, 404, "文件不存在")
return
}
encodedFilename := url.QueryEscape(originalName)
c.Header("Content-Description", "File Transfer")
c.Header("Content-Type", "application/octet-stream")
c.Header("Content-Disposition", "attachment; filename*=UTF-8''"+encodedFilename)
c.Header("Content-Transfer-Encoding", "binary")
c.Header("Expires", "0")
c.Header("Cache-Control", "must-revalidate")
c.Header("Pragma", "public")
c.FileAttachment(filePath, originalName)
}
func handleAppUpdateVariables(c *gin.Context) {
appKey := c.Param("appKey")
userID, _ := c.Get("user_id")
var app model.Application
if err := database.DB.Where("app_key = ?", appKey).First(&app).Error; err != nil {
response.Error(c, 404, "应用不存在")
return
}
if service.GetApplicationDisabledStatus(app.ID) {
response.Error(c, 403, "该应用已被禁用")
return
}
var appUser model.AppUser
if err := database.DB.Where("id = ? AND application_id = ?", userID, app.ID).First(&appUser).Error; err != nil {
response.Error(c, 403, "无权访问该应用的云端变量")
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("user_id = ? AND app_id = ?", app.UserID, app.ID).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, app.ID, key).First(&userVar).Error; err != nil {
userVar = model.UserVariable{
UserID: userID.(uint),
AppID: app.ID,
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 handleAppUploadVariableBinary(c *gin.Context) {
appKey := c.Param("appKey")
key := c.Param("key")
userID, _ := c.Get("user_id")
var app model.Application
if err := database.DB.Where("app_key = ?", appKey).First(&app).Error; err != nil {
response.Error(c, 404, "应用不存在")
return
}
if service.GetApplicationDisabledStatus(app.ID) {
response.Error(c, 403, "该应用已被禁用")
return
}
var appUser model.AppUser
if err := database.DB.Where("id = ? AND application_id = ?", userID, app.ID).First(&appUser).Error; err != nil {
response.Error(c, 403, "无权访问该应用的云端变量")
return
}
var variable model.CloudVariable
if err := database.DB.Where("user_id = ? AND app_id = ? AND key = ? AND status = ?", app.UserID, app.ID, key, "active").First(&variable).Error; err != nil {
response.Error(c, 404, "云端变量不存在")
return
}
if variable.VarType != "binary" {
response.Error(c, 400, "该变量不是二进制类型")
return
}
if variable.WritePermission != "user" {
response.Error(c, 403, "该变量不允许用户写入")
return
}
file, header, err := c.Request.FormFile("file")
if err != nil {
response.Error(c, 400, "请选择要上传的文件")
return
}
defer file.Close()
var developer model.User
if err := database.DB.First(&developer, app.UserID).Error; err != nil {
response.Error(c, 500, "获取开发者信息失败")
return
}
if developer.CurrentPackageID != nil {
var permission model.PackagePermission
if err := database.DB.Where("package_id = ?", developer.CurrentPackageID).First(&permission).Error; err == nil {
maxStorageBytes := int64(permission.MaxStorage) * 1024 * 1024
if developer.StorageUsed+header.Size > maxStorageBytes {
usedMB := float64(developer.StorageUsed) / 1024 / 1024
maxMB := float64(permission.MaxStorage)
response.Error(c, 403, fmt.Sprintf("存储空间不足,已使用 %.2f MB / %.2f MB", usedMB, maxMB))
return
}
}
}
uploadDir := "uploads/cloud-files"
if err := os.MkdirAll(uploadDir, 0755); err != nil {
response.Error(c, 500, "创建上传目录失败")
return
}
ext := filepath.Ext(header.Filename)
filename := fmt.Sprintf("%d_%d_%d%s", app.UserID, userID, time.Now().UnixNano(), ext)
filePath := filepath.Join(uploadDir, filename)
dst, err := os.Create(filePath)
if err != nil {
response.Error(c, 500, "创建文件失败")
return
}
defer dst.Close()
hash := md5.New()
multiWriter := io.MultiWriter(dst, hash)
if _, err := io.Copy(multiWriter, file); err != nil {
response.Error(c, 500, "保存文件失败")
return
}
fileMD5 := hex.EncodeToString(hash.Sum(nil))
fileURL := "/uploads/cloud-files/" + filename
mimeType := header.Header.Get("Content-Type")
if mimeType == "" {
mimeType = "application/octet-stream"
}
if variable.Scope == "app" {
if variable.FilePath != "" && variable.FileSize > 0 {
oldFilePath := variable.FilePath
if strings.HasPrefix(oldFilePath, "/") {
oldFilePath = oldFilePath[1:]
}
os.Remove(oldFilePath)
if err := middleware.UpdateStorageUsed(app.UserID, variable.FileSize, "delete"); err != nil {
fmt.Printf("更新存储使用量失败: %v\n", err)
}
}
variable.DefaultValue = fileURL
variable.FilePath = fileURL
variable.FileSize = header.Size
variable.MimeType = mimeType
variable.OriginalName = header.Filename
variable.FileMD5 = fileMD5
database.DB.Save(&variable)
} else {
var userVar model.UserVariable
existingFile := false
if err := database.DB.Where("user_id = ? AND app_id = ? AND var_name = ?", userID, app.ID, key).First(&userVar).Error; err == nil {
if userVar.FilePath != "" && userVar.FileSize > 0 {
existingFile = true
}
}
userVar = model.UserVariable{
UserID: userID.(uint),
AppID: app.ID,
VarName: key,
VarValue: fileURL,
VarType: "binary",
FilePath: fileURL,
FileSize: header.Size,
MimeType: mimeType,
OriginalName: header.Filename,
FileMD5: fileMD5,
}
if existingFile {
var oldVar model.UserVariable
if err := database.DB.Where("user_id = ? AND app_id = ? AND var_name = ?", userID, app.ID, key).First(&oldVar).Error; err == nil {
if oldVar.FilePath != "" && oldVar.FileSize > 0 {
oldFilePath := oldVar.FilePath
if strings.HasPrefix(oldFilePath, "/") {
oldFilePath = oldFilePath[1:]
}
os.Remove(oldFilePath)
if err := middleware.UpdateStorageUsed(app.UserID, oldVar.FileSize, "delete"); err != nil {
fmt.Printf("更新存储使用量失败: %v\n", err)
}
}
}
}
database.DB.Where("user_id = ? AND app_id = ? AND var_name = ?", userID, app.ID, key).Assign(userVar).FirstOrCreate(&userVar)
}
if err := middleware.UpdateStorageUsed(app.UserID, header.Size, "upload"); err != nil {
fmt.Printf("更新存储使用量失败: %v\n", err)
}
response.Success(c, gin.H{
"message": "上传成功",
"file_url": fileURL,
"file_size": header.Size,
"mime_type": mimeType,
"original_name": header.Filename,
"download_url": "/api/v1/app/" + appKey + "/variables/" + key + "/download",
})
}
func handleAppCallFunction(c *gin.Context) {
appKey := c.Param("appKey")
var app model.Application
if err := database.DB.Where("app_key = ?", appKey).First(&app).Error; err != nil {
response.Error(c, 404, "应用不存在")
return
}
if service.GetApplicationDisabledStatus(app.ID) {
response.Error(c, 403, "该应用已被禁用")
return
}
var req struct {
UserID uint `json:"user_id"`
Name string `json:"name"`
Params map[string]interface{} `json:"params"`
}
if err := c.ShouldBindJSON(&req); err != nil {
response.Error(c, 400, "参数错误")
return
}
response.Success(c, gin.H{
"result": nil,
})
}
func handleAppCreateVariableRecord(c *gin.Context) {
appKey := c.Param("appKey")
key := c.Param("key")
userID, _ := c.Get("user_id")
var app model.Application
if err := database.DB.Where("app_key = ?", appKey).First(&app).Error; err != nil {
response.Error(c, 404, "应用不存在")
return
}
if service.GetApplicationDisabledStatus(app.ID) {
response.Error(c, 403, "该应用已被禁用")
return
}
var appUser model.AppUser
if err := database.DB.Where("id = ? AND application_id = ?", userID, app.ID).First(&appUser).Error; err != nil {
response.Error(c, 403, "无权访问该应用的云端变量")
return
}
var variable model.CloudVariable
if err := database.DB.Where("user_id = ? AND app_id = ? AND key = ? AND status = ?", app.UserID, app.ID, key, "active").First(&variable).Error; err != nil {
response.Error(c, 404, "云端变量不存在")
return
}
if variable.DataType != "stream" {
response.Error(c, 400, "该变量不是流水类型")
return
}
if variable.WritePermission != "user" && variable.WritePermission != "app_user" {
response.Error(c, 403, "该变量不允许应用用户写入")
return
}
var req map[string]interface{}
if err := c.ShouldBindJSON(&req); err != nil {
response.Error(c, 400, "参数错误")
return
}
dataBytes, err := json.Marshal(req)
if err != nil {
response.Error(c, 500, "序列化数据失败")
return
}
record := model.CloudVariableRecord{
CloudVariableID: variable.ID,
Data: string(dataBytes),
}
if variable.Scope == "user" {
appUserID := userID.(uint)
record.AppUserID = &appUserID
}
if err := database.DB.Create(&record).Error; err != nil {
response.Error(c, 500, "创建记录失败")
return
}
if variable.MaxRecords > 0 {
var total int64
database.DB.Model(&model.CloudVariableRecord{}).Where("cloud_variable_id = ?", variable.ID).Count(&total)
if int(total) > variable.MaxRecords {
deleteCount := int(total) - variable.MaxRecords
var oldRecords []model.CloudVariableRecord
database.DB.Where("cloud_variable_id = ?", variable.ID).
Order("created_at ASC").
Limit(deleteCount).
Find(&oldRecords)
for _, r := range oldRecords {
database.DB.Delete(&r)
}
}
}
response.Success(c, gin.H{
"id": record.ID,
"created_at": record.CreatedAt,
})
}
func handleAppGetVariableRecords(c *gin.Context) {
appKey := c.Param("appKey")
key := c.Param("key")
userID, _ := c.Get("user_id")
var app model.Application
if err := database.DB.Where("app_key = ?", appKey).First(&app).Error; err != nil {
response.Error(c, 404, "应用不存在")
return
}
if service.GetApplicationDisabledStatus(app.ID) {
response.Error(c, 403, "该应用已被禁用")
return
}
var appUser model.AppUser
if err := database.DB.Where("id = ? AND application_id = ?", userID, app.ID).First(&appUser).Error; err != nil {
response.Error(c, 403, "无权访问该应用的云端变量")
return
}
var variable model.CloudVariable
if err := database.DB.Where("user_id = ? AND app_id = ? AND key = ? AND status = ?", app.UserID, app.ID, key, "active").First(&variable).Error; err != nil {
response.Error(c, 404, "云端变量不存在")
return
}
if variable.DataType != "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
}
var total int64
query := database.DB.Model(&model.CloudVariableRecord{}).Where("cloud_variable_id = ?", variable.ID)
if variable.Scope == "user" {
query = query.Where("app_user_id = ?", userID)
}
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 data map[string]interface{}
json.Unmarshal([]byte(r.Data), &data)
result[i] = gin.H{
"id": r.ID,
"data": data,
"created_at": r.CreatedAt,
}
}
response.Success(c, gin.H{
"records": result,
"total": total,
"page": page,
"page_size": pageSize,
"total_pages": (total + int64(pageSize) - 1) / int64(pageSize),
})
}
func handleAppDeleteVariableRecord(c *gin.Context) {
appKey := c.Param("appKey")
key := c.Param("key")
recordID := c.Param("record_id")
userID, _ := c.Get("user_id")
var app model.Application
if err := database.DB.Where("app_key = ?", appKey).First(&app).Error; err != nil {
response.Error(c, 404, "应用不存在")
return
}
if service.GetApplicationDisabledStatus(app.ID) {
response.Error(c, 403, "该应用已被禁用")
return
}
var appUser model.AppUser
if err := database.DB.Where("id = ? AND application_id = ?", userID, app.ID).First(&appUser).Error; err != nil {
response.Error(c, 403, "无权访问该应用的云端变量")
return
}
var variable model.CloudVariable
if err := database.DB.Where("user_id = ? AND app_id = ? AND key = ? AND status = ?", app.UserID, app.ID, key, "active").First(&variable).Error; err != nil {
response.Error(c, 404, "云端变量不存在")
return
}
if variable.DataType != "stream" {
response.Error(c, 400, "该变量不是流水类型")
return
}
var record model.CloudVariableRecord
query := database.DB.Where("id = ? AND cloud_variable_id = ?", recordID, variable.ID)
if variable.Scope == "user" {
query = query.Where("app_user_id = ?", userID)
}
if err := query.First(&record).Error; err != nil {
response.Error(c, 404, "记录不存在")
return
}
if err := database.DB.Delete(&record).Error; err != nil {
response.Error(c, 500, "删除记录失败")
return
}
response.Success(c, nil)
}
+390
View File
@@ -0,0 +1,390 @@
package app
import (
"fmt"
"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"
)
func SetupDeviceRoutes(r *gin.RouterGroup) {
r.GET("/devices", handleAppGetDevices)
r.POST("/unbind-device", handleAppUnbindDevice)
r.GET("/device-count", handleAppGetDeviceCount)
r.GET("/instances", handleAppGetInstances)
r.POST("/instances/:instance_id/offline", handleAppForceOfflineInstance)
}
func SetupDevicePublicRoutes(r *gin.RouterGroup) {
r.POST("/unbind-device-with-auth", handleAppUnbindDeviceWithAuth)
r.POST("/change-password", handleAppChangePassword)
}
func handleAppUnbindDeviceWithAuth(c *gin.Context) {
appKey := c.Param("appKey")
var app model.Application
if err := database.DB.Where("app_key = ?", appKey).First(&app).Error; err != nil {
response.Error(c, 404, "应用不存在")
return
}
if service.GetApplicationDisabledStatus(app.ID) {
response.Error(c, 403, "该应用已被禁用")
return
}
var req struct {
Username string `json:"username"`
Password string `json:"password"`
DeviceID string `json:"device_id"`
}
if err := c.ShouldBindJSON(&req); err != nil {
response.Error(c, 400, "参数错误")
return
}
if req.Username == "" || req.Password == "" || req.DeviceID == "" {
response.Error(c, 400, "用户名、密码和设备ID不能为空")
return
}
var user model.AppUser
if err := database.DB.Where("username = ? AND application_id = ?", req.Username, app.ID).First(&user).Error; err != nil {
response.Error(c, 404, "用户不存在")
return
}
if user.Password != req.Password {
response.Error(c, 401, "密码错误")
return
}
var device model.UserDevice
if err := database.DB.Where("user_id = ? AND application_id = ? AND device_id = ?", user.ID, app.ID, req.DeviceID).First(&device).Error; err != nil {
response.Error(c, 404, "设备不存在")
return
}
database.DB.Where("device_id = ?", device.ID).Delete(&model.DeviceSession{})
if err := database.DB.Delete(&device).Error; err != nil {
response.Error(c, 500, "解绑设备失败")
return
}
if user.DeviceID == req.DeviceID {
now := time.Now()
database.DB.Model(&user).Updates(map[string]interface{}{
"device_id": "",
"last_heartbeat_at": &now,
})
}
service.LogVerification(c, &app.ID, &user.ID, "unbind_device", fmt.Sprintf("用户解绑设备(认证): %s", req.DeviceID), req.DeviceID, nil)
response.Success(c, gin.H{
"message": "解绑成功",
})
}
func handleAppGetDevices(c *gin.Context) {
appKey := c.Param("appKey")
var app model.Application
if err := database.DB.Where("app_key = ?", appKey).First(&app).Error; err != nil {
response.Error(c, 404, "应用不存在")
return
}
if service.GetApplicationDisabledStatus(app.ID) {
response.Error(c, 403, "该应用已被禁用")
return
}
userID, exists := c.Get("user_id")
if !exists {
response.Error(c, 401, "未授权")
return
}
var devices []model.UserDevice
if err := database.DB.Where("user_id = ? AND application_id = ?", userID, app.ID).Find(&devices).Error; err != nil {
response.Error(c, 500, "获取设备列表失败")
return
}
result := make([]gin.H, 0)
for _, device := range devices {
var onlineSessionCount int64
tenMinutesAgo := time.Now().Add(-10 * time.Minute)
database.DB.Model(&model.DeviceSession{}).
Where("device_id = ? AND last_heartbeat > ?", device.ID, tenMinutesAgo).
Count(&onlineSessionCount)
result = append(result, gin.H{
"id": device.ID,
"device_id": device.DeviceID,
"device_name": device.DeviceName,
"device_type": device.DeviceType,
"status": device.Status,
"online_sessions": onlineSessionCount,
"created_at": device.CreatedAt,
})
}
response.Success(c, result)
}
func handleAppGetDeviceCount(c *gin.Context) {
appKey := c.Param("appKey")
var app model.Application
if err := database.DB.Where("app_key = ?", appKey).First(&app).Error; err != nil {
response.Error(c, 404, "应用不存在")
return
}
if service.GetApplicationDisabledStatus(app.ID) {
response.Error(c, 403, "该应用已被禁用")
return
}
var req struct {
UserID uint `json:"user_id"`
}
if err := c.ShouldBindJSON(&req); err != nil {
response.Error(c, 400, "参数错误")
return
}
var count int64
if err := database.DB.Model(&model.UserDevice{}).Where("user_id = ? AND application_id = ?", req.UserID, app.ID).Count(&count).Error; err != nil {
response.Error(c, 500, "获取设备数量失败")
return
}
response.Success(c, gin.H{
"count": count,
"max_devices": app.MaxDevices,
"remaining": app.MaxDevices - int(count),
})
}
func handleAppUnbindDevice(c *gin.Context) {
appKey := c.Param("appKey")
var app model.Application
if err := database.DB.Where("app_key = ?", appKey).First(&app).Error; err != nil {
response.Error(c, 404, "应用不存在")
return
}
if service.GetApplicationDisabledStatus(app.ID) {
response.Error(c, 403, "该应用已被禁用")
return
}
var req struct {
UserID uint `json:"user_id"`
DeviceID string `json:"device_id"`
}
if err := c.ShouldBindJSON(&req); err != nil {
response.Error(c, 400, "参数错误")
return
}
var device model.UserDevice
if err := database.DB.Where("user_id = ? AND application_id = ? AND device_id = ?", req.UserID, app.ID, req.DeviceID).First(&device).Error; err != nil {
response.Error(c, 404, "设备不存在")
return
}
database.DB.Where("device_id = ?", device.ID).Delete(&model.DeviceSession{})
if err := database.DB.Delete(&device).Error; err != nil {
response.Error(c, 500, "解绑设备失败")
return
}
var user model.AppUser
if err := database.DB.First(&user, req.UserID).Error; err == nil {
if user.DeviceID == req.DeviceID {
now := time.Now()
database.DB.Model(&user).Updates(map[string]interface{}{
"device_id": "",
"last_heartbeat_at": &now,
})
}
}
service.LogVerification(c, &app.ID, &user.ID, "unbind_device", fmt.Sprintf("用户解绑设备: %s", req.DeviceID), req.DeviceID, nil)
response.Success(c, gin.H{
"message": "解绑成功",
})
}
func handleAppGetInstances(c *gin.Context) {
appKey := c.Param("appKey")
var app model.Application
if err := database.DB.Where("app_key = ?", appKey).First(&app).Error; err != nil {
response.Error(c, 404, "应用不存在")
return
}
if service.GetApplicationDisabledStatus(app.ID) {
response.Error(c, 403, "该应用已被禁用")
return
}
var req struct {
UserID uint `json:"user_id"`
}
if err := c.ShouldBindJSON(&req); err != nil {
response.Error(c, 400, "参数错误")
return
}
var devices []model.UserDevice
if err := database.DB.Where("user_id = ? AND application_id = ?", req.UserID, app.ID).Find(&devices).Error; err != nil {
response.Error(c, 500, "获取设备列表失败")
return
}
deviceIDs := make([]uint, len(devices))
for i, d := range devices {
deviceIDs[i] = d.ID
}
heartbeatTimeout := app.HeartbeatTimeout
if heartbeatTimeout <= 0 {
heartbeatTimeout = 10
}
timeoutThreshold := time.Now().Add(-time.Duration(heartbeatTimeout) * time.Minute)
var sessions []model.DeviceSession
if len(deviceIDs) > 0 {
if err := database.DB.Where("device_id IN ?", deviceIDs).Order("last_heartbeat DESC").Find(&sessions).Error; err != nil {
response.Error(c, 500, "获取实例列表失败")
return
}
}
result := make([]gin.H, 0)
for _, session := range sessions {
isOnline := session.LastHeartbeat != nil && session.LastHeartbeat.After(timeoutThreshold)
var device model.UserDevice
for _, d := range devices {
if d.ID == session.DeviceID {
device = d
break
}
}
result = append(result, gin.H{
"id": session.ID,
"instance_id": session.InstanceID,
"device_id": device.DeviceID,
"device_name": device.DeviceName,
"is_online": isOnline,
"last_heartbeat": session.LastHeartbeat,
"created_at": session.CreatedAt,
})
}
response.Success(c, result)
}
func handleAppForceOfflineInstance(c *gin.Context) {
appKey := c.Param("appKey")
instanceID := c.Param("instance_id")
var app model.Application
if err := database.DB.Where("app_key = ?", appKey).First(&app).Error; err != nil {
response.Error(c, 404, "应用不存在")
return
}
if service.GetApplicationDisabledStatus(app.ID) {
response.Error(c, 403, "该应用已被禁用")
return
}
var req struct {
UserID uint `json:"user_id"`
}
if err := c.ShouldBindJSON(&req); err != nil {
response.Error(c, 400, "参数错误")
return
}
var devices []model.UserDevice
if err := database.DB.Where("user_id = ? AND application_id = ?", req.UserID, app.ID).Find(&devices).Error; err != nil {
response.Error(c, 500, "获取设备列表失败")
return
}
deviceIDs := make([]uint, len(devices))
for i, d := range devices {
deviceIDs[i] = d.ID
}
if len(deviceIDs) == 0 {
response.Error(c, 404, "实例不存在")
return
}
result := database.DB.Where("instance_id = ? AND device_id IN ?", instanceID, deviceIDs).Delete(&model.DeviceSession{})
if result.RowsAffected == 0 {
response.Error(c, 404, "实例不存在")
return
}
service.LogVerification(c, &app.ID, &req.UserID, "force_offline", fmt.Sprintf("强制离线实例: %s", instanceID), instanceID, nil)
response.Success(c, gin.H{
"message": "已强制离线",
})
}
func handleAppChangePassword(c *gin.Context) {
appKey := c.Param("appKey")
var app model.Application
if err := database.DB.Where("app_key = ?", appKey).First(&app).Error; err != nil {
response.Error(c, 404, "应用不存在")
return
}
var req struct {
Username string `json:"username" binding:"required"`
OldPassword string `json:"old_password" binding:"required"`
NewPassword string `json:"new_password" binding:"required,min=6"`
}
if err := c.ShouldBindJSON(&req); err != nil {
response.Error(c, 400, "参数错误")
return
}
var user model.AppUser
if err := database.DB.Where("username = ? AND application_id = ?", req.Username, app.ID).First(&user).Error; err != nil {
response.Error(c, 404, "用户不存在")
return
}
if user.Password != req.OldPassword {
response.Error(c, 400, "原密码错误")
return
}
user.Password = req.NewPassword
if err := database.DB.Save(&user).Error; err != nil {
response.Error(c, 500, "密码修改失败")
return
}
response.Success(c, gin.H{
"message": "密码修改成功",
})
}
+427
View File
@@ -0,0 +1,427 @@
package app
import (
"net/http"
"strings"
"time"
"verification-platform-backend/internal/database"
"verification-platform-backend/internal/model"
"verification-platform-backend/internal/service"
"verification-platform-backend/pkg/jwt"
"verification-platform-backend/pkg/response"
"github.com/dop251/goja"
"github.com/gin-gonic/gin"
)
func SetupDynamicRoutes(r *gin.RouterGroup) {
dynamicCode := r.Group("/dynamic-code")
{
dynamicCode.POST("/:key/execute", handleExecuteDynamicCode)
}
}
func handleExecuteDynamicCode(c *gin.Context) {
var token string
authHeader := c.GetHeader("Authorization")
if authHeader != "" {
parts := strings.SplitN(authHeader, " ", 2)
if len(parts) == 2 && parts[0] == "Bearer" {
token = parts[1]
}
}
if token == "" {
token = c.Query("token")
}
if token == "" {
response.Error(c, http.StatusUnauthorized, "Authorization header is required")
return
}
claims, err := jwt.ParseToken(token)
if err != nil {
response.Error(c, http.StatusUnauthorized, "Invalid token")
return
}
if time.Now().Unix() > claims.ExpiresAt.Unix() {
response.Error(c, http.StatusUnauthorized, "Token expired")
return
}
c.Set("user_id", claims.UserID)
c.Set("username", claims.Username)
c.Set("role", claims.Role)
appKey := c.Param("appKey")
key := c.Param("key")
var app model.Application
if err := database.DB.Where("app_key = ?", appKey).First(&app).Error; err != nil {
response.Error(c, 404, "应用不存在")
return
}
if service.GetApplicationDisabledStatus(app.ID) {
response.Error(c, 403, "该应用已被禁用")
return
}
var appUser model.AppUser
if err := database.DB.Where("id = ? AND application_id = ?", claims.UserID, app.ID).First(&appUser).Error; err != nil {
response.Error(c, http.StatusForbidden, "无权访问该应用的动态代码")
return
}
var dynamicCode model.DynamicCode
if err := database.DB.Where("application_id = ? AND key = ?", app.ID, key).First(&dynamicCode).Error; err != nil {
response.Error(c, 404, "动态代码不存在")
return
}
if dynamicCode.Status != "active" {
response.Error(c, 400, "动态代码未启用")
return
}
var req struct {
Params map[string]interface{} `json:"params"`
UserID *uint `json:"user_id,omitempty"`
}
if err := c.ShouldBindJSON(&req); err != nil {
response.Error(c, 400, "参数错误")
return
}
startTime := time.Now()
vm := goja.New()
appData := map[string]interface{}{
"id": app.ID,
"name": app.Name,
"description": app.Description,
"app_key": app.AppKey,
"status": app.Status,
"billing_type": app.BillingType,
"deduction_mode": app.DeductionMode,
"deduction_type": app.DeductionType,
"deduction_interval": app.DeductionInterval,
"deduction_unit": app.DeductionUnit,
"deduction_amount": app.DeductionAmount,
"enable_trial": app.EnableTrial,
"trial_balance": app.TrialBalance,
"trial_days": app.TrialDays,
"enable_free_period": app.EnableFreePeriod,
"free_period_type": app.FreePeriodType,
"free_period_start": app.FreePeriodStart,
"free_period_end": app.FreePeriodEnd,
"free_period_weekdays": app.FreePeriodWeekdays,
"free_period_start_time": app.FreePeriodStartTime,
"free_period_end_time": app.FreePeriodEndTime,
"max_devices": app.MaxDevices,
"bind_type": app.BindType,
"multi_open": app.MultiOpen,
"multi_open_mode": app.MultiOpenMode,
"max_instances": app.MaxInstances,
"login_policy": app.LoginPolicy,
"max_attempts": app.MaxAttempts,
"lock_duration": app.LockDuration,
"heartbeat_interval": app.HeartbeatInterval,
"heartbeat_timeout": app.HeartbeatTimeout,
"change_limit": app.ChangeLimit,
"change_interval": app.ChangeInterval,
"change_exceed_action": app.ChangeExceedAction,
"change_deduct_amount": app.ChangeDeductAmount,
}
if err := vm.Set("app", appData); err != nil {
response.Error(c, 500, "应用数据设置失败")
return
}
var constants []model.CloudConstant
database.DB.Where("app_id = ? AND status = ?", app.ID, "active").Find(&constants)
constantsData := make(map[string]interface{})
for _, c := range constants {
constantsData[c.Key] = c.Value
}
if err := vm.Set("constants", constantsData); err != nil {
response.Error(c, 500, "云端常量设置失败")
return
}
var cloudVariables []model.CloudVariable
database.DB.Where("app_id = ? AND status = ?", app.ID, "active").Find(&cloudVariables)
appVariables := make(map[string]interface{})
for _, v := range cloudVariables {
appVariables[v.Key] = v.DefaultValue
}
if err := vm.Set("appVariables", appVariables); err != nil {
response.Error(c, 500, "云端变量设置失败")
return
}
userData := map[string]interface{}{}
subscription := map[string]interface{}{}
userVariables := map[string]interface{}{}
devices := []map[string]interface{}{}
if req.UserID != nil {
var user model.AppUser
if err := database.DB.Where("id = ? AND application_id = ?", *req.UserID, app.ID).First(&user).Error; err == nil {
userData = map[string]interface{}{
"id": user.ID,
"username": user.Username,
"email": user.Email,
"status": user.Status,
"device_id": user.DeviceID,
"avatar": user.Avatar,
"created_at": user.CreatedAt,
"last_login_at": user.LastLoginAt,
}
subscription = map[string]interface{}{
"balance": user.Balance,
"is_trial_user": user.IsTrialUser,
"trial_start_at": user.TrialStartAt,
"trial_end_at": user.TrialEndAt,
"expiry_at": user.ExpiryAt,
"is_expired": user.ExpiryAt != nil && user.ExpiryAt.Before(time.Now()),
"is_lifetime": user.Balance == -1,
"days_remaining": calculateDaysRemaining(user.ExpiryAt, user.Balance),
}
var userDevices []model.UserDevice
database.DB.Where("user_id = ? AND application_id = ?", user.ID, app.ID).Find(&userDevices)
for _, d := range userDevices {
devices = append(devices, map[string]interface{}{
"id": d.ID,
"device_id": d.DeviceID,
"device_name": d.DeviceName,
"device_type": d.DeviceType,
"status": d.Status,
"created_at": d.CreatedAt,
})
}
var userVars []model.UserVariable
database.DB.Where("user_id = ? AND app_id = ?", user.ID, app.ID).Find(&userVars)
for _, v := range userVars {
userVariables[v.VarName] = v.VarValue
}
}
}
if err := vm.Set("user", userData); err != nil {
response.Error(c, 500, "用户数据设置失败")
return
}
if err := vm.Set("subscription", subscription); err != nil {
response.Error(c, 500, "订阅数据设置失败")
return
}
if err := vm.Set("userVariables", userVariables); err != nil {
response.Error(c, 500, "用户变量设置失败")
return
}
if err := vm.Set("devices", devices); err != nil {
response.Error(c, 500, "设备数据设置失败")
return
}
for k, v := range req.Params {
if err := vm.Set(k, v); err != nil {
response.Error(c, 500, "参数设置失败")
return
}
}
if err := vm.Set("params", req.Params); err != nil {
response.Error(c, 500, "参数设置失败")
return
}
value, err := vm.RunString("(function() { " + dynamicCode.Code + " })()")
if err != nil {
response.Error(c, 400, "代码执行错误: "+err.Error())
return
}
executionTime := time.Since(startTime).Milliseconds()
result := value.Export()
if actionMap, ok := result.(map[string]interface{}); ok {
if action, hasAction := actionMap["action"]; hasAction {
switch action {
case "extend_time":
if err := handleExtendTime(app.ID, actionMap); err != nil {
response.Error(c, 500, "执行加时操作失败: "+err.Error())
return
}
case "deduct_points":
if err := handleDeductPoints(app.ID, actionMap); err != nil {
response.Error(c, 500, "执行扣点操作失败: "+err.Error())
return
}
case "update_user_variable":
appID := app.ID
if err := handleUpdateUserVariable(&appID, req.UserID, actionMap); err != nil {
response.Error(c, 500, "更新用户变量失败: "+err.Error())
return
}
case "update_app_variable":
if err := handleUpdateAppVariable(app.ID, actionMap); err != nil {
response.Error(c, 500, "更新应用变量失败: "+err.Error())
return
}
}
}
}
response.Success(c, gin.H{
"result": result,
"execution_time": executionTime,
})
}
func calculateDaysRemaining(expiryAt *time.Time, balance float64) int {
if balance == -1 {
return -1
}
if expiryAt == nil {
return 0
}
remaining := int(time.Until(*expiryAt).Hours() / 24)
if remaining < 0 {
return 0
}
return remaining
}
func handleExtendTime(appID uint, actionMap map[string]interface{}) error {
userID, ok := actionMap["user_id"].(float64)
if !ok {
return nil
}
days, ok := actionMap["days"].(float64)
if !ok {
return nil
}
var user model.AppUser
if err := database.DB.Where("id = ? AND application_id = ?", uint(userID), appID).First(&user).Error; err != nil {
return err
}
if user.Balance == -1 {
return nil
}
user.Balance += float64(days)
return database.DB.Save(&user).Error
}
func handleDeductPoints(appID uint, actionMap map[string]interface{}) error {
userID, ok := actionMap["user_id"].(float64)
if !ok {
return nil
}
points, ok := actionMap["points"].(float64)
if !ok {
return nil
}
var user model.AppUser
if err := database.DB.Where("id = ? AND application_id = ?", uint(userID), appID).First(&user).Error; err != nil {
return err
}
if user.Balance == -1 {
return nil
}
user.Balance -= points
if user.Balance < 0 {
user.Balance = 0
}
return database.DB.Save(&user).Error
}
func handleUpdateUserVariable(appID *uint, userID *uint, actionMap map[string]interface{}) error {
if userID == nil {
return nil
}
varName, ok := actionMap["name"].(string)
if !ok {
return nil
}
varValue, ok := actionMap["value"].(string)
if !ok {
if v, ok := actionMap["value"]; ok {
varValue = toString(v)
} else {
return nil
}
}
var userVar model.UserVariable
if err := database.DB.Where("user_id = ? AND app_id = ? AND var_name = ?", *userID, appID, varName).First(&userVar).Error; err != nil {
userVar = model.UserVariable{
UserID: *userID,
AppID: *appID,
VarName: varName,
VarValue: varValue,
}
return database.DB.Create(&userVar).Error
}
userVar.VarValue = varValue
return database.DB.Save(&userVar).Error
}
func handleUpdateAppVariable(appID uint, actionMap map[string]interface{}) error {
varName, ok := actionMap["name"].(string)
if !ok {
return nil
}
varValue, ok := actionMap["value"].(string)
if !ok {
if v, ok := actionMap["value"]; ok {
varValue = toString(v)
} else {
return nil
}
}
var appVar model.CloudVariable
if err := database.DB.Where("app_id = ? AND key = ?", appID, varName).First(&appVar).Error; err != nil {
return err
}
appVar.DefaultValue = varValue
return database.DB.Save(&appVar).Error
}
func toString(v interface{}) string {
switch val := v.(type) {
case string:
return val
case float64:
return string(rune(int(val)))
case int:
return string(rune(val))
default:
return ""
}
}
+146
View File
@@ -0,0 +1,146 @@
package app
import (
"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"
)
func SetupInfoRoutes(r *gin.RouterGroup) {
r.GET("/info", handleAppGetInfo)
r.GET("/check-update", handleAppCheckUpdate)
r.GET("/announcements", handleAppGetAnnouncements)
}
func handleAppGetInfo(c *gin.Context) {
appKey := c.Param("appKey")
var app model.Application
if err := database.DB.Where("app_key = ?", appKey).First(&app).Error; err != nil {
response.Error(c, 404, "应用不存在")
return
}
if service.GetApplicationDisabledStatus(app.ID) {
response.Error(c, 403, "该应用已被禁用")
return
}
response.Success(c, gin.H{
"id": app.ID,
"name": app.Name,
"description": app.Description,
"icon_url": app.IconURL,
"status": app.Status,
"billing_type": app.BillingType,
"login_policy": app.LoginPolicy,
"max_devices": app.MaxDevices,
"multi_open": app.MultiOpen,
"multi_open_mode": app.MultiOpenMode,
"max_instances": app.MaxInstances,
"enable_trial": app.EnableTrial,
"trial_balance": app.TrialBalance,
"heartbeat_interval": app.HeartbeatInterval,
"heartbeat_timeout": app.HeartbeatTimeout,
})
}
func handleAppCheckUpdate(c *gin.Context) {
appKey := c.Param("appKey")
var app model.Application
if err := database.DB.Where("app_key = ?", appKey).First(&app).Error; err != nil {
response.Error(c, 404, "应用不存在")
return
}
if service.GetApplicationDisabledStatus(app.ID) {
response.Error(c, 403, "该应用已被禁用")
return
}
clientVersion := c.Query("version")
var latestVersion model.Version
err := database.DB.Where("application_id = ? AND status = ?", app.ID, "active").Order("created_at DESC").Preload("Files").First(&latestVersion).Error
if err != nil {
response.Success(c, gin.H{
"has_update": false,
"latest_version": "",
"download_url": "",
"update_notes": "",
"update_strategy": "",
"update_method": "",
"files": []interface{}{},
})
return
}
hasUpdate := clientVersion != latestVersion.Version
if latestVersion.ForceUpdate {
hasUpdate = true
}
files := make([]gin.H, len(latestVersion.Files))
for i, f := range latestVersion.Files {
files[i] = gin.H{
"file_path": f.FilePath,
"file_name": f.FileName,
"file_size": f.FileSize,
"file_hash": f.FileHash,
"file_type": f.FileType,
"is_required": f.IsRequired,
}
}
response.Success(c, gin.H{
"has_update": hasUpdate,
"latest_version": latestVersion.Version,
"download_url": latestVersion.FilePath,
"file_size": latestVersion.FileSize,
"file_hash": latestVersion.FileHash,
"entry_file": latestVersion.EntryFile,
"update_notes": latestVersion.Description,
"update_strategy": latestVersion.UpdateStrategy,
"update_method": latestVersion.UpdateMethod,
"min_version": latestVersion.MinVersion,
"changelog": latestVersion.Changelog,
"files": files,
})
}
func handleAppGetAnnouncements(c *gin.Context) {
appKey := c.Param("appKey")
var app model.Application
if err := database.DB.Where("app_key = ?", appKey).First(&app).Error; err != nil {
response.Error(c, 404, "应用不存在")
return
}
if service.GetApplicationDisabledStatus(app.ID) {
response.Error(c, 403, "该应用已被禁用")
return
}
var announcements []model.Announcement
if err := database.DB.Where("application_id = ? AND status = ?", app.ID, "active").Order("is_top DESC, created_at DESC").Find(&announcements).Error; err != nil {
response.Error(c, 500, "获取公告失败")
return
}
result := make([]gin.H, len(announcements))
for i, a := range announcements {
result[i] = gin.H{
"id": a.ID,
"title": a.Title,
"content": a.Content,
"type": a.Type,
"is_top": a.IsTop,
"created_at": a.CreatedAt,
}
}
response.Success(c, result)
}
+217
View File
@@ -0,0 +1,217 @@
package app
import (
"fmt"
"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"
)
func SetupPaymentRoutes(r *gin.RouterGroup) {
r.POST("/recharge", handleAppRecharge)
r.POST("/trial", handleAppTrial)
}
func handleAppRecharge(c *gin.Context) {
appKey := c.Param("appKey")
var app model.Application
if err := database.DB.Where("app_key = ?", appKey).First(&app).Error; err != nil {
response.Error(c, 404, "应用不存在")
return
}
if service.GetApplicationDisabledStatus(app.ID) {
response.Error(c, 403, "该应用已被禁用")
return
}
var req struct {
Username string `json:"username"`
CardKey string `json:"card_key"`
DeviceID string `json:"device_id"`
}
if err := c.ShouldBindJSON(&req); err != nil {
response.Error(c, 400, "参数错误")
return
}
if blocked, reason := checkRiskControl(c, app.ID, req.DeviceID, req.Username); blocked {
response.Error(c, 403, reason)
return
}
var card model.Card
if err := database.DB.Preload("CardType").Where("card_key = ?", req.CardKey).First(&card).Error; err != nil {
service.LogVerification(c, &app.ID, nil, "recharge_failed", fmt.Sprintf("充值失败: 卡密不存在 - %s", req.CardKey), "", fmt.Errorf("卡密不存在"))
response.Error(c, 404, "卡密不存在")
return
}
fmt.Printf("[DEBUG] 卡密信息: ID=%d, CardKey=%s, Status=%s, CardTypeID=%d\n", card.ID, card.CardKey, card.Status, card.CardTypeID)
fmt.Printf("[DEBUG] 卡类信息: ID=%d, Name=%s, Value=%f, Price=%f\n", card.CardType.ID, card.CardType.Name, card.CardType.Value, card.CardType.Price)
if card.Status != "unused" {
service.LogVerification(c, &app.ID, nil, "recharge_failed", fmt.Sprintf("充值失败: 卡密已使用 - %s", req.CardKey), "", fmt.Errorf("卡密已使用"))
response.Error(c, 400, "卡密已使用")
return
}
var user model.AppUser
if err := database.DB.Where("username = ? AND application_id = ?", req.Username, app.ID).First(&user).Error; err != nil {
service.LogVerification(c, &app.ID, nil, "recharge_failed", fmt.Sprintf("充值失败: 用户不存在 - %s", req.Username), "", fmt.Errorf("用户不存在"))
response.Error(c, 404, "用户不存在")
return
}
if user.Balance == -1 {
service.LogVerification(c, &app.ID, &user.ID, "recharge_failed", fmt.Sprintf("充值失败: 用户已是永久会员 - %s", user.Username), "", fmt.Errorf("该用户已是永久会员,无法再次充值"))
response.Error(c, 400, "该用户已是永久会员,无法再次充值")
return
}
fmt.Printf("[DEBUG] 充值前用户信息: ID=%d, Username=%s, Balance=%f, ExpiryAt=%v\n", user.ID, user.Username, user.Balance, user.ExpiryAt)
tx := database.DB.Begin()
card.Status = "used"
card.AppUserID = &user.ID
now := time.Now()
card.UsedAt = &now
if err := tx.Save(&card).Error; err != nil {
tx.Rollback()
response.Error(c, 500, "充值失败")
return
}
user.IsTrialUser = false
if card.CardType.Value == -1 {
user.Balance = -1
user.ExpiryAt = nil
} else {
switch card.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 card.CardType.ValueUnit {
case "minute":
duration = time.Duration(card.CardType.Value) * time.Minute
case "hour":
duration = time.Duration(card.CardType.Value) * time.Hour
case "day":
duration = time.Duration(card.CardType.Value) * 24 * time.Hour
case "month":
duration = time.Duration(card.CardType.Value) * 30 * 24 * time.Hour
case "year":
duration = time.Duration(card.CardType.Value) * 365 * 24 * time.Hour
default:
duration = time.Duration(card.CardType.Value) * time.Second
}
newExpiry := baseTime.Add(duration)
user.ExpiryAt = &newExpiry
case "balance":
fallthrough
default:
user.Balance += card.CardType.Value
}
}
fmt.Printf("[DEBUG] 充值后用户信息: Balance=%f, ExpiryAt=%v (CardType.Value=%f, BillingType=%s)\n", user.Balance, user.ExpiryAt, card.CardType.Value, app.BillingType)
if err := tx.Save(&user).Error; err != nil {
tx.Rollback()
response.Error(c, 500, "更新用户信息失败")
return
}
rechargeRecord := model.RechargeRecord{
UserID: user.ID,
OrderNo: generateOrderNo("R"),
CardID: &card.ID,
CardCode: card.CardKey,
Amount: card.CardType.Price,
Status: "success",
PaymentType: "card",
Remark: "卡密充值 - " + card.CardType.Name,
}
if err := tx.Create(&rechargeRecord).Error; err != nil {
tx.Rollback()
response.Error(c, 500, "创建充值记录失败")
return
}
if err := tx.Commit().Error; err != nil {
response.Error(c, 500, "充值失败")
return
}
service.LogVerification(c, &app.ID, &user.ID, "recharge", fmt.Sprintf("用户充值: %s, 卡密: %s, 金额: %.2f", user.Username, card.CardKey, card.CardType.Price), "", nil)
response.SuccessWithMessage(c, "充值成功", gin.H{
"message": "充值成功",
"value": card.CardType.Value,
})
}
func handleAppTrial(c *gin.Context) {
appKey := c.Param("appKey")
var app model.Application
if err := database.DB.Where("app_key = ?", appKey).First(&app).Error; err != nil {
response.Error(c, 404, "应用不存在")
return
}
if service.GetApplicationDisabledStatus(app.ID) {
response.Error(c, 403, "该应用已被禁用")
return
}
if !app.EnableTrial {
response.Error(c, 400, "该应用不支持试用")
return
}
var req struct {
UserID uint `json:"user_id"`
}
if err := c.ShouldBindJSON(&req); err != nil {
response.Error(c, 400, "参数错误")
return
}
var user model.AppUser
if err := database.DB.First(&user, req.UserID).Error; err != nil {
response.Error(c, 404, "用户不存在")
return
}
response.SuccessWithMessage(c, "试用成功", gin.H{
"message": "试用成功",
"trial_balance": app.TrialBalance,
})
}
func generateOrderNo(prefix string) string {
return prefix + time.Now().Format("20060102150405") + randomString(6)
}
func randomString(length int) string {
const charset = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
b := make([]byte, length)
for i := range b {
b[i] = charset[time.Now().Nanosecond()%len(charset)]
time.Sleep(1 * time.Nanosecond)
}
return string(b)
}
@@ -0,0 +1,73 @@
package app
import (
"bytes"
"encoding/json"
"fmt"
"verification-platform-backend/pkg/crypto"
"github.com/gin-gonic/gin"
)
type bodyLogWriter struct {
gin.ResponseWriter
body *bytes.Buffer
}
func (w *bodyLogWriter) Write(b []byte) (int, error) {
return w.body.Write(b)
}
func (w *bodyLogWriter) WriteHeader(statusCode int) {
w.ResponseWriter.WriteHeader(statusCode)
}
type EncryptedResponse struct {
Data string `json:"data"`
}
func ResponseEncryption() gin.HandlerFunc {
return func(c *gin.Context) {
blw := &bodyLogWriter{body: bytes.NewBufferString(""), ResponseWriter: c.Writer}
c.Writer = blw
c.Next()
shouldEncrypt := c.GetBool("should_encrypt_response")
fmt.Printf("[ResponseEncryption] ShouldEncrypt: %v\n", shouldEncrypt)
blw.ResponseWriter.Header().Set("Content-Type", "application/json; charset=utf-8")
if shouldEncrypt {
var response map[string]interface{}
if err := json.Unmarshal(blw.body.Bytes(), &response); err == nil {
responseJSON, _ := json.Marshal(response)
fmt.Printf("[ResponseEncryption] Response to encrypt: %s\n", string(responseJSON))
var cryptoManager *crypto.CryptoManager
if cm, ok := c.Get("crypto_manager"); ok {
cryptoManager = cm.(*crypto.CryptoManager)
}
if cryptoManager != nil {
encrypted, err := cryptoManager.Encrypt(string(responseJSON))
if err == nil {
encryptedResponse := EncryptedResponse{Data: encrypted}
encryptedJSON, _ := json.Marshal(encryptedResponse)
blw.ResponseWriter.Write(encryptedJSON)
fmt.Printf("[ResponseEncryption] Encrypted response sent\n")
return
} else {
fmt.Printf("[ResponseEncryption] Encryption error: %v\n", err)
}
} else {
fmt.Printf("[ResponseEncryption] CryptoManager is nil\n")
}
} else {
fmt.Printf("[ResponseEncryption] JSON unmarshal error: %v\n", err)
}
}
blw.ResponseWriter.Write(blw.body.Bytes())
}
}