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

Java实现文件自动下载,XXL-Job定时任务中的HTTP文件下载最佳实践

目录

    • 1、前言
    • 2、业务背景
    • 3、核心实现
      • 1. XXL-Job任务入口
      • 2. URL生成与中文编码处理
      • 3. HTTP下载核心实现
    • 4、技术要点分析
      • 1. 中文URL编码处理
      • 2. 重试机制设计
      • 3. HTTP连接优化
      • 4. 异常处理与日志记录
    • 5、常见问题与解决方案
      • 1. 文件下载为空
      • 2. 连接超时
      • 3. 中文文件名乱码
    • 6、总结

## 更多学习内容:https://www.processon.com/mindmap/60504b5ff346fb348a93b4fa

1、前言

在企业级应用开发中,定时任务是一个常见的需求场景。特别是在气象、农业等数据密集型行业,需要定期从外部系统下载最新的数据文件。本文将基于一个真实的项目案例,详细介绍如何在XXL-Job框架中实现可靠的HTTP文件下载功能。

2、业务背景

在农业气象系统中,我们需要定期下载中长期天气预报文件,这些文件通常存储在内网文件服务器上,文件名包含中文字符和日期信息。系统需要具备以下特性:

  • 自动重试机制:如果当天文件不存在,自动尝试前几天的文件
  • 中文文件名支持:正确处理URL中的中文字符编码
  • 详细的日志记录:便于问题排查和监控
  • 异常处理:优雅处理各种网络异常情况

3、核心实现

1. XXL-Job任务入口

@XxlJob("downloadMediumAndLongTermForecast")
public ReturnT<String> downloadMediumAndLongTermForecast(String param) throws Exception {XxlJobLogger.log("获取最新的周预报文件");// 示例模板 URL(注意:文件名中有中文)String templateUrl = "http://172.18.152.109:10003/Files/Weather/SWP/Original/zqtqyb/{yyyy}/{MM}/{dd}/{yyyyMMdd}中期天气预报.docx";LocalDate currentDate = LocalDate.now();boolean success = false;final int MAX_RETRY_DAYS = 3;for (int i = 0; i <= MAX_RETRY_DAYS; i++) {String fullUrl = generateFullUrl(templateUrl, currentDate);try {XxlJobLogger.log("尝试下载:" + fullUrl);// 构建目标文件路径File targetDir = new File(dirInfo.getRoot() + "Product/");if (!targetDir.exists()) {targetDir.mkdirs();}File targetFile = new File(targetDir, "WeatherForecastNew.doc");// 下载并记录结果boolean result = downloadUsingHttpWithDebug(fullUrl, targetFile);if (result) {XxlJobLogger.log("✅ 成功下载文件:" + targetFile.getAbsolutePath());success = true;break;} else {XxlJobLogger.log("❌ 下载失败,尝试前一日...");currentDate = currentDate.minusDays(1);}} catch (Exception e) {XxlJobLogger.log("❌ 下载失败(" + e.getClass().getSimpleName() + ": " + e.getMessage() + "),尝试前一日...");currentDate = currentDate.minusDays(1);}}if (!success) {XxlJobLogger.log("❌ 在过去 " + MAX_RETRY_DAYS + " 天内均未找到有效的文件");return ReturnT.FAIL;}XxlJobLogger.log("获取最新的周预报文件完成");return ReturnT.SUCCESS;
}

2. URL生成与中文编码处理

处理中文文件名是这个实现的关键难点之一:

public static String generateFullUrl(String templateUrl, LocalDate date) {DateTimeFormatter pathFormatter = DateTimeFormatter.ofPattern("yyyy/MM/dd");DateTimeFormatter filePrefixFormatter = DateTimeFormatter.ofPattern("yyyyMMdd");String formattedDatePath = date.format(pathFormatter);String formattedDatePrefix = date.format(filePrefixFormatter);String url = templateUrl.replace("{yyyy}", String.valueOf(date.getYear())).replace("{MM}", String.format("%02d", date.getMonthValue())).replace("{dd}", String.format("%02d", date.getDayOfMonth())).replace("{yyyyMMdd}", formattedDatePrefix);// 对中文文件名进行URL编码try {// 只对文件名部分进行编码,保持路径部分不变int lastSlashIndex = url.lastIndexOf('/');if (lastSlashIndex != -1) {String pathPart = url.substring(0, lastSlashIndex + 1);String fileNamePart = url.substring(lastSlashIndex + 1);String encodedFileName = URLEncoder.encode(fileNamePart, "UTF-8").replace("+", "%20"); // 空格用%20而不是+url = pathPart + encodedFileName;}} catch (UnsupportedEncodingException e) {XxlJobLogger.log("URL编码失败: " + e.getMessage());}return url;
}

3. HTTP下载核心实现

