fix: 订阅模式登录验证、永久会员类型区分、动态代码HTTP返回值修复、侧边栏滚动位置保持

- 修复订阅模式登录时错误检查余额的问题
- 区分无限余额和永久订阅两种永久会员类型
- 修复动态代码HTTP请求返回值在JS中无法正确访问的问题
- 添加侧边栏滚动位置保持功能
- 移除developer角色相关代码,统一使用admin
- 添加缺失的i18n翻译key
This commit is contained in:
2026-05-01 16:39:31 +08:00
parent c0edb32614
commit ea8ffb6c74
69 changed files with 554 additions and 369 deletions
+17 -6
View File
@@ -116,13 +116,24 @@ func handleAppHeartbeat(c *gin.Context) {
}
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)
if user.Balance == -1 {
log.Printf("[DEBUG] User %d is permanent member, skip deduction", user.ID)
} else if app.BillingType == "subscription" {
if user.ExpiryAt == nil || user.ExpiryAt.Before(now) {
log.Printf("[DEBUG] User %d subscription expired, ExpiryAt=%v", user.ID, user.ExpiryAt)
response.Error(c, 403, "订阅已过期")
return
}
log.Printf("[DEBUG] User %d subscription valid, skip balance deduction", user.ID)
} else {
log.Printf("[DEBUG] User %d has insufficient balance: %.2f < %.2f", user.ID, user.Balance, app.DeductionAmount)
response.Error(c, 403, "余额不足")
return
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
}
}
}
}
+18 -5
View File
@@ -456,11 +456,24 @@ func handleAppLogin(c *gin.Context) {
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
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 {
+7 -7
View File
@@ -549,18 +549,18 @@ func handleAppUploadVariableBinary(c *gin.Context) {
}
defer file.Close()
var developer model.User
if err := database.DB.First(&developer, app.UserID).Error; err != nil {
response.Error(c, 500, "获取开发者信息失败")
var adminUser model.User
if err := database.DB.First(&adminUser, app.UserID).Error; err != nil {
response.Error(c, 500, "获取管理员信息失败")
return
}
if developer.CurrentPackageID != nil {
if adminUser.CurrentPackageID != nil {
var permission model.PackagePermission
if err := database.DB.Where("package_id = ?", developer.CurrentPackageID).First(&permission).Error; err == nil {
if err := database.DB.Where("package_id = ?", adminUser.CurrentPackageID).First(&permission).Error; err == nil {
maxStorageBytes := int64(permission.MaxStorage) * 1024 * 1024
if developer.StorageUsed+header.Size > maxStorageBytes {
usedMB := float64(developer.StorageUsed) / 1024 / 1024
if adminUser.StorageUsed+header.Size > maxStorageBytes {
usedMB := float64(adminUser.StorageUsed) / 1024 / 1024
maxMB := float64(permission.MaxStorage)
response.Error(c, 403, fmt.Sprintf("存储空间不足,已使用 %.2f MB / %.2f MB", usedMB, maxMB))
return
+20 -4
View File
@@ -250,19 +250,35 @@ func handleExecuteDynamicCode(c *gin.Context) {
httpClient := httputil.NewHTTPClient(10 * time.Second)
httpObj := map[string]interface{}{
"get": func(url string, headers map[string]interface{}) *httputil.HTTPResponse {
"get": func(url string, headers map[string]interface{}) map[string]interface{} {
convertedHeaders := make(map[string]string)
for k, v := range headers {
convertedHeaders[k] = fmt.Sprintf("%v", v)
}
return httpClient.Get(url, convertedHeaders)
resp := httpClient.Get(url, convertedHeaders)
return map[string]interface{}{
"statusCode": resp.StatusCode,
"status": resp.Status,
"headers": resp.Headers,
"body": resp.Body,
"json": resp.JSON,
"error": resp.Error,
}
},
"post": func(url string, headers map[string]interface{}, body interface{}) *httputil.HTTPResponse {
"post": func(url string, headers map[string]interface{}, body interface{}) map[string]interface{} {
convertedHeaders := make(map[string]string)
for k, v := range headers {
convertedHeaders[k] = fmt.Sprintf("%v", v)
}
return httpClient.Post(url, convertedHeaders, body)
resp := httpClient.Post(url, convertedHeaders, body)
return map[string]interface{}{
"statusCode": resp.StatusCode,
"status": resp.Status,
"headers": resp.Headers,
"body": resp.Body,
"json": resp.JSON,
"error": resp.Error,
}
},
}
if err := vm.Set("http", httpObj); err != nil {
+8 -2
View File
@@ -91,8 +91,14 @@ func handleAppRecharge(c *gin.Context) {
user.IsTrialUser = false
if card.CardType.Value == -1 {
user.Balance = -1
user.ExpiryAt = nil
if card.CardType.RechargeType == "subscription" {
permanentExpiry := time.Date(9999, 12, 31, 23, 59, 59, 0, time.UTC)
user.ExpiryAt = &permanentExpiry
user.Balance = -1
} else {
user.Balance = -1
user.ExpiryAt = nil
}
} else {
switch card.CardType.RechargeType {
case "subscription":