<!-- go-zero microservices combat series (three, API definition and table structure design) -->

The first two articles respectively introduced the background of this series of articles and the service split of the mall system according to business functions. Each service can be divided into the following three categories:

  • api service - BFF layer, providing external HTTP interface
  • rpc service - an internally dependent microservice that implements a single business function
  • rmq service - responsible for the processing of streaming tasks, such as consuming kafka, etc.
  • admin service - provides HTTP interface to the internal management background, usually with high data operation authority

If you haven't read the first two articles, you can view them through the following portal

go-zero microservices combat series (1, the beginning)

go-zero microservice combat series (2, service split)

The first two articles are more theoretical, so that after the article was published, some students felt that the writing was relatively watery, and they understood everyone's eagerness to write code. I also carried out a deep reflection hahaha. So from the beginning of this article, we will enter the much-anticipated code link. However, the so-called sharpening of knives does not cut firewood by mistake. In the real production and development process, we generally spend a lot of time on the understanding of requirements and the design of protocols. If the understanding of requirements is not thorough or the protocol design is unreasonable, we will It greatly increases the possibility of rework of our project, and it has to be refactored before it even goes live. So investing some time up front is totally worth it. When we have a thorough understanding of the requirements, and the project structure and protocols are clearly defined, in fact, writing code is a matter of smooth sailing, and the speed is much faster. Without further ado, let's start today's content.

API Definition

Maybe everyone has encountered such a scenario in their work, that is, the code is updated but the documentation is not updated, which causes some problems and leads to some wrangling. The essence of the problem is that services and documents are separated. What we expect is that documents are protocols and protocols are services. This concept coincides with go-zero's api definition.

We define the BFF layer. BFF is a unified export that provides HTTP interfaces to the outside world. Therefore, the definition of API here is mainly the definition of API for BFF services.

API Compatibility

When we define or modify the API, we must consider forward compatibility. The following situations are forward compatible:

  • Add new API interface protocol
  • When adding a field to the request parameter, it is necessary to ensure that the new and old clients handle the field differently
  • Add a field to the response result, the field information will only be displayed in the new version of the client

The following situations are forward incompatible:

  • Delete or rename services, fields, methods, etc. Essentially, if client code can refer to something, then deleting or renaming it is an incompatible change, and the major version number must be modified.
  • Modify the field type, which will cause the code generated by the client library to change, so the major version number must be increased. For compiled static languages, compilation errors may occur
  • To modify the visible behavior of existing requests, clients often rely on API behavior and semantics, even if such behavior is not explicitly supported or documented. Therefore, in most cases the behavior or semantics of modifying API data will be considered disruptive by consumers
  • Add read/write fields to resource messages
Home API Definition

The homepage function is mainly divided into four parts, search, banner map, limited-time purchase and recommended product list. Clicking the search box will jump to the search page. The recommended part is displayed in pages. Users can load the next page by continuously sliding up. . By analyzing the homepage, we roughly need to provide three interfaces, namely the Banner interface, the time-limited purchase interface and the recommended interface.

What needs to be noted here is the recommended interface. The data returned by the recommended interface needs to support paging. Here, the pagination adopts the cursor method. The Ps parameter is the number of data returned per page. By default, one page returns 20 data. Note that it must be required on the server side. Check the Ps value again to prevent performance problems caused by malicious Ps values. For example, if Ps is passed 10000, when it is an illegal value, you need to set Ps to the default value, Cursor is the cursor value, and the cursor is the RecommendTime of the last piece of data on each page.

In the returned value, Products defines the returned product list, IsEnd indicates whether it is the last page, the client decides whether to terminate the request by judging whether IsEnd is true or not, RecommendTime is the recommended time of the last data of the returned data on the page, and the advance list is based on the recommended time Return in reverse order.

 RecommendRequest {
        Cursor int64 `json:"cursor"`
        Ps     int64 `form:"ps,default=20"` // 每页大小
}

RecommendResponse {
        Products      []*Product `json:"products"`
        IsEnd         bool       `json:"is_end"`         // 是否最后一页
        RecommendTime int64      `json:"recommend_time"` // 商品列表最后一个商品的推荐时间
}

