现在的位置: 首页 > 综合 > 正文

文章附件上传:

2013年01月24日 ⁄ 综合 ⁄ 共 2656字 ⁄ 字号 评论关闭

背景:

因系统存储改造,文件由磁盘存储改为数据库存储,而文件上传处理一直性能不佳,经公司内部网络测试,上传133MB的文件平均耗时80秒左右(甚至数次达到90多秒),时间较长,急需对文件处理过程进行优化改进。

鉴于原struts文件上传处理的实现,包括xx 使用的CommonsMultipartRequestHandler,都是在调用action之前就已经接收上传文件,并保存为临时文件,再将临时文件的实例赋予FormFile,传到action使用。也就是在程序运行到action时,文件实际已经上传到服务器了,而我们还再做了一次复制操作,将文件复制到特定的目录。文件被复制了两次,而且都涉及磁盘IO操作(尤其是上传写入临时文件时,而在服务器上的文件复制,因为是本地磁盘复制,性能相对要好些)耗费时间。

 

改进:

经再次对FileUpload组件的调研,发现它能够支持直接对multipart request的文件输入流操作,可以不需要保存临时文件,直接将文件输入流存入数据库表,省掉了保存临时文件,再打开读取临时文件存入数据库表的过程。经初步测试,同样上传133MB的文件平均耗时减少至30秒左右,缩短将近1分钟,当然,应用效果仍需进一步测试检验。

新的文件上传处理类StreamingMultipartRequestHandler,除了在处理过程进行优化外,还提供一项特性:内置文件上传记录。就是在完成文件存入数据库的同时,也完成了表其他字段的填写,最终传入到action的FormFile,将带着文件ID等tb_xxxx 的字段数据,供开发人员直接调用。换句话说,以后不需要在action内对文件上传做任何编程(现有action里的upload处理方法都可以删掉了),而直接使用FormFile所带的信息进行业务操作,例如:将文件ID(假设是上传合同附件)赋给合同对象保存至合同信息

StreamingMultipartRequestHandler除支持xx 系统的数据库存储文件外,也支持磁盘存储文件,同样的处理过程,在StreamingMultipartRequestHandler内完成文件输入流存入文件的操作,并生成tb_xxxx 表记录,接着FormFile带着tb_xxxx 的数据(还有文件大小)传入action,开发人员直接调用。在action不需要再次复制文件(省掉复制文件的时间),也不需要编写upload方法(同样,action里已有的upload处理方法可以删掉)。

 

配置:

功能程序包括两个类:StreamingMultipartRequestHandler、StreamingFormFile,另需要commons-fileupload-1.2.1.jar、commons-io-1.4.jar支持

以上所说FormFile的文件ID、保存路径等数据,需转换类型为StreamingFormFile才有对应方法。

例:StreamingFormFile file = (StreamingFormFile) ((DynaBean) form).get("accessory");

以现有config目录下的global.properties作为配置文件,配置项有三项:

upload.mode

上传处理模式,"disk"将上传的文件保存在磁盘,保存路径通过root.path、sub.path定义;"db"将上传的文件保存在数据库,保存于tb_xxxxx 表(表结构参照xx项目)

root.path

文件上传根路径,在上传模式为disk(磁盘保存)时有效,不使用t_drawer表的定义,减少数据库查询操作

sub.path

文件上传子路径,格式为key(键值,用于匹配对应子路径)+ 半角冒号 + value(子路径),用半角逗号分隔多组子路径,不使用t_xxxx 表的定义,减少数据库查询操作

示例(也可以参考xxx 项目的global.properties)

#磁盘存储文件
upload.mode=disk
#根路径 
root.path= 
#declaceStock:/declaceStock 申报书附件
#stprAccessory:/stprAccessory 项目资料
sub.path=d

 

应用说明:

这里特别说明下磁盘存储文件的应用方式,为了识别文件保存的路径,需要在input标签的name属性内标识,例如: <input type="file" name="file_declaceStock"/>,程序会识别第一个"_"半角下划线后的部分作为global.properties内sub.path项的匹配键值,取"file_declaceStock"第一个"_"半角下划线后的部分"declaceStock",匹配declaceStock:/declaceStock的键值declaceStock,取半角冒号后的字符串"/declaceStock"作为子路径,和根路径合并,得到完整的文件保存路径:

使用StreamingMultipartRequestHandler后,FormFile的getInputStream()等方法(具体可查看StreamingFormFile代码)已弃置,调用会抛出UnsupportedOperationException异常。考虑过支持现有方式,但这又恢复到两次文件操作的情景,改进就没了效果,所以弃置相关方法。

结合以上的说明,在此明确一点,使用StreamingMultipartRequestHandler将涉及到现有上传功能点的程序修改,如:删除action内文件上传处理代码(因为都是用getInputStream()实现的,StreamingFormFile不支持);使用磁盘存储模式的,页面的<input type="file" name=""/>标签,name值需要按要求改;磁盘存储模式还需要配置global.properties的sub.path。这些改动对于已有系统是有一定工作量的,需评估后再做决定是否更新。

 

结语:

代码现保存在项目内,待正式上线后,会考虑整合到sniper.jar中,这步需要的评估,确认是否合适。

 

希望能给各个项目带来帮助改善。

在log4j.properties里加入log4j.logger.com.sniper.core.web.support=DEBUG,设为DEBUG级别,可以在后台看到handler整个处理耗时,显示为"handler spend time: "

抱歉!评论已关闭.