1.接口調(diào)用
理想情況來說,客戶端想要的是目標接口成功響應(yīng)的結(jié)果。比如查詢用戶信息,服務(wù)器只需要返回我想要的用戶信息給我就可以了。類似:
{
"name":"zouwei",
"age":26,
......
}
當然,以上也只是停留在理想上。正常接口大多數(shù)情況下,會正常響應(yīng)給出用戶信息。但是在非正常情況下呢,比如訪問量劇增,服務(wù)器響應(yīng)不過來了;或者因為用戶的查詢參數(shù)不合理,導(dǎo)致查詢不出任何結(jié)果;亦或者程序的代碼不夠健壯,在某種情況下才會報錯;此時,有一些用戶就不能順利地獲取用戶信息,那么他們得到的響應(yīng)數(shù)據(jù)是什么呢?頁面的表現(xiàn)形式是怎樣的呢?假如是編碼問題,如何才能讓開發(fā)人員快速的定位到報錯位置呢?
2.統(tǒng)一響應(yīng)數(shù)據(jù)結(jié)構(gòu)
為了避免程序沒有給出正確的響應(yīng)數(shù)據(jù)導(dǎo)致客戶端不知道如何與用戶交互,我們需要協(xié)商出一個統(tǒng)一的響應(yīng)數(shù)據(jù)結(jié)構(gòu)。也就是說,我們需要為異常情況考慮一個合理的交互方式,讓用戶知道,我們的服務(wù),只是暫時有一些問題(ps:這里的問題不僅僅是指服務(wù)器的問題,還包括用戶操作正確性的問題)。也能讓開發(fā)人員通過響應(yīng)數(shù)據(jù)了解到程序的出錯信息,包括出錯位置,異常類型,甚至包括修正方式。
成功示例
{
"message":"處理成功",
"data":{
"name":"zouwei",
"age":26
}
}
失敗示例
{
"message":"服務(wù)擁堵,請稍后再試!",
"data":null
}
這樣的數(shù)據(jù)結(jié)構(gòu)設(shè)計貌似能讓用戶感覺到我們滿滿的誠意,客戶端開發(fā)人員可以在data為空的時候,把“message”的數(shù)據(jù)展示給用戶,告知用戶,我們的服務(wù)當前的一個狀態(tài),并指示用戶正確的操作方式。
可是細想一下,對于客戶端開發(fā)人員來說,data為null的時候一定就是異常的響應(yīng)嘛?這樣的判斷顯然過于武斷。比如某些添加,或者修改的接口,data完全沒有必要返回任何數(shù)據(jù)。所以我們還需要給出某個標識讓客戶端知道我們確實是處理成功了。
成功示例
{
//錯誤碼
"code":0,
"message":"處理成功",
"data":{
"name":"zouwei",
"age":26
}
}
失敗示例
{
//錯誤碼
"code":10001,
"message":"輸入的手機號碼還未注冊!",
"data":null
}
現(xiàn)在,客戶端開發(fā)人員可以根據(jù)協(xié)商好的錯誤碼區(qū)分當前的請求是否被成功處理,而不是通過判斷data=null來確定是否成功,避免潛在的編碼風(fēng)險,提高開發(fā)體驗度。
假如服務(wù)器代碼已經(jīng)非常健壯的話,上面的數(shù)據(jù)結(jié)構(gòu)是完全沒有問題的,可是還是過于理論。因為在實際場景中,沒有人能保證代碼沒有任何潛在的問題,這類問題就不屬于用戶造成的問題了,而是在編碼過程中因為各種原則造成的紕漏或者不夠健壯,這類問題是可以避免的。比如常見的NullPointerException,NumberFormatException等,這類異常一旦發(fā)生,響應(yīng)數(shù)據(jù)應(yīng)該怎么定義,因為這一類異常不需要事先聲明,所以不能準確地對這一類異常定性,那么可以做一個默認的code,歸類為“未知錯誤”。為了能讓開發(fā)人員在開發(fā)階段能盡快地定位到異常類型和位置,可以考慮添加一個字斷展示異常堆棧。
示例
{
//錯誤碼
"code":-1,
//異常堆棧,只有在開發(fā)和測試環(huán)境打開
"error":"java.lang.NullPointerException",
"message":"未知錯誤!",
"data":null
}
(ps:堆棧信息字段只是簡單表示,實際情況會包含異常位置,異常詳細信息等)
上述的error字段僅僅在開發(fā)和測試階段出現(xiàn),線上環(huán)境需要去掉??赏ㄟ^配置化的方式實現(xiàn)這個功能。
3.java服務(wù)端實現(xiàn)
依賴
ext {//依賴版本
springBootVersion = "2.2.2.RELEASE"
lombokVersion = "1.18.10"
guavaVersion = "28.1-jre"
commonsLangversion = "3.9"
}
dependencies {
annotationProcessor("org.projectlombok:lombok:$lombokVersion")
compileOnly("org.projectlombok:lombok:$lombokVersion")
compile("org.springframework.boot:spring-boot-starter-web:$springBootVersion")
compile("org.springframework.boot:spring-boot-configuration-processor:$springBootVersion")
compile("org.apache.commons:commons-lang3:$commonsLangversion")
compile("com.google.guava:guava:$guavaVersion")
compile("org.yaml:snakeyaml:1.25")
compile("org.hibernate.validator:hibernate-validator:6.1.0.Final")
}
統(tǒng)一的響應(yīng)數(shù)據(jù)結(jié)構(gòu)
import com.zx.eagle.common.exception.EagleException;
import lombok.Data;
import org.apache.commons.lang3.StringUtils;
import java.util.List;
/** @author zouwei */
@Data
public class CommonResponse<T> {
/** 成功CODE */
private static final String DEFAULT_SUCCESS_CODE = "0";
/** 成功MESSAGE */
private static final String DEFAULT_SUCCESS_MSG = "SUCCESS";
/** 響應(yīng)碼 */
private String code;
/** 異常信息 */
private String error;
/** 用戶提示 */
private String message;
/** 參數(shù)驗證錯誤 */
private List<EagleException.ValidMessage> validMessage;
/** 響應(yīng)數(shù)據(jù) */
private T data;
private CommonResponse() {}
private CommonResponse(String code, String error, String message) {
this();
this.code = code;
this.message = message;
this.error = error;
}
/**
* 成功響應(yīng)
*
* @param data
*/
private CommonResponse(T data) {
this(DEFAULT_SUCCESS_CODE, StringUtils.EMPTY, DEFAULT_SUCCESS_MSG);
this.data = data;
}
/** @param e */
private CommonResponse(EagleException e, String error) {
this();
this.code = e.getCode();
this.message = e.getTips();
this.error = error;
this.validMessage = e.getValidMessages();
}
/**
* 用戶行為導(dǎo)致的錯誤
*
* @param code
* @param error
* @param message
* @param <T>
* @return
*/
public static <T> CommonResponse<T> exceptionInstance(
String code, String error, String message) {
return new CommonResponse<>(code, error, message);
}
/**
* 正常響應(yīng)
*
* @param data
* @param <T>
* @return
*/
public static <T> CommonResponse<T> successInstance(T data) {
return new CommonResponse<>(data);
}
/**
* 正常響應(yīng)
*
* @param <T>
* @return
*/
public static <T> CommonResponse<T> successInstance() {
return (CommonResponse<T>) successInstance(StringUtils.EMPTY);
}
/**
* 用戶行為導(dǎo)致的錯誤
*
* @param e
* @param <T>
* @return
*/
public static <T> CommonResponse<T> exceptionInstance(EagleException e, String error) {
return new CommonResponse<>(e, error);
}
}
統(tǒng)一異常類型
import com.zx.eagle.common.cache.ExceptionTipsCache;
import lombok.Data;
import java.util.List;
import java.util.Objects;
/** @author zouwei */
@Data
public class EagleException extends Exception {
/** 參數(shù)驗證異常 */
private static final String VALID_ERROR = "VALID_ERROR";
/** 默認的未知錯誤 */
private static final String DEFAULT_TPIPS_KEY = "UNKNOWN_ERROR";
/** 錯誤碼 */
private String code;
/** 用戶提示Key */
private String tipsKey;
/** 用戶提示 */
private String tips;
/** 驗證異常提示 */
private List<ValidMessage> validMessages;
private EagleException(String message) {
super(message);
}
private EagleException(String code, String tipsKey, String tips, String message) {
this(message);
this.code = code;
this.tipsKey = tipsKey;
this.tips = tips;
}
/**
* 創(chuàng)建異常
*
* @param tipsKey
* @param message
* @return
*/
public static EagleException newInstance(String tipsKey, String message) {
ExceptionTipsCache.ExceptionTips tips = ExceptionTipsCache.get(tipsKey);
return new EagleException(tips.getCode(), tipsKey, tips.getTips(), message);
}
/**
* 未知異常
*
* @param message
* @return
*/
public static EagleException unknownException(String message) {
return newInstance(DEFAULT_TPIPS_KEY, message);
}
/**
* 參數(shù)驗證錯誤
*
* @param validMessages
* @return
*/
public static EagleException validException(List<ValidMessage> validMessages) {
ExceptionTipsCache.ExceptionTips tips = ExceptionTipsCache.get(VALID_ERROR);
final String validCode = tips.getCode();
EagleException eagleException =
new EagleException(validCode, VALID_ERROR, tips.getTips(), tips.getTips());
validMessages.forEach(
msg -> {
ExceptionTipsCache.ExceptionTips tmpTips = null;
try {
tmpTips = ExceptionTipsCache.get(msg.getTipsKey());
} catch (Exception e) {
msg.setTips(msg.getDefaultMessage());
// 參數(shù)驗證錯誤
msg.setCode(validCode);
}
if (Objects.nonNull(tmpTips)) {
msg.setTips(tmpTips.getTips());
msg.setCode(tmpTips.getCode());
}
});
eagleException.setValidMessages(validMessages);
return eagleException;
}
@Data
public static class ValidMessage {
private String fieldName;
private Object fieldValue;
private String code;
private String tips;
private String tipsKey;
private String defaultMessage;
}
}
考慮到用戶提示信息需要避免直接硬編碼,建議配置化,所以用到了ExceptionTipsCache這個類實現(xiàn)了異常提示信息配置化,緩存化,國際化
import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.core.io.ClassPathResource;
import org.yaml.snakeyaml.Yaml;
import java.io.IOException;
import java.io.InputStream;
import java.util.Map;
import java.util.StringJoiner;
import java.util.concurrent.TimeUnit;
/** @author zouwei */
@Slf4j
public class ExceptionTipsCache {
/** 指定的classpath文件夾 */
private static String classpath;
/** 默認的文件夾 */
private static final String DEFAULT_DIR = "config/tips";
/** 默認的國際化 */
private static final String DEFAULT_I18N = "zh-cn";
/** 提示文件后綴 */
private static final String TIPS_FILE_SUFFIX = "_tips";
/** 構(gòu)建一個本地緩存 */
private static final LoadingCache<String, ExceptionTips> CACHE =
CacheBuilder.newBuilder()
// 初始化100個
.initialCapacity(100)
// 最大10000
.maximumSize(10000)
// 30分鐘沒有讀寫操作數(shù)據(jù)就過期
.expireAfterAccess(30, TimeUnit.MINUTES)
// 只有當內(nèi)存不夠的時候才會value才會被回收
.softValues()
.build(
new CacheLoader<String, ExceptionTips>() {
// 如果get()沒有拿到緩存,直接點用load()加載緩存
@Override
public ExceptionTips load(String key) throws IOException {
return getTips(key);
}
/**
* 在調(diào)用getAll()的時候,如果沒有找到緩存,就會調(diào)用loadAll()加載緩存
*
* @param keys
* @return
* @throws Exception
*/
@Override
public Map<String, ExceptionTips> loadAll(
Iterable<? extends String> keys) throws Exception {
// 暫不支持
return super.loadAll(keys);
}
});
/**
* 設(shè)置指定的classpath
*
* @param classpath
*/
public static void setClasspath(String classpath) {
ExceptionTipsCache.classpath = StringUtils.isBlank(classpath) ? DEFAULT_DIR : classpath;
}
/**
* @param key
* @return
*/
public static ExceptionTips get(String key) {
try {
return CACHE.get(key);
} catch (Exception e) {
throw new RuntimeException("沒有找到指定的配置:" + key);
}
}
/**
* 加載默認yaml進緩存
*
* @return
* @throws IOException
*/
public static Map<String, Map<String, String>> loadTips() throws IOException {
return loadTips(null, DEFAULT_I18N);
}
/**
* 加載默認yaml進緩存
*
* @param directory
* @throws IOException
*/
public static Map<String, Map<String, String>> loadTips(String directory) throws IOException {
return loadTips(directory, DEFAULT_I18N);
}
/**
* 加載指定yaml進緩存
*
* @param directory
* @param i18n
* @throws IOException
*/
public static Map<String, Map<String, String>> loadTips(String directory, String i18n)
throws IOException {
classpath = StringUtils.isBlank(directory) ? DEFAULT_DIR : directory;
StringJoiner sj = new StringJoiner("/");
sj.add(classpath);
sj.add(i18n + TIPS_FILE_SUFFIX);
ClassPathResource resource = new ClassPathResource(sj.toString());
return doLoadTips(i18n, resource.getInputStream());
}
/**
* 添加緩存
*
* @param i18n
* @param inputStream
*/
private static Map<String, Map<String, String>> doLoadTips(
String i18n, InputStream inputStream) {
Yaml yaml = new Yaml();
Map<String, Map<String, String>> map = yaml.loadAs(inputStream, Map.class);
map.forEach(
(k, v) -> {
String code = String.valueOf(v.get("code"));
String tips = String.valueOf(v.get("tips"));
CACHE.put(i18n + ":" + k, new ExceptionTips(i18n, k, code, tips));
});
return map;
}
/**
* 沒有獲取到緩存時單獨調(diào)用
*
* @param key
* @return
* @throws IOException
*/
private static ExceptionTips getTips(String key) throws IOException {
if (StringUtils.isBlank(key)) {
throw new RuntimeException("錯誤的key值,請按照\"zh-cn:USER_NO_EXIST\"格式輸入");
}
String[] keys = StringUtils.splitByWholeSeparatorPreserveAllTokens(key, ":");
if (ArrayUtils.isNotEmpty(keys) && keys.length > 2) {
throw new RuntimeException("錯誤的key值,請按照\"zh-cn:USER_NO_EXIST\"格式輸入");
}
String i18n = DEFAULT_I18N;
String k;
if (ArrayUtils.isNotEmpty(keys) && keys.length < 2) {
k = keys[0];
} else {
i18n = keys[0];
k = keys[1];
}
Map<String, Map<String, String>> map = loadTips(classpath, i18n);
Map<String, String> v = map.get(k);
String code = String.valueOf(v.get("code"));
String tips = String.valueOf(v.get("tips"));
return new ExceptionTips(i18n, k, code, tips);
}
@Data
@AllArgsConstructor
public static class ExceptionTips {
private String i18n;
private String key;
private String code;
private String tips;
}
}
默認會加載resources里面的config/tips/zh-cn_tips文件

