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

前言

其实就是利用了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);
    }
    private String buildCsvFileBody(List<District> dataLists) {
        StringBuilder lineBuilder = new StringBuilder();
        for (District rowData : dataLists) {
            lineBuilder
                    .append(rowData.getCode()).append(MyCsvFileUtil.CSV_DELIMITER)
                    .append(rowData.getName()).append(MyCsvFileUtil.CSV_DELIMITER)
                    .append(rowData.getParentCode()).append(MyCsvFileUtil.CSV_DELIMITER)
                    .append(rowData.getFullName()).append(MyCsvFileUtil.CSV_DELIMITER)
                    .append(MyCsvFileUtil.CSV_TAIL);
        }
        return lineBuilder.toString();
    }

 

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

可以看到从数据量里面查出来这 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,然后考虑并行查询,并行拼接后,再按顺序插入。
有想法的还可以写个注解,标记相关表头别名,是否参与导出,然后再拼接的时候魔改一手,反射自动拿字段属性等等。

好了该篇就到这。 

当然可以!以下是一个简单的示例,展示了如何手写一个Spring Boot Starter: 首先,创建一个 Maven 项目,并添加以下依赖项: ```xml <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter</artifactId> <version>2.5.4</version> </dependency> </dependencies> ``` 接下来,创建一个自定义的自动配置类,用于配置你的 Starter: ```java @Configuration @EnableConfigurationProperties(MyStarterProperties.class) public class MyStarterAutoConfiguration { private final MyStarterProperties properties; public MyStarterAutoConfiguration(MyStarterProperties properties) { this.properties = properties; } // 在此处定义你的自动配置逻辑 @Bean public MyStarterService myStarterService() { return new MyStarterService(properties); } } ``` 然后,创建一个属性类,用于将外部配置绑定到属性上: ```java @ConfigurationProperties("my.starter") public class MyStarterProperties { private String message; // 提供 getter 和 setter } ``` 最后,创建一个自定义的服务类,该服务类将在你的 Starter 中使用: ```java public class MyStarterService { private final MyStarterProperties properties; public MyStarterService(MyStarterProperties properties) { this.properties = properties; } public void showMessage() { System.out.println(properties.getMessage()); } } ``` 现在,你的 Spring Boot Starter 已经准备就绪了!你可以将其打包并使用在其他 Spring Boot 项目中。在其他项目的 `pom.xml` 文件中,添加你的 Starter 依赖: ```xml <dependencies> <dependency> <groupId>com.example</groupId> <artifactId>my-starter</artifactId> <version>1.0.0</version> </dependency> </dependencies> ``` 然后,在你的应用程序中使用 `MyStarterService`: ```java @SpringBootApplication public class MyApplication implements CommandLineRunner { private final MyStarterService myStarterService; public MyApplication(MyStarterService myStarterService) { this.myStarterService = myStarterService; } public static void main(String[] args) { SpringApplication.run(MyApplication.class, args); } @Override public void run(String... args) throws Exception { myStarterService.showMessage(); } } ``` 这样,你就成功地创建了一个简单的 Spring Boot Starter!当其他项目引入你的 Starter 并运行时,将会输出预定义的消息。 当然,这只是一个简单的示例,真实的 Starter 可能包含更多的配置和功能。你可以根据自己的需求进行扩展和定制。希望对你有所帮助!
评论 20
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

小目标青年

对你有帮助的话,谢谢你的打赏。

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值