SpringBoot校验框架Validation、统一异常处理及项目的国际化

Validation校验框架

传统代码开发,接口参数基本校验一般在Controller层完成,在校验时会写很多的if判断,导致代码冗长。幸运的是lombok的Validation校验框架提供了很多注解,运用这些注解做参数校验就变得非常简单了。具体做法是在Controller层方法入口参数上加上@Valid注解开启参数验证

    @PostMapping("/save")
    @ApiOperation("生成提货任务")
    public Result save(@RequestBody @Valid TakeOutDto takeOutDto) {
        log.info("生成提货任务,参数:{}", takeOutDto);
        return Results.newSuccessResult();
    }

在实体属性上加上对应注解

@Data
public class TakeOutDto {
    // 提货方式
    @NotNull(message = "请选择提货方式")
    private Integer takeOutCheck;

    // 出库负责人
    private Integer operator;

    // 提货要求
    @Size(max = 500, message = "提货要求太长了")
    private String comments;
}

这样就完成了对属性takeOutCheck和comments的校验,是不是很方便。

统一异常处理

代码开发业务处理中会遇到各种情况,抛出各种异常,甚至会有我们没有处理到的异常抛到页面上,让用户看到一堆无法理解的异常信息,即不友好也不安全,所以找一个方法把异常统一处理掉,以我们规定的形式反馈出去就变得非常有必要了。
首先在项目中,我们可能定义了一堆异常类,用于在不同场景下的异常处理
在这里插入图片描述
异常基类

/**
 * 异常基类
 * @author: sts
 * @since: 2021/5/19 15:59
 */
public class BaseException extends RuntimeException {
    int code;

    protected BaseException(String message, int code) {
        super(message);
        this.code = code;
    }
}

具体异常类

/**
 * 参数异常类
 * @author: sts
 * @since: 2021/6/24 16:20
 */
public class ParamException extends BaseException {
    public ParamException(String message) {
        super(message, CommonStateCode.PARAM_ERROR);
    }
}

在业务处理中使用这些异常类抛出异常

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void discard(TakeOutVo takeOutVo) {
        TakeOutEntity takeOutEntity = takeOutRepo.getById(takeOutVo.getId());
        if (1 == takeOutEntity.getIsDeleted()) {
            throw new ParamException("任务已被删除");
        }
     }

此时代码运行到异常抛出行就会把异常信息抛到页面,显示我们提供的异常描述信息。页面要想友好的提醒用户,就要处理这个异常,需要判断接口返回是否正常,那有没有一种办法让接口返回统一的格式方便接收者处理呢,答案是肯定的,这就是统一异常处理。
利用@ExceptionHandler注解,分别处理不同的异常

/**
 * 全局异常处理器
 *
 * @author Alan
 * @since 2021-12-30 14:08
 **/
@Slf4j
@ControllerAdvice
public class GlobalExceptionHandler {

    /**
     * 处理自定义异常
     */
    @ExceptionHandler(BusinessException.class)
    @ResponseBody
    public Result handleBusinessException(BusinessException e) {
        log.error(e.getMessage(), e);
        return Results.newFailedResult(CommonStateCode.BUSI_ERROR, e.getMessage());
    }

    /**
     * 处理自定义异常
     */
    @ExceptionHandler(ParamException.class)
    @ResponseBody
    public Result handleParamException(ParamException e) {
        log.error(e.getMessage(), e);
        return Results.newFailedResult(CommonStateCode.PARAM_ERROR, e.getMessage());
    }

    /**
     * 处理自定义异常
     */
    @ExceptionHandler(AuthException.class)
    @ResponseBody
    public Result handleAuthException(AuthException e) {
        log.error(e.getMessage(), e);
        return Results.newFailedResult(CommonStateCode.AUTH_ERROR, e.getMessage());
    }
    
