后端-模块加载-模块未以modules前缀注册导致导入失败
问题现象
- 访问
/api/web-crawler/sites返回 200,但 SSE 流中某阶段中断 - 服务端日志报错:
ModuleNotFoundError: No module named 'modules.ai_recommendation' - 报错位置:
modules/web-crawler/routes.py:294from modules.ai_recommendation.service import RecommendationService - 同级
modules/wechat/routes.py、src/api/favorites.py中同样写法的导入均受影响
核心问题
- 根因:
src/core/module_manager.py的_load_module()将模块目录modules/ai-recommendation/(含连字符)转换为 Python 安全的ai_recommendation,但仅注册了短名:未注册sys.modules[safe_name] = pkg # 注册 ai_recommendation sys.modules[f"{safe_name}.models"] = mod # 注册 ai_recommendation.modelsmodules.ai_recommendation和modules.ai_recommendation.models,导致所有from modules.ai_recommendation.xxx import写法全部失败 - 触发条件:模块目录名含连字符(如
ai-recommendation),且调用方使用modules.前缀的完整路径导入 - 为什么 CLI 能跑:
src/cli/daily_crawl.py在手动加载模块时也注册了短名,且自身使用from ai_recommendation.service import(不加modules.前缀)——两种写法不统一
解决方法
在 src/core/module_manager.py 的 _load_module() 中,为包、models、routes 三个实体均追加 modules. 前缀注册:
- 包注册(第 129-130 行):
sys.modules[safe_name] = pkg sys.modules[f'modules.{safe_name}'] = pkg # 新增 - models 注册(第 144-145 行):
sys.modules[mod_key] = mod sys.modules[f'modules.{mod_key}'] = mod # 新增 - routes 注册(第 170-171 行):
sys.modules[mod_key] = mod sys.modules[f'modules.{mod_key}'] = mod # 新增 - 验证:
cd d:/product/news && venv/Scripts/python.exe -c " import sys; sys.path.insert(0, '.') from fastapi import FastAPI from pathlib import Path from src.core.module_manager import ModuleManager mm = ModuleManager() mm.load_all(FastAPI(), Path('modules')) from modules.ai_recommendation.service import RecommendationService print('OK:', RecommendationService) " - 重启后端服务生效
下次防错措施
- 快速定位:搜索
ModuleNotFoundError.*modules\.— 若出现在from modules.xxx import语句且 xxx 对应目录名含连字符,基本可确定是此问题 - 开发规范:模块间导入统一使用
modules.前缀(如from modules.ai_recommendation.service import ...),不再混用短名写法 - 可添加的验证脚本:
# 启动后验证所有模块的 modules. 前缀是否可用 cd d:/product/news && venv/Scripts/python.exe -c " import sys; sys.path.insert(0, '.') from fastapi import FastAPI from pathlib import Path from src.core.module_manager import ModuleManager mm = ModuleManager() mm.load_all(FastAPI(), Path('modules')) for name in mm._modules: safe = name.replace('-', '_') assert f'modules.{safe}' in sys.modules, f'Missing: modules.{safe}' print('All modules registered with modules. prefix') "