diff --git a/.env.example b/.env.example index df0de9b..3d1df94 100644 --- a/.env.example +++ b/.env.example @@ -76,9 +76,9 @@ INTERNAL_API_KEY=<必填> INTERNAL_ALLOWED_IPS=127.0.0.1 # 登录白名单(以下路径跳过登录校验) -LOGIN_WHITELIST_EXACT=/login,/captcha,/favicon.ico -LOGIN_WHITELIST_PREFIX=/job/sendSms,/public,/static,/api/webhook -LOGIN_WHITELIST_WILDCARD=/job/*/send*,/api/v1/*/public* +LOGIN_WHITELIST_EXACT=/api/v1/login,/api/v1/captcha,/favicon.ico +LOGIN_WHITELIST_PREFIX=/api/v1/job/sendSms,/public,/static,/api/webhook +LOGIN_WHITELIST_WILDCARD=/api/v1/job/*/send*,/api/v1/*/public* LOGIN_WHITELIST_DEBUG=True LOGIN_WHITELIST_LOG_ACCESS=True diff --git a/application/views/__init__.py b/application/views/__init__.py index 79f849d..a567ba4 100644 --- a/application/views/__init__.py +++ b/application/views/__init__.py @@ -102,36 +102,16 @@ _blueprint_modules = [ def register_blueprint(app): """ - 自动导入所有视图模块并注册蓝图到Flask应用实例 + 注册 API v1 路由到 Flask 应用实例 - 功能说明: - 遍历 _blueprint_modules 列表,动态导入每个视图模块, - 自动发现模块中的 Blueprint 实例并注册到应用。 - 此函数在应用启动时调用(通常位于 create_app 工厂函数中)。 + 所有业务蓝图统一挂载到 /api/v1 前缀(见 src/routes/v1.py)。 + 原自动发现机制已由 routes/v1.py 的显式注册取代。 Args: app: Flask应用实例 Returns: Flask: 注册完成后的Flask应用实例 - - Example: - # 在应用工厂函数中调用 - def create_app(): - app = Flask(__name__) - # ... 其他配置 ... - register_blueprint(app) - return app """ - registered = set() - for module_name in _blueprint_modules: - mod = importlib.import_module(f'application.views.{module_name}') - for attr_name in dir(mod): - attr = getattr(mod, attr_name) - if isinstance(attr, Blueprint): - # 合并模块的 shim 会重复导出同一批 Blueprint,按名去重 - if attr.name in registered: - continue - app.register_blueprint(attr) - registered.add(attr.name) - return app + from src.routes.v1 import register_v1 + return register_v1(app) diff --git a/src/modules/job/scheduler_service.py b/src/modules/job/scheduler_service.py index 75ef51c..0100b70 100644 --- a/src/modules/job/scheduler_service.py +++ b/src/modules/job/scheduler_service.py @@ -132,6 +132,9 @@ def build_full_url(url: str) -> str: # 相对路径,拼接站点域名 if url.startswith("/"): + # 内部 API 调用统一挂到 /api/v1(P6 API 版本化) + if not url.startswith("/api/"): + url = f"/api/v1{url}" url = f"{FLASK_SITE_DOMAIN}{url}" return url diff --git a/src/routes/__init__.py b/src/routes/__init__.py new file mode 100644 index 0000000..9a08fe2 --- /dev/null +++ b/src/routes/__init__.py @@ -0,0 +1 @@ +"""路由注册中心包。""" diff --git a/src/routes/v1.py b/src/routes/v1.py new file mode 100644 index 0000000..dd8c658 --- /dev/null +++ b/src/routes/v1.py @@ -0,0 +1,61 @@ +""" +API v1 路由注册中心 +============================================================= + +所有业务蓝图统一挂载到 /api/v1 前缀下: + - 模块内蓝图保持各自的 url_prefix(如 /user、/role) + - 整体前缀由 v1 父蓝图提供 → /api/v1/user/page 等 + +联动说明: + - 登录白名单需改为 /api/v1/login 等(见 config/auth.py 与 .env) + - 内部 HTTP 调用(定时任务)需追加 /api/v1 +""" + +from flask import Blueprint + +from src.modules.auth.router import login_blue, index_blue +from src.modules.association.role_menu.router import role_menu_blue +from src.modules.association.user_role.router import user_role_blue +from src.modules.city.router import city_blue +from src.modules.cms.article.router import article_blue +from src.modules.cms.category.router import category_blue +from src.modules.cms.link.router import link_blue +from src.modules.configuration.router import config_blue, config_item_blue +from src.modules.dictionary.router import dict_blue, dict_item_blue +from src.modules.file_template.router import file_template_blue +from src.modules.job.router import job_blue +from src.modules.log.router import login_log_blue, operation_log_blue +from src.modules.notice.router import notice_blue +from src.modules.param.router import param_blue +from src.modules.system.dept.router import dept_blue +from src.modules.system.level.router import level_blue +from src.modules.system.menu.router import menu_blue +from src.modules.system.position.router import position_blue +from src.modules.system.role.router import role_blue +from src.modules.system.user.router import user_blue +from src.modules.upload.router import upload_blue + +# 全部业务蓝图(按领域分组排序) +BLUEPRINTS = [ + login_blue, index_blue, + user_blue, role_blue, menu_blue, dept_blue, position_blue, level_blue, + user_role_blue, role_menu_blue, + dict_blue, dict_item_blue, config_blue, config_item_blue, + param_blue, notice_blue, category_blue, article_blue, link_blue, + upload_blue, file_template_blue, login_log_blue, operation_log_blue, + city_blue, job_blue, +] + + +def register_v1(app): + """ + 注册 API v1 路由(所有业务蓝图挂载到 /api/v1)。 + + Args: + app: Flask 应用实例 + """ + v1 = Blueprint('v1', __name__, url_prefix='/api/v1') + for blue in BLUEPRINTS: + v1.register_blueprint(blue) + app.register_blueprint(v1) + return app diff --git a/重构方案-v2.md b/重构方案-v2.md index db8ad99..7b932c0 100644 --- a/重构方案-v2.md +++ b/重构方案-v2.md @@ -356,7 +356,7 @@ from src.core.database import db # 统一 db 单例 | **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` 白名单放行 | 中 | +| **P6 API v1** | routes/v1.py + `/api/v1`;白名单/内部调用/前端前缀同步 | **✅ 已完成**:148 条路由全部挂 /api/v1;白名单已更新(/api/v1/login 等);build_full_url 内部调用自动加前缀;冒烟验证通过。**前端联动**:ui/ 需将 API 调用前缀改为 /api/v1(待 ui/ 迁移时一并处理) | 中(已完成) | | **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` 健康检查通过 | 低 |