【服务层新范式批量铺开】4 个 uniform 模块: - service:get_x_page(params)->dict、add/update/status(data)->None、 delete(ids)->int、get_info 委托 Repository(新建 repository.py) - 业务失败 raise BizError;router 取参包 R - level 的 Excel import/export 暂保留 R 返回(后续统一改造) 【工具】codemod 函数级转换(try/except 退格移除、R.failed→BizError、 R.ok→return data)+ black 格式化 + 未用导入清理 【教训】清理脚本 startswith 前缀误删含多名字的 import 行(models_to_list, model_to_dict 同行)导致 F821,已按 F821 精确补回 【验收】应用启动;4 服务分页查询运行时正常;12 passed;flake8 零告警 Co-Authored-By: Claude <noreply@anthropic.com>
24 KiB
DjangoAdmin Flask+EleVue 重构方案 v2(补齐版)
本文档为《重构设计.txt》的企业级补齐版,是后续执行的唯一依据。 适用范围:后端全部代码,不含
ui/(前端后续整体迁走)。 依赖分析基于 2026-08-07 代码库快照:152 个 Python 文件、约 33K 行、528 处内部 import。
0. 决策记录
| # | 决策点 | 结论 | 影响 |
|---|---|---|---|
| D1 | core→modules 依赖倒置 | 依赖注入:core/permission.py 定义权限校验接口,应用工厂启动时注入 menu 服务 |
见 §2.1 |
| D2 | 验证层 | 仅用 WTForms,schema.py 只是表单文件改名,不引入 Marshmallow |
24 个表单文件零重写 |
| D3 | 执行策略 | 增量迁移·绞杀者模式:application/ 留薄转发层,模块逐一搬迁 |
见 §2.5,全程可运行可回滚 |
| D4 | API 版本化 | 引入 /api/v1 前缀,所有蓝图挂在 v1 下 |
见 §2.3,需联动前端与白名单 |
| D5 | 服务层改造 | 做:服务函数改为「入参数据、出参数据」,router 负责取 request/包 R | 见 §2.4,本次最大工作量 |
| D6 | tenant 模块 | 本轮不做,从方案删除,后续作为新功能单独立项 | 见 §3 映射表 |
前置硬约束(P0):动手前必须先 git init + 基线提交。当前不是 git 仓库,无任何回滚兜底。
1. 目标架构总览
1.1 目录树(更新版)
DjangoAdmin_Flask_EleVue/
├── .env / .env.example / .gitignore / .editorconfig
├── .flake8 / .pre-commit-config.yaml
├── pyproject.toml # 项目管理 + tool 配置(pytest/black/isort/mypy 并入,不另设 pytest.ini)
├── requirements.txt # 生产依赖(保持)
├── requirements-dev.txt # 开发依赖
├── Makefile
├── README.md / README.en.md / CHANGELOG.md / CONTRIBUTING.md
├── CLAUDE.md / USAGE.md / LICENSE
├── gunicorn.conf.py / gunicorn_dev.conf.py
├── Dockerfile # ★ 从 docker/ 提到根目录(修复 compose build 上下文)
├── docker-compose.yml # ★ 从 docker/ 提到根目录
├── .dockerignore
│
├── src/ # ★ 核心源码(原 application/)
│ ├── __init__.py
│ ├── app.py # 应用工厂(原 application/__init__.py)
│ ├── wsgi.py # 生产 WSGI 入口
│ ├── extensions.py # register_extends:扩展编排(原 extensions/__init__.py)
│ ├── models_registry.py # ★ 模型聚合器(Flask-Migrate 依赖,见 §2.2)
│ │
│ ├── core/ # 基础设施层(不依赖 modules)
│ │ ├── __init__.py
│ │ ├── database.py # db 实例(原 extensions/sqlalchemy.py)
│ │ ├── redis_client.py # redis 实例(原 extensions/redis.py)
│ │ ├── scheduler.py # APScheduler 扩展实例(原 extensions/scheduler.py)
│ │ ├── mail.py # Flask-Mail 实例(原 extensions/mail.py)
│ │ ├── jwt.py # 原 utils/jwt.py
│ │ ├── password.py # 原 utils/password.py
│ │ ├── security.py # get_user_id / get_username(原 utils/security.py)
│ │ ├── response.py # R(原 utils/response.py)
│ │ ├── exceptions.py # BizError / AuthError 等 + 全局异常处理器
│ │ ├── middleware.py # check_login / check_demo / transaction / op_log(op_log 接口化)
│ │ ├── permission.py # ★ PermissionProvider 协议 + check_permission 装饰器(见 §2.1)
│ │ ├── logger.py # 结构化日志初始化(配置在 config/logging.py)
│ │ └── context.py # 请求上下文管理
│ │
│ ├── common/ # 共享层(依赖 core)
│ │ ├── __init__.py
│ │ ├── constants/
│ │ │ ├── __init__.py
│ │ │ ├── common.py
│ │ │ ├── dictionary_constants.py # ★ 原 constants/dict.py(改名避内建遮蔽)
│ │ │ └── error_code.py # 新增:错误码定义
│ │ ├── enums.py
│ │ ├── pagination.py # 分页统一封装
│ │ ├── base_model.py # 原 models/base_model.py
│ │ ├── base_db.py # ★ 原 models/base_db.py(原方案漏列)
│ │ ├── base_repository.py # 新增
│ │ ├── base_service.py # 新增
│ │ ├── forms/ # ★ 原 application/forms/ 拆出
│ │ │ ├── __init__.py
│ │ │ ├── base_form.py
│ │ │ └── fields.py # ★ 原 forms/fields.py(ArrayField,原方案漏列)
│ │ ├── decorators.py
│ │ ├── validators.py
│ │ └── utils/
│ │ ├── __init__.py # ★ 保留 `from common.utils import R, regular, password` 兼容
│ │ ├── request.py / model_utils.py / case_converter.py / regular.py
│ │ ├── captcha.py / file.py / rich_text.py / ip.py / user_agent.py
│ │ ├── formatter.py / json.py
│ │ ├── dict_utils.py # ★ 原 utils/dict.py(改名)
│ │ └── ip2region/ # 原 utils/ip2region.py + .xdb(10MB 二进制)
│ │ ├── __init__.py
│ │ └── ip2region.xdb
│ │
│ ├── modules/ # 业务层(依赖 core/common;允许跨模块调用业务服务)
│ │ ├── __init__.py # ★ 导入全部模块(含各模块 models),供模型聚合
│ │ ├── auth/ # router(login/captcha/logout + index 自服务) / schema / service / repository
│ │ ├── system/ # user / role / menu / dept / position / level
│ │ ├── association/ # user_role / role_menu
│ │ ├── dictionary/ # dict + dict_item 合并(router 保留 /dict 与 /dict/item 两个前缀)
│ │ ├── configuration/ # config + config_item 合并(/config 与 /config/item)
│ │ ├── cms/ # article / category / link
│ │ ├── notice/ param/ city/ file_template/ upload/
│ │ ├── job/ # router / schema / service / models / executor
│ │ │ # + scheduler_service.py(★ 原 services/scheduler.py)
│ │ │ # + mail_service.py(★ 原 services/mail_svc.py)
│ │ │ # + sms_service.py(★ 原 services/sms_svc.py)
│ │ ├── log/ # login_log + operation_log 合并
│ │ └── monitor/ # ★ 新增:/health 健康检查
│ │
│ ├── routes/
│ │ ├── __init__.py
│ │ └── v1.py # v1 = Blueprint('/api/v1'),注册全部业务蓝图
│ │
│ └── templates/ # ★ 从方案移除(当前异常全走 JSON,无模板渲染需求,见 §3 说明)
│
├── config/ # 保持并新增 logging.py
├── migrations/ # 保持(env.py 不变,模型聚合依赖 src/models_registry.py)
├── tests/
│ ├── conftest.py # 测试 App 构建 + 测试库/Redis fixture
│ ├── unit/ # core / common / modules 服务单测
│ └── integration/ # API 集成测试 + test_database.py
├── scripts/ # init_db / seed_data / migrate_structure / backup_db / health_check
├── static/ uploads/ logs/ instance/
├── docker/ # ★ 保留目录,仅放 nginx/ + entrypoint.sh(Dockerfile/compose 已上提根目录)
└── document/
1.2 分层依赖规则(硬性约束)
┌─────────────────────────────┐
│ modules(业务层,可互调) │
└──────────────┬──────────────┘
│
┌──────────────▼──────────────┐
│ common(共享层) │
└──────────────┬──────────────┘
│
┌──────────────▼──────────────┐
│ core(基础设施层) │
└─────────────────────────────┘
约束:
1. core 不得 import modules(依赖倒置由 DI 解决,见 §2.1)
2. modules 之间允许跨模块 import 业务服务(如 auth→log、user→association)
3. 全局异常由 core/exceptions.py 统一处理
4. 所有模型必须经 src/models_registry.py 在启动时导入一次
2. 关键设计
2.1 权限校验依赖注入(D1,P0-1)
问题:现 application/middleware/check_permission.py:49 直接 from application.services import menu。迁入 core/ 后违反依赖规则。
设计:
# src/core/permission.py
from typing import Protocol, List
class PermissionProvider(Protocol):
"""权限数据提供方接口:core 只认接口,不认实现。"""
def get_user_permissions(self, user_id: int) -> List[str]: ...
# check_permission 装饰器内部:
provider = current_app.extensions.get('permission_provider')
if provider is None:
# 未注入(如 PERMISSION_ENABLED=False)→ 放行或报错,由配置决定
...
permissions = provider.get_user_permissions(user_id)
# src/app.py 应用工厂,注册完蓝图后注入:
from modules.system.menu.service import get_user_permissions
from modules.system.menu.repository import MenuRepository
app.extensions['permission_provider'] = MenuPermissionProvider(get_user_permissions)
同一模式覆盖 op_log:op_log.py 直接引用 OperationLog 模型(application/middleware/op_log.py:21)。同样在 core/middleware.py 定义 OperationLogWriter 回调协议,由 modules/log/ 在启动时注册写入实现。
校验项:check_permission 与 op_log 迁移到 core 后,grep -r "from application" src/core/ 结果必须为空。
2.2 模型聚合与 Flask-Migrate(P1-3)
migrations/env.py:26 依赖 current_app.extensions['migrate'].db.metadata 全量收集表。模型打散到各模块后,必须保证每个模型模块在启动时被 import 一次。
# src/models_registry.py —— 唯一权威清单(等价原 application/models/__init__.py)
from modules.auth import models as _auth_models # auth 无独立表则不导入
from modules.system.user import models as _user_models
from modules.system.role import models as _role_models
# ... 全量列出
src/app.py在register_extends之前import src.models_registry。- 绞杀者过渡期:已迁移模型进 registry,未迁移的仍由
application/models/__init__.py导入,二者共同填满db.metadata;每次迁移一批后跑一次flask db migrate验证 autogenerate 仍全量识别。 - 校验项:
flask db migrate生成的空迁移中upgrade_ops.is_empty()必须为 False(即所有表仍被识别)。
2.3 API v1 版本化(D4)
# src/routes/v1.py
v1 = Blueprint('v1', __name__, url_prefix='/api/v1')
v1.register_blueprint(user_blue) # 模块内蓝图保持 url_prefix='/user'
v1.register_blueprint(menu_blue)
# ...
app.register_blueprint(v1)
联动清单(缺一不可):
config/auth.py的LOGIN_WHITELIST_*增加/api/v1/login、/api/v1/captcha白名单。- 前端
ui/所有 API 调用前缀改为/api/v1/...(VITE_PROXY仍为/api→ 8021,无需改 proxy)。 - 后端内部 HTTP 调用(
modules/job/scheduler_service.py用requests调内部接口,携带X-Scheduler-Key)URL 追加/api/v1。 - 验收点:当前
ui/与后端将短暂不同步,需排一个前端 API 前缀同步改动(可在 ui/ 迁走前一次性改)。
2.4 服务层测试化改造(D5)
现状:user.get_user_page() 内部直接读 request、返回 R 对象 → 无法单测。
目标形态:
# router.py
@user_blue.route('/page', methods=['GET'])
@check_permission('sys:user:page')
def page():
params, err = parse_query_params() # router 层取参
if err: return err
return R.ok(data=user_service.get_user_page(params)) # router 层包 R
# service.py(纯函数化,可单测)
def get_user_page(params: dict) -> dict:
query = repository.build_query(params)
items, total = repository.paginate(query, params.get('page', 1), PAGE_SIZE)
return {'records': convert(items), 'total': total}
# 业务错误
def reset_user_password(params: dict) -> None:
user = repository.get_by_id(params['id'])
if not user:
raise BizError('用户不存在', code=1) # 由 core/exceptions.py 统一转 R.failed
- 业务错误一律 raise BizError,不再
return R.failed(core/exceptions.py 提供register_error_handlers捕获转换)。 base_service.py/base_repository.py提供泛型 CRUD 骨架,各模块repository.py承载查询。- 工作量提示:这是本次对业务代码最深的一刀,26 个服务文件全部涉及,独立成 Phase 4(见 §4)。
2.5 绞杀者迁移模式(D3)
目标:全程 app.py(app:app)可启动、接口可访问,每步可回滚。
原则:自底向上、按依赖序迁移;迁完一个旧文件立即换成薄转发层。
旧文件 shim 示例:
# application/services/user.py → shim
from src.modules.system.user.service import * # 保持 `from application.services import user` 可用
# application/models/user.py → shim
from src.modules.system.user.models import *
# (注意 models shim 必须同时导出模型类 User 与 BaseDB/BaseModel 组合,保证 application/models/__init__.py 汇总正常)
# extensions/sqlalchemy.py → shim
from src.core.database import db # 统一 db 单例
迁移顺序(严格依赖序):
src/core/(db/redis/R/exceptions/logger)→ 打 shim 于extensions/、utils/src/common/(base_model/base_db/base_form/utils)→ 打 shim 于application/models/、application/forms/、utils/- 各模块 models → forms → services → views,逐组迁移、逐组验证
core/permission.py+ 应用工厂注入src/routes/v1.py+/api/v1切换src/app.py全量接管,删除application/extensions/utils/,WSGI 切src.wsgi:app
回滚:任一阶段失败,回滚到上一阶段 shim(git commit 粒度)。
2.6 验证层(D2)
- 24 个表单文件零逻辑重写,仅随模块搬移并改名
schema.py(文件名forms/login.py→modules/auth/schema.py)。 BaseForm+ArrayField迁至common/forms/。- 不引入 Marshmallow,
requirements.txt不加新依赖。
2.7 健康检查与 Docker 修复
src/modules/monitor/router.py:GET /api/v1/health(返回{code:0, data:{status:'ok', version:...}}),并加入登录白名单。- Docker 修复清单:
Dockerfile/docker-compose.yml上提根目录,build: context: ./、dockerfile: Dockerfile(修复现有 build 上下文错误);- 去掉
privileged: true、user: root、network_mode: host→cap_add+ 非特权用户 + bridge 网络; HEALTHCHECK改为探测/api/v1/health;- CMD 改为
gunicorn -c gunicorn.conf.py src.wsgi:app。
3. 完整文件映射清单(含原方案遗漏项修正)
3.1 原方案遗漏文件(★ = 补齐项)
| 原文件 | 去向 | 说明 |
|---|---|---|
services/scheduler.py(19KB) |
modules/job/scheduler_service.py |
业务调度器,被 extensions/scheduler.py 6 处引用 |
services/mail_svc.py / sms_svc.py |
modules/job/mail_service.py / sms_service.py |
被 job_executor / job 引用 |
forms/fields.py |
common/forms/fields.py |
ArrayField,被 base_form 与 user 表单引用 |
views/index.py |
modules/auth/router.py |
自服务接口(getMenu/getUser/userInfo/updatePwd),依赖 menu/user 服务 |
models/base_db.py |
common/base_db.py |
原方案只列了 base_model.py |
models/login_log.py |
modules/log/models.py |
★ 原方案 auth/models.py 与 log/models.py 冲突,定案入 log |
models/operation_log.py |
modules/log/models.py |
随 log 合并 |
constant/(空目录) |
删除 | 仅 pycache |
src/templates/ |
从方案移除 | 根目录无 templates,异常全走 JSON;如确需 HTML 错误页再单独立项 |
system/tenant/ |
从方案删除 | 当前代码不存在(D6) |
| Dockerfile / docker-compose.yml | 上提根目录 | 见 §2.7 |
src/extensions.py |
保留 | 与 core/*.py 分工:core 持有实例,extensions.py 只做 init_app 编排 |
3.2 模块映射汇总
| 新模块 | 蓝图中路由前缀(保持不变) | 来源 |
|---|---|---|
| auth | /login /captcha /logout /index/* |
views/login.py + index.py |
| system/user | /user |
views/user.py |
| system/role | /role |
views/role.py |
| system/menu | /menu |
views/menu.py |
| system/dept | /dept |
views/dept.py |
| system/position | /position |
views/position.py |
| system/level | /level |
views/level.py |
| association/user_role | /user/role |
views/user_role.py |
| association/role_menu | /role/menu |
views/role_menu.py |
| dictionary | /dict + /dict/item |
views/dict.py + dict_item.py |
| configuration | /config + /config/item |
views/config.py + config_item.py |
| cms/article | /article |
views/article.py |
| cms/category | /category |
views/category.py |
| cms/link | /link |
views/link.py |
| notice | /notice |
views/notice.py |
| param | /param |
views/param.py |
| city | /city |
views/city.py |
| job | /job |
views/job.py + services/scheduler.py + job_executor.py + mail_svc.py + sms_svc.py |
| log | /login/log + /oper/log |
views/login_log.py + operation_log.py |
| file_template | /file/template |
views/file_template.py |
| upload | /upload |
views/upload.py |
| monitor | /health |
新增 |
Blueprint 名称(
config_item、login_log等)在 op_log 记录request.endpoint时使用。合并后 endpoint 名变化只影响日志展示字段,不破坏功能;如需稳定可保留原 blueprint name。
4. 分阶段执行计划(每阶段含验收标准)
| 阶段 | 内容 | 验收标准 | 风险 |
|---|---|---|---|
| P0 基线 | git init;.gitignore 补 logs/ instance/ *.pyc;基线提交 |
git 仓库可用,可 git reset --hard 回滚 |
无 |
| P1 工具链 | pyproject.toml / requirements-dev.txt / .flake8 / .pre-commit / Makefile / pytest 骨架 | 现有代码 flake8 通过(或登记存量告警);pytest 空跑通过。已登记存量告警:154 条(F401×111 / E402×36 / 其余 7);pytest 5 passed |
低 |
| P2 core+common | 建 src/core/ src/common/;extensions/utils 打 shim;模型聚合器建 src/models_registry.py |
已验收:22 模型表全量识别且 DB 全部存在;src/ flake8 零告警;pytest 5 passed;应用启动;/login 表单校验与 /user/page 鉴权拦截冒烟正常。基线修复:application/models + middleware 原被嵌套 .gitignore(*)误排除,已补入。登记既有缺陷:① flask db CLI 因 .env 的 FLASK_VERSION 与 --version 冲突报错(Makefile 已加 FLASK_SKIP_DOTENV 规避);② 本机 Redis 未设密码但 REDIS_AUTH=True,/captcha 报 AUTH 错误(环境问题,非重构) |
高(依赖面最大,已完成) |
| P3 模块搬迁 | 按 §2.5 顺序逐组迁移 23 个模块(models→forms→services→views),每批打 shim + 验证 | 已验收:22 模型→19 模块 models.py;23 表单→schema.py;29 服务→service.py(含 executor/scheduler_service/mail_service/sms_service);26 视图→router.py(login+index 合并入 auth)。应用启动、25 蓝图无重复注册(views/__init__.py 已加去重)、pytest 5 passed、接口冒烟(登录校验/鉴权 401)正常 | 中(已完成) |
| P4 服务层改造 | 26 个服务文件改「入参数据/出参数据」;router 取参包 R;引入 base_service/base_repository + BizError | P4a 地基 ✓(BizError/base_repository/base_service)。P4b 标杆 position ✓(含单测)。P4c 批次1 ✓(notice/param/link/level 服务+router 改造,flake8 零告警,服务运行时验证通过)。待铺开:其余 ~20 个服务。注:level 的 Excel import/export 仍返回 R(暂保留 R 导入,后续统一改造) | 高(地基+标杆+批次1 完成,铺开中) |
| P5 DI 注入 | core/permission.py + op_log 接口化;app 工厂注入 menu 服务 | grep -r "from application" src/core/ 为空;权限/日志行为与现状一致 |
中 |
| P6 API v1 | routes/v1.py + /api/v1;白名单/内部调用/前端前缀同步 |
全部接口路径 curl /api/v1/... 通过;/login 白名单放行 |
中 |
| P7 切尾 | 删除 application/extensions/utils;src/wsgi.py;gunicorn.conf 指向 src.wsgi;更新 CLAUDE.md |
gunicorn -c gunicorn.conf.py src.wsgi:app 启动;全量回归 |
高 |
| P8 测试+文档 | tests/ 全量落地;CHANGELOG / CONTRIBUTING / API 文档 | pytest 通过;覆盖率报告 |
低 |
| P9 运维 | Docker 修复(§2.7)+ CI/CD(lint→type-check→pytest→build) | docker-compose up -d 健康检查通过 |
低 |
执行纪律:
- 每阶段独立 commit,提交信息带阶段前缀(
P2: ...)。 - 每个 P3 迁移批次后跑一遍全接口冒烟(可先用 Postman collection)。
- P4 与 P5 可并行准备,但P3 未全部收口前不得开始 P4(服务层改造需在稳定结构上进行)。
5. 风险登记
| 风险 | 等级 | 缓解 |
|---|---|---|
| 无版本控制 | 高 | P0 先行 git init |
| core→modules 依赖(permission/op_log) | 高 | DI 协议 + 启动注入(§2.1) |
| Flask-Migrate 表丢失 | 高 | models_registry + 每批迁移后 flask db migrate 空迁移校验(§2.2) |
/api/v1 切换导致前端/内部调用不同步 |
中 | P6 联动清单一次性核对(§2.3) |
| 服务层改造回归 | 高 | P4 单独阶段 + 接口回归为硬门槛 |
utils R 的 import utils.response as R 别名模式 |
中 | common/utils/__init__.py 保留同形兼容导出 |
10MB ip2region.xdb 路径解析 |
低 | 迁移时同步改 ip2region/__init__.py 内路径定位逻辑 |
6. 遗留命名权衡(已知,接受)
| 项 | 说明 |
|---|---|
根 config/ 与 modules/configuration/ |
两个「config」含义不同(应用配置 vs 业务配置),import 路径不冲突,予以保留 |
modules/dictionary/ 与 common/ 内字典文件 |
已通过 dictionary_constants.py / dict_utils.py 改名消解,不再遮蔽内建 dict |
| repository 层为新增代码 | 23 个模块各加 repository.py,属结构性增量,工作量已计入 P3/P4 |
附:与《重构设计.txt》的主要差异
- 补全 9 处遗漏文件映射、修正 2 处矛盾(auth 与 log 争 login_log、templates 不存在)
- 新增 5 项关键设计(DI、模型聚合、/api/v1、服务层改造、绞杀者)
- 新增分层依赖规则与分层校验项
- 新增分阶段验收标准、风险登记、执行纪律