Springboot 那年我双手插兜,手写一个excel导出

news2024/11/29 10:41:43

前言

其实就是利用了csv 和txt  文件转换 。

 

不多说,开始玩代码。

正文


本篇内容:


① 了解根本生成excel内容的CSV文件玩法

② 手动拼接文本演示

③ 项目内实战写法,从数据库到导出

④ 解决list数据过多,使用分批分页处理生成csv (EXCEL)
 

思路:

创建csv文件, 往里面写入符合转换成csv文件的内容 即可。

① 了解根本生成excel内容的CSV文件玩法

先看看什么原理 :

首先我们创建一个csv文件



 

然后打开里面填充一些数据:
 

 

然后反手把文件后缀改成.txt 看看里面是啥 :
 

 

看看里面:
 

 

② 手动拼接文本演示

 

工具类 :

MyCsvFileUtil.java

import lombok.extern.slf4j.Slf4j;
import org.apache.commons.io.IOUtils;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStreamWriter;

@Slf4j
public class MyCsvFileUtil {
    public static final String CSV_DELIMITER = ",";
    public static final String CSV_TAIL = "\r\n";
    /**
     * 将字符串转成csv文件
     */
    public  static  void createCsvFile(String savePath,String contextStr) throws IOException {

        File file = new File(savePath);
        //创建文件
        file.createNewFile();
        //创建文件输出流
        FileOutputStream fileOutputStream = new FileOutputStream(file);
        //将指定字节写入此文件输出流
        fileOutputStream.write(contextStr.getBytes("gbk"));
        fileOutputStream.flush();
        fileOutputStream.close();
    }

    /**
     * 写文件
     *
     * @param fileName
     * @param content
     */
    public static void writeFile(String fileName, String content) {
        FileOutputStream fos = null;
        OutputStreamWriter writer = null;
        try {
            fos = new FileOutputStream(fileName, true);
            writer = new OutputStreamWriter(fos, "GBK");
            writer.write(content);
            writer.flush();
        } catch (Exception e) {
            log.error("写文件异常|{}", e);
        } finally {
            if (fos != null) {
                IOUtils.closeQuietly(fos);
            }
            if (writer != null) {
                IOUtils.closeQuietly(writer);
            }
        }
    }

演示拼接调用工具类生成CSV文件:
 

    @RequestMapping("/createCsvFileTest")
    public void doTest() throws IOException {
        //存放地址
        String path = "D:\\mycsv\\test.csv";
        String word = "";
        //表头固定好
        String tableNames = "CODE,NAME,PARENT_CODE,FULL_NAME";
        //数据内容。
        String oneRows = "110100,北京市,110000,北京北京市";
        String twoRows = "110101,东城区,110100,北京北京市东城区";
        String threeRows = "110102,西城区,110100,北京北京市西城区";
        String fourRows = "110105,朝阳区,110100,北京北京市朝阳区";
        //拼接
        word += tableNames + "\r\n";
        word += oneRows+ "\r\n";
        word += twoRows+ "\r\n";
        word += threeRows+ "\r\n";
        word += fourRows+ "\r\n";
        //调用方法生成
        MyCsvFileUtil.createCsvFile(path,word);
    }

代码简析:

调用这个示例接口,看看效果:

 

 

 

③ 项目内实战写法,从数据库到导出

接口使用写法:
 

    @RequestMapping("/createCsvFileTest2")
    public void createCsvFileTest2() throws IOException {

        List<District> districts = districtMapper.queryByParentCodes(Arrays.asList("110100"));
        //存放地址&文件名
        String fileName = "D:\\mycsv\\test2.csv";
        String tableNames = "CODE,NAME,PARENT_CODE,FULL_NAME"+MyCsvFileUtil.CSV_DELIMITER;
       //创建文件
        MyCsvFileUtil.createCsvFile(fileName,tableNames);
        //写入数据
        String contentBody =buildCsvFileBody(districts);
        //调用方法生成
        MyCsvFileUtil.createCsvFile(fileName,contentBody);
    }

解析数据list 做内容拼接处理 函数:
 

ps: 表头 多少个,字段就多少个, 默认值赋值啥的,格式转换啥的都可以,只要每一行数量对的上即可,每一个最后结尾的标记就是 String CSV_TAIL = "\r\n"

    @RequestMapping("/createCsvFileTest2")
    public void createCsvFileTest2() throws IOException {

        List<District> districts = districtMapper.queryByParentCodes(Arrays.asList("110100"));
        //存放地址&文件名
        String fileName = "D:\\mycsv\\test2.csv";
        String tableNames = "CODE,NAME,PARENT_CODE,FULL_NAME"+MyCsvFileUtil.CSV_TAIL;
       //创建文件
        MyCsvFileUtil.writeFile(fileName,tableNames);
        //写入数据
        String contentBody =buildCsvFileBody(districts);
        //调用方法生成
        MyCsvFileUtil.writeFile(fileName,contentBody);
    }

 

 

事不宜迟,看看接口调用效果:

可以看到从数据量里面查出来这 16条数据,list集合:

 

最后转换生成文件:
 

 

 可以看到成功出货:


 

那么问题来了,如果我们需要导出的数据条数很多,拼接的contentBody 会非常长。

那么我们就需要考虑分批查询、分批拼接处理、分批写入,按照实际业务场景和数据长度去考量,每一批的限制。 甚至还可以实现拼接好每一批,然后慢慢再根据当前批次ID,做顺序写入。

④ 解决list数据过多,使用分批分页处理生成csv (EXCEL)

抛转引玉,给大家整个简单的分批玩法,生成csv 。

改造点 1  , 整一个分页查询。

老面孔手动分页DTO:

import lombok.Data;

/**
 * @Author: JCccc
 * @Date: 2022-6-15 16:53
 * @Description:
 */
@Data
public class PageLimitDTO {

    private Integer pageSize;
    private Integer currIndex;

}

工具类  MyPageCutUtil.java

import lombok.extern.slf4j.Slf4j;

import java.util.LinkedList;
import java.util.List;

@Slf4j
public class MyPageCutUtil {

    public static List<PageLimitDTO> getPageLimitGroupList(Integer totalCount, Integer batchSizeLimit) {
        log.info("这一次处理的总数据条数为 ={} 条, 每一批次处理条数为 ={} 条,现在开始做分批切割处理。",totalCount,batchSizeLimit);
        int pageNum = totalCount / batchSizeLimit;
        int surplus = totalCount % batchSizeLimit;
        if (surplus > 0) {
            pageNum = pageNum + 1;
        }
        List<PageLimitDTO> pageLimitGroupList =new LinkedList<>();
        for(int i = 0; i < pageNum; i++){
            Integer currIndex = i * batchSizeLimit;
            PageLimitDTO pageLimitDTO=new PageLimitDTO();
            pageLimitDTO.setPageSize(batchSizeLimit);
            pageLimitDTO.setCurrIndex(currIndex);
            pageLimitGroupList.add(pageLimitDTO);
            log.info("分批切割,第={}次,每次={}条,最终会处理到={}条。",pageLimitGroupList.size(),batchSizeLimit,currIndex+batchSizeLimit);

        }
        log.info("这一次处理的总数据条数为 ={} 条, 每一批次处理条数为 ={} 条,总共切割分成了 ={} 次,一切准备就绪。",totalCount,batchSizeLimit,pageLimitGroupList.size());
        return pageLimitGroupList;
    }
    
}

老面孔手动分页统计sql :

mapper 简单打个样

/**
 * 统计所有符合搜索条件的数据
 * @param codeList
 * @return
 */
int  getCountAllList(@Param("codeList") List<String> codeList);

对应xml 的sql 

<select id="getCountAllList" resultType="java.lang.Integer">
    SELECT
    COUNT(*)
    FROM district_info
    WHERE PARENT_CODE IN
    <foreach collection="codeList" item="code" open="(" separator="," close=")">
        #{code}
    </foreach>

</select>

老面孔手动分页查询sql:
 

/**
 * 手动分页查询
 * @param codeList
 * @param currIndex
 * @param pageSize
 * @return
 */
List<District> getPageList(@Param("codeList") List<String> codeList,Integer currIndex,Integer pageSize);

sql:

<select id="getPageList" resultMap="BaseResultMap">
    SELECT
    <include refid="Base_Column_List"/>
    FROM district_info
    <where>
        <if test="codeList != null and  !codeList.isEmpty()">
            PARENT_CODE  IN
            <foreach collection="codeList" item="code" open="(" separator="," close=")">
                #{code}
            </foreach>
        </if>
    </where>
    LIMIT #{currIndex} , #{pageSize}
</select>

ok接下来继续改造我们的分批查询,处理数据:

 

