126 lines
3.7 KiB
Go
126 lines
3.7 KiB
Go
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")
|
||
}
|