这里核心思想是使用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里面放了文件流。
下载resources下资源文件
/** * 通用从classpath根据模板文件名下载模板文件 * @param fileName src/main/resources下的文件名 * @param request * @return */ @RequestMapping(value = "/export") public ResponseEntity<byte[]> fileExport(@RequestParam String fileName, HttpServletRequest request){ byte [] body = null; InputStream inputStream=null; try { if(StringUtils.isEmpty(fileName)){ logger.error("fileExport fileName:{} is empty!",fileName); return null; } //获取到文件流 复制模板到临时文件目录 InputStream inputStreamSrc = getClass().getResourceAsStream("/"+fileName); // inputStreamSrc = FileController.class.getClassLoader().getResourceAsStream(fileName); // 这里判断无意义 部署到容器中,需要注释掉这个判断 ,否则获取不到文件 // if(inputStreamSrc.available()<1){ // logger.error("fileExport fileName:{} inputStreamSrc is empty!",fileName); // return null; // } String tmpPath = System.getProperty("java.io.tmpdir"); String keyDate = DateUtils.format(new Date(), "yyyyMMddHHmmss"); String excelTemplateNew = keyDate+fileName.substring(fileName.lastIndexOf(".")); boolean send = fileCopy(inputStreamSrc,tmpPath, excelTemplateNew); File file = new File(tmpPath + File.separator + excelTemplateNew); inputStream= new FileInputStream(file); body = new byte[inputStream.available()]; inputStream.read(body); //添加响应头 HttpHeaders headers = new HttpHeaders(); headers.add("Content-Disposition", "attachment;filename="+setFileDownloadHeader(request, fileName)); headers.setContentType(MediaType.APPLICATION_OCTET_STREAM); HttpStatus statusCode = HttpStatus.OK; return new ResponseEntity<byte[]>(body, headers, statusCode); } catch (Exception e) { logger.error(e.getMessage(),e); return null; }finally { if(inputStream!=null){ try { inputStream.close(); } catch (IOException e) { logger.error(e.getMessage(),e); } } } }
② 文件下载与表格整合
需求:以某个已经存在的模板为参考,导出所需的数据。
代码如下:
//导出预约列表 @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; }
⑤ 常见的文件复制
private boolean fileCopy(InputStream inputStream,String destUrl,String fileName) { FileOutputStream fous = null; try { File out_file = new File(destUrl + File.separator + fileName); if (out_file.exists()) { out_file.delete(); } else { out_file.getParentFile().mkdirs(); } out_file.createNewFile(); fous = new FileOutputStream(out_file); byte[] b = new byte[1024]; int read_len; while((read_len = inputStream.read(b)) > 0) { fous.write(b, 0, read_len); } } catch (IOException e) { logger.error(e.getMessage(),e); } finally { if (fous!= null) { try { fous.close(); } catch (IOException e) { logger.error(e.getMessage(),e); } } if (inputStream != null) { try { inputStream.close(); } catch (IOException e) { logger.error(e.getMessage(),e); } } } return true; }