前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >想要接口做的好、入参校验少不了!

想要接口做的好、入参校验少不了!

作者头像
敲得码黛
发布2021-03-27 21:38:54
1.4K1
发布2021-03-27 21:38:54
举报
文章被收录于专栏:敲得码黛敲得码黛

前言

两年前我刚步入社会接受毒打时,对整个开发流程还没什么概念,当时我以为的开发工作=“CRUD+接口对接”,所以代码写的那叫一个为所欲为(Map传参、不加注释、不打日志等等等等)。后来我被分到另一个项目组,原来的代码被我一个同事接手,后来这位同事辞职了。。。。。

老大瞅了一眼我写的代码,差点没把早上吃的两个包子吐出来,然后拉着我就是长达一个小时的谈话,啥代码可读性、接口可用性、系统健壮性啥的,咱也听不懂呀,从头到尾就听明白了一句话:"系统的Bug 80%以上都是因为没有做入参校验"。 小何内心ps:曾经有一个机会摆在我面前,我没有好好珍惜,如果上天再给我个机会,我一定会做好入参校验,如果非要给这件事情加个期限,我希望是Just Now

环境准备

假设我们现在需要编写一个下单接口,这个接口的参数包含商户订单号(orderId)、订单金额(money)、支付方式(payType)、平台商户号(pfMchId)、子商户号(subMchId)等字段,按照之前的写法,这个接口应该长如下模样

代码语言:javascript
复制
@RestController
public class OrderController {

 @PostMapping("/unifiedOrder")
 public String unifiedOrder(@RequestBody Map map){
     if (map.get("orderId") != null) {
         if (map.get("orderMoney") != null) {
             if (map.get("payType") != null) {
                 if(map.get("mchId") !=null&& map.get("subMchId")!=null){
                     // TO DO SOMETHING
                     return "SUCCESS";
                 }else{
                     return "平台商户号与子商户号不能同时为空";
                 }
             }else{
                 return "支付类型不能为空";
             }
         }else{
             return "订单金额不能为空";
         }
     }else{
         return "订单号不能为空";
     }

 }
}

看完这段代码不知道你怎么想,反正放到现在肯定是要被我DS的,下面就开始着手于此接口的改造。

Spring validation

引入依赖

如果是基于SpringBoot 2.3之前版本的项目会默认引入此依赖。2.3之后的版本则需要手动引入此依赖

代码语言:javascript
复制
  <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-validation</artifactId>
      <version>2.2.5.RELEASE</version>
  </dependency>

创建数据模型

这一步其实是为了避免使用Map传递数据,毕竟Map的可读性实在是不怎么理想(觉得这一步没有必要的拉出去毒打一顿) 于是将接口的入参模型Map改为如下数据模型

代码语言:javascript
复制
/**
 * 下单接口入参模型
 * @author hcq
 * @date 2021/3/13 13:23
 */
@Data
public class OrderModel {

    /**
     * 订单Id
     */
    private String orderId;

    /**
     * 订单金额
     */
    private String money;

    /**
     * 支付方式
     */
    private String payType;

    /**
     * 平台商户号
     */
    private String pfMchId;

    /**
     * 子商户号
     */
    private String subMchId;
}

接口变更如下

代码语言:javascript
复制
@RestController
public class OrderController {

    @PostMapping("/unifiedOrder")
    public String unifiedOrder(@RequestBody OrderModel order){
        if (order.getOrderId() != null) {
            if (order.getMoney() != null) {
                if (order.getPayType() != null) {
                    if(order.getPfMchId()!=null&& order.getSubMchId()!=null){
                        // TO DO SOMETHING
                        return "SUCCESS";
                    }else{
                        return "平台商户号与子商户号不能同时为空";
                    }
                }else{
                    return "支付类型不能为空";
                }
            }else{
                return "订单金额不能为空";
            }
        }else{
            return "订单号不能为空";
        }

    }
}

好吧~看起来几乎没啥变动,重头戏还在后面

SpringValidation(划重点了呃)

Spring的Validation组件使用起来十分简洁,只需要简单的两步

  • 在需要校验的接口参数前加上@Valid或者是@Validated 注解 @RestController public class OrderController { @PostMapping("/unifiedOrder") public String unifiedOrder(@Validated @RequestBody OrderModel order) { if (order.getPfMchId() != null && order.getSubMchId() != null) { // TO DO SOMETHING return "SUCCESS"; } else { return "平台商户号与子商户号不能同时为空"; } } }
  • 在需要校验的字段上添加校验规则及提示信息 @Data public class OrderModel { /** * 订单Id */ @NotNull(message = "订单Id不能为空") private String orderId; /** * 订单金额 */ @NotNull(message = "订单金额不能为空") private String money; /** * 支付方式 */ @NotNull(message = "支付方式不能为空") private String payType; /** * 平台商户号 */ private String pfMchId; /** * 子商户号 */ private String subMchId; }

做完这两步后,一个简单的入参校验就已经完成了。如果@NotNull修饰的字段为null值,那么后端服务器将会抛出BindException参数绑定异常,json类型入参则抛出MethodArgumentNotValidException异常,两种异常内部都包含着所有不符合规则的字段提示信息,此时可以由全局异常处理器捕获到此异常并进行异常响应(不清楚全局异常处理器怎样使用的可以参考我之前的文章)。

执行结果分析

PostMan发起请求,后端服务器抛出的MethodArgumentNotValidException异常被默认异常处理器DefaultHandlerExceptionResolver拦截,然后打印了相关异常信息

Validation常见的校验注解

  • @NotNull :该字段不允许为null值
  • @NotEmpty:该字段不允许为null值或空值,此注解同样适用于校验集合不允许为空
  • @Null:该注解与@NotNull正好相反,标识该字段必须为Null
  • @Pattern:通过正则表达式进行匹配,若该值无法匹配成功则抛出异常
  • @Max:通常使用在数字类型字段,标识该字段最大取值
  • @Min:通常使用在数字类型字段,标识该字段最小取值
  • @Lenth:标识该字段长度范围

自定义参数校验注解

我们会发现Validation提供的注解大多时候只能满足一些简单的校验场景,稍微复杂一点的场景就不适用于此规则了,例如最常见的一些接口规则有:多选一必填(Or)、只允许某些值中的一个(In)、多个字段不能同时上送(Mutex)等,这个时候我们可以通过自定义注解来完成相关参数的校验。还是拿刚才的栗子来讲,我们可以通过定义一个@Or自定义注解实现"pfMchId与subMchId二选一必填"的校验规则

接口改造如下

代码语言:javascript
复制
@PostMapping("/unifiedOrder")
public String unifiedOrder(@Validated @RequestBody OrderModel order) {
        // TO DO SOMETHING
        return "SUCCESS";
}
创建自定义注解Or
代码语言:javascript
复制
@Target({ElementType.TYPE})
@Repeatable(List.class)
@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy = InVerifyHandler.class)
public @interface Or {

    String[] fields() default {};

    String message() default "Invalid ID!";

    Class<?>[] groups() default {};

    Class<? extends Payload>[] payload() default {};

    
    @Target({ElementType.TYPE})
    @Retention(RetentionPolicy.RUNTIME)
    @interface List{
        Or[] value();
    }

}
  • @Target({ElementType.TYPE}):标识该注解的作用域(作用Class上)
  • @Repeatable(List.class):标志该注解可以在同一对象上重复声明
  • @Retention(RetentionPolicy.RUNTIME):标识该注解的生命周期(保留至运行时)
  • @Constraint(validatedBy = InVerifyHandler.class):标识该注解的处理类
注解处理类
代码语言:javascript
复制
@Slf4j
public class InVerifyHandler implements ConstraintValidator<Or,Object> {

    private String[] fields;

    @Override
    public void initialize(Or targetAnno) {
        fields = targetAnno.fields();
    }

    @Override
    public boolean isValid(Object value, ConstraintValidatorContext context) {
        if(fields.length==0){
            return true;
        }
        try {
            for (String field : fields) {
                Field file = value.getClass().getDeclaredField(field);
                file.setAccessible(true);
                if(file.get(value)!=null){
                    return true;
                }
            }
        }catch (Exception e){
            log.error("自定义校验注解异常:",e);
        }
        return false;
    }
}
使用介绍
代码语言:javascript
复制
@Data
@Or(fields = {"pfMchId","subMchId"},message = "pfMchId、subMchId不能同时为空")
public class OrderModel {
    /**
     * 平台商户号
     */
    private String pfMchId;

    /**
     * 子商户号
     */
   private String subMchId;
}

通过Validation注解+自定义参数校验注解几乎可以覆盖绝大多数的校验场景,当然也不可能做到百分之百覆盖,一些业务场景上的校验还是有必要手动处理一下的。

本期文章就到这里啦,你学废了吗?

本文参与?腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2021-03-13,如有侵权请联系?cloudcommunity@tencent.com 删除

本文分享自 敲得码黛 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与?腾讯云自媒体分享计划? ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 前言
  • 环境准备
  • Spring validation
    • 引入依赖
      • 创建数据模型
        • SpringValidation(划重点了呃)
          • 执行结果分析
            • Validation常见的校验注解
              • 自定义参数校验注解
                • 创建自定义注解Or
                • 注解处理类
                • 使用介绍
            领券
            问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
            http://www.vxiaotou.com