使用easyexcel将csv转为excel

ops/2024/9/20 1:22:33/ 标签: excel, easyexcel, excel转csv, java

一.背景

        供应商系统下载的csv文件不支持域控(主要是第三方wps服务不能对csv文件加密,但是可以对office系列产品进行权限访问的加密控制)。因此思路就改为现将csv文件转为excel文件,然后对excel文件进行加域控制。本文主要介绍如何将csv文件转为excel文件。

二.要求

  1.         Csv文件可能比较大,达到40-60M,需要控制内存使用率;
    1.         考虑接口的并发,需要进行接口的限流
  2. 三.方案

    1.         采用alibaba的easyexcel,降低内存占用率,根据压测结果,设置合理的接口限流参数(限流
    2. 本文不再介绍,可以使用java注解+redis+lua, 或者nginx限流等)
    3. 四.代码

    4. CsvController

  3. java">package com.xxx.xxx.controller;import java.io.IOException;
    import java.io.InputStream;
    import java.io.OutputStream;
    import java.util.concurrent.ExecutionException;
    import java.util.concurrent.Future;import javax.annotation.Resource;
    import javax.servlet.http.HttpServletResponse;import com.xxx.xxx.common.utils.EasyExcelUtil;
    import com.xxx.xxx.common.utils.ObjectUtil;
    import com.xxx.xxx.service.ExcelAnalysisService;import lombok.extern.slf4j.Slf4j;
    import org.springframework.web.bind.annotation.PostMapping;
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.bind.annotation.RequestParam;
    import org.springframework.web.bind.annotation.RestController;
    import org.springframework.web.multipart.MultipartFile;/*** description:** @author: lgq* @create: 2024-04-16 11:06*/
    @Slf4j
    @RestController
    @RequestMapping("/csv")
    public class CsvController {@Resourceprivate ExcelAnalysisService excelAnalysisService;/*** 读取传入的csv  文本的内容可以存入数据库** @param file* @return*/@PostMapping("/uploadCsvAndImportExcel")public void uploadCsvAndImportExcel(@RequestParam("file") MultipartFile file, HttpServletResponse response) {String[] splitName = file.getOriginalFilename().split(".csv");if (ObjectUtil.isEmpty(splitName) || ObjectUtil.isEmpty(splitName[0])) {return;}EasyExcelUtil.setResponseParam(response, splitName[0]);long startTime = System.currentTimeMillis();log.info("导出开始时间:{}", startTime);try {// 输出流可以为本地文件
    //          OutputStream outputStream = new FileOutputStream("D:\\templateExcel\\filename.xlsx");OutputStream outputStream = response.getOutputStream();InputStream inputStream = file.getInputStream();Future<String> future = excelAnalysisService.csv2Excel(inputStream, outputStream);future.get();} catch (IOException ioException) {log.error("csv转为excel出错!", ioException.getMessage());ioException.printStackTrace();} catch (InterruptedException interruptedException) {log.error("csv转为excel出错!", interruptedException.getMessage());interruptedException.printStackTrace();} catch (ExecutionException executionException) {log.error("csv转为excel出错!", executionException.getMessage());executionException.printStackTrace();}// 导出时间结束long endTime = System.currentTimeMillis();log.info("导出结束时间:{}", endTime + "ms");log.info("导出所用时间:{}", (endTime - startTime) / 1000 + "秒");}}
    

    EasyExcelGeneralCsvListener 

  4. java">package com.xxx.xxx.listener;import java.util.ArrayList;
    import java.util.Collections;
    import java.util.List;
    import java.util.Map;import com.alibaba.excel.ExcelWriter;
    import com.alibaba.excel.context.AnalysisContext;
    import com.alibaba.excel.event.AnalysisEventListener;
    import com.alibaba.excel.write.metadata.WriteSheet;
    import com.xxx.xxx.constants.ExcelConstants;/*** description:** @author: lgq* @create: 2024-04-16 11:25*/
    public class EasyExcelGeneralCsvListener extends AnalysisEventListener<Map<Integer, String>>  {/*** 用于存储读取的数据*/private List<Map<Integer, String>> dataList = new ArrayList<>();private ExcelWriter excelWriter;private WriteSheet writeSheet;public EasyExcelGeneralCsvListener() {}public EasyExcelGeneralCsvListener(ExcelWriter excelWriter, WriteSheet writeSheet) {this.excelWriter = excelWriter;this.writeSheet = writeSheet;}@Overridepublic void invoke(Map<Integer, String> data, AnalysisContext context) {// 数据add进入集合dataList.add(data);// size是否为2000条:这里其实就是分批.当数据等于2k的时候执行一次写入excelif (dataList.size() >= ExcelConstants.PER_WRITE_EXCEL_ROW_COUNT) {save2Excel();// 清理集合便于GC回收dataList.clear();}}@Overridepublic void invokeHeadMap(Map<Integer, String> headers, AnalysisContext context) {List<List<String>> titles = new ArrayList<>();for (int i = 0; i < headers.size(); i++) {titles.add(Collections.singletonList(headers.get(i)));}this.writeSheet.setHead(titles);}/*** 保存数据到 excel*/private void save2Excel() {if (dataList.size() > 0) {List<List<String>> consumerDataList = new ArrayList<>();dataList.stream().forEach( e ->{List<String> objects = new ArrayList<>();for (int i = 0; i < e.size(); i++) {objects.add(e.get(i));}consumerDataList.add(objects);});this.excelWriter.write(consumerDataList, writeSheet);}}/*** Excel 中所有数据解析完毕会调用此方法*/@Overridepublic void doAfterAllAnalysed(AnalysisContext context) {save2Excel();dataList.clear();}}
    

    VisiableThreadPoolTaskExecutor

  5. java">package com.xxx.xxx.task;import java.util.concurrent.Callable;
    import java.util.concurrent.Future;
    import java.util.concurrent.ThreadPoolExecutor;import lombok.extern.slf4j.Slf4j;
    import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
    import org.springframework.util.concurrent.ListenableFuture;/*** description:VisiableThreadPoolTaskExecutor** @author: lgq* @create: 2024-04-17 10:52*/
    @Slf4j
    public class VisiableThreadPoolTaskExecutor extends ThreadPoolTaskExecutor {private void showThreadPoolInfo(String prefix){ThreadPoolExecutor threadPoolExecutor = getThreadPoolExecutor();if(null==threadPoolExecutor){return;}log.info("{}, {},taskCount [{}], completedTaskCount [{}], activeCount [{}], queueSize [{}]",this.getThreadNamePrefix(),prefix,threadPoolExecutor.getTaskCount(),threadPoolExecutor.getCompletedTaskCount(),threadPoolExecutor.getActiveCount(),threadPoolExecutor.getQueue().size());}@Overridepublic void execute(Runnable task) {showThreadPoolInfo("1. do execute");super.execute(task);}@Overridepublic void execute(Runnable task, long startTimeout) {showThreadPoolInfo("2. do execute");super.execute(task, startTimeout);}@Overridepublic Future<?> submit(Runnable task) {showThreadPoolInfo("1. do submit");return super.submit(task);}@Overridepublic <T> Future<T> submit(Callable<T> task) {showThreadPoolInfo("2. do submit");return super.submit(task);}@Overridepublic ListenableFuture<?> submitListenable(Runnable task) {showThreadPoolInfo("1. do submitListenable");return super.submitListenable(task);}@Overridepublic <T> ListenableFuture<T> submitListenable(Callable<T> task) {showThreadPoolInfo("2. do submitListenable");return super.submitListenable(task);}
    }
    ExcelAnalysisService
  6. java">package com.xxx.xxx.service;import java.io.OutputStream;
    import java.io.InputStream;
    import java.util.concurrent.Future;/*** description:excel文档分析处理类** @author: lgq* @create: 2024-04-17 11:42*/
    public interface ExcelAnalysisService {/*** csv文档转为excel文档*/Future<String> csv2Excel(InputStream inputStream, OutputStream outputStream);
    }
    

    ExcelAnalysisServiceImpl

  7. java">package com.xxx.xxx.service.impl;import java.io.OutputStream;
    import java.nio.charset.Charset;import com.alibaba.excel.EasyExcel;
    import com.alibaba.excel.ExcelWriter;
    import com.alibaba.excel.support.ExcelTypeEnum;
    import com.alibaba.excel.write.metadata.WriteSheet;
    import com.xxx.xxx.listener.EasyExcelGeneralCsvListener;
    import com.xxx.xxx.service.ExcelAnalysisService;import lombok.extern.slf4j.Slf4j;
    import java.io.InputStream;
    import java.util.concurrent.Future;import org.springframework.scheduling.annotation.Async;
    import org.springframework.scheduling.annotation.AsyncResult;
    import org.springframework.stereotype.Service;/*** description:ExcelAnalysisService实现类** @author: lgq* @create: 2024-04-17 14:53*/
    @Service
    @Slf4j
    public class ExcelAnalysisServiceImpl implements ExcelAnalysisService {@Async("asyncExcelAnalysisServiceExecutor")@Overridepublic Future<String> csv2Excel(InputStream inputStream, OutputStream outputStream) {try {ExcelWriter writer = EasyExcel.write(outputStream).excelType(ExcelTypeEnum.XLSX).build();EasyExcel.read(inputStream, new EasyExcelGeneralCsvListener(writer, new WriteSheet())).excelType(ExcelTypeEnum.CSV).charset(Charset.forName("UTF-8")).sheet().doRead();writer.finish();outputStream.flush();} catch (Exception e) {log.error("csv转为excel出错!", e.getMessage());e.printStackTrace();} finally {if (outputStream != null) {try {outputStream.close();} catch (Exception e) {log.error("outputStream.close() -> csv转为excel出错!", e.getMessage());e.printStackTrace();}}if (inputStream != null) {try {inputStream.close();} catch (Exception e) {log.error("inputStream.close() -> csv转为excel出错!", e.getMessage());e.printStackTrace();}}}return new AsyncResult<>("task complete!");}
    }
    

    ExecutorConfig

  8. java">package com.xxx.xxx.config;import java.util.concurrent.Executor;
    import java.util.concurrent.ThreadPoolExecutor;import com.xxx.xxx.task.VisiableThreadPoolTaskExecutor;import lombok.extern.slf4j.Slf4j;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.scheduling.annotation.EnableAsync;
    import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;/*** description:线程池配置类** @author: lgq* @create: 2024-04-17 10:28*/
    @Configuration
    @Slf4j
    @EnableAsync
    public class ExecutorConfig {private static int corePoolSize = Runtime.getRuntime().availableProcessors() + 1;private static int maxPoolSize = Runtime.getRuntime().availableProcessors() + 1;private static int queueCapacity = 100;private static final String namePrefix = "ExcelAnalysis";@Bean(name = "asyncExcelAnalysisServiceExecutor")public Executor asyncExcelServiceExecutor() {log.info("start asyncExcelAnalysisServiceExecutor----------------");//ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();//使用可视化运行状态的线程池ThreadPoolTaskExecutor executor = new VisiableThreadPoolTaskExecutor();//配置核心线程数executor.setCorePoolSize(corePoolSize);//配置最大线程数executor.setMaxPoolSize(maxPoolSize);//配置队列大小executor.setQueueCapacity(queueCapacity);//配置线程池中的线程的名称前缀executor.setThreadNamePrefix(namePrefix);// rejection-policy:当pool已经达到max size的时候,如何处理新任务// CALLER_RUNS:不在新线程中执行任务,而是有调用者所在的线程来执行executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());//执行初始化executor.initialize();log.info("end asyncExcelAnalysisServiceExecutor------------");return executor;}}
    

    ExcelConstants

  9. java">package com.xxx.xxx.constants;/*** description:线程池配置类** @author: lgq* @create: 2024-04-17 10:28*/
    public class ExcelConstants {public static final Integer PER_SHEET_ROW_COUNT = 100*10000;public static final Integer PER_WRITE_ROW_COUNT = 20*10000;public static final Integer PER_WRITE_EXCEL_ROW_COUNT = 2 * 1000;public static final Integer GENERAL_ONCE_SAVE_TO_DB_ROWS_JDBC = 10*10000;public static final Integer GENERAL_ONCE_SAVE_TO_DB_ROWS_MYBATIS = 5*10000;
    }
    

    配置文件

  10. java">spring:servlet:multipart:enabled: truemax-file-size: 100MB # 单个文件的最大值max-request-size: 100MB # 上传文件总的最大值

    pom依赖

  11. java">        <dependency><groupId>com.alibaba</groupId><artifactId>easyexcel</artifactId><version>3.3.2</version></dependency>
  12. 五.压测

  13. jvm参数(本地电脑,性能较差)
  14. -Xms2g -Xmx2g
  15. 导出日志

性能监控

压测结果


http://www.ppmy.cn/ops/7789.html

相关文章

数据结构--双向链表

在讲双向链表之前&#xff0c;我们先了解一下链表的分类&#xff1a; 链表的结构⾮常多样&#xff0c;主要分为带头与不带头、单向与双向、循环与不循环。三个种类可以任意搭配&#xff0c;所以总共可以形成八种链表&#xff0c;但是最常用的是单向不带头不循环链表和双向带头循…

html、css、QQ音乐移动端静态页面,资源免费分享,可作为参考,提供InsCode在线运行演示

CSDN将我上传的免费资源私自变成VIP专享资源&#xff0c;且作为作者的我不可修改为免费资源&#xff0c;不可删除&#xff0c;寻找客服无果&#xff0c;很愤怒&#xff0c;&#xff08;我发布免费资源就是希望大家能免费一起用、一起学习&#xff09;&#xff0c;接下来继续寻找…

代码托管基础操作

在待上传代码文件夹中右键&#xff0c;打开Git Bash Here依次输入以下命令&#xff1a; git init(在本地初始化一个代码仓库&#xff0c;具体表现为会在你的文件夹里出现一个隐藏的.git文件夹) git add .&#xff08;先把代码放到本地的一个缓冲区&#xff09;添加当前目录下的…

命理八字之答案之书前端uniapp效果实现

#uniapp# #答案之书# 不讲废话&#xff0c;先上截图 <div class"padding"><div class"flex align-center justify-center" style"padding-top:100px;"><div class"radarContainer"><div id"radarBox"…

初识ansible变量及实例配置

目录 1、为什么要使用变量 2、变量分类 3、 变量详解 3.1 vars,vars_files , group_vars 3.1 .1 vars 剧本中定义变量 3.1.2 vars_file 将变量存放到一个文件中&#xff0c;并在剧本中引用 3.1.3 group_vars 创建一个变量文件给某个组使用 实例1-根据不同的主机…

[ LeetCode ] 题刷刷(Python)-第35题:搜索插入位置

题目描述 给定一个排序数组和一个目标值&#xff0c;在数组中找到目标值&#xff0c;并返回其索引。如果目标值不存在于数组中&#xff0c;返回它将会被按顺序插入的位置。 nums 为 无重复元素 的 升序 排列数组 请必须使用时间复杂度为 O(log n) 的算法。 示例 示例 1: 输入: …

Django老项目升级到新版本

手上有个 Django 老项目&#xff0c;一直跑得好好的&#xff0c;好几年没动过了&#xff0c;维护费收得正爽&#xff0c;没想到客户来了个新的运营人员&#xff0c;丢了个改动需求过来。我一看也没啥大改&#xff0c;就答应了。大意了。 问题 刚开始改&#xff0c;我这种老鸟…

MongoDB聚合运算符:$sampleRate

MongoDB聚合运算符&#xff1a;$sampleRate 文章目录 MongoDB聚合运算符&#xff1a;$sampleRate语法使用举例 $sampleRate聚合运算符用$match&#xff0c;按照指定的抽样比例&#xff0c;从输入的文档中随机选择相应的文档。 语法 { $sampleRate: <non-negative float>…

使用Spring Boot整合定时任务(Schedule)

1、添加依赖&#xff1a; 在pom.xml文件中添加Spring Boot的定时任务依赖 <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter</artifactId> </dependency> 2、创建定时任务类&#xff1a; 创建…

搜索+剪枝,LeetCode 216. 组合总和 III

目录 一、题目 1、题目描述 2、接口描述 python3 cpp 3、原题链接 二、解题报告 1、思路分析 2、复杂度 3、代码详解 python3 cpp 一、题目 1、题目描述 找出所有相加之和为 n 的 k 个数的组合&#xff0c;且满足下列条件&#xff1a; 只使用数字1到9每个数字 最多…

Linux下跟踪某个进程的内核处理时延消耗情况

1.利用系统自动的trace功能&#xff0c;编辑如下脚本&#xff0c;vim trace_process.sh #!/bin/sh cd /sys/kernel/debug/tracing/ #清空原有跟踪信息 echo > trace echo nop > current_tracer #设置要跟踪的进程 echo "pid281255" echo 281255 > set_ftra…

【项目】仿muduo库One Thread One Loop式主从Reactor模型实现高并发服务器(TcpServer板块)

【项目】仿muduo库One Thread One Loop式主从Reactor模型实现⾼并发服务器&#xff08;TcpServer板块&#xff09; 一、思路图二、模式关系图三、定时器的设计1、Linux本身给我们的定时器2、我们自己实现的定时器&#xff08;1&#xff09;代码部分&#xff08;2&#xff09;思…

ASP.Net MVC 登录页面实现RSA非对称加密

一、什么是RSA非对称加密 RSA是1977年由罗纳德李维斯特&#xff08;Ron Rivest&#xff09;、阿迪萨莫尔&#xff08;Adi Shamir&#xff09;和伦纳德阿德曼&#xff08;Leonard Adleman&#xff09;一起提出的。 RSA算法是一种非对称加密算法&#xff0c;与对称加密算法不同…

【CSS】CSS实现元素逐渐消失(实现元素透明逐渐消失/模糊)

mask-image: linear-gradient(to bottom, rgba(0, 0, 0, 0) 0%, rgba(0, 0, 0, 1) 10%);mask-image 属性用于定义一个遮罩&#xff0c;它可以隐藏元素的一部分或全部内容。在这个示例中&#xff0c;我们使用 mask-image 属性来定义一个线性渐变的遮罩&#xff0c;使得列表项的内…

达梦数据库执行sql报错:数据溢出

数据库执行sql报错数据溢出 单独查询对应的数字进行计算是不是超过了某个字段类型的上限或下限 如果已经超过了&#xff0c;进行对字段进行cast类型转换处理&#xff0c;转换为dec num都可以尝试 这里就是从 max(T.BLOCK_ID as dec*8192t.bytes)/1024/1024 max_MB,换成了这个…

rust 卸载重新安装 安装

原因&#xff1a;接触区块链时报错 linking with x86_64-w64-mingw32-gcc failed: exit code: 1 Rust编译需要C环境&#xff0c;如果你没有&#xff0c;Rust也能安装成功&#xff0c;只是无法编译代码 C的编译工具有两个&#xff0c;一个是msvc&#xff0c;也就是visual studi…

汽车视频智能剪辑解决方案,满足用户对高品质汽车视频的追求

随着汽车智能化和互联网技术的快速发展&#xff0c;车载视频已经成为现代驾驶生活不可或缺的一部分。然而面对海量的行车视频&#xff0c;如何高效地剪辑、整理并分享这些精彩瞬间&#xff0c;一直是车主和汽车内容创作者们所面临的难题。美摄科技&#xff0c;作为领先的视频智…

k8s调度场景

15个KUBERNETES调度情景实用指南 Kubernetes调度是确保集群中的Pod在适当节点上运行的关键组件。通过灵活配置调度策略&#xff0c;可以提高资源利用率、负载平衡和高可用性。 在本文中&#xff0c;我们将深入探讨一些实际的Kubernetes调度场景&#xff0c;并提供相应的配置示…

【AI自媒体制作】【AI工具】Midjourney中文站

Midjourney Midjourney中文站, MJ中文站 - 专业AI绘图网站 广场 绘画广场&#xff1a; 包含大量其他用户生成好的图片&#xff0c;可以自由保存。 视频广场&#xff1a; 普通用户目前只支持查看&#xff0c;无法下载 画夹广场&#xff1a; 有很多免费的画夹&#xff0c;比…

华为机考入门python3--(16)牛客16-购物单最大满意度

分类&#xff1a;动态规划&#xff0c;组合&#xff0c;最大值&#xff0c;装箱问题 知识点&#xff1a; 生成递减数 100, 90, 80, ..., 0 range(100, -1, -10) 访问列表的下标key for key, value in enumerate(my_list): 动态规划-捆绑装箱问题 a. 把有捆绑约束的物…