RPC服务的编写|豆包MarsCode AI刷题

59 阅读4分钟

本小文用于记录如何编写一个完整的rpc通讯的client和service部分,不包括前端的调用

首先明确我们需要什么,依据rpc的基本通讯流程,我首先需要client和service的代码,利用cwgo(kitex)的代码生成能力分别生成双端的代码,需要注意的点有两个,1.服务端和客户端共用一个idl作为依赖2.服务端的代码不再生成客户端代码,将客户端代码统一管理(从代码角度就是要pass -use)

完成这些就有了一个基础的rpc框架

在开发前,要注意一个问题,在 go mod 开发模式下,相对路径的导包方式不支持。会报错误: local import "./package1" in non-local package

解决方案

go.mod 文件中使用 replace 语法,将要导入的包替换为本地的相对地址

github.com/hcdog/pycode/gomall/rpc_gen => ../../rpc_gen

2.2.1 定义用户功能的数据模型

数据模型:数据库系统的核心组成部分,它提供了一种抽象的方式来描述和组织数据,以及这些数据之间的关系。数据模型定义了数据的结构、类型、约束条件以及数据之间的操作。它是数据库设计和实现的基础,为数据库系统的构建、使用和维护提供了指导。

建立一个结构体,为后续的代码提供可传递的变量,这是client代码和service代码生成完后第一个要做的事

这里涉及到一个关键的东西,标签tag

标签(Tag)通常指的是结构体字段后面跟着的反引号(``)内的字符串。这些标签为结构体字段提供了元数据,可以被用于多种目的,如文档说明,但更关键的是用于JSON序列化/反序列化、数据库ORM映射、验证等。

1.数据库ORM映射:

结构体关联数据库,正常操作数据库是使用mysql语句,但是为了简化这一流程,我们使用结构体映射到数据库,这样开发起来就会比较简单,这也是我们的主要用法,依据查到的资料,常见的gorm标签有:

1. `gorm:"primary_key"`: 定义字段作为模型的主键。

 2. `gorm:"column:<column_name>"`: 指定字段在数据库表中的列名。

 3. `gorm:"type:<data_type>"`: 指定字段的数据库数据类型。 

4. `gorm:"unique"`: 定义字段的值在数据库中是唯一的。 

5. `gorm:"not null"`: 定义字段在数据库中不允许为空。 

6. `gorm:"default:<default_value>"`: 指定字段的默认值。 

7. `gorm:"size:"`: 指定字段的大小,通常用于字符串类型字段。

 8. `gorm:"autoIncrement"`: 定义字段自增。

 9. `gorm:"index"`: 在数据库中为字段创建索引。 

10. `gorm:"uniqueIndex"`: 在数据库中为字段创建唯一索引。 

11. `gorm:"primaryKey"`: 定义字段为数据库表的主键。 

12. `gorm:"default:"`: 指定字段的默认值。 

13. `gorm:"-"`: 定义字段在数据库模型中不可见。 

14. `gorm:"type:<data_type>;size:"`: 结合使用 `type` 和 `size` 标签,可以同时指定字段的数据类型和大小。 

15. `gorm:"association_foreignkey:<column_name>"`: 定义关联表中外键的列名。 

16. `gorm:"many2many:<join_table_name>"`: 定义多对多关联关系,并指定连接表的表名。 

17. `gorm:"->;"`: 指定外键约束,例如 `ONDELETE:CASCADE`。

比如我们这里编写一个商品的数据模型,设置一个标签

type Base struct {ID int gorm:"primarykey"CreatedAt time.TimeUpdatedAt time.Time}

这里设置的标签就是这个id是int类型,且是主键,作为唯一标识

2.JSON序列化/反序列化:

字符串转化为go对象是反序列化,反之是序列化

比如这个结构体

type Product struct {

Base

Name string json:"name"

Description string json:"description"

Picture string json:"picture"

Price float32 json:"price"

Categories []Category json:"categories" gorm:"many2many:product_category"

}

这里就是说 Name 变量会被命名为name,这意味着当Product结构体被序列化为JSON时,Name字段将出现在JSON对象中作为name键的值;同样地,当从JSON反序列化回Product结构体时,JSON对象中的name键的值将被赋给Name字段。

自定义表名称

别忘了写一个自定义表名称函数,不然创建的表都是结构体的名字

func (p Product) TableName() string {

return "product"

}

后续有数据处理方面方法再写在这里,暂时结束,可以开始编写服务代码

2.2.2改造数据库初始化

为刚才建立的数据模型,配置mysql初始化函数,在主函数中完成初始化

2.2.3编写服务函数

主要在service文件夹下的各个服务文件

逻辑很简单,想清楚需要什么,比如,我要让注册时候密码两次输入一致,那就写

if req.Password != req.ConfirmPassword {err = errors.New("Password must be the same as ConfirmPassword")return}

2.2.4服务注册

服务编写完后,在主函数中完成服务注册

r, err := consul.NewConsulRegister("127.0.0.1:8500")
if err != nil {    
log.Fatal(err)
}
opts = append(opts, server.WithRegistry(r))

注册完后,你可以在localhost:8500(即你设定的consul端口)查看服务情况。若注册不成功,注意更改配置文件中的address为本地ip+服务端口号"172.30.0.1:8888"

以上就完成了一个服务的后端部分的程序编写,后续再在前端代码中写相关接口和调用