go-zero使用gorm-gen实现了基本的单体服务的增删改查

目录

目的

数据库表设计

使用gorm gen

使用gorm gen测试

修改项目的api文件等配置

jwt用户登录

错误处理

创建用户

异常处理

目的

完成基于go-zero单体服务

尽量展示开发过程遇到的问题,并解决,不会为了排版就把问题提前,什么时候遇到什么时候展示解决。

数据库表设计

  1. 基于GoDockerDev启动mysql,redis

// 创建compose目录

mkdir dev_compose

// 下载GoDockerDev

git clone https://github.com/timzzx/GoDockerDev.git

cd GoDockerDev/

// 启动docker-compose

docker-compose up -d

// 显示

root@tdev:/home/code/dev_compose/GoDockerDev# docker-compose up -d

[+] Running 2/2

⠿ Container godockerdev-redis-1 Started 1.1s

⠿ Container godockerdev-mysql-1 Started 1.0s

创建bk数据库并且创建user表


CREATE  TABLE `user` (

`id`  bigint(20) unsigned zerofill NOT NULL AUTO_INCREMENT COMMENT '用户表主键',

`name`  varchar(128) NOT NULL COMMENT '用户名',

`password`  varchar(64) NOT NULL COMMENT '密码',

`status`  int(11) NOT NULL  DEFAULT  '1' COMMENT '是否有效1.有效 2.无效',

`ctime`  int(11) NOT NULL  DEFAULT  '0' COMMENT '创建时间',

`utime`  int(11) DEFAULT  NULL COMMENT '更新时间',

PRIMARY KEY (`id`)

) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='用户表'

使用gorm gen

安装生成


// 安装Gen Tool

go install gorm.io/gen/tools/gentool@latest

// 报错了

cgo: C compiler "gcc" not found: exec: "gcc": executable file not found in $PATH

// 安装Ubuntu开发包(省事)

sudo apt install build-essential

// 再次安装Gen Tool

go install gorm.io/gen/tools/gentool@latest

cd tapi/

mkdir bkmodel

// 生成gen

gentool -dsn "root:123456@tcp(192.168.1.13:3306)/bk?charset=utf8mb4&parseTime=True&loc=Local" -outPath "./bkmodel/dao/query"

// 更新依赖

go mod tidy

注意 -outPath “./bkmodel/dao/query” 能改变的只能是bkmodel这个目录,后面的是固定的。

测试表增加一个字段,增加一张表,重新执行命令后新的表和字段都会生成。bkmodel下所有文件都不要修改直接使用就好

创建makefile


# 命令

gen:

gentool -dsn "root:123456@tcp(192.168.1.13:3306)/bk?charset=utf8mb4&parseTime=True&loc=Local" -outPath "./bkmodel/dao/query"

make gen运行展示


root@tdev:/home/code/tapi# make gen

gentool -dsn "root:123456@tcp(192.168.1.13:3306)/bk?charset=utf8mb4&parseTime=True&loc=Local" -outPath "./bkmodel/dao/query"

2023/02/11 07:19:53 got 6 columns from table <user>

2023/02/11 07:19:53 Start generating code.

2023/02/11 07:19:53 generate model file(table <user> -> {model.User}): /home/code/tapi/bkmodel/dao/model/user.gen.go

2023/02/11 07:19:53 generate query file: /home/code/tapi/bkmodel/dao/query/user.gen.go

2023/02/11 07:19:53 generate query file: /home/code/tapi/bkmodel/dao/query/gen.go

2023/02/11 07:19:53 Generate code done.

时区有问题改一下


sudo timedatectl set-timezone Asia/Shanghai

// 检查

root@tdev:/home/code/tapi# sudo timedatectl set-timezone Asia/Shanghai

root@tdev:/home/code/tapi# timedatectl

Local time: Sat 2023-02-11 15:31:07 CST

Universal time: Sat 2023-02-11 07:31:07 UTC

RTC time: Sat 2023-02-11 07:31:07

Time zone: Asia/Shanghai (CST, +0800)

System clock synchronized: yes

NTP service: active

RTC in local TZ: no

go-zero引入gorm gen

说明一下,go-zero的orm封装的比较简单,虽然带cahce的封装,不过这个功能对于后台来说不需要,反而后台涉及统计sql比较复杂,所以改用gorm。

增加make命令,编辑makefile


gen_api:

goctl api go -api project.api -dir ./

dev:

go run user.go -f etc/user.yaml

运行项目看看


go run user.go -f etc/user.yaml

