Blogger Information
Blog 41
fans 0
comment 0
visits 25691
Popular Tutorials
More>
Latest Downloads
More>
Web Effects
Website Source Code
Website Materials
Front End Template
26、【转载】Spring Boot整合Druid数据源
自由之上
Original
597 people have browsed it

Spring Boot 2.x 默认使用 HikariCP 作为数据源,我们只要在项目中导入了 Spring Boot 的 JDBC 场景启动器,便可以使用 HikariCP 数据源获取数据库连接,对数据库进行增删改查等操作。

HikariCP 是目前市面上性能最好的数据源产品,但在实际的开发过程中,企业往往更青睐于另一款数据源产品:Druid,它是目前国内使用范围最广的数据源产品。

Druid 是阿里巴巴推出的一款开源的高性能数据源产品,Druid 支持所有 JDBC 兼容的数据库,包括 Oracle、MySQL、SQL Server 和 H2 等等。Druid 不仅结合了 C3P0、DBCP 和 PROXOOL 等数据源产品的优点,同时还加入了强大的监控功能。通过 Druid 的监控功能,可以实时观察数据库连接池和 SQL 的运行情况,帮助用户及时排查出系统中存在的问题。

Druid 不是 Spring Boot 内部提供的技术,它属于第三方技术,我们可以通过以下两种方式进行整合:

  • 自定义整合 Druid
  • 通过 starter 整合 Druid

接下来我们将结合 Spring Boot 项目 spring-boot-adminex,分别对这两种整合方式进行讲解。

1、自定义整合 Druid

自定义整合 Druid 是指:根据 Druid 官方文档和自身的需求,通过手动创建 Druid 数据源的方式,将 Druid 整合到 Spring Boot 中。

1、引入 Druid 依赖

Druid 0.1.18 之后版本都已经发布到 Maven 中央仓库中了,所以我们只需要在项目的 pom.xml 中引入 Druid 的依赖(dependency ),即可将 Druid 导入到 Spring Boot 项目中。

在 spring-boot-adminex 的 pom.xml 中添加以下代码,引入 JDBC 场景启动器、MySql 数据库驱动 和 Druid 依赖。

  1. <!--导入 JDBC 场景启动器-->
  2. <dependency>
  3. <groupId>org.springframework.boot</groupId>
  4. <artifactId>spring-boot-starter-data-jdbc</artifactId>
  5. </dependency>
  6. <!--导入数据库驱动-->
  7. <dependency>
  8. <groupId>mysql</groupId>
  9. <artifactId>mysql-connector-java</artifactId>
  10. <scope>runtime</scope>
  11. </dependency>
  12. <!--采用自定义方式整合 druid 数据源-->
  13. <!--自定义整合需要编写一个与之相关的配置类-->
  14. <dependency>
  15. <groupId>com.alibaba</groupId>
  16. <artifactId>druid</artifactId>
  17. <version>1.2.6</version>
  18. </dependency>

2、创建数据源

我们知道,Spring Boot 使用 HikariCP 作为其默认数据源,但其中有一个十分重要的条件,如下图。

图1:Hikari 数据源生效条件

@ConditionalOnMissingBean(DataSource.class) 的含义是:当容器中没有 DataSource(数据源类)时,Spring Boot 才会使用 HikariCP 作为其默认数据源。 也就是说,若我们向容器中添加 Druid 数据源类(DruidDataSource,继承自 DataSource)的对象时,Spring Boot 就会使用 Druid 作为其数据源,而不再使用 HikariCP。

例如,在 net.biancheng.www.config 包中,创建一个名为 MyDataSourceConfig 的配置类,并将 Druid 数据源对象添加到容器中,代码如下。

  1. package net.biancheng.www.config;
  2. import com.alibaba.druid.pool.DruidDataSource;
  3. import org.springframework.boot.context.properties.ConfigurationProperties;
  4. import org.springframework.context.annotation.Bean;
  5. import org.springframework.context.annotation.Configuration;
  6. import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
  7. import javax.sql.DataSource;
  8. import java.sql.SQLException;
  9. @Configuration
  10. public class MyDataSourceConfig implements WebMvcConfigurer {
  11. /**
  12. * 当向容器中添加了 Druid 数据源
  13. * 使用 @ConfigurationProperties 将配置文件中 spring.datasource 开头的配置与数据源中的属性进行绑定
  14. * @return
  15. */
  16. @ConfigurationProperties("spring.datasource")
  17. @Bean
  18. public DataSource dataSource() throws SQLException {
  19. DruidDataSource druidDataSource = new DruidDataSource();
  20. //我们一般不建议将数据源属性硬编码到代码中,而应该在配置文件中进行配置(@ConfigurationProperties 绑定)
  21. // druidDataSource.setUrl("jdbc:mysql://127.0.0.1:3306/bianchengbang_jdbc");
  22. // druidDataSource.setUsername("root");
  23. // druidDataSource.setPassword("root");
  24. // druidDataSource.setDriverClassName("com.mysql.cj.jdbc.Driver");
  25. return druidDataSource;
  26. }
  27. }

在配置文件 application.yml 中添加以下数据源配置,它们会与与 Druid 数据源中的属性进行绑定,代码如下。

  1. #数据源连接信息
  2. spring:
  3. datasource:
  4. username: root
  5. password: root
  6. url: jdbc:mysql://127.0.0.1:3306/bianchengbang_jdbc
  7. driver-class-name: com.mysql.cj.jdbc.Driver

注:配置类创建 Druid 数据源对象时,应该尽量避免将数据源信息(例如 url、username 和 password 等)硬编码到代码中,而应该通过 @ConfigurationProperties(“spring.datasource”) 注解,将 Druid 数据源对象的属性与配置文件中的以“spring.datasource”开头的配置进行绑定。
至此,我们就已经将数据源从 HikariCP 切换到了 Druid 了。
示例 1
接下来,我们可以使用 Spring Boot 提供的默认测试类 SpringBootAdminexApplicationTests 进行简单的测试,来验证数据源类型是否为 Druid 以及是否能正常获取数据库连接、访问数据库,代码如下。

  1. package net.biancheng.www;
  2. import org.junit.jupiter.api.Test;
  3. import org.springframework.beans.factory.annotation.Autowired;
  4. import org.springframework.boot.test.context.SpringBootTest;
  5. import org.springframework.jdbc.core.JdbcTemplate;
  6. import javax.sql.DataSource;
  7. import java.sql.SQLException;
  8. @SpringBootTest
  9. class SpringBootAdminexApplicationTests {
  10. //数据源组件
  11. @Autowired
  12. DataSource dataSource;
  13. //用于访问数据库的组件
  14. @Autowired
  15. JdbcTemplate jdbcTemplate;
  16. @Test
  17. void contextLoads() throws SQLException {
  18. System.out.println("默认数据源为:" + dataSource.getClass());
  19. System.out.println("数据库连接实例:" + dataSource.getConnection());
  20. //访问数据库
  21. Integer i = jdbcTemplate.queryForObject("SELECT count(*) from `user`", Integer.class);
  22. System.out.println("user 表共有" + i + "条数据。");
  23. }
  24. }

运行测试程序,结果如下。

  1. 默认数据源为:class com.alibaba.druid.pool.DruidDataSource
  2. 数据库连接实例:com.alibaba.druid.proxy.jdbc.ConnectionProxyImpl@46e190ed
  3. user 表共有2条数据。

根据以上运行结果可以看出,数据源已经成功地切换到了 Druid 数据源,且通过它也可以正常的获取数据库连接,访问数据库。

3、开启 Druid 内置监控页面

Druid 内置提供了一个名为 StatViewServlet 的 Servlet,这个 Servlet 可以开启 Druid 的内置监控页面功能, 展示 Druid 的统计信息,它的主要用途如下:

  • 提供监控信息展示的 html 页面
  • 提供监控信息的 JSON API
    注意:使用 StatViewServlet,建议使用 Druid 0.2.6 以上版本。
    根据 Druid 官方文档-配置_StatViewServlet 配置,StatViewServlet 是一个标准的 javax.servlet.http.HttpServlet,想要开启 Druid 的内置监控页面,需要将该 Servlet 配置在 Web 应用中的 WEB-INF/web.xml 中,web.xml 中配置如下。
  1. <servlet>
  2. <servlet-name>DruidStatView</servlet-name>
  3. <servlet-class>com.alibaba.druid.support.http.StatViewServlet</servlet-class>
  4. </servlet>
  5. <servlet-mapping>
  6. <servlet-name>DruidStatView</servlet-name>
  7. <url-pattern>/druid/*</url-pattern>
  8. </servlet-mapping>

我们知道,Spring Boot 项目中是没有 WEB-INF/web.xml 的,因此我们可以在配置类中,通过 ServletRegistrationBean 将 StatViewServlet 注册到容器中,来开启 Druid 的内置监控页面。
关于 ServletRegistrationBean 的详细使用,请参考 Spring Boot注册Web原生组件(Servlet、Filter、Listener)。
示例 2

1、在 MyDataSourceConfig 配置类中添加以下代码,将 StatViewServlet 注入到容器中,开启 Druid 内置监控页面功能。

  1. /**
  2. * 开启 Druid 数据源内置监控页面
  3. *
  4. * @return
  5. */
  6. @Bean
  7. public ServletRegistrationBean statViewServlet() {
  8. StatViewServlet statViewServlet = new StatViewServlet();
  9. //向容器中注入 StatViewServlet,并将其路径映射设置为 /druid/*
  10. ServletRegistrationBean servletRegistrationBean = new ServletRegistrationBean(statViewServlet, "/druid/*");
  11. //配置监控页面访问的账号和密码(选配)
  12. servletRegistrationBean.addInitParameter("loginUsername", "admin");
  13. servletRegistrationBean.addInitParameter("loginPassword", "123456");
  14. return servletRegistrationBean;
  15. }

2、启动 Spring Boot,浏览器访问“http://localhost:8080/druid”,即可访问 Druid 的内置监控页面的登陆页,结果如下。


图2:Druid 内置监控页面的登陆页

3、在登陆页输入框内,分别输入我们自定义的用户名(loginUsername)和密码(loginPassword),点击 Sign in 按钮访问监控页面,如下图。


图3:Druid 内置监控页面首页(猛击图片,查看原图)

4、开启 SQL 监控

Druid 内置提供了一个 StatFilter,通过它可以开启 Druid 的 SQL 监控功能,对 SQL 进行监控。

StatFilter 的别名是 stat,这个别名的映射配置信息保存在 druid-xxx.jar!/META-INF/druid-filter.properties 中。Druid 官方文档-配置_StatFilter 中给出了在 Spring 中配置该别名(stat)开启 Druid SQL 监控的方式,配置如下。

  1. <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close">
  2. ... ...
  3. <property name="filters" value="stat" />
  4. </bean>

根据以上配置我们可以看出,只要在 dataSource 的 Bean 中添加一个取值为“stat”的“filters”属性,就能开启 Druid SQL 监控,因此我们只要将该配置转换为在配置类中进行即可,代码如下。

  1. @ConfigurationProperties("spring.datasource")
  2. @Bean
  3. public DataSource dataSource() throws SQLException {
  4. DruidDataSource druidDataSource = new DruidDataSource();
  5. //设置 filters 属性值为 stat,开启 SQL 监控
  6. druidDataSource.setFilters("stat");
  7. return druidDataSource;
  8. }

示例 3
1、为了验证 Druid SQL 监控是否开启,我们需要在 IndexController 中添加一个能够查询数据库的方法,代码如下。

  1. @Controller
  2. public class IndexController {
  3. //自动装配 jdbcTemplate
  4. @Autowired
  5. JdbcTemplate jdbcTemplate;
  6. /**
  7. * 访问"/testSql",访问数据库
  8. * @return
  9. */
  10. @ResponseBody
  11. @GetMapping("/testSql")
  12. public String testSql() {
  13. String SQL = "SELECT count(*) from `user`";
  14. Integer integer = jdbcTemplate.queryForObject(SQL, Integer.class);
  15. return integer.toString();
  16. }
  17. }

2、重启 Spring Boot,浏览器访问“http://localhost:8080/testSql”,结果如下图。

图4:访问数据库

3、访问 Druid 的内置监控页面,切换到 SQL 监控模块,可以看到 Druid SQL 监控已经开启,如下图。

图5:Druid SQL 监控(猛击图片,查看原图)

5. 开启防火墙

Druid 内置提供了一个 WallFilter,使用它可以开启防火墙功能,防御 SQL 注入攻击。

