Excel导入导出——学习笔记

前言

  利用Poi进行Excel导入导出,在SpringBoot框架下进行测试,前端小小的使用了下Vue,并使用了Bootstrap-fileinput作为前端上传组件作为参考,下载采用原生ajax下载形式,因为一直忘记前端下载怎么写还有后端怎么设置Request-Header,所以提供一下一整个前后端的思路,以后方便进行查阅,只是个人学习用,所以仅供参考。

引入POI依赖

        <!-- https://mvnrepository.com/artifact/org.apache.poi/poi -->
        <dependency>
            <groupId>org.apache.poi</groupId>
            <artifactId>poi</artifactId>
            <version>3.17</version>
        </dependency>
        <!-- https://mvnrepository.com/artifact/org.apache.poi/poi-ooxml -->
        <dependency>
            <groupId>org.apache.poi</groupId>
            <artifactId>poi-ooxml</artifactId>
            <version>3.17</version>
        </dependency>

ExcelUtil工具类

  excel的工具类,这里基本上把导入导出的功能大致做了一遍,但还有值得改进的地方,不过想说的是,基本能用!

package com.podago.demo.utils;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

import org.apache.commons.lang3.StringUtils;
import org.apache.poi.hssf.usermodel.HSSFCell;
import org.apache.poi.hssf.usermodel.HSSFCellStyle;
import org.apache.poi.hssf.usermodel.HSSFFont;
import org.apache.poi.hssf.usermodel.HSSFRow;
import org.apache.poi.hssf.usermodel.HSSFSheet;
import org.apache.poi.hssf.usermodel.HSSFWorkbook;
import org.apache.poi.hssf.util.HSSFColor.HSSFColorPredefined;
import org.apache.poi.ss.usermodel.*;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.multipart.MultipartFile;

/**
 * Excel工具类 - 提供导入导出功能
 * @author chenxiaotao
 */
public class ExcelUtil {
    private static final Logger logger = LoggerFactory.getLogger(ExcelUtil.class);

    private static final String XLS = "xls";

    private static final String XLSX = "xlsx";

    /**
     * 读取Excel文件数据,并转换为二维数组形式输出
     * @param file 文件流
     * @return Excel数据
     */
    public static List<List<Object>> readExcel(MultipartFile file) {
        Workbook workbook = null;
        String fileName = file.getOriginalFilename();
        InputStream in = null;
        try {
            in = file.getInputStream();
            if (fileName != null && fileName.endsWith(XLS)) {
                // 2003
                workbook = readExcel2003(in);
            } else if (fileName != null && fileName.endsWith(XLSX)) {
                // 2007
                workbook = readExcel2007(in);
            }
        } catch (IOException e) {
            logger.error("excel输入流读取错误,返回空列表", e);
            return Collections.emptyList();
        } finally {
            if (in != null) {
                try {
                    in.close();
                } catch (IOException e) {
                    logger.error("excel输入流关闭错误", e);
                }
            }
        }
        if (workbook == null) {
            return Collections.emptyList();
        }
        return readExcel(workbook);
    }


    /**
     * 读取2007+版本的excel文件,即后缀为xlsx
     */
    private static Workbook readExcel2007(InputStream in) {
        Workbook workbook = null;
        try {
            workbook = new XSSFWorkbook(in);
        } catch (IOException e) {
            e.printStackTrace();
        }
        return workbook;
    }

    /**
     * 读取2003版本的excel文件,即后缀为xls
     */
    private static Workbook readExcel2003(InputStream in) {
        Workbook workbook = null;
        try {
            workbook = new HSSFWorkbook(in);
        } catch (IOException e) {
            e.printStackTrace();
        }
        return workbook;
    }


    /**
     * 根据Workbook,读取Excel数据
     * @param workbook excel工作簿
     * @return excel中数据
     */
    private static List<List<Object>> readExcel(Workbook workbook) {
        List<List<Object>> res = new ArrayList<>();
        Sheet sheet = workbook.getSheetAt(0);
        int lastRowNum = sheet.getLastRowNum();
        for (int i = 0; i <= lastRowNum; i++) {
            Row row = sheet.getRow(i);
            int lastCellNum = row.getLastCellNum();
            List<Object> rdata = new ArrayList<>(lastCellNum);
            for (int j = 0 ; j < lastCellNum; j++) {
                Cell cell = row.getCell(j);
                rdata.add(getCell(cell));
            }
            res.add(rdata);
        }
        return res;
    }

    /**
     * 获取单元格数据
     * @param cell 单元格对象
     * @return 单元格中数据
     */
    private static Object getCell(Cell cell) {
        switch (cell.getCellTypeEnum()) {
            case NUMERIC:
                return cell.getNumericCellValue();
            case STRING:
                return cell.getStringCellValue();
            case BOOLEAN:
                return cell.getBooleanCellValue();
            case FORMULA:
                return cell.getCellFormula();
            case BLANK:
                return StringUtils.EMPTY;
            case ERROR:
                return StringUtils.EMPTY;
            case _NONE:
                return StringUtils.EMPTY;
            default:
                return null;
        }
    }

    /**
     * 输出Excel数据流,并最终关闭流
     * @param dataLists 传入数据
     * @param out 输出流
     */
    public static void createExcel(List<List<Object>> dataLists, OutputStream out) {
        HSSFWorkbook workbook = new HSSFWorkbook();
        HSSFSheet sheet = workbook.createSheet();
        HSSFCellStyle titleCellStyle = workbook.createCellStyle();

        //设置单元标题样式
        titleCellStyle.setAlignment(HorizontalAlignment.CENTER);
        titleCellStyle.setFillPattern(FillPatternType.SOLID_FOREGROUND);
        titleCellStyle.setFillForegroundColor(HSSFColorPredefined.SKY_BLUE.getIndex());
        titleCellStyle.setWrapText(true);
        //设置单元标题字体
        HSSFFont titleFont = workbook.createFont();
        titleFont.setFontHeightInPoints((short) 13);
        titleCellStyle.setFont(titleFont);

        //设置表格内容单元样式
        HSSFCellStyle valueCellStyle = workbook.createCellStyle();
        valueCellStyle.setAlignment(HorizontalAlignment.CENTER);
        valueCellStyle.setVerticalAlignment(VerticalAlignment.CENTER);

        HSSFFont cellFont = workbook.createFont();
        cellFont.setFontHeightInPoints((short) 12);
        valueCellStyle.setFont(cellFont);

        // 二维形式
        for (int i = 0; i < dataLists.size(); i++) {
            HSSFRow row = sheet.createRow(i);
            List<Object> objects = dataLists.get(i);

            for (int j = 0; j < objects.size(); j++) {
                HSSFCell valueCell = row.createCell(j);
                valueCell.setCellStyle(valueCellStyle);
                valueCell.setCellValue(objects.get(j).toString());
            }
        }
        try {
            workbook.write(out);
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (out != null) {
                try {
                    out.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
        //导出成功!
    }
}


分解读取Excel文件的思路

  1. 获取MultiPartFile文件,因为是用的SpringBoot,所以直接传入这个就可以获取到文件名以及文件流,如果觉得和Spring有耦合的话可以将方法写成 readExcel(String fileName, InputStream in)
  2. 判断文件类型,简单判断文件后缀名,然后根据文件类型(因为Excel2003和Excel2007+的读取方式不同),调用不同的读取方法,Excel2003是用的 HSSFWorkbook,Excel2007用的是XSSFWorkbook,获取后赋值给Workbook引用,这里利用了多态的思想。
  3. 获取到Workbook后,我们便可以开始读取数据
        /**
         * 根据Workbook,读取Excel数据
         * @param workbook excel工作簿
         * @return excel中数据
         */
        private static List<List<Object>> readExcel(Workbook workbook) {
            List<List<Object>> res = new ArrayList<>();
            Sheet sheet = workbook.getSheetAt(0);
            int lastRowNum = sheet.getLastRowNum();
            for (int i = 0; i <= lastRowNum; i++) {
                Row row = sheet.getRow(i);
                int lastCellNum = row.getLastCellNum();
                List<Object> rdata = new ArrayList<>(lastCellNum);
                for (int j = 0 ; j < lastCellNum; j++) {
                    Cell cell = row.getCell(j);
                    rdata.add(getCell(cell));
                }
                res.add(rdata);
            }
            return res;
        }

    解释下各个方法:

  • workbook.getSheetAt(index): 获取指定下标下的sheet,sheet就是你打开Excel后右下角就可以看到这样一个标识                

          

  主要是用于打印时区分纸张,一个Sheet表示一张纸,一般我们读取第一个也就是下标为0的一个,当然你可以自行对方法进行扩展,写死总是不好的。

  • sheet.getLastRowNum(): 获取该Sheet行数
  • row.getLastCellNum(): 获取该行有多少单元格,即多少列

  了解这些方法后,我们就可以用遍历二维数组的思想去遍历每个单元格就行了,需要注意的是要根据单元格类型调用不同方法获取单元格数据

   /**
     * 获取单元格数据
     * @param cell 单元格对象
     * @return 单元格中数据
     */
    private static Object getCell(Cell cell) {
        switch (cell.getCellTypeEnum()) {
            case NUMERIC:
                return cell.getNumericCellValue();
            case STRING:
                return cell.getStringCellValue();
            case BOOLEAN:
                return cell.getBooleanCellValue();
            case FORMULA:
                return cell.getCellFormula();
            case BLANK:
                return StringUtils.EMPTY;
            case ERROR:
                return StringUtils.EMPTY;
            case _NONE:
                return StringUtils.EMPTY;
            default:
                return null;
        }
    }

分解导出Excel的思路

  1. 创建一个空的Workbook (工作簿)
  2. 创建一个空的Sheet
  3. 设置样式
  4. 根据数据(依旧是二维数组的形式),来创建行,创建单元格
  5. 将工作簿内容写入输出流进行输出,最后关闭流
   /**
     * 输出Excel数据流,并最终关闭流
     * @param dataLists 传入数据
     * @param out 输出流
     */
    public static void createExcel(List<List<Object>> dataLists, OutputStream out) {
        HSSFWorkbook workbook = new HSSFWorkbook();
        HSSFSheet sheet = workbook.createSheet();
        HSSFCellStyle titleCellStyle = workbook.createCellStyle();

        //设置单元标题样式
        titleCellStyle.setAlignment(HorizontalAlignment.CENTER);
        titleCellStyle.setFillPattern(FillPatternType.SOLID_FOREGROUND);
        titleCellStyle.setFillForegroundColor(HSSFColorPredefined.SKY_BLUE.getIndex());
        titleCellStyle.setWrapText(true);
        //设置单元标题字体
        HSSFFont titleFont = workbook.createFont();
        titleFont.setFontHeightInPoints((short) 13);
        titleCellStyle.setFont(titleFont);

        //设置表格内容单元样式
        HSSFCellStyle valueCellStyle = workbook.createCellStyle();
        valueCellStyle.setAlignment(HorizontalAlignment.CENTER);
        valueCellStyle.setVerticalAlignment(VerticalAlignment.CENTER);

        HSSFFont cellFont = workbook.createFont();
        cellFont.setFontHeightInPoints((short) 12);
        valueCellStyle.setFont(cellFont);

        // 二维形式
        for (int i = 0; i < dataLists.size(); i++) {
            HSSFRow row = sheet.createRow(i);
            List<Object> objects = dataLists.get(i);

            for (int j = 0; j < objects.size(); j++) {
                HSSFCell valueCell = row.createCell(j);
                valueCell.setCellStyle(valueCellStyle);
                valueCell.setCellValue(objects.get(j).toString());
            }
        }
        try {
            workbook.write(out);
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (out != null) {
                try {
                    out.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
        //导出成功!
    }

ExcelController

  控制层,主要记忆一下SpringMVC的上传下载流程,还有下载文件时ResponseHeader的设置

package com.podago.demo.controller;

import com.podago.demo.utils.ExcelUtil;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;

import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

/**
 * @author chenxiaotao
 * @version 1.0
 * @date 2019/3/27 9:57
 **/
@RestController
@RequestMapping("/excel")
public class ExcelController {
    private static final String EXCEL2003_CONTENT_TYPE = "application/vnd.ms-excel";
    private static final String EXCEL2007_CONTENT_TYPE = "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet";

    @RequestMapping(value = "/upload", method = RequestMethod.POST)
    public List<List<Object>> upload(@RequestPart MultipartFile excel) {
        String contentType = excel.getContentType();
        if (contentType.equals(EXCEL2003_CONTENT_TYPE) || contentType.equals(EXCEL2007_CONTENT_TYPE)) {
            return ExcelUtil.readExcel(excel);
        } else {
            return new ArrayList<>(0);
        }
    }

    @RequestMapping(value = "/download", method = RequestMethod.POST, produces = {"application/vnd.ms-excel;charset=UTF-8"})
    public void download(@RequestBody List<List<Object>> data, HttpServletResponse response) throws IOException {
        response.setContentType("application/vnd.ms-excel;charset=utf-8");
        response.setHeader("Content-Disposition", "attachment;filename=excel.xls");
        ServletOutputStream outputStream = response.getOutputStream();
        ExcelUtil.createExcel(data, outputStream);
    }
}

前端上传下载

  前端使用了boostrap-fileinput组件,这个组件是可以中文化的,并且感觉很强大,关于这个组件的使用这里不过多描述,可自行查阅资料。

  前端实现的就是,把Excel导入后转换为JSON数据返回回来,把这段JSON数据传回后端能够导出为Excel文件

  excel.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <!-- 最新版本的 Bootstrap 核心 CSS 文件 -->
    <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@3.3.7/dist/css/bootstrap.min.css" integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous">
    <!-- Bootstrap-FileInput CSS -->    
    <link rel="stylesheet" href="dict/plugins/bootstrap-fileinput/css/fileinput.css">

</head>
<body>
    <div id="app" class="panel col-md-8">
        <input id="txt_file" name="excel" type="file" class="file" multiple
                 data-show-upload="true" data-show-caption="true" data-show-preview="false" data-upload-url="/excel/upload" data-allowed-file-extensions='["xlsx","xls"]'>
        <h5>excel文件内容:</h5>
        <textarea class="form-control" disabled rows="5">{{excel_data}}</textarea>
        <a @click="download" class="btn btn-primary">导出Excel</a>
    </div>
</body>
<script src="dict/plugins/jquery-3.3.1.min.js"></script>
<!-- 最新的 Bootstrap 核心 JavaScript 文件 -->
<script src="https://cdn.jsdelivr.net/npm/bootstrap@3.3.7/dist/js/bootstrap.min.js" integrity="sha384-Tc5IQib027qvyjSMfHjOMaLkfuWVxZxUPnCJA7l2mCWNIpG9mGCD8wGNIcPD7Txa" crossorigin="anonymous"></script>
<!-- Bootstrap-FileInput JS -->
<script src="dict/plugins/bootstrap-fileinput/js/fileinput.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script src="dict/excel/excel.js"></script>
</html>

  excel.js

  这里的原生ajax异步下载可以多注意下,它异步获取后 response 的 blob 后利用a标签进行下载,我觉得是个挺好用的东西。

var app = new Vue({
   el: '#app',
   data: {
       excel_data: ''
   },methods:{
        download:function() {
        var url = '/excel/download';
        var xhr = new XMLHttpRequest();
        xhr.open('POST', url, true);        // 也可以使用POST方式,根据接口
        xhr.responseType = "blob";    // 返回类型blob
        xhr.setRequestHeader("Content-Type", "application/json;charset=utf-8");
        // 定义请求完成的处理函数,请求前也可以增加加载框/禁用下载按钮逻辑
        xhr.onload = function () {
            // 请求完成
            if (this.status === 200) {
                // 返回200
                var blob = this.response;
                var reader = new FileReader();
                var filename = this.getResponseHeader("Content-Disposition").substring(20);
                reader.readAsDataURL(blob);    // 转换为base64,可以直接放入a表情href
                reader.onload = function (e) {
                    // 转换完成,创建一个a标签用于下载
                    var a = document.createElement('a');
                    a.download = filename;
                    a.href = e.target.result;
                    $("body").append(a);    // 修复firefox中无法触发click
                    a.click();
                    $(a).remove();
                }
            }
        };
        // 发送ajax请求
        xhr.send(JSON.stringify(app.excel_data))

}
    }, mounted: function() {
        //导入文件上传完成之后的事件
        $("#txt_file").on("fileuploaded", function (event, data, previewId, index) {
            app.excel_data = data.response;
        });
   }
});

最后展示的页面:

测试

测试用Excel:

在前端页面选择文件后上传:

上传成功

 

测试一下 下载 功能:

点击导出Excel按钮进行导出,弹出下载框下载,打开Excel内容进行对比可知下载成功!

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值