← 返回首页

ARTICLE

请求处理流程:一个完成的API请求,从客户端到数据库

第一步:客户端发起请求 请求的构成 客户端(Unity 或 Web)发送一个 HTTP 请求到网关: POST /api/[API_ID] HTTP/1.1 Host: api.example.com Authorization: Bearer [JWT_TOKEN] Content-Type: a

第一步:客户端发起请求

请求的构成

客户端(Unity 或 Web)发送一个 HTTP 请求到网关:

POST /api/[API_ID] HTTP/1.1
Host: api.example.com
Authorization: Bearer [JWT_TOKEN]
Content-Type: application/json

{
  "param1": "value1",
  "param2": 12345,
  "param3": "value3"
}

请求的关键信息:

  • API ID:数字路由(便于版本管理和路由编排)
  • HTTP 方法:POST(修改数据)或 GET(查询数据)
  • 认证令牌:Authorization Header 中的 JWT Token
  • 请求体:JSON 格式的业务参数

为什么使用数字 ID 而不是字符串路由?

❌ 字符串路由:/api/user/action
  - 容易输入错误
  - 难以版本隔离
  - 路由冲突风险大

✅ 数字 ID:/api/[ID]
  - 硬编码在客户端,不易出错
  - 支持版本隔离(负载均衡器可按版本路由)
  - 便于 API 编排(不同范围的 ID 对应不同功能模块)

第二步:GateServer 接收请求

请求进入网关

当请求到达网关时,会经过一系列的处理步骤:

请求进入
    ↓
[1] 参数验证过滤器
    ├─ 检查 ModelState(参数绑定是否成功)
    ├─ 检查必填参数是否缺失
    └─ 如果出错,返回错误码
    ↓
[2] 业务逻辑处理
    ├─ 从 Token 提取用户标识
    ├─ 调用业务层方法
    └─ 或抛出异常
    ↓
[3] 响应处理过滤器
    ├─ 成功:封装为标准响应格式
    └─ 失败:捕获异常,返回错误码
    ↓
响应返回客户端

参数验证

网关的第一道防线是参数验证。系统会检查:

  • 参数完整性:所有必填参数是否都提供了
  • 参数类型:参数类型是否匹配(如整数、字符串等)
  • 参数范围:参数值是否在合理范围内
验证规则示例:
├─ param1:必填,类型为 string
├─ param2:必填,类型为 int
└─ param3:可选,类型为 string

身份认证

网关从多个地方提取 Token:

优先级顺序:
1. 请求参数中的 token 字段
2. Cookie 中的 token
3. Authorization Header 中的 Bearer Token

验证流程:
├─ 检查 Token 是否存在
├─ 验证 Token 签名
├─ 检查 Token 是否过期
└─ 如果有效,提取 Payload 中的用户标识和其他信息

Token 验证失败的处理:

Token 无效或过期
    ↓
抛出异常
    ↓
异常过滤器捕获
    ↓
返回错误响应给客户端

第三步:调用业务逻辑

网关调用业务层

验证通过后,网关调用业务层的方法:

网关调用流程:
1. 从 Token 提取用户标识
2. 获取业务实体(自动激活或返回已有实例)
3. 调用业务方法:await businessEntity.ProcessRequest(params)
4. 等待返回结果

关键点:这里的调用是异步的 RPC 调用,网关不需要知道业务实体在哪个服务器上运行。

业务实体的自动激活

当网关请求一个业务实体时,系统会:

GetBusinessEntity(entityId)
    ↓
检查该实体是否已在内存中
    ├─ 如果存在 → 返回已有实例
    └─ 如果不存在 → 自动激活
        ├─ 选择合适的服务器节点
        ├─ 创建实体实例
        ├─ 调用 OnActivateAsync()
        └─ 返回实例

OnActivateAsync() 做了什么?

OnActivateAsync() 执行流程:
1. 提取实体的唯一标识
2. 从数据库加载该实体的数据
3. 初始化各个功能组件
4. 注册事件监听器
5. 准备就绪,等待业务方法调用

第四步:业务逻辑执行

业务实体处理请求

业务实体收到请求后,执行具体的业务逻辑:

业务方法执行:
1. 检查业务前置条件
   ├─ 实体是否存在
   ├─ 用户是否有权限
   └─ 业务状态是否合法

2. 执行核心业务逻辑
   ├─ 修改实体的内存数据
   ├─ 调用其他实体(如需要)
   └─ 生成业务结果

3. 返回结果给网关

数据修改与自动标记

当业务逻辑修改数据时,系统会自动标记为"脏数据":

修改数据:
entity.property = newValue
    ↓
系统自动标记:DirtyFlag = true
    ↓
后续保存时,只有标记为脏的数据才会被持久化

这样做的好处:

  • 减少数据库写入次数
  • 提高性能
  • 自动追踪哪些数据被修改了

业务异常处理

如果业务逻辑出现问题,会抛出异常:

业务异常示例:
├─ 实体不存在 → throw EntityNotFound
├─ 权限不足 → throw PermissionDenied
├─ 业务规则违反 → throw BusinessRuleViolation
└─ 系统错误 → throw SystemError

这些异常会被网关的异常过滤器捕获,转换为错误响应。


第五步:数据持久化

何时保存数据?

业务实体的数据不是立即保存到数据库,而是在特定时刻:

数据保存时机:
1. 业务实体长时间不活跃(自动钝化)
   └─ 系统调用 OnDeactivateAsync()
   └─ 将所有脏数据保存到数据库

2. 显式调用保存方法
   └─ 某些关键操作需要立即持久化

3. 定时批量保存
   └─ 后台定时任务定期保存

保存流程

OnDeactivateAsync() 执行:
1. 扫描所有数据模型
2. 找出 DirtyFlag = true 的模型
3. 批量执行 INSERT OR UPDATE
   ├─ UPDATE table SET column = value WHERE id = xxx
   ├─ UPDATE table SET column = value WHERE id = yyy
   └─ ...其他更新语句

4. 事务提交
5. 清理内存资源

为什么使用批量保存?

❌ 逐条保存:
  - 每次修改都写数据库
  - 数据库压力大
  - 性能差

✅ 批量保存:
  - 积累多个修改后一次性保存
  - 减少数据库往返
  - 性能好
  - 支持事务一致性

数据库连接

系统使用连接池管理数据库连接:

连接池特性:
├─ 预先创建多个连接
├─ 复用连接,避免频繁创建销毁
├─ 自动管理连接生命周期
└─ 支持并发访问

第六步:返回结果

业务层返回结果

业务实体处理完请求后,返回结果给网关:

返回结果示例:
{
  "code": 0,           // 0 表示成功
  "data": {
    "field1": "value1",
    "field2": 12345,
    "field3": "value3"
  },
  "message": null      // 成功时为 null
}

异常处理

如果业务层抛出异常,网关的异常过滤器会捕获并转换:

异常捕获流程:
1. 业务层抛出异常
2. 异常过滤器捕获
3. 判断异常类型
   ├─ 业务异常 → 返回对应的错误码
   ├─ 系统异常 → 返回通用错误码
   └─ 未知异常 → 返回服务器错误

4. 记录日志(如需要)
5. 返回错误响应

错误响应示例:

{
  "code": 1001,
  "data": null,
  "message": "Error description"
}

第七步:响应封装与返回

响应过滤器

网关的响应过滤器负责统一封装响应:

响应封装流程:
1. 检查业务方法是否成功
2. 提取返回数据
3. 统一封装为标准响应格式
4. 添加元数据(如服务器版本)
5. 序列化为 JSON
6. 返回给客户端

完整的响应格式

{
  "code": 0,
  "data": {
    "field1": "value1",
    "field2": 12345
  },
  "lastMessageIdx": 42,
  "serverVersion": "1.2.5",
  "errorMsg": null
}

响应字段说明:

字段 说明
code 结果码(0=成功,非0=失败)
data 业务数据
lastMessageIdx 最后一条消息的序列号(用于消息系统)
serverVersion 服务器版本
errorMsg 错误信息(成功时为 null)

第八步:客户端接收与处理

客户端接收响应

客户端收到 JSON 响应后:

客户端处理流程:
1. 检查 HTTP 状态码
   ├─ 200 → 继续处理
   └─ 其他 → 网络错误

2. 解析 JSON
3. 检查业务结果码
   ├─ code == 0 → 成功,处理数据
   └─ code != 0 → 失败,显示错误信息

4. 更新本地数据
5. 刷新 UI

错误处理

错误处理伪代码:
if (response.code == 0) {
    // 成功:更新 UI
    UpdateUI(response.data);
} else {
    // 失败:显示错误提示
    ShowErrorMessage(response.errorMsg);
}

完整的时间线

让我们看一个完整请求的时间线:

T0:   客户端发送请求
      POST /api/[ID]

T1:   网关接收请求
      ├─ 参数验证
      ├─ 身份认证
      └─ 参数绑定

T2:   网关调用业务层
      RPC 调用

T3:   业务实体激活
      ├─ 从数据库加载数据
      └─ 初始化组件

T4:   业务逻辑执行
      ├─ 检查前置条件
      ├─ 修改数据(标记脏数据)
      └─ 生成结果

T5:   业务实体返回结果
      RPC 返回

T6:   网关处理响应
      ├─ 异常处理(如有)
      ├─ 响应封装
      └─ 序列化为 JSON

T7:   客户端接收响应
      ├─ 解析 JSON
      ├─ 检查结果码
      └─ 更新 UI

T8:   业务实体钝化(后台)
      ├─ 扫描脏数据
      ├─ 批量保存到数据库
      └─ 释放内存

关键设计原则

1. 分层设计

客户端 ↔ 网关 ↔ 业务层 ↔ 数据库

优势:
├─ 职责清晰
├─ 易于扩展
├─ 便于测试
└─ 支持独立扩容

2. 异步处理

所有 I/O 操作都是异步的:
├─ 网关调用业务层 → 异步 RPC
├─ 业务层加载数据 → 异步数据库查询
└─ 业务层保存数据 → 异步数据库写入

优势:
├─ 不阻塞线程
├─ 高并发支持
└─ 资源利用率高

3. 自动化管理

系统自动处理:
├─ 业务实体的激活和钝化
├─ 数据的脏标记和批量保存
├─ 异常的捕获和转换
└─ 响应的统一封装

优势:
├─ 减少手动管理
├─ 降低出错概率
└─ 提高开发效率

4. 单线程模型

同一个业务实体的所有请求串行执行:
├─ 无需加锁
├─ 无死锁风险
├─ 逻辑简单

优势:
├─ 开发简单
├─ 可靠性高
└─ 易于调试

总结

一个完整的 API 请求涉及多个层次的处理:

阶段 组件 职责
请求 客户端 发送 HTTP 请求
验证 网关 参数验证、身份认证
调用 网关 调用业务层
激活 业务层 自动激活实体、加载数据
处理 业务层 执行业务逻辑、修改数据
返回 业务层 返回结果
响应 网关 异常处理、响应封装
接收 客户端 解析响应、更新 UI
持久化 业务层 后台保存数据到数据库

这种设计通过分层、异步、自动化、单线程等原则,实现了一个高效、可靠、易维护的分布式系统。