feat(goctl/rpc): support external proto imports with cross-package ty… (#5472)

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
kesonan
2026-03-22 12:01:20 +08:00
committed by GitHub
parent c12c82b2f6
commit 004995f06a
93 changed files with 4871 additions and 270 deletions

View File

@@ -0,0 +1,315 @@
# goctl rpc — RPC 代码生成
[English](README.md) | 中文
goctl rpc 是 `goctl` 脚手架下的 RPC 服务代码生成模块,基于 `.proto` 文件生成完整的 zRPC 服务代码。你只需编写 proto 定义和业务逻辑,其余代码均由工具自动生成。
## 特性
- **贴近 protoc**:与 protoc 完全兼容,透传所有 protoc 参数
- **外部 Proto 导入**:支持跨目录、跨包的 proto 导入,自动解析传递性依赖
- **多服务模式**:单个 proto 文件中定义多个 service按服务名自动分组
- **流式支持**:支持服务端流、客户端流和双向流
- **Google 标准类型**:自动识别 `google.protobuf.*` 类型并生成正确的 Go 导入
- **客户端生成**:自动生成封装好的 RPC 客户端代码
## 前置条件
```bash
# 安装 protoc 插件
go install google.golang.org/protobuf/cmd/protoc-gen-go@latest
go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@latest
```
## 快速开始
### 方式一:一键创建服务
```bash
goctl rpc new greeter
```
生成完整的项目结构:
```
greeter/
├── etc/
│ └── greeter.yaml
├── greeter/
│ ├── greeter.pb.go
│ └── greeter_grpc.pb.go
├── greeter.go
├── greeter.proto
├── greeterclient/
│ └── greeter.go
└── internal/
├── config/
│ └── config.go
├── logic/
│ └── pinglogic.go
├── server/
│ └── greeterserver.go
└── svc/
└── servicecontext.go
```
### 方式二:基于 Proto 文件生成
1. 生成 proto 模板:
```bash
goctl rpc template -o=user.proto
```
2. 初始化输出目录并生成服务代码:
```bash
mkdir -p output && cd output && go mod init example.com/demo && cd ..
goctl rpc protoc user.proto \
--go_out=output --go-grpc_out=output --zrpc_out=output \
--go_opt=module=example.com/demo --go-grpc_opt=module=example.com/demo \
--module=example.com/demo -I .
```
---
## 命令参考
### `goctl rpc protoc`
`.proto` 文件生成 zRPC 服务代码。
```bash
goctl rpc protoc <proto_file> [flags]
```
**示例:**
```bash
# 基础用法
goctl rpc protoc user.proto \
--go_out=output --go-grpc_out=output --zrpc_out=output \
--go_opt=module=example.com/demo --go-grpc_opt=module=example.com/demo \
--module=example.com/demo -I .
# 多服务模式
goctl rpc protoc multi.proto \
--go_out=output --go-grpc_out=output --zrpc_out=output \
--go_opt=module=example.com/demo --go-grpc_opt=module=example.com/demo \
--module=example.com/demo -I . -m
# 导入外部 proto
goctl rpc protoc service.proto \
--go_out=output --go-grpc_out=output --zrpc_out=output \
--go_opt=module=example.com/demo --go-grpc_opt=module=example.com/demo \
--module=example.com/demo -I . -I ./shared_protos
# 使用 Google 标准类型
goctl rpc protoc service.proto \
--go_out=output --go-grpc_out=output --zrpc_out=output \
--go_opt=module=example.com/demo --go-grpc_opt=module=example.com/demo \
--module=example.com/demo -I .
```
**参数说明:**
| 参数 | 缩写 | 类型 | 默认值 | 说明 |
|------|------|------|--------|------|
| `--zrpc_out` | | string | **必填** | zRPC 服务代码输出目录 |
| `--go_out` | | string | **必填** | protoc Go 代码输出目录 |
| `--go-grpc_out` | | string | **必填** | protoc gRPC 代码输出目录 |
| `--go_opt` | | string | | protoc-gen-go 选项(如 `module=example.com/demo` |
| `--go-grpc_opt` | | string | | protoc-gen-go-grpc 选项(如 `module=example.com/demo` |
| `--proto_path` | `-I` | string[] | | proto 导入搜索目录(可多次指定) |
| `--multiple` | `-m` | bool | `false` | 多服务模式 |
| `--client` | `-c` | bool | `true` | 是否生成 RPC 客户端代码 |
| `--style` | | string | `gozero` | 文件命名风格 |
| `--module` | | string | | 自定义 Go module 名称 |
| `--name-from-filename` | | bool | `false` | 使用文件名而非 package 名命名服务 |
| `--verbose` | `-v` | bool | `false` | 显示详细日志 |
| `--home` | | string | | goctl 模板目录 |
| `--remote` | | string | | 远程模板 Git 仓库地址 |
| `--branch` | | string | | 远程模板分支 |
### `goctl rpc new`
快速创建一个完整的 RPC 服务项目。
```bash
goctl rpc new <service_name> [flags]
```
**参数说明:**
| 参数 | 缩写 | 类型 | 默认值 | 说明 |
|------|------|------|--------|------|
| `--style` | | string | `gozero` | 文件命名风格 |
| `--client` | `-c` | bool | `true` | 是否生成 RPC 客户端代码 |
| `--module` | | string | | 自定义 Go module 名称 |
| `--verbose` | `-v` | bool | `false` | 显示详细日志 |
| `--idea` | | bool | `false` | 生成 IDE 项目标记 |
| `--name-from-filename` | | bool | `false` | 使用文件名而非 package 名命名服务 |
| `--home` | | string | | goctl 模板目录 |
| `--remote` | | string | | 远程模板 Git 仓库地址 |
| `--branch` | | string | | 远程模板分支 |
### `goctl rpc template`
生成 proto 文件模板。
```bash
goctl rpc template -o=<output_file> [flags]
```
**参数说明:**
| 参数 | 类型 | 说明 |
|------|------|------|
| `-o` | string | 输出文件路径(必填) |
| `--home` | string | goctl 模板目录 |
| `--remote` | string | 远程模板 Git 仓库地址 |
| `--branch` | string | 远程模板分支 |
---
## 功能详解
### 多服务模式(`--multiple`
当 proto 文件包含多个 `service` 定义时,必须使用 `--multiple` 标志。
```protobuf
service SearchService {
rpc Search(SearchReq) returns (SearchReply);
}
service NotifyService {
rpc Notify(NotifyReq) returns (NotifyReply);
}
```
**启用 `--multiple` 后的目录变化:**
| 特性 | 默认模式 | `--multiple` 模式 |
|------|---------|-------------------|
| 服务数量 | 仅 1 个 | 1 个或多个 |
| 客户端目录 | 以服务名命名 | 固定为 `client/` |
| 代码组织 | 扁平结构 | 按服务名分组 |
**`--multiple=false`(默认)的目录结构:**
```
output/
├── greeterclient/
│ └── greeter.go
├── internal/
│ ├── logic/
│ │ └── sayhellologic.go
│ └── server/
│ └── greeterserver.go
└── ...
```
**`--multiple=true` 的目录结构:**
```
output/
├── client/
│ ├── searchservice/
│ │ └── searchservice.go
│ └── notifyservice/
│ └── notifyservice.go
├── internal/
│ ├── logic/
│ │ ├── searchservice/
│ │ │ └── searchlogic.go
│ │ └── notifyservice/
│ │ └── notifylogic.go
│ └── server/
│ ├── searchservice/
│ │ └── searchserviceserver.go
│ └── notifyservice/
│ └── notifyserviceserver.go
└── ...
```
### 外部 Proto 导入(`--proto_path`
通过 `-I` / `--proto_path` 指定额外的 proto 搜索目录,支持以下场景:
- **同目录导入**`import "types.proto";`
- **子目录导入**`import "common/types.proto";`
- **外部目录导入**proto 文件位于项目外部
- **传递性导入**A 导入 BB 导入 Cgoctl 自动递归解析
- **跨包导入**:不同 `go_package` 的 proto 文件,自动生成正确的 Go 导入
```bash
# 从多个目录搜索 proto 文件
goctl rpc protoc service.proto \
--go_out=output --go-grpc_out=output --zrpc_out=output \
--go_opt=module=example.com/demo --go-grpc_opt=module=example.com/demo \
--module=example.com/demo \
-I . -I ./shared_protos -I /path/to/external_protos
```
### 服务命名
默认情况下,服务名称来自 proto 的 **package 名称**(例如 `package user;` → 服务名 `user`)。这使得多个 proto 文件可以共享同一个 package
```
protos/
├── user_base.proto # package user;
├── user_auth.proto # package user;
└── user_profile.proto # package user;
```
三个文件会生成到同一个 `user` 服务中。
如需使用 proto 文件名命名(旧版行为),请添加 `--name-from-filename` 标志。
### 流式 RPC
支持 gRPC 的三种流式模式:
```protobuf
service StreamService {
rpc ServerStream(Req) returns (stream Reply); // 服务端流
rpc ClientStream(stream Req) returns (Reply); // 客户端流
rpc BidiStream(stream Req) returns (stream Reply); // 双向流
}
```
### Google 标准类型
goctl 自动识别并正确处理 Google protobuf 标准类型:
| Proto 类型 | Go 类型 |
|-----------|---------|
| `google.protobuf.Empty` | `emptypb.Empty` |
| `google.protobuf.Timestamp` | `timestamppb.Timestamp` |
| `google.protobuf.Duration` | `durationpb.Duration` |
| `google.protobuf.Any` | `anypb.Any` |
| `google.protobuf.Struct` | `structpb.Struct` |
| `google.protobuf.FieldMask` | `fieldmaskpb.FieldMask` |
| `google.protobuf.*Value` | `wrapperspb.*Value` |
这些类型可直接用作 RPC 参数类型goctl 会自动生成正确的导入。
---
## 完整示例
详见 [example/](example/) 目录,包含 10 个完整示例,覆盖所有生成场景。
| # | 示例 | 场景 |
|---|------|------|
| 01 | [基础服务](example/01-basic/) | 单服务,无导入 |
| 02 | [同级导入](example/02-import-sibling/) | 导入同目录 proto |
| 03 | [子目录导入](example/03-import-subdir/) | 导入子目录 proto |
| 04 | [传递性导入](example/04-transitive-import/) | A → B → C 依赖链 |
| 05 | [多服务](example/05-multiple-services/) | `--multiple` 模式 |
| 06 | [标准类型](example/06-wellknown-types/) | 消息中使用 Timestamp 等 |
| 07 | [外部 Proto同包](example/07-external-proto-same-pkg/) | 外部 proto相同 go_package |
| 08 | [外部 Proto跨包](example/08-external-proto-diff-pkg/) | 外部 proto不同 go_package |
| 09 | [标准类型作参数](example/09-google-types-as-rpc/) | Empty/Timestamp 作为 RPC 参数 |
| 10 | [流式通信](example/10-streaming/) | 服务端/客户端/双向流 |