Skip to content

Ledger API 设计规范

概述

Ledger 模块采用 API First 的设计理念,通过 Protocol Buffers 定义统一的 API 契约,同时支持 gRPC 和 HTTP RESTful 两种协议。本文档详细规范了 API 的设计原则、接口定义、错误处理、版本管理等各个方面。

设计原则

  • 契约优先: 通过 Protobuf 定义明确的 API 契约
  • 双协议支持: gRPC (内部服务) + HTTP (外部客户端)
  • RESTful 设计: HTTP API 遵循 REST 架构风格
  • 版本兼容: 向后兼容的 API 版本管理
  • 标准化: 统一的错误码、状态码和响应格式

技术栈

  • Protocol Buffers 3: API 契约定义
  • gRPC: 高性能内部服务通信
  • gRPC-Gateway: HTTP RESTful API 自动生成
  • OpenAPI/Swagger: API 文档自动生成
  • Buf: 现代化 Protobuf 工具链

API 架构设计

1. 协议层次架构

2. API 分层设计

3. 服务拆分策略

Protocol Buffers 设计

1. 文件组织和命名规范

protobuf
// 文件路径: proto/ledger/v1/ledger_service.proto
syntax = "proto3";

package ledger.v1;

// Go 包路径规范
option go_package = "github.com/FixIterate/lz-stash/gen/ledger/v1;ledgerv1";

// C# 命名空间
option csharp_namespace = "FixIterate.Ledger.V1";

// PHP 命名空间
option php_namespace = "FixIterate\\Ledger\\V1";

// 导入其他 proto 文件
import "google/api/annotations.proto";
import "google/protobuf/empty.proto";
import "google/protobuf/timestamp.proto";
import "google/protobuf/field_mask.proto";
import "common/v1/types.proto";
import "common/v1/money.proto";

2. 服务定义规范

protobuf
// 账本管理服务
service LedgerService {
  // 创建账本
  rpc CreateLedger(CreateLedgerRequest) returns (CreateLedgerResponse) {
    option (google.api.http) = {
      post: "/api/v1/ledgers"
      body: "*"
    };
  }
  
  // 获取账本详情
  rpc GetLedger(GetLedgerRequest) returns (GetLedgerResponse) {
    option (google.api.http) = {
      get: "/api/v1/ledgers/{id}"
    };
  }
  
  // 列出账本 (支持分页和筛选)
  rpc ListLedgers(ListLedgersRequest) returns (ListLedgersResponse) {
    option (google.api.http) = {
      get: "/api/v1/ledgers"
    };
  }
  
  // 更新账本 (支持部分更新)
  rpc UpdateLedger(UpdateLedgerRequest) returns (UpdateLedgerResponse) {
    option (google.api.http) = {
      patch: "/api/v1/ledgers/{id}"
      body: "*"
    };
  }
  
  // 删除账本 (软删除)
  rpc DeleteLedger(DeleteLedgerRequest) returns (google.protobuf.Empty) {
    option (google.api.http) = {
      delete: "/api/v1/ledgers/{id}"
    };
  }
  
  // 批量操作
  rpc BatchCreateLedgers(BatchCreateLedgersRequest) returns (BatchCreateLedgersResponse) {
    option (google.api.http) = {
      post: "/api/v1/ledgers:batch"
      body: "*"
    };
  }
  
  // 归档账本
  rpc ArchiveLedger(ArchiveLedgerRequest) returns (ArchiveLedgerResponse) {
    option (google.api.http) = {
      post: "/api/v1/ledgers/{id}:archive"
      body: "*"
    };
  }
}

