238 lines
8.8 KiB
Markdown
238 lines
8.8 KiB
Markdown
# Backend 渐进式优化设计
|
||
|
||
> 本文面向 dcalc backend 的渐进式架构优化。目标是在不改变 `/calc` API 契约、不破坏 frontendNew 计算链路的前提下,逐步提升后端可维护性、扩展性、计算效率与开发规范。
|
||
|
||
## 背景
|
||
|
||
当前仓库前端迁移主线是 `frontend -> frontendNew`,backend 必须优先服务 Dcalc Character 模块的稳定计算链路:equipment、skill、forge、result。backend 现有结构已经完成过一次 `core/basic/character_old.py` 到 `core/basic/character/` 多文件 mixin 的拆分,说明当前更适合继续做小步收口,而不是推倒重写。
|
||
|
||
关键入口:
|
||
|
||
- `/calc` 路由:`backend/api/routes/calc.py`
|
||
- 应用入口:`backend/main.py`
|
||
- 角色计算核心:`backend/core/basic/character/`
|
||
- 装备数据装载:`backend/core/basic/equipment.py`
|
||
- 数据库连接:`backend/database/connect.py`
|
||
- Redis 预热:`backend/api/core/RedisPreheat.py`
|
||
|
||
## 非目标
|
||
|
||
- 不改 `/calc` 的请求语义、响应包装、`data` 主体字段结构。
|
||
- 不改 frontendNew 依赖的 IResult 数据消费方式。
|
||
- 不重写 Skill、Forge、装备效果、职业技能的核心业务逻辑。
|
||
- 不在 backend 优化中引入新的 UI、前端状态管理或前端接口契约调整。
|
||
- 不一次性格式化全仓 Python 文件,避免制造大规模无关 diff。
|
||
|
||
## 现状问题
|
||
|
||
### API 与计算耦合
|
||
|
||
`backend/api/routes/calc.py` 直接 `createCharacter(...).calc(config)`。这让鉴权、请求适配、计算编排、错误处理和未来耗时统计都挤在 API 调用路径上,不利于扩展。
|
||
|
||
### config 读取分散
|
||
|
||
计算过程中大量直接访问 `setInfo.get(...)`、`info['equips']`、`info['oaths']`。默认值、兼容逻辑和输入容错散落在 mixin 中,后续 Skill/Forge 配置演进时容易漏字段。
|
||
|
||
### 装备数据全局缓存边界不清
|
||
|
||
`backend/core/basic/equipment.py` 启动时构造 `equ0 = Equipments('0')`,`get_equipment()` 通过动态变量和 `eval` 获取版本。该方式对当前性能有帮助,但多版本扩展、测试隔离、热更新和错误定位较弱。
|
||
|
||
### 计算对象状态可变
|
||
|
||
`Character` 实例在 `calc_init -> calc_* -> calc_*_skills` 过程中持续变异属性、技能等级、装备效果和输出类型。该模式符合当前业务写法,但缺少明确的一次性计算上下文,测试和复用成本偏高。
|
||
|
||
### 性能缺少量化
|
||
|
||
已有内存化装备数据、技能类缓存、Redis 预热等优化点,但 `/calc` 的耗时瓶颈没有分段统计。后续不应凭感觉优化。
|
||
|
||
### 测试安全网不足
|
||
|
||
当前仅看到 `backend/test.py`,没有正式 `tests/` 体系。后续重构计算链路时,缺少 golden case 来确认字段与数值没有回退。
|
||
|
||
## 优化原则
|
||
|
||
1. 接口冻结:先用测试固定 `/calc` 外部行为,再改内部结构。
|
||
2. 小步可回滚:每个阶段能单独合并、单独验证。
|
||
3. 先边界后算法:优先整理 service、config view、repository/registry,再考虑公式与缓存。
|
||
4. 只搬运不改义:拆文件、封 helper 时不改变公式含义。
|
||
5. 先量化后性能:先加耗时指标,再按热点优化。
|
||
|
||
## 目标架构
|
||
|
||
```text
|
||
backend/
|
||
api/
|
||
routes/
|
||
calc.py # 只负责 HTTP 层:依赖注入、Body、response
|
||
services/
|
||
calc_service.py # /calc 应用服务:创建角色、执行计算、计时、错误上下文
|
||
schemas/
|
||
calc.py # 宽松 config schema 与响应类型别名
|
||
response.py # 通用响应类型,保持旧格式兼容
|
||
core/
|
||
basic/
|
||
character/
|
||
config_view.py # CalcConfigView:统一默认值、兼容旧 config
|
||
effect_apply.py # 统一属性提取与 effect 执行 helper
|
||
equipment.py # 保留现有模型,逐步委托给 registry
|
||
equipment_registry.py # EquipmentRegistry:装备版本懒加载与缓存
|
||
tests/
|
||
contract/
|
||
test_calc_contract.py
|
||
golden/
|
||
test_calc_golden.py
|
||
unit/
|
||
test_equipment_registry.py
|
||
test_calc_config_view.py
|
||
```
|
||
|
||
## 数据流
|
||
|
||
```text
|
||
HTTP POST /api/calc
|
||
-> AltersDep 解析 alter/equVersion
|
||
-> calc_service.calculate(state, raw_config)
|
||
-> CalcConfigView.from_raw(raw_config)
|
||
-> createCharacter(state.alter, state.equVersion)
|
||
-> character.calc(config_view 或兼容 dict)
|
||
-> calc_service 返回原始计算 data
|
||
-> response(data=data)
|
||
```
|
||
|
||
短期内 `Character.calc()` 仍接收原 `dict`,`CalcConfigView` 先作为内部读取辅助逐步接入。等测试稳定后,再考虑让 `Character.calc()` 接收 `CalcConfigView`。
|
||
|
||
## 分阶段设计
|
||
|
||
### Phase 0:测试安全网
|
||
|
||
建立 pytest 测试结构,固定 `/calc` 契约和典型职业结果结构。
|
||
|
||
重点:
|
||
|
||
- 增加 `pytest` 开发依赖。
|
||
- 增加可复用的最小 config fixture。
|
||
- 增加 `/api/calc` HTTP contract 测试。
|
||
- 增加 `createCharacter(...).calc(...)` golden smoke 测试。
|
||
|
||
成功标准:
|
||
|
||
- `uv run pytest` 可执行。
|
||
- `/calc` 响应仍包含 `code/message/data`。
|
||
- `data` 至少包含 `uuid/skills/info/suits/buffer`。
|
||
|
||
### Phase 1:API service 边界
|
||
|
||
新增 `backend/services/calc_service.py`,API route 不再直接编排计算。
|
||
|
||
重点:
|
||
|
||
- route 保持原路径、请求体和响应格式。
|
||
- service 负责 `createCharacter`、空 config 兜底、计算执行。
|
||
- 为后续日志、耗时、缓存预留 service 层。
|
||
|
||
成功标准:
|
||
|
||
- `/api/calc` 行为不变。
|
||
- route 文件只保留 HTTP 层职责。
|
||
- Phase 0 测试全部通过。
|
||
|
||
### Phase 2:config view 与计算输入收口
|
||
|
||
新增 `CalcConfigView`,集中处理 config 的默认值与字段读取。
|
||
|
||
重点:
|
||
|
||
- `equips/oaths/emblems/skills/avatar/sundry/jades/options` 都有稳定默认值。
|
||
- 先在 `calc_init()`、`SetDetail()`、`SetSkills()` 等入口使用,不一次性替换所有 mixin。
|
||
- 保留向后兼容,仍支持原始 dict。
|
||
|
||
成功标准:
|
||
|
||
- 缺少可选字段时不抛出 KeyError。
|
||
- Skill/Forge 配置链路不变:config 变化 -> result.execute -> saveConfig。
|
||
- golden case 结果字段不变。
|
||
|
||
### Phase 3:装备数据 registry
|
||
|
||
新增 `EquipmentRegistry`,替换 `get_equipment()` 内部的 `eval` 与模块全局变量访问。
|
||
|
||
重点:
|
||
|
||
- `get_equipment(version)` 作为兼容门面继续存在。
|
||
- registry 内部使用 `functools.cache` 或显式 dict 缓存。
|
||
- 版本不存在时保持回退 `0` 的现有行为,但记录原因。
|
||
|
||
成功标准:
|
||
|
||
- 现有调用方无需一次性改动。
|
||
- 多版本装备数据加载路径清晰。
|
||
- 单元测试覆盖缓存、回退和不存在版本。
|
||
|
||
### Phase 4:计算核心 helper 化
|
||
|
||
抽取重复且无业务争议的 helper,降低 mixin 复杂度。
|
||
|
||
重点:
|
||
|
||
- `apply_uppercase_attrs(character, source)` 统一处理首字母大写属性应用。
|
||
- `execute_effect(effects, key, *args)` 统一处理效果函数不存在时的行为。
|
||
- 优先处理 `calc_suits/calc_equs/calc_oaths/calc_emblems` 中重复逻辑。
|
||
|
||
成功标准:
|
||
|
||
- helper 有单元测试。
|
||
- 业务公式位置不变或仅做等价搬迁。
|
||
- `calc_equip.py` 可读性提升,结果不变。
|
||
|
||
### Phase 5:性能观测与热点优化
|
||
|
||
在 service 层引入轻量计时,再针对热点做优化。
|
||
|
||
重点:
|
||
|
||
- 记录 `createCharacter`、`calc` 总耗时。
|
||
- 可选记录 `calc_*` 分段耗时,默认不影响响应结构。
|
||
- 优化重复 `获取基础属性()` 调用。
|
||
- 评估技能实例模板缓存,但必须避免可变技能状态污染请求。
|
||
|
||
成功标准:
|
||
|
||
- 能拿到典型配置的平均耗时和 p95。
|
||
- 优化前后 golden case 一致。
|
||
- 性能优化有数据支撑。
|
||
|
||
### Phase 6:规范与文档
|
||
|
||
清理运行缓存、废弃文件和文档入口。
|
||
|
||
重点:
|
||
|
||
- 确认 `.venv/.ruff_cache/__pycache__/.DS_Store` 不进入 git。
|
||
- 评估 `character_old.py` 移到归档或删除。
|
||
- 补充 backend README:运行、测试、质量检查、目录职责。
|
||
- 不在本阶段大规模格式化老文件。
|
||
|
||
成功标准:
|
||
|
||
- 新人可通过文档理解 backend 入口和测试方式。
|
||
- 废弃代码不会继续误导维护。
|
||
- 规范变更不产生大规模业务 diff。
|
||
|
||
## 风险与缓解
|
||
|
||
| 风险 | 缓解 |
|
||
| --- | --- |
|
||
| 重构导致 `/calc` 字段变化 | Phase 0 先做 contract/golden 测试 |
|
||
| 技能对象可变状态污染缓存 | 技能模板缓存必须后置,且只缓存类或不可变模板 |
|
||
| 装备版本回退行为改变 | `get_equipment()` 保留兼容门面,并测试回退 |
|
||
| config 字段默认值与前端不一致 | `CalcConfigView` 先只集中现有默认值,不新增语义 |
|
||
| 性能统计污染响应 | 默认只写日志,不改变 API data |
|
||
|
||
## 验收总标准
|
||
|
||
- `uv run pytest` 通过。
|
||
- `/api/calc` 响应契约不变。
|
||
- frontendNew 的 result.execute 与 saveConfig 链路无需调整。
|
||
- 典型职业 golden case 在重构前后字段一致,关键数值在允许误差内。
|
||
- backend 文档说明清晰,后续任务可以分阶段执行。
|