帶你十天輕松搞定 Go 微服務(wù)系列(六)
序言
我們通過一個(gè)系列文章跟大家詳細(xì)展示一個(gè) go-zero 微服務(wù)示例,整個(gè)系列分十篇文章,目錄結(jié)構(gòu)如下:
環(huán)境搭建:帶你十天輕松搞定 Go 微服務(wù)系列(一) 服務(wù)拆分:帶你十天輕松搞定 Go 微服務(wù)系列(二) 用戶服務(wù):帶你十天輕松搞定 Go 微服務(wù)系列(三) 產(chǎn)品服務(wù):帶你十天輕松搞定 Go 微服務(wù)系列(四) 訂單服務(wù):帶你十天輕松搞定 Go 微服務(wù)系列(五) 支付服務(wù)(本文) RPC 服務(wù) Auth 驗(yàn)證 服務(wù)監(jiān)控 鏈路追蹤 分布式事務(wù)
期望通過本系列帶你在本機(jī)利用 Docker 環(huán)境利用 go-zero 快速開發(fā)一個(gè)商城系統(tǒng),讓你快速上手微服務(wù)。
完整示例代碼:https://github.com/nivin-studio/go-zero-mall
首先,我們來(lái)看一下整體的服務(wù)拆分圖:

6 支付服務(wù)(pay)
進(jìn)入服務(wù)工作區(qū)
$?cd?mall/service/pay
6.1 生成 pay model 模型
創(chuàng)建 sql 文件
$?vim?model/pay.sql
編寫 sql 文件
CREATE?TABLE?`pay`?(
?`id`?bigint?unsigned?NOT?NULL?AUTO_INCREMENT,
?`uid`?bigint?unsigned?NOT?NULL?DEFAULT?'0'?COMMENT?'用戶ID',
?`oid`?bigint?unsigned?NOT?NULL?DEFAULT?'0'?COMMENT?'訂單ID',
?`amount`?int(10)?unsigned?NOT?NULL?DEFAULT?'0'??COMMENT?'產(chǎn)品金額',
?`source`?tinyint(3)?unsigned?NOT?NULL?DEFAULT?'0'?COMMENT?'支付方式',
?`status`?tinyint(3)?unsigned?NOT?NULL?DEFAULT?'0'?COMMENT?'支付狀態(tài)',
?`create_time`?timestamp?NULL?DEFAULT?CURRENT_TIMESTAMP,
?`update_time`?timestamp?NULL?DEFAULT?CURRENT_TIMESTAMP?ON?UPDATE?CURRENT_TIMESTAMP,
?PRIMARY?KEY?(`id`),
?KEY?`idx_uid`?(`uid`),
?KEY?`idx_oid`?(`oid`)
)?ENGINE=InnoDB??DEFAULT?CHARSET=utf8mb4;
運(yùn)行模板生成命令
$?goctl?model?mysql?ddl?-src?./model/pay.sql?-dir?./model?-c
6.2 生成 pay api 服務(wù)
創(chuàng)建 api 文件
$?vim?api/pay.api
編寫 api 文件
type?(
?//?支付創(chuàng)建
?CreateRequest?{
??Uid????int64?`json:"uid"`
??Oid????int64?`json:"oid"`
??Amount?int64?`json:"amount"`
?}
?CreateResponse?{
??Id?int64?`json:"id"`
?}
?//?支付創(chuàng)建
?//?支付詳情
?DetailRequest?{
??Id?int64?`json:"id"`
?}
?DetailResponse?{
??Id?????int64?`json:"id"`
??Uid????int64?`json:"uid"`
??Oid????int64?`json:"oid"`
??Amount?int64?`json:"amount"`
??Source?int64?`json:"source"`
??Status?int64?`json:"status"`
?}
?//?支付詳情
?//?支付回調(diào)
?CallbackRequest?{
??Id?????int64?`json:"id"`
??Uid????int64?`json:"uid"`
??Oid????int64?`json:"oid"`
??Amount?int64?`json:"amount"`
??Source?int64?`json:"source"`
??Status?int64?`json:"status"`
?}
?CallbackResponse?{
?}
?//?支付回調(diào)
)
@server(
?jwt:?Auth
)
service?Pay?{
?@handler?Create
?post?/api/pay/create(CreateRequest)?returns?(CreateResponse)
?
?@handler?Detail
?post?/api/pay/detail(DetailRequest)?returns?(DetailResponse)
?
?@handler?Callback
?post?/api/pay/callback(CallbackRequest)?returns?(CallbackResponse)
}
運(yùn)行模板生成命令
$?goctl?api?go?-api?./api/pay.api?-dir?./api
6.3 生成 pay rpc 服務(wù)
創(chuàng)建 proto 文件
$?vim?rpc/pay.proto
編寫 proto 文件
syntax = "proto3";
package payclient;
option go_package = "pay";
// 支付創(chuàng)建
message CreateRequest {
int64 Uid = 1;
int64 Oid = 2;
int64 Amount = 3;
}
message CreateResponse {
int64 id = 1;
}
// 支付創(chuàng)建
// 支付詳情
message DetailRequest {
int64 id = 1;
}
message DetailResponse {
int64 id = 1;
int64 Uid = 2;
int64 Oid = 3;
int64 Amount = 4;
int64 Source = 5;
int64 Status = 6;
}
// 支付詳情
// 支付詳情
message CallbackRequest {
int64 id = 1;
int64 Uid = 2;
int64 Oid = 3;
int64 Amount = 4;
int64 Source = 5;
int64 Status = 6;
}
message CallbackResponse {
}
// 支付詳情
service Pay {
rpc Create(CreateRequest) returns(CreateResponse);
rpc Detail(DetailRequest) returns(DetailResponse);
rpc Callback(CallbackRequest) returns(CallbackResponse);
}
運(yùn)行模板生成命令
$?goctl?rpc?proto?-src?./rpc/pay.proto?-dir?./rpc
6.4 編寫 pay rpc 服務(wù)
6.4.1 修改配置文件
修改 pay.yaml 配置文件
$?vim?rpc/etc/pay.yaml
修改服務(wù)監(jiān)聽地址,端口號(hào)為0.0.0.0:9003, Etcd服務(wù)配置,Mysql服務(wù)配置,CacheRedis服務(wù)配置
Name:?pay.rpc
ListenOn:?0.0.0.0:9003
Etcd:
??Hosts:
??-?etcd:2379
??Key:?pay.rpc
Mysql:
??DataSource:?root:123456@tcp(mysql:3306)/mall?charset=utf8mb4&parseTime=true&loc=Asia%2FShanghai
CacheRedis:
-?Host:?redis:6379
??Type:?node
??Pass:
6.4.2 添加 pay model 依賴
添加 Mysql服務(wù)配置,CacheRedis服務(wù)配置的實(shí)例化
$?vim?rpc/internal/config/config.go
package?config
import?(
?"github.com/tal-tech/go-zero/core/stores/cache"
?"github.com/tal-tech/go-zero/zrpc"
)
type?Config?struct?{
?zrpc.RpcServerConf
?Mysql?struct?{
??DataSource?string
?}
????
?CacheRedis?cache.CacheConf
}
注冊(cè)服務(wù)上下文 pay model的依賴
$?vim?rpc/internal/svc/servicecontext.go
package?svc
import?(
?"mall/service/pay/model"
?"mall/service/pay/rpc/internal/config"
?"github.com/tal-tech/go-zero/core/stores/sqlx"
)
type?ServiceContext?struct?{
?Config?config.Config
????
?PayModel?model.PayModel
}
func?NewServiceContext(c?config.Config)?*ServiceContext?{
?conn?:=?sqlx.NewMysql(c.Mysql.DataSource)
?return?&ServiceContext{
??Config:???c,
??PayModel:?model.NewPayModel(conn,?c.CacheRedis),
?}
}
6.4.3 添加 user rpc,order rpc 依賴
添加 user rpc, order rpc服務(wù)配置
$?vim?rpc/etc/pay.yaml
Name:?pay.rpc
ListenOn:?0.0.0.0:9003
Etcd:
??Hosts:
??-?etcd:2379
??Key:?pay.rpc
...
UserRpc:
??Etcd:
????Hosts:
????-?etcd:2379
????Key:?user.rpc
OrderRpc:
??Etcd:
????Hosts:
????-?etcd:2379
????Key:?order.rpc
添加 user rpc, order rpc服務(wù)配置的實(shí)例化
$?vim?rpc/internal/config/config.go
package?config
import?(
?"github.com/tal-tech/go-zero/core/stores/cache"
?"github.com/tal-tech/go-zero/zrpc"
)
type?Config?struct?{
?zrpc.RpcServerConf
?Mysql?struct?{
??DataSource?string
?}
????
?CacheRedis?cache.CacheConf
?UserRpc??zrpc.RpcClientConf
?OrderRpc?zrpc.RpcClientConf
}
注冊(cè)服務(wù)上下文 user rpc, order rpc的依賴
$?vim?rpc/internal/svc/servicecontext.go
package?svc
import?(
?"mall/service/order/rpc/orderclient"
?"mall/service/pay/model"
?"mall/service/pay/rpc/internal/config"
?"mall/service/user/rpc/userclient"
?"github.com/tal-tech/go-zero/core/stores/sqlx"
?"github.com/tal-tech/go-zero/zrpc"
)
type?ServiceContext?struct?{
?Config?config.Config
????
?PayModel?model.PayModel
?UserRpc??userclient.User
?OrderRpc?orderclient.Order
}
func?NewServiceContext(c?config.Config)?*ServiceContext?{
?conn?:=?sqlx.NewMysql(c.Mysql.DataSource)
?return?&ServiceContext{
??Config:???c,
??PayModel:?model.NewPayModel(conn,?c.CacheRedis),
??UserRpc:??userclient.NewUser(zrpc.MustNewClient(c.UserRpc)),
??OrderRpc:?orderclient.NewOrder(zrpc.MustNewClient(c.OrderRpc)),
?}
}
6.4.4 添加支付創(chuàng)建邏輯 Create
添加根據(jù) oid查詢訂單支付記錄PayModel方法FindOneByOid
$?vim?model/paymodel.go
package?model
...
var?(
?...
?cachePayIdPrefix??=?"cache:pay:id:"
?cachePayOidPrefix?=?"cache:pay:oid:"
)
type?(
?PayModel?interface?{
??Insert(data?*Pay)?(sql.Result,?error)
??FindOne(id?int64)?(*Pay,?error)
??FindOneByOid(oid?int64)?(*Pay,?error)
??Update(data?*Pay)?error
??Delete(id?int64)?error
?}
?...
)
...
func?(m?*defaultPayModel)?FindOneByOid(oid?int64)?(*Pay,?error)?{
?payOidKey?:=?fmt.Sprintf("%s%v",?cachePayOidPrefix,?oid)
?var?resp?Pay
?err?:=?m.QueryRow(&resp,?payOidKey,?func(conn?sqlx.SqlConn,?v?interface{})?error?{
??query?:=?fmt.Sprintf("select?%s?from?%s?where?`oid`?=???limit?1",?payRows,?m.table)
??return?conn.QueryRow(v,?query,?oid)
?})
?switch?err?{
?case?nil:
??return?&resp,?nil
?case?sqlc.ErrNotFound:
??return?nil,?ErrNotFound
?default:
??return?nil,?err
?}
}
......
添加支付創(chuàng)建邏輯
支付流水創(chuàng)建流程,通過調(diào)用
user rpc服務(wù)查詢驗(yàn)證用戶是否存在,再通過調(diào)用order rpc服務(wù)查詢驗(yàn)證訂單是否存在,然后通過查詢庫(kù)判斷此訂單是否已經(jīng)創(chuàng)建過支付流水,最后創(chuàng)建落庫(kù)。
$?vim?rpc/internal/logic/createlogic.go
package?logic
import?(
?"context"
?"mall/service/order/rpc/order"
?"mall/service/pay/model"
?"mall/service/pay/rpc/internal/svc"
?"mall/service/pay/rpc/pay"
?"mall/service/user/rpc/user"
?"github.com/tal-tech/go-zero/core/logx"
?"google.golang.org/grpc/status"
)
type?CreateLogic?struct?{
?ctx????context.Context
?svcCtx?*svc.ServiceContext
?logx.Logger
}
func?NewCreateLogic(ctx?context.Context,?svcCtx?*svc.ServiceContext)?*CreateLogic?{
?return?&CreateLogic{
??ctx:????ctx,
??svcCtx:?svcCtx,
??Logger:?logx.WithContext(ctx),
?}
}
func?(l?*CreateLogic)?Create(in?*pay.CreateRequest)?(*pay.CreateResponse,?error)?{
?//?查詢用戶是否存在
?_,?err?:=?l.svcCtx.UserRpc.UserInfo(l.ctx,?&user.UserInfoRequest{
??Id:?in.Uid,
?})
?if?err?!=?nil?{
??return?nil,?err
?}
?//?查詢訂單是否存在
?_,?err?=?l.svcCtx.OrderRpc.Detail(l.ctx,?&order.DetailRequest{
??Id:?in.Oid,
?})
?if?err?!=?nil?{
??return?nil,?err
?}
?//?查詢訂單是否已經(jīng)創(chuàng)建支付
?_,?err?=?l.svcCtx.PayModel.FindOneByOid(in.Oid)
?if?err?==?nil?{
??return?nil,?status.Error(100,?"訂單已創(chuàng)建支付")
?}
?newPay?:=?model.Pay{
??Uid:????in.Uid,
??Oid:????in.Oid,
??Amount:?in.Amount,
??Source:?0,
??Status:?0,
?}
?res,?err?:=?l.svcCtx.PayModel.Insert(&newPay)
?if?err?!=?nil?{
??return?nil,?status.Error(500,?err.Error())
?}
?newPay.Id,?err?=?res.LastInsertId()
?if?err?!=?nil?{
??return?nil,?status.Error(500,?err.Error())
?}
?return?&pay.CreateResponse{
??Id:?newPay.Id,
?},?nil
}
6.4.5 添加支付詳情邏輯 Detail
$?vim?rpc/internal/logic/detaillogic.go
package?logic
import?(
?"context"
?"mall/service/pay/model"
?"mall/service/pay/rpc/internal/svc"
?"mall/service/pay/rpc/pay"
?"github.com/tal-tech/go-zero/core/logx"
?"google.golang.org/grpc/status"
)
type?DetailLogic?struct?{
?ctx????context.Context
?svcCtx?*svc.ServiceContext
?logx.Logger
}
func?NewDetailLogic(ctx?context.Context,?svcCtx?*svc.ServiceContext)?*DetailLogic?{
?return?&DetailLogic{
??ctx:????ctx,
??svcCtx:?svcCtx,
??Logger:?logx.WithContext(ctx),
?}
}
func?(l?*DetailLogic)?Detail(in?*pay.DetailRequest)?(*pay.DetailResponse,?error)?{
?//?查詢支付是否存在
?res,?err?:=?l.svcCtx.PayModel.FindOne(in.Id)
?if?err?!=?nil?{
??if?err?==?model.ErrNotFound?{
???return?nil,?status.Error(100,?"支付不存在")
??}
??return?nil,?status.Error(500,?err.Error())
?}
?return?&pay.DetailResponse{
??Id:?????res.Id,
??Uid:????res.Uid,
??Oid:????res.Oid,
??Amount:?res.Amount,
??Source:?res.Source,
??Status:?res.Status,
?},?nil
}
6.4.6 添加支付回調(diào)邏輯 Callback
支付流水回調(diào)流程,通過調(diào)用 user rpc 服務(wù)查詢驗(yàn)證用戶是否存在,再通過調(diào)用 order rpc 服務(wù)查詢驗(yàn)證訂單是否存在,然后通過查詢庫(kù)判斷此訂單支付流水是否存在,以及回調(diào)支付金額和庫(kù)中流水支付金額是否一致,最后更新支付流水狀態(tài)和通過調(diào)用 order rpc 服務(wù)更新訂單狀態(tài)。
$?vim?rpc/internal/logic/callbacklogic.go
package?logic
import?(
?"context"
?"mall/service/order/rpc/order"
?"mall/service/pay/model"
?"mall/service/pay/rpc/internal/svc"
?"mall/service/pay/rpc/pay"
?"mall/service/user/rpc/user"
?"github.com/tal-tech/go-zero/core/logx"
?"google.golang.org/grpc/status"
)
type?CallbackLogic?struct?{
?ctx????context.Context
?svcCtx?*svc.ServiceContext
?logx.Logger
}
func?NewCallbackLogic(ctx?context.Context,?svcCtx?*svc.ServiceContext)?*CallbackLogic?{
?return?&CallbackLogic{
??ctx:????ctx,
??svcCtx:?svcCtx,
??Logger:?logx.WithContext(ctx),
?}
}
func?(l?*CallbackLogic)?Callback(in?*pay.CallbackRequest)?(*pay.CallbackResponse,?error)?{
?//?查詢用戶是否存在
?_,?err?:=?l.svcCtx.UserRpc.UserInfo(l.ctx,?&user.UserInfoRequest{
??Id:?in.Uid,
?})
?if?err?!=?nil?{
??return?nil,?err
?}
?//?查詢訂單是否存在
?_,?err?=?l.svcCtx.OrderRpc.Detail(l.ctx,?&order.DetailRequest{
??Id:?in.Oid,
?})
?if?err?!=?nil?{
??return?nil,?err
?}
?//?查詢支付是否存在
?res,?err?:=?l.svcCtx.PayModel.FindOne(in.Id)
?if?err?!=?nil?{
??if?err?==?model.ErrNotFound?{
???return?nil,?status.Error(100,?"支付不存在")
??}
??return?nil,?status.Error(500,?err.Error())
?}
?//?支付金額與訂單金額不符
?if?in.Amount?!=?res.Amount?{
??return?nil,?status.Error(100,?"支付金額與訂單金額不符")
?}
?res.Source?=?in.Source
?res.Status?=?in.Status
?err?=?l.svcCtx.PayModel.Update(res)
?if?err?!=?nil?{
??return?nil,?status.Error(500,?err.Error())
?}
?//?更新訂單支付狀態(tài)
?_,?err?=?l.svcCtx.OrderRpc.Paid(l.ctx,?&order.PaidRequest{
??Id:?in.Oid,
?})
?if?err?!=?nil?{
??return?nil,?status.Error(500,?err.Error())
?}
?return?&pay.CallbackResponse{},?nil
}
6.5 編寫 pay api 服務(wù)
6.5.1 修改配置文件
修改 pay.yaml 配置文件
$?vim?api/etc/pay.yaml
修改服務(wù)地址,端口號(hào)為0.0.0.0:8003, Mysql服務(wù)配置,CacheRedis服務(wù)配置,Auth驗(yàn)證配置
Name:?Pay
Host:?0.0.0.0
Port:?8003
Mysql:
??DataSource:?root:123456@tcp(mysql:3306)/mall?charset=utf8mb4&parseTime=true&loc=Asia%2FShanghai
CacheRedis:
-?Host:?redis:6379
??Type:?node
??Pass:
Auth:
??AccessSecret:?uOvKLmVfztaXGpNYd4Z0I1SiT7MweJhl
??AccessExpire:?86400
6.5.2 添加 pay rpc 依賴
添加 pay rpc服務(wù)配置
$?vim?api/etc/pay.yaml
Name:?Pay
Host:?0.0.0.0
Port:?8003
......
PayRpc:
??Etcd:
????Hosts:
????-?etcd:2379
????Key:?pay.rpc
添加 pay rpc服務(wù)配置的實(shí)例化
$?vim?api/internal/config/config.go
package?config
import?(
?"github.com/tal-tech/go-zero/rest"
?"github.com/tal-tech/go-zero/zrpc"
)
type?Config?struct?{
?rest.RestConf
?Auth?struct?{
??AccessSecret?string
??AccessExpire?int64
?}
?PayRpc?zrpc.RpcClientConf
}
注冊(cè)服務(wù)上下文 pay rpc的依賴
$?vim?api/internal/svc/servicecontext.go
package?svc
import?(
?"mall/service/pay/api/internal/config"
?"mall/service/pay/rpc/payclient"
?"github.com/tal-tech/go-zero/zrpc"
)
type?ServiceContext?struct?{
?Config?config.Config
????
?PayRpc?payclient.Pay
}
func?NewServiceContext(c?config.Config)?*ServiceContext?{
?return?&ServiceContext{
??Config:?c,
??PayRpc:?payclient.NewPay(zrpc.MustNewClient(c.PayRpc)),
?}
}
6.5.3 添加支付創(chuàng)建邏輯 Create
$?vim?api/internal/logic/createlogic.go
package?logic
import?(
?"context"
?"mall/service/pay/api/internal/svc"
?"mall/service/pay/api/internal/types"
?"mall/service/pay/rpc/pay"
?"github.com/tal-tech/go-zero/core/logx"
)
type?CreateLogic?struct?{
?logx.Logger
?ctx????context.Context
?svcCtx?*svc.ServiceContext
}
func?NewCreateLogic(ctx?context.Context,?svcCtx?*svc.ServiceContext)?CreateLogic?{
?return?CreateLogic{
??Logger:?logx.WithContext(ctx),
??ctx:????ctx,
??svcCtx:?svcCtx,
?}
}
func?(l?*CreateLogic)?Create(req?types.CreateRequest)?(resp?*types.CreateResponse,?err?error)?{
?res,?err?:=?l.svcCtx.PayRpc.Create(l.ctx,?&pay.CreateRequest{
??Uid:????req.Uid,
??Oid:????req.Oid,
??Amount:?req.Amount,
?})
?if?err?!=?nil?{
??return?nil,?err
?}
?return?&types.CreateResponse{
??Id:?res.Id,
?},?nil
}
6.5.4 添加支付詳情邏輯 Detail
$?vim?api/internal/logic/detaillogic.go
package?logic
import?(
?"context"
?"mall/service/pay/api/internal/svc"
?"mall/service/pay/api/internal/types"
?"mall/service/pay/rpc/pay"
?"github.com/tal-tech/go-zero/core/logx"
)
type?DetailLogic?struct?{
?logx.Logger
?ctx????context.Context
?svcCtx?*svc.ServiceContext
}
func?NewDetailLogic(ctx?context.Context,?svcCtx?*svc.ServiceContext)?DetailLogic?{
?return?DetailLogic{
??Logger:?logx.WithContext(ctx),
??ctx:????ctx,
??svcCtx:?svcCtx,
?}
}
func?(l?*DetailLogic)?Detail(req?types.DetailRequest)?(resp?*types.DetailResponse,?err?error)?{
?res,?err?:=?l.svcCtx.PayRpc.Detail(l.ctx,?&pay.DetailRequest{
??Id:?req.Id,
?})
?if?err?!=?nil?{
??return?nil,?err
?}
?return?&types.DetailResponse{
??Id:?????req.Id,
??Uid:????res.Uid,
??Oid:????res.Oid,
??Amount:?res.Amount,
??Source:?res.Source,
??Status:?res.Status,
?},?nil
}
6.5.5 添加支付回調(diào)邏輯 Callback
$?vim?api/internal/logic/callbacklogic.go
package?logic
import?(
?"context"
?"mall/service/pay/api/internal/svc"
?"mall/service/pay/api/internal/types"
?"mall/service/pay/rpc/pay"
?"github.com/tal-tech/go-zero/core/logx"
)
type?CallbackLogic?struct?{
?logx.Logger
?ctx????context.Context
?svcCtx?*svc.ServiceContext
}
func?NewCallbackLogic(ctx?context.Context,?svcCtx?*svc.ServiceContext)?CallbackLogic?{
?return?CallbackLogic{
??Logger:?logx.WithContext(ctx),
??ctx:????ctx,
??svcCtx:?svcCtx,
?}
}
func?(l?*CallbackLogic)?Callback(req?types.CallbackRequest)?(resp?*types.CallbackResponse,?err?error)?{
?_,?err?=?l.svcCtx.PayRpc.Callback(l.ctx,?&pay.CallbackRequest{
??Id:?????req.Id,
??Uid:????req.Uid,
??Oid:????req.Oid,
??Amount:?req.Amount,
??Source:?req.Source,
??Status:?req.Status,
?})
?if?err?!=?nil?{
??return?nil,?err
?}
?return?&types.CallbackResponse{},?nil
}
6.6 啟動(dòng) pay rpc 服務(wù)
提示:?jiǎn)?dòng)服務(wù)需要在
golang容器中啟動(dòng)
$?cd?mall/service/pay/rpc
$?go?run?pay.go?-f?etc/pay.yaml
Starting?rpc?server?at?127.0.0.1:9003...
6.7 啟動(dòng) pay api 服務(wù)
提示:?jiǎn)?dòng)服務(wù)需要在
golang容器中啟動(dòng)
$?cd?mall/service/pay/api
$?go?run?pay.go?-f?etc/pay.yaml
Starting?server?at?0.0.0.0:8003...
項(xiàng)目地址
https://github.com/zeromicro/go-zero
歡迎使用 go-zero 并 star 支持我們!
推薦閱讀
