宇恒一号官网
This commit is contained in:
294
server/handlers/register.go
Normal file
294
server/handlers/register.go
Normal file
@@ -0,0 +1,294 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"log"
|
||||
"net/http"
|
||||
"regexp"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"go.mongodb.org/mongo-driver/v2/bson"
|
||||
"go.mongodb.org/mongo-driver/v2/mongo"
|
||||
|
||||
"yh_web/server/config"
|
||||
"yh_web/server/models"
|
||||
"yh_web/server/utils"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
const (
|
||||
testVerifyCode = "8888" // 测试验证码(未接入短信时使用)
|
||||
codeExpire = 5 * time.Minute // 验证码有效期
|
||||
)
|
||||
|
||||
var (
|
||||
codeStore = make(map[string]codeEntry)
|
||||
codeStoreMu sync.RWMutex
|
||||
)
|
||||
|
||||
type codeEntry struct {
|
||||
Code string
|
||||
ExpireAt time.Time
|
||||
}
|
||||
|
||||
// SendCodeInput 发送验证码请求
|
||||
type SendCodeInput struct {
|
||||
Mobile string `json:"mobile" binding:"required"`
|
||||
}
|
||||
|
||||
// RegisterInput 手机注册请求
|
||||
type RegisterInput struct {
|
||||
Mobile string `json:"mobile" binding:"required"`
|
||||
Code string `json:"code" binding:"required"`
|
||||
Password string `json:"password" binding:"required"`
|
||||
Username string `json:"username"` // 可选,默认用手机号
|
||||
Email string `json:"email"` // 可选
|
||||
}
|
||||
|
||||
// SendCode 发送验证码(测试阶段:任意手机号输入 8888 即可)
|
||||
func SendCode(c *gin.Context) {
|
||||
var input SendCodeInput
|
||||
if err := c.ShouldBindJSON(&input); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "请输入手机号"})
|
||||
return
|
||||
}
|
||||
|
||||
if !isValidMobile(input.Mobile) {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "手机号格式不正确"})
|
||||
return
|
||||
}
|
||||
|
||||
// 未接入短信平台,使用测试验证码 8888
|
||||
codeStoreMu.Lock()
|
||||
codeStore[input.Mobile] = codeEntry{
|
||||
Code: testVerifyCode,
|
||||
ExpireAt: time.Now().Add(codeExpire),
|
||||
}
|
||||
codeStoreMu.Unlock()
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"message": "验证码已发送(测试环境请输入 8888)",
|
||||
"expire": int(codeExpire.Seconds()),
|
||||
})
|
||||
}
|
||||
|
||||
// Register 手机号注册
|
||||
func Register(c *gin.Context) {
|
||||
var input RegisterInput
|
||||
if err := c.ShouldBindJSON(&input); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "请填写手机号、验证码和密码"})
|
||||
return
|
||||
}
|
||||
|
||||
if !isValidMobile(input.Mobile) {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "手机号格式不正确"})
|
||||
return
|
||||
}
|
||||
|
||||
if len(input.Password) < 6 {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "密码至少6位"})
|
||||
return
|
||||
}
|
||||
|
||||
if input.Email != "" && !isValidEmail(input.Email) {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "邮箱格式不正确"})
|
||||
return
|
||||
}
|
||||
|
||||
// 校验验证码
|
||||
codeStoreMu.RLock()
|
||||
entry, ok := codeStore[input.Mobile]
|
||||
codeStoreMu.RUnlock()
|
||||
|
||||
if !ok || entry.Code != input.Code {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "验证码错误"})
|
||||
return
|
||||
}
|
||||
if time.Now().After(entry.ExpireAt) {
|
||||
codeStoreMu.Lock()
|
||||
delete(codeStore, input.Mobile)
|
||||
codeStoreMu.Unlock()
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "验证码已过期,请重新获取"})
|
||||
return
|
||||
}
|
||||
|
||||
// 删除已用验证码
|
||||
codeStoreMu.Lock()
|
||||
delete(codeStore, input.Mobile)
|
||||
codeStoreMu.Unlock()
|
||||
|
||||
username := input.Username
|
||||
if username == "" {
|
||||
username = input.Mobile
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
||||
defer cancel()
|
||||
|
||||
coll := config.GetDB(config.DBName).Collection("users")
|
||||
|
||||
// 检查手机号是否已注册
|
||||
var exist models.User
|
||||
err := coll.FindOne(ctx, bson.M{"mobile": input.Mobile}).Decode(&exist)
|
||||
if err == nil {
|
||||
c.JSON(http.StatusConflict, gin.H{"error": "该手机号已注册"})
|
||||
return
|
||||
}
|
||||
if !errors.Is(err, mongo.ErrNoDocuments) {
|
||||
log.Printf("[Register] FindOne mobile error: %v", err)
|
||||
resp := gin.H{"error": "注册失败,请稍后重试"}
|
||||
if gin.Mode() == gin.DebugMode {
|
||||
resp["debug"] = err.Error()
|
||||
}
|
||||
c.JSON(http.StatusInternalServerError, resp)
|
||||
return
|
||||
}
|
||||
err = coll.FindOne(ctx, bson.M{"username": username}).Decode(&exist)
|
||||
if err == nil {
|
||||
c.JSON(http.StatusConflict, gin.H{"error": "用户名已存在"})
|
||||
return
|
||||
}
|
||||
if !errors.Is(err, mongo.ErrNoDocuments) {
|
||||
log.Printf("[Register] FindOne username error: %v", err)
|
||||
resp := gin.H{"error": "注册失败,请稍后重试"}
|
||||
if gin.Mode() == gin.DebugMode {
|
||||
resp["debug"] = err.Error()
|
||||
}
|
||||
c.JSON(http.StatusInternalServerError, resp)
|
||||
return
|
||||
}
|
||||
|
||||
// 若提供了邮箱,检查是否已注册
|
||||
if input.Email != "" {
|
||||
err = coll.FindOne(ctx, bson.M{"email": input.Email}).Decode(&exist)
|
||||
if err == nil {
|
||||
c.JSON(http.StatusConflict, gin.H{"error": "该邮箱已注册"})
|
||||
return
|
||||
}
|
||||
if !errors.Is(err, mongo.ErrNoDocuments) {
|
||||
log.Printf("[Register] FindOne email error: %v", err)
|
||||
resp := gin.H{"error": "注册失败,请稍后重试"}
|
||||
if gin.Mode() == gin.DebugMode {
|
||||
resp["debug"] = err.Error()
|
||||
}
|
||||
c.JSON(http.StatusInternalServerError, resp)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
doc := bson.M{
|
||||
"username": username,
|
||||
"mobile": input.Mobile,
|
||||
"password": utils.HashPassword(input.Password),
|
||||
"role": "admin",
|
||||
"role_id": models.RoleIDSuperAdmin,
|
||||
}
|
||||
if input.Email != "" {
|
||||
doc["email"] = input.Email
|
||||
}
|
||||
|
||||
_, err = coll.InsertOne(ctx, doc)
|
||||
if err != nil {
|
||||
log.Printf("[Register] InsertOne error: %v", err)
|
||||
resp := gin.H{"error": "注册失败,请稍后重试"}
|
||||
if gin.Mode() == gin.DebugMode {
|
||||
resp["debug"] = err.Error()
|
||||
}
|
||||
c.JSON(http.StatusInternalServerError, resp)
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"message": "注册成功",
|
||||
})
|
||||
}
|
||||
|
||||
// ResetPasswordInput 密码找回请求
|
||||
type ResetPasswordInput struct {
|
||||
Mobile string `json:"mobile" binding:"required"`
|
||||
Code string `json:"code" binding:"required"`
|
||||
NewPassword string `json:"new_password" binding:"required"`
|
||||
}
|
||||
|
||||
// ResetPassword 密码找回(手机号+验证码)
|
||||
func ResetPassword(c *gin.Context) {
|
||||
var input ResetPasswordInput
|
||||
if err := c.ShouldBindJSON(&input); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "请填写手机号、验证码和新密码"})
|
||||
return
|
||||
}
|
||||
|
||||
if !isValidMobile(input.Mobile) {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "手机号格式不正确"})
|
||||
return
|
||||
}
|
||||
|
||||
if len(input.NewPassword) < 6 {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "新密码至少6位"})
|
||||
return
|
||||
}
|
||||
|
||||
// 校验验证码
|
||||
codeStoreMu.RLock()
|
||||
entry, ok := codeStore[input.Mobile]
|
||||
codeStoreMu.RUnlock()
|
||||
|
||||
if !ok || entry.Code != input.Code {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "验证码错误"})
|
||||
return
|
||||
}
|
||||
if time.Now().After(entry.ExpireAt) {
|
||||
codeStoreMu.Lock()
|
||||
delete(codeStore, input.Mobile)
|
||||
codeStoreMu.Unlock()
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "验证码已过期,请重新获取"})
|
||||
return
|
||||
}
|
||||
|
||||
codeStoreMu.Lock()
|
||||
delete(codeStore, input.Mobile)
|
||||
codeStoreMu.Unlock()
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
||||
defer cancel()
|
||||
|
||||
coll := config.GetDB(config.DBName).Collection("users")
|
||||
var user models.User
|
||||
err := coll.FindOne(ctx, bson.M{"mobile": input.Mobile}).Decode(&user)
|
||||
if err != nil {
|
||||
if errors.Is(err, mongo.ErrNoDocuments) {
|
||||
c.JSON(http.StatusNotFound, gin.H{"error": "该手机号未注册"})
|
||||
return
|
||||
}
|
||||
log.Printf("[ResetPassword] FindOne error: %v", err)
|
||||
resp := gin.H{"error": "操作失败"}
|
||||
if gin.Mode() == gin.DebugMode {
|
||||
resp["debug"] = err.Error()
|
||||
}
|
||||
c.JSON(http.StatusInternalServerError, resp)
|
||||
return
|
||||
}
|
||||
|
||||
_, err = coll.UpdateOne(ctx, bson.M{"_id": user.ID}, bson.M{"$set": bson.M{"password": utils.HashPassword(input.NewPassword)}})
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "重置失败"})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{"message": "密码已重置,请登录"})
|
||||
}
|
||||
|
||||
func isValidMobile(mobile string) bool {
|
||||
// 简单校验:11位数字,1开头
|
||||
matched, _ := regexp.MatchString(`^1\d{10}$`, mobile)
|
||||
return matched
|
||||
}
|
||||
|
||||
func isValidEmail(email string) bool {
|
||||
// 简单邮箱格式校验
|
||||
matched, _ := regexp.MatchString(`^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$`, email)
|
||||
return matched
|
||||
}
|
||||
Reference in New Issue
Block a user