手把手教你 Java 文件斷點(diǎn)下載
來源:juejin.cn/post/7026372482110079012
?? 歡迎加入小哈的星球 ,你將獲得: 專屬的項(xiàng)目實(shí)戰(zhàn) / Java 學(xué)習(xí)路線 / 一對(duì)一提問 / 學(xué)習(xí)打卡 / 贈(zèng)書福利
全棧前后端分離博客項(xiàng)目 1.0 版本完結(jié)啦,2.0 正在更新中..., 演示鏈接:http://116.62.199.48/ ,全程手摸手,后端 + 前端全棧開發(fā),從 0 到 1 講解每個(gè)功能點(diǎn)開發(fā)步驟,1v1 答疑,直到項(xiàng)目上線。目前已更新了189小節(jié),累計(jì)31w+字,講解圖:1308張,還在持續(xù)爆肝中.. 后續(xù)還會(huì)上新更多項(xiàng)目,目標(biāo)是將Java領(lǐng)域典型的項(xiàng)目都整一波,如秒殺系統(tǒng), 在線商城, IM即時(shí)通訊,Spring Cloud Alibaba 等等,戳我加入學(xué)習(xí),已有850+小伙伴加入(早鳥價(jià)超低)
- 前言
- 生產(chǎn)實(shí)戰(zhàn)
- 服務(wù)端 - 業(yè)務(wù)開發(fā)
前言
?
互聯(lián)網(wǎng)的連接速度慢且不穩(wěn)定,有可能由于網(wǎng)絡(luò)故障導(dǎo)致斷開連接。
在客戶端下載一個(gè)大對(duì)象時(shí),因網(wǎng)絡(luò)斷開導(dǎo)致上傳下載失敗的概率就會(huì)變得不可忽視。
?
圖片
客戶端在GET對(duì)象請(qǐng)求時(shí)通過設(shè)置Range頭部來告訴接口服務(wù)需要從什么位置開始輸出對(duì)象的數(shù)據(jù)。
判斷是否支持?jǐn)帱c(diǎn)下載,根據(jù)文檔:14.35.1 Byte Rangeshttps://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html
// 直接判斷是否有 Accept-Ranges = bytes
boolean support = urlConnection.getHeaderField("Accept-Ranges").equals("bytes");
System.out.println("Partial content retrieval support = " + (support ? "Yes" : "No));
例如:
donald@donald-pro:~$ curl -i --range 0-9 http://localhost:8080/file/chunk/download
HTTP/1.1 206
Accept-Ranges: bytes
Content-Disposition: inline;filename=pom.xml
Content-Range: bytes 0-9/13485
Content-Length: 10
Date: Mon, 01 Nov 2021 09:53:25 GMT
直接判斷頭部 HEAD,例如:
?
HeadObject接口用于獲取某個(gè)文件(Object)的元信息。使用此接口不會(huì)返回文件內(nèi)容。?
HEAD /ObjectName HTTP/1.1
Host: BucketName.oss-cn-hangzhou.aliyuncs.com
Date: GMT Date
Authorization: SignatureValue
需知,對(duì)應(yīng) HTTP 狀態(tài)碼:
-
206 Partial Content:HTTP Range請(qǐng)求成功 -
416 Requested Range Not Satisfiable status:HTTP Range請(qǐng)求超出界限 -
200 OK:不支持范圍請(qǐng)求
小結(jié)如下:
-
HTTP范圍請(qǐng)求:需要HTTP/1.1及之上支持,如果雙端某一段低于此版本,則認(rèn)為不支持。 - 通過響應(yīng)頭中的
Accept-Ranges來確定是否支持范圍請(qǐng)求。 - 通過在請(qǐng)求頭中添加
Range這個(gè)請(qǐng)求頭,來指定請(qǐng)求的內(nèi)容實(shí)體的字節(jié)范圍。 - 在響應(yīng)頭中,通過
Content-Range來標(biāo)識(shí)當(dāng)前返回的內(nèi)容實(shí)體范圍,并使用 Content-Length 來標(biāo)識(shí)當(dāng)前返回的內(nèi)容實(shí)體范圍長(zhǎng)度。 - 在請(qǐng)求過程中,可以通過
If-Range來區(qū)分資源文件是否變動(dòng),它的值來自ETag或者Last-Modifled。如果資源文件有改動(dòng),會(huì)重新走下載流程。
生產(chǎn)實(shí)戰(zhàn)
開發(fā)也得依靠依據(jù),設(shè)定好邊界,才能掌控全局。
有現(xiàn)成的文檔,來看阿里云文檔https://help.aliyun.com/document_detail/39571.html
-
Range: bytes=0-499:表示第0~499字節(jié)范圍的內(nèi)容。 -
Range: bytes=500-999:表示第500~999字節(jié)范圍的內(nèi)容。 -
Range: bytes=-500:表示最后500字節(jié)的內(nèi)容。 -
Range: bytes=500-:表示從第500字節(jié)開始到文件結(jié)束部分的內(nèi)容。 -
Range: bytes=0-:表示第一個(gè)字節(jié)到最后一個(gè)字節(jié),即完整的文件內(nèi)容。
HTTP Range 是否合法對(duì)應(yīng)處理:
- 如果
HTTP Range請(qǐng)求合法,響應(yīng)返回值為 206 并在響應(yīng)頭中包含Content-Range - 如果
HTTP Range請(qǐng)求不合法,或者指定范圍不在有效區(qū)間,會(huì)導(dǎo)致Range不生效,響應(yīng)返回值為200,并傳送整個(gè)Object內(nèi)容。
?
如下為
HTTP Range請(qǐng)求不合法的示例及錯(cuò)誤說明: 假設(shè)Object資源大小為1000字節(jié),Range有效區(qū)間為0~999
Range: byte=0-499:格式錯(cuò)誤,byte應(yīng)為bytes。Range: bytes=0-1000:末字節(jié)1000超出有效區(qū)間。Range: bytes=1000-2000:指定范圍超出有效區(qū)間。Range: bytes=1000-:首字節(jié)超出有效區(qū)間。Range: bytes=-2000:指定范圍超出有效區(qū)間。?
舉一些栗子:
# 正常范圍下載
donald@donald-pro:~$ curl -i --range 0-9 http://localhost:8080/file/chunk/download
HTTP/1.1 206
Accept-Ranges: bytes
Content-Disposition: inline;filename=Screen_Recording_20211101-162729_Settings.mp4
Content-Range: bytes 0-9
Content-Type: application/force-download;charset=UTF-8
Content-Length: 16241985
Date: Wed, 03 Nov 2021 09:50:50 GMT
服務(wù)端 - 業(yè)務(wù)開發(fā)
這里以 SpringBoot 為栗子:
- 對(duì)外支持
range下載 - 底層存儲(chǔ):使用
ceph -
Controller如下:
@Slf4j
@RestController
public class Controller {
@Autowired
private FileService fileService;
/**
* 下載文件
*
* 對(duì)外提供
*
* @param fileId 文件Id
* @param token token
* @param accountId 帳號(hào)Id
* @param response 響應(yīng)
*/
@GetMapping("/oceanfile/download")
public void downloadOceanfile(@RequestParam String fileId,
@RequestHeader(value = "Range") String range,
HttpServletResponse response) {
this.fileService.downloadFile(fileId, response, range);
}
}
-
Service如下:
@Slf4j
@Service
public class FileService {
@Autowired
private CephUtils cephUtils;
/**
* 直接下載文件
*
* Tips: 支持?jǐn)帱c(diǎn)下載
* @param fileId 文件Id
* @param response 返回
* @param range 范圍
*/
public void downloadFile(String fileId, HttpServletResponse response, String range) {
// 根據(jù) fileId 獲取文件信息
FileInfo fileInfo = getFileInfo(fileId);
String bucketName = fileInfo.getBucketName();
String relativePath = fileInfo.getRelativePath();
// 處理 range,范圍信息
RangeDTO rangeInfo = executeRangeInfo(range, fileInfo.getFileSize());
// rangeInfo = null,直接下載整個(gè)文件
if (Objects.isNull(rangeInfo)) {
cephUtils.downloadFile(response, bucketName, relativePath);
return;
}
// 下載部分文件
cephUtils.downloadFileWithRange(response, bucketName, relativePath, rangeInfo);
}
private RangeDTO executeRangeInfo(String range, Long fileSize) {
if (StringUtils.isEmpty(range) || !range.contains("bytes=") || !range.contains("-")) {
return null;
}
long startByte = 0;
long endByte = fileSize - 1;
range = range.substring(range.lastIndexOf("=") + 1).trim();
String[] ranges = range.split("-");
if (ranges.length <= 0 || ranges.length > 2) {
return null;
}
try {
if (ranges.length == 1) {
if (range.startsWith("-")) {
//1. 如:bytes=-1024 從開始字節(jié)到第1024個(gè)字節(jié)的數(shù)據(jù)
endByte = Long.parseLong(ranges[0]);
} else if (range.endsWith("-")) {
//2. 如:bytes=1024- 第1024個(gè)字節(jié)到最后字節(jié)的數(shù)據(jù)
startByte = Long.parseLong(ranges[0]);
}
} else {
//3. 如:bytes=1024-2048 第1024個(gè)字節(jié)到2048個(gè)字節(jié)的數(shù)據(jù)
startByte = Long.parseLong(ranges[0]);
endByte = Long.parseLong(ranges[1]);
}
} catch (NumberFormatException e) {
startByte = 0;
endByte = fileSize - 1;
}
if (startByte >= fileSize) {
log.error("range error, startByte >= fileSize. " +
"startByte: {}, fileSize: {}", startByte, fileSize);
return null;
}
return new RangeDTO(startByte, endByte);
}
}
以上內(nèi)容,大家可以收藏起來,如果以后遇到這樣的場(chǎng)景,分分鐘搞定!
?? 歡迎加入小哈的星球 ,你將獲得: 專屬的項(xiàng)目實(shí)戰(zhàn) / Java 學(xué)習(xí)路線 / 一對(duì)一提問 / 學(xué)習(xí)打卡 / 贈(zèng)書福利
全棧前后端分離博客項(xiàng)目 1.0 版本完結(jié)啦,2.0 正在更新中..., 演示鏈接:http://116.62.199.48/ ,全程手摸手,后端 + 前端全棧開發(fā),從 0 到 1 講解每個(gè)功能點(diǎn)開發(fā)步驟,1v1 答疑,直到項(xiàng)目上線。目前已更新了189小節(jié),累計(jì)31w+字,講解圖:1308張,還在持續(xù)爆肝中.. 后續(xù)還會(huì)上新更多項(xiàng)目,目標(biāo)是將Java領(lǐng)域典型的項(xiàng)目都整一波,如秒殺系統(tǒng), 在線商城, IM即時(shí)通訊,Spring Cloud Alibaba 等等,戳我加入學(xué)習(xí),已有850+小伙伴加入(早鳥價(jià)超低)
2. 卷翻了,功能強(qiáng)大,企業(yè)級(jí)的微服務(wù)開發(fā)平臺(tái)開源了....
3. 公司系統(tǒng)太多,能不能實(shí)現(xiàn)賬號(hào)互通?
最近面試BAT,整理一份面試資料 《Java面試BATJ通關(guān)手冊(cè)》 ,覆蓋了Java核心技術(shù)、JVM、Java并發(fā)、SSM、微服務(wù)、數(shù)據(jù)庫(kù)、數(shù)據(jù)結(jié)構(gòu)等等。
獲取方式:點(diǎn)“ 在看 ”,關(guān)注公眾號(hào)并回復(fù) Java 領(lǐng)取,更多內(nèi)容陸續(xù)奉上。
PS:因公眾號(hào)平臺(tái)更改了推送規(guī)則,如果不想錯(cuò)過內(nèi)容,記得讀完點(diǎn)一下 “在看” ,加個(gè) “星標(biāo)” ,這樣每次新文章推送才會(huì)第一時(shí)間出現(xiàn)在你的訂閱列表里。
點(diǎn)“在看”支持小哈呀,謝謝啦

