Files
lovife 2ff17cd0c9 refactor(h5)!: H5 改由 nginx 托管,后端退回纯 API
用户决定:后端不再对外发静态文件,学生端 H5 交给网关。两者**仍然同源**
(微信只给一个授权回调域名;H5 的接口地址取自 location.origin),
所以前端业务代码、微信后台、application-dev.yml 的 h5-base-url 都不用改。

后端
- 删掉 dev/test/prod 三份 profile 里的 spring.web.resources.static-locations
- 主配置加 spring.web.resources.add-mappings=false —— 用开关而不是"指到空目录":
  后者会连带启用 Spring Boot 的默认位置(classpath:/static 等),谁丢个文件进去就悄悄对外可见
- StaticCacheHeaderFilter → ApiNoStoreFilter,只剩 /api/** 的 no-store
  (静态资源的缓存策略整体归 nginx,避免"改一处漏一处")
- 补 NoHandlerFoundException → 404:关掉静态映射后未匹配路径不再经过
  ResourceHttpRequestHandler,抛的是 NoHandlerFoundException,只处理
  NoResourceFoundException 时访问 / 会变成 500「系统繁忙」并刷 ERROR 日志

deploy
- 新增 xt-nginx(80):发 frontend-mp/dist/build/h5 + /api 反代 8080(host.docker.internal,
  Linux 靠 extra_hosts host-gateway),端口与产物目录走 .env 的 H5_PORT/H5_DIR
- 三条"删了就会出事"的都已带上理由:client_max_body_size 50m(默认 1m,超了回 413
  而后端日志一个字都没有)、/assets 的 immutable 与 *.html 的 no-cache(§一·十 那个
  "发版后学生看到旧页面"的坑)、健康检查写 127.0.0.1(容器里 localhost 先解析到 IPv6,
  nginx 只听 IPv4 → 永远 starting 而首页是好的)

CI 与文档
- CI:学生端 H5 构建从"e2e 的输入"改为编译检查,不再留 artifact(e2e 不消费它)
- 同步 AGENTS.md / README.md / docs/09(启动步骤、CI 表、发布注意、排障表)/ docs/11
- 交接摘要新增 §一·四十三(含理由、验证数字、要人工做的一步与环境限制)

验证
- mvn clean package 通过(必须 clean:删掉的旧类会留在 target/classes 被组件扫描)
- 后端直连:/ 与 /assets/* → 404({"code":40001,"msg":"接口不存在"})、
  /api/.../captcha → 200 + no-store、/doc.html → 302→200、/v3/api-docs 与 /actuator/health → 200
- 经 nginx(80):首页 200 + no-cache、/assets/*.js 200 + immutable、/nope.js 404(无回退)、
  /api 200 + no-store、gzip 生效;2MB multipart 上传 → 401(与直连一致 ⇒ 未踩 1m 的坑)、
  55MB → 413
- e2e(XT_BASE=http://localhost,全程走 nginx):e2e-m0.py 37/37、e2e-m3-h5-auth.py 24/24、
  e2e-m2-scope.py 38/38
- verify-all.py --quick 6/6;check-test-profile.py:test profile 仍能起来
2026-10-19 17:35:33 +08:00

111 lines
4.5 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.
# 深学在线 · 本地开发基础设施
#
# 用法:
# cd deploy
# copy .env.example .env (首次)
# docker compose up -d
#
# ⚠️ nginx 挂的是 H5 **构建产物**,所以第一次起它之前要先:
# cd frontend-mp && pnpm build:h5
# (产物不入库;目录不存在时 docker 会建一个空目录 → 页面 404,容器却一切正常)
#
# ⚠️ 容器名统一加 xt- 前缀,端口刻意避开 55432 / 6379
# (本机已有其他项目的 pg-verify 与 redis-local 在跑,不要动它们)
#
# 对象存储:用**七牛云**,这里不起容器。
# 材料原图与规范件存七牛(空间 tools-matrix,私有 → 下载走签名链接),
# 由后端用官方 SDK 直连,不走 S3 协议。
# 曾经起过一个 MinIO 备用("本地/生产走同一套 S3 接口"),但代码最终走的是七牛 SDK,
# 那个容器从来没被连过 —— 留着只会让人以为"本地还有个对象存储要用",所以删掉了。
# 本地开发也不需要它:provider=local 时文件落在 backend/data/uploads。
name: xiaotutongxue
services:
# ── PostgreSQL 16 ─────────────────────────────────
pg:
image: postgres:16-alpine
container_name: xt-pg
restart: unless-stopped
environment:
POSTGRES_DB: ${PG_DB:-filldb}
POSTGRES_USER: ${PG_USER:-fill}
POSTGRES_PASSWORD: ${PG_PASSWORD:-fill_dev_pwd}
# 中文排序与大小写:用 C 排序 + UTF8,避免 locale 差异导致索引行为不一致
POSTGRES_INITDB_ARGS: "--encoding=UTF8 --locale=C"
TZ: Asia/Shanghai
ports:
- "${PG_PORT:-5432}:5432"
volumes:
- pg-data:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U ${PG_USER:-fill} -d ${PG_DB:-filldb}"]
interval: 5s
timeout: 3s
retries: 12
start_period: 10s
command:
- postgres
# 超大图处理是内存密集的,DB 侧保留默认即可,但把慢查询记下来
- -c
- log_min_duration_statement=1000
- -c
- timezone=Asia/Shanghai
# ── Redis 7 ───────────────────────────────────────
redis:
image: redis:7-alpine
container_name: xt-redis
restart: unless-stopped
ports:
- "${REDIS_PORT:-16379}:6379"
volumes:
- redis-data:/data
command:
- redis-server
- --appendonly
- "yes"
- --requirepass
- "${REDIS_PASSWORD:-redis_dev_pwd}"
healthcheck:
test: ["CMD-SHELL", "redis-cli -a ${REDIS_PASSWORD:-redis_dev_pwd} ping | grep PONG"]
interval: 5s
timeout: 3s
retries: 12
start_period: 5s
# ── nginx:学生端 H5 站点 + /api 反代 ──────────────
# 2026-10 起 H5 **不再由后端托管**(原来是 spring.web.resources.static-locations),
# 改由本服务对外提供,后端退回纯 API。两者仍然**同源**,理由见 nginx/xt.conf 头部。
nginx:
image: nginx:1.27-alpine
container_name: xt-nginx
restart: unless-stopped
ports:
- "${H5_PORT:-80}:80"
volumes:
# H5 构建产物(**产物不入库**):先在 frontend-mp 里跑 `pnpm build:h5`。
# ⚠️ 目录不存在时 docker 会**建一个空目录**,于是页面 404 而容器一切正常
# (`docker compose ps` 是 healthy、nginx 日志里也没有错)—— 先 build 再起。
- ${H5_DIR:-../frontend-mp/dist/build/h5}:/usr/share/nginx/html:ro
- ./nginx/xt.conf:/etc/nginx/conf.d/default.conf:ro
extra_hosts:
# 后端是**宿主机上的 jar**,不在这个 compose 里。
# Docker Desktop 自带 host.docker.internal;Linux 上必须有下面这行才有这个名字
- "host.docker.internal:host-gateway"
healthcheck:
# 探的是"首页拿不拿得到",而不是"进程在不在" ——
# 挂进来的产物目录是空的时候,前者才有意义。
# ⚠️ 必须写 **127.0.0.1**,不能写 localhost:容器里 localhost 会先解析到 IPv6
# ::1,而 nginx 的 `listen 80` 只监听 IPv4 → wget 报 Connection refused、
# 健康检查永远停在 unhealthy/starting(首页从宿主机看却是好的,极易误判)。
test: ["CMD-SHELL", "wget -q --spider http://127.0.0.1/ || exit 1"]
interval: 10s
timeout: 3s
retries: 6
start_period: 5s
volumes:
pg-data:
redis-data: