package app import ( "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.EnableRegisterVerify { 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(`

邮箱验证

您的验证码是:%s

验证码有效期为15分钟,请尽快使用。


此邮件由 %s 系统自动发送,请勿回复。

`, 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"` Phone string `json:"phone"` SmsCode string `json:"sms_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 } if appModel.EnableRegisterVerify { if appModel.VerifyMethod == "email" { 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) } else if appModel.VerifyMethod == "sms" { if req.Phone == "" { response.Error(c, 400, "请输入手机号") return } if req.SmsCode == "" { response.Error(c, 400, "请输入短信验证码") return } var verifyCode model.SmsVerifyCode if err := database.DB.Where( "application_id = ? AND phone = ? AND code = ? AND purpose = ? AND used = ?", appModel.ID, req.Phone, req.SmsCode, "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 } } tx := database.DB.Begin() defer func() { if r := recover(); r != nil { tx.Rollback() } }() if err := tx.Create(&user).Error; err != nil { tx.Rollback() 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 := tx.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 := tx.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 := tx.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 := tx.Create(&session).Error; err != nil { log.Printf("[DEBUG] Failed to create session: %v", err) } } else { session.LastHeartbeat = &now if err := tx.Save(&session).Error; err != nil { log.Printf("[DEBUG] Failed to update session: %v", err) } } } } tx.Commit() 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" { if user.Balance == -1 { log.Printf("[DEBUG] User %d is permanent member, allowing login", user.ID) } else if appModel.BillingType == "subscription" { if user.ExpiryAt == nil || user.ExpiryAt.Before(time.Now()) { log.Printf("[DEBUG] User %d subscription expired, ExpiryAt=%v", user.ID, user.ExpiryAt) service.LogVerification(c, &appModel.ID, &user.ID, "login_failed", "登录失败: 订阅已过期 - "+req.Username, req.DeviceID, fmt.Errorf("订阅已过期,请充值后继续使用")) response.Error(c, 403, "订阅已过期,请充值后继续使用") return } } else { if 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.MultiOpenMode != "unlimited" && 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) { 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) } } } } tokenInstanceID := req.InstanceID if tokenInstanceID == "" { tokenInstanceID = req.DeviceID } token, err := jwt.GenerateTokenWithDevice(user.ID, user.Username, "app_user", req.DeviceID, tokenInstanceID) 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) // 根据运营模式返回不同参数 loginData := gin.H{ "user_id": user.ID, "token": token, } // 永久会员返回永久标识 if user.Balance == -1 { loginData["is_permanent"] = true } else if appModel.BillingType == "subscription" { // 订阅模式返回到期时间 if user.ExpiryAt != nil { loginData["expiry_at"] = user.ExpiryAt.Format("2006-01-02 15:04:05") loginData["expiry_timestamp"] = user.ExpiryAt.Unix() } } else if appModel.BillingType != "free" { // 次数/余额模式返回余额 loginData["balance"] = user.Balance } response.SuccessWithMessage(c, "登录成功", loginData) } 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 } tx := database.DB.Begin() defer func() { if r := recover(); r != nil { tx.Rollback() } }() user.Password = string(hashedPassword) if err := tx.Save(&user).Error; err != nil { tx.Rollback() response.Error(c, 500, "密码重置失败") return } verifyCode.Used = true tx.Save(&verifyCode) tx.Commit() response.Success(c, gin.H{ "message": "密码重置成功", }) }