0%

德鲁伊简单使用

德鲁伊简单使用

​ 在翻阅项目底层代码时发现,在底层的framework模块中有引入druid监控端,但是好像在开发环境、测试环境甚至生产环境中并没有看到有具体的使用,所以想启用一下druid的监控模块。并没有什么实质性内容的介绍,仅仅是踩的一些坑的记录。

注:我使用的版本是1.1.22,如果不是这版本请绕道,避免引起不适,不同版本中修改了部分配置的前缀!!

启用德鲁伊管理页

1.根据配置项加载这个管理页bean。前缀为my.druid.monitor.enable

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
// 创建德鲁伊数据源
@Bean("dataSource")
@ConditionalOnMissingBean(DataSource.class)
@ConfigurationProperties(prefix = "spring.datasource")
public DataSource dataSource() {
return DruidDataSourceBuilder.create().build();
}

@Bean
@ConditionalOnProperty(prefix = "my.druid.monitor", name = "enable", havingValue = "true")
public ServletRegistrationBean<StatViewServlet> druidServlet(
@Value("${spring.datasource.druid.stat-view-servlet.login-password:admin}") String password,
@Value("${spring.datasource.druid.stat-view-servlet.login-username:admin}") String userName,
@Value("${spring.datasource.druid.stat-view-servlet.reset-enable:true}") String reset,
@Value("${spring.datasource.druid.stat-view-servlet.url-pattern:/druid/*}") String urlPattern,
@Value("${spring.datasource.druid.stat-view-servlet.allow:}") String allow,
@Value("${spring.datasource.druid.stat-view-servlet.deny:}") String deny
) {
ServletRegistrationBean<StatViewServlet> reg = new ServletRegistrationBean();
reg.setServlet(new StatViewServlet());
reg.addUrlMappings(urlPattern);
reg.addInitParameter("resetEnable", reset);
reg.addInitParameter("loginUsername", userName);
reg.addInitParameter("loginPassword", password);
reg.addInitParameter("allow",deny );
reg.addInitParameter("deny",allow);
return reg;
}

yaml或者properties配置文件中添加相关配置

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
spring:
# 忽略默认自动注入druid,避免走默认druid配置
autoconfigure:
exclude: com.alibaba.druid.spring.boot.autoconfigure.DruidDataSourceAutoConfigure
#数据库连接池配置
datasource:
druid:
stat-view-servlet:
enabled: true
login-password: admin
login-username: admin
reset-enable: true
url-pattern: /druid/*
allow:
deny:

# 全局druid参数,单独数据源配置为空取全局配置
dynamic:
# 声明默认的主数据源
primary: master
druid:
# 连接池的配置信息
filters: stat,wall
initial-size: 10
max-active: 20
max-pool-prepared-statement-per-connection-size: 20
max-wait: 60000
# 配置一个连接在池中最小生存的时间,单位是毫秒
min-evictable-idle-time-millis: 300000
min-idle: 10
# 打开PSCache,并且指定每个连接上PSCache的大小
pool-prepared-statements: true
test-on-borrow: false
test-on-return: false
test-while-idle: true
# 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒
time-between-eviction-runs-millis: 60000
validation-query: SELECT 1 FROM DUAL
# 通过connectProperties属性来打开mergeSql功能;慢SQL记录
connection-properties: druid.stat.mergeSql\=true;druid.stat.slowSqlMillis\=5000

到此在本地启动项目,访问http://127.0.0.1:8064/xxxx/druid/login.html进入管理页,登录之后,虽然进入了管理页,但是其实只是一个空壳,执行对应的SQL发现,并不会在监控页面上显示出来,后经过查询资料发现,这里的filter为空。

druid中filter为空.png

在配置文件中明明在spring.datasource.druid.filters设置了stat,wall等过滤器,但是实际上没有读到,排查发现,在1.1.22上,并不是这个配置,修改了配置前缀为spring.datasource.druid.filter.stat.enabled

具体代码:

1
2
3
4
5
6
7
8
9
10
11
public class DruidFilterConfiguration {

@Bean
@ConfigurationProperties(FILTER_STAT_PREFIX)
@ConditionalOnProperty(prefix = FILTER_STAT_PREFIX, name = "enabled")
@ConditionalOnMissingBean
public StatFilter statFilter() {
// 主要是为了创建这个filter
return new StatFilter();
}
}

修改配置后,在启动项目,在管理页上就显示出了执行的SQL语句。

开发环境启用

​ 本以为到这里基本上已经万事大吉了,开发环境只需要复刻本地环境的配置即可。但是操作之后发现,在开发环境上访问login.html之后原本应该跳转到druid/index.html,但是404,看样子是nginx拦截处理了。

​ 在nginx中,对于/index.html的路径直接转发到本地的某个文件夹中,所以修改nginx.conf相关配置信息,是的对/druid/index.html不进行这个拦截处理。

1
2
3
4
5
6
7
8
9
10
11
# 修改前
location ~ .*/index.html {
add_header Cache-Control "private, no-store, no-cache, must-revalidate, proxy-revalidate";
root /opt/html;
}

