一、常量类
在项目开发中,经常需要约定一些常量,比如接口返回响应请求指定状态码、异常类型、默认页数等,为了增加代码的可阅读性以及开发团队中规范一些常量的使用,可开发一些常量类。下面有3个常量类示例,代码位于openjweb-common的org/openjweb/common/constant路径:
下面是接口调用返回的状态码常量类ResponseConst.java:
package org.openjweb.common.constant;
public class ResponseConst {
//按HTTP返回码定义的常量
public static final int STATE_HTTP_SUCCESS = 200;
//有的项目设置0为调用成功的返回值
public static final int STATE_SUCCESS = 0;
//有的项目设置-1为默认的请求失败返回的错误码
public static final int STATE_FAIL = -1;
}
下面是异常提示信息常量类ExceptionConst.java:
/**
* 异常相关常量
*/
public class ExceptionConst {
public static final String UNKNOWN_ERROR_MSG = "数据加载失败,请稍后重试";
public static final String SQL_ERROR_MSG = "数据异常,请稍后重试";
public static final String CONNECT_ERROR_MSG = "连接异常,请稍后重试";
public static final String LOGIN_ERROR_MSG = "用户名或密码错误";
public static final String CLICK_HOUSE_ERROR_MSG = "clickhouse查询异常";
public static final String UPLOAD_FILE_ERROR_MSG = "上传文件失败";
public static final String DELETE_FILE_ERROR_MSG = "删除文件失败";
public static final String XCX_ERROR_MSG = "小程序维护中,请稍后再试";
}
下面是公共常量类CommonConst.java:
package org.openjweb.common.constant;
/**
* 公共常量
*/
public class CommonConst {
/**
* 默认显示页数20
*/
public static final int DEFAULT_PAGE_SIZE = 20;
/**
* 数据量大时,分页查询数据条数
*/
public static final int DEFAULT_SEARCH_PAGE_SIZE = 500;
/**
* 微信支付access-token
*/
public static final String WX_ACCESS_TOKEN = "wx_access_token";
/**
* 默认排序
*/
public static final int DEFAULT_SORT = 1;
/**
* 状态:0-禁用;1-启用
*/
public static final int IS_ENABLE_0 = 0;
public static final int IS_ENABLE_1 = 1;
}
在项目开发中还可以定义更多的常量类。
二、自定义错误页面
大家在SpringBoot开发过程中,对下面这个页面应该是很熟悉:
如果我们访问一个不存在的资源的时候,就会显示这个页面。这个页面不太友好,我们如何自定义这个默认的错误页呢?
现在在openjweb-sys的resources目录下建一个static目录,然后在这个目录下建一个error-404.html页面(特别注意:经测试,如果页面命名为404.html是有问题的,所以不能用404.html文件名):
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>404</title>
</head>
<body>
<div class="text" style=" text-align:center;">
页面出错了!!
</div>
</body></html>
然后在openjweb-sys的org/openjweb/sys/config下面建一个错位页配置类ErrorPageConfig.java:
package org.openjweb.sys.config;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.web.server.ConfigurableWebServerFactory;
import org.springframework.boot.web.server.ErrorPage;
import org.springframework.boot.web.server.WebServerFactoryCustomizer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpStatus;
@Configuration
@Slf4j
public class ErrorPageConfig {
@Bean
public WebServerFactoryCustomizer<ConfigurableWebServerFactory> webServerFactoryCustomizer() {
return new WebServerFactoryCustomizer<ConfigurableWebServerFactory>() {
@Override
public void customize(ConfigurableWebServerFactory factory) {
log.info("重新指定404页面................");
ErrorPage err404Page = new ErrorPage(HttpStatus.NOT_FOUND, "/error-404.html");//不能直接命名404.html
//ErrorPage err500Page = new ErrorPage(HttpStatus.INTERNAL_SERVER_ERROR, "/error/error-500.html");//
//ttpStatus.BAD_REQUEST:400,ttpStatus.INTERNAL_SERVER_ERROR 500
factory.addErrorPages(err404Page);
//factory.addErrorPages(err404Page,err500Page);
}
};
}
}
注意在上面的代码中,仅示例了出现404(HttpStatus.NOT_FOUND)的时候跳转到error-404.html,另外这个类只是演示了替代默认的错误处理,并没有根据不同的HttpStatus指定不同的错误处理页。现在重启动SpringBoot,访问一个不存在的链接:http://localhost:8001/demo/api/redis/se1
界面显示: 这个就是我们定义的error-404.html的内容,已经不再显示SpringBoot默认的错误页了。不过在实际项目中,光做到这一步还是不够的。应该针对不同的错误返回不同的错误信息。
三、全局异常处理及升级错误处理页
在实际开发中,需要做一个全局异常处理,以便通过框架自动处理异常,而不是在每个业务方法中手工处理产生的异常,这样开发效率很低。另外在错误处理页上,需要展示错误码、错误信息等。首先我们需要实现一个全局异常处理类,我们命名为GlobalException,放在openjweb-common的org/openjweb/common/exception下:
package org.openjweb.common.exception;
import org.openjweb.common.constant.ResponseConst;
public class GlobalException extends RuntimeException {
private int code = ResponseConst.STATE_FAIL;//默认错误码
private String appName;//异常产生的应用名称
public GlobalException(String msg) {
super(msg);
}
public GlobalException(String msg, int code) {
super(msg);
this.code = code;
}
public GlobalException(String message, String appName) {
super(message);
this.appName = appName;
}
public GlobalException(String message, int code, String appName) {
super(message);
this.code = code;
this.appName = appName;
}
public int getCode() {
return code;
}
public void setCode(int code) {
this.code = code;
}
public String getAppName() {
return this.appName;
}
public void setAppName(String appName) {
this.appName = appName;
}
}
接下来我们在oopenjweb-common工程的org.openjweb.common.handler包下创建一个GlobalExceptionHandler类,在这个类中,我们指定GlobalException类的拦截处理以及指定错误视图:
package org.openjweb.common.handler;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import lombok.extern.slf4j.Slf4j;
import org.openjweb.common.exception.GlobalException;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.servlet.ModelAndView;
@Slf4j
@ControllerAdvice(basePackages= {"org.openjweb"})
public class GlobalExceptionHandler {
@ExceptionHandler({GlobalException.class})
@ResponseBody
public ModelAndView globalErrorHandler(HttpServletRequest request,HttpServletResponse response,Exception ex) throws Exception {
ModelAndView mv = new ModelAndView();
if(true) {
log.info("自定义全局异常 GlobalException 异常处理........");
GlobalException thisException = (GlobalException)ex;
int errorCode = thisException.getCode();
mv.addObject("errorCode", errorCode);
mv.addObject("errorMessage","GlobalException处理异常:"+ex.getMessage());
mv.addObject("requestUrl",request.getRequestURL().toString());
mv.setViewName("errorPage");
}
return mv;
}
}
在上面的类中,通过增加了@ControllerAdvice注解,通过在方法中增加@ExceptionHandler(GlobalException.class)注解,则凡是在控制层中抛出GlobalException异常,则都会被此类拦截,并返回错误页视图errorPage。
接下来我们定义一个errorPage错误处理页面,因为我们现在要使用thymeleaf解析视图里的页面错误信息,所以需要在openjweb-sys工程的resources/template目录下创建一个errorPage.html:
<!DOCTYPE HTML>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<title>error</title>
<meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/>
</head>
<body>
<table>
<tr>
<td>错误码:</td>
<td th:text="${errorCode}"></td>
</tr>
<tr>
<td>错误信息:</td>
<td th:text="${errorMessage}"></td>
</tr>
<tr>
<td>请求地址:</td>
<td th:text="${requestUrl}"></td>
</tr>
</table>
</body>
</html>
在errorPage.html里显示错误码、错误信息、错误请求的URL。然后我们需要实现一个api测试类来演示下效果。在openjweb-sys的org.openjweb.sys.api下创建一个DefaultErrorApi.java:
package org.openjweb.sys.api;
import lombok.extern.slf4j.Slf4j;
import org.openjweb.common.exception.GlobalException;
import org.openjweb.sys.entity.CommUser;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* 测试:http://localhost:8001/demo/error/testError?flag=1
* http://localhost:8001/demo/error/testError?flag=2
* *
*/
@RestController
@RequestMapping("/demo/error")
@Slf4j
public class DefaultErrorApi {
@RequestMapping("testError")
public CommUser testError(String flag){
CommUser user = new CommUser();
if("1".equals(flag)) {
throw new GlobalException("演示全局异常", -1);
}
else {
user.setRealName("张三");
user.setLoginId("admin");
return user;
}
}
}
在上面的示例代码中,通过传递flag=1演示带自定义错误页的异常显示,传其他值为正常接口调用,访问 http://localhost:8001/demo/error/testError?flag=1 演示自定义错误页:
访问http://localhost:8001/demo/error/testError?flag=2为正常的接口调用:
在实际开发中,控制层一种是返回API JSON数据,第二种就是返回视图,我们在调用数据请求接口时,希望异常时返回统一的错误信息JSON包,请求视图时,异常返回统一的错误视图,这种需求怎么实现?上面的示例是返回视图的异常。接下来我们再实现一个返回JSON的异常处理:
(1)首先我们再加一个用于JSON处理的异常处理类,命名为GlobalJsonException,内容直接复制GlobalException,然后把构造方法修改下:
package org.openjweb.common.exception;
import org.openjweb.common.constant.ResponseConst;
/**
* 用于API调用时统一处理错误异常
*/
public class GlobalJsonException extends RuntimeException {
private int code = ResponseConst.STATE_FAIL;//默认错误码
private String appName;//异常产生的应用名称
public GlobalJsonException(String msg) {
super(msg);
}
public GlobalJsonException(String msg, int code) {
super(msg);
this.code = code;
}
public GlobalJsonException(String message, String appName) {
super(message);
this.appName = appName;
}
public GlobalJsonException(String message, int code, String appName) {
super(message);
this.code = code;
this.appName = appName;
}
public int getCode() {
return code;
}
public void setCode(int code) {
this.code = code;
}
public String getAppName() {
return this.appName;
}
public void setAppName(String appName) {
this.appName = appName;
}
}
然后在GlobalExceptionHandler.java中增加一个新的方法:
@ExceptionHandler({GlobalJsonException.class})
@ResponseBody
public JSONObject globalJsonErrorHandler(HttpServletRequest request, HttpServletResponse response, Exception ex) throws Exception {
GlobalJsonException thisException = (GlobalJsonException)ex;
JSONObject errJson = new JSONObject();
errJson.put("code",thisException.getCode());
errJson.put("msg",thisException.getMessage());
return errJson;
}
然后我们在defaultErrorApi中在增加一个方法演示JSON接口调用异常的拦截处理:
@RequestMapping("testJsonError")
public CommUser testJsonError(String flag){
CommUser user = new CommUser();
if("1".equals(flag)) {
throw new GlobalJsonException("演示全局异常", -1);
}
else {
user.setRealName("张三");
user.setLoginId("admin");
}
return user;
}
测试地址:http://localhost:8001/demo/error/testJsonError?flag=1
这样我们就不需要在每个业务接口中专门针对异常来做JSON处理了。只需要在全局异常处理类中做处理即可,不过业务接口中,需要把code、message放到异常中。