5.7 KiB
OSS 存储架构规划与最佳实践 (V5.0)
文档状态: 已发布 (v5.0 - 极简生存版)
适用项目: AIclinicalresearch
核心原则: 不做过早优化,只做必要隔离
最后更新: 2026-01-22
1. 架构核心决策 (The MVP Way)
我们摒弃一切复杂的云原生概念,怎么简单怎么来。
- 上传方式:全线 后端流式上传 (stream.pipeline)。前端只负责 Form Data 提交。
- 权限管理:SAE 实例绑定 AliyunOSSFullAccess。不搞自定义策略。
- 成本策略:全标准存储。不搞分层,不搞归档,直到月账单超过 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 项):
- CORS:来源 *,允许 Methods GET。
- 生命周期:仅针对 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 条规则必须死守,其他都可以妥协:
- 内网连接:SAE 必须配 -internal 的 Endpoint。一旦配成公网,上传大文件会卡死且扣费。
- 私有存储:ai-clinical-data 必须是 Private。这是医疗底线,不能为了省代码而裸奔。
- 流式处理:代码里必须用 pipe / putStream。严禁 await file.toBuffer(),否则 2 人的小服务器一定会内存溢出崩掉。
6. 成本预警 (Cost Watch)
MVP 阶段不配置复杂的归档策略,但需关注一条警戒线:
- 警戒线:当 OSS 月账单超过 100 元 (约 500GB 存储) 时。
- 行动:此时再去研究“生命周期管理”和“归档存储”。在此之前,专注于业务代码。