1.2. Http響應(yīng)內(nèi)容統(tǒng)一封裝
我們在開發(fā)前端
和后端
進(jìn)行交互服務(wù)過程中玫霎,受制于前后端的工作職責(zé)明確,在交互協(xié)議的定義上理解也較為不同妈橄,造成一個項(xiàng)目服務(wù)中重復(fù)定義交互內(nèi)容以及編碼上重復(fù)編寫庶近,不利于項(xiàng)目維護(hù)。所以基于此眷蚓,將后端
按照約定請求URL路徑鼻种,并傳入相關(guān)參數(shù),后端
服務(wù)器接收請求沙热,進(jìn)行業(yè)務(wù)處理叉钥,返回數(shù)據(jù)給前端,進(jìn)行再次封裝篙贸,供前端以及外部調(diào)用投队。
通常情況下我們后端
返回給前端
都會采用 JSON
的定義,具體如下:
{
// 返回狀態(tài)碼
code:integer,
// 返回信息描述
message:string,
// 返回值
data:object
}
- code狀態(tài)碼
在狀態(tài)碼的定義上爵川,在滿足業(yè)務(wù)需求的基礎(chǔ)上蛾洛,避免凌亂,一般業(yè)界同行做法就是參考HTTP請求返回的狀態(tài)碼雁芙。
具體如 百度 - HTTP狀態(tài)碼轧膘。
這里我貼出我將我項(xiàng)目中的常用的羅列出來,供大家參考兔甘。
package xyz.wongs.drunkard.base.message.enums;
/**
* @ClassName
* @Description
* 1000~1999 區(qū)間表示參數(shù)錯誤
* 2000~2999 區(qū)間表示用戶錯誤
* 3000~3999 區(qū)間表示接口異常
* @author WCNGS@QQ.COM
* @Github <a>https://github.com/rothschil</a>
* @date 2020/8/2 13:31
* @Version 1.0.0
*/
public enum ResultCode {
/** 成功 **/
SUCCESS(0,"成功"),
/** 失敗 **/
FAILURE(-1,"失敗"),
EXCEPTION(201, "未知異常"),
RUNTIME_EXCEPTION(202, "運(yùn)行時異常"),
NULL_POINTER_EXCEPTION(203, "空指針異常"),
CLASS_CAST_EXCEPTION(204, "類型轉(zhuǎn)換異常"),
IO_EXCEPTION(205, "IO異常"),
SYSTEM_EXCEPTION(210, "系統(tǒng)異常"),
NOT_FOUND(404, "Not Found"),
/**
* 1000~1999 區(qū)間表示參數(shù)錯誤
*/
PARAMS_IS_INVALID(1001,"參數(shù)無效"),
PARAMS_IS_BANK(1002,"參數(shù)為空"),
PARAMS_TYPE_BIND_ERROR(1003,"參數(shù)類型錯誤"),
PARAMS_NOT_COMPLETE(1004,"參數(shù)缺失"),
/**
* 2000~2999 區(qū)間表示用戶錯誤
*/
USER_NOT_LOGGED_IN(2001,"用戶未登錄谎碍,訪問路徑需要驗(yàn)證"),
USER_NOT_LOGIN_ERROR(2002,"用戶不存在或密碼錯誤"),
USER_ACCOUNT_FORBIDDEN(2003,"用戶被禁用"),
USER_NOT_EXIST(2004,"用戶不存在"),
USER_HAS_EXISTED(2005,"用戶已存在"),
USER_IS_EXPIRED(2006,"用戶賬號已過期"),
USER_FIRST_LANDING(2007, "首次登錄"),
USER_TOKEN_EXPIRED(2008,"Token過期"),
USER_TOKEN_GENERTATION_FAIL(2009,"生成Token失敗"),
USER_SIGN_VERIFI_NOT_COMPLIANT(2010,"簽名校驗(yàn)不合規(guī)"),
USER_PASSWORD_RESET_FAILED(2011, "重置密碼失敗"),
USER_UNKONWN_INDENTITY(2012, "未知身份"),
MANY_USER_LOGINS(2111,"多用戶在線"),
TOO_MANY_PASSWD_ENTER(2112, "密碼輸入次數(shù)過多"),
VERIFICATION_CODE_INCORECT(2202,"圖形驗(yàn)證碼不正確"),
VERIFICATION_CODE_FAIL(2203,"圖形驗(yàn)證碼生產(chǎn)失敗"),
/**
* 3000~3999 區(qū)間表示接口異常
*/
API_EXCEPTION(3000, "接口異常"),
API_NOT_FOUND_EXCEPTION(3002, "接口不存在"),
API_REQ_MORE_THAN_SET(3003, "接口訪問過于頻繁,請稍后再試"),
API_IDEMPOTENT_EXCEPTION(3004, "接口不可以重復(fù)提交洞焙,請稍后再試"),
API_PARAM_EXCEPTION(3005, "參數(shù)異常"),
API_PARAM_MISSING_EXCEPTION(3006, "缺少參數(shù)"),
API_METHOD_NOT_SUPPORTED_EXCEPTION(3007, "不支持的Method類型"),
API_METHOD_PARAM_TYPE_EXCEPTIION(3008, "參數(shù)類型不匹配"),
ARRAY_EXCEPTION(11001, "數(shù)組異常"),
ARRAY_OUT_OF_BOUNDS_EXCEPTION(11002, "數(shù)組越界異常"),
JSON_SERIALIZE_EXCEPTION(30000, "序列化數(shù)據(jù)異常"),
JSON_DESERIALIZE_EXCEPTION(30001, "反序列化數(shù)據(jù)異常"),
READ_RESOURSE_EXCEPTION(31002, "讀取資源異常"),
READ_RESOURSE_NOT_FOUND_EXCEPTION(31003, "資源不存在異常"),
DATA_EXCEPTION(32004, "數(shù)據(jù)異常"),
DATA_NOT_FOUND_EXCEPTION(32005, "未找到符合條件的數(shù)據(jù)異常"),
DATA_CALCULATION_EXCEPTION(32006, "數(shù)據(jù)計(jì)算異常"),
DATA_COMPRESS_EXCEPTION(32007, "數(shù)據(jù)壓縮異常"),
DATA_DE_COMPRESS_EXCEPTION(32008, "數(shù)據(jù)解壓縮異常"),
DATA_PARSE_EXCEPTION(32009, "數(shù)據(jù)轉(zhuǎn)換異常"),
ENCODING_EXCEPTION(33006, "編碼異常"),
ENCODING_UNSUPPORTED_EXCEPTION(33006, "編碼不支持異常"),
DATE_PARSE_EXCEPTION(34001, "日期轉(zhuǎn)換異常"),
MAILE_SEND_EXCEPTION(35001, "郵件發(fā)送異常");
/**
*
*/
private Integer code;
/**
*
*/
private String msg;
ResultCode(Integer code, String msg) {
this.code = code;
this.msg = msg;
}
public Integer getCode() {
return code;
}
public void setCode(Integer code) {
this.code = code;
}
public String getMsg() {
return msg;
}
public void setMsg(String msg) {
this.msg = msg;
}
}
- message內(nèi)容
這個不解釋啦蟆淀, 就是編碼的文字的意義,說清楚就行澡匪,沒必要太較真熔任,自行腦補(bǔ)。
- data數(shù)據(jù)
這就是業(yè)務(wù)具體數(shù)據(jù)啦唁情,根據(jù)具體業(yè)務(wù)疑苔,內(nèi)容也不同,這一章節(jié)也沒必要說甸鸟。
這里小結(jié)下惦费,我們除了要有需要定義的內(nèi)容有兩塊:返回的JSON消息體(這里區(qū)分正常響應(yīng)返回兵迅、異常響應(yīng)返回),還需要一套狀態(tài)碼詳細(xì)定義薪贫;再有我們這里做的是WEB恍箭,既然做通用,怎能少攔截器瞧省。
擺脫了繁瑣的文字扯夭,下面開始張羅著貼實(shí)現(xiàn)代碼啦。
1.2.1. 消息體
結(jié)合我們定義的狀態(tài)碼鞍匾,我們返回的消息體主要實(shí)現(xiàn)一個 Serializable
勉抓,不要問我為什么。
1.2.1.1. 正常響應(yīng)
package xyz.wongs.drunkard.base.message.response;
import lombok.Data;
import xyz.wongs.drunkard.base.message.enums.ResultCode;
import java.io.Serializable;
/**
* @ClassName
* @Description
* @author WCNGS@QQ.COM
* @Github <a>https://github.com/rothschil</a>
* @date 2020/8/2 13:48
* @Version 1.0.0
*/
@Data
public class Result implements Serializable {
private static final long serialVersionUID = -4505655308965878999L;
private Integer code;
private String message;
private Object data;
private Result() {
}
public Result(ResultCode resultCode, Object data) {
this.code = resultCode.getCode();
this.message = resultCode.getMsg();
this.data = data;
}
private void setResultCode(ResultCode resultCode) {
this.code = resultCode.getCode();
this.message = resultCode.getMsg();
}
/** 返回成功
* @Description
* @param
* @return xyz.wongs.drunkard.base.message.response.R
* @throws
* @date 20/11/13 17:15
*/
public static Result success() {
Result result = new Result();
result.setResultCode(ResultCode.SUCCESS);
return result;
}
/** 返回成功
* @Description
* @param
* @return xyz.wongs.drunkard.base.message.response.R
* @throws
* @date 20/11/13 17:15
*/
public static Result success(Object data) {
Result result = new Result();
result.setResultCode(ResultCode.SUCCESS);
result.setData(data);
return result;
}
/** 返回失敗
* @Description
* @param
* @return xyz.wongs.drunkard.base.message.response.R
* @throws
* @date 20/11/13 17:15
*/
public static Result fail(Integer code, String message) {
Result result = new Result();
result.setCode(code);
result.setMessage(message);
return result;
}
/** 返回失敗
* @Description
* @param
* @return xyz.wongs.drunkard.base.message.response.R
* @throws
* @date 20/11/13 17:15
*/
public static Result fail(ResultCode resultCode) {
Result result = new Result();
result.setResultCode(resultCode);
return result;
}
}
1.2.1.2. 異常響應(yīng)
package xyz.wongs.drunkard.base.message.response;
import lombok.Data;
import xyz.wongs.drunkard.base.message.enums.ResultCode;
import java.io.Serializable;
/**
* @author WCNGS@QQ.COM
* @ClassName ErrorResult
* @Description 異常錯誤的返回信息實(shí)體
* @Github <a>https://github.com/rothschil</a>
* @date 20/11/18 10:42
* @Version 1.0.0
*/
@Data
public class ErrorResult implements Serializable {
private static final long serialVersionUID = -4505655308965878999L;
/**
* 錯誤編碼
**/
private Integer code;
/**
* 消息描述
**/
private String msg;
/**
* 錯誤
**/
private String exception;
public static ErrorResult fail(ResultCode resultCode, Throwable e, String message) {
ErrorResult errorResult = ErrorResult.fail(resultCode, e);
errorResult.setMsg(message);
return errorResult;
}
public static ErrorResult fail(ResultCode resultCode, Throwable e) {
ErrorResult errorResult = new ErrorResult();
errorResult.setCode(resultCode.getCode());
errorResult.setMsg(resultCode.getMsg());
errorResult.setException(e.getClass().getName());
return errorResult;
}
public static ErrorResult fail(Integer code, String message) {
ErrorResult errorResult = new ErrorResult();
errorResult.setCode(code);
errorResult.setMsg(message);
return errorResult;
}
}
這樣兩個消息體就寫完啦候学。
1.2.2. 攔截器
我們這里需要做的就是利用攔截器攔截請求,檢查判斷是否此請求返回的值需要包裝纵散。核心就是判斷一個注解annoation
是否存在方法或類中梳码。
為了演示的完整,我將代碼貼完整伍掀。
1.2.2.1. Annoation注解
/**
* @ClassName ResponseResult
* @Description
* @author WCNGS@QQ.COM
* @Github <a>https://github.com/rothschil</a>
* @date 20/10/30 21:57
* @Version 1.0.0
*/
@Target({ElementType.TYPE,ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface ResponseResult {
}
1.2.2.2. 攔截器
package xyz.wongs.drunkard.base.interceptor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.HandlerInterceptor;
import xyz.wongs.drunkard.base.message.annoation.ResponseResult;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.lang.reflect.Method;
/**
* @author WCNGS@QQ.COM
* @ClassName ResponseResultInterceptor
* @Description 請求的攔截器
* @Github <a>https://github.com/rothschil</a>
* @date 20/10/30 22:08
* @Version 1.0.0
*/
@Slf4j
@Component
public class ResponseResultInterceptor implements HandlerInterceptor {
private static final String RESPONSE_RESULT_ANN = "RESPONSE-RESULT-ANN";
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
if (handler instanceof HandlerMethod) {
final HandlerMethod handlerMethod = (HandlerMethod) handler;
final Class<?> clazz = handlerMethod.getBeanType();
final Method method = handlerMethod.getMethod();
if (clazz.isAnnotationPresent(ResponseResult.class)) {
request.setAttribute(RESPONSE_RESULT_ANN, clazz.getAnnotation(ResponseResult.class));
} else if (method.isAnnotationPresent(ResponseResult.class)) {
request.setAttribute(RESPONSE_RESULT_ANN, method.getAnnotation(ResponseResult.class));
}
}
return true;
}
}
著十幾行代碼的核心處理邏輯掰茶,就是獲取此請求Annoation注解,是否需要返回值包裝蜜笤,并設(shè)置一個屬性標(biāo)記濒蒋,交由下一處理ResponseResultHandler
來具體封裝返回值。
細(xì)心的人會發(fā)現(xiàn)這里只處置正常成功的內(nèi)容返回把兔,對于異常的內(nèi)容并未處置沪伙。關(guān)于異常處置我理解統(tǒng)一放在一起來編寫,這樣代碼結(jié)構(gòu)性會更好县好。由此引出下一章節(jié)围橡,全局異常
。
package xyz.wongs.drunkard.base.handler;
import lombok.extern.slf4j.Slf4j;
import org.springframework.core.MethodParameter;
import org.springframework.http.MediaType;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.server.ServerHttpRequest;
import org.springframework.http.server.ServerHttpResponse;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice;
import xyz.wongs.drunkard.base.message.annoation.ResponseResult;
import xyz.wongs.drunkard.base.message.response.Result;
import javax.servlet.http.HttpServletRequest;
/**
* @ClassName ResponseResultHandler
* @Description 消息返回體
* @author WCNGS@QQ.COM
* @Github <a>https://github.com/rothschil</a>
* @date 20/11/10 09:28
* @Version 1.0.0
*/
@Slf4j
@ControllerAdvice(basePackages = "xyz.wongs.drunkard")
public class ResponseResultHandler implements ResponseBodyAdvice<Object> {
private static final String RESPONSE_RESULT_ANN = "RESPONSE-RESULT-ANN";
/**
* @Description 判斷是否要執(zhí)行 beforeBodyWrite 方法缕贡,true為執(zhí)行翁授,false不執(zhí)行,有注解標(biāo)記的時候處理返回值
* @param returnType
* @param converterType
* @return boolean
* @throws
* @date 20/11/13 10:50
*/
@Override
public boolean supports(MethodParameter returnType, Class<? extends HttpMessageConverter<?>> converterType) {
ServletRequestAttributes sra =(ServletRequestAttributes)RequestContextHolder.getRequestAttributes();
HttpServletRequest request = sra.getRequest();
ResponseResult responseResult = (ResponseResult)request.getAttribute(RESPONSE_RESULT_ANN);
return responseResult==null?false:true;
}
@Override
public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectContentType, Class<? extends HttpMessageConverter<?>> selectConverterType, ServerHttpRequest request, ServerHttpResponse response) {
log.error(" ENTER MSG .... Excu");
if(body instanceof Result){
return (Result) body;
}
return Result.success(body);
}
}
1.2.2.3. 全局異常
這里所有的異常都使用到 ErrorResult
類晾咪。
package xyz.wongs.drunkard.base.message.exception;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpStatus;
import org.springframework.validation.BindException;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import xyz.wongs.drunkard.base.message.enums.ResultCode;
import xyz.wongs.drunkard.base.message.response.ErrorResult;
import javax.servlet.http.HttpServletRequest;
import javax.validation.ConstraintViolationException;
/**
* @author WCNGS@QQ.COM
* @ClassName GlobalExceptionHandler
* @Description 全局異常處理Handler
* @Github <a>https://github.com/rothschil</a>
* @date 2019/9/23 15:03
* @Version 1.0.0
*/
@RestControllerAdvice
@Slf4j
public class GlobalExceptionHandler {
/**
* 參數(shù)校驗(yàn)不通過
*
* @param ex
* @return xyz.wongs.drunkard.base.message.response.ErrorResult
* @throws
* @author WCNGS@QQ.COM
* @See
* @date 2019/9/23 17:53
* @since
*/
@ExceptionHandler(value = ConstraintViolationException.class)
@ResponseBody
public ErrorResult handleConstraintViolationException(ConstraintViolationException ex) {
log.error("ConstraintViolationException msg:{}", ex.getMessage());
return ErrorResult.fail(ResultCode.PARAMS_IS_INVALID, ex);
}
/**
* 自定義異常
*
* @param request
* @param ex
* @return xyz.wongs.drunkard.base.message.response.ErrorResult
* @throws
* @author WCNGS@QQ.COM
* @See
* @date 2019/9/23 17:53
* @since
*/
@org.springframework.web.bind.annotation.ExceptionHandler(DrunkardException.class)
@ResponseBody
public ErrorResult handleWeathertopException(HttpServletRequest request, DrunkardException ex) {
log.error("WeathertopRuntimeException code:{},msg:{}", ex.getCode(), ex.getMessage());
return ErrorResult.fail(ex.getCode(), ex.getMessage());
}
/**
* @param e
* @param request
* @return xyz.wongs.drunkard.base.message.response.ErrorResult
* @throws
* @Description 攔截拋出的異常收擦,@ResponseStatus:用來改變響應(yīng)狀態(tài)碼
* @date 20/11/13 11:14
*/
@ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
@ExceptionHandler(Throwable.class)
public ErrorResult handlerThrowable(Throwable e, HttpServletRequest request) {
log.error("發(fā)生未知異常!原因是: ", e);
ErrorResult error = ErrorResult.fail(ResultCode.RUNTIME_EXCEPTION, e);
return error;
}
/**
* @param e
* @param request
* @return xyz.wongs.drunkard.base.message.response.ErrorResult
* @throws
* @Description 參數(shù)校驗(yàn)異常
* @date 20/11/13 11:14
*/
@ExceptionHandler(BindException.class)
public ErrorResult handleBindExcpetion(BindException e, HttpServletRequest request) {
log.error("發(fā)生參數(shù)校驗(yàn)異常谍倦!原因是:", e);
ErrorResult error = ErrorResult.fail(ResultCode.API_PARAM_EXCEPTION, e, e.getAllErrors().get(0).getDefaultMessage());
return error;
}
@ExceptionHandler(MethodArgumentNotValidException.class)
public ErrorResult handleMethodArgumentNotValidException(MethodArgumentNotValidException e, HttpServletRequest request) {
log.error("發(fā)生參數(shù)校驗(yàn)異常塞赂!原因是:", e);
ErrorResult error = ErrorResult.fail(ResultCode.API_PARAM_EXCEPTION, e, e.getBindingResult().getAllErrors().get(0).getDefaultMessage());
return error;
}
}
1.2.3. 例子
以上雖然將所有代碼貼出,這列為湊完整昼蛀,順道將寫個例子來减途,寫個 Controller
package xyz.wongs.drunkard.war3.web.controller;
import com.github.hiwepy.ip2region.spring.boot.IP2regionTemplate;
import com.github.hiwepy.ip2region.spring.boot.ext.RegionAddress;
import lombok.extern.slf4j.Slf4j;
import org.nutz.plugins.ip2region.DataBlock;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import xyz.wongs.drunkard.base.aop.annotion.ApplicationLog;
import xyz.wongs.drunkard.base.message.annoation.ResponseResult;
import xyz.wongs.drunkard.base.message.exception.DrunkardException;
import xyz.wongs.drunkard.war3.limit.RequestLimit;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
/**
* @ClassName IndexController
* @Description
* @author WCNGS@QQ.COM
* @Github <a>https://github.com/rothschil</a>
* @date 20/11/18 11:00
* @Version 1.0.0
*/
@Slf4j
@RestController
@ResponseResult
public class IndexController {
@RequestLimit(maxCount=3,second=20)
@ApplicationLog
@GetMapping("/test")
public Map<String, Object> test() {
HashMap<String, Object> data = new HashMap<>(3);
data.put("info", "測試成功");
return data;
}
@ApplicationLog
@GetMapping("/fail")
public Integer error() {
// 查詢結(jié)果數(shù)
int res = 0;
if( res == 0 ) {
throw new DrunkardException("沒有數(shù)據(jù)");
}
return res;
}
@Autowired
IP2regionTemplate template;
/** 根據(jù)輸入IP地址酣藻,返回解析后的地址
* @Description
* @param ip
* @return xyz.wongs.drunkard.base.message.response.ResponseResult
* @throws
* @date 2020/8/17 18:26
*/
@GetMapping(value = "/convert/{ip}")
public DataBlock convertDataBlock(@PathVariable String ip){
DataBlock dataBlock = null;
try {
dataBlock = template.binarySearch(ip);
} catch (IOException e) {
e.printStackTrace();
}
return dataBlock;
}
/** 根據(jù)輸入IP地址,返回解析后的地址
* @Description
* @param ip
* @return xyz.wongs.drunkard.base.message.response.ResponseResult
* @throws
* @date 2020/8/17 18:26
*/
@RequestLimit(maxCount=3)
@GetMapping(value = "/region/{ip}")
public RegionAddress convert(@PathVariable String ip){
RegionAddress regionAddress = null;
try {
regionAddress = template.getRegionAddress(ip);
} catch (IOException e) {
e.printStackTrace();
}
return regionAddress;
}
@GetMapping(value = "/region/ip={ip}")
public RegionAddress caseInsensitive(@PathVariable String ip){
RegionAddress regionAddress = null;
try {
regionAddress = template.getRegionAddress(ip);
} catch (IOException e) {
e.printStackTrace();
}
return regionAddress;
}
}
訪問 http://localhost:9090/region/ip=109.27.45.12
這是我之前一個例子鳍置,用來解析IP地址辽剧,獲取地域信息的。