E A S Y 实现文件上传下载【更新版本】

2016-07-05 原创 北风  程序员日记
程序员日记

code2note

我喜欢分享,分享一起进步。


注:上一篇是一个错误的版本,已删除,补发一次。

在《Web应用开发中的附件上传/下载大全》中我分享了常见的几种附件上传下载组件,每个组件都附带有官网和下载地址,在开始浏览本篇分享之前,有兴趣的朋友可以将组件jar包下载下来,自己动手实践。


一、上传文件原理

为了简单明确上传文件原理,我先贴上一个jsp上传附件到servlet的例子,结合例子一步一步分析。

1)在eclipse中创建动态web项目:


创建如下文件:


其中web.xml文件的内容如下:


Upload.jsp文件内容如下:


UploadServlet.java文件如下:

package com.code2note;

/*导入包略...*/

public classUploadServlet extends HttpServlet {


 @Override
 protected void doGet(HttpServletRequest req,
   HttpServletResponse resp)
   throws ServletException, IOException {
  this.doPost(req, resp);
 }
 
 @Override
 protected void doPost(HttpServletRequest req,
   HttpServletResponse resp)
   throws ServletException, IOException {
  handleReq1(req,resp);
 }
 
 /**
  * 自定义上传处理方法
  * @param req 请求对象
  * @param resp 响应对象
  * @throws IOException
  */

 private void handleReq1(HttpServletRequest req,
   HttpServletResponse resp)
   throws IOException{
  req.setCharacterEncoding("utf-8");
  InputStream in=req.getInputStream();
  File f = new File("D:/pic.txt");
  FileOutputStream out=new FileOutputStream(f);
  byte []b=new byte[1024];
  int n=0;
  while((n=in.read(b))!=-1){
   out.write(b, 0, n);
  }
  out.flush();
  out.close();
  in.close();
 }
}

注意:handleReq1中获取请求对象的输入流,然后将其写入本地磁盘,为了简单,所有异常全部抛出。

2)将项目加入到Tomcat中,然后启动Tomcat,在浏览器输入访问地址并填写相关测试参数和上传文件:

3)上传后,找到上传的文件打开文件如下:

可见,请求对象将我们请求的参数分割成一块一块的,每一块内包含一个Content-Disposition、一个name和对应的值,当然文件的也不例外。所以如果没有任何上传下载组件可用,我们的就自己动手编写相关代码将附件的内容从请求中分离出来实现上传文件解析。下面第一节是这种方法的一个解决方案。


二、原始的Form/Servlet(或者Jsp)中附件上传方法

在了解文件上传原理后,我们着手实现最简陋和最原始的上传文件方法。

当上传文件到服务器后,UploadServlet会收到web请求,我们的目的就是在servlet中将文件分离出来,然后保存文件到服务器。

这里要演示的环境是:servlet3.0,tomcat7.0,web.xml文件版本是3.0。servlet3.0提供了Part类,通过request.getParts()获取上传的文件,这和我们在上传原理那一节里研究的一样。由于struts的action在request中将请求流中分割的文件提取出来并封装到Part对象中,我们就可以在servlet中通过request.getParts获取这些上传文件了,不多说了直接代码:

1)编写web.xml,在UploadServlet的配置中增加multipart-config来配置上传文件的相关属性:

也可以采用注解的方式在servlet上进行注解:

@MultipartConfig(location = "D:/temp", maxFileSize = 33554432, fileSizeThreshold = 0)
public class UploadServlet extends HttpServlet

2)创建handleReq2方法处理上传附件:

/**
  * 自定义上传处理方法
  * @param req 请求对象
  * @param resp 响应对象
  * @throws IOException
  * @throws ServletException
  * @throws IllegalStateException
  */

 private void handleReq2(HttpServletRequest req,
   HttpServletResponse resp)
   throws IOException, IllegalStateException, ServletException{
  req.setCharacterEncoding("utf-8");
  for(Part part:req.getParts()){
   if(part.getName().startsWith("file")){
    String fileName=getFileName(part);
    part.write(fileName);
   }
  }
 }


 private StringgetFileName(Part part) throws UnsupportedEncodingException {
  String header=part.getHeader("Content-Disposition");
  String fileName=header.substring(header.indexOf("filename=\"")+10, header.lastIndexOf("\""));
  fileName=fileName.substring(fileName.lastIndexOf("\\")+1, fileName.length());
  /*fileName=new String(fileName.getBytes("ISO-8859-1"),"gbk");*/
  fileName="D:/upload/"+fileName;
  return fileName;
 }

注意:handleReq2从request中获取分割的多文件的part对象,多个文件多个part。我这里拼好了地址然后再调用Part的write方法保存文件。此外也可以调用Part对象的getInputStream(),通过流的方式保存文件。在doPost注释掉handleReq1调用handleReq2实现文件上传处理。

前台的jsp页面如下:

3)测试上传附件

在1~3步骤里,我主要是使用servlet3.0提供的快捷方法上传文件,当然如果servlet是3.0以下的版本,那只有自己实现Part包装的方法了^_^,这里不做介绍。所有上传组件都是基于如何将普通表单和文件表单分隔开来实现文件的上传并优化上传效率。

4)附件下载:

附件下载本不是我们研究的重点,几乎所有的附件下载只需要在服务器端调用response的输出流输出相关的文件到前台即可,操作上大大易于上传文件。相关文件如下:

DownloadServlet.java文件:

web.xml添加servlet映射:

前台jsp添加下载按钮:

5)在浏览器输入地址:http://localhost:8080/Upload/Upload.jsp点击下载按钮下载文件。

这一小节主要介绍了在基本的jsp/servlet中实现上传下载附件的方法,可以看到主要是上传附件的问题,幸好的是servlet3.0提供了简洁的方式,是我们在服务器获取保存附件的难度大大降低。倘若已存在的项目的servlet版本不支持Part和request.getParts(),那也不要灰心。分析一下第一小节中的上传文件的内容,想想也不过是将附件从其中分解出来嘛,这样着手实现类似于action封装附件到Part对象一样的解析附件的方法也不是不可行。


三、struts2中附件上传下载方法

分享完如何在基本的servlet中上传文件下载的方法,现在我分享一下在struts2中上传下载文件的方法,下面我们就介绍一下这个方法。

1)下载struts2依赖包地址:http://struts.apache.org/download.cgi#struts251,我是下载比较新的版本:struts-2.3.29-lib.zip,解压该包,可以在lib中看到有很多的组件包。

2)配置struts2环境,目录如下:

注意,jar包一个都不能少。从项目目录中可以看到虽然struts2核心框架本身并没有提供上传附件的方法,但是在struts-2.3.29-lib.zip中默认提供了apache的commons-fileupload-1.3.1.jar组件,所以我们本节用该组件上传附件。此外commons-io-2.2.jar是commons-fileupload-1.3.1.jar依赖文件流框架。搭建好环境后我们准备上传文件的配置。

3)配置上传解析器

struts2上传需要配置解析器,其默认配置的上传下载框架位于org.apache.struts2包下的default.porperties资源文件中。如下图,我们可看到struts2默认是将jakarta作为其文件上传的解析器。

jakarta是commo-fileppload的框架,即我们上面导入的commons-fileupload-1.3.1.jar组件,如果不用该框架可以将对应的注释代码中的一个打开:

# struts.multipart.parser=cos
# struts.multipart.parser=pell
# struts.multipart.parser=jakarta-stream

当然这都是struts2支持的上传框架,更改解析框架后需要像导入commons-fileupload-1.3.1.jar一样导入指定jar包,这里不做详细解释。此外,default.porperties中的常量配置也都可以在struts.xml中配置,并且struts.xml中的配置优先级更高。如,如果我们指定上传框架是cos,那可以在struts.xml中这样配置:<constant name="struts.multipart.paeser" value="cos"></constant>,为了说明这种方式可行,我们也在struts.xml中配置commo-fileupload解析器,完整的staruts.xml内容如下(action对应的类文件暂时不用关注将在后面讲解):

