一、这是什么
一个后台管理系统,基于已有的开源框架做二次开发。这类工作与从零起项目的区别很实际: 你面对的是一套别人定的分层、命名约定与扩展方式,而你的每个新增都要让这套结构继续成立,而不是把它撑破。
系统的功能分三层,这个分层决定了扩展点怎么划:
| 层 | 内容 | 变更频率 |
|---|---|---|
| 系统层 | 用户、角色、权限、菜单、审计日志 | 低——改动影响面最大 |
| 监控层 | 运行状态、任务执行情况、异常记录 | 中 |
| 业务层 | 具体业务模块,二十余个 | 高——持续新增 |
分层的价值在于:变更频率高的层可以放心改,变更频率低的层要谨慎。 没有这个划分,改一个业务模块时没人说得清会不会牵动权限。
二、规模
三、三个关键设计
1 · 插件化 manifest:扩展不改核心
新增一个业务模块时,无需改动框架的核心代码——模块通过一份清单声明自己提供什么路由、 占哪些菜单、依赖哪些权限、需要哪些数据表。框架在启动时扫描并装配。
2 · 版本化迁移:十三条,每一条都能单独回退
数据库结构变更全部走迁移脚本,按版本号链式排列。这套机制解决的是 「开发、测试、生产三套环境的表结构怎么保证一致」—— 手工改表在这里是不可行的,因为没人记得住每个环境被改到了哪一步。
十三条迁移是迭代的结果而非一次设计:每加一个业务模块通常会带一两条。 关键在于迁移只增不改——已经上线过的迁移不允许回改,需要调整就再写一条新的。 这样任何环境都能从任意历史版本向前推到最新。
3 · 三层模块解耦,各自独立交付
系统层、监控层、业务层在代码组织上分离开,业务模块之间不直接互相引用, 需要共享的能力下沉到公共层。收益在多模块并行开发时才体现出来—— 没有这层隔离,两个模块同时开发时冲突会集中在公共文件上。
四、决策记录
- 为什么这么选
- 在 2272 文件的既有代码库上,改核心的代价不是这一次改动本身,而是它让后续每一次框架升级都变成一次合并冲突。 插件化把自定义部分隔离在核心之外,升级时冲突面小得多。
- 放弃了什么
- 直接的表达力。有些需求用改核心的方式三行就能写出来,走插件机制要写清单、注册、适配,代码量更大。 另外插件机制本身也是一层抽象,新人理解系统时需要先懂这套装配规则。
- 什么条件下会失效
- 如果这个系统不再跟着上游框架升级、自定义部分也不打算复用,插件化就是纯负担。 它的收益来自「未来还要演进」这个前提。
- 为什么这么选
- 已上线的迁移如果在原文件上改,开发环境会得到新结构,而生产环境不会重跑它—— 两边结构出现静默分叉,且没有任何机制会报错。只增不改让「当前结构」永远等于「按顺序跑完全部迁移」的结果,可推导。
- 放弃了什么
- 迁移文件的整洁。迭代几轮后,同一个表可能出现在三条迁移里(建表、加字段、改类型), 要看全貌必须从头读一遍。这个代价用文档补,而不是用改文件的方式消除。
- 什么条件下会失效
- 如果迁移还没上过生产(还在开发期),直接改是合理的——只增不改的规则从第一次上线开始生效。
- 为什么这么选
- 依赖关系复杂(框架、数据库驱动、前端构建产物、系统库)。直接部署意味着服务器上装了什么, 这个系统就依赖什么,换一台机器要重新踩一遍环境。容器把「环境」变成交付物的一部分。
- 放弃了什么
- 镜像体积与构建时间。分层缓存设计得不好时,改一行代码要重建整个镜像。 另外容器内的调试比直接在宿主机上调试要绕一层。
- 什么条件下会失效
- 单机、单服务、环境固定不变时,容器带来的收益有限,直接部署更轻。容器真正开始划算是在 环境数量大于一、或者部署需要重复发生时。
五、失败与边界
- 扩展点划错最初把某类能力放在业务层,后来发现系统层也要用,导致要么复制一份、要么把业务层的代码反向依赖上来。根因是「按当前谁用它」而不是「按它属于谁」去决定分层。
- 迁移顺序依赖有两条迁移之间存在隐性顺序依赖(后一条依赖前一条建的表),但版本号编排没有体现出来。在全新环境上按序执行时暴露了问题。迁移应该只依赖它之前的版本,不能跨越。
- 插件装配的报错清单写错时,报错发生在启动阶段且信息不指向具体模块——因为扫描器是先收集后统一装配。后来给每个模块的装配过程加了明确标识,报错能指出是哪个模块的问题。
它不适合什么场景。功能固定、不会持续新增模块的小系统——插件化与分层带来的抽象成本没有对应的收益。 一个稳定的单模块后台,直接写更快也更好维护。
六、链接与状态
| 状态 | 业务系统,源码与界面不公开 |
|---|---|
| 原因 | 业务系统且包含未公开的业务逻辑与数据结构 |
| 可提供的 | 以上架构说明与设计决策;不含业务逻辑细节与界面截图 |