# AR01 — 后端架构设计 > **版本:V1.1 | 框架:FastAPI + SQLAlchemy + MySQL 8.0** > **参考:pj006-zhilianyuan2 的 BE01_backend_arch + main.py 装配模式** > > **⚠️ 本文档为 V1.1 设计期历史快照,不再反映当前实现。** 后端已重写为 **Go + Gin + GORM + MySQL 8.0 + FAISS**,以 `docs/changelog.md`(V1.2)、`docs/db_schema.md`、`docs/deploy.md` 为准;下文 FastAPI/Python 结构与 `backend/` 路径仅作设计参考。 --- ## 1. 架构分层 ``` ┌─────────────────────────────────────────────┐ │ API 路由层 (routes) │ │ auth / company_train / product / course │ │ exam / media / ai_chat / system │ ├─────────────────────────────────────────────┤ │ Pydantic 模型层 (schemas) │ │ 请求/响应模型,统一响应格式 Envelope │ ├─────────────────────────────────────────────┤ │ 服务层 (services) │ │ exam_service / media_service / ai_service │ ├─────────────────────────────────────────────┤ │ SQLAlchemy ORM 模型层 (models) │ │ User / Product / Course / MediaFile / ... │ ├─────────────────────────────────────────────┤ │ 核心层 (core) │ │ config / security / deps │ ├─────────────────────────────────────────────┤ │ MySQL 8.0 + data/media │ └─────────────────────────────────────────────┘ ``` ## 2. 目录结构 ``` backend/ ├── app/ │ ├── main.py # FastAPI 应用装配 + CORS + 异常处理 │ ├── api/ # 路由层 │ │ ├── auth.py # /api/auth/* — 登录/注册/me │ │ ├── company_train.py # /api/company-train/* │ │ ├── product.py # /api/products/* │ │ ├── sales_train.py # /api/courses/* │ │ ├── exam.py # /api/exam/* — 题库/组卷/考试/记录 │ │ ├── media.py # /api/media/* — 上传/预览/审批 │ │ ├── ai_chat.py # /api/ai-chat/* — PathCoach SSE │ │ └── system.py # /api/system/* — 用户/成绩/配置 │ ├── models/ # SQLAlchemy ORM 模型 │ │ ├── user.py │ │ ├── product.py │ │ ├── course.py │ │ ├── media_file.py │ │ ├── knowledge_chunk.py │ │ ├── question.py │ │ ├── exam_paper.py │ │ └── exam_record.py │ ├── schemas/ # Pydantic 请求/响应模型 │ ├── services/ # 业务逻辑层 │ │ ├── media_service.py # 上传/转换/提取 │ │ ├── ai_service.py # LLM 调用 + 知识检索 │ │ └── exam_service.py # 题库/组卷/判分/记录 │ ├── core/ # 核心基础设施 │ │ ├── config.py # .env + 系统参数读取 │ │ ├── security.py # JWT 签发/校验 + bcrypt │ │ └── deps.py # FastAPI Depends(get_db / get_current_user) │ └── utils/ # 工具函数 ├── data/media/ # 文件存储(git忽略) │ ├── upload/ │ └── _preview_cache/ ├── requirements.txt └── .env ``` ## 3. 应用装配模式(main.py) 参考 zhilianyuan2 的模式,每个模块的 router 独立注册: ```python from fastapi import FastAPI from app.api import auth, company_train, product, sales_train from app.api import exam, media, ai_chat, system app = FastAPI(title="eai_agentplatform_app", version="1.1.0") # 异常处理器 @app.exception_handler(AppError) def handle_app_error(request, exc): return JSONResponse(status_code=exc.status_code, content={...}) # 路由注册 app.include_router(auth.router) app.include_router(company_train.router) app.include_router(product.router) app.include_router(sales_train.router) app.include_router(exam.router) app.include_router(media.router) app.include_router(ai_chat.router) app.include_router(system.router) ``` ## 4. 依赖注入模式 参考 zhilianyuan2 的 `auth/dependencies.py`: ```python # core/deps.py async def get_current_user( credentials: HTTPAuthorizationCredentials | None = Depends(HTTPBearer(auto_error=False)), db: Session = Depends(get_db), ) -> User: """解析 JWT → 校验用户状态 → 返回 User""" if credentials is None: raise AuthError("缺少 Authorization Bearer 令牌") payload = decode_access_token(credentials.credentials, settings) user = db.query(User).filter(User.username == payload["sub"]).first() if user is None or user.status != "active": raise AuthError("用户不存在或已禁用") return user def require_admin(user: User = Depends(get_current_user)) -> User: """管理员角色守卫""" if user.role != "admin": raise ForbiddenError("需要管理员权限") return user ``` ## 5. API 路由前缀 | 路由前缀 | 模块 | 说明 | |---------|------|------| | `/api/auth/*` | auth | 登录/注册/当前用户 | | `/api/company-train/*` | company_train | 公司介绍内容 | | `/api/products/*` | product | 产品 CRUD + 导入 | | `/api/courses/*` | sales_train | 课程 CRUD + 绑定产品 | | `/api/exam/*` | exam | 题库/组卷/考试/记录 | | `/api/media/*` | media | 上传/预览/审批/状态 | | `/api/ai-chat/*` | ai_chat | PathCoach 流式对话 | | `/api/system/*` | system | 用户/成绩/配置 | | `/api/health` | — | 健康检查 | ## 6. 异步任务模式 文档转换管线(审批通过后异步执行): ```python # services/media_service.py import threading def _async_convert_and_extract(media_file_id: int): """审批通过后异步执行:文档转 PDF → 文本提取 → 切片入库""" with Session() as db: media = db.query(MediaFile).get(media_file_id) # 1. 调用 LibreOffice 转 PDF pdf_path = libreoffice_convert(media.stored_path) # 2. PyMuPDF 提取文本 text = pymupdf_extract(pdf_path) # 3. 按段落切片写入 knowledge_chunk chunks = split_into_chunks(text) for i, chunk in enumerate(chunks): db.add(KnowledgeChunk(media_file_id=media.id, ...)) media.extracted = True db.commit() def approve_media(media_file_id: int, auditor_id: int): """审批通过 → 触发异步转换""" media.status = "approved" media.audit_by = auditor_id media.audit_at = datetime.utcnow() db.commit() # 启动异步任务 threading.Thread(target=_async_convert_and_extract, args=(media_file_id,)).start() ```