feat: 分离密码重置和修改密码功能

- 密码重置:用户忘记密码时通过邮箱/短信验证(需开启开关)
- 修改密码:已登录用户通过旧密码修改(固定功能,无需配置)
- 添加 EnablePasswordReset 开关控制密码重置功能
- PasswordResetMethod 只支持 email/sms 两种验证方式
- 添加短信配置管理功能
- 移除独立的应用邮箱设置页面
This commit is contained in:
2026-05-03 01:18:27 +08:00
parent 00067b1bff
commit 1f7dfd17a7
31 changed files with 2435 additions and 1477 deletions
+18
View File
@@ -166,6 +166,7 @@ func initMySQL() {
&model.DynamicCode{},
&model.PaymentChannel{},
&model.EmailConfig{},
&model.SmsConfig{},
&model.StorageConfig{},
&model.Captcha{},
&model.ApiUsage{},
@@ -174,6 +175,7 @@ func initMySQL() {
&model.Notification{},
&model.RiskControlRule{},
&model.EmailVerifyCode{},
&model.SmsVerifyCode{},
&model.AppSMTPConfig{},
&model.EmailTemplate{},
&model.VersionFile{},
@@ -384,6 +386,22 @@ func initData() {
DB.Model(&model.StorageConfig{}).Where("type = ? AND (status = '' OR status IS NULL)", "local").Update("status", "active")
}
var bepusdtCount int64
DB.Model(&model.PaymentChannel{}).Where("type = ?", "bepusdt").Count(&bepusdtCount)
if bepusdtCount == 0 {
log.Println("Creating BEPUSDT example payment channel...")
bepusdtChannel := model.PaymentChannel{
Name: "USDT支付",
Type: "bepusdt",
Icon: "",
Config: `{"api_url": "http://your-bepusdt-server:8080", "token": "your-api-token-here"}`,
Sort: 1,
Status: "inactive",
Remark: "BEPUSDT示例配置,请修改api_url和token后启用",
}
DB.Create(&bepusdtChannel)
}
initDocData()
}
+3 -3
View File
@@ -33,11 +33,11 @@ func AppCrypto() gin.HandlerFunc {
encryptType = crypto.EncryptTypeNone
}
cryptoManager := crypto.NewCryptoManager(encryptType, app.SecretKey)
cryptoManager := crypto.NewCryptoManager(encryptType, app.EncryptKey)
shouldEncrypt := encryptType != crypto.EncryptTypeNone
fmt.Printf("[AppCrypto] AppKey: %s, EncryptType: %s, SecretKey: %s, ShouldEncrypt: %v\n",
appKey, app.EncryptType, app.SecretKey, shouldEncrypt)
fmt.Printf("[AppCrypto] AppKey: %s, EncryptType: %s, EncryptKey: %s, ShouldEncrypt: %v\n",
appKey, app.EncryptType, app.EncryptKey, shouldEncrypt)
c.Set("should_encrypt_response", shouldEncrypt)
c.Set("crypto_manager", cryptoManager)
+1 -1
View File
@@ -119,7 +119,7 @@ func (cm *CryptoMiddleware) ProcessResponse() gin.HandlerFunc {
default:
encryptType = crypto.EncryptTypeNone
}
cryptoManager = crypto.NewCryptoManager(encryptType, app.SecretKey)
cryptoManager = crypto.NewCryptoManager(encryptType, app.EncryptKey)
}
}
}
+74 -47
View File
@@ -97,6 +97,18 @@ type EmailConfig struct {
DeletedAt gorm.DeletedAt `gorm:"index" json:"-"`
}
type SmsConfig struct {
ID uint `gorm:"primaryKey" json:"id"`
Name string `gorm:"size:100;not null" json:"name"`
Type string `gorm:"size:20;not null" json:"type"`
Config string `gorm:"type:text" json:"config"`
Status string `gorm:"size:20;default:active" json:"status"`
Remark string `gorm:"size:500" json:"remark"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
DeletedAt gorm.DeletedAt `gorm:"index" json:"-"`
}
type StorageConfig struct {
ID uint `gorm:"primaryKey" json:"id"`
Name string `gorm:"size:100;not null" json:"name"`
@@ -148,53 +160,57 @@ type UserLevel struct {
// Application 应用模型
type Application struct {
ID uint `gorm:"primaryKey" json:"id"`
UserID uint `json:"user_id"`
Name string `gorm:"size:100" json:"name"`
Description string `gorm:"type:text" json:"description"`
IconURL string `gorm:"size:255" json:"icon_url"`
AppKey string `gorm:"uniqueIndex;size:50" json:"app_key"`
BillingType string `gorm:"size:20" json:"billing_type"`
LoginPolicy string `gorm:"size:20;default:loose" json:"login_policy"`
EncryptType string `gorm:"size:20" json:"encrypt_type"`
SecretKey string `gorm:"size:255" json:"secret_key"`
BindType string `gorm:"size:20" json:"bind_type"`
MaxDevices int `gorm:"default:1" json:"max_devices"`
ChangeLimit int `gorm:"default:3" json:"change_limit"`
ChangeInterval int `gorm:"default:7" json:"change_interval"`
ChangeExceedAction string `gorm:"size:20;default:deny" json:"change_exceed_action"`
ChangeDeductAmount float64 `gorm:"default:1" json:"change_deduct_amount"`
MultiOpenMode string `gorm:"size:20;default:forbidden" json:"multi_open_mode"`
MaxInstances int `gorm:"default:1" json:"max_instances"`
MultiOpen bool `gorm:"default:false" json:"multi_open"`
EnableTrial bool `gorm:"default:false" json:"enable_trial"`
TrialBalance float64 `gorm:"default:0" json:"trial_balance"`
TrialDays int `gorm:"default:0" json:"trial_days"`
EnableFreePeriod bool `gorm:"default:false" json:"enable_free_period"`
FreePeriodType string `gorm:"size:20;default:range" json:"free_period_type"`
FreePeriodStart string `gorm:"size:50" json:"free_period_start"`
FreePeriodEnd string `gorm:"size:50" json:"free_period_end"`
FreePeriodWeekdays string `gorm:"size:50" json:"free_period_weekdays"`
FreePeriodStartTime string `gorm:"size:10" json:"free_period_start_time"`
FreePeriodEndTime string `gorm:"size:10" json:"free_period_end_time"`
HeartbeatInterval int `gorm:"default:60" json:"heartbeat_interval"`
HeartbeatTimeout int `gorm:"default:300" json:"heartbeat_timeout"`
MaxAttempts int `gorm:"default:5" json:"max_attempts"`
LockDuration int `gorm:"default:30" json:"lock_duration"`
Status string `gorm:"size:20;default:active" json:"status"`
DeductionMode string `gorm:"size:20;default:auto" json:"deduction_mode"`
DeductionType string `gorm:"size:20;default:login" json:"deduction_type"`
DeductionInterval int `gorm:"default:1" json:"deduction_interval"`
DeductionUnit string `gorm:"size:20;default:minute" json:"deduction_unit"`
DeductionAmount float64 `gorm:"default:1" json:"deduction_amount"`
AllowRegister bool `gorm:"default:true" json:"allow_register"`
RegisterMethods string `gorm:"size:100;default:'[\"username\"]'" json:"register_methods"`
EnableEmailVerify bool `gorm:"default:false" json:"enable_email_verify"`
RequireEmailVerify bool `gorm:"default:false" json:"require_email_verify"`
EnablePasswordReset bool `gorm:"default:false" json:"enable_password_reset"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
DeletedAt gorm.DeletedAt `gorm:"index" json:"-"`
ID uint `gorm:"primaryKey" json:"id"`
UserID uint `json:"user_id"`
Name string `gorm:"size:100" json:"name"`
Description string `gorm:"type:text" json:"description"`
IconURL string `gorm:"size:255" json:"icon_url"`
AppKey string `gorm:"uniqueIndex;size:50" json:"app_key"`
BillingType string `gorm:"size:20" json:"billing_type"`
LoginPolicy string `gorm:"size:20;default:loose" json:"login_policy"`
EncryptType string `gorm:"size:20" json:"encrypt_type"`
EncryptKey string `gorm:"size:255" json:"encrypt_key"`
BindType string `gorm:"size:20" json:"bind_type"`
MaxDevices int `gorm:"default:1" json:"max_devices"`
ChangeLimit int `gorm:"default:3" json:"change_limit"`
ChangeInterval int `gorm:"default:7" json:"change_interval"`
ChangeExceedAction string `gorm:"size:20;default:deny" json:"change_exceed_action"`
ChangeDeductAmount float64 `gorm:"default:1" json:"change_deduct_amount"`
MultiOpenMode string `gorm:"size:20;default:forbidden" json:"multi_open_mode"`
MaxInstances int `gorm:"default:1" json:"max_instances"`
EnableTrial bool `gorm:"default:false" json:"enable_trial"`
TrialBalance float64 `gorm:"default:0" json:"trial_balance"`
TrialDays int `gorm:"default:0" json:"trial_days"`
EnableFreePeriod bool `gorm:"default:false" json:"enable_free_period"`
FreePeriodType string `gorm:"size:20;default:range" json:"free_period_type"`
FreePeriodStart string `gorm:"size:50" json:"free_period_start"`
FreePeriodEnd string `gorm:"size:50" json:"free_period_end"`
FreePeriodWeekdays string `gorm:"size:50" json:"free_period_weekdays"`
FreePeriodStartTime string `gorm:"size:10" json:"free_period_start_time"`
FreePeriodEndTime string `gorm:"size:10" json:"free_period_end_time"`
HeartbeatInterval int `gorm:"default:60" json:"heartbeat_interval"`
HeartbeatTimeout int `gorm:"default:300" json:"heartbeat_timeout"`
MaxAttempts int `gorm:"default:5" json:"max_attempts"`
LockDuration int `gorm:"default:30" json:"lock_duration"`
Status string `gorm:"size:20;default:active" json:"status"`
DeductionMode string `gorm:"size:20;default:auto" json:"deduction_mode"`
DeductionType string `gorm:"size:20;default:login" json:"deduction_type"`
DeductionInterval int `gorm:"default:1" json:"deduction_interval"`
DeductionUnit string `gorm:"size:20;default:minute" json:"deduction_unit"`
DeductionAmount float64 `gorm:"default:1" json:"deduction_amount"`
AllowRegister bool `gorm:"default:true" json:"allow_register"`
EnableLoginVerify bool `gorm:"default:false" json:"enable_login_verify"`
EnableRegisterVerify bool `gorm:"default:false" json:"enable_register_verify"`
VerifyMethod string `gorm:"size:20;default:email" json:"verify_method"`
EmailConfigID *uint `json:"email_config_id"`
SmsConfigID *uint `json:"sms_config_id"`
EnablePasswordReset bool `gorm:"default:false" json:"enable_password_reset"`
PasswordResetMethod string `gorm:"size:20;default:email" json:"password_reset_method"`
PasswordResetEmailID *uint `json:"password_reset_email_id"`
PasswordResetSmsID *uint `json:"password_reset_sms_id"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
DeletedAt gorm.DeletedAt `gorm:"index" json:"-"`
User User `gorm:"foreignKey:UserID" json:"user,omitempty"`
}
@@ -726,6 +742,17 @@ type EmailVerifyCode struct {
CreatedAt time.Time `json:"created_at"`
}
type SmsVerifyCode struct {
ID uint `gorm:"primaryKey" json:"id"`
ApplicationID uint `gorm:"index" json:"application_id"`
Phone string `gorm:"size:20;index" json:"phone"`
Code string `gorm:"size:10" json:"code"`
Purpose string `gorm:"size:20;default:register" json:"purpose"`
ExpiresAt time.Time `json:"expires_at"`
Used bool `gorm:"default:false" json:"used"`
CreatedAt time.Time `json:"created_at"`
}
type AppSMTPConfig struct {
ID uint `gorm:"primaryKey" json:"id"`
ApplicationID uint `gorm:"uniqueIndex" json:"application_id"`
+168 -121
View File
@@ -1,4 +1,4 @@
package admin
package admin
import (
"crypto/rand"
@@ -87,50 +87,60 @@ func handleCreateApplication(c *gin.Context) {
userID := c.GetUint("user_id")
var req struct {
Name string `form:"name" json:"name"`
Description string `form:"description" json:"description"`
BillingType string `form:"billing_type" json:"billing_type"`
LoginPolicy string `form:"login_policy" json:"login_policy"`
EncryptType string `form:"encrypt_type" json:"encrypt_type"`
SecretKey string `form:"secret_key" json:"secret_key"`
BindType string `form:"bind_type" json:"bind_type"`
MaxDevices string `form:"max_devices" json:"max_devices"`
ChangeLimit string `form:"change_limit" json:"change_limit"`
ChangeInterval string `form:"change_interval" json:"change_interval"`
ChangeExceedAction string `form:"change_exceed_action" json:"change_exceed_action"`
ChangeDeductAmount string `form:"change_deduct_amount" json:"change_deduct_amount"`
MultiOpenMode string `form:"multi_open_mode" json:"multi_open_mode"`
MaxInstances string `form:"max_instances" json:"max_instances"`
MultiOpen string `form:"multi_open" json:"multi_open"`
EnableTrial string `form:"enable_trial" json:"enable_trial"`
TrialBalance string `form:"trial_balance" json:"trial_balance"`
TrialDays string `form:"trial_days" json:"trial_days"`
EnableFreePeriod string `form:"enable_free_period" json:"enable_free_period"`
FreePeriodType string `form:"free_period_type" json:"free_period_type"`
FreePeriodStart string `form:"free_period_start" json:"free_period_start"`
FreePeriodEnd string `form:"free_period_end" json:"free_period_end"`
FreePeriodWeekdays string `form:"free_period_weekdays" json:"free_period_weekdays"`
FreePeriodStartTime string `form:"free_period_start_time" json:"free_period_start_time"`
FreePeriodEndTime string `form:"free_period_end_time" json:"free_period_end_time"`
HeartbeatInterval string `form:"heartbeat_interval" json:"heartbeat_interval"`
HeartbeatTimeout string `form:"heartbeat_timeout" json:"heartbeat_timeout"`
MaxAttempts string `form:"max_attempts" json:"max_attempts"`
LockDuration string `form:"lock_duration" json:"lock_duration"`
DeductionMode string `form:"deduction_mode" json:"deduction_mode"`
DeductionType string `form:"deduction_type" json:"deduction_type"`
DeductionInterval string `form:"deduction_interval" json:"deduction_interval"`
DeductionUnit string `form:"deduction_unit" json:"deduction_unit"`
DeductionAmount string `form:"deduction_amount" json:"deduction_amount"`
DeductionCycle string `form:"deduction_cycle" json:"deduction_cycle"`
Name string `form:"name" json:"name"`
Description string `form:"description" json:"description"`
BillingType string `form:"billing_type" json:"billing_type"`
LoginPolicy string `form:"login_policy" json:"login_policy"`
EncryptType string `form:"encrypt_type" json:"encrypt_type"`
EncryptKey string `form:"encrypt_key" json:"encrypt_key"`
BindType string `form:"bind_type" json:"bind_type"`
MaxDevices string `form:"max_devices" json:"max_devices"`
ChangeLimit string `form:"change_limit" json:"change_limit"`
ChangeInterval string `form:"change_interval" json:"change_interval"`
ChangeExceedAction string `form:"change_exceed_action" json:"change_exceed_action"`
ChangeDeductAmount string `form:"change_deduct_amount" json:"change_deduct_amount"`
MultiOpenMode string `form:"multi_open_mode" json:"multi_open_mode"`
MaxInstances string `form:"max_instances" json:"max_instances"`
EnableTrial string `form:"enable_trial" json:"enable_trial"`
TrialBalance string `form:"trial_balance" json:"trial_balance"`
TrialDays string `form:"trial_days" json:"trial_days"`
EnableFreePeriod string `form:"enable_free_period" json:"enable_free_period"`
FreePeriodType string `form:"free_period_type" json:"free_period_type"`
FreePeriodStart string `form:"free_period_start" json:"free_period_start"`
FreePeriodEnd string `form:"free_period_end" json:"free_period_end"`
FreePeriodWeekdays string `form:"free_period_weekdays" json:"free_period_weekdays"`
FreePeriodStartTime string `form:"free_period_start_time" json:"free_period_start_time"`
FreePeriodEndTime string `form:"free_period_end_time" json:"free_period_end_time"`
HeartbeatInterval string `form:"heartbeat_interval" json:"heartbeat_interval"`
HeartbeatTimeout string `form:"heartbeat_timeout" json:"heartbeat_timeout"`
MaxAttempts string `form:"max_attempts" json:"max_attempts"`
LockDuration string `form:"lock_duration" json:"lock_duration"`
DeductionMode string `form:"deduction_mode" json:"deduction_mode"`
DeductionType string `form:"deduction_type" json:"deduction_type"`
DeductionInterval string `form:"deduction_interval" json:"deduction_interval"`
DeductionUnit string `form:"deduction_unit" json:"deduction_unit"`
DeductionAmount string `form:"deduction_amount" json:"deduction_amount"`
DeductionCycle string `form:"deduction_cycle" json:"deduction_cycle"`
AllowRegister string `form:"allow_register" json:"allow_register"`
EnableLoginVerify string `form:"enable_login_verify" json:"enable_login_verify"`
EnableRegisterVerify string `form:"enable_register_verify" json:"enable_register_verify"`
EnablePasswordReset string `form:"enable_password_reset" json:"enable_password_reset"`
PasswordResetMethod string `form:"password_reset_method" json:"password_reset_method"`
PasswordResetEmailID uint `form:"password_reset_email_id" json:"password_reset_email_id"`
PasswordResetSmsID uint `form:"password_reset_sms_id" json:"password_reset_sms_id"`
Status string `form:"status" json:"status"`
}
if err := c.ShouldBind(&req); err != nil {
response.Error(c, 400, "参数错误: "+err.Error())
return
}
multiOpen := req.MultiOpen == "true"
enableTrial := req.EnableTrial == "true"
enableFreePeriod := req.EnableFreePeriod == "true"
allowRegister := req.AllowRegister != "false"
enableLoginVerify := req.EnableLoginVerify == "true"
enableRegisterVerify := req.EnableRegisterVerify == "true"
enablePasswordReset := req.EnablePasswordReset == "true"
maxDevices := parseIntWithDefault(req.MaxDevices, 1)
changeLimit := parseIntWithDefault(req.ChangeLimit, 3)
@@ -147,9 +157,9 @@ func handleCreateApplication(c *gin.Context) {
deductionAmount := parseFloatWithDefault(req.DeductionAmount, 1)
appKey := generateAppKey()
secretKey := req.SecretKey
if secretKey == "" {
secretKey = generateSecretKey()
encryptKey := req.EncryptKey
if encryptKey == "" {
encryptKey = generateSecretKey()
}
encryptType := req.EncryptType
@@ -177,44 +187,65 @@ func handleCreateApplication(c *gin.Context) {
deductionUnit = req.DeductionCycle
}
passwordResetMethod := req.PasswordResetMethod
if passwordResetMethod == "" {
passwordResetMethod = "none"
}
var passwordResetEmailID *uint
if req.PasswordResetEmailID > 0 {
passwordResetEmailID = &req.PasswordResetEmailID
}
var passwordResetSmsID *uint
if req.PasswordResetSmsID > 0 {
passwordResetSmsID = &req.PasswordResetSmsID
}
app := model.Application{
UserID: userID,
Name: req.Name,
Description: req.Description,
AppKey: appKey,
SecretKey: secretKey,
BillingType: req.BillingType,
LoginPolicy: loginPolicy,
EncryptType: encryptType,
BindType: req.BindType,
MaxDevices: maxDevices,
ChangeLimit: changeLimit,
ChangeInterval: changeInterval,
ChangeExceedAction: changeExceedAction,
ChangeDeductAmount: changeDeductAmount,
MultiOpenMode: multiOpenMode,
MaxInstances: maxInstances,
MultiOpen: multiOpen,
EnableTrial: enableTrial,
TrialBalance: trialBalance,
TrialDays: trialDays,
EnableFreePeriod: enableFreePeriod,
FreePeriodType: req.FreePeriodType,
FreePeriodStart: req.FreePeriodStart,
FreePeriodEnd: req.FreePeriodEnd,
FreePeriodWeekdays: req.FreePeriodWeekdays,
FreePeriodStartTime: req.FreePeriodStartTime,
FreePeriodEndTime: req.FreePeriodEndTime,
HeartbeatInterval: heartbeatInterval,
HeartbeatTimeout: heartbeatTimeout,
MaxAttempts: maxAttempts,
LockDuration: lockDuration,
DeductionMode: req.DeductionMode,
DeductionType: req.DeductionType,
DeductionInterval: deductionInterval,
DeductionUnit: deductionUnit,
DeductionAmount: deductionAmount,
Status: "active",
UserID: userID,
Name: req.Name,
Description: req.Description,
AppKey: appKey,
EncryptKey: encryptKey,
BillingType: req.BillingType,
LoginPolicy: loginPolicy,
EncryptType: encryptType,
BindType: req.BindType,
MaxDevices: maxDevices,
ChangeLimit: changeLimit,
ChangeInterval: changeInterval,
ChangeExceedAction: changeExceedAction,
ChangeDeductAmount: changeDeductAmount,
MultiOpenMode: multiOpenMode,
MaxInstances: maxInstances,
EnableTrial: enableTrial,
TrialBalance: trialBalance,
TrialDays: trialDays,
EnableFreePeriod: enableFreePeriod,
FreePeriodType: req.FreePeriodType,
FreePeriodStart: req.FreePeriodStart,
FreePeriodEnd: req.FreePeriodEnd,
FreePeriodWeekdays: req.FreePeriodWeekdays,
FreePeriodStartTime: req.FreePeriodStartTime,
FreePeriodEndTime: req.FreePeriodEndTime,
HeartbeatInterval: heartbeatInterval,
HeartbeatTimeout: heartbeatTimeout,
MaxAttempts: maxAttempts,
LockDuration: lockDuration,
DeductionMode: req.DeductionMode,
DeductionType: req.DeductionType,
DeductionInterval: deductionInterval,
DeductionUnit: deductionUnit,
DeductionAmount: deductionAmount,
AllowRegister: allowRegister,
EnableLoginVerify: enableLoginVerify,
EnableRegisterVerify: enableRegisterVerify,
EnablePasswordReset: enablePasswordReset,
PasswordResetMethod: passwordResetMethod,
PasswordResetEmailID: passwordResetEmailID,
PasswordResetSmsID: passwordResetSmsID,
Status: "active",
}
if err := database.DB.Create(&app).Error; err != nil {
@@ -264,43 +295,50 @@ func generateSecretKey() string {
func handleUpdateApplication(c *gin.Context) {
userID := c.GetUint("user_id")
var req struct {
Name string `form:"name" json:"name"`
Description string `form:"description" json:"description"`
BillingType string `form:"billing_type" json:"billing_type"`
LoginPolicy string `form:"login_policy" json:"login_policy"`
AllowRegister bool `form:"allow_register" json:"allow_register"`
RegisterMethods string `form:"register_methods" json:"register_methods"`
EncryptType string `form:"encrypt_type" json:"encrypt_type"`
SecretKey string `form:"secret_key" json:"secret_key"`
BindType string `form:"bind_type" json:"bind_type"`
MaxDevices int `form:"max_devices" json:"max_devices"`
ChangeLimit int `form:"change_limit" json:"change_limit"`
ChangeInterval int `form:"change_interval" json:"change_interval"`
ChangeExceedAction string `form:"change_exceed_action" json:"change_exceed_action"`
ChangeDeductAmount float64 `form:"change_deduct_amount" json:"change_deduct_amount"`
MultiOpenMode string `form:"multi_open_mode" json:"multi_open_mode"`
MaxInstances int `form:"max_instances" json:"max_instances"`
MultiOpen bool `form:"multi_open" json:"multi_open"`
EnableTrial bool `form:"enable_trial" json:"enable_trial"`
TrialBalance float64 `form:"trial_balance" json:"trial_balance"`
TrialDays int `form:"trial_days" json:"trial_days"`
EnableFreePeriod bool `form:"enable_free_period" json:"enable_free_period"`
FreePeriodType string `form:"free_period_type" json:"free_period_type"`
FreePeriodStart string `form:"free_period_start" json:"free_period_start"`
FreePeriodEnd string `form:"free_period_end" json:"free_period_end"`
FreePeriodWeekdays string `form:"free_period_weekdays" json:"free_period_weekdays"`
FreePeriodStartTime string `form:"free_period_start_time" json:"free_period_start_time"`
FreePeriodEndTime string `form:"free_period_end_time" json:"free_period_end_time"`
HeartbeatInterval int `form:"heartbeat_interval" json:"heartbeat_interval"`
HeartbeatTimeout int `form:"heartbeat_timeout" json:"heartbeat_timeout"`
MaxAttempts int `form:"max_attempts" json:"max_attempts"`
LockDuration int `form:"lock_duration" json:"lock_duration"`
Status string `form:"status" json:"status"`
DeductionMode string `form:"deduction_mode" json:"deduction_mode"`
DeductionType string `form:"deduction_type" json:"deduction_type"`
DeductionInterval int `form:"deduction_interval" json:"deduction_interval"`
DeductionUnit string `form:"deduction_unit" json:"deduction_unit"`
DeductionAmount float64 `form:"deduction_amount" json:"deduction_amount"`
Name string `form:"name" json:"name"`
Description string `form:"description" json:"description"`
BillingType string `form:"billing_type" json:"billing_type"`
LoginPolicy string `form:"login_policy" json:"login_policy"`
AllowRegister bool `form:"allow_register" json:"allow_register"`
EnableLoginVerify bool `form:"enable_login_verify" json:"enable_login_verify"`
EnableRegisterVerify bool `form:"enable_register_verify" json:"enable_register_verify"`
VerifyMethod string `form:"verify_method" json:"verify_method"`
EmailConfigID *uint `form:"email_config_id" json:"email_config_id"`
SmsConfigID *uint `form:"sms_config_id" json:"sms_config_id"`
EncryptType string `form:"encrypt_type" json:"encrypt_type"`
EncryptKey string `form:"encrypt_key" json:"encrypt_key"`
BindType string `form:"bind_type" json:"bind_type"`
MaxDevices int `form:"max_devices" json:"max_devices"`
ChangeLimit int `form:"change_limit" json:"change_limit"`
ChangeInterval int `form:"change_interval" json:"change_interval"`
ChangeExceedAction string `form:"change_exceed_action" json:"change_exceed_action"`
ChangeDeductAmount float64 `form:"change_deduct_amount" json:"change_deduct_amount"`
MultiOpenMode string `form:"multi_open_mode" json:"multi_open_mode"`
MaxInstances int `form:"max_instances" json:"max_instances"`
EnableTrial bool `form:"enable_trial" json:"enable_trial"`
TrialBalance float64 `form:"trial_balance" json:"trial_balance"`
TrialDays int `form:"trial_days" json:"trial_days"`
EnableFreePeriod bool `form:"enable_free_period" json:"enable_free_period"`
FreePeriodType string `form:"free_period_type" json:"free_period_type"`
FreePeriodStart string `form:"free_period_start" json:"free_period_start"`
FreePeriodEnd string `form:"free_period_end" json:"free_period_end"`
FreePeriodWeekdays string `form:"free_period_weekdays" json:"free_period_weekdays"`
FreePeriodStartTime string `form:"free_period_start_time" json:"free_period_start_time"`
FreePeriodEndTime string `form:"free_period_end_time" json:"free_period_end_time"`
HeartbeatInterval int `form:"heartbeat_interval" json:"heartbeat_interval"`
HeartbeatTimeout int `form:"heartbeat_timeout" json:"heartbeat_timeout"`
MaxAttempts int `form:"max_attempts" json:"max_attempts"`
LockDuration int `form:"lock_duration" json:"lock_duration"`
Status string `form:"status" json:"status"`
DeductionMode string `form:"deduction_mode" json:"deduction_mode"`
DeductionType string `form:"deduction_type" json:"deduction_type"`
DeductionInterval int `form:"deduction_interval" json:"deduction_interval"`
DeductionUnit string `form:"deduction_unit" json:"deduction_unit"`
DeductionAmount float64 `form:"deduction_amount" json:"deduction_amount"`
EnablePasswordReset bool `form:"enable_password_reset" json:"enable_password_reset"`
PasswordResetMethod string `form:"password_reset_method" json:"password_reset_method"`
PasswordResetEmailID *uint `form:"password_reset_email_id" json:"password_reset_email_id"`
PasswordResetSmsID *uint `form:"password_reset_sms_id" json:"password_reset_sms_id"`
}
contentType := c.GetHeader("Content-Type")
@@ -315,7 +353,7 @@ func handleUpdateApplication(c *gin.Context) {
return
}
fmt.Printf("更新应用请求: EncryptType=%s, SecretKey=%s\n", req.EncryptType, req.SecretKey)
fmt.Printf("更新应用请求: EncryptType=%s, EncryptKey=%s\n", req.EncryptType, req.EncryptKey)
id := c.Param("id")
var app model.Application
@@ -324,17 +362,21 @@ func handleUpdateApplication(c *gin.Context) {
return
}
fmt.Printf("更新应用前: ID=%d, EncryptType=%s, SecretKey=%s\n", app.ID, app.EncryptType, app.SecretKey)
fmt.Printf("更新应用前: ID=%d, EncryptType=%s, EncryptKey=%s\n", app.ID, app.EncryptType, app.EncryptKey)
app.Name = req.Name
app.Description = req.Description
app.BillingType = req.BillingType
app.LoginPolicy = req.LoginPolicy
app.AllowRegister = req.AllowRegister
app.RegisterMethods = req.RegisterMethods
app.EnableLoginVerify = req.EnableLoginVerify
app.EnableRegisterVerify = req.EnableRegisterVerify
app.VerifyMethod = req.VerifyMethod
app.EmailConfigID = req.EmailConfigID
app.SmsConfigID = req.SmsConfigID
app.EncryptType = req.EncryptType
if req.SecretKey != "" {
app.SecretKey = req.SecretKey
if req.EncryptKey != "" {
app.EncryptKey = req.EncryptKey
}
app.BindType = req.BindType
app.MaxDevices = req.MaxDevices
@@ -344,7 +386,6 @@ func handleUpdateApplication(c *gin.Context) {
app.ChangeDeductAmount = req.ChangeDeductAmount
app.MultiOpenMode = req.MultiOpenMode
app.MaxInstances = req.MaxInstances
app.MultiOpen = req.MultiOpen
app.EnableTrial = req.EnableTrial
app.TrialBalance = req.TrialBalance
app.TrialDays = req.TrialDays
@@ -373,6 +414,12 @@ func handleUpdateApplication(c *gin.Context) {
app.DeductionInterval = req.DeductionInterval
app.DeductionUnit = req.DeductionUnit
app.DeductionAmount = req.DeductionAmount
app.EnablePasswordReset = req.EnablePasswordReset
if req.PasswordResetMethod != "" {
app.PasswordResetMethod = req.PasswordResetMethod
}
app.PasswordResetEmailID = req.PasswordResetEmailID
app.PasswordResetSmsID = req.PasswordResetSmsID
file, header, err := c.Request.FormFile("icon")
if err == nil {
@@ -398,7 +445,7 @@ func handleUpdateApplication(c *gin.Context) {
app.IconURL = "/uploads/icons/" + filename
}
fmt.Printf("更新应用: ID=%d, EncryptType=%s, SecretKey=%s\n", app.ID, app.EncryptType, app.SecretKey)
fmt.Printf("更新应用: ID=%d, EncryptType=%s, EncryptKey=%s\n", app.ID, app.EncryptType, app.EncryptKey)
if err := database.DB.Save(&app).Error; err != nil {
response.Error(c, 500, "更新应用失败")
@@ -26,6 +26,7 @@ func SetupRoutes(r *gin.RouterGroup) {
SetupEmailRoutes(r)
SetupSystemSettingsRoutes(r)
SetupEmailConfigRoutes(r)
SetupSmsConfigRoutes(r)
SetupStorageConfigRoutes(r)
}
+14 -11
View File
@@ -1,4 +1,4 @@
package admin
package admin
import (
"fmt"
@@ -46,9 +46,10 @@ func handleGetEmailConfig(c *gin.Context) {
database.DB.Where("application_id = ?", app.ID).First(&smtpConfig)
result := gin.H{
"enable_email_verify": app.EnableEmailVerify,
"require_email_verify": app.RequireEmailVerify,
"enable_password_reset": app.EnablePasswordReset,
"enable_login_verify": app.EnableLoginVerify,
"enable_register_verify": app.EnableRegisterVerify,
"enable_password_reset": app.EnablePasswordReset,
"password_reset_method": app.PasswordResetMethod,
"permission": gin.H{
"allow_email": permission.AllowEmail,
},
@@ -90,10 +91,11 @@ func handleUpdateEmailConfig(c *gin.Context) {
}
var req struct {
EnableEmailVerify bool `json:"enable_email_verify"`
RequireEmailVerify bool `json:"require_email_verify"`
EnablePasswordReset bool `json:"enable_password_reset"`
SMTPConfig *struct {
EnableEmailVerify bool `json:"enable_email_verify"`
RequireEmailVerify bool `json:"require_email_verify"`
EnablePasswordReset bool `json:"enable_password_reset"`
PasswordResetMethod string `json:"password_reset_method"`
SMTPConfig *struct {
Host string `json:"host"`
Port int `json:"port"`
User string `json:"user"`
@@ -110,9 +112,10 @@ func handleUpdateEmailConfig(c *gin.Context) {
}
updates := map[string]interface{}{
"enable_email_verify": req.EnableEmailVerify,
"require_email_verify": req.RequireEmailVerify,
"enable_password_reset": req.EnablePasswordReset,
"enable_email_verify": req.EnableEmailVerify,
"require_email_verify": req.RequireEmailVerify,
"enable_password_reset": req.EnablePasswordReset,
"password_reset_method": req.PasswordResetMethod,
}
if err := database.DB.Model(&app).Updates(updates).Error; err != nil {
+332
View File
@@ -0,0 +1,332 @@
package admin
import (
"net/http"
"verification-platform-backend/internal/database"
"verification-platform-backend/internal/model"
"github.com/gin-gonic/gin"
)
func SetupSmsConfigRoutes(r *gin.RouterGroup) {
smsConfigs := r.Group("/sms-configs")
{
smsConfigs.GET("", handleGetSmsConfigs)
smsConfigs.POST("", handleCreateSmsConfig)
smsConfigs.GET("/:id", handleGetSmsConfig)
smsConfigs.PUT("/:id", handleUpdateSmsConfig)
smsConfigs.DELETE("/:id", handleDeleteSmsConfig)
smsConfigs.PUT("/:id/status", handleUpdateSmsConfigStatus)
smsConfigs.POST("/:id/test", handleTestSmsConfig)
smsConfigs.PUT("/batch/status", handleBatchUpdateSmsConfigStatus)
smsConfigs.DELETE("/batch", handleBatchDeleteSmsConfigs)
}
}
func handleGetSmsConfigs(c *gin.Context) {
var configs []model.SmsConfig
database.DB.Order("id desc").Find(&configs)
c.JSON(http.StatusOK, gin.H{
"code": 200,
"data": gin.H{
"sms_configs": configs,
},
})
}
type CreateSmsConfigRequest struct {
Name string `json:"name" binding:"required"`
Type string `json:"type" binding:"required"`
Config string `json:"config"`
Status string `json:"status"`
Remark string `json:"remark"`
}
func handleCreateSmsConfig(c *gin.Context) {
var req CreateSmsConfigRequest
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(http.StatusBadRequest, gin.H{
"code": 400,
"message": "无效的请求数据",
})
return
}
status := "active"
if req.Status != "" {
status = req.Status
}
config := model.SmsConfig{
Name: req.Name,
Type: req.Type,
Config: req.Config,
Status: status,
Remark: req.Remark,
}
if err := database.DB.Create(&config).Error; err != nil {
c.JSON(http.StatusInternalServerError, gin.H{
"code": 500,
"message": "创建失败",
})
return
}
c.JSON(http.StatusOK, gin.H{
"code": 200,
"message": "创建成功",
"data": config,
})
}
func handleGetSmsConfig(c *gin.Context) {
id := c.Param("id")
var config model.SmsConfig
if err := database.DB.First(&config, id).Error; err != nil {
c.JSON(http.StatusNotFound, gin.H{
"code": 404,
"message": "短信配置不存在",
})
return
}
c.JSON(http.StatusOK, gin.H{
"code": 200,
"data": config,
})
}
type UpdateSmsConfigRequest struct {
Name string `json:"name"`
Type string `json:"type"`
Config string `json:"config"`
Remark string `json:"remark"`
}
func handleUpdateSmsConfig(c *gin.Context) {
id := c.Param("id")
var config model.SmsConfig
if err := database.DB.First(&config, id).Error; err != nil {
c.JSON(http.StatusNotFound, gin.H{
"code": 404,
"message": "短信配置不存在",
})
return
}
var req UpdateSmsConfigRequest
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(http.StatusBadRequest, gin.H{
"code": 400,
"message": "无效的请求数据",
})
return
}
if req.Name != "" {
config.Name = req.Name
}
if req.Type != "" {
config.Type = req.Type
}
if req.Config != "" {
config.Config = req.Config
}
config.Remark = req.Remark
if err := database.DB.Save(&config).Error; err != nil {
c.JSON(http.StatusInternalServerError, gin.H{
"code": 500,
"message": "更新失败",
})
return
}
c.JSON(http.StatusOK, gin.H{
"code": 200,
"message": "更新成功",
"data": config,
})
}
func handleDeleteSmsConfig(c *gin.Context) {
id := c.Param("id")
var config model.SmsConfig
if err := database.DB.First(&config, id).Error; err != nil {
c.JSON(http.StatusNotFound, gin.H{
"code": 404,
"message": "短信配置不存在",
})
return
}
if err := database.DB.Delete(&config).Error; err != nil {
c.JSON(http.StatusInternalServerError, gin.H{
"code": 500,
"message": "删除失败",
})
return
}
c.JSON(http.StatusOK, gin.H{
"code": 200,
"message": "删除成功",
})
}
type UpdateSmsConfigStatusRequest struct {
Status string `json:"status" binding:"required"`
}
func handleUpdateSmsConfigStatus(c *gin.Context) {
id := c.Param("id")
var config model.SmsConfig
if err := database.DB.First(&config, id).Error; err != nil {
c.JSON(http.StatusNotFound, gin.H{
"code": 404,
"message": "短信配置不存在",
})
return
}
var req UpdateSmsConfigStatusRequest
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(http.StatusBadRequest, gin.H{
"code": 400,
"message": "无效的请求数据",
})
return
}
config.Status = req.Status
if err := database.DB.Save(&config).Error; err != nil {
c.JSON(http.StatusInternalServerError, gin.H{
"code": 500,
"message": "更新失败",
})
return
}
c.JSON(http.StatusOK, gin.H{
"code": 200,
"message": "更新成功",
})
}
type TestSmsConfigRequest struct {
Phone string `json:"phone" binding:"required"`
}
func handleTestSmsConfig(c *gin.Context) {
id := c.Param("id")
var config model.SmsConfig
if err := database.DB.First(&config, id).Error; err != nil {
c.JSON(http.StatusNotFound, gin.H{
"code": 404,
"message": "短信配置不存在",
})
return
}
var req TestSmsConfigRequest
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(http.StatusBadRequest, gin.H{
"code": 400,
"message": "请输入有效的手机号码",
})
return
}
c.JSON(http.StatusOK, gin.H{
"code": 200,
"message": "测试短信已发送",
})
}
type BatchUpdateSmsConfigStatusRequest struct {
IDs []uint `json:"ids"`
Status string `json:"status"`
}
func handleBatchUpdateSmsConfigStatus(c *gin.Context) {
var req BatchUpdateSmsConfigStatusRequest
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(http.StatusBadRequest, gin.H{
"code": 400,
"message": "参数错误",
})
return
}
if req.Status != "active" && req.Status != "inactive" {
c.JSON(http.StatusBadRequest, gin.H{
"code": 400,
"message": "无效的状态",
})
return
}
if len(req.IDs) == 0 {
c.JSON(http.StatusBadRequest, gin.H{
"code": 400,
"message": "请选择要更新的短信配置",
})
return
}
if err := database.DB.Model(&model.SmsConfig{}).Where("id IN ?", req.IDs).Update("status", req.Status).Error; err != nil {
c.JSON(http.StatusInternalServerError, gin.H{
"code": 500,
"message": "批量更新失败",
})
return
}
c.JSON(http.StatusOK, gin.H{
"code": 200,
"message": "批量更新成功",
})
}
type BatchDeleteSmsConfigsRequest struct {
IDs []uint `json:"ids"`
}
func handleBatchDeleteSmsConfigs(c *gin.Context) {
var req BatchDeleteSmsConfigsRequest
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(http.StatusBadRequest, gin.H{
"code": 400,
"message": "参数错误",
})
return
}
if len(req.IDs) == 0 {
c.JSON(http.StatusBadRequest, gin.H{
"code": 400,
"message": "请选择要删除的短信配置",
})
return
}
if err := database.DB.Where("id IN ?", req.IDs).Delete(&model.SmsConfig{}).Error; err != nil {
c.JSON(http.StatusInternalServerError, gin.H{
"code": 500,
"message": "批量删除失败",
})
return
}
c.JSON(http.StatusOK, gin.H{
"code": 200,
"message": "批量删除成功",
})
}
@@ -42,11 +42,11 @@ func AppCryptoMiddleware() gin.HandlerFunc {
shouldEncrypt := encryptType != crypto.EncryptTypeNone
fmt.Printf("[AppCrypto] AppKey: %s, EncryptType: %s, SecretKey: %s, ShouldEncrypt: %v\n",
appKey, app.EncryptType, app.SecretKey, shouldEncrypt)
fmt.Printf("[AppCrypto] AppKey: %s, EncryptType: %s, EncryptKey: %s, ShouldEncrypt: %v\n",
appKey, app.EncryptType, app.EncryptKey, shouldEncrypt)
c.Set("should_encrypt_response", shouldEncrypt)
c.Set("crypto_manager", crypto.NewCryptoManager(encryptType, app.SecretKey))
c.Set("crypto_manager", crypto.NewCryptoManager(encryptType, app.EncryptKey))
c.Next()
}
@@ -68,7 +68,7 @@ func getAppCryptoManager(appKey string) (*crypto.CryptoManager, error) {
encryptType = crypto.EncryptTypeNone
}
return crypto.NewCryptoManager(encryptType, app.SecretKey), nil
return crypto.NewCryptoManager(encryptType, app.EncryptKey), nil
}
func decryptRequest(c *gin.Context, appKey string) ([]byte, error) {
+81 -81
View File
@@ -1,7 +1,6 @@
package app
import (
"encoding/json"
"fmt"
"log"
"math/rand"
@@ -96,8 +95,8 @@ func handleAppSendEmailCode(c *gin.Context) {
}
if req.Purpose == "register" {
if !app.EnableEmailVerify {
response.Error(c, 400, "该应用未启用邮箱验证")
if !app.EnableRegisterVerify {
response.Error(c, 400, "该应用未启用注册验证")
return
}
} else if req.Purpose == "reset_password" {
@@ -203,6 +202,8 @@ func handleAppRegister(c *gin.Context) {
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"`
@@ -229,59 +230,60 @@ func handleAppRegister(c *gin.Context) {
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"}
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)
}
} 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
@@ -609,40 +611,38 @@ func handleAppLogin(c *gin.Context) {
}
if device.ID > 0 {
if appModel.MultiOpen && appModel.MaxInstances > 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) {
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,
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,
})
service.LogVerification(c, &appModel.ID, &user.ID, "login_failed", "登录失败: 多开数量已达上限 - "+req.Username, req.DeviceID, fmt.Errorf("多开数量已达上限"))
return
}
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
}
}
-1
View File
@@ -128,7 +128,6 @@ func handleExecuteDynamicCode(c *gin.Context) {
"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,
-1
View File
@@ -37,7 +37,6 @@ func handleAppGetInfo(c *gin.Context) {
"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,
@@ -713,7 +713,7 @@ func handleGetAppInfo(c *gin.Context) {
"encrypt_type": app.EncryptType,
"bind_type": app.BindType,
"max_devices": app.MaxDevices,
"multi_open": app.MultiOpen,
"multi_open_mode": app.MultiOpenMode,
"enable_trial": app.EnableTrial,
"trial_balance": app.TrialBalance,
"status": app.Status,
+45 -41
View File
@@ -67,43 +67,45 @@ func GetApplicationDisabledStatus(appID uint) bool {
type ApplicationWithStatus struct {
ID uint `json:"id"`
UserID uint `json:"user_id"`
Name string `json:"name"`
Description string `json:"description"`
AppKey string `json:"app_key"`
IconURL string `json:"icon_url"`
Status string `json:"status"`
BillingType string `json:"billing_type"`
LoginPolicy string `json:"login_policy"`
MaxDevices int `json:"max_devices"`
MultiOpen bool `json:"multi_open"`
MultiOpenMode string `json:"multi_open_mode"`
EnableTrial bool `json:"enable_trial"`
TrialDays int `json:"trial_days"`
EnableFreePeriod bool `json:"enable_free_period"`
FreePeriodType string `json:"free_period_type"`
FreePeriodStart string `json:"free_period_start"`
FreePeriodEnd string `json:"free_period_end"`
FreePeriodWeekdays string `json:"free_period_weekdays"`
FreePeriodStartTime string `json:"free_period_start_time"`
FreePeriodEndTime string `json:"free_period_end_time"`
HeartbeatInterval int `json:"heartbeat_interval"`
HeartbeatTimeout int `json:"heartbeat_timeout"`
MaxAttempts int `json:"max_attempts"`
LockDuration int `json:"lock_duration"`
DeductionMode string `json:"deduction_mode"`
DeductionType string `json:"deduction_type"`
DeductionInterval int `json:"deduction_interval"`
DeductionUnit string `json:"deduction_unit"`
DeductionAmount float64 `json:"deduction_amount"`
AllowRegister bool `json:"allow_register"`
RegisterMethods string `json:"register_methods"`
EnableEmailVerify bool `json:"enable_email_verify"`
RequireEmailVerify bool `json:"require_email_verify"`
EnablePasswordReset bool `json:"enable_password_reset"`
CreatedAt string `json:"created_at"`
UpdatedAt string `json:"updated_at"`
DisabledByPackage bool `json:"disabled_by_package"`
UserID uint `json:"user_id"`
Name string `json:"name"`
Description string `json:"description"`
AppKey string `json:"app_key"`
IconURL string `json:"icon_url"`
Status string `json:"status"`
BillingType string `json:"billing_type"`
LoginPolicy string `json:"login_policy"`
MaxDevices int `json:"max_devices"`
MultiOpenMode string `json:"multi_open_mode"`
EnableTrial bool `json:"enable_trial"`
TrialDays int `json:"trial_days"`
EnableFreePeriod bool `json:"enable_free_period"`
FreePeriodType string `json:"free_period_type"`
FreePeriodStart string `json:"free_period_start"`
FreePeriodEnd string `json:"free_period_end"`
FreePeriodWeekdays string `json:"free_period_weekdays"`
FreePeriodStartTime string `json:"free_period_start_time"`
FreePeriodEndTime string `json:"free_period_end_time"`
HeartbeatInterval int `json:"heartbeat_interval"`
HeartbeatTimeout int `json:"heartbeat_timeout"`
MaxAttempts int `json:"max_attempts"`
LockDuration int `json:"lock_duration"`
DeductionMode string `json:"deduction_mode"`
DeductionType string `json:"deduction_type"`
DeductionInterval int `json:"deduction_interval"`
DeductionUnit string `json:"deduction_unit"`
DeductionAmount float64 `json:"deduction_amount"`
AllowRegister bool `json:"allow_register"`
EnableLoginVerify bool `json:"enable_login_verify"`
EnableRegisterVerify bool `json:"enable_register_verify"`
VerifyMethod string `json:"verify_method"`
EnablePasswordReset bool `json:"enable_password_reset"`
PasswordResetMethod string `json:"password_reset_method"`
PasswordResetEmailID *uint `json:"password_reset_email_id"`
PasswordResetSmsID *uint `json:"password_reset_sms_id"`
CreatedAt string `json:"created_at"`
UpdatedAt string `json:"updated_at"`
DisabledByPackage bool `json:"disabled_by_package"`
}
func GetApplicationsWithDisabledStatus(userID uint) ([]ApplicationWithStatus, error) {
@@ -130,7 +132,6 @@ func GetApplicationsWithDisabledStatus(userID uint) ([]ApplicationWithStatus, er
BillingType: app.BillingType,
LoginPolicy: app.LoginPolicy,
MaxDevices: app.MaxDevices,
MultiOpen: app.MultiOpen,
MultiOpenMode: app.MultiOpenMode,
EnableTrial: app.EnableTrial,
TrialDays: app.TrialDays,
@@ -151,10 +152,13 @@ func GetApplicationsWithDisabledStatus(userID uint) ([]ApplicationWithStatus, er
DeductionUnit: app.DeductionUnit,
DeductionAmount: app.DeductionAmount,
AllowRegister: app.AllowRegister,
RegisterMethods: app.RegisterMethods,
EnableEmailVerify: app.EnableEmailVerify,
RequireEmailVerify: app.RequireEmailVerify,
EnableLoginVerify: app.EnableLoginVerify,
EnableRegisterVerify: app.EnableRegisterVerify,
VerifyMethod: app.VerifyMethod,
EnablePasswordReset: app.EnablePasswordReset,
PasswordResetMethod: app.PasswordResetMethod,
PasswordResetEmailID: app.PasswordResetEmailID,
PasswordResetSmsID: app.PasswordResetSmsID,
CreatedAt: app.CreatedAt.Format("2006-01-02T15:04:05Z07:00"),
UpdatedAt: app.UpdatedAt.Format("2006-01-02T15:04:05Z07:00"),
}