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
+673
View File
@@ -0,0 +1,673 @@
package admin
import (
"fmt"
"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"
)
type UserWithStatus struct {
model.AppUser
OnlineStatus string `json:"online_status"`
DeviceCount int `json:"device_count"`
}
func getUserOnlineStatus(user model.AppUser, heartbeatTimeout int) string {
if user.Status == "banned" {
return "banned"
}
if user.LastHeartbeatAt == nil {
return "offline"
}
offlineThreshold := time.Duration(heartbeatTimeout) * time.Second
if time.Since(*user.LastHeartbeatAt) > offlineThreshold {
return "offline"
}
return "online"
}
func SetupUserRoutes(r *gin.RouterGroup) {
appUsers := r.Group("/app-users")
{
appUsers.GET("", handleGetUsers)
appUsers.POST("", handleCreateUser)
appUsers.GET("/:id", handleGetUser)
appUsers.PUT("/:id", handleUpdateUser)
appUsers.DELETE("/:id", handleDeleteUser)
appUsers.GET("/:id/devices", handleGetUserDevices)
appUsers.DELETE("/:id/devices/:deviceId", handleUnbindDevice)
appUsers.PUT("/:id/expiry", handleUpdateExpiry)
appUsers.POST("/batch/status", handleBatchUpdateStatus)
appUsers.DELETE("/batch", handleBatchDelete)
}
}
func handleGetUsers(c *gin.Context) {
userID := c.GetUint("user_id")
log.Printf("[DEBUG] handleGetUsers called, userID: %d", userID)
var users []model.AppUser
var appHeartbeatTimeoutMap map[uint]int
applicationID := c.Query("application_id")
if applicationID != "" {
var appID uint
if _, err := fmt.Sscanf(applicationID, "%d", &appID); err != nil {
response.Error(c, 400, "应用ID格式错误")
return
}
var app model.Application
if err := database.DB.First(&app, appID).Error; err != nil {
response.Error(c, 404, "应用不存在")
return
}
appHeartbeatTimeoutMap = make(map[uint]int)
timeout := app.HeartbeatTimeout
if timeout == 0 {
timeout = 300
}
appHeartbeatTimeoutMap[app.ID] = timeout
if app.UserID == userID {
if err := database.DB.Preload("Application").Where("application_id = ?", appID).Find(&users).Error; err != nil {
response.Error(c, 500, "获取用户列表失败")
return
}
} else {
var agentApp model.AgentApplication
if err := database.DB.Where("application_id = ? AND agent_id = ? AND is_received = ?", appID, userID, true).First(&agentApp).Error; err != nil {
response.Error(c, 403, "无权限查看该应用的用户")
return
}
var cardUserIDs []uint
if err := database.DB.Model(&model.Card{}).
Where("creator_id = ? AND application_id = ? AND app_user_id IS NOT NULL", userID, appID).
Pluck("app_user_id", &cardUserIDs).Error; err != nil {
response.Error(c, 500, "获取用户列表失败")
return
}
if len(cardUserIDs) > 0 {
if err := database.DB.Preload("Application").Where("id IN ?", cardUserIDs).Find(&users).Error; err != nil {
response.Error(c, 500, "获取用户列表失败")
return
}
}
}
} else {
var ownApps []model.Application
if err := database.DB.Where("user_id = ?", userID).Find(&ownApps).Error; err != nil {
response.Error(c, 500, "获取应用列表失败")
return
}
var agentApps []model.AgentApplication
if err := database.DB.Where("agent_id = ? AND is_received = ?", userID, true).Find(&agentApps).Error; err != nil {
log.Printf("[DEBUG] Failed to get agent apps: %v", err)
response.Error(c, 500, "获取授权列表失败")
return
}
log.Printf("[DEBUG] Found %d agent apps for user %d", len(agentApps), userID)
appHeartbeatTimeoutMap = make(map[uint]int)
for _, app := range ownApps {
timeout := app.HeartbeatTimeout
if timeout == 0 {
timeout = 300
}
appHeartbeatTimeoutMap[app.ID] = timeout
var appUsers []model.AppUser
if err := database.DB.Preload("Application").Where("application_id = ?", app.ID).Find(&appUsers).Error; err != nil {
response.Error(c, 500, "获取用户列表失败")
return
}
users = append(users, appUsers...)
}
for _, agentApp := range agentApps {
var app model.Application
if err := database.DB.First(&app, agentApp.ApplicationID).Error; err != nil {
log.Printf("[DEBUG] Failed to get application %d: %v", agentApp.ApplicationID, err)
continue
}
log.Printf("[DEBUG] Processing agent app: ApplicationID=%d, AppName=%s", app.ID, app.Name)
timeout := app.HeartbeatTimeout
if timeout == 0 {
timeout = 300
}
appHeartbeatTimeoutMap[app.ID] = timeout
var cardUserIDs []uint
if err := database.DB.Model(&model.Card{}).
Where("creator_id = ? AND application_id = ? AND app_user_id IS NOT NULL", userID, app.ID).
Pluck("app_user_id", &cardUserIDs).Error; err != nil {
log.Printf("[DEBUG] Failed to get card user IDs for app %d: %v", app.ID, err)
continue
}
log.Printf("[DEBUG] Found %d card user IDs for app %d: %v", len(cardUserIDs), app.ID, cardUserIDs)
if len(cardUserIDs) > 0 {
var appUsers []model.AppUser
if err := database.DB.Preload("Application").Where("id IN ?", cardUserIDs).Find(&appUsers).Error; err != nil {
log.Printf("[DEBUG] Failed to get users for app %d: %v", app.ID, err)
continue
}
log.Printf("[DEBUG] Found %d users for app %d", len(appUsers), app.ID)
users = append(users, appUsers...)
}
}
}
totalCount := len(users)
onlineCount := 0
offlineCount := 0
bannedCount := 0
usersWithStatus := make([]UserWithStatus, 0, len(users))
for _, user := range users {
log.Printf("[DEBUG] User ID=%d, Username=%s, LastLoginAt=%v, LastHeartbeatAt=%v", user.ID, user.Username, user.LastLoginAt, user.LastHeartbeatAt)
heartbeatTimeout := 300
if applicationID != "" {
heartbeatTimeout = appHeartbeatTimeoutMap[user.ApplicationID]
} else if appHeartbeatTimeoutMap != nil {
heartbeatTimeout = appHeartbeatTimeoutMap[user.ApplicationID]
}
onlineStatus := getUserOnlineStatus(user, heartbeatTimeout)
switch onlineStatus {
case "online":
onlineCount++
case "offline":
offlineCount++
case "banned":
bannedCount++
}
var deviceCount int64
database.DB.Model(&model.UserDevice{}).Where("user_id = ?", user.ID).Count(&deviceCount)
log.Printf("[DEBUG] 用户 %s (ID=%d) 余额: %f", user.Username, user.ID, user.Balance)
usersWithStatus = append(usersWithStatus, UserWithStatus{
AppUser: user,
OnlineStatus: onlineStatus,
DeviceCount: int(deviceCount),
})
}
responseData := gin.H{
"users": usersWithStatus,
"total": totalCount,
"online_count": onlineCount,
"offline_count": offlineCount,
"banned_count": bannedCount,
}
log.Printf("[DEBUG] Response data: %+v", responseData)
response.Success(c, responseData)
}
func handleCreateUser(c *gin.Context) {
userID := c.GetUint("user_id")
var req struct {
Username string `json:"username"`
Email string `json:"email"`
Password string `json:"password"`
ApplicationID uint `json:"application_id"`
}
if err := c.ShouldBindJSON(&req); err != nil {
response.Error(c, 400, "参数错误")
return
}
if req.Username == "" {
response.Error(c, 400, "用户名不能为空")
return
}
if req.Password == "" {
response.Error(c, 400, "密码不能为空")
return
}
if req.ApplicationID == 0 {
response.Error(c, 400, "所属应用不能为空")
return
}
var app model.Application
if err := database.DB.First(&app, req.ApplicationID).Error; err != nil {
response.Error(c, 404, "应用不存在")
return
}
if app.UserID != userID {
var agentApp model.AgentApplication
if err := database.DB.Where("application_id = ? AND agent_id = ? AND is_received = ?", req.ApplicationID, userID, true).First(&agentApp).Error; err != nil {
response.Error(c, 403, "无权限在该应用下创建用户")
return
}
}
var existingUser model.AppUser
if err := database.DB.Where("username = ? AND application_id = ?", req.Username, app.ID).First(&existingUser).Error; err == nil {
response.Error(c, 400, "用户已存在")
return
}
user := model.AppUser{
Username: req.Username,
Email: req.Email,
Password: req.Password,
Avatar: "",
Status: "active",
ApplicationID: app.ID,
}
if err := database.DB.Create(&user).Error; err != nil {
response.Error(c, 500, "创建用户失败")
return
}
service.LogOperation(c, "create", "app_user", &user.ID, fmt.Sprintf("创建用户: %s (应用: %s)", user.Username, app.Name), nil)
response.Success(c, user)
}
func handleGetUser(c *gin.Context) {
userID := c.GetUint("user_id")
id := c.Param("id")
var user model.AppUser
if err := database.DB.Preload("Application").First(&user, id).Error; err != nil {
response.Error(c, 404, "用户不存在")
return
}
var app model.Application
if err := database.DB.First(&app, user.ApplicationID).Error; err != nil {
response.Error(c, 404, "应用不存在")
return
}
if app.UserID != userID {
var agentApp model.AgentApplication
if err := database.DB.Where("application_id = ? AND agent_id = ? AND is_received = ?", app.ID, userID, true).First(&agentApp).Error; err != nil {
response.Error(c, 403, "无权限查看该用户")
return
}
var card model.Card
if err := database.DB.Where("creator_id = ? AND app_user_id = ?", userID, user.ID).First(&card).Error; err != nil {
response.Error(c, 403, "无权限查看该用户")
return
}
}
response.Success(c, gin.H{
"user": user,
})
}
func handleUpdateUser(c *gin.Context) {
userID := c.GetUint("user_id")
id := c.Param("id")
var req struct {
Username string `json:"username"`
Email string `json:"email"`
Password string `json:"password"`
Status string `json:"status"`
ApplicationID uint `json:"application_id"`
}
if err := c.ShouldBindJSON(&req); err != nil {
response.Error(c, 400, "参数错误")
return
}
var user model.AppUser
if err := database.DB.First(&user, id).Error; err != nil {
response.Error(c, 404, "用户不存在")
return
}
var app model.Application
if err := database.DB.First(&app, user.ApplicationID).Error; err != nil {
response.Error(c, 404, "应用不存在")
return
}
if app.UserID != userID {
var agentApp model.AgentApplication
if err := database.DB.Where("application_id = ? AND agent_id = ? AND is_received = ?", app.ID, userID, true).First(&agentApp).Error; err != nil {
response.Error(c, 403, "无权限修改该用户")
return
}
var card model.Card
if err := database.DB.Where("creator_id = ? AND app_user_id = ?", userID, user.ID).First(&card).Error; err != nil {
response.Error(c, 403, "无权限修改该用户")
return
}
}
if req.Username != "" {
user.Username = req.Username
}
if req.Email != "" {
user.Email = req.Email
}
if req.Password != "" {
user.Password = req.Password
}
if req.Status != "" {
user.Status = req.Status
}
if req.ApplicationID != 0 {
var newApp model.Application
if err := database.DB.First(&newApp, req.ApplicationID).Error; err != nil {
response.Error(c, 404, "目标应用不存在")
return
}
if newApp.UserID != userID {
var agentApp model.AgentApplication
if err := database.DB.Where("application_id = ? AND agent_id = ? AND is_received = ?", req.ApplicationID, userID, true).First(&agentApp).Error; err != nil {
response.Error(c, 403, "无权限将用户转移到该应用")
return
}
}
user.ApplicationID = req.ApplicationID
}
if err := database.DB.Save(&user).Error; err != nil {
response.Error(c, 500, "更新用户失败")
return
}
service.LogOperation(c, "update", "app_user", &user.ID, fmt.Sprintf("更新用户: %s", user.Username), nil)
response.Success(c, user)
}
func handleDeleteUser(c *gin.Context) {
userID := c.GetUint("user_id")
id := c.Param("id")
var user model.AppUser
if err := database.DB.First(&user, id).Error; err != nil {
response.Error(c, 404, "用户不存在")
return
}
var app model.Application
if err := database.DB.First(&app, user.ApplicationID).Error; err != nil {
response.Error(c, 404, "应用不存在")
return
}
if app.UserID != userID {
var agentApp model.AgentApplication
if err := database.DB.Where("application_id = ? AND agent_id = ? AND is_received = ?", app.ID, userID, true).First(&agentApp).Error; err != nil {
response.Error(c, 403, "无权限删除该用户")
return
}
var card model.Card
if err := database.DB.Where("creator_id = ? AND app_user_id = ?", userID, user.ID).First(&card).Error; err != nil {
response.Error(c, 403, "无权限删除该用户")
return
}
}
if err := database.DB.Delete(&user).Error; err != nil {
response.Error(c, 500, "删除用户失败")
return
}
service.LogOperation(c, "delete", "app_user", &user.ID, fmt.Sprintf("删除用户: %s (应用: %s)", user.Username, app.Name), nil)
response.Success(c, nil)
}
func handleGetUserDevices(c *gin.Context) {
userID := c.GetUint("user_id")
id := c.Param("id")
var user model.AppUser
if err := database.DB.First(&user, id).Error; err != nil {
response.Error(c, 404, "用户不存在")
return
}
var app model.Application
if err := database.DB.First(&app, user.ApplicationID).Error; err != nil {
response.Error(c, 404, "应用不存在")
return
}
if app.UserID != userID {
var agentApp model.AgentApplication
if err := database.DB.Where("application_id = ? AND agent_id = ? AND is_received = ?", app.ID, userID, true).First(&agentApp).Error; err != nil {
response.Error(c, 403, "无权限查看该用户设备")
return
}
}
var devices []model.UserDevice
if err := database.DB.Where("user_id = ? AND application_id = ?", user.ID, app.ID).Find(&devices).Error; err != nil {
response.Error(c, 500, "获取设备列表失败")
return
}
response.Success(c, devices)
}
func handleUpdateExpiry(c *gin.Context) {
userID := c.GetUint("user_id")
id := c.Param("id")
var req struct {
Amount float64 `json:"amount"`
Type string `json:"type"`
Field string `json:"field"`
}
if err := c.ShouldBindJSON(&req); err != nil {
response.Error(c, 400, "参数错误")
return
}
var appUser model.AppUser
if err := database.DB.First(&appUser, id).Error; err != nil {
response.Error(c, 404, "用户不存在")
return
}
var app model.Application
if err := database.DB.First(&app, appUser.ApplicationID).Error; err != nil {
response.Error(c, 404, "应用不存在")
return
}
if app.UserID != userID {
var agentApp model.AgentApplication
if err := database.DB.Where("application_id = ? AND agent_id = ? AND is_received = ?", app.ID, userID, true).First(&agentApp).Error; err != nil {
response.Error(c, 403, "无权限修改该用户")
return
}
var card model.Card
if err := database.DB.Where("creator_id = ? AND app_user_id = ?", userID, appUser.ID).First(&card).Error; err != nil {
response.Error(c, 403, "无权限修改该用户")
return
}
}
if req.Amount == 0 {
response.Error(c, 400, "数值不能为0")
return
}
if appUser.Balance == -1 {
response.Error(c, 400, "该用户为永久会员,无需操作")
return
}
now := time.Now()
if app.BillingType == "subscription" || req.Field == "days" {
if req.Type == "recharge" {
var baseTime time.Time
if appUser.ExpiryAt != nil && appUser.ExpiryAt.After(now) {
baseTime = *appUser.ExpiryAt
} else {
baseTime = now
}
duration := time.Duration(req.Amount) * 24 * time.Hour
newExpiry := baseTime.Add(duration)
appUser.ExpiryAt = &newExpiry
} else if req.Type == "deduct" {
if appUser.ExpiryAt == nil || appUser.ExpiryAt.Before(now) {
response.Error(c, 400, "用户订阅已过期")
return
}
duration := time.Duration(req.Amount) * 24 * time.Hour
newExpiry := appUser.ExpiryAt.Add(-duration)
if newExpiry.Before(now) {
newExpiry = now
}
appUser.ExpiryAt = &newExpiry
} else {
response.Error(c, 400, "操作类型错误")
return
}
} else {
if req.Type == "recharge" {
appUser.Balance += req.Amount
} else if req.Type == "deduct" {
if appUser.Balance < req.Amount {
response.Error(c, 400, "余额不足")
return
}
appUser.Balance -= req.Amount
} else {
response.Error(c, 400, "操作类型错误")
return
}
}
if err := database.DB.Save(&appUser).Error; err != nil {
response.Error(c, 500, "更新失败")
return
}
response.Success(c, appUser)
}
func handleBatchUpdateStatus(c *gin.Context) {
userID := c.GetUint("user_id")
var req struct {
UserIDs []uint `json:"user_ids"`
Status string `json:"status"`
}
if err := c.ShouldBindJSON(&req); err != nil {
response.Error(c, 400, "参数错误")
return
}
var users []model.AppUser
if err := database.DB.Where("id IN ?", req.UserIDs).Find(&users).Error; err != nil {
response.Error(c, 500, "获取用户列表失败")
return
}
var validUserIDs []uint
for _, user := range users {
var app model.Application
if err := database.DB.First(&app, user.ApplicationID).Error; err != nil {
continue
}
if app.UserID == userID {
validUserIDs = append(validUserIDs, user.ID)
} else {
var agentApp model.AgentApplication
if err := database.DB.Where("application_id = ? AND agent_id = ? AND is_received = ?", app.ID, userID, true).First(&agentApp).Error; err == nil {
var card model.Card
if err := database.DB.Where("creator_id = ? AND app_user_id = ?", userID, user.ID).First(&card).Error; err == nil {
validUserIDs = append(validUserIDs, user.ID)
}
}
}
}
if len(validUserIDs) > 0 {
if err := database.DB.Model(&model.AppUser{}).Where("id IN ?", validUserIDs).Update("status", req.Status).Error; err != nil {
response.Error(c, 500, "批量更新状态失败")
return
}
}
response.Success(c, nil)
}
func handleBatchDelete(c *gin.Context) {
userID := c.GetUint("user_id")
var req struct {
UserIDs []uint `json:"user_ids"`
}
if err := c.ShouldBindJSON(&req); err != nil {
response.Error(c, 400, "参数错误")
return
}
var users []model.AppUser
if err := database.DB.Where("id IN ?", req.UserIDs).Find(&users).Error; err != nil {
response.Error(c, 500, "获取用户列表失败")
return
}
var validUserIDs []uint
for _, user := range users {
var app model.Application
if err := database.DB.First(&app, user.ApplicationID).Error; err != nil {
continue
}
if app.UserID == userID {
validUserIDs = append(validUserIDs, user.ID)
} else {
var agentApp model.AgentApplication
if err := database.DB.Where("application_id = ? AND agent_id = ? AND is_received = ?", app.ID, userID, true).First(&agentApp).Error; err == nil {
var card model.Card
if err := database.DB.Where("creator_id = ? AND app_user_id = ?", userID, user.ID).First(&card).Error; err == nil {
validUserIDs = append(validUserIDs, user.ID)
}
}
}
}
if len(validUserIDs) > 0 {
if err := database.DB.Where("id IN ?", validUserIDs).Delete(&model.AppUser{}).Error; err != nil {
response.Error(c, 500, "批量删除失败")
return
}
}
response.Success(c, nil)
}