前言

最近在项目中遇到一个需求,需要后端提供一个下载Csv和Excel表格的接口。这个接口接收前端的查询参数,针对这些参数对数据库做查询操作。将查询到的结果生成Excel和Csv文件,再以字节流的形式返回给前端。

前端拿到这个流文件之后,最开始用ajax来接收,但是前端发送的请求却被浏览器cancel掉了。后来发现,发展了如此之久的Ajax居然不支持流文件下载。后来前端换成了最原始的XMLHttpRequest,才修复了这个问题。

首先给出项目源码的地址。这是源码,欢迎大家star或者提MR。

Csv

新建controller

先来一个简单的例子。首先在controller中新建这样一个接口。

  1. @GetMapping("csv")
  2. public void csv(
  3. HttpServletRequest request,
  4. HttpServletResponse response
  5. ) throws IOException {
  6. String fileName = this.getFileName(request, "测试数据.csv");
  7. response.setContentType(MediaType.APPLICATION_OCTET_STREAM.toString());
  8. response.setHeader("Content-Disposition", "attachment; filename=\"" + fileName + "\";");
  9. LinkedHashMap<String, Object> header = new LinkedHashMap<>();
  10. LinkedHashMap<String, Object> body = new LinkedHashMap<>();
  11. header.put("1", "姓名");
  12. header.put("2", "年龄");
  13. List<LinkedHashMap<String, Object>> data = new ArrayList<>();
  14. body.put("1", "小明");
  15. body.put("2", "小王");
  16. data.add(header);
  17. data.add(body);
  18. data.add(body);
  19. data.add(body);
  20. FileCopyUtils.copy(ExportUtil.exportCSV(data), response.getOutputStream());
  21. }

其中this.getFileName(request, "测试数据.csv")函数是用来获取导出文件名的函数。单独提出来是因为不同浏览器使用的默认的编码不同。例如,如果使用默认的UTF-8编码。在chrome浏览器中下载会出现中文乱码。代码如下。

  1. private String getFileName(HttpServletRequest request, String name) throws UnsupportedEncodingException {
  2. String userAgent = request.getHeader("USER-AGENT");
  3. return userAgent.contains("Mozilla") ? new String(name.getBytes(), "ISO8859-1") : name;
  4. }

response.getOutputStream()则是用于创建字节输出流,在导出csv文件的controller代码结尾,通过工具类中的复制文件函数将字节流写入到输出流中,从而将csv文件以字节流的形式返回给客户端。

当前端通过http请求访问服务器接口的时候,http中的所有的请求信息都会封装在HttpServletRequest对象中。例如,你可以通过这个对象获取到请求的URL地址,请求的方式,请求的客户端IP和完整主机名,Web服务器的IP和完整主机名,请求行中的参数,获取请求头的参数等等。

针对每一次的HTTP请求,服务器会自动创建一个HttpServletResponse对象和请求对象相对应。响应对象可以对当前的请求进行重定向,自定义响应体的头部,设置返回流等等。

新建导出工具类

我们新建一个导出工具类,来专门负责导出各种格式的文件。代码如下。

  1. public class ExportUtil {
  2. public static byte[] exportCSV(List<LinkedHashMap<String, Object>> exportData) {
  3. ByteArrayOutputStream out = new ByteArrayOutputStream();
  4. BufferedWriter buffCvsWriter = null;
  5. try {
  6. buffCvsWriter = new BufferedWriter(new OutputStreamWriter(out, StandardCharsets.UTF_8));
  7. // 将body数据写入表格
  8. for (Iterator<LinkedHashMap<String, Object>> iterator = exportData.iterator(); iterator.hasNext(); ) {
  9. fillDataToCsv(buffCvsWriter, iterator.next());
  10. if (iterator.hasNext()) {
  11. buffCvsWriter.newLine();
  12. }
  13. }
  14. // 刷新缓冲
  15. buffCvsWriter.flush();
  16. } catch (IOException e) {
  17. e.printStackTrace();
  18. } finally {
  19. // 释放资源
  20. if (buffCvsWriter != null) {
  21. try {
  22. buffCvsWriter.close();
  23. } catch (IOException e) {
  24. e.printStackTrace();
  25. }
  26. }
  27. }
  28. return out.toByteArray();
  29. }
  30. private static void fillDataToCsv(BufferedWriter buffCvsWriter, LinkedHashMap row) throws IOException {
  31. Map.Entry propertyEntry;
  32. for (Iterator<Map.Entry> propertyIterator = row.entrySet().iterator(); propertyIterator.hasNext(); ) {
  33. propertyEntry = propertyIterator.next();
  34. buffCvsWriter.write("\"" + propertyEntry.getValue().toString() + "\"");
  35. if (propertyIterator.hasNext()) {
  36. buffCvsWriter.write(",");
  37. }
  38. }
  39. }
  40. }

fillDataToCsv主要是抽离出来为csv填充一行一行的数据的。

运行

然后运行项目,调用http://localhost:8080/csv,就可以下载示例的csv文件。示例如下。

Excel

新建controller

新建下载xlsx文件的接口。

  1. @GetMapping("xlsx")
  2. public void xlsx(
  3. HttpServletRequest request,
  4. HttpServletResponse response
  5. ) throws IOException {
  6. String fileName = this.getFileName(request, "测试数据.xlsx");
  7. response.setContentType(MediaType.APPLICATION_OCTET_STREAM.toString());
  8. response.setHeader("Content-Disposition", "attachment; filename=\"" + fileName + "\";");
  9. List<LinkedHashMap<String, Object>> datas = new ArrayList<>();
  10. LinkedHashMap<String, Object> data = new LinkedHashMap<>();
  11. data.put("1", "姓名");
  12. data.put("2", "年龄");
  13. datas.add(data);
  14. for (int i = 0; i < 5; i++) {
  15. data = new LinkedHashMap<>();
  16. data.put("1", "小青");
  17. data.put("2", "小白");
  18. datas.add(data);
  19. }
  20. Map<String, List<LinkedHashMap<String, Object>>> tableData = new HashMap<>();
  21. tableData.put("日报表", datas);
  22. tableData.put("周报表", datas);
  23. tableData.put("月报表", datas);
  24. FileCopyUtils.copy(ExportUtil.exportXlsx(tableData), response.getOutputStream());
  25. }

补充工具类

上面新建的导出工具类中,只有导出csv的函数,接下来我们要添加导出xlsx的函数。

  1. public static byte[] exportXlsx(Map<String, List<LinkedHashMap<String, Object>>> tableData) {
  2. ByteArrayOutputStream out = new ByteArrayOutputStream();
  3. try {
  4. HSSFWorkbook workbook = new HSSFWorkbook();
  5. // 创建多个sheet
  6. for (Map.Entry<String, List<LinkedHashMap<String, Object>>> entry : tableData.entrySet()) {
  7. fillDataToXlsx(workbook.createSheet(entry.getKey()), entry.getValue());
  8. }
  9. workbook.write(out);
  10. } catch (IOException e) {
  11. e.printStackTrace();
  12. }
  13. return out.toByteArray();
  14. }
  15. /**
  16. * 将linkedHashMap中的数据,写入xlsx表格中
  17. *
  18. * @param sheet
  19. * @param data
  20. */
  21. private static void fillDataToXlsx(HSSFSheet sheet, List<LinkedHashMap<String, Object>> data) {
  22. HSSFRow currRow;
  23. HSSFCell cell;
  24. LinkedHashMap row;
  25. Map.Entry propertyEntry;
  26. int rowIndex = 0;
  27. int cellIndex = 0;
  28. for (Iterator<LinkedHashMap<String, Object>> iterator = data.iterator(); iterator.hasNext(); ) {
  29. row = iterator.next();
  30. currRow = sheet.createRow(rowIndex++);
  31. for (Iterator<Map.Entry> propertyIterator = row.entrySet().iterator(); propertyIterator.hasNext(); ) {
  32. propertyEntry = propertyIterator.next();
  33. if (propertyIterator.hasNext()) {
  34. String value = String.valueOf(propertyEntry.getValue());
  35. cell = currRow.createCell(cellIndex++);
  36. cell.setCellValue(value);
  37. } else {
  38. String value = String.valueOf(propertyEntry.getValue());
  39. cell = currRow.createCell(cellIndex++);
  40. cell.setCellValue(value);
  41. break;
  42. }
  43. }
  44. if (iterator.hasNext()) {
  45. cellIndex = 0;
  46. }
  47. }
  48. }

fillDataToXlsx的用途与csv一样,为xlsx文件的每一行刷上数据。

运行

然后运行项目,调用http://localhost:8080/xlsx,就可以下载示例的csv文件。示例如下。

项目地址

最后再次给出项目地址,大家如果没有理解到其中的一些地方,不妨把项目clone下来,自己亲自操作一波。

参考

这是在解决请求被浏览器cancel掉的过程中,很重要的一个参考,分享给大家。

想在Java中实现Excel和Csv的导出吗?看这就对了的更多相关文章

  1. SpringBoot中关于Excel的导入和导出

    前言   由于在最近的项目中使用Excel导入和导出较为频繁,以此篇博客作为记录,方便日后查阅.本文前台页面将使用layui,来演示对Excel文件导入和导出的效果.本文代码已上传至我的gitHub, ...

  2. JAVA中生成Excel方法

    java 操作 Excel 最常用的就是JXL(java excel api)和POI,今先看下JXL吧.首先可以到 http://www.andykhan.com/jexcelapi/downloa ...

  3. 用java代码解决excel打开csv文件乱码问题

      Java 读取csv文件后,再保存到磁盘上,然后直接用Excel打开,你会发现里面都是乱码. 贴上代码: public class Test { public static void main(S ...

  4. POI开发:Java中的Excel相关操作

    一.Apache POI 1.简介: Apache POI支持大多数中小规模的应用程序开发,提供API给Java程序对Microsoft Office格式档案读和写的功能,呈现和文本提取是它的主要特点 ...

  5. Java中使用jxl.jar将数据导出为excel文件

      Java对Excel文件的读写操作可由jxl.jar或poi.jar实现,这里使用jxl.jar完成对Excel文件的导出. 一.将Excel文件导出在本地 步骤:   创建文件 -> 创建 ...

  6. Java中读取Excel功能实现_POI

    这里使用apache的poi进行读取excel 1,新建javaproject 项目:TestExcel 2,导入包 包下载地址:http://poi.apache.org/download.html ...

  7. java中解析excel 批量插入数据库

    Facade 层 实现类 (@Service("samePeriodModelImportFacade")) 1.  获取cells 的方法 public Cells getCel ...

  8. java中的excel操作

    导入jxl.jar包: 下载个jxl.jar包,然后这个包放在什么位置都行,在你的项目中导入这个包就可以.   具体做法: 项目上右键,点击“属性”, 类别那里选择”库“,点击"添加jar文 ...

  9. java中的Excel导出功能

    public void exportExcel(Long activityId, HttpServletResponse response) throws IOException { // 获取统计报 ...

随机推荐

  1. 土制Excel导入导出及相关问题探讨

    转载请注明出处https://www.cnblogs.com/funnyzpc/p/10392085.html 新的一年,又一个开始,不见收获,却见年龄,好一个猪年,待我先来一首里尔克的诗: < ...

  2. 发现Chrome 浏览器 JavaScript Date对象的几个Bug

    打开浏览器F12 Console 输入: 第一个 位数影响 new Date("2018-06-9") Sat Jun 09 2018 00:00:00 GMT+0800 (中国标 ...

  3. ionic基于GPS定位并通过百度地图获取定位详细信息

    相信所有的前端攻城狮都会碰到移动端App.里面获取用户定位信息. 那么问题来了,怎么获取用户的定位信息(经纬度)呢. 当然方法有很多,通过百度地图API 以及 高德地图 API都是可以的.但是两个获取 ...

  4. Java解析json字符串和json数组

    Java解析json字符串和json数组 public static Map<String, String> getUploadTransactions(String json){ Map ...

  5. java学习之路--零碎的知识笔记

    java运算符: 自增自减运算符: int b = ++a; 拆分运算过程为: a=a+1=4; b=a=4, 最后结果为b=4,a=4 前缀自增自减法(++a,--a): 先进行自增或者自减运算,再 ...

  6. C语言复习4_while循环

    1.while循环 循环三要素: 1).循环变量的初值 2).循环变量的判断 3).循环变量的更新 #include <stdio.h> #include <stdlib.h> ...

  7. OJ002

    register:这个关键字请求编译器尽可能的将变量存在CPU内部寄存器中,而不是通过内存寻址访问,以提高效率.注意是尽可能,不是绝对. 因为,如果定义了很多register变量,可能会超过CPU的寄 ...

  8. Javascript——浅谈 Event Flow

    1.Javascript Events : Event Bubbling(事件冒泡) 如果事件从最特定的元素开始,则事件流中的一个阶段称为事件冒泡(DOM中可能最深的节点)然后向上流向最不特定的节点( ...

  9. Hadoop 操作常见问题解决

    1. 安全模式下不可操作 提示信息: Hadoop "Cannot create directory .Name node is in safe mode." 解决方法: $ ha ...

  10. Dora.Interception,为.NET Core度身打造的AOP框架 [3]:多样化拦截器应用方式

    在<以约定的方式定义拦截器>中,我们通过对拦截器的介绍了Dora.Interception的两种拦截机制,即针对接口的“实例拦截”针对虚方法的“类型拦截”.我们介绍了拦截器的本质以及基于约 ...