文件上传功能在很多软件中都是必备的功能之一,所以,文件上传也就不显得那么有技术含量了,但是如果要把这个功能做好甚至完美,可不是三两下就能搞定的,其中包含的诸多细节尽在不言中。我们来看一下,做一个上传功能需要考虑什么,以apk文件上传为例:
1、上传的文件的类型(选择文件时对文件格式做限制)
2、上传的文件的大小(设置文件上传的最大范围)3、上传的文件的名称(这里包括加密后的名称和realName)
4、删除文件(必须考虑是否能真正删除)5、修改时能否回显文件
6、修改后保存文件应该怎么对文件进行处理
7、单独复制文件绝对路径能否下载
8、文件的相对路径生成
虽然总结出了八条需要考虑的点,但应该还是存在欠缺的地方的。常用的上传方式有两种:异步上传和同步上传,现在好像异步上传用的比较多一些,下面先以同步上传方式来说说吧:
同步上传就是先仅仅选择一个文件,填完其它内容后再统一提交到后台,在后台做文件上传的逻辑处理并保存一个完整内容。一般来说,我们在数据库中都会用一个字段(URL)来存储文件的相对路径,但是这个最终存储到数据库里的URL是要在后台生成的,因此,提交表单时将文件同请求一起发送到后台即可。下面以app版本管理功能为例讲解。
app版本添加页面:
JSP代码:
<form:form id="inputForm" modelAttribute="safeFireAppVersion" action="${ctx}/safe/fire/safeFireAppVersion/save" method="post" class="form-horizontal"
enctype="multipart/form-data">
<form:hidden path="id"/>
<sys:message content="${message}"/>
<div class="control-group">
<label class="control-label">版本号:</label>
<div class="controls">
<form:input path="code" htmlEscape="false" maxlength="16" class="input-xlarge required"/>
<span class="help-inline"><font color="red">*</font> </span>
</div>
</div>
<div class="control-group">
<label class="control-label">版本数:</label>
<div class="controls">
<form:input path="codenum" htmlEscape="false" maxlength="3" class="input-xlarge required"
οnkeyup="this.value=this.value.replace(/\D/g,'')"
onafterpaste="this.value=this.value.replace(/\D/g,'')" />
<span class="help-inline"><font color="red">*</font> </span>
</div>
</div>
<div class="control-group">
<label class="control-label">文件:</label>
<div class="controls">
<form:input path="" type="file" name="apkFile" accept=".apk"/>
<br/>${safeFireAppVersion.realName }
</div>
</div>
<div class="control-group">
<label class="control-label">备注:</label>
<div class="controls">
<form:textarea path="remarks" htmlEscape="false" rows="4" maxlength="255" class="input-xxlarge "/>
</div>
</div>
<div class="form-actions">
<shiro:hasPermission name="safe:fire:safeFireAppVersion:edit"><input id="btnSubmit" class="btn btn-primary" type="submit" value="保 存"/> </shiro:hasPermission>
<input id="btnCancel" class="btn" type="button" value="返 回" οnclick="history.go(-1)"/>
</div>
</form:form>
通过观察代码可以发现,path属性的名称与这个实体对象的属性相对应且名称相同,提交表单时会自动把这些属性的值存到modelAttribute中传到后台,我们不可以直接选择文件后就直接得到相对路径存到modelAttribute中,只有经过后台处理后才可以进行存储,所以,在页面中文件只是一个独立的文件而已,path的值可以不填甚至不要这个属性,但是name属性是必须要用到的,这是用于后台识别文件的唯一标识。这就好比采摘水果,传统售卖摘下来就可以拿去卖了,因为客户需要的只是普通水果,而果汁饮料需要采摘下来再加工后做成饮料产品才能满足客户需求。
表单提交的方式为POST,我们只要在后台利用Springmvc的@RequestParam注解就可以对前台发送过来的文件进行接收了。后台代码如下:
@RequiresPermissions("safe:fire:safeFireAppVersion:edit")
@RequestMapping(value = "save")
public String save(@RequestParam("apkFile") MultipartFile apkFile, SafeFireAppVersion safeFireAppVersion, Model model, RedirectAttributes redirectAttributes) throws Exception {
if (!beanValidator(model, safeFireAppVersion)) {
return form(safeFireAppVersion, model);
}
if (!apkFile.isEmpty()) { // 判断文件是否为空
String oldPath = null;
if(!safeFireAppVersion.getIsNewRecord()){ // 判断是否是新记录,不是的话就是修改中的保存
SafeFireAppVersion old = safeFireAppVersionService.get(safeFireAppVersion); // 获取原先的文件
FileUtils.delFileByPath(old.getUrl()); // 删除原文件
oldPath = old.getUrl(); // 获取原先的文件的路径,用于保留之前的uuid,换了文件还是用之前的uuid作为加密后的文件名
}
String filePath = UploadUtils.appVersionUpload(FireConstant.UPLOAD_VERSION_PATH, apkFile, oldPath); // 开始文件上传
safeFireAppVersion.setUrl(filePath);
safeFireAppVersion.setRealName(apkFile.getOriginalFilename()); // 真实名称
}
safeFireAppVersionService.save(safeFireAppVersion);
addMessage(redirectAttributes, "保存灭火器APP版本信息成功");
return "redirect:" + Global.getAdminPath() + "/safe/fire/safeFireAppVersion/?repage";
}