创建用户 原创
1、创建用户模型
首先,在 internal/app/models
中创建 base.go
,定义基本的公共模型,如下:
package models
type Base struct {
ID uint32 `gorm:"primary_key" json:"id" description:"自增ID"`
CreatedAt uint32 `json:"created_at" description:"创建时间"`
ModifiedAt uint32 `json:"modified_at" description:"修改时间"`
DeletedAt uint32 `json:"deleted_at" description:"删除时间"`
IsDel uint8 `json:"is_del" description:"是否删除,1 表示删除,0表示未删除"`
}
然后,再在该目录下创建 user.go 文件,用于定义用户模型,如下:
package models
import "gorm.io/gorm"
type User struct {
Username string `json:"username" description:"用户名"`
Password string `json:"-" description:"密码"`
*Base
}
func (u *User) TableName() string {
return "user"
}
// Create 创建用户
func (u *User) Create(db *gorm.DB) error {
return db.Create(&u).Error
}
注意,因为我们不希望将敏感信息输出给用户,所以这里 Password 后面设置了 json:"-" ,这是在指示 JSON 解析器忽略字段 。后面接口返回用户数据时候,这个字段都会被隐藏。
然后,创建了一个 Create
方法,用于创建用户。
2、创建 dao 方法
首先,在 internal/app/dao
目录中创建 dao.go
,写入以下内容:
package dao
import "gorm.io/gorm"
type Dao struct {
engine *gorm.DB
}
func New(engine *gorm.DB) *Dao {
return &Dao{engine: engine}
}
然后,再在该目录下创建 user.go
文件,写入以下内容:
package dao
import (
"time"
"github.com/joker-bai/hawkeye/internal/app/models"
)
func (d *Dao) UserCreate(name, password string) error {
nowTime := uint32(time.Now().Unix())
user := models.User{
Username: name,
Password: password,
Base: &models.Base{
CreatedAt: nowTime,
ModifiedAt: nowTime,
IsDel: 0,
},
}
return user.Create(d.engine)
}
dao
层主要是对数据操作进行封装。
3、新建 services 方法
3.1、定义参数
用户注册,主要涉及以下参数:
{
"name": "joker",
"password": "secret",
"password_confirm": "secret"
}
在 internal/app/requests 目录中创建 user.go 文件,写入以下参数以及校验规则。
package requests
import (
"github.com/gin-gonic/gin"
"github.com/joker-bai/hawkeye/pkg/app"
"github.com/thedevsaddam/govalidator"
)
type UserCreateRequest struct {
Username string `json:"username" form:"username" valid:"username"`
Password string `json:"password" form:"password" valid:"password"`
PasswordConfirm string `json:"password_confirm" form:"password_confirm" valid:"password_confirm"`
}
func ValidUserCreateRequest(data interface{}, ctx *gin.Context) map[string][]string {
rules := govalidator.MapData{
"username": []string{"required", "not_exists:user,username"},
"password": []string{"required", "min:6"},
"password_confirm": []string{"required"},
}
messages := govalidator.MapData{
"username": []string{
"required: 用户名为必填字段,字段为 username",
},
"password": []string{
"required: 密码为必填字段,字段为 password",
"min:密码长度需大于 6",
},
"password_confirm": []string{
"required:确认密码框为必填项",
},
}
// 校验入参
errs := app.ValidateOptions(data, rules, messages)
// 验证两次密码是否一致
_data := data.(*UserCreateRequest)
errs = app.ValidatePasswordConfirm(_data.Password, _data.PasswordConfirm, errs)
return errs
}
现在,要自定义ValidatePasswordConfirm
验证器,用于验证两次密码是否一致。
在pkg/app/valid.go
中,增加以下代码即可:
// 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
}
上面还有一个not_exists
校验规则,用于校验用户名 name 是否已经存在,本身的校验规则里是没有这个字段的,所以需要自定义。
在 pkg/app/valid.go 中新增如下代码:
// 此方法会在初始化时执行,注册自定义表单验证规则
func init() {
// 自定义规则 not_exists,验证请求数据必须不存在于数据库中。
// 常用于保证数据库某个字段的值唯一,如用户名、邮箱、手机号、或者分类的名称。
// not_exists 参数可以有两种,一种是 2 个参数,一种是 3 个参数:
// not_exists:users,email 检查数据库表里是否存在同一条信息
// not_exists:users,email,32 排除用户掉 id 为 32 的用户
govalidator.AddCustomRule("not_exists", func(field string, rule string, message string, value interface{}) error {
rng := strings.Split(strings.TrimPrefix(rule, "not_exists:"), ",")
// 第一个参数,表名称,如 users
tableName := rng[0]
// 第二个参数,字段名称,如 email 或者 phone
dbFiled := rng[1]
// 第三个参数,排除 ID
var exceptID string
if len(rng) > 2 {
exceptID = rng[2]
}
// 用户请求过来的数据
requestValue := value.(string)
// 拼接 SQL
query := global.DB.Table(tableName).Where(dbFiled+" = ?", requestValue)
// 如果传参第三个参数,加上 SQL Where 过滤
if len(exceptID) > 0 {
query.Where("id != ?", exceptID)
}
// 查询数据库
var count int64
query.Count(&count)
// 验证不通过,数据库能找到对应的数据
if count != 0 {
// 如果有自定义错误消息的话
if message != "" {
return errors.New(message)
}
// 默认的错误消息
return fmt.Errorf("%v 已被占用", requestValue)
}
// 验证通过
return nil
})
}
3.2、新建 services 方法
在 internal/app/services
目录中新建services.go
,写入以下内容:
package services
import (
"context"
"github.com/joker-bai/kubemana/global"
"github.com/joker-bai/kubemana/internal/app/dao"
)
type Services struct {
ctx context.Context
dao *dao.Dao
}
func New(ctx context.Context) *Services {
return &Services{
ctx: ctx,
dao: dao.New(global.DB),
}
}
Services
会对Dao
进行一层封装,可以直接使用 Services.Dao
调用。
再在该目录下创建 user.go
文件,写入以下内容:
package services
import "github.com/joker-bai/hawkeye/internal/app/requests"
// UserCreate 创建用户
func (s *Services) UserCreate(param *requests.UserCreateRequest) error {
return s.dao.UserCreate(param.Username, param.Password)
}
4、新建 controllers 方法
4.1、新增错误代码
在 pkg/errorcode 目录中创建 user.go 文件,写入以下内容:
package errorcode
var (
ErrorUserCreateFail = NewError(200001, "创建用户失败")
ErrorUserDeleteFail = NewError(200002, "删除用户失败")
ErrorUserUpdateFail = NewError(200003, "更新用户失败")
ErrorUserListFail = NewError(200004, "列出用户失败")
)
4.2、新增控制器
在 internal/app/controllers/api/v1
目录中新增 user.go
文件,写入以下内容:
package v1
import (
"github.com/gin-gonic/gin"
"github.com/joker-bai/hawkeye/global"
"github.com/joker-bai/hawkeye/internal/app/requests"
"github.com/joker-bai/hawkeye/internal/app/services"
"github.com/joker-bai/hawkeye/pkg/app"
"github.com/joker-bai/hawkeye/pkg/errorcode"
"go.uber.org/zap"
)
type UserController struct{}
// Create godoc
// @Summary 创建用户
// @Description 创建用户
// @Tags 用户管理
// @Produce json
// @Param body body requests.UserCreateRequest true "body"
// @Success 200 {object} string "成功"
// @Failure 400 {object} errorcode.Error "请求错误"
// @Failure 500 {object} errorcode.Error "内部错误"
// @Router /api/v1/user/create [post]
func (u *UserController) Create(ctx *gin.Context) {
param := requests.UserCreateRequest{}
response := app.NewResponse(ctx)
if ok := app.Validate(ctx, ¶m, requests.ValidUserCreateRequest); !ok {
return
}
svc := services.New(ctx)
if err := svc.UserCreate(¶m); err != nil {
global.Log.Error("创建用户失败,", zap.String("error", err.Error()))
response.ToErrorResponse(errorcode.ErrorUserCreateFail)
return
}
response.ToResponse(gin.H{})
}
5、增加路由
在 internal/routers
中新增 auth.go
文件,写入以下内容:
package routers
import (
"github.com/gin-gonic/gin"
v1 "github.com/joker-bai/hawkeye/internal/app/controllers/api/v1"
)
type UserRouter struct{}
func (u *UserRouter) Inject(r *gin.RouterGroup) {
uc := new(v1.UserController)
r.POST("/user/create", uc.Create)
}
然后,在 initalize/router.go
中添加 AuthRouter
的路由,如下:
package initialize
import (
"github.com/gin-gonic/gin"
_ "github.com/joker-bai/hawkeye/docs"
"github.com/joker-bai/hawkeye/internal/app/routers"
swaggerFiles "github.com/swaggo/files"
ginSwagger "github.com/swaggo/gin-swagger"
)
type injector interface {
Inject(router *gin.RouterGroup)
}
func (s *Engine) injectRouterGroup(router *gin.RouterGroup) {
{
for _, r := range []injector{
new(routers.HelloWorldRouter),
} {
r.Inject(router.Group("/api"))
}
}
{
for _, r := range []injector{
new(routers.UserRouter),
} {
r.Inject(router.Group("/api/v1"))
}
}
// swagger
router.GET("/swagger/*any", ginSwagger.WrapHandler(swaggerFiles.Handler))
}
6、测试一下
首先,创建一个名叫 hawkeye
的数据库,命令如下:
CREATE DATABASE `hawkeye`
然后,再创建一个 user 表,命令如下:
CREATE TABLE `user` (
`id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '自增id',
`username` varchar(255) NOT NULL DEFAULT '' COMMENT '用户名',
`password` varchar(255) NOT NULL DEFAULT '' COMMENT '密码',
`created_at` int(10) unsigned default '0' comment '创建时间',
`modified_at` int(10) unsigned default '0' comment '修改时间',
`deleted_at` int(10) unsigned default '0' comment '删除时间',
`is_del` tinyint(3) unsigned default '0' comment '是否删除,0表示未删除,1表示删除',
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8 COMMENT='用户表';
再到 apifox 上新建测试接口,如下:
运行后无任何报错,表示成功,
日志如下:
也可以到数据库查看是否生成数据了,如下:
如果再次用这个用户名注册,则会返回失败,如下:
7、生成 swagger
上面代码开发没有问题之后,使用 swag init
生成接口文档。
8、代码版本
本节内容开发完成后,记得给代码标记,如下:
$ git add .
$ git commit -m "新增用户注册模块"