Files
gongxue-base/docs/refactor/object-storage-production-runbook.md
2026-07-01 06:09:23 +08:00

8.8 KiB
Raw Blame History

对象存储生产验收 Runbook

更新时间2026-06-30

本系统的题图、PDF、视频、音频、资料包、题库导出文件都必须进入 content_assets 台账,并由 apps/api 做权限判断、短期签名、水印和审计。前端不得直接持有云厂商密钥、service role key、私有 bucket 路径或长期私有资源 URL。

官方能力依据

  • 阿里云 OSS 官方文档说明可由服务端生成预签名 URL客户端用该 URL 上传或下载对象,并通过有效期限制访问时间。阿里云也建议在更复杂的大文件场景中评估 STS 授权直传。参考:OSS 预签名 URL 上传OSS 预签名 URL 下载/预览
  • 腾讯云 COS 官方文档说明预签名 URL 可用于临时上传/下载私有对象URL 中携带签名和有效期;官方也建议签名有效期设置为完成本次操作所需的最短期限。参考:COS 预签名 URL 访问COS 预签名授权上传
  • Supabase Storage 官方文档说明 createSignedUrl 可以按秒设置下载 URL 有效期,访问私有对象需要相应 select 权限;本项目由后端 service key 集中签名,前端只拿短期 URL。参考Supabase Storage signed URLSupabase Storage downloads

生产阻断项

上线前必须满足:

  • NODE_ENV=production 下 API 和 worker 都不能使用 STORAGE_DEFAULT_PROVIDER=local_dev
  • STORAGE_DEFAULT_BUCKET 必须配置。
  • STORAGE_REQUIRE_TENANT_PREFIX=true 必须保持开启。
  • 阿里云 OSS 必须配置 ALIYUN_OSS_REGION、官方 HTTPS aliyuncs.com ALIYUN_OSS_ENDPOINT,以及 AccessKey生产禁止 ALIYUN_OSS_INTERNAL=true 用于面向用户的签名 URL。
  • 腾讯云 COS 必须配置 TENCENT_COS_REGIONTENCENT_COS_APP_IDTENCENT_COS_SECRET_IDTENCENT_COS_SECRET_KEY
  • Supabase Storage 必须配置 SUPABASE_STORAGE_URLSUPABASE_STORAGE_SERVICE_KEY
  • WORKER_ASSET_SECURITY_SCANNER 生产必须包含 http,例如 metadata_rules,http
  • WORKER_ASSET_SECURITY_SCAN_HTTP_ENDPOINT 必须是 HTTPS 生产地址。
  • WORKER_ASSET_SECURITY_SCAN_HTTP_TOKEN 必须是强随机密钥。
  • WORKER_ASSET_SECURITY_SCAN_FAIL_OPEN=false,外部扫描服务不可用时必须 fail-closed。

对应自动化命令:

npm run test:readiness
npm run readiness:production
npm run readiness:production:db

其中 test:readiness 会同时验证 production readiness 脚本和 API/worker 的生产配置 fail-fast。

推荐环境变量模板

阿里云 OSS

NODE_ENV=production
STORAGE_DEFAULT_PROVIDER=aliyun_oss
STORAGE_DEFAULT_BUCKET=tiku-assets-prod
STORAGE_REQUIRE_TENANT_PREFIX=true
ALIYUN_OSS_REGION=cn-hangzhou
ALIYUN_OSS_ENDPOINT=https://oss-cn-hangzhou.aliyuncs.com
ALIYUN_OSS_ACCESS_KEY_ID=...
ALIYUN_OSS_ACCESS_KEY_SECRET=...
WORKER_ASSET_SECURITY_SCANNER=metadata_rules,http
WORKER_ASSET_SECURITY_SCAN_HTTP_ENDPOINT=https://scanner.example.com/api/scan
WORKER_ASSET_SECURITY_SCAN_HTTP_TOKEN=...
WORKER_ASSET_SECURITY_SCAN_FAIL_OPEN=false

腾讯云 COS

NODE_ENV=production
STORAGE_DEFAULT_PROVIDER=tencent_cos
STORAGE_DEFAULT_BUCKET=tiku-assets-prod
STORAGE_REQUIRE_TENANT_PREFIX=true
TENCENT_COS_REGION=ap-shanghai
TENCENT_COS_APP_ID=...
TENCENT_COS_SECRET_ID=...
TENCENT_COS_SECRET_KEY=...
WORKER_ASSET_SECURITY_SCANNER=metadata_rules,http
WORKER_ASSET_SECURITY_SCAN_HTTP_ENDPOINT=https://scanner.example.com/api/scan
WORKER_ASSET_SECURITY_SCAN_HTTP_TOKEN=...
WORKER_ASSET_SECURITY_SCAN_FAIL_OPEN=false

Supabase Storage

