HttpServletResponse附件下载详解

Java随作 专栏收录该内容
11 篇文章 0 订阅

背景

最近做了一些附件上传下载的功能,附件都是上传第三方平台,例如青云。该章记录一下附件下载。

目的

有一些浏览器在打开第三方附件下载地址的时候,会直接打开文件,看到的都是二进制乱码,而不是弹出下载框,例如火狐浏览器,该实现方式是为了兼容这种情况。

流程

第一步:前端把第三方下载地址传递给服务端,或者服务端自己从业务数据库直接获取。

第二步:前端把附件的名称传递给服务端,或者服务端自己从业务数据库直接获取,或自动生成。

第三步:通过http请求的方式获取附件的二进制流。

第四步:通过HttpServletResponse输出,浏览器会自动匹配,直接弹出下载框。

实现

备注:文章结尾有源码下载地址,下载之后,可直接用eclipse导入maven项目运行。

一)DownloadController对外接口类代码

package com.oysept.attachment.controller;

import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;

import javax.servlet.http.HttpServletResponse;

import org.springframework.stereotype.Controller;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;

import com.oysept.attachment.utils.DownloadUtils;
import com.oysept.attachment.utils.HttpUtils;

/**
 * 附件下载controller
 * @author ouyangjun
 * 由于下载时返回一个流给前端,所以不能使用@RestController注解
 */
@Controller
@RequestMapping(value = "/attachment")
public class DownloadController {

    /**
     * 首页测试接口
     * 地址: http://localhost:8080/attachment/download/home
     * @return
     */
    @RequestMapping(value = "/download/home", method = RequestMethod.GET, produces = "application/json;charset=UTF-8")
    @ResponseBody
    public String home(){
        return "oysept attachment download home!";
    }
	
    /**
     * 通过第三方下载地址,获取流返回给前端
     * 原因: 有一些浏览器,直接输入第三方下载地址,会直接打开文件,不会直接下载,需要手动在浏览器配置之后才ok
     * 注意: fileUrl和fileName需要urlencode
     */
    @RequestMapping(value = "/download/urlDownload", method = RequestMethod.GET, produces = "application/json;charset=UTF-8")
    public void urlDownload(@RequestParam(value="fileUrl") String fileUrl,
            @RequestParam(value="fileName") String fileName, HttpServletResponse response) {
        if (StringUtils.isEmpty(fileUrl) || StringUtils.isEmpty(fileName)) {
            System.out.println("fileUrl or fileName is null!");
            return;
        }
		
        try {
            // 获取文件类型
            String fileType = fileName.substring(fileName.lastIndexOf(".")+1,fileName.length())
					.toLowerCase();
			
            // 解码
            String decodeFileUrl = URLDecoder.decode(fileUrl, "UTF-8");
            System.out.println("decodeFileUrl: " + decodeFileUrl);
			
            // 设置附件信息,fileName中如果包含中文,可能会出现乱码
            response.setHeader("Content-disposition", "attachment; filename=" + URLDecoder.decode(fileName, "UTF-8"));
            // ContentType,如果不设置,附件会打不开
            String contentType = DownloadUtils.getContentType(URLDecoder.decode(decodeFileUrl, "UTF-8"));
            System.out.println("/download/urlDownload ContentType: " + contentType);
            if (StringUtils.isEmpty(contentType)) {
                contentType = DownloadUtils.getResponseContentType(fileType);
                // 设置HttpServletResponse设置的ContentType参数
                if (!StringUtils.isEmpty(contentType)) {
                    response.setContentType(contentType);
                }
            } else {
                response.setContentType(contentType + ";charset=utf-8");
            }
			
			// InputStream in = HttpUtils.getHttpInputStream(decodeFileUrl);
            InputStream in = HttpUtils.getHttpInputStream(decodeFileUrl, "GET");
            OutputStream out = null;
            try {
                out = response.getOutputStream();
                int l = -1;
                byte[] tmp =new byte[1024];
                while ((l = in.read(tmp)) != -1) {
                    out.write(tmp, 0, l);
                }
            } catch (FileNotFoundException e) {
                e.printStackTrace();
            } catch (IOException e) {
                e.printStackTrace();
            } finally {
                // 关闭低层流。
                try {
                    if (out != null) {
                        out.close();
                    }
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
        }
    }
}

二)http工具类HttpUtils类代码

注意:该工具类有两种请求方式

说明:只要服务端能把附件读取成流的方式,就能通过HttpServletResponse输出的,可自行琢磨。

package com.oysept.attachment.utils;

import java.io.IOException;
import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.URL;

import org.apache.http.HttpEntity;
import org.apache.http.HttpStatus;
import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;

public class HttpUtils {

    /**
     * http获取InputStream, 方式一
     * @param url
     * @return
     */
    public static InputStream getHttpInputStream(String requestUrl) {
        try {
            CloseableHttpClient httpClient = HttpClients.createDefault();
			
            HttpGet httpGet =new HttpGet(requestUrl);
		    
            CloseableHttpResponse response = httpClient.execute(httpGet);
			
            // 判断请求是否成功
            if (response.getStatusLine().getStatusCode() == HttpStatus.SC_OK) {
                HttpEntity entity = response.getEntity();
		    	
                // 返回InputStream
                return entity.getContent();
            }
        } catch (ClientProtocolException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
        return null;
    }
	
    /**
     * http获取InputStream, 方式二
     * @param requestUrl: 请求路径
     * @param requestMethod: 请求方式(GET、POST)
     * @return
     */
    public static InputStream getHttpInputStream(String requestUrl, String requestMethod) {
		
        HttpURLConnection con = null;
        InputStream is = null;
        try {
            URL url = new URL(requestUrl);
			
            // 原生访问http请求,未代理请求
            con = (HttpURLConnection) url.openConnection();
            con.setDoOutput(true);
            con.setDoInput(true);
            con.setUseCaches(false);
            con.setRequestMethod(requestMethod);
            con.setReadTimeout(60000);
            con.setConnectTimeout(60000);
			
            // 获取InputStream
            is = con.getInputStream();
            return is;
        } catch (IOException e) {
            e.printStackTrace();
        }
        return null;
    }
}

二)DownloadUtils工具类代码,主要是获取附件ContentType

package com.oysept.attachment.utils;

import java.io.File;

import javax.activation.MimetypesFileTypeMap;

import org.springframework.util.StringUtils;

/**
 * 
 * @Author: ouyangjun
 */
public class DownloadUtils {

    /**
     * 获取HttpServletResponse设置的ContentType参数
     * @return
     */
    public static String getResponseContentType(String type) {
    String contentType = null;
    	
        if ("xlsx,xls,docx,doc,pptx,ppt,dotx,docm,dotm,xltx,xlsm,xltm,xlam,xlsb,potx,ppsx,ppam,pptm,potm,ppsm,xlt,xla,pot,pps,ppa"
				.contains(type)) {
            // 附件header处理
            if("xlsx".equals(type)) {
                contentType = "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet;charset=utf-8";
            } else if("xls".equals(type) || "xlt".equals(type) || "xla".equals(type)) {
                contentType = "application/vnd.ms-excel;charset=utf-8";
            } else if("docx".equals(type)) {
                contentType = "application/vnd.openxmlformats-officedocument.wordprocessingml.document;charset=utf-8";
            } else if("doc".equals(type) || "dot".equals(type)) {
                contentType = "application/msword;charset=utf-8";
            } else if("pptx".equals(type)) {
                contentType = "application/vnd.openxmlformats-officedocument.presentationml.presentation;charset=utf-8";
            } else if("ppt".equals(type) || "pot".equals(type) || "pps".equals(type) || "ppa".equals(type)) {
                contentType = "application/vnd.ms-powerpoint;charset=utf-8";
            } else if("dotx".equals(type)) {
                contentType = "application/vnd.openxmlformats-officedocument.wordprocessingml.template;charset=utf-8";
            } else if("docm".equals(type)) {
                contentType = "application/vnd.ms-word.document.macroEnabled.12;charset=utf-8";
            } else if("dotm".equals(type)) {
                contentType = "application/vnd.ms-word.template.macroEnabled.12;charset=utf-8";
            } else if("xltx".equals(type)) {
                contentType = "application/vnd.openxmlformats-officedocument.spreadsheetml.template;charset=utf-8";
            } else if("xlsm".equals(type)) {
                contentType = "application/vnd.ms-excel.sheet.macroEnabled.12;charset=utf-8";
            } else if("xltm".equals(type)) {
                contentType = "application/vnd.ms-excel.template.macroEnabled.12;charset=utf-8";
            } else if("xlam".equals(type)) {
                contentType = "application/vnd.ms-excel.addin.macroEnabled.12;charset=utf-8";
            } else if("xlsb".equals(type)) {
                contentType = "application/vnd.ms-excel.sheet.binary.macroEnabled.12;charset=utf-8";
            } else if("potx".equals(type)) {
                contentType = "application/vnd.openxmlformats-officedocument.presentationml.template;charset=utf-8";
            } else if("ppsx".equals(type)) {
                contentType = "application/vnd.openxmlformats-officedocument.presentationml.slideshow;charset=utf-8";
            } else if("ppam".equals(type)) {
                contentType = "application/vnd.ms-powerpoint.addin.macroEnabled.12;charset=utf-8";
            } else if("pptm".equals(type)) {
                contentType = "application/vnd.ms-powerpoint.presentation.macroEnabled.12;charset=utf-8";
            } else if("potm".equals(type)) {
                contentType = "application/vnd.ms-powerpoint.template.macroEnabled.12;charset=utf-8";
            } else if("ppsm".equals(type)) {
                contentType = "application/vnd.ms-powerpoint.slideshow.macroEnabled.12;charset=utf-8";
            }
        } else if ("txt".equals(type)) {
            contentType = "text/plain;charset=utf-8";
        } else if ("pdf".equals(type)) {
            contentType = "application/pdf;charset=utf-8";
        } else if ("png".equals(type)) {
            contentType = "image/png;charset=utf-8";
        } else if ("jpg".equals(type) || "jpeg".equals(type)) {
            contentType = "image/jpeg;charset=utf-8";
        } else if ("bmp".equals(type)) {
            contentType = "application/x-bmp;charset=utf-8";
        } else if ("gif".equals(type)) {
            contentType = "image/gif;charset=utf-8";
        } else if ("html".equals(type) || "htm".equals(type)) {
            contentType = "text/html;charset=utf-8";
        } else if ("xml".equals(type)) {
            contentType = "text/xml;charset=utf-8";
        } else if ("css".equals(type)) {
            contentType = "text/css;charset=utf-8";
        } else if ("js".equals(type)) {
            contentType = "application/x-javascript;charset=utf-8";
        } else if ("eml".equals(type)) {
            contentType = "message/rfc822;charset=utf-8";
        } else if ("xlw".equals(type)) {
            contentType = "application/x-xlw;charset=utf-8";
        } else if ("ai".equals(type)) {
            contentType = "application/postscript;charset=utf-8";
        } else if ("rtf".equals(type)) {
            contentType = "application/msword;charset=utf-8";
        } else if ("mp4".equals(type)) {
            contentType = "video/mpeg4;charset=utf-8";
        } else if ("tif".equals(type) || "tiff".equals(type)) {
            contentType = "image/tiff;charset=utf-8";
        } else if ("exe".equals(type)) {
            contentType = "application/x-msdownload;charset=utf-8";
        } else if ("pls".equals(type)) {
            contentType = "audio/scpls;charset=utf-8";
        } else if ("rpm".equals(type)) {
            contentType = "audio/x-pn-realaudio-plugin;charset=utf-8";
        } else if ("mht".equals(type)) {
        contentType = "message/rfc822;charset=utf-8";
        }
        return contentType;
    }
    
     /**
     * 获取附件 ContentType
     * @param url
     * @return
     */
    public static String getContentType(String url) {
        if (StringUtils.isEmpty(url)) {
            return null;
        }
        try {
            return new MimetypesFileTypeMap().getContentType(new File(url));
        } catch (Exception e) {
            return null;
        }
    }
}

 

识别二维码关注个人微信公众号

本章完结,待续,欢迎转载!
 
本文说明:该文章属于原创,如需转载,请标明文章转载来源!

  • 1
    点赞
  • 0
    评论
  • 2
    收藏
  • 打赏
    打赏
  • 扫一扫,分享海报

参与评论 您还未登录,请先 登录 后发表或查看评论
©️2022 CSDN 皮肤主题:大白 设计师:CSDN官方博客 返回首页

打赏作者

ouyangjun__

你的鼓励将是我创作的最大动力

¥2 ¥4 ¥6 ¥10 ¥20
输入1-500的整数
余额支付 (余额:-- )
扫码支付
扫码支付:¥2
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值