【Java+EasyExcel】使用 SpringBoot 实现 Excel 文件的导入(含示例代码)

在这里插入图片描述

😎 作者介绍:我是程序员洲洲,一个热爱写作的非著名程序员。CSDN全栈优质领域创作者、华为云博客社区云享专家、阿里云博客社区专家博主。
🤓 同时欢迎大家关注其他专栏,我将分享Web前后端开发、人工智能、机器学习、深度学习从0到1系列文章。
🌼 同时洲洲已经建立了程序员技术交流群,如果您感兴趣,可以私信我加入社群,可以直接vx联系(文末有名片)v:bdizztt
🖥 随时欢迎您跟我沟通,一起交流,一起成长、进步!点此也可获得联系方式~

前言

我们先复习下EasyExcel处理文件的思路:

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

Excel 导出
设定查询条件;
数据库中查询相应的数据 ;
将数据写入Excel;
将 Excel 下载至本地。

1、导入pom依赖

<dependency>
  <groupId>com.alibaba</groupId>
  <artifactId>easyexcel</artifactId>
  <version>3.2.1</version>
</dependency>

<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>fastjson</artifactId>
    <version>1.2.5</version>
</dependency>

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>

2、Excel导入

Controller接口

@RestController
@RequestMapping("/file")
public class FileController {
    @Autowired
    private FileService fileService;
    @PostMapping("/importExcel")
    public ResultVo importExcel(@RequestParam("file") MultipartFile excel) {
        return fileService.importExcel(excel);
    }
}

FileServiceImpl

@Service
@Slf4j
public class FileServiceImpl implements FileService {

    @Autowired
    private ExcelUtil excelUtil;

    @Override
    public ResultVo importExcel(MultipartFile file) {
        // 1.可做入参校验,这里省略
        // 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<ExcelVo> excelVos = excelUtil.simpleExcelRead(filePath, ExcelVo.class);
        if (CollectionUtil.isEmpty(excelVos) || excelVos.size() < 2) {
            log.error("【导入Excel文件】上传Excel文件{}为空", file.getOriginalFilename());
            return ResultVoUtil.error("上传Excel文件为空");
        } 
        // 4.删除临时文件
        boolean deleteFile = FileUtil.deleteFile(new File(filePath));
        if (!deleteFile) {
            log.error("【导入Excel文件】删除临时文件失败,临时文件路径为{}", filePath);
            return ResultVoUtil.error("删除临时文件失败");
        }
        log.info("【导入Excel文件】删除临时文件成功,临时文件路径为:{}", filePath);
        return ResultVoUtil.success(excelVos);
    }
	
	// 上传文件
	public ResultVo<String> uploadFile(MultipartFile file) {
        log.info("【文件上传】进入到文件上传方法");
        // 1.参数校验
        if (null == file || file.isEmpty()) {
            log.error("【文件上传】文件为空!");
            throw new ParamErrorException();
        }
        // 2.上传文件
        ResultVo<String> resultVo = FileUtil.uploadFile(file);
        return resultVo;
    }
}

封装FileUtil工具代码

public class FileUtil {

    // 下划线
    public static final String UNDER_LINE = "_";

    // 上传文件
    public static ResultVo<String> uploadFile(MultipartFile file) {
        // 1.获取一个新的文件名
        String newFileName = getNewFileName(file);
        if (StringUtil.isBlank(newFileName)) {
            log.error("【上传文件】转换文件名称失败");
            return ResultVoUtil.error("【上传文件】转换文件名称失败");
        }
        // 2.获取文件上传路径
        String uploadPath = "E:\\temp";
        if (StringUtil.isBlank(uploadPath)) {
            log.error("【上传文件】获取文件上传路径失败");
            return ResultVoUtil.error("【上传文件】获取文件上传路径失败");
        }
        uploadPath = uploadPath + File.separator +  DateUtil.getCurrentDate();
        // 3.生成上传目录
        File uploadDir = mkdirs(uploadPath);
        if (!uploadDir.exists()) {
            log.error("【上传文件】生成上传目录失败");
            return ResultVoUtil.error("【上传文件】生成上传目录失败");
        }
        // 4.文件全路径
        String fileFullPath = uploadPath + File.separator + newFileName;
        log.info("上传的文件:" + file.getName() + "," + file.getContentType() + ",保存的路径为:" + fileFullPath);
        try {
            // 5.上传文件
            doUploadFile(file, fileFullPath);
        } catch (IOException e) {
            log.error("【上传文件】上传文件报IO异常,异常信息为{}", e.getMessage());
            return ResultVoUtil.error(e.getMessage());
        }
        return ResultVoUtil.success(fileFullPath);
    }

    public static void doUploadFile(MultipartFile file, String path) throws IOException {
        // 法一:
        Streams.copy(file.getInputStream(), new FileOutputStream(path), true);

        // 法二: 通过MultipartFile#transferTo(File)
        // 使用此方法保存,必须要绝对路径且文件夹必须已存在,否则报错
        //file.transferTo(new File(path));

        // 法三:通过NIO将字节写入文件
        //Path filePath = Paths.get(path);
        //Files.write(filePath, file.getBytes());

        // 法四:
        /*try (InputStream in = file.getInputStream();
             FileOutputStream out = new FileOutputStream(path)) {
            IOUtils.copy(in, out);
        } catch (Exception e) {
            log.error("【上传文件】上传文件失败,失败信息为:{}", e.getMessage());
        }*/

        // 法五:
        /*InputStream in = file.getInputStream();
        OutputStream out = new FileOutputStream(path);
        int len = 0;
        byte[] bytes = new byte[1024];
        while ((len = in.read(bytes)) != -1) {
            out.write(bytes, 0, len);
        }
        in.close();
        out.close();*/

        // 法六:
        /*byte[] bytes = file.getBytes();
        OutputStream out = new FileOutputStream(path);
        out.write(bytes);
        out.close();*/
    }

    /**
     * @Description:递归生成父级路径
     **/
    public static File mkdirs(String path) {
        File file = new File(path);
        if(!file.exists() || !file.isDirectory()) {
            file.mkdirs();
        }
        return file;
    }

    /**
     * @Description:将上传的文件转换为一个新的文件名
     **/
    public static String getNewFileName(MultipartFile file) {
        // 1.获取上传的文件名称(包含后缀。如:test.jpg)
        String originalFilename = file.getOriginalFilename();
        log.info("【上传文件】上传的文件名为{}", originalFilename);
        // 2.以小数点进行分割
        String[] split = originalFilename.split("\\.");
        String newFileName = null;
        if (null == split || split.length == 0) {
            return null;
        }
        StringBuilder builder = new StringBuilder();
        if (1 == split.length) {
            // 3.此文件无后缀
            newFileName = builder.append(originalFilename).append(UNDER_LINE).append(System.nanoTime()).toString();
            return newFileName;
        }
        // 4.获取文件的后缀
        String fileSuffix = split[split.length - 1];
        for (int i = 0; i < split.length - 1; i++) {
            builder.append(split[i]);
            if (null != split[i + 1] && "" != split[i + 1]) {
                builder.append(UNDER_LINE);
            }
        }
        newFileName = builder.append(System.nanoTime()).append(".").append(fileSuffix).toString();
        return newFileName;
    }

    /**
     * @Description:下载文件
     **/
    public static ResultVo<String> downloadFile(File file, HttpServletResponse response) {
        try {
            // 1.设置响应头
            setResponse(file, response);
        } catch (UnsupportedEncodingException e) {
            log.error("文件名{}不支持转换为字符集{}", file.getName(), "UTF-8");
            return ResultVoUtil.error(e.getMessage());
        }
        // 2.下载文件
        return doDownLoadFile(file, response);
    }

    /**
     * @Description:下载文件
     **/
    public static ResultVo<String> doDownLoadFile(File file, HttpServletResponse response) {
        // 法一:IOUtils
        /*try (FileInputStream in = new FileInputStream(file);
             OutputStream out = response.getOutputStream()) {
            // 2.下载文件
            IOUtils.copy(in, out);
            log.info("【文件下载】文件下载成功");
            return null;
        } catch (FileNotFoundException e) {
            log.error("【文件下载】下载文件时,没有找到相应的文件,文件路径为{}", file.getAbsolutePath());
            return ResultVoUtil.error(e.getMessage());
        } catch (IOException e) {
            log.error("【文件下载】下载文件时,出现文件IO异常");
            return ResultVoUtil.error(e.getMessage());
        }*/

        // 法二:将文件以流的形式一次性读取到内存,通过响应输出流输出到前端
        /*try (InputStream in = new BufferedInputStream(new FileInputStream(file));
             OutputStream out = new BufferedOutputStream(response.getOutputStream())) {
            byte[] buffer = new byte[in.available()];
            in.read(buffer);
            out.write(buffer);
            log.info("【文件下载】文件下载成功");
            return null;
        } catch (IOException e) {
            log.error("【文件下载】下载文件时,出现文件IO异常");
            return ResultVoUtil.error(e.getMessage());
        }*/

        // 法三:将输入流中的数据循环写入到响应输出流中,而不是一次性读取到内存,通过响应输出流输出到前端
        try (InputStream in = new FileInputStream(file);
             OutputStream out = response.getOutputStream()) {
            byte[] buffer = new byte[1024];
            int len = 0;
            while ((len = in.read(buffer)) != -1) {
                out.write(buffer, 0, len);
            }
            log.info("【文件下载】文件下载成功");
            return null;
        } catch (FileNotFoundException e){
            log.error("【文件下载】下载文件时,没有找到相应的文件,文件路径为{}", file.getAbsolutePath());
            return ResultVoUtil.error(e.getMessage());
        } catch (IOException e) {
            log.error("【文件下载】下载文件时,出现文件IO异常");
            return ResultVoUtil.error(e.getMessage());
        }
    }


