Spring 流式下载文件

编者注

目的是编写一个带有权限控制的文件访问功能,直接想到集成到Spring中。这里和之前的apache或nigix指定url不同的在于需要手动编写。先解决的是url的mapping问题,之后仿照网络代码快速实现,最后查看由于使用bytes会导致性能问题,则修改为stream的方式解决。

Upload

实现原理

Spring通过CommonsMultipartFile来Upload文件,其实现通过apache-fileupload来实现。
实现原理,接收request时,形成多个FileItem,在由多个FileItem,实施item.write获取通过item.getInputStream来实现。

代码

    @RequestMapping(method = RequestMethod.POST)
    public ResponseEntity uploadPreview(@RequestParam("file")CommonsMultipartFile multipartFile, UriComponentsBuilder uriBuilder) throws IOException {

        HttpHeaders httpheader = new HttpHeaders();
        ResponseEntity responseEntity;

        // done: file storage where
        Date now = new Date();
        String save_path= storageConfig.getPath() + dayFormat.format(now) + "/" + saveFormat.format(now) + "_"+ multipartFile.getOriginalFilename();
        System.out.println("[save_path]:" + save_path);
        // save file
        String normalized = FilenameUtils.normalize(save_path);
        File saveFile = new File(normalized);
        // 创建这个文件的parent目录
        FileUtils.forceMkdirParent(saveFile);
        System.out.println("[file.path]:" + saveFile.getPath());
        multipartFile.transferTo(saveFile);

        // create new entity
        org.aicfve.global.snapshot.entity.File entity = new org.aicfve.global.snapshot.entity.File();

        //multipartFile.getFileItem(); ??
        entity.setOriginalFilename(multipartFile.getOriginalFilename());
        entity.setName(multipartFile.getName());
        entity.setContentType(multipartFile.getContentType());
        entity.setSize(multipartFile.getSize());
        entity.setSavepath(save_path);

        fileService.create(entity);

        // todo: return new version url
        //通过反射获取entity的url
        String EntityURL = Entity.URL(this) + "/" + entity.getId();//snapshot.getVersion();
        //生成Respone header显示Entity创建后的资源路径
        URI uri = uriBuilder.path(EntityURL).build().toUri();
        httpheader.setLocation(uri);
        responseEntity = new ResponseEntity(httpheader, HttpStatus.CREATED);

        return responseEntity;
    }

Download

URL Mapping

首先在RequestMapping当中应当使用/**的方式,把所有Controller下的url引导到这个方法中。

    @RequestMapping(value = "/**")
    public ResponseEntity download(HttpServletRequest request) throws IOException {
        logger.debug("{} method {} header {}",this.getClass(),request);

        // get url
        String uri = request.getRequestURI();
        ...
    }

这里使用HttpServletRequest来获取完整的URI,后续必须对URI和本地存储进行转换

ResponseEntity 非流式

直接找的网路代码,能够明显发现是使用的byte [],首先由于数组的扩容问题,会导致性能问题,其次,对内存的占用也是非常高的。

        // get File
        File download_file = new File(URLDecoder.decode(local_path,"UTF-8"));
        if(!download_file.exists())
        {
            // 没有找到文件
            return new ResponseEntity<byte[]>(HttpStatus.NO_CONTENT);
        }

        //make httpheader
        HttpHeaders httpHeaders = new HttpHeaders();
        httpHeaders.setContentType(MediaType.APPLICATION_OCTET_STREAM);
        httpHeaders.setContentDispositionFormData("attachment",download_file.getName());
        return new ResponseEntity<byte []>(FileUtils.readFileToByteArray(download_file),httpHeaders,HttpStatus.CREATED);
    }

ResponseEntity流式

这里使用spring已经实现的InputStreamResource,这个方式是实现静态资源访问的实现。

        long contentLength = download_file.length();

        FileInputStream fileInputStream = new FileInputStream(download_file);

        // file inputstream
        InputStreamResource inputStreamResource = new InputStreamResource(fileInputStream);

        //make httpheader
        HttpHeaders httpHeaders = new HttpHeaders();
        httpHeaders.setContentType(MediaType.APPLICATION_OCTET_STREAM);
        httpHeaders.setContentDispositionFormData("attachment",download_file.getName());
        httpHeaders.setContentLength(contentLength);
        return new ResponseEntity(inputStreamResource,httpHeaders,HttpStatus.CREATED);

Download完整代码

package org.aicfve.asset;

import org.aicfve.config.StorageConfig;
import org.apache.commons.io.FileUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.io.InputStreamResource;
import org.springframework.http.*;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;

import javax.servlet.http.HttpServletRequest;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.URLDecoder;

@Controller
@RequestMapping(value="/assets")
public class AssetController {

    Logger logger = LoggerFactory.getLogger(AssetController.class);

    @Autowired
    StorageConfig storageConfig;

    @RequestMapping(value = "/**")
    public ResponseEntity download(HttpServletRequest request) throws IOException {
        logger.debug("{} method {} header {}",this.getClass(),request);

        // get url
        String uri = request.getRequestURI();

        // convert local path

        String local_path = storageConfig.getPath() + uri.replaceFirst("^/assets/","");

        // get File
        File download_file = new File(URLDecoder.decode(local_path,"UTF-8"));
        if(!download_file.exists())
        {
            // 没有找到文件
            return new ResponseEntity<byte[]>(HttpStatus.NO_CONTENT);
        }

        long contentLength = download_file.length();

        FileInputStream fileInputStream = new FileInputStream(download_file);

        // file inputstream
        InputStreamResource inputStreamResource = new InputStreamResource(fileInputStream);

        //make httpheader
        HttpHeaders httpHeaders = new HttpHeaders();
        httpHeaders.setContentType(MediaType.APPLICATION_OCTET_STREAM);
        httpHeaders.setContentDispositionFormData("attachment",download_file.getName());
        httpHeaders.setContentLength(contentLength);
        return new ResponseEntity(inputStreamResource,httpHeaders,HttpStatus.CREATED);
    }
}

转载于:https://my.oschina.net/hava/blog/1576190

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值