首页 > 技术文章 > 文件上传和下载

rayguo 2014-04-01 09:51 原文

  基本的文件上传:

    最基本的文件上传是在不利用其它额外jar包的帮助下,实现上传,这就是其它jar包封装的基础。

    我们在文件上传的时候大多都是利用表单里面的input标签,input标签里面有个type为file,就是定义了一个上传文件的输入框,现在我们要了解的是form表单的enctype属性,这个属性决定的是表单发到服务器之前如何对数据进行编码。

    enctype属性有三个值可取:

      1.application/x-www-form-urlencoded,是表单该属性的默认值,意思是发到服务器之前对所有的数据进行编码。

      2.multipart/form-data,意思是不对字符进行编码,如果你要上传文件,必须用这个值。

      3.text/plain,意思空格转换为 "+" 加号,但不对特殊字符编码。

下面我们写个简单的html文件,定义一个表单,用input标签进行文件上传:

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
  <head>
    <title>file.html</title>
    
    <meta http-equiv="keywords" content="keyword1,keyword2,keyword3">
    <meta http-equiv="description" content="this is my page">
    <meta http-equiv="content-type" content="text/html; charset=UTF-8">
  </head>
  <body>
    <form action="dealFile.jsp" enctype="multipart/form-data" method="post">
        上传文件:<input type="file" name="file" /><br>
        姓名:<input type="text" name="name" /><br>
        <input type="submit" value="提交"/>
    </form>
  </body>
</html>

    我们都知道,表单的数据是通过http协议传给服务器的,所以服务器的处理也是从http协议中获取数据,对其进行解析,request对象中有一个getInputStream方法,这个方法可以得到一个输入流,从这个流中我们就可以读取到文件的内容,当然也包括其他表单附带的数据:

dealFile.jsp:

<%@ page language="java" import="java.util.*, java.io.*" pageEncoding="UTF-8"%>
<%
String path = request.getContextPath();
String basePath = request.getScheme()+"://"+request.getServerName()+":"+request.getServerPort()+path+"/";
%>

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
  <head>
    <base href="<%=basePath%>">
    
    <title>My JSP 'dealFile.jsp' starting page</title>
    
    <meta http-equiv="pragma" content="no-cache">
    <meta http-equiv="cache-control" content="no-cache">
    <meta http-equiv="expires" content="0">    
    <meta http-equiv="keywords" content="keyword1,keyword2,keyword3">
    <meta http-equiv="description" content="This is my page">
  </head>
  <body>
    <% 
        request.setCharacterEncoding("UTF-8");
        InputStream is = request.getInputStream();
        BufferedReader br = new BufferedReader(new InputStreamReader(is, "UTF-8"));
        String buffer = null;
        out.println(buffer == null);
        while((buffer = br.readLine()) != null) {
            out.println(buffer + "<br>");
        }
    %>
  </body>
</html>

  下面介绍使用Common-FileUpload框架进行上传,首先你必须引进两个jar包,一个是Commons-FileUpload.jar和Commons-io.jar,这两个jar包百度都可以搜索到:

  我们使用的还是上面的file.html,把它的action改为dealFileByFU.jsp:

dealFileByFU.jsp:

<%@page import="org.apache.commons.fileupload.FileItem"%>
<%@page import="org.apache.commons.fileupload.servlet.ServletFileUpload"%>
<%@page import="org.apache.commons.fileupload.disk.DiskFileItemFactory"%>
<%@ page language="java" import="java.util.*, java.io.*" pageEncoding="UTF-8"%>
<%
String path = request.getContextPath();
String basePath = request.getScheme()+"://"+request.getServerName()+":"+request.getServerPort()+path+"/";
%>

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
  <head>
    <base href="<%=basePath%>">
    
    <title>My JSP 'dealFile.jsp' starting page</title>
    
    <meta http-equiv="pragma" content="no-cache">
    <meta http-equiv="cache-control" content="no-cache">
    <meta http-equiv="expires" content="0">    
    <meta http-equiv="keywords" content="keyword1,keyword2,keyword3">
    <meta http-equiv="description" content="This is my page">
    <!--
    <link rel="stylesheet" type="text/css" href="styles.css">
    -->

  </head>
  
  <body>
    <% 
        //定义上传工厂
        DiskFileItemFactory factory = new DiskFileItemFactory();
        //设置工厂的大小限制
        factory.setSizeThreshold(1024*1024*20);
        //设置文件的存放路径
        factory.setRepository(new File(request.getRealPath("/")));
        //根据工厂创建一个上传文件的ServletFileUpload对象
        ServletFileUpload upload = new ServletFileUpload(factory);
        //设置最大的上传限制
        upload.setSizeMax(1024*1024*20);
        //根绝request对其进行解析,items是所有的表单项
        List items = upload.parseRequest(request);
        //遍历所有的表单项
        for(Iterator it = items.iterator(); it.hasNext();) {
            FileItem item = (FileItem)it.next();
            //判断是普通的表单域还是上传文件域
            if(item.isFormField()) {
                //拿到对应的字段名和值
                String name = item.getFieldName();
                String value = item.getString("UTF-8");
                out.print(name + " = " + value);
            } else {
                //拿到字段名
                String fieldName = item.getFieldName();
                //拿到文件名
                String fileName = item.getName();
                //拿到文件类型
                String contentType = item.getContentType();
                //命名文件名
                FileOutputStream fos = new FileOutputStream(request.getRealPath("/")
                        + System.currentTimeMillis() + 
                        fileName.substring(fileName.lastIndexOf("."), fileName.length()));
                //判断内存中是否有该文件
                if(item.isInMemory()) {
                    fos.write(item.get());
                } else {
                    InputStream is = item.getInputStream();
                    byte[] buffer = new byte[1024];
                    int len;
                    while((len = is.read(buffer)) > 0) {
                        fos.write(buffer, 0, len);
                    }
                    is.close();
                    fos.close();
                }
            }
        }
    %>
  </body>
</html>

   接下来介绍的是另外一个小框架COS进行上传,首先我们同样要引入cos.jar包,COS的核心类是MultipartParser,该类用于解析HttpServletRequest请求,取出所有的表单域,每个表单域对应一个Part示例,通过这个Part我们就可以判断这个域是普通的表单域还是文件域。

dealFileByCOS.jsp:

<%@page import="com.oreilly.servlet.multipart.FilePart"%>
<%@page import="com.oreilly.servlet.multipart.ParamPart"%>
<%@page import="com.oreilly.servlet.multipart.Part"%>
<%@page import="com.oreilly.servlet.multipart.MultipartParser"%>
<%@ page language="java" import="java.util.*, java.io.*" pageEncoding="UTF-8"%>
<%
String path = request.getContextPath();
String basePath = request.getScheme()+"://"+request.getServerName()+":"+request.getServerPort()+path+"/";
%>

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
  <head>
    <base href="<%=basePath%>">
    
    <title>My JSP 'dealFile.jsp' starting page</title>
    
    <meta http-equiv="pragma" content="no-cache">
    <meta http-equiv="cache-control" content="no-cache">
    <meta http-equiv="expires" content="0">    
    <meta http-equiv="keywords" content="keyword1,keyword2,keyword3">
    <meta http-equiv="description" content="This is my page">
    <!--
    <link rel="stylesheet" type="text/css" href="styles.css">
    -->

  </head>
  
  <body>
    <% 
        //使用MultipartParser解析request并且设置最大容量为10M
        MultipartParser mp = new MultipartParser(request, 10*1024*1024);
        //一个part就代表着一个表单域
        Part part;
        while((part = mp.readNextPart()) != null) {
            //取得所有表单域的name的属性值
            String name = part.getName();]
            //判断是普通的表单域还是文件域
            if(part.isParam()) {
                //强制转化为普通的表单域
                ParamPart paramPart = (ParamPart)part;
                String value = paramPart.getStringValue("UTF-8");
                out.println("Parm: " + name + " = " + value);
                //String value = paramPart.
            } else if(part.isFile()) {
                //强制转化为普通的文件域
                FilePart filePart = (FilePart)part;
                String fileName = filePart.getFileName();
                if(fileName != null) {
                    //写进文件中
                    FileOutputStream fos = new FileOutputStream(request.getRealPath("/")
                            + System.currentTimeMillis() + 
                            fileName.substring(fileName.lastIndexOf("."), fileName.length()));
                    InputStream is = filePart.getInputStream();
                    byte[] buffer = new byte[1024];
                    int len;
                    while((len = is.read(buffer)) > 0) {
                        fos.write(buffer, 0, len);
                    }
                    is.close();
                    fos.close();
                }
            } else {
                out.println("file : name " + name);
            }
            out.flush();
        }
    %>
  </body>