    public static void setResponse(File file, HttpServletResponse response) throws UnsupportedEncodingException {
        // 清空response
        response.reset();
        response.setCharacterEncoding("UTF-8");
        // 返回给客户端类型,任意类型
        response.setContentType("application/octet-stream");
        // Content-Disposition的作用:告知浏览器以何种方式显示响应返回的文件,用浏览器打开还是以附件的形式下载到本地保存
        // attachment表示以附件方式下载 inline表示在线打开 "Content-Disposition: inline; filename=文件名.mp3"
        response.addHeader("Content-Disposition", "attachment;filename=" + URLEncoder.encode(file.getName(), "UTF-8"));
        // 告知浏览器文件的大小
        response.addHeader("Content-Length", String.valueOf(file.length()));
    }

    /**
     * @Description:递归删除目录下的所有文件及子目录下所有文件
     **/
    public static boolean deleteFile(File file) {
        if (!file.exists()) {
            return false;
        }
        if (file.isDirectory()) {
            String[] children = file.list();
            //递归删除目录中的子目录下
            for (int i=0; i<children.length; i++) {
                boolean success = deleteFile(new File(file, children[i]));
                if (!success) {
                    return false;
                }
            }
        }
        // 目录此时为空,可以删除
        return file.delete();
    }

    /**
     * @Description:获取文件下载时生成文件的路径
     **/
    public static String getDownLoadPath() {
        return fileConfig.getDownloadPath();
    }
}

ExcelUtuil

@Component
@Slf4j
public class ExcelUtil<T> {

    // 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<T> simpleExcelRead(String filePath, Class<T> clazz) {
        ExcelListener<T> excelListener = new ExcelListener();
        EasyExcel.read(filePath, clazz, excelListener).sheet().doRead();
        List<T> dataList = excelListener.getDataList();
        return dataList;
    }
}

ExcelListener

@Slf4j
public class ExcelListener<T> extends AnalysisEventListener<T> {

    // 返回读取到的excel中的数据
    List<T> 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<T> getDataList() {
        return dataList;
    }
}

ExcelVo

ExcelVo:Excel 中数据信息的模板

@Data
@NoArgsConstructor
@AllArgsConstructor
public class ExcelVo {
	// 姓名
    private String name;
    // 创建时间
    @DateTimeFormat("yyyy年MM月dd日HH时mm分ss秒")
    private String createTime;
}

删除临时Excel文件

// 递归删除目录下的所有文件及子目录下所有文件
public static boolean deleteFile(File file) {
    if (!file.exists()) {
        return false;
    }
    if (file.isDirectory()) {
        String[] children = file.list();
        //递归删除目录中的子目录下
        for (int i=0; i<children.length; i++) {
            boolean success = deleteFile(new File(file, children[i]));
            if (!success) {
                return false;
            }
        }
    }
    // 目录此时为空,可以删除
    return file.delete();
}

总结

📝Hello,各位看官老爷们好,我已经建立了CSDN技术交流群,如果你很感兴趣,可以私信我加入我的社群。

📝社群中不定时会有很多活动,例如每周都会包邮免费送一些技术书籍及精美礼品、学习资料分享、大厂面经分享、技术讨论谈等等。

📝社群方向很多,相关领域有Web全栈(前后端)、人工智能、机器学习、自媒体副业交流、前沿科技文章分享、论文精读等等。

📝不管你是多新手的小白,都欢迎你加入社群中讨论、聊天、分享,加速助力你成为下一个大佬!

📝想都是问题,做都是答案!行动起来吧!欢迎评论区or后台与我沟通交流,也欢迎您点击下方的链接直接加入到我的交流社群!~ 跳转链接社区~

在这里插入图片描述

  • 31
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

程序员洲洲

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值