Starting server at 0.0.0.0:8888...

增加Mysql配置,修改etc/user.yaml


Name: User

Host: 0.0.0.0

Port: 8888

Mysql:

DataSource: root:123456@tcp(192.168.1.13:3306)/bk?charset=utf8mb4&parseTime=true&loc=Asia%2FShanghai

修改tapi/internal/config/config.go


package config

import  "github.com/zeromicro/go-zero/rest"

type  Config  struct {

rest.RestConf

Mysql struct {

DataSource string

}

}

修改tapi/internal/svc/servicecontext.go


package svc

import (

"tapi/bkmodel/dao/query"

"tapi/internal/config"

"gorm.io/driver/mysql"

"gorm.io/gorm"

)

type  ServiceContext  struct {

Config config.Config

BkModel *query.Query

}

func  NewServiceContext(c config.Config) *ServiceContext {

db, _ := gorm.Open(mysql.Open(c.Mysql.DataSource), &gorm.Config{})

return &ServiceContext{

Config: c,

BkModel: query.Use(db),

}

}

修改tapi/internal/logic/loginlogic.go


package logic

import (

"context"

"tapi/internal/svc"

"tapi/internal/types"

"github.com/zeromicro/go-zero/core/logx"

)

type  LoginLogic  struct {

logx.Logger

ctx context.Context

svcCtx *svc.ServiceContext

}

func  NewLoginLogic(ctx context.Context, svcCtx *svc.ServiceContext) *LoginLogic {

return &LoginLogic{

Logger: logx.WithContext(ctx),

ctx: ctx,

svcCtx: svcCtx,

}

}

func (l *LoginLogic) Login(req *types.LoginRequest) (resp *types.LoginResponse, err error) {

// todo: add your logic here and delete this line

table := l.svcCtx.BkModel.User

user, err := table.WithContext(l.ctx).Where(table.Name.Eq(req.Name)).Debug().First()

if err != nil {

return &types.LoginResponse{

Code: 500,

Msg: err.Error(),

}, nil

}

return &types.LoginResponse{

Code: 200,

Msg: user.Name,

}, nil

}

go-zero引入gorm gen测试

插入一条数据


INSERT INTO  `user`  VALUES ('1', 'tim', '123456', '1', '0', '0');

运行项目


make dev

// 返回

go run user.go -f etc/user.yaml

Starting server at 0.0.0.0:8888...

打开postman访问

name 发送tim11 是个错误的,所以返回没有记录。

正确返回

debug选项要注意一下


// debug在这里,去掉就不会输出sql语句注意一下

user, err := table.WithContext(l.ctx).Where(table.Name.Eq(req.Name)).Debug().First()

// console 输出正确的信息

2023/02/11 17:15:07 /home/code/tapi/bkmodel/dao/query/user.gen.go:234

[0.784ms] [rows:1] SELECT * FROM `user` WHERE `user`.`name` = 'tim' ORDER BY `user`.`id` LIMIT 1

// console 输出错误的信息

2023/02/11 17:11:32 /home/code/tapi/bkmodel/dao/query/user.gen.go:234 record not found

[0.908ms] [rows:0] SELECT * FROM `user` WHERE `user`.`name` = 'tim11' ORDER BY `user`.`id` LIMIT 1

修改项目的api文件等配置

tapi项目的启动go文件为user.go,不是很合理。所以处理一下。

修改project.api


type (

LoginRequest {

Name string  `form:"name"`

Password string  `form:"password"`

}

LoginResponse {

Code int64  `json:"code"`

Msg string  `json:"msg"`

}

)

service Backend {

@handler Login

post /api/user/login(LoginRequest) returns (LoginResponse)

}

修改makefile


# 命令

help:

@echo 'Usage:'

@echo ' db 生成sql执行代码'

@echo ' api 根据api文件生成go-zero api代码'

@echo ' dev 运行'

db:

gentool -dsn 'root:123456@tcp(192.168.1.13:3306)/bk?charset=utf8mb4&parseTime=True&loc=Local' -outPath './bkmodel/dao/query'

api:

goctl api go -api project.api -dir ./ -style gozero

dev:

go run backend.go -f etc/backend.yaml

删除

user.go

/etc/user.api

internal/handle/下所有的

internal/logic/所有

执行 make api

重新生成好后

目前项目目录


root@tdev:/home/code/tapi# tree

.

├── backend.go

├── bkmodel

│ └── dao

│ ├── model

│ │ └── user.gen.go

│ └── query

│ ├── gen.go

│ └── user.gen.go

