package developer import ( "crypto/rand" "encoding/hex" "fmt" "io" "log" "os" "path/filepath" "strings" "time" "verification-platform-backend/internal/database" "verification-platform-backend/internal/model" "verification-platform-backend/pkg/response" "github.com/gin-gonic/gin" "golang.org/x/crypto/bcrypt" ) func SetupProfileRoutes(r *gin.RouterGroup) { profile := r.Group("/profile") { profile.GET("", handleGetProfile) profile.PUT("", handleUpdateProfile) profile.PUT("/password", handleChangePassword) profile.POST("/avatar", handleUploadAvatar) profile.POST("/api-token", handleGenerateApiToken) } } func handleGetProfile(c *gin.Context) { userID := c.GetUint("user_id") var user model.User if err := database.DB.First(&user, userID).Error; err != nil { response.Error(c, 404, "用户不存在") return } profile := struct { ID uint `json:"id"` Username string `json:"username"` Email string `json:"email"` Phone string `json:"phone"` Avatar string `json:"avatar"` Role string `json:"role"` Status string `json:"status"` ApiToken string `json:"api_token"` CreatedAt time.Time `json:"created_at"` LastLoginAt *time.Time `json:"last_login_at"` }{ ID: user.ID, Username: user.Username, Email: "", Phone: "", Avatar: user.Avatar, Role: user.Role, Status: user.Status, ApiToken: user.ApiToken, CreatedAt: user.CreatedAt, LastLoginAt: user.LastLoginAt, } if user.Email != nil { profile.Email = *user.Email } subscription := getSubscriptionInfo(&user) transactions := getRecentTransactions(userID) response.Success(c, gin.H{ "user": profile, "subscription": subscription, "transactions": transactions, }) } func getSubscriptionInfo(user *model.User) gin.H { var pkg model.Package var packagePermission model.PackagePermission defaultQuota := 10000 defaultStorage := int64(100 * 1024 * 1024) if user.CurrentPackageID != nil { if err := database.DB.First(&pkg, *user.CurrentPackageID).Error; err == nil { database.DB.Where("package_id = ?", pkg.ID).First(&packagePermission) } } planName := "基础版" if pkg.ID > 0 { planName = pkg.Name } apiQuota := defaultQuota if packagePermission.MaxApiCalls > 0 { apiQuota = packagePermission.MaxApiCalls } storageQuota := defaultStorage if packagePermission.MaxStorage > 0 { storageQuota = int64(packagePermission.MaxStorage) * 1024 * 1024 } var expireDate string if pkg.Period == "monthly" { expireDate = time.Now().AddDate(0, 1, 0).Format("2006-01-02") } else if pkg.Period == "yearly" { expireDate = time.Now().AddDate(1, 0, 0).Format("2006-01-02") } else { expireDate = "永久有效" } return gin.H{ "plan": planName, "status": "active", "expire_date": expireDate, "api_quota": apiQuota, "api_used": user.ApiCallsUsed, "storage_quota": storageQuota, "storage_used": user.StorageUsed, } } func getRecentTransactions(userID uint) []gin.H { var orders []model.Order database.DB.Where("user_id = ? AND status = ?", userID, "paid"). Order("created_at DESC"). Limit(5). Find(&orders) transactions := make([]gin.H, 0, len(orders)) for _, order := range orders { txType := "consume" if order.OrderType == "user_recharge" || order.OrderType == "card_recharge" { txType = "recharge" } else if order.OrderType == "refund" { txType = "refund" } transactions = append(transactions, gin.H{ "id": order.ID, "type": txType, "amount": order.Amount, "description": order.Title, "created_at": order.CreatedAt, }) } return transactions } type UpdateProfileRequest struct { Username string `json:"username"` Email string `json:"email"` Phone string `json:"phone"` } func handleUpdateProfile(c *gin.Context) { userID := c.GetUint("user_id") var req UpdateProfileRequest if err := c.ShouldBindJSON(&req); err != nil { response.Error(c, 400, "请求参数错误") return } if req.Username == "" { response.Error(c, 400, "用户名不能为空") return } if req.Email == "" { response.Error(c, 400, "邮箱不能为空") return } var user model.User if err := database.DB.First(&user, userID).Error; err != nil { response.Error(c, 404, "用户不存在") return } var existingUser model.User if err := database.DB.Where("username = ? AND id != ?", req.Username, userID).First(&existingUser).Error; err == nil { response.Error(c, 400, "用户名已被使用") return } if err := database.DB.Where("email = ? AND id != ?", req.Email, userID).First(&existingUser).Error; err == nil { response.Error(c, 400, "邮箱已被使用") return } user.Username = req.Username email := req.Email user.Email = &email if err := database.DB.Save(&user).Error; err != nil { log.Printf("更新用户信息失败: %v", err) response.Error(c, 500, "更新用户信息失败") return } response.Success(c, gin.H{ "user": gin.H{ "id": user.ID, "username": user.Username, "email": user.Email, "avatar": user.Avatar, "role": user.Role, "status": user.Status, }, }) } type ChangePasswordRequest struct { CurrentPassword string `json:"current_password"` NewPassword string `json:"new_password"` } func handleChangePassword(c *gin.Context) { userID := c.GetUint("user_id") var req ChangePasswordRequest if err := c.ShouldBindJSON(&req); err != nil { response.Error(c, 400, "请求参数错误") return } if req.CurrentPassword == "" { response.Error(c, 400, "请输入当前密码") return } if req.NewPassword == "" { response.Error(c, 400, "请输入新密码") return } if len(req.NewPassword) < 6 { response.Error(c, 400, "密码长度至少6位") return } var user model.User if err := database.DB.First(&user, userID).Error; err != nil { response.Error(c, 404, "用户不存在") return } if err := bcrypt.CompareHashAndPassword([]byte(user.Password), []byte(req.CurrentPassword)); err != nil { response.Error(c, 400, "当前密码错误") return } hashedPassword, err := bcrypt.GenerateFromPassword([]byte(req.NewPassword), bcrypt.DefaultCost) if err != nil { log.Printf("密码加密失败: %v", err) response.Error(c, 500, "密码加密失败") return } user.Password = string(hashedPassword) if err := database.DB.Save(&user).Error; err != nil { log.Printf("更新密码失败: %v", err) response.Error(c, 500, "更新密码失败") return } response.Success(c, gin.H{"message": "密码修改成功"}) } func handleUploadAvatar(c *gin.Context) { userID := c.GetUint("user_id") file, header, err := c.Request.FormFile("avatar") if err != nil { response.Error(c, 400, "请选择要上传的文件") return } defer file.Close() ext := strings.ToLower(filepath.Ext(header.Filename)) allowedExts := map[string]bool{ ".jpg": true, ".jpeg": true, ".png": true, ".gif": true, ".webp": true, } if !allowedExts[ext] { response.Error(c, 400, "不支持的文件格式,仅支持 JPG、PNG、GIF、WEBP") return } const maxSize = 2 * 1024 * 1024 if header.Size > maxSize { response.Error(c, 400, "文件大小不能超过2MB") return } uploadDir := "uploads/avatars" if err := os.MkdirAll(uploadDir, 0755); err != nil { log.Printf("创建上传目录失败: %v", err) response.Error(c, 500, "创建上传目录失败") return } filename := fmt.Sprintf("%d_%d%s", userID, time.Now().UnixNano(), ext) filePath := filepath.Join(uploadDir, filename) dst, err := os.Create(filePath) if err != nil { log.Printf("创建文件失败: %v", err) response.Error(c, 500, "创建文件失败") return } defer dst.Close() if _, err := io.Copy(dst, file); err != nil { log.Printf("保存文件失败: %v", err) response.Error(c, 500, "保存文件失败") return } avatarURL := "/uploads/avatars/" + filename var user model.User if err := database.DB.First(&user, userID).Error; err != nil { response.Error(c, 404, "用户不存在") return } if user.Avatar != "" && strings.HasPrefix(user.Avatar, "/uploads/avatars/") { oldPath := "." + user.Avatar if _, err := os.Stat(oldPath); err == nil { os.Remove(oldPath) } } user.Avatar = avatarURL if err := database.DB.Save(&user).Error; err != nil { log.Printf("更新头像失败: %v", err) response.Error(c, 500, "更新头像失败") return } response.Success(c, gin.H{"avatar": avatarURL}) } func handleGenerateApiToken(c *gin.Context) { userID := c.GetUint("user_id") var user model.User if err := database.DB.First(&user, userID).Error; err != nil { response.Error(c, 404, "用户不存在") return } bytes := make([]byte, 32) if _, err := rand.Read(bytes); err != nil { log.Printf("生成API Token失败: %v", err) response.Error(c, 500, "生成API Token失败") return } apiToken := hex.EncodeToString(bytes) user.ApiToken = apiToken if err := database.DB.Save(&user).Error; err != nil { log.Printf("保存API Token失败: %v", err) response.Error(c, 500, "保存API Token失败") return } response.Success(c, gin.H{ "api_token": apiToken, }) }