生产环境-图片上传404-排障手记
AI-摘要
阿狄 GPT
AI初始化中...
介绍自己
生成本文简介
推荐相关文章
前往主页
前往tianli博客
生产环境图片访问 404 排障手记(Docker + Spring Boot + 1Panel + Nginx)
TL;DR
- 404 的根因不是代码,而是 Docker 挂载目录导致容器内看不到宿主机文件。
- 两种可行方案:
- 方案 A(最终采用):Nginx 直出宿主机目录
/data/uploads。 - 方案 B:Nginx 转发到后端,后端静态资源映射读取容器内
/app/uploads(或挂载后的宿主机目录)。
- 方案 A(最终采用):Nginx 直出宿主机目录
- 关键点:
- 给 Nginx 容器挂载宿主机目录
/data/uploads:/data/uploads:ro。 - 给后端容器明确上传目录(相对路径
./uploads→/app/uploads)。 location匹配优先级与proxy_pass/alias尾斜杠语义。
- 给 Nginx 容器挂载宿主机目录
一、背景
- 访问 URL:
https://example.com/api/uploads/…/xxx.jpg - 目标:尽量只改 Nginx,就能访问到上传的图片。
- 实际:访问长期 404。
二、现象 & 关键日志
-
早期 Nginx 错误日志:
open() "/usr/local/openresty/nginx/html/api/uploads/..." failed (2: No such file or directory)说明命中了其它
location(如location /),没有命中我们为/api/uploads/配的块。 -
调整后错误日志:
open() "/data/uploads/feedback/20251117/xxx.jpg" failed (2: No such file or directory)说明命中了
/api/uploads/的配置,但 Nginx 容器内不存在/data/uploads(没挂载,容器看不到宿主机目录)。 -
后端日志(用于确认后端路径解析):
配置的上传路径: ./uploads 绝对路径: /app/uploads/ 目录是否存在: true 目录是否可读: true说明后端容器工作目录是
/app,相对路径./uploads会解析到/app/uploads。
三、根因
- Nginx 容器内未挂载宿主机目录
/data/uploads,导致直出方案里 Nginx 看不到文件。 - 早期
location优先级不当,或alias/root/rewrite配置不严谨,命中到了默认站点目录。 - 若走后端代理,后端容器未使用正确的上传目录(默认走
/app/uploads),或没有在容器内创建该目录。
四、解决方案
方案 A:Nginx 直出宿主机目录(最终采用)
- 在 1Panel 给 Nginx/OpenResty 容器新增挂载:
- 主机目录:
/data/uploads - 容器目录:
/data/uploads - 权限:只读
- Nginx 配置(必须放在
location /api/和location /之前,并使用^~提升优先级):
location ^~ /api/uploads/ {
alias /data/uploads/; # 末尾斜杠必须有
try_files $uri =404; # 文件不存在立即 404,不回退到其他 location
expires 30d;
add_header Cache-Control "public, immutable";
add_header Access-Control-Allow-Origin *;
}
- 也可用
rewrite + root等价写法(任选一种,勿混用):
location ^~ /api/uploads/ {
rewrite ^/api/uploads/(.*)$ /$1 break; # 去掉前缀
root /data/uploads; # 结果路径:/data/uploads/<相对路径>
try_files $uri =404;
expires 30d;
add_header Cache-Control "public, immutable";
add_header Access-Control-Allow-Origin *;
}
- 重载并验证:
nginx -t && nginx -s reload
# 验证容器可见性(在 Nginx 容器内)
docker exec <nginx容器名> ls -lh /data/uploads/feedback/20251116/
# 访问验证(先用确认存在的老文件)
curl -I "https://example.com/api/uploads/feedback/20251116/xxxx.jpg"
适用场景:不经后端,降低后端负载,直接由前端网关分发静态文件。
方案 B:代理到后端静态资源(后端映射到容器内目录或宿主机)
- Nginx 将
/api/uploads/转发为后端的/uploads/:
location ^~ /api/uploads/ {
proxy_pass http://127.0.0.1:8080/uploads/; # 注意最后的斜杠!
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;
proxy_redirect off; proxy_buffering off;
expires 30d; add_header Cache-Control "public, immutable";
}
- 后端 Spring Boot 静态资源映射(兼容
/uploads/**与/api/uploads/**):
WebConfig.addResourceHandlers()增加:registry.addResourceHandler("/uploads/**", "/api/uploads/**").addResourceLocations("file:/app/uploads/")(示例)- 规范化路径:使用
getCanonicalPath()处理..、. - 启用诊断日志,打印“配置的上传路径 / 绝对路径 / 目录是否存在”等信息
- 后端上传目录:
application-prod.ymlfile: upload: path: ./uploads # 相对 /app url-prefix: https://example.com/api/uploads- 在后端容器内创建目录:
mkdir -p /app/uploads
- 验证链路:
- 查看后端日志是否打印“目录存在 true、可读 true”
- 访问
https://example.com/api/uploads/...,命中后端静态资源
适用场景:需要统一由后端鉴权、限流、审计或自定义资源逻辑时。
五、常见坑与避坑要点
location优先级:location ^~ /api/uploads/必须在location /api/和location /之前。alias末尾斜杠:alias /data/uploads/;尾斜杠必须有,否则拼接路径错误。proxy_pass尾斜杠:proxy_pass http://127.0.0.1:8080/uploads/;会把/api/uploads/...改写到/uploads/...;如果少了斜杠会保留原路径。- 容器可见性:直出方案需要把宿主机目录挂载到 Nginx 容器;后端方案需要容器内存在
/app/uploads(或映射到宿主机目录)。 - 默认根目录误命中:未命中专用
location时会落到默认站点根目录(如/usr/local/openresty/nginx/html),导致路径不对。 - 验证顺序:
- 先在容器内
ls确认文件可见。 - 再
nginx -t && nginx -s reload。 - 最后
curl -I直连 URL 验证。
- 先在容器内
六、最终结论
- 本次采用“方案 A:Nginx 直出宿主机目录”。
- 在 1Panel 中为 Nginx 容器新增
/data/uploads:/data/uploads:ro挂载后,配合alias与try_files,访问恢复正常。 - 同时,保留后端双路径映射与日志,便于后续排障与回退。
七、附录:配置片段汇总
- Nginx 直出(最终使用):
location ^~ /api/uploads/ {
alias /data/uploads/;
try_files $uri =404;
expires 30d;
add_header Cache-Control "public, immutable";
add_header Access-Control-Allow-Origin *;
}
- Nginx 转发后端:
location ^~ /api/uploads/ {
proxy_pass http://127.0.0.1:8080/uploads/; # 注意尾斜杠
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;
proxy_redirect off; proxy_buffering off;
expires 30d; add_header Cache-Control "public, immutable";
}
- 后端 application-prod.yml(示例):
file:
upload:
path: ./uploads
url-prefix: https://example.com/api/uploads
- 验证命令:
# 验证容器能否看到文件
docker exec <nginx容器名> ls -lh /data/uploads/feedback/20251116/
# 检查 Nginx 配置 & 重载
nginx -t && nginx -s reload
# 访问验证
curl -I "https://example.com/api/uploads/feedback/20251116/xxxx.jpg"
本文是原创文章,采用 CC BY-NC-ND 4.0 协议,完整转载请注明来自 程序员小刘
评论
匿名评论
隐私政策
你无需删除空行,直接评论以获取最佳展示效果