天天速看:告别混乱代码:SpringBoot 后端接口规范
2023-06-30 10:12:51 来源: 互联网
往期热门文章:
1、项目终于用上了Spring状态机,非常优雅!
(相关资料图)
2、自从用了这款 IDEA 神器,领导都夸我代码写得好!
3、MyBatis的二级缓存,慎用!
4、8种专坑同事的 SQL 写法,性能降低100倍,不来看看?
5、最近火起的 Bean Searcher 与 MyBatis Plus 到底有啥区别?
一、前言
一个后端接口大致分为四个部分组成:接口地址(url)、接口请求方式(get、post等)、请求数据(request)、响应数据(response)。虽然说后端接口的编写并没有统一规范要求,而且如何构建这几个部分每个公司要求都不同,没有什么“一定是最好的”标准,但其中最重要的关键点就是看是否规范。
二、环境说明
因为讲解的重点是后端接口,所以需要导入一个 spring-boot-starter-web 包,而 lombok 作用是简化类,前端显示则使用了 knife4j,具体使用在 Spring Boot 整合 knife4j 实现 API 文档已写明。另外从 springboot-2.3 开始,校验包被独立成了一个 starter 组件,所以需要引入如下依赖:
三、参数校验
3.1介绍
一个接口一般对参数(请求数据)都会进行安全校验,参数校验的重要性自然不必多说,那么如何对参数进行校验就有讲究了。一般来说有三种常见的校验方式,我们使用了最简洁的第三种方法:
业务层校验
Validator + BindResult 校验
Validator + 自动抛出异常
业务层校验无需多说,即手动在 Java 的 Service 层进行数据校验判断。不过这样太繁琐了,光校验代码就会有很多。
而使用 Validator+ BindingResult 已经是非常方便实用的参数校验方式了,在实际开发中也有很多项目就是这么做的,不过这样还是不太方便,因为你每写一个接口都要添加一个 BindingResult 参数,然后再提取错误信息返回给前端(简单看一下)。
@PostMapping("/addUser")public String addUser(@RequestBody @Validated User user, BindingResult bindingResult) { // 如果有参数校验失败,会将错误信息封装成对象组装在BindingResult里 List3.2 Validator + 自动抛出异常(使用)
内置参数校验如下:
首先,Validator可以非常方便的制定校验规则,并自动帮你完成校验。在入参里需要校验的字段加上注解,每个注解对应不同的校验规则,并可制定校验失败后的信息:
@Datapublic class User { @NotNull(message = "用户id不能为空") private Long id; @NotNull(message = "用户账号不能为空") @Size(min = 6, max = 11, message = "账号长度必须是6-11个字符") private String account; @NotNull(message = "用户密码不能为空") @Size(min = 6, max = 11, message = "密码长度必须是6-16个字符") private String password; @NotNull(message = "用户邮箱不能为空") @Email(message = "邮箱格式不正确") private String email;}校验规则和错误提示信息配置完毕后,接下来只需要在接口仅需要在校验的参数上加上 @Valid 注解(去掉 BindingResult 后会自动引发异常,异常发生了自然而然就不会执行业务逻辑):
@RestController@RequestMapping("user")public class ValidationController { @Autowired private ValidationService validationService; @PostMapping("/addUser") public String addUser(@RequestBody @Validated User user) { return validationService.addUser(user); }}现在我们进行测试,打开 knife4j 文档地址,当输入的请求数据为空时,Validator 会将所有的报错信息全部进行返回,所以需要与全局异常处理一起使用。
// 使用form data方式调用接口,校验异常抛出 BindException// 使用 json 请求体调用接口,校验异常抛出 MethodArgumentNotValidException// 单个参数校验异常抛出ConstraintViolationException// 处理 json 请求体调用接口校验失败抛出的异常@ExceptionHandler(MethodArgumentNotValidException.class)public ResultVO3.3 分组校验和递归校验
分组校验有三个步骤:
定义一个分组类(或接口)
在校验注解上添加 groups 属性指定分组
Controller 方法的 @Validated 注解添加分组类
public interface Update extends Default{}@Datapublic class User { @NotNull(message = "用户id不能为空",groups = Update.class) private Long id; ......}@PostMapping("update")public String update(@Validated({Update.class}) User user) { return "success";}如果Update不继承Default,@Validated({Update.class})就只会校验属于Update.class分组的参数字段;如果继承了,会校验了其他默认属于Default.class分组的字段。
对于递归校验(比如类中类),只要在相应属性类上增加@Valid注解即可实现(对于集合同样适用)
3.4 自定义校验
Spring Validation 允许用户自定义校验,实现很简单,分两步:
自定义校验注解
编写校验者类
@Target({ METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER, TYPE_USE })@Retention(RUNTIME)@Documented@Constraint(validatedBy = {HaveNoBlankValidator.class})// 标明由哪个类执行校验逻辑public @interface HaveNoBlank { // 校验出错时默认返回的消息 String message() default "字符串中不能含有空格"; Class>[] groups() default { }; Class extends Payload>[] payload() default { }; /** * 同一个元素上指定多个该注解时使用 */ @Target({ METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER, TYPE_USE }) @Retention(RUNTIME) @Documented public @interface List { NotBlank[] value(); }}public class HaveNoBlankValidator implements ConstraintValidator四、全局异常处理
参数校验失败会自动引发异常,我们当然不可能再去手动捕捉异常进行处理。但我们又不想手动捕捉这个异常,又要对这个异常进行处理,那正好使用SpringBoot全局异常处理来达到一劳永逸的效果!
4.1 基本使用
首先,我们需要新建一个类,在这个类上加上 @ControllerAdvice 或 @RestControllerAdvice 注解,这个类就配置成全局处理类了。
这个根据你的 Controller 层用的是 @Controller 还是 @RestController 来决定。
然后在类中新建方法,在方法上加上 @ExceptionHandler 注解并指定你想处理的异常类型,接着在方法内编写对该异常的操作逻辑,就完成了对该异常的全局处理!我们现在就来演示一下对参数校验失败抛出的 MethodArgumentNotValidException 全局处理:
package com.csdn.demo1.global;import org.springframework.validation.ObjectError;import org.springframework.web.bind.MethodArgumentNotValidException;import org.springframework.web.bind.annotation.ExceptionHandler;import org.springframework.web.bind.annotation.RestControllerAdvice;@RestControllerAdvice@ResponseBodypublic class ExceptionControllerAdvice { @ExceptionHandler(MethodArgumentNotValidException.class) @ResponseStatus(HttpStatus.BAD_REQUEST) public String MethodArgumentNotValidExceptionHandler(MethodArgumentNotValidException e) { // 从异常对象中拿到ObjectError对象 ObjectError objectError = e.getBindingResult().getAllErrors().get(0); // 然后提取错误提示信息进行返回 return objectError.getDefaultMessage(); } /** * 系统异常 预期以外异常 */ @ExceptionHandler(Exception.class) @ResponseStatus(value = HttpStatus.INTERNAL_SERVER_ERROR) public ResultVO> handleUnexpectedServer(Exception ex) { log.error("系统异常:", ex); // GlobalMsgEnum.ERROR是我自己定义的枚举类 return new ResultVO<>(GlobalMsgEnum.ERROR); } /** * 所以异常的拦截 */ @ExceptionHandler(Throwable.class) @ResponseStatus(value = HttpStatus.INTERNAL_SERVER_ERROR) public ResultVO> exception(Throwable ex) { log.error("系统异常:", ex); return new ResultVO<>(GlobalMsgEnum.ERROR); }}我们再次进行测试,这次返回的就是我们制定的错误提示信息!我们通过全局异常处理优雅的实现了我们想要的功能!
以后我们再想写接口参数校验,就只需要在入参的成员变量上加上 Validator 校验规则注解,然后在参数上加上 @Valid 注解即可完成校验,校验失败会自动返回错误提示信息,无需任何其他代码!
4.2 自定义异常
在很多情况下,我们需要手动抛出异常,比如在业务层当有些条件并不符合业务逻辑,而使用自定义异常有诸多优点:
自定义异常可以携带更多的信息,不像这样只能携带一个字符串。
项目开发中经常是很多人负责不同的模块,使用自定义异常可以统一了对外异常展示的方式。
自定义异常语义更加清晰明了,一看就知道是项目中手动抛出的异常。
我们现在就来开始写一个自定义异常:
package com.csdn.demo1.global;import lombok.Getter;@Getter //只要getter方法,无需setterpublic class APIException extends RuntimeException { private int code; private String msg; public APIException() { this(1001, "接口错误"); } public APIException(String msg) { this(1001, msg); } public APIException(int code, String msg) { super(msg); this.code = code; this.msg = msg; }}然后在刚才的全局异常类中加入如下:
//自定义的全局异常@ExceptionHandler(APIException.class)public String APIExceptionHandler(APIException e) { return e.getMsg();}这样就对异常的处理就比较规范了。当然还可以添加对 Exception 的处理,这样无论发生什么异常我们都能屏蔽掉然后响应数据给前端,不过建议最后项目上线时这样做,能够屏蔽掉错误信息暴露给前端,在开发中为了方便调试还是不要这样做。
另外,当我们抛出自定义异常的时候全局异常处理只响应了异常中的错误信息 msg 给前端,并没有将错误代码 code 返回。这还需要配合数据统一响应。
如果在多模块使用,全局异常等公共功能抽象成子模块,则在需要的子模块中需要将该模块包扫描加入,@SpringBootApplication(scanBasePackages = {"com.xxx"})。
五、数据统一响应
统一数据响应是我们自己自定义一个响应体类,无论后台是运行正常还是发生异常,响应给前端的数据格式是不变的!这里我包括了响应信息代码 code 和响应信息说明 msg,首先可以设置一个枚举规范响应体中的响应码和响应信息。
@Getterpublic enum ResultCode { SUCCESS(1000, "操作成功"), FAILED(1001, "响应失败"), VALIDATE_FAILED(1002, "参数校验失败"), ERROR(5000, "未知错误"); private int code; private String msg; ResultCode(int code, String msg) { this.code = code; this.msg = msg; }}自定义响应体:
package com.csdn.demo1.global;import lombok.Getter;@Getterpublic class ResultVO最后需要修改全局异常处理类的返回类型:
@RestControllerAdvicepublic class ExceptionControllerAdvice { @ExceptionHandler(APIException.class) public ResultVO最后,在 controller 层进行接口信息数据的返回:
@GetMapping("/getUser")public ResultVO经过测试,这样响应码和响应信息只能是枚举规定的那几个,就真正做到了响应数据格式、响应码和响应信息规范化、统一化!
还有一种全局返回类如下:
@Data@AllArgsConstructor@NoArgsConstructorpublic class Msg { //状态码 private int code; //提示信息 private String msg; //用户返回给浏览器的数据 private Map六、全局处理响应数据(可选择)
接口返回统一响应体 + 异常也返回统一响应体,其实这样已经很好了,但还是有可以优化的地方。要知道一个项目下来定义的接口搞个几百个太正常不过了,要是每一个接口返回数据时都要用响应体来包装一下好像有点麻烦,有没有办法省去这个包装过程呢?
当然是有的,还是要用到全局处理。但是为了扩展性,就是允许绕过数据统一响应(这样就可以提供多方使用),我们可以自定义注解,利用注解来选择是否进行全局响应包装。
首先,创建自定义注解,作用相当于全局处理类开关:
@Retention(RetentionPolicy.RUNTIME)@Target({ElementType.METHOD}) // 表明该注解只能放在方法上public @interface NotResponseBody {}其次,创建一个类并加上注解使其成为全局处理类。然后继承 ResponseBodyAdvice 接口重写其中的方法,即可对我们的 controller 进行增强操作,具体看代码和注释:
package com.csdn.demo1.global;import com.fasterxml.jackson.core.JsonProcessingException;import com.fasterxml.jackson.databind.ObjectMapper;import org.springframework.core.MethodParameter;import org.springframework.http.MediaType;import org.springframework.http.converter.HttpMessageConverter;import org.springframework.http.server.ServerHttpRequest;import org.springframework.http.server.ServerHttpResponse;import org.springframework.web.bind.annotation.RestControllerAdvice;import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice;@RestControllerAdvice(basePackages = {"com.scdn.demo1.controller"}) // 注意哦,这里要加上需要扫描的包public class ResponseControllerAdvice implements ResponseBodyAdvice为您推荐
精彩放送
热门文章
-
中药板块盘初冲高 世界快看
-
世界快讯:国家统计局:非制造业商务活动指数继续保持扩张
-
天天快讯:减速器概念股延续涨势
-
PET铜箔板块异动拉升
-
世界视点!锂电池概念股盘初走高
-
酒企频跨界扩大消费群体
-
今年以来A股IPO募资额超2100亿元 特色中型投行承销额跻身前列
-
上半年公募基金首募总额超5100亿元 同比下滑33% 全球看热讯
-
南财研选快讯|中信证券:家居消费加码提档 城市更新内外兼修
-
环球最新:南财研选快讯|中信证券:预计下游厂商会加速三氯蔗糖等更安全的甜味剂对阿斯巴甜的替代
-
辉瑞与石药集团宣布达成中国本土化新冠口服抗病毒治疗药物战略合作 全球观焦点
-
当虹科技加入中国移动元宇宙产业联盟 _环球动态
精彩图片
-
成本大减!新一轮的旗舰大战也将在即将到来的9月正式拉开帷幕
-
博览会开幕 中国首款具有自主知识产权的国产通用型科学计算软件正式发布
-
技术下降!Intel独立显卡驱动一次评测就发现43个Bug
-
高性能的台式机彻底告别“光污染” 雷克沙推出简洁纯白外观设计
-
韩国媒体率先报道:三星电子236层NAND闪存预计年内开始生产 市场竞争更激烈
-
新科技!苹果正在积极研发某种形式的AR/VR头显或智能眼镜
-
谷歌测试开展新功能 向用户展示哪些云流媒体服务拥有特定的视频游戏
-
支付宝积极响应国家为小微降费的政策号召 一年降费让利近80亿
-
京东汽车就与浦林成山旗下新能源车轮胎品牌浦林达成战略合作 助力轮胎“电动化转型”
-
苹果新专利公布:暗示未来 iPhone手机或许有陶瓷材质版
-
盖茨和韩国能源供应商SK共同牵头 其中SK投资2.5亿美元
-
海底捞早已经捞不动了 据统计上半年最高亏损达2.97亿
热文
-
【全球报资讯】埃弗顿官方:34岁队长科尔曼续约1年,将迎太妃糖生涯第15个赛季
-
全球最资讯丨淳常在结局(淳常在)
-
快讯:做了对的事情反而被起诉?天理来了!雷军给同行好好上了一“课”
-
墙纸撕不干净怎么处理(墙纸撕不干净怎么清除)
-
世界速看:感官先生表达什么意思_感官先生表达的含义
-
电焊打眼睛的小妙招_电焊打眼睛的小妙招_天天快播
-
08726_0872
-
消息!财政部:2021年全国政府采购规模为36399亿元
-
“中非”奇妙游Vlog丨连办三届,何以星沙?
-
股票短线是多长时间(最新短线股票推荐)|环球新资讯
-
【环球新视野】缅甸突发!一公路桥梁被炸,致多人伤亡
-
辉瑞与石药集团宣布达成中国本土化新冠口服抗病毒治疗药物战略合作 全球观焦点
-
今日观点!背靠能源第一省 光伏强市的电不够用了?
-
世界微动态丨再赴猕猴桃受灾区 专家组干了啥?
-
丫丫解锁新玩具竹笼:一刻也没闲着-天天最资讯
-
上海陆家嘴一场交流会,嘉宾和主持人侃侃而谈,股民全跑了?券商回应
-
环球微头条丨中国反垄断新规出台:知识产权行使范围也有“禁区”
-
599元!小米电视EA32新款发布:用上四核CPU、双频Wi-Fi
-
央行刚刚发布了三份调查问卷 折射出怎样的经济现状?
-
天天即时:下半年股价有望"翻倍"股在此!比尔·盖茨、王孝安、丘栋荣已埋伏
-
视焦点讯!笔记本运行内存4g升8g_笔记本运行内存怎么升
-
环球播报:狗没拴绳被撞伤,主人被判担主责
-
关于大连一高校食堂放多台彩票机及大连一高校食堂放多台彩票机详情
-
当虹科技加入中国移动元宇宙产业联盟 _环球动态
-
环球讯息:丽尚国潮:拟定增募资不超过7.3亿元
-
中邮理财、华夏理财“固收+权益”产品收益亮眼,部分重仓资管计划超99%|机警理财日报 最新消息
-
当前快报:易事特:拟与员工持股平台设钠离子电池公司
-
今日热门!方萍萍:黄金在1902一线多头主力以时间换空间的可能性比较大
-
世界讯息:福州房地产中介协会倡议:暑期减免应届大学毕业生租房佣金
-
环球讯息:表示喜欢句子
-
人代会定思路谋新篇,助力响水高质量发展
-
公募“中考”揭榜(一)|年内分红金额不足千亿 债基占比超八成扛大旗_看热讯
-
胡锡进:世卫要宣布无糖可乐等大量使用的阿斯巴甜致癌?这太震动了
-
当前速看:盛视科技:中标约1.06亿元设备采购项目
-
热议:晶科能源向保加利亚最大的光伏电站提供N型TOPCon双面组件
-
香港暂停进口瑞典一地区禽肉及禽类产品
-
电影《消失的她》总票房破14亿 _全球最资讯
-
红蜻蜓(603116.SH)2022年度权益分派:每股派0.35元 7月6日股权登记|全球热点
-
热点在线丨国内首台无人智慧加油通航服务站在上海投放使用
-
盒马在郑开第4店 “豫制菜”发展迎新机
-
【全球新视野】蒙犹以为犯军令不可以乡里故而废法遂垂涕斩之翻译_此言孙可以为王父尸 子不可以为父尸 翻译
-
全球观察:与年轻人完美契合 荣耀90系列销量大涨
-
中大力德:控股股东及其一致行动人拟合计减持不超2%公司股份
-
灰指甲怎么防止传染_灰指甲会传染别人吗-环球要闻
-
万里扬:机器人减速器产品处于样机试制阶段
-
英国王室伦敦地产贬值5亿英镑
-
中电联与华为数字能源技术有限公司签署战略合作协议 _微速讯
-
帝科股份:实控人之一拟减持公司不超2%股份 环球快看点
-
还敢喝无糖可乐么?世卫或在下月宣布:阿斯巴甜“可能致癌”! 世界新动态
-
时讯:中国石油大学(华东)2023年广西理工(高校专项)招生计划