B/S系统的文件上传,最基本的就是通过form的文件域进行上传,不过这样看不到上传进度,甚至上传之前不能获得文件大小——至少IE不行。既然传统的上传用户体验太差,就想办法换用其他方法实现上传吧。最先尝试的是叫做ajax上传的方式,实现了无刷新上传。具体上传机制没有深入研究,大概是通过构建iframe的方式。

再然后就是比较流行的flash上传,貌似126邮箱的附件上传就是用这种方式。flash上传好处多多,无刷新上传、可以实时反馈上传进度,上传前能获取文件大小,甚至上传前可以对要上传的图片进行缩放裁切。缺点大概就是需要在网页中嵌入flash,如果浏览器不支持flash的话就没办法了。

html5的文件上传好像更强大,还支持拖放上传,不过在国内环境下考虑到还有很多用户在用IE6……短期内是没法用了。这次优化文件上传组件,没有太明确的目标,基本上两部分工作:选型与整理使用文档,还有就是要针对目前平台需要制定上传流程。首先进行选型,当然之前的工作已经基本选定swfupload了,并且也是淘宝前端js库kissy的推荐第三方组件。

选型

首先是想既然已经广泛使用了JQuery,如果有基于JQuery对swfupload进行包装的上传组件岂不是更好。这种东西果然有。

uploadify

uploadify是之前在使用过swfupload之后发现的,这次稍微深入了解一下。官方网址是:http://www.uploadify.com/ ,网站风格清新简洁。提供的例子有两个,一种是基本的使用,另一个是自定义的。尝试上传了几个,都不成功。下载了个最新的3.0.0beta版,对照文档进行配置,一直加载不到swf文件。仔细一看原来文档是针对2.1.4版,3.0.0暂时还没有文档,并且新版本和旧版本配置参数变化较大。新版中列举的特性确实很诱人,但是还没有正式版且没有文档,暂时再等等吧,等到新版成熟稳定了之后再回来看。

jqswfupload

jqswfupload是我使用jquery和swfupload做关键字查询出的上传组件,顾名思义,一个基于swfupload的jquery ui插件。项目托管在GitHub上:https://github.com/alexanmtz/jqswfupload 。可惜提供的demo页面始终无法打开,并且本身也不是广泛使用的上传组件,于是很快就放弃了尝试。

jquery file upload

jquery file upload 是我拿jquery和upload做关键字搜出来的,也是托管在GitHub上:https://github.com/blueimp/jQuery-File-Upload demo的页面可以打开,不过尝试上传了几个文件,都没有成功。比较瞩目的特性是不需要浏览器插件,也就是说不需要使用flash。貌似对老的浏览器使用iframe,现代浏览器则使用html5。牵扯到的文档比较多,一时也没有精力深入研究,并且它提供的默认界面不是很符合项目需要,定制的话看上去还是颇有些难度。这个项目可以适时关注一下,有精力再详细了解一下,可以作为swfupload的互补方案。

swfupload

swfupload是之前研究过的flash上传组件,并且在新版物流平台开发之前已经做出一个可用于项目的小例子,后来不知为何据说IE6下报错,而当时我有其他事情没能参与分析,问题没能解决于是就放弃了,最后所有涉及文件上传的部分还是使用文件域提交表单……

隔了这么久再来看swfupload,比我之前使用时又有了一些升级,kissy推荐的稳定版版本号也比我之前使用的要高。找回原来的例子尝试运行了一下,果然报错了,首先是struts2命名空间的问题,然后就是IE6还会报JS错误。排查了一下发现是作为配置参数的json对象,最后的两项注释掉了,倒数第三个变成了最后一项但是忘记删除它后面的逗号……擦,真是个低级错误。该不会是当初因为这个问题没有解决导致整个项目都没有用起来吧?这也太可惜了。

