报表下载工具
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 | 数组排序算法
一、冒泡排序 冒泡排序的基本思想是对比相邻的元素值,如果满足条件就交换元素值,把较小的元素移到数组前面,把较大的元素移到数组后面(也就是交换两个元素的位置),这样较小的元素就像气泡一样从底部升到顶…...
AI Agent在智能风控中的实战:多智能体欺诈检测与预警
AI Agent在智能风控中的实战:多智能体欺诈检测与预警 你有没有过明明是正常交易却被银行冻结账户的糟糕体验?或是听说过某电商平台上线新活动首日就被黑产团伙薅走数千万补贴的新闻?随着黑产欺诈向团伙化、专业化、动态化演进,传统依赖规则引擎、单模型机器学习的风控体系已…...
AX-MES生产制造管理系统-总览
前言说起 MES 就不得不说 ERP,但是 ERP 大家基本上都知道,MES 就不一定了,常见的 ERP 系统包括 SAP、金蝶、用友等,ERP的流程相对来说也比较统一;MES就不同了,基本上熟悉业务流程的软件公司都可以开发并实施…...
Hirschmann RS20-0800M4M4SDAE工业以太网交换机
Hirschmann RS20-0800M4M4SDAE 工业以太网交换机产品特点:端口配置:共8个端口,含6个RJ45电口和2个ST光纤接口。端口速率:所有端口均为100Mbps快速以太网。光纤类型:2个光纤端口为多模、ST接头。管理类型:二…...
华硕笔记本终极性能控制指南:用G-Helper完全替代Armoury Crate
华硕笔记本终极性能控制指南:用G-Helper完全替代Armoury Crate 【免费下载链接】g-helper Lightweight Armoury Crate alternative for Asus laptops with nearly the same functionality. Works with ROG Zephyrus, Flow, TUF, Strix, Scar, ProArt, Vivobook, Zen…...
关联规则挖掘在Calabi-Yau流形Hodge数分析中的应用与复现
1. 项目概述:当数据挖掘遇见高维几何在理论物理和代数几何的交叉领域,Calabi-Yau流形一直扮演着核心角色。这些具有特殊拓扑结构的空间,不仅是弦理论中额外维度紧化的关键候选者,其本身丰富的数学性质也吸引着无数研究者。然而&am…...
树莓派工业GPIO接口板:电气隔离与电平转换实战指南
1. 项目概述:为什么需要一块工业级GPIO接口板?如果你用树莓派做过一些硬件项目,尤其是涉及到控制继电器、电机或者连接工业设备(比如PLC、变频器)时,大概率踩过这样的坑:直接用树莓派的GPIO引脚…...
基于MAX78000与CNN的智能螺栓巡检小车:嵌入式AI实战解析
1. 项目概述与核心思路在轨道交通的日常运维中,螺栓的紧固状态检查是一项繁重且关键的任务。无论是轨道上的紧固螺栓,还是列车转向架、轮对轴承上的关键螺栓,其松动或失效都可能引发严重的安全事故。传统的人工巡检方式不仅效率低下ÿ…...
Git Bash 中无法启动 Claude Code ?
最近需要在 git bash 中跑 Claude Code 。git bash 是随 git for windows 套件安装的,很久没更新了,结果启动 Claude Code 报错:Warning: no stdin data received in 3s, proceeding without it. If piping from a slow command, redirect st…...
构建智能音乐档案:SoundCloud Downloader 的技术架构与实现哲学
构建智能音乐档案:SoundCloud Downloader 的技术架构与实现哲学 【免费下载链接】scdl Soundcloud Music Downloader 项目地址: https://gitcode.com/gh_mirrors/sc/scdl 在流媒体音乐主导的时代,音乐爱好者面临着一种矛盾:我们享受着…...
保姆级教程:手把手教你为ESXi 6.7配置主板BIOS(VT-x/VT-d/AES全开)
从零开始:ESXi 6.7主板BIOS设置完全指南当你第一次接触企业级虚拟化平台时,那种既兴奋又忐忑的心情我完全理解。作为过来人,我清楚地记得自己第一次为ESXi配置BIOS时的迷茫——那些专业术语像天书一样,生怕设置错误导致服务器无法…...
