import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.ResponseEntity;
import org.springframework.web.client.RestTemplate;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.channels.FileChannel;
@Slf4j
public class SliceUtil {
public final static long PER_PAGE = (long) 1024 * 1024;
private static final RestTemplate REST_TEMPLATE = new RestTemplate();
public static ResponseEntity<byte[]> getFileContentByUrlAndPosition(String downloadUrl, long start, long end) {
HttpHeaders httpHeaders = new HttpHeaders();
httpHeaders.set("Range", "bytes=" + start + "-" + end);
org.springframework.http.HttpEntity<Object> httpEntity = new org.springframework.http.HttpEntity<>(httpHeaders);
return REST_TEMPLATE.exchange(downloadUrl, HttpMethod.GET, httpEntity, byte[].class);
}
public static void download(String tempPath, String downloadUrl, SliceInfo sliceInfo, String fName) {
log.info("下载分片文件:{},分片序号 {}", fName, sliceInfo.getPage());
File file = new File(tempPath, sliceInfo.getPage() + "-" + fName);
if (file.exists() && file.length() == PER_PAGE) {
log.info("此分片文件 {} 已存在", sliceInfo.getPage());
return;
}
try (FileOutputStream fos = new FileOutputStream(file);) {
ResponseEntity<byte[]> responseEntity = SliceUtil.getFileContentByUrlAndPosition(downloadUrl, sliceInfo.getStart(), sliceInfo.getEnd());
byte[] body = responseEntity.getBody();
if (body != null && body.length == 0) {
log.warn("分片文件:{},没有内容", file.getName());
return;
}
fos.write(body);
} catch (IOException e) {
e.printStackTrace();
}
}
public static void mergeFileTranTo(String tempPath, String fName, long page) {
try (FileChannel channel = new FileOutputStream(new File(tempPath, fName)).getChannel()) {
for (long i = 1; i <= page; i++) {
File file = new File(tempPath, i + "-" + fName);
FileChannel fileChannel = new FileInputStream(file).getChannel();
long size = fileChannel.size();
for (long left = size; left > 0; ) {
left -= fileChannel.transferTo((size - left), left, channel);
}
fileChannel.close();
file.delete();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
@Data
public class SlicePageInfo {
private CopyOnWriteArrayList<SliceInfo> sliceInfoList;
private Long page;
}
import lombok.AllArgsConstructor;
import lombok.Data;
@Data
@AllArgsConstructor
public class SliceInfo {
private long start;
private long end;
private long page;
}
import com.ruoyi.common.utils.spring.SpringUtils;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpHeaders;
import org.springframework.http.ResponseEntity;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import java.util.Objects;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.CountDownLatch;
import static com.ruoyi.download.slice.SliceUtil.PER_PAGE;
@Slf4j
public class DownLoadEngine {
private static ThreadPoolTaskExecutor executorService = SpringUtils.getBean("threadPoolTaskExecutor");
public static void downloadSlice(String downloadUrl, String tempPath, String fileName) {
ResponseEntity<byte[]> responseEntity = SliceUtil.getFileContentByUrlAndPosition(downloadUrl, 0, 1);
HttpHeaders headers = responseEntity.getHeaders();
String rangeBytes = headers.getFirst("Content-Range");
if (Objects.isNull(rangeBytes)) {
log.error("url:{},不支持分片下载", downloadUrl);
return;
}
long allBytes = Long.parseLong(rangeBytes.split("/")[1]);
log.info("文件总大小:{}M", allBytes / 1024.0 / 1024.0);
SlicePageInfo slicePageInfo = splitPage(allBytes);
CountDownLatch countDownLatch = new CountDownLatch(Math.toIntExact(slicePageInfo.getPage()));
CountDownLatch mainLatch = new CountDownLatch(1);
executorService.execute(() -> {
try {
countDownLatch.await();
SliceUtil.mergeFileTranTo(tempPath, fileName, slicePageInfo.getPage());
mainLatch.countDown();
} catch (InterruptedException e) {
e.printStackTrace();
}
});
for (SliceInfo sliceInfo : slicePageInfo.getSliceInfoList()) {
executorService.submit(() -> {
SliceUtil.download(tempPath, downloadUrl, sliceInfo, fileName);
countDownLatch.countDown();
});
}
try {
mainLatch.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public static SlicePageInfo splitPage(long allBytes) {
CopyOnWriteArrayList<SliceInfo> list = new CopyOnWriteArrayList<>();
long size = allBytes;
long left = 0;
long page = 0;
while (size > 0) {
long start = 0;
long end;
start = left;
if (size < PER_PAGE) {
end = left + size;
} else {
end = left += PER_PAGE;
}
size -= PER_PAGE;
page++;
if (start != 0) {
start++;
}
log.info("页码:{},开始位置:{},结束位置:{}", page, start, end);
final SliceInfo sliceInfo = new SliceInfo(start, end, page);
list.add(sliceInfo);
}
SlicePageInfo slicePageInfo = new SlicePageInfo();
slicePageInfo.setSliceInfoList(list);
slicePageInfo.setPage(page);
return slicePageInfo;
}
}
@Test
@DisplayName("大文件分片下载")
public void downloadSliceFile() {
DownLoadEngine.downloadSlice("https://dldir1.qq.com/qqfile/qq/PCQQ9.6.1/QQ9.6.1.28732.exe", "D:/temp", "qq.exe");
}