【业务功能篇78】微服务-前端后端校验- 统一异常处理-JSR-303-validation注解

5. 前端校验

我们在前端提交的表单数据,我们也是需要对提交的数据做相关的校验的

Form 组件提供了表单验证的功能,只需要通过 rules 属性传入约定的验证规则,并将 Form-Item 的 prop 属性设置为需校验的字段名即可

校验的页面效果

前端数据校验就搞定了。后端校验也是不可避免的

6. 后端服务校验

6.1 JSR-303介绍

  JSR是Java Specification Requests的缩写,意思是Java 规范提案。是指向JCP(Java Community Process)提出新增一个标准化技术规范的正式请求。任何人都可以提交JSR,以向Java平台增添新的API和服务。JSR已成为Java界的一个重要标准。

  JSR-303 是JAVA EE 6 中的一项子规范,叫做Bean Validation,Hibernate Validator 是 Bean Validation 的参考实现 . Hibernate Validator 提供了 JSR 303 规范中所有内置 constraint 的实现,除此之外还有一些附加的 constraint。

  Hibernate Validator 是 Bean Validation 的参考实现 . Hibernate Validator 提供了 JSR 303 规范中所有内置 constraint 的实现,除此之外还有一些附加的 constraint。

Hibernate 中填充一部分

6.2 后端校验实现

1.需要在commons服务中添加对应的依赖

xml 复制代码
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-validation</artifactId>
            <version>2.4.12</version>
        </dependency>

2.在需要校验的Bean的字段头部添加对应的注解

3.通过@Valid注解来开启JSR303的校验

4.测试,通过postman提交空的数据

当我们提交一个非空的数据是可以通过的

5.校验不合法的提示信息,在Controller中通过 BindingResult对象来获取校验的结果信息,然后解析出来后封装为R对象响应

java 复制代码
    @RequestMapping("/save")
    //@RequiresPermissions("product:brand:save")
    public R save(@Valid @RequestBody BrandEntity brand, BindingResult result){
        if(result.hasErrors()){
            // 提交的数据经过JSR303校验后有非法的字段
            Map<String,String> map = new HashMap<>();
            List<FieldError> fieldErrors = result.getFieldErrors();
            for (FieldError fieldError : fieldErrors) {
                // 获取非法数据的 field
                String field = fieldError.getField();
                // 获取非法的field的提示信息
                String defaultMessage = fieldError.getDefaultMessage();
                map.put(field,defaultMessage);
            }
            return R.error(400,"提交的品牌表单数据不合法").put("data",map);
        }
		brandService.save(brand);

        return R.ok();
    }

然后完善其他字段你的校验规则

java 复制代码
@Data
@TableName("pms_brand")
public class BrandEntity implements Serializable {
	private static final long serialVersionUID = 1L;

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

}

6.3 统一的异常处理

  在SpringMVC中的统一异常处理我们通过ControllerAdvice来处理

java 复制代码
/**
 * 统一的异常处理类
 */
/*@ResponseBody
@ControllerAdvice*/
@Slf4j
@RestControllerAdvice(basePackages = "com.msb.mall.product.controller")
public class ExceptionControllerAdvice {
    /**
     * 处理验证异常的方法
     * @param e
     */

    @ExceptionHandler(value = MethodArgumentNotValidException.class)
    public R handlerValidExecption(MethodArgumentNotValidException e){
        Map<String,String> map = new HashMap<>();
        e.getFieldErrors().forEach((fieldError)->{
            map.put(fieldError.getField(),fieldError.getDefaultMessage());
        });
        return R.error(400,"提交的数据不合法").put("data",map);
    }

    /**
     * 系统其他的异常处理
     * @param throwable
     * @return
     */
    @ExceptionHandler(Throwable.class)
    public R handlerExecption(Throwable throwable){
        log.error("错误信息:",throwable);
        return R.error(400,"未知异常信息").put("data",throwable.getMessage());
    }
}

响应编码的规制制订,因为随着后面的业务越来越复杂,我们在响应异常信息的时候尽量准确的给客户端有用的提示信息。

通用的错误列表,响应的编码统一为5位数字,前面两位约定为业务场景,最后三位约定为错误码

10:表示通用

/001:参数格式错误 10001

/002:未知异常 10002

11:商品

12:订单

13:物流

14:会员

...

定义对应的枚举类

java 复制代码
package com.msb.common.exception;

/**
 * 错误编码和错误信息的枚举类
 */
public enum BizCodeEnume {

    UNKNOW_EXCEPTION(10000,"系统未知异常"),
    VALID_EXCEPTION(10001,"参数格式异常");

    private int code;
    private String msg;

    BizCodeEnume(int code,String msg){
        this.code = code;
        this.msg = msg;
    }
    public int getCode(){
        return code;
    }

    public String getMsg(){
        return msg;
    }
}

在统一异常处理中我们就可以使用通用的编码

java 复制代码
package com.msb.mall.product.exception;