// 交易记录服务
service TransactionService {
  // 创建交易记录
  rpc CreateTransaction(CreateTransactionRequest) returns (CreateTransactionResponse) {
    option (google.api.http) = {
      post: "/api/v1/ledgers/{ledger_id}/transactions"
      body: "*"
    };
  }
  
  // 快速记账 (移动端优化)
  rpc QuickCreateTransaction(QuickCreateTransactionRequest) returns (QuickCreateTransactionResponse) {
    option (google.api.http) = {
      post: "/api/v1/transactions:quick"
      body: "*"
    };
  }
  
  // 批量创建交易记录
  rpc BatchCreateTransactions(BatchCreateTransactionsRequest) returns (BatchCreateTransactionsResponse) {
    option (google.api.http) = {
      post: "/api/v1/ledgers/{ledger_id}/transactions:batch"
      body: "*"
    };
  }
  
  // 获取交易记录
  rpc GetTransaction(GetTransactionRequest) returns (GetTransactionResponse) {
    option (google.api.http) = {
      get: "/api/v1/transactions/{id}"
    };
  }
  
  // 列出交易记录 (高级筛选)
  rpc ListTransactions(ListTransactionsRequest) returns (ListTransactionsResponse) {
    option (google.api.http) = {
      get: "/api/v1/ledgers/{ledger_id}/transactions"
    };
  }
  
  // 搜索交易记录
  rpc SearchTransactions(SearchTransactionsRequest) returns (SearchTransactionsResponse) {
    option (google.api.http) = {
      get: "/api/v1/transactions:search"
    };
  }
  
  // 更新交易记录
  rpc UpdateTransaction(UpdateTransactionRequest) returns (UpdateTransactionResponse) {
    option (google.api.http) = {
      patch: "/api/v1/transactions/{id}"
      body: "*"
    };
  }
  
  // 删除交易记录
  rpc DeleteTransaction(DeleteTransactionRequest) returns (google.protobuf.Empty) {
    option (google.api.http) = {
      delete: "/api/v1/transactions/{id}"
    };
  }
  
  // 导入交易记录 (银行流水等)
  rpc ImportTransactions(ImportTransactionsRequest) returns (ImportTransactionsResponse) {
    option (google.api.http) = {
      post: "/api/v1/ledgers/{ledger_id}/transactions:import"
      body: "*"
    };
  }
  
  // 导出交易记录
  rpc ExportTransactions(ExportTransactionsRequest) returns (stream ExportTransactionsResponse) {
    option (google.api.http) = {
      get: "/api/v1/ledgers/{ledger_id}/transactions:export"
    };
  }
}

3. 实体定义规范

protobuf
// 账本实体
message Ledger {
  // 主键字段
  string id = 1;                                    // 账本唯一标识
  
  // 基本信息
  string name = 2;                                  // 账本名称
  string description = 3;                           // 账本描述
  LedgerType type = 4;                             // 账本类型
  LedgerStatus status = 5;                         // 账本状态
  
  // 配置信息
  common.v1.Currency default_currency = 6;         // 默认货币
  LedgerSettings settings = 7;                     // 账本设置
  
  // 所有者信息
  string owner_id = 8;                             // 所有者ID
  
  // 时间戳
  google.protobuf.Timestamp created_at = 10;      // 创建时间
  google.protobuf.Timestamp updated_at = 11;      // 更新时间
  google.protobuf.Timestamp archived_at = 12;     // 归档时间
  
  // 版本控制
  int64 version = 13;                              // 乐观锁版本号
  
  // 统计信息 (只读)
  LedgerStats stats = 20;                          // 统计数据
  
  // 扩展字段 (100-199 为扩展保留)
  map<string, string> metadata = 100;             // 元数据
  repeated Extension extensions = 101;              // 扩展信息
}

// 交易记录实体
message Transaction {
  // 主键和关联
  string id = 1;                                   // 交易唯一标识
  string ledger_id = 2;                           // 所属账本ID
  
  // 交易信息
  TransactionType type = 3;                        // 交易类型
  common.v1.Money amount = 4;                      // 交易金额
  string description = 5;                          // 交易描述
  string note = 6;                                // 备注信息
  
  // 分类信息
  repeated string tag_ids = 7;                    // 标签ID列表
  string category_id = 8;                         // 分类ID
  
  // 时间信息
  google.protobuf.Timestamp transaction_date = 9; // 交易发生时间
  google.protobuf.Timestamp created_at = 10;     // 记录创建时间
  google.protobuf.Timestamp updated_at = 11;     // 记录更新时间
  
  // 附加信息
  TransactionLocation location = 12;               // 交易地点
  repeated Attachment attachments = 13;            // 附件列表
  string source = 14;                             // 数据来源
  
  // 状态信息
  TransactionStatus status = 15;                   // 交易状态
  string receipt_number = 16;                     // 票据号码
  
  // 关联信息
  string parent_transaction_id = 17;              // 父交易ID (分期付款等)
  repeated string child_transaction_ids = 18;     // 子交易ID列表
  
  // 版本控制
  int64 version = 19;                             // 乐观锁版本号
  
  // 扩展字段
  map<string, string> metadata = 100;
}

