超好用的【通用Excel导入功能】


  • 一、需求

    • 1-1、下载模板(excel)

    • 1-2、数据填充并导入

    • 1-3、导入结果展示

    • 1-4、数据校验规则

  • 二、方案思路

  • 三、代码使用

    • 3-1、新建一个VO,比如你导入的字段是 `name`、`age`、`gender` 那么你的实体应该如下:

    • 3-2、新增一个ImportExcel接口的实现类,实现里面的 check 方法(这个接口下面讲解)

    • 3-3、调用

  • 四、代码思路讲解

    • 4-1、准备

    • 4-2、importCheck方法

    • 4-3、check 方法

    • 4-4、其它


之前写过一个简单的Excel导入功能,大家可以先看这个:

最近接到一个需求,做一个Excel的批量导入功能,具体的步骤如下:


一、需求

1-1、下载模板(excel)

姓名(2-4个字符) 年纪(1-150) 性别(男/女)


1-2、数据填充并导入

姓名(2-4个字符) 年纪(1-150) 性别(男/女)
张三丰 20
小道仙97 24
慕容晓晓 18 女性


1-3、导入结果展示

超好用的【通用Excel导入功能】

1-4、数据校验规则

  • 姓名不能为空且只能是2-4个字符
  • 年纪不能为空且只能是1-150
  • 性别不能为空且只能是男/女
  • 导入的数据不能有两个同名,如果存在则都导入失败
  • 数据库如果有同名的人也不可以导入


二、方案思路

使用上面的Excel导入功能,把Excel模板数据解析成对应的 List<Map<String,Object>> 数据。

使用数据把对应的数据格式化成对应的实体,再依次循环按照条件去校验对应的数据。

其实除了导入数据校验规则之外,其它都是通用的,比如返回字段、解析excel并封装成bean,对异常数据进行处理等。


三、代码使用

3-1、新建一个VO,比如你导入的字段是 nameagegender 那么你的实体应该如下:

注:省略了 get 、set 、和空参构造方法


public class TestImportExcelVo {

    /**
     * 表头
     */

    public final static String[] HEADER = {"name","age","gender"};


    /**
     * 姓名
     */

    private String name;

    /**
     * 年纪
     */

    private String age;

    /**
     * 性别
     */

    private String gender;
}

可以看到唯一不同的就是多了一个常量HEADER,它的规则就是导入模板字段顺序


3-2、新增一个ImportExcel接口的实现类,实现里面的 check 方法(这个接口下面讲解)


@Service
public class TestImportExcelImpl implements ImportExcel<TestImportExcelVo{

    @Override
    public String[] check(ImportItem importItem, List<TestImportExcelVo> successResult, List<ImportItem> errorResult) {
        
        return new String[]{"1",""};
    }
}


3-3、调用

@RestController
public class ImportExcelController {
    @Autowired
    public TestImportExcelImpl testImportExcelImpl;

    @PostMapping("/importExcel")
    public Object importExcel(MultipartFile file) {

       return testImportExcelImpl.importCheck(file, TestImportExcelVo.HEADER, TestImportExcelVo.class);
    }
}


四、代码思路讲解

其实就是看接口 ImportExcel 里面的两个方法。

4-1、准备

这里使用了两个bean如下(去掉了get/set)

/**
 * 导出错误结果集实体bean
 *
 * 因为错误结果集需要多一个原因字段
 *
 * @author 小道仙
 * @date 2021年8月4日
 */

public class ImportItem<T{

    private String errorReason;

    public T t;
}
/**
 * 导入Excel返回结果实体
 *
 * @author 小道仙
 * @date 2021-08-04
 */

public class ImportResultVo<T{

    /**
     * 总行数
     */

    private Integer totalRows;

    /**
     * 错误行数
     */

    private Integer errorRows;

    /**
     * 成功行数
     */

    private Integer successRows;

    /**
     * 失败结果集
     */

    private List<T> errorResults;

    /**
     * 成功结果集
     */

    private List<T> successResults;

    public ImportResultVo() {
    }

    public ImportResultVo(Integer totalRows,Integer errorRows,Integer successRows,List<T> errorResults,List<T> successResults) {
        this.totalRows = totalRows;
        this.errorRows = errorRows;
        this.successRows = successRows;
        this.errorResults =errorResults;
        this.successResults = successResults;
    }
}


4-2、importCheck方法

这是一个默认方法完成一些通用的功能,如:excel解析、实体bean封装等

default ImportResultVo importCheck(MultipartFile file, String[] header,Class clazz){
    // 从excel里面解析出来数据
    List<Map<String, Object>> maps;
    try {
        maps = ExcelUtils.importExcel(file, header);
    }catch (Exception e){
        logger.error("解析失败,数据模板不对",e);
        throw new RuntimeException("解析失败,数据模板不对");
    }
    if (CollectionUtils.isEmpty(maps)){
        return new ImportResultVo(0,0,0,null,null);
    }

    // 对数据进行逐步的验证
    int totalRows = 0;
    int successRows = 0;
    int errorRows = 0;
    List<T> successResult = new ArrayList<>(50);
    List<ImportItem> errorResult = new ArrayList<>(50);
    for (Map map : maps) {
        ImportItem<T> importItem = new ImportItem();
        totalRows ++;
        try {
            Object tmpT = JsonUtils.objectToPojo(map, clazz);
            importItem.setT((T)tmpT);
        }catch (Exception e) {
            errorRows ++;
            logger.error("bean转换失败", e);
            continue;
        }
        String[] check = check(importItem, successResult, errorResult);
        if ("1".equals(check[0])) {
            successRows ++;
            successResult.add(importItem.getT());
        }else if ("2".equals(check[0])) {
            errorRows ++;
            importItem.setErrorReason(check[1]);
            errorResult.add(importItem);
        }else if ("3".equals(check[0])){
            successRows --;
            errorRows ++;
            importItem.setErrorReason(check[1]);
            errorResult.add(importItem);
        }
    }
    return new ImportResultVo(totalRows,errorRows,successRows,errorResult,successResult);
}


4-3、check 方法

这是一个接口方法,需要自己去实现具体的校验规则

/**
 *
 * @param t 当前需要校验的实体bean
 * @param successResult 已校验的正确结果集
 * @param errorResult 已校验错误的结果集
 * @return 第一个参数是校验结果,第二个是失败的原因
 *      1  成功
 *      2  正常失败(比如字段为空,字段格式不对,和数据库冲突等)
 *      3  异常失败(和之前已经成功的数据冲突,比如唯一性字段已经存在了)
 *      注:【异常失败】的时候,你需要在check方法里面去删除掉之前成功的数据,并把之前的数据插入到 errorResult 里面
 */

String[] check(ImportItem t, List<T> successResult, List<ImportItem> errorResult);


4-4、其它

这里的excel导出工具类之前已经说过了,这里就简单的复制一下吧。

/**
 * Excel 导入
 * @param file 文件
 * @param keys 数据顺序
 */

public static  List<Map<String, Object>>  importExcel(MultipartFile file, String[] keys) throws Exception{
    Workbook wb = null;
    String fileName = file.getOriginalFilename();
    if (fileName.endsWith("xls")) {
        POIFSFileSystem pois = new POIFSFileSystem(file.getInputStream());
        wb = new HSSFWorkbook(pois);
    } else if (fileName.endsWith("xlsx")) {
        wb = new XSSFWorkbook(file.getInputStream());
    }
    Sheet sheet = wb.getSheetAt(0);
    int rowCount = sheet.getPhysicalNumberOfRows();
    if (sheet.getRow( 1).getPhysicalNumberOfCells() != keys.length){
        throw new RuntimeException("导入的Excel和模板的列不匹配");
    }
    List<Map<String,Object>> result = new ArrayList<>();
    for (int i = 0; i < rowCount - 1; i++) {
        Row row = sheet.getRow(i + 1);
        Map<String,Object> tmp = new HashMap<>();
        for (int j = 0;j < keys.length; j++){
            Cell cell = row.getCell(j);
            if(cell == null) {
                tmp.put(keys[j], null);
            }else {
                // 把类型转行Spring
                cell.setCellType(CellType.STRING);
                tmp.put(keys[j], cell.getStringCellValue());
            }
        }
        result.add(tmp);
    }
    return result;
}



原文始发于微信公众号(小道仙97):超好用的【通用Excel导入功能】

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/41415.html

(0)
小半的头像小半

相关推荐

发表回复

登录后才能评论
极客之音——专业性很强的中文编程技术网站,欢迎收藏到浏览器,订阅我们!