太方便了!SpringBoot 只需一个注解,就能搞定任意对象下载!

开发 前端
​在日常开发中,文件下载是一个常见的功能,虽然在项目中出现的频率可能不算太高,但几乎每个项目都会涉及。而有些下载需求相对复杂,虽然不是难点,但实现起来却十分繁琐。

在日常开发中,文件下载是一个常见的功能,虽然在项目中出现的频率可能不算太高,但几乎每个项目都会涉及。而有些下载需求相对复杂,虽然不是难点,但实现起来却十分繁琐。

因此,为了简化这一过程,有一个工具库,使得下载功能的实现变得更加简单快捷。

https://github.com/Linyuzai/concept/wiki/Concept-Download

一键下载任意对象

如果告诉你,现在仅需一个注解就能轻松下载任意对象,你会不会觉得很方便?

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";
    }
}

看起来似乎没有太大变化?那让我们看看一个实际场景。

真实业务中的应用

在一个设备管理平台中,每个设备都会有一个二维码图片,其地址存储在数据库的一个字段中。现需导出所有设备的二维码图片,并以设备名称命名,最终打包成 ZIP 文件。

实现这一需求,需要:

  1. 查询设备列表。
  2. 根据二维码 URL 下载图片并存入本地缓存。
  3. 处理缓存判断,避免重复下载。
  4. 并发下载以提升性能。
  5. 下载完成后生成 ZIP 文件。
  6. 将 ZIP 文件写入响应流。

整个实现过程大约需要 200 行代码,显得十分冗长繁琐。于是我思考是否有更简单的方法。

其实,我们只需要提供待下载的数据,比如文件路径、文件对象、文本内容、HTTP 地址,甚至是一个自定义对象,而无需关注下载逻辑。

于是,我们可以这样简化实现:

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";
    }
}

只需标注注解,系统会自动处理文件名称、下载内容、打包等逻辑,无需手动编写大量代码。

设计思路

这一功能的核心思想是基于 AOP 拦截下载请求,并结合 Spring WebFlux 进行异步处理。

@Download 注解说明

参数

说明

source

需要下载的内容,但是优先级低于返回值 如果方法返回值不为null则会使用返回值作为下载的内容

inline

如果为true,可以直接在浏览器预览 需要配合contentType,如图片或视频,默认false 视频文件目前存在一些问题,还在测试阶段

filename

指定下载时浏览器上显示的名称 如果不指定则会获取下载内容的名称,如文件则使用文件名

contentType

如果未指定,会尝试获取 如果尝试获取失败,则默认application/octet-stream 或application/x-zip-compressed

compressFormat

压缩格式,默认zip

forceCompress

强制压缩 如果为true,不管下载的文件有几个都会压缩 如果为false,有多个文件时压缩,只有一个文件时不压缩 默认false

charset

如果下载包含中文的文本文件出现乱码,可以尝试指定编码

headers

统一的响应头,每2个为一组

extra

额外的数据,当需要自行编写额外流程业务时可能会用到

整体流程

图片图片

响应式支持

为了兼容 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 获取响应对象。

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)));
    }
}

处理下载任务

下载任务分为多个步骤,例如:

  1. 获取文件路径或 File 对象。
  2. 如果是多个文件,则先进行压缩处理。
  3. 将最终文件写入响应流。

因此,我们采用类似 Spring Cloud Gateway 过滤链的方式,设计了 DownloadHandler:

import reactor.core.publisher.Mono;

public interface DownloadHandler {
    Mono<Void> handle(DownloadContext context, DownloadHandlerChain chain);
}

每个 DownloadHandler 处理特定任务,如下载、压缩、写入响应流等。

适配多种数据源

不同类型的下载对象需要不同的处理方式,例如文件、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);
    }
}

结语

这个工具库极大简化了文件下载功能,尤其是针对复杂的批量下载需求,只需简单的注解即可完成。如果你正在开发 SpringBoot 3.4 版本的项目,并需要实现高效的下载功能,不妨试试这个方案!

责任编辑:武晓燕 来源: 路条编程
相关推荐

2022-09-14 10:16:12

MyBatis加密解密

2024-10-17 11:09:46

2022-05-26 10:42:30

数据权限注解

2024-09-27 15:24:15

Spring数据加解密

2022-06-14 10:47:27

项目日志PUT

2022-06-27 08:36:27

分布式事务XA规范

2022-07-15 14:26:36

开源工具IP

2021-10-19 18:22:50

Map 注册表源码

2021-09-24 15:00:26

微信PC电脑移动应用

2021-11-23 23:01:40

Windows微软系统

2024-02-19 00:21:45

开源图片

2021-09-24 09:59:59

复制粘贴PythonPDF

2019-07-24 10:50:56

Python 开发编程语言

2021-02-08 11:46:17

Python自动化邮件

2020-01-07 11:30:50

图像识别AI人工智能

2021-10-25 22:50:04

手机系统设置

2021-10-22 22:38:01

手机系统技术

2024-04-15 00:00:02

OpenAI模型性能

2023-11-27 07:33:55

2023-02-20 10:16:20

ChatGPT模型
点赞
收藏

51CTO技术栈公众号