<?xml  version="1.0"  encoding="UTF-8"?> 

<!DOCTYPE struts PUBLIC"-//Apache
Software Foundation//DTD Struts Configuration 2.3//EN"

       "http://struts.apache.org/dtds/struts-2.3.dtd">

<struts>

   <!-- <constant name="struts.enable.DynamicMethodInvocation"

        value="ture"/>

   <constant name="struts.devMode" value="true"/>

   <constant name="struts.i18n.encoding" value="UTF-8" />

   <constant name="struts.configuration.xml.reload" value="true"/>

   <constant name="struts.serve.static.browserCache" value="false"/>

   <constant name="struts.configuration.xml.reload" value="true"/>

   <constant name="struts.ui.theme" value="simple"/> -->

   <!--上传框架解析器 -->

   <constantname="struts.multipart.parser" value="jakarta"></constant>

    <!--struts后缀  -->

    <constant name="struts.action.extension" value="do,action"></constant>

    <!--最大文件大小这里是4MB -->

    <constant name="struts.multipart.maxSize" value="33554432"/>

    <!--临时文件 -->

    <constantname="struts.multipart.saveDir " value="d:/temp" />

    <packagename="com.code2note" namespace="/" extends="struts-default">

        <global-results>

            <resultname="error">/error.jsp</result>

        </global-results>

        <global-exception-mappings>

            <exception-mapping exception="java.lang.Exception" result="error"/>

        </global-exception-mappings>

        <actionname="upload" class="com.code2note.UploadAction">

          <!-- 动态设置savePath的属性值 -->    
         
  <!--<paramname="savePath">/images</param>-->

           <resultname="success" type="dispatcher">/result.jsp</result>

           <resultname="input">/Upload.jsp</result>  
           <interceptor-ref name="fileUpload">

              <!-- 动态文件过滤通过文件类型-->

              <!--<param name="allowedTypes">

                      doc/docx,image/bmp,image/png,image/gif,image/jpeg

                     </param>-->

                    <param name="allowedExtensions">

                          doc,docx,xls,xlsx,png,jpg,jpeg,zip,rar,txt

                     </param>

                      <!-- 动态设置文件大小, 以字节为单位 -->

                      <param name="maximumSize">33554432</param>
            </interceptor-ref> 
            <!-- 默认拦截器必须放在fileUpload之后,否则无效 -->       
            <interceptor-ref name="defaultStack"/>

        </action>

    </package>

</struts>

注意灰色部分都不是必须的,但是对于文件大小过滤等参数可以通过配置常量(struts.multipart.maxSize)或者在指定的action中配置,特别注意在action中动态配置的上传文件参数中allowedExtensions表示上传文件的类型过滤,allowedExtensions表示按照文件后缀名过滤,若二者同时存在时按照类型过滤,我用后缀名过滤。最后在action中配置的param savePath 也要在Action中定义该属性,本例中没有使用。

完整的web.xml文件配置如下,用于struts框架,这里用的是struts2.3.29:

前台的Upload.jsp文件内容如下:

后台上传处理UploadAction.java文件,最终结果如下:

public classUploadAction extends ActionSupport {

   // desc 普通表单和前台表单的name相同

   private String desc;

   // testfile 文件注意和前台表单file的name一致

   private File testfile;

   // testfileContentType属性用来封装上传文件的类型

   // 注意前缀和testfile一致

   private String testfileContentType;

   // testfileFileName属性用来封装上传文件的文件名

   // 注意前缀和testfile一致

   private String testfileFileName;

   public String execute() throws
Exception {

      InputStream is = new FileInputStream(testfile);

      String uploadPath = ServletActionContext.getServletContext()

           .getRealPath("/upload");

      File file = new File(uploadPath);

      if(!file.exists()){

        file.mkdirs();

      }

      file=new File(file, this.getTestfileFileName());

      OutputStream os = new FileOutputStream(file);

      byte[] buffer = newbyte[1024];

      int length = 0;

      while ((length = is.read(buffer)) > 0) {

        os.write(buffer, 0, length);

      }

      is.close();

      os.close();

      return SUCCESS;

   }

   //setters and getters 略

   //...

}

Action将附件封装到testfile中并且还自动封装了文件名称,文件类型等,在处理附件的时候也可以不直接调用流操作,另一种方式我将在struts2多文件上传中使用。

4)struts2多文件上传

如何实现多文件上传?可以看到在单文件上传的时候,testfile封装了一个上传文件,testfileFileName封装了上传文件的名称等,联想到多个文件,是不是可以用数组或者list方式封装附件,代码如下:

public class UploadAction2 extends ActionSupport {

 // desc 普通表单和前台表单的name相同
 private String desc;

 // testfile 文件注意和前台表单file的name一致
 private List<File> testfile;

 // testfileContentType属性用来封装上传文件的类型
 // 注意前缀和testfile一致
 private List<String> testfileContentType;

 // testfileFileName属性用来封装上传文件的文件名
 // 注意前缀和testfile一致

 private List<String> testfileFileName;
 public String execute() throws Exception {
  if (testfile != null) {
   String uploadPath = ServletActionContext.getServletContext()
     .getRealPath("/upload");
            File savedir=new File(uploadPath);
            if(!savedir.exists())
                savedir.mkdirs();
            for(int i=0;i<testfile.size();i++){
                File savefile = new File(savedir, testfileFileName.get(i));
                FileUtils.copyFile(testfile.get(i), savefile);
            }
        }
        return SUCCESS;
 }

  //getters setters 略

  //...

}

事实的确是可以的,上传的多文件被封装成list(或者数组),这样testfile封装了多个文件,我们就可以获取各个文件并保存到服务器,在execute方法中我们用FileUtils的copy方法保存附件,这样用的好处不言而喻。此外注意前台的jsp中各个文件的name属性一定相同且是testfile。

前台jsp页面如下:

在struts.xml中添加相应的action,这个和单文件配置的方式相同:

显然这种方式上传文件的前台比较简陋,比如没有任何信息提示上传过程。在后面Spring的上传文件中我们将会介绍使用jquery的uploadify作为前台UI实现动态跟踪上传过程,当然在struts2上传文件也是可以用的。

5)struts中附件下载

方式一:和servlet中的差不多,在Action中增加download方法作为下载的处理action,代码如下:

publicString download()throwsException{
  File infile=new File("d:/测试图片.jpg");
  InputStream in =new FileInputStream(infile);
  byte [] b=new byte[1024];
  int l=0;
  HttpServletResponse res=ServletActionContext.getResponse();


  //不是必须的
  res.reset();
  res.setContentType("application/x-msdownload;charset=UTF-8");
  
  //注意文件名称反之乱码唯一解决办法
  res.addHeader("Content-Disposition", "attachment;filename=\""
    +java.net.URLEncoder.encode(infile.getName(), "UTF-8")+"\"");
  res.getHeader("Content-Disposition");
  ServletOutputStream  out =res.getOutputStream();
  while((l=in.read(b))!=-1){
   out.write(b, 0, l);
  }
  in.close();
  out.close();
  //return SUCCESS;//error
  //避免调用res.getOutputStream().write和action默认调用冲突而终止文件下载;
  //返回null可以避免将异常抛出到页面

  return null;

}

struts.xml中配置新的以方法名作为action的节点:

<actionname="download" class="com.code2note.UploadAction"
                method="download"> </action>

前台就不说了,随便在form表单中添加一个按钮,提交到下载的action即可。

方式二:正宗的struts下载,新建DownloadAction,代码如下:

public class DownloadActionextends ActionSupport {
  //保存文件名
  private String filename;
  //可有前台提交一个文件路径
  private String path;
  //下载数据流 这里使用默认的方法
  public InputStream getInputStream()throwsException{
     //d:/测试图片.jpg
     File infile=new File(this.getPath());
  InputStream in =new FileInputStream(infile);
  this.setFilename(java.net.URLEncoder.encode(infile.getName(), "UTF-8"));
        return in;  
}
 public String execute()throwsException{ 
        return SUCCESS;  
  }

