欢迎来到尧图网

客户服务 关于我们

您的位置:首页 > 文旅 > 艺术 > 【Go】十六、protobuf构建基础服务信息、grpc服务启动的基础信息

【Go】十六、protobuf构建基础服务信息、grpc服务启动的基础信息

2025/2/28 12:02:05 来源:https://blog.csdn.net/weixin_41365204/article/details/145916243  浏览:    关键词:【Go】十六、protobuf构建基础服务信息、grpc服务启动的基础信息

商品服务

服务结构

创建 goods 服务,将之前 user 服务的基本结构迁移到 goods 服务上,完整目录是:

mxshop_srvs

user_srv

tmp

goods_srv

config

config.go 配置的读取表

global

global.go 数据库、日志初始化、全局变量定义

handler

xxx.go 拦截器

initialize

xxx.go 初始化信息

model

xxx.go 数据库表、数据库对象

proto proto 相关信息

xxx.proto

xxx.pb.go

xxx_grpc.pb.go

tests 测试信息

utils

xxx.go 工具

config-debug.yaml 配置信息

main.go 启动类

数据表结构

在 model 目录中创建需要的表结构对应的数据对象:

  • 创建基础数据类:base.go:
package modelimport ("gorm.io/gorm""time"
)type BaseModel struct {ID        int32     `gorm:"primarykey";type:int` // 注意这里对应数据库的int,我们进行统一定义,避免出现问题,若数据量过大也可以采用bigintCreatedAt time.Time `gorm:"column:add_time"`UpdatedAt time.Time `gorm:"column:update_time"`DeletedAt gorm.DeletedAtIsDeleted bool
}
  • 创建商品表
package model// 商品分类数据对象:一级分类、二级分类...
type Category struct {BaseModelName             string    `gorm:"type:varchar(20);not null;"` // 分类名ParentCategoryID int32     // 父分类IDParentCategory   *Category // 父分类对象	此处因为是自己指向自己,必须使用指针Level            int32     `gorm:"type:int;not null;default:1"` // 分类级别IsTab            bool      `gorm:"default:false;not null"`      // 是否显示在 Tab 栏
}// 品牌数据对象
type Brands struct {BaseModelName string `gorm:"type:varchar(20);not null"`Logo string `gorm:"type:varchar(200);default:'';not null"`
}// 品牌 / 类型 对应表
type GoodsCategoryBrand struct {BaseModelCategoryID int32 `gorm:"type:int;index:idx_category_brand,unique"`Category   CategoryBrandsID   int32 `gorm:"type:int;index:idx_category_brand,unique"`Brands     Brands
}// 轮播图数据对象
type Banner struct {BaseModelImage string `gorm:"type:varchar(200);not null"`Url   string `gorm:"type:varchar(200);not null"`Index int32  `gorm:"type:int;default:1;not null"`
}
  • 商品对象的创建,注意:商品对象的创建较为复杂,单独拎出来处理:
// 商品表
type Goods struct {BaseModelCategoryID int32 `gorm:"type:int;not null"`Category   CategoryBrandsID   int32 `gorm:"type:int;not null"`Brands     BrandsOnSale   bool `gorm:"default:false;not null"` // 是否上架ShipFree bool `gorm:"default:false;not null"` // 是否xxxISNew    bool `gorm:"default:false;not null""`IsHot    bool `gorm:"default:false;not null"`Name            string   `gorm:"type:varchar(50);not null"`GoodsSn         string   `gorm:"type:varchar(50);not null"`ClickNum        int32    `gorm:"type:int;default:0;not null"`SoldNum         int32    `gorm:"type:int;default:0;not null"`FavNum          int32    `gorm:"type:int;default:0;not null"`MarketPrice     float32  `gorm:"not null"`ShopPrice       float32  `gorm:"not null"`GoodsBrief      string   `gorm:"type:varchar(100);not null"`Images          GormList `gorm:"type:varchar(1000);not null"`DescImages      GormList `gorm:"type:varchar(1000);not null"`GoodsFrontImage string   `gorm:"type:varchar(200);not null"`
}

这里需要注意的是 对于 图片列表的处理,我们单独存储一个图片是没问题的,但是如果需要存储多个图片的话,我们就有两种方式选择了:

  1. 建立一个图片表,表里是所有的图片,每个图片存储一个归属商品,但这样的缺陷是无法避免连表操作,到后期数据量极大的时候,这种程度的连表能够造成极大的性能隐患。
  2. 直接将图片路径形成 json 格式的字符串,存储在表中,在 代码中通过 marshal 和 unmarshal 进行编码和解码,再进行图片的存取,这种方式有效规避了连表带来的性能损耗。

故而这里选用第二种方式。

这里就需要我们在 model/Base.go 中添加编解码的工具代码

type GormList []string// 设定这种变量在插入数据库的时候怎么插入:
// 这里是 将json格式的内容转换为 字符串再进行插入
func (g GormList) Value() (driver.Value, error) {return json.Marshal(g)
}// 设定在从数据库中取数据时,自动将数据转换为 []string 的列表
// 从数据库中取出来的时候是 GormList 数据类型,并将它的地址传入这个方法,直接修改其地址中的内容,将其修改为 []string
func (g *GormList) Scan(value interface{}) error {return json.Unmarshal(value.([]byte), &g)
}type BaseModel struct {ID        int32     `gorm:"primarykey";type:int` // 注意这里对应数据库的int,我们进行统一定义,避免出现问题,若数据量过大也可以采用bigintCreatedAt time.Time `gorm:"column:add_time"`UpdatedAt time.Time `gorm:"column:update_time"`DeletedAt gorm.DeletedAtIsDeleted bool
}

这样我们存入的数据或者取出的数据只要是 定义为了 GormList 格式,就会在存入时自动转为字符串,取出是自动转为 json

之后进行数据库创建操作

这里我们默认数据库自行创建完成了,利用 gorm 来建表:

goods_srv/model/main/main.go

package mainimport ("log""os""time""gorm.io/driver/mysql""gorm.io/gorm""gorm.io/gorm/logger""gorm.io/gorm/schema""mxshop_srvs/goods_srv/model"
)// 这里的代码是用来在数据库中建表的
func main() {dsn := "root:123456@tcp(192.168.202.140:3306)/mxshop_goods_srv?charset=utf8mb4&parseTime=True&loc=Local"// 添加日志信息newLogger := logger.New(log.New(os.Stdout, "\r\n", log.LstdFlags), // io writerlogger.Config{SlowThreshold:             time.Second, // Slow SQL thresholdLogLevel:                  logger.Info, // Log levelIgnoreRecordNotFoundError: true,        // Ignore ErrRecordNotFound error for loggerParameterizedQueries:      true,        // Don't include params in the SQL logColorful:                  true,        // Disable color,true is colorful, false to black and white},)db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{// 阻止向创建的数据库表后添加复数NamingStrategy: schema.NamingStrategy{SingularTable: true,},// 将日添加到配置中Logger: newLogger,})if err != nil {panic(err)}// 建表_ = db.AutoMigrate(&model.Category{}, &model.Brands{}, &model.GoodsCategoryBrand{}, &model.Banner{}, &model.Goods{})
}

这里相当于是一个商品的单元测试,用来做一些一次性的事情

protobuf 数据定义、定义所有的接口和请求和返回信息

注意这里,一定是先确定好需要的所有的需要的接口信息,再进行后续的接口定义操作

下面是全量的详细proto信息:

syntax = "proto3";
import "google/protobuf/empty.proto";
option go_package = ".;proto";// 在这里定义一个个的接口
service Goods {// 商品部分// 获取商品的接口,包括条件获取rpc GoodsList(GoodsFilterRequest) returns(GoodsListResponse);// 批量查询商品信息的接口,避免查商品时发生一个一个调用服务、一条一条查的低效情况rpc BatchGetGoods(BatchGoodsIdInfo) returns(GoodsListResponse);// 添加商品rpc CreateGoods(CreateGoodsInfo) returns(GoodsInfoResponse);// 删除商品,没有明确需要返回的信息,返回一个占位符rpc DeleteGoods(DeleteGoodsInfo) returns(google.protobuf.Empty);// 更新商品信息rpc UpdateGoods(CreateGoodsInfo) returns(google.protobuf.Empty);// 获取商品信息(单独获取)rpc GetGoodsDetail(GoodInfoRequest) returns(GoodsInfoResponse);// 分类部分// 获取所有商品分类rpc GetAllCategorysList(google.protobuf.Empty) returns(CategoryListResponse);// 获取子分类 todo 把这个补齐,文件在桌面上,视频已经看完了rpc GetSubCategory(CategoryListRequest) returns(SubCategoryListResponse);rpc CreateCategory(CategoryInfoRequest) returns(CategoryInfoResponse);rpc DeleteCategory(DeleteCategoryRequest) returns(google.protobuf.Empty);rpc UpdateCategory(CategoryInfoRequest) returns(google.protobuf.Empty);// 品牌部分rpc BrandList(BrandFilterRequest) returns(BrandListResponse);rpc CreateBrand(BrandRequest) returns(BrandInfoResponse);rpc DeleteBrand(BrandRequest) returns(google.protobuf.Empty);rpc UpdateBrand(BrandRequest) returns(google.protobuf.Empty);// 轮播图部分rpc BannerList(google.protobuf.Empty) returns(BannerListResponse);rpc CreateBanner(BannerRequest) returns(BannerResponse);rpc DeleteBranner(BannerRequest) returns(google.protobuf.Empty);rpc UpdateBanner(BannerRequest) returns(google.protobuf.Empty);// 品牌分类信息// 过滤需要的品牌、分类信息rpc CategoryBrandList(CategoryBrandFilterRequest) returns(CategoryBrandListResponse);// 获取某个分类下所有品牌的接口rpc GetCategoryBrandList(CategoryInfoRequest) returns(BrandListResponse);rpc CreateCategoryBrand(CategoryBrandRequest) returns(CategoryBrandResponse);rpc DeleteCategoryBrand(CategoryBrandRequest) returns(google.protobuf.Empty);rpc UpdateCategoryBrand(CategoryBrandRequest) returns(google.protobuf.Empty);
}// 在过滤商品时传入的条件信息
message GoodsFilterRequest {int32 priceMin = 1;int32 priceMax = 2;bool isHot = 3;bool isNew = 4;bool isTab = 5;int32 topCategory = 6;int32 pages = 7;int32 pagePerNums = 8;string keyWords = 9;int32 brand = 10;
}// 单独的一条关联信息
message CategoryBrandRequest{int32 id = 1;int32 categoryId = 2;int32 brandId = 3;
}// 返回的品牌、分类信息集合、也就是联系信息
message CategoryBrandListResponse {int32 total = 1;repeated CategoryBrandResponse data = 2;
}// 返回一个品牌信息、一个分类信息
message CategoryBrandResponse{int32 id = 1;BrandInfoResponse brand = 2;CategoryInfoResponse category = 3;
}// 轮播图的返回结果
message BannerListResponse {int32 total = 1;repeated BannerResponse data = 2;
}// 过滤品牌、分类信息请求
message CategoryBrandFilterRequest  {int32 pages = 1;int32 pagePerNums = 2;
}// 单个轮播图
message BannerResponse {int32 id = 1;int32 index = 2;string image = 3;string url = 4;
}// 单个轮播图的请求
message BannerRequest {int32 id = 1;int32 index = 2;string image = 3;string url = 4;
}// 过滤品牌请求的信息
message BrandFilterRequest {int32 pages = 1;int32 pagePerNums = 2;
}// 品牌查询请求
message BrandRequest {int32 id = 1;string name = 2;string logo = 3;
}// 创建分类的请求信息
message CategoryInfoRequest {int32 id = 1;string name = 2;int32 parentCategory = 3;int32 level = 4;bool isTab = 5;
}// 传入删除信息的ID
message DeleteCategoryRequest {int32 id = 1;
}// 商品ID 列表,便于批量查询
message BatchGoodsIdInfo {repeated int32 id = 1;
}// 获取分类信息集合
message CategoryListResponse {int32 total = 1;repeated CategoryInfoResponse data = 2;string jsonData = 3;
}// 获取子分类集合(需要传入选中分类的id,level选传)
message CategoryListRequest {int32 id = 1;int32 level = 2;
}// 子分类的返回
message SubCategoryListResponse {int32 total = 1;CategoryInfoResponse info = 2;    // 将本分类的所有信息返回repeated CategoryInfoResponse subCategorys = 3;  // 将子分类的所有信息返回
}// 分类信息
message CategoryInfoResponse {int32 id = 1;string name = 2;int32 parentCategory = 3;int32 level = 4;bool isTab = 5;
}// 获取单独商品详情
message GoodInfoRequest {int32 id = 1;
}// 商品列表的返回信息
message GoodsListResponse {int32 total = 1;repeated GoodsInfoResponse data = 2;
}// 单个商品的信息
message GoodsInfoResponse {int32 id = 1;int32 categoryId = 2;string name = 3;string goodsSn = 4;int32 clickNum = 5;int32 soldNum = 6;int32 favNum = 7;float marketPrice = 9;float shopPrice = 10;string goodsBrief = 11;string goodsDesc = 12;bool shipFree = 13;repeated string images = 14;repeated string descImages = 15;string goodsFrontImage = 16;bool isNew = 17;bool isHot = 18;bool onSale = 19;int64 addTime = 20;CategoryBriefInfoResponse category = 21;BrandInfoResponse brand = 22;
}// 删除时传入一个ID
message DeleteGoodsInfo {int32 id = 1;
}// 创建商品去要传递的信息
message CreateGoodsInfo {int32 id = 1;string name = 2;string goodsSn = 3;int32 stocks = 7;float marketPrice = 8;float shopPrice = 9;string goodsBrief = 10;string goodsDesc = 11;bool shipFree = 12;repeated string images = 13;repeated string descImages = 14;string goodsFrontImage = 15;bool isNew = 16;bool isHot = 17;bool onSale = 18;int32 categoryId = 19;int32 brandId = 20;
}// 商品分类的简要信息
message CategoryBriefInfoResponse {int32 id = 1;string name = 2;
}message CategoryFilterRequest {int32 id = 1;string name = 2;
}// 品牌单个信息
message BrandInfoResponse {int32 id = 1;string name = 2;string logo = 3;
}// 品牌列表信息
message BrandListResponse {int32 total = 1;repeated BrandInfoResponse data = 2;
}

protobuf 文件的生成

在对应的文件夹目录下输入:

// 标准版
protoc --go_out=. xxxx.proto
// gprc 定制版
protoc --go_out=. --go-grpc_out=. *.proto

就可以在当前目录下创建好我们所需要的 xxxx.pb.go 文件,这个文件就是我们的以proto 作为传输协议的正式接口文件。

注意:此命令需要 protoc 环境完善,并配置好完整的环境变量(protoc 的环境变量)

protobuf 的构建

此时,我们发现,我们的接口过于多了,这就需要我们分开进行,我们在 .pb.go 文件中找到: GoodsServer,这里面定义的就是所有的接口,我们在 handler 文件中将所有的接口进行定义:

handler

banner.go

brands.go

goods.go

category.go

category_brand.go

示例定义(goods.go):

测试定义:若我们希望进行快速测试,就可以给 自己的 GoodsServer 添加一个属性,有这个属性存在,就可以进行服务器快速测试。

package handlerimport ("mxshop_srvs/goods_srv/proto"
)type GoodsServer struct {proto.UnimplementedGoodsServer
}

记得修改 main文件中的 注册:

	proto.RegisterGoodsServer(server, &handler.GoodsServer{})

之后进行Nacos 相关配置:
创建一个新的命名空间、添加文件:goods-srv.json :

{"name": "goods-srv","tags": ["imooc", "bobby", "goods", "srv"],"web-host": "192.168.10.108","mysql": {"host": "192.168.202.140","port": 3306,"db": "mxshop_goods_srv","user": "root","password": "123456"},"consul": {"host": "192.168.202.140","port": 8500}
}

之后修改启动的配置文件的命名空间的ID

记得同步修改 config.go 中,新添加了一个 Tags 标签

type ServerConfig struct {// 这里是为了配置服务端口使用的,后期会移植到//Host       string       `mapstruce:"host" json:"host"`//Port       int          `mapstruct:"port" json:"port"`Name       string       `mapstructure:"name" json:"name"`Tags       []string     `mapstructure:"tags" json:"tags"`MysqlInfo  MysqlConfig  `mapstructure:"mysql" json:"mysql"`ConsulInfo ConsulConfig `mapstructure:"consul" json:"consul"`WebHost    string       `json:"web-host"`
}

然后在 main 中读取:

registration.Tags = global.ServerConfig.Tags

GRPC服务启动的相关信息

main.go:

package mainimport ("flag""fmt""go.uber.org/zap""mxshop_srvs/goods_srv/global""mxshop_srvs/goods_srv/initialize""mxshop_srvs/goods_srv/utils""net""os""os/signal""syscall""github.com/hashicorp/consul/api""github.com/satori/go.uuid""google.golang.org/grpc""google.golang.org/grpc/health""google.golang.org/grpc/health/grpc_health_v1""mxshop_srvs/goods_srv/handler""mxshop_srvs/goods_srv/proto"
)func main() {// 由于ip和端口号有可能需要用户输入,所以这里摘出来// flag 包是一个命令行工具包,允许从命令行中设置参数IP := flag.String("ip", "0.0.0.0", "ip地址")Port := flag.Int("port", 0, "端口号")initialize.InitLogger()initialize.InitConfig()flag.Parse()fmt.Println("ip: ", *IP)// 设置端口号自动获取if *Port == 0 {*Port, _ = utils.GetFreePort()}fmt.Println("port: ", *Port)// 创建新服务器server := grpc.NewServer()// 注册自己的已实现的方法进来proto.RegisterGoodsServer(server, &handler.GoodsServer{})//lis, err := net.Listen("tcp", fmt.Sprintf("192.168.202.140:8021"))lis, err := net.Listen("tcp", fmt.Sprintf("%s:%d", *IP, *Port))if err != nil {panic("failed to listen" + err.Error())}// 绑定服务健康检查grpc_health_v1.RegisterHealthServer(server, health.NewServer())// 服务注册cfg := api.DefaultConfig()cfg.Address = fmt.Sprintf("%s:%d", global.ServerConfig.ConsulInfo.Host, global.ServerConfig.ConsulInfo.Port)client, err := api.NewClient(cfg)if err != nil {panic(err)}check := &api.AgentServiceCheck{GRPC:     fmt.Sprintf("%s:%d", global.ServerConfig.Host, *Port),Interval: "5s",//Timeout:                        "10s",DeregisterCriticalServiceAfter: "30s",}registration := new(api.AgentServiceRegistration)registration.Address = global.ServerConfig.Host//registration.Address = "127.0.0.1"//registration.ID = global.ServerConfig.Name		// 此处修改为使用 UUID 生成serviceID := fmt.Sprintf("%s", uuid.NewV4()) // 此处修改为使用 UUID 生成registration.ID = serviceIDregistration.Port = *Portregistration.Tags = global.ServerConfig.Tagsregistration.Name = global.ServerConfig.Nameregistration.Check = checkerr = client.Agent().ServiceRegister(registration)if err != nil {panic(err)}//err = server.Serve(lis)// 注意此处是阻塞式的所以需要一个 goroutine 来进行异步操作// 将自己的服务绑定端口go func() {err = server.Serve(lis)if err != nil {panic("fail to start grpc" + err.Error())}}()// 创建一个通道quit := make(chan os.Signal)signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)// 阻塞住,若接到请求则放通,直接将服务注销<-quitif err = client.Agent().ServiceDeregister(serviceID); err != nil {zap.S().Info("注销失败...")}zap.S().Info("注销成功")
}

InitConfig:(配置文件的相关信息)

intialize/config.go:

package initializeimport ("encoding/json""fmt""github.com/nacos-group/nacos-sdk-go/clients""github.com/nacos-group/nacos-sdk-go/vo""github.com/nacos-group/nacos-sdk-go/common/constant""github.com/spf13/viper""go.uber.org/zap""mxshop_srvs/goods_srv/global"
)func GetEnvInfo(env string) bool {viper.AutomaticEnv()var rs boolrs = viper.GetBool(env)return rsreturn true
}func InitConfig() {debug := GetEnvInfo("MXSHOP-DEBUG")zap.S().Info(fmt.Sprintf("------------", debug))configFileNamePrefix := "config"configFileName := fmt.Sprintf("goods_srv/%s-pro.yaml", configFileNamePrefix)if debug {configFileName = fmt.Sprintf("goods_srv/%s-debug.yaml", configFileNamePrefix)}v := viper.New()v.SetConfigFile(configFileName)if err := v.ReadInConfig(); err != nil {panic(err)}// 将配置文件进行解析if err := v.Unmarshal(&global.NacosConfig); err != nil {panic(err)}sc := []constant.ServerConfig{{IpAddr: global.NacosConfig.Host,Port:   global.NacosConfig.Port,},}cc := constant.ClientConfig{TimeoutMs:           5000,NamespaceId:         global.NacosConfig.Namespace,CacheDir:            "tmp/nacos/cache",NotLoadCacheAtStart: true,LogDir:              "tmp/nacos/log",LogLevel:            "debug",}configClient, err := clients.CreateConfigClient(map[string]interface{}{"serverConfigs": sc,"clientConfig":  cc,})if err != nil {zap.S().Fatalf("%s", err.Error())}content, err := configClient.GetConfig(vo.ConfigParam{DataId: global.NacosConfig.Dataid,Group:  global.NacosConfig.Group,})if err != nil {zap.S().Fatalf("%s", err.Error())}err = configClient.ListenConfig(vo.ConfigParam{DataId: global.NacosConfig.Dataid,Group:  global.NacosConfig.Group,OnChange: func(namespace, group, dataId, data string) {fmt.Println("配置文件发生变化")fmt.Println("namespace: " + namespace)fmt.Println("group: " + group)fmt.Println("dataId: " + dataId)fmt.Println("data: " + data)},})if err != nil {zap.S().Fatalf("%s", err.Error())}err = json.Unmarshal([]byte(content), &global.ServerConfig)if err != nil {zap.S().Fatalf("%s", err.Error())}zap.S().Info(global.ServerConfig)
}

此处还需要注意配置文件和 Nacos 的配置文件:

config-debug.yml

host: '192.168.202.140'
port: 8848
namespace: '043d2547-bd1e-44df-b097-75f649848099'
user: 'nacos'
password: 'nacos'
dataid: 'goods-srv.json'
group: 'dev'

Nacos配置:

{"name": "goods-srv","host": "192.168.10.107","mysql": {"host": "192.168.202.140","port": 3306,"db": "mxshop_user_srv","user": "root","password": "123456"},"consul": {"host": "192.168.202.140","port": 8500}
}

版权声明:

本网仅为发布的内容提供存储空间,不对发表、转载的内容提供任何形式的保证。凡本网注明“来源:XXX网络”的作品,均转载自其它媒体,著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

我们尊重并感谢每一位作者,均已注明文章来源和作者。如因作品内容、版权或其它问题,请及时与我们联系,联系邮箱:809451989@qq.com,投稿邮箱:809451989@qq.com