Java 項(xiàng)目構(gòu)建基礎(chǔ),“ 三個(gè)統(tǒng)一” 天下!
閱讀本文大概需要 12?分鐘。
來(lái)自:juejin.im/post/5e073980f265da33f8653f2e
統(tǒng)一結(jié)果返回
目前的前后端開(kāi)發(fā)大部分?jǐn)?shù)據(jù)的傳輸格式都是json,因此定義一個(gè)統(tǒng)一規(guī)范的數(shù)據(jù)格式有利于前后端的交互與UI的展示。
統(tǒng)一結(jié)果的一般形式
是否響應(yīng)成功; 響應(yīng)狀態(tài)碼; 狀態(tài)碼描述; 響應(yīng)數(shù)據(jù) 其他標(biāo)識(shí)符
結(jié)果類(lèi)枚舉
前三者可定義結(jié)果枚舉,如:success,code,message
@Getter
public?enum?ResultCodeEnum?{
????SUCCESS(true,20000,"成功"),
????UNKNOWN_ERROR(false,20001,"未知錯(cuò)誤"),,
????PARAM_ERROR(false,20002,"參數(shù)錯(cuò)誤"),
????;
????//?響應(yīng)是否成功
????private?Boolean?success;
????//?響應(yīng)狀態(tài)碼
????private?Integer?code;
????//?響應(yīng)信息
????private?String?message;
????ResultCodeEnum(boolean?success,?Integer?code,?String?message)?{
????????this.success?=?success;
????????this.code?=?code;
????????this.message?=?message;
????}
}
統(tǒng)一結(jié)果類(lèi)
第5個(gè)屬于自定義返回,利用前4者可定義統(tǒng)一返回對(duì)象
外接只可以調(diào)用統(tǒng)一返回類(lèi)的方法,不可以直接創(chuàng)建,影刺構(gòu)造器私有; 內(nèi)置靜態(tài)方法,返回對(duì)象; 為便于自定義統(tǒng)一結(jié)果的信息,建議使用鏈?zhǔn)骄幊蹋瑢⒎祷貙?duì)象設(shè)類(lèi)本身,即return this; 響應(yīng)數(shù)據(jù)由于為json格式,可定義為JsonObject或Map形式;
@Data
public?class?R?{
????private?Boolean?success;
????private?Integer?code;
????private?String?message;
????private?Map?data?=?new?HashMap<>();
????//?構(gòu)造器私有
????private?R(){}
????//?通用返回成功
????public?static?R?ok()?{
????????R?r?=?new?R();
????????r.setSuccess(ResultCodeEnum.SUCCESS.getSuccess());
????????r.setCode(ResultCodeEnum.SUCCESS.getCode());
????????r.setMessage(ResultCodeEnum.SUCCESS.getMessage());
????????return?r;
????}
????//?通用返回失敗,未知錯(cuò)誤
????public?static?R?error()?{
????????R?r?=?new?R();
????????r.setSuccess(ResultCodeEnum.UNKNOWN_ERROR.getSuccess());
????????r.setCode(ResultCodeEnum.UNKNOWN_ERROR.getCode());
????????r.setMessage(ResultCodeEnum.UNKNOWN_ERROR.getMessage());
????????return?r;
????}
????//?設(shè)置結(jié)果,形參為結(jié)果枚舉
????public?static?R?setResult(ResultCodeEnum?result)?{
????????R?r?=?new?R();
????????r.setSuccess(result.getSuccess());
????????r.setCode(result.getCode());
????????r.setMessage(result.getMessage());
????????return?r;
????}
????/**------------使用鏈?zhǔn)骄幊蹋祷仡?lèi)本身-----------**/
????//?自定義返回?cái)?shù)據(jù)
????public?R?data(Map?map)?{
????????this.setData(map);
????????return?this;
????}
????//?通用設(shè)置data
????public?R?data(String?key,Object?value)?{
????????this.data.put(key,?value);
????????return?this;
????}
????//?自定義狀態(tài)信息
????public?R?message(String?message)?{
????????this.setMessage(message);
????????return?this;
????}
????//?自定義狀態(tài)碼
????public?R?code(Integer?code)?{
????????this.setCode(code);
????????return?this;
????}
????//?自定義返回結(jié)果
????public?R?success(Boolean?success)?{
????????this.setSuccess(success);
????????return?this;
????}
}
控制層返回
視圖層使用統(tǒng)一結(jié)果
@RestController
@RequestMapping("/api/v1/users")
public?class?TeacherAdminController?{
????@Autowired
????private?UserService?userService;
????@GetMapping
????public?R?list()?{
????????List?list?=?teacherService.list(null);
????????return?R.ok().data("itms",?list).message("用戶(hù)列表");
????}
}
json結(jié)果
{
??"success":?true,
??"code":?20000,
??"message":?"查詢(xún)用戶(hù)列表",
??"data":?{
????"itms":?[
??????{
????????"id":?"1",
????????"username":?"admin",
????????"role":?"ADMIN",
????????"deleted":?false,
????????"gmtCreate":?"2019-12-26T15:32:29",
????????"gmtModified":?"2019-12-26T15:41:40"
??????},{
????????"id":?"2",
????????"username":?"zhangsan",
????????"role":?"USER",
????????"deleted":?false,
????????"gmtCreate":?"2019-12-26T15:32:29",
????????"gmtModified":?"2019-12-26T15:41:40"
??????}
????]
??}
}
統(tǒng)一異常處理
使用統(tǒng)一返回結(jié)果時(shí),還有一種情況,就是程序的保存是由于運(yùn)行時(shí)異常導(dǎo)致的結(jié)果,有些異常我們可以無(wú)法提前預(yù)知,不能正常走到我們r(jià)eturn的R對(duì)象返回。
@ControllerAdvice
是一種作用于控制層的切面通知(Advice),該注解能夠?qū)⑼ㄓ玫腀ExceptionHandler、@InitBinder和@ModelAttributes方法收集到一個(gè)類(lèi)型,并應(yīng)用到所有控制器上
使用@ExceptionHandler注解捕獲指定或自定義的異常; 使用@ControllerAdvice集成@ExceptionHandler的方法到一個(gè)類(lèi)中; 必須定義一個(gè)通用的異常捕獲方法,便于捕獲未定義的異常信息; 自定一個(gè)異常類(lèi),捕獲針對(duì)項(xiàng)目或業(yè)務(wù)的異常; 異常的對(duì)象信息補(bǔ)充到統(tǒng)一結(jié)果枚舉中;
自定義全局異常類(lèi)
@Data
public?class?CMSException?extends?RuntimeException?{
????private?Integer?code;
????public?CMSException(Integer?code,?String?message)?{
????????super(message);
????????this.code?=?code;
????}
????public?CMSException(ResultCodeEnum?resultCodeEnum)?{
????????super(resultCodeEnum.getMessage());
????????this.code?=?resultCodeEnum.getCode();
????}
????@Override
????public?String?toString()?{
????????return?"CMSException{"?+?"code="?+?code?+?",?message="?+?this.getMessage()?+?'}';
????}
}
統(tǒng)一異常處理器
//?...
import?org.springframework.web.bind.annotation.ControllerAdvice;
import?org.springframework.web.bind.annotation.ExceptionHandler;
import?org.springframework.web.bind.annotation.ResponseBody;
@ControllerAdvice
public?class?GlobalExceptionHandler?{
????/**--------?通用異常處理方法?--------**/
????@ExceptionHandler(Exception.class)
????@ResponseBody
????public?R?error(Exception?e)?{
????????e.printStackTrace();
????????return?R.error();?//?通用異常結(jié)果
????}
????/**--------?指定異常處理方法?--------**/
????@ExceptionHandler(NullPointerException.class)
????@ResponseBody
????public?R?error(NullPointerException?e)?{
????????e.printStackTrace();
????????return?R.setResult(ResultCodeEnum.NULL_POINT);
????}
????@ExceptionHandler(HttpClientErrorException.class)
????@ResponseBody
????public?R?error(IndexOutOfBoundsException?e)?{
????????e.printStackTrace();
????????return?R.setResult(ResultCodeEnum.HTTP_CLIENT_ERROR);
????}
????/**--------?自定義定異常處理方法?--------**/
????@ExceptionHandler(CMSException.class)
????@ResponseBody
????public?R?error(CMSException?e)?{
????????e.printStackTrace();
????????return?R.error().message(e.getMessage()).code(e.getCode());
????}
}
控制層展示
{
??"success":?false,
??"code":?20007,
??"message":?"空指針異常",
??"data":?{}
}
統(tǒng)一日志收集
日志是追蹤錯(cuò)誤定位問(wèn)題的關(guān)鍵,尤其在生產(chǎn)環(huán)境中,需要及時(shí)修復(fù)熱部署,不會(huì)提供開(kāi)發(fā)者debug的環(huán)境,此時(shí)日志將會(huì)是最快解決問(wèn)題的關(guān)鍵
Logback
配置
"1.0"?encoding="UTF-8"?>
"true"?scanPeriod="10?seconds">
????logback
????
????"log.path"?value="D:/Documents/logs/edu"?/>
????
????
????"clr"?converterClass="org.springframework.boot.logging.logback.ColorConverter"?/>
????"wex"?converterClass="org.springframework.boot.logging.logback.WhitespaceThrowableProxyConverter"?/>
????"wEx"?converterClass="org.springframework.boot.logging.logback.ExtendedWhitespaceThrowableProxyConverter"?/>
????
????"CONSOLE_LOG_PATTERN"?value="${CONSOLE_LOG_PATTERN:-%clr(%d{yyyy-MM-dd?HH:mm:ss.SSS}){faint}?%clr(${LOG_LEVEL_PATTERN:-%5p})?%clr(${PID:-?}){magenta}?%clr(---){faint}?%clr([%15.15t]){faint}?%clr(%-40.40logger{39}){cyan}?%clr(:){faint}?%m%n${LOG_EXCEPTION_CONVERSION_WORD:-%wEx}}"/>
????
????"CONSOLE"?class="ch.qos.logback.core.ConsoleAppender">
????????
????????"ch.qos.logback.classic.filter.ThresholdFilter">
????????????debug
????????
????????
????????????${CONSOLE_LOG_PATTERN}
????????????
????????????UTF-8
????????
????
????
????
????"DEBUG_FILE"?class="ch.qos.logback.core.rolling.RollingFileAppender">
????????
????????${log.path}/edu_debug.log
????????
????????
????????????%d{yyyy-MM-dd?HH:mm:ss.SSS}?[%thread]?%-5level?%logger{50}?-?%msg%n
????????????UTF-8 ?
????????
????????
????????"ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
????????????
????????????${log.path}/web-debug-%d{yyyy-MM-dd}.%i.log
????????????"ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
????????????????100MB
????????????
????????????
????????????15
????????
????????
????????"ch.qos.logback.classic.filter.LevelFilter">
????????????debug
????????????ACCEPT
????????????DENY
????????
????
????
????"INFO_FILE"?class="ch.qos.logback.core.rolling.RollingFileAppender">
????????
????????${log.path}/edu_info.log
????????
????????
????????????%d{yyyy-MM-dd?HH:mm:ss.SSS}?[%thread]?%-5level?%logger{50}?-?%msg%n
????????????UTF-8
????????
????????
????????"ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
????????????
????????????${log.path}/web-info-%d{yyyy-MM-dd}.%i.log
????????????"ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
????????????????100MB
????????????
????????????
????????????15
????????
????????
????????"ch.qos.logback.classic.filter.LevelFilter">
????????????info
????????????ACCEPT
????????????DENY
????????
????
????
????"WARN_FILE"?class="ch.qos.logback.core.rolling.RollingFileAppender">
????????
????????${log.path}/edu_warn.log
????????
????????
????????????%d{yyyy-MM-dd?HH:mm:ss.SSS}?[%thread]?%-5level?%logger{50}?-?%msg%n
????????????UTF-8 ?
????????
????????
????????"ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
????????????${log.path}/web-warn-%d{yyyy-MM-dd}.%i.log
????????????"ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
????????????????100MB
????????????
????????????
????????????15
????????
????????
????????"ch.qos.logback.classic.filter.LevelFilter">
????????????warn
????????????ACCEPT
????????????DENY
????????
????
????
????"ERROR_FILE"?class="ch.qos.logback.core.rolling.RollingFileAppender">
????????
????????${log.path}/edu_error.log
????????
????????
????????????%d{yyyy-MM-dd?HH:mm:ss.SSS}?[%thread]?%-5level?%logger{50}?-?%msg%n
????????????UTF-8 ?
????????
????????
????????"ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
????????????${log.path}/web-error-%d{yyyy-MM-dd}.%i.log
????????????"ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
????????????????100MB
????????????
????????????
????????????15
????????
????????
????????"ch.qos.logback.classic.filter.LevelFilter">
????????????ERROR
????????????ACCEPT
????????????DENY
????????
????
????
????
????
????
????
????"dev">
????????"com.cms"?level="info"/>
????????"info">
????????????"CONSOLE"?/>
????????????"DEBUG_FILE"?/>
????????????"INFO_FILE"?/>
????????????"WARN_FILE"?/>
????????????"ERROR_FILE"?/>
????????
????
????
????"pro">
????????"com.cms"?level="warn"/>
????????"info">
????????????"ERROR_FILE"?/>
????????????"WARN_FILE"?/>
????????
????
日志收集異常信息
異常信息文件工具類(lèi)
@Slf4j
public?class?ExceptionUtil?{
????/**
?????*?打印異常信息
?????*/
????public?static?String?getMessage(Exception?e)?{
????????String?swStr?=?null;
????????try?(StringWriter?sw?=?new?StringWriter();?PrintWriter?pw?=?new?PrintWriter(sw))?{
????????????e.printStackTrace(pw);
????????????pw.flush();
????????????sw.flush();
????????????swStr?=?sw.toString();
????????}?catch?(IOException?ex)?{
????????????ex.printStackTrace();
????????????log.error(ex.getMessage());
????????}
????????return?swStr;
????}
}
修改統(tǒng)一異常處理器,將異常方法中的直接打印改為日志輸入并打印
//?...
import?lombok.extern.slf4j.Slf4j;
@ControllerAdvice
@Slf4j
public?class?GlobalExceptionHandler?{
????/**--------?通用異常處理方法?--------**/
????@ExceptionHandler(Exception.class)
????@ResponseBody
????public?R?error(Exception?e)?{
????????//?e.printStackTrace();
????????log.error(ExceptionUtil.getMessage(e));
????????return?R.error();
????}
???//?...
}
日志的環(huán)境即spring.profiles.acticve,跟隨項(xiàng)目啟動(dòng); 啟動(dòng)后,即可到自定目錄查找到生成的日志文件; 本地idea調(diào)試時(shí),推薦Grep Console插件可實(shí)現(xiàn)控制臺(tái)的自定義顏色輸出
推薦閱讀:
大學(xué)生“暗網(wǎng)”發(fā)現(xiàn)“財(cái)富密碼”,結(jié)果悲劇了~
Docker鏡像優(yōu)化:從1.16GB到22.4MB,真強(qiáng)!
最近面試BAT,整理一份面試資料《Java面試BATJ通關(guān)手冊(cè)》,覆蓋了Java核心技術(shù)、JVM、Java并發(fā)、SSM、微服務(wù)、數(shù)據(jù)庫(kù)、數(shù)據(jù)結(jié)構(gòu)等等。
朕已閱?

