springboot项目中简单的统一异常处理
刚工作时,接触的第一个项目是一个新开发的运维系统,该项目是springboot 框架,也按照控制层(controller),业务层(service),数据层(dao)的结构进行的开发。而进入到开发中,难免会遇到许多业务异常,和运行时异常需要处理。那时候,项目没有统一的返回实体包装数据,返回的数据结构很随意,甚至很多异常都不会去处理,任由错误经框架抛出,很是粗暴。
后续开发过程中,意识到任由异常抛出,这样不妥,且前端需要"优雅的"展示异常信息。于是开始采用 try catch 捕获异常后,获取异常信息,赋给异常字段后返回给前端。甚至图方便直接 controller 里面直接获取。@Controller public class AutoController { @Autowired private UserInfoService userInfoService; @RequestMapping("/test") @ResponseBody public ResultDto test(){ try{ String name = userInfoService.getName(); ResultDto resultDto = new ResultDto(); resultDto.setData(name); return resultDto; }catch (Exception e){ ResultDto resultDto = new ResultDto(); resultDto.setErrorInfo(e.getMessage()); return resultDto; } }
这种方式简直折磨人,每一个controller 都搞一堆 try catch 。而且,业务层的代码,遇到问题也是new 一个对象处理,后面当我接触老的项目的时候,我发现很多异常都是这么处理的,着实让人崩溃。ResultDto resultDto = new ResultDto(); resultDto.setErrorInfo("参数错误");
后面的工作中认识到,其实项目中的统一异常处理真的很简单创建一个统一返回包装类package com.chinamobile.cmss.dmp.deployer.common.response; import com.chinamobile.cmss.dmp.deployer.common.response.HttpStatusJsonSerializer; import com.chinamobile.cmss.dmp.deployer.exception.BusinessException; import com.fasterxml.jackson.databind.annotation.JsonSerialize; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Data; import lombok.NoArgsConstructor; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; /** * @description: * @date: 2020/11/12/16:05 */ @AllArgsConstructor @NoArgsConstructor @Builder @Data public class ApiResponse { /** * http状态码 */ @JsonSerialize(using = HttpStatusJsonSerializer.class) private HttpStatus status; /** * 系统错误码 */ private String code; /** * 提示信息,展示给用户 */ private String message; /** * 调试信息 */ private Object debugInfo; /** * 响应主体 */ private Object content; }
2.创建一个包装返回实体的工具类,定义一些成功返回的方法。package com.chinamobile.cmss.dmp.deployer.common.response; import org.springframework.http.HttpStatus; /** * 封装统一返回类 * * @author */ public class Response { /** * 具有返回数据的封装 * * @param content 数据内容 * @return ApiResponse */ public static ApiResponse invokeSuccess(Object content){ ApiResponse apiResponse = ApiResponse.builder() .status(HttpStatus.OK) .code(SystemCode.OK) .message("请求成功") .content(content) .build(); return apiResponse; } /** * 没有返回数据的封装 * * @return ApiResponse */ public static ApiResponse invokeSuccess(){ ApiResponse apiResponse = ApiResponse.builder() .status(HttpStatus.OK) .code(SystemCode.OK) .message("请求成功") .content(null) .build(); return apiResponse; } /** * @param content 返回数据 * @param message 消息 * @return ApiResponse */ public static ApiResponse invokeSuccess(Object content,String message){ ApiResponse apiResponse = ApiResponse.builder() .status(HttpStatus.OK) .code(SystemCode.OK) .message(message) .content(content) .build(); return apiResponse; } /** * * @param status 请求状态 * @param code 状态码 * @param content 返回内容 * @param message 消息 * @return ApiResponse */ public static ApiResponse invoke(HttpStatus status, String code, Object content, String message){ ApiResponse apiResponse = ApiResponse.builder() .status(status) .code(code) .message(message) .content(content) .build(); return apiResponse; } /** * * @param status 请求状态 * @param code 状态码 * @param message 消息 * @return ApiResponse */ public static ApiResponse invoke(HttpStatus status, String code, String message){ ApiResponse apiResponse = ApiResponse.builder() .status(status) .code(code) .message(message) .build(); return apiResponse; } }
3.创建一个定义错误信息的枚举,定义业务错误信息package com.li.core.hellomeeting.common.Response; /** * 返回码和返回消息 * */ public enum ResponseCodeEnum { SUCCESS(200,"请求成功"), INTERNAL_SERVER_ERROR(500,"服务器内部错误"), /** 参数错误 **/ PARAM_INVALID_ERROR(1001,"参数校验错误"), USER_NOTE_EXIST(2004,"用户不存在"), FORBIDDEN(403,"禁止访问"), UNAUTHORIZED(401,"未登录"); private Integer code; private String message; ResponseCodeEnum(Integer code, String message) { this.code = code; this.message = message; } public Integer getCode() { return code; } public void setCode(Integer code) { this.code = code; } public String getMessage() { return message; } public void setMessage(String message) { this.message = message; } }
3.创建一个业务异常类,继承 RuntimeException 类package com.li.core.hellomeeting.exception; import com.li.core.hellomeeting.common.Response.ResponseResult; /** * 统一业务异常 * */ public class BusinessException extends RuntimeException{ private ResponseResult responseResult; public BusinessException(String message) { super(message); } public BusinessException(String message, Throwable cause) { super(message, cause); } public BusinessException(Throwable cause) { super(cause); } public BusinessException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) { super(message, cause, enableSuppression, writableStackTrace); } public ResponseResult getResponseResult() { return responseResult; } public void setResponseResult(ResponseResult responseResult) { this.responseResult = responseResult; } }
4.创建一个异常捕获处理类,使用 @RestControllerAdvice 注解,拦截抛出的异常。package com.li.core.hellomeeting.exception; import com.li.core.hellomeeting.common.Response.ErrorInfo; import com.li.core.hellomeeting.common.Response.ResponseResult; import lombok.extern.slf4j.Slf4j; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.security.access.AccessDeniedException; import org.springframework.validation.BindException; import org.springframework.web.HttpRequestMethodNotSupportedException; import org.springframework.web.bind.MethodArgumentNotValidException; import org.springframework.web.bind.MissingServletRequestParameterException; import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.ResponseBody; import org.springframework.web.bind.annotation.RestControllerAdvice; import org.springframework.web.multipart.MultipartException; import java.util.List; import java.util.stream.Collectors; import static com.li.core.hellomeeting.common.Response.StatusCode.*; import static com.li.core.hellomeeting.common.Response.StatusCode.PERMISSION_NO_ACCESS; /** * 全局异常拦截 * * @author */ @Slf4j @RestControllerAdvice public class GlobalExceptionHandler { private final Logger logger = LoggerFactory.getLogger(GlobalExceptionHandler.class); /** * 参数校验错误 * * @param e 错误信息 * @return ResponseResult */ @ExceptionHandler(value = BindException.class) public ResponseResult handleBindException(BindException e){ log.error("参数校验错误", e); ErrorInfo errorInfo = new ErrorInfo(); List fieldErrorInfos = e.getFieldErrors().stream() .map(fieldError -> new ErrorInfo.FieldErrorInfo(fieldError.getField(),fieldError.getRejectedValue(),fieldError.getDefaultMessage())) .collect(Collectors.toList()); errorInfo.setFieldErrorInfos(fieldErrorInfos); return ResponseResult.builder() .code(PARAM_IS_INVALID.code()) .message(PARAM_IS_INVALID.message()) .flag(PARAM_IS_INVALID.status()) .build(); } /** *处理参数错误信息 * * @param e 错误信息 * @return ResponseResult */ @ExceptionHandler(value = MethodArgumentNotValidException.class) public ResponseResult handleBindException(MethodArgumentNotValidException e) { log.error("参数错误{}", e); return ResponseResult.builder() .code(PARAM_IS_INVALID.code()) .message(PARAM_IS_INVALID.message()) .flag(PARAM_IS_INVALID.status()) .build(); } /** *处理非法的请求方法错误 * * @param e 错误信息 * @return ResponseResult */ @ExceptionHandler(value = HttpRequestMethodNotSupportedException.class) public ResponseResult handleHttpRequestMethodNotSupportedException(HttpRequestMethodNotSupportedException e) { String msg = String.format("不支持 [%s] 请求", e.getMethod()); log.error(msg+"{}", e); return ResponseResult.builder() .code(METHOD_NOT_ALLOWED.code()) .message(METHOD_NOT_ALLOWED.message()) .flag(METHOD_NOT_ALLOWED.status()) .build(); } /** * 处理参数缺失错误信息 * * @param e 错误信息 * @return ResponseResult */ @ExceptionHandler(value = MissingServletRequestParameterException.class) public ResponseResult handleMissingServletRequestParameterException(MissingServletRequestParameterException e) { String msg = String.format("缺少 %s 参数", e.getParameterName()); log.error(msg+"{}", e); return ResponseResult.builder() .code(PARAM_NOT_COMPLETE.code()) .message(PARAM_NOT_COMPLETE.message()) .flag(PARAM_NOT_COMPLETE.status()) .build(); } /** * 处理上传异常信息 * * @param e 错误信息 * @return ResponseResult */ @ExceptionHandler(value = MultipartException.class) public ResponseResult handleMultipartException(MultipartException e) { log.error("上传异常{}", e); return ResponseResult.builder() .code(EXCEED_MAX_SIZE.code()) .message(EXCEED_MAX_SIZE.message()) .flag(EXCEED_MAX_SIZE.status()) .build(); } /** * 处理访问权限错误 * * @param e 错误信息 * @return ResponseResult */ @ResponseBody @ExceptionHandler(AccessDeniedException.class) public ResponseResult handleAccessDeniedException(AccessDeniedException e) { return ResponseResult.builder() .code(PERMISSION_NO_ACCESS.code()) .message(PERMISSION_NO_ACCESS.message()) .flag(PERMISSION_NO_ACCESS.status()) .build(); } /** * 处理全局异常 * 通常处理统一的未知异常 * * @param e 异常信息 * @return ResponseResult */ @ExceptionHandler(value = Exception.class) public ResponseResult handleException(Exception e) { log.error("服务器内部错误", e); return ResponseResult.builder() .code(INTERNAL_SERVER_ERROR.code()) .message(INTERNAL_SERVER_ERROR.message()) .flag(INTERNAL_SERVER_ERROR.status()) .build(); } /** * 处理业务异常 * 如果携带了一个 ResponseResult,则提取出来进行返回 * 否则返回 null * * @param e 错误信息 * @return ResponseResult */ @ExceptionHandler(value = BusinessException.class) public ResponseResult handleBusinessException(BusinessException e) { log.error(e.getMessage(), e); ResponseResult responseResult; if(e.getResponseResult() != null){ responseResult = e.getResponseResult(); }else{ log.error("服务器内部错误{}", e.getCause()); responseResult = ResponseResult.builder() .code(INTERNAL_SERVER_ERROR.code()) .message(INTERNAL_SERVER_ERROR.message()) .flag(INTERNAL_SERVER_ERROR.status()) .build(); } return responseResult; } }
5.封装一些异常错误,配合定义好的错误枚举类使用。package com.chinamobile.cmss.dmp.deployer.exception; import com.chinamobile.cmss.dmp.deployer.common.response.Response; import com.chinamobile.cmss.dmp.deployer.common.response.SystemCode; import org.springframework.http.HttpStatus; /** * 异常统一抛出封装 * */ public class MyException { /** *抛出错误信息 * * @param status 状态 * @param code 状态码 * @param content 内容 * @param message 错误信息 * @return BusinessException */ public BusinessException throwException(HttpStatus status, String code, Object content, String message){ BusinessException businessException = new BusinessException(message); businessException.setApiResponse(Response.invoke(status,code,content,message)); return businessException; } /** * 参数错误异常 * @param message 异常信息 * @return BusinessException */ public BusinessException badRequest(String message){ BusinessException businessException = new BusinessException(message); businessException.setApiResponse(Response.invoke(HttpStatus.BAD_REQUEST, SystemCode.BAD_REQUEST,null,message)); return businessException; } /** * 找不到目标值错误 * * @param message 错误信息 * @return BusinessException */ public BusinessException notFound(String message){ BusinessException businessException = new BusinessException(message); businessException.setApiResponse(Response.invoke(HttpStatus.NOT_FOUND, SystemCode.NOT_FOUND,null,message)); return businessException; } /** * 无权限错误 * * @param message 错误信息 * @return BusinessException */ public BusinessException forbideen(String message){ BusinessException businessException = new BusinessException(message); businessException.setApiResponse(Response.invoke(HttpStatus.FORBIDDEN,SystemCode.FORBIDDEN,null,message)); return businessException; } }
在业务中使用,截取个例子一部分代码@Override public void deployService(DeployParam deployParam){ log.info("------> 【自动化部署接口】参数:{}",deployParam); //校验一下 checkParam(deployParam); //获取环境配置信息 ServiceDeployInfoDto serviceDeployInfoDto = getServiceDeployInfoDto(deployParam); log.info("------> 整合环境配置信息:{}",serviceDeployInfoDto); //判断服务是否在部署中 boolean status = DeployUtils.isDeploying(deployParam.getEnv(),deployParam.getInstance()); log.info("------> 判断当前实例是否正在部署,status: {}",status); if(status){ log.error("{} 服务正在部署中,请稍后再部署",deployParam.getInstance()); throw new MyException().badRequest("该服务正在部署中,请稍后"); } //2.开始部署 log.info("------> 开始进行异步部署......"); if(deployParam.getInstance().equals("openapi2")){ serviceDeployInfoDto.setInstanceName("openapi"); } deployManager.deployService(serviceDeployInfoDto); }
在controller 中使用@PostMapping(value = "/deploy") public ResponseEntity
王者荣耀你知道吗新英雄云樱抄袭了峡谷中很多英雄的动作哈喽大家好,我是伟哥,王者荣耀新英雄云樱已经上线体验服一段时间了,英雄的造型还是挺不错,不过不知道大家有没有发现云樱的技能抄袭了很多英雄的技能,那么下面我们就来看看云樱抄袭了多少英
2。5D冒险新作面包之子发布免费试玩版本WildArts工作室近日发布了全新的游戏作品面包之子(BornofBread),并公开了该游戏的试玩版,但正式版的具体上市日期暂未公开。面包之子是一款根据纸片马里奥灵感制作的2。
网易改邪归正?砸重金请金牌制作人做游戏,Steam一次性买断大家好,我是人菜瘾大的吃鸡动作双料爱好者X博士!在国产游戏中,硬核动作游戏曾经是一段令人心酸的历史。比如最经典的流星蝴蝶剑系列,不论单机还是网游,都是叫好不叫座,令无数X博士我这样
阴阳师如何打造游戏角色的设计感?4月26日4月28日,由网易游戏学院举办的2021N。Game网易游戏开发者峰会盛大召开,本次大会将以传承洞见匠心为主题,从策划技术美术三个游戏研发与运营的重点方面,携手十位海内外
元气骑士超级大宝贝后,氢弹来了!吸血鬼猎人专属分享游戏故事,汇集欢声笑语大家好,我是菜鸟熊猫欢迎来到由菜鸟熊猫主讲的元气骑士大型栏目元气搞笑日常第573期本期我们来聊一聊元气骑士玩家遇到的有趣故事,主要内容有道士3技能再现穿墙
沉迷游戏的人有多么认真我玩的网游就不说了,免得看了我文章的人又进了这个大坑。这是为了涨一点点的评分,我记录下来鼓励自己滴水穿石铁杵成针,让自己相信总有一天我能成为游戏中的大神,在里面说一不二,万人敬仰。
二十年前,甘肃人小时候都玩哪些游戏?谢谢。二十年前,甘肃人小时候都玩哪些游戏?曾记得我们定西乡村人玩的游戏五花八门,门户各不相同。最影响深的有几大亮点一,乡村学校学生爱玩(一)弹蛋儿,规则是双方各持有一颗花式圆蛋,在
如果把S3时期的OMGWEIG皇族放到现在,有机会得到冠军吗?lpl历史上最成功的四只全华班队伍,老we,老ig,老皇族,老omg,不光在职业时期赢得不错的成绩,在队员或转会或退役后依然在国内电竞领域影响力不减,厂长,小狗仍是lpl中流砥柱,
阴阳师深渊之围怎么通关深渊之围通关打法思路一览阴阳师深渊之围怎么才能通关?玩家们很多都不熟悉阴阳师全新的玩法,阴阳师深渊之围还有很多玩家不知道怎么过,因为阵容上没有啥说的,新手玩家或者大神都有自己的角色,只需要几个步骤就好了,
欧易OKEx上线的SAND币值不值得投资?TheSandbox是一个虚拟世界,玩家可以使用平台代币SAND在以太坊区块链中创建拥有自己的游戏体验并从中获利。在这里你可以玩创造收集赚取治理和拥有游戏中的任何东西。欧易OKEx
沉浮我们自己的海上物理搭建游戏导语如果让你造一艘称霸海洋的战舰,你会如何设计它呢?沉浮给了你尝试的机会。近年来,我们在自由沙盒游戏中造过桥,建过房,这次建议你体验一下造船的乐趣。最近,一款由无端科技旗下Puzz
因为空难而迷失在丛林,如何在有野人的丛林里生存?森林游戏简介森林是一款恐怖冒险类游戏。森林为一款描述因为空难而迷失在丛林里的幸存者主角,如何在丛林里生存和如何在野人的进攻下免遭伤害。玩家们所遭遇的野人拥有着原始的社会观念,虽然乍看之
Doinb首秀轻松进入四强!LNG以31战胜OMG,下一轮直接打FPX在刚刚结束的LOL德玛西亚杯的最后一场八强淘汰赛的比赛中,LNG战队是以31成功击败OMG战队顺利晋级四强,Doinb在LNG战队的首秀表现出色,自此德杯四强战队全部诞生,均是来自
12月中旬更新在12月中旬进行了更新,主要更新内容如下1设备封禁功能在12月中旬封禁功能进行了更新,如果有外挂开挂被封,以前是换个号就行了,而现在是整台设备封禁,预计将在2022。3看出效果,封
用云桌面打游戏,性价比是否更高?曾经风靡90后的网页挂机游戏,相信很多人以前总是去网吧挂游戏,各种网页端游戏,不为体验,就是为了领装备升级。休闲挂机类游戏是很多的玩家很喜欢的游戏的类型之一,在休闲挂机类游戏中玩家
网游创物尘晶现已从虚幻4升级到虚幻5确认采用虚幻5引擎的游戏名单越来越长了起来。近日开发商Intrepid宣布他们的网游创物尘晶(AshesofCreation)现已完成了从虚幻4到虚幻5引擎的升级。视频Intrep
为什么网游中名留青史的战役都发生在深夜?无烬文明计划对于MMORPG游戏来说经常会发生大规模作战的情况,这也是这类网游最吸引玩家的特点之一,如果能够亲身参与其中对于玩家来说也是游戏生涯里非常有意义的一段时光。而拥有多人会
电竞假赛为何会如此猖狂?电竞作为近年新兴的运动项目,也和其他运动体育一样面对打假赛的诚信威胁,正好最近两大MOBA电竞项目英雄联盟及DOTA2的世界赛都出现了疑似假赛的风波,这次我就带大家回顾一下电竞发展
云顶11。24b你真的会玩极客吗?冷门偷分阵容极客巨魔大家好,我就是那个每天算得比谁都精,输得比谁都惨的云顶会计师OV说游戏。我最近有点迷恋上极客体系了,主要是现在玩极客的人比较少,而且主要体系下的装备加成,抢牌压力不大。特别是极客巨
明日方舟幻神干员大洗牌,风笛加强稳进前三,不出限定难超越在风雪过境银灰灵知博士和黑骑士的剧情进行的如火如荼之时,游戏中一批老干员的模组也在近期得到了强化。其中作为先锋之光高难常客的幻神之壁风笛,更是在这次强化后如虎添翼,进入了三幻神干员
战争为我而生今天给大家介绍一下战神吕布,这个英雄打边路一般都能自爆一路,这个英雄唯一的短板可能就是没什么控制效果,可能这个英雄前中期的效果并不怎么明显但是到了中后期装备成型之后伤害又高防御能力
李白到底是刺客还是诗人?从静夜思的床前明月光到将进酒的天生我材必有用。大多数人对李白的印象,他是一位著名的诗人,而在游戏中则是一名剑术高超的刺客,有着高机动性和强大的输出能力。包括许多成年玩家都认为,这不