# 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/` 后违反依赖规则。 **设计**: ```python # 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) ``` ```python # 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 一次**。 ```python # 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) ```python # 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) ``` **联动清单**(缺一不可): 1. `config/auth.py` 的 `LOGIN_WHITELIST_*` 增加 `/api/v1/login`、`/api/v1/captcha` 白名单。 2. 前端 `ui/` 所有 API 调用前缀改为 `/api/v1/...`(`VITE_PROXY` 仍为 `/api` → 8021,无需改 proxy)。 3. 后端内部 HTTP 调用(`modules/job/scheduler_service.py` 用 `requests` 调内部接口,携带 `X-Scheduler-Key`)URL 追加 `/api/v1`。 4. **验收点**:当前 `ui/` 与后端将短暂不同步,需排一个前端 API 前缀同步改动(可在 ui/ 迁走前一次性改)。 ### 2.4 服务层测试化改造(D5) **现状**:`user.get_user_page()` 内部直接读 `request`、返回 `R` 对象 → 无法单测。 **目标形态**: ```python # 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 单例 ``` **迁移顺序**(严格依赖序): 1. `src/core/`(db/redis/R/exceptions/logger)→ 打 shim 于 `extensions/`、`utils/` 2. `src/common/`(base_model/base_db/base_form/utils)→ 打 shim 于 `application/models/`、`application/forms/`、`utils/` 3. 各模块 models → forms → services → views,逐组迁移、逐组验证 4. `core/permission.py` + 应用工厂注入 5. `src/routes/v1.py` + `/api/v1` 切换 6. `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 修复清单: 1. `Dockerfile`/`docker-compose.yml` 上提根目录,`build: context: ./`、`dockerfile: Dockerfile`(修复现有 build 上下文错误); 2. 去掉 `privileged: true`、`user: root`、`network_mode: host` → `cap_add` + 非特权用户 + bridge 网络; 3. `HEALTHCHECK` 改为探测 `/api/v1/health`; 4. 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》的主要差异 1. 补全 9 处遗漏文件映射、修正 2 处矛盾(auth 与 log 争 login_log、templates 不存在) 2. 新增 5 项关键设计(DI、模型聚合、/api/v1、服务层改造、绞杀者) 3. 新增分层依赖规则与分层校验项 4. 新增分阶段验收标准、风险登记、执行纪律