    @RequestMapping("/createCsvFileTest3")
    public void createCsvFileTest3() throws IOException {

        //存放地址&文件名
        String fileName = "D:\\mycsv\\test3.csv";
        String tableNames = "地域编码,地域名称,父级编码,地域全称" + MyCsvFileUtil.CSV_TAIL;
        //创建文件
        MyCsvFileUtil.writeFile(fileName, tableNames);

        //获取数据总计数
        Integer totalCount = districtMapper.getCountAllList(null);
        //每批同步的数据条数
        Integer batchSizeLimit = 500;
        //分批切割处理
        List<PageLimitDTO> pageLimitGroupList = MyPageCutUtil.getPageLimitGroupList(totalCount, batchSizeLimit);
        int count = 1;
        //物理批次查询
        for (PageLimitDTO pageBatchLimit : pageLimitGroupList) {
            List<District> pageBatchList = districtMapper.getPageList(null, pageBatchLimit.getCurrIndex(), pageBatchLimit.getPageSize());
            if (!CollectionUtils.isEmpty(pageBatchList)) {
                //写入数据
                String contentBody = buildCsvFileBody(pageBatchList);
                //调用方法生成
                MyCsvFileUtil.writeFile(fileName, contentBody);
            }
            log.info("第{}批次,District数据处理结束执行", count);
            count = count + 1;
        }
    }

代码简析: 

 

OK,调用一下接口,看看效果:

