跳到主要内容

Proto 文件编写规范

1. 文件结构规范

1.1 基本结构

syntax = "proto3";

package gateway.v1;

// 导入顺序:标准库 -> 第三方库 -> 项目内部
import "google/api/annotations.proto";
import "google/protobuf/empty.proto";
import "google/protobuf/timestamp.proto";
import "validate/validate.proto";
import "openapi/v3/annotations.proto";
import "common_gateway.proto";

option go_package = "jingyun_center/services/gateway/api;api";

1.2 导入规范

  • 必需导入
    • google/api/annotations.proto - HTTP 路由
    • validate/validate.proto - 字段验证
    • openapi/v3/annotations.proto - OpenAPI 文档
  • 按需导入
    • google/protobuf/empty.proto - 空响应
    • google/protobuf/timestamp.proto - 时间戳
    • 其他项目内部 proto 文件

2. HTTP 路径规范

2.1 RESTful 路径设计

// 资源操作路径规范
service ResourceService {
// 创建资源: POST /resources
rpc CreateResource(CreateResourceRequest) returns (CreateResourceReply) {
option (google.api.http) = {
post: "/resources"
body: "*"
};
}

// 获取资源列表: GET /resources
rpc ListResources(ListResourcesRequest) returns (ListResourcesReply) {
option (google.api.http) = {
get: "/resources"
};
}

// 获取单个资源: GET /resources/{id}
rpc GetResource(GetResourceRequest) returns (GetResourceReply) {
option (google.api.http) = {
get: "/resources/{id}"
};
}

// 更新资源: PUT /resources/{id}
rpc UpdateResource(UpdateResourceRequest) returns (UpdateResourceReply) {
option (google.api.http) = {
put: "/resources/{id}"
body: "*"
};
}

// 删除资源: DELETE /resources/{id}
rpc DeleteResource(DeleteResourceRequest) returns (DeleteResourceReply) {
option (google.api.http) = {
delete: "/resources/{id}"
};
}
}

2.2 嵌套资源路径

service NestedResourceService {
// 获取用户的订单列表: GET /users/{user_id}/orders
rpc ListUserOrders(ListUserOrdersRequest) returns (ListUserOrdersReply) {
option (google.api.http) = {
get: "/users/{user_id}/orders"
};
}

// 获取租户的用户列表: GET /tenants/{tenant_id}/users
rpc ListTenantUsers(ListTenantUsersRequest) returns (ListTenantUsersReply) {
option (google.api.http) = {
get: "/tenants/{tenant_id}/users"
};
}
}

2.3 特殊操作路径

service SpecialActionService {
// 批量操作: POST /resources/batch
rpc BatchCreateResources(BatchCreateResourcesRequest) returns (BatchCreateResourcesReply) {
option (google.api.http) = {
post: "/resources/batch"
body: "*"
};
}

// 搜索操作: GET /resources/search
rpc SearchResources(SearchResourcesRequest) returns (SearchResourcesReply) {
option (google.api.http) = {
get: "/resources/search"
};
}

// 状态变更: POST /resources/{id}/activate
rpc ActivateResource(ActivateResourceRequest) returns (ActivateResourceReply) {
option (google.api.http) = {
post: "/resources/{id}/activate"
body: "*"
};
}
}

2.4 路径命名规范

  • 使用复数名词/users, /orders, /products
  • 使用小写字母/user-profiles, /order-items
  • 使用连字符分隔/user-profiles 而不是 /userProfiles
  • 避免动词:使用 HTTP 方法表示动作
  • 保持一致性:同类资源使用相同的命名模式

3. 分页查询规范

3.1 标准分页请求

