5.0 KiB
代码设计与目录
原则与当前状态
下列为远期目标设计;第一批核心已按职责实现为独立模块,见09-foundation.md。当前旧代码位于 legacy,未把其全局变量、 数据库管理器和服务定位器直接当作新核心继续复用。
采用一个共享 Python 包,内部按职责划分,避免先建多个互相依赖的 独立发行包。共享源码只维护一份;发布时复制进各插件私有目录。
目标结构
farm/
src/farm_core/
domain/ # 规则、值对象、状态转换,不做 I/O
application/ # 注册/购买/播种等用例,拥有事务边界
ports/ # 必要的仓储事务、时钟、外部钱包契约
infrastructure/
sqlite/ # 连接、仓储、schema migration
resources/ # 作物目录、资源校验与更新
presentation/ # 框架无关展示数据;可选图片渲染
integrations/
zhenxun/ # 真寻命令、配置、金币、数据目录、生命周期
nonebot/ # 普通 NoneBot 插件,不导入真寻
astrbot/ # AstrBot 插件,不导入 NoneBot 或真寻
assets/ # 经整理、版本化的资源,尚未从 legacy 提取
tests/
unit/ integration/ contracts/ migrations/ packaging/
release/ # 构建目标、文件白名单、依赖与模板
scripts/ # 离线校验、迁移、未来打包工具
legacy/ # 旧实现参考;不进入新插件安装包
docs/
初始化时只创建职责说明;当前已实现第一批核心,不生成大量空模块。 当一个用例实施时,才创建所需模块。
依赖方向
flowchart TD
Z[真寻接入] --> A[应用用例]
N[NoneBot 接入] --> A
B[AstrBot 接入] --> A
A --> D[领域规则]
A --> P[必要接口]
S[SQLite 实现] --> P
R[资源实现] --> P
Z --> C[组装与生命周期]
N --> C
B --> C
C --> S
C --> R
A --> V[展示数据]
V --> I[可选图片渲染]
基础设施实现接口,由宿主启动阶段组装并注入;领域与应用代码不能 访问框架全局对象、事件、配置管理器或消息发送 API。 记录日志使用标准 logging,接入层按需桥接宿主日志。
每层的责任
| 层 | 应做 | 禁止混入 |
|---|---|---|
| 命令接入 | 参数解析、用户上下文、权限、确认、发送回复 | 再写一套价格/奖励计算 |
| 应用用例 | 业务校验、幂等、事务、调用领域计算 | 对聊天平台对象直接操作 |
| 领域规则 | 土地/星级/经验计算与状态转换 | SQL、网络、图片、框架导入 |
| 存储 | 条件更新、查询、schema 升级 | 命令文案、签到奖励选择、图片 |
| 展示 | 将结果渲染为文本/图片 | 修改余额和农场状态 |
| 资源 | 校验、版本、缓存与原子切换 | 未验证即覆盖线上目录 |
命令与业务服务返回明确结果,例如成功、余额不足、种子不足、 已处理、状态冲突;结果包含展示所需数值和错误码。 数据库故障不能转换成“没有记录”或“没有农场”。
最小接口建议
- UnitOfWork:一次用例获取同一事务下的仓储;成功才提交。
- Clock:提供带时区的当前时间,使跨日、成熟和签到可测试。
- PlantCatalog:提供带版本的作物定义,已种植实例绑定规则版本。
- ExternalWallet:仅在真寻金币等外部经济接入时启用,明确幂等与补偿。
不要为字符串工具、日志和每个函数都建接口。 领域对象使用明确数据类型,取代任意 dict[Any, Any]。 注册、库存、地块、货币、收获周期分别建模,枚举及字段有说明。
生命周期和异步模型
导入模块时不启动清理线程、不连接数据库、不发送网络请求。 宿主启动:验证配置→迁移→初始化仓储/资源→就绪; 宿主停止:拒绝新写入→等待在途用例→关闭连接和任务。
首期建议单进程嵌入式部署。不要用 threading.RLock 代替异步业务锁。 事务连接生命周期由 UnitOfWork 管理;即使使用进程内锁,SQL 仍需 条件更新和唯一约束。查询缓存首期可不做;需要时在提交后失效, 不缓存不存在的用户作为有效玩家,不把缓存余额作为扣款依据。
发布时的包隔离
src/farm_core 内部一律采用相对导入。 构建时复制为 <插件包>/_vendor/farm_core,接入层经包内路径导入。 _vendor 必须是普通包且顶层初始化无副作用。 禁止把共享核心作为全局同名 farm_core 安装后又在插件中带另一份, 避免同时安装真寻/NoneBot 包时导入碰撞。
第三方依赖只列核心必需项和当前接入层项;渲染依赖可选。 私有命名空间能隔离源码名称,不能隔离同一 Python 进程的第三方依赖 版本;冲突必须通过兼容测试或分进程解决。