WallFilter 的别名是 wall,这个别名映射配置信息保存在 druid-xxx.jar!/META-INF/druid-filter.properties 中。Druid 官方文档-配置 wallfilter 中给出了在 Spring 中使用该别名(wall)开启防火墙的方式,配置如下。

  1. <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close">
  2. ... ...
  3. <property name="filters" value="wall" />
  4. </bean>

WallFilter 可以结合其他 Filter 一起使用,例如:

  1. <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close">
  2. ...
  3. <property name="filters" value="wall,stat"/>
  4. </bean>

根据以上配置我们可以看出,只要在 dataSource 的 Bean 中添加一个取值为“wall”的“filters”属性,就能开启 Druid 的防火墙功能,因此我们只需要在配置类中为 dataSource 的 filters 属性再添加一个“wall”即可(多个属性值之间使用逗号“,”隔开),代码如下。

  1. @ConfigurationProperties("spring.datasource")
  2. @Bean
  3. public DataSource dataSource() throws SQLException {
  4. DruidDataSource druidDataSource = new DruidDataSource();
  5. //同时开启 sql 监控(stat) 和防火墙(wall),中间用逗号隔开。
  6. //开启防火墙能够防御 SQL 注入攻击
  7. druidDataSource.setFilters("stat,wall");
  8. return druidDataSource;
  9. }

示例 4
1、重启 Spring Boot,浏览器访问“http://localhost:8080/testSql”,结果如下图。

图6:访问数据库

2、访问 Druid 的内置监控页面,切换到 SQL 防火墙,可以看到 Druid 防火墙已经开启,如下图。

图7:Druid 防火墙(猛击图片,原图查看)

6. 开启 Web-JDBC 关联监控

Druid 还内置提供了一个名为 WebStatFilter 的过滤器,它可以用来监控与采集 web-jdbc 关联监控的数据。

根据 Druid 官方文档-配置_配置WebStatFilter,想要开启 Druid 的 Web-JDBC 关联监控,只需要将 WebStatFilter 配置在 Web 应用中的 WEB-INF/web.xml 中即可,web.xml 配置如下。

  1. <filter>
  2. <filter-name>DruidWebStatFilter</filter-name>
  3. <filter-class>com.alibaba.druid.support.http.WebStatFilter</filter-class>
  4. <init-param>
  5. <param-name>exclusions</param-name>
  6. <param-value>*.js,*.gif,*.jpg,*.png,*.css,*.ico,/druid/*</param-value>
  7. </init-param>
  8. </filter>
  9. <filter-mapping>
  10. <filter-name>DruidWebStatFilter</filter-name>
  11. <url-pattern>/*</url-pattern>
  12. </filter-mapping>

Spring Boot 项目中是没有 WEB-INF/web.xml 的,但是我们可以在配置类中,通过 FilterRegistrationBean 将 WebStatFilter 注入到容器中,来开启 Druid 的 Web-JDBC 关联监控。

配置类中示例代码如下。

  1. /**
  2. * 向容器中添加 WebStatFilter
  3. * 开启内置监控中的 Web-jdbc 关联监控的数据
  4. * @return
  5. */
  6. @Bean
  7. public FilterRegistrationBean druidWebStatFilter() {
  8. WebStatFilter webStatFilter = new WebStatFilter();
  9. FilterRegistrationBean filterRegistrationBean = new FilterRegistrationBean(webStatFilter);
  10. // 监控所有的访问
  11. filterRegistrationBean.setUrlPatterns(Arrays.asList("/*"));
  12. // 监控访问不包括以下路径
  13. filterRegistrationBean.addInitParameter("exclusions", "*.js,*.gif,*.jpg,*.png,*.css,*.ico,/druid/*");
  14. return filterRegistrationBean;
  15. }

示例 5
1、重启 Spring Boot,浏览器访问“http://localhost:8080/testSql”,结果如下图。

图8:访问数据库

2、浏览器访问 AdminEx 系统的任意页面,然后再访问 Druid 的内置监控页面,切换到 Web 应用模块,可以看到 Druid 的 Web 监控已经开启,如下图。

图9:Druid Web 应用模块

与此同时,URI 监控和 Session 监控也都被开启,如下图。

图10:Druid URI 监控

