3a898e8aa0
后端修复: - 验证码存储与校验机制 - 订单创建事务+库存扣减 - GetStats字段名错误 - VerifyEmail改为POST - 供应商更新字段白名单 - 文件删除安全检查 - 抽奖安全随机数 - 用户管理CRUD - 订单取消/确认收货 - 工单回复 - Toggle返回新数据 - 移除死代码 前端修复: - 404兜底路由 - 401软跳转 - API层统一 - 面包屑补充banners - 国际化完善 - 购物车并行删除 - 退出清理购物车 - 供应商Dashboard数据 - 工单详情页 - 订单取消/确认收货
434 lines
12 KiB
Go
434 lines
12 KiB
Go
package handlers
|
|
|
|
import (
|
|
"net/http"
|
|
"strconv"
|
|
"sync"
|
|
"time"
|
|
|
|
"sale/internal/models"
|
|
"sale/internal/schemas"
|
|
"sale/internal/utils"
|
|
|
|
"github.com/gin-gonic/gin"
|
|
)
|
|
|
|
type AuthHandler struct{}
|
|
|
|
func NewAuthHandler() *AuthHandler {
|
|
return &AuthHandler{}
|
|
}
|
|
|
|
var (
|
|
verifyCodes = make(map[string]verifyCodeEntry)
|
|
verifyCodesMux sync.Mutex
|
|
)
|
|
|
|
type verifyCodeEntry struct {
|
|
Code string
|
|
ExpiresAt time.Time
|
|
}
|
|
|
|
func storeVerifyCode(email, code string) {
|
|
verifyCodesMux.Lock()
|
|
defer verifyCodesMux.Unlock()
|
|
verifyCodes[email] = verifyCodeEntry{
|
|
Code: code,
|
|
ExpiresAt: time.Now().Add(30 * time.Minute),
|
|
}
|
|
}
|
|
|
|
func checkVerifyCode(email, code string) bool {
|
|
verifyCodesMux.Lock()
|
|
defer verifyCodesMux.Unlock()
|
|
entry, ok := verifyCodes[email]
|
|
if !ok {
|
|
return false
|
|
}
|
|
if time.Now().After(entry.ExpiresAt) {
|
|
delete(verifyCodes, email)
|
|
return false
|
|
}
|
|
if entry.Code != code {
|
|
return false
|
|
}
|
|
delete(verifyCodes, email)
|
|
return true
|
|
}
|
|
|
|
func (h *AuthHandler) Register(c *gin.Context) {
|
|
var req schemas.RegisterRequest
|
|
if err := c.ShouldBindJSON(&req); err != nil {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
|
return
|
|
}
|
|
|
|
var existingUser models.User
|
|
if err := utils.DB.Where("email = ?", req.Email).First(&existingUser).Error; err == nil {
|
|
c.JSON(http.StatusConflict, gin.H{"error": "Email already registered"})
|
|
return
|
|
}
|
|
|
|
if err := utils.DB.Where("username = ?", req.Username).First(&existingUser).Error; err == nil {
|
|
c.JSON(http.StatusConflict, gin.H{"error": "Username already taken"})
|
|
return
|
|
}
|
|
|
|
hashedPassword, err := utils.HashPassword(req.Password)
|
|
if err != nil {
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to hash password"})
|
|
return
|
|
}
|
|
|
|
user := models.User{
|
|
Username: req.Username,
|
|
Email: req.Email,
|
|
PasswordHash: hashedPassword,
|
|
Role: "user",
|
|
InviteCode: utils.GenerateInviteCode(),
|
|
IsActive: true,
|
|
}
|
|
|
|
if req.InviteCode != "" {
|
|
var referrer models.User
|
|
if err := utils.DB.Where("invite_code = ?", req.InviteCode).First(&referrer).Error; err == nil {
|
|
user.ReferredBy = &referrer.ID
|
|
}
|
|
}
|
|
|
|
if err := utils.DB.Create(&user).Error; err != nil {
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to create user"})
|
|
return
|
|
}
|
|
|
|
if user.ReferredBy != nil {
|
|
utils.DB.Model(&models.User{}).Where("id = ?", *user.ReferredBy).
|
|
UpdateColumn("purchase_credits", utils.DB.Raw("purchase_credits + 1"))
|
|
}
|
|
|
|
verifyCode := utils.GenerateVerifyCode()
|
|
storeVerifyCode(req.Email, verifyCode)
|
|
utils.SendVerifyEmail(req.Email, verifyCode)
|
|
|
|
token, _ := utils.GenerateToken(user.ID, user.Role)
|
|
c.JSON(http.StatusCreated, gin.H{
|
|
"token": token,
|
|
"user": schemas.UserResponse{
|
|
ID: user.ID,
|
|
Username: user.Username,
|
|
Email: user.Email,
|
|
Role: user.Role,
|
|
PurchaseCredits: user.PurchaseCredits,
|
|
InviteCode: user.InviteCode,
|
|
EmailVerified: user.EmailVerified,
|
|
},
|
|
})
|
|
}
|
|
|
|
func (h *AuthHandler) Login(c *gin.Context) {
|
|
var req schemas.LoginRequest
|
|
if err := c.ShouldBindJSON(&req); err != nil {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
|
return
|
|
}
|
|
|
|
var user models.User
|
|
if err := utils.DB.Where("email = ?", req.Email).First(&user).Error; err != nil {
|
|
c.JSON(http.StatusUnauthorized, gin.H{"error": "Invalid email or password"})
|
|
return
|
|
}
|
|
|
|
if !utils.CheckPassword(req.Password, user.PasswordHash) {
|
|
c.JSON(http.StatusUnauthorized, gin.H{"error": "Invalid email or password"})
|
|
return
|
|
}
|
|
|
|
if !user.IsActive {
|
|
c.JSON(http.StatusForbidden, gin.H{"error": "Account is disabled"})
|
|
return
|
|
}
|
|
|
|
token, _ := utils.GenerateToken(user.ID, user.Role)
|
|
c.JSON(http.StatusOK, gin.H{
|
|
"token": token,
|
|
"user": schemas.UserResponse{
|
|
ID: user.ID,
|
|
Username: user.Username,
|
|
Email: user.Email,
|
|
Role: user.Role,
|
|
PurchaseCredits: user.PurchaseCredits,
|
|
InviteCode: user.InviteCode,
|
|
EmailVerified: user.EmailVerified,
|
|
},
|
|
})
|
|
}
|
|
|
|
func (h *AuthHandler) ForgotPassword(c *gin.Context) {
|
|
var req schemas.ForgotPasswordRequest
|
|
if err := c.ShouldBindJSON(&req); err != nil {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
|
return
|
|
}
|
|
|
|
var user models.User
|
|
if err := utils.DB.Where("email = ?", req.Email).First(&user).Error; err != nil {
|
|
c.JSON(http.StatusOK, gin.H{"message": "If the email exists, a verification code has been sent"})
|
|
return
|
|
}
|
|
|
|
code := utils.GenerateVerifyCode()
|
|
storeVerifyCode(req.Email, code)
|
|
utils.SendResetPasswordEmail(req.Email, code)
|
|
|
|
c.JSON(http.StatusOK, gin.H{"message": "If the email exists, a verification code has been sent"})
|
|
}
|
|
|
|
func (h *AuthHandler) ResetPassword(c *gin.Context) {
|
|
var req schemas.ResetPasswordRequest
|
|
if err := c.ShouldBindJSON(&req); err != nil {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
|
return
|
|
}
|
|
|
|
if !checkVerifyCode(req.Email, req.Code) {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid or expired verification code"})
|
|
return
|
|
}
|
|
|
|
var user models.User
|
|
if err := utils.DB.Where("email = ?", req.Email).First(&user).Error; err != nil {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid email"})
|
|
return
|
|
}
|
|
|
|
hashedPassword, err := utils.HashPassword(req.Password)
|
|
if err != nil {
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to hash password"})
|
|
return
|
|
}
|
|
|
|
utils.DB.Model(&user).Update("password_hash", hashedPassword)
|
|
c.JSON(http.StatusOK, gin.H{"message": "Password reset successfully"})
|
|
}
|
|
|
|
func (h *AuthHandler) VerifyEmail(c *gin.Context) {
|
|
var req schemas.VerifyEmailRequest
|
|
if err := c.ShouldBindJSON(&req); err != nil {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
|
return
|
|
}
|
|
|
|
if !checkVerifyCode(req.Email, req.Code) {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid or expired verification code"})
|
|
return
|
|
}
|
|
|
|
var user models.User
|
|
if err := utils.DB.Where("email = ?", req.Email).First(&user).Error; err != nil {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": "User not found"})
|
|
return
|
|
}
|
|
|
|
utils.DB.Model(&user).Update("email_verified", true)
|
|
c.JSON(http.StatusOK, gin.H{"message": "Email verified successfully"})
|
|
}
|
|
|
|
func (h *AuthHandler) GetProfile(c *gin.Context) {
|
|
userID := c.GetUint("user_id")
|
|
var user models.User
|
|
if err := utils.DB.First(&user, userID).Error; err != nil {
|
|
c.JSON(http.StatusNotFound, gin.H{"error": "User not found"})
|
|
return
|
|
}
|
|
|
|
c.JSON(http.StatusOK, schemas.UserResponse{
|
|
ID: user.ID,
|
|
Username: user.Username,
|
|
Email: user.Email,
|
|
Role: user.Role,
|
|
PurchaseCredits: user.PurchaseCredits,
|
|
InviteCode: user.InviteCode,
|
|
EmailVerified: user.EmailVerified,
|
|
})
|
|
}
|
|
|
|
func (h *AuthHandler) UpdateProfile(c *gin.Context) {
|
|
userID := c.GetUint("user_id")
|
|
var user models.User
|
|
if err := utils.DB.First(&user, userID).Error; err != nil {
|
|
c.JSON(http.StatusNotFound, gin.H{"error": "User not found"})
|
|
return
|
|
}
|
|
|
|
var updateData map[string]interface{}
|
|
if err := c.ShouldBindJSON(&updateData); err != nil {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
|
return
|
|
}
|
|
|
|
if username, ok := updateData["username"].(string); ok && username != "" {
|
|
user.Username = username
|
|
}
|
|
|
|
utils.DB.Save(&user)
|
|
c.JSON(http.StatusOK, gin.H{"message": "Profile updated successfully"})
|
|
}
|
|
|
|
func (h *AuthHandler) ChangePassword(c *gin.Context) {
|
|
userID := c.GetUint("user_id")
|
|
var req schemas.ChangePasswordRequest
|
|
if err := c.ShouldBindJSON(&req); err != nil {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
|
return
|
|
}
|
|
|
|
var user models.User
|
|
if err := utils.DB.First(&user, userID).Error; err != nil {
|
|
c.JSON(http.StatusNotFound, gin.H{"error": "User not found"})
|
|
return
|
|
}
|
|
|
|
if !utils.CheckPassword(req.OldPassword, user.PasswordHash) {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": "Old password is incorrect"})
|
|
return
|
|
}
|
|
|
|
hashedPassword, _ := utils.HashPassword(req.NewPassword)
|
|
utils.DB.Model(&user).Update("password_hash", hashedPassword)
|
|
c.JSON(http.StatusOK, gin.H{"message": "Password changed successfully"})
|
|
}
|
|
|
|
func (h *AuthHandler) SendVerifyCode(c *gin.Context) {
|
|
userID := c.GetUint("user_id")
|
|
var user models.User
|
|
if err := utils.DB.First(&user, userID).Error; err != nil {
|
|
c.JSON(http.StatusNotFound, gin.H{"error": "User not found"})
|
|
return
|
|
}
|
|
|
|
if user.EmailVerified {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": "Email already verified"})
|
|
return
|
|
}
|
|
|
|
code := utils.GenerateVerifyCode()
|
|
storeVerifyCode(user.Email, code)
|
|
utils.SendVerifyEmail(user.Email, code)
|
|
|
|
c.JSON(http.StatusOK, gin.H{"message": "Verification code sent", "expires_at": time.Now().Add(30 * time.Minute)})
|
|
}
|
|
|
|
func (h *AuthHandler) CheckInstalled(c *gin.Context) {
|
|
var count int64
|
|
utils.DB.Model(&models.User{}).Where("role = ?", "admin").Count(&count)
|
|
c.JSON(http.StatusOK, gin.H{"installed": count > 0})
|
|
}
|
|
|
|
func (h *AuthHandler) Install(c *gin.Context) {
|
|
var count int64
|
|
utils.DB.Model(&models.User{}).Where("role = ?", "admin").Count(&count)
|
|
if count > 0 {
|
|
c.JSON(http.StatusForbidden, gin.H{"error": "System already installed"})
|
|
return
|
|
}
|
|
|
|
var req schemas.InstallRequest
|
|
if err := c.ShouldBindJSON(&req); err != nil {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
|
return
|
|
}
|
|
|
|
hashedPassword, err := utils.HashPassword(req.Password)
|
|
if err != nil {
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to hash password"})
|
|
return
|
|
}
|
|
|
|
admin := models.User{
|
|
Username: req.Username,
|
|
Email: req.Email,
|
|
PasswordHash: hashedPassword,
|
|
Role: "admin",
|
|
InviteCode: utils.GenerateInviteCode(),
|
|
IsActive: true,
|
|
}
|
|
|
|
if err := utils.DB.Create(&admin).Error; err != nil {
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to create admin user"})
|
|
return
|
|
}
|
|
|
|
token, _ := utils.GenerateToken(admin.ID, admin.Role)
|
|
c.JSON(http.StatusCreated, gin.H{
|
|
"token": token,
|
|
"user": schemas.UserResponse{
|
|
ID: admin.ID,
|
|
Username: admin.Username,
|
|
Email: admin.Email,
|
|
Role: admin.Role,
|
|
PurchaseCredits: admin.PurchaseCredits,
|
|
InviteCode: admin.InviteCode,
|
|
EmailVerified: admin.EmailVerified,
|
|
},
|
|
})
|
|
}
|
|
|
|
func (h *AuthHandler) AdminGetUsers(c *gin.Context) {
|
|
var users []models.User
|
|
utils.DB.Select("id, username, email, role, purchase_credits, is_active, email_verified, created_at").Find(&users)
|
|
c.JSON(http.StatusOK, gin.H{"data": users})
|
|
}
|
|
|
|
func (h *AuthHandler) AdminUpdateUser(c *gin.Context) {
|
|
id, _ := strconv.Atoi(c.Param("id"))
|
|
var user models.User
|
|
if err := utils.DB.First(&user, id).Error; err != nil {
|
|
c.JSON(http.StatusNotFound, gin.H{"error": "User not found"})
|
|
return
|
|
}
|
|
|
|
var req struct {
|
|
Username *string `json:"username"`
|
|
Email *string `json:"email"`
|
|
Role *string `json:"role"`
|
|
IsActive *bool `json:"is_active"`
|
|
}
|
|
if err := c.ShouldBindJSON(&req); err != nil {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
|
return
|
|
}
|
|
|
|
updates := make(map[string]interface{})
|
|
if req.Username != nil {
|
|
updates["username"] = *req.Username
|
|
}
|
|
if req.Email != nil {
|
|
updates["email"] = *req.Email
|
|
}
|
|
if req.Role != nil {
|
|
updates["role"] = *req.Role
|
|
}
|
|
if req.IsActive != nil {
|
|
updates["is_active"] = *req.IsActive
|
|
}
|
|
|
|
utils.DB.Model(&user).Updates(updates)
|
|
utils.DB.First(&user, id)
|
|
c.JSON(http.StatusOK, gin.H{"data": user})
|
|
}
|
|
|
|
func (h *AuthHandler) AdminDeleteUser(c *gin.Context) {
|
|
id, _ := strconv.Atoi(c.Param("id"))
|
|
var user models.User
|
|
if err := utils.DB.First(&user, id).Error; err != nil {
|
|
c.JSON(http.StatusNotFound, gin.H{"error": "User not found"})
|
|
return
|
|
}
|
|
if user.Role == "admin" {
|
|
c.JSON(http.StatusForbidden, gin.H{"error": "Cannot delete admin user"})
|
|
return
|
|
}
|
|
utils.DB.Delete(&user)
|
|
c.JSON(http.StatusOK, gin.H{"message": "User deleted successfully"})
|
|
}
|