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

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

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

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

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


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

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


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

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

这里来个标准的实现:

 

        //创建新专题
	@POST
	@Path("createSubject.do")
        @Produces("application/json;charset=UTF-8")
	@Consumes(MediaType.MULTIPART_FORM_DATA)
	public String createSubject(@FormDataParam("pic") InputStream imageInputStream,
			@FormDataParam("pic") FormDataContentDisposition imageDetail)
        {
                                //获取工程根目录
				String rootPath=new File("").getAbsolutePath();
                                rootPath+= File.separator+"webapps";
				
				//拼接文件目录
				String imageFileLocation = rootPath + File.separator+"res"+ File.separator
						+ System.currentTimeMillis() + "."
						+ FileUtil.getEndWith(imageDetail.getFileName());
                                File image=writeToFile(imageInputStream, imageFileLocation);
                                SimpleJSONObject res=new SimpleJSONObject();
                                res.add("status", 1);
		                res.add("msg", "创建专题成功");
		                return res.toString();
        }


        public static File writeToFile(InputStream is, String uploadedFileLocation) {
		// TODO Auto-generated method stub
		File file = new File(uploadedFileLocation);
		OutputStream os = null;
		try {
			os = new FileOutputStream(file);
			byte buffer[] = new byte[4 * 1024];
			while ((is.read(buffer)) != -1) {
				os.write(buffer);
			}
			os.flush();
		} catch (Exception e) {
			e.printStackTrace();
		} finally {
			try {
				os.close();
			} catch (Exception e) {
				e.printStackTrace();
			}
		}
		System.out.println(uploadedFileLocation+"文件大小"+file.length());
		if (file.length()<5) {
			file.delete();
			return null;
		}
		return file;

	}


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

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

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

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

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

<input type='file' name='file'><br>
<input type='file' name='file1'><br>
<input type='file' name='file2'><br>
...
<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:


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

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

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

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

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

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

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

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

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



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

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


html表单:

<form id="classForm" method="post" enctype="multipart/form-data" action="../api/createClass.do">

						<fieldset>
							<legend>请尽量填写完整这些信息。</legend>
							<input  type="text" hidden="hidden" id="apiKey" class="half" value='<%=me.getApiKey() %>' name="apiKey"/>
							<p>
								<label class="required" for="text">文字内容:</label><br/>
								<input type="text" id="text" class="half" value='' name="text"/>
								<small>例如:今天的课太无聊啦!</small>
							</p>
							<p>
								<label class="required" for="subject">专题:</label><br/>
								<select id="subject" name="subject" class="half">
									<option >请选择专题</option>
								
								</select>
								
							</p>
							<div id="files">
							<p>
								<label class="required" for="file">图片:</label><br/>
								<input type="file" id="file" class="half" value='' name="file"/>
							</p>
							
							</div>
							<div id="addition"></div>
							<label class="btn" οnclick="addMore();">不够!我还要添加图片</label>
							<p>
								<label class="required" for="file">语音:</label><br/>
								<input type="file" id="file" class="half" value='' name="file"/>
							</p>
							
							
						
							
							
						
							
							
							
						</fieldset>

					</form>

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

function addMore() {
	var html=$("#files").html();
	var html2=$("#addition").html();
	html2+=html;
	$("#addition").html(html2);
}

效果:






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

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

两张图片的


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



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

  • 9
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 5
    评论
您可以按照以下步骤将JAX-WS接口替换为JAX-RS接口: 1. 创建JAX-RS接口:创建一个新的Java接口来定义您的JAX-RS服务。在接口上使用`@Path`注解指定资源的URL路径。 ```java import javax.ws.rs.GET; import javax.ws.rs.Path; import javax.ws.rs.Produces; import javax.ws.rs.core.MediaType; @Path("/your-resource") public interface YourResource { @GET @Produces(MediaType.APPLICATION_JSON) String getResource(); } ``` 2. 实现JAX-RS接口:创建一个类来实现您的JAX-RS接口,并实现接口中定义的方法。 ```java public class YourResourceImpl implements YourResource { @Override public String getResource() { // 实现您的业务逻辑 return "Hello JAX-RS!"; } } ``` 3. 注册JAX-RS服务:将您的JAX-RS服务注册到应用程序中。这可以通过创建一个`javax.ws.rs.core.Application`子类并在其中注册资源类来完成。 ```java import javax.ws.rs.ApplicationPath; import javax.ws.rs.core.Application; import java.util.HashSet; import java.util.Set; @ApplicationPath("/api") public class YourApplication extends Application { @Override public Set<Class<?>> getClasses() { Set<Class<?>> classes = new HashSet<>(); classes.add(YourResourceImpl.class); return classes; } } ``` 4. 配置JAX-RS:根据您使用的应用程序服务器,将JAX-RS实现(如Jersey或RestEasy)添加到您的应用程序的构建配置文件中。您还需要确保在应用程序服务器上正确配置JAX-RS。 5. 测试JAX-RS接口:启动您的应用程序服务器,并使用JAX-RS客户端或浏览器等工具测试您的JAX-RS接口。 请注意,以上步骤是一般的指导,具体步骤可能因您使用的框架和工具而有所不同。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值