這是一個yml類型的文件,數(shù)據(jù)結(jié)構(gòu)如下:
UNKNOWN_ERROR:
code: -1
tips: "未知錯誤"
VALID_ERROR:
code: -2
tips: "參數(shù)驗證錯誤"
USER_NO_EXIST:
code: 11023
tips: "用戶不存在"
USER_REPEAT_REGIST:
code: 11024
tips: "重復(fù)注冊"
USER_NAME_NOT_NULL:
code: 11025
tips: "用戶名不能為空"
USER_NAME_LENGTH_LIMIT:
code: 11026
tips: "用戶名不能長度要5到10個字符"
響應(yīng)數(shù)據(jù)結(jié)構(gòu)和異常類型統(tǒng)一后,我們需要統(tǒng)一處理controller的返回數(shù)據(jù),全部包裝成CommonResponse類型的數(shù)據(jù)。
import com.zx.eagle.annotation.IgnoreResponseAdvice;
import com.zx.eagle.vo.CommonResponse;
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.RestControllerAdvice;
import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice;
import java.util.Objects;
/** @author zouwei */
@RestControllerAdvice
public class CommonResponseAdvice implements ResponseBodyAdvice<Object> {
@Override
public boolean supports(
MethodParameter returnType, Class<? extends HttpMessageConverter<?>> converterType) {
boolean ignore = false;
IgnoreResponseAdvice ignoreResponseAdvice =
returnType.getMethodAnnotation(IgnoreResponseAdvice.class);
if (Objects.nonNull(ignoreResponseAdvice)) {
ignore = ignoreResponseAdvice.value();
return !ignore;
}
Class<?> clazz = returnType.getDeclaringClass();
ignoreResponseAdvice = clazz.getDeclaredAnnotation(IgnoreResponseAdvice.class);
if (Objects.nonNull(ignoreResponseAdvice)) {
ignore = ignoreResponseAdvice.value();
}
return !ignore;
}
@Override
public Object beforeBodyWrite(
Object body,
MethodParameter returnType,
MediaType selectedContentType,
Class<? extends HttpMessageConverter<?>> selectedConverterType,
ServerHttpRequest request,
ServerHttpResponse response) {
if (Objects.isNull(body)) {
return CommonResponse.successInstance();
}
if (body instanceof CommonResponse) {
return body;
}
CommonResponse commonResponse = CommonResponse.successInstance(body);
return commonResponse;
}
}
很明顯,并不是所有的返回對象都需要包裝的,比如controller已經(jīng)返回了CommonResponse,那么就不需要包裝
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/** @author zouwei */
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE, ElementType.METHOD})
public @interface IgnoreResponseAdvice {
/**
* 是否需要被CommonResponseAdvice忽略
*
* @return
*/
boolean value() default true;
}
其次,我們還需要統(tǒng)一處理異常
import com.google.common.collect.Lists;
import com.zx.eagle.common.config.ExceptionTipsStackConfig;
import com.zx.eagle.common.exception.EagleException;
import com.zx.eagle.common.exception.handler.ExceptionNotifier;
import com.zx.eagle.common.vo.CommonResponse;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.util.CollectionUtils;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.validation.ConstraintViolation;
import javax.validation.ConstraintViolationException;
import javax.validation.Path;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
/** @author zouwei */
@Slf4j
@RestControllerAdvice
public class ExceptionResponseAdvice {
@Autowired private ExceptionTipsStackConfig exceptionStack;
@Autowired(required = false)
private List<ExceptionNotifier> exceptionNotifierList;
/**
* 用戶行為導(dǎo)致的錯誤
*
* @param e
* @return
*/
@ExceptionHandler(EagleException.class)
public CommonResponse handleEagleException(
EagleException e, HttpServletRequest request, HttpServletResponse response) {
String massage = handleExceptionMessage(e);
CommonResponse commonResponse =
CommonResponse.exceptionInstance(e.getCode(), massage, e.getTips());
sendNotify(e, request, response);
return commonResponse;
}
/**
* 處理未知錯誤
*
* @param e
* @return
*/
@ExceptionHandler(RuntimeException.class)
public CommonResponse handleRuntimeException(
RuntimeException e, HttpServletRequest request, HttpServletResponse response) {
String error = handleExceptionMessage(e);
EagleException unknownException = EagleException.unknownException(error);
CommonResponse commonResponse =
CommonResponse.exceptionInstance(
unknownException.getCode(), error, unknownException.getTips());
sendNotify(unknownException, request, response);
return commonResponse;
}
/**
* 處理參數(shù)驗證異常
*
* @param e
* @param request
* @param response
* @return
*/
@ExceptionHandler(ConstraintViolationException.class)
public CommonResponse handleValidException(
ConstraintViolationException e,
HttpServletRequest request,
HttpServletResponse response) {
String error = handleExceptionMessage(e);
Set<ConstraintViolation<?>> set = e.getConstraintViolations();
Iterator<ConstraintViolation<?>> iterator = set.iterator();
List<EagleException.ValidMessage> list = Lists.newArrayList();
while (iterator.hasNext()) {
EagleException.ValidMessage validMessage = new EagleException.ValidMessage();
ConstraintViolation<?> constraintViolation = iterator.next();
String message = constraintViolation.getMessage();
Path path = constraintViolation.getPropertyPath();
Object fieldValue = constraintViolation.getInvalidValue();
String tipsKey = constraintViolation.getMessageTemplate();
validMessage.setTipsKey(tipsKey);
validMessage.setFieldName(path.toString());
validMessage.setFieldValue(fieldValue);
validMessage.setDefaultMessage(message);
list.add(validMessage);
}
EagleException validException = EagleException.validException(list);
sendNotify(validException, request, response);
return CommonResponse.exceptionInstance(validException, error);
}
/**
* 發(fā)送請求
*
* @param exception
* @param request
* @param response
*/
private void sendNotify(
EagleException exception, HttpServletRequest request, HttpServletResponse response) {
if (!CollectionUtils.isEmpty(exceptionNotifierList)) {
for (ExceptionNotifier notifier : exceptionNotifierList) {
if (notifier.support(exception.getTipsKey())) {
notifier.handle(exception, request, response);
}
}
}
}
/**
* 處理異常信息
*
* @param e
* @return
*/
private String handleExceptionMessage(Exception e) {
String massage = e.getMessage();
String stackInfo = toStackTrace(e);
String messageStackInfo = massage + "{" + stackInfo + "}";
// 無論是否讓客戶端顯示堆棧信息,后臺都要記錄
log.error(messageStackInfo);
if (exceptionStack.isShowMessage() && exceptionStack.isShowStack()) {
return messageStackInfo;
} else if (exceptionStack.isShowMessage()) {
return massage;
} else if (exceptionStack.isShowStack()) {
return stackInfo;
}
return StringUtils.EMPTY;
}
/**
* 獲取異常堆棧信息
*
* @param e
* @return
*/
private static String toStackTrace(Exception e) {
StringWriter sw = new StringWriter();
PrintWriter pw = new PrintWriter(sw);
try {
e.printStackTrace(pw);
return sw.toString();
} catch (Exception e1) {
return StringUtils.EMPTY;
}
}
}
為了解決有一些異常需要額外處理的,例如調(diào)用第三方接口,接口返回異常并告知費用不夠需要充值,這個時候就需要額外通知到相關(guān)人員及時充值。為此,特地添加一個接口:
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
* 異常通知器
*
* @author zouwei
*/
public interface ExceptionNotifier {
/**
* 是否支持處理該異常
*
* @param exceptionKey
* @return
*/
boolean support(String exceptionKey);
/**
* 處理該異常
*
* @param e
* @param request
*/
void handle(EagleException e, HttpServletRequest request, HttpServletResponse response);
}
為了滿足返回的異常信息可配置化,通過配置決定不同的環(huán)境返回指定的字段信息
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
/** @author zouwei */
@Data
@Component
@ConfigurationProperties(prefix = "exception-tips-stack")
public class ExceptionTipsStackConfig {
/** 是否顯示堆棧信息 */
private boolean showStack = false;
/** 是否顯示exception message */
private boolean showMessage = false;
}
application.yaml中配置示例(根據(jù)環(huán)境配置):
exceptionTipsStack:
#異常堆棧是否需要顯示
showStack: true
#開發(fā)提示信息是否需要顯示
showMessage: true
為了保證返回的數(shù)據(jù)是指定的json格式,需要配置HttpMessageConverter
import org.springframework.context.annotation.Configuration;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import java.util.List;
/** @author zouwei */
@Configuration
public class CustomWebConfigure implements WebMvcConfigurer {
@Override
public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
converters.clear();
converters.add(new MappingJackson2HttpMessageConverter());
}
}
4.測試
先將application.yaml調(diào)整為:
exceptionTipsStack:
#異常堆棧是否需要顯示
showStack: true
#開發(fā)提示信息是否需要顯示
showMessage: true
編寫TestController:
import com.zx.eagle.exception.EagleException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import lombok.Data;
import org.hibernate.validator.constraints.Length;
import org.springframework.validation.BindingResult;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import javax.validation.Valid;
import javax.validation.constraints.*;
/** @author zouwei */
@Validated
@RestController
@RequestMapping("/test")
public class TestController {
/**
*
* @return
* @throws EagleException
*/
@GetMapping("/user_repeat")
public String userRepeat() throws EagleException {
throw EagleException.newInstance("USER_REPEAT_REGIST", "用戶重復(fù)注冊了,正常提示");
}
/**
* 對于用戶來說,不應(yīng)該直接看到NoSuchAlgorithmException,因為這并不是用戶造成的,所以應(yīng)該使用未知錯誤
*
* @return
*/
@GetMapping("/unknownException")
public String unknownException() throws EagleException {
final MessageDigest md;
try {
md = MessageDigest.getInstance("MD4");
} catch (final NoSuchAlgorithmException e) {
throw EagleException.unknownException("顯然是因為程序沒有獲取MD5算法導(dǎo)致的異常,這是完全可以避免的");
}
return "success";
}
@GetMapping("/valid")
public String validException(
@NotNull(message = "USER_NAME_NOT_NULL")
@Length(min = 5, max = 10, message = "USER_NAME_LENGTH_LIMIT")
String username,
@NotNull(message = "年齡不能為空")
@Min(value = 18, message = "年齡必須大于18歲")
@Max(value = 70, message = "年齡不能超過70歲")
int age)
throws EagleException {
// return "success";
throw EagleException.newInstance("USER_NO_EXIST", "用戶不存在,這個地方要注意");
}
@PostMapping("/valid4Post")
public String validException2(@Valid @RequestBody User user, BindingResult result) {
return "success";
}
@Data
private static class User {
@Length(min = 5, max = 10, message = "USER_NAME_LENGTH_LIMIT")
private String username;
@Min(value = 18, message = "年齡必須大于18歲")
@Max(value = 70, message = "年齡不能超過70歲")
private int age;
}
}
測試結(jié)果:
url: /test/user_repeat
{
code: "11023",
error: "用戶重復(fù)注冊了,正常提示{EagleException(code=11023, tipsKey=USER_REPEAT_REGIST, tips=重復(fù)注冊, validMessages=null) at com.zx.eagle.common.exception.EagleException.newInstance(EagleException.java:50) at com.zx.eagle.common.controller.InsuranceController.userRepeat(InsuranceController.java:23)",
message: "重復(fù)注冊",
validMessage: null,
data: null
}
url: /test/unknownException
{
code: "-1",
error: "顯然是因為程序沒有獲取MD5算法導(dǎo)致的異常,這是完全可以避免的{EagleException(code=-1, tipsKey=UNKNOWN_ERROR, tips=未知錯誤, validMessages=null) at com.zx.eagle.common.exception.EagleException.newInstance(EagleException.java:50) at com.zx.eagle.common.exception.EagleException.unknownException(EagleException.java:60) at com.zx.eagle.common.controller.InsuranceController.unknownException(InsuranceController.java:37) ",
message: "未知錯誤",
validMessage: null,
data: null
}
url:/test/valid?username=z2341d&age=10
{
code: "-2",
error: "test.age: 年齡必須大于18歲{javax.validation.ConstraintViolationException: test.age: 年齡必須大于18歲 at org.springframework.validation.beanvalidation.MethodValidationInterceptor.invoke(MethodValidationInterceptor.java:117) }",
message: "參數(shù)驗證錯誤",
validMessage: [
{
fieldName: "test.age",
fieldValue: 10,
code: "-2",
tips: "年齡必須大于18歲",
tipsKey: "年齡必須大于18歲",
defaultMessage: "年齡必須大于18歲"
}
],
data: null
}
url:/test/valid4Post
結(jié)果同上
至此,關(guān)于異常處理的相關(guān)思考和實現(xiàn)闡述完畢。小伙伴們可以依據(jù)類似的思考方式實現(xiàn)符合自身實際情況的異常處理方式。
歡迎有過類似思考的小伙伴一起討論。