// 预算实体
message Budget {
  // 基本信息
  string id = 1;
  string ledger_id = 2;
  string name = 3;
  string description = 4;
  
  // 预算配置
  common.v1.Money amount = 5;                     // 预算金额
  BudgetPeriod period = 6;                        // 预算周期
  google.protobuf.Timestamp start_date = 7;      // 开始日期
  google.protobuf.Timestamp end_date = 8;        // 结束日期
  
  // 适用范围
  repeated string tag_ids = 9;                    // 适用标签
  repeated string category_ids = 10;              // 适用分类
  BudgetScope scope = 11;                         // 预算范围
  
  // 预警配置
  repeated BudgetAlert alerts = 12;               // 预警设置
  
  // 状态信息
  BudgetStatus status = 13;                       // 预算状态
  BudgetUsage usage = 14;                         // 使用情况
  
  // 时间戳
  google.protobuf.Timestamp created_at = 15;
  google.protobuf.Timestamp updated_at = 16;
  
  // 扩展字段
  map<string, string> metadata = 100;
}

4. 枚举定义规范

protobuf
// 账本类型
enum LedgerType {
  LEDGER_TYPE_UNSPECIFIED = 0;                    // 未指定
  LEDGER_TYPE_PERSONAL = 1;                       // 个人账本
  LEDGER_TYPE_FAMILY = 2;                         // 家庭账本  
  LEDGER_TYPE_PROJECT = 3;                        // 项目账本
  LEDGER_TYPE_TRAVEL = 4;                         // 旅行账本
  LEDGER_TYPE_BUSINESS = 5;                       // 商务账本
  LEDGER_TYPE_SHARED = 6;                         // 共享账本
}

// 账本状态
enum LedgerStatus {
  LEDGER_STATUS_UNSPECIFIED = 0;
  LEDGER_STATUS_ACTIVE = 1;                       // 活跃
  LEDGER_STATUS_ARCHIVED = 2;                     // 已归档
  LEDGER_STATUS_SUSPENDED = 3;                    // 已暂停
  LEDGER_STATUS_DELETED = 4;                      // 已删除
}

// 交易类型
enum TransactionType {
  TRANSACTION_TYPE_UNSPECIFIED = 0;
  TRANSACTION_TYPE_INCOME = 1;                    // 收入
  TRANSACTION_TYPE_EXPENSE = 2;                   // 支出
  TRANSACTION_TYPE_TRANSFER = 3;                  // 转账
  TRANSACTION_TYPE_ADJUSTMENT = 4;                // 调整
}

// 交易状态
enum TransactionStatus {
  TRANSACTION_STATUS_UNSPECIFIED = 0;
  TRANSACTION_STATUS_PENDING = 1;                 // 待确认
  TRANSACTION_STATUS_COMPLETED = 2;               // 已完成
  TRANSACTION_STATUS_CANCELLED = 3;               // 已取消
  TRANSACTION_STATUS_FAILED = 4;                  // 失败
  TRANSACTION_STATUS_REFUNDED = 5;                // 已退款
}

// 预算周期
enum BudgetPeriod {
  BUDGET_PERIOD_UNSPECIFIED = 0;
  BUDGET_PERIOD_DAILY = 1;                        // 日
  BUDGET_PERIOD_WEEKLY = 2;                       // 周
  BUDGET_PERIOD_MONTHLY = 3;                      // 月
  BUDGET_PERIOD_QUARTERLY = 4;                    // 季度
  BUDGET_PERIOD_YEARLY = 5;                       // 年
  BUDGET_PERIOD_CUSTOM = 6;                       // 自定义
}

5. 请求响应消息规范

protobuf
// 创建账本请求
message CreateLedgerRequest {
  // 必填字段
  string name = 1;                                // 账本名称
  LedgerType type = 2;                           // 账本类型
  
  // 可选字段
  string description = 3;                         // 账本描述
  common.v1.Currency default_currency = 4;       // 默认货币
  LedgerSettings settings = 5;                   // 账本设置
  
  
  // 元数据
  map<string, string> metadata = 100;
}

// 创建账本响应
message CreateLedgerResponse {
  Ledger ledger = 1;                             // 创建的账本
  OperationResult result = 2;                    // 操作结果
}

// 列出账本请求
message ListLedgersRequest {
  // 分页参数
  common.v1.PaginationRequest pagination = 1;
  
  // 筛选条件
  LedgerFilter filter = 2;
  
  // 排序参数
  repeated common.v1.SortOrder sort_orders = 3;
  
  // 字段掩码 (部分字段返回)
  google.protobuf.FieldMask field_mask = 4;
  
  // 包含关联数据
  LedgerInclude include = 5;
}

// 列出账本响应
message ListLedgersResponse {
  repeated Ledger ledgers = 1;                   // 账本列表
  common.v1.PaginationResponse pagination = 2;   // 分页信息
  ListStats stats = 3;                          // 列表统计
}

// 更新账本请求 (支持部分更新)
message UpdateLedgerRequest {
  string id = 1;                                 // 账本ID
  
  // 可更新字段
  optional string name = 2;
  optional string description = 3;
  optional LedgerSettings settings = 4;
  
  // 字段掩码 (指定要更新的字段)
  google.protobuf.FieldMask field_mask = 5;
  
  // 乐观锁版本号
  int64 version = 6;
}

// 筛选条件
message LedgerFilter {
  // 基本筛选
  repeated LedgerType types = 1;                 // 账本类型
  repeated LedgerStatus statuses = 2;            // 账本状态
  string owner_id = 3;                          // 所有者ID
  
  // 时间筛选
  common.v1.TimeRange created_time_range = 4;   // 创建时间范围
  common.v1.TimeRange updated_time_range = 5;   // 更新时间范围
  
  // 搜索关键词
  string search_query = 6;                      // 搜索查询
  repeated string search_fields = 7;            // 搜索字段
  
  // 高级筛选
  repeated string tag_ids = 8;                  // 包含标签
  common.v1.Currency currency = 9;              // 货币类型
  
  // 自定义筛选
  map<string, string> custom_filters = 100;
}

HTTP RESTful API 设计

1. URL 设计规范

http
# 账本管理
GET    /api/v1/ledgers                          # 列出账本
POST   /api/v1/ledgers                          # 创建账本
GET    /api/v1/ledgers/{id}                     # 获取账本详情
PATCH  /api/v1/ledgers/{id}                     # 更新账本
DELETE /api/v1/ledgers/{id}                     # 删除账本

# 账本操作
POST   /api/v1/ledgers/{id}:archive             # 归档账本
POST   /api/v1/ledgers/{id}:restore             # 恢复账本
POST   /api/v1/ledgers:batch                    # 批量操作

# 交易记录管理  
GET    /api/v1/ledgers/{ledger_id}/transactions # 列出交易记录
POST   /api/v1/ledgers/{ledger_id}/transactions # 创建交易记录
GET    /api/v1/transactions/{id}                # 获取交易详情
PATCH  /api/v1/transactions/{id}                # 更新交易记录
DELETE /api/v1/transactions/{id}                # 删除交易记录

# 交易记录操作
GET    /api/v1/transactions:search              # 搜索交易记录
POST   /api/v1/transactions:quick               # 快速记账
POST   /api/v1/transactions:batch               # 批量创建
POST   /api/v1/transactions:import              # 导入交易
GET    /api/v1/transactions:export              # 导出交易

# 标签管理
GET    /api/v1/ledgers/{ledger_id}/tags         # 列出标签
POST   /api/v1/ledgers/{ledger_id}/tags         # 创建标签
GET    /api/v1/tags/{id}                        # 获取标签详情
PATCH  /api/v1/tags/{id}                        # 更新标签
DELETE /api/v1/tags/{id}                        # 删除标签

# 预算管理
GET    /api/v1/ledgers/{ledger_id}/budgets      # 列出预算
POST   /api/v1/ledgers/{ledger_id}/budgets      # 创建预算
GET    /api/v1/budgets/{id}                     # 获取预算详情
PATCH  /api/v1/budgets/{id}                     # 更新预算
DELETE /api/v1/budgets/{id}                     # 删除预算

# 统计分析
GET    /api/v1/ledgers/{ledger_id}/analytics    # 获取统计分析
GET    /api/v1/ledgers/{ledger_id}/reports      # 获取报表数据
GET    /api/v1/ledgers/{ledger_id}/trends       # 获取趋势分析

# 移动端专用接口
GET    /api/mobile/v1/dashboard                 # 移动端仪表板
POST   /api/mobile/v1/sync                      # 数据同步
POST   /api/mobile/v1/ocr                       # OCR 识别

# 管理后台接口
GET    /api/admin/v1/users/{user_id}/ledgers    # 用户账本管理
GET    /api/admin/v1/stats                      # 系统统计
POST   /api/admin/v1/maintenance                # 系统维护

2. HTTP 状态码规范

http
# 成功响应
200 OK                 # 查询成功
201 Created           # 创建成功
204 No Content        # 删除成功 / 更新成功无返回内容

# 客户端错误
400 Bad Request       # 请求参数错误
401 Unauthorized      # 未认证
403 Forbidden         # 权限不足
404 Not Found         # 资源不存在
409 Conflict          # 资源冲突 (如重复创建)
422 Unprocessable Entity # 数据验证失败
429 Too Many Requests # 请求过于频繁

# 服务器错误
500 Internal Server Error # 服务器内部错误
502 Bad Gateway          # 网关错误
503 Service Unavailable  # 服务不可用
504 Gateway Timeout      # 网关超时

3. 请求响应格式

json
// 创建账本请求
POST /api/v1/ledgers
Content-Type: application/json

{
  "name": "我的个人账本",
  "description": "记录日常收支",
  "type": "LEDGER_TYPE_PERSONAL",
  "default_currency": "CURRENCY_CNY",
  "settings": {
    "enable_budget_alerts": true,
    "budget_alert_threshold": 80,
    "timezone": "Asia/Shanghai",
    "locale": "zh-CN"
  },
  "metadata": {
    "source": "mobile_app",
    "version": "1.0.0"
  }
}

// 创建账本响应
HTTP/1.1 201 Created
Content-Type: application/json
Location: /api/v1/ledgers/ledger_123

{
  "ledger": {
    "id": "ledger_123",
    "name": "我的个人账本",
    "description": "记录日常收支",
    "type": "LEDGER_TYPE_PERSONAL",
    "status": "LEDGER_STATUS_ACTIVE",
    "default_currency": "CURRENCY_CNY",
    "owner_id": "user_456",
    "settings": {
      "enable_budget_alerts": true,
      "budget_alert_threshold": 80,
      "timezone": "Asia/Shanghai",
      "locale": "zh-CN"
    },
    "created_at": "2024-01-15T10:30:00Z",
    "updated_at": "2024-01-15T10:30:00Z",
    "version": 1,
    "stats": {
      "transaction_count": 0,
      "total_income": {
        "amount": 0,
        "currency": "CURRENCY_CNY"
      },
      "total_expense": {
        "amount": 0,
        "currency": "CURRENCY_CNY"
      }
    }
  },
  "result": {
    "success": true,
    "message": "账本创建成功",
    "operation_id": "op_789"
  }
}

// 列出账本请求
GET /api/v1/ledgers?page_size=20&page_token=abc123&type=LEDGER_TYPE_PERSONAL&sort=created_at:desc

// 列出账本响应
{
  "ledgers": [
    {
      "id": "ledger_123",
      "name": "我的个人账本",
      // ... 其他字段
    }
  ],
  "pagination": {
    "next_page_token": "def456",
    "total_count": 1
  },
  "stats": {
    "total_count": 1,
    "active_count": 1,
    "archived_count": 0
  }
}

// 错误响应格式
HTTP/1.1 400 Bad Request
Content-Type: application/json

{
  "error": {
    "code": "INVALID_ARGUMENT",
    "message": "请求参数错误",
    "details": [
      {
        "field": "name",
        "message": "账本名称不能为空"
      },
      {
        "field": "type",
        "message": "无效的账本类型"
      }
    ],
    "request_id": "req_123456",
    "timestamp": "2024-01-15T10:30:00Z"
  }
}

4. 查询参数规范

http
# 分页参数
?page_size=20              # 页大小 (默认 20, 最大 100)
?page_token=abc123         # 分页令牌

# 筛选参数
?type=LEDGER_TYPE_PERSONAL # 按类型筛选
?status=LEDGER_STATUS_ACTIVE # 按状态筛选
?owner_id=user_123         # 按所有者筛选
?currency=CURRENCY_CNY     # 按货币筛选

# 时间范围筛选
?created_after=2024-01-01T00:00:00Z
?created_before=2024-12-31T23:59:59Z
?updated_after=2024-01-01T00:00:00Z

# 搜索参数
?q=账本名称                # 搜索查询
?search_fields=name,description # 搜索字段

# 排序参数
?sort=created_at:desc      # 按创建时间降序
?sort=name:asc,updated_at:desc # 多字段排序

# 字段选择
?fields=id,name,created_at # 只返回指定字段
?include=stats,members     # 包含关联数据

# 交易记录特有参数
?transaction_type=TRANSACTION_TYPE_EXPENSE # 交易类型
?amount_min=1000          # 最小金额 (分)
?amount_max=100000        # 最大金额 (分)
?tag_ids=tag1,tag2        # 标签筛选
?date_from=2024-01-01     # 交易日期开始
?date_to=2024-01-31       # 交易日期结束

错误处理和状态码

1. 错误码设计

protobuf
// 通用错误码
enum ErrorCode {
  ERROR_CODE_UNSPECIFIED = 0;
  
  // 客户端错误 (4xx)
  ERROR_CODE_INVALID_ARGUMENT = 400;
  ERROR_CODE_UNAUTHORIZED = 401;
  ERROR_CODE_FORBIDDEN = 403;
  ERROR_CODE_NOT_FOUND = 404;
  ERROR_CODE_CONFLICT = 409;
  ERROR_CODE_UNPROCESSABLE_ENTITY = 422;
  ERROR_CODE_RATE_LIMITED = 429;
  
