Files
AIclinicalresearch/docs/01-平台基础层/02-存储服务/OSS存储架构规划与最佳实践 V5.md

5.7 KiB
Raw Permalink Blame History

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 存储) 时。
  • 行动:此时再去研究“生命周期管理”和“归档存储”。在此之前,专注于业务代码。