宇恒一号官网

This commit is contained in:
whm
2026-03-17 00:59:32 +08:00
commit eb56519df7
105 changed files with 10783 additions and 0 deletions

294
server/handlers/register.go Normal file
View 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
}