• 【Excel】使用 SpringBoot 实现 Excel 文件的导入与导出


    为了大家能更好地理解这篇博文,在看之前,建议去看看我的上一篇博文:【File】使用 SpringBoot 实现文件的上传与下载。因为,这篇博文是在上一篇博文基础之上继续开发的。

    这两篇博文互有联系:文件的导入就是文件的上传;文件的导出就是文件的下载。

    至于为什么是 导入/导出 Excel 文件呢?因为 Excel 文件用的频率更多吧。顺便来复习下通过 EasyExcel 来操作 Excel 文件。

    编码思路:

    Excel 导入:

    1. 浏览文件夹,选择需要上传的 Excel 文件,这里使用 POSTMAN 工具;
    2. 将本地文件上传至服务器指定位置;
    3. 服务器解析Excel文件;
    4. 将Excel中解析的数据存入数据库中。

    Excel 导出

    1. 设定查询条件;
    2. 数据库中查询相应的数据 ;
    3. 将数据写入Excel;
    4. 将 Excel 下载至本地。

    导入 POM 依赖:

    
      com.alibaba
      easyexcel
      2.0.0-beta2
    
    
    
        com.alibaba
        fastjson
        1.2.5
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    1. Excel 导入

    FileController:添加一个导入接口

    @RestController
    @RequestMapping("/file")
    public class FileController {
    
        @Autowired
        private FileService fileService;
    
        @PostMapping("/importExcel")
        public ResultVo importExcel(@RequestParam("file") MultipartFile excel) {
            return fileService.importExcel(excel);
        }
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    FileServiceImpl

    @Service
    @Slf4j
    public class FileServiceImpl implements FileService {
    
        @Autowired
        private ExcelUtil excelUtil;
    
        @Override
        public ResultVo importExcel(MultipartFile file) {
            // 1.入参校验
            ResultVo checkExcelParam = checkExcelParam(file);
            if (!checkExcelParam.checkSuccess()) {
                log.error(checkExcelParam.getMsg());
                return checkExcelParam;
            }
            // 2.上传至服务器某路径下
            ResultVo resultVo = uploadFile(file);
            if (!resultVo.checkSuccess()) {
                return resultVo;
            }
            String filePath = (String)resultVo.getData();
            if (StringUtil.isBlank(filePath)) {
                return ResultVoUtil.error("【导入Excel文件】生成的Excel文件的路径为空");
            }
            // 3.读取excel文件
            List excelVos = excelUtil.simpleExcelRead(filePath, ExcelVo.class);
            if (CollectionUtil.isEmpty(excelVos) || excelVos.size() < 2) {
                log.error("【导入Excel文件】上传Excel文件{}为空", file.getOriginalFilename());
                return ResultVoUtil.error("上传Excel文件为空");
            }
            // 4.通过线程池开启一个线程去执行数据库操作,主线程继续往下执行
            // 4.1开启一个线程
            TaskCenterUtil taskCenterUtil = TaskCenterUtil.getTaskCenterUtil();
            taskCenterUtil.submitTask(() -> {
                log.info("【批量添加】批量添加数据:{}", JSON.toJSONString(excelVos));
                return null;
            });
            // 4.2删除临时文件
            boolean deleteFile = FileUtil.deleteFile(new File(filePath));
            if (!deleteFile) {
                log.error("【导入Excel文件】删除临时文件失败,临时文件路径为{}", filePath);
                return ResultVoUtil.error("删除临时文件失败");
            }
            log.info("【导入Excel文件】删除临时文件成功,临时文件路径为:{}", filePath);
            return ResultVoUtil.success(excelVos);
        }
        
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48

    说明:

    1. List excelVos = excelUtil.simpleExcelRead(filePath, ExcelVo.class); 你给我一个Excel文件路径,和一个数据类型,我就能将这个Excel文件中的数据封装成一个集合,并返回给你;
    2. TaskCenterUtil taskCenterUtil = TaskCenterUtil.getTaskCenterUtil();获取了一个线程池;taskCenterUtil.submitTask() 另开启了一个新线程去执行其它操作;原来的线程继续往下执行

    checkExcelParam():校验入参

    public ResultVo checkExcelParam(MultipartFile file) {
        log.info("【上传Excel文件】进入到上传Excel文件方法...");
        if (null == file || file.isEmpty()) {
            log.error("【上传Excel文件】上传的文件为空,file={}", file);
            throw new ParamErrorException();
        }
        boolean b = ExcelUtil.checkExcelExtension(file);
        if (!b) {
            return ResultVoUtil.error("上传的不是Excel文件,请上传正确格式的Excel文件");
        }
        return ResultVoUtil.success();
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    uploadFile():这个上传文件的方法,还是使用的上一篇博文的方法

    ExcelUtil

    @Component
    @Slf4j
    public class ExcelUtil {
    
        // excel文件后缀
        private final static String EXCE_L2003 = "xls";
        private final static String EXCEL_2007 = "xlsx";
    
        // 校验文件后缀是否为 xls、xlsx
        public static boolean checkExcelExtension(MultipartFile excel) {
            String filename = excel.getOriginalFilename();
            if (StringUtil.isBlank(filename)) {
                log.info("【校验Excel文件后缀】Excel文件名为空");
                return false;
            }
            int index = filename.lastIndexOf(".");
            if (index == -1) {
                log.info("【校验Excel文件后缀】Excel文件名中没有点号");
                return false;
            }
            String extension = filename.substring(index + 1);
            return Arrays.asList(EXCE_L2003, EXCEL_2007).contains(extension);
        }
    
        // 读取excel文件
        public List simpleExcelRead(String filePath, Class clazz) {
            ExcelListener excelListener = new ExcelListener();
            EasyExcel.read(filePath, clazz, excelListener).sheet().doRead();
            List dataList = excelListener.getDataList();
            return dataList;
        }
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33

    说明:

    1. EasyExcel.read(filePath, clazz, excelListener).sheet().doRead(); 会调用 ExcelListener 类中的方法,将它读取到的数据存储在 dataList 集合中;
    2. 这里 ExcelUtil 类用的是泛型,你要导入什么类型的数据,就传什么类型的数据。我这里导入的是 ExcelVo 类型。

    ExcelListener

    @Slf4j
    public class ExcelListener extends AnalysisEventListener {
    
        // 返回读取到的excel中的数据
        List dataList = new ArrayList<>();
    
        public ExcelListener() {
        }
    
        // 每一条数据解析都会来调用
        @Override
        public void invoke(T t, AnalysisContext analysisContext) {
            log.info("【Excel文件】解析到一条数据{}:", JSON.toJSONString(t));
            dataList.add(t);
        }
    
        // 所有数据解析完成了 才会来调用
        @Override
        public void doAfterAllAnalysed(AnalysisContext analysisContext) {
            log.info("【Excel文件】Excel所有数据解析完毕!");
        }
    
        public List getDataList() {
            return dataList;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26

    ExcelVo:Excel 中数据信息的模板

    @Data
    @NoArgsConstructor
    @AllArgsConstructor
    public class ExcelVo {
    	// 姓名
        private String name;
        // 性别 1:女 0:男
        @ExcelProperty(converter = SexConvert.class)
        private Integer sex;
        // 创建时间
        @DateTimeFormat("yyyy年MM月dd日HH时mm分ss秒")
        private String createTime;
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    说明:

    1. ExcelVo 中的属性和 Excel 文件对应
    2. 性别 sex 属性,我们使用了一个转换器。Excel 文件中性别对应的是男、女,但我们要用整形0、1 去接收它,所以,需要自定义一个性别转换器

    Excel 内容:
    在这里插入图片描述
    SexConvert:性别转换器

    public class SexConvert implements Converter {
    
        @Override
        public Class supportJavaTypeKey() {
            return Integer.class;
        }
    
        @Override
        public CellDataTypeEnum supportExcelTypeKey() {
            return CellDataTypeEnum.STRING;
        }
    
        // 这里读的时候会调用
        @Override
        public Integer convertToJavaData(CellData cellData, ExcelContentProperty excelContentProperty, GlobalConfiguration globalConfiguration) throws Exception {
            switch (cellData.getStringValue()) {
                case "男":
                    return 0;
                case "女":
                    return 1;
                default:
                    return 0;
            }
        }
    
        // 这里写的时候会调用
        @Override
        public CellData convertToExcelData(Integer integer, ExcelContentProperty excelContentProperty, GlobalConfiguration globalConfiguration) throws Exception {
            switch (integer) {
                case 1:
                    return new CellData("女");
                case 0:
                    return new CellData("男");
                default:
                    return new CellData(String.valueOf(integer));
            }
        }
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39

    TaskCenterUtil:线程池工具类

    public class TaskCenterUtil {
    
        public static Integer CORE_POOL_SIZE = 10;
        public static Integer MAX_NUM_POOL_SIZE = 10;
        public static Integer MAX_MESSAGE_SIZE = 100;
        public static Long KEEP_ALIVE_TIME = 60L;
    
        private ThreadPoolExecutor poolExecutor = new ThreadPoolExecutor(CORE_POOL_SIZE, MAX_NUM_POOL_SIZE, KEEP_ALIVE_TIME,
                TimeUnit.SECONDS, new LinkedBlockingQueue<>(MAX_MESSAGE_SIZE), new ThreadPoolExecutor.CallerRunsPolicy());
    
    
        private TaskCenterUtil() {}
    
        private static TaskCenterUtil taskCenterUtil = new TaskCenterUtil();
    
        public static TaskCenterUtil getTaskCenterUtil() {
            return taskCenterUtil;
        }
    
        public void submitTask(Callable task) {
            poolExecutor.submit(task);
        }
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24

    FileUtil.deleteFile():删除临时文件

    // 递归删除目录下的所有文件及子目录下所有文件
    public static boolean deleteFile(File file) {
        if (!file.exists()) {
            return false;
        }
        if (file.isDirectory()) {
            String[] children = file.list();
            //递归删除目录中的子目录下
            for (int i=0; i
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    2. Excel 导出

    FileController:添加一个文件导出接口

    @PostMapping("/exportExcel")
    public ResultVo exportExcel(final HttpServletResponse response) {
        return fileService.exportExcel(response);
    }
    
    • 1
    • 2
    • 3
    • 4

    FileServiceImpl

    @Override
    public ResultVo exportExcel(HttpServletResponse response) {
        // 1.根据查询条件获取结果集
        List excelWriteVos = getExcelWriteVoListByCondition();
        if (CollectionUtil.isEmpty(excelWriteVos)) {
            log.info("【导出Excel文件】要导出的数据为空,无法导出!");
            return ResultVoUtil.success("数据为空");
        }
        // 2.获取要下载Excel文件的路径
        ResultVo resultVo = getDownLoadPath(ExcelWriteVo.class, excelWriteVos);
        if (!resultVo.checkSuccess()) {
            log.error("【导出Excel文件】获取要下载Excel文件的路径失败");
            return resultVo;
        }
        // 3.下载Excel文件
        String fileDownLoadPath = resultVo.getData();
        ResultVo downLoadResultVo = downloadFile(fileDownLoadPath, response);
        if (null != downLoadResultVo && !downLoadResultVo.checkSuccess()) {
            log.error("【导出Excel文件】下载文件失败");
            return downLoadResultVo;
        }
        // 4.删除临时文件
        boolean deleteFile = FileUtil.deleteFile(new File(fileDownLoadPath));
        if (!deleteFile) {
            log.error("【导入Excel文件】删除临时文件失败,临时文件路径为{}", fileDownLoadPath);
            return ResultVoUtil.error("删除临时文件失败");
        }
        log.info("【导入Excel文件】删除临时文件成功,临时文件路径为:{}", fileDownLoadPath);
        return null;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30

    getExcelWriteVoListByCondition():根据条件获取要导出的数据

    这里本应该通过数据库查询的,我这里直接用假数据了。

    public List getExcelWriteVoListByCondition() {
        List excelWriteVos = new ArrayList<>(5);
        excelWriteVos.add(new ExcelWriteVo("zzc", "男", "2021-11-14 20:00:00"));
        excelWriteVos.add(new ExcelWriteVo("wzc", "女", "2021-11-14 20:00:00"));
        excelWriteVos.add(new ExcelWriteVo("wxc", "男", "2021-11-14 20:00:00"));
        return excelWriteVos;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    ExcelWriteVo:Excel 文件导出对应的Vo类

    @Data
    @NoArgsConstructor
    @AllArgsConstructor
    public class ExcelWriteVo {
    
        // 姓名
        @ExcelProperty("姓名")
        private String name;
    
        // 性别 1:女 0:男
        @ExcelProperty("性别")
        private String sex;
    
        // 创建时间
        @DateTimeFormat("yyyy年MM月dd日HH时mm分ss秒")
        @ExcelProperty("创建时间")
        private String createTime;
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    导出的 Excel:
    在这里插入图片描述

    getDownLoadPath():获取要下载Excel文件的路径

    public ResultVo getDownLoadPath(Class clazz, List excelWriteVos) {
        String downLoadPath = FileUtil.getDownLoadPath();
        if (StringUtil.isBlank(downLoadPath)) {
            log.error("【导出Excel文件】生成临时文件失败");
            return ResultVoUtil.error("生成临时文件失败");
        }
        // 1.创建一个临时目录
        FileUtil.mkdirs(downLoadPath);
        String fullFilePath = downLoadPath + File.separator + System.currentTimeMillis() + "." + ExcelUtil.EXCEL_2007;
        log.info("【导出Excel文件】文件的临时路径为:{}", fullFilePath);
        // 2.写入数据
        excelUtil.simpleExcelWrite(fullFilePath, clazz, excelWriteVos);
        return ResultVoUtil.success(fullFilePath);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    application.yml:添加了一个文件下载时生成文件的路径

    file:
      uploadPath: E:/upload
      downloadPath: E:/download
    
    • 1
    • 2
    • 3

    FileUtil

    // 获取文件下载时生成文件的路径
    public static String getDownLoadPath() {
        return fileConfig.getDownloadPath();
    }
    
    • 1
    • 2
    • 3
    • 4

    ExcelUtil

    @Slf4j
    public class ExcelUtil {
    
        // excel文件后缀
        public final static String EXCE_L2003 = "xls";
        public final static String EXCEL_2007 = "xlsx";
    
        // sheet名字
        public final static String SHEET_NAME = "模板";
    	
    	// 写Excel文件
    	public void simpleExcelWrite(String filePath, Class clazz, List dataList) {
            EasyExcel.write(filePath, clazz).sheet(SHEET_NAME).doWrite(dataList);
        }
        
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    好了,通过 EasyExcel 文件操作 Excel 就到这了哈。

    ===========================================================================================
    2021-12-07 更:

    使用 easyexcel 设置超链接或附件地址

    参考地址

    修改 POM 依赖:修改 easyexcel 的版本。2.0.0-beta2 版本无法引入 AbstractCellWriteHandler

    
        com.alibaba
        easyexcel
        2.2.6
    
    
    • 1
    • 2
    • 3
    • 4
    • 5

    ExcelWriteVo:添加一个字段,表示附件

    @Data
    @NoArgsConstructor
    @AllArgsConstructor
    public class ExcelWriteVo {
    	// ...
    	
    	// 附件
        private String photo;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    FileServiceImpl#getExcelWriteVoListByCondition():修改构造方法

    public List getExcelWriteVoListByCondition() {
        List excelWriteVos = new ArrayList<>(5);
        excelWriteVos.add(new ExcelWriteVo("zzc", "男", "2021-11-14 20:00:00", "附件1"));
        excelWriteVos.add(new ExcelWriteVo("wzc", "女", "2021-11-14 20:00:00", "附件1"));
        excelWriteVos.add(new ExcelWriteVo("wxc", "男", "2021-11-14 20:00:00", "附件1"));
        return excelWriteVos;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    添加一个处理器 PhotoHandler

    @Slf4j
    public class PhotoHandler extends AbstractCellWriteHandler {
    
        @Override
        public void afterCellDispose(WriteSheetHolder writeSheetHolder, WriteTableHolder writeTableHolder, List cellDataList, Cell cell, Head head, Integer relativeRowIndex, Boolean isHead) {
            // 这里可以对cell进行任何操作
            log.info("第{}行,第{}列写入完成。", cell.getRowIndex(), cell.getColumnIndex());
            if (isHead && cell.getColumnIndex() == 0) { // 对第一行第一列的头超链接
                CreationHelper createHelper = writeSheetHolder.getSheet().getWorkbook().getCreationHelper();
                Hyperlink hyperlink = createHelper.createHyperlink(HyperlinkType.URL);
                hyperlink.setAddress("https://github.com/alibaba/easyexcel");
                cell.setHyperlink(hyperlink);
            }
            if (!isHead && head.getFieldName().equals("photo")) { // 头为 photo 的列设置附件地址
                CreationHelper createHelper = writeSheetHolder.getSheet().getWorkbook().getCreationHelper();
                Hyperlink hyperlink = createHelper.createHyperlink(HyperlinkType.FILE); // 类型设置为 FILE
                hyperlink.setAddress("C:/Users/07979/Pictures/1.jpg");  // 附件的地址(相对地址、绝对地址都行)
                cell.setHyperlink(hyperlink);
            }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    导出 Excel 时,需要注册这个处理器:

    ExcelUtil#()

    @Component
    @Slf4j
    public class ExcelUtil {
    	// ...
    
    	public void simpleExcelWrite(String filePath, Class clazz, List dataList) {
            EasyExcel.write(filePath, clazz)
            		.registerWriteHandler(new PhotoHandler())
            		.sheet(SHEET_NAME).doWrite(dataList);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    说明:

    1. 可以注册多个处理器。如:

      EasyExcel.write(filePath, clazz)
      .registerWriteHandler(new PhotoHandler())
      .registerWriteHandler(new PhotoHandler2())
      .sheet(SHEET_NAME).doWrite(dataList);

    运行代码后:
    在这里插入图片描述
    鼠标点击,第一列的头,可跳转;

    在这里插入图片描述
    鼠标点击“photo” 这一列,也可跳转。

    先自我介绍一下,小编13年上师交大毕业,曾经在小公司待过,去过华为OPPO等大厂,18年进入阿里,直到现在。深知大多数初中级java工程师,想要升技能,往往是需要自己摸索成长或是报班学习,但对于培训机构动则近万元的学费,着实压力不小。自己不成体系的自学效率很低又漫长,而且容易碰到天花板技术停止不前。因此我收集了一份《java开发全套学习资料》送给大家,初衷也很简单,就是希望帮助到想自学又不知道该从何学起的朋友,同时减轻大家的负担。添加下方名片,即可获取全套学习资料哦

  • 相关阅读:
    # lvs负载均衡
    洋葱集团携手OceanBase实现分布式升级,全球数据首次实现跨云融合
    前端自动化require.context的使用
    如何设计一个安全的系统架构?
    QDockWidget DEMO 动态添加QDockWidget ,无主窗口,禁止tab重叠
    华为云云耀云服务器L实例评测|轻量级应用服务器对决:基于 STREAM 深度测评华为云云耀云服务器L实例的内存性能
    Pytorch在训练时冻结某些层使其不参与反向传播
    从Mpx资源构建优化看splitChunks代码分割
    GoogLeNet 08
    网络安全入门学习资源汇总
  • 原文地址:https://blog.csdn.net/m0_67401270/article/details/126080561