Product {
        ID          int64   `json:"id"`          // 商品ID
        Name        string  `json:"name"`        // 产品名称
        Description string  `json:"description"` // 商品描述
        Price       float64 `json:"price"`       // 商品价格
        Stock       int64   `json:"stock"`       // 库存
        Category    string  `json:"category"`    // 分类
        Status      int64   `json:"status"`      // 状态:1-正常,2-下架
        CreateTime  int64   `json:"create_time"` // 创建时间
        UpdateTime  int64   `json:"update_time"` // 更新时间
}

There is a countdown function for panic buying. Here we return the start time of panic buying, and the client calculates the remaining time to count down.

 FlashSaleResponse {
        StartTime int64      `json:"start_time"` // 抢购开始时间
        Products  []*Product `json:"products"`
}
Classification API Definition

In the category list, you can switch different tabs to select different categories, and at the same time, you can sort according to different dimensions under each category, and support paging.

The classified product list is the same as the paging method of the recommended interface. Both use the cursor method. At the same time, the classified product list needs to be sorted according to different classification and sorting attributes. This kind of list that needs to be sorted is generally implemented through the sorted set of redis. Score is the attribute that needs to be sorted, such as sales volume, and member is the id of the corresponding product.

 CategoryListRequest {
        Cursor   int64  `form:"cursor"`        // 分页游标
        Ps       int64  `form:"ps,default=20"` // 每页大小
        Category string `form:"category"`      // 分类
        Sort     string `form:"sort"`          // 排序
}

CategoryListResponse {
        Products []*Product `json:"products"`
        IsEnd    bool       `json:"is_end"`
        LastVal  int64      `json:"last_val"`
}

Referring to the sorted set, here is a pit that the author has stepped on using the sorted set. The common posture we use for caching is the cache aside mode, that is, read the cache first, if the cache hits, return the data directly from the cache, if the read cache misses, go back to the source to read the data in the DB, and for faster reading later When the data is retrieved, the data read from the DB will be stuffed back into the cache, and an expiration time will be set for the cache.

In order to ensure the consistency of cache and database data, when we add new data, we need to write this data into the cache to ensure that the cache and database data are consistent. Generally, the code will be written in this way. First, the key corresponding to the cache is judged by Exists. Whether it exists, if it exists, add a piece of data to the sorted set. If it does not exist, it will not be processed, and the list data will be reloaded into the cache when the list is read next time. We found that sometimes the list data in the cache will become one piece, but there are actually multiple pieces of data. At that time, it felt very strange. After investigation, the problem was finally located. It turned out that the two operations of Exists and Zadd are not atomic operations. Yes, that is, the cached key has not expired when Exists, but the key expires after Exists and before Zadd, and then executing Zadd will result in only the newly added data in the cache list. The solution to this problem is also very simple. Instead of using Exists to determine whether the key exists, the key is renewed through Expire. If the key does not exist, Expire returns 0, and if the key exists, Expire returns 1, and the renewal is successful. We have also stepped on many pits in the use of cache, especially in high concurrency scenarios, which will be introduced in detail in this follow-up article.

Shopping Cart API Definition

Here we limit the number of shopping carts. We limit the maximum number of items in the shopping cart to 200. This is to avoid excessive write enlargement when placing an order when selecting all. Due to the addition of 200 items restrictions, so the shopping cart list does not need pagination.

The cart list request and return are defined as follows:

 CartListRequest {
        UID int64 `form:"uid"`
    }

    CartListResponse {
        Products []*CartProduct `json:"products"`
    }

    CartProduct {
        Product *Product `json:"product"`
        Count   int64    `json:"count"` // 购买数量
    }
Product Evaluation API Definition

The function of product evaluation also needs to support paging, using the cursor method to paginate, and reverse order according to the comment time.

The list of comments is defined as follows:

 ProductCommentRequest {
        ProductID int64 `form:"product_id"`
        Cursor    int64 `form:"cursor"`
        Ps        int64 `form:"ps,default=20"`
    }

    ProductCommentResponse {
        Comments    []*Comment `json:"comments"`
        IsEnd       bool       `json:"is_end"`       // 是否最后一页
        CommentTime int64      `json:"comment_time"` // 评论列表最后一个评论的时间
    }

    Comment {
        ID         int64    `json:"id"`          // 评论ID
        ProductID  int64    `json:"product_id"`  // 商品ID
        Content    string   `json:"content"`     // 评论内容
        Images     []*Image `json:"images"`      // 评论图片
        User       *User    `json:"user"`        // 用户信息
        CreateTime int64    `json:"create_time"` // 评论时间
        UpdateTime int64    `json:"update_time"` // 更新时间
    }

    User {
        ID     int64  `json:"id"`     // 用户ID
        Name   string `json:"name"`   // 用户名
        Avatar string `json:"avatar"` // 头像
    }

    Image {
        ID  int64  `json:"id"`
        URL string `json:"url"`
    }

