关于go:gozero微服务实战系列三API定义和表结构设计

5次阅读

共计 15391 个字符,预计需要花费 39 分钟才能阅读完成。

<!– go-zero 微服务实战系列(三、API 定义和表结构设计)–>

前两篇文章别离介绍了本系列文章的背景以及依据业务职能对商城零碎做了服务的拆分,其中每个服务又可分为如下三类:

  • api 服务 – BFF 层,对外提供 HTTP 接口
  • rpc 服务 – 外部依赖的微服务,实现繁多的业务性能
  • rmq 服务 – 负责流式工作的解决,如生产 kafka 等等
  • admin 服务 – 对外部治理后盾提供 HTTP 接口,通常数据操作权限比拟高

如果没看过前两篇文章可通过如下传送门查看

go-zero 微服务实战系列(一、开篇)

go-zero 微服务实战系列(二、服务拆分)

前两篇文章比拟偏实践,以至于文章收回去后有些同学感觉写得比拟水,十分了解大家迫切想要写代码的情绪,我也进行了粗浅的反思哈哈哈。所以从本篇开始就要进入万众期待的代码环节了。然而,所谓磨刀不误砍柴工,在真正的生产开发过程中,咱们个别都会花大量的工夫在需要的了解和协定的设计上,如果需要了解的不透彻或者协定设计的不合理就会大大增加咱们我的项目返工的可能,甚至还没上线就得重构。所以后期多投入一些工夫也齐全是值得的。当咱们把需要了解透彻,我的项目构造和协定定义清晰后,其实写代码就是因势利导的事件,速度那是大大滴快。闲言少叙,咱们开始明天的内容。

API 定义

可能大家在工作中都遇到过这样的场景,就是代码更新了然而文档没有更新,从而产生一些问题导致一些扯皮事件的产生。这个问题的实质是服务和文档是割裂的。咱们冀望的是文档即协定,协定即服务,这个理念与 go-zero 的 api 定义不约而同。

咱们定义了 BFF 层,BFF 是对外提供 HTTP 接口的对立进口,所以咱们这里 API 的定义次要是针对 BFF 服务的 API 的定义。

API 的兼容性

咱们定义或批改 API 的时候肯定要思考向前兼容,如下几种状况是向前兼容的:

  • 减少新的 API 接口协议
  • 申请参数增加字段,须要保障新老客户端对该字段的解决形式不同
  • 响应后果增加字段,该字段信息只会在新版本客户端中展现

如下几种状况是向前不兼容的:

  • 删除或重命名服务、字段、办法等,从实质上说,如果客户端代码能够援用某些内容,那么删除或者重命名它都是不兼容的变动,这时必须批改 major 版本号
  • 批改字段类型,这会导致客户端库生成的代码发生变化,因而必须减少 major 版本号,对于编译型动态语言来说,可能会编译谬误
  • 批改现有申请的可见行为,客户端通常依赖于 API 行为和语义,即便这样的行为没有被明确反对或记录。因而,在大多数状况下,批改 API 数据的行为或语义将被消费者视为是破坏性的
  • 给资源音讯增加 读取 / 写入 字段
首页 API 定义

首页性能次要分为四个局部,搜寻、Banner 图、限时抢购和举荐商品列表,点击搜寻框会跳转到搜寻页,举荐局部是分页展现的,用户通过一直地往上滑动能够加载下一页。通过剖析首页咱们大抵须要提供三个接口,别离是 Banner 接口,限时抢购接口和举荐接口。

这里须要留神的是举荐接口,举荐接口返回的数据是须要反对分页的,这里分页采纳游标的形式,Ps 参数为每页返回数据条数,默认一页返回 20 条数据,留神在服务端肯定须要再次校验 Ps 值,避免 Ps 歹意值导致的性能问题,比方 Ps 传了 10000,当为非法值的时候须要把 Ps 置为默认值,Cursor 为游标值,游标为每页最初一条数据的 RecommendTime。

返回值中 Products 定义了返回的商品列表,IsEnd 示意是否是最初一页,客户端通过判断 IsEnd 是否为 true 决定是否终止申请,RecommendTime 为本页返回数据最初一条数据的举荐工夫,推动列表依照举荐工夫倒序返回。

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"` // 更新工夫
}

抢购有一个倒计时的性能,咱们这里返回抢购开始工夫,客户端计算剩余时间进行倒计时。

FlashSaleResponse {
        StartTime int64      `json:"start_time"` // 抢购开始工夫
        Products  []*Product `json:"products"`}
分类 API 定义

分类列表中能够切换不同的 tab 来抉择不同的分类,同时在每一种分类上面又能够依照不同的维度进行排序,且反对分页。

分类商品列表和举荐接口的分页形式一样,都是采纳游标的形式,同时分类商品列表须要依据不同的分类和排序属性进行排序,此类须要排序的列表咱们个别会通过 redis 的 sorted set 来实现,score 为须要排序的属性,比方销量,member 为对应商品的 id。

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"`
}

提到 sorted set 在这里说一个笔者应用 sorted set 已经踩过的一个坑。咱们应用缓存的罕用姿态是 cache aside 模式,即先读缓存,如果缓存命中则间接从缓存中返回数据,如果读取缓存 miss 了,则回源到 DB 中读数据,且为了前面更快的读取数据,从 DB 中读取的数据会回塞到缓存中,且会给缓存设置一个过期工夫。