 打开看看数据:

 

 

 

ps:还有没有优化封装余地?
有的,文中讲到了,还可以考虑加上整个大批次的ID,然后考虑并行查询,并行拼接后,再按顺序插入。
有想法的还可以写个注解,标记相关表头别名,是否参与导出,然后再拼接的时候魔改一手,反射自动拿字段属性等等。

好了该篇就到这。 

 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/85179.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

极智AI | centos7源码编译tensorflow

欢迎关注我的公众号 [极智视界]&#xff0c;获取我的更多笔记分享 大家好&#xff0c;我是极智视界&#xff0c;本文介绍一下 centos7 源码编译 tensorflow 的方法。 之前这篇《极智开发 | centos7源码编译bazel》已经为这篇 tensorflow 的源码编译铺平了道路&#xff0c;所以…

[附源码]Nodejs计算机毕业设计基于web的小说浏览系统Express(程序+LW)

该项目含有源码、文档、程序、数据库、配套开发软件、软件安装教程。欢迎交流 项目运行 环境配置&#xff1a; Node.js Vscode Mysql5.7 HBuilderXNavicat11VueExpress。 项目技术&#xff1a; Express框架 Node.js Vue 等等组成&#xff0c;B/S模式 Vscode管理前后端分…

【Docker】Docker安装MySQL,并解决中文乱码和配置数据备份同步到宿主机

专栏精选文章 《Docker是什么&#xff1f;Docker从介绍到Linux安装图文详细教程》《30条Docker常用命令图文举例总结》《Docker如何构建自己的镜像&#xff1f;从镜像构建到推送远程镜像仓库图文教程》《Docker多个容器和宿主机之间如何进行数据同步和数据共享&#xff1f;容器…

银河麒麟操作系统V10SP1创建网页快捷方式至桌面的方法

修改浏览器配置文件添加快捷方式 1.在桌面点击鼠标右键&#xff0c;选择‘’打开终端‘’&#xff0c;终端界面显示‘桌面’ 2.在终端界面输入命令行 sudo vim qaxbrowser-safe.desktop (奇安信浏览 器的快捷方式) 进去后按‘/’然后输入‘Exec’&#xff0c;最后按回车键。…

Android监听UEvent之UEventObserver分析

&#xff08;1&#xff09;背景概述 众所周知&#xff0c;在安卓系统中有状态栏&#xff0c;在插入外设的时候&#xff0c;会在顶部状态栏显示小图标。 比如&#xff0c;camera设备&#xff0c;耳机设备&#xff0c;U盘&#xff0c;以及电池等等。这些都需要在状态栏动态显示。…

wy的leetcode刷题记录_Day58

wy的leetcode刷题记录_Day58 声明 本文章的所有题目信息都来源于leetcode 如有侵权请联系我删掉! 时间&#xff1a;2022-12-2 前言 力扣每日一题简单模拟左右抵消和二叉平衡搜索树 1769. 移动所有球到每个盒子所需的最小操作数和108. 将有序数组转换为二叉搜索树 目录wy的l…

无线路由器首次配置、修改WiFi名称和密码—— Cisco实验/家里实验

一、Csico实验 192.168.0.1、192.168.1.0和192.168.1.1是路由器常用的默认IP 1. 在PC打开浏览器&#xff08;PC用网线直连无线路由器&#xff09;&#xff0c; 输入无线路由器在局域网内的静态IP&#xff1a;192.168.0.1 2. 输入管理者的账号和密码&#xff0c;默认都是admin…

搜索与图论- Dijkstra 算法

文章目录一、Dijkstra 算法1. 简介2. 基本思想3. 朴素 Dijkstra 算法&#xff08;重点&#xff09;3.1 朴素 Dijkstra 算法实现步骤3.2 朴素 Dijkstra 算法伪代码4. 朴素 Dijkstra 算法具体实现详见例题 Dijkstra 求最短路 I 。5. 堆优化朴素 Dijkstra 算法6. 堆优化 Dijkstra …

cookie、sessionStorage和localStorage的区别(二)

cookie、sessionStorage和localStorage的区别&#xff08;一&#xff09;详细精炼知识调用前言引入核心干货webstorage本地存储cookiesessionStoragelocalStorage知识调用 文章中可能用到的知识点前端学习&#xff1a;浏览器缓存方式有哪些&#xff08; cookie localstorage s…

rocketmq源码学习-broker启动

前言 这篇笔记记录broker启动的源码学习 broker主要完成一下几件事情&#xff1a; 1.接收producer的发送请求&#xff0c;并对消息进行持久化、同步其他节点 2.接收consumer读取消息星球 3.定时向nameSrv注册心跳信息&#xff0c;保持连接 在启动的时候&#xff0c;也是分了…

Ant Design 6.0.0 实践集合

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 使用的6.0.0 beta版本 文章目录 前言一、pandas是什么&#xff1f;二、使用步骤 1.引入库2.读入数据总结前言 Ant Design 简称为 Antd antd 为 Web 应用提供了丰富的基础 U…

操作指南|通过JumpServer实现Kubernetes运维安全审计

本文重点介绍如何通过JumpServer实现Kubernetes的运维安全审计。此前&#xff0c;我们专门介绍过在Kubernetes集群上快速部署JumpServer的方法步骤&#xff0c;可参见《操作指南&#xff5c;在Kubernetes集群上快速部署JumpServer开源堡垒机》一文。 一、Kubernetes运维审计现…

ABP Vnext 学习03-授权中心微信小程序登录

前言 小程序开发的 前置条件 1 需要服务端是https 和域名 Ip 是不可以的 2 需要申请appid 小程序的官方流程图 个人理解 对于上面的流程图 步骤一 客户端 小程序调用wx.login 方法 获取用户的code 这个code 是限时的五分钟就会过期 拿到code 就可以向服务端发起登录请求了 …

vue3中ref的作用及ref和reactive之间的转化

ref的作用&#xff1a; &#xff08;1&#xff09;第一个作用&#xff1a;和vue一样&#xff1a; 绑在dom节点上拿到的是dom节点&#xff1b;绑在组件上拿到的是组件对象&#xff1b; 定义方式&#xff1a; <template><div><input type"text" ref&…

java运行数据区域分布

Java在运行程序过程中&#xff0c;会将自己的内存划分为若干个不同的数据数据区域&#xff0c;这些若干个区域&#xff0c;每个区域都有自己的用途&#xff0c;具体看下图 java是面向对象的语言&#xff0c;那么虚拟机中的数据&#xff08;对象&#xff09;是怎么被创建出来的呢…

加密 笔记

文章目录简单异或加密对称加密DES加密AES加密1.简单的加密解密逻辑2.填充方式**noPadding**3.加密模式1、**ECB模式&#xff08;默认&#xff09;**2、**CBC模式**3、CFB模式4、OFB模式5、CTR模式代码案例ECB加密和CBC加密测试非对称加密RSA加密AES和RSA混合加密哈希散列算法什…

[附源码]Python计算机毕业设计动物保护资讯推荐网站Django(程序+LW)

该项目含有源码、文档、程序、数据库、配套开发软件、软件安装教程 项目运行 环境配置&#xff1a; Pychram社区版 python3.7.7 Mysql5.7 HBuilderXlist pipNavicat11Djangonodejs。 项目技术&#xff1a; django python Vue 等等组成&#xff0c;B/S模式 pychram管理等…

RabbitMQ[3]-RabbitMQ如何保证消息的可靠性投递与消费?

上篇文章&#xff1a;RabbitMQ的核心概念有哪些&#xff1f;它们的职责是什么&#xff1f;中我们详细介绍了RabbitMQ的工作模式&#xff0c;根据它的工作模式&#xff0c;一条消息从生产者发出&#xff0c;到消费者消费&#xff0c;需要经历以下4个步骤&#xff1a; 生产者将消…

Java笔记——String类各种方法的使用总结(附带实例)

String类的获取方法 String类实现获取功能的方法有 int length() —— 获取字符串长度 char charAt(int index) —— 获取指定索引处的字符值 int indexOf(int ch) —— 获取指定字符第一次出现的索引位置 int indexOf(String str) —— 获取指定字符串第一次出现的索引位…

ArrayDeque源码解析

ArrayDeque源码解析 问题 &#xff08;1&#xff09;什么是双端队列&#xff1f; &#xff08;2&#xff09;ArrayDeque 是怎么实现双端队列的&#xff1f; &#xff08;3&#xff09;ArrayDeque 是线程安全的吗&#xff1f; &#xff08;4&#xff09;ArrayDeque 是有界的…