SpringBoot实现上传下载功能

注:demo保存在码云项目中

1、设置maven依赖

  这里我们集成knife4j方便测试

<dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
            <version>2.7.8</version>
        </dependency>

        <!--knife4j start-->
        <dependency>
            <groupId>com.github.xiaoymin</groupId>
            <artifactId>knife4j-spring-boot-starter</artifactId>
            <version>2.0.9</version>
        </dependency>
        <!--knife4j end-->

        <!--上传下载 start-->
        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-compress</artifactId>
            <version>1.20</version>
        </dependency>

        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.18.24</version>
        </dependency>

        <dependency>
            <groupId>cn.hutool</groupId>
            <artifactId>hutool-all</artifactId>
            <version>5.7.22</version>
        </dependency>

        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-lang3</artifactId>
            <version>3.12.0</version>
        </dependency>
        <!--上传下载 end-->

2、设置上传的相关配置

application.yml

spring:
  application:
    name: demo
  profiles:
    active: dev
  servlet:
    multipart:
      max-request-size: 300MB
      max-file-size: 300MB

server:
  servlet:
    context-path: /demo
  port: 8000

knife4j:
  enable: true

application-dev.yml

spring:
  servlet: #上传文件使用
    multipart:
      max-file-size: 100MB      #单个文件最大上传大小
      max-request-size: 1024MB  #每次请求上传文件大小最大值

#自定义参数,获取值@Value("${define..uploadPath}")
define:
  uploadPath: C:\Users\admin\Desktop\file_upload\  #文件上传根路径:用户上传的文件 存储到这里
  downPath: C:\Users\admin\Desktop\file_upload\    #文件下载根路径

knife4j配置

@Configuration
@EnableSwagger2WebMvc
public class Knife4jConfiguration {

    @Bean(value = "defaultApi2")
    public Docket defaultApi2() {
        Docket docket=new Docket(DocumentationType.SWAGGER_2)
                .apiInfo(new ApiInfoBuilder()
                        //.title("swagger-bootstrap-ui-demo RESTful APIs")
                        .description("# swagger-bootstrap-ui-demo RESTful APIs")
                        .termsOfServiceUrl("http://www.xx.com/")
                        .contact("xx@qq.com")
                        .version("1.0")
                        .build())
                //分组名称
                .groupName("2.X版本")
                .select()
                //这里指定Controller扫描包路径
                .apis(RequestHandlerSelectors.basePackage("com.sc.controller"))
                .paths(PathSelectors.any())
                .build();
        return docket;
    }
}
@Configuration
public class WebMvcConfigurer extends WebMvcConfigurationSupport {

    @Override
    public void addResourceHandlers(ResourceHandlerRegistry registry) {
        registry.addResourceHandler("/**").addResourceLocations("classpath:/static/");
        registry.addResourceHandler("doc.html").addResourceLocations("classpath:/META-INF/resources/");
        registry.addResourceHandler("/webjars/**").addResourceLocations("classpath:/META-INF/resources/webjars/");
        super.addResourceHandlers(registry);
    }

    @Bean
    public HttpMessageConverter<String> responseBodyConverter() {
        return new StringHttpMessageConverter(Charset.forName("UTF-8"));
    }

    @Override
    public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
        converters.add(responseBodyConverter());
    }
}

3、上传下载工具类,可以在任意的项目中进行使用

import org.apache.commons.compress.archivers.ArchiveEntry;
import org.apache.commons.compress.archivers.zip.Zip64Mode;
import org.apache.commons.compress.archivers.zip.ZipArchiveEntry;
import org.apache.commons.compress.archivers.zip.ZipArchiveOutputStream;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.*;
import java.net.HttpURLConnection;
import java.net.URL;
import java.net.URLEncoder;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.List;
import java.util.Map;
 
/**
 * 文件上传下载工具类
 */
public class FileUploadDownUtils {
    /**
     * 文件上传:用户将自己电脑上的的文件 上传到服务器filePath路径下
     * @param file :文件
     * @param filePath:文件上传地址
     * @param fileName:文件名称
     */
    public static void saveFile(byte[] file, String filePath, String fileName) throws Exception {
        File targetFile = new File(filePath);
        if (!targetFile.exists()) {
            targetFile.mkdirs();
        }
        FileOutputStream out = new FileOutputStream(filePath + fileName);
        out.write(file);
        out.flush();
        out.close();
    }
 
    /**
     * 本地单个文件下载
     * @param file
     * @param request
     * @param response
     * @throws UnsupportedEncodingException
     */
    public static void downloadFile(File file, HttpServletRequest request,HttpServletResponse response) {
        if (!file.exists() || file.length() <= 0L) {
            throw new RuntimeException("文件为空或不存在!");
        }
        FileInputStream fis = null;
        BufferedInputStream bis = null;
        OutputStream os = null;
        try {
            boolean isPreview = "preview".equalsIgnoreCase(request.getParameter("source"));
            String s = (!isPreview ? "attachment;" : "") + "filename=" + URLEncoder.encode(file.getName(), "UTF-8");
            response.setHeader("Content-Disposition", s.trim());

            os = response.getOutputStream();
            fis = new FileInputStream(file);
            bis = new BufferedInputStream(fis);
            byte[] buffer = new byte[bis.available()];
            int i = bis.read(buffer);
            while(i != -1){
                os.write(buffer, 0, i);
                i = bis.read(buffer);
            }
        } catch (Exception e) {
            throw new RuntimeException("单个文件下载异常");
        }finally {
            try {
                if (bis!=null){
                    bis.close();
                }
                if (fis!=null){
                   fis.close();
                }
                if (os!=null){
                   os.close();
                }
            } catch (IOException e) {
                throw new RuntimeException("关闭流处理异常");
            }
        }
    }
 
    /**
     * 本地多个文件下载(打包zip)
     * @param fileList
     * @param response
     */
    public static void downloadFileZip(String zipName,List<File> fileList, HttpServletResponse response) {
        for (File file : fileList) {
            if (file == null || !file.exists() || file.length() <= 0L) {
                throw new RuntimeException("文件为空或不存在!");
            }
        }
        ZipArchiveOutputStream zout = null;
        try {
            //1、设置response参数并且获取ServletOutputStream
            zout = getServletOutputStream(zipName,response);
            for (File file : fileList) {
                InputStream in = new FileInputStream(file);
                //2、设置字节数组输出流,并开始输出
                setByteArrayOutputStream(file.getName(),in,zout);
            }
        } catch (Exception e) {
            throw new RuntimeException("多个文件打包成zip,下载异常");
        }finally {
            try {
                if (zout != null) {
                    zout.close();
                }
            } catch (IOException e) {
                throw new RuntimeException("关闭流处理异常");
            }
        }
    }
 
    /**
     * 网络单个文件下载
     *
     * @param urlStr
     * @param request
     * @param response
     * @param fileName
     * @return
     */
    public static void downloadHttpFile(String urlStr, HttpServletRequest request, HttpServletResponse response, String fileName) {
        ServletOutputStream out = null;
        InputStream in = null;
        try {
            //1、通过网络地址获取文件InputStream
            in = getInputStreamFromUrl(urlStr);
            //2、FileInputStream 转换为byte数组
            byte[] getData = inputStreamToByte(in);
            out = response.getOutputStream();
            long contentLength = getData.length;
            // 3、设置Response
            setResponse(fileName, contentLength, request, response);
            out.write(getData);
            out.flush();
        } catch (Exception e) {
            throw new RuntimeException("下载失败!");
        } finally {
            try {
                if (out != null) {
                    out.close();
                }
                if (in != null) {
                    in.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
 
    /**
     * 网络多个文件下载(打包zip)
     *
     * @param pathList
     * @param request
     * @param response
     */
    public static void downloadHttpFileZip(String zipName,List<Map<String, String>> pathList, HttpServletRequest request, HttpServletResponse response) {
        try {
            // 1、设置response参数并且获取ServletOutputStream
            ZipArchiveOutputStream zous = getServletOutputStream(zipName,response);
            for (Map<String, String> map : pathList) {
                String fileName = map.get("name");
                //2、通过网络地址获取文件InputStream
                InputStream inputStream = getInputStreamFromUrl(map.get("path"));
                //3、设置字节数组输出流,并开始输出
                setByteArrayOutputStream(fileName, inputStream, zous);
            }
            zous.close();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
 
    //设置response参数并且获取ServletOutputStream
    private static ZipArchiveOutputStream getServletOutputStream(String zipName,HttpServletResponse response){
        String outputFileName = zipName + new SimpleDateFormat("yyyyMMddHHmmss").format(new Date()) + ".zip";
        response.reset();
        response.setHeader("Content-Type", "application/octet-stream");
        try {
            response.setHeader("Content-Disposition", "attachment;filename=" + URLEncoder.encode(outputFileName, "UTF-8"));
        } catch (UnsupportedEncodingException e) {
            throw new RuntimeException("设置Content-Disposition失败");
        }
        response.setHeader("Pragma", "no-cache");
        response.setHeader("Cache-Control", "no-cache");
        ServletOutputStream out = null;
        try {
            out = response.getOutputStream();
        } catch (IOException e) {
            throw new RuntimeException("获取ServletOutputStream失败");
        }
        ZipArchiveOutputStream zout = new ZipArchiveOutputStream(out);
        zout.setUseZip64(Zip64Mode.AsNeeded);
        return zout;
    }
 
    //通过网络地址获取文件InputStream
    private static InputStream getInputStreamFromUrl(String path) {
        URL url = null;
        InputStream is = null;
        try {
            url = new URL(path);
            HttpURLConnection conn = (HttpURLConnection) url.openConnection();
            conn.setDoInput(true);
            conn.connect();
            is = conn.getInputStream();
        } catch (IOException e) {
            throw new RuntimeException("网络地址获取文件失败:"+ url);
        }
        return is;
    }
 
    //FileInputStream 转换为byte数组
    public static byte[] inputStreamToByte(InputStream inputStream) {
        try {
            byte[] buffer = new byte[1024];
            int len = 0;
            ByteArrayOutputStream bos = new ByteArrayOutputStream();
            while ((len = inputStream.read(buffer)) != -1) {
                bos.write(buffer, 0, len);
            }
            bos.close();
            return bos.toByteArray();
        } catch (Exception e) {
            throw new RuntimeException("文件转换失败!");
        }
    }
 
    /**
     * @param fileName
     * @param contentLength
     * @param request
     * @param response
     * @return
     */
    public static void setResponse(String fileName, long contentLength, HttpServletRequest request, HttpServletResponse response) {
        try {
            boolean isPreview = "preview".equalsIgnoreCase(request.getParameter("source"));
            response.addHeader("Content-Disposition", (!isPreview ? "attachment;" : "") + "filename=" + URLEncoder.encode(fileName, "UTF-8"));
            response.setHeader("Accept-Ranges", "bytes");
 
            String range = request.getHeader("Range");
            if (range == null) {
                response.setHeader("Content-Length", String.valueOf(contentLength));
            } else {
                response.setStatus(206);
                long requestStart = 0L;
                long requestEnd = 0L;
                String[] ranges = range.split("=");
                if (ranges.length > 1) {
                    String[] rangeDatas = ranges[1].split("-");
                    requestStart = Long.parseLong(rangeDatas[0]);
                    if (rangeDatas.length > 1) {
                        requestEnd = Long.parseLong(rangeDatas[1]);
                    }
                }
                long length = 0L;
                if (requestEnd > 0L) {
                    length = requestEnd - requestStart + 1L;
                    response.setHeader("Content-Length", String.valueOf(length));
                    response.setHeader("Content-Range", "bytes " + requestStart + "-" + requestEnd + "/" + contentLength);
                } else {
                    length = contentLength - requestStart;
                    response.setHeader("Content-Length", String.valueOf(length));
                    response.setHeader("Content-Range", "bytes " + requestStart + "-" + (contentLength - 1L) + "/" + contentLength);
                }
            }
        } catch (Exception e) {
            throw new RuntimeException("response响应失败!");
        }
    }
 
    //设置字节数组输出流,并开始输出
    private static void setByteArrayOutputStream(String fileName, InputStream in, ZipArchiveOutputStream zout){
        try {
            ByteArrayOutputStream baos = new ByteArrayOutputStream();
            byte[] buffer = new byte[1024];
            int len;
            while ((len = in.read(buffer)) != -1) {
                baos.write(buffer, 0, len);
            }
            baos.flush();
            byte[] bytes = baos.toByteArray();
            //设置文件名
            ArchiveEntry entry = new ZipArchiveEntry(fileName);
            zout.putArchiveEntry(entry);
            zout.write(bytes);
            zout.closeArchiveEntry();
            baos.close();
            in.close();
        } catch (Exception e) {
            throw new RuntimeException("设置字节数组输出流失败!");
        }
    }
 
}

4、编写controller测试一下

import cn.hutool.core.collection.CollectionUtil;
import com.github.xiaoymin.knife4j.annotations.ApiOperationSupport;
import com.sc.utils.uploadDown.FileUploadDownUtils;
import io.swagger.annotations.*;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.File;
import java.io.UnsupportedEncodingException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
 
@RestController
@RequestMapping("/upload")
@Api(tags = "上传下载模块")
public class UploadController {

    @Value("${define.uploadPath}")
    private String uploadPath;

    @Value("${define.downPath}")
    private String downPath;
 
    @Resource
    private HttpServletResponse response;
 
    @Resource
    private HttpServletRequest request;
 
    /**
     * 单文件上传
     * @param file
     * @return
     */
    @PostMapping("/uploadFile")
    @ApiOperationSupport(order = 1)
    @ApiOperation(value = "单文件上传")
    @ApiImplicitParams(
            {@ApiImplicitParam(name = "file", value = "文件流对象", required = true,dataType = "MultipartFile")}
    )
    public String singleFileUpload(@RequestParam("file") MultipartFile file) {
        try {
            if (file == null || StringUtils.isBlank(file.getOriginalFilename())) {
                return "上传的文件为空";
            }
            FileUploadDownUtils.saveFile(file.getBytes(), uploadPath, file.getOriginalFilename());
        } catch (Exception e) {
            return "文件上传失败";
        }
        return "文件上传成功";
    }

    /**
     * 批量文件上传,按住ctrl可以选择多个文件
     * @param files
     * @return
     */
    @PostMapping("/uploadFiles")
    @ApiOperationSupport(order = 2)
    @ApiOperation(value = "批量文件上传")
    @ApiImplicitParams(
            {@ApiImplicitParam(name = "file[]", value = "文件流对象,接收数组格式", required = true,dataType = "MultipartFile",allowMultiple = true)}
    )
    public String multiFileUpload(@RequestParam(value="file[]",required = true) MultipartFile[] files) {
        try {
            for (int i = 0; i < files.length; i++) {
                //check file
                if (StringUtils.isBlank(files[i].getOriginalFilename())) {
                    continue;
                }
                FileUploadDownUtils.saveFile(files[i].getBytes(), uploadPath, files[i].getOriginalFilename());
            }
        } catch (Exception e) {
            return "文件批量上传失败";
        }
        return "文件批量上传成功";
    }


    /**
     * 本地单个文件下载
     * @throws UnsupportedEncodingException
     */
    @GetMapping(value = "/download/file")
    @ApiOperationSupport(order = 3)
    @ApiOperation(value = "本地单个文件下载",produces = MediaType.APPLICATION_OCTET_STREAM_VALUE)
    public void downloadFile(@ApiParam(name = "fileName", value = "本地单个文件名称", required = true) @RequestParam(value = "fileName") String fileName){
        //文件本地位置
        String filePath = downPath+fileName;
        File file = new File(filePath);
        //文件下载
        FileUploadDownUtils.downloadFile(file, request, response);
    }


    /**
     * 本地多个文件下载(打包zip)
     */
    @PostMapping(value = "/download/zip")
    @ApiOperationSupport(order = 4)
    @ApiOperation(value = "本地多个文件下载",produces = MediaType.APPLICATION_OCTET_STREAM_VALUE)
    public void downloadFileZip(@ApiParam(name = "strs", value = "本地多个文件名称", required = true) @RequestBody List<String> strs) {
        //设置输出文件名字
        String zipName = "test";
        //文件本地位置
        if (CollectionUtil.isEmpty(strs)){
            return;
        }
        List<File> fileList = new ArrayList<>();
        for (String fileName : strs) {
            fileList.add(new File(downPath+fileName));
        }
        //文件下载
        FileUploadDownUtils.downloadFileZip(zipName,fileList,response);
    }
 
    /**
     * 网络单个文件下载
     * 测试地址  https://code.jquery.com/jquery-3.6.0.js
     */
    @GetMapping(value = "/download/httpFile")
    @ApiOperationSupport(order = 5)
    @ApiOperation(value = "网络单个文件下载", notes = "网络单个文件下载",produces = MediaType.APPLICATION_OCTET_STREAM_VALUE)
    public void downloadHttpFile(@ApiParam(name = "urlStr", value = "网络单个文件名称", required = true) @RequestParam(value = "urlStr") String urlStr) {
        // 文件名称
        String fileName = "测试文件.js";
        FileUploadDownUtils.downloadHttpFile(urlStr, request, response, fileName);
    }
 
    /**
     * 网络多个文件下载(打包zip)
     * https://code.jquery.com/jquery-3.6.0.js
     * https://code.jquery.com/jquery-3.6.0.js
     */
    @PostMapping(value = "/download/httpFile/zip")
    @ApiOperationSupport(order = 6)
    @ApiOperation(value = "网络多个文件下载(打包zip)",produces = MediaType.APPLICATION_OCTET_STREAM_VALUE)
    public void downloadHttpFileZip(@ApiParam(name = "strs", value = "本地多个文件名称", required = true) @RequestBody List<String> strs) {
        if (CollectionUtil.isEmpty(strs)){
            return;
        }

        List<Map<String, String>> mapList = new ArrayList<>();
        for (int i =0 ;i<strs.size();i++) {
            Map<String, String> map = new HashMap<>();
            map.put("path", strs.get(i));
            map.put("name", "测试文件"+i+".js");
            mapList.add(map);
        }

        String zipName = "test";
        FileUploadDownUtils.downloadHttpFileZip(zipName,mapList, request, response);
    }
 
}

注:下载时,Knife4j的一个小问题

经过测试Content-Disposition filename前带空格会导致文件下载始终是Knife4j.txt 

查看了下UI的源码,filename 前有空格,就会解析不到文件名

@ApiOperation(value = "Excel下载[流下载]", produces = ApiEnum.PRODUCES_FILE)
    public void downloadExcel2() throws IOException {
        HttpServletResponse response = this.getResponse();

        File file = new File(PathKit.getWebRootPath() + "/temp/test.xlsx");

        response.setContentType("application/octet-stream");
        // UI点下载文件 正常下载文件名
//        response.addHeader("Content-Disposition", "attachement;filename=test.xlsx");

        // UI点下载文件, 始终为Knife4j.txt(filename前面有空格 )
//        response.addHeader("Content-Disposition", "attachement; filename=test.xlsx");

        // UI点下载文件, 下载文件名为 _test.xlsx_(filename 文件名带双引号)
//        response.addHeader("Content-Disposition", "attachement;filename=\"test.xlsx\"");

        // UI点下载文件, 始终为Knife4j.txt (JFinal renderFile 的格式)
        response.addHeader("Content-Disposition", "attachement; filename=\"test.xlsx\"");

        OutputStream outputStream = response.getOutputStream();
        FileInputStream fileInputStream = new FileInputStream(file);
        int i = -1;
        byte[] b = new byte[1024 * 1024];
        while ((i = fileInputStream.read(b)) != -1) {
            outputStream.write(b, 0, i);
        }
        fileInputStream.close();
        outputStream.close();

        this.renderNull();
    }
  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值