107 lines
6.8 KiB
Markdown
107 lines
6.8 KiB
Markdown
# NEX Docus 后端
|
||
|
||
FastAPI + SQLAlchemy 2.0(异步)+ MySQL + Redis 的文档管理后端,同时提供 RAG 知识库问答与 MCP 服务。
|
||
|
||
> **上手请先看 [`docs/quickstart.md`](../docs/quickstart.md)**(一键启动、环境变量、初始化、常见问题)。
|
||
> 部署/运维见 [`docs/deploy/README.md`](../docs/deploy/README.md),表结构见 [`docs/database.md`](../docs/database.md)。
|
||
> 本文件只描述**后端代码内部结构**与开发约定。
|
||
|
||
## 技术栈
|
||
|
||
| 用途 | 选型 |
|
||
| --- | --- |
|
||
| Web 框架 | FastAPI + Uvicorn |
|
||
| ORM | SQLAlchemy 2.0 异步(aiomysql),Alembic 迁移 |
|
||
| 数据库 / 缓存 | MySQL 8(utf8mb4)/ Redis |
|
||
| 认证 | JWT(python-jose)+ bcrypt(passlib) |
|
||
| 全文检索 | Whoosh 3 + jieba 分词 |
|
||
| 向量检索 | ZVec(远端 Embedding 或本地 Sentence-Transformers) |
|
||
| 文件与导出 | aiofiles、自研 Markdown/PDF 导出服务 |
|
||
| Git 同步 | 通过命令行调用 `git`(主机需自行安装) |
|
||
| 测试 | pytest + pytest-asyncio |
|
||
|
||
## 目录结构
|
||
|
||
```
|
||
backend/
|
||
├── app/
|
||
│ ├── api/v1/ # HTTP 路由(前缀 /api/v1),__init__.py 汇总注册
|
||
│ ├── core/ # config / database / deps / security / enums / migrations / redis_client
|
||
│ ├── mcp/ # MCP server 与请求上下文(挂载在 /mcp)
|
||
│ ├── models/ # SQLAlchemy 模型(__init__.py 必须导入全部模型,见下方陷阱)
|
||
│ ├── schemas/ # Pydantic Schema 与统一响应包装
|
||
│ └── services/ # 业务逻辑:project/file/search/rag/zvec/git/vectorization/notification/log/storage/llm…
|
||
├── scripts/ # init_db.py(建表+种子数据)、generate_password.py,详见 scripts/README.md
|
||
├── tests/ # pytest 用例
|
||
├── models/ # 本地向量模型目录(gitignore,需自行放置 HF 模型,如 m3e-small)
|
||
├── main.py # 应用入口:/api/v1 路由、/mcp 挂载、/ 与 /health
|
||
├── requirements.txt # 运行依赖
|
||
├── requirements-dev.txt # 开发/测试依赖(含 pytest)
|
||
├── pytest.ini # testpaths=tests, pythonpath=.
|
||
└── .env # 本地配置(不入库;键名见下表)
|
||
```
|
||
|
||
## 路由一览
|
||
|
||
`main.py` 暴露:`GET /`(名称/版本/状态)、`GET /health`(`{"status":"healthy"}`)、`/docs`、`/openapi.json`、`/mcp`(MCP Streamable HTTP,需 `X-Bot-Id` / `X-Bot-Secret`)。
|
||
|
||
`app/api/v1/__init__.py` 注册的子路由前缀:
|
||
|
||
| 前缀 | 模块 | 说明 |
|
||
| --- | --- | --- |
|
||
| `/auth` | auth | 登录、当前用户、资料/密码/头像、MCP 凭证签发与轮换 |
|
||
| `/projects` | projects | 项目 CRUD、成员、所有权转移、分享开关、Git pull/push 与目录选择 |
|
||
| (同 `/projects/{id}/git-repos`) | git_repos | 仓库配置的增删改查与连通性测试(路由内部自带前缀) |
|
||
| `/files` | files | 目录树、读写文件、文件操作、上传、导入导出、PDF 导出、文档与静态资源流式访问 |
|
||
| `/preview` | preview | 项目预览 |
|
||
| `/shares` | shares | 分享链接创建/校验/访问 |
|
||
| `/search` | search | 全文 + 向量双引擎检索 |
|
||
| `/chat` | chat | 会话管理、SSE 流式问答、引用与中断 |
|
||
| `/llm-model-configs` | llm_model_configs | chat / embedding 模型配置与连通性测试 |
|
||
| `/dashboard` | dashboard | 管理员统计 |
|
||
| `/users` `/roles` `/role-permissions` `/menu` | users/roles/role_permissions/menu | 用户、角色、角色权限、菜单树 |
|
||
| `/notifications` | notifications | 站内通知 |
|
||
| `/logs` | logs | 系统日志查询 |
|
||
|
||
## 环境变量(`backend/.env`)
|
||
|
||
完整键表与说明见 [`docs/quickstart.md`](../docs/quickstart.md);此处只列必填项与常见坑:
|
||
|
||
- 必填:`DB_HOST` `DB_USER` `DB_PASSWORD` `DB_NAME`、`REDIS_HOST` `REDIS_PASSWORD`、`SECRET_KEY`。
|
||
- 文件存储:`STORAGE_ROOT` / `PROJECTS_PATH` / `USERS_PATH` / `TEMP_PATH`(**本地开发用这组绝对路径**;Docker 部署用根 `.env` 的 `STORAGE_PATH`,两套体系不同,别混用)。
|
||
- `DEBUG=True` 时 SQLAlchemy 会打印全量 SQL,**生产必须 `false`**。
|
||
- `DB_PASSWORD` 含特殊字符无需手动转义(连接串构造时已 `quote_plus`)。
|
||
|
||
> ⚠️ **不要在代码、文档或提交里写真实环境的账号密码。** 需要示例时用占位值(如 `password_change_me`)。
|
||
|
||
## 开发命令
|
||
|
||
```bash
|
||
cd backend
|
||
python3 -m venv venv
|
||
./venv/bin/pip install -r requirements-dev.txt # 含运行依赖 + pytest
|
||
cp .env.example .env # 若无,按 docs/quickstart.md 的键表创建
|
||
|
||
./venv/bin/python scripts/init_db.py # 建表 + 种子数据(幂等)
|
||
./venv/bin/python scripts/generate_password.py # 生成 bcrypt 哈希
|
||
./venv/bin/python -m pytest # 单元测试
|
||
./venv/bin/uvicorn main:app --reload --port 8000 # 单独起服务(推荐用仓库根 ./scripts/start.sh)
|
||
```
|
||
|
||
## 必须知道的实现约定
|
||
|
||
1. **新增模型要在 `app/models/__init__.py` 里导入。** 只建表逻辑而不导入,`Base.metadata` 就看不到该表,`init_db.py` 会**静默漏建表**,线上表现为 `Table ... doesn't exist`。
|
||
2. **项目权限只走 `app/services/project_service.py`**(`require_project_read_access` / `require_project_write_access` / `normalize_project_role`)。不要在路由里手写角色判断:历史数据里 `project_members.role` 存在大写(`ADMIN`/`EDITOR`/`VIEWER`),直接 `== "admin"` 比较会静默失效。返回给前端的角色必须先 `normalize_project_role`。
|
||
3. **异步 Session 配了 `expire_on_commit=False`**,但 `onupdate=func.now()` 的列(如 `updated_at`)在 UPDATE 提交后仍会被标记过期;提交后若还要序列化整个 ORM 对象,**必须 `await db.refresh(obj)`**,否则会在异步上下文触发同步 IO 抛 `MissingGreenlet`(HTTP 500)。
|
||
4. **统一响应**用 `app/schemas/response.py` 的 `success_response()`;业务错误抛 `HTTPException` 并用 4xx 表达(不要用 500 表达"参数/状态不合法")。
|
||
5. **SSE 接口**(`/chat`)要求网关关闭响应缓冲,见 `docs/deploy/README.md` 的 nginx 示例。
|
||
6. **本地向量模型**放在 `backend/models/<模型名>/`(含 `config.json`、`1_Pooling/config.json`、`model.safetensors` 或 `pytorch_model.bin`),由 `LocalEmbeddingService` 扫描列出;该目录已 gitignore,不要提交。
|
||
|
||
## 测试
|
||
|
||
```bash
|
||
cd backend && ./venv/bin/python -m pytest
|
||
```
|
||
|
||
覆盖范围(v1.0.0):项目权限与角色归一化、Git 服务命令构造、搜索服务、模型与向量配置、RAG 引用解析。**前端目前没有自动化测试**,界面回归需手工验证,重点清单见 [`docs/sdd/releases/v1.0.0.md`](../docs/sdd/releases/v1.0.0.md)。
|