├── etc

│ ├── backend.yaml

├── go.mod

├── go.sum

├── internal

│ ├── config

│ │ └── config.go

│ ├── handler

│ │ ├── loginhandler.go

│ │ └── routes.go

│ ├── logic

│ │ └── loginlogic.go

│ ├── svc

│ │ └── servicecontext.go

│ └── types

│ └── types.go

├── makefile

└── project.api

11 directories, 16 files

jwt用户登录

修改etc/backend.yaml


Name: Backend

Host: 0.0.0.0

Port: 8888

Mysql:

DataSource: root:123456@tcp(192.168.1.13:3306)/bk?charset=utf8mb4&parseTime=true&loc=Asia%2FShanghai

# 增加jwt参数

Auth:

AccessSecret: uOvKLmVfztaXGpNYd4Z0I1SiT7MweJhl

AccessExpire: 86400

修改config/config.go


package config

import  "github.com/zeromicro/go-zero/rest"

type  Config  struct {

rest.RestConf

// 增加jwt验证

Auth struct {

AccessSecret string

AccessExpire int64

}

Mysql struct {

DataSource string

}

}

修改 backend.api


syntax = "v1"

info(

title: "tapi"

desc: "接口"

author: "tim"

version: 1.0

)

type (

LoginRequest {

Name string  `form:"name"`

Password string  `form:"password"`

}

LoginResponse {

Code int64  `json:"code"`

Msg string  `json:"msg"`

Token string  `json:"token,optional"`

}

UserInfo {

Id int64  `json:"id"`

Name string  `json:"name"`

Ctime int64  `json:"ctime"`

Utime int64  `json:"utime"`

}

UserInfoRequest {

}

UserInfoResponse {

Code int64  `json:"code"`

Msg string  `json:"msg"`

Data UserInfo `json:"data,optional"`

}

)

service Backend {

@handler Login

post /api/login(LoginRequest) returns (LoginResponse)

}

@server(

jwt: Auth // 开启auth验证

)

service Backend {

@handler UserInfo

post /api/user/info(UserInfoRequest) returns (UserInfoResponse)

}

执行 make api 生成代码

创建目录和文件common/jwtx/jwt.go


package jwtx

import  "github.com/golang-jwt/jwt/v4"

func  GetToken(secretKey string, iat, seconds, uid int64) (string, error) {

claims := make(jwt.MapClaims)

claims["exp"] = iat + seconds

claims["iat"] = iat

claims["uid"] = uid

token := jwt.New(jwt.SigningMethodHS256)

token.Claims = claims

return token.SignedString([]byte(secretKey))

}

修改internal/logic/loginlogic.go


package logic

import (

"context"

"time"

"tapi/common/jwtx"

"tapi/internal/svc"

"tapi/internal/types"

"github.com/zeromicro/go-zero/core/logx"

)

type  LoginLogic  struct {

logx.Logger

ctx context.Context

svcCtx *svc.ServiceContext

}

func  NewLoginLogic(ctx context.Context, svcCtx *svc.ServiceContext) *LoginLogic {

return &LoginLogic{

Logger: logx.WithContext(ctx),

ctx: ctx,

svcCtx: svcCtx,

}

}

func (l *LoginLogic) Login(req *types.LoginRequest) (resp *types.LoginResponse, err error) {

// user表

table := l.svcCtx.BkModel.User

// 查询用户

user, err := table.WithContext(l.ctx).Where(table.Name.Eq(req.Name)).Debug().First()

if err != nil {

return &types.LoginResponse{

Code: 500,

Msg: err.Error(),

}, nil

}

// 判断密码是否正确

if user.Password != req.Password {

return &types.LoginResponse{

Code: 500,

Msg: "密码错误",

}, nil

}

// 获取accessToken

now := time.Now().Unix()

accessExpire := l.svcCtx.Config.Auth.AccessExpire

accessToken, err := jwtx.GetToken(l.svcCtx.Config.Auth.AccessSecret, now, accessExpire, user.ID)

if err != nil {

return &types.LoginResponse{

Code: 500,

Msg: err.Error(),

}, nil

}

return &types.LoginResponse{

Code: 200,

Token: accessToken,

Msg: "成功",

}, nil

}

测试访问一下

修改internal/logic/userinfologic.go


package logic

import (

"context"

"encoding/json"

"tapi/internal/svc"

"tapi/internal/types"

"github.com/zeromicro/go-zero/core/logx"

)

type  UserInfoLogic  struct {

logx.Logger

ctx context.Context

svcCtx *svc.ServiceContext

}

func  NewUserInfoLogic(ctx context.Context, svcCtx *svc.ServiceContext) *UserInfoLogic {

return &UserInfoLogic{

Logger: logx.WithContext(ctx),

ctx: ctx,

svcCtx: svcCtx,

}

}

func (l *UserInfoLogic) UserInfo(req *types.UserInfoRequest) (resp *types.UserInfoResponse, err error) {

// 获取token中的uid,具体自行查看go-zero的文档和源码,access的验证框架已经实现,我们只需要配置Auth的对应参数

uid, _ := l.ctx.Value("uid").(json.Number).Int64()

table := l.svcCtx.BkModel.User

user, err := table.WithContext(l.ctx).Where(table.ID.Eq(uid)).First()

if err != nil {

return &types.UserInfoResponse{

Code: 500,

Msg: err.Error(),

}, nil

}

return &types.UserInfoResponse{

Code: 200,

Msg: "成功",

Data: types.UserInfo{

Id: user.ID,

Name: user.Name,

Ctime: int64(user.Ctime),

Utime: int64(user.Utime),

},

}, nil

}

测试

错误处理

编辑product.api


syntax = "v1"

info(

title: "tapi"

desc: "接口"

author: "tim"

version: 1.0

)

type (

// 错误 增加这个结构

CodeErrorResponse {

Code int64  `json:"code"`

Msg string  `json:"msg"`

}

// 登录请求

LoginRequest {

Name string  `form:"name"`

Password string  `form:"password"`

}

// 登录返回

LoginResponse {

Code int64  `json:"code"`

Msg string  `json:"msg"`

Token string  `json:"token,optional"`

}

// 用户数据

UserInfo {

Id int64  `json:"id"`

Name string  `json:"name"`

Ctime int64  `json:"ctime"`

Utime int64  `json:"utime"`

}

UserInfoRequest {

}

UserInfoResponse {

Code int64  `json:"code"`

Msg string  `json:"msg"`

Data UserInfo `json:"data,optional"`

}

)

service Backend {

@handler Login

post /api/login(LoginRequest) returns (LoginResponse)

}

@server(

jwt: Auth // 开启auth验证

)

service Backend {

@handler UserInfo

post /api/user/info(UserInfoRequest) returns (UserInfoResponse)

}

运行 make api 生成代码

修改 backend.go


package main

import (

"context"

"flag"

"fmt"

"net/http"

"tapi/internal/config"

"tapi/internal/handler"

"tapi/internal/svc"

"tapi/internal/types"

"github.com/zeromicro/go-zero/core/conf"

"github.com/zeromicro/go-zero/rest"

"github.com/zeromicro/go-zero/rest/httpx"

)

var  configFile = flag.String("f", "etc/backend.yaml", "the config file")

func  main() {

flag.Parse()

var  c config.Config

conf.MustLoad(*configFile, &c)

server := rest.MustNewServer(c.RestConf)

defer server.Stop()

ctx := svc.NewServiceContext(c)

handler.RegisterHandlers(server, ctx)

// 全局错误处理 增加这段代码

httpx.SetErrorHandlerCtx(func(ctx context.Context, err error) (int, interface{}) {

fmt.Println(err.Error())

return http.StatusOK, &types.CodeErrorResponse{

Code: 500,

Msg: err.Error(),

}

})

fmt.Printf("Starting server at %s:%d...\n", c.Host, c.Port)

server.Start()

}

启动服务 make dev

go-zero的自定义错误,有点麻烦,后台暂时用不上,这里就用最简单的处理方式。处理方式自行决定。

创建用户

修改 project.api


syntax = "v1"

info(

title: "tapi"

desc: "接口"

author: "tim"

version: 1.0

)

type (

// 错误

CodeErrorResponse {

Code int64  `json:"code"`

Msg string  `json:"msg"`

}

// 登录请求

LoginRequest {

Name string  `form:"name"`

Password string  `form:"password"`

}

// 登录返回

LoginResponse {

Code int64  `json:"code"`

Msg string  `json:"msg"`

Token string  `json:"token,optional"`

}

// 用户数据

UserInfo {

Id int64  `json:"id"`

Name string  `json:"name"`

Ctime int64  `json:"ctime"`

Utime int64  `json:"utime"`

}

UserInfoRequest {

}

UserInfoResponse {

Code int64  `json:"code"`

Msg string  `json:"msg"`

Data UserInfo `json:"data,optional"`

}

// 创建用户

UserAddRequest {

Name string  `form:"name"`

Password string  `form:"password"`

}

UserAddResponse {

Code int64  `json:"code"`

Msg string  `json:"msg"`

}

)

service Backend {

// 登录

@handler Login

post /api/login(LoginRequest) returns (LoginResponse)

}

@server(

jwt: Auth // 开启auth验证

)

service Backend {

// 用户信息

@handler UserInfo

post /api/user/info(UserInfoRequest) returns (UserInfoResponse)

// 创建用户

@handler UserAdd

post /api/user/add(UserAddRequest) returns (UserAddResponse)

}

运行 make api

修改 internal/logic/useraddlogic.go


package logic

import (

"context"

"time"

"tapi/bkmodel/dao/model"

"tapi/internal/svc"

"tapi/internal/types"

"github.com/zeromicro/go-zero/core/logx"

)

type  UserAddLogic  struct {

logx.Logger

ctx context.Context

svcCtx *svc.ServiceContext

}

func  NewUserAddLogic(ctx context.Context, svcCtx *svc.ServiceContext) *UserAddLogic {

return &UserAddLogic{

Logger: logx.WithContext(ctx),

ctx: ctx,

svcCtx: svcCtx,

}

}

func (l *UserAddLogic) UserAdd(req *types.UserAddRequest) (resp *types.UserAddResponse, err error) {

table := l.svcCtx.BkModel.User

// 查询用户是否存在

u, err := table.WithContext(l.ctx).Where(table.Name.Eq(req.Name)).First()

if err != nil {

return &types.UserAddResponse{

Code: 500,

Msg: err.Error(),

}, nil

}

if u.Name == req.Name {

return &types.UserAddResponse{

Code: 500,

Msg: "用户已存在",

}, nil

}

// 新建用户

currTime := time.Now().Unix()

user := model.User{

Name: req.Name,

Password: req.Password,

Status: 1,

Utime: int32(currTime),

Ctime: int32(currTime),

}

err = table.WithContext(l.ctx).Create(&user)

if err != nil {

return &types.UserAddResponse{

Code: 500,

Msg: err.Error(),

}, nil

}

return &types.UserAddResponse{

Code: 200,

Msg: "成功",

}, nil

}

运行 make dev

测试

上面代码测试返回为空数据,dlv调试发现

if u.Name == req.Name

这句中u为nil

修改成

if u != nil && u.Name == req.Name

重新运行 make dev 再测试OK

异常处理

上面创建用户出现了异常,go-zero捕获了,改变了http的code,这种方式对于接口不是很合理,所以改造了rcover中间件

修改backend.go


package main

import (

"context"

"flag"

"fmt"

"log"

"net/http"

"runtime/debug"

"tapi/internal/config"

"tapi/internal/handler"

"tapi/internal/svc"

"tapi/internal/types"

"github.com/zeromicro/go-zero/core/conf"

"github.com/zeromicro/go-zero/rest"

"github.com/zeromicro/go-zero/rest/httpx"

)

var  configFile = flag.String("f", "etc/backend.yaml", "the config file")

func  main() {

flag.Parse()

var  c config.Config

conf.MustLoad(*configFile, &c)

server := rest.MustNewServer(c.RestConf)

defer server.Stop()

ctx := svc.NewServiceContext(c)

handler.RegisterHandlers(server, ctx)

httpx.SetErrorHandlerCtx(func(ctx context.Context, err error) (int, interface{}) {

fmt.Println(err.Error())

return http.StatusOK, &types.CodeErrorResponse{

Code: 500,

Msg: err.Error(),

}

})

// 全局recover中间件

server.Use(func(next http.HandlerFunc) http.HandlerFunc {

return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {

defer  func() {

if  result := recover(); result != nil {

log.Println(fmt.Sprintf("%v\n%s", result, debug.Stack()))

httpx.OkJson(w, &types.CodeErrorResponse{

Code: 500,

Msg: "服务器错误", //string(debug.Stack()),

})

}

}()

next.ServeHTTP(w, r)

})

})

fmt.Printf("Starting server at %s:%d...\n", c.Host, c.Port)

server.Start()

}

上面改造的方式参考了go-zero/rest/handler/recoverhandler.go,这种改造方式不确定是否正确,暂时先解决了遇到的问题,后续再研究go-zero源码

本作品采用《CC 协议》,转载必须注明作者和本文链接
讨论数量: 0
(= ̄ω ̄=)··· 暂无内容!

讨论应以学习和精进为目的。请勿发布不友善或者负能量的内容,与人为善,比聪明更重要!