修复完之后,只剩IE6下我用div+css实现的进度条仿佛不受高度控制的变粗了,这个问题之前解决过,IE6的空元素最小高度受字体高度限制。目前唯一担心的是flash插件的版本问题,文档上说swfupload2已经不支持flash8,只支持9、10,并且10因为有严格的安全机制他们又做了些妥协。我看了一下我的chrome flash插件版本号已经是11了,上传没问题,天知道那些IE6用户的flash都自动升级了没有。

上传流程

其实相对于前面说的选型,整理文档来说,我认为整理上传流程才是这次工作的重点。之前使用传统方式上传,将上传的文件和其他的表单项一起提交,在添加操作时还好说,但在更新操作时就有问题了。且不说更新时有文件上传还得先删除原来的文件,如果不上传文件呢?如果用户仅仅想修改其他表单项中的数据还好,如果不上传文件就保持原来上传的文件不动;但是如果用户的意图是想删除以前上传的文件呢?

把上传文件和其他表单项混在一起不是个好主意,无论用户想修改资料还是重新上传文件,都得重新提交表单。较好的做法是把提交表单和上传文件的操作分离。用户在修改资料的时候可以不去动上传的文件,也可以随时删除文件或者进一步上传新文件。这种做法会出现的问题在添加操作上。如果用户上传了文件但是最后没提交表单怎么办?很多情况下上传的文件是作为一条记录的附件而存在的,首先添加操作中用户提交表单之前这条记录并不存在,上传的文件也没有依附的主体,其次如果用户这时不提交表单就关闭了页面,将产生垃圾文件。

解决的办法有两种,第一种就是把上传的文件放在一个临时目录中,上传完成后将代表这个文件的标识符(可以是文件名)返回给客户端,客户端逻辑将这个标识符放在表单的隐藏域中随表单一起提交,提交之后将文件从临时目录拷贝到正式目录中。临时目录的文件可以定期清除,或者每上传一个文件就设定一个计划任务,在超时之后删除。第二种是在转入表单页面之前就插入一条临时记录,并将这条记录的id放在表单的隐藏域中,上传文件时将id一起提交,上传完文件就可以更新这条临时记录的附件字段,提交表单后再更新这条记录的其他字段并将记录改为有效记录。标志记录状态可以用单独的状态字段,也可以用有效记录的必填项字段。用户可能没有提交表单就离开表单页,但下次用户再进行添加操作时,再把这条临时记录放在表单里呈现给用户就可以了。这个方法有个有趣的副作用,所有提交表单之后进行的操作就都是更新操作了。

上传文件的保存

上传文件在服务器的保存也是一个问题。项目初期上传文件放在应用的部署目录下,这样弊端很大:每次项目重新部署都需要备份恢复上传文件。后来改到一个独立的目录,应用服务器访问不到,必须借助web服务器。正常情况下这样已经很好了,但还可以继续改进。

随着时间流逝,上传目录中的文件越来越多,超过一定量后操作系统对文件的检索会非常慢。解决方法是把上传的文件根据时间分到不同的目录中。负载比较重的图片服务器还会考虑把文件分散在不同服务器中。

这就引入第二个问题,如何将文件上传到应用服务器外的其他服务器。虽然目前物流平台达不到那种访问压力,但是对于开发调试来说是个很现实的问题。每个开发人员共用同一个开发数据库,但是每个人却只能往自己的开发机器上传文件,其他人虽然可以看到这条记录,却无法访问到对应的上传文件。初步考虑可以使用ftp将上传文件发送到一个统一的地方去。

最后还有一个问题,用户可能会一次次的把相同的东西保存进来,虽然文件名可能不一样。服务端没理由保存那么多相同文件的副本,太浪费存储空间了。可以使用一种哈希校验算法,比如SHA-1,算出一个校验值,这个字符串相当于文件的指纹,相同的文件具有相同的SHA-1哈希值。保存文件之前先检索是否已经保存过相同文件,若没有则保存文件,若已经有了则相应文件的引用数加一就可以了。删除文件前也是先减引用数,减到零的时候再真正把文件删除。



blog comments powered by Disqus