最近公司可能用到微服务,同事们推荐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 | |
执行代码 | |
dir . | goctl.exe model mysql ddl -src .\user.sql -|
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 | |
├───user.go | |
└───internal | |
├───config | |
├───handler | |
├───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} |
到此本文结束