Files
verify/backend/internal/router/developer/profile.go
T
2026-04-27 17:22:56 +08:00

384 lines
9.1 KiB
Go

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,
})
}