解决spring boot中rest接口404,500等错误返回统一的json格式
总阅读 次
在开发rest接口时,我们往往会定义统一的返回格式,列如:1
2
3
4
5
6{
"code":200,
"message":"123",
"success":true,
"data":null
}
但是如果调用方请求我们的api时把接口地址写错了,就会得到一个404错误,在传统的web系统中我们可自定义404错误页面,展示更友好。在spring boot中其实也是返回了一个json格式的数据,如下:1
2
3
4
5
6
7{
"timestamp": 1492063521109,
"status": 404,
"error": "Not Found",
"message": "No message available",
"path": "/rest11/auth"
}
告诉我们哪个地址是没找到,其实也挺友好的,但是因为我们上面自定义的数据格式跟下面的不一致,当用户拿到这个返回的时候是无法识别的,其中最明显的是status字段。我们自定义的是boolean类型,表示是否成功,这边返回的就是http的状态码,所以我们需要在发生这种系统错误时也能返回我们自定义的那种格式。
1 | @ControllerAdvice |
Result是我们返回格式的实体类,这种在发生错误时这边会捕获到,然后封装好返回格式,返回给调用方。
最后关键的一步是在spring boot的配置文件中加上如下配置:1
2
3
4#出现错误时, 直接抛出异常
spring.mvc.throw-exception-if-no-handler-found=true
#不要为我们工程中的资源文件建立映射
spring.resources.add-mappings=false
然后我们调用一个不存在的接口时,返回的错误信息就是我们自定义的那种格式了1
2
3
4
5
6{
"code":400,
"message":"api not found",
"success":false,
"data":null
}
引发的问题
添加上面2个配置后,会过滤静态资源,导致某些资源找不到,比如swagger,解决方案如下:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18@Configuration
public class WebMvcConfiguration implements WebMvcConfigurer {
/**
* 将swagger-ui.html 添加 到 resources目录下
*
* @param registry
*/
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("swagger-ui.html").addResourceLocations("classpath:/META-INF/resources/");
registry.addResourceHandler("/webjars/**").addResourceLocations("classpath:/META-INF/resources/webjars/");
registry.addResourceHandler("/web_frontend/**").addResourceLocations("classpath:/web_frontend/");
}
}
备份一份SpringMVC-config配置代码,里面也包含统一异常处理代码,贴上:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196@Configuration
public class WebMvcConfigurer extends WebMvcConfigurerAdapter {
private final Logger logger = LoggerFactory.getLogger(WebMvcConfigurer.class);
@Value("${spring.profiles.active}")
private String env;//当前激活的配置文件
//使用阿里 FastJson 作为JSON MessageConverter
@Override
public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
FastJsonHttpMessageConverter converter = new FastJsonHttpMessageConverter();
FastJsonConfig config = new FastJsonConfig();
config.setSerializerFeatures(SerializerFeature.WriteMapNullValue,//保留空的字段
SerializerFeature.WriteNullStringAsEmpty,//String null -> ""
SerializerFeature.WriteNullNumberAsZero);//Number null -> 0
converter.setFastJsonConfig(config);
converter.setDefaultCharset(Charset.forName("UTF-8"));
converters.add(converter);
}
//统一异常处理
@Override
public void configureHandlerExceptionResolvers(List<HandlerExceptionResolver> exceptionResolvers) {
exceptionResolvers.add(new HandlerExceptionResolver() {
public ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response, Object handler, Exception e) {
ResponseMsg result;
if (e instanceof ServiceException) {//业务失败的异常,如“账号或密码错误”
result = new ResponseMsg("501", "业务层出错:" + e.getMessage());
logger.info(e.getMessage());
} else if (e instanceof NoHandlerFoundException) {
result = new ResponseMsg("404", "接口 [" + request.getRequestURI() + "] 不存在");
} else {
result = new ResponseMsg("500", "接口 [" + request.getRequestURI() + "] 错误,请联系管理员!");
String message;
if (handler instanceof HandlerMethod) {
HandlerMethod handlerMethod = (HandlerMethod) handler;
message = String.format("接口 [%s] 出现异常,方法:%s.%s,异常摘要:%s",
request.getRequestURI(),
handlerMethod.getBean().getClass().getName(),
handlerMethod.getMethod().getName(),
e.getMessage());
} else {
message = e.getMessage();
}
logger.error(message, e);
}
responseResult(response, result);
return new ModelAndView();
}
});
}
//解决跨域问题
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**") // **代表所有路径
.allowedOrigins("*") // allowOrigin指可以通过的ip,*代表所有,可以使用指定的ip,多个的话可以用逗号分隔,默认为*
.allowedMethods("GET", "POST", "HEAD", "PUT", "DELETE") // 指请求方式 默认为*
.allowCredentials(false) // 支持证书,默认为true
.maxAge(3600) // 最大过期时间,默认为-1
.allowedHeaders("*");
}
//添加拦截器
@Override
public void addInterceptors(InterceptorRegistry registry) {
//接口登录验证拦截器
if (!"dev".equals(env)) { //开发环境忽略登录验证
registry.addInterceptor(new HandlerInterceptorAdapter() {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
//验证登录
Object obj = request.getSession().getAttribute(Const.LOGIN_SESSION_KEY);
if (obj != null) {
return true;
} else {
logger.warn("请先登录!==> 请求接口:{},请求IP:{},请求参数:{}",
request.getRequestURI(), getIpAddress(request), JSON.toJSONString(request.getParameterMap()));
responseResult(response, new ResponseMsg(Result.SIGNERROR));
return false;
}
}
});
}
}
private void responseResult(HttpServletResponse response, ResponseMsg result) {
response.setCharacterEncoding("UTF-8");
response.setHeader("Content-type", "application/json;charset=UTF-8");
response.setStatus(200);
try {
response.getWriter().write(JSON.toJSONString(result));
} catch (IOException ex) {
logger.error(ex.getMessage());
}
}
private String getIpAddress(HttpServletRequest request) {
String ip = request.getHeader("x-forwarded-for");
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = request.getHeader("Proxy-Client-IP");
}
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = request.getHeader("WL-Proxy-Client-IP");
}
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = request.getHeader("HTTP_CLIENT_IP");
}
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = request.getHeader("HTTP_X_FORWARDED_FOR");
}
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = request.getRemoteAddr();
}
// 如果是多级代理,那么取第一个ip为客户端ip
if (ip != null && ip.indexOf(",") != -1) {
ip = ip.substring(0, ip.indexOf(",")).trim();
}
return ip;
}
/**
* druidServlet注册
*/
@Bean
public ServletRegistrationBean druidServletRegistration() {
ServletRegistrationBean registration = new ServletRegistrationBean(new StatViewServlet());
registration.addUrlMappings("/druid/*");
return registration;
}
/**
* druid监控 配置URI拦截策略
*
* @return
*/
@Bean
public FilterRegistrationBean druidStatFilter() {
FilterRegistrationBean filterRegistrationBean = new FilterRegistrationBean(new WebStatFilter());
// 添加过滤规则.
filterRegistrationBean.addUrlPatterns("/*");
// 添加不需要忽略的格式信息.
filterRegistrationBean.addInitParameter("exclusions", "/web_frontend/*,*.js,*.gif,*.jpg,*.png,*.css,*.ico,/druid,/druid/*,/error,/login*");
// 用于session监控页面的用户名显示 需要登录后主动将username注入到session里
filterRegistrationBean.addInitParameter("principalSessionName", "username");
return filterRegistrationBean;
}
/**
* druid数据库连接池监控
*/
@Bean
public DruidStatInterceptor druidStatInterceptor() {
return new DruidStatInterceptor();
}
/**
* druid数据库连接池监控
*/
@Bean
public BeanTypeAutoProxyCreator beanTypeAutoProxyCreator() {
BeanTypeAutoProxyCreator beanTypeAutoProxyCreator = new BeanTypeAutoProxyCreator();
beanTypeAutoProxyCreator.setTargetBeanType(DruidDataSource.class);
beanTypeAutoProxyCreator.setInterceptorNames("druidStatInterceptor");
return beanTypeAutoProxyCreator;
}
/**
* RequestContextListener注册
*/
@Bean
public ServletListenerRegistrationBean<RequestContextListener> requestContextListenerRegistration() {
return new ServletListenerRegistrationBean<>(new RequestContextListener());
}
/**
* 将swagger-ui.html 添加 到 resources目录下
*
* @param registry
*/
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("swagger-ui.html").addResourceLocations("classpath:/META-INF/resources/");
registry.addResourceHandler("/webjars/**").addResourceLocations("classpath:/META-INF/resources/webjars/");
registry.addResourceHandler("/web_frontend/**").addResourceLocations("classpath:/web_frontend/");
}
}

