Preamble
We will show you a go-zero microservice example in detail through a series of articles. The whole series is divided into ten articles. The directory structure is as follows:
- Environment construction
- service split
- User service
- product service
- Order service
- Payment Services (this article)
- RPC Service Auth Authentication
- service monitoring
- link tracking
- Distributed transaction
I hope that through this series, you can quickly develop a mall system using go-zero in the Docker environment on the local machine, so that you can quickly get started with microservices.
Complete example code: https://github.com/nivin-studio/go-zero-mall
First, let's take a look at the overall service split diagram:
6 Payment Services (pay)
- Enter the service workspace
$ cd mall/service/pay
6.1 Generate pay model model
- create sql file
$ vim model/pay.sql
- write sql file
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 '产品金额',
`source` tinyint(3) unsigned NOT NULL DEFAULT '0' COMMENT '支付方式',
`status` tinyint(3) unsigned NOT NULL DEFAULT '0' COMMENT '支付状态',
`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;
- Run the template generation command
$ goctl model mysql ddl -src ./model/pay.sql -dir ./model -c
6.2 Generate pay api service
- create api file
$ vim api/pay.api
- write api file
type (
// 支付创建
CreateRequest {
Uid int64 `json:"uid"`
Oid int64 `json:"oid"`
Amount int64 `json:"amount"`
}
CreateResponse {
Id int64 `json:"id"`
}
// 支付创建
// 支付详情
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"`
}
// 支付详情
// 支付回调
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 {
}
// 支付回调
)
@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)
}
- Run the template generation command
$ goctl api go -api ./api/pay.api -dir ./api
6.3 Generate pay rpc service
- Create proto file
$ vim rpc/pay.proto
- write proto file
syntax = "proto3";
package payclient;
option go_package = "pay";
// 支付创建
message CreateRequest {
int64 Uid = 1;
int64 Oid = 2;
int64 Amount = 3;
}
message CreateResponse {
int64 id = 1;
}
// 支付创建
// 支付详情
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);
}
- Run the template generation command
$ goctl rpc proto -src ./rpc/pay.proto -dir ./rpc
6.4 Write pay rpc service
6.4.1 Modify the configuration file
- Modify the pay.yaml configuration file
$ vim rpc/etc/pay.yaml
- Modify the service listening address, the port number is 0.0.0.0:9003,
Etcd
service configuration,Mysql
service configuration,CacheRedis
service configuration
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 Add pay model dependency
- Added
Mysql
service configuration,CacheRedis
instantiation of service configuration
$ 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
}
- Dependency of registration service context
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 Add user rpc, order rpc dependencies
- Add
user rpc, order rpc
service configuration
$ 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
- Added
user rpc, order rpc
instantiation of service configuration
$ 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
}
- Dependency of registration service context
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 Add payment creation logic Create
- Add according to
oid
query order payment recordPayModel
methodFindOneByOid
$ 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
}
}
......
Add payment creation logic
In the payment flow creation process, verify whether the user exists by calling
order rpc
user rpc
query, and then verify whether the order exists by calling ---ca129ae7c4c2299e5219853f800a6b04---service query, and then check whether the order has been created for payment through the query library Flow, and finally create a drop library.
$ 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
}
// 查询订单是否已经创建支付
_, err = l.svcCtx.PayModel.FindOneByOid(in.Oid)
if err == nil {
return nil, status.Error(100, "订单已创建支付")
}
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 Add payment details logic 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 Add payment callback logic Callback
In the payment flow callback process, verify whether the user exists by calling order rpc
user rpc
query, and then verify whether the order exists by calling ---ad99f556372743c99d909bf7e9b53d6c---service query, and then judge whether the order payment flow exists through the query library, And whether the callback payment amount is consistent with the flow payment amount in the library, and finally update the payment flow status and update the order status by calling the order rpc
service.
$ 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())
}
// 更新订单支付状态
_, 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 Write pay api service
6.5.1 Modify the configuration file
- Modify the pay.yaml configuration file
$ vim api/etc/pay.yaml
- Modify the service address, the port number is 0.0.0.0:8003,
Mysql
service configuration,CacheRedis
service configuration,Auth
verification
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 Add pay rpc dependency
- Add
pay rpc
Service Configuration
$ vim api/etc/pay.yaml
Name: Pay
Host: 0.0.0.0
Port: 8003
......
PayRpc:
Etcd:
Hosts:
- etcd:2379
Key: pay.rpc
- Added
pay rpc
instantiation of service configuration
$ 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
}
- Dependency of registered service context
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 Add payment creation logic 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 Add payment details logic 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 Add payment callback logic 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 Start pay rpc service
Tip: The startup service needs to be started in the golang
container
$ cd mall/service/pay/rpc
$ go run pay.go -f etc/pay.yaml
Starting rpc server at 127.0.0.1:9003...
6.7 Start the pay api service
Tip: The startup service needs to be started in the golang
container
$ cd mall/service/pay/api
$ go run pay.go -f etc/pay.yaml
Starting server at 0.0.0.0:8003...
project address
https://github.com/zeromicro/go-zero
Welcome go-zero
and star support us!
WeChat exchange group
Follow the official account of " Microservice Practice " and click on the exchange group to get the QR code of the community group.
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。