The definitions of some core APIs are listed above. There are many function points in the mall, and it is difficult to define them all in a short time. The author will continue to improve them after work. When defining the interface to return data, we should try our best to return only the necessary data.

After defining the api, we use the following command to regenerate the project code, and output the following information to indicate that the generation is successful

 $ goctl api go -api api.api -dir .

etc/api-api.yaml exists, ignored generation
internal/config/config.go exists, ignored generation
api.go exists, ignored generation
internal/svc/servicecontext.go exists, ignored generation
internal/handler/homebannerhandler.go exists, ignored generation
internal/handler/flashsalehandler.go exists, ignored generation
internal/handler/recommendhandler.go exists, ignored generation
internal/handler/categorylisthandler.go exists, ignored generation
internal/handler/cartlisthandler.go exists, ignored generation
internal/handler/productcommenthandler.go exists, ignored generation
internal/logic/homebannerlogic.go exists, ignored generation
internal/logic/flashsalelogic.go exists, ignored generation
internal/logic/recommendlogic.go exists, ignored generation
internal/logic/categorylistlogic.go exists, ignored generation
internal/logic/cartlistlogic.go exists, ignored generation
internal/logic/productcommentlogic.go exists, ignored generation
Done.
RPC definition

Because BFF is only responsible for data assembly, the real source of data is provided by each microservice through the RPC interface. Next, let's define the proto of each microservice. The order list page shown below consists of two parts of data, namely order data and product data, that is, our BFF needs to rely on order-rpc and product-rpc to complete the assembly of the page data. Let's define order-rpc separately. and product-rpc

The definition of order.proto is as follows, the service name is Order, and the rpc interface of Orders to get the order list is added.

 syntax = "proto3";

package order;
option go_package="./order";


service Order {
  rpc Orders(OrdersRequest) returns(OrdersResponse);
}

message OrdersRequest {
  int64 user_id = 1;
  int32 status = 2;
  int64 cursor = 3;
  int32 ps = 4;
}

message OrdersResponse {
  repeated OrderItem orders = 1;
  bool is_end = 2;
  string create_time = 3;
}

message OrderItem {
  string order_id = 1;
  int64 quantity = 2;
  float payment = 3;
  int64 product_id = 4;
  int64 user_id = 5;
  int64 create_time = 6;
}

Use the following command to regenerate the code. Note that you need to rely on the two plugins protoc-gen-go and protoc-gen-go-grpc . If there is no installation, executing the following command will report an error

 $ goctl rpc protoc order.proto --go_out=. --go-grpc_out=. --zrpc_out=.

After generating, start the order-rpc service, and the output is as follows:

 $ go run order.go

Starting rpc server at 127.0.0.1:8080...
{"level":"warn","ts":"2022-06-09T15:42:21.680+0800","logger":"etcd-client","caller":"v3@v3.5.4/retry_interceptor.go:62","msg":"retrying of unary invoker failed","target":"etcd-endpoints://0xc000029c00/127.0.0.1:2379","attempt":0,"error":"rpc error: code = DeadlineExceeded desc = latest balancer error: last connection error: connection error: desc = \"transport: Error while dialing dial tcp 127.0.0.1:2379: connect: connection refused\""}
{"@timestamp":"2022-06-09T15:42:21.682+08:00","caller":"zrpc/server.go:90","content":"context deadline exceeded","level":"error"}
panic: context deadline exceeded

what's the situation? It reported an error. Fortunately, the log output is more detailed. It can be seen from the log that the local etcd has not been started. Then we will start the local etcd. After starting, run the order rpc service again, which is already listening on the default port 8080. superior

 $ go run order.go

Starting rpc server at 127.0.0.1:8080...

product.proto is defined as follows

 syntax = "proto3";

package product;
option go_package="./product";

service Product {
  rpc Products(ProductRequest) returns(ProductResponse);
}

