Files
DjangoAdmin_Flask_EleVue/重构方案-v2.md
T

401 lines
24 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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 | **✅ 全部完成(21 模块)**:P4a 地基、P4b position 标杆(含单测)、P4c 六个批次(notice/param/link/level、article/category/file_template、dept/city、role/menu/user_role/role_menu、log/dictionary/configuration、job/auth/user)。每模块 flake8 零告警 + 应用启动 + 运行时验证。**合并模块已修复重名函数覆盖/双 import 块/路由导入 bug**。注:level 的 Excel import/export 仍返回 R(暂保留,后续统一改造) | 高(全部完成) |
| **P5 DI 注入** | core/permission.py + op_log 接口化;app 工厂注入 menu 服务 | **✅ 已完成**:PermissionProvider 协议 + check_permission 去 menu 依赖;core/middleware.py 整合全部中间件;factory 注入 MenuPermissionProvider + record_operation_log;src/core 零 application 引用;权限/日志行为一致;flake8 零告警 | 中(已完成) |
| **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. 新增分阶段验收标准、风险登记、执行纪律