Forráskód Böngészése

手机号+短信验证码注册

runningwater 11 hónapja
szülő
commit
06d4cb0eab

+ 51 - 26
app/http/controllers/api/v1/auth/signup_controller.go

@@ -1,42 +1,67 @@
 package auth
 
 import (
-	"github.com/gin-gonic/gin"
-	v1 "github.com/runningwater/gohub/app/http/controllers/api/v1"
-	"github.com/runningwater/gohub/app/models/user"
-	"github.com/runningwater/gohub/app/requests"
-	"github.com/runningwater/gohub/pkg/response"
+    "github.com/gin-gonic/gin"
+
+    v1 "github.com/runningwater/gohub/app/http/controllers/api/v1"
+    "github.com/runningwater/gohub/app/models/user"
+    "github.com/runningwater/gohub/app/requests"
+    "github.com/runningwater/gohub/pkg/response"
 )
 
 // SignupController 处理用户注册相关的逻辑
 type SignupController struct {
-	v1.BaseApiController
+    v1.BaseApiController
 }
 
-func (controller *SignupController) IsPhoneExist(c *gin.Context) {
+func (sc *SignupController) IsPhoneExist(c *gin.Context) {
 
-	// 初始化请求对象
-	req := requests.SignupPhoneExistRequest{}
-	if ok := requests.Validate(c, &req, requests.ValidateSignupPhoneExist); !ok {
-		return
-	}
+    // 初始化请求对象
+    req := requests.SignupPhoneExistRequest{}
+    if ok := requests.Validate(c, &req, requests.ValidateSignupPhoneExist); !ok {
+        return
+    }
 
-	// 检查数据库并返回响应
-	response.JSON(c, gin.H{
-		"exist": user.IsPhoneExist(req.Phone),
-	})
+    // 检查数据库并返回响应
+    response.JSON(c, gin.H{
+        "exist": user.IsPhoneExist(req.Phone),
+    })
 }
 
-func (controller *SignupController) IsEmailExist(c *gin.Context) {
+func (sc *SignupController) IsEmailExist(c *gin.Context) {
+
+    // 初始化请求对象
+    req := requests.SignupEmailExistRequest{}
+    if ok := requests.Validate(c, &req, requests.ValidateSignupEmailExist); !ok {
+        return
+    }
+
+    // 检查数据库并返回响应
+    response.JSON(c, gin.H{
+        "exist": user.IsEmailExist(req.Email),
+    })
+}
 
-	// 初始化请求对象
-	req := requests.SignupEmailExistRequest{}
-	if ok := requests.Validate(c, &req, requests.ValidateSignupEmailExist); !ok {
-		return
-	}
+// SignupUsingPhone 使用手机和验证码进行注册
+func (sc *SignupController) SignupUsingPhone(c *gin.Context) {
+    // 1. 验证表单
+    request := requests.SignupUsingPhoneRequest{}
+    if ok := requests.Validate(c, &request, requests.SignupUsingPhone); !ok {
+        return
+    }
+    // 2. 验证成功,创建数据
+    _user := user.User{
+        Name:     request.Name,
+        Phone:    request.Phone,
+        Password: request.Password,
+    }
+    _user.Create()
 
-	// 检查数据库并返回响应
-	response.JSON(c, gin.H{
-		"exist": user.IsEmailExist(req.Email),
-	})
+    if _user.ID > 0 {
+        response.CreatedJSON(c, gin.H{
+            "data": _user,
+        })
+    } else {
+        response.Abort500(c, "创建用户失败,请稍后尝试~")
+    }
 }

+ 40 - 40
app/http/controllers/api/v1/auth/verify_code_controller.go

@@ -1,64 +1,64 @@
 package auth
 
 import (
-	"github.com/gin-gonic/gin"
+    "github.com/gin-gonic/gin"
 
-	v1 "github.com/runningwater/gohub/app/http/controllers/api/v1"
-	"github.com/runningwater/gohub/app/requests"
-	"github.com/runningwater/gohub/pkg/captcha"
-	"github.com/runningwater/gohub/pkg/logger"
-	"github.com/runningwater/gohub/pkg/response"
-	"github.com/runningwater/gohub/pkg/verifycode"
+    v1 "github.com/runningwater/gohub/app/http/controllers/api/v1"
+    "github.com/runningwater/gohub/app/requests"
+    "github.com/runningwater/gohub/pkg/captcha"
+    "github.com/runningwater/gohub/pkg/logger"
+    "github.com/runningwater/gohub/pkg/response"
+    "github.com/runningwater/gohub/pkg/verifycode"
 )
 
 type VerifyCodeController struct {
-	v1.BaseApiController
+    v1.BaseApiController
 }
 
 // ShowCaptcha 显示图片验证码
 func (vc *VerifyCodeController) ShowCaptcha(c *gin.Context) {
-	// 生成验证码
-	id, b64s, answer, err := captcha.NewCaptcha().GenerateCaptcha()
-	logger.LogIf(err)
-	logger.DebugString("captchaController", "captcha id: ", id)
-	logger.DebugString("captchaController", "captcha answer: ", answer)
-	response.JSON(c, gin.H{
-		"captcha_id":    id,
-		"captcha_image": b64s,
-	})
+    // 生成验证码
+    id, b64s, answer, err := captcha.NewCaptcha().GenerateCaptcha()
+    logger.LogIf(err)
+    logger.DebugString("captchaController", "captcha id: ", id)
+    logger.DebugString("captchaController", "captcha answer: ", answer)
+    response.JSON(c, gin.H{
+        "captcha_id":    id,
+        "captcha_image": b64s,
+    })
 }
 
 // SendUsingPhone 发送手机验证码
 func (vc *VerifyCodeController) SendUsingPhone(c *gin.Context) {
 
-	// 1. 验证表单
-	request := requests.VerifyCodeRequest{}
-	if ok := requests.Validate(c, &request, requests.VerifyCodePhone); !ok {
-		return
-	}
+    // 1. 验证表单
+    request := requests.VerifyCodeRequest{}
+    if ok := requests.Validate(c, &request, requests.VerifyCodePhone); !ok {
+        return
+    }
 
-	// 2. 发送手机验证码
-	//  if ok := verifycode.NewVerifyCode().SendSMS(request.Phone); !ok {
-	// 	response.Abort500(c, "发送短信失败~~")
-	// }else {
-	// 	response.Success(c)
-	// }
+    // 2. 发送手机验证码
+    if ok := verifycode.NewVerifyCode().SendSMS(request.Phone); !ok {
+        response.Abort500(c, "发送短信失败~~")
+    } else {
+        response.Success(c)
+    }
 
-	response.Success(c)
+    response.Success(c)
 }
 
 // SendUsingEmail 发送邮箱验证码
 func (vc *VerifyCodeController) SendUsingEmail(c *gin.Context) {
-	// 1. 验证表单
-	request := requests.VerifyCodeEmailRequest{}
-	if ok := requests.Validate(c, &request, requests.VerifyCodeEmail); !ok {
-		return
-	}
+    // 1. 验证表单
+    request := requests.VerifyCodeEmailRequest{}
+    if ok := requests.Validate(c, &request, requests.VerifyCodeEmail); !ok {
+        return
+    }
 
-	// 2. 发送邮箱验证码
-	if err := verifycode.NewVerifyCode().SendEmail(request.Email); err != nil {
-		response.Abort500(c, "发送邮件失败~~")
-	} else {
-		response.Success(c)
-	}
+    // 2. 发送邮箱验证码
+    if err := verifycode.NewVerifyCode().SendEmail(request.Email); err != nil {
+        response.Abort500(c, "发送邮件失败~~")
+    } else {
+        response.Success(c)
+    }
 }

+ 15 - 7
app/models/user/user_model.go

@@ -1,16 +1,24 @@
 // Package user 存放用户 Model 相关逻辑
 package user
 
-import "github.com/runningwater/gohub/app/models"
+import (
+    "github.com/runningwater/gohub/app/models"
+    "github.com/runningwater/gohub/pkg/database"
+)
 
 // User 用户模型
 type User struct {
-	models.BaseModel
+    models.BaseModel
 
-	Name     string `json:"name,omitempty"`
-	Email    string `json:"-"`
-	Phone    string `json:"-"`
-	Password string `json:"-"`
+    Name     string `json:"name,omitempty"`
+    Email    string `json:"-"`
+    Phone    string `json:"-"`
+    Password string `json:"-"`
 
-	models.CommonTimestampsField
+    models.CommonTimestampsField
+}
+
+// Create 创建用户, 通过 User.ID 来判断是否创建成功
+func (u *User) Create() {
+    database.DB.Create(&u)
 }

+ 93 - 36
app/requests/signup_request.go

@@ -1,53 +1,110 @@
-// Package: requests 处理请求数据和表单验证逻辑
+// Package requests 处理请求数据和表单验证逻辑
 package requests
 
 import (
-	"github.com/gin-gonic/gin"
-	"github.com/thedevsaddam/govalidator"
+    "github.com/gin-gonic/gin"
+    "github.com/thedevsaddam/govalidator"
+
+    "github.com/runningwater/gohub/app/requests/validators"
 )
 
 type SignupPhoneExistRequest struct {
-	Phone string `json:"phone,omitempty" valid:"phone"`
+    Phone string `json:"phone,omitempty" valid:"phone"`
 }
 type SignupEmailExistRequest struct {
-	Email string `json:"email,omitempty" valid:"email"`
+    Email string `json:"email,omitempty" valid:"email"`
 }
 
 func ValidateSignupPhoneExist(data any, c *gin.Context) map[string][]string {
 
-	// 自定义验证规则
-	rules := govalidator.MapData{
-		"phone": []string{"required", "digits:11"},
-	}
-
-	// 自定义错误信息
-	messages := govalidator.MapData{
-		"phone": []string{
-			"required:手机号不能为空",
-			"digits:手机号必须是11位数字",
-		},
-	}
-
-	// 执行验证
-	return validate(data, rules, messages)
+    // 自定义验证规则
+    rules := govalidator.MapData{
+        "phone": []string{"required", "digits:11"},
+    }
+
+    // 自定义错误信息
+    messages := govalidator.MapData{
+        "phone": []string{
+            "required:手机号不能为空",
+            "digits:手机号必须是11位数字",
+        },
+    }
+
+    // 执行验证
+    return validate(data, rules, messages)
 }
 
 func ValidateSignupEmailExist(data any, c *gin.Context) map[string][]string {
 
-	// 自定义验证规则
-	rules := govalidator.MapData{
-		"email": []string{"required", "min:4", "max:30", "email"},
-	}
-
-	// 自定义错误信息
-	messages := govalidator.MapData{
-		"email": []string{
-			"required:Email 不能为空",
-			"min:Email 长度需大于 4",
-			"max:Email 长度需小于 30",
-			"email:Email 格式不正确,请提供有效的邮箱地址",
-		},
-	}
-	// 执行验证
-	return validate(data, rules, messages)
+    // 自定义验证规则
+    rules := govalidator.MapData{
+        "email": []string{"required", "min:4", "max:30", "email"},
+    }
+
+    // 自定义错误信息
+    messages := govalidator.MapData{
+        "email": []string{
+            "required:Email 不能为空",
+            "min:Email 长度需大于 4",
+            "max:Email 长度需小于 30",
+            "email:Email 格式不正确,请提供有效的邮箱地址",
+        },
+    }
+    // 执行验证
+    return validate(data, rules, messages)
+}
+
+// SignupUsingPhoneRequest 用于手机注册的请求结构体
+// 该结构体包含了手机号码、验证码、姓名、密码和确认密码等字段
+type SignupUsingPhoneRequest struct {
+    Phone           string `json:"phone,omitempty" valid:"phone"`
+    VerifyCode      string `json:"verify_code,omitempty" valid:"verify_code"`
+    Name            string `valid:"name" json:"name,omitempty"`
+    Password        string `valid:"password" json:"password,omitempty"`
+    PasswordConfirm string `valid:"password_confirm" json:"password_confirm,omitempty"`
+}
+
+// SignupUsingPhone 验证手机号码注册表单
+func SignupUsingPhone(data any, c *gin.Context) map[string][]string {
+    // 自定义验证规则
+    rules := govalidator.MapData{
+        "phone":            []string{"required", "digits:11", "not_exists:users,phone"},
+        "verify_code":      []string{"required", "digits:6"},
+        "name":             []string{"required", "alpha_num", "between:3,20", "not_exists:users,name"},
+        "password":         []string{"required", "min:6"},
+        "password_confirm": []string{"required"},
+    }
+
+    // 自定义错误信息
+    messages := govalidator.MapData{
+        "phone": []string{
+            "required:手机号不能为空",
+            "digits:手机号必须是 11 位数字",
+        },
+        "verify_code": []string{
+            "required:验证码不能为空",
+            "digits:验证码必须是 6 位数字",
+        },
+        "name": []string{
+            "required:姓名不能为空",
+            "alpha_num:姓名格式错误,只允许数字和英文",
+            "between:姓名长度需在 3~20 之间",
+        },
+        "password": []string{
+            "required:密码不能为空",
+            "min:密码长度需大于 6",
+        },
+        "password_confirm": []string{
+            "required:确认密码框不能为空",
+        },
+    }
+
+    errs := validate(data, rules, messages)
+
+    // 检查密码和确认密码是否匹配
+    _data := data.(*SignupUsingPhoneRequest)
+    errs = validators.ValidatePasswordConfirm(_data.Password, _data.PasswordConfirm, errs)
+    errs = validators.ValidateVerifyCode(_data.Phone, _data.VerifyCode, errs)
+
+    return errs
 }

+ 59 - 0
app/requests/validators/custom_rules.go

@@ -0,0 +1,59 @@
+package validators
+
+import (
+	"errors"
+	"fmt"
+	"strings"
+
+	"github.com/runningwater/gohub/pkg/database"
+	"github.com/thedevsaddam/govalidator"
+)
+
+func init() {
+	// 自定义验证规则 not_exists, 用于验证数据不存在于数据库中
+	// 常用于保证数据库某个字段的值唯一, 如用户名、邮箱、手机号或者分类的名称
+	//
+	// 用法示例
+	//
+	// 规则: not_exists:users,phone
+	// 解释:验证 users 表中是否存在 phone 字段的值,phone 为待验证的值
+	//
+	// 规则: not_exists:users,phone,10
+	// 解释:验证 users 表中是否存在 phone 字段的值,同时排除 id 为 10 的记录
+	govalidator.AddCustomRule("not_exists", func(field string, rule string, message string, value any) error {
+		rng := strings.Split(strings.TrimPrefix(rule, "not_exists:"), ",")
+
+		// 第一个参数,表名称,如 users
+		tableName := rng[0]
+		// 第二个参数,字段名称,如 phone
+		fieldName := rng[1]
+		// 第三个参数,排除 ID
+		var exceptID string
+		if len(rng) > 2 {
+			exceptID = rng[2]
+		}
+
+		// 用户请求的值
+		requestValue := value.(string)
+
+		// 调用数据库查询方法,检查数据是否存在
+		query := database.DB.Table(tableName).Where(fieldName+" = ?", requestValue)
+		if len(exceptID) > 0 {
+			query.Where("id != ?", exceptID)
+		}
+		var count int64
+		query.Count(&count)
+
+		// 如果 count 大于 0,表示数据已存在,返回错误信息
+		if count > 0 {
+			// 如果有自定义错误消息的话,使用自定义错误消息
+			if len(message) > 0 {
+				return errors.New(message)
+			}
+			return fmt.Errorf("%v 已存在", requestValue)
+		}
+
+		return nil
+
+	})
+}

+ 21 - 0
app/requests/validators/custom_validators.go

@@ -0,0 +1,21 @@
+package validators
+
+import "github.com/runningwater/gohub/pkg/verifycode"
+
+// ValidatePasswordConfirm 用于验证密码和确认密码是否一致
+func ValidatePasswordConfirm(password, passwordConfirm string, errs map[string][]string) map[string][]string {
+	if password != passwordConfirm {
+		errs["password_confirm"] = append(errs["password_confirm"], "密码和确认密码不匹配")
+	}
+	return errs
+}
+
+// ValidateVerifyCode 用于验证验证码是否正确
+// 该函数接受验证码的 key 和用户输入的答案
+// 如果验证码不正确,则将错误信息添加到 errs 映射中
+func ValidateVerifyCode(key, answer string, errs map[string][]string) map[string][]string {
+	if ok := verifycode.NewVerifyCode().CheckAnswer(key, answer); !ok {
+		errs["verify_code"] = append(errs["verify_code"], "验证码错误")
+	}
+	return errs
+}

+ 12 - 0
gohub.http

@@ -36,4 +36,16 @@ Content-Type: application/json
   "email": "summer@example.com",
   "captcha_id": "captcha_skip_test",
   "captcha_answer": "123456"
+}
+
+### 注册用户
+POST {{base_url}}/v1/auth/signup/using-phone HTTP/1.1
+Content-Type: application/json
+
+{
+    "name":"summer",
+    "password":"secret",
+    "password_confirm":"no_match",
+    "verify_code": "{{verify_code_phone}}",
+    "phone": "00011059149"
 }

+ 24 - 21
routes/api.go

@@ -2,29 +2,32 @@
 package routes
 
 import (
-	"github.com/gin-gonic/gin"
-	"github.com/runningwater/gohub/app/http/controllers/api/v1/auth"
+    "github.com/gin-gonic/gin"
+
+    "github.com/runningwater/gohub/app/http/controllers/api/v1/auth"
 )
 
 // RegisterAPIRoutes 注册路由
 func RegisterAPIRoutes(router *gin.Engine) {
-	// v1 路由组,所有 v1 版本的路由都放在这里
-	v1 := router.Group("/v1")
-	{
-		authGroup := v1.Group("/auth")
-		{
-			suc := new(auth.SignupController)
-			vcc := new(auth.VerifyCodeController)
-			// 注册手机号是否已存在
-			authGroup.POST("/signup/phone/exist", suc.IsPhoneExist)
-			// 注册邮箱是否已存在
-			authGroup.POST("/signup/email/exist", suc.IsEmailExist)
-			// 显示图片验证码
-			authGroup.POST("/verify_code/captcha", vcc.ShowCaptcha)
-			// 发送手机验证码
-			authGroup.POST("/verify_code/phone", vcc.SendUsingPhone)
-			// 发送邮箱验证码
-			authGroup.POST("/verify_code/email", vcc.SendUsingEmail)
-		}
-	}
+    // v1 路由组,所有 v1 版本的路由都放在这里
+    v1 := router.Group("/v1")
+    {
+        authGroup := v1.Group("/auth")
+        {
+            suc := new(auth.SignupController)
+            vcc := new(auth.VerifyCodeController)
+            // 注册手机号是否已存在
+            authGroup.POST("/signup/phone/exist", suc.IsPhoneExist)
+            // 注册邮箱是否已存在
+            authGroup.POST("/signup/email/exist", suc.IsEmailExist)
+            // 注册用户
+            authGroup.POST("/signup/using-phone", suc.SignupUsingPhone)
+            // 显示图片验证码
+            authGroup.POST("/verify_code/captcha", vcc.ShowCaptcha)
+            // 发送手机验证码
+            authGroup.POST("/verify_code/phone", vcc.SendUsingPhone)
+            // 发送邮箱验证码
+            authGroup.POST("/verify_code/email", vcc.SendUsingEmail)
+        }
+    }
 }