文章目录
- 一 、增加路由
- 二、书写流程控制(controller)逻辑
- 三、书写业务逻辑
- 四、与DB交互
- 五、测试
代码地址:https://gitee.com/lymgoforIT/bluebell
一 、增加路由
添加路由,使用分组管理
v1 := r.Group("/api/v1")// 注册
v1.POST("/signup", controller.SignUpHandler)
由于我们的项目是非常简单的上入门项目,所以并不会用到DDD等复杂的目录设计和管理,而是用了最基本的MVC三层结构。
- controller目录用于接收路由,进行基本的参数校验,不做业务逻辑处理。
- logic目录用于处理业务逻辑。
- dao目录用于用数据源交互。
绿色即为本次注册功能需要新增的代码文件
二、书写流程控制(controller)逻辑
controller/user.go user.go文件主要负责用户的注册和登录。 这里首先写注册逻辑,后续的登录逻辑也会在这个文件中实现。
package controllerimport ("bluebell/models""github.com/gin-gonic/gin""github.com/go-playground/validator/v10""go.uber.org/zap"
)// SignUpHandler 处理注册请求的函数
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 {ResponseError(c, CodeInvalidParam)return}ResponseErrorWithMsg(c, CodeInvalidParam, removeTopStruct(errs.Translate(trans)))return}// 2. 业务处理if err := logic.SignUp(p); err != nil {zap.L().Error("logic.SignUp failed", zap.Error(err))if errors.Is(err, mysql.ErrorUserExist) {ResponseError(c, CodeUserExist)return}ResponseError(c, CodeServerBusy)return}// 3. 返回响应ResponseSuccess(c, nil)
}
代码说明:
- controller层不做业务逻辑,主要就是组织整体流程的,如参数校验,业务处理,返回响应。
- 返回结果比较通用,所以封装工具函数更为简介、易读。
- 适当的定义一些错误码是不错的选择。
定义状态码并封装响应方法
controller/code.go
package controllertype ResCode int64const (CodeSuccess ResCode = 1000 + iotaCodeInvalidParamCodeUserExistCodeUserNotExistCodeInvalidPasswordCodeServerBusyCodeNeedLoginCodeInvalidToken
)var codeMsgMap = map[ResCode]string{CodeSuccess: "success",CodeInvalidParam: "请求参数错误",CodeUserExist: "用户名已存在",CodeUserNotExist: "用户名不存在",CodeInvalidPassword: "用户名或密码错误",CodeServerBusy: "服务繁忙",CodeNeedLogin: "需要登录",CodeInvalidToken: "无效的token",
}func (c ResCode) Msg() string {msg, ok := codeMsgMap[c]if !ok {msg = codeMsgMap[CodeServerBusy]}return msg
}
controller/response.go
package controllerimport ("net/http""github.com/gin-gonic/gin"
)/*
{"code": 10000, // 程序中的错误码"msg": xx, // 提示信息"data": {}, // 数据
}*/type ResponseData struct {Code ResCode `json:"code"`Msg interface{} `json:"msg"`Data interface{} `json:"data,omitempty"`
}func ResponseError(c *gin.Context, code ResCode) {c.JSON(http.StatusOK, &ResponseData{Code: code,Msg: code.Msg(),Data: nil,})
}func ResponseErrorWithMsg(c *gin.Context, code ResCode, msg interface{}) {c.JSON(http.StatusOK, &ResponseData{Code: code,Msg: msg,Data: nil,})
}func ResponseSuccess(c *gin.Context, data interface{}) {c.JSON(http.StatusOK, &ResponseData{Code: CodeSuccess,Msg: CodeSuccess.Msg(),Data: data,})
}
三、书写业务逻辑
首先,我们需要查询DB中要注册的用户名是否已经存在,所以需要先提供一个可以和DB交互的model,user
models/user.go
提示:可以直接SQL转对应的结构体
package modelsimport "time"type User struct {ID int64 `gorm:"column:id" db:"id" json:"id" form:"id"`UserId int64 `gorm:"column:user_id" db:"user_id" json:"user_id" form:"user_id"`Username string `gorm:"column:username" db:"username" json:"username" form:"username"`Password string `gorm:"column:password" db:"password" json:"password" form:"password"`Email string `gorm:"column:email" db:"email" json:"email" form:"email"`Gender int64 `gorm:"column:gender" db:"gender" json:"gender" form:"gender"`CreateTime time.Time `gorm:"column:create_time" db:"create_time" json:"create_time" form:"create_time"`UpdateTime time.Time `gorm:"column:update_time" db:"update_time" json:"update_time" form:"update_time"`
}func (User) TableName() string {return "user"
}
logic/user.go
负责校验用户名是否已经存在,若不存在,则生成唯一的用户ID,注册用户
import ("bluebell/models""bluebell/pkg/snowflake"
)func SignUp(p *models.ParamSignUp) (err error) {// 1.判断用户存不存在if err := mysql.CheckUserExist(p.Username); err != nil {return err}// 2.生成UIDuserID := snowflake.GenID()// 构造一个User实例user := &models.User{UserID: userID,Username: p.Username,Password: p.Password,}// 3.保存进数据库return mysql.InsertUser(user)
}
四、与DB交互
定义与DB交互时可能出现的相关错误码
dao/mysql/error_code.go
package mysqlimport "errors"var (ErrorUserExist = errors.New("用户已存在")ErrorUserNotExist = errors.New("用户不存在")ErrorInvalidPassword = errors.New("用户名或密码错误")ErrorInvalidID = errors.New("无效的ID")
)
校验用户名是否已经存在、对密码加密、保存新注册的用户
dao/mysql/user.go
package mysqlimport ("bluebell/models""crypto/md5""encoding/hex"
)// 把每一步数据库操作封装成函数
// 待logic层根据业务需求调用const secret = "lym.com"// CheckUserExist 检查指定用户名的用户是否存在
func CheckUserExist(username string) (err error) {var count int64if err := db.Model(models.User{}).Where("username = ?", username).Count(&count).Error; err != nil {return err}if count > 0 {return ErrorUserExist}return
}// InsertUser 想数据库中插入一条新的用户记录
func InsertUser(user *models.User) (err error) {// 对密码进行加密user.Password = encryptPassword(user.Password)// 执行SQL语句入库u := &models.User{UserId: user.UserId,Username: user.Username,Password: user.Password,}err = db.Create(&u).Errorreturn
}// encryptPassword 密码加密
func encryptPassword(oPassword string) string {h := md5.New()h.Write([]byte(secret))return hex.EncodeToString(h.Sum([]byte(oPassword)))
}
五、测试
编译
go build
运行
./bluebell.exe ./config/config.yaml
注册,因为lym已经存在,所以会返回用户名已存在
注册一个不存在的名字试试,成功!
查询DB,注册成功,且密码是经过MD5加密过的(实际就是123)