message ListResourcesRequest {
option (openapi.v3.schema) = {
required: ["page", "page_size"]
};

// 页码(必填,从1开始)
int32 page = 1 [(validate.rules).int32.gte = 1];

// 每页大小(必填,1-100)
int32 page_size = 2 [(validate.rules).int32 = {gte: 1, lte: 100}];

// 搜索关键词(可选)
string keyword = 3 [(validate.rules).string.max_len = 100];

// 状态筛选(可选)
optional bool is_active = 4;

// 排序字段(可选,默认按创建时间倒序)
string sort_by = 5 [(validate.rules).string = {
in: ["created_at", "updated_at", "name", "id"]
}];

// 排序方向(可选,默认desc)
string sort_order = 6 [(validate.rules).string = {
in: ["asc", "desc"]
}];
}

3.2 标准分页响应

message ListResourcesReply {
// 资源列表
repeated Resource resources = 1;

// 分页信息
PaginationInfo pagination = 2;
}

// 分页信息
message PaginationInfo {
// 当前页码
int32 page = 1;

// 每页大小
int32 page_size = 2;

// 总记录数
int64 total_count = 3;

// 总页数
int32 total_pages = 4;

// 是否有下一页
bool has_next = 5;

// 是否有上一页
bool has_prev = 6;
}

3.3 游标分页(大数据量场景)

message ListResourcesCursorRequest {
option (openapi.v3.schema) = {
required: ["limit"]
};

// 每页大小(必填,1-100)
int32 limit = 1 [(validate.rules).int32 = {gte: 1, lte: 100}];

// 游标(可选,首次查询不传)
string cursor = 2;

// 筛选条件
string keyword = 3;
optional bool is_active = 4;
}

message ListResourcesCursorReply {
// 资源列表
repeated Resource resources = 1;

// 下一页游标(为空表示没有更多数据)
string next_cursor = 2;

// 是否有更多数据
bool has_more = 3;
}

3.4 分页参数验证规范

  • page: 必须 >= 1
  • page_size: 必须在 1-100 之间
  • sort_by: 只能是预定义的字段
  • sort_order: 只能是 "asc" 或 "desc"
  • keyword: 长度限制在 100 字符以内

4. 服务定义规范

4.1 服务注释

// ========================
// 服务名称 服务定义
// ========================
// 服务功能描述,说明该服务的主要职责和功能范围
service ServiceName {
// 接口方法定义...
}

4.2 接口方法规范

// 接口功能简述
rpc MethodName(RequestMessage) returns (ResponseMessage) {
option (google.api.http) = {
post: "/api/path"
body: "*"
};
option (openapi.v3.operation) = {
summary: "中文接口名称"
description: "详细的接口功能描述,包括使用场景和注意事项"
tags: "服务分组名称"
};
}

4.3 HTTP 方法映射

  • POST: 创建资源、复杂查询、登录认证
  • GET: 获取资源、简单查询
  • PUT: 更新资源(完整更新)
  • PATCH: 更新资源(部分更新)
  • DELETE: 删除资源

5. Message 定义规范

5.1 基本结构

// Message 功能描述
message MessageName {
option (openapi.v3.schema) = {
required: ["field1", "field2"] // 必填字段列表
};

// 字段中文说明(必须在字段上一行)
string field1 = 1 [(validate.rules).string = {min_len: 1}];

// 可选字段说明(必须在字段上一行)
string optional_field = 2;
}

5.2 字段注释规范

message ExampleRequest {
// 名称
string name = 1 [(validate.rules).string = {min_len: 1, max_len: 100}];
// duration_days
int32 duration_days = 2;
// 排序
int32 sort_order = 3;
// 价格
int64 price = 4;
// 原价
int64 original_price = 5;
// 版本描述(可选,详细介绍版本特性和功能)
string description = 6;
// 是否推荐(可选,默认false,是否为推荐版本)
bool is_featured = 7;
// 部署类型(必填,只能是预定义的枚举值)
DeploymentType deployment_type = 8 [(validate.rules).enum.defined_only = true];
// 状态
string status = 9;
// 创建时间
int64 created_at = 10;
// 更新时间
int64 updated_at = 11;
}