private boolean downloadUsingHttpWithDebug(String fileUrl, File outputFile) {try {// 添加URL编码日志XxlJobLogger.log("原始URL: " + fileUrl);URL url = new URL(fileUrl);HttpURLConnection httpConn = (HttpURLConnection) url.openConnection();// 设置更完整的请求头httpConn.setRequestMethod("GET");httpConn.setConnectTimeout(30000); // 增加到30秒httpConn.setReadTimeout(60000);    // 增加到60秒httpConn.setRequestProperty("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36");httpConn.setRequestProperty("Accept", "*/*");httpConn.setRequestProperty("Accept-Language", "zh-CN,zh;q=0.9,en;q=0.8");httpConn.setRequestProperty("Accept-Encoding", "identity"); // 不使用压缩httpConn.setRequestProperty("Connection", "keep-alive");// 处理重定向httpConn.setInstanceFollowRedirects(true);int responseCode = httpConn.getResponseCode();// 输出详细调试信息XxlJobLogger.log("HTTP 状态码:" + responseCode);XxlJobLogger.log("Content-Type: " + httpConn.getContentType());XxlJobLogger.log("Content-Length: " + httpConn.getContentLength());if (responseCode == HttpURLConnection.HTTP_MOVED_TEMP|| responseCode == HttpURLConnection.HTTP_MOVED_PERM|| responseCode == HttpURLConnection.HTTP_SEE_OTHER) {String newUrl = httpConn.getHeaderField("Location");XxlJobLogger.log("检测到重定向,新地址:" + newUrl);return downloadUsingHttpWithDebug(newUrl, outputFile); // 递归处理}if (responseCode != HttpURLConnection.HTTP_OK) {XxlJobLogger.log("服务器返回非 200 状态码: " + responseCode);// 尝试读取错误响应try (InputStream errorStream = httpConn.getErrorStream()) {if (errorStream != null) {byte[] errorBytes = new byte[1024];int bytesRead = errorStream.read(errorBytes);if (bytesRead > 0) {String errorResponse = new String(errorBytes, 0, bytesRead, "UTF-8");XxlJobLogger.log("错误响应: " + errorResponse);}}}return false;}// 开始下载try (InputStream inputStream = httpConn.getInputStream();FileOutputStream outputStream = new FileOutputStream(outputFile)) {byte[] buffer = new byte[4096];int bytesRead;while ((bytesRead = inputStream.read(buffer)) != -1) {outputStream.write(buffer, 0, bytesRead);}// 强制刷新确保写入完成outputStream.flush();// 检查文件大小是否为 0if (outputFile.length() == 0) {XxlJobLogger.log("❌ 下载文件为空(0字节)");return false;}return true;}} catch (Exception e) {XxlJobLogger.log("⚠️ 下载过程中发生异常:" + e.getClass().getName() + ": " + e.getMessage());e.printStackTrace(); // 打印完整堆栈跟踪return false;}
}

4、技术要点分析

1. 中文URL编码处理

问题:直接使用包含中文的URL会导致HTTP请求失败。

解决方案

  • 只对文件名部分进行URL编码,保持路径部分不变
  • 使用UTF-8编码
  • +替换为%20(空格的正确编码)

2. 重试机制设计

策略

  • 从当前日期开始,逐日向前尝试
  • 最多重试3天
  • 每次失败后记录详细日志

3. HTTP连接优化

关键配置

  • 连接超时:30秒
  • 读取超时:60秒
  • User-Agent:模拟浏览器请求
  • Accept-Encoding:设置为identity避免压缩问题

4. 异常处理与日志记录

最佳实践

  • 详细记录每个步骤的执行情况
  • 区分不同类型的异常
  • 提供足够的调试信息

5、常见问题与解决方案

1. 文件下载为空

原因:服务器返回200状态码但内容为空

解决:检查文件大小,如果为0字节则认为下载失败

2. 连接超时

原因:网络不稳定或服务器响应慢

解决:适当增加超时时间,添加重试机制

3. 中文文件名乱码

原因:URL编码问题

解决:正确使用URLEncoder进行UTF-8编码

6、总结

本文介绍了一个完整的HTTP文件下载解决方案,特别适用于定时任务场景。通过合理的重试机制、正确的中文编码处理和详细的日志记录,可以构建一个稳定可靠的文件下载系统。

在实际应用中,还需要根据具体的业务需求进行调整和优化。希望这个案例能为大家在类似场景下的开发工作提供参考。

http://www.dtcms.com/a/279002.html

相关文章:

  • C++-linux 7.文件IO(二)文件描述符、阻塞与非阻塞
  • .net天擎分钟降水数据统计
  • .NET控制台应用程序中防止程序立即退出
  • Flask服务器公外网访问,IPv6(亲测有效!!!!)
  • 网络编程(TCP连接)
  • .NET ExpandoObject 技术原理解析
  • LeetCode经典题解:206、两数之和(Two Sum)
  • OV-DINO:基于语言感知选择性融合的统一开放词汇检测
  • 深入解析ThreadLocal:线程隔离利器
  • C#自定义控件
  • c# 深度解析:实现一个通用配置管理功能,打造高并发、可扩展的配置管理神器
  • Typecho加密文章HTML结构自定义完全指南
  • 在 Windows 主机和 VMware 虚拟机中的 Ubuntu 系统之间实现复制粘贴,
  • Spring IoC 容器实战:从解耦到集成的 6 大核心应用场景
  • 【PTA数据结构 | C语言版】字符串插入操作(不限长)
  • 微前端框架深度对决:qiankun、micro-app、wujie 技术内幕与架构选型指南
  • Ubuntu-25.04 Wayland桌面环境安装Anaconda3之后无法启动anaconda-navigator问题解决
  • 如何降低AIGC的查重率?精选六个AIGC降重让论文更出色
  • Spring Boot项目结构解析:构建高效、清晰的代码框架
  • 【Python进阶】深度复制——deepcopy
  • STM32F1_Hal库学习EXTI
  • 苍穹外卖学习指南(java的一个项目)(老师能运行,但你不行,看这里!!)
  • 最近要上Android 15的高通平台,按照之前Android14的高通平台的裁剪APP的方法修改,发现一改编译之后就不能进系统
  • LLaMA.cpp HTTP 服务参数: --pooling 嵌入模型 池化类型详解
  • 笔试——Day7
  • Datawhale AI夏令营大模型 task2.1
  • QML 常用控件(二)
  • Qt小组件 - 3 imageLabel
  • 【CV综合实战】基于深度学习的工业压力表智能检测与读数系统【3】使用OpenCV读取分割后的压力表读数
  • 《C++内存泄漏8大战场:Qt/MFC实战详解 + 面试高频陷阱破解》