第一步:客户端发起请求
请求的构成
客户端(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(查询数据)
- 认证令牌:
AuthorizationHeader 中的 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 |
| 持久化 | 业务层 | 后台保存数据到数据库 |
这种设计通过分层、异步、自动化、单线程等原则,实现了一个高效、可靠、易维护的分布式系统。