程序项目代做,有需求私信(小程序、网站、爬虫、电路板设计、驱动、应用程序开发、毕设疑难问题处理等)

Spring MVC -- 下载文件

像图片或者HTML文件这样的静态资源,在浏览器中打开正确的URL即可下载,只要该资源是放在应用程序的目录下,或者放在应用程序目录的子目录下,而不是放在WEB-INF下,tomcat服务器就会将该资源发送到浏览器。然而,有时静态资源是保存在应用程序目录之外,或者是保存在某一个数据库中,或者有时需要控制它的访问权限,防止其他网站交叉引用它。如果出现以上任意一种情况,都必要通过编程来发送资源。

简言之,通过编程进行的文件下载,使你可以有选择地将文件发送到浏览器。本篇博客将介绍如果通过编程把资源发送到浏览器,并通过两个案例进行示范。

一 文件下载概览

为了将像文件这样的资源发送到浏览器,需要在控制器中完成以下工作:

  • 对请求处理方法添加HttpServletResponse、HttpServletRequest参数;
  • 将相应的内容类型设为文件的内容类型。content-Type标题在某个实体的body中定义数据的类型,并包含媒体类型和子类型标识符。如果不清楚内容类型,并且希望浏览器式中显示Save As(另存为)对话框,则将它设为application/octet-stream。这个值是不区分大小写的(HTTP Content-type 对照表)。
  • 添加一个属性为content-Disposition的HTTP的响应标题,并赋予attachement;filename=fileName,这里的fileName是默认文件名,应该出现在File Download(文件下载)对话框中,它通常与文件同名,但是也并非一定如此。

文件下载的流程具体如下:

  • 通过浏览器,输入URL请求控制器的请求处理函数;
  • 请求处理方法根据文件路径,将文件转换为输入流;
  • 通过输出流将刚才已经转为输入流的文件,输出给用户(浏览器);

例如,以下代码将一个文件发送到浏览器:

复制代码
        //下载文件:需要设置消息头
        response.setCharacterEncoding("UTF-8");
        response.addHeader("content-Type", "application/octet-stream");   //指定文件类型 MIME类型:二进制文件(任意文件)        

        String encodeFileName = null;
        
        if(userAgent.contains("MSIE") || userAgent.contains("Trident") || (userAgent.contains("GECKO") && userAgent.contains("RV:11"))) {
            //处理IE浏览器下载中文文件名乱码问题            
            encodeFileName = URLEncoder.encode( filename,"UTF-8");            
        }else {
            encodeFileName = "=?UTF-8?B?" + new String(Base64.encodeBase64(filename.getBytes("UTF-8"))) + "?=";
            //encodeFileName = new String(filename.getBytes("UTF-8"),"ISO-8859-1");    
        }
        
        System.out.println(filename + ":" + encodeFileName);
        //如果有换行,对于文本文件没有什么问题,但是对于其他格式:比如AutoCAD,Word,Excel等文件下载下来的文件中就会多出来一些换行符//0x0d和0x0a,这样可能导致某些格式的文件无法打开
        response.reset();
        response.addHeader("content-Disposition", "attachement;filename="+encodeFileName);  //告诉浏览器该文件以附件方式处理,而不是去解析
        
        //通过文件地址,将文件转换为输入流
        InputStream in = request.getServletContext().getResourceAsStream(filename);
        
        //通过输出流将刚才已经转为输入流的文件,输出给用户
        ServletOutputStream out= response.getOutputStream();
        
        byte[] bs = new byte[1000];
        int len = -1;
        while((len=in.read(bs)) != -1) {
            out.write(bs,0,len);
        }
        out.close();
        in.close();
复制代码

为了编程将一个文件发送到浏览器,首先要读取该文件作为InputStream ,随后,获取HttpServletResponse的OutputStream;循环从in中读取1000个字节,写入out中,直至文件读取完毕。

注意:这里将文件转换为输入流使用的是:

request.getServletContext().getResourceAsStream(filename);

filename指的是相对当前应用根路径下的文件。如果给定的路径是绝对路径,可以采用如下函数将文件转换为输入流:

FileInputStream in = new FileInputStream(filename) ;

将文件发送到HTTP客户端的更好方法是使用Java NIO的Files.copy()方法:

        //将文件的虚拟路径转为在文件系统中的真实路径
        String realpath = request.getServletContext().getRealPath(filename);
        System.out.print(realpath);
        Path file = Paths.get(realpath);
        Files.copy(file,response.getOutputStream());

代码更短,运行速度更快。

二 范例1:隐藏资源

我们创建一个download应用程序,用于展示如何向浏览器发送文件。

1、目录结构

2、 Login类

Login类有两个属性,登录名和登录密码:

复制代码
package domain;

import java.io.Serializable;

//登录实体类
public class Login  implements Serializable {
    private static final long serialVersionUID = 1L;

    //用户名
    private String userName;
    //用户密码
    private String password;
    
    public String getUserName() {
        return userName;
    }
    public void setUserName(String userName) {
        this.userName = userName;
    }
    public String getPassword() {
        return password;
    }
    public void setPassword(String password) {
        this.password = password;
    }
}
复制代码

3、ResourceController类

在这个应用程序中,由ResourceController类处理用户登录,并将一个secret.pdf文件发送给浏览器。secret.pdf文件放在/WEB-INF/data目录下,因此不能直接方法。只能得到授权的用户,才能看到它,如果用户没有登录,应用程序就会跳转到登录页面。

复制代码
package controller;

import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.RequestMapping;
import domain.Login;

@Controller
public class ResourceController {
    
    private static final Log logger = LogFactory.getLog(ResourceController.class);
    
    //请求URL:/login 
    @RequestMapping(value="/login")
    public String login(@ModelAttribute Login login, HttpSession session, Model model) {
        model.addAttribute("login", new Login());
        //校验用户名和密码
        if ("paul".equals(login.getUserName()) &&
                "secret".equals(login.getPassword())) {
            //设置sessopm属性"loggedIn"
            session.setAttribute("loggedIn", Boolean.TRUE);
            //校验通过 请求转发到Main.jsp页面
            return "Main";
        } else {
            //校验失败 请求转发到LoginForm.jsp页面
            return "LoginForm";
        }
    }

    //请求URL:/download-resource 
    @RequestMapping(value="/download-resource")
    public String downloadResource(HttpSession session, HttpServletRequest request,
            HttpServletResponse response, Model model) {
        //如果用户没有登录
        if (session == null || 
                session.getAttribute("loggedIn") == null) {
            model.addAttribute("login", new Login());
            //请求转发到LoginForm.jsp页面 等待用户登录
            return "LoginForm";
        }
        //用户已经登录  获取待下载文件夹/WEB-INF/data在文件系统的真实路径
        String dataDirectory = request.
                getServletContext().getRealPath("/WEB-INF/data");
        //创建Path对象  文件为/WEB-INF/data/secret.pdf
        Path file = Paths.get(dataDirectory, "secret.pdf");
        //如果文件存在 下载文件
        if (Files.exists(file)) {
            //指定文件类型 pdf类型
            response.setContentType("application/pdf");
          //告诉浏览器该文件以附件方式处理,而不是去解析
            response.addHeader("Content-Disposition", "attachment; filename=secret.pdf");
            try {
                Files.copy(file, response.getOutputStream());
            } catch (IOException ex) {
            }
        }
        return null;
    }
}
复制代码

4、视图

控制器的第一个请求处理方法是login(),将用户请求转发到登录表单LoginForm.jsp:

复制代码
<%@ taglib prefix="form" uri="http://www.springframework.org/tags/form" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<!DOCTYPE html>
<html>
<head>
<title>Login</title>
<style type="text/css">@import url("<c:url value="/css/main.css"/>");</style>
</head>
<body>
<div id="global">
<form:form modelAttribute="login" action="login" method="post">
    <fieldset>
        <legend>Login</legend>
        <p>
            <label for="userName">User Name: </label>
            <form:input id="userName" path="userName" cssErrorClass="error"/>
        </p>
        <p>
            <label for="password">Password: </label>
            <form:password id="password" path="password" cssErrorClass="error"/>
        </p>
        <p id="buttons">
            <input id="reset" type="reset" tabindex="4">
            <input id="submit" type="submit" tabindex="5" 
                value="Login">
        </p>
    </fieldset>
</form:form>
</div>
</body>
</html>
复制代码

当我们输入用户名"paul",密码"secret",将会成功登录,然后请求转发到Main.jsp页面,该页面包含一个链接,点击它可以将secret.pdf文件下载下来:

复制代码
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
<!DOCTYPE html>
<html>
<head>
<title>Download Page</title>
<style type="text/css">@import url("<c:url value="/css/main.css"/>");</style>
</head>
<body>
<div id="global">
    <h4>Please click the link below.</h4>
    <p>
        <a href="download-resource">Download</a>
    </p>
</div>
</body>
</html>
复制代码

控制器的第二个方法downloadResource(),它通过验证session属性loggedIn是否存在,来核实用户是否已经成功登录。如果找到该属性,就会将文件发送到浏览器。否则,用户就会跳转到登录页面。

main.css:

复制代码
#global {
    text-align: left;
    border: 1px solid #dedede;
    background: #efefef;
    width: 560px;
    padding: 20px;
    margin: 100px auto;
}

form {
  font:100% verdana;
  min-width: 500px;
  max-width: 600px;
  width: 560px;
}

form fieldset {
  border-color: #bdbebf;
  border-width: 3px;
  margin: 0;
}

legend {
    font-size: 1.3em;
}

form label { 
    width: 250px;
    display: block;
    float: left;
    text-align: right;
    padding: 2px;
}

#buttons {
    text-align: right;
}
#errors, li {
    color: red;
}
.error {
    color: red;
    font-size: 9pt;    
}
View Code
复制代码

5、配置文件

下面给出springmvc-config.xml文件的所有内容:

复制代码
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p"
    xmlns:mvc="http://www.springframework.org/schema/mvc" xmlns:context="http://www.springframework.org/schema/context"
    xsi:schemaLocation="
        http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/mvc
        http://www.springframework.org/schema/mvc/spring-mvc.xsd     
        http://www.springframework.org/schema/context
        http://www.springframework.org/schema/context/spring-context.xsd">

    <context:component-scan base-package="controller" />
    <mvc:annotation-driven/>
    <mvc:resources mapping="/css/**" location="/css/" />
    <mvc:resources mapping="/*.html" location="/" />

    <bean id="viewResolver"
        class="org.springframework.web.servlet.view.InternalResourceViewResolver">
        <property name="prefix" value="/WEB-INF/jsp/" />
        <property name="suffix" value=".jsp" />
    </bean>
</beans>
复制代码

部署描述符(web.xml文件):

复制代码
<?xml version="1.0" encoding="UTF-8"?>
<web-app version="3.1" 
        xmlns="http://xmlns.jcp.org/xml/ns/javaee" 
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
        xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee 
            http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd"> 
    <servlet>
        <servlet-name>springmvc</servlet-name>
        <servlet-class>
            org.springframework.web.servlet.DispatcherServlet
        </servlet-class>
        <init-param>
            <param-name>contextConfigLocation</param-name>
            <param-value>/WEB-INF/config/springmvc-config.xml</param-value>
        </init-param>
        <load-on-startup>1</load-on-startup>    
    </servlet>

    <servlet-mapping>
        <servlet-name>springmvc</servlet-name>
        <url-pattern>/</url-pattern>
    </servlet-mapping>
</web-app>
View Code
复制代码

6、测试

将应用程序部署到tomcat服务器,并在网页输入以下URL:

http://localhost:8008/download/login

将会看到一个表单:

输入用户名"paul",密码"secret",将会跳转到文件下载页面:

点击超链接,下载secret.pdf文件。

三 范例2:防止交叉引用

心怀叵测的竞争对手有可能通过交叉引用“窃取”你的网站资产,比如,将你的资料公然放在他的资源上,好像那些东西原本就属于他的一样。如果通过编程控制,使其只有当referer中包含你的域名时才发出资源,就可以防止那种情况发生。当然,那些心意坚决的窃贼仍有办法下载到你的东西,只不过不像之前那么简单罢了。

亲爱的读者和支持者们,自动博客加入了打赏功能,陆陆续续收到了各位老铁的打赏。在此,我想由衷地感谢每一位对我们博客的支持和打赏。你们的慷慨与支持,是我们前行的动力与源泉。

日期姓名金额
2023-09-06*源19
2023-09-11*朝科88
2023-09-21*号5
2023-09-16*真60
2023-10-26*通9.9
2023-11-04*慎0.66
2023-11-24*恩0.01
2023-12-30I*B1
2024-01-28*兴20
2024-02-01QYing20
2024-02-11*督6
2024-02-18一*x1
2024-02-20c*l18.88
2024-01-01*I5
2024-04-08*程150
2024-04-18*超20
2024-04-26.*V30
2024-05-08D*W5
2024-05-29*辉20
2024-05-30*雄10
2024-06-08*:10
2024-06-23小狮子666
2024-06-28*s6.66
2024-06-29*炼1
2024-06-30*!1
2024-07-08*方20
2024-07-18A*16.66
2024-07-31*北12
2024-08-13*基1
2024-08-23n*s2
2024-09-02*源50
2024-09-04*J2
2024-09-06*强8.8
2024-09-09*波1
2024-09-10*口1
2024-09-10*波1
2024-09-12*波10
2024-09-18*明1.68
2024-09-26B*h10
2024-09-3010
2024-10-02M*i1
2024-10-14*朋10
2024-10-22*海10
2024-10-23*南10
2024-10-26*节6.66
2024-10-27*o5
2024-10-28W*F6.66
2024-10-29R*n6.66
2024-11-02*球6
2024-11-021*鑫6.66
2024-11-25*沙5
2024-11-29C*n2.88
posted @   大奥特曼打小怪兽  阅读(2428)  评论(0编辑  收藏  举报
编辑推荐:
· 记一次.NET内存居高不下排查解决与启示
· 探究高空视频全景AR技术的实现原理
· 理解Rust引用及其生命周期标识(上)
· 浏览器原生「磁吸」效果!Anchor Positioning 锚点定位神器解析
· 没有源码,如何修改代码逻辑?
阅读排行:
· 全程不用写代码,我用AI程序员写了一个飞机大战
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· DeepSeek 开源周回顾「GitHub 热点速览」
· 记一次.NET内存居高不下排查解决与启示
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了
如果有任何技术小问题,欢迎大家交流沟通,共同进步

公告 & 打赏

>>

欢迎打赏支持我 ^_^

最新公告

程序项目代做,有需求私信(小程序、网站、爬虫、电路板设计、驱动、应用程序开发、毕设疑难问题处理等)。

了解更多

点击右上角即可分享
微信分享提示