Files
0451meishi/backend/internal/handlers/user_auth.go

126 lines
3.7 KiB
Go
Raw Normal View History

2026-01-15 11:37:22 +08:00
package handlers
import (
"errors"
"net/http"
"strings"
"time"
"0451meishiditu/backend/internal/auth"
"0451meishiditu/backend/internal/models"
"0451meishiditu/backend/internal/resp"
"github.com/gin-gonic/gin"
mysqlerr "github.com/go-sql-driver/mysql"
"golang.org/x/crypto/bcrypt"
)
type userRegisterReq struct {
Username string `json:"username" binding:"required"`
Password string `json:"password" binding:"required"`
CaptchaID string `json:"captcha_id" binding:"required"`
CaptchaCode string `json:"captcha_code" binding:"required"`
}
func (h *Handlers) UserRegister(c *gin.Context) {
var req userRegisterReq
if err := c.ShouldBindJSON(&req); err != nil {
resp.Fail(c, http.StatusBadRequest, "invalid payload: "+safeErrMsg(err))
return
}
if !h.verifyCaptcha(c, req.CaptchaID, req.CaptchaCode) {
resp.Fail(c, http.StatusBadRequest, "invalid captcha")
return
}
hash, err := bcrypt.GenerateFromPassword([]byte(req.Password), bcrypt.DefaultCost)
if err != nil {
resp.Fail(c, http.StatusInternalServerError, "hash error")
return
}
req.Username = strings.TrimSpace(req.Username)
u := models.User{Username: req.Username, PasswordHash: string(hash), Status: "active"}
if err := h.db.Create(&u).Error; err != nil {
if isDuplicateErr(err) {
resp.Fail(c, http.StatusConflict, "username already exists")
return
}
resp.Fail(c, http.StatusBadRequest, "register failed: "+safeErrMsg(err))
return
}
token, _ := auth.NewUserToken(h.cfg.JWTSecret, u.ID, u.Username, 30*24*time.Hour)
resp.OK(c, gin.H{"token": token, "user": u})
}
type userLoginReq struct {
Username string `json:"username" binding:"required"`
Password string `json:"password" binding:"required"`
CaptchaID string `json:"captcha_id" binding:"required"`
CaptchaCode string `json:"captcha_code" binding:"required"`
}
func (h *Handlers) UserLogin(c *gin.Context) {
var req userLoginReq
if err := c.ShouldBindJSON(&req); err != nil {
resp.Fail(c, http.StatusBadRequest, "invalid payload: "+safeErrMsg(err))
return
}
if !h.verifyCaptcha(c, req.CaptchaID, req.CaptchaCode) {
resp.Fail(c, http.StatusBadRequest, "invalid captcha")
return
}
var u models.User
if err := h.db.Where("username = ?", req.Username).First(&u).Error; err != nil {
resp.Fail(c, http.StatusUnauthorized, "invalid username or password")
return
}
if u.Status != "active" {
resp.Fail(c, http.StatusUnauthorized, "account disabled")
return
}
if err := bcrypt.CompareHashAndPassword([]byte(u.PasswordHash), []byte(req.Password)); err != nil {
resp.Fail(c, http.StatusUnauthorized, "invalid username or password")
return
}
token, _ := auth.NewUserToken(h.cfg.JWTSecret, u.ID, u.Username, 30*24*time.Hour)
resp.OK(c, gin.H{"token": token, "user": gin.H{"id": u.ID, "username": u.Username}})
}
func isDuplicateErr(err error) bool {
var me *mysqlerr.MySQLError
if strings.Contains(err.Error(), "Duplicate entry") {
return true
}
if errors.As(err, &me) {
return me.Number == 1062
}
return false
}
func safeErrMsg(err error) string {
s := err.Error()
if len(s) > 200 {
return s[:200]
}
return s
}
func (h *Handlers) UserMe(c *gin.Context) {
id := c.GetUint("user_id")
var u models.User
if err := h.db.Select("id", "username", "status", "created_at", "updated_at").Where("id = ?", id).First(&u).Error; err != nil {
resp.Fail(c, http.StatusInternalServerError, "db error")
return
}
resp.OK(c, u)
}
// 预留:抖音登录(需要对接抖音服务端换取 openid/session_key
type douyinLoginReq struct {
Code string `json:"code" binding:"required"`
}
func (h *Handlers) DouyinLogin(c *gin.Context) {
_ = douyinLoginReq{}
resp.Fail(c, http.StatusNotImplemented, "douyin login not implemented yet")
}