# 修改后
location ~ (?!.*druid/index.html$).*/index.html {
add_header Cache-Control "private, no-store, no-cache, must-revalidate, proxy-revalidate";
root /opt/html;
}

重启nginx之后,发现还是有问题,观察发送的请求,在login.html提交表单之后,发起了一个submitLogin请求,如果用户名密码校验正确,会直接跳转到druid/index.html,但是这个druid/index.html的请求居然进行了302跳转,又跳回druid/login.html页面。通过和本地的工程发起的请求对比发现,在submitLogin登录之后会返回一个JSESSIONIDcookies,发起druid/index.html请求需要携带这个cookies值,但是在开发环境中不知道是nginx还是哪里给弄丢了,由于时间紧迫我没有细看在那个环节丢调了这个cookies值,我手动将这个cookies值打入浏览器的cookies中,正常访问。

关闭广告

​ 在开源项目若依中看到,感觉挺好玩的,便记录一下,用过druid管理页都知道,在面板的最下方有一个阿里的横幅广告,这个广告是请求服务器的common.js中会添加到页脚,请求公网的banner连接。

1
2
3
4
5
6
7
8
9
buildFooter : function() {

var html ='<footer class="footer">'+
' <div class="container">'+
'<a href="https://render.alipay.com/p/s/taobaonpm_click/druid_banner_click" target="new"><img src="https://render.alipay.com/p/s/taobaonpm_click/druid_banner"></a><br/>' +
' powered by <a href="https://github.com/alibaba/" target="_blank">Alibaba</a> & sandzhang & <a href="http://melin.iteye.com/" target="_blank">melin</a> & <a href="https://github.com/shrekwang" target="_blank">shrek.wang</a>'+
' </div>'+
' </footer>';
$(document.body).append(html);

​ 所以思路上,通过对获取common.js进行拦截,并将这段页脚的html代码重写,即可达到关闭广告的目的。

1.设置一个过滤器,对js/common.js的请求进行拦截。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
@Configuration
public class DruidConfig {

/**
* 创建 DruidAdRemoveFilter 过滤器,过滤 common.js 的广告
*/
@Bean
@ConditionalOnProperty(prefix = "my.druid.monitor", name = "enable", havingValue = "true")
public FilterRegistrationBean<DruidAdRemoveFilter> druidAdRemoveFilterFilter(DruidStatProperties properties) {
// 获取 druid web 监控页面的参数
DruidStatProperties.StatViewServlet config = properties.getStatViewServlet();
// 提取 common.js 的配置路径
String pattern = config.getUrlPattern() != null ? config.getUrlPattern() : "/druid/*";
String commonJsPattern = pattern.replaceAll("\\*", "js/common.js");

// 创建 DruidAdRemoveFilter Bean
FilterRegistrationBean<DruidAdRemoveFilter> registrationBean = new FilterRegistrationBean<>();
registrationBean.setFilter(new DruidAdRemoveFilter());
registrationBean.addUrlPatterns(commonJsPattern);
return registrationBean;
}
}

2.拦截过滤器中替换这段html

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class DruidAdRemoveFilter extends OncePerRequestFilter {

/**
* common.js 的路径
*/
private static final String COMMON_JS_ILE_PATH = "support/http/resources/js/common.js";

@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws ServletException, IOException {
chain.doFilter(request, response);
// 重置缓冲区,响应头不会被重置
response.resetBuffer();
// 获取 common.js
String text = Utils.readFromResource(COMMON_JS_ILE_PATH);
// 正则替换 banner, 除去底部的广告信息
text = text.replaceAll("<a.*?banner\"></a><br/>", "");
text = text.replaceAll("powered.*?shrek.wang</a>", "");
response.getWriter().write(text);
}
}

替换之后的html变量值为:

1
2
3
4
5
6
var html ='<footer class="footer">'+
' <div class="container">'+
'' +
' '+
' </div>'+
' </footer>';
-------------本文结束感谢您的阅读-------------