太方便了!SpringBoot 只需一個注解,就能搞定任意對象下載!
在日常開發(fā)中,文件下載是一個常見的功能,雖然在項目中出現(xiàn)的頻率可能不算太高,但幾乎每個項目都會涉及。而有些下載需求相對復(fù)雜,雖然不是難點,但實現(xiàn)起來卻十分繁瑣。
因此,為了簡化這一過程,有一個工具庫,使得下載功能的實現(xiàn)變得更加簡單快捷。
https://github.com/Linyuzai/concept/wiki/Concept-Download
一鍵下載任意對象
如果告訴你,現(xiàn)在僅需一個注解就能輕松下載任意對象,你會不會覺得很方便?
import com.icoderoad.download.annotation.Download;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import java.io.File;
@RestController
public class DownloadController {
@Download(source = "classpath:/download/README.txt")
@GetMapping("/classpath")
public void downloadFromClasspath() {
}
@Download
@GetMapping("/file")
public File downloadFile() {
return new File("/Users/Shared/README.txt");
}
@Download
@GetMapping("/http")
public String downloadFromHttp() {
return "http://127.0.0.1:8080/icoderoad-download/image.jpg";
}
}
看起來似乎沒有太大變化?那讓我們看看一個實際場景。
真實業(yè)務(wù)中的應(yīng)用
在一個設(shè)備管理平臺中,每個設(shè)備都會有一個二維碼圖片,其地址存儲在數(shù)據(jù)庫的一個字段中。現(xiàn)需導(dǎo)出所有設(shè)備的二維碼圖片,并以設(shè)備名稱命名,最終打包成 ZIP 文件。
實現(xiàn)這一需求,需要:
- 查詢設(shè)備列表。
- 根據(jù)二維碼 URL 下載圖片并存入本地緩存。
- 處理緩存判斷,避免重復(fù)下載。
- 并發(fā)下載以提升性能。
- 下載完成后生成 ZIP 文件。
- 將 ZIP 文件寫入響應(yīng)流。
整個實現(xiàn)過程大約需要 200 行代碼,顯得十分冗長繁瑣。于是我思考是否有更簡單的方法。
其實,我們只需要提供待下載的數(shù)據(jù),比如文件路徑、文件對象、文本內(nèi)容、HTTP 地址,甚至是一個自定義對象,而無需關(guān)注下載邏輯。
于是,我們可以這樣簡化實現(xiàn):
import com.icoderoad.download.annotation.Download;
import com.icoderoad.download.annotation.SourceName;
import com.icoderoad.download.annotation.SourceObject;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
@RestController
public class DeviceDownloadController {
private final DeviceService deviceService;
public DeviceDownloadController(DeviceService deviceService) {
this.deviceService = deviceService;
}
@Download(filename = "二維碼.zip")
@GetMapping("/download")
public List<Device> downloadDevices() {
return deviceService.all();
}
}
class Device {
private String name;
@SourceObject
private String qrCodeUrl;
@SourceName
public String getQrCodeName() {
return name + ".png";
}
}
只需標注注解,系統(tǒng)會自動處理文件名稱、下載內(nèi)容、打包等邏輯,無需手動編寫大量代碼。
設(shè)計思路
這一功能的核心思想是基于 AOP 攔截下載請求,并結(jié)合 Spring WebFlux 進行異步處理。
@Download 注解說明
參數(shù) | 說明 |
| 需要下載的內(nèi)容,但是優(yōu)先級低于返回值 如果方法返回值不為 |
| 如果為 |
| 指定下載時瀏覽器上顯示的名稱 如果不指定則會獲取下載內(nèi)容的名稱,如文件則使用文件名 |
| 如果未指定,會嘗試獲取 如果嘗試獲取失敗,則默認 |
| 壓縮格式,默認 |
| 強制壓縮 如果為 |
| 如果下載包含中文的文本文件出現(xiàn)亂碼,可以嘗試指定編碼 |
| 統(tǒng)一的響應(yīng)頭,每2個為一組 |
| 額外的數(shù)據(jù),當需要自行編寫額外流程業(yè)務(wù)時可能會用到 |
整體流程
圖片
響應(yīng)式支持
為了兼容 Spring WebFlux,我們需要獲取 ServerHttpResponse,但不能直接使用 RequestContextHolder,因此可以通過 WebFilter 進行注入:
import org.springframework.web.server.ServerWebExchange;
import org.springframework.web.server.WebFilter;
import org.springframework.web.server.WebFilterChain;
import reactor.core.publisher.Mono;
public class ReactiveDownloadFilter implements WebFilter {
@Override
public Mono<Void> filter(ServerWebExchange exchange, WebFilterChain chain) {
return chain.filter(exchange)
.contextWrite(ctx -> ctx.put(ServerHttpResponse.class, exchange.getResponse()));
}
}
然后在需要的地方通過 ReactiveDownloadHolder 獲取響應(yīng)對象。
import org.springframework.web.server.ServerHttpResponse;
import reactor.core.publisher.Mono;
public class ReactiveDownloadHolder {
public static Mono<ServerHttpResponse> getResponse() {
return Mono.deferContextual(ctx -> Mono.just(ctx.get(ServerHttpResponse.class)));
}
}
處理下載任務(wù)
下載任務(wù)分為多個步驟,例如:
- 獲取文件路徑或 File 對象。
- 如果是多個文件,則先進行壓縮處理。
- 將最終文件寫入響應(yīng)流。
因此,我們采用類似 Spring Cloud Gateway 過濾鏈的方式,設(shè)計了 DownloadHandler:
import reactor.core.publisher.Mono;
public interface DownloadHandler {
Mono<Void> handle(DownloadContext context, DownloadHandlerChain chain);
}
每個 DownloadHandler 處理特定任務(wù),如下載、壓縮、寫入響應(yīng)流等。
適配多種數(shù)據(jù)源
不同類型的下載對象需要不同的處理方式,例如文件、HTTP 地址、自定義對象等,因此我們抽象出 Source 接口,并通過 SourceFactory 進行匹配。
public interface SourceFactory {
boolean support(Object source, DownloadContext context);
Source create(Object source, DownloadContext context);
}
例如:
public class FileSourceFactory implements SourceFactory {
@Override
public boolean support(Object source, DownloadContext context) {
return source instanceof File;
}
@Override
public Source create(Object source, DownloadContext context) {
return new FileSource((File) source);
}
}
結(jié)語
這個工具庫極大簡化了文件下載功能,尤其是針對復(fù)雜的批量下載需求,只需簡單的注解即可完成。如果你正在開發(fā) SpringBoot 3.4 版本的項目,并需要實現(xiàn)高效的下載功能,不妨試試這個方案!