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:

  1. Environment set up
  2. Service split
  3. Customer service
  4. Product/service
  5. Order service
  6. Payment Services (Article)
  7. Auth authentication for RPC service
  8. Service monitoring
  9. Link to track
  10. 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.EtcdService configuration,MysqlService configuration,CacheRedisService 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

  • addMysqlService configuration,CacheRedisInstantiation 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 Contextpay modelThe 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

  • adduser rpc, order rpcService 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
  • adduser rpc, order rpcInstantiation 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 Contextuser rpc, order rpcThe 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 tooidQuery order payment recordsPayModelmethodsFindOneByOid
$ 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.MysqlService configuration,CacheRedisService configuration,AuthVerify 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

  • addpay rpcService 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
  • addpay rpcInstantiation 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 Contextpay rpcThe 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.