当前位置: 首页 > news >正文

报表下载工具

1.需求说明

我有一堆文件的Url地址, 现在需要按照企业,项目和报表类型分类下载到对应的文件夹中

2.相关实体类

  • 企业文件夹定义
package com.vz.utils.report;import lombok.Data;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;/*** @author visy.wang* @description: 企业文件夹* @date 2023/7/19 16:49*/
@Data
public class EnterpriseDto {/*** 企业名称(文件夹名)*/private String name;/*** 项目文件夹列表*/private List<ProjectDto> projectList;/*** 添加项目*/public void addProject(ProjectDto project){if(Objects.isNull(projectList)){projectList = new ArrayList<>();}projectList.add(project);}
}
  • 项目文件夹定义
package com.vz.utils.report;import lombok.Data;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;/*** @author visy.wang* @description: 项目文件夹* @date 2023/7/19 16:50*/
@Data
public class ProjectDto {/*** 项目名称(文件夹名)*/private String name;/*** 报表文件夹列表*/private List<ReportDto> reportList;/*** 添加报表快捷方式* @param reportType 数据类型* @param urlList 下载Url列表*/public void addReport(ReportTypeEnum reportType, List<String> urlList){if(Objects.isNull(reportList)){reportList = new ArrayList<>();}ReportDto reportDto = new ReportDto();reportDto.setReportType(reportType);reportDto.setUrlList(urlList);reportList.add(reportDto);}
}
  • 报表文件夹定义
package com.vz.utils.report;import lombok.Data;
import java.util.List;/*** @author visy.wang* @description: 报表文件夹* @date 2023/7/19 17:23*/
@Data
public class ReportDto {/*** 报表类型(文件夹名)*/private ReportTypeEnum reportType;/*** 文件下载地址列表*/private List<String> urlList;
}
  • 报表类型枚举
package com.vz.utils.report;import lombok.AllArgsConstructor;
import lombok.Getter;/*** @author visy.wang* @description: 报表类型枚举* @date 2023/7/19 16:57*/
@Getter
@AllArgsConstructor
public enum ReportTypeEnum {SETTLEMENT("结算单", "结算单"),BI("BI", "BI"),DATA_REPORT("数据报表", "数据报表"),CONTRACT("合同", "合同");//文件夹名private final String folder;//类型描述private final String desc;
}

3.核心下载类

package com.vz.utils.report;import cn.hutool.core.io.FileUtil;
import cn.hutool.core.net.URLDecoder;
import cn.hutool.http.HttpResponse;
import cn.hutool.http.HttpUtil;
import com.google.common.util.concurrent.ThreadFactoryBuilder;
import com.server.utils.mail.MailUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpStatus;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;import java.io.File;
import java.nio.charset.StandardCharsets;
import java.util.*;
import java.util.concurrent.*;/*** @author visy.wang* @description: 下载工具* @date 2023/7/19 16:47*/
@Slf4j
public class DownUtil {private static final Map<String,Boolean> folderCreateFlag = new HashMap<>();private static final String rootPath = "/usr/temp_dir/data_report"; //Linux文件存放根路径//private static final String rootPath = "E:\\test\\down"; //测试用,Windows系统// 创建线程池private static final ExecutorService pool;static {ThreadFactory namedThreadFactory = new ThreadFactoryBuilder().setNameFormat("report-down-pool-%d").build();pool = new ThreadPoolExecutor(20, 100, 5000L, TimeUnit.MILLISECONDS,new LinkedBlockingQueue<>(5000), namedThreadFactory, new ThreadPoolExecutor.AbortPolicy());}/*** 下载* @param enterpriseList 企业列表* @param name 下载类型 (根文件夹名)*/public static void download(List<EnterpriseDto> enterpriseList, String name){//创建基础文件夹String baseFolder = rootPath + File.separator + name;createFolder(baseFolder);if(CollectionUtils.isEmpty(enterpriseList)){log.info("企业列表为空,下载结束!!!");return;}int downSuccessCount = 0, downFailureCount = 0, downRepeatCount = 0;long startTime = System.currentTimeMillis();List<CompletableFuture<Boolean>> futureList = new ArrayList<>();//遍历企业并创建文件夹for(EnterpriseDto enterprise: enterpriseList) {//创建企业文件夹String enterpriseFolder = baseFolder + File.separator + enterprise.getName();createFolder(enterpriseFolder);//处理项目列表List<ProjectDto> projectList = enterprise.getProjectList();if(CollectionUtils.isEmpty(projectList)){log.info("项目列表为空,文件夹:{}", enterpriseFolder);continue;}//遍历项目并创建文件夹for (ProjectDto project : projectList) {//创建项目文件夹String projectFolder = enterpriseFolder + File.separator + project.getName();createFolder(projectFolder);//处理数据列表List<ReportDto> reportList = project.getReportList();if(CollectionUtils.isEmpty(reportList)){log.info("数据列表为空,文件夹:{}", projectFolder);continue;}//遍历数据并创建文件夹for (ReportDto report : reportList) {ReportTypeEnum reportType = report.getReportType();//创建报表文件夹String reportFolder = projectFolder + File.separator + reportType.getFolder();createFolder(reportFolder);List<String> urlList = report.getUrlList();if(CollectionUtils.isEmpty(urlList)){log.info("文件URL列表为空,文件夹:{}", reportFolder);continue;}for (String url : urlList) {//下载文件到当前文件夹if(!StringUtils.hasText(url)){log.info("{}下载失败,URL为空", reportType.getDesc());downFailureCount ++;continue;}log.info("正在下载{},URL:{}", reportType.getDesc(), url);try{String uri = URLDecoder.decode(url.trim(), StandardCharsets.UTF_8);//从Url提取文件名int index = uri.lastIndexOf("/");String fileName = index==-1 ? uri : uri.substring(index+1);//检查是否下载过String filePath = reportFolder + File.separator + fileName;if(new File(filePath).exists()){log.info("已下载过,无需再次下载,URL:{}", url);downRepeatCount ++;continue;}//下载并保存CompletableFuture<Boolean> future = CompletableFuture.supplyAsync(() -> {return downFile(reportType, url, filePath);}, pool);futureList.add(future);}catch (Exception e){downFailureCount ++;log.info("{}下载失败,error:{},URL:{}", reportType.getDesc(), url, e.getMessage(), e);}}}}}for (CompletableFuture<Boolean> future: futureList) {try {if(future.get()){downSuccessCount ++;}else{downFailureCount ++;}}catch (Exception e){downFailureCount ++;e.printStackTrace();}}long spendTime = System.currentTimeMillis() - startTime;log.info("【{}】全部下载完成,下载成功数:{},下载失败数:{},下载重复数:{},总耗时:{}ms",name, downSuccessCount, downFailureCount, downRepeatCount, spendTime);}/*** 下载文件并保存在服务器* @param reportType 报表类型* @param url 下载地址* @param filePath 本地保存路径(全路径,含文件名和后缀)* @return 是否成功*/private static boolean downFile(ReportTypeEnum reportType, String url, String filePath){try(HttpResponse response = HttpUtil.createGet(url).execute()){if(response.getStatus() == HttpStatus.OK.value()){//写入本地文件FileUtil.writeBytes(response.bodyBytes(), filePath);return true;}else{throw new RuntimeException("HttpStatus="+response.getStatus());}}catch (Exception e){log.info("{}下载失败,Error:{},URL:{}", reportType.getDesc(), e.getMessage(), url, e);return false;}}//创建文件夹(如果不存在的话)private static void createFolder(String folder){if(Boolean.TRUE.equals(folderCreateFlag.get(folder))){return;}File f = new File(folder);boolean exists = f.exists();if(!exists){exists = f.mkdirs();log.info("文件夹[{}]已创建!", folder);}if(exists){folderCreateFlag.put(folder, Boolean.TRUE);}}
}

4.测试

package com.vz.utils.report;import java.util.Arrays;
import java.util.Collections;
import java.util.List;/*** @author visy.wang* @description:* @date 2023/7/25 16:02*/
public class DownTest {public static void main(String[] args) {String url1 = "";String url2 = "";String url3 = "";String url4 = "";String url5 = "";String url6 = "";String url7 = "";String url8 = "";String url9 = "";String url10 = "";List<String> urlList1 = Arrays.asList(url1, url2, url3);List<String> urlList2 = Arrays.asList(url4, url5, url6);List<String> urlList3 = Arrays.asList(url7, url8, url9, url10);ProjectDto project1 = new ProjectDto();project1.setName("项目1");project1.addReport(ReportTypeEnum.SETTLEMENT, urlList1);project1.addReport(ReportTypeEnum.BI, urlList2);ProjectDto project2 = new ProjectDto();project2.setName("项目2");project2.addReport(ReportTypeEnum.DATA_REPORT, urlList3);EnterpriseDto enterprise = new EnterpriseDto();enterprise.setName("企业1");enterprise.addProject(project1);enterprise.addProject(project2);DownUtil.download(Collections.singletonList(enterprise), "第一批次");System.out.println("Finished");}
}

相关文章:

报表下载工具

1.需求说明 我有一堆文件的Url地址&#xff0c; 现在需要按照企业&#xff0c;项目和报表类型分类下载到对应的文件夹中 2.相关实体类 企业文件夹定义 package com.vz.utils.report;import lombok.Data; import java.util.ArrayList; import java.util.List; import java.uti…...

树及其遍历

文章目录 树树定义专业术语树分类 二叉树分类存储连续存储&#xff08;完全二叉树&#xff09;链式存储一般树的存储森林的存储 线索二叉树哈夫曼树构造步骤 遍历先序遍历中序遍历后续遍历 链式二叉树遍历具体代码已知两种遍历序列求原始二叉树已知先序和中序求后序已知中序和后…...

Qt报错解决办法

anaconda环境安装qt报错解决办法 报错&#xff1a;thresholdGap: 20 pointsShape: 164142 qt.qpa.plugin: Could not find the Qt platform plugin “wayland” in “/home/tianhailong/anaconda3/envs/edge_algorithm/lib/python3.8/site-packages/cv2/qt/plugins” This app…...

Python(四十七)列表对象的创建

❤️ 专栏简介&#xff1a;本专栏记录了我个人从零开始学习Python编程的过程。在这个专栏中&#xff0c;我将分享我在学习Python的过程中的学习笔记、学习路线以及各个知识点。 ☀️ 专栏适用人群 &#xff1a;本专栏适用于希望学习Python编程的初学者和有一定编程基础的人。无…...

#systemverilog# 说说Systemverilog中《automatic》那些事儿

前面我们学习了有关systemverilog语言中有关《static》的一些知识,同static 关系比较好的哥们,那就是 《automatic》。今天,我们了解认识一下。 在systemveriog中,存在三种并发执行语句,分别是fork..join,fork...join_any和fork..join_none,其中只有fork...join_none不…...

C/C++ 动态内存分配与它的指针变量

一、什么是内存的动态分配 全局变量分配在内存中的静态存储区。局部变量&#xff08;包括形参&#xff09;分配在内存中的动态存储区&#xff0c;这个存储区是一个称为栈的区域。除此之外&#xff0c;C语言还允许建立内存动态分配区域&#xff0c;以存放一些临时用的数据&…...

UE5初学者快速入门教程

虚幻引擎是一系列游戏开发工具&#xff0c;能够将 2D 手机游戏制作为 AAA 游戏机游戏。虚幻引擎 5 用于开发下一代游戏&#xff0c;包括Senuas Saga: Hellblade 2、Redfall&#xff08;来自 Arkane Austin 的合作射击游戏&#xff09;、Dragon Quest XII: The Flames of Fate、…...

论文笔记--FEDERATED LEARNING: STRATEGIES FOR IMPROVING COMMUNICATION EFFICIENCY

论文笔记--FEDERATED LEARNING: STRATEGIES FOR IMPROVING COMMUNICATION EFFICIENCY 1. 文章简介2. 文章概括3 文章重点技术3.1 联邦学习(federated learning, FL)3.2 Structured updates3.3 Sketched Update 4. 文章亮点5. 原文传送门 1. 文章简介 标题&#xff1a;FEDERATE…...

STM32MP157驱动开发——按键驱动(异步通知)

文章目录 “异步通知 ”机制&#xff1a;信号的宏定义&#xff1a;信号注册 APP执行过程驱动编程做的事应用编程做的事异步通知方式的按键驱动程序(stm32mp157)button_test.cgpio_key_drv.cMakefile修改设备树文件编译测试 “异步通知 ”机制&#xff1a; 信号的宏定义&#x…...

医疗器械维修工程师心得

彩虹医械维修技能班9月将开展本年第三期长期班&#xff0c;目前咨询人员也陆续多了起来&#xff0c;很多刚了解到医疗行业的&#xff0c;自身也没有多少相关的基础&#xff0c;在咨询时会问到没有基础能否学的会&#xff1f; 做了这行业的都知道&#xff0c;无论多么复杂的设备…...

Vue3 Radio单选切换展示不同内容

Vue3 Radio单选框切换展示不同内容 环境&#xff1a;vue3tsviteelement plus 技巧&#xff1a;v-if&#xff0c;v-show的使用 实现功能&#xff1a;点击单选框展示不同的输入框 效果实现前的代码&#xff1a; <template><div class"home"><el-row …...

FreeRTOS之二值信号量

什么是信号量&#xff1f; 信号量&#xff08;Semaphore&#xff09;&#xff0c;是在多任务环境下使用的一种机制&#xff0c;是可以用来保证两个或多个关键代 码段不被并发调用。 信号量这个名字&#xff0c;我们可以把它拆分来看&#xff0c;信号可以起到通知信号的作用&am…...

ChatGPT API进阶调用指南

原文&#xff1a;ChatGPT API进阶调用指南 ChatGPT API 进阶调用指南 ChatGPT API 是基于 OpenAI 的 GPT模型的一个强大工具&#xff0c;可以用于构建各种对话式应用。以下是一些使用 Markdown 语法的进阶调用指南&#xff0c;以帮助您更好地利用 ChatGPT API。 设置用户角色…...

人工智能术语翻译(四)

文章目录 摘要MNOP 摘要 人工智能术语翻译第四部分&#xff0c;包括I、J、K、L开头的词汇&#xff01; M 英文术语中文翻译常用缩写备注Machine Learning Model机器学习模型Machine Learning机器学习ML机器学习Machine Translation机器翻译MTMacro Average宏平均Macro-F1宏…...

kubernetes持久化存储卷

kubernetes持久化存储卷 kubernetes持久化存储卷一、存储卷介绍二、存储卷的分类三、存储卷的选择四、本地存储卷之emptyDir五、本地存储卷之 hostPath六、网络存储卷之nfs七、PV(持久存储卷)与PVC(持久存储卷声明)7.1 认识pv与pvc7.2 pv与pvc之间的关系7.3 实现nfs类型pv与pvc…...

【Rust笔记】意译解构 Object Safety for trait

意译解构Object Safety for trait 借助【虚表vtable】对被调用成员函数【运行时内存寻址】的作法允许系统编程语言Rust模仿出OOP高级计算机语言才具备的【专用多态Ad-hoc Polymorphism】特性。 计算机高级语言中的“多态”术语是一个泛指。它通常可被细化为 基于继承关系的“子…...

Spring Boot单元测试入门指南

Spring Boot单元测试入门指南 JUnit是一个成熟和广泛应用的Java单元测试框架&#xff0c;它提供了丰富的功能和灵活的扩展机制&#xff0c;可以帮助开发人员编写高质量的单元测试。通过JUnit&#xff0c;开发人员可以更加自信地进行重构、维护和改进代码&#xff0c;同时提高代…...

《面试1v1》如何能从Kafka得到准确的信息

&#x1f345; 作者简介&#xff1a;王哥&#xff0c;CSDN2022博客总榜Top100&#x1f3c6;、博客专家&#x1f4aa; &#x1f345; 技术交流&#xff1a;定期更新Java硬核干货&#xff0c;不定期送书活动 &#x1f345; 王哥多年工作总结&#xff1a;Java学习路线总结&#xf…...

2023秋招面试题持续更新中。。。

目录 1.八股文渐进式MVVM三次握手&#xff0c;四次挥手viteajax组件化和模块化虚拟dom原理流程浏览器内核浏览器渲染过程回流和重绘nextTick 2.项目相关1.声明式导航和编程式导航重写push和replace方法&#xff1a;性能优化图片懒加载路由懒加载 http请求方式 1.八股文 渐进式…...

Java | 数组排序算法

一、冒泡排序 冒泡排序的基本思想是对比相邻的元素值&#xff0c;如果满足条件就交换元素值&#xff0c;把较小的元素移到数组前面&#xff0c;把较大的元素移到数组后面&#xff08;也就是交换两个元素的位置&#xff09;&#xff0c;这样较小的元素就像气泡一样从底部升到顶…...

uniapp 对接腾讯云IM群组成员管理(增删改查)

UniApp 实战&#xff1a;腾讯云IM群组成员管理&#xff08;增删改查&#xff09; 一、前言 在社交类App开发中&#xff0c;群组成员管理是核心功能之一。本文将基于UniApp框架&#xff0c;结合腾讯云IM SDK&#xff0c;详细讲解如何实现群组成员的增删改查全流程。 权限校验…...

手游刚开服就被攻击怎么办?如何防御DDoS?

开服初期是手游最脆弱的阶段&#xff0c;极易成为DDoS攻击的目标。一旦遭遇攻击&#xff0c;可能导致服务器瘫痪、玩家流失&#xff0c;甚至造成巨大经济损失。本文为开发者提供一套简洁有效的应急与防御方案&#xff0c;帮助快速应对并构建长期防护体系。 一、遭遇攻击的紧急应…...

【单片机期末】单片机系统设计

主要内容&#xff1a;系统状态机&#xff0c;系统时基&#xff0c;系统需求分析&#xff0c;系统构建&#xff0c;系统状态流图 一、题目要求 二、绘制系统状态流图 题目&#xff1a;根据上述描述绘制系统状态流图&#xff0c;注明状态转移条件及方向。 三、利用定时器产生时…...

Spring Boot+Neo4j知识图谱实战:3步搭建智能关系网络!

一、引言 在数据驱动的背景下&#xff0c;知识图谱凭借其高效的信息组织能力&#xff0c;正逐步成为各行业应用的关键技术。本文聚焦 Spring Boot与Neo4j图数据库的技术结合&#xff0c;探讨知识图谱开发的实现细节&#xff0c;帮助读者掌握该技术栈在实际项目中的落地方法。 …...

IP如何挑?2025年海外专线IP如何购买?

你花了时间和预算买了IP&#xff0c;结果IP质量不佳&#xff0c;项目效率低下不说&#xff0c;还可能带来莫名的网络问题&#xff0c;是不是太闹心了&#xff1f;尤其是在面对海外专线IP时&#xff0c;到底怎么才能买到适合自己的呢&#xff1f;所以&#xff0c;挑IP绝对是个技…...

0x-3-Oracle 23 ai-sqlcl 25.1 集成安装-配置和优化

是不是受够了安装了oracle database之后sqlplus的简陋&#xff0c;无法删除无法上下翻页的苦恼。 可以安装readline和rlwrap插件的话&#xff0c;配置.bahs_profile后也能解决上下翻页这些&#xff0c;但是很多生产环境无法安装rpm包。 oracle提供了sqlcl免费许可&#xff0c…...

【51单片机】4. 模块化编程与LCD1602Debug

1. 什么是模块化编程 传统编程会将所有函数放在main.c中&#xff0c;如果使用的模块多&#xff0c;一个文件内会有很多代码&#xff0c;不利于组织和管理 模块化编程则是将各个模块的代码放在不同的.c文件里&#xff0c;在.h文件里提供外部可调用函数声明&#xff0c;其他.c文…...

用 FFmpeg 实现 RTMP 推流直播

RTMP&#xff08;Real-Time Messaging Protocol&#xff09; 是直播行业中常用的传输协议。 一般来说&#xff0c;直播服务商会给你&#xff1a; ✅ 一个 RTMP 推流地址&#xff08;你推视频上去&#xff09; ✅ 一个 HLS 或 FLV 拉流地址&#xff08;观众观看用&#xff09;…...

解决MybatisPlus使用Druid1.2.11连接池查询PG数据库报Merge sql error的一种办法

目录 前言 一、问题重现 1、环境说明 2、重现步骤 3、错误信息 二、关于LATERAL 1、Lateral作用场景 2、在四至场景中使用 三、问题解决之道 1、源码追踪 2、关闭sql合并 3、改写处理SQL 四、总结 前言 在博客&#xff1a;【写在创作纪念日】基于SpringBoot和PostG…...

XXE漏洞知识

目录 1.XXE简介与危害 XML概念 XML与HTML的区别 1.pom.xml 主要作用 2.web.xml 3.mybatis 2.XXE概念与危害 案例&#xff1a;文件读取&#xff08;需要Apache >5.4版本&#xff09; 案例&#xff1a;内网探测&#xff08;鸡肋&#xff09; 案例&#xff1a;执行命…...