解决 Spring Boot 应用中静态资源 404 问题的完整指南
在部署 Spring Boot 应用时,经常遇到静态资源(如图片、CSS、JavaScript 文件)无法访问的问题,浏览器返回 404 错误。本文将详细介绍如何诊断和解决这类问题,通过一个真实案例展示完整的排查和修复过程。
问题场景
最近在部署一个二手交易平台项目时遇到了这样的问题:
- 后端是基于 Spring Boot 开发的应用
- 前端通过 NGINX 反向代理访问后端 API
- 用户访问图片资源时返回 404 错误
- 控制台显示路径为:
/mnt/projeckDisk/projeck/template/tab41_mall/src/main/resources/static/img/
访问地址类似:
1
| http://服务器ip:8214/api/img/bd743b44-725a-4e66-89cb-2ba0fc38cdf6.jpg
|
初步分析
首先,我们需要理解项目的结构和静态资源配置:
项目结构
1 2 3 4 5 6 7 8 9
| /mnt/projeckDisk/projeck/template/tab41_mall/ ├── src/ │ └── main/ │ └── resources/ │ └── static/ │ └── img/ ├── dist/ ├── SecondStore-0.0.1-SNAPSHOT.jar └── ...
|
Spring Boot 静态资源配置
在 Spring Boot 中,静态资源默认由ResourceHttpRequestHandler处理。通过继承WebMvcConfigurationSupport类并重写addResourceHandlers方法,我们可以自定义静态资源的映射规则。
原始配置代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| @Configuration public class MyInterceptorConfig extends WebMvcConfigurationSupport {
@Override public void addResourceHandlers(ResourceHandlerRegistry registry) { String imagePath = System.getProperty("user.dir") + "/" + Util.IMAGE_PATH; imagePath = imagePath.replace("\\", "/"); System.out.println("addResourceHandlers=======>" + imagePath); registry.addResourceHandler("/img/**").addResourceLocations("file:/" + imagePath); registry.addResourceHandler("/api/img/**").addResourceLocations("file:/" + imagePath); } }
|
其中Util.IMAGE_PATH常量定义为:
1
| public final static String IMAGE_PATH = "src/main/resources/static/img/";
|
问题诊断
通过查看应用日志,发现以下错误信息:
1
| java.net.UnknownHostException: mnt
|
这个错误表明 Spring 在解析资源路径时,错误地将路径中的mnt部分识别为主机名,试图建立网络连接而不是读取本地文件。
问题原因
问题的根本原因是路径构造不当。在原始代码中:
System.getProperty("user.dir")返回的是/mnt/projeckDisk/projeck/template/tab41_mall
- 加上
Util.IMAGE_PATH后得到/mnt/projeckDisk/projeck/template/tab41_mall/src/main/resources/static/img/
- 最终构造的路径为
file:///mnt/projeckDisk/projeck/template/tab41_mall/src/main/resources/static/img/
虽然看起来正确,但在某些环境或 Spring 版本中,这种路径构造方式可能导致解析错误。
解决方案
为了解决这个问题,我们采用了更加规范和安全的路径构造方式:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
| package com.example.SecondStore.config;
import com.example.SecondStore.util.Util; import org.springframework.context.annotation.Configuration; import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry; import org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport;
import java.io.File; import java.nio.file.Paths;
@Configuration public class MyInterceptorConfig extends WebMvcConfigurationSupport {
@Override public void addResourceHandlers(ResourceHandlerRegistry registry) { String imagePath = Paths.get(System.getProperty("user.dir"), Util.IMAGE_PATH).toString(); String imageLocation = "file:///" + imagePath + "/"; imageLocation = imageLocation.replace("\\", "/"); System.out.println("addResourceHandlers=======>" + imagePath); registry.addResourceHandler("/img/**").addResourceLocations(imageLocation); registry.addResourceHandler("/api/img/**").addResourceLocations(imageLocation); } }
|
关键改进点
使用Paths.get()方法:这个方法能够正确处理不同操作系统的路径分隔符,提高代码的跨平台兼容性。
明确的协议前缀:使用file:///确保 Spring 将其识别为本地文件系统路径而不是网络资源。
路径结尾斜杠:确保路径以/结尾,避免路径拼接问题。
保持转义逻辑:继续保留将反斜杠替换为正斜杠的逻辑,确保在 Windows 系统中的兼容性。
NGINX 配置优化
除了修复 Spring Boot 应用中的路径问题外,还需要确保 NGINX 正确地将请求代理到应用:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41
| server { listen 8214; server_name localhost 服务器ip;
location /api/img/ { proxy_pass http://服务器ip:8314/img/; proxy_set_header Host $http_host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; add_header Access-Control-Allow-Origin *; add_header Access-Control-Allow-Methods 'GET, POST, OPTIONS, PUT, DELETE'; add_header Access-Control-Allow-Headers 'DNT,X-Mx-ReqToken,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Authorization'; }
location /api { rewrite ^.+api/?(.*)$ /$1 break; include uwsgi_params; proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection "upgrade"; proxy_read_timeout 18000s; proxy_send_timeout 30s; proxy_pass http://服务器ip:8314; client_max_body_size 1024M; proxy_set_header Host $http_host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; add_header Access-Control-Allow-Origin *; add_header Access-Control-Allow-Methods 'GET, POST, OPTIONS, PUT, DELETE'; add_header Access-Control-Allow-Headers 'DNT,X-Mx-ReqToken,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Authorization'; }
location / { root /mnt/projeckDisk/projeck/template/tab41_mall/dist; index index.html index.htm; try_files $uri $uri/ /index.html; } }
|
部署步骤
重新构建应用:
上传新的 JAR 文件到服务器
确保目录结构存在:
1
| mkdir -p src/main/resources/static/img
|
重启应用服务
重新加载 NGINX 配置:
1 2
| sudo nginx -t sudo nginx -s reload
|
总结
通过本次问题解决,我们学到了几个重要经验:
路径处理要规范:使用标准库函数(如Paths.get())处理文件路径,避免手动拼接字符串。
协议前缀要明确:对于文件系统路径,明确使用file:///协议前缀。
日志是最好的调试工具:通过详细的日志输出,能够快速定位问题根源。
跨平台兼容性很重要:特别是在 Linux 和 Windows 之间切换部署时,要注意路径分隔符的差异。
NGINX 配置要精细:针对不同的资源类型配置专门的 location 块,能够更好地控制请求路由。
通过上述修改,我们的 Spring Boot 应用现在能够正确处理静态资源请求,用户可以正常访问图片等文件了。