最近公司可能用到微服務,同事們推薦
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}
到此本文結束
本作品採用《CC 協議》,轉載必須註明作者和本文連結