2、通过 starter 整合 Druid

Druid 可以说是国内使用最广泛的数据源连接池产品,但到目前为止 Spring Boot 官方只对 Hikari、Tomcat、Dbcp2 和 OracleUcp 等 4 种数据源产品提供了自动配置支持,对于其他的数据源连接池产品(包括 Druid),则并没有提供自动配置支持。这就导致用户只能通过自定义的方式(第一种整合方式)整合 Druid,非常繁琐。

为了解决这一问题,于是阿里官方提供了 Druid Spring Boot Starter,它可以帮助我们在 Spring Boot 项目中,轻松地整合 Druid 的数据库连接池和监控功能。

使用 Druid Spring Boot Starter 将 Druid 与 Spring Boot 整合,步骤如下。

1、引入 Druid Spring Boot Starter 依赖

在 Spring Boot 项目的 pom.xml 中添加以下依赖,引入 Druid Spring Boot Starter(点击查询最新版本),示例代码如下。

  1. <!--添加 druid starter-->
  2. <dependency>
  3. <groupId>com.alibaba</groupId>
  4. <artifactId>druid-spring-boot-starter</artifactId>
  5. <version>1.1.17</version>
  6. </dependency>

2、配置属性

Druid Spring Boot Starter 已经将 Druid 数据源中的所有模块都进行默认配置,我们也可以通过 Spring Boot 配置文件(application.properties/yml)来修改 Druid 各个模块的配置,否则将使用默认配置。

在 Spring Boot 配置文件中配置以下内容:

  • JDBC 通用配置
  • Druid 数据源连接池配置
  • Druid 监控配置
  • Druid 内置 Filter 配置
    这些配置内容既可以在 application.properties 中进行配置,也可以在 application.yml 中尽心配置,当配置内容较多时,我们通常推荐使用 application.yml(示例中使用的是 application.yml)。

3、JDBC 通用配置

我们可以在 Spring Boot 的配置文件中对 JDBC 进行通用配置,例如,数据库用户名、数据库密码、数据库 URL 以及 数据库驱动等等,示例代码如下。

  1. ################################################## JDBC 通用配置 ##########################################
  2. spring:
  3. datasource:
  4. username: root #数据库登陆用户名
  5. password: root #数据库登陆密码
  6. url: jdbc:mysql://127.0.0.1:3306/bianchengbang_jdbc #数据库url
  7. driver-class-name: com.mysql.cj.jdbc.Driver #数据库驱动

4、Druid 数据源连接池配置

我们还可以在 Spring Boot 的配置文件中对 Druid 数据源连接池进行配置,示例代码如下。

  1. ################################################## Druid连接池的配置 ##########################################
  2. spring:
  3. datasource:
  4. druid:
  5. initial-size: 5 #初始化连接大小
  6. min-idle: 5 #最小连接池数量
  7. max-active: 20 #最大连接池数量
  8. max-wait: 60000 #获取连接时最大等待时间,单位毫秒
  9. time-between-eviction-runs-millis: 60000 #配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒
  10. min-evictable-idle-time-millis: 300000 #配置一个连接在池中最小生存的时间,单位是毫秒
  11. validation-query: SELECT 1 FROM DUAL #测试连接
  12. test-while-idle: true #申请连接的时候检测,建议配置为true,不影响性能,并且保证安全性
  13. test-on-borrow: false #获取连接时执行检测,建议关闭,影响性能
  14. test-on-return: false #归还连接时执行检测,建议关闭,影响性能
  15. pool-prepared-statements: false #是否开启PSCache,PSCache对支持游标的数据库性能提升巨大,oracle建议开启,mysql下建议关闭
  16. max-pool-prepared-statement-per-connection-size: 20 #开启poolPreparedStatements后生效
  17. filters: stat,wall #配置扩展插件,常用的插件有=>stat:监控统计 wall:防御sql注入
  18. connection-properties: 'druid.stat.mergeSql=true;druid.stat.slowSqlMillis=5000' #通过connectProperties属性来打开mergeSql功能;慢SQL记录

5、Druid 监控配置

