Skip to content

创建用户 原创

1、创建用户模型

首先,在 internal/app/models 中创建 base.go,定义基本的公共模型,如下:

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 文件,用于定义用户模型,如下:

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,写入以下内容:

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 文件,写入以下内容:

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、定义参数

用户注册,主要涉及以下参数:

json
{
  "name": "joker",
  "password": "secret",
  "password_confirm": "secret"
}

在 internal/app/requests 目录中创建 user.go 文件,写入以下参数以及校验规则。

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 中,增加以下代码即可:

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 中新增如下代码:

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,写入以下内容:

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 文件,写入以下内容:

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 文件,写入以下内容:

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 文件,写入以下内容:

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, &param, requests.ValidUserCreateRequest); !ok {
		return
	}

	svc := services.New(ctx)
	if err := svc.UserCreate(&param); err != nil {
		global.Log.Error("创建用户失败,", zap.String("error", err.Error()))
		response.ToErrorResponse(errorcode.ErrorUserCreateFail)
		return
	}
	response.ToResponse(gin.H{})
}

5、增加路由

internal/routers 中新增 auth.go 文件,写入以下内容:

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 的路由,如下:

go
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的数据库,命令如下:

sql
CREATE DATABASE `hawkeye`

然后,再创建一个 user 表,命令如下:

sql
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 上新建测试接口,如下:

ba3889ea6bf6b1b828f0bc8678a3423f MD5

运行后无任何报错,表示成功,

bf3463d635d33f7fba895553cfb7de00 MD5

日志如下:

e39ea27287d6d7444a93ac7370191536 MD5

也可以到数据库查看是否生成数据了,如下:

3844a3530a7634fe1cb01f9e298efd5a MD5

如果再次用这个用户名注册,则会返回失败,如下:

d2ae1e21b8b457b315d1a6b4fea2f2d6 MD5

7、生成 swagger

上面代码开发没有问题之后,使用 swag init 生成接口文档。

748a3711cbf23b2c8f2c78ea25bf85d3 MD5

8、代码版本

本节内容开发完成后,记得给代码标记,如下:

go
$ git add .
$ git commit -m "新增用户注册模块"
最近更新