 //getters setters 略...

struts中配置如下:

<actionname="download2" class="com.code2note.DownloadAction">  
      <result type="stream" name="success">  
      <param name="contentType">application/x-msdownload;charset=UTF-8</param>
                
      <!--动态获取action中要下载的文件名称,注意$用法是取acction中定义的属性  -->
      <param name="contentDisposition">attachment;fileName="${filename}"</param>
      <!--该参表示下载调用哪一个流方法和流方法去掉get相同,默认为inputStream --> 
      <!--也就是说如果在action的下载流方法名不是  getInputStream需要用该参数指定具体方法-->
        <!-- <param name="inputName">downstream</param> --> 
         <paramname="bufferSize">33554432</param>  
         </result>  
   </action>

注意返回中的相关参数配置:

(1)结果类型必须要写成 type="stream" ,与之对应的处理类是 org.apache.struts2.dispatcher.StreamResult;涉及到的参数参查看源代码得到见图:

(2)<param name="contentDisposition">attachment;fileName="${filename}"</param>

  • contentDisposition:默认是inline(内联的), 比如说下载的文件是文本类型的,就直接在网页上打开,不能直接打开的才会打开下载框自己选择,

    而用attachment 后下载时会打开下载框。

  • fileName="${fileName}" :在这定义的名字是一个动态的,该名字是显示在下载框上的文件名字,注意fileName是DownloadAction的属性。

(3)<param name="inputName">downstream</param>,这个downstream名字要和指定的action类中的getDownstream()方法名去掉get一致。一个action可以定义多个数据流获取方法,可在struts中分别指定,默认调用getDownstream()方法,如果名称不同需要重新配置该节点的内容,如如果下载流方法是getDownloadFile则配置成<param name="inputName">downloadFile</param>

struts2中也可以使用smartupload插件下载,但是该插件下载文件大小限制10M,所以本篇分享不涉及该组件的方法,虽然用起来很简单。


四、SpringMvc中附件上传方法

这一小节主要研究一下如何在Spring中实现文件上传下载,由于现在使用的都是注释的方式,所以配置方式不做介绍,用的插件还是common-fileupload组件和struts一样,不过前端我们采用Jquery的uploadify组件实现动态跟踪。

1)下载springmvc环境,地址:http://cn.jarfire.org/org.springframework.flex.html,下载uploadify组件,地址:http://www.uploadify.com/demos/

2)搭建的springmvc项目:

3)相关文件分列如下:

web.xml文件:


springmvc-servlet.xml文件:


特别注意我们需要在springmvc-servlet.xml中配置上传文件解析器CommonMultipartResolver,该类将会在后面的Controller中用到。

UploadController.java文件:


Upload.jsp文件:


注意引入uploadify组件和Jquery组件,在Jquery初始化中,我们配置了uploadify组件并将其绑定到文件表单,这样我们操作附件就是直接操作uploadifyUI。

4)在浏览器测试效果如下:



可以看到我们的多个文件都显示在列表中,并且上传都有跟踪效果。

5)下载文件的方式和servlet、staruts2中差不多,上面上传代码中都包括了,应该不用再单列了。


五、本篇所有demo

Upload.zip:http://pan.baidu.com/s/1eSltFii密码:mzai

UploadStruts.zip:http://pan.baidu.com/s/1geMpkFT密码:9pwd

UploadSpringMvc.zip:http://pan.baidu.com/s/1miGp73y密码:k96g


-----------------

本篇分享就到这里,上传下载确实很简单,但是开发项目的时候有时候就感觉很混淆,于是就想到把它总结出来,方便以后查询。此外本文对servlet、struts、springmvc以及注解都有涉及,如果这些东西帮助到了你,这纯属意外。哈哈不多说了,这篇文章写第二遍了,第一遍莫名其妙的丢失了,喜欢这篇文章的朋友一定不要留意一下我,我的公众号:code2note,我会持续更新公众号文章。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值