【绝对干货来啦】巧用jax-rs之jersey实现不确定数量、多类型文件批量一次上传接口

原文地址

非常棒的一篇教程,很实用。

如果使用的是com.sun.jersey加入下面的包

<dependency>
            <groupId>com.sun.jersey.contribs</groupId>
            <artifactId>jersey-multipart</artifactId>
            <version>1.19</version>

        </dependency>

如果使用的时glassfish则加入下面的包:

<dependency>
    <groupId>org.glassfish.jersey.media</groupId>
    <artifactId>jersey-media-multipart</artifactId>
    <version>2.21</version>
</dependency>


以下是原文内容:

好久没更新博客啦,跟各位博友说声抱歉~~~今天这篇博文的标题不知道是否有些拗口,如果是 那请容许我解释下为何有这篇博文:

大家都知道我们在处理网络请求的时候一般分为两种:

  1. 普通表单
  2. multipart/formdata表单

这两种表单在html上的区别很直接,前者不需要修饰,后者需要enctype="multipart/form-data" 这一个属性来修饰所在的html。


但是如果我们的html中的表单提交被js(jquery)所代劳了,那么jquery内部是很聪明的,即使你没有用multipart/form-data去修饰,只要有文件他在ajax提交的时候会自己帮你转换。

而在我们的ios/android基本上道理相同,如果你使用了较为成熟的框架,他会自动检测你的提交数据中是否有文件流而帮你转换格式,例如我们的ASIHttpRequest就会聪明的判断,使服务器端更容易处理。


===================分割线==========================

想必使用JAX-RS中的jersey实现来处理单文件上传的接口,这个太过于简单了,这里就不仔细说了,相信网上一搜一大片。

这里来个标准的实现:

 

  1.        //创建新专题  
  2. @POST  
  3. @Path("createSubject.do")  
  4.        @Produces("application/json;charset=UTF-8")  
  5. @Consumes(MediaType.MULTIPART_FORM_DATA)  
  6. public String createSubject(@FormDataParam("pic") InputStream imageInputStream,  
  7.         @FormDataParam("pic") FormDataContentDisposition imageDetail)  
  8.        {  
  9.                                //获取工程根目录  
  10.             String rootPath=new File("").getAbsolutePath();  
  11.                                rootPath+= File.separator+"webapps";  
  12.               
  13.             //拼接文件目录  
  14.             String imageFileLocation = rootPath + File.separator+"res"+ File.separator  
  15.                     + System.currentTimeMillis() + "."  
  16.                     + FileUtil.getEndWith(imageDetail.getFileName());  
  17.                                File image=writeToFile(imageInputStream, imageFileLocation);  
  18.                                SimpleJSONObject res=new SimpleJSONObject();  
  19.                                res.add("status"1);  
  20.                     res.add("msg""创建专题成功");  
  21.                     return res.toString();  
  22.        }  
  23.   
  24.   
  25.        public static File writeToFile(InputStream is, String uploadedFileLocation) {  
  26.     // TODO Auto-generated method stub  
  27.     File file = new File(uploadedFileLocation);  
  28.     OutputStream os = null;  
  29.     try {  
  30.         os = new FileOutputStream(file);  
  31.         byte buffer[] = new byte[4 * 1024];  
  32.         while ((is.read(buffer)) != -1) {  
  33.             os.write(buffer);  
  34.         }  
  35.         os.flush();  
  36.     } catch (Exception e) {  
  37.         e.printStackTrace();  
  38.     } finally {  
  39.         try {  
  40.             os.close();  
  41.         } catch (Exception e) {  
  42.             e.printStackTrace();  
  43.         }  
  44.     }  
  45.     System.out.println(uploadedFileLocation+"文件大小"+file.length());  
  46.     if (file.length()<5) {  
  47.         file.delete();  
  48.         return null;  
  49.     }  
  50.     return file;  
  51.   
  52. }  

注意这里为什么说他一定是针对单个文件的上传呢,因为前文已经说了我们既然是上传文件,那么文件必定是以I/O流的形式来传输的,对服务器来讲,他铁定是InputStream了,而这里我们将InputStream 以WebService注解的形式作为参数声明到方法中,实际上无论怎样我们都只能获取一个InputStream,即一个文件。即使你上传了多个文件,我们也只会获取一个,如果你企图多次调用这个inputStream对象,那么会抛出该inputStream已经被关闭的异常。

所以本文的目的是在于探讨如果多快好省地实现多个、不定数量、多类型的文件上传策略。

众所周知有一个笨方法很多人都在用,那就是给我们这个接口制定多个文件参数,即:

除了你可以上传文件file之外,你还能上传file1,file2,file3,file4....理论上限是程序员的耐心和服务器的承受能力。如果这样做的话,我们这个方法中的参数需要复制N次,并且以这种加后缀的参数命名方式愚蠢地进行下去。

而我们如果想调用这个接口,我们的html页面还得这样写:

  1. <input type='file' name='file'><br>  
  2. <input type='file' name='file1'><br>  
  3. <input type='file' name='file2'><br>  
  4. ...  
  5. <input type='file' name='file100'>  

虽然效果是好的,但是我认为既然jersey这么简单易用,那他肯定有别的办法可以避免这么愚蠢的行为,于是在我的研究下得到了一个好的解决办法:

所有的文件提交参数名都保持一致,例如都叫"file",而我们在获取的时候,不再获取具体的某个inputStream,而是获取整个multipart 表单体。

什么是multipart表单体?如果有兴趣的朋友可以打开浏览器的调试工具,找到network这一栏,然后找个带文件的表单提交一下 看看他的报文头,例如我写的这一个表单:


我们提交他,看看报文头:



可以明显地看到 我们刚刚表单中的几个字段都已经以"WebKitFormBoundaryxxxxx"什么的分开了,而每一项正好对应着我们的input项。

大家可以试试不带文件的表单提交,报文头是否是有这个"WebKitFormBoundaryxxxxx",此处略去关于他的废话,关键来了,这里有我们可以看到他的文件名,文件类型,这是上面的text字段所没有的,这就是文件的特殊之处,当然更特殊的地方就在于他的inputStream了,我们在这里是看不到的。




402881e843a0279e0143a027edc70000

------WebKitFormBoundary8xwjqtqZ60aSNAIz

Content-Disposition: form-data; name="file"; filename="20130328010936284_easyicon_net_96.png"

Content-Type: image/png

------WebKitFormBoundary8xwjqtqZ60aSNAIz

Content-Disposition: form-data; name="file"; filename="Price.png"

Content-Type: image/png


不好意思由于截图工具不太好没截取完整,但是我们可以看到实际上2个file的name是一模一样的~而且他们都在报文中了,那么我们的jersey岂有只理会其中一个文件的道理?怎样把多个file的inputStream拿到手,就是此文的终极目标啦。



细心研究后发现,我们的multipartform被jersey划分为多个FormDataBodyPart,并且以一个List对象来存放,所以我们的策略就是直接获取这个multipart:


  1. @POST  
  2. @Path("addNewTopic.do")  
  3. @Consumes(MediaType.MULTIPART_FORM_DATA)  
  4. public String addNewTopic(@FormDataParam("apiKey") String apiKey,  
  5.         @FormDataParam("text") String text,  
  6.         @FormDataParam("subject") String subject,  
  7.         FormDataMultiPart form)  
就是最后一项了,这里我们没有用@来修饰他。

取到他之后,我们获取里面的每一个part:

  1. List<FormDataBodyPart> l= form.getFields("file");  

接下来,按道理 我们传了2个文件,就算是100个name是file的文件,我们都能够获取:

  1. for (FormDataBodyPart p : l) {  
  2.             InputStream is=p.getValueAs(InputStream.class);  
  3.             FormDataContentDisposition detail=p.getFormDataContentDisposition();  
其实是我们把最简单的方法中用@注解的参数手动获取了,但是他们现在在循环体内了。

接下来,我们除了接受不定数量的文件,还要判断他的类型!

  1. MediaType type=p.getMediaType();  
拿到它之后,他有个属性,getType()和getSubType分别获取出来是我们刚刚在报文头看到的image 和png,他们拼在一起就是MIME-Type image/png,这个东西我们手机端是可以手动修改了,ASI里面就有 ,大家记得吗?

接下来的操作如出一辙了:

  1. String fileLocation = rootPath + File.separator+"res"+ File.separator  
  2.                     + System.currentTimeMillis() + "."  
  3.                     + FileUtil.getEndWith(detail.getFileName());  
  4.             File file=writeToFile(is, fileLocation);  



好了,我们这个无敌万能的文件上传接口就做好了,他不仅可以接受不确定数量的文件,而且文件格式也来者不拒,都能分开处理,只需要对这个type进行判断了,如果是图片/音频则分开存放。

最后我们来写一个简单的html+js来测试这个接口:


html表单:

  1. <form id="classForm" method="post" enctype="multipart/form-data" action="../api/createClass.do">  
  2.   
  3.                         <fieldset>  
  4.                             <legend>请尽量填写完整这些信息。</legend>  
  5.                             <input  type="text" hidden="hidden" id="apiKey" class="half" value='<%=me.getApiKey() %>' name="apiKey"/>  
  6.                             <p>  
  7.                                 <label class="required" for="text">文字内容:</label><br/>  
  8.                                 <input type="text" id="text" class="half" value='' name="text"/>  
  9.                                 <small>例如:今天的课太无聊啦!</small>  
  10.                             </p>  
  11.                             <p>  
  12.                                 <label class="required" for="subject">专题:</label><br/>  
  13.                                 <select id="subject" name="subject" class="half">  
  14.                                     <option >请选择专题</option>  
  15.                                   
  16.                                 </select>  
  17.                                   
  18.                             </p>  
  19.                             <div id="files">  
  20.                             <p>  
  21.                                 <label class="required" for="file">图片:</label><br/>  
  22.                                 <input type="file" id="file" class="half" value='' name="file"/>  
  23.                             </p>  
  24.                               
  25.                             </div>  
  26.                             <div id="addition"></div>  
  27.                             <label class="btn" onclick="addMore();">不够!我还要添加图片</label>  
  28.                             <p>  
  29.                                 <label class="required" for="file">语音:</label><br/>  
  30.                                 <input type="file" id="file" class="half" value='' name="file"/>  
  31.                             </p>  
  32.                               
  33.                               
  34.                           
  35.                               
  36.                               
  37.                           
  38.                               
  39.                               
  40.                               
  41.                         </fieldset>  
  42.   
  43.                     </form>  

js(这里用了下jquery,也可以直接用DOM):

[javascript] view plain copy print ? 在CODE上查看代码片 派生到我的代码片
  1. function addMore() {  
  2.     var html=$("#files").html();  
  3.     var html2=$("#addition").html();  
  4.     html2+=html;  
  5.     $("#addition").html(html2);  
  6. }  

效果:






这里的不管你添加多少张文件,他的name都是"file",并且我们后台都可以通过一个枚举遍历里面的每一个文件~

看看最后发布成功的效果:

两张图片的


不定数量文件的(这里音频文件不好显示出来,所以都用的图片,但是原则上肯定是所有格式都支持的)



原创文章,版权所有,转载请注明转自墨半成霜的博客,谢谢~~


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值