欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

Springboot2.x yaml文件整合logback+druid+mysql及多个环境管理

程序员文章站 2022-07-07 11:26:50
...
  1. 新建项目,首先配置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>

     

  2. 在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"

     

  3. 分别创建所需环境文件并配置,我用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

     

  4. 在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&amp;characterEncoding=UTF-8&amp;useSSL=false&amp;autoReconnect=true&amp;failOverReadOnly=false&amp;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>

     

  5. 如果需要输出到数据库,需要在数据库创建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;

     

  6. 需要的话,在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();
    	}
    }

     

  7. 在项目中引用配置文件的属性
    @Value("${my-properties.file-path}")
    private String filePath;

     

  8. 更换环境只需要更改application.yaml中的spring-profiles-active的值
  9. 自定义异常枚举类
    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;
        }
    }

     

  10. 自定义响应格式类
    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 = "";
        }
    }

     

  11. 自定义线程池进行异步处理
    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;
        }
    }

     

  12. 自定义异常类
    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 + "]";
        }
    }

     

  13. 全局异常处理程序,并在异常发生时输出日志
    @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());
        }
    
    }
  14. 跨域配置
    @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);
        }
    }

     

  15. 主动抛出自定义异常代码,基本全局都不用try-catch
    throw new BaseException(BaseEnum.SHARE_ERROR,"className","methodName");

     

  16. 自定义基础控制器类,统一异步处理请求,并输出日志
    @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,"测试接口");
        }
    }

     

  17. 网络请求工具类
    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");
            }
        }
    }

     

  18. 代码要尽可能的考虑周全,错误码细分,尽量排除逻辑错误,有任何问题欢迎指正