Initial commit: 网络验证平台
This commit is contained in:
@@ -0,0 +1,383 @@
|
||||
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,
|
||||
})
|
||||
}
|
||||
Reference in New Issue
Block a user