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

🚀 优质资源分享 🚀

学习路线指引(点击解锁)知识定位人群定位
🧡 Python实战微信订餐小程序 🧡进阶级本课程是python flask+微信小程序的完美结合,从项目搭建到腾讯云部署上线,打造一个全栈订餐系统。
💛Python量化交易实战💛入门级手把手带你打造一个易扩展、更安全、效率更高的量化交易系统

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

  • 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"`          // 商品IDName        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"`          // 评论IDProductID  int64    `json:"product\_id"`  // 商品IDContent    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"`     // 用户IDName   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.goStarting 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.goStarting 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:2379Key: product.rpc
$ go run product.goStarting 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:2379Key: order.rpc
ProductRPC:Etcd:Hosts:- 127.0.0.1:2379Key: product.rpc

然后在ServiceContext中添加RPC的客户端,如下:

type ServiceContext struct {Config config.ConfigOrderRPC order.OrderProductRPC 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 []stringfor _, 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.Orderfor _, 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/zhoushuguang/lebron

项目地址

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

欢迎使用 go-zerostar 支持我们!

微信交流群

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/400872.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

mongo经验总结

http://wenku.baidu.com/view/7c279cbfc77da26925c5b0e4.html转载于:https://www.cnblogs.com/yuechaotian/archive/2013/02/19/2916692.html

STM32 串口DMA接收 Openmv / K210 整数、小数字符串数据 (基于HAL库)

目录前言一、工程配置二、串口DMA部分代码1.源文件UART_DMA.c2.头文件UART_DMA.h3.stm32f1xx_it.c的修改4.串口收发DMA测试三、字符串数字提取代码1.源文件NumAndStr.c:2.头文件NumAndStr.h:3.测试:四、Openmv / K210 发送、STM32接收测试总结修订版本UART_DMA.cUART_DMA.h平台…

6000字|22张图 带你彻底弄懂Zookeeper分布式锁

🚀 优质资源分享 🚀 学习路线指引(点击解锁)知识定位人群定位🧡 Python实战微信订餐小程序 🧡进阶级本课程是python flask微信小程序的完美结合,从项目搭建到腾讯云部署上线,打造一…

静态与非静态(转改)

这里有一个C的测试,由于c#的sizeof不能对一般类使用,所以没法试验。 class Test {int i;static int j;void Method1() { printf("Method1"); };void Method2() { printf("Method2"); };void Method3() { printf("Method3"…

【HDU2896】病毒侵袭——ac自动机

网上很多代码都略显繁琐,看了一下yy dalao的代码感觉很好,但他懒得打题解(好吧我也是 以0为根节点的话,我把yy的一段代码删了改用fail[c]x0?0:ch[fail[x]][i];来实现特判,效果还不错!也算是AC自动机的模版…

经典 HTML5 Javascript 俄罗斯方块游戏

Blockrain.js 是一个使用 HTML5 & JavaScript 开发的经典俄罗斯方块游戏。只需要复制和粘贴一段代码就可以玩起来了。最重要的是,它是响应式的,无论你的显示屏多么宽都能自动匹配。你可以自定义你想要的颜色以适应您的网站,也可以调整方块…

【电赛PID半天入门】从接触编码器到调出好康的PID波形

从接触编码器到调出好康的PID波形认识电机及编码器只需动动手指,就能让STM32得到电机转过的角度让电机转起来认识PID控制①比例调节器②积分调节③微分调节④比例积分微分调节数字PID调节器(1)数字PID位置型控制算法(2&#xff09…

算法的复杂度分析

🚀 优质资源分享 🚀 学习路线指引(点击解锁)知识定位人群定位🧡 Python实战微信订餐小程序 🧡进阶级本课程是python flask微信小程序的完美结合,从项目搭建到腾讯云部署上线,打造一…

PHP面向对象 封装与继承

知识点: PHP封装三个关键词: 一、public 公有的,被public修饰的属性和方法,对象可以任意访问和调用 二、private 私有的,被private修饰的属性和方法,只能在类内部的方法可以进行调用,或者被子类…

POJ 1380 坐标旋转

题意&#xff1a; 问第二个矩形能不能放进第一个矩形中。 题解&#xff1a; 暴力旋转第二个矩形&#xff0c;判断左右、上下是否同时小于第一个矩形 当然&#xff0c;数学推导也可以&#xff0c;挺简单的相似神马的胡搞就行~ View Code 1 #include <iostream>2 #include…

冲刺省选习题集

1.火柴棒等式 单击此处看题目 考察算法&#xff1a;数学分析枚举 1 #include <iostream>2 #include <cstdio>3 using namespace std;4 5 const int match[10]{6,2,5,5,4,5,6,3,7,6};6 7 int n;8 9 int merge(int x){ 10 int sum0; 11 if(x0)return 6; 12 …

MSP430F5529 DriverLib 库函数学习笔记(一)时钟配置和闪烁LED

目录一、新建工程二、时钟树时钟系统结构时钟系统的原理时钟树配置实战三、点灯工程师封装好的初始化函数平台&#xff1a;Code Composer Studio 10.3.1 MSP430F5529 LaunchPad™ Development Kit (MSP‑EXP430F5529LP) 一、新建工程 二、时钟树 时钟系统结构 &#xff08;1&…

解决maven依赖冲突,这篇就够了!

&#x1f680; 优质资源分享 &#x1f680; 学习路线指引&#xff08;点击解锁&#xff09;知识定位人群定位&#x1f9e1; Python实战微信订餐小程序 &#x1f9e1;进阶级本课程是python flask微信小程序的完美结合&#xff0c;从项目搭建到腾讯云部署上线&#xff0c;打造一…

SOAP 1.1 - 学习

SOAP 1.1 --- 每天发送SOAP request&#xff0c;不搞明白啥叫SOAP,惭愧 SOAP 1.1 XML 架构定义 <xs:schema xmlns:xs"http://www.w3.org/2001/XMLSchema" xmlns:tns"http://schemas.xmlsoap.org/soap/envelope/" targetNamespace"h…

Codeforces Round #168 (Div. 2)---A. Lights Out

Lights Outtime limit per test2 secondsmemory limit per test256 megabytesinputstandard inputoutputstandard outputLenny is playing a game on a 3  3 grid of lights. In the beginning of the game all lights are switched on. Pressing any of the lights will tog…

【C#/VB.NET】 将PDF转为SVG/Image, SVG/Image转PDF

&#x1f680; 优质资源分享 &#x1f680; 学习路线指引&#xff08;点击解锁&#xff09;知识定位人群定位&#x1f9e1; Python实战微信订餐小程序 &#x1f9e1;进阶级本课程是python flask微信小程序的完美结合&#xff0c;从项目搭建到腾讯云部署上线&#xff0c;打造一…

hiho模拟面试题2 补提交卡 (贪心,枚举)

题目&#xff1a; 时间限制:2000ms单点时限:1000ms内存限制:256MB描写叙述 小Ho给自己定了一个雄伟的目标&#xff1a;连续100天每天坚持在hihoCoder上提交一个程序。100天过去了。小Ho查看自己的提交记录发现有N天由于贪玩忘记提交了。于是小Ho软磨硬泡、强忍着小Hi歧视的眼神…

libcurl上传文件

libcurl参数很多&#xff0c;一不小心就容易遇到问题。曾经就遇到过一个很蛋疼的问题&#xff1a;libcurl断点下载>> 这里主要汇总一下&#xff0c;libcurl上传的二种方式&#xff1a; 1、直接上传文件&#xff0c;类似form表单<input type”file” />&#xff0c;…

MSP430F5529 DriverLib 库函数学习笔记(二)GPIO

目录硬知识一、MSP430单片机端口概述二、通用IO端口输出特性三、端口P1和P21&#xff0e;输入寄存器PxIN2&#xff0e;输出寄存器PxOUT3&#xff0e;方向寄存器PxDIR4&#xff0e;上拉/下拉电阻使能寄存器PxREN5&#xff0e;输出驱动能力调节寄存器PxDS6&#xff0e;功能选择寄…

IOS项目中加入Google Admob SDK

错误1. Undefined symbols for architecture i386: "_OBJC_CLASS_$_ASIdentifierManager", referenced from: objc-class-ref in libGoogleAdMobAds.a(GADIdentifierUtilities.o) "_OBJC_CLASS_$_SKStoreProductViewController", referenced from: …