NODE_ENV=production
STORAGE_DEFAULT_PROVIDER=supabase_storage
STORAGE_DEFAULT_BUCKET=tiku-assets-prod
STORAGE_REQUIRE_TENANT_PREFIX=true
SUPABASE_STORAGE_URL=https://<project-ref>.supabase.co/storage/v1
SUPABASE_STORAGE_SERVICE_KEY=...
WORKER_ASSET_SECURITY_SCANNER=metadata_rules,http
WORKER_ASSET_SECURITY_SCAN_HTTP_ENDPOINT=https://scanner.example.com/api/scan
WORKER_ASSET_SECURITY_SCAN_HTTP_TOKEN=...
WORKER_ASSET_SECURITY_SCAN_FAIL_OPEN=false

云侧配置验收

以下配置无法仅靠仓库代码证明,必须在云控制台或云 API 上确认:

项目 验收标准
Bucket 权限 默认私有;公开 bucket 只允许存放确认为公开的品牌素材
CORS 只允许学生端 H5、租户后台 H5、平台后台 H5 域名和微信小程序合法域名;允许必要方法 GET/HEAD/PUT 和必要 headers
RAM/CAM 权限 服务端密钥最小权限,仅允许目标 bucket 的指定前缀读写和 HEAD不要使用主账号密钥
租户前缀 对象 key 必须以 {tenantId}/ 开头bucket policy 不应允许跨前缀写入
CDN/防盗链 私有资料、SVIP 资料、视频、资料包禁止普通长效 CDN URL如使用 CDN必须由 provider 侧签名或回源鉴权
生命周期 临时上传、导出中间文件、失败扫描资源设置清理规则;正式资料按业务保留周期设置归档或低频
版本控制/备份 生产 bucket 开启版本控制、跨区域复制或定时备份,满足误删和容灾要求
日志审计 开启对象访问日志或云审计,至少保留一个完整售后周期
内容安全 外部 scanner 真实接入,并覆盖 PDF、图片、压缩包、视频封面或转码前文件

联调步骤

  1. 启动 API 和 worker确保不是 local_dev provider。
npm run build:api
npm run build:worker
npm run readiness:production
npm run readiness:production:db
  1. 租户后台申请上传签名:
POST /api/tenant-content/assets/sign-upload

验收点:

  • 返回 URL 为目标云厂商域名或 Supabase Storage 域名。
  • headers 包含上传所需 content-type
  • objectKey 带当前 tenantId/ 前缀。
  • content_asset_access_events 写入 upload_sign
  1. 前端直传云存储后调用确认上传:
POST /api/tenant-content/assets/confirm-upload

验收点:

  • 后端读取云对象元数据。
  • 大小、MIME、checksum 能对上。
  • 状态变为 uploadStatus=verifiedsecurityScanStatus=pending
  • 即使传 publish=true,也不能绕过扫描直接发布。
  1. 运行 assets worker
npm --workspace @tiku-saas/worker run assets:once

验收点:

  • 外部 scanner 收到请求。
  • scanner token 未进入前端响应。
  • 扫描通过后 securityScanStatus=passed
  • 扫描失败、超时或服务不可用时资源退回 draft,并记录 content_asset_security_scan_events
  1. 后台发布资源:
PUT /api/tenant-content/assets

验收点:

  • 只有 verified + passed 的托管对象能发布为 active
  • failed/skipped/pending 资源发布被拒绝。
  1. 学生端预览/下载:
GET /api/catalog/assets/preview?assetId=...
GET /api/catalog/assets/download?assetId=...

验收点:

  • 未登录、无权益、跨租户、扫描未通过都被拒绝。
  • 私有/SVIP/视频/资料包 TTL 不超过 300 秒。
  • 响应包含 watermark.mode=visible_overlaytraceId
  • content_asset_access_events 记录 granted/denied、TTL、signatureMode、watermark traceId。
  1. 题库导出 worker
npm --workspace @tiku-saas/worker run exports:once

验收点:

  • PDF/Word/每日一练 ZIP 写入对象存储。
  • 自动创建 content_assets
  • securityScanProvider=trusted_export_worker
  • 后台通过 sign-downloadsign-preview 取短签名 URL。

抽样清单

每次生产联调至少抽样:

  • PDF 10 个,含大文件、中文文件名、水印预览。
  • 图片 20 张,含题图、手册图、品牌图。
  • 视频 10 个,含有会员限制和播放次数限制的题目视频。
  • ZIP/资料包 5 个,含每日一练导出包。
  • 扫描失败样本 3 个,确认不能发布、不能下载。
  • 跨租户访问 5 组,确认无法签名下载或预览。
  • 私有 CDN URL 样本 3 个,未标记 provider-managed 时必须拒绝。

准出标准

满足以下条件后,才允许把资料、题图、视频迁到生产对象存储:

  • npm run test:readiness 通过。
  • npm run readiness:production 无 blocker。
  • npm run readiness:production:db 无 blocker。
  • npm run test:worker:assets 通过。
  • API/worker 在 NODE_ENV=production 下无法用 local_dev 启动。
  • 真实云存储上传、确认、扫描、发布、预览、下载、导出全链路通过。
  • 云侧 CORS、防盗链、生命周期、备份/版本控制、访问日志有截图或变更记录。