一、常量类
在项目开发中,经常需要约定一些常量,比如接口返回响应请求指定状态码、异常类型、默认页数等,为了增加代码的可阅读性以及开发团队中规范一些常量的使用,可开发一些常量类。下面有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 {@Beanpublic WebServerFactoryCustomizer<ConfigurableWebServerFactory> webServerFactoryCustomizer() {return new WebServerFactoryCustomizer<ConfigurableWebServerFactory>() {@Overridepublic 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 500factory.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})@ResponseBodypublic 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})@ResponseBodypublic 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放到异常中。