message ProductRequest {
  string product_ids = 1;
}

message ProductResponse {
  repeated ProductItem products = 1;
}

message ProductItem {
  int64 product_id = 1;
  string name = 2;
  string description = 3;
  string image_url = 4;
}

Execute the following command to generate the code for product rpc

 $ goctl rpc protoc product.proto --go_out=. --go-grpc_out=. --zrpc_out=.

Note that the rpc service generated by goctl listens on port 8080 by default, because we are testing locally, so change the default port of product rpc to 8081, and then start the service.

 Name: product.rpc
ListenOn: 127.0.0.1:8081
Etcd:
  Hosts:
  - 127.0.0.1:2379
  Key: product.rpc
 $ go run product.go

Starting rpc server at 127.0.0.1:8081...

Because our BFF needs to depend on order.rpc and product.rpc, we need to add the configuration file first, as follows:

 Name: api-api
Host: 0.0.0.0
Port: 8888
OrderRPC:
    Etcd:
        Hosts:
          - 127.0.0.1:2379
        Key: order.rpc
ProductRPC:
  Etcd:
    Hosts:
      - 127.0.0.1:2379
    Key: product.rpc

Then add the RPC client in the ServiceContext, as follows:

 type ServiceContext struct {
    Config config.Config
    OrderRPC order.Order
    ProductRPC product.Product
}

func NewServiceContext(c config.Config) *ServiceContext {
    return &ServiceContext{
        Config: c,
        OrderRPC: order.NewOrder(zrpc.MustNewClient(c.OrderRPC)),
        ProductRPC: product.NewProduct(zrpc.MustNewClient(c.ProductRPC)),
    }
}

Finally, you only need to add logic to the logic method of the order interface. This is just a demonstration, so it will be relatively simple:

 func (l *OrderListLogic) OrderList(req *types.OrderListRequest) (resp *types.OrderListResponse, err error) {
    orderRet, err := l.svcCtx.OrderRPC.Orders(l.ctx, &order.OrdersRequest{UserId: req.UID})
    if err != nil {
        return nil, err
    }
    var pids []string
    for _, o := range orderRet.Orders {
        pids = append(pids, strconv.Itoa(int(o.ProductId)))
    }
    productRet, err := l.svcCtx.ProductRPC.Products(l.ctx, &product.ProductRequest{ProductIds: strings.Join(pids, ",")})
    if err != nil {
        return nil, err
    }
    var orders []*types.Order
    for _, o := range orderRet.Orders {
        if p, ok := productRet.Products[o.ProductId]; ok {
            orders = append(orders, &types.Order{
                OrderID: o.OrderId,
                ProductName: p.Name,
            })
        }
    }
    return &types.OrderListResponse{Orders: orders}, nil
}

Then request the order interface in the browser, and you can see that the following data is output, indicating that the link from BFF to RPC has been opened:

 http://127.0.0.1:8888/v1/order/list?uid=123

{
  "orders": [
    {
      "order_id": "20220609123456",
      "status": 0,
      "quantity": 0,
      "payment": 0,
      "total_price": 0,
      "create_time": 0,
      "product_id": 0,
      "product_name": "测试商品名称",
      "product_image": "",
      "product_description": ""
    }
  ],
  "is_end": false,
  "order_time": 0
}

table structure definition

Data isolation needs to be done between different microservices. Each microservice has exclusive database resources and obtains data dependencies through RPC calls. The overall architecture is shown in the following figure:

Through the above definition of API, we have a general understanding of which data fields are needed. Let's start designing the data table. The table building statement is placed in the data.sql file in the project root directory. This file will be updated continuously. The main libraries and tables involved are Defined as follows:

The user table mainly saves user information. In the user library, functions such as user points and user levels may be extended in the future.

 CREATE DATABASE user;
USE user;

