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