The preface
We will show you a detailed example of a Go-Zero microservice in a series of ten articles. The table of contents is as follows:
- Environment set up
- Service split
- Customer service
- Product/service
- Order service
- Payment Services (Article)
- Auth authentication for RPC service
- Service monitoring
- Link to track
- Distributed transaction
I hope this series will take you to use the Docker environment on the machine and use go-Zero to quickly develop a mall system, so that you can quickly get started with micro services.
Full example code: github.com/nivin-studi…
First, let’s look at the overall service breakdown diagram:
6 Payment Service (Pay)
- Enter the service workspace
$ cd mall/service/pay
Copy the code
6.1 Generating the Pay Model
- Create SQL file
$ vim model/pay.sql
Copy the code
- Writing SQL files
CREATE TABLE `pay` (
`id` bigint unsigned NOT NULL AUTO_INCREMENT,
`uid` bigint unsigned NOT NULL DEFAULT '0' COMMENT 'user ID',
`oid` bigint unsigned NOT NULL DEFAULT '0' COMMENT 'order ID',
`amount` int(10) unsigned NOT NULL DEFAULT '0' COMMENT 'Product Amount',
`source` tinyint(3) unsigned NOT NULL DEFAULT '0' COMMENT 'Method of Payment',
`status` tinyint(3) unsigned NOT NULL DEFAULT '0' COMMENT 'Payment status',
`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;
Copy the code
- Run the template generation command
$ goctl model mysql ddl -src ./model/pay.sql -dir ./model -c
Copy the code
6.2 Generating the Pay API Service
- Creating an API file
$ vim api/pay.api
Copy the code
- Writing API files
type (
// Pay is created
CreateRequest {
Uid int64 `json:"uid"`
Oid int64 `json:"oid"`
Amount int64 `json:"amount"`
}
CreateResponse {
Id int64 `json:"id"`
}
// Pay is created
// Payment details
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"`
}
// Payment details
// Pay callback
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 {
}
// Pay callback
)
@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)
}
Copy the code
- Run the template generation command
$ goctl api go -api ./api/pay.api -dir ./api
Copy the code
6.3 Generating the Pay RPC Service
- Create the proto file
$ vim rpc/pay.proto
Copy the code
- Write proto files
syntax = "proto3";
package payclient;
option go_package = "pay";
// Pay is created
message CreateRequest {
int64 Uid = 1;
int64 Oid = 2;
int64 Amount = 3;
}
message CreateResponse {
int64 id = 1;
}
// Pay is created
// Payment details
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;
}
// Payment details
// Payment details
message CallbackRequest {
int64 id = 1;
int64 Uid = 2;
int64 Oid = 3;
int64 Amount = 4;
int64 Source = 5;
int64 Status = 6;
}
message CallbackResponse {}// Payment details
service Pay {
rpc Create(CreateRequest) returns(CreateResponse);
rpc Detail(DetailRequest) returns(DetailResponse);
rpc Callback(CallbackRequest) returns(CallbackResponse);
}
Copy the code
- Run the template generation command
$ goctl rpc proto -src ./rpc/pay.proto -dir ./rpc
Copy the code
6.4 Writing the Pay RPC Service
6.4.1 Modifying a Configuration File
- Modify the pay.yaml configuration file
$ vim rpc/etc/pay.yaml
Copy the code
- Change the service listening address to 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:
Copy the code
6.4.2 Adding a Pay Model Dependency
- add
Mysql
Service configuration,CacheRedis
Instantiation of the service configuration
$ vim rpc/internal/config/config.go
Copy the code
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
}
Copy the code
- Register the service Context
pay model
The dependence of
$ vim rpc/internal/svc/servicecontext.go
Copy the code
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),
}
}
Copy the code
6.4.3 Adding user RPC and order RPC dependencies
- add
user rpc, order rpc
Service configuration
$ vim rpc/etc/pay.yaml
Copy the code
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
Copy the code
- add
user rpc, order rpc
Instantiation of the service configuration
$ vim rpc/internal/config/config.go
Copy the code
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
}
Copy the code
- Register the service Context
user rpc, order rpc
The dependence of
$ vim rpc/internal/svc/servicecontext.go
Copy the code
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)),
}
}
Copy the code
6.4.4 Adding the payment logic Create
- Add according to
oid
Query order payment recordsPayModel
methodsFindOneByOid
$ vim model/paymodel.go
Copy the code
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
}
}
......
Copy the code
-
Add payment creation logic
The payment flow creation process verifies whether the user exists by calling the User RPC service query, and then verifies whether the order exists by calling the Order RPC service query, and then determines whether the order has created the payment flow through the query library, and finally creates the database.
$ vim rpc/internal/logic/createlogic.go
Copy the code
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) {
// Check whether the user exists
_, err := l.svcCtx.UserRpc.UserInfo(l.ctx, &user.UserInfoRequest{
Id: in.Uid,
})
iferr ! =nil {
return nil, err
}
// Check whether the order exists
_, err = l.svcCtx.OrderRpc.Detail(l.ctx, &order.DetailRequest{
Id: in.Oid,
})
iferr ! =nil {
return nil, err
}
// Check whether the order has been created for payment
_, err = l.svcCtx.PayModel.FindOneByOid(in.Oid)
if err == nil {
return nil, status.Error(100."Order has been created for payment")
}
newPay := model.Pay{
Uid: in.Uid,
Oid: in.Oid,
Amount: in.Amount,
Source: 0,
Status: 0,
}
res, err := l.svcCtx.PayModel.Insert(&newPay)
iferr ! =nil {
return nil, status.Error(500, err.Error())
}
newPay.Id, err = res.LastInsertId()
iferr ! =nil {
return nil, status.Error(500, err.Error())
}
return &pay.CreateResponse{
Id: newPay.Id,
}, nil
}
Copy the code
6.4.5 Adding logical Detail of payment details
$ vim rpc/internal/logic/detaillogic.go
Copy the code
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) {
// Check whether the payment exists
res, err := l.svcCtx.PayModel.FindOne(in.Id)
iferr ! =nil {
if err == model.ErrNotFound {
return nil, status.Error(100."Payment does not exist.")}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
}
Copy the code
6.4.6 Adding the payment Callback logic Callback
The payment flow callback process verifies whether the user exists by calling the User RPC service query, and then verifies whether the order exists by calling the ORDER RPC service query, and then determines whether the order payment flow exists by querying the database, and whether the callback payment amount is consistent with the payment amount in the database. Finally, the payment flow state is updated and the order state is updated by calling the ORDER RPC service.
$ vim rpc/internal/logic/callbacklogic.go
Copy the code
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) {
// Check whether the user exists
_, err := l.svcCtx.UserRpc.UserInfo(l.ctx, &user.UserInfoRequest{
Id: in.Uid,
})
iferr ! =nil {
return nil, err
}
// Check whether the order exists
_, err = l.svcCtx.OrderRpc.Detail(l.ctx, &order.DetailRequest{
Id: in.Oid,
})
iferr ! =nil {
return nil, err
}
// Check whether the payment exists
res, err := l.svcCtx.PayModel.FindOne(in.Id)
iferr ! =nil {
if err == model.ErrNotFound {
return nil, status.Error(100."Payment does not exist.")}return nil, status.Error(500, err.Error())
}
// The payment amount does not match the order amount
ifin.Amount ! = res.Amount {return nil, status.Error(100."The payment amount does not match the order amount")
}
res.Source = in.Source
res.Status = in.Status
err = l.svcCtx.PayModel.Update(res)
iferr ! =nil {
return nil, status.Error(500, err.Error())
}
// Update the order payment status
_, err = l.svcCtx.OrderRpc.Paid(l.ctx, &order.PaidRequest{
Id: in.Oid,
})
iferr ! =nil {
return nil, status.Error(500, err.Error())
}
return &pay.CallbackResponse{}, nil
}
Copy the code
6.5 Writing the Pay API Service
6.5.1 Modifying the Configuration File
- Modify the pay.yaml configuration file
$ vim api/etc/pay.yaml
Copy the code
- Change the service address to 0.0.0.0:8003.
Mysql
Service configuration,CacheRedis
Service configuration,Auth
Verify the configuration
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
Copy the code
6.5.2 Adding a Pay RPC Dependency
- add
pay rpc
Service configuration
$ vim api/etc/pay.yaml
Copy the code
Name: Pay
Host: 0.0. 0. 0
Port: 8003
.
PayRpc:
Etcd:
Hosts:
- etcd:2379
Key: pay.rpc
Copy the code
- add
pay rpc
Instantiation of the service configuration
$ vim api/internal/config/config.go
Copy the code
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
}
Copy the code
- Register the service Context
pay rpc
The dependence of
$ vim api/internal/svc/servicecontext.go
Copy the code
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)),
}
}
Copy the code
6.5.3 Adding the payment Create logic Create
$ vim api/internal/logic/createlogic.go
Copy the code
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,
})
iferr ! =nil {
return nil, err
}
return &types.CreateResponse{
Id: res.Id,
}, nil
}
Copy the code
6.5.4 Adding logical Detail of payment details
$ vim api/internal/logic/detaillogic.go
Copy the code
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,
})
iferr ! =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
}
Copy the code
6.5.5 Adding the payment Callback logic Callback
$ vim api/internal/logic/callbacklogic.go
Copy the code
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,
})
iferr ! =nil {
return nil, err
}
return &types.CallbackResponse{}, nil
}
Copy the code
6.6 Starting the Pay RPC Service
Tip: To start the service, you need to start it in the Golang container
$ cdMall /service/pay/ RPC $go run pay.go -f etc/pay.yaml Starting RPC server at 127.0.0.1:9003...Copy the code
6.7 Starting the Pay API Service
Tip: To start the service, you need to start it in the Golang container
$ cdMall /service/pay/ API $go run pay.go -f etc/pay.yaml Starting server at 0.0.0.00:8003...Copy the code
The project address
Github.com/zeromicro/g…
Welcome to Go-Zero and star support us!
Wechat communication group
Pay attention to the public account of “micro-service Practice” and click on the exchange group to obtain the QR code of the community group.