  // 服务器错误 (5xx)
  ERROR_CODE_INTERNAL_ERROR = 500;
  ERROR_CODE_SERVICE_UNAVAILABLE = 503;
  ERROR_CODE_TIMEOUT = 504;
}

// 业务错误码
enum LedgerErrorCode {
  LEDGER_ERROR_CODE_UNSPECIFIED = 0;
  
  // 账本相关错误
  LEDGER_ERROR_CODE_NAME_REQUIRED = 1001;
  LEDGER_ERROR_CODE_NAME_TOO_LONG = 1002;
  LEDGER_ERROR_CODE_INVALID_TYPE = 1003;
  LEDGER_ERROR_CODE_ALREADY_EXISTS = 1004;
  LEDGER_ERROR_CODE_MEMBER_LIMIT_EXCEEDED = 1005;
  
  // 交易相关错误
  LEDGER_ERROR_CODE_INVALID_AMOUNT = 2001;
  LEDGER_ERROR_CODE_INVALID_CURRENCY = 2002;
  LEDGER_ERROR_CODE_INVALID_DATE = 2003;
  LEDGER_ERROR_CODE_TAG_NOT_FOUND = 2004;
  
  // 预算相关错误
  LEDGER_ERROR_CODE_BUDGET_EXCEEDED = 3001;
  LEDGER_ERROR_CODE_INVALID_PERIOD = 3002;
  LEDGER_ERROR_CODE_OVERLAPPING_BUDGET = 3003;
  
  // 权限相关错误
  LEDGER_ERROR_CODE_INSUFFICIENT_PERMISSION = 4001;
  LEDGER_ERROR_CODE_INVALID_MEMBER_ROLE = 4002;
}

// 错误详情
message ErrorDetail {
  string field = 1;           // 错误字段
  string message = 2;         // 错误消息
  ErrorCode code = 3;         // 错误码
  map<string, string> metadata = 4; // 额外信息
}

// 错误响应
message ErrorResponse {
  ErrorCode code = 1;         // 主错误码
  string message = 2;         // 错误消息
  repeated ErrorDetail details = 3; // 错误详情
  string request_id = 4;      // 请求ID
  google.protobuf.Timestamp timestamp = 5; // 错误时间
}

2. 错误处理策略

go
// 错误处理中间件
func ErrorHandlingMiddleware() gin.HandlerFunc {
    return func(c *gin.Context) {
        c.Next()
        
        if len(c.Errors) > 0 {
            err := c.Errors.Last()
            
            // 转换为标准错误响应
            errorResp := convertToErrorResponse(err.Err, c.GetString("request_id"))
            
            // 设置对应的 HTTP 状态码
            statusCode := getHTTPStatusCode(errorResp.Code)
            c.JSON(statusCode, map[string]interface{}{
                "error": errorResp,
            })
        }
    }
}

// 业务层错误定义
type LedgerError struct {
    Code    ErrorCode
    Message string
    Details []ErrorDetail
}

func (e *LedgerError) Error() string {
    return e.Message
}

// 创建业务错误
func NewValidationError(field, message string) *LedgerError {
    return &LedgerError{
        Code:    ERROR_CODE_INVALID_ARGUMENT,
        Message: "validation failed",
        Details: []ErrorDetail{
            {
                Field:   field,
                Message: message,
                Code:    ERROR_CODE_INVALID_ARGUMENT,
            },
        },
    }
}

func NewNotFoundError(resource, id string) *LedgerError {
    return &LedgerError{
        Code:    ERROR_CODE_NOT_FOUND,
        Message: fmt.Sprintf("%s not found", resource),
        Details: []ErrorDetail{
            {
                Field:   "id",
                Message: fmt.Sprintf("%s with id '%s' not found", resource, id),
                Code:    ERROR_CODE_NOT_FOUND,
            },
        },
    }
}

API 版本管理

1. 版本策略

protobuf
// v1 版本
syntax = "proto3";
package ledger.v1;
option go_package = "github.com/FixIterate/lz-stash/gen/ledger/v1;ledgerv1";

// v2 版本 (向后兼容)
syntax = "proto3";
package ledger.v2;
option go_package = "github.com/FixIterate/lz-stash/gen/ledger/v2;ledgerv2";

// 版本兼容性规则:
// 1. 不能删除已有字段
// 2. 不能改变已有字段的类型
// 3. 不能改变已有字段的编号
// 4. 新增字段必须是可选的或有默认值
// 5. 新增字段使用新的编号

2. 字段演化规范

protobuf
// v1.0 - 初始版本
message Ledger {
  string id = 1;
  string name = 2;
  string description = 3;
}

// v1.1 - 添加新字段 (兼容)
message Ledger {
  string id = 1;
  string name = 2;
  string description = 3;
  
  // 新增字段,使用新编号
  LedgerType type = 4;              // v1.1 新增
  common.v1.Currency currency = 5;  // v1.1 新增
}

// v1.2 - 标记废弃字段 (兼容)
message Ledger {
  string id = 1;
  string name = 2;
  string description = 3 [deprecated = true]; // 标记为废弃
  
  LedgerType type = 4;
  common.v1.Currency currency = 5;
  
  // 替代字段
  string summary = 6;               // v1.2 新增,替代 description
}

// v2.0 - 破坏性变更 (不兼容)
message Ledger {
  string id = 1;
  string name = 2;
  // 移除 description 字段
  
  LedgerType type = 3;              // 重新编号
  common.v1.Currency currency = 4;  // 重新编号
  string summary = 5;               // 重新编号
  
  // 新的字段结构
  LedgerSettings settings = 6;      // v2.0 新增
}

3. HTTP API 版本管理

http
# URL 路径版本控制 (推荐)
GET /api/v1/ledgers
GET /api/v2/ledgers

# Header 版本控制
GET /api/ledgers
Accept: application/vnd.ledger.v1+json

# 查询参数版本控制
GET /api/ledgers?version=v1

# 客户端版本协商
GET /api/ledgers
Accept: application/json
X-API-Version: v1

性能优化

1. 分页和游标

protobuf
// 基于游标的分页 (推荐大数据集)
message ListTransactionsRequest {
  string ledger_id = 1;
  int32 page_size = 2;              // 页大小
  string cursor = 3;                // 游标位置
  ListOrder order = 4;              // 排序方式
}

message ListTransactionsResponse {
  repeated Transaction transactions = 1;
  string next_cursor = 2;           // 下一页游标
  bool has_more = 3;                // 是否有更多数据
  int64 total_count = 4;            // 总记录数 (可选)
}

// 基于偏移的分页 (适合小数据集)
message PaginationRequest {
  int32 page_size = 1;              // 页大小 (默认 20, 最大 100)
  int32 page_number = 2;            // 页码 (从 1 开始)
}

message PaginationResponse {
  int32 page_size = 1;              // 当前页大小
  int32 page_number = 2;            // 当前页码
  int64 total_count = 3;            // 总记录数
  int32 total_pages = 4;            // 总页数
  bool has_next = 5;                // 是否有下一页
  bool has_prev = 6;                // 是否有上一页
}

2. 字段选择和关联查询

protobuf
// 字段掩码 (只返回需要的字段)
message GetLedgerRequest {
  string id = 1;
  google.protobuf.FieldMask field_mask = 2; // 字段掩码
}

// 关联数据包含选项
message LedgerInclude {
  bool include_stats = 1;           // 包含统计信息
  bool include_members = 2;         // 包含成员信息
  bool include_recent_transactions = 3; // 包含最近交易
  int32 recent_transaction_limit = 4;   // 最近交易数量限制
}

message ListLedgersRequest {
  common.v1.PaginationRequest pagination = 1;
  LedgerFilter filter = 2;
  google.protobuf.FieldMask field_mask = 3;
  LedgerInclude include = 4;        // 包含关联数据
}

3. 批量操作

protobuf
// 批量创建
message BatchCreateTransactionsRequest {
  string ledger_id = 1;
  repeated CreateTransactionRequest requests = 2;
  BatchOptions options = 3;
}

message BatchCreateTransactionsResponse {
  repeated BatchResult results = 1;
  BatchSummary summary = 2;
}

message BatchResult {
  int32 index = 1;                  // 请求索引
  oneof result {
    Transaction transaction = 2;     // 成功结果
    ErrorResponse error = 3;         // 错误结果
  }
}

message BatchSummary {
  int32 total_requests = 1;         // 总请求数
  int32 success_count = 2;          // 成功数量
  int32 error_count = 3;            // 错误数量
  repeated string error_indices = 4; // 错误索引列表
}

message BatchOptions {
  bool continue_on_error = 1;       // 遇到错误是否继续
  bool atomic = 2;                  // 是否原子操作
  int32 batch_size = 3;             // 批次大小
}

4. 缓存策略

http
# HTTP 缓存头
GET /api/v1/ledgers/123
Cache-Control: max-age=3600, must-revalidate
ETag: "v1-123-20240115103000"
Last-Modified: Mon, 15 Jan 2024 10:30:00 GMT

# 条件请求
GET /api/v1/ledgers/123
If-None-Match: "v1-123-20240115103000"
If-Modified-Since: Mon, 15 Jan 2024 10:30:00 GMT

