注册路由
r.POST("/signup", controller.SignupHandler)
控制层实现
第一步,获取参数和参数校验
在models包中定义一个注册请求参数的结构体(ParamSignUp),用于将JSON参数绑定至结构体中。
json 的tag用于绑定参数,binding参数是gin内置validator校验的tag,required代表此字段是传入参数中必须要有的,eqfield=xxx,代表此字段与结构体中的xxx字段值需要相等,否则会校验失败,返回错误
package models
// 定义请求的参数结构体
// ParamSignUp 注册请求参数
type ParamSignUp struct {
Username string `json:"username" binding:"required"` // 代表校验此字段是必须在的
Password string `json:"password" binding:"required"`
RePassword string `json:"re_password" binding:"required,eqfield=Password"`
}
得到一个注册请求参数结构体指针
p := new(models.ParamSignUp) // 得到一个注册请求参数结构体指针
ps:new返回类型的指针。
gin自带ShouldBindJSON方法将传入参数序列化到p结构体中
if err := c.ShouldBindJSON(p); err != nil {
// 请求参数有误,直接返回响应
zap.L().Error("SignUp with invalid param", zap.Error(err))
// 判断err类型是不是validator.ValidationErrors类型
errs, ok := err.(validator.ValidationErrors)
// 如果不是就返回正常错误
if !ok {
c.JSON(http.StatusOK, gin.H{
"msg": "请求参数错误",
})
return
}
// 如果是就返回翻译过后的内容
c.JSON(http.StatusOK, gin.H{
"msg": errs.Translate(trans),
})
return
}
Validator翻译器完整代码
由于validator校验返回的错误信息是英文,为方便用户读取,做一个翻译器,将错误类型为validator.ValidationErrors的内容翻译成中文。
package controller
/*
用于翻译validator.ValidationErrors错误的内容
*/
import (
"bluebell/models"
"fmt"
"github.com/gin-gonic/gin/binding"
"github.com/go-playground/locales/en"
"github.com/go-playground/locales/zh"
ut "github.com/go-playground/universal-translator"
"github.com/go-playground/validator/v10"
enTranslations "github.com/go-playground/validator/v10/translations/en"
zhTranslations "github.com/go-playground/validator/v10/translations/zh"
"reflect"
"strings"
)
// 定义一个全局翻译器T
var trans ut.Translator
// InitTrans 初始化翻译器
func InitTrans(locale string) (err error) {
// 修改gin框架中的Validator引擎属性,实现自定制
if v, ok := binding.Validator.Engine().(*validator.Validate); ok {
// 注册一个获取json tag的自定义方法
v.RegisterTagNameFunc(func(fld reflect.StructField) string {
name := strings.SplitN(fld.Tag.Get("json"), ",", 2)[0]
if name == "-" {
return ""
}
return name
})
// 为SignUpParam注册自定义校验方法
v.RegisterStructValidation(SignUpParamStructLevelValidation, models.ParamSignUp{})
zhT := zh.New() // 中文翻译器
enT := en.New() // 英文翻译器
// 第一个参数是备用(fallback)的语言环境
// 后面的参数是应该支持的语言环境(支持多个)
// uni := ut.New(zhT, zhT) 也是可以的
uni := ut.New(enT, zhT, enT)
// locale 通常取决于 http 请求头的 'Accept-Language'
var ok bool
// 也可以使用 uni.FindTranslator(...) 传入多个locale进行查找
trans, ok = uni.GetTranslator(locale)
if !ok {
return fmt.Errorf("uni.GetTranslator(%s) failed", locale)
}
// 注册翻译器
switch locale {
case "en":
err = enTranslations.RegisterDefaultTranslations(v, trans)
case "zh":
err = zhTranslations.RegisterDefaultTranslations(v, trans)
default:
err = enTranslations.RegisterDefaultTranslations(v, trans)
}
return
}
return
}
// removeTopStruct 去除提示信息中的结构体名称
func removeTopStruct(fields map[string]string) map[string]string {
res := map[string]string{}
for field, err := range fields {
res[field[strings.Index(field, ".")+1:]] = err
}
return res
}
// SignUpParamStructLevelValidation 自定义SignUpParam结构体校验函数
func SignUpParamStructLevelValidation(sl validator.StructLevel) {
su := sl.Current().Interface().(models.ParamSignUp)
if su.Password != su.RePassword {
// 输出错误提示信息,最后一个参数就是传递的param
sl.ReportError(su.RePassword, "re_password", "RePassword", "eqfield", "password")
}
}
第二步,业务处理,logic层进行业务处理
调用logic包中的Signup方法,实现用户插入数据库的操作,完成注册的功能。
// 2.业务处理 logic层进行业务处理
if err := logic.Signup(p); err != nil {
c.JSON(http.StatusOK, gin.H{
"msg": err.Error(),
})
return
}
Signup方法实现
第一步,校验用户是否存在
// 1.校验用户是否存在
if err := mysql.CheckUserExist(p); err != nil {
return err
}
调用mysql中的CheckUserExist方法,判断该用户是否已存在,如果存在,说明该用户已经注册过了。
mysql包中CheckUserExist方法实现
// CheckUserExist 判断用户是否存在
func CheckUserExist(user *models.ParamSignUp) error {
sqlStr := "select count(user_id) from user where username = ?"
var count int
if err := db.Get(&count, sqlStr, user.Username); err != nil {
return err
}
if count > 0 {
return errors.New("用户已存在")
}
return nil
}
搜索相关用户名,统计个数,判断是否大于0,大于0即是存在。
第二步,雪花算法生成UID,生成一个用户实例
调用snowflake包中的GenID获取UID
// 2.生成UID
UID := snowflake.GenID()
在models表中创建一个用户User结构体,对应数据库中的user表,相互绑定。
package models
/*
构造user结构体与数据库中user表相对应,相互绑定
*/
type User struct {
UserID int64 `db:"user_id"`
UserName string `db:"username"`
PassWord string `db:"password"`
}
生成一个用户实例
// 生成一个用户实例
user := &models.User{
UserID: UID,
UserName: p.Username,
PassWord: p.Password,
}
第三步,保存到数据库
调用mysql包中的InsertUser方法,传入用户实例
// 3.保存到数据库
if err := mysql.InsertUser(user); err != nil {
return err
}
mysql包中InsertUser方法实现
// InsertUser 向数据库中插入一条用户数据
func InsertUser(user *models.User) error {
sqlStr := "insert into user(user_id,username,password) values(?,?,?)"
user.PassWord = encryptPassword(user.PassWord)
if _, err := db.Exec(sqlStr, user.UserID, user.UserName, user.PassWord); err != nil {
return err
}
return nil
}
创建相关sql语句,执行写入数据库。
mysql包中encryptPassword方法实现
数据库中不能存储明文密码,所以要对密码进行加密。
// 对密码进行加密
func encryptPassword(oPassword string) string {
h := md5.New()
h.Write([]byte(secret))
return hex.EncodeToString(h.Sum([]byte(oPassword)))
}
logic/Signup完整代码
package logic
import (
"bluebell/dao/mysql"
"bluebell/models"
"bluebell/pkg/snowflake"
)
func Signup(p *models.ParamSignUp) error {
// 1.校验用户是否存在
if err := mysql.CheckUserExist(p); err != nil {
return err
}
// 2.生成UID
UID := snowflake.GenID()
// 生成一个用户实例
user := &models.User{
UserID: UID,
UserName: p.Username,
PassWord: p.Password,
}
// 3.保存到数据库
if err := mysql.InsertUser(user); err != nil {
return err
}
return nil
}
第三步,返回响应
如果没有任何报错,则返回success
// 3.返回响应
c.JSON(http.StatusOK, gin.H{
"msg": "success",
})
控制层完整代码
package controller
import (
"bluebell/logic"
"bluebell/models"
"github.com/gin-gonic/gin"
"github.com/go-playground/validator/v10"
"go.uber.org/zap"
"net/http"
)
func SignupHandler(c *gin.Context) {
// 1.获取参数和参数校验
p := new(models.ParamSignUp) // 得到一个注册请求参数结构体指针
if err := c.ShouldBindJSON(p); err != nil {
// 请求参数有误,直接返回响应
zap.L().Error("SignUp with invalid param", zap.Error(err))
// 判断err类型是不是validator.ValidationErrors类型
errs, ok := err.(validator.ValidationErrors)
// 如果不是就返回正常错误
if !ok {
c.JSON(http.StatusOK, gin.H{
"msg": "请求参数错误",
})
return
}
// 如果是就返回翻译过后的内容
c.JSON(http.StatusOK, gin.H{
"msg": errs.Translate(trans),
})
return
}
// 2.业务处理 logic层进行业务处理
if err := logic.Signup(p); err != nil {
c.JSON(http.StatusOK, gin.H{
"msg": err.Error(),
})
return
}
// 3.返回响应
c.JSON(http.StatusOK, gin.H{
"msg": "success",
})
return
}