1. 问题现象与背景分析
那天我正在将若依框架部署到生产环境,Nginx作为反向代理服务器配置完成后,发现了一个奇怪的问题:Druid监控页面的CSS和JS文件全部报404错误,而其他静态资源却能正常加载。这直接导致Druid监控页面样式错乱,功能无法使用。
若依框架(RuoYi)是一个基于Spring Boot的快速开发平台,内置了Druid数据库连接池的监控功能。Druid的监控页面依赖静态资源(如druid.js、druid.css等)来实现界面展示和交互功能。正常情况下,这些资源应该通过/druid/路径下的URL访问。
在生产环境中,我们通常会用Nginx作为反向代理,将请求转发到后端的Tomcat或Spring Boot应用。我的Nginx配置中使用了正则表达式location来匹配不同的路径规则,正是这个配置导致了静态资源404的问题。
2. Nginx配置原貌与问题定位
先来看出问题的Nginx配置片段:
server { listen 80; server_name example.com; location ~ ^/api/ { proxy_pass http://backend; proxy_set_header Host $host; } location ~ ^/druid/ { proxy_pass http://backend; proxy_set_header Host $host; } location / { root /var/www/html; try_files $uri $uri/ /index.html; } }这个配置看起来没什么问题,但实际上会导致Druid的静态资源加载失败。原因在于:
- Druid的监控页面会加载类似
/druid/js/druid.js这样的静态资源 - Nginx的正则location匹配
^/druid/后,会将请求代理到后端应用 - 但后端Spring Boot应用默认不会处理这些静态资源的请求
关键点:Druid的静态资源实际上位于Druid的jar包中,需要通过Spring Boot的静态资源处理机制来访问。当Nginx直接代理这些请求时,Spring Boot没有机会处理这些资源请求。
3. 问题根因深度解析
要彻底理解这个问题,我们需要了解几个关键机制:
3.1 Spring Boot静态资源处理机制
Spring Boot对于静态资源的处理有明确的优先级顺序:
- 首先查找
/META-INF/resources/目录 - 然后查找
/resources/目录 - 接着查找
/static/目录 - 最后查找
/public/目录
Druid的静态资源被打包在druid-xxx.jar的/META-INF/resources/目录下。当请求到达Spring Boot应用时,DispatcherServlet会先尝试匹配Controller,如果没有匹配的Controller,就会交给静态资源处理器来处理。
3.2 Nginx正则location的匹配行为
Nginx的location匹配有以下几种形式:
= /path精确匹配^~ /path前缀匹配~ pattern正则匹配(区分大小写)~* pattern正则匹配(不区分大小写)
在我的配置中使用了~ ^/druid/这种正则匹配,它会拦截所有以/druid/开头的请求,包括静态资源请求。
3.3 反向代理与静态资源服务的冲突
问题的本质在于:
- Druid的静态资源需要通过Spring Boot的静态资源处理器来访问
- Nginx的正则location拦截了这些请求并直接代理到后端
- 后端没有对应的Controller来处理这些请求
- 结果就是404错误
4. 解决方案与配置优化
经过上述分析,我们有以下几种解决方案:
4.1 方案一:精确匹配Druid的路径
修改Nginx配置,只代理特定的Druid路径:
location = /druid { proxy_pass http://backend; proxy_set_header Host $host; } location = /druid/ { proxy_pass http://backend; proxy_set_header Host $host; } location /druid/ { root /var/www/html; try_files $uri $uri/ =404; }这个方案的优点是:
- 精确控制哪些路径走代理
- 静态资源由Nginx直接处理
缺点是:
- 需要明确知道哪些路径需要代理
- 配置相对复杂
4.2 方案二:排除静态资源路径
在正则location中排除静态资源路径:
location ~ ^/druid/(?!js/|css/|img/).* { proxy_pass http://backend; proxy_set_header Host $host; } location /druid/ { proxy_pass http://backend; proxy_set_header Host $host; }这个方案使用负向先行断言(?!...)来排除静态资源路径。
4.3 方案三:最佳实践配置
结合前两种方案的优点,推荐以下配置:
location = /druid { return 302 /druid/; } location = /druid/ { proxy_pass http://backend/druid/; proxy_set_header Host $host; } location = /druid/login.html { proxy_pass http://backend/druid/login.html; proxy_set_header Host $host; } location ~ ^/druid/(.*\.(js|css|png|jpg|gif|ico))$ { alias /path/to/spring/boot/static/resources/; expires 30d; } location ~ ^/druid/ { proxy_pass http://backend/druid/; proxy_set_header Host $host; }这个配置实现了:
/druid重定向到/druid/- 静态资源由Nginx直接服务
- 其他Druid路径走代理
5. 实施步骤与验证
5.1 实施步骤
- 备份当前Nginx配置
- 根据选择的方案修改Nginx配置
- 重新加载Nginx配置
sudo nginx -t && sudo nginx -s reload - 清理浏览器缓存
- 访问Druid监控页面验证
5.2 验证方法
检查静态资源是否加载成功
- 浏览器开发者工具查看Network标签
- 确认js/css文件返回200状态码
检查代理功能是否正常
- 登录Druid监控页面
- 验证数据统计功能是否正常
性能测试
- 使用ab或wrk测试静态资源访问性能
- 对比优化前后的响应时间
6. 进阶优化与注意事项
6.1 静态资源缓存优化
对于Druid的静态资源,可以设置长期缓存:
location ~ ^/druid/(.*\.(js|css|png|jpg|gif|ico))$ { alias /path/to/resources/$1; expires 365d; add_header Cache-Control "public"; }6.2 安全加固建议
Druid监控页面包含敏感信息,应该做安全加固:
修改默认路径:
# application.properties druid.stat-view-servlet.url-pattern=/custom-monitor/*添加访问控制:
location ~ ^/druid/ { auth_basic "Restricted"; auth_basic_user_file /etc/nginx/.htpasswd; proxy_pass http://backend/druid/; }IP白名单限制:
location ~ ^/druid/ { allow 192.168.1.0/24; deny all; proxy_pass http://backend/druid/; }
6.3 性能监控
配置Nginx日志记录Druid访问情况:
log_format druid_log '$remote_addr - $remote_user [$time_local] ' '"$request" $status $body_bytes_sent ' '"$http_referer" "$http_user_agent" ' '$request_time $upstream_response_time'; access_log /var/log/nginx/druid_access.log druid_log;7. 常见问题排查指南
7.1 静态资源仍然404
可能原因:
路径配置错误
- 检查alias或root指向的实际路径
- 确认文件权限
Spring Boot资源处理被禁用
- 检查
spring.web.resources.add-mappings配置
- 检查
排查命令:
# 检查文件是否存在 ls -l /path/to/resources/druid/js/druid.js # 检查Nginx错误日志 tail -f /var/log/nginx/error.log7.2 代理后会话丢失
现象:登录后跳转又回到登录页面
解决方案:
location ~ ^/druid/ { proxy_pass http://backend/druid/; 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_cookie_path /druid/ /; }7.3 性能问题
如果Druid页面加载慢:
- 检查静态资源是否启用了gzip
gzip on; gzip_types text/css application/javascript; - 考虑使用CDN分发静态资源
- 调整Druid的监控数据采集间隔
druid.stat.sql.SlowSqlMillis=3000 druid.stat.logSlowSql=true
8. 经验总结与最佳实践
经过这次踩坑,我总结了以下几点经验:
谨慎使用正则location:正则表达式虽然强大,但容易产生意料之外的行为。在代理配置中,尽量使用精确匹配或前缀匹配。
理解各组件的工作机制:Nginx和Spring Boot对静态资源的处理方式不同,只有理解它们各自的工作原理,才能正确配置。
静态资源与API请求分离:最好将静态资源和API请求分开处理,静态资源由Nginx直接服务,API请求走代理。
测试要全面:修改Nginx配置后,不仅要测试主页面,还要检查所有依赖的静态资源。
监控与日志:为关键路径配置专门的日志格式,便于问题排查。
对于若依框架的部署,我现在的推荐做法是:
# 静态资源 location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff|woff2|ttf|eot)$ { root /path/to/static; expires 30d; access_log off; } # Druid特殊处理 location = /druid { return 302 /druid/; } location = /druid/ { proxy_pass http://backend/druid/; } location ~ ^/druid/(.*\.(js|css|png))$ { alias /path/to/spring/boot/resources/$1; expires 365d; } location ~ ^/druid/ { proxy_pass http://backend/druid/; } # API代理 location /api/ { proxy_pass http://backend/api/; } # 前端路由 location / { root /path/to/frontend; try_files $uri $uri/ /index.html; }这种配置结构清晰,职责分明,能够避免大多数代理和静态资源相关的问题。