Springboot项目中进行优化Excel数据的导出

前提
因为Excel数据的导出之前使用HSSFWorkbook,HSSFWorkbook导出的数据有上限,最多导出65535条数据,现在因为项目数据量变大,需要导出的数据变多,现在每次点击导出都卡死掉,所以现在就对其进行优化。
优化方案
使用异步的方式+任务的形式进行数据的导出,当点击的时候导出按钮的时候,进行添加任务到数据库中,Excel数据的异步导出做成一个模块,方便复用,把异步任务做成模块的思路是,项目启动时开始线程进行任务的执行。
代码和解析
首先先创建一张任务数据库表
以下是我业务需求的数据库表,你们自行根据你们的需要进行创建
CREATE TABLE ops_task (
id bigint(20) NOT NULL AUTO_INCREMENT COMMENT ‘导出任务列表’,
source tinyint(2) NOT NULL DEFAULT 0 COMMENT ‘来源 0pc端 10渠道商端’,
account_id bigint(20) NOT NULL DEFAULT 0 COMMENT ‘账号id’,
account_type tinyint(2) NOT NULL DEFAULT 0 COMMENT ‘账号类型 0 admin 10部门账号 20主渠道 21子渠道’,
title varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT ‘’ COMMENT ‘标题’,
params varchar(1024) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT ‘’ COMMENT ‘导出参数’,
type smallint(4) NOT NULL DEFAULT 0 COMMENT ‘类型:10:投手账户数据列表 20 用户充值记录列表 21 vip充值记录列表’,
status tinyint(2) NOT NULL DEFAULT 0 COMMENT ‘状态:10:导出中,20:导出失败,30:已完成’,
url varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT ‘’ COMMENT ‘oss的下载链接’,
create_time datetime NOT NULL DEFAULT ‘2000-01-01 00:00:00’,
count int(11) NOT NULL DEFAULT 0 COMMENT ‘导出条数’,
is_del tinyint(1) NOT NULL DEFAULT 0,
update_time timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (id) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 316 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci ROW_FORMAT = Dynamic;
点击导出按钮生成任务的Controller具体实现
@RequestMapping(value=“/export-vip-excel”,method=RequestMethod.POST)
public void exportVipExcel(HttpServletResponse response,HttpServletRequest request)throws IOException{
try {
//获取当前的渠道商信息
Business business = (Business) WebUtils.getSessionAttribute(request, “BUSINESS”);
//把请求参数转化成Map
Map<String,String> map = MapUtils.paramsMap(request);
businessService.addMap(map,business);
//组成标题
String title = map.get(“startTime”) + “到” + map.get(“endTime”) + “VIP用户充值记录”;
//判断是否为主渠道商
if (business.getParentId() == 0) {
//进行添加任务
opsTaskService.add(Long.valueOf(business.getId()), OpsTask.SOURCE_TYPE.BUSINESS,
OpsTask.ACCOUNT_TYPE.MAIN_BUSINESS,JSONObject.toJSONString(map),
title,OpsTask.TYPE.VIP_RECHARGE_COLLECTION_REPORT);
}else {
opsTaskService.add(Long.valueOf(business.getId()),
//进行添加任务
OpsTask.SOURCE_TYPE.BUSINESS,OpsTask.ACCOUNT_TYPE.SON_BUSINESS,
JSONObject.toJSONString(map),title,OpsTask.TYPE.VIP_RECHARGE_COLLECTION_REPORT);
}
//页面跳转刷新
response.sendRedirect(“go/list-vip-rechargeLog”);
} catch (Exception e) {
e.printStackTrace();
ControllerHelper.sendErrorJson(response, “获取VIP用户充值记录列表失败,请稍后重试”, e);
}
}

异步导出Excel模块实现

  1. 首先先写一个类实现CommandLineRunner,这个类的作用时项目启动的时候就可以进行运行,然后在里面开启线程进行处理任务,目前只使用单个线程,new Thread()方法,需求量高了之后可以进行优化使用线程池。
    @Component
    public class AutoStart implements CommandLineRunner {

    Logger logger= LoggerFactory.getLogger(AutoStart.class);

    @Override
    public void run(String… args) throws Exception {
    logger.info(“===AutoStart开始=导出任务启动”);
    startExport();
    }

    /**

    • @Description: 导出任务
    • @Param: []
    • @return: void
    • @Author linchuanndong
    • @Date: 2024/3/1 10:39
      */
      private void startExport() {
      new Thread(new ExportProcessor()).start();
      }
      }
  2. 写一个线程类实现Runnable接口,实现思路:主要通过Reids存储当前执行任务来判断当前是否有任务在进行导出。
    public class ExportProcessor implements Runnable{

    private static final Logger logger = LoggerFactory.getLogger(ExportProcessor.class);
    OpsTaskService opsTaskService;

    public ExportProcessor() {
    opsTaskService = SpringContextUtils.getBean(OpsTaskService.class);
    }

    @Override
    public void run() {
    OpsTask opsTask = null;
    //要redis中获取值的key值
    String key = Key.REDIS_KEY.EXPORT_KEY;
    while (true) {
    try {
    //如果当前key不存在就进行初始化0和进行+1,存在就直接+1
    Long index = Redis.incr(key);
    //如果index获取后为>1所名有任务在进行
    if (index > 1L) {
    //进行设置过期时间
    expire(key, Key.EXPIRE_TIME.TEN_MIN);
    logger.info(" -----处理导出任务正在执行------index:{}“, index);
    //睡眠2分钟结束此次循环
    Thread.sleep(1000602);
    continue ;
    }
    //进行设置过期时间
    expire(key, Key.EXPIRE_TIME.TEN_MIN);
    //获取是否还要可以进行导出的 任务
    opsTask = opsTaskService.get();
    //为空情况
    if (null == opsTask) {
    //删除redis
    Redis.del(key);
    logger.info(“无导出任务…”);
    //睡眠3分钟结束此次循环
    Thread.sleep(1000603);
    continue;
    }
    //不为空进行数据的导出操作,传入opsTask对象,根据里面的类型进行判断要进行什么方式导出
    new ExportTask(opsTask).export();
    Redis.del(key);
    } catch (Exception e) {
    Redis.del(key);
    logger.error(“导出任务异常,taskId:”+(null == opsTask ? 0 : opsTask.getId())+”;msg:"+e.getMessage(), e);
    }
    }
    }

    public static void expire(String key, int time) {
    Long ttl = Redis.ttl(key);
    if (ttl == -1 || ttl > Long.valueOf(time)) {
    Redis.expire(key, time);
    }
    }
    }

  3. 使用阿里的Excel数据的导出逻辑
    private void writeVipUserRechargeCollectionExcel(Map<String, String> paramsMap, Long id, String fileName) {
    logger.info("线程开始导出VIP用户充值报表, " + fileName);
    try {
    long startTime = System.currentTimeMillis();
    String path = generateFileName(fileName);
    Integer count = extendDayReportService.exportVipUserRecharge(paramsMap, path, fileName);
    File file = new File(path);
    OssUtils.uploadFile(file, file.getName(), false);
    opsTaskService.modify(id, OpsTask.STATUS.OK, count, OssUtils.FILE_URL + fileName + Constant.FILE_PATH.EXCEL_SUFFIX_XLSX);
    boolean flag = file.delete();
    logger.info(“线程导出VIP用户充值报表结束-export time:[{}], flag:[{}]”, (System.currentTimeMillis() - startTime), flag);
    } catch (Exception e) {
    opsTaskService.modify(id, OpsTask.STATUS.FAIL, 0, “”);
    logger.error(“线程导出VIP用户充值报表异常,msg:” + e.getMessage(), e);
    }
    }
    public Integer exportVipUserRecharge(Map<String, String> paramsMap, String path, String fileName) {
    //使用阿里的Excel
    ExcelWriter writer = EasyExcelUtil.buildEasyExcel(path, VipUserRechargeReportVO.class);
    //设置文件名
    WriteSheet sheet = EasyExcel.writerSheet(fileName).build();
    //根据条件查询要导出的数据
    List list = rechargeLogService.query(Constant.EXTREMITY.BUSINESS,RechargeLog.RECHARGE_TYPE.VIP,paramsMap);
    //数据没空进行处理
    if (CollectionUtils.isEmpty(list)) {
    writer.write(new ArrayList<>(), sheet);
    writer.finish();
    return 0;
    }
    //把需要的数据进行导出
    List userRechargeReportVOList = new ArrayList<>();
    list.forEach(item -> userRechargeReportVOList.add(new VipUserRechargeReportVO(item)));
    writer.write(userRechargeReportVOList, sheet);
    writer.finish();
    //返回导出的数量进行返回
    return list.size();
    }
    public static ExcelWriter buildEasyExcel(String pathName, Class cls) {
    return EasyExcel
    .write(pathName, cls)
    .registerWriteHandler(setHorizontalCellStyleStrategy())
    .registerWriteHandler(new CustomCellWeightWeightConfig())
    .build();
    }

  • 3
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值