    @ExceptionHandler(Exception.class)
    @ResponseBody
    public Result handleException(Exception e) {
        log.error(e.getMessage(), e);
        return Results.newFailedResult(CommonStateCode.BUSI_ERROR, "系统异常,请联系管理员");
    }

可以看到我们返回的信息结构与Controller层是一致的,这样调用者处理接口结果就可以统一了。

国际化

项目涉及国际业务或国外用户,就要考虑国际化问题。后台代码的国际化主要是处理校验信息和异常信息。
首先是建资源文件
在这里插入图片描述
在这里插入图片描述
为资源文件命名,点+号增加语言种类
在这里插入图片描述
最后点OK按钮
在这里插入图片描述
生成如下文件
在这里插入图片描述
将描述信息以key value形式写入资源文件,key的命名要规范,方便开发

## VO参数校验类
vo.TakeOutDto.takeOutCheck.NOT_EMPTY=请选择提货方式
vo.InventoryDetailDto.quantity.NOT_EMPTY =请输入新增数量

## 异常类
ex.NoHandlerFoundException.001=路径不存在,请检查路径是否正确
ex.DuplicateKeyException.001=数据重复,请检查后提交
ex.Exception.001=系统异常,请联系管理员

## 参数异常
ex.ParamException.TAKEOUT.001=请先选择关联出库单
ex.ParamException.TAKEOUT.002=启动提货任务失败:没找到关联任务
ex.ParamException.TAKEOUT.003=任务{0}已删除

## 业务异常
ex.BusinessException.TAKEOUT.001=库存数量不足,库位{0},剩余数量{1}
ex.BusinessException.TAKEOUT.002=产品待提货数量不足,提货任务行{0},剩余数量{1}

校验的国际化

@Data
public class TakeOutDto {
    // 提货方式
    @NotNull(message = "{vo.TakeOutDto.takeOutCheck.NOT_EMPTY}")
    private Integer takeOutCheck;

    // 出库负责人
    private Integer operator;

    // 提货要求
    private String comments;

    // 关联提货任务单列表
    private List<Integer> outstockTaskEntityIdList;
}

异常的国际化

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void discard(TakeOutVo takeOutVo) {
        TakeOutEntity takeOutEntity = takeOutRepo.getById(takeOutVo.getId());
        if (1 == takeOutEntity.getIsDeleted()) {
            throw new ParamException(i18nUtil.getInterNationalMsg("ex.ParamException.TAKEOUT.003", takeOutVo.getId()));
        }

        if (takeOutEntity.getStatus() == 1 || takeOutEntity.getStatus() == 6) {
            takeOutEntity.setStatus(5);
            takeOutEntity.setUpdatedBy(0);
            takeOutEntity.setUpdatedAt(LocalDateTime.now());
            takeOutRepo.updateById(takeOutEntity);
        } else {
            throw new BusinessException("ex.BusinessException.TAKEOUT.003");
        }
    }

获取异常信息的工具类

/**
 * 国际化工具类
 *
 * @author Alan
 * @since 2022-02-08 18:00
 **/

@Component
public class I18nUtil {
    @Resource
    private HttpServletRequest request;

    private static final String ACCEPT_LANGUAGE = "Accept-Language";
    private static final String FILTER_PREFIX = "#";

    /**
     * 获取国际化消息
     *
     * @param key  国际化的key
     * @param args 被替换占位符的值
     *             1.带#号的不是key的字符串,直接过滤#号
     *             2.是key的字符串,有占位符的替换
     *             3.不是key的字符串,返回字符串本身
     */
    public String getInterNationalMsg(String key, Object... args) {
        //带#号的不是key的字符串,直接过滤#号
        if (key.startsWith(FILTER_PREFIX)) {
            return key.substring(1);
        }
        String requestHeader = request.getHeader(ACCEPT_LANGUAGE);
        //是key的字符串,有占位符的替换
        try {
            String propertiesName = "/I18nMessages.properties";
            //如en-US,zh-CN
            if (!StringUtils.isEmpty(requestHeader) && "en-US".equals(requestHeader)) {
                propertiesName = "/I18nMessages_en_US.properties";
            }

            InputStream inputStream = this.getClass().getResourceAsStream(propertiesName);
            InputStreamResource resource = new InputStreamResource(inputStream);
            EncodedResource encodedResource = new EncodedResource(resource, "utf-8");
            ResourcePropertySource rp = new ResourcePropertySource(encodedResource);
            return MessageFormat.format((String) rp.getProperty(key), args);
        } catch (Exception e) {
            //不是key的字符串,返回字符串本身
            return key;
        }
    }
}

增加拦截器统一处理接口返回值,将key替换为对应语言描述信息

/**
 * 全局控制器处理
 *
 * @author Alan
 * @since 2022-02-09 15:23
 **/

@ControllerAdvice
public class ResponseBodyHandler implements ResponseBodyAdvice {
    @Autowired
    private I18nUtil i18nUtil;

    @Override
    public boolean supports(MethodParameter methodParameter, Class aClass) {
        return true;
    }

    @Override
    public Object beforeBodyWrite(Object body, MethodParameter methodParameter, MediaType mediaType, Class aClass, ServerHttpRequest serverHttpRequest, ServerHttpResponse serverHttpResponse) {
        if (body instanceof Result) {
            if (200 != ((Result) body).getCode()) {
                ((Result) body).setMsg(i18nUtil.getInterNationalMsg(((Result) body).getMsg()));
            }
        }
        return body;
    }
}

至此,后台的国际化处理完成

