数据合法性校验
实现方式
实现方式可分为隐式调用与显示调用两种,下面我分别的介绍一下这两种方式的使用。
隐式调用
应用场景
- 服务间调用,例:对外提供的接口
使用方式
- 请求参数定义
@Data
public class AddSpuRequest {@NotBlank(message = "spu名称不能为空")private String spuName;@NotBlank(message = "spu编码不能为空")private String spuCode;@NotEmpty(message = "sku不能为空")@Validprivate List<AddSkuRequest> skuList;// ... 其它字段
}@Data
public class AddSkuRequest {@NotBlank(message = "sku名称不能为空")private String skuName;@NotBlank(message = "sku编码不能为空")private String skuCode;@NotBlank(message = "upc不能为空")private String upc;@NotNull(message = "库存不能为空")private Integer stock;// ... 其它字段
}
- Controller
@RestController
@RequestMapping(value = "/spu")
public class SpuController {@Resourcepublic SpuService spuService;@PostMapping(path = "/add")public Result<Boolean> addSpu(@RequestBody @Valid AddSpuRequest request) {spuService.addSpu(request);return Result.success();}
}
- 统一异常捕获
@ControllerAdvice
@ResponseBody
@Slf4j
public class GlobalExceptionHandler {@ExceptionHandler(BindException.class)public Result<?> notValidExceptionHandle(BindException e) {return Result.fail(e.getBindingResult().getAllErrors().get(0).getDefaultMessage());}@ExceptionHandler(Exception.class)public Result<?> exceptionHandler(Exception ex) {log.error("其他错误, message:{}", ex.getMessage(), ex);return Result.error();}// ... 其它异常捕获处理
}
小结
通过以上隐式调用的方式,我们可以处理项目中的接口请求参数的必传校验,并通过全局的异常捕获处理,将校验抛出的异常进行返回给客户端进行友好提示。
显示调用
应用场景
- 服务内调用,例:数据导入
使用方式
- 数据导入DTO对象
@Data
public class ImportSkuDTO {@NotBlank(message = "sku名称不能为空")private String skuName;@NotBlank(message = "sku编码不能为空")private String skuCode;@NotBlank(message = "upc不能为空")private String upc;@NotNull(message = "库存不能为空")private Integer stock;// ... 其它字段
}
- 数据导入响应VO
@Data
public class ImportSkuVO {/*** 成功数量*/private Integer successCount;/*** 失败数量*/private Integer failCount;/*** 异常详细内容*/private List<ImportSkuFailVO> errorList;
}@Data
@NoArgsConstructor
@AllArgsConstructor
public class ImportSkuFailVO {/*** 异常数据行数*/private Integer lineNumber;/*** 异常原因*/private String message;
}
- 业务方法,以数据导入为例
@Slf4j
@Service
public class SkuService {@Resourceprivate Validator validator;private static final int MAX_IMPORT_SIZE = 100;@Overridepublic ImportSkuVO importSku(MultipartFile file) {ImportSkuVO importSkuVO = new ImportSkuVO();List<ImportSkuDTO> successList = new ArrayList<>();List<ImportSkuFailVO> errorList = new ArrayList<>();try (InputStream inputStream = file.getInputStream()) {ExcelReaderBuilder excelReaderBuilder = EasyExcel.read(inputStream).head(ImportSkuDTO.class);excelReaderBuilder.registerReadListener(new AnalysisEventListener<ImportSkuDTO>() {int currentRow = 1;@Overridepublic void invoke(ImportSkuDTO data, AnalysisContext context) {try {if (successList.size() >= MAX_IMPORT_SIZE) {throw new BizException("单次导入不得超过" + MAX_IMPORT_SIZE + "条");}validate(data);successList.add(data);} catch (Exception e) {errorList.add(new ImportSkuFailVO(currentRow, e.getMessage()));}currentRow++;}@Overridepublic void doAfterAllAnalysed(AnalysisContext context) {if (!errorList.isEmpty()) {log.info("导入完成,成功{}条,失败{}条", successList.size(), errorList.size());}}}).sheet().doRead();// 批量保存成功数据if (!successList.isEmpty()) {List<Sku> skuList = this.convertSkuList(successList);// 保存sku信息}importSkuVO.setSuccessCount(successList.size());importSkuVO.setFailCount(errorList.size());importSkuVO.setErrorList(errorList);} catch (Exception e) {log.error("import sku exception", e);throw new BizException("文件读取异常", e);}return importSkuVO;}private List<Sku> convertSkuList(List<ImportSkuDTO> successList) {List<Sku> skuList = new ArrayList<>();for (ImportSkuDTO importSkuDTO : successList) {// ... 数据转换赋值}return skuList;}/*** 显示调用校验参数,将参数异常,数据抛出业务异常,并在调用处捕获记录*/public void validate(ImportSkuDTO importSkuDTO) {Set<ConstraintViolation<ImportSkuDTO >> violations = validator.validate(importSkuDTO);if (!violations.isEmpty()) {String errorMsg = violations.stream().map(v -> v.getPropertyPath() + " " + v.getMessage()).collect(Collectors.joining("; "));throw new BizException(errorMsg);}}
}
小结
显示调用的方式,更加的灵活,适用于数据已进入方法内部,业务逻辑处理运行时状态进行数据校验,此种方式更加的简洁,当然开发者也可以手动进行if判断,逐个字段取出,但如果没有业务逻辑属性,单纯空值,数据范围,基础性的校验时完全可以采用此方式。
总结
- 隐式调用更加常见,适用于服务间的接口调用,进入方法时进行拦截处理,减少业务逻辑的报错可能性,也可以使得代码更加的简洁。
- 显示调用更适合于服务内部的调用,数据已进入业务逻辑中,在一个方法需要调用其它本服务的逻辑组件时,可以采取这种方式,如:数据导入时的校验