# 响应
HTTP/1.1 304 Not Modified

# 缓存失效
PATCH /api/v1/ledgers/123
# 服务器返回新的 ETag 和 Last-Modified

安全性考虑

1. 认证和授权

protobuf
// 请求认证信息
message AuthContext {
  string user_id = 1;               // 用户ID
  repeated string roles = 2;        // 用户角色
  repeated string permissions = 3;  // 用户权限
  string session_id = 4;            // 会话ID
  google.protobuf.Timestamp expires_at = 5; // 过期时间
}

// 权限检查
message PermissionCheck {
  string resource = 1;              // 资源类型
  string resource_id = 2;           // 资源ID
  string action = 3;                // 操作类型
  AuthContext auth_context = 4;     // 认证上下文
}

2. 数据验证

protobuf
// 输入验证注解 (使用 protoc-gen-validate)
import "validate/validate.proto";

message CreateLedgerRequest {
  string name = 1 [(validate.rules).string = {
    min_len: 1,
    max_len: 100,
    pattern: "^[a-zA-Z0-9\\s\\u4e00-\\u9fa5\\-_]+$"
  }];
  
  string description = 2 [(validate.rules).string = {
    max_len: 500
  }];
  
  LedgerType type = 3 [(validate.rules).enum = {
    defined_only: true,
    not_in: [0]  // 不能是 UNSPECIFIED
  }];
}

message CreateTransactionRequest {
  common.v1.Money amount = 1 [(validate.rules).message = {
    required: true
  }];
  
  string description = 2 [(validate.rules).string = {
    min_len: 1,
    max_len: 200
  }];
  
  google.protobuf.Timestamp transaction_date = 3 [(validate.rules).timestamp = {
    lt_now: true,
    gte: {seconds: 946684800}  // 不能早于 2000-01-01
  }];
}

3. 敏感数据处理

protobuf
// 敏感字段标记
message User {
  string id = 1;
  string username = 2;
  string email = 3 [(sensitive) = true];       // 敏感信息
  string phone = 4 [(sensitive) = true];
  string password_hash = 5 [(internal) = true]; // 内部字段
}

// 脱敏响应
message PublicUserProfile {
  string id = 1;
  string username = 2;
  string masked_email = 3;          // 已脱敏的邮箱
  // 不包含敏感信息
}

文档生成和工具

1. OpenAPI 文档生成

bash
# 生成 OpenAPI 文档
buf generate --template buf.gen.openapi.yaml

# buf.gen.openapi.yaml
version: v1
plugins:
  - plugin: buf.build/grpc-ecosystem/openapiv2
    out: docs/openapi
    opt:
      - allow_merge=true
      - merge_file_name=api
      - json_names_for_fields=true
      - include_package_in_tags=true

2. 客户端 SDK 生成

bash
# 生成多语言客户端
buf generate --template buf.gen.nodejs.yaml
buf generate --template buf.gen.python.yaml
buf generate --template buf.gen.php.yaml

# 发布到包管理器
npm publish ./clients/nodejs
pip upload ./clients/python
composer install ./clients/php

3. API 测试工具

bash
# 使用 grpcurl 测试 gRPC API
grpcurl -plaintext localhost:9090 list
grpcurl -plaintext localhost:9090 ledger.v1.LedgerService/ListLedgers

# 使用 Evans 交互式测试
evans --host localhost --port 9090 repl

# 使用 Postman 测试 HTTP API
curl -X POST http://localhost:8080/api/v1/ledgers \
  -H "Content-Type: application/json" \
  -d '{"name": "测试账本", "type": "LEDGER_TYPE_PERSONAL"}'

最佳实践总结

1. API 设计原则

  • 一致性: 统一的命名规范和响应格式
  • 简洁性: 避免过度设计,保持接口简单明了
  • 可扩展性: 预留扩展字段,支持向后兼容
  • 安全性: 输入验证、权限控制、敏感数据保护
  • 性能: 分页、缓存、批量操作优化

2. 错误处理

  • 使用标准的错误码和消息格式
  • 提供详细的错误信息帮助调试
  • 区分客户端错误和服务器错误
  • 记录错误日志便于故障排查

3. 版本管理

  • 语义化版本控制
  • 向后兼容的字段演化
  • 废弃字段的标记和迁移计划
  • 多版本并存的支持策略

4. 性能优化

  • 合理的分页策略
  • 字段选择减少传输量
  • 批量操作提高效率
  • 缓存机制减少服务器负载

这个 API 设计规范为 Ledger 模块提供了完整的接口定义标准,确保 API 的一致性、可维护性和可扩展性。

基于 MIT 许可证发布