feat: media 域最小闭环——存储适配层 + MinIO 预签名两步上传(ADR-016/017,T3-03)

- ObjectStorage 适配层(供应商唯一缝)+ S3ObjectStorage(AWS SDK v2,
  path-style,内网/公网双端点分离签发);endpoint 未配置时服务可启动、
  media 端点 500(沿 JWT 公钥先例)
- POST /api/v1/media/uploads:白名单校验(purpose/mime/byteSize 全配置项)
  → uploading 行(objectKey 服务端生成不含用户输入)→ 预签名 PUT 凭据
  (requiredHeaders 定型仅 Content-Type,TTL 10m)
- complete:HEAD 校验存在性/大小/类型 → uploading→ready(guarded UPDATE
  并发幂等);对象缺失保持 uploading 可重试,内容不符置 failed 终态;
  防枚举 40405;新错误码 40405/42205
- 读取侧预签名 GET(TTL 1h),桶保持私有
- compose 纳入 minio 第六容器(镜像与 Testcontainer 同 tag,桶初始化走
  应用启动 ensureBucket,三环境零分叉);init-secrets.sh 幂等追加 MinIO
  根凭证到 .env(凭证零入库,ADR-021)
- MediaUploadIntegrationTest 12 例:MinIO Testcontainer 全链路直传取回 +
  六类失败路径 + 库层约束一致性;uploading 超时清理只记方案(报告 13 §6)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
2026-09-08 17:11:44 +08:00
parent 8330885b06
commit 10a43f876c
17 changed files with 1248 additions and 0 deletions
+32
View File
@@ -25,6 +25,27 @@ services:
retries: 30
# 数据库不对宿主机发布端口;调试需要时可临时加 ports: ["15432:5432"]
# M3ADR-016):自托管 MinIO 对象存储。镜像 tag 与集成测试的 MinIO
# Testcontainer 钉同一版本(三环境零分叉);对象数据落 volume(ADR-007
# 应用容器保持无状态)。桶初始化由 user 服务启动时执行(ensureBucket
# 本地/compose/CI 同一条路径),无需 mc 初始化容器。9000 端口必须对客户端
# 可达:预签名直传/读取 URL 都直接指向 MinIO,不经应用服务器。
minio:
image: minio/minio:RELEASE.2025-04-22T22-12-26Z
command: server /data
environment:
MINIO_ROOT_USER: ${PATBOND_MINIO_ROOT_USER:?先运行 deploy/init-secrets.sh 生成 .env}
MINIO_ROOT_PASSWORD: ${PATBOND_MINIO_ROOT_PASSWORD:?先运行 deploy/init-secrets.sh 生成 .env}
volumes:
- minio-data:/data
healthcheck:
test: ["CMD-SHELL", "curl -sf http://127.0.0.1:9000/minio/health/live"]
interval: 2s
timeout: 3s
retries: 30
ports:
- "${PATBOND_MINIO_PORT:-9000}:9000"
user:
build: ./patbond-user
environment:
@@ -34,6 +55,14 @@ services:
PATBOND_DB_PASSWORD: ${PATBOND_DB_PASSWORD:?先运行 deploy/init-secrets.sh 生成 .env}
PATBOND_INTERNAL_TOKEN: ${PATBOND_INTERNAL_TOKEN:?先运行 deploy/init-secrets.sh 生成 .env}
PATBOND_JWT_PUBLIC_KEY: /run/patbond/keys/jwt-public.pem
# ADR-016/017media 上传流程在 user 服务。服务自身走内网端点;
# 预签名 URL 按 PATBOND_MINIO_PUBLIC_ENDPOINT 签发(默认本机回环,
# 真机联调/生产改为客户端可达地址)。
PATBOND_MINIO_ENDPOINT: http://minio:9000
PATBOND_MINIO_PUBLIC_ENDPOINT: ${PATBOND_MINIO_PUBLIC_ENDPOINT:-http://127.0.0.1:9000}
PATBOND_MINIO_ACCESS_KEY: ${PATBOND_MINIO_ROOT_USER:?先运行 deploy/init-secrets.sh 生成 .env}
PATBOND_MINIO_SECRET_KEY: ${PATBOND_MINIO_ROOT_PASSWORD:?先运行 deploy/init-secrets.sh 生成 .env}
PATBOND_MINIO_BUCKET: ${PATBOND_MINIO_BUCKET:-patbond-media}
volumes:
- ./patbond-user/src/main/resources/application.yml.sample:/config/application.yml:ro
- ./deploy/keys:/run/patbond/keys:ro
@@ -44,6 +73,8 @@ services:
depends_on:
postgres:
condition: service_healthy
minio:
condition: service_healthy
auth:
build: ./patbond-auth
@@ -107,3 +138,4 @@ services:
volumes:
pgdata:
minio-data: