package com.meituan.show.sell.web.advice;
import java.io.IOException;
import java.nio.charset.Charset;
import java.util.Map;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletResponse;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestController;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.serializer.ExceptionSerializer;
import com.alibaba.fastjson.serializer.ObjectSerializer;
import com.alibaba.fastjson.serializer.SerializeConfig;
import com.alibaba.fastjson.serializer.SimplePropertyPreFilter;
import com.alibaba.fastjson.support.spring.FastJsonHttpMessageConverter;
import com.google.common.collect.Maps;
import com.meituan.show.sell.service.exception.ValidationFailException;
import com.meituan.show.sell.web.exception.APIException;
import com.meituan.show.utils.APIUtils;
@RestController
@ControllerAdvice(basePackages = {""})
public class ExceptionHandlerAdvice {
private static final Logger LOGGER = LoggerFactory.getLogger(ExceptionHandlerAdvice.class);
private static final SerializeConfig serializeConfig = new CustomForExceptionSerializeConfig();
private static final SimplePropertyPreFilter exceptionFieldFilter = new SimplePropertyPreFilter();
private static final Charset UTF8 = Charset.forName("UTF-8");
@Value("#{configProperties['response_error_isPrintStackTrace']}")
private boolean isPrintStackTrace;
@Resource
private FastJsonHttpMessageConverter fastJsonHttpMessageConverter;
static {
exceptionFieldFilter.getExcludes().add("localizedMessage");
exceptionFieldFilter.getExcludes().add("statusCode");
exceptionFieldFilter.getExcludes().add("stackTrace");
exceptionFieldFilter.getExcludes().add("formattedMessage");
exceptionFieldFilter.getExcludes().add("suppressed");
}
/*
* 对 APIException 和 ValidationFailException:
* 1. 在响应里加上 code 字段(eg. code:异常类名trim掉"Exception"后缀得到的字符串);
* 2. 把异常序列化到客户端(eg. errorDetail:异常)
* 方便客户端了解发生了何种异常,以及异常的具体信息。
*/
private void writeSerializeExceptionResult(HttpServletResponse response, Map<String, Object> exceptionResult) throws IOException {
Map<String, Object> map = Maps.newHashMap(exceptionResult);
// 异常不保存在data中,方便C端解析
Object exception = map.remove("data");
map.put("errorDetail", exception);
String exceptionClass = exception.getClass().getSimpleName();
map.put("code", exceptionClass.endsWith("Exception") ? exceptionClass.substring(0, exceptionClass.length() - "Exception".length()) : exceptionClass);
String json = JSON.toJSONString(map, serializeConfig, exceptionFieldFilter, fastJsonHttpMessageConverter.getFeatures());
response.setContentType("application/json");
response.getOutputStream().write(json.getBytes(UTF8));
}
@ExceptionHandler(value = {APIException.class, BussinessRuleViolationException.class})
public void exceptionHandler(APIException ex, HttpServletResponse response) throws IOException {
LOGGER.warn(ex.getMessage(), ex);
String message = ex.getFormattedMessage();
Map<String, Object> result = APIUtils.makeResponse(false, ex, null, message);
writeSerializeExceptionResult(response, result);
}
@ExceptionHandler
public Map<String, Object> defaultExceptionHandler(Throwable ex) {
LOGGER.error(ex.getMessage(), ex);
String message = isPrintStackTrace ? (ex.getMessage() == null ? "" : ex.getMessage()) : "服务器内部错误";
return APIUtils.makeResponse(false, null, null, message);
}
// 异常的序列化用JavaBeanSerializer,不用ExceptionSerializer,后者会带上异常类名,这里并不适用
static class CustomForExceptionSerializeConfig extends SerializeConfig {
@Override
public ObjectSerializer getObjectWriter(Class<?> clazz) {
ObjectSerializer serializer = super.getObjectWriter(clazz);
if (serializer != null && serializer.getClass() == ExceptionSerializer.class) {
put(clazz, createJavaBeanSerializer(clazz));
}
return super.getObjectWriter(clazz);
}
}
}