# ═══════════════════════════════════════════════════════════ # 深学在线 · 学生端 H5 站点(本地开发 / 开发的"生产形态") # # 它做两件事,**而且必须在同一个 server 上做**: # ① 把 H5 构建产物(frontend-mp/dist/build/h5)当静态站发出去; # ② 把 /api 反代到后端 8080。 # # ⚠️ 为什么"同源"是硬要求,不是审美: # · 微信网页授权的「授权回调页面域名」**只能配一个**,而回调落在 # /api/v1/mp/h5/callback;学生授权后被 302 回页面,页面必须与回调**同域** # (两个源就要配两个域名,微信不给配)。 # · H5 的接口地址取自 `location.origin`(见 frontend-mp/src/config/index.ts), # 跨源时它会把请求打到 nginx 自己身上,表现是"页面能开、接口全 404"。 # · 同源还顺带省掉 CORS。 # # 用法: # cd frontend-mp && pnpm build:h5 # 先出产物(产物不入库) # cd deploy && docker compose up -d nginx # → http://localhost/ H5 # → http://localhost/api/... 后端 8080 # # ⚠️ 内网穿透要指到**本服务的端口**(默认 80),不是后端的 8080 —— # 指到 8080 的表现是"接口好用、页面 404"。 # ═══════════════════════════════════════════════════════════ server { listen 80; server_name _; root /usr/share/nginx/html; index index.html; # ⚠️⚠️ 这一条必须有。nginx 默认只收 1MB,而学生从聊天记录里选一张照片就可能超过; # 超了 nginx 直接回 413 —— 而**后端日志里一个字都没有**, # 排查时会一路怀疑"请求根本没发出来"。 # 后端自己的上限是 50MB/60MB(application.yml 的 spring.servlet.multipart), # 这里对齐到 50m 即可,网关不该比应用更严。 client_max_body_size 50m; # 上传与导出都是长连接(导出打包是长事务,见 application-prod.yml 的说明), # 别用默认的 60s 把还在跑的请求掐掉 proxy_connect_timeout 10s; proxy_send_timeout 300s; proxy_read_timeout 300s; # H5 的包有好几 MB,穿透/真机下压缩收益很直接 gzip on; gzip_min_length 1024; gzip_types text/css application/javascript application/json image/svg+xml; # ── /api:反代到后端 ──────────────────────────────────── # # ⚠️ 后端是**宿主机上的 jar**,不在这个 compose 里,所以用 host.docker.internal # (Linux 上靠 compose 里的 extra_hosts: host-gateway 才有这个名字)。 # # ⚠️ 这里**故意不写** Cache-Control:后端的 ApiNoStoreFilter 已经发了 no-store。 # 再加一条会变成两个 Cache-Control 头 —— 虽然合法,但"这个头谁负责"就有了两个来源。 location /api/ { proxy_pass http://host.docker.internal:8080; proxy_http_version 1.1; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; # 上传是流式的,别在 nginx 侧攒完再转发(大文件时表现为"进度条卡住不动") proxy_request_buffering off; } # ── 带内容哈希的资源:可以按"永久"缓存 ────────────────── # 文件名里带内容哈希,内容变了名字就变 —— 这是唯一值得缓存的东西 # (这些包加起来好几 MB,学生不该每次发版都重下) # # ⚠️ 不加 `always`:那会让 404 也带上 immutable,浏览器就把"文件不存在"缓存一年 location /assets/ { add_header Cache-Control "public, max-age=31536000, immutable"; } # ── 其余:HTML 与没有内容哈希的静态文件(uni-app 的 /static/**)── # # ⚠️ 这两行不能省:`no-cache` ≠ 不缓存,而是"用之前先问一下" —— # 配合 Last-Modified 走 304,只花一个往返、不重传正文。 # 没有显式过期时间时浏览器会**启发式缓存**(RFC 9111 §4.2.2:按 # Last-Modified 年龄的 10% 自行推断),后果是发版后学生继续打开旧页面 # (旧 index.html 里写死了旧 chunk 名),且只在"打开过页面的设备"上复现。 # 这个坑的完整来龙去脉见 backend/xt-common/.../ApiNoStoreFilter.java 的类注释。 # # ⚠️ 这里**故意不做** history 回退(try_files ... /index.html): # uni-app H5 是 hash 路由(#/pages/...),服务端只需要提供 / 与静态文件。 # 加了回退反而有害:缺失的 chunk 会返回 200 + HTML, # 浏览器报的是"Unexpected token '<'",而真正的原因只是文件不存在。 location / { add_header Cache-Control "no-cache"; try_files $uri $uri/ =404; } }