# 前端静态资源 + 反向代理。移植自旧后端 deploy/caddy/Caddyfile, # 上游从「同容器里的 gunicorn」换成「api 容器」,其余策略照搬。 { admin off servers { # TLS 由前面的 Nginx Proxy Manager 终止,转发进来是明文 http。 # 只信任内网来源的 X-Forwarded-For,{client_ip} 才解析得出学生的真实 IP。 trusted_proxies static private_ranges } } # 站点地址只写端口,Caddy 就不会去申请证书(auto-HTTPS 自动关闭)。 :8000 { encode gzip # 和 NPM 那一层的 client_max_body_size 保持一致,上传测试用例压缩包用得上。 # 必须写 MiB:Caddy 的 MB 按 10^6 算,200MB 比 nginx 的 200M 小了将近 10M。 request_body { max_size 200MiB } # Caddy 的 header 会跨层叠加,不像 nginx 的 add_header 一旦在子块出现就不再继承, # 所以这里写一次就够,下面各 handle 里只管自己的 Cache-Control。 header { X-XSS-Protection "1; mode=block" X-Frame-Options SAMEORIGIN X-Content-Type-Options nosniff } log { output file /data/log/caddy_access.log { roll_size 10MiB roll_keep 10 } } # 判题机心跳每秒一次;静态资源量大且带永久缓存。两者都不记访问日志。 # 路径跟着新后端改了:judge_server_heartbeat → judge-server/heartbeat @nolog path /api/judge-server/heartbeat /api/judge-server/heartbeat/ /assets/* log_skip @nolog handle /api/* { reverse_proxy oj-api:3000 { header_up X-Real-IP {client_ip} # AI 分析走 SSE。X-Accel-Buffering 是 nginx 专有的,Caddy 不认, # 这里显式关掉响应缓冲,保证流式输出逐块下发。 flush_interval -1 } } handle /ws/* { reverse_proxy oj-api:3000 { header_up X-Real-IP {client_ip} } } # 头像和题面图片。旧配置是 Caddy 直接读 /data 下的盘,这里改成反代给后端: # 后端本来就要处理头像缺省回退,开发环境(Vite 代理)也走同一条路, # 少一处「只在生产才生效」的分支。文件不大、量也不大,多一跳无所谓。 handle /public/* { reverse_proxy oj-api:3000 } # 构建产物文件名带内容 hash,内容一变文件名就变,可以永久缓存。 # 命中后浏览器直接读磁盘,不再发条件请求。 handle /assets/* { root * /srv header Cache-Control "public, max-age=31536000, immutable" file_server } # index.html 引用着带 hash 的文件名,必须每次回源校验, # 否则发新版后学生刷不到。no-cache 是「缓存但每次校验」,命中走 304。 handle { root * /srv header Cache-Control "no-cache" try_files {path} /index.html file_server } }