Vue2+SpringBoot实现数据导出到csv文件并下载

前言

  • 该功能用于导出数据到csv文件,并且前端进行下载操作。
  • 涉及到java后端以及前端。后端获取数据并处理,前端获取返回流并进行下载操作。
  • csvexcel文件不大相同。如果对导出的数据操作没有很高要求的话,csv文件就够了。具体差异自行百度。
  • 我这里使用的数据是假数据,并没有从数据库获取。

使用csv好处:

  • 由于功能少,所以要比excel文件小,下载快。
  • 后端不需要添加apache-poi等依赖,处理好数据,返回值为字符串字节即可。

1、后端代码

1.1、搭建springBoot项目

搭建项目就不说了,最基本的要求。不会的话需要先学习springBoot(下面演示是基于springBoot的)。

1.2、创建CSV工具类

package com.tcc.utils;

import org.springframework.util.CollectionUtils;

import javax.servlet.http.HttpServletResponse;
import java.io.OutputStream;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.List;
import java.util.Map;

public class CsvUtils {
    /**
     * CSV文件列分隔符
     */
    private static final String CSV_COLUMN_SEPARATOR = ",";

    /**
     * CSV文件行分隔符
     */
    private static final String CSV_ROW_SEPARATOR = "\r\n";

    /**
     * @param dataList 集合数据
     * @param titles   表头部数据
     * @param keys     表内容的键值
     * @param os       输出流
     */
    public static void doExport(List<Map<String, Object>> dataList, String titles, String keys, OutputStream os)
            throws Exception {

        // 保证线程安全
        StringBuffer buf = new StringBuffer();

        String[] titleArr = null;
        String[] keyArr = null;

        titleArr = titles.split(",");
        keyArr = keys.split(",");

        // 组装表头
        for (String title : titleArr) {
            buf.append(title).append(CSV_COLUMN_SEPARATOR);
        }
        buf.append(CSV_ROW_SEPARATOR);
        // 组装数据
        if (!CollectionUtils.isEmpty(dataList)) {
            for (Map<String, Object> data : dataList) {
                for (String key : keyArr) {
                    buf.append("\t" +data.get(key)).append(CSV_COLUMN_SEPARATOR);
                }
                buf.append(CSV_ROW_SEPARATOR);
            }
        }

        // 写出响应
        os.write(buf.toString().getBytes("GBK"));
        os.flush();
    }

    /**
     * 设置Header 辅助函数, 可用可不用
     *
     * @param fileName
     * @param response
     * @throws UnsupportedEncodingException
     */
    public static void responseSetProperties(String fileName, HttpServletResponse response)
            throws UnsupportedEncodingException {
        // 设置文件后缀
        SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMddHHmmss");
        String fn = fileName + sdf.format(new Date()) + ".csv";
        // 读取字符编码
        String utf = "UTF-8";

        // 设置响应
        response.setContentType("application/ms-txt.numberformat:@");
        response.setCharacterEncoding(utf);
        response.setHeader("Pragma", "public");
        response.setHeader("Cache-Control", "max-age=30");
        response.setHeader("Content-Disposition", "attachment; filename=" + URLEncoder.encode(fn, utf));
    }
}

1.3、编写接口

package com.tcc.controller;

import com.tcc.utils.CsvUtils;
import org.springframework.web.bind.annotation.CrossOrigin;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletResponse;
import java.util.*;

@RestController
@RequestMapping("/demo")
public class DemoController {

    @RequestMapping("generateCSV")
    // 解决跨域问题
    @CrossOrigin
    public void generateCSV(HttpServletResponse response) throws Exception {
        ServletOutputStream outputStream = response.getOutputStream();

        List<Map<String, Object>> dataList = new ArrayList();
        HashMap<String, Object> map = new HashMap<>();
        // 第一条数据
        map.put("name", "张三");
        map.put("age", 20);
        map.put("sex", "男");
        map.put("brithday",  new Date());
        dataList.add(map);

        // 第二条数据
        map = new HashMap<>();
        map.put("name", "李四");
        map.put("age", 22);
        map.put("sex", "女");
        map.put("brithday",  new Date());
        dataList.add(map);

        // 辅助函数,可用可不用
//        CsvUtils.responseSetProperties("test", response);
        CsvUtils.doExport(dataList,
                "姓名,年龄,性别,生日", // 所有列名
                "name,age,sex,brithday", // 列名对应的数据列的字段
                outputStream);
    }
}

2、前端代码

2.1、搭建vue2框架

也是最基本的,就不说了。

2.2、调用接口,并进行下载

<template>
  <div class="home">
    <button @click="downLoadFile">测试按钮</button>
  </div>
</template>

<script>
export default {
  name: 'HomeView',
  methods: {
    downLoadFile() {
      this.axios.post("http://localhost:8080/demo/generateCSV", {}, {
        responseType: 'blob' // 设置响应结果类型为blob类型
      }).then(res => {
         // 处理数据,并下载
        const blob = new Blob([res.data]);
        let url = window.URL.createObjectURL(blob)
        let link = document.createElement('a')
        link.href = url
        link.setAttribute('download', 'test.csv')
        document.body.appendChild(link)
        link.click()
      })
    }
  }
}
</script>

3、效果

在这里插入图片描述

优化

1、数据中含有,

  1. 因为csv文件,默认使用,来当作单元格分隔符。当我们数据中有,时,也会自动根据,进行分割,导致数据显示错乱,这时候就需要转义等。
  2. 如果数据中含有,,则么在数据外层包一层"",即可防止分割。

1.1、后台代码优化

package com.tcc.utils;

import lombok.val;
import org.springframework.util.CollectionUtils;

import javax.servlet.http.HttpServletResponse;
import java.io.OutputStream;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.List;
import java.util.Map;

public class CsvUtils {
    /**
     * CSV文件列分隔符
     */
    private static final String CSV_COLUMN_SEPARATOR = ",";

    /**
     * CSV文件行分隔符
     */
    private static final String CSV_ROW_SEPARATOR = "\r\n";

    /**
     * @param dataList 集合数据
     * @param titles   表头部数据
     * @param keys     表内容的键值
     * @param os       输出流
     */
    public static void doExport(List<Map<String, Object>> dataList, String titles, String keys, OutputStream os)
            throws Exception {

        // 保证线程安全
        StringBuffer buf = new StringBuffer();

        String[] titleArr = null;
        String[] keyArr = null;

        titleArr = titles.split(",");
        keyArr = keys.split(",");

        // 组装表头
        for (String title : titleArr) {
            buf.append(title).append(CSV_COLUMN_SEPARATOR);
        }
        buf.append(CSV_ROW_SEPARATOR);
        // 组装数据
        if (!CollectionUtils.isEmpty(dataList)) {
            for (Map<String, Object> data : dataList) {
                for (String key : keyArr) {
                    buf.append(CsvUtils.handleFormatValue(data.get(key))).append(CSV_COLUMN_SEPARATOR);
                }
                buf.append(CSV_ROW_SEPARATOR);
            }
        }

        // 写出响应
        os.write(buf.toString().getBytes("GBK"));
        os.flush();
    }

    /**
     * 设置Header 辅助函数, 可用可不用
     *
     * @param fileName
     * @param response
     * @throws UnsupportedEncodingException
     */
    public static void responseSetProperties(String fileName, HttpServletResponse response)
            throws UnsupportedEncodingException {
        // 设置文件后缀
        SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMddHHmmss");
        String fn = fileName + sdf.format(new Date()) + ".csv";
        // 读取字符编码
        String utf = "UTF-8";

        // 设置响应
        response.setContentType("application/ms-txt.numberformat:@");
        response.setCharacterEncoding(utf);
        response.setHeader("Pragma", "public");
        response.setHeader("Cache-Control", "max-age=30");
        response.setHeader("Content-Disposition", "attachment; filename=" + URLEncoder.encode(fn, utf));
    }

    // 处理数据
    private static String handleFormatValue(Object data) {
        if (data == null) {
            return "";
        }
        String val = data.toString();

        // 如果数据中含有 " 则在 "外层再包一层 ""
        if (val.contains("\"") ){
            val=val.replace("\"", "\"\"");
        }

        // 如果数据中含有 , 则在 , 外层再包一层 ""
         if (val.contains(",")) {
            val = "\""+val+"\"";
        } else {
             // \t 是为了处理字符串的日期类型数据导出显示异常
             val = "\t" + val;
         }

         return val;
    }
}

1.2效果展示

在这里插入图片描述

2、传参优化

  1. 当列很多时,像那样传参会很长,并且对不齐,维护起来非常费力。那么我们可以传入一个Map结构的数据,一个标题,对应一个字段名,即可很好的维护。

1.1、工具类方法优化

public static void doExport2(List<Map<String, Object>> dataList, Map<String, String> titleAndFields, OutputStream os) throws Exception{
        // 保证线程安全
        StringBuffer buf = new StringBuffer();

        String titles = String.join(CSV_COLUMN_SEPARATOR, titleAndFields.keySet());

        Collection<String> keys = titleAndFields.values();

        // 组装表头
        buf.append(titles);
        buf.append(CSV_ROW_SEPARATOR);
        // 组装数据
        if (!CollectionUtils.isEmpty(dataList)) {
            for (Map<String, Object> data : dataList) {
                for (String key : keys) {
                    buf.append(CsvUtils.handleFormatValue(data.get(key))).append(CSV_COLUMN_SEPARATOR);
                }
                buf.append(CSV_ROW_SEPARATOR);
            }
        }

        // 写出响应
        os.write(buf.toString().getBytes("GBK"));
        os.flush();
    }

1.2、传参代码

@RequestMapping("generateCSV")
    // 解决跨域问题
    @CrossOrigin
    public void generateCSV(HttpServletResponse response) throws Exception {
        ServletOutputStream outputStream = response.getOutputStream();

        List<Map<String, Object>> dataList = new ArrayList();
        HashMap<String, Object> map = new HashMap<>();
        // 第一条数据
        map.put("name", "张三");
        map.put("age", 20);
        map.put("sex", "男");
        map.put("brithday",  new Date());
        map.put("hobby", "打篮球,乒乓球,台球");
        dataList.add(map);

        // 第二条数据
        map = new HashMap<>();
        map.put("name", "李四");
        map.put("age", 22);
        map.put("sex", "女");
        map.put("brithday",  new Date());
        map.put("hobby", "踢足球,羽毛球");
        dataList.add(map);

        CsvUtils.responseSetProperties("test", response);
        /*CsvUtils.doExport(dataList,
                "姓名,年龄,性别,生日,爱好", // 所有列名
                "name,age,sex,brithday,hobby", // 列名对应的数据列的字段
                outputStream);*/

        // 一定要使用 LinkedHashMap,否则列名会乱序
        CsvUtils.doExport2(dataList, new LinkedHashMap<String, String>() {{
            put("姓名", "name");
            put("年龄", "age");
            put("性别", "sex");
            put("生日", "brithday");
            put("爱好", "hobby");
        }}, outputStream);

    }

1.3效果

在这里插入图片描述

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值