</html>

  下面介绍Struts2的文件上传,使用Struts框架进行解析,更加的方便,因为里面已经为我们封装好了实现,但是我们千万不要以为Struts里面的全部上传文件都是自己实现的,它里面也是使用小框架实现的,只是它做了更近一步的封装。

  在Struts2的default.properties配置文件中,我们可以看到:

  所以我们可以发现你导入struts2的jar包的时候是需要导入commons-io.jar和common-fileupload.jar的,struts2默认是使用FileUpload框架。

  下面我们写一个action对应我们的表单域:

package com.xujianguo.action;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;

import org.apache.struts2.ServletActionContext;

import com.opensymphony.xwork2.ActionSupport;

@SuppressWarnings("all")
public class FileUpload extends ActionSupport {
    private String name;
    private File upload;
    private String uploadContentType;
    private String uploadFileName;
    private String savePath;
    
    public String execute() throws Exception {
        FileOutputStream fos = new FileOutputStream(getSavePath() + "\\" + getUploadFileName());
        FileInputStream fis = new FileInputStream(getUpload());
        byte[] buffer = new byte[1024];
        int len = 0;
        while((len = fis.read(buffer)) > 0) {
            fos.write(buffer, 0, len);
        }
        return SUCCESS;
    }
    
    public void setSavePath(String value) {
        this.savePath = value;
    }
    
    public String getSavePath() {
        return ServletActionContext.getRequest().getRealPath(savePath);
    }
    
    public String getName() {
        return name;
    }
    
    public void setName(String name) {
        this.name= name;
    }
    
    public File getUpload() {
        return upload;
    }
    
    public void setUpload(File upload) {
        this.upload = upload;
    }

    public String getUploadContentType() {
        return uploadContentType;
    }

    public void setUploadContentType(String uploadContentType) {
        this.uploadContentType = uploadContentType;
    }

    public String getUploadFileName() {
        return uploadFileName;
    }

    public void setUploadFileName(String uploadFileName) {
        this.uploadFileName = uploadFileName;
    }
}

   这里必须提醒的是:File类型的变量是对应的表单中input类型为file的name属性的值,其他属性如uploadFileName和uploadContentType都是以它为基础的。

struts.xml中的action配置:

<action name="upload" class="com.xujianguo.action.FileUpload">
    <param name="savePath">/upload</param>
    <result name="success">/showFile.jsp</result>
</action>

 showFile.jsp:

<%@ page language="java" import="java.util.*" pageEncoding="UTF-8"%>
<%@ taglib prefix="s" uri="/struts-tags" %>
<%
String path = request.getContextPath();
String basePath = request.getScheme()+"://"+request.getServerName()+":"+request.getServerPort()+path+"/";
%>

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
  <head>
    <base href="<%=basePath%>">
    
    <title>My JSP 'index.jsp' starting page</title>
    <meta http-equiv="pragma" content="no-cache">
    <meta http-equiv="cache-control" content="no-cache">
    <meta http-equiv="expires" content="0">    
    <meta http-equiv="keywords" content="keyword1,keyword2,keyword3">
    <meta http-equiv="description" content="This is my page">
    <!--
    <link rel="stylesheet" type="text/css" href="styles.css">
    -->
  </head>
  
  <body>
        上传成功<br>
        姓名:<s:property value="name"/><br>
        文件为:<img src="<s:property value="'upload/' + uploadFileName" />"/>
  </body>
</html>

   使用拦截器实现文件过滤:

    Struts2提供了一个文件上传的拦截器-fileUpload,为了让该拦截器起作用,只需要要action中配置一下就ok了,它有两个参数:

      allowedTypes:该参数指定允许上传文件的类型

      maximumSize:该参数指定上传文件的大小,单位为字节。

struts.xml配置:

<action name="upload" class="com.xujianguo.action.FileUpload">
            <interceptor-ref name="fileUpload">
                <param name="allowedTypes">image/bmp,image/png,image/gif,image/jpeg</param>
                <param name="maximumSize">200000</param>
            </interceptor-ref>
            <interceptor-ref name="defaultStack"></interceptor-ref>
            <param name="savePath">/upload</param>
            <result name="success">/showFile.jsp</result>
            <result name="input">/file.html</result>
</action>

  Struts2多文件上传:

    其实多文件上传跟单文件上传是同样的道理,只是你的Action方面就不能单用一个File类型去接受多个文件了,我们可以改成List<File>对应去存储文件,修改后的文件为:

package com.xujianguo.action;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.util.List;

import org.apache.struts2.ServletActionContext;

import com.opensymphony.xwork2.ActionSupport;

@SuppressWarnings("all")
public class MultiFileUpload extends ActionSupport {
    private String name;
    private List<File> upload;
    private List<String> uploadContentType;
    private List<String> uploadFileName;
    private String savePath;
    
    public String execute() throws Exception {
        List<File> files = getUpload();
        for(int i = 0; i < files.size(); i++) {
            FileOutputStream fos = new FileOutputStream(getSavePath() + "\\" + getUploadFileName().get(i));
            FileInputStream fis = new FileInputStream(files.get(i));
            byte[] buffer = new byte[1024];
            int len = 0;
            while((len = fis.read(buffer)) > 0) {
                fos.write(buffer, 0, len);
            }
        }
        return SUCCESS;
    }
    
    public void setSavePath(String value) {
        this.savePath = value;
    }
    
    public String getSavePath() {
        return ServletActionContext.getRequest().getRealPath(savePath);
    }
    
    public String getName() {
        return name;
    }
    
    public void setName(String name) {
        this.name= name;
    }

    public List<File> getUpload() {
        return upload;
    }

    public void setUpload(List<File> upload) {
        this.upload = upload;
    }

    public List<String> getUploadContentType() {
        return uploadContentType;
    }

    public void setUploadContentType(List<String> uploadContentType) {
        this.uploadContentType = uploadContentType;
    }

    public List<String> getUploadFileName() {
        return uploadFileName;
    }

    public void setUploadFileName(List<String> uploadFileName) {
        this.uploadFileName = uploadFileName;
    }
}

  其他方面跟单文件的一致,哈哈,是不是很简单呢,不要给多文件吓到了。

  Struts2的文件下载:

    文件下载也是很简单的,我们简单的演示一下,首先是前端的页面:

<a href="/Struts2Demo/guo/download.action">图片下载</a>

     前端最核心的就是这一句,访问action,让Struts去处理诸如文件名为中文之类的麻烦问题,接下来就是要配制我们的struts.xml:

        <action name="download" class="com.xujianguo.action.DownloadFile">
            <result name="success" type="stream">
                <param name="contentType">image/png</param>
                <param name="contentDisposition">attachment;filename="Snap1.png"</param>
                <param name="inputName">input</param>
            </result>
        </action>

    我们一一来看里面的属性:

      stream:如果你的要实现文件下载,就必须使用type为stream的result,就是使用一个流。

      contentType:指定你下载文件的类型,我这里是image,如果你要的是文本的话就使用text/plain。

      contentDisposition:这里指定的是文件名和文件的下载方式,我使用的附件的形式,也就是attachment。

      inputName:指定你对应action里面的类型为InputStream的属性,指定的文件的流的获取。

    DownloadFile.java:

package com.xujianguo.action;

import java.io.InputStream;

import org.apache.struts2.ServletActionContext;

import com.opensymphony.xwork2.ActionSupport;

@SuppressWarnings("serial")
public class DownloadFile extends ActionSupport {
    public InputStream input;
    
    public InputStream getInput() {
        return ServletActionContext.getServletContext().getResourceAsStream("/upload/Snap1.png");
    }
    
    public void setInput(InputStream input) {
        this.input = input;
    }
    
    public String execute() {
        return SUCCESS;
    }
}

推荐阅读