文章目录
- 一 、增加路由
- 二、书写流程控制(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 controller
import (
"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 controller
type ResCode int64
const (
CodeSuccess ResCode = 1000 + iota
CodeInvalidParam
CodeUserExist
CodeUserNotExist
CodeInvalidPassword
CodeServerBusy
CodeNeedLogin
CodeInvalidToken
)
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 controller
import (
"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 models
import "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.生成UID
userID := 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 mysql
import "errors"
var (
ErrorUserExist = errors.New("用户已存在")
ErrorUserNotExist = errors.New("用户不存在")
ErrorInvalidPassword = errors.New("用户名或密码错误")
ErrorInvalidID = errors.New("无效的ID")
)
校验用户名是否已经存在、对密码加密、保存新注册的用户
dao/mysql/user.go
package mysql
import (
"bluebell/models"
"crypto/md5"
"encoding/hex"
)
// 把每一步数据库操作封装成函数
// 待logic层根据业务需求调用
const secret = "lym.com"
// CheckUserExist 检查指定用户名的用户是否存在
func CheckUserExist(username string) (err error) {
var count int64
if 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).Error
return
}
// 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)