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

原文链接: 霸道的程序猿的博客 (cnblogs.com)

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

https://blog.csdn.net/BADAO_LIUMANG_QIZHI/article/details/108278834

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

https://blog.csdn.net/BADAO_LIUMANG_QIZHI/article/details/127821880

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

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

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

http://doc.ruoyi.vip/ruoyi/document/htsc.html#%E5%8F%82%E6%95%B0%E9%AA%8C%E8%AF%81

 

 

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

 

 

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

复制代码
    @Xss(message = "用户账号不能包含脚本字符")
    @NotBlank(message = "用户账号不能为空")
    @Size(min = 0, max = 30, message = "用户账号长度不能超过30个字符")
    public String getUserName()
    {
        return userName;
    }
复制代码

查看@NotBlank注解的实现

 

 

发现其来自javax.validation。

注:

博客:
https://blog.csdn.net/badao_liumang_qizhi
关注公众号
霸道的程序猿
获取编程相关电子书、教程推送与免费下载。

实现

后台实现流程

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

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

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

添加pom依赖

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

若依框架添加在common模块中

 

 

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

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

 

 

 

自定义全局捕获异常

 

复制代码
/**
 * 全局异常处理器
 *
 * @author ruoyi
 */
@RestControllerAdvice
public class GlobalExceptionHandler
{
    private static final Logger log = LoggerFactory.getLogger(GlobalExceptionHandler.class);

    /**
     * 自定义验证异常
     */
    @ExceptionHandler(BindException.class)
    public AjaxResult handleBindException(BindException e)
    {
        log.error(e.getMessage(), e);
        String message = e.getAllErrors().get(0).getDefaultMessage();
        return AjaxResult.error(message);
    }

    /**
     * 自定义验证异常
     */
    @ExceptionHandler(MethodArgumentNotValidException.class)
    public Object handleMethodArgumentNotValidException(MethodArgumentNotValidException e)
    {
        log.error(e.getMessage(), e);
        String message = e.getBindingResult().getFieldError().getDefaultMessage();
        return AjaxResult.error(message);
    }
}
复制代码

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

 

 

  

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

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

复制代码
    @Xss(message = "用户昵称不能包含脚本字符")
    @Size(min = 0, max = 30, message = "用户昵称长度不能超过30个字符")
    public String getNickName()
    {
        return nickName;
    }

    public void setNickName(String nickName)
    {
        this.nickName = nickName;
    }

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

    public void setUserName(String userName)
    {
        this.userName = userName;
    }

    @Email(message = "邮箱格式不正确")
    @Size(min = 0, max = 50, message = "邮箱长度不能超过50个字符")
    public String getEmail()
    {
        return email;
    }

    public void setEmail(String email)
    {
        this.email = email;
    }

    @Size(min = 0, max = 11, message = "手机号码长度不能超过11个字符")
    public String getPhonenumber()
    {
        return phonenumber;
    }
复制代码

注解参数说明

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

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

比如添加用户的Controller

复制代码
    @PreAuthorize("@ss.hasPermi('system:user:add')")
    @Log(title = "用户管理", businessType = BusinessType.INSERT)
    @PostMapping
    public AjaxResult add(@Validated @RequestBody SysUser user)
    {

    }
复制代码

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

复制代码
    @PostMapping("/importData")
    public AjaxResult importData(MultipartFile file, String planDateString) throws Exception {
        ExcelUtil<LimitQuotaStatistics> util = new ExcelUtil<LimitQuotaStatistics>(LimitQuotaStatistics.class);
        List<LimitQuotaStatistics> limitQuotaStatisticsList = util.importExcel(file.getInputStream());
        String message =limitQuotaStatisticsService.insertLimitQuotaStatisticsBatch(limitQuotaStatisticsList,planDateString);
        return success(message);
    }
复制代码

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

先通过

    @Autowired
    protected Validator validator;

注入依赖

再通过

  BeanValidators.validateWithException(validator, limitQuotaStatistics);

进行参数校验

复制代码
@Service
public class LimitQuotaStatisticsServiceImpl implements ILimitQuotaStatisticsService {

    private static final Logger log = LoggerFactory.getLogger(LimitQuotaStatisticsServiceImpl.class);

    @Autowired
    private LimitQuotaStatisticsMapper limitQuotaStatisticsMapper;

    @Autowired
    protected Validator validator;


    @Override
    public String insertLimitQuotaStatisticsBatch(List<LimitQuotaStatistics> limitQuotaStatisticsList, String planDateString) throws ParseException {

        if (StringUtils.isNull(limitQuotaStatisticsList) || limitQuotaStatisticsList.size() == 0) {
            throw new ServiceException("导入数据不能为空!");
        }
        int successNum = 0;
        int failureNum = 0;
        StringBuilder successMsg = new StringBuilder();
        StringBuilder failureMsg = new StringBuilder();

        Date plaeDate = new SimpleDateFormat("yyyy-MM-dd").parse(planDateString);

        for (LimitQuotaStatistics limitQuotaStatistics : limitQuotaStatisticsList) {
            try {
                BeanValidators.validateWithException(validator, limitQuotaStatistics);
                limitQuotaStatistics.setPlanDate(plaeDate);
                this.insertLimitQuotaStatistics(limitQuotaStatistics);
                successNum++;
                successMsg.append("<br/>" + successNum + "、" + limitQuotaStatistics.getDeptName() + " 导入成功");

            } catch (Exception e) {
                failureNum++;
                String msg = "<br/>" + failureNum + "、" + limitQuotaStatistics.getDeptName() + " 导入失败:";
                failureMsg.append(msg + e.getMessage());
                log.error(msg, e);
            }
        }
        if (failureNum > 0) {
            failureMsg.insert(0, "很抱歉,导入失败!共 " + failureNum + " 条数据格式不正确,错误如下:");
            throw new ServiceException(failureMsg.toString());
        } else {
            successMsg.insert(0, "恭喜您,数据已全部导入成功!共 " + successNum + " 条,数据如下:");
        }
        return successMsg.toString();
    }
}
复制代码

其中BeanValidators的实现如下

复制代码
package com.bdtd.limit.common.utils.bean;

import java.util.Set;
import javax.validation.ConstraintViolation;
import javax.validation.ConstraintViolationException;
import javax.validation.Validator;

/**
 * bean对象属性验证
 *
 * @author ruoyi
 */
public class BeanValidators
{
    public static void validateWithException(Validator validator, Object object, Class<?>... groups)
            throws ConstraintViolationException
    {
        Set<ConstraintViolation<Object>> constraintViolations = validator.validate(object, groups);
        if (!constraintViolations.isEmpty())
        {
            throw new ConstraintViolationException(constraintViolations);
        }
    }
}
复制代码

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

复制代码
@Data
@AllArgsConstructor
@NoArgsConstructor
@Builder
public class LimitQuotaStatistics extends BaseEntity
{
    private static final long serialVersionUID = 1L;

    /** id */
    private Long id;

    /** 部门id */
    private Long deptId;

    /** 部门名称 */
    @Excel(name = "部门名称")
    @NotNull(message = "部门名称为空")
    private String deptName;

    /** 夜班人数 */
    @Excel(name = "夜班人数")
    @NotNull(message = "夜班人数为空或格式不对")
    @Range(max = 1000, min = 1, message = "夜班人数需在1-1000之间")
    private Long nightShiftNum;
}
复制代码

部分字段,仅供参考。

前端页面组件实现

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

复制代码
<template>
  <!-- 用户导入对话框 -->
  <el-dialog :title="title" :visible.sync="open" width="400px" append-to-body>
    <div class="block">
      <span class="demonstration">计划日期: </span>
      <el-date-picker
        v-model="planDate"
        type="date"
        placeholder="选择计划日期"
        size="small"
        value-format="yyyy-MM-dd"
      >
      </el-date-picker>
    </div>
    <br />
    <el-upload
      ref="upload"
      :limit="1"
      accept=".xlsx, .xls"
      :headers="headers"
      :action="upLoadUrl + '?planDateString=' + this.planDate"
      :disabled="isUploading"
      :on-progress="handleFileUploadProgress"
      :on-success="handleFileSuccess"
      :auto-upload="false"
      drag
    >
      <i class="el-icon-upload"></i>
      <div class="el-upload__text">将文件拖到此处,或<em>点击上传</em></div>
      <div class="el-upload__tip text-center" slot="tip">
        <span>仅允许导入xls、xlsx格式文件。</span>
      </div>
    </el-upload>
    <div slot="footer" class="dialog-footer">
      <el-button type="primary" @click="submitFileForm">确 定</el-button>
      <el-button @click="open = false">取 消</el-button>
    </div>
  </el-dialog>
</template>

<script>
import { getToken } from "@/utils/auth";
import moment from "moment";

export default {
  data() {
    return {
      // 是否显示弹出层(用户导入)
      open: false,
      // 弹出层标题(用户导入)
      title: "",
      // 是否禁用上传
      isUploading: false,
      //计划日期
      planDate: new Date(),
      // 设置上传的请求头部
      headers: { Authorization: "Bearer " + getToken() },
      // 上传的地址
      upLoadUrl: "",
    };
  },
  mounted() {
    //默认计划日期为明天
    this.planDate = moment().subtract(-1, "days").format("YYYY-MM-DD");
  },
  methods: {
    /** 导入按钮操作 */
    handleImport(data) {
      this.title = data.title;
      this.upLoadUrl = process.env.VUE_APP_BASE_API + data.upLoadUrl;
      this.open = true;
    },
    // 提交上传文件
    submitFileForm() {
      this.$refs.upload.submit();
    },
    // 文件上传中处理
    handleFileUploadProgress() {
      this.isUploading = true;
    },
    // 文件上传成功处理
    handleFileSuccess(response) {
      this.open = false;
      this.isUploading = false;
      this.$refs.upload.clearFiles();
      this.$alert(
        "<div style='overflow: auto;overflow-x: hidden;max-height: 70vh;padding: 10px 20px 0;'>" +
          response.msg +
          "</div>",
        "导入结果",
        { dangerouslyUseHTMLString: true }
      );
      //上传数据成功后重新请求数据
      this.$emit("getList");
    },
  },
};
</script>

<style>
</style>
复制代码

导入测试效果

posted @ 2024-07-25 11:59  枫树湾河桥  阅读(231)  评论(0编辑  收藏  举报
Live2D