import com.msb.common.exception.BizCodeEnume;
import com.msb.common.utils.R;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestControllerAdvice;

import java.util.HashMap;
import java.util.Map;

/**
 * 统一的异常处理类
 */
/*@ResponseBody
@ControllerAdvice*/
@Slf4j
@RestControllerAdvice(basePackages = "com.msb.mall.product.controller")
public class ExceptionControllerAdvice {
    /**
     * 处理验证异常的方法
     * @param e
     */

    @ExceptionHandler(value = MethodArgumentNotValidException.class)
    public R handlerValidExecption(MethodArgumentNotValidException e){
        Map<String,String> map = new HashMap<>();
        e.getFieldErrors().forEach((fieldError)->{
            map.put(fieldError.getField(),fieldError.getDefaultMessage());
        });
        //return R.error(400,"提交的数据不合法").put("data",map);
        return R.error(BizCodeEnume.VALID_EXCEPTION.getCode(), BizCodeEnume.VALID_EXCEPTION.getMsg())
                .put("data",map);
    }

    /**
     * 系统其他的异常处理
     * @param throwable
     * @return
     */
    @ExceptionHandler(Throwable.class)
    public R handlerExecption(Throwable throwable){
        log.error("错误信息:",throwable);
        //return R.error(400,"未知异常信息").put("data",throwable.getMessage());
        return R.error(BizCodeEnume.UNKNOW_EXCEPTION.getCode(), BizCodeEnume.UNKNOW_EXCEPTION.getMsg())
                .put("data",throwable.getMessage());
    }
}

6.4 分组校验

  在实际的业务场景中同一个Entity的校验可能会有不同的规则,比如添加数据品牌id必须为空,而更新数据品牌Id必须不为空,针对这种情况我们需要使用分组校验来实现

1>定义标志类

2>在Entity中指定分组规则

3>通过@Validated注解来实现分组校验

6.5 自定义校验注解

  面临特殊的校验需要我们可以通过正则表达式来处理,当然我们也可以通过自定义校验注解的方式来实现。

1> 创建自定义的校验注解

java 复制代码
/**
 * 自定义的校验注解
 */
@Documented
@Constraint(validatedBy = { })
@Target({ METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER, TYPE_USE })
@Retention(RUNTIME)
public @interface ListValue {

    String message() default "{com.msb.common.valid.ListValue.message}";

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

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

    int[] val() default {};
}

对应需要创建提示信息的属性文件

2>创建一个自定义的校验器

java 复制代码
package com.msb.common.valid;

import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;
import java.lang.annotation.Annotation;
import java.sql.ClientInfoStatus;
import java.util.HashSet;

/**
 * 对应的校验注解的校验器
 */
public class ListValueConstraintValidator implements ConstraintValidator<ListValue,Integer> {
    private HashSet<Integer> set = new HashSet<>();

    /**
     * 初始化的方法
     * 举例:@ListValue(val={1,0})
     * 获取到 1 0
     * @param constraintAnnotation
     */
    @Override
    public void initialize(ListValue constraintAnnotation) {
        int[] val = constraintAnnotation.val();// 0 1
        for (int i : val) {
            set.add(i);
        }
    }

    /**
     * 判断校验是否成功的方法
     * @param value 客户端传递的对应的属性的值 判断value是否在0 , 1 中
     * @param context
     * @return
     */
    @Override
    public boolean isValid(Integer value, ConstraintValidatorContext context) {
        return set.contains(value);
    }
}

3>关联自定义的校验注解和校验器

相关推荐
蝎子莱莱爱打怪2 天前
XZLL-IM干货系列 04|Netty 长连接实战:Pipeline 怎么排、心跳怎么跳、连接怎么管
后端·微服务·面试
SamDeepThinking3 天前
Java微服务练习方式
java·后端·微服务
米丘6 天前
微前端之 Web Components 完全指南
微服务·html
霸道流氓气质9 天前
领域驱动设计(DDD)在 Spring Boot 微服务中的实践指南
运维·spring boot·微服务
霸道流氓气质9 天前
Spring Boot 微服务性能优化完全指南
spring boot·微服务·性能优化
星恒随风9 天前
C++ string 类详解:常用接口、OJ 场景与模拟实现中的深浅拷贝
开发语言·c++·笔记·学习·状态模式
地瓜伯伯9 天前
从MESI缓存一致性协议讲透synchronized的底层
java·spring boot·spring·spring cloud·微服务·springcloud
Devin~Y9 天前
大厂 Java 面试实录:从音视频内容社区到 AI RAG 的全链路技术设计
java·spring boot·redis·spring cloud·微服务·kafka·音视频
递归尽头是星辰9 天前
AI 访问数据仓库:从直连到微服务化
数据仓库·人工智能·微服务·dataagent·ai数据治理
就改了10 天前
Windows 环境 SkyWalking 完整实操教程
windows·微服务·skywalking