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")
|
|||
|
|
}
|