feat(storage): integrate Alibaba Cloud OSS for file persistence - Add OSSAdapter and LocalAdapter with StorageFactory pattern - Integrate PKB module with OSS upload - Rename difyDocumentId to storageKey - Create 4 OSS buckets and development specification

This commit is contained in:
2026-01-22 22:02:20 +08:00
parent 483c62fb6f
commit 9c96f75c52
309 changed files with 4583 additions and 172 deletions

View File

@@ -0,0 +1,192 @@
# OSS 集成开发记录
> 日期2026-01-22
> 版本v1.0
> 状态MVP 阶段完成
---
## 📋 开发目标
将阿里云 OSS 集成到平台中,替代本地文件存储,为 PKB个人知识库等业务模块提供可靠的云端文件持久化能力。
---
## ✅ 完成的工作
### 1. 基础设施层
#### 1.1 OSS Bucket 创建
| Bucket 名称 | 用途 | 权限 | 加密 |
|-------------|------|------|------|
| `ai-clinical-data` | 生产环境数据存储 | 私有 | SSE-OSS |
| `ai-clinical-data-dev` | 开发环境数据存储 | 私有 | 无 |
| `ai-clinical-static` | 生产环境静态资源 | 公共读 | 无 |
| `ai-clinical-static-dev` | 开发环境静态资源 | 公共读 | 无 |
#### 1.2 RAM 账号配置
- 创建专用 RAM 用户:`aiclinical-oss`
- 配置 AccessKey ID/Secret
- 授权 OSS 操作权限
### 2. 后端存储适配器
#### 2.1 架构设计
```
StorageAdapter (接口)
├── OSSAdapter (阿里云 OSS 实现)
└── LocalAdapter (本地文件系统实现)
StorageFactory (工厂类)
└── getInstance() 根据 STORAGE_TYPE 返回对应适配器
```
#### 2.2 核心文件
| 文件 | 说明 |
|------|------|
| `src/common/storage/StorageAdapter.ts` | 存储适配器接口定义 |
| `src/common/storage/OSSAdapter.ts` | OSS 实现上传、下载、删除、签名URL |
| `src/common/storage/LocalAdapter.ts` | 本地存储实现 |
| `src/common/storage/StorageFactory.ts` | 工厂类,根据环境变量创建实例 |
| `src/common/storage/index.ts` | 统一导出 |
#### 2.3 关键功能
- **上传文件**`storage.upload(key, buffer)`
- **下载文件**`storage.download(key)`
- **删除文件**`storage.delete(key)`
- **获取签名URL**`storage.getSignedUrl(key, expires, originalFilename)`
- **检查文件存在**`storage.exists(key)`
#### 2.4 签名URL与原始文件名
通过 `Content-Disposition` 响应头,让浏览器下载时恢复原始文件名:
```typescript
getSignedUrl(key: string, expires: number = 900, originalFilename?: string): string {
const options: OSS.SignatureUrlOptions = { expires };
if (originalFilename) {
options.response = {
'content-disposition': `attachment; filename="${encodeURIComponent(originalFilename)}"`
};
}
return this.client.signatureUrl(key, options);
}
```
### 3. PKB 模块集成
#### 3.1 目录结构规范
```
tenants/{tenantId}/users/{userId}/pkb/{kbId}/{uuid}.{ext}
```
示例:
```
tenants/yizhengxun/users/user-001/pkb/kb-001/9f206cc1c1ac4478.pdf
```
#### 3.2 代码修改
**documentController.ts**
- 新增 `generatePkbStorageKey()` 函数生成存储路径
- 上传流程:先上传到 OSS再调用 documentService
**documentService.ts**
- `uploadDocument()` 接收 `storageKey` 参数并存储
- `deleteDocument()` 删除 OSS 文件
#### 3.3 数据库字段重命名
| 原字段名 | 新字段名 | 说明 |
|----------|----------|------|
| `difyDocumentId` | `storageKey` | 存储 OSS 路径(数据库列名保持 `dify_document_id` 以避免迁移) |
### 4. 环境变量配置
```bash
# 存储类型oss 或 local
STORAGE_TYPE=oss
# OSS 配置
OSS_REGION=oss-cn-beijing
OSS_BUCKET=ai-clinical-data-dev
OSS_BUCKET_STATIC=ai-clinical-static-dev
OSS_ACCESS_KEY_ID=LTAI5tBHkL39GjdLfcr77Y3f
OSS_ACCESS_KEY_SECRET=********
OSS_INTERNAL=false # 本地开发用公网,生产用内网
```
### 5. 文档产出
| 文档 | 路径 |
|------|------|
| MVP 实施方案 | `docs/01-平台基础层/02-存储服务/OSS存储实施方案-MVP版.md` |
| 开发规范 | `docs/04-开发规范/11-OSS存储开发规范.md` |
| 账号配置信息 | `docs/01-平台基础层/02-存储服务/OSS账号与配置信息.md` |
---
## 🧪 测试结果
### OSS 适配器测试
```
✅ 上传成功
✅ 文件存在检查
✅ 下载成功(内容匹配)
✅ 签名URL生成含原始文件名
✅ 删除成功
```
### PKB 文档上传测试
```
✅ 文件上传到 OSS
✅ 数据库记录 storageKey
⚠️ 删除文档时遇到 pg-boss 队列冲突(与 OSS 无关)
```
---
## 🔧 待解决问题
| 问题 | 优先级 | 说明 |
|------|--------|------|
| pg-boss 队列重复初始化 | 高 | 导致服务启动报错 |
| 删除文档功能验证 | 中 | 需解决 pg-boss 后继续测试 |
| 前端文件预览/下载 | 中 | 需实现签名URL获取接口 |
| Tool C / RVW / ASL 集成 | 低 | 按实施方案逐步推进 |
---
## 📊 技术决策记录
### 决策 1文件命名使用 UUID
- **原因**:防止文件名冲突、避免特殊字符问题、增加安全性
- **方案**:上传时生成 UUID 作为 OSS Key原始文件名存数据库
### 决策 2保留 local 存储模式
- **原因**:支持医疗机构私有化部署(数据不出内网)
- **方案**:通过 `STORAGE_TYPE` 环境变量切换
### 决策 3复用 difyDocumentId 字段
- **原因**Dify 已移除,该字段可复用存储 OSS 路径
- **方案**Prisma 字段重命名为 `storageKey``@map` 保持原列名
### 决策 4文件大小限制 30MB
- **原因**:平衡用户体验和服务器内存安全
- **方案**30MB 以下用 `toBuffer()`,超大文件考虑流式处理
---
## 📅 后续计划
1. **Phase 2.1**:完成 PKB 删除功能验证
2. **Phase 2.2**:实现前端文件预览/下载签名URL
3. **Phase 3**:集成 Tool C、RVW、ASL 模块
4. **Phase 4**:生产环境部署与内网 Endpoint 配置
---
## 👥 参与人员
- 开发AI 助手 + 用户
- 审核:用户