Class FileResponseBuilder
檔案下載 Response 建構器
提供統一的檔案下載回應建構,支援:
- 完整檔案下載(200 OK)
- 部分內容下載(206 Partial Content)- 支援 Range Request
- 影音串流(自動處理 Range Request)
- 斷點續傳
使用範例
@GetMapping("/products/{id}/image")
public ResponseEntity<Resource> getProductImage(@PathVariable String id, HttpServletRequest request) {
Product product = productService.findById(id); // 權限檢查
String partition = TenantContext.getCurrentTenantId();
// 讀取方法、Range、If-Range、If-None-Match;HEAD 不讀取內容
return FileResponseBuilder.from(fileStorage, partition, product.getImageFileId())
.request(request)
.build();
}
影音串流範例
@GetMapping("/videos/{id}")
public ResponseEntity<Resource> streamVideo(@PathVariable String id, HttpServletRequest request) {
Video video = videoService.findById(id);
String partition = TenantContext.getCurrentTenantId();
// 儲存支援區段讀取(RangeSupport.NATIVE)時,拖動進度條只傳輸所需區段
return FileResponseBuilder.from(fileStorage, partition, video.getFileId())
.request(request)
.build();
}
Range 語意(RFC 9110)
只服務 GET 與 HEAD;其他方法在任何評估之前丟出 IllegalArgumentException。評估順序:
404 → If-None-Match 相符回 304 → HEAD 以 metadata 回 headers、不讀內容 → 沒有 Range、
儲存為 RangeSupport.NONE 或 If-Range 不符時回 200 完整內容 → RangeUtils.evaluate(String, long):
格式錯誤或多個 range 回 200、無法滿足回 416、可滿足回 206(以 FileStorage.openRange(String, String, long, long) 只讀該區段)。
If-Range 只接受與 ETag 相同的強 ETag。跨來源前端(如 PDF.js)需 expose RANGE_EXPOSED_HEADERS。
- See Also:
-
Field Summary
FieldsModifier and TypeFieldDescriptionrange 回應需要讓跨來源前端讀取的 headers;應用的 CORS 設定把它加入exposedHeaders -
Method Summary
Modifier and TypeMethodDescriptionorg.springframework.http.ResponseEntity<org.springframework.core.io.Resource> build()建構 ResponseEntitycacheControl(org.springframework.http.CacheControl cacheControl) 覆寫 Cache-Control 策略設定下載時的檔名強制下載(而非在瀏覽器中開啟)static FileResponseBuilderfrom(FileStorage fileStorage, String partition, String fileId) 建立 FileResponseBuilderifNoneMatch(String ifNoneMatch) 設定請求的If-None-Matchheader(啟用條件式下載)設定請求的If-Rangeheader宣告內容不可變,套用長效快取method(org.springframework.http.HttpMethod method) 設定請求方法;未設定時視為GET設定 Range header(支援部分內容下載)request(jakarta.servlet.http.HttpServletRequest request) 由目前請求讀取方法、Range、If-Range與If-None-Match
-
Field Details
-
RANGE_EXPOSED_HEADERS
-
-
Method Details
-
from
建立 FileResponseBuilder- Parameters:
fileStorage- FileStorage 實例partition- 儲存分區鍵fileId- 檔案 ID- Returns:
- FileResponseBuilder 實例
-
request
由目前請求讀取方法、
Range、If-Range與If-None-Match建議的用法:controller 不必逐一傳入條件式 headers,
HEAD也不會讀取內容。- Parameters:
request- 目前請求- Returns:
- this
-
method
設定請求方法;未設定時視為
GET只接受
GET與HEAD,其他方法在build()丟出IllegalArgumentException。HEAD以 metadata 建構 headers,不開啟內容串流。- Parameters:
method- 請求方法- Returns:
- this
-
ifRange
設定請求的
If-Rangeheader只有與 ETag 完全相同的強 ETag 才處理 Range;弱 ETag、日期形式與空值一律回完整內容。
- Parameters:
ifRange- 請求的 If-Range header 值;null表示請求沒有 If-Range- Returns:
- this
-
range
設定 Range header(支援部分內容下載)- Parameters:
rangeHeader- HTTP Range header 值(如 "bytes=0-1000"),可為 null- Returns:
- this
-
filename
設定下載時的檔名
若不設定,會使用檔案的原始檔名。
- Parameters:
filename- 下載時顯示的檔名- Returns:
- this
-
forceDownload
強制下載(而非在瀏覽器中開啟)
設定 Content-Disposition: attachment,讓瀏覽器下載檔案而非直接開啟。
- Returns:
- this
-
ifNoneMatch
設定請求的
If-None-Matchheader(啟用條件式下載)傳入後,
build()會以ETag(見build()的 ETag 說明)比對; 相符則在開啟儲存串流之前直接回 304 Not Modified、不送 body, 同時省下傳輸與 storage 讀取。Controller 端以
@RequestHeader(value = "If-None-Match", required = false)取得。- Parameters:
ifNoneMatch- 請求的 If-None-Match header 值,可為 null- Returns:
- this
-
immutable
宣告內容不可變,套用長效快取
設定
Cache-Control: public, max-age=31536000, immutable,讓瀏覽器與共享快取 快取一年且不再回源驗證。適用於以 fileId 定址的 URL(如/files/{fileId})—— 因為 fileId 為 write-once(見FileStorage),同一 fileId 的內容永不改變。勿用於「URL 穩定但內容可換」的實體型 URL(如
/products/{id}/image); 那類請維持預設(no-cache+ ETag 每次 revalidate),換圖時才不會拿到舊快取。- Returns:
- this
-
cacheControl
覆寫 Cache-Control 策略
預設為
CacheControl.noCache(帶 ETag、每次 revalidate、命中回 304)。 需要私有快取、短時效或其他策略時以此覆寫,例如cacheControl(CacheControl.noCache().cachePrivate())。- Parameters:
cacheControl- Cache-Control 策略- Returns:
- this
-
build
public org.springframework.http.ResponseEntity<org.springframework.core.io.Resource> build()建構 ResponseEntity
- 方法不是
GET/HEAD→IllegalArgumentException(不讀取任何資料) - 檔案不存在 → 404 Not Found
If-None-Match與 ETag 相符 → 304 Not Modified(不開串流、不送 body)HEAD→ 200,headers 由 metadata 建構,不開串流、不處理 Range- 無 Range、儲存不支援區段讀取、
If-Range不符、Range 格式錯誤或多個 range → 200 OK(完整檔案) - Range 無法滿足 → 416,
Content-Range: bytes */{size} - Range 可滿足 → 206 Partial Content(
FileStorage.openRange(String, String, long, long)只讀該區段)
ETag:以 fileId 為強驗證器(
"{fileId}")。fileId 為 write-once (見FileStorage),同一 fileId 的內容永不改變,故可安全作為 ETag。回應一律帶 ETag 與 Cache-Control(預設no-cache,或immutable()的長效策略)。- Returns:
- ResponseEntity
- Throws:
IllegalArgumentException- 請求方法不是GET或HEAD
- 方法不是
-