而为了保障缓存和数据库数据的一致性,当咱们新增数据的时候须要把这条数据也写到缓存中从而保障缓存和数据库数据统一,个别代码会这么写,先通过 Exists 判断缓存对应的 key 是否存在,如果存在就往 sorted set 中减少一条数据,如果不存在则不解决,期待下次来读取列表的时候从新加载列表数据到缓存中。咱们发现有时候缓存中列表数据会变成一条,然而数据其实是有多条的,过后感觉是很诡异的,通过排查最终定位到问题,原来是 Exists 操作和 Zadd 两个操作不是原子的操作导致的,也就是在 Exists 的时候缓存的 Key 还没有过期,然而在 Exists 后和进行 Zadd 前这个 key 过期了,而后再执行 Zadd 就导致缓存列表中就只有本次新增的这条数据了。解决这个问题的方法也很简略,不应用 Exists 判断 key 是否存在,而是通过 Expire 给这个 key 续期,如果 key 不存在则 Expire 返回 0,key 存在则 Expire 返回 1,续期胜利。缓存的应用咱们还踩过很多坑,特地是在高并发的场景下,这个后续文章再具体介绍。

购物车 API 定义

在这里咱们对购物车的数量做一下限度,咱们限度购物车最多只能加 200 个商品,这样做是为了在全选的时候下单不会导致过高的写放大,因为加了 200 条的限度,所以购物车列表不须要分页。

购物车列表申请和返回定义如下:

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

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

    CartProduct {
        Product *Product `json:"product"`
        Count   int64    `json:"count"` // 购买数量
    }
商品评估 API 定义

商品评估的性能同样也是须要反对分页的,采纳游标的形式进行分页,同时依照评论工夫进行倒序

评论列表定义如下:

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"`
    }

以上列出了一些外围的 API 的定义,商城的性能点十分多,很难短时间内全副定义完,笔者会在工作之余一直的欠缺。定义接口返回数据的时候咱们要尽量的收敛只返回必要的数据。

定义好 api 后,咱们应用如下命令从新生成我的项目代码,输入如下信息表明生成胜利

$ 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 定义

因为 BFF 只负责数据的组装工作,数据真正的起源是各个微服务通过 RPC 接口提供,接下来咱们来定义各个微服务的 proto。如下展现的订单列表页面由两局部数据组成,别离是订单数据和商品数据,也就是咱们的 BFF 须要依赖 order-rpc 和 product-rpc 来实现该页面数据的组装,上面咱们别离来定义 order-rpc 和 product-rpc

order.proto 定义如下,service 名字为 Order,增加了 Orders 获取订单列表 rpc 接口。

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;
}

应用如下命令从新生成代码,留神这里须要依赖 protoc-gen-goprotoc-gen-go-grpc两个插件,木有装置的话执行上面命令会报错

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

生成好后而后启动 order-rpc 服务,输入如下:

$ 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

什么状况?居然报错了,还好日志输入的比拟具体,通过日志能够看进去如同是本地的 etcd 没有启动,那咱们就把本地的 etcd 启动,启动后再次运行 order rpc 服务,曾经侦听在默认的 8080 端口上

$ go run order.go

Starting rpc server at 127.0.0.1:8080...

product.proto 定义如下

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;
}

执行如下命令生成 product rpc 的代码

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

留神,goctl 生成的 rpc 服务默认侦听在 8080 端口,因为咱们当初是在本地测试,所以把 product rpc 默认的端口改为 8081,而后启动服务。

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...

因为咱们的 BFF 须要依赖 order.rpc 和 product.rpc,咱们须要先增加配置文件,如下:

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

而后在 ServiceContext 中增加 RPC 的客户端,如下:

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)),
    }
}

最初只有在订单接口的 logic 办法中增加逻辑就能够啦,这里只是演示,所以会比较简单:

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
}

而后在浏览器中申请订单接口,就能够看到输入了如下的数据,阐明从 BFF 到 RPC 的链路曾经买通:

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
}

表构造定义

不同的微服务间须要做数据的隔离,每个微服务独占数据库资源,通过 RPC 调用来获取数据依赖,整体架构如下图所示:

通过以上对 API 的定义咱们大抵理解了须要哪些数据字段,上面开始进行数据表的设计,建表语句放在我的项目根目录下 data.sql 文件中,该文件会不断更新,次要波及的库和表定义如下:

用户表次要保留用户信息,在 user 库中后续可能还会扩大比方用户积分,用户等级等性能

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='用户表';

商品库中次要波及商品表和商品分类表:

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='商品类别表';

购物车

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='购物车表';

订单相干:

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='收货信息表';

领取相干:

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='领取信息表';

结束语

本篇文章介绍了如何定义 API,并依据定义好的 api 文件通过 goctl 生成服务代码,整个我的项目波及的 api 十分多,没方法一次性定义完,后续还会一直的补充。

接着演示了如何在 BFF 服务中调用 RPC 服务,把整个调用链路买通,这里只是为了演示所以写死了代码,前面 RPC 返回的数据会从缓存或者数据库中获取。

最初定义了整个我的项目次要波及的库和表,咱们采纳了微服务的架构,服务间数据做了隔离,每个服务独享了数据库。

到这里后期的筹备工作根本实现了,前面次要就是依照需要实现业务性能,和应答高并发来做优化。

因为笔者程度无限,难免会呈现了解有误的中央,如果你发现有能够改良的中央,心愿可能失去你贵重的意见。

另外,如果你感兴趣,十分欢送你退出,咱们一起来实现这个我的项目,为社区献出本人的一份力。

心愿本篇文章对你有所帮忙,谢谢。

每周一、周四更新

代码仓库 https://github.com/zhoushugua…

我的项目地址

https://github.com/zeromicro/go-zero

欢送应用 go-zerostar 反对咱们!

微信交换群

关注『微服务实际 』公众号并点击 交换群 获取社区群二维码。

正文完
 0