  • 2
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: 要校验多个列表是否相同,可以按照以下步骤: 1. 定义一个自定义注解,用于标注需要校验的列表。例如: ```java @Target({ElementType.FIELD}) @Retention(RetentionPolicy.RUNTIME) @Documented @Constraint(validatedBy = ListValidator.class) public @interface SameList { String message() default "Lists are not same"; Class<?>[] groups() default {}; Class<? extends Payload>[] payload() default {}; } ``` 2. 定义一个校验器,用于校验被@SameList注解标注的字段。例如: ```java public class ListValidator implements ConstraintValidator<SameList, List<?>> { @Override public boolean isValid(List<?> value, ConstraintValidatorContext context) { if (value == null || value.size() == 0) { return true; } // 判断多个列表是否相同 // ... return true; // 返回校验结果 } } ``` 3. 在需要校验的实体类中,使用@SameList注解标注需要校验的列表。例如: ```java public class MyEntity { @SameList private List<String> list1; @SameList private List<Integer> list2; // getters and setters } ``` 4. 在需要进行校验的地方,使用Spring Boot提供的@Valid注解进行校验。例如: ```java @RestController public class MyController { @PostMapping("/myapi") public ResponseEntity<?> myApi(@RequestBody @Valid MyEntity myEntity) { // 处理请求 return ResponseEntity.ok().build(); } } ``` 在以上示例中,@Valid注解将会触发Spring Boot自动校验MyEntity对象中被@SameList注解标注的字段。如果列表不相同,则校验将失败,并且会抛出异常。否则,校验将会通过,程序将会继续执行。 ### 回答2: 可以通过比较多个List的大小、元素以及顺序来校验它们是否一样。下面是一个使用Spring Boot校验多个List是否一样的示例代码: ```java import java.util.ArrayList; import java.util.List; public class ListComparator { public static boolean compareLists(List<?> list1, List<?> list2) { if (list1 == null && list2 == null) { return true; } if ((list1 == null && list2 != null) || (list1 != null && list2 == null)) { return false; } if (list1.size() != list2.size()) { return false; } for (int i = 0; i < list1.size(); i++) { if (!list1.get(i).equals(list2.get(i))) { return false; } } return true; } public static void main(String[] args) { List<Integer> list1 = new ArrayList<>(); list1.add(1); list1.add(2); list1.add(3); List<Integer> list2 = new ArrayList<>(); list2.add(1); list2.add(2); list2.add(3); List<Integer> list3 = new ArrayList<>(); list3.add(1); list3.add(2); list3.add(4); System.out.println(compareLists(list1, list2)); // 输出 true System.out.println(compareLists(list1, list3)); // 输出 false } } ``` 以上代码中的`compareLists`方法用于比较两个List是否一样,在比较过程中,首先检查两个List是否同时为空或同时不为空,然后比较它们的大小是否一致,最后逐个比较元素是否相等。在`main`方法中,我们创建了几个不同的List,并使用`compareLists`方法进行比较,最终输出比较结果。在本示例中,`list1`和`list2`的元素相同,而`list1`和`list3`的元素不同,所以比较结果分别为true和false。 以上是通过比较List的大小、元素以及顺序来校验多个List是否一样的方法,你也可以根据实际需求自定义校验规则。 ### 回答3: 在Spring Boot中,我们可以使用CollectionUtils工具类来校验多个List是否一样。 首先,我们需要导入spring-boot-starter-validation依赖,该依赖包含了Hibernate Validator校验框架。 然后,我们可以定义一个自定义的校验注解,用于校验多个List是否一样。例如: ```java @Target({ElementType.TYPE, ElementType.ANNOTATION_TYPE}) @Retention(RetentionPolicy.RUNTIME) @Constraint(validatedBy = ListEqualsValidator.class) public @interface ListEquals { String message() default "Lists are not equal"; Class<?>[] groups() default {}; Class<? extends Payload>[] payload() default {}; } ``` 接下来,我们需要实现一个校验器ListEqualsValidator,用于校验多个List是否一样。例如: ```java public class ListEqualsValidator implements ConstraintValidator<ListEquals, List<?>> { @Override public boolean isValid(List<?> value, ConstraintValidatorContext context) { return CollectionUtils.isEqualCollection(value); } } ``` 最后,我们可以在需要校验的类上使用@ListEquals注解进行校验。例如: ```java @ListEquals public class MyListValidation { private List<String> list1; private List<String> list2; // getter and setter } ``` 在使用时,我们可以通过调用Validator进行校验。例如: ```java @Autowired private Validator validator; public void validate(MyListValidation myListValidation) { Set<ConstraintViolation<MyListValidation>> violations = validator.validate(myListValidation); if (!violations.isEmpty()) { // 校验不通过,做相应处理 } } ``` 以上就是使用Spring Boot校验多个List是否一样的简单步骤。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值