384 lines
9.1 KiB
Go
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,
|
|
})
|
|
}
|