Springboot2.x yaml文件整合logback+druid+mysql及多个环境管理
程序员文章站
2022-07-07 11:26:50
...
- 新建项目,首先配置pom.xml,有其他需要可以自行加入,这些都是我写项目必备的
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.2.2.RELEASE</version> <relativePath/> </parent> <!-- 通过maven的package插件打包成的文件名为name.version.packaging --> <groupId>com</groupId> <artifactId>test</artifactId> <version>1.0.0</version> <packaging>war</packaging> <name>test</name> <!-- Java版本 --> <properties> <java.version>13</java.version> </properties> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.mybatis.spring.boot</groupId> <artifactId>mybatis-spring-boot-starter</artifactId> <version>2.1.1</version> </dependency> <!-- mysql配置 --> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>8.0.11</version> </dependency> <!-- 使用lombok配置 --> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <optional>true</optional> </dependency> <!-- 内置tomcat配置 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-tomcat</artifactId> <scope>provided</scope> </dependency> <!-- 使用fastjson解析配置 --> <dependency> <groupId>com.alibaba</groupId> <artifactId>fastjson</artifactId> <version>1.2.62</version> </dependency> <!-- druid配置,打包会提示失效,但功能不影响,目前没找到原因 --> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid-spring-boot-starter</artifactId> <version>1.1.21</version> </dependency> <!-- 自定义配置文件,并通过@Value("${attributeName}")注解获取值配置 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-configuration-processor</artifactId> <optional>true</optional> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build> </project>
-
在src/main/resource下创建application.yaml文件,并配置
spring: # 环境配置,会在加载完当前yaml文件后,根据输入的名称加载application-环境名称.yaml文件,现在会加载application-dev.yaml文件 profiles: active: dev # 数据源配置,使用当前版本druid不需要注明Driver,url在环境配置中区分配置 datasource: username: root password: 123456 # 声明使用druid作为连接池 type: com.alibaba.druid.pool.DruidDataSource # druid配置 druid: # 初始化时建立物理连接的个数。初始化发生在显示调用 init 方法,或者第一次 getConnection 时 initialSize: 5 # 最小连接池数量 minIdle: 5 # 最大连接池数量 maxActive: 10 # 获取连接时最大等待时间,单位毫秒,配置之后,缺省启用公平锁,并发效率会有所下降,如果需要可以通过配置useUnfairLock属性为true使用非公平锁 maxWait: 60000 # 线程检测连接的间隔时间,如果连接空闲时间超出则关闭物理连接 timeBetweenEvictionRunsMillis: 60000 # 生成sql日志间隔时间 timeBetweenLogStatsMillis: 300000 # 连接保持空闲而不被驱逐的最小时间 minEvictableIdleTimeMillis: 300000 # 检测连接是否有效,oracle写成 SELECT 1 FROM DUAL,我用的mysql validationQuery: SELECT 1 # 申请连接的时候检测,如果空闲时间大于线程检测连接的间隔时间,检测连接是否有效,不影响性能,并且保证安全性 testWhileIdle: true # 申请连接时检测连接是否有效,做了这个配置会降低性能 testOnBorrow: false # 归还连接时检测连接是否有效,做了这个配置会降低性能 testOnReturn: false # 是否自动回收超时连接 removeAbandoned: true # 超时时间 remove-abandoned-timeout: 180 # 监控配置,访问地址http://localhost:8080/druid/login.html web-stat-filter: # 是否开启WebStatFilter enabled: true # 需要拦截的url url-pattern: /* # 排除静态资源的请求 exclusions: "*.js,*.gif,*.jpg,*.png,*.css,*.ico,/druid/*" # 展示统计信息。 stat-view-servlet: #是否启用StatViewServlet enabled: true # 需要拦截的 url url-pattern: /druid/* # 是否允许清空统计数据 reset-enable: true login-username: druid login-password: druid # 白名单,只能写ip allow: 127.0.0.1 # 黑名单,优先级高于白名单 # deny: xxx.xxx.xxx.xxx # mybatis配置 mybatis: # mapper存放路径 type-aliases-package: com.demo.bean.mapper configuration: # 将下划线命名转化为驼峰命名 map-underscore-to-camel-case: true # 自定义的配置文件,会报黄线不知道怎么去除,但不影响使用 my-properties: # logback相关配置 logback: # 项目名称,用于log文件的命名 project-name: test # 文件空间 file-size: 100MB # 最大存储历史文件数量 max-history: 30 # 最大占用空间 total-size-cap: 1GB # 日志文件的格式化方式,最后显示的格式如下 # [2020-01-07 11:05:55.679]-[INFO]-[main]-[92]-[o.s.b.w.e.tomcat.TomcatWebServer]-[Tomcat initialized with port(s): 8080 (http)] file-log-pattern: "[%d{yyyy-MM-dd HH:mm:ss.SSS}]-[%level]-[%thread]-[%L]-[%logger{36}]-[%msg]%n"
- 分别创建所需环境文件并配置,我用application-dev.yaml来举例
# 生产模式 spring: datasource: <!-- 生产模式使用的mysql路径,分别配置第一因为数据库测试时与生产时使用的不同,第二因为生产模式访问数据库用内网地址,可以修改mysql的用户权限限制其他ip访问,速度快更安全--> url: jdbc:mysql://xxx.xxx.xxx.xxx:3306/test?useUnicode=true&characterEncoding=UTF-8&useSSL=false&autoReconnect=true&failOverReadOnly=false&serverTimezone=CTT # 区分环境的自定义配置文件 my-properties: # 生产环境下的文件路径 file-path: /home/user/document/document-dev logback: # xml文件不能直接用&符号,需要转义,所以这里只给出url,参数在logback.xml中进行拼接 mysql-url: jdbc:mysql://xxx.xxx.xxx.xxx:3306/test
- 在src/main/resource下创建logback-spring.xml文件,并配置,如果创建成别的名称,则不能使用spring配置文件中的属性,因为会先于spring的配置文件加载
<?xml version="1.0" encoding="UTF-8"?> <!-- debug="true"可以将log内容输出在catalina.out中 --> <configuration debug="true"> <!-- 引入控制台输出颜色插件,在开发时候看起来比较舒服 --> <conversionRule conversionWord="clr" converterClass="org.springframework.boot.logging.logback.ColorConverter"/> <!-- 引用spring配置文件的属性 --> <springProperty scope="context" name="MYSQL_URL" source="my-properties.logback.mysql-url"/> <springProperty scope="context" name="MYSQL_USERNAME" source="spring.datasource.username"/> <springProperty scope="context" name="MYSQL_PASSWORD" source="spring.datasource.password"/> <springProperty scope="context" name="PROJECT" source="my-properties.logback.project-name"/> <springProperty scope="context" name="FILE_SIZE" source="my-properties.logback.file-size"/> <springProperty scope="context" name="MAX_HISTORY" source="my-properties.logback.max-history"/> <springProperty scope="context" name="TOTAL_SIZE_CAP" source="my-properties.logback.total-size-cap"/> <property name="ROOT" value="logs/${PROJECT}/"/> <springProperty scope="context" name="FILE_LOG_PATTERN" source="my-properties.logback.file-log-pattern"/> <!-- 控制台输出内容格式化,INFO绿色,WAR黄色,ERROR红色 --> <property name="CONSOLE_LOG_PATTERN" value="%clr([%d{yyyy-MM-dd HH:mm:ss.SSS}])-%clr([%level])-%clr([%thread])-%clr([%L])-%clr([%logger{36}])-%clr([%msg]) %n"/> <!-- 输出到控制台 --> <!-- 控制台输出配置 --> <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender"> <encoder> <pattern>${CONSOLE_LOG_PATTERN}</pattern> </encoder> </appender> <!-- 输出到文件配置,文件生成在logs/test/test.log--> <appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender"> <file>${ROOT}/${PROJECT}.log</file> <rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy"> <fileNamePattern>${ROOT}/%d{yyyy-MM-dd}.%i.log</fileNamePattern> <maxFileSize>${FILE_SIZE}</maxFileSize> <maxHistory>${MAX_HISTORY}</maxHistory> <totalSizeCap>${TOTAL_SIZE_CAP}}</totalSizeCap> </rollingPolicy> <encoder charset="utf-8"> <pattern>${FILE_LOG_PATTERN}</pattern> </encoder> <filter class="ch.qos.logback.classic.filter.ThresholdFilter"> <level>INFO</level> </filter> </appender> <!-- ERROR级别输出到文件配置,文件生成在logs/test/test-error.log--> <appender name="ERROR_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender"> <file>${ROOT}/${PROJECT}-error.log</file> <rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy"> <fileNamePattern>${ROOT}/%d{yyyy-MM-dd}.%i-error.log</fileNamePattern> <maxFileSize>${FILE_SIZE}</maxFileSize> <maxHistory>${MAX_HISTORY}</maxHistory> <totalSizeCap>${TOTAL_SIZE_CAP}}</totalSizeCap> </rollingPolicy> <encoder charset="utf-8"> <pattern>${FILE_LOG_PATTERN}</pattern> </encoder> <filter class="ch.qos.logback.classic.filter.LevelFilter"> <level>ERROR</level> <onMatch>ACCEPT</onMatch> <onMismatch>DENY</onMismatch> </filter> </appender> <!-- 输出到数据库配置 --> <appender name="DB" class="ch.qos.logback.classic.db.DBAppender"> <connectionSource class="ch.qos.logback.core.db.DriverManagerConnectionSource"> <driverClass>com.mysql.cj.jdbc.Driver</driverClass> <url> ${MYSQL_URL}?useUnicode=true&characterEncoding=UTF-8&useSSL=false&autoReconnect=true&failOverReadOnly=false&serverTimezone=CTT </url> <user>${MYSQL_USERNAME}</user> <password>${MYSQL_PASSWORD}</password> </connectionSource> <!-- WARN级别以上的保存至数据库 --> <filter class="ch.qos.logback.classic.filter.ThresholdFilter"> <level>WARN</level> </filter> </appender> <root level="INFO"> <appender-ref ref="STDOUT"/> <appender-ref ref="FILE"/> <appender-ref ref="ERROR_FILE"/> <appender-ref ref="DB"/> </root> </configuration>
- 如果需要输出到数据库,需要在数据库创建3张表,创建语句如下,我用的是utf8mb4编码格式,可以支持emoji,比较方便
CREATE TABLE `logging_event` ( `timestmp` bigint(20) NOT NULL, `formatted_message` text COLLATE utf8mb4_bin NOT NULL, `logger_name` varchar(254) COLLATE utf8mb4_bin NOT NULL, `level_string` varchar(254) COLLATE utf8mb4_bin NOT NULL, `thread_name` varchar(254) COLLATE utf8mb4_bin DEFAULT NULL, `reference_flag` smallint(6) DEFAULT NULL, `arg0` varchar(254) COLLATE utf8mb4_bin DEFAULT NULL, `arg1` varchar(254) COLLATE utf8mb4_bin DEFAULT NULL, `arg2` varchar(254) COLLATE utf8mb4_bin DEFAULT NULL, `arg3` varchar(254) COLLATE utf8mb4_bin DEFAULT NULL, `caller_filename` varchar(254) COLLATE utf8mb4_bin NOT NULL, `caller_class` varchar(254) COLLATE utf8mb4_bin NOT NULL, `caller_method` varchar(254) COLLATE utf8mb4_bin NOT NULL, `caller_line` char(4) COLLATE utf8mb4_bin NOT NULL, `event_id` bigint(20) NOT NULL AUTO_INCREMENT, PRIMARY KEY (`event_id`) ) ENGINE=InnoDB AUTO_INCREMENT=460 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin; CREATE TABLE `logging_event_property` ( `event_id` bigint(20) NOT NULL, `mapped_key` varchar(100) COLLATE utf8mb4_bin NOT NULL, `mapped_value` text COLLATE utf8mb4_bin, PRIMARY KEY (`event_id`,`mapped_key`), CONSTRAINT `logging_event_property_ibfk_1` FOREIGN KEY (`event_id`) REFERENCES `logging_event` (`event_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin; CREATE TABLE `logging_event_exception` ( `event_id` bigint(20) NOT NULL, `i` smallint(6) NOT NULL, `trace_line` varchar(254) COLLATE utf8mb4_bin NOT NULL, PRIMARY KEY (`event_id`,`i`), CONSTRAINT `logging_event_exception_ibfk_1` FOREIGN KEY (`event_id`) REFERENCES `logging_event` (`event_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
- 需要的话,在src/main/com/test/controller下创建DruidStatController用以使用请求方式获取健康检查信息,访问路径为http://localhost:8080/stat,返回的是json数据
@RestController @Slf4j public class DruidStatController { @RequestMapping("stat") private Object method() { log.info("德鲁伊健康检查"); return DruidStatManagerFacade.getInstance().getDataSourceStatDataList(); } }
- 在项目中引用配置文件的属性
@Value("${my-properties.file-path}") private String filePath;
- 更换环境只需要更改application.yaml中的spring-profiles-active的值
- 自定义异常枚举类
public enum BaseEnum { UNKNOWN_ERROR(501,"未知错误"); @Getter private Integer statusCode; @Getter private String errMsg; BaseEnum(Integer statusCode, String errMsg) { this.statusCode = statusCode; this.errMsg = errMsg; } }
- 自定义响应格式类
public class BaseResponse { @Getter @Setter private Integer statusCode; @Getter @Setter private Object responseData; @Getter @Setter private String errMsg; public BaseResponse(){ suc(""); } public BaseResponse(BaseEnum baseEnum){ fail(baseEnum.getStatusCode(),baseEnum.getErrMsg()); } public BaseResponse(Object responseData){ suc(responseData); } public BaseResponse(Integer statusCode,String errMsg){ fail(statusCode,errMsg); } private void suc(Object responseData){ this.statusCode = 200; this.responseData = responseData; this.errMsg = ""; } private void fail(Integer statusCode,String errMsg){ this.statusCode = statusCode; this.errMsg = errMsg; this.responseData = ""; } }
- 自定义线程池进行异步处理
public class VisibleAsync extends ThreadPoolTaskExecutor { private void showThreadPoolInfo(String prefix){ final ThreadPoolExecutor threadPoolExecutor = getThreadPoolExecutor(); } } @Component public class BaseAsync { ThreadPoolTaskExecutor executor; public ThreadPoolTaskExecutor async() { if(executor==null){ executor = new VisibleAsync(); //配置核心线程数 executor.setCorePoolSize(5); //配置最大线程数 executor.setMaxPoolSize(100); //配置队列大小 executor.setQueueCapacity(99999); //配置线程池中的线程的名称前缀 executor.setThreadNamePrefix("线程池"); // rejection-policy:当pool已经达到max size的时候,如何处理新任务 // CALLER_RUNS:不在新线程中执行任务,而是有调用者所在的线程来执行 executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy()); //执行初始化 executor.initialize(); } return executor; } }
- 自定义异常类
public class BaseException extends RuntimeException{ @Setter @Getter private Integer statusCode; @Setter @Getter private String errMsg; @Setter @Getter private String className; @Setter @Getter private String methodName; public BaseException(BaseEnum baseEnum,String className,String methodName){ this(baseEnum.getStatusCode(),baseEnum.getErrMsg(),className,methodName); } public BaseException(Integer statusCode,String errMsg,String className,String methodName){ this.statusCode = statusCode; this.errMsg = errMsg; this.className = className; this.methodName = methodName; } @Override public String toString() { return "[" + statusCode + "][" + errMsg +"][" + className + "][" + methodName + "]"; } }
- 全局异常处理程序,并在异常发生时输出日志
@Slf4j @ControllerAdvice public class MyExceptionHandler { // 处理绑定异常 @ResponseBody @ExceptionHandler(value= BindException.class) public Object MethodArgumentNotValidHandler(BindException ex) { log.error(ex.getMessage()); return new BaseResponse(500,ex.getMessage()); } // 处理自定义异常 @ResponseBody @ExceptionHandler(value = BaseException.class) public BaseResponse myExceptionHandler(BaseException ex){ log.warn(String.valueOf(ex)); return new BaseResponse(ex.getStatusCode(),ex.getErrMsg()); } // 处理其他异常 @ResponseBody @ExceptionHandler(value = Exception.class) public BaseResponse exceptionHandler(Exception ex){ log.error(ex.getMessage()); return new BaseResponse(500,ex.getMessage()); } }
- 跨域配置
@Configuration public class CorsConfig { private CorsConfiguration buildConfig() { CorsConfiguration corsConfiguration = new CorsConfiguration(); corsConfiguration.addAllowedOrigin("*"); corsConfiguration.addAllowedHeader("*"); corsConfiguration.addAllowedMethod("*"); return corsConfiguration; } @Bean public CorsFilter corsFilter() { UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(); source.registerCorsConfiguration("/**", buildConfig()); // 4 return new CorsFilter(source); } }
- 主动抛出自定义异常代码,基本全局都不用try-catch
throw new BaseException(BaseEnum.SHARE_ERROR,"className","methodName");
- 自定义基础控制器类,统一异步处理请求,并输出日志
@Slf4j @Component public class BaseController { private final BaseAsync baseAsync; public BaseController(BaseAsync baseAsync) { this.baseAsync = baseAsync; } public BaseResponse getResponse(BaseResponse callback,Object param,String info) throws ExecutionException, InterruptedException { log.info("请求]-["+info+"]-[{}",param); return baseAsync.async().submit(()->callback).get(); } }
其他控制器代码
@RestController @Slf4j public class TestController { private final BaseController baseController; public TestController(BaseController baseController) { this.baseController = baseController; } @RequestMapping("test") private BaseResponse testParameter(TestRequestVo testRequestVo) throws ExecutionException, InterruptedException { return baseController.getResponse(new BaseResponse(testRequestVo),testRequestVo,"测试接口"); } }
- 网络请求工具类
public class HttpUtil { public static JSONObject get(String domain) throws IOException { StringBuilder stringBuilder = new StringBuilder(); URL url = new URL(domain); URLConnection urlConnection = url.openConnection(); urlConnection.connect(); @Cleanup BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(urlConnection.getInputStream())); String line; while ((line = bufferedReader.readLine()) != null) stringBuilder.append(line); return JSON.parseObject(stringBuilder.toString()); } public static JSONObject post(String domain, JSONObject jsonParam) throws IOException { StringBuilder stringBuilder = new StringBuilder(); URL url = new URL(domain); HttpURLConnection httpURLConnection = (HttpURLConnection) url.openConnection(); httpURLConnection.setDoInput(true); httpURLConnection.setDoOutput(true); httpURLConnection.setRequestMethod("POST"); httpURLConnection.setRequestProperty("Content-Type", "application/json;charset=utf-8"); @Cleanup DataOutputStream dataOutputStream = new DataOutputStream(httpURLConnection.getOutputStream()); dataOutputStream.write(jsonParam.toString().getBytes()); dataOutputStream.flush(); if (HttpURLConnection.HTTP_OK == httpURLConnection.getResponseCode()) { @Cleanup BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(httpURLConnection.getInputStream(), StandardCharsets.UTF_8)); String line; while ((line = bufferedReader.readLine()) != null) stringBuilder.append(line); return JSONObject.parseObject(stringBuilder.toString()); } else { throw new BaseException(httpURLConnection.getResponseCode(),httpURLConnection.getResponseMessage(),"HttpUtils","post"); } } }
- 代码要尽可能的考虑周全,错误码细分,尽量排除逻辑错误,有任何问题欢迎指正