跳转至

后端-模块加载-模块未以modules前缀注册导致导入失败

问题现象

  • 访问 /api/web-crawler/sites 返回 200,但 SSE 流中某阶段中断
  • 服务端日志报错:
    ModuleNotFoundError: No module named 'modules.ai_recommendation'
    
  • 报错位置:modules/web-crawler/routes.py:294
    from modules.ai_recommendation.service import RecommendationService
    
  • 同级 modules/wechat/routes.pysrc/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.models
    
    未注册 modules.ai_recommendationmodules.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. 前缀注册:

  1. 包注册(第 129-130 行):
    sys.modules[safe_name] = pkg
    sys.modules[f'modules.{safe_name}'] = pkg  # 新增
    
  2. models 注册(第 144-145 行):
    sys.modules[mod_key] = mod
    sys.modules[f'modules.{mod_key}'] = mod  # 新增
    
  3. routes 注册(第 170-171 行):
    sys.modules[mod_key] = mod
    sys.modules[f'modules.{mod_key}'] = mod  # 新增
    
  4. 验证
    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)
    "
    
  5. 重启后端服务生效

下次防错措施

  • 快速定位:搜索 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')
    "