• SpringBoot+@Validated实现参数验证(非空、类型、范围、格式等)-若依前后端导入Excel数据并校验为例


    场景

    若依管理系统前后端分离版基于ElementUI和SpringBoot怎样实现Excel导入和导出:

    若依管理系统前后端分离版基于ElementUI和SpringBoot怎样实现Excel导入和导出_霸道流氓气质的博客-CSDN博客

    SpringBoot+Vue实现excel导入带格式化的时间参数(moment格式化明天日期并设置el-date-picker默认值):

    SpringBoot+Vue实现excel导入带格式化的时间参数(moment格式化明天日期并设置el-date-picker默认值)_霸道流氓气质的博客-CSDN博客

    在上面搭建SpringBoot+Vue并实现Excel导入的基础上,为避免导入的excel中数据格式不规范

    导致产生大量脏数据,所以需要对excel的数据进行校验,比如不能为空、类型是否为数字、数字范围等等规则。

    若依官方对这块有专门说明

    后台手册 | RuoYi

     

    说明比较简单,具体实现流程如下,也可参考其用户导入的实现代码和流程。

     

    这里对用户账号做了非空校验,需要在实体类上对应属性添加

    1.     @Xss(message = "用户账号不能包含脚本字符")
    2.     @NotBlank(message = "用户账号不能为空")
    3.     @Size(min = 0, max = 30, message = "用户账号长度不能超过30个字符")
    4.     public String getUserName()
    5.     {
    6.         return userName;
    7.     }

    查看@NotBlank注解的实现

     

    发现其来自javax.validation。

    注:

    博客:
    霸道流氓气质的博客_CSDN博客-C#,架构之路,SpringBoot领域博主
    关注公众号
    霸道的程序猿
    获取编程相关电子书、教程推送与免费下载。

    实现

    后台实现流程

    Hibernate Validator 是 Bean Validation 的参考实现 。Hibernate Validator 提供了 JSR 303 规范中所有内置 constraint 的实现,

    除此之外还有一些附加的 constraint 在日常开发中,Hibernate Validator经常用来验证bean的字段,基于注解,方便快捷高效。

    在SpringBoot中可以使用@Validated,注解Hibernate Validator加强版,也可以使用@Valid原来Bean Validation java版本

    添加pom依赖

    1.         <!-- 自定义验证注解 -->
    2.         <dependency>
    3.             <groupId>org.springframework.boot</groupId>
    4.             <artifactId>spring-boot-starter-validation</artifactId>
    5.         </dependency>

    若依框架添加在common模块中

     

    使用 Validation API 进行参数效验步骤整个过程如下图所示,用户访问接口,然后进行参数效验 ,

    如果效验通过,则进入业务逻辑,否则抛出异常,交由全局异常处理器进行处理

     

     

    自定义全局捕获异常

    1. /**
    2.  * 全局异常处理器
    3.  *
    4.  * @author ruoyi
    5.  */
    6. @RestControllerAdvice
    7. public class GlobalExceptionHandler
    8. {
    9.     private static final Logger log = LoggerFactory.getLogger(GlobalExceptionHandler.class);
    10.     /**
    11.      * 自定义验证异常
    12.      */
    13.     @ExceptionHandler(BindException.class)
    14.     public AjaxResult handleBindException(BindException e)
    15.     {
    16.         log.error(e.getMessage(), e);
    17.         String message = e.getAllErrors().get(0).getDefaultMessage();
    18.         return AjaxResult.error(message);
    19.     }
    20.     /**
    21.      * 自定义验证异常
    22.      */
    23.     @ExceptionHandler(MethodArgumentNotValidException.class)
    24.     public Object handleMethodArgumentNotValidException(MethodArgumentNotValidException e)
    25.     {
    26.         log.error(e.getMessage(), e);
    27.         String message = e.getBindingResult().getFieldError().getDefaultMessage();
    28.         return AjaxResult.error(message);
    29.     }
    30. }

    若依框架中将其定义在framework模块下

     

    @ExceptionHandler用于指定异常处理方法。当与@RestControllerAdvice配合使用时,用于全局处理控制器里的异常。

    在需要校验的实体类属性上或者get方法上添加校验规则注解 ,比如下面

    1.     @Xss(message = "用户昵称不能包含脚本字符")
    2.     @Size(min = 0, max = 30, message = "用户昵称长度不能超过30个字符")
    3.     public String getNickName()
    4.     {
    5.         return nickName;
    6.     }
    7.     public void setNickName(String nickName)
    8.     {
    9.         this.nickName = nickName;
    10.     }
    11.     @Xss(message = "用户账号不能包含脚本字符")
    12.     @NotBlank(message = "用户账号不能为空")
    13.     @Size(min = 0, max = 30, message = "用户账号长度不能超过30个字符")
    14.     public String getUserName()
    15.     {
    16.         return userName;
    17.     }
    18.     public void setUserName(String userName)
    19.     {
    20.         this.userName = userName;
    21.     }
    22.     @Email(message = "邮箱格式不正确")
    23.     @Size(min = 0, max = 50, message = "邮箱长度不能超过50个字符")
    24.     public String getEmail()
    25.     {
    26.         return email;
    27.     }
    28.     public void setEmail(String email)
    29.     {
    30.         this.email = email;
    31.     }
    32.     @Size(min = 0, max = 11, message = "手机号码长度不能超过11个字符")
    33.     public String getPhonenumber()
    34.     {
    35.         return phonenumber;
    36.     }

    注解参数说明

    注解名称功能
    @Xss检查该字段是否存在跨站脚本工具
    @Null检查该字段为空
    @NotNull不能为null
    @NotBlank不能为空,常用于检查空字符串
    @NotEmpty不能为空,多用于检测list是否size是0
    @Max该字段的值只能小于或等于该值
    @Min该字段的值只能大于或等于该值
    @Past检查该字段的日期是在过去
    @Future检查该字段的日期是否是属于将来的日期
    @Email检查是否是一个有效的email地址
    @Pattern(regex=,flag=)被注释的元素必须符合指定的正则表达式
    @Range(min=,max=,message=)被注释的元素必须在合适的范围内
    @Size(min=, max=)检查该字段的size是否在min和max之间,可以是字符串、数组、集合、Map等
    @Length(min=,max=)检查所属的字段的长度是否在min和max之间,只能用于字符串
    @AssertTrue用于boolean字段,该字段只能为true
    @AssertFalse该字段的值只能为false

    其它格式校验的注解可自行搜索,用法较多。

    如果是在Controller中传参时进行校验,可以直接添加注解@Validated

    比如添加用户的Controller

    1.     @PreAuthorize("@ss.hasPermi('system:user:add')")
    2.     @Log(title = "用户管理", businessType = BusinessType.INSERT)
    3.     @PostMapping
    4.     public AjaxResult add(@Validated @RequestBody SysUser user)
    5.     {
    6.     }

    但是形如Excel导入时,Controller接收的是MultipartFile数据,无法在Controller添加校验

    1.     @PostMapping("/importData")
    2.     public AjaxResult importData(MultipartFile file, String planDateString) throws Exception {
    3.         ExcelUtil<LimitQuotaStatistics> util = new ExcelUtil<LimitQuotaStatistics>(LimitQuotaStatistics.class);
    4.         List<LimitQuotaStatistics> limitQuotaStatisticsList = util.importExcel(file.getInputStream());
    5.         String message =limitQuotaStatisticsService.insertLimitQuotaStatisticsBatch(limitQuotaStatisticsList,planDateString);
    6.         return success(message);
    7.     }

    就需要在serviceImpl中通过如下方式进行注入和使用

    先通过

    1.     @Autowired
    2.     protected Validator validator;

    注入依赖

    再通过

      BeanValidators.validateWithException(validator, limitQuotaStatistics);

    进行参数校验

    1. @Service
    2. public class LimitQuotaStatisticsServiceImpl implements ILimitQuotaStatisticsService {
    3.     private static final Logger log = LoggerFactory.getLogger(LimitQuotaStatisticsServiceImpl.class);
    4.     @Autowired
    5.     private LimitQuotaStatisticsMapper limitQuotaStatisticsMapper;
    6.     @Autowired
    7.     protected Validator validator;
    8.     @Override
    9.     public String insertLimitQuotaStatisticsBatch(List<LimitQuotaStatistics> limitQuotaStatisticsList, String planDateString) throws ParseException {
    10.         if (StringUtils.isNull(limitQuotaStatisticsList) || limitQuotaStatisticsList.size() == 0) {
    11.             throw new ServiceException("导入数据不能为空!");
    12.         }
    13.         int successNum = 0;
    14.         int failureNum = 0;
    15.         StringBuilder successMsg = new StringBuilder();
    16.         StringBuilder failureMsg = new StringBuilder();
    17.         Date plaeDate = new SimpleDateFormat("yyyy-MM-dd").parse(planDateString);
    18.         for (LimitQuotaStatistics limitQuotaStatistics : limitQuotaStatisticsList) {
    19.             try {
    20.                 BeanValidators.validateWithException(validator, limitQuotaStatistics);
    21.                 limitQuotaStatistics.setPlanDate(plaeDate);
    22.                 this.insertLimitQuotaStatistics(limitQuotaStatistics);
    23.                 successNum++;
    24.                 successMsg.append("
      "
      + successNum + "、" + limitQuotaStatistics.getDeptName() + " 导入成功");
    25.             } catch (Exception e) {
    26.                 failureNum++;
    27.                 String msg = "
      "
      + failureNum + "、" + limitQuotaStatistics.getDeptName() + " 导入失败:";
    28.                 failureMsg.append(msg + e.getMessage());
    29.                 log.error(msg, e);
    30.             }
    31.         }
    32.         if (failureNum > 0) {
    33.             failureMsg.insert(0, "很抱歉,导入失败!共 " + failureNum + " 条数据格式不正确,错误如下:");
    34.             throw new ServiceException(failureMsg.toString());
    35.         } else {
    36.             successMsg.insert(0, "恭喜您,数据已全部导入成功!共 " + successNum + " 条,数据如下:");
    37.         }
    38.         return successMsg.toString();
    39.     }
    40. }

    其中BeanValidators的实现如下

    1. package com.bdtd.limit.common.utils.bean;
    2. import java.util.Set;
    3. import javax.validation.ConstraintViolation;
    4. import javax.validation.ConstraintViolationException;
    5. import javax.validation.Validator;
    6. /**
    7.  * bean对象属性验证
    8.  *
    9.  * @author ruoyi
    10.  */
    11. public class BeanValidators
    12. {
    13.     public static void validateWithException(Validator validator, Object object, Class<?>... groups)
    14.             throws ConstraintViolationException
    15.     {
    16.         Set<ConstraintViolation<Object>> constraintViolations = validator.validate(object, groups);
    17.         if (!constraintViolations.isEmpty())
    18.         {
    19.             throw new ConstraintViolationException(constraintViolations);
    20.         }
    21.     }
    22. }

    这里要校验的实体类添加规则为

    1. @Data
    2. @AllArgsConstructor
    3. @NoArgsConstructor
    4. @Builder
    5. public class LimitQuotaStatistics extends BaseEntity
    6. {
    7.     private static final long serialVersionUID = 1L;
    8.     /** id */
    9.     private Long id;
    10.     /** 部门id */
    11.     private Long deptId;
    12.     /** 部门名称 */
    13.     @Excel(name = "部门名称")
    14.     @NotNull(message = "部门名称为空")
    15.     private String deptName;
    16.     /** 夜班人数 */
    17.     @Excel(name = "夜班人数")
    18.     @NotNull(message = "夜班人数为空或格式不对")
    19.     @Range(max = 1000, min = 1, message = "夜班人数需在1-1000之间")
    20.     private Long nightShiftNum;
    21. }

    部分字段,仅供参考。

    前端页面组件实现

    组件实现代码参考若依官方文档中示例

    1. <template>
    2.   <!-- 用户导入对话框 -->
    3.   <el-dialog :title="title" :visible.sync="open" width="400px" append-to-body>
    4.     <div class="block">
    5.       <span class="demonstration">计划日期: </span>
    6.       <el-date-picker
    7.         v-model="planDate"
    8.         type="date"
    9.         placeholder="选择计划日期"
    10.         size="small"
    11.         value-format="yyyy-MM-dd"
    12.       >
    13.       </el-date-picker>
    14.     </div>
    15.     <br />
    16.     <el-upload
    17.       ref="upload"
    18.       :limit="1"
    19.       accept=".xlsx, .xls"
    20.       :headers="headers"
    21.       :action="upLoadUrl + '?planDateString=' + this.planDate"
    22.       :disabled="isUploading"
    23.       :on-progress="handleFileUploadProgress"
    24.       :on-success="handleFileSuccess"
    25.       :auto-upload="false"
    26.       drag
    27.     >
    28.       <i class="el-icon-upload"></i>
    29.       <div class="el-upload__text">将文件拖到此处,或<em>点击上传</em></div>
    30.       <div class="el-upload__tip text-center" slot="tip">
    31.         <span>仅允许导入xls、xlsx格式文件。</span>
    32.       </div>
    33.     </el-upload>
    34.     <div slot="footer" class="dialog-footer">
    35.       <el-button type="primary" @click="submitFileForm">确 定</el-button>
    36.       <el-button @click="open = false">取 消</el-button>
    37.     </div>
    38.   </el-dialog>
    39. </template>
    40. <script>
    41. import { getToken } from "@/utils/auth";
    42. import moment from "moment";
    43. export default {
    44.   data() {
    45.     return {
    46.       // 是否显示弹出层(用户导入)
    47.       open: false,
    48.       // 弹出层标题(用户导入)
    49.       title: "",
    50.       // 是否禁用上传
    51.       isUploading: false,
    52.       //计划日期
    53.       planDate: new Date(),
    54.       // 设置上传的请求头部
    55.       headers: { Authorization: "Bearer " + getToken() },
    56.       // 上传的地址
    57.       upLoadUrl: "",
    58.     };
    59.   },
    60.   mounted() {
    61.     //默认计划日期为明天
    62.     this.planDate = moment().subtract(-1, "days").format("YYYY-MM-DD");
    63.   },
    64.   methods: {
    65.     /** 导入按钮操作 */
    66.     handleImport(data) {
    67.       this.title = data.title;
    68.       this.upLoadUrl = process.env.VUE_APP_BASE_API + data.upLoadUrl;
    69.       this.open = true;
    70.     },
    71.     // 提交上传文件
    72.     submitFileForm() {
    73.       this.$refs.upload.submit();
    74.     },
    75.     // 文件上传中处理
    76.     handleFileUploadProgress() {
    77.       this.isUploading = true;
    78.     },
    79.     // 文件上传成功处理
    80.     handleFileSuccess(response) {
    81.       this.open = false;
    82.       this.isUploading = false;
    83.       this.$refs.upload.clearFiles();
    84.       this.$alert(
    85.         "
      " +
    86.           response.msg +
    87.           "
      ",
  •         "导入结果",
  •         { dangerouslyUseHTMLString: true }
  •       );
  •       //上传数据成功后重新请求数据
  •       this.$emit("getList");
  •     },
  •   },
  • };
  • </script>
  • <style>
  • </style>
  • 导入测试效果

     

  • 相关阅读:
    MoeCTF2023web
    蓝牙5.0对比4.2的主要优势
    我用Python写了几个摸鱼小游戏,赐你2023年度上班上学摸鱼必备良品!(附源码)
    5.从ODS层抽取数据到DWD层(字段脱敏,字段名字统一)
    仿黑马点评-redis整合【二——商户查询缓存】——缓存穿透、缓存击穿的解决
    Scala 高阶(七):集合内容汇总(上篇)
    大恒相机SDK开发
    Android异步和线程
    【CI/CD】详解自动化开发之CI/CD(持续集成、持续交付、持续部署)
    TS的内置对象
  • 原文地址:https://blog.csdn.net/BADAO_LIUMANG_QIZHI/article/details/127871424