Files
danyang-match/infrastructure/docker-compose.prod.yml
T
WorkBuddy dc0a62d4c5 fix(storage): presigned URL 改用对外可达 host,修复客户端直传断链
SeaweedFS 换存储后 MINIO_ENDPOINT 变为 seaweedfs:8333(容器名),
PhotoService 生成的 presigned URL 把该 host 直接下发给客户端,
客户端不在 docker 网络内无法解析 → 直传/直链全断。

- S3Config:新增 amazonS3Signer Bean,指向 object-storage.public-endpoint
  (缺省回退内网 endpoint,向后兼容)。独立签名端是因为 SigV4 签名覆盖
  host 头,事后改 URL 字符串会让签名失效返 403;独立客户端让签名 host
  与客户端实际命中的 host 天然一致。
- PhotoService:去掉 @RequiredArgsConstructor,改显式 @Qualifier 构造器;
  getUploadToken / getSignedUrl / getSignedUrlForAuthorizedReview 三处
  presigned 生成改用 amazonS3Signer;putObject 服务端上传仍走内网 amazonS3。
- application-docker.yml / application-dev.yml:public-endpoint 读
  MINIO_PUBLIC_ENDPOINT(缺省回退 MINIO_ENDPOINT)。
- 三套 compose:注入 MINIO_PUBLIC_ENDPOINT
  (dev localhost:8333 / staging localhost:8334 / prod 留作单一旋钮)。
- 测试:PhotoServiceTest 新增嵌套 PresignedUrlPublicHostTest,用真实
  AmazonS3 客户端(仅本地签名不联网)断言下发 URL host 为对外地址、
  不含 seaweedfs 容器名;同步补齐两处 5→6 参构造调用。

验证:本机 65 E2E 全绿 + storage_link_probe path-style 全通 +
PhotoServiceTest/PhotoUploadValidationTest/PhotoLimitGuardTest 19 例单测全绿。

顺带:backend/maven-settings.xml 的 Central 兜底由投毒的 repo1.maven.org
改为可达的 repo.maven.apache.org,使本机镜像构建可走 aliyun 主源。
2026-09-22 17:10:40 +08:00

