## 如何加一个新服务 以加 `order` 为例(对标现有 `product`)。 ### 0. 安装 goctl ```bash go install github.com/zeromicro/go-zero/tools/goctl@latest goctl --version go install google.golang.org/protobuf/cmd/protoc-gen-go@latest go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@latest ``` ### 1. 创建 RPC 服务(goctl) 在仓库根目录执行: ```bash goctl rpc new order cd order ``` ### 2. 将 proto 移到 `proto/` 目录 `goctl rpc new` 默认把 proto 放在服务根目录(如 `order/order.proto`)。统一挪到 `proto/`: ```bash mkdir proto mv order.proto proto/ ``` ### 4. 配置 Zero 兼容 Nacos 编辑 `order/internal/config/config.go`: ```go package config import "github.com/zeromicro/go-zero/zrpc" type Config struct { zrpc.RpcServerConf Nacos NacosConf } type NacosConf struct { Hosts []string NamespaceId string `json:",optional"` Group string `json:",optional"` RegisterIP string `json:",optional"` } ``` ### 5. 配置服务名与监听端口 `order/etc/order.yaml`: ```yaml Name: order-service # 注册到 Nacos 的 serviceName;BFF Upstream.Target 必须与此一致 ListenOn: 0.0.0.0:10200 Nacos: Hosts: - rnacos:8848 NamespaceId: test Group: LONE_SERVICES RegisterIP: order # Docker Compose 里的服务名 / 主机名 Mysql: Host: mysql Port: 3306 User: root Password: "123123" Database: dms-order Charset: utf8mb4 Prefix: ``` ### 3. 构建 mod 编辑 `order/go.mod` ```go go 1.24.3 require ( github.com/zeromicro/go-zero v1.10.2 google.golang.org/grpc v1.80.0 google.golang.org/protobuf v1.36.11 ) ``` ```bash go mod tidy ``` 编辑 `order/go.mod` 增加本地包 pkg.local ```go require ( github.com/zeromicro/go-zero v1.10.2 google.golang.org/grpc v1.80.0 google.golang.org/protobuf v1.36.11 pkg.local v0.0.0 ) replace pkg.local => ../pkg ``` ### 6. 服务注册 编辑 `order/order.go`: ```go func main() { flag.Parse() var c config.Config conf.MustLoad(*configFile, &c) if err := discovery.Init(discovery.Config{ Hosts: c.Nacos.Hosts, NamespaceId: c.Nacos.NamespaceId, Group: c.Nacos.Group, }); err != nil { log.Fatalf("nacos init: %v", err) } registerIP := c.Nacos.RegisterIP port, err := listenPort(c.ListenOn) if err != nil { log.Fatalf("parse ListenOn: %v", err) } if err := discovery.Register(discovery.Instance{ ServiceName: c.Name, IP: registerIP, Port: port, Group: c.Nacos.Group, }); err != nil { log.Fatalf("nacos register: %v", err) } defer func() { if err := discovery.Deregister(); err != nil { log.Printf("nacos deregister: %v", err) } }() ctx := svc.NewServiceContext(c) s := zrpc.MustNewServer(c.RpcServerConf, func(grpcServer *grpc.Server) { order.RegisterOrderServer(grpcServer, server.NewOrderServer(ctx)) if c.Mode == service.DevMode || c.Mode == service.TestMode { reflection.Register(grpcServer) } }) defer s.Stop() fmt.Printf("Starting rpc server at %s...\n", c.ListenOn) s.Start() } func listenPort(listenOn string) (uint64, error) { _, portStr, err := net.SplitHostPort(listenOn) if err != nil { return 0, err } return strconv.ParseUint(portStr, 10, 64) } ``` 编辑 `order/order.go`:增加本地包 ```go import ( "pkg.local/discovery" ) ``` 再次执行 ```bash go mod tidy ``` ### 8. 接入 Docker Compose 在 `deploy/docker-compose.override.yml` 增加服务: ```yaml order: image: golang:1.24.3 volumes: - ..:/src - go-mod-cache:/go/pkg/mod working_dir: /src/order command: go run . -f etc/order.yaml ports: - "10200:10200" environment: NACOS_SERVER_ADDR: rnacos:8848 depends_on: - rnacos ``` ### 9. 生成 ProtoSet 给 BFF ```bash protoc --include_imports --proto_path=proto --descriptor_set_out=../bff/etc/order.pb order.proto ``` ### 10. 在 BFF 配 Upstream + Mapping 编辑 `bff/etc/bff.yaml`,增加一段(go-zero gateway **每个 RPC 都要写 Mapping**): ```yaml - Name: order Grpc: Target: order-service # 必须等于 order.yaml 的 Name Timeout: 5000 ProtoSets: - etc/order.pb Mappings: - Method: post Path: /admin/v3/order/ping RpcPath: order.Order/Ping ``` ### 11. 加接口需要 只改业务逻辑(`internal/logic/`)不必跑 goctl。 改 `proto/order.proto`(新增/改名 RPC、改字段等)后,按文末「三条命令何时用」处理; 若 HTTP 路径有变,再改第 10 步的 Mapping。 --- ### 命令 ①:生成服务端 pb / grpc ```bash protoc -I. -I../pkg/third_party --go_out=. --go-grpc_out=. proto/order.proto ``` **什么时候跑:** - 只改 **校验规则**(必填/长度/范围等)→ **只跑这条** - 增删改字段、增删 RPC → 也要跑(服务端描述符要更新) ### 命令 ②:生成 BFF ProtoSet ```bash protoc -I. -I../pkg/third_party --descriptor_set_out=../bff/etc/product.pb --include_imports proto/order.proto ``` **什么时候跑:** - 增删改 **字段**(BFF gateway 编解码需要) - 增删 **RPC** ### 命令 ③:goctl 生成/更新脚手架 ```bash goctl rpc protoc proto/order.proto --go_out=. --go-grpc_out=. --zrpc_out=. ``` **什么时候跑:** - **新增 RPC**:需要生成 `internal/logic`、更新 `server` / `productclient` 等 - 注意:可能覆盖已改过的 `product.go` 等,生成后对比合并;也可用手写 logic/server 代替 ## Proto 常用校验规则(protovalidate) 文档:https://protovalidate.com/schemas/standard-rules/ 依赖:`import "buf/validate/validate.proto";`,服务启动挂 `pkg.local/validate` 拦截器。 ### 必填字符串 ```protobuf string name = 1 [(buf.validate.field).string = {min_len: 1, max_len: 256}]; ``` `min_len: 1` 表示不能为空串。 ### 可选字符串(仅限长度) ```protobuf string subhead = 2 [(buf.validate.field).string = {max_len: 255}]; ``` 空串可通过;有内容时限制最大长度。 ### 必填数值(含 0 合法) ```protobuf double price = 6 [(buf.validate.field).double = {gte: 0}]; int32 period_validity = 16 [(buf.validate.field).int32 = {gte: 0}]; ``` ### 非必填数值(0 表示未传,跳过校验) ```protobuf double store_price = 7 [(buf.validate.field) = { ignore: IGNORE_IF_ZERO_VALUE, double: {gte: 0} }]; ``` 有值时仍要求 `>= 0`;未传/为 0 不校验。 ### 枚举 / 固定取值(0 表示未传) ```protobuf uint32 type = 19 [(buf.validate.field) = { ignore: IGNORE_IF_ZERO_VALUE, uint32: {in: [1, 2, 3]} }]; uint32 sales_model = 4 [(buf.validate.field) = { ignore: IGNORE_IF_ZERO_VALUE, uint32: {in: [1, 2]} }]; ``` ### ID 必须大于 0 ```protobuf int64 id = 1 [(buf.validate.field).int64 = {gt: 0}]; ``` ### 无规则字段 ```protobuf uint32 number = 20; // 不做 protovalidate ``` ### 删除字段编号(避免复用) ```protobuf // reserved 2; ``` 业务条件校验(如「普通商品必须带齐价格」)仍写在 `internal/logic`,不要全塞进 proto。