我们还可以在 Spring Boot 的配置文件中对 Druid 内置监控页面、Web-JDBC 关联监控和 Spring 监控等功能进行配置,示例代码如下。

  1. ###################################################### Druid 监控配置信息 ##########################################
  2. spring:
  3. datasource:
  4. druid:
  5. # StatViewServlet配置,说明请参考Druid Wiki,配置_StatViewServlet配置
  6. stat-view-servlet:
  7. enabled: true #是否开启内置监控页面,默认值为 false
  8. url-pattern: '/druid/*' #StatViewServlet 的映射路径,即内置监控页面的访问地址
  9. reset-enable: true #是否启用重置按钮
  10. login-username: admin #内置监控页面的登录页用户名 username
  11. login-password: admin #内置监控页面的登录页密码 password
  12. # WebStatFilter配置,说明请参考Druid Wiki,配置_配置WebStatFilter
  13. web-stat-filter:
  14. enabled: true #是否开启内置监控中的 Web-jdbc 关联监控的数据
  15. url-pattern: '/*' #匹配路径
  16. exclusions: '*.js,*.gif,*.jpg,*.png,*.css,*.ico,/druid/*' #排除路径
  17. session-stat-enable: true #是否监控session
  18. # Spring监控配置,说明请参考Druid Github Wiki,配置_Druid和Spring关联监控配置
  19. aop-patterns: net.biancheng.www.* #Spring监控AOP切入点,如x.y.z.abc.*,配置多个英文逗号分隔

6、Druid 内置 Filter 配置

Druid Spring Boot Starter 对以下 Druid 内置 Filter,都提供了默认配置:

  • StatFilter
  • WallFilter
  • ConfigFilter
  • EncodingConvertFilter
  • Slf4jLogFilter
  • Log4jFilter
  • Log4j2Filter
  • CommonsLogFilter

我们可以通过 spring.datasource.druid.filters=stat,wall … 的方式来启用相应的内置 Filter,不过这些 Filter 使用的都是默认配置。如果默认配置不能满足我们的需求,我们还可以在配置文件使用 spring.datasource.druid.filter.* 对这些 Filter 进行配置,示例代码如下。

  1. # ####################################################### Druid 监控配置信息 ##########################################
  2. spring:
  3. datasource:
  4. druid:
  5. # 对配置已开启的 filters 即 stat(sql 监控) wall(防火墙)
  6. filter:
  7. #配置StatFilter (SQL监控配置)
  8. stat:
  9. enabled: true #开启 SQL 监控
  10. slow-sql-millis: 1000 #慢查询
  11. log-slow-sql: true #记录慢查询 SQL
  12. #配置WallFilter (防火墙配置)
  13. wall:
  14. enabled: true #开启防火墙
  15. config:
  16. update-allow: true #允许更新操作
  17. drop-table-allow: false #禁止删表操作
  18. insert-allow: true #允许插入操作
  19. delete-allow: true #删除数据操作

在配置 Druid 内置 Filter 时,需要先将对应 Filter 的 enabled 设置为 true,否则内置 Filter 的配置不会生效。

以上所有内容都只是示例配置,Druid Spring Boot Starter 并不是只支持以上属性,它支持 DruidDataSource 内所有具有 setter 方法的属性。

至此,我们就完成了使用 Druid Spring Boot Starter 整合 Druid 的全部过程,至于验证步骤请参考自定义整合。

3、总结

无论是自定义整合还是通过 Druid Spring Boot Starter 整合,都能实现 Spring Boot 整合 Druid 数据源的目的,它们都各有利弊。

  • 根据官方文档,自定义整合 Druid 数据源能够更加清晰地了解 Druid 的各种功能及其实现方式,但整合过程繁琐。
  • 通过 Druid Spring Boot Starter 整合 Druid 数据源,则更加方便快捷,大大简化了整合过程,但无法清晰地了解 Druid 的功能内部的实现方式和原理。

这里,我们更加推荐使用 Druid Spring Boot Starter 进行整合,毕竟这种整合方式大大简化了整个整合的过程。


加入
QQ群:722461036
微信群:
一起督促、学习、练习、温习、复习 ~ ~ ~

Statement of this Website
The copyright of this blog article belongs to the blogger. Please specify the address when reprinting! If there is any infringement or violation of the law, please contact admin@php.cn Report processing!
All comments Speak rationally on civilized internet, please comply with News Comment Service Agreement
0 comments
Author's latest blog post