204 lines
7.4 KiB
YAML
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Production Docker Compose
# 生产环境配置,敏感信息通过 .env 文件注入,不进 Git
services:
postgres:
image: postgres:16-alpine
container_name: danyang-match-postgres
environment:
POSTGRES_DB: ${DB_NAME:-danyang_match}
POSTGRES_USER: ${DB_USER:-danyang}
POSTGRES_PASSWORD_FILE: /run/secrets/db_password
ports:
- "127.0.0.1:5432:5432"
volumes:
- postgres_data:/var/lib/postgresql/data
- ./backups:/backups
healthcheck:
test: ["CMD-SHELL", "pg_isready -U ${DB_USER:-danyang} -d ${DB_NAME:-danyang_match}"]
interval: 10s
timeout: 5s
retries: 5
networks:
- danyang-net
secrets:
- db_password
restart: unless-stopped
redis:
image: redis:7-alpine
container_name: danyang-match-redis
command: redis-server --appendonly yes --requirepass ${REDIS_PASSWORD:?请设置REDIS_PASSWORD}
volumes:
- redis_data:/data
healthcheck:
test: ["CMD", "redis-cli", "-a", "${REDIS_PASSWORD:?请设置REDIS_PASSWORD}", "ping"]
interval: 10s
timeout: 5s
retries: 5
networks:
- danyang-net
restart: unless-stopped
seaweedfs:
# 同 docker-compose.yml:对象存储已从 MinIO 换成 SeaweedFS。
# 生产换的动力比 CI 更强 —— MinIO 仓库已归档、不再产出安全补丁,
# 而这里存的是身份证与实名材料。详见 docker-compose.yml 中的完整说明。
# 版本与本地/CI 一致(钉 4.47),避免各环境跑不同实现。
image: chrislusf/seaweedfs:4.47
container_name: danyang-match-seaweedfs
# 必须覆盖 entrypoint:镜像 ENTRYPOINT 是 /usr/bin/weed,只写 command 会被追加成
# `weed sh -c ...` → `weed: unknown subcommand "sh"`(本地实测踩到)。
entrypoint: ["sh", "-c"]
command:
- |
cat > /tmp/s3cfg.json <<CFG
{"identities":[{"name":"app","credentials":[{"accessKey":"$${MINIO_ACCESS_KEY}","secretKey":"$${MINIO_SECRET_KEY}"}],"actions":["Admin","Read","Write","List","Tagging"]}]}
CFG
# -volume.max=100:bucket 名即 collection,默认上限 8 个 volume 只够一个 bucket
# (第二个桶写入会 InternalError)。详见 docker-compose.yml 中的说明。
exec weed server -s3 -s3.port=8333 -s3.config=/tmp/s3cfg.json -dir=/data -volume.max=100
environment:
MINIO_ACCESS_KEY: ${MINIO_ACCESS_KEY:?请设置MINIO_ACCESS_KEY}
MINIO_SECRET_KEY: ${MINIO_SECRET_KEY:?请设置MINIO_SECRET_KEY}
volumes:
- seaweedfs_data:/data
networks:
- danyang-net
healthcheck:
# 与本地一致:鉴权开启后匿名列桶返回 403,故 200/403 都算健康。
test: ["CMD-SHELL", "code=$$(curl -s -o /dev/null -w '%{http_code}' http://127.0.0.1:8333/); [ \"$$code\" = 200 ] || [ \"$$code\" = 403 ]"]
interval: 5s
timeout: 5s
retries: 24
restart: unless-stopped
storage-init:
# SeaweedFS 不会自动建桶,PutObject 到不存在的 bucket 直接失败。
# 生产同样必须预建 public / private,否则照片与实名材料全部上传不了。
# 走 filer 目录接口,无需签名,复用同一镜像的 curl。幂等:201 新建 / 409 已存在。
image: chrislusf/seaweedfs:4.47
container_name: danyang-match-storage-init
depends_on:
seaweedfs:
condition: service_healthy
entrypoint:
- sh
- -c
- |
for b in public private; do
code=$$(curl -s -o /dev/null -w '%{http_code}' "http://seaweedfs:8888/buckets/$$b/" || echo 000)
if [ "$$code" = "200" ]; then echo "bucket $$b: already exists"; continue; fi
i=0
while [ $$i -lt 30 ]; do
code=$$(curl -s -o /dev/null -w '%{http_code}' -X POST "http://seaweedfs:8888/buckets/$$b/" || echo 000)
case "$$code" in 200|201|409) break ;; esac
i=$$((i+1)); sleep 2
done
echo "bucket $$b: create -> $$code"
done
networks:
- danyang-net
restart: "no"
backend:
build:
context: ../backend
dockerfile: Dockerfile
container_name: danyang-match-backend
depends_on:
postgres:
condition: service_healthy
redis:
condition: service_healthy
seaweedfs:
condition: service_healthy
storage-init:
condition: service_completed_successfully
environment:
SPRING_PROFILES_ACTIVE: docker,production
SPRING_CONFIG_IMPORT: configtree:/run/secrets/
DB_HOST: postgres
DB_PORT: 5432
DB_NAME: ${DB_NAME:-danyang_match}
DB_USER: ${DB_USER:-danyang}
REDIS_HOST: redis
REDIS_PORT: 6379
REDIS_PASSWORD: ${REDIS_PASSWORD:?请设置REDIS_PASSWORD}
# 变量名仍为 MINIO_*(服务端配置键未改),实际指向 SeaweedFS 的 S3 网关。
MINIO_ENDPOINT: http://seaweedfs:8333
# 客户端直传 presigned URL 用的「对外可达」地址。
# 生产 S3 网关当前未 host-publish(客户端经 nginx/ingress 访问),故缺省仍是容器名
# —— 即「presigned host 对外可达」缺陷在生产仍待配置 ingress 后才彻底解决。
# 一旦生产 S3 网关暴露了对外地址,把该变量设成那个地址即可,无需再改代码。
MINIO_PUBLIC_ENDPOINT: ${MINIO_PUBLIC_ENDPOINT:http://seaweedfs:8333}
MINIO_ACCESS_KEY: ${MINIO_ACCESS_KEY:?请设置MINIO_ACCESS_KEY}
MINIO_SECRET_KEY: ${MINIO_SECRET_KEY:?请设置MINIO_SECRET_KEY}
JWT_ACCESS_SECRET: ${JWT_ACCESS_SECRET:?请设置JWT_ACCESS_SECRET}
JWT_REFRESH_SECRET: ${JWT_REFRESH_SECRET:?请设置JWT_REFRESH_SECRET}
CHECKIN_TOKEN_SECRET: ${CHECKIN_TOKEN_SECRET:?请设置CHECKIN_TOKEN_SECRET}
ENCRYPTION_KEY: ${ENCRYPTION_KEY:?请设置ENCRYPTION_KEY}
WECHAT_APP_ID: ${WECHAT_APP_ID:?请设置WECHAT_APP_ID}
WECHAT_APP_SECRET: ${WECHAT_APP_SECRET:?请设置WECHAT_APP_SECRET}
VERIFICATION_PROVIDER: ${VERIFICATION_PROVIDER:?请设置VERIFICATION_PROVIDER}
SPRING_SECURITY_FALLBACK_PASSWORD: ${SPRING_SECURITY_FALLBACK_PASSWORD:?请设置SPRING_SECURITY_FALLBACK_PASSWORD}
FEATURES_APPEAL_ENABLED: ${FEATURES_APPEAL_ENABLED:-false}
ports:
- "127.0.0.1:8080:8080"
networks:
- danyang-net
secrets:
- source: db_password
target: spring.datasource.password
restart: unless-stopped
deploy:
resources:
limits:
memory: 512M
reservations:
memory: 256M
admin-web:
build:
context: ../admin-web
dockerfile: Dockerfile
container_name: danyang-match-admin
depends_on:
- backend
ports:
# 管理端端口统一 13520(与 admin-web/vite.config.ts 的开发端口一致,
# 由 ui-tests/ui-document-baseline-contracts.mjs 钉住三处一致)。
# 仍绑定 127.0.0.1:对外访问走 nginx 的 80/443 + /admin/,此处仅供本机直连排查。
- "127.0.0.1:13520:80"
networks:
- danyang-net
restart: unless-stopped
nginx:
image: nginx:alpine
container_name: danyang-match-nginx
depends_on:
- backend
- admin-web
ports:
- "80:80"
- "443:443"
volumes:
- ./nginx-production.conf:/etc/nginx/nginx.conf:ro
- ./certs:/etc/nginx/certs:ro
networks:
- danyang-net
restart: unless-stopped
volumes:
postgres_data:
redis_data:
seaweedfs_data:
networks:
danyang-net:
driver: bridge
secrets:
db_password:
file: ./secrets/db_password.txt