开源项目【commons】一 || 开发文档
1、项目地址
工具类-commons【Git地址 https://gitee.com/ying_kevin/commons】
2、项目简介
本项目构建目的主要是致力于开发一套适用于微服务架构体系的公共类封装,解决各微服务间的代码重复利用,规范微服务项目中API接口规范,约定代码规范性。项目的完善是一个不断迭代更新的过程,同时也欢迎各位小伙伴的加入,共同完善本项目。
3、项目结构
项目包含以下部分:
commons-api:封装API接口报文、统一异常处理、基本字段填充、分布式唯一id生产
commons-core:封装公共的工具类
4、commons-api功能列表
- 统一REST ful风格API接口报文
- 基本字段填充
- 全局异常处理
- 分布式唯一主键生成工具类
5、commons-core功能列表
- AsyncUtil 异步工具类
- IDCardCheckUtil 身份证合法性校验工具类
6、springBoot项目中接入全局异常处理
commons-api中已经对全局异常进行了处理,默认的统一业务异常为自定义的ServiceException,以及实现了参数校验异常的全局捕获处理,对应的异常信息如下:
org.springframework.validation.BindException:参数绑定异常,例: 参数名错误或未传参数
org.springframework.web.bind.MethodArgumentNotValidException:Bean 校验异常,例:不符合添加注解要求,NotNull等
javax.validation.ConstraintViolationException:方法参数校验异常(违反约束,javax扩展定义),例: 如实体类中的@Size注解配置和数据库中该字段的长度不统一等问题
com.kevin.commons.api.exception.ServiceException:自定义业务异常
如果抛出的异常不是以上列出的,则统一抛出系统异常。
6.1、接入全局异常测试
那么在项目中怎么接入commons-api中全局异常?只用创建一个类去继承GlobalExceptionHandler,添加RestControllerAdvice注解
@Order(Integer.MAX_VALUE - 1)
@RestControllerAdvice
public class GlobalException extends GlobalExceptionHandler {
}
接下来,我们写一个接口测试全局异常处理是否成功接入。调用的接口如下:
@GetMapping("/getCategoryName")
ResultTemplate<CategoryRes> getCategoryName(@RequestParam("parent_id") Long parentId);
在接口实现中,如果传入的参数为空或者为0,则抛出一个异常
@Override
public List<CategoryRes> getCategoryName(Long parentId) {
if(ObjectUtils.isEmpty(parentId)){
throw new ServiceException("传入的参数不能为空");
}
List<CategoryRes> categoryResList = tabCategoryMapper.selectByParentId(parentId);
return categoryResList;
}
调用接口,可以看到ServiceException异常已经被捕获到了。
6.2、新增自定义异常
那么,是否可以对项目中某个异常进行特殊处理?当然可以,接下来就举例说明一下。如果我的项目中需要对DuplicateKeyException(mysql插入数据主键冲突)进行捕获,而不是直接显示系统错误,那需要怎么处理?我们接着上面的接口实现方法,在代码中添加了插入数据的代码,并且指定id为5,该id值在数据库中已经存在。
@Override
public List<CategoryRes> getCategoryName(Long parentId) {
if(ObjectUtils.isEmpty(parentId) || parentId.equals(0L)){
throw new ServiceException("传入的参数不能为空");
}
TabCategory tabCategory=new TabCategory();
tabCategory.setCategoryId(5L);
tabCategoryMapper.insertSelective(tabCategory);
List<CategoryRes> categoryResList = tabCategoryMapper.selectByParentId(parentId);
return categoryResList;
}
调用接口,从日志信息可以看出抛出的异常为DuplicateKeyException,该异常我们没有进行全局捕获,也没有针对上面插入数据代码进行异常捕获,所以提示信息为系统异常:
如果我们想把该异常信息提示为主键冲突,那么我们可以在插入数据代码中进行异常捕获,但这样操作,每次插入数据都有捕获,代码冗余。所以,我们需要进行全局捕获。
commons-api中全局异常捕获,使用的是责任链模式进行的封装,在我们抛出一个异常后,不会再继续调用后面异常的业务逻辑。当然,对于全局异常捕获封装的方法不是很完美,新增的时候操作太繁琐,后期我会抽出时间继续优化该方法。下面,我们从以下案例中进行学习一下,如何新增一个我们需要捕获的异常。
1.新建一个类继承AbstractOtherException,重写setOtherException方法,在该方法中添加需要捕获的异常全称,注意:key值从var4开始,示例如下:
public class CustomException extends AbstractOtherException {
@Override
protected Map<String, Object> setOtherException() {
System.out.println(exceptionMaps.size());
exceptionMaps.put("var4", "org.springframework.dao.DuplicateKeyException");
return exceptionMaps;
}
}
2.新建一个类继承BaseHandler并实现ExceptHandler,重写handler方法
public class DuplicateKeyExceptionHandler extends BaseHandler implements ExceptHandler {
@Override
public Map<String, Object> handler(HttpServletRequest request, HttpServletResponse response, Throwable throwable) {
if (throwable instanceof DuplicateKeyException) {
String subCode = ServiceErrorEnum.ISV_COMMON_ERROR.getSubCode();
String subMsg = "主键错误";
return result(subCode, subMsg);
}
return nextHandler.handler(request, response, throwable);
}
@Override
public ExceptHandler getNextHandler() {
return null;
}
}
3.因为默认的DefaultFactoryHandler中没有对该异常进行调用,所以要实现FactoryHandler,重写该方法。handler1、handler2、handler3分别负责数据绑定异常的掉一个呢,
handler4负责自定义serviceException异常的调用、handlerLast负责对未定义异常的调用,其中的1、2...等数字代表的是调用顺序,比如:抛出handler1的异常就不会继续走后面异常的业务逻辑。
那么,我们添加的异常就放在handlerLast前面。
public class CustomFactoryHandler implements FactoryHandler {
@Override
public ExceptHandler getOneHandler() {
ExceptHandler handler1 = ApiContext.getApiConfig().getBindExceptionHandler();
ExceptHandler handler2 = ApiContext.getApiConfig().getMethodArgumentNotValidExceptionHandler();
handler1.setNextHandler(handler2);
ExceptHandler handler3 = ApiContext.getApiConfig().getConstraintViolationExceptionHandler();
handler2.setNextHandler(handler3);
ExceptHandler handler4 = ApiContext.getApiConfig().getServiceExceptionHandler();
handler3.setNextHandler(handler4);
// 新增异常 start
ExceptHandler handler5 = new DuplicateKeyExceptionHandler();
handler4.setNextHandler(handler5);
// end
ExceptHandler handlerLast = ApiContext.getApiConfig().getLastExceptionHandler();
handler5.setNextHandler(handlerLast);
return handler1;
}
}
3.以上方法重写完成后我们需要在项目启动的时候就加载,我们只用新建一个类继承BaseConfiguration,进行覆盖调用。
@Configuration
public class WeixinConfig extends BaseConfiguration {
static {
ApiContext.getApiConfig().setFactoryHandler(new CustomFactoryHandler());
ApiContext.getApiConfig().setOtherException(new CustomException());
}
}
上述配置完成后,我们重写启动项目,再次调用的时候,已经抛出了主键错误的异常信息。