@Resource
private DzhlConfig dzhlConfig;
@Resource
private SysDownloadChunksService chunksService;
@Resource
private WGConfig wgConfig;
/**
* 任务ID到Future的映射,用于控制任务
*/
private final ConcurrentHashMap<Long, Future<?>> taskFutures = new ConcurrentHashMap<>();
/**
* 下载任务线程池
*/
private final ExecutorService downloadExecutor = Executors.newFixedThreadPool(5);
/**
* 分片下载线程池
*/
private final ExecutorService chunkExecutor = Executors.newFixedThreadPool(5 * 3);
// 在服务类中添加HttpClient成员变量
private CloseableHttpClient httpClient;
// 在服务类初始化时创建HttpClient
@PostConstruct
public void init() {
this.httpClient = createHttpClient();
}
// 在服务类销毁时关闭HttpClient
@PreDestroy
public void destroy() {
try {
if (httpClient != null) {
httpClient.close();
}
} catch (IOException e) {
log.error("关闭HTTP客户端失败", e);
}
}
@Override
@Transactional
public Long createTask(String url, String onlyName) throws IOException {
// 创建任务对象
SysDownloadTasks task = new SysDownloadTasks();
task.setFileUrl(url);
task.setSavePath(DzhlConfig.getUploadPath() + "/" + DateUtils.dateTime() + "/" + onlyName);
task.setOnlyName(onlyName);
task.setStatus(SysDownloadTasks.DownloadStatus.PENDING);
task.setDownloadedSize(0L);
log.info("创建下载任务: url={}, savePath={}", url, task.getSavePath());
// 解析文件名
URL fileUrl = new URL(url);
String path = fileUrl.getPath();
task.setFileName(path.substring(path.lastIndexOf('/') + 1));
// 获取文件大小并初始化分片
initializeChunks(task, dzhlConfig.getDefaultChunkSize());
// 保存任务到数据库
baseMapper.insert(task);
log.info("下载任务创建成功: taskId={}", task.getId());
return task.getId();
}
/**
* 初始化文件分片
*
* @param task 下载任务
* @param chunkSize 分片大小
*/
private void initializeChunks(SysDownloadTasks task, long chunkSize) throws IOException {
log.info("初始化文件分片: taskId={}, chunkSize={}", task.getId(), chunkSize);
HttpResponse httpResponse = casterLogin();
JSONObject loginResultObj = JSONObject.parseObject(EntityUtils.toString(httpResponse.getEntity(), "UTF-8"));
if (httpResponse.getStatusLine().getStatusCode() == 200) {
// 创建HTTP连接获取文件大小
HttpURLConnection connection = null;
try {
URL url = new URL(task.getFileUrl());
connection = (HttpURLConnection) url.openConnection();
connection.setRequestProperty("Authorization", loginResultObj.get("token").toString());
connection.setRequestMethod("HEAD");
int responseCode = connection.getResponseCode();
if (responseCode != 200) {
throw new IOException("获取文件信息失败,HTTP状态码: " + responseCode);
}
// 获取文件大小
long fileSize = connection.getContentLengthLong();
if (fileSize <= 0) {
throw new IOException("无法获取文件大小");
}
task.setTotalSize(fileSize);
// 计算分片数量
int chunkCount = (int) Math.ceil((double) fileSize / chunkSize);
task.setChunkCount(chunkCount);
// 创建分片信息
List<SysDownloadChunks> chunks = new ArrayList<>();
for (int i = 0; i < chunkCount; i++) {
long startByte = i * chunkSize;
long endByte = (i == chunkCount - 1) ? fileSize - 1 : (i + 1) * chunkSize - 1;
SysDownloadChunks chunk = new SysDownloadChunks();
chunk.setTaskId(task.getId());
chunk.setChunkIndex(i);
chunk.setStartByte(startByte);
chunk.setEndByte(endByte);
chunk.setDownloadedBytes(0L);
chunk.setTempLocation(dzhlConfig.getTempLocation() + DateUtils.dateTime() + task.getOnlyName() + "/chunk_" + i);
chunk.setStatus(SysDownloadChunks.ChunkStatus.PENDING);
chunk.setRetryCount(0);
chunks.add(chunk);
}
// 保存分片信息到数据库
chunksService.saveBatch(chunks);
log.info("文件分片初始化完成: taskId={}, 分片数量={}", task.getId(), chunkCount);
} finally {
if (connection != null) {
connection.disconnect();
}
}
// }
}
log.info("导播机登录失败!");
}
@Override
@Async
public void startTask(Long taskId) {
log.info("开始下载任务: taskId={}", taskId);
// 获取任务信息
SysDownloadTasks task = baseMapper.selectById(taskId);
System.out.println("aaaa" + task);
System.out.println("eeeee" + task.getStatus());
if (task == null) {
log.error("下载任务不存在: taskId={}", taskId);
return;
}
// 检查任务状态
if (!"PENDING".equals(task.getStatus().toString()) && !"PAUSED".equals(task.getStatus().toString())) {
log.info("任务状态不是待下载或已暂停,无法开始: taskId={}, status={}", taskId, task.getStatus());
return;
}
// 更新任务状态
task.setStatus(SysDownloadTasks.DownloadStatus.DOWNLOADING);
task.setCreateTime(new Date());
baseMapper.updateById(task);
// 提交任务到线程池
Future<?> future = downloadExecutor.submit(() -> downloadFile(task));
taskFutures.put(taskId, future);
log.info("下载任务已提交执行: taskId={}", taskId);
}
/**
* 下载文件主方法
*
* @param task 下载任务
*/
private void downloadFile(SysDownloadTasks task) {
log.info("开始下载文件: taskId={}, fileName={}", task.getId(), task.getFileName());
try {
// 获取所有分片
List<SysDownloadChunks> chunks = chunksService.list(new LambdaQueryWrapper<SysDownloadChunks>()
.eq(SysDownloadChunks::getTaskId, task.getId()));
if (chunks == null || chunks.isEmpty()) {
log.error("没有找到分片信息: taskId={}", task.getId());
task.setStatus(SysDownloadTasks.DownloadStatus.FAILED);
task.setErrorMessage("没有找到分片信息");
baseMapper.updateById(task);
return;
}
// 并行下载所有分片
List<Future<Boolean>> futures = new ArrayList<>();
for (SysDownloadChunks chunk : chunks) {
if (!"COMPLETED".equals(chunk.getStatus().toString())) {
SysDownloadTasks finalTask = task;
futures.add(chunkExecutor.submit(() -> downloadChunk(finalTask, chunk)));
}
}
// 等待所有分片下载完成
boolean allSuccess = true;
for (Future<Boolean> future : futures) {
if (!future.get()) {
allSuccess = false;
break;
}
}
// 检查任务状态
task = baseMapper.selectById(task.getId());
if (!allSuccess || !"DOWNLOADING".equals(task.getStatus().toString())) {
log.info("下载任务已暂停或取消: taskId={}", task.getId());
return;
}
// 合并所有分片
if (mergeChunks(task)) {
// 计算MD5校验和
String md5 = calculateMD5(task.getSavePath());
task.setOnlyName(md5);
// 清理临时文件
cleanupTempFiles(task);
// 更新任务状态为已完成
task.setStatus(SysDownloadTasks.DownloadStatus.COMPLETED);
task.setUpdateTime(new Date());
baseMapper.updateById(task);
log.info("文件下载完成: taskId={}, fileName={}, MD5={}", task.getId(), task.getFileName(), md5);
} else {
task.setStatus(SysDownloadTasks.DownloadStatus.FAILED);
task.setErrorMessage("合并分片失败");
baseMapper.updateById(task);
log.error("合并分片失败: taskId={}", task.getId());
}
} catch (Exception e) {
log.error("下载文件异常: taskId={}", task.getId(), e);
task.setStatus(SysDownloadTasks.DownloadStatus.FAILED);
task.setErrorMessage("下载过程中发生异常: " + e.getMessage());
baseMapper.updateById(task);
} finally {
// 从任务映射中移除
taskFutures.remove(task.getId());
}
}
/**
* 下载单个分片
*
* @param task 下载任务
* @param chunk 分片信息
* @return 下载是否成功
*/
private boolean downloadChunk(SysDownloadTasks task, SysDownloadChunks chunk) {
log.info("开始下载分片: taskId={}, chunkIndex={}", task.getId(), chunk.getChunkIndex());
// 更新分片状态
chunk.setStatus(SysDownloadChunks.ChunkStatus.DOWNLOADING);
chunksService.updateById(chunk);
// 检查任务状态
task = baseMapper.selectById(task.getId());
if (!"DOWNLOADING".equals(task.getStatus().toString())) {
log.info("任务状态不是下载中,停止下载分片: taskId={}, chunkIndex={}",
task.getId(), chunk.getChunkIndex());
chunk.setStatus(SysDownloadChunks.ChunkStatus.PENDING);
chunksService.updateById(chunk);
return false;
}
//封装导播机登录参数
// JSONObject loginJsonObject = new JSONObject();
// loginJsonObject.put("username", wgConfig.getUsername());
// loginJsonObject.put("password", wgConfig.getPassword());
//
// // 登录
// HttpRequest login = HttpUtil.createPost(wgConfig.getHost(1L) + "login");
// HttpResponse loginExecute = login.body(loginJsonObject.toJSONString()).execute();
// JSONObject loginResultObj = JSONObject.parseObject(loginExecute.body());
// if (loginResultObj.getInteger("code") == 200) {
HttpResponse httpResponse = casterLogin();
JSONObject loginResultObj = null;
try {
loginResultObj = JSONObject.parseObject(EntityUtils.toString(httpResponse.getEntity(), "UTF-8"));
} catch (IOException e) {
e.printStackTrace();
throw new ServiceException("系统错误:" + e.getMessage());
}
if (httpResponse.getStatusLine().getStatusCode() == 200) {
// 创建HTTP连接
HttpGet httpGet = null;
// HttpURLConnection connection = null;
try {
// 创建HTTP请求
httpGet = new HttpGet(task.getFileUrl());
// URL url = new URL(task.getFileUrl());
// connection = (HttpURLConnection) url.openConnection();
httpGet.setHeader("Authorization", loginResultObj.get("token").toString());
// connection.setRequestProperty("Authorization", loginResultObj.get("token").toString());
// connection.setRequestMethod("GET");
// 设置Range头,指定下载范围
long startPos = chunk.getStartByte() + chunk.getEndByte();
httpGet.setHeader("Range", "bytes=" + startPos + "-" + chunk.getEndByte());
// connection.setRequestProperty("Range", "bytes=" + startPos + "-" + chunk.getEndByte());
// 设置超时时间
// connection.setConnectTimeout(5000);
// connection.setReadTimeout(dzhlConfig.getReadTimeout());
// 发送请求并获取响应
// int responseCode = connection.getResponseCode();
org.apache.http.HttpResponse response = httpClient.execute(httpGet);
int responseCode = response.getStatusLine().getStatusCode();
// 206表示Partial Content
if (responseCode != 206) {
log.error("下载分片失败,HTTP状态码: {}, taskId={}, chunkIndex={}",
responseCode, task.getId(), chunk.getChunkIndex());
// 消耗实体避免连接泄漏
EntityUtils.consumeQuietly(response.getEntity());
return handleChunkDownloadFailure(task, chunk);
}
// 确保临时文件存在
File tempFile = new File(chunk.getTempLocation());
if (!tempFile.exists()) {
tempFile.getParentFile().mkdirs();
tempFile.createNewFile();
}
// 打开文件进行追加写入
try (RandomAccessFile out = new RandomAccessFile(tempFile, "rw");
InputStream in = response.getEntity().getContent()) {
// 设置文件指针位置
out.seek(chunk.getDownloadedBytes());
// 缓冲区大小8KB
byte[] buffer = new byte[8192];
int bytesRead;
// 读取数据并写入文件
while ((bytesRead = in.read(buffer)) != -1) {
// 检查任务状态
task = baseMapper.selectById(task.getId());
if (!"DOWNLOADING".equals(task.getStatus().toString())) {
log.info("任务状态不是下载中,停止下载分片: taskId={}, chunkIndex={}",
task.getId(), chunk.getChunkIndex());
return false;
}
out.write(buffer, 0, bytesRead);
// 更新已下载大小
long currentDownloaded = chunk.getDownloadedBytes() + bytesRead;
chunk.setDownloadedBytes(currentDownloaded);
chunksService.updateById(chunk);
// 更新任务已下载大小
System.out.println("$$$$$" + task.getDownloadedSize() + "/" + bytesRead);
task.setDownloadedSize(task.getDownloadedSize() + bytesRead);
baseMapper.updateById(task);
}
// 分片下载完成
chunk.setStatus(SysDownloadChunks.ChunkStatus.COMPLETED);
chunksService.updateById(chunk);
log.info("分片下载完成: taskId={}, chunkIndex={}", task.getId(), chunk.getChunkIndex());
return true;
}
} catch (Exception e) {
log.error("下载分片异常: taskId={}, chunkIndex={}", task.getId(), chunk.getChunkIndex(), e);
return handleChunkDownloadFailure(task, chunk);
} finally {
if (httpGet != null) {
httpGet.releaseConnection();
}
}
// }
}
log.info("导播机登录失败!");
return false;
}
/**
* 处理分片下载失败
*
* @param task 下载任务
* @param chunk 分片信息
* @return 是否需要重试
*/
private boolean handleChunkDownloadFailure(SysDownloadTasks task, SysDownloadChunks chunk) {
// 增加重试次数
chunk.setRetryCount(chunk.getRetryCount() + 1);
chunk.setStatus(SysDownloadChunks.ChunkStatus.FAILED);
chunk.setErrormessage("下载失败,尝试重试");
chunksService.updateById(chunk);
// 如果重试次数小于配置的最大重试次数,则重试
if (chunk.getRetryCount() < dzhlConfig.getRetryCount()) {
log.info("分片下载失败,准备重试: taskId={}, chunkIndex={}, 重试次数={}",
task.getId(), chunk.getChunkIndex(), chunk.getRetryCount());
// 等待一段时间后重试
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
return false;
}
return downloadChunk(task, chunk);
} else {
// 达到最大重试次数,标记为失败
chunk.setStatus(SysDownloadChunks.ChunkStatus.FAILED);
chunk.setErrormessage("达到最大重试次数");
chunksService.updateById(chunk);
// 整个任务也标记为失败
task.setStatus(SysDownloadTasks.DownloadStatus.FAILED);
task.setErrorMessage("分片 " + chunk.getChunkIndex() + " 下载失败,达到最大重试次数");
baseMapper.updateById(task);
log.error("分片下载失败,达到最大重试次数: taskId={}, chunkIndex={}, 重试次数={}",
task.getId(), chunk.getChunkIndex(), chunk.getRetryCount());
return false;
}
}
/**
* 合并所有分片
*
* @param task 下载任务
* @return 合并是否成功
*/
private boolean mergeChunks(SysDownloadTasks task) {
log.info("开始合并分片: taskId={}", task.getId());
// 确保目标目录存在
File targetFile = new File(task.getSavePath());
File parentDir = targetFile.getParentFile();
if (!parentDir.exists()) {
parentDir.mkdirs();
}
// 获取所有分片
List<SysDownloadChunks> chunks = chunksService.list(new LambdaQueryWrapper<SysDownloadChunks>()
.eq(SysDownloadChunks::getTaskId, task.getId()));
if (chunks == null || chunks.isEmpty()) {
log.error("没有找到分片信息,无法合并: taskId={}", task.getId());
return false;
}
// 按分片索引排序
chunks.sort(Comparator.comparing(SysDownloadChunks::getChunkIndex));
try (RandomAccessFile out = new RandomAccessFile(targetFile, "rw")) {
// 按顺序合并所有分片
for (SysDownloadChunks chunk : chunks) {
try (FileInputStream in = new FileInputStream(chunk.getTempLocation())) {
byte[] buffer = new byte[8192];
int bytesRead;
while ((bytesRead = in.read(buffer)) != -1) {
out.write(buffer, 0, bytesRead);
}
}
}
log.info("分片合并完成: taskId={}", task.getId());
return true;
} catch (IOException e) {
log.error("合并分片失败: taskId={}", task.getId(), e);
return false;
}
}
/**
* 计算文件的MD5校验和
*
* @param filePath 文件路径
* @return MD5校验和
*/
private String calculateMD5(String filePath) {
log.info("计算文件MD5: filePath={}", filePath);
try (FileInputStream fis = new FileInputStream(filePath)) {
MessageDigest md = MessageDigest.getInstance("MD5");
byte[] buffer = new byte[8192];
int bytesRead;
while ((bytesRead = fis.read(buffer)) != -1) {
md.update(buffer, 0, bytesRead);
}
byte[] digest = md.digest();
StringBuilder sb = new StringBuilder();
for (byte b : digest) {
sb.append(String.format("%02x", b & 0xff));
}
String md5 = sb.toString();
log.info("MD5计算完成: filePath={}, MD5={}", filePath, md5);
return md5;
} catch (IOException | NoSuchAlgorithmException e) {
log.error("计算MD5失败: filePath={}", filePath, e);
return null;
}
}
/**
* 清理临时文件
*
* @param task 下载任务
*/
private void cleanupTempFiles(SysDownloadTasks task) {
log.info("清理临时文件: taskId={}", task.getId());
try {
// 删除所有分片临时文件
List<SysDownloadChunks> chunks = chunksService.list(new LambdaQueryWrapper<SysDownloadChunks>()
.eq(SysDownloadChunks::getTaskId, task.getId()));
if (chunks != null) {
for (SysDownloadChunks chunk : chunks) {
Path path = Paths.get(chunk.getTempLocation());
Files.deleteIfExists(path);
}
// 删除任务临时目录
Path tempDirPath = Paths.get(chunks.get(0).getTempLocation());
Files.deleteIfExists(tempDirPath);
}
log.info("临时文件清理完成: taskId={}", task.getId());
} catch (IOException e) {
log.error("清理临时文件失败: taskId={}", task.getId(), e);
}
}
@Override
public void pauseTask(Long taskId) {
log.info("暂停下载任务: taskId={}", taskId);
// 获取任务信息
SysDownloadTasks task = baseMapper.selectById(taskId);
if (task == null) {
log.error("下载任务不存在: taskId={}", taskId);
return;
}
// 检查任务状态
if (!"DOWNLOADING".equals(task.getStatus().toString())) {
log.info("任务状态不是下载中,无法暂停: taskId={}, status={}", taskId, task.getStatus());
return;
}
// 更新任务状态
task.setStatus(SysDownloadTasks.DownloadStatus.PAUSED);
baseMapper.updateById(task);
// 取消任务执行
Future<?> future = taskFutures.get(taskId);
if (future != null && !future.isDone()) {
future.cancel(true);
}
log.info("下载任务已暂停: taskId={}", taskId);
}
@Override
public void resumeTask(Long taskId) {
log.info("恢复下载任务: taskId={}", taskId);
// 直接调用startTask,它会检查状态并处理恢复逻辑
startTask(taskId);
}
@Override
public void cancelTask(Long taskId) {
log.info("取消下载任务: taskId={}", taskId);
// 获取任务信息
SysDownloadTasks task = baseMapper.selectById(taskId);
if (task == null) {
log.error("下载任务不存在: taskId={}", taskId);
return;
}
// 更新任务状态
task.setStatus(SysDownloadTasks.DownloadStatus.CANCELED);
baseMapper.updateById(task);
// 取消任务执行
Future<?> future = taskFutures.get(taskId);
if (future != null && !future.isDone()) {
future.cancel(true);
}
// 清理临时文件
cleanupTempFiles(task);
log.info("下载任务已取消: taskId={}", taskId);
}
@Override
public SysDownloadTasks getTask(Long taskId) {
return baseMapper.selectById(taskId);
}
private HttpResponse casterLogin() {
try {
//封装导播机登录参数
JSONObject loginJsonObject = new JSONObject();
loginJsonObject.put("username", wgConfig.getUsername());
loginJsonObject.put("password", wgConfig.getPassword());
HttpPost httpPost = new HttpPost(wgConfig.getHost(1L) + "login");
// 设置JSON实体到请求中
StringEntity entity = new StringEntity(loginJsonObject.toString(), "UTF-8");
entity.setContentType("application/json");
httpPost.setEntity(entity);
// 执行请求
return httpClient.execute(httpPost);
} catch (IOException e) {
e.printStackTrace();
throw new ServiceException("系统错误:" + e.getMessage());
}
}
/**
* HTTP 客户端创建方法
*/
private CloseableHttpClient createHttpClient() {
// 创建连接池管理器
PoolingHttpClientConnectionManager cm = new PoolingHttpClientConnectionManager();
cm.setMaxTotal(dzhlConfig.getHttpMaxTotalConnections());
cm.setDefaultMaxPerRoute(dzhlConfig.getHttpMaxConnectionsPerRoute());
// 创建HTTP客户端,配置连接超时和读取超时
return HttpClients.custom()
.setConnectionManager(cm)
.setConnectionTimeToLive(dzhlConfig.getHttpConnectionTimeToLive(), TimeUnit.MILLISECONDS)
.setDefaultRequestConfig(RequestConfig.custom()
.setConnectTimeout((int) dzhlConfig.getConnectTimeout())
.setSocketTimeout(dzhlConfig.getReadTimeout())
.build())
.build();
} 该代码下载文件报 请求超时错误 文件下载不下来 为什么?