- 新建項目,首先配置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"); } } }
- 代碼要儘可能的考慮周全,錯誤碼細分,儘量排除邏輯錯誤,有任何問題歡迎指正
Springboot2.x yaml文件整合logback+druid+mysql及多個環境管理
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.