这里核心思想是使用​​ResponseEntity<T>​​其继承自​​HttpEntity<T>​​并增加了status。 其可以在Spring MVC框架中作为方法返回值使用。同HttpEntity一样,​​ResponseEntity​​代表了一个请求/响应实体对象,该对象包含了header和body,即请求(响应)头和请求(响应)体,另外还有Status。

​ResponseEntity类似于@ResponseBody,但有Status和header​​。例如:

@GetMapping("/something")
public ResponseEntity<String> handle() {
String body = ... ;
String etag = ... ;
return ResponseEntity.ok().eTag(etag).build(body);
}

如下所示,直接返回结果到response,不再跳页面:

@PostMapping("test")
public ResponseEntity testMap(@RequestParam List name) throws URISyntaxException {
HttpHeaders responseHeaders = new HttpHeaders();
responseHeaders.setLocation(new URI("/test/location"));
responseHeaders.set("MyResponseHeader", "MyValue");
ResponseEntity<String> responseEntity = new ResponseEntity<>("hello", responseHeaders, HttpStatus.CREATED);
return responseEntity;
}

Spring MVC支持使用单值反应类型异步生成响应,和/或为主体生成单值和多值反应类型。这允许以下类型的异步响应:

  • ​ResponseEntity<Mono<T>>​​​ 或​​ResponseEntity<Flux<T>>​​可在稍后异步提供主体时立即了解响应状态和头。如果主体由0…1个值组成,则使用Mono;如果主体可以生成多个值,则使用Flux。
  • ​Mono<ResponseEntity<T>>​​​提供了这三种功能 —​​response status, headers, and body​​​。这允许​​response status and headers​​根据异步请求处理的结果而变化。

基于SpringMVC的文件下载,xml不需要额外配置,默认使用​​HttpMessageConverter​​​进行信息解析。具体点击查看HttpMessageConverter信息解析

① 文件下载实例

/* 
* @param fileName--下载文件名
* @param filePath--文件路径+原始文件名
* @return
*/
@RequestMapping("/fileDown")
public ResponseEntity<byte[]> fileDown(String fileName,String filePath){

byte [] body = null;

try {
InputStream in = new FileSystemResource(filePath).getInputStream();
body = new byte[in.available()];
in.read(body);
} catch (IOException e1) {
log.debug("文件读入出错,文件路径为:"+lastFilePath);
e1.printStackTrace();
}

//添加响应头
HttpHeaders headers = new HttpHeaders();
try {
fileName = URLDecoder.decode(fileName, "UTF-8");
log.debug("获取的文件名为:"+new String(fileName.getBytes("ISO8859-1"),"UTF-8"));
} catch (UnsupportedEncodingException e1) {
// TODO Auto-generated catch block
e1.printStackTrace();
}
//这里fileName有可能出现下载文件乱码-需要自己处理
headers.add("Content-Disposition", "attachment;filename="+fileName);

headers.setContentType(MediaType.APPLICATION_OCTET_STREAM);

HttpStatus statusCode = HttpStatus.OK;

ResponseEntity<byte[]> response = new ResponseEntity<byte[]>(body, headers, statusCode);

return response;
}

可以看到这里主要思想就是返回一个​​ResponseEntity<byte[]>​​,body里面放了文件流。


② 文件下载与表格整合

需求:以某个已经存在的模板为参考,导出所需的数据。

代码如下:

//导出预约列表
@RequestMapping("/downloadList")
public ResponseEntity<byte[]> fileDown(){
Page<SysUserAppoint> appointList = userAppointService.findPageListByYear();
try {
InputStream inputStream = getClass().getClassLoader().getResourceAsStream("exportAppointTemplate.xls");
String newFileName= UUID.randomUUID().toString().replaceAll("-","")+".xls";
boolean fileCopy = FileUtil.fileCopy(inputStream, baseFilePath, newFileName);
//从新文件中获取流
File file = new File(baseFilePath + File.separator + newFileName);
inputStream= new FileInputStream(file);
//获取hssfWorkbook对象
HSSFWorkbook hssfWorkbook = new HSSFWorkbook(inputStream);
HSSFSheet sheet=hssfWorkbook.getSheetAt(0);
//填充表格数据
//...这里写你的代码
String exportName=UUID.randomUUID().toString().replaceAll("-","")+"exportAppoint.xls";
//创建一个字节输出流,将表格对象写到该流中并获取ByteArray给ResponseEntity使用
ByteArrayOutputStream outByteStream = new ByteArrayOutputStream();
hssfWorkbook.write(outByteStream);
//添加响应头

HttpHeaders headers = new HttpHeaders();
headers.add("Content-Disposition", "attachment;filename="+exportName);
headers.setContentType(MediaType.APPLICATION_OCTET_STREAM);
HttpStatus statusCode = HttpStatus.OK;
ResponseEntity<byte[]> response = new ResponseEntity<byte[]>(outByteStream.toByteArray(), headers, statusCode);
return response;
} catch (Exception e1) {
logger.debug("文件读入出错,文件路径为:exportAppointTemplate.xls");
e1.printStackTrace();
return null;
}
}

使用​​ByteArrayOutputStream​​​装在文件流,然后封装​​ResponseEntity​​​时转换为字节数组给​​ResponseEntity<byte[]>​

③ 获取网络图片

这里基本原理是获取HttpURLConnection然后读取字节流并保存到本地。需要注意的是,网络请求可能发生重定向,需要自己处理重定向请求。

//获取网络图片保存到本地
public static String saveFileByUrl(String imgUrl,String baseFilePath) throws IOException {
FileOutputStream out = null;
BufferedInputStream in = null;
HttpURLConnection connection = null;
byte[] buf = new byte[10240];
int len = 0;
String saveFileName= "";
try {
String redirectUrl = getRedirectUrl(imgUrl);
if(StringUtils.isEmpty(redirectUrl)){
redirectUrl=imgUrl;
}
String extension=StringUtils.isEmpty(getExtension(redirectUrl))?"jpg":getExtension(redirectUrl);
saveFileName= UUID.randomUUID().toString().replaceAll("-","")+"."+extension;
URL url = new URL(redirectUrl);
connection = (HttpURLConnection)url.openConnection();
connection.setInstanceFollowRedirects(false);
connection.setConnectTimeout(15000);
connection.connect();
in = new BufferedInputStream(connection.getInputStream());
File saveFile=new File(baseFilePath+File.separator+saveFileName);
if(!saveFile.getParentFile().exists()){
saveFile.getParentFile().mkdirs();
}
out = new FileOutputStream(saveFile);
logger.debug("字节流大小:{}",in.available());
while ((len = in.read(buf)) != -1) {
out.write(buf, 0, len);
logger.debug("当前读取字节长度:{},当前byte[]:{}",len,buf.length);
}
out.flush();
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
in.close();
out.close();
connection.disconnect();
} catch (IOException e) {
e.printStackTrace();
}
}
return saveFileName;
}

//获取重定向后的URL
private static String getRedirectUrl(String path){
HttpURLConnection connection=null;
try {
URL url = new URL(path);
connection = (HttpURLConnection)url.openConnection();
//设置是否请求自动处理重定向,设置为false,自己获取重定向url进行解析
connection.setInstanceFollowRedirects(false);
connection.setConnectTimeout(15000);
//从响应头里面获取Location,也就是目标重定向地址
String location = connection.getHeaderField("Location");
logger.debug("connection.getHeaderField(\"Location\"):{}",location);
return location;
} catch (IOException e) {
e.printStackTrace();
} finally {
connection.disconnect();
}
return null;
}

④ 下载文件名兼容处理

主要为了处理下载的文件在不同浏览器中文乱码问题。

/**
* 下载文件名重新编码
* @param request 请求对象
* @param fileName 文件名
* @return 编码后的文件名
*/
public static String setFileDownloadHeader(HttpServletRequest request, String fileName) throws UnsupportedEncodingException
{
final String agent = request.getHeader("USER-AGENT");
String filename = fileName;
if (agent.contains("MSIE"))
{
// IE浏览器
filename = URLEncoder.encode(filename, "utf-8");
filename = filename.replace("+", " ");
}
else if (agent.contains("Firefox"))
{
// 火狐浏览器
filename = new String(fileName.getBytes(), "ISO8859-1");
}
else if (agent.contains("Chrome"))
{
// google浏览器
filename = URLEncoder.encode(filename, "utf-8");
}
else
{
// 其它浏览器
filename = URLEncoder.encode(filename, "utf-8");
}
return filename;
}

⑤ 常见的文件复制

public static void copyFile(InputStream in, String destAllPath) {
FileOutputStream outputStream=null;
try {
File file = new File(destAllPath);
if(!file.getParentFile().exists()){
file.getParentFile().mkdirs();
}

if (!file.exists()){
file.createNewFile();
}else{
file.delete();
file.createNewFile();
}
outputStream=new FileOutputStream(file);
byte[] b = new byte[1024];
int length;
while ((length = in.read(b)) !=-1){
outputStream.write(b, 0, length);
}
outputStream.flush();
} catch (IOException e) {
e.printStackTrace();
} finally {
if(in!=null){
try {
in.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if(outputStream!=null){
try {
outputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}

}