编者注
目的是编写一个带有权限控制的文件访问功能,直接想到集成到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);
}
}