148 lines
5.7 KiB
Markdown
148 lines
5.7 KiB
Markdown
# **OSS 存储架构规划与最佳实践 (V5.0)**
|
||
|
||
**文档状态:** 已发布 (v5.0 \- 极简生存版)
|
||
|
||
**适用项目:** AIclinicalresearch
|
||
|
||
**核心原则:** **不做过早优化,只做必要隔离**
|
||
|
||
**最后更新:** 2026-01-22
|
||
|
||
## **1\. 架构核心决策 (The MVP Way)**
|
||
|
||
我们摒弃一切复杂的云原生概念,怎么简单怎么来。
|
||
|
||
1. **上传方式**:全线 **后端流式上传** (stream.pipeline)。前端只负责 Form Data 提交。
|
||
2. **权限管理**:SAE 实例绑定 **AliyunOSSFullAccess**。不搞自定义策略。
|
||
3. **成本策略**:**全标准存储**。不搞分层,不搞归档,直到月账单超过 100 元。
|
||
|
||
## **2\. Bucket 物理规划**
|
||
|
||
为了**安全底线**(防止生产数据被误删、防止患者数据裸奔),我们保留 **4 个 Bucket** 的设计。这是唯一的坚持。
|
||
|
||
| 环境 | Bucket 名称 (示例) | 权限 (ACL) | 说明 |
|
||
| :---- | :---- | :---- | :---- |
|
||
| **生产** | **ai-clinical-data** | **私有** | 核心数据。**仅配置跨域(CORS)用于GET**。 |
|
||
| **生产** | **ai-clinical-static** | **公共读** | 头像、Logo、RAG切片图。**配置跨域(CORS)**。 |
|
||
| **开发** | **ai-clinical-data-dev** | **私有** | 开发测试用。 |
|
||
| **开发** | **ai-clinical-static-dev** | **公共读** | 开发测试用。 |
|
||
|
||
**配置项 (仅需配这 2 项)**:
|
||
|
||
1. **CORS**:来源 \*,允许 Methods GET。
|
||
2. **生命周期**:仅针对 temp/ 目录设置 1 天删除。其他不用管。
|
||
|
||
## **3\. 极简目录结构 (Flat & Simple)**
|
||
|
||
基于**全员租户化**的管理逻辑,所有数据根目录统一为 tenants/。
|
||
|
||
后端生成 Key 的逻辑统一为:tenants/{TenantID}/{Scope}/{Module}/{UUID}.{ext}
|
||
|
||
### **3.1 核心数据 Bucket (ai-clinical-data)**
|
||
|
||
\# 1\. 用户私有数据 (User-Level Data)
|
||
\# 逻辑:归属于特定租户下的特定用户
|
||
\# 结构: tenants/{tenantId}/users/{userId}/{module}/{uuid}.pdf
|
||
tenants/t999/users/u123/pkb/a1b2c3d4.pdf \# PKB 文献 (个人)
|
||
tenants/t999/users/u123/asl/e5f6g7h8.pdf \# ASL 文献 (个人)
|
||
tenants/t999/users/u123/rvw/i9j0k1l2.docx \# RVW 稿件 (个人)
|
||
tenants/t999/users/u123/ssa/m3n4o5p6.xlsx \# 统计数据 (个人)
|
||
|
||
\# 2\. 租户共享数据 (Tenant-Level Shared Data)
|
||
\# 逻辑:归属于租户,通常由管理员上传或全员共享
|
||
\# 结构: tenants/{tenantId}/shared/{module}/{uuid}.pdf
|
||
tenants/t999/shared/ekb/q7r8s9t0.pdf \# 租户知识库 (EKB)
|
||
tenants/t999/shared/emr/u1v2w3x4.json \# 原始病历数据 (EMR)
|
||
|
||
\# 3\. 临时中转区 (唯一配置生命周期的目录)
|
||
\# 结构: temp/{date}/{uuid}.xlsx
|
||
temp/20260122/y5z6a7b8.xlsx \# Tool C 上传 / ASL 导入 / 临时导出
|
||
\# (OSS配置: 1天后自动删除)
|
||
|
||
\# 4\. 系统级文件
|
||
system/templates/gcp\_guide.pdf \# 系统模板
|
||
|
||
### **3.2 静态资源 Bucket (ai-clinical-static)**
|
||
|
||
\# 1\. RAG 切片图 (混淆文件名)
|
||
\# 结构: rag/{uuid}.png
|
||
rag/f9e8d7c6b5a4.png
|
||
|
||
\# 2\. 头像
|
||
avatars/u123.png
|
||
|
||
## **4\. 后端实现指南 (Node.js)**
|
||
|
||
### **4.1 环境变量 (SAE)**
|
||
|
||
不需要复杂的 AK/SK 管理,直接用 SAE 的 RAM 角色(需绑定 AliyunOSSFullAccess)。
|
||
|
||
\# 只有这 3 个变量是必须的
|
||
OSS\_REGION=oss-cn-beijing
|
||
OSS\_ENDPOINT=oss-cn-beijing-internal.aliyuncs.com \# 内网地址,省流量费
|
||
OSS\_BUCKET\_DATA=ai-clinical-data
|
||
|
||
### **4.2 核心代码 (Common Service)**
|
||
|
||
不要过度封装,一个简单的 StorageService 足矣。
|
||
|
||
// common/storage/storage.service.ts
|
||
import OSS from 'ali-oss';
|
||
import { pipeline } from 'stream/promises';
|
||
|
||
// 使用 STS 或 SAE 自动注入的凭证,或者直接配 AK/SK (MVP阶段最简单)
|
||
const client \= new OSS({
|
||
region: process.env.OSS\_REGION,
|
||
accessKeyId: process.env.OSS\_ACCESS\_KEY\_ID, // 简单起见,MVP先用 AK
|
||
accessKeySecret: process.env.OSS\_ACCESS\_KEY\_SECRET,
|
||
bucket: process.env.OSS\_BUCKET\_DATA,
|
||
internal: true, // 开启内网模式
|
||
});
|
||
|
||
export const StorageService \= {
|
||
// 1\. 流式上传 (核心方法)
|
||
async uploadStream(key: string, stream: any) {
|
||
// usePutObject 接口支持 stream
|
||
return await client.putStream(key, stream);
|
||
},
|
||
|
||
// 2\. 获取临时链接 (私有文件预览)
|
||
getSignedUrl(key: string) {
|
||
return client.signatureUrl(key, { expires: 3600 });
|
||
},
|
||
|
||
// 3\. 删除文件
|
||
async delete(key: string) {
|
||
return await client.delete(key);
|
||
}
|
||
};
|
||
|
||
### **4.3 业务调用范例**
|
||
|
||
// Tool C 上传接口
|
||
fastify.post('/upload', async (req, reply) \=\> {
|
||
const data \= await req.file();
|
||
|
||
// 极简 Key 生成
|
||
const key \= \`temp/${Date.now()}/${data.filename}\`;
|
||
|
||
// 一行代码上传,无需 try-catch 复杂逻辑 (全局 Error Handler 会捕获)
|
||
await StorageService.uploadStream(key, data.file);
|
||
|
||
return { url: key }; // 返回 Key 即可,前端不需要知道真实 URL
|
||
});
|
||
|
||
## **5\. 2人团队的“生死红线”**
|
||
|
||
在极简模式下,只有这 3 条规则必须死守,其他都可以妥协:
|
||
|
||
1. **内网连接**:SAE 必须配 \-internal 的 Endpoint。一旦配成公网,上传大文件会卡死且扣费。
|
||
2. **私有存储**:ai-clinical-data 必须是 **Private**。这是医疗底线,不能为了省代码而裸奔。
|
||
3. **流式处理**:代码里必须用 pipe / putStream。严禁 await file.toBuffer(),否则 2 人的小服务器一定会内存溢出崩掉。
|
||
|
||
## **6\. 成本预警 (Cost Watch)**
|
||
|
||
MVP 阶段不配置复杂的归档策略,但需关注一条警戒线:
|
||
|
||
* **警戒线**:当 OSS 月账单超过 **100 元** (约 500GB 存储) 时。
|
||
* **行动**:此时再去研究“生命周期管理”和“归档存储”。在此之前,专注于业务代码。 |