CREATE TABLE `user` (
    `id` bigint(20) UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '用户ID',
    `username` varchar(50) NOT NULL DEFAULT '' COMMENT '用户名',
    `password` varchar(50) NOT NULL DEFAULT '' COMMENT '用户密码,MD5加密',
    `phone` varchar(20) NOT NULL DEFAULT '' COMMENT '手机号',
    `question` varchar(100) NOT NULL DEFAULT '' COMMENT '找回密码问题',
    `answer` varchar(100) NOT NULL DEFAULT '' COMMENT '找回密码答案',
    `create_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
    `update_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
    PRIMARY KEY (`id`),
    KEY `ix_update_time` (`update_time`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='用户表';

The commodity library mainly involves commodity tables and commodity classification tables:

 CREATE DATABASE product;
USE product;

CREATE TABLE `product` (
    `id` bigint(20) UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '商品id',
    `cateid` smallint(6) UNSIGNED NOT NULL DEFAULT 0 COMMENT '类别Id',
    `name` varchar(100) NOT NULL DEFAULT '' COMMENT '商品名称',
    `subtitle` varchar(200) DEFAULT NULL DEFAULT '' COMMENT '商品副标题',
    `images` text COMMENT '图片地址,json格式,扩展用',
    `detail` text COMMENT '商品详情',
    `price` decimal(20,2) NOT NULL DEFAULT 0 COMMENT '价格,单位-元保留两位小数',
    `stock` int(11) NOT NULL DEFAULT 0 COMMENT '库存数量',
    `status` int(6) NOT NULL DEFAULT 1 COMMENT '商品状态.1-在售 2-下架 3-删除',
    `create_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
    `update_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
    PRIMARY KEY (`id`),
    KEY `ix_cateid` (`cateid`),
    KEY `ix_update_time` (`update_time`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='商品表';


CREATE TABLE `category` (
    `id` smallint(6) UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '分类id',
    `parentid` smallint(6) NOT NULL DEFAULT 0 COMMENT '父类别id当id=0时说明是根节点,一级类别',
    `name` varchar(50) NOT NULL DEFAULT '' COMMENT '类别名称',
    `status` tinyint(4) NOT NULL DEFAULT 1 COMMENT '类别状态1-正常,2-已废弃',
    `create_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
    `update_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
    PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='商品类别表';

shopping cart

 CREATE DATABASE cart;
USE cart;

CREATE TABLE `cart` (
    `id` bigint(20) UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '购物车id',
    `userid` bigint(20) UNSIGNED NOT NULL DEFAULT 0 COMMENT '用户id',
    `proid` bigint(20) UNSIGNED NOT NULL DEFAULT 0 COMMENT '商品id',
    `quantity` int(11) NOT NULL DEFAULT 0 COMMENT '数量',
    `checked` int(11) NOT NULL DEFAULT 0 COMMENT '是否选择,1=已勾选,0=未勾选',
    `create_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
    `update_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
    PRIMARY KEY (`id`),
    KEY `ix_userid` (`userid`),
    KEY `ix_proid` (`proid`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='购物车表';

Order related:

 CREATE DATABASE order;
USE order;

CREATE TABLE `orders` (
    `id` varchar(64) NOT NULL DEFAULT '' COMMENT '订单id',
    `userid` bigint(20) UNSIGNED NOT NULL DEFAULT 0 COMMENT '用户id',
    `shoppingid` bigint(20) NOT NUMBER DEFAULT 0 COMMENT '收货信息表id',
    `payment` decimal(20,2) DEFAULT NULL DEFAULT 0 COMMENT '实际付款金额,单位是元,保留两位小数',
    `paymenttype` tinyint(4) NOT NULL DEFAULT 1 COMMENT '支付类型,1-在线支付',
    `postage` int(10)  NOT NULL DEFAULT 0 COMMENT '运费,单位是元',
    `status` smallint(6) NOT NULL DEFAULT 10 COMMENT '订单状态:0-已取消-10-未付款,20-已付款,30-待发货 40-待收货,50-交易成功,60-交易关闭',
    `payment_time` timestamp NOT NULL DEFAULT '0000-00-00 00:00:00' COMMENT '支付时间',
    `send_time` timestamp NOT NULL DEFAULT '0000-00-00 00:00:00' COMMENT '发货时间',
    `end_time` timestamp NOT NULL DEFAULT '0000-00-00 00:00:00' COMMENT '交易完成时间',
    `close_time` timestamp NOT NULL DEFAULT '0000-00-00 00:00:00' COMMENT '交易关闭时间',
    `create_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
    `update_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
    PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='订单表';

CREATE TABLE `orderitem` (
     `id` bigint(20) UNSIGNED NOT NULL COMMENT '订单子表id',
     `orderid` varchar(64) NOT NULL DEFAULT '' COMMENT '订单id',
     `userid` bigint(20) UNSIGNED NOT NULL DEFAULT 0 COMMENT '用户id',
     `proid` bigint(20) UNSIGNED NOT NULL DEFAULT 0 COMMENT '商品id',
     `proname` varchar(100) NOT NULL DEFAULT '' COMMENT '商品名称',
     `proimage` varchar(500) NOT NULL DEFAULT '' COMMENT '商品图片地址',
     `currentunitprice` decimal(20,2) NOT NULL DEFAULT 0 COMMENT '生成订单时的商品单价,单位是元,保留两位小数',
     `quantity` int(10) NOT NULL DEFAULT 0 COMMENT '商品数量',
     `totalprice` decimal(20,2) NOT NULL DEFAULT 0 COMMENT '商品总价,单位是元,保留两位小数',
     `create_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
     `update_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
     PRIMARY KEY (`id`),
     KEY `ix_orderid` (`orderid`),
     KEY `ix_userid` (`userid`),
     KEY `ix_proid` (`proid`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='订单明细表';

CREATE TABLE `shopping` (
    `id` bigint(20) UNSIGNED NOT NULL COMMENT '收货信息表id',
    `orderid` varchar(64) NOT NULL DEFAULT '' COMMENT '订单id',
    `userid` bigint(20) UNSIGNED NOT NULL DEFAULT 0 COMMENT '用户id',
    `receiver_name` varchar(20) NOT NULL DEFAULT '' COMMENT '收货姓名',
    `receiver_phone` varchar(20) NOT NULL DEFAULT '' COMMENT '收货固定电话',
    `receiver_mobile` varchar(20) NOT NULL DEFAULT '' COMMENT '收货移动电话',
    `receiver_province` varchar(20) NOT NULL DEFAULT '' COMMENT '省份',
    `receiver_city` varchar(20) NOT NULL DEFAULT '' COMMENT '城市',
    `receiver_district` varchar(20) NOT NULL DEFAULT '' COMMENT '区/县',
    `receiver_address` varchar(200) NOT NULL DEFAULT '' COMMENT '详细地址',
    `create_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
    `update_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
    PRIMARY KEY (`id`),
    KEY `ix_orderid` (`orderid`),
    KEY `ix_userid` (`userid`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='收货信息表';

Payment related:

 CREATE DATABASE pay;
USE pay;

CREATE TABLE `payinfo` (
    `id` bigint(20) UNSIGNED NOT NULL COMMENT '支付信息表id',
    `orderid` varchar(64) NOT NULL DEFAULT '' COMMENT '订单id',
    `userid` bigint(20) UNSIGNED NOT NULL DEFAULT 0 COMMENT '用户id',
    `payplatform` tinyint(4) NOT NULL DEFAULT 0 COMMENT '支付平台:1-支付宝,2-微信',
    `platformnumber` varchar(200) NOT NULL DEFAULT '' COMMENT '支付流水号',
    `platformstatus` varchar(20) NOT NULL DEFAULT '' COMMENT '支付状态',
    `create_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
    `update_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
    PRIMARY KEY (`id`),
    KEY `ix_orderid` (`orderid`),
    KEY `ix_userid` (`userid`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='支付信息表';

concluding remarks

This article introduces how to define the API, and generate the service code through goctl according to the defined api file. The whole project involves a lot of APIs, which cannot be defined at one time, and will be supplemented continuously in the future.

Then it demonstrates how to call the RPC service in the BFF service to open up the entire call chain. This is just for demonstration, so the code is written dead, and the data returned by the RPC will be obtained from the cache or database.

Finally, the main libraries and tables involved in the entire project are defined. We adopt the micro-service architecture, the data between services is isolated, and each service has its own database.

The preparatory work in the early stage is basically completed, and the main task is to complete the business functions according to the requirements and optimize the response to high concurrency.

Due to the limited level of the author, there will inevitably be misunderstandings. If you find there are areas that can be improved, I hope to get your valuable opinions.

In addition, if you are interested, you are very welcome to join, we will work together to complete this project and contribute to the community.

Hope this article is helpful to you, thank you.

Updated every Monday and Thursday

Code repository https://github.com/zhoushuguang/lebron

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.


kevinwan
931 声望3.5k 粉丝

go-zero作者