344 lines
9.7 KiB
Markdown
344 lines
9.7 KiB
Markdown
这是一份为您量身定制的《NEX Docus 产品技术方案设计文档》。
|
||
|
||
这份文档整合了我们之前的讨论,采用了**“数据库管理权限 + 文件系统存储内容”**的混合架构,后端确定采用 **FastAPI**,以实现高性能和快速开发。
|
||
|
||
---
|
||
|
||
# NEX Docus 产品技术方案设计文档 (V1.0)
|
||
|
||
**文档状态:** 正式版
|
||
**最后更新:** 2023-12-20
|
||
**技术负责人:** [Mula.liu]
|
||
|
||
---
|
||
|
||
## 1. 项目概述 (Overview)
|
||
|
||
### 1.1 项目简介
|
||
|
||
**NEX Docus** 是一款面向团队协作的轻量级文档管理平台。它结合了传统文档系统的权限管理便利性和本地文件存储的数据透明性。
|
||
|
||
### 1.2 核心设计理念
|
||
|
||
* **文件即真理 (File as Truth):** 所有文档内容、图片、附件直接以文件形式存储在服务器磁盘,不存入数据库 `BLOB` 字段。方便备份、迁移及后续支持 Git 版本控制。
|
||
* **三级架构:** 用户 (User) -> 项目 (Project) -> 文档/文件夹 (File/Folder)。
|
||
* **严格隔离:** 基于项目的物理文件夹隔离,结合数据库的 RBAC 权限控制。
|
||
|
||
### 1.3 功能目标
|
||
|
||
1. **多用户/多租户:** 支持用户注册、登录,创建私有项目。
|
||
2. **项目协作:** 项目拥有者可邀请成员(只读/读写权限)。
|
||
3. **文档管理:** 支持无限层级目录(物理文件夹映射),支持 Markdown 编辑与预览。
|
||
4. **资源管理:** 支持图片、附件拖拽上传与引用。
|
||
|
||
---
|
||
|
||
## 2. 技术选型 (Tech Stack)
|
||
|
||
### 2.1 前端 (Frontend)
|
||
|
||
* **核心框架:** React 18+
|
||
* **UI 组件库:** Ant Design 5.0 (企业级交互)
|
||
* **样式库:** Tailwind CSS (快速排版)
|
||
* **状态管理:**
|
||
* **Markdown:**
|
||
|
||
### 2.2 后端 (Backend)
|
||
|
||
* **核心框架:** **Python 3.9.6+** & **FastAPI** (高性能异步框架)
|
||
* **WSGI/ASGI:** Uvicorn
|
||
* **ORM:** SQLAlchemy (配合 Pydantic 做数据校验)
|
||
* **认证:** PyJWT (OAuth2 with Password Bearer)
|
||
* **文件操作:** `aiofiles` (异步文件读写) + `shutil`
|
||
|
||
### 2.3 数据存储 (Storage)
|
||
|
||
* **结构化数据:** MySQL 5.7.5 (存储用户、项目元数据、权限关系)
|
||
* **非结构化数据:** 本地文件系统 (Local File System)
|
||
|
||
---
|
||
|
||
## 3. 系统架构设计
|
||
|
||
### 3.1 逻辑架构图
|
||
|
||
```mermaid
|
||
graph TD
|
||
Client[前端 React App] --> |JSON/HTTP| API[FastAPI 后端服务]
|
||
|
||
subgraph "后端核心层"
|
||
API --> |Auth & Meta| DB[(MySQL 数据库)]
|
||
API --> |IO Stream| FS[文件管理服务]
|
||
end
|
||
|
||
subgraph "存储层"
|
||
DB -- 存储关系/权限 --> Metadata[元数据表]
|
||
FS -- 读写 MD/图片 --> Disk[服务器磁盘 /data/projects/]
|
||
end
|
||
|
||
```
|
||
|
||
### 3.2 目录存储结构 (物理设计)
|
||
|
||
为了避免中文乱码和项目重名问题,**磁盘文件夹名使用 UUID,数据库存储映射关系**。
|
||
|
||
**根目录:** `/data/nex_docus_store/`
|
||
|
||
```text
|
||
/data/nex_docus_store/
|
||
├── projects/
|
||
│ ├── 550e8400-e29b-41d4-a716-446655440000/ <-- Project A (UUID)
|
||
│ │ ├── README.md <-- 首页文档
|
||
│ │ ├── _assets/ <-- 附件资源目录
|
||
│ │ │ ├── logo.png
|
||
│ │ │ └── demo.mp4
|
||
│ │ ├── 01-产品设计/ <-- 普通目录
|
||
│ │ │ ├── 需求文档.md
|
||
│ │ │ └── 原型图.md
|
||
│ │ └── 02-技术方案/
|
||
│ │ └── 架构.md
|
||
│ └── 7f8c... (Project B UUID)/
|
||
└── temp/ <-- 临时上传缓存
|
||
|
||
```
|
||
|
||
---
|
||
|
||
## 4. 数据库设计 (主要表结构)
|
||
|
||
|
||
### 4.1 用户表 (`users`)
|
||
|
||
```sql
|
||
CREATE TABLE `users` (
|
||
`id` BIGINT AUTO_INCREMENT PRIMARY KEY,
|
||
`username` VARCHAR(50) NOT NULL UNIQUE,
|
||
`password_hash` VARCHAR(128) NOT NULL,
|
||
`nickname` VARCHAR(50),
|
||
`avatar` VARCHAR(255),
|
||
`status` TINYINT DEFAULT 1
|
||
);
|
||
|
||
```
|
||
|
||
### 4.2 项目表 (`projects`)
|
||
|
||
```sql
|
||
CREATE TABLE `projects` (
|
||
`id` BIGINT AUTO_INCREMENT PRIMARY KEY,
|
||
`name` VARCHAR(100) NOT NULL COMMENT '项目展示名称',
|
||
`description` VARCHAR(255),
|
||
`storage_key` CHAR(36) NOT NULL COMMENT '磁盘文件夹UUID名称',
|
||
`owner_id` BIGINT NOT NULL,
|
||
`is_public` TINYINT DEFAULT 0,
|
||
`created_at` DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||
UNIQUE KEY `uk_storage` (`storage_key`)
|
||
);
|
||
|
||
```
|
||
|
||
### 4.3 项目成员表 (`project_members`)
|
||
|
||
```sql
|
||
CREATE TABLE `project_members` (
|
||
`id` BIGINT AUTO_INCREMENT PRIMARY KEY,
|
||
`project_id` BIGINT NOT NULL,
|
||
`user_id` BIGINT NOT NULL,
|
||
`role` ENUM('admin', 'editor', 'viewer') DEFAULT 'viewer',
|
||
UNIQUE KEY `uk_member` (`project_id`, `user_id`)
|
||
);
|
||
|
||
```
|
||
|
||
---
|
||
|
||
## 5. API 接口设计 (FastAPI)
|
||
|
||
所有接口前缀: `/api/v1`
|
||
|
||
### 5.1 项目管理 (Project)
|
||
|
||
* `GET /projects`: 获取我的项目列表(包括创建的和协作的)。
|
||
* `POST /projects`: 创建新项目。
|
||
* *逻辑:* 1. DB插入记录; 2. 生成UUID; 3. `os.makedirs` 创建物理文件夹; 4. 创建默认 `README.md`。
|
||
|
||
|
||
* `POST /projects/{id}/members`: 邀请成员。
|
||
|
||
### 5.2 文件系统操作 (File System) - **核心**
|
||
|
||
* **获取目录树**
|
||
* `GET /projects/{id}/tree`
|
||
* *逻辑:* 递归遍历 `storage_key` 对应的目录,忽略 `.` 开头文件,返回 AntD Tree 格式的 JSON。
|
||
|
||
|
||
* **获取文件内容**
|
||
* `GET /projects/{id}/file`
|
||
* *Query Param:* `?path=01-产品设计/需求文档.md`
|
||
* *逻辑:* 读取文件文本内容返回。
|
||
|
||
|
||
* **保存文件**
|
||
* `POST /projects/{id}/file`
|
||
* *Body:* `{ "path": "...", "content": "..." }`
|
||
* *逻辑:* 覆盖写入。如果路径中的文件夹不存在,自动创建。
|
||
|
||
|
||
* **新建/重命名/删除**
|
||
* `POST /projects/{id}/file/operate`
|
||
* *Body:* `{ "action": "rename|delete|create_dir", "path": "...", "new_path": "..." }`
|
||
|
||
|
||
|
||
### 5.3 资源服务 (Assets)
|
||
|
||
* `POST /projects/{id}/upload`: 上传图片 -> 存入 `_assets` -> 返回相对路径。
|
||
* `GET /projects/{id}/assets/{filename}`: 流式返回图片数据 (StreamResponse)。
|
||
* *注意:* 必须鉴权,防止直接通过 URL 盗链访问私有项目图片。
|
||
|
||
|
||
|
||
---
|
||
|
||
## 6. 关键模块实现逻辑 (Python 代码示意)
|
||
|
||
### 6.1 路径安全检查 (Security Util)
|
||
|
||
这是文件存储系统最重要的部分,防止 `../../etc/passwd` 攻击。
|
||
|
||
```python
|
||
import os
|
||
from fastapi import HTTPException
|
||
|
||
BASE_STORE_PATH = "/data/nex_docus_store/projects"
|
||
|
||
def get_secure_path(project_uuid: str, relative_path: str):
|
||
# 1. 构建项目根目录绝对路径
|
||
project_root = os.path.abspath(os.path.join(BASE_STORE_PATH, project_uuid))
|
||
|
||
# 2. 构建目标文件绝对路径
|
||
target_path = os.path.abspath(os.path.join(project_root, relative_path))
|
||
|
||
# 3. 核心校验: 目标路径必须以项目根目录开头
|
||
if not target_path.startswith(project_root):
|
||
raise HTTPException(status_code=403, detail="非法路径访问")
|
||
|
||
return target_path
|
||
|
||
```
|
||
|
||
### 6.2 目录树生成 (Tree Generator)
|
||
|
||
```python
|
||
import os
|
||
|
||
def generate_tree(path, relative_root=""):
|
||
tree = []
|
||
# 按名称排序,文件夹在前
|
||
items = sorted(os.listdir(path), key=lambda x: (not os.path.isdir(os.path.join(path, x)), x))
|
||
|
||
for item in items:
|
||
if item.startswith('.'): continue # 跳过隐藏文件
|
||
|
||
full_path = os.path.join(path, item)
|
||
rel_path = os.path.join(relative_root, item)
|
||
|
||
node = {
|
||
"title": item,
|
||
"key": rel_path, # 前端用这个路径请求文件
|
||
}
|
||
|
||
if os.path.isdir(full_path):
|
||
node["isLeaf"] = False
|
||
node["children"] = generate_tree(full_path, rel_path)
|
||
else:
|
||
node["isLeaf"] = True
|
||
|
||
tree.append(node)
|
||
return tree
|
||
|
||
```
|
||
|
||
---
|
||
|
||
## 7. 前端实现细节 (React)
|
||
|
||
### 7.1 编辑器组件
|
||
|
||
建议封装一个 `FileEditor` 组件。
|
||
|
||
```jsx
|
||
// 伪代码
|
||
const FileEditor = ({ projectId, filePath }) => {
|
||
const [content, setContent] = useState('');
|
||
|
||
// 1. 加载文件
|
||
useEffect(() => {
|
||
if(!filePath) return;
|
||
fetch(`/api/v1/projects/${projectId}/file?path=${filePath}`)
|
||
.then(res => res.text())
|
||
.then(text => setContent(text));
|
||
}, [filePath]);
|
||
|
||
// 2. 自动保存/快捷键保存
|
||
const handleSave = () => {
|
||
post(`/api/v1/projects/${projectId}/file`, { path: filePath, content });
|
||
};
|
||
|
||
return (
|
||
<div className="h-full flex flex-col">
|
||
<Toolbar onSave={handleSave} />
|
||
<MarkdownEditor
|
||
value={content}
|
||
onChange={setContent}
|
||
imageUploadHandler={async (file) => {
|
||
// 调用后端上传接口,返回 markdown 图片语法
|
||
// 
|
||
}}
|
||
/>
|
||
</div>
|
||
);
|
||
}
|
||
|
||
```
|
||
|
||
---
|
||
|
||
## 8. 开发路线图 (Roadmap)
|
||
|
||
### 第一阶段:MVP (最小可行性产品)
|
||
|
||
1. **后端:** 搭建 FastAPI 基础框架,连接 MySQL。
|
||
2. **后端:** 实现 `StorageManager`,跑通文件读写和路径安全校验。
|
||
3. **API:** 实现“项目列表”、“目录树获取”、“读取文档”三个接口。
|
||
4. **前端:** 首页展示项目列表 -> 点击进入文档页 -> 左侧树展示目录 -> 右侧展示 Markdown。
|
||
5. **目标:** 能看、能跑通流程,暂不支持在线编辑。
|
||
|
||
### 第二阶段:编辑与协作
|
||
|
||
1. **API:** 实现文件保存、新建文件/文件夹接口。
|
||
2. **前端:** 集成编辑器,实现 Cmd+S 保存。
|
||
3. **功能:** 图片上传接口实现。
|
||
|
||
### 第三阶段:权限与优化
|
||
|
||
1. **后端:** 完善 `Dependency` 注入,实现严格的 API 权限校验。
|
||
2. **前端:** 添加“成员管理”模态框。
|
||
3. **系统:** 增加 Nginx 缓存策略,提升静态资源加载速度。
|
||
|
||
---
|
||
|
||
## 9. 部署架构建议
|
||
|
||
```text
|
||
Server (Linux)
|
||
├── Nginx (80/443)
|
||
│ ├── /api/ --> Proxy Pass to FastAPI (Port 8000)
|
||
│ └── / --> Static Files (React Build)
|
||
├── Docker Container: FastAPI App
|
||
└── Docker Container: MySQL 5.7
|
||
|
||
```
|
||
|
||
---
|