SpringMVC实现文件下载实践

简介: SpringMVC实现文件下载实践

这里核心思想是使用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;
}


目录
相关文章
|
4月前
SpringMVC之文件上传和下载
【1月更文挑战第20天】SpringMVC之文件上传和下载
25 1
|
7月前
|
SQL 前端开发 Java
SpringMVC系列(四)之SpringMVC实现文件上传和下载
SpringMVC系列(四)之SpringMVC实现文件上传和下载
|
6月前
|
前端开发 Java 数据库连接
SpringMvc第四战-【SpringMvc文件上传,下载】
SpringMvc第四战-【SpringMvc文件上传,下载】
|
6月前
|
前端开发 Java 数据库
SpringMVC之文件的上传下载(教你如何使用有关SpringMVC知识实现文件上传下载的超详细博客)
SpringMVC之文件的上传下载(教你如何使用有关SpringMVC知识实现文件上传下载的超详细博客)
57 0
|
11天前
|
存储 前端开发 Java
SpringMVC 文件上传和下载
SpringMVC 文件上传和下载
10 0
|
9月前
SpringMVC-文件下载
SpringMVC-文件下载
29 0
SpringMVC-文件下载
SpringMVC学习(十):文件的上传和下载
在SpringMVC中使用ResponseEntity实现下载文件的功能
|
前端开发 Java Maven
SpringMVC文件上传与下载
SpringMVC文件上传与下载
|
Java 开发者
springmvc.实现文件上传|学习笔记
快速学习springmvc.实现文件上传