preface
We will show you a go zero micro service example in detail through a series of articles. The whole series is divided into ten articles, and the directory structure is as follows:
- Environment construction
- Service splitting
- User services
- Product service
- Order service
- Payment services (this article)
- RPC service Auth authentication
- Service monitoring
- Link tracking
- Distributed transaction
This series is expected to take you to quickly develop a mall system using go zero in the Docker environment on the machine, so that you can quickly start microservices.
Complete sample code: github.com/nivin-studio/go-zero-ma...
First, let's take a look at the overall service splitting diagram:
6 payment service (pay)
- Enter the service workspace
$ cd mall/service/pay
6.1 generate pay 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 '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 'Payment method', `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;
- Run 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
- Writing api files
type ( // Payment creation CreateRequest { Uid int64 `json:"uid"` Oid int64 `json:"oid"` Amount int64 `json:"amount"` } CreateResponse { Id int64 `json:"id"` } // Payment creation // 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 // Payment 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 { } // Payment 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) }
- Run 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"; // Payment creation message CreateRequest { int64 Uid = 1; int64 Oid = 2; int64 Amount = 3; } message CreateResponse { int64 id = 1; } // Payment creation // 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); }
- Run template generation command
$ goctl rpc proto -src ./rpc/pay.proto -dir ./rpc
6.4 write pay rpc service
6.4.1 modify configuration file
- Modify pay Yaml profile
$ 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, and 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
- Add Mysql service configuration and instantiate CacheRedis 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 }
- Register the dependency of the 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 and 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
- Add user RPC and order RPC service configuration instantiation
$ 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 }
- Register the dependency of the service context user RPC and 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 PayModel method FindOneByOid to query order payment records according to oid
$ 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, the user rpc service is called to query and verify whether the user exists, then the order rpc service is called to query and verify whether the order exists, and then the query library is used to determine whether the payment flow has been created for the order, and finally the drop library is created.
$ 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) { // Query whether the user exists _, err := l.svcCtx.UserRpc.UserInfo(l.ctx, &user.UserInfoRequest{ Id: in.Uid, }) if err != nil { return nil, err } // Query whether the order exists _, err = l.svcCtx.OrderRpc.Detail(l.ctx, &order.DetailRequest{ Id: in.Oid, }) if err != nil { return nil, err } // Query whether payment has been created for the order _, err = l.svcCtx.PayModel.FindOneByOid(in.Oid) if err == nil { return nil, status.Error(100, "Payment order created") } 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 Detail 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) { // Query whether payment exists res, err := l.svcCtx.PayModel.FindOne(in.Id) if err != 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 }
6.4.6 add payment Callback logic
In the payment flow callback process, verify whether the user exists by calling the user rpc service query, and then verify whether the order exists by calling the order rpc service query, and then judge whether the payment flow of this order exists by querying the library, and whether the callback payment amount is consistent with the flow payment amount in the library, Finally, the payment flow status is updated and the order status is updated 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) { // Query whether the user exists _, err := l.svcCtx.UserRpc.UserInfo(l.ctx, &user.UserInfoRequest{ Id: in.Uid, }) if err != nil { return nil, err } // Query whether the order exists _, err = l.svcCtx.OrderRpc.Detail(l.ctx, &order.DetailRequest{ Id: in.Oid, }) if err != nil { return nil, err } // Query whether payment exists res, err := l.svcCtx.PayModel.FindOne(in.Id) if err != nil { if err == model.ErrNotFound { return nil, status.Error(100, "Payment does not exist") } return nil, status.Error(500, err.Error()) } // The payment amount is inconsistent with the order amount if in.Amount != res.Amount { return nil, status.Error(100, "The payment amount is inconsistent with the order amount") } res.Source = in.Source res.Status = in.Status err = l.svcCtx.PayModel.Update(res) if err != nil { return nil, status.Error(500, err.Error()) } // Update order payment status _, 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 writing pay api service
6.5.1 modify configuration file
- Modify pay Yaml profile
$ vim api/etc/pay.yaml
- Modify the service address, the port number is 0.0.0.0:8003, the Mysql service configuration, the CacheRedis service configuration, and the Auth authentication 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
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
- Add pay rpc service configuration instantiation
$ 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 }
- Register the dependency of 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 Detail 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
$ 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: start the service 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 pay api service
Tip: start the service 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
Welcome to go zero and star support us!
Wechat communication group
Focus on the "micro service practice" official account and click on the exchange group to get the community community's two-dimensional code.