注释规范要点

  • 位置:注释必须写在字段的上一行
  • 格式:使用 // 开头,后跟一个空格
  • 内容:简洁说明字段用途,可选字段标注"可选",必填字段标注验证规则
  • 语言:使用中文,简洁明了
  • 一致性:项目中统一使用这种简洁的注释风格

## 6. 验证规则规范

### 6.1 字符串验证
```protobuf
// 必填字符串
string required_field = 1 [(validate.rules).string = {min_len: 1}];

// 长度限制
string name = 2 [(validate.rules).string = {min_len: 3, max_len: 50}];

// 正则表达式
string username = 3 [(validate.rules).string = {
min_len: 3
max_len: 32
pattern: "^[a-zA-Z0-9_]+$"
}];

// 枚举值
string status = 4 [(validate.rules).string = {
in: ["active", "inactive", "pending"]
}];

6.2 数值验证

// 整数范围
int32 age = 1 [(validate.rules).int32 = {gte: 0, lte: 150}];
int64 id = 2 [(validate.rules).int64.gt = 0];

// 浮点数
double price = 3 [(validate.rules).double = {gte: 0.0}];

6.3 数组验证

// 数组长度限制
repeated string tags = 1 [(validate.rules).repeated = {
min_items: 1
max_items: 10
}];

// 数组元素验证
repeated string emails = 2 [(validate.rules).repeated = {
min_items: 1
items: {
string: {
min_len: 5
pattern: "^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}$"
}
}
}];

7. 必填字段规范

7.1 OpenAPI Schema 注解

message CreateUserRequest {
option (openapi.v3.schema) = {
required: ["username", "email", "phone", "tenant_id"]
};

string username = 1 [(validate.rules).string = {min_len: 3}];
string email = 2 [(validate.rules).string = {min_len: 5}];
string phone = 3 [(validate.rules).string = {min_len: 11}];
int64 tenant_id = 4 [(validate.rules).int64.gt = 0];
string description = 5; // 可选字段不在 required 列表中
}

7.2 必填字段判断原则

  • 业务必需:业务逻辑必须的字段
  • 有验证规则:有 min_len: 1gt: 0 等规则的字段
  • 不可为空:不能接受空值的字段

8. 枚举定义规范

8.1 枚举命名规范

// 枚举类型注释
enum UserStatus {
USER_STATUS_UNSPECIFIED = 0; // 未指定状态(默认值)
USER_STATUS_ACTIVE = 1; // 激活状态,用户可正常使用
USER_STATUS_INACTIVE = 2; // 未激活状态,用户需要激活后才能使用
USER_STATUS_SUSPENDED = 3; // 暂停状态,用户被临时禁用
USER_STATUS_DELETED = 4; // 已删除状态,用户账号已被删除
}

// 或者带OpenAPI注解的枚举(需要导入protoc-gen-openapiv2)
enum DeploymentType {
option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_enum) = {
json_schema: {
title: "部署类型"
description: "版本的部署类型枚举"
}
};

DEPLOYMENT_TYPE_UNSPECIFIED = 0 [(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_enum_value) = {
description: "未指定类型(默认为云服务)"
}];
DEPLOYMENT_TYPE_CLOUD = 1 [(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_enum_value) = {
description: "云服务版本"
}];
DEPLOYMENT_TYPE_STANDALONE = 2 [(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_enum_value) = {
description: "独立部署版本"
}];
}

8.2 枚举使用规范

message User {
// 用户状态(必填,只能是预定义的枚举值)
UserStatus status = 1 [(validate.rules).enum.defined_only = true];
}

8.3 枚举导入要求

使用枚举注解需要导入以下文件:

import "protoc-gen-openapiv2/options/annotations.proto";

注意:项目中的枚举定义有两种风格:

  1. 简洁风格(推荐):仅使用行内注释,如tenant服务和agent服务中的枚举
  2. OpenAPI注解风格:添加完整的OpenAPI注解,适合需要生成详细API文档的场景

根据项目实际情况选择合适的风格。

8.4 枚举注解规范

8.4.1 枚举类型注解

enum OrderStatus {
option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_enum) = {
json_schema: {
title: "订单状态"
description: "订单的状态枚举,包含从创建到完成的所有状态"
}
};

// 枚举值定义...
}

8.4.2 枚举值注解

ORDER_STATUS_PENDING = 1 [(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_enum_value) = {
description: "待支付状态,订单已创建但未支付"
}];

8.5 常用枚举模式

8.5.1 状态类枚举

enum AgentType {
option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_enum) = {
json_schema: {
title: "智能体类型"
description: "智能体的类型分类"
}
};

AGENT_TYPE_UNSPECIFIED = 0 [(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_enum_value) = {
description: "未指定类型"
}];
AGENT_TYPE_LINK = 1 [(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_enum_value) = {
description: "链接类型智能体"
}];
AGENT_TYPE_WORKFLOW_COLLECTION = 2 [(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_enum_value) = {
description: "工作流集合类型智能体"
}];
AGENT_TYPE_WORKFLOW = 3 [(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_enum_value) = {
description: "工作流类型智能体"
}];
AGENT_TYPE_BOT = 4 [(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_enum_value) = {
description: "机器人类型智能体"
}];
AGENT_TYPE_PROMPT = 5 [(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_enum_value) = {
description: "提示词类型智能体"
}];
}

8.5.2 排序类枚举

enum SortOrder {
option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_enum) = {
json_schema: {
title: "排序方向"
description: "数据排序的方向"
}
};

SORT_ORDER_UNSPECIFIED = 0 [(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_enum_value) = {
description: "未指定排序方向"
}];
SORT_ORDER_ASC = 1 [(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_enum_value) = {
description: "升序排列"
}];
SORT_ORDER_DESC = 2 [(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_enum_value) = {
description: "降序排列"
}];
}

8.6 枚举命名约定

项目中使用两种枚举命名风格:

风格1:简洁前缀风格(推荐,如tenant服务)

  • 枚举类型名:使用 PascalCase,如 DisplayType, DeploymentType
  • 枚举值前缀:使用 TYPE_ 前缀,如 DISPLAY_TYPE_, DEPLOYMENT_TYPE_
  • 枚举值名:使用 UPPER_SNAKE_CASE,如 SIGNUP, RENEWAL, BOTH
  • 默认值:使用 UNSPECIFIED = 0 作为默认值

风格2:完整前缀风格(如agent服务)

  • 枚举类型名:使用 PascalCase,如 AgentType, PlatformType
  • 枚举值前缀:使用完整类型前缀,如 AGENT_TYPE_, PLATFORM_TYPE_
  • 枚举值名:使用 UPPER_SNAKE_CASE,如 LINK, BOT, WORKFLOW
  • 默认值:使用 UNSPECIFIED = 0 作为默认值

选择建议

  • 对于简单枚举,使用风格1
  • 对于可能与其他模块冲突的枚举,使用风格2
  • 保持同一服务内的风格一致性

8.7 枚举验证规范

message CreateOrderRequest {
// 订单类型(必填,只能是预定义的枚举值)
OrderType order_type = 1 [(validate.rules).enum.defined_only = true];

// 支付方式(可选,如果提供必须是有效枚举值)
PaymentMethod payment_method = 2 [(validate.rules).enum.defined_only = true];
}

8.8 枚举迁移指南

8.8.1 从 protoc-gen-openapi 迁移

将原有的 openapi.v3 注解替换为 openapiv2 注解:

旧格式(protoc-gen-openapi)

enum UserStatus {
option (openapi.v3.enum) = {
title: "用户状态"
description: "用户状态枚举"
};

USER_STATUS_ACTIVE = 1;
}

新格式(protoc-gen-openapiv2)

enum UserStatus {
option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_enum) = {
json_schema: {
title: "用户状态"
description: "用户状态枚举"
}
};

USER_STATUS_ACTIVE = 1 [(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_enum_value) = {
description: "激活状态"
}];
}

8.8.2 批量迁移步骤

  1. 更新导入语句:

    // 删除
    import "openapi/v3/annotations.proto";

    // 添加
    import "protoc-gen-openapiv2/options/annotations.proto";
  2. 替换枚举类型注解:

    • option (openapi.v3.enum)option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_enum)
  3. 添加枚举值注解:

    • 为每个枚举值添加 openapiv2_enum_value 注解
  4. 更新 Makefile 生成命令:

    # 确保使用 protoc-gen-openapiv2
    --openapiv2_out=allow_merge=true,merge_file_name=openapi,output_format=yaml:./

9. 常用验证模式

9.1 身份验证字段

// 手机号(中国大陆)
string phone = 1 [(validate.rules).string = {
min_len: 11
max_len: 20
pattern: "^1[0-9]{10}$"
}];

// 邮箱
string email = 2 [(validate.rules).string = {
min_len: 5
max_len: 255
pattern: "^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}$"
}];

// 用户名
string username = 3 [(validate.rules).string = {
min_len: 3
max_len: 32
pattern: "^[a-zA-Z0-9_]+$"
}];

// 密码
string password = 4 [(validate.rules).string = {
min_len: 6
max_len: 128
}];

// 验证码
string verification_code = 5 [(validate.rules).string = {
min_len: 4
max_len: 10
pattern: "^[0-9]{4,10}$"
}];

9.2 业务字段

// ID 字段
int64 id = 1 [(validate.rules).int64.gt = 0];

// 租户ID
int64 tenant_id = 2 [(validate.rules).int64.gt = 0];

// URL 字段
string url = 3 [(validate.rules).string = {uri: true}];

// 金额字段(分为单位)
int64 amount = 4 [(validate.rules).int64.gte = 0];

// 创建时间
int64 created_at = 5;

// 更新时间
int64 updated_at = 6;

10. 响应消息规范

10.1 标准响应结构

// 创建资源响应
message CreateResourceReply {
// 创建的资源对象
Resource resource = 1;
}

// 获取资源响应
message GetResourceReply {
// 资源对象
Resource resource = 1;
}

// 列表响应(使用标准分页)
message ListResourcesReply {
// 资源列表
repeated Resource resources = 1;
// 分页信息
PaginationInfo pagination = 2;
}

// 删除响应
message DeleteResourceReply {
// 是否成功
bool success = 1;
// 消息
string message = 2;
}

11. 错误处理规范

11.1 错误响应

// 使用标准的 google.rpc.Status
import "google/rpc/status.proto";

// 或自定义错误响应
message ErrorReply {
// 错误码
int32 code = 1;
// 错误消息
string message = 2;
// 详细信息
map<string, string> details = 3;
}

12. 完整示例

syntax = "proto3";

package tenant.v1;

option go_package = "jingyun_center/api/tenant/v1;v1";

import "google/api/annotations.proto";
import "google/protobuf/empty.proto";
import "google/protobuf/timestamp.proto";
import "validate/validate.proto";

// DisplayType 版本显示类型枚举
enum DisplayType {
DISPLAY_TYPE_UNSPECIFIED = 0; // 未指定(默认为都显示)
DISPLAY_TYPE_SIGNUP = 1; // 开户显示
DISPLAY_TYPE_RENEWAL = 2; // 续费显示
DISPLAY_TYPE_BOTH = 3; // 都显示
}

// DeploymentType 部署类型枚举
enum DeploymentType {
DEPLOYMENT_TYPE_UNSPECIFIED = 0; // 未指定(默认为云服务)
DEPLOYMENT_TYPE_CLOUD = 1; // 云服务版本
DEPLOYMENT_TYPE_STANDALONE = 2; // 独立部署版本
}

// CustomExplanation 自定义说明
message CustomExplanation {
// 说明项的键
string key = 1;
// 说明项的值
string value = 2;
}

// ========================
// 版本管理服务
// ========================
// 提供版本创建、更新、查询等功能
service VersionService {
// 创建版本
rpc CreateVersion(CreateVersionRequest) returns (CreateVersionReply) {
option (google.api.http) = {
post: "/versions"
body: "*"
};
}

// 获取版本列表
rpc ListVersions(ListVersionsRequest) returns (ListVersionsReply) {
option (google.api.http) = {
get: "/versions"
};
}

// 获取版本详情
rpc GetVersion(GetVersionRequest) returns (GetVersionReply) {
option (google.api.http) = {
get: "/versions/{id}"
};
}
}

// 创建版本请求
message CreateVersionRequest {
// 名称
string name = 1 [(validate.rules).string = {min_len: 1, max_len: 100}];
// 有效期(天)
int32 duration_days = 2;
// 排序
int32 sort_order = 3;
// 显示类型
DisplayType display_type = 4;
// 价格
int64 price = 5;
// 原价
int64 original_price = 6;
// 版本描述(可选,详细介绍版本特性和功能)
string description = 7;
// 是否推荐(可选,默认false,是否为推荐版本)
bool is_featured = 8;
// 部署类型(必填,只能是预定义的枚举值)
DeploymentType deployment_type = 9 [(validate.rules).enum.defined_only = true];
// 状态
string status = 10;
}

// 版本信息
message Version {
// 版本ID
int64 id = 1;
// 名称
string name = 2;
// 有效期(天)
int32 duration_days = 3;
// 排序
int32 sort_order = 4;
// 显示类型
DisplayType display_type = 5;
// 价格
int64 price = 6;
// 原价
int64 original_price = 7;
// 版本描述
string description = 8;
// 是否推荐
bool is_featured = 9;
// 部署类型
DeploymentType deployment_type = 10;
// 状态
string status = 11;
// 创建时间
int64 created_at = 12;
// 更新时间
int64 updated_at = 13;
}

// 创建版本响应
message CreateVersionReply {
// 创建的版本对象
Version version = 1;
}

// 获取版本列表请求
message ListVersionsRequest {
// 页码
int32 page = 1;
// 每页大小
int32 page_size = 2;
// 状态筛选
optional string status = 3;
}

// 获取版本列表响应
message ListVersionsReply {
// 版本列表
repeated Version versions = 1;
// 总数
int32 total = 2;
// 页码
int32 page = 3;
// 每页大小
int32 page_size = 4;
}

13. 检查清单

13.1 文件级检查

  • 正确的 syntax 声明
  • 合适的 package 名称
  • 必要的 import 导入
  • 正确的 go_package 选项

13.2 服务级检查

  • 服务有清晰的注释说明
  • 所有方法都有 HTTP 路由
  • 所有方法都有 OpenAPI 注解
  • 方法命名符合规范
  • HTTP 路径符合 RESTful 规范

13.3 消息级检查

  • 消息有功能描述注释
  • 必填字段在 required 列表中
  • 所有字段都有中文注释
  • 验证规则合理且完整
  • 字段类型选择恰当

13.4 分页规范检查

  • 分页请求包含 page 和 page_size
  • 分页参数有合理的验证规则
  • 分页响应包含完整的分页信息
  • 大数据量场景考虑使用游标分页

13.5 验证规则检查

  • 字符串字段有长度限制
  • 数值字段有范围限制
  • 必填字段有 min_len 或 gt 规则
  • 格式字段有正则表达式
  • 枚举字段有 defined_only 规则

13.6 枚举规范检查

  • 枚举有清晰的中文注释
  • 枚举有默认的 UNSPECIFIED = 0 值
  • 枚举命名符合项目风格(简洁前缀或完整前缀)
  • 枚举字段使用了 defined_only 验证规则(可选)
  • 如需OpenAPI文档,可添加 openapiv2 注解

遵循此规范可以确保生成的 OpenAPI 文档完整、准确,并且具有良好的可读性和可维护性。