feat(deploy): D1 后端部署就绪(配置外置 + prod fail-fast + 自包含 Compose + 冒烟/回滚)
为什么:用户下午要部署,但全仓没有任何部署产物;且生产漏配密钥会静默使用代码里的 dev 默认值,
属上线阻塞项(04-部署架构 / 12-上线检查清单)。
- 配置外置(三件):DB_URL、JWT 双密钥(**发现 app-secret 此前没外置,一直用 dev 默认值**)、
MEDIA_SIGNATURE_SECRET、MEDIA_SIGNATURE_REQUIRED、CORS_ALLOWED_ORIGINS、MEDIA_ROOT_DIR
- 新增三份 application-prod.yml:签名强制开启、CORS 收紧、媒资指向挂载点、日志级别
- 新增 ProdSecretGuard(@Profile("prod")):密钥缺失/仍是 dev 默认值/不足 32 位、签名未开、
DB_URL 或 CORS 为空 → 一次列全并终止启动(exit 1)
- 新增 /healthz 轻量健康端点:查明 /actuator/health 的"超时"是旧 jar 未重启所致,项目本就
没有 actuator 依赖,故不引新依赖;同时把 /healthz 加进安全白名单
- Docker 化:多阶段 Dockerfile(非 root、tini、依赖层缓存、ffmpeg 只给 worker)、.dockerignore、
自包含 docker-compose.yml(含 PostgreSQL 18 + Redis + 命名卷 + 按序初始化 sql + healthcheck)
- tools/smoke.sh(失败非 0 退出)+ DEPLOY.md(Compose 主线 / 裸 jar 兜底 / 回滚 / 故障速查)
- 实测:mvn test 83 条全过;两入口 package 通过;prod 缺密钥退出码 1 且报错明确、配齐后正常启动;
冒烟脚本全 PASS;本机无 Docker,镜像与 compose 未经构建验证(已在回执如实标注)
This commit is contained in:
1 parent
9f76302e53
commit
b567365460
16 files changed
+814
-9
No files matched your search
@@ -0,0 +1,147 @@
|
||||
# Author: fruit.com.cn
|
||||
#
|
||||
# 办公室单机部署(docs/04-部署架构.md §三 精简方案 + §五 第一阶段用 Compose):
|
||||
# app-api(8081) + admin-api(8080,只绑本机) + transcode-worker + PostgreSQL 18 + Redis
|
||||
# 转码 worker 与在线服务**同机但独立进程/容器**(§8.5 隔离要求:不抢在线服务资源)。
|
||||
#
|
||||
# 用法(目标主机需要 Docker + Compose):
|
||||
# cp .env.example .env # 填密钥与数据库密码
|
||||
# docker compose up -d --build
|
||||
# tools/smoke.sh
|
||||
#
|
||||
# 本文件在本机**未验证**(本机没有 Docker),见 DEPLOY.md「未在本机验证的部分」。
|
||||
services:
|
||||
|
||||
# ─────────── 基础设施 ───────────
|
||||
db:
|
||||
image: postgres:18-alpine
|
||||
container_name: fruitshorts-db
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
POSTGRES_DB: ${DB_NAME:-fruitshorts}
|
||||
POSTGRES_USER: ${DB_USER:-fruitshorts}
|
||||
POSTGRES_PASSWORD: ${DB_PASSWORD:?数据库密码必填,见 .env.example}
|
||||
# 中文与印尼语内容需要 UTF-8(镜像默认已是 UTF8,这里显式一次)
|
||||
POSTGRES_INITDB_ARGS: "--encoding=UTF8 --locale=C"
|
||||
volumes:
|
||||
- db-data:/var/lib/postgresql/data
|
||||
# 首次启动自动建表 + 造种子数据;顺序即文件名顺序(脚本本身幂等,可重复执行)
|
||||
- ./sql/schema.sql:/docker-entrypoint-initdb.d/01-schema.sql:ro
|
||||
- ./sql/schema-phase1.sql:/docker-entrypoint-initdb.d/02-schema-phase1.sql:ro
|
||||
- ./sql/partition-maintenance.sql:/docker-entrypoint-initdb.d/03-partition-maintenance.sql:ro
|
||||
- ./sql/data.sql:/docker-entrypoint-initdb.d/04-data.sql:ro
|
||||
- ./sql/data-phase1.sql:/docker-entrypoint-initdb.d/05-data-phase1.sql:ro
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "pg_isready -U ${DB_USER:-fruitshorts} -d ${DB_NAME:-fruitshorts}"]
|
||||
interval: 10s
|
||||
timeout: 5s
|
||||
retries: 10
|
||||
start_period: 20s
|
||||
# 不映射端口:只有同网络的容器需要访问;本机要连库时再放开 5432
|
||||
expose:
|
||||
- "5432"
|
||||
|
||||
redis:
|
||||
# 第一阶段还没有代码依赖 Redis(C2 才接缓存与限流),先把基础设施就位,
|
||||
# 免得上线时再改编排。要持久化就留 volume,纯缓存场景可去掉。
|
||||
image: redis:7-alpine
|
||||
container_name: fruitshorts-redis
|
||||
restart: unless-stopped
|
||||
command: ["redis-server", "--appendonly", "yes"]
|
||||
volumes:
|
||||
- redis-data:/data
|
||||
healthcheck:
|
||||
test: ["CMD", "redis-cli", "ping"]
|
||||
interval: 10s
|
||||
timeout: 3s
|
||||
retries: 10
|
||||
expose:
|
||||
- "6379"
|
||||
|
||||
# ─────────── 应用 ───────────
|
||||
app-api:
|
||||
build:
|
||||
context: .
|
||||
args:
|
||||
MODULE: fruitshorts-app-api
|
||||
image: fruitshorts/app-api:latest
|
||||
container_name: fruitshorts-app-api
|
||||
restart: unless-stopped
|
||||
env_file: .env
|
||||
environment:
|
||||
SERVER_PORT: "8081"
|
||||
# 默认连编排里的 db;要连外部库就在 .env 里覆盖 DB_URL
|
||||
DB_URL: ${DB_URL:-jdbc:postgresql://db:5432/${DB_NAME:-fruitshorts}}
|
||||
ports:
|
||||
- "8081:8081"
|
||||
volumes:
|
||||
# 媒资落盘:三个服务共享同一个卷
|
||||
- ./data/uploads:/data/uploads
|
||||
depends_on:
|
||||
db:
|
||||
condition: service_healthy
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "curl -fsS http://127.0.0.1:8081/healthz || exit 1"]
|
||||
interval: 30s
|
||||
timeout: 5s
|
||||
start_period: 40s
|
||||
retries: 3
|
||||
|
||||
admin-api:
|
||||
build:
|
||||
context: .
|
||||
args:
|
||||
MODULE: fruitshorts-admin-api
|
||||
image: fruitshorts/admin-api:latest
|
||||
container_name: fruitshorts-admin-api
|
||||
restart: unless-stopped
|
||||
env_file: .env
|
||||
environment:
|
||||
SERVER_PORT: "8080"
|
||||
DB_URL: ${DB_URL:-jdbc:postgresql://db:5432/${DB_NAME:-fruitshorts}}
|
||||
ports:
|
||||
# docs/12-上线检查清单「管理端仅内网」:只绑本机,公网不直接暴露
|
||||
- "127.0.0.1:8080:8080"
|
||||
volumes:
|
||||
- ./data/uploads:/data/uploads
|
||||
depends_on:
|
||||
db:
|
||||
condition: service_healthy
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "curl -fsS http://127.0.0.1:8080/healthz || exit 1"]
|
||||
interval: 30s
|
||||
timeout: 5s
|
||||
start_period: 40s
|
||||
retries: 3
|
||||
|
||||
transcode-worker:
|
||||
build:
|
||||
context: .
|
||||
args:
|
||||
MODULE: fruitshorts-transcode-worker
|
||||
# worker 镜像里装 ffmpeg,两个 API 镜像不装(省 80MB)
|
||||
INSTALL_FFMPEG: "true"
|
||||
image: fruitshorts/transcode-worker:latest
|
||||
container_name: fruitshorts-transcode-worker
|
||||
restart: unless-stopped
|
||||
env_file: .env
|
||||
environment:
|
||||
DB_URL: ${DB_URL:-jdbc:postgresql://db:5432/${DB_NAME:-fruitshorts}}
|
||||
volumes:
|
||||
# 与两个入口同一个媒资根,否则找不到源文件
|
||||
- ./data/uploads:/data/uploads
|
||||
depends_on:
|
||||
db:
|
||||
condition: service_healthy
|
||||
healthcheck:
|
||||
# worker 不监听端口(server.port=0),用进程存活判断
|
||||
test: ["CMD-SHELL", "pgrep -f app.jar >/dev/null || exit 1"]
|
||||
interval: 60s
|
||||
timeout: 5s
|
||||
start_period: 40s
|
||||
retries: 3
|
||||
|
||||
volumes:
|
||||
# 命名卷:跟着 compose 项目走,`docker compose down -v` 才会删
|
||||
db-data:
|
||||
redis-data:
|
||||
Reference in new issue
Block a user