最近公司可能用到微服务,同事们推荐go-zero
,然后自己实践操作了下。记录下来学习过程。
关于go-zero
介绍,请看这里,不多介绍了。
微服务是将一个大系统拆分成多个子系统,每个子系统拥有独立的存储,如用户系统,订单系统,商品管理系统等等。
这里我们只测试下用户系统和订单系统
service # 服务目录
└───user # 子系统
├───api # http服务访问,业务实现
└───rpc # rpc服务,为其它子系统提供数据访问
└───model # model访问持久化数据层,生成CURD
model生成代码
创建sql
CREATE TABLE `user` (
`id` BIGINT NOT NULL AUTO_INCREMENT,
`name` VARCHAR(255) NOT NULL DEFAULT '' COMMENT '用户名称',
`password` VARCHAR(255) NOT NULL DEFAULT '' COMMENT '用户密码',
`age` TINYINT(3) NOT NULL DEFAULT 0 COMMENT '年龄',
`gender` CHAR(5) NOT NULL COMMENT '男|女|未公开',
`create_time` TIMESTAMP NULL DEFAULT CURRENT_TIMESTAMP,
`update_time` TIMESTAMP NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
UNIQUE KEY `name_unique` (`name`)
) ENGINE=INNODB DEFAULT CHARSET=utf8mb4 ;
生成代码
官网上有三种生成方式,这里只用到第一种。
# 进入目录
> cd .\service\user\model
# 执行代码
> goctl.exe model mysql ddl -src .\user.sql -dir .
Done.
命令详解
goctl.exe 命令名称
model 生成model代码
mysql 数据库类型
ddl 指定数据源为ddl文件生成model代码
src 指定源
user.sql sql文件
dir 指定生成目录
. 当前目录
执行后,生成两个文件usermodel.go
, vars.go
注意:如果代码中存在包无法加载的情况,请到项目根目录执行以下命令
# 初始化
❯ go mod init
go: creating new go.mod: module go-zero-demo1
go: to add module requirements and sums:
go mod tidy
# 加载依赖
❯ go mod tidy
go: finding module for package github.com/tal-tech/go-zero/tools/goctl/model/sql/builderx
go: finding module for package github.com/tal-tech/go-zero/core/stores/sqlc
go: finding module for package github.com/tal-tech/go-zero/core/stores/sqlx
go: finding module for package github.com/tal-tech/go-zero/core/stringx
go: found github.com/tal-tech/go-zero/core/stores/sqlc in github.com/tal-tech/go-zero v1.2.1
go: found github.com/tal-tech/go-zero/core/stores/sqlx in github.com/tal-tech/go-zero v1.2.1
go: found github.com/tal-tech/go-zero/core/stringx in github.com/tal-tech/go-zero v1.2.1
go: found github.com/tal-tech/go-zero/tools/goctl/model/sql/builderx in github.com/tal-tech/go-zero v1.2.1
接口编写
编写api
> vim .\service\user\api\user.api
syntax = "v1"
info(
title: "用户管理"
desc: "用户管理"
author: "charlie"
email: "cenhuqing@163.com"
version: "1.0"
)
type (
RegisterReq {
Username string `json:"username"`
Password string `json:"password"`
Age int `json:"age"`
Gender string `json:"gender"`
}
RegisterResp {
Msg string `json:"msg"`
}
LoginReq {
Username string `json:"username"`
Password string `json:"password"`
}
LoginResp {
Username string `json:"Username"`
Age int `json:"age"`
Gender string `json:"gender"`
}
)
// 用户接口
service user-api {
// 注册
@handler signIn
// 请求方式, 路由地址, (请求数据), (响应数据)
post /user/register (RegisterReq) returns (RegisterResp)
// 登录
@handler getUser
post /user/login (LoginReq) returns(LoginResp)
}
生成接口代码
❯ goctl.exe api go -api user.api -dir .
Done.
命令详解
goctl.exe 命令名称
api 生成api代码
go 文件类型
api 指定源
user.api api文件
dir 指定生成目录
. 当前目录
生成的目录结构
├───etc # 配置文件目录
├───user.api # api描述文件
├───user.go # main函数入口文件
└───internal
├───config # 声明配置类型
├───handler # 路由和handle转发
├───logic # 业务逻辑
├───svc # 依赖资源池
└───types # 请求和响应的结构体,自动生成。不建议编辑
编写逻辑
添加yaml配置
> vim .\user\api\etc\user-api.yaml
Name: user-api
Host: 0.0.0.0
Port: 8888
Mysql:
# 用户名:密码@协议(IP:PORT)/DBNAME?...
DataSource: root:root@tcp(172.15.0.11:3306)/go_zero?charset=utf8mb4&parseTime=true&loc=Asia%2FShanghai
CacheRedis:
- Host: 172.15.0.11:6379
Pass: root
Type: node
添加配置声明
> vim .\user\api\internal\config\config.go
package config
import (
"github.com/tal-tech/go-zero/core/stores/cache"
"github.com/tal-tech/go-zero/rest"
)
type Config struct {
rest.RestConf
Mysql struct{
DataSource string
}
CacheRedis cache.CacheConf
}
完善服务依赖
> vim .\user\api\internal\svc\servicecontext.go
package svc
import (
"github.com/tal-tech/go-zero/core/stores/sqlx"
"go-zero-demo1/service/user/api/internal/config"
"go-zero-demo1/service/user/model"
)
type ServiceContext struct {
Config config.Config
UserModel model.UserModel
}
func NewServiceContext(c config.Config) *ServiceContext {
# 数据库连接
conn := sqlx.NewMysql(c.Mysql.DataSource)
return &ServiceContext{
Config: c,
UserModel: model.NewUserModel(conn),
}
}
填充注册业务逻辑
> vim .\user\api\internal\logic\signinlogic.go
package logic
import (
"context"
"errors"
"go-zero-demo1/service/user/model"
"strings"
"time"
"go-zero-demo1/service/user/api/internal/svc"
"go-zero-demo1/service/user/api/internal/types"
"github.com/tal-tech/go-zero/core/logx"
)
type SignInLogic struct {
logx.Logger
ctx context.Context
svcCtx *svc.ServiceContext
}
func NewSignInLogic(ctx context.Context, svcCtx *svc.ServiceContext) SignInLogic {
return SignInLogic{
Logger: logx.WithContext(ctx),
ctx: ctx,
svcCtx: svcCtx,
}
}
func (l *SignInLogic) SignIn(req types.RegisterReq) (*types.RegisterResp, error) {
// todo: add your logic here and delete this line
if len(strings.TrimSpace(req.Username)) == 0 || len(strings.TrimSpace(req.Password)) == 0 {
return nil, errors.New("用户名和密码不为空")
}
// 插入数据
_, err = l.svcCtx.UserModel.Insert(model.User{
Name: req.Username,
Password: req.Password,
Age: int64(req.Age),
Gender: req.Gender,
CreateTime: time.Now(),
UpdateTime: time.Now(),
})
if err != nil {
return nil, err
}
return &types.RegisterResp{
Msg: "用户注册成功",
}, nil
}
启动服务
❯ go run user.go -f .\etc\user-api.yaml
Starting server at 0.0.0.0:8888...
注册登录
❯ curl -i -X POST http://localhost:8888/user/register -H 'content-type: application/json' -d '{"username":"charlie", "password":"123456","age":30,"gender":"男"}'
HTTP/1.1 200 OK
Content-Type: application/json
X-Trace-Id: d054b56b5e3b06b5
Date: Tue, 12 Oct 2021 06:06:34 GMT
Content-Length: 28
{"msg":"用户注册成功"}
下面就可以写登录业务了,这里登录用到jwt
,所以先需要生成token
jwt认证
介绍
JSON Web令牌(JWT)是一个开放标准(RFC 7519),它定义了一种紧凑而独立的方法,用于在各方之间安全地将信息作为JSON对象传输。由于此信息是经过数字签名的,因此可以被验证和信任。可以使用秘密(使用HMAC算法)或使用RSA或ECDSA的公钥/私钥对对JWT进行签名。
注意:关于详细的介绍,可以看官网
配置参数
vim .\user\api\etc\user-api.yaml
Jwt:
AccessExpire: 3600 # 生成token的有效期,时间单位为秒
AccessSecret: charlie # 生成token的密钥
声明参数类型
vim .\user\api\internal\config\config.go
package config
import (
"github.com/tal-tech/go-zero/core/stores/cache"
"github.com/tal-tech/go-zero/rest"
)
type Config struct {
rest.RestConf
Mysql struct{
DataSource string
}
CacheRedis cache.CacheConf
Jwt struct{
AccessExpire string
AccessSecret string
}
}
修改api接口
登录需要获取token,所以返回需要添加token信息
> .\user\api\user.api
LoginResp {
Username string `json:"Username"`
Age int `json:"age"`
Gender string `json:"gender"`
Token string `json:"token"`
ExpireTime int64 `json:"expire_time"`
RefreshAfter int64 `json:"refreshAfter"`
}
重新生成代码
❯ goctl.exe api go -api user.api -dir .
etc/user-api.yaml exists, ignored generation
internal/config/config.go exists, ignored generation
user.go exists, ignored generation
internal/svc/servicecontext.go exists, ignored generation
internal/handler/signinhandler.go exists, ignored generation
internal/handler/getuserhandler.go exists, ignored generation
internal/logic/signinlogic.go exists, ignored generation
internal/logic/getuserlogic.go exists, ignored generation
Done.
填充登录业务逻辑
> vim .\user\api\internal\logic\getuserlogic.go
package logic
import (
"context"
"errors"
"fmt"
"github.com/dgrijalva/jwt-go"
"github.com/tal-tech/go-zero/core/stores/sqlx"
"strings"
"time"
"go-zero-demo1/service/user/api/internal/svc"
"go-zero-demo1/service/user/api/internal/types"
"github.com/tal-tech/go-zero/core/logx"
)
type GetUserLogic struct {
logx.Logger
ctx context.Context
svcCtx *svc.ServiceContext
}
func NewGetUserLogic(ctx context.Context, svcCtx *svc.ServiceContext) GetUserLogic {
return GetUserLogic{
Logger: logx.WithContext(ctx),
ctx: ctx,
svcCtx: svcCtx,
}
}
func (l *GetUserLogic) GetUser(req types.LoginReq) (*types.LoginResp, error) {
// todo: add your logic here and delete this line
if len(strings.TrimSpace(req.Username)) == 0 || len(strings.TrimSpace(req.Password)) == 0 {
return nil, errors.New("用户名和密码不为空")
}
userInfo, err := l.svcCtx.UserModel.FindOneByName(req.Username)
fmt.Println(userInfo)
switch err {
case nil:
case sqlx.ErrNotFound:
return nil, errors.New("用户不存在")
default:
return nil, err
}
if req.Password != userInfo.Password {
return nil, errors.New("密码错误")
}
// jwt
now := time.Now().Unix()
accessExpire := l.svcCtx.Config.Jwt.AccessExpire
accessSecret := l.svcCtx.Config.Jwt.AccessSecret
token, err := l.getJwtToken(accessSecret,
now,
accessExpire,
userInfo.Id)
if err != nil {
return nil, err
}
return &types.LoginResp{
Username: userInfo.Name,
Age: int(userInfo.Age),
Gender: userInfo.Gender,
Token: token,
ExpireTime: now + accessExpire,
RefreshAfter: now + accessExpire / 2,
}, nil
}
// 获取token
func (l *GetUserLogic) getJwtToken(key string, iat, seconds, userId int64) (string, error) {
claims := make(jwt.MapClaims)
claims["exp"] = iat + seconds
claims["iat"] = iat
claims["userId"] = userId
token := jwt.New(jwt.SigningMethodHS256)
token.Claims = claims
return token.SignedString([]byte(key))
}
登录
❯ curl -i -X POST http://localhost:8888/user/login -H 'content-type: application/json' -d '{"username":"charlie", "password":"123456"}'
HTTP/1.1 200 OK
Content-Type: application/json
X-Trace-Id: 6379fd19805b0101
Date: Wed, 13 Oct 2021 02:07:07 GMT
Content-Length: 251
{"Username":"charlie","age":30,"gender":"男","token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE2MzQwOTQ0MjcsImlhdCI6MTYzNDA5MDgyNywidXNlcklkIjoxfQ.kwskqpz_VLZmyU_XDJ2C68xjOI0lsGyjmUWf3YYDKuA","expire_time":1634094427,"refreshAfter":1634092627}
到此本文结束