报表下载工具
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地址, 现在需要按照企业,项目和报表类型分类下载到对应的文件夹中 2.相关实体类 企业文件夹定义 package com.vz.utils.report;import lombok.Data; import java.util.ArrayList; import java.util.List; import java.uti…...

树及其遍历
文章目录 树树定义专业术语树分类 二叉树分类存储连续存储(完全二叉树)链式存储一般树的存储森林的存储 线索二叉树哈夫曼树构造步骤 遍历先序遍历中序遍历后续遍历 链式二叉树遍历具体代码已知两种遍历序列求原始二叉树已知先序和中序求后序已知中序和后…...
Qt报错解决办法
anaconda环境安装qt报错解决办法 报错: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(四十七)列表对象的创建
❤️ 专栏简介:本专栏记录了我个人从零开始学习Python编程的过程。在这个专栏中,我将分享我在学习Python的过程中的学习笔记、学习路线以及各个知识点。 ☀️ 专栏适用人群 :本专栏适用于希望学习Python编程的初学者和有一定编程基础的人。无…...

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

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

UE5初学者快速入门教程
虚幻引擎是一系列游戏开发工具,能够将 2D 手机游戏制作为 AAA 游戏机游戏。虚幻引擎 5 用于开发下一代游戏,包括Senuas Saga: Hellblade 2、Redfall(来自 Arkane Austin 的合作射击游戏)、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. 文章简介 标题:FEDERATE…...

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

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

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

FreeRTOS之二值信号量
什么是信号量? 信号量(Semaphore),是在多任务环境下使用的一种机制,是可以用来保证两个或多个关键代 码段不被并发调用。 信号量这个名字,我们可以把它拆分来看,信号可以起到通知信号的作用&am…...
ChatGPT API进阶调用指南
原文:ChatGPT API进阶调用指南 ChatGPT API 进阶调用指南 ChatGPT API 是基于 OpenAI 的 GPT模型的一个强大工具,可以用于构建各种对话式应用。以下是一些使用 Markdown 语法的进阶调用指南,以帮助您更好地利用 ChatGPT API。 设置用户角色…...

人工智能术语翻译(四)
文章目录 摘要MNOP 摘要 人工智能术语翻译第四部分,包括I、J、K、L开头的词汇! 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单元测试框架,它提供了丰富的功能和灵活的扩展机制,可以帮助开发人员编写高质量的单元测试。通过JUnit,开发人员可以更加自信地进行重构、维护和改进代码,同时提高代…...

《面试1v1》如何能从Kafka得到准确的信息
🍅 作者简介:王哥,CSDN2022博客总榜Top100🏆、博客专家💪 🍅 技术交流:定期更新Java硬核干货,不定期送书活动 🍅 王哥多年工作总结:Java学习路线总结…...

2023秋招面试题持续更新中。。。
目录 1.八股文渐进式MVVM三次握手,四次挥手viteajax组件化和模块化虚拟dom原理流程浏览器内核浏览器渲染过程回流和重绘nextTick 2.项目相关1.声明式导航和编程式导航重写push和replace方法:性能优化图片懒加载路由懒加载 http请求方式 1.八股文 渐进式…...

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

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

简易版抽奖活动的设计技术方案
1.前言 本技术方案旨在设计一套完整且可靠的抽奖活动逻辑,确保抽奖活动能够公平、公正、公开地进行,同时满足高并发访问、数据安全存储与高效处理等需求,为用户提供流畅的抽奖体验,助力业务顺利开展。本方案将涵盖抽奖活动的整体架构设计、核心流程逻辑、关键功能实现以及…...

工业安全零事故的智能守护者:一体化AI智能安防平台
前言: 通过AI视觉技术,为船厂提供全面的安全监控解决方案,涵盖交通违规检测、起重机轨道安全、非法入侵检测、盗窃防范、安全规范执行监控等多个方面,能够实现对应负责人反馈机制,并最终实现数据的统计报表。提升船厂…...

Redis相关知识总结(缓存雪崩,缓存穿透,缓存击穿,Redis实现分布式锁,如何保持数据库和缓存一致)
文章目录 1.什么是Redis?2.为什么要使用redis作为mysql的缓存?3.什么是缓存雪崩、缓存穿透、缓存击穿?3.1缓存雪崩3.1.1 大量缓存同时过期3.1.2 Redis宕机 3.2 缓存击穿3.3 缓存穿透3.4 总结 4. 数据库和缓存如何保持一致性5. Redis实现分布式…...
是否存在路径(FIFOBB算法)
题目描述 一个具有 n 个顶点e条边的无向图,该图顶点的编号依次为0到n-1且不存在顶点与自身相连的边。请使用FIFOBB算法编写程序,确定是否存在从顶点 source到顶点 destination的路径。 输入 第一行两个整数,分别表示n 和 e 的值(1…...

C++使用 new 来创建动态数组
问题: 不能使用变量定义数组大小 原因: 这是因为数组在内存中是连续存储的,编译器需要在编译阶段就确定数组的大小,以便正确地分配内存空间。如果允许使用变量来定义数组的大小,那么编译器就无法在编译时确定数组的大…...

排序算法总结(C++)
目录 一、稳定性二、排序算法选择、冒泡、插入排序归并排序随机快速排序堆排序基数排序计数排序 三、总结 一、稳定性 排序算法的稳定性是指:同样大小的样本 **(同样大小的数据)**在排序之后不会改变原始的相对次序。 稳定性对基础类型对象…...
Oracle11g安装包
Oracle 11g安装包 适用于windows系统,64位 下载路径 oracle 11g 安装包...

Ubuntu系统多网卡多相机IP设置方法
目录 1、硬件情况 2、如何设置网卡和相机IP 2.1 万兆网卡连接交换机,交换机再连相机 2.1.1 网卡设置 2.1.2 相机设置 2.3 万兆网卡直连相机 1、硬件情况 2个网卡n个相机 电脑系统信息,系统版本:Ubuntu22.04.5 LTS;内核版本…...

《信号与系统》第 6 章 信号与系统的时域和频域特性
目录 6.0 引言 6.1 傅里叶变换的模和相位表示 6.2 线性时不变系统频率响应的模和相位表示 6.2.1 线性与非线性相位 6.2.2 群时延 6.2.3 对数模和相位图 6.3 理想频率选择性滤波器的时域特性 6.4 非理想滤波器的时域和频域特性讨论 6.5 一阶与二阶连续时间系统 6.5.1 …...