6.9 KiB
6.9 KiB
如何加一个新服务
以加 order 为例(对标现有 product)。
0. 安装 goctl
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)
在仓库根目录执行:
goctl rpc new order
cd order
2. 将 proto 移到 proto/ 目录
goctl rpc new 默认把 proto 放在服务根目录(如 order/order.proto)。统一挪到 proto/:
mkdir proto
mv order.proto proto/
4. 配置 Zero 兼容 Nacos
编辑 order/internal/config/config.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:
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 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
)
go mod tidy
编辑 order/go.mod 增加本地包 pkg.local
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:
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:增加本地包
import (
"pkg.local/discovery"
)
再次执行
go mod tidy
8. 接入 Docker Compose
在 deploy/docker-compose.override.yml 增加服务:
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
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):
- 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
protoc -I. -I../pkg/third_party --go_out=. --go-grpc_out=. proto/order.proto
什么时候跑:
- 只改 校验规则(必填/长度/范围等)→ 只跑这条
- 增删改字段、增删 RPC → 也要跑(服务端描述符要更新)
命令 ②:生成 BFF ProtoSet
protoc -I. -I../pkg/third_party --descriptor_set_out=../bff/etc/product.pb --include_imports proto/order.proto
什么时候跑:
- 增删改 字段(BFF gateway 编解码需要)
- 增删 RPC
命令 ③:goctl 生成/更新脚手架
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 拦截器。
必填字符串
string name = 1 [(buf.validate.field).string = {min_len: 1, max_len: 256}];
min_len: 1 表示不能为空串。
可选字符串(仅限长度)
string subhead = 2 [(buf.validate.field).string = {max_len: 255}];
空串可通过;有内容时限制最大长度。
必填数值(含 0 合法)
double price = 6 [(buf.validate.field).double = {gte: 0}];
int32 period_validity = 16 [(buf.validate.field).int32 = {gte: 0}];
非必填数值(0 表示未传,跳过校验)
double store_price = 7 [(buf.validate.field) = {
ignore: IGNORE_IF_ZERO_VALUE,
double: {gte: 0}
}];
有值时仍要求 >= 0;未传/为 0 不校验。
枚举 / 固定取值(0 表示未传)
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
int64 id = 1 [(buf.validate.field).int64 = {gt: 0}];
无规则字段
uint32 number = 20; // 不做 protovalidate
删除字段编号(避免复用)
// reserved 2;
业务条件校验(如「普通商品必须带齐价格」)仍写在 internal/logic,不要全塞进 proto。