• 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
    • 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
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85

    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);
        }
    }
    
    
    • 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

    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>
    
    • 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

    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
    • 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
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109

    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
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25

    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
    • 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

    1.3效果

    在这里插入图片描述

  • 相关阅读:
    【爬虫作业】使用scrapy爬取菜谱,存入elasticsearch中建立菜谱搜索引擎
    python、pycharm、pip介绍与安装
    【IMX6ULL笔记】-- 从驱动到应用(基于Qt)- CAN总线
    JVM-垃圾回收
    step num 问题
    锅炉智能制造工厂工业物联数字孪生平台,推进制造业数字化转型
    MongoDB之用户与权限管理、备份与恢复管理以及客户端工具的使用
    时序预测 | MATLAB实现贝叶斯优化CNN-GRU时间序列预测(股票价格预测)
    数据分析-利用gpt进行电商平台用户细分专题分析(RFM模型)
    博客数量为啥产出少了呢
  • 原文地址:https://blog.csdn.net/qq_57404736/article/details/133834155