IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> JavaScript知识库 -> 生活小妙招之前后端校验 -> 正文阅读

[JavaScript知识库]生活小妙招之前后端校验

关于前后端校验

关于前端表单校验

这里指的前端校验特指表单校验,即el-form组件,分为一般校验和自定义校验规则校验:

一般校验
<el-form :model="ruleForm" :rules="MyRules" ref="ruleForm" label-width="100px" class="demo-ruleForm">
  <el-form-item label="活动名称" prop="name">
    <el-input v-model="ruleForm.name"></el-input>
  </el-form-item>
</el-form> 

这里的:rules="MyRules"绑定了一个校验规则属性,我们在data中定义这个MyRules数组,如下:

MyRules: {
          name: [
            { required: true, message: '请输入活动名称', trigger: 'blur' },
            { min: 3, max: 5, message: '长度在 3 到 5 个字符', trigger: 'blur' }
          ],
}  

"name"表示给哪个字段校验,可以为字段匹配很多校验规则

自定义校验规则校验
<el-form
      :model="dataForm"
      :rules="dataRule"
      ref="dataForm"
      @keyup.enter.native="dataFormSubmit()"
      label-width="80px"
    >
      <el-form-item label="检索首字母" prop="firstLetter">
        <el-input
          v-model="dataForm.firstLetter"
          placeholder="检索首字母"
        ></el-input>
      </el-form-item>
      
      <el-form-item label="排序" prop="sort">
        <el-input v-model.number="dataForm.sort" placeholder="排序"></el-input>
      </el-form-item>
</el-form>
dataRule: {
        firstLetter: [
          {
            validator: (rule, value, callback) => {
              if (value == "") {
                callback(new Error("首字母不能为空"));
              } else if (!/^[a-zA-Z]$/.test(value)) {
                callback(new Error("首字母必须到a-z或A-Z之间的单一字符"));
              }
              callback();
            },
            trigger: "blur",
          },
        ],
        sort: [
          {
            validator: (rule, value, callback) => {
              if (value == "") {
                callback(new Error("排序字段不能为空"));
              } else if (!Number.isInteger(value * 1) || value < 0) {
                callback(new Error("排序字段必须是整数并且非负"));
              }
              callback();
            },
            trigger: "blur",
          },
        ],
      },
关于后端校验(JSR303)

JSR303规定了数据校验的标准,在包:javax.validation.constraints下有很多用于校验的注解,以下就是一些截图:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-f7kG994P-1643636550183)(1643544476932.png)]

如何在springboot中使用JSR303相关注解:

  1. 给需要校验的类的字段添加注解,比如@NotBlank,如果没有通过校验则会显示默认message,如果想修改,可以更新message字段:

    /**
     * 品牌名
     */
    @NotBlank(message = "品牌名不能为空")
    private String name;
    
  2. 在给后端提交数据的时候告诉SpringMVC这个数据需要校验

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-MQ8tI8Wd-1643636550197)(1643544897258.png)]

  3. 通过postman演示:

    {
        "timestamp": "2022-01-30T12:24:53.679+0000",
        "status": 400,
        "error": "Bad Request",
        "errors": [
            {
                "codes": [
                    "NotBlank.brandEntity.name",
                    "NotBlank.name",
                    "NotBlank.java.lang.String",
                    "NotBlank"
                ],
                "arguments": [
                    {
                        "codes": [
                            "brandEntity.name",
                            "name"
                        ],
                        "arguments": null,
                        "defaultMessage": "name",
                        "code": "name"
                    }
                ],
                "defaultMessage": "品牌名不能为空",
                "objectName": "brandEntity",
                "field": "name",
                "rejectedValue": "",
                "bindingFailure": false,
                "code": "NotBlank"
            }
        ],
        "message": "Validation failed for object='brandEntity'. Error count: 1",
        "path": "/product/brand/save"
    }
    
  4. 我们其实是想将校验结果放到我们自定义的R(即统一响应对象中)一并返给前端,这样我们就可以随意的自己获取到错误信息:

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-K1uUl0np-1643636550199)(1643545735883.png)]

    我们只需要在Controller层多接收一个BindingResult字段用以拿到将错误结果封装的类即可:

        @RequestMapping("/save")
        public R save(@Valid @RequestBody BrandEntity brand, BindingResult result){
            if(result.hasErrors()){
                Map<String,String> map = new HashMap<>();
                result.getFieldErrors().forEach((item) ->{
                    //获取到错误提示
                    String message = item.getDefaultMessage();
                    //获取的发生错误的字段
                    String field = item.getField();
                    map.put(field,message);
                });
                return R.error(400,"提交的数据不合法").put("data",map);
            }else{
                brandService.save(brand);
            }
            return R.ok();
        }
    
  5. 这时候再通过postman请求你会发现成了现在这样:

    {
        "msg": "提交的数据不合法",
        "code": 400,
        "data": {
            "name": "品牌名不能为空"
        }
    }
    
  6. 我们再完善完善我们的校验注解:

    /**
     * 品牌id
     */
    @NotNull(message = "修改必须指定品牌id")
    @TableId
    private Long brandId;
    /**
     * 品牌名
     */
    @NotBlank(message = "品牌名必须提交")
    private String name;
    /**
     * 品牌logo地址
     */
    @URL(message = "logo必须是一个合法的url地址")
    private String logo;
    /**
     * 介绍
     */
    private String descript;
    /**
     * 显示状态[0-不显示;1-显示]
     */
    private Integer showStatus;
    /**
     * 检索首字母
     */
    @NotEmpty
    @Pattern(regexp="^[a-zA-Z]$",message = "检索首字母必须是一个字母")
    private String firstLetter;
    /**
     * 排序
     */
    @NotNull
    @Min(value = 0,message = "排序必须大于等于0")
    private Integer sort;
    

    我们指定logo必须得提交,因此添加了非空注解,并且提交的必须符合url格式,因此该字段添加了两个注解。另外我们需要注意,@NotEmpty不能修饰Integer字段,这种情况我们就使用@NotNull


上述我们使用BindingResult接收错误结果,但我们发现得在每个Controller的方法中写那些封装结果的逻辑:

if(result.hasErrors()){
            Map<String,String> map = new HashMap<>();
            result.getFieldErrors().forEach((item) ->{
                //获取到错误提示
                String message = item.getDefaultMessage();
                //获取的发生错误的字段
                String field = item.getField();
                map.put(field,message);
            });
            return R.error(400,"提交的数据不合法").put("data",map);

好像脑子有什么大问题似的,我们可以做一个统一的产后处理,即写一个集中处理所有异常的类。我们使用的是SpringMVC提供的ControllerAdvice功能。

自定义全局异常统一处理校验结果
  1. 新建全局异常处理类,并添加@ControllerAdvice注解,该注解可以指明哪些Controller可以通过该异常类捕获,比如我们写:@ControllerAdvice(basePackages = “com.fiji.shangpinhui.product.controller”)

  2. 我们在刚才的Controller中接收BindingResult是为了捕获由于数据校验有问题而产生的异常,并封装到自己的响应对象R中,现在将异常统一交由全局异常类处理,因此不需要捕获异常,只考虑正确的逻辑即可。将代码都删除利索:

        @RequestMapping("/save")
        public R save(@Valid @RequestBody BrandEntity brand/*BindingResult result*/){
    //        if(result.hasErrors()){
    //            Map<String,String> map = new HashMap<>();
    //            result.getFieldErrors().forEach((item) ->{
    //                //获取到错误提示
    //                String message = item.getDefaultMessage();
    //                //获取的发生错误的字段
    //                String field = item.getField();
    //                map.put(field,message);
    //            });
    //            return R.error(400,"提交的数据不合法").put("data",map);
    //        }else{
                brandService.save(brand);
    //        }
            return R.ok();
        }
    
  3. 编写全局异常处理类:

    @ExceptionHandler(value = MethodArgumentNotValidException.class)
    public R handleValidException(MethodArgumentNotValidException e){
        log.error("数据校验出现问题{},\r\n异常类型:{}",e.getMessage(),e.getClass());
        BindingResult result = e.getBindingResult();
        Map<String,String> map = new HashMap<>();
        result.getFieldErrors().forEach((item) ->{
            //获取到错误提示
            String message = item.getDefaultMessage();
            //获取的发生错误的字段
            String field = item.getField();
            map.put(field,message);
            log.error("field:{},\r\n::message:{}",field,message);
        });
        return R.error(400,"提交的数据不合法").put("data",map);
    }
    

    还记得我们是如何知道异常处理器接受的是MethodArgumentNotValidException类型的异常么?我们最先接收道德是Exception这个巨大的异常类,通过e.getClass()打印出实际捕获到的异常,这点非常考察经验呀雷神牛逼!!之所以这么做我们是想只有在发生数据校验异常的时候走我们的handleValidException()这个全局异常处理方法,功能细化;除此之外,我们还会写一个兜底的方法,用来处理所有的异常

  4. 最后我们可以使用postman进行请求:

    {
        "msg": "提交的数据不合法",
        "code": 400,
        "data": {
            "brandId": "修改必须指定品牌id",
            "name": "品牌名必须提交"
        }
    }
    
为全局异常定义不同类型的状态码

刚才我们为数据校验赋上了一个400的状态码,所有的状态码汇聚到一个枚举类,这样前端会根据不同的状态码给出不同的解决方式,这都是从公司中累积的经验与解决方案,比如我们使用如下规范的错误码:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-xPVnNqN8-1643636550200)(1643630063402.png)]

我们可以编写全局异常枚举类来细化我们的异常响应:

public enum BizCodeEnum {

    INVALID_EXCEPTION(10000,"数据校验错误"),
    UNKNOW_EXCEPTION(10001,"系统为止异常");

    private Integer code;
    private String msg;

    BizCodeEnum(Integer code, String msg){
        this.code = code;
        this.msg = msg;
    }

    public Integer getCode() {
        return code;
    }

    public String getMsg() {
        return msg;
    }
}

接着修改我们的全局异常代码,使用postman测试如下:

{
    "msg": "数据校验错误",
    "code": 10000,
    "data": {
        "brandId": "修改必须指定品牌id",
        "name": "品牌名必须提交"
    }
}
数据校验更高级的功能:分组校验

场景:对于品牌管理来讲,新增品牌和修改品牌需要校验的同一个字段的规则可能是不一样的,比如说品牌id,作为品牌表的主键,新增品牌时不需要进行非空校验,而修改品牌时必须要进行校验;再比如品牌的logo,如果是新增的话我们限制logo必须非空,而当我们修改品牌的时候,logo如果不发生变化就允许提交的时候为空,不提交,也就是说logo字段在不同的情况下校验规则不同。面对这种情况,我们使用JSR303提供给我们的分组校验功能

想使用分组校验功能,那就得在我们熟悉的校验注解里面设置属性(message就是我们设置的一个属性),groups,用于标注我们的校验注解是用于哪一种情况才进行校验的,也就是属于哪一组。以下是实现步骤:

  1. 为校验注解标注什么情况需要校验。为此我们可以看到groups属性接收一个Class类型的数组。在这种情况我们一般会编写空接口,比如AddGroup.java或是UpdateGroup.java 用以指定是添加的分组还是更新时的分组。代码修改如下:

    /**
     * 品牌id
     */
    @NotNull(message = "修改必须指定品牌id",groups = {UpdateGroup.class})
    @Null(message = "添加时不能指定品牌id",groups = {AddGroup.class})
    @TableId
    private Long brandId;
    
  2. 在Controller层将@Valid注解替换成Spring提供的 @Validated注解,里面有属性指明是哪个分组,这样就能确定是哪种校验了:

    public R save(@Validated({AddGroup.class}) @RequestBody BrandEntity brand/*BindingResult result*/){
    
  3. 使用postman进行测试两部分内容:a、分组校验是否生效,给save接口提交一个brandId;b、未指定分组的字段校验是否生效,给提交的logo一个错误格式的url。

    你会发现分组校验确实好用,但与此同时,原来给logo字段添加的校验却失效了。没办法,现在我们想让它生效就得给他指定是哪一个组的。加上之后再用postman测试:

    {
        "msg": "数据校验错误",
        "code": 10000,
        "data": {
            "logo": "logo必须是一个合法的url地址"
        }
    }
    
  4. 最后说明以下@Validated注解,该注解当添加了分组时,会使标注了分组的校验注解生效,而当@Validated没有指明分组时,情况就反过来了,只会让没有标注分组的校验注解生效,这个很好验证

数据校验更高级的功能:自定义校验

场景:对于只能接收0或1的字段,我们可以使用@Pattern(regexp = “”)设置正则表达式进行校验。但如果校验规则过于复杂的话我们就可以使用自定义校验,它就像service里的一段可以复用的代码,在此将其抽取出来。我们就以品牌的showStatus举例,showStatus只能有0或1两种状态。原来想实现这样的校验规则,可能我们会添加@Max注解和@Min注解,并设置最大值为1,最小值为0

  1. 编写一个自定义的校验注解。我们想达到这样一种效果:那就是在showStatus上面标注一个比如说是@ListValue注解,并指明value是1,2;即@ListValue(value={1,2}),这样来控制showStatus取值范围。因此首先我们创建一个校验注解:ListValue。

    • 根据JSR303规范,一个校验注解首先必须有三个属性:message(当校验出错以后错误信息去哪里取)、groups(支持分组校验的功能)、payload(自定义一些负载信息)。因此我们就仿照着@NotNull将这三个属性先复制过来

    • 添加元注解信息。比较重要的是@Constraint注解,用于指定校验器。

    • 我们看到一些地方爆红,我们需要导入Validation API

      <dependency>
          <groupId>javax.validation</groupId>
          <artifactId>validation-api</artifactId>
          <version>2.0.1.Final</version>
      </dependency>
      
  2. 编写一个自定义的校验器

    @Constraint里面指明我们需要的校验器。点击进该注解我们看到里面有一个属性:

    Class<? extends ConstraintValidator<?, ?>>[] validatedBy();
    

    就是我们需要指明的校验器,这个校验器你看到了是需要实现ConstraintValidator接口。ConstraintValidator接口含有两个泛型,一个是@Constraint所修饰的自定义校验器,那就是我们的ListValue注解,另一个是该自定义注解需要修饰的字段的类型,这里shoistValuewStatus是Integer类型,因此我们创建一个名为ListValueConstraintValidator的实现类如下:

    public class ListValueConstraintValidator implements ConstraintValidator<ListValue,Integer> {
    }
    

    而以下就是自定义校验器的完整代码:

    public class ListValueConstraintValidator implements ConstraintValidator<ListValue,Integer> {
    
        private Set<Integer> set = new HashSet<>();
    
        /**
         * 初始化方法
         * @param integer  这个值就是需要校验的实际值,比如前端提交了个3,我们就需要校验3是否在{0,1}里面
         * @param constraintValidatorContext
         * @return
         */
        @Override
        public boolean isValid(Integer integer, ConstraintValidatorContext constraintValidatorContext) {
            return set.contains(integer);
        }
    
        //判断是否校验成功
        @Override
        public void initialize(ListValue constraintAnnotation) {
            //value里面就是0和1
            int[] value = constraintAnnotation.value();
            for (int i : value) {
                set.add(i);
            }
        }
    }
    
  3. 关联自定义的校验注解和自定义的校验器,即:

    @Constraint(
            validatedBy = {ListValueConstraintValidator.class}
    )
    
  4. 使用自定义校验注解:

    @ListValue(value = {0,1},groups = {AddGroup.class},message = "必须在0、1之间")
    private Integer showStatus;
    
  5. 使用postman测试

    {
        "msg": "数据校验错误",
        "code": 10000,
        "data": {
            "showStatus": "必须在0、1之间"
        }
    }
    
  6. 最后插上那么一句,我们刚才那个自定义校验器只能校验Integer类型的,@Constraint可以指定多个不同的校验器,适配不同类型的校验

  JavaScript知识库 最新文章
ES6的相关知识点
react 函数式组件 & react其他一些总结
Vue基础超详细
前端JS也可以连点成线(Vue中运用 AntVG6)
Vue事件处理的基本使用
Vue后台项目的记录 (一)
前后端分离vue跨域,devServer配置proxy代理
TypeScript
初识vuex
vue项目安装包指令收集
上一篇文章      下一篇文章      查看所有文章
加:2022-02-01 20:30:57  更:2022-02-01 20:31:19 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/24 11:33:07-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码