前言:项目需求中涉及到多图片上传的注册功能,项目中前端使用的是比较老的ionic1+angularJS,后端SpringMVC,开发途中的遇到的各种爬坑血泪史,在此分享出来给大家参考。
1.组建表单
因涉及到多图片上传以及加载本地图片,故采用H5的FormData表单对象
这里先粘出部分HTML代码作为示例,以下表单上传到后台的数据有两个文本以及三张图片
<ion-view ng-controller="regController as vm" title="用户注册"> <ion-content class="content-bg-color" has-supheader="false" overflow-scroll="true"> <form enctype="multipart/form-data" method="post" novalidate ng-submit="vm.save(userForm)" name='userForm' id='userForm'> <label class="item item-input item-stacked-label"> <span class="input-label">账号:</span> <input type="text" required placeholder="建议使用手机号" ng-model="vm.user.account" name='account'> </label> <label class="item item-input item-stacked-label"> <span class="input-label">密码:</span> <input type="password" required ng-pattern="^[a-zA-Z]\w{5,17}$" ng-maxlength="18" ng-minlength="6" ng-model="vm.user.password" name='password' placeholder="以字母开头,长度在6~18之间,只能包含字母、数字和下划线"> </label>
<label class="item item-input item-stacked-label"> <span class="input-label">姓名:</span> <input type="text" required ng-maxlength="6" ng-model="vm.user.name" placeholder="长度不得超过6个字" name='name' ></label> <div class="item item-input item-stacked-label"> <span class="input-label">上传照片:</span> <div class="row"> <div class="col "> <label for="">1.周围环境照片 <br> <a href="javascript:;" class="file">选择照片 <input accept="image/*" type="file" name='img_environment'> </a> </label> </div> </div> <div class="row"> <div class="col"> <label for="">2.逆安装区域照片 <br> <a href="javascript:;" class="file">选择照片 <input accept="image/*" type="file" name='img_area'> </a> </label> </div> </div> <div class="row"> <div class="col"> <label for="">3.基础及配重图 <br> <a href="javascript:;" class="file">选择照片 <input accept="image/*" type="file" name='img_basics'> </a> </label> </div> </div> </div> <div class="row"> <div class="col"> <button class="button button-block button-positive" type="submit">完成</button> </div> </div> </form> </ion-content></ion-view>
这里需要留意的是所有<form>标签以及<input>标签,以下我会逐步讲解每个标签的属性
<form>:
- enctype='multipart/form-data',是设置表单的MIME编码。默认情况,这个编码格式是application/x-www-form-urlencoded,不能用于文件上传;只有使用了multipart/form-data才能将文件以二进制的形式上传,从而实现多种类型的文件上传。
- method="post",如果表单指定请求方式为post,则在使用XMLHttpRequest对象.open()方法请求后台时,该请求为post请求。
- novalidate ,该属性规定当提交表单时不对其进行验证。由于本项目中用的是angularJS的表单验证故屏蔽了H5的表单验证。
- ng-submit,指令用于在表单提交后执行指定函数
<input>:
- accept="image/*",H5中允许上传图片的类型。
- ng-pattern,确保输入匹配一个正则表达式。
2.组装FormData对象,准备上传
这里使用的是ng-submit方法save(userForm)传入表单对象进行表单验证
下面还是先粘出controller层的js代码,
function save(userForm){ if(userForm.$valid){//成功为 true 否则为 false if(vm.user.img[0]!=undefined){ if(vm.user.img[0].size===3){//如果三张图片都上传了 var formdata = new FormData(document.getElementById('userForm'));//获取页面上的表单对象 var countImgSize = formdata.get('img_environment').size+formdata.get('img_area').size+formdata.get('img_basics').size; if(countImgSize<1024*1024*10){//如果三张图片总大小小于10mb console.log('表单验证成功,正在准备上传用户信息'); formdata.set('name',encodeURI(formdata.get('name')));//转码 regService.reg(formdata).then(function(data){ if (data.data.state=="success") { $timeout(function () { $ionicPopup.alert({ title: '注册状态', template: data.data.mes }); }, 500).then(function () { $state.go('login'); }); } else if(data.data.state=="faild"){ $ionicPopup.alert({ title: '注册状态', template: data.data.mes }).then(function (res) { }); } }) }else{ $ionicPopup.alert({ title: '警告', template: '图片总大小不能超过10MB,当前图片总大小为:'+(countImgSize/1024/1024).toFixed(2)+'MB' }); } }else{ $ionicPopup.alert({ title: '图片没有上传完' }); } }else{ $ionicPopup.alert({ title: '请上传图片' }); } }else{ if(userForm.account.$error.required){ $ionicPopup.alert({ title: '账号没填' }); } if(userForm.password.$error.required){ $ionicPopup.alert({ title: '密码没填' }); } if(userForm.password.$error.pattern){ $ionicPopup.alert({ title: '密码不符合填写规则' }); } if(userForm.password.$error.maxlength){ $ionicPopup.alert({ title: '密码超出最大长度' }); } if(userForm.password.$error.minlength){ $ionicPopup.alert({ title: '密码小于最小长度' }); }
if(userForm.name.$error.maxlength){//如果姓名超出最大长度 $ionicPopup.alert({ title: '姓名超出最大长度' }); } if(userForm.name.$error.required){//如果姓名没填 $ionicPopup.alert({ title: '用姓名没填' }); } } }以上代码重点在第5-10行,这里主要详细讲解第8行:
因为表单使用multipart/form-data编码,
请求头里面内容类型为Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryW8oohHhEVIBMNJp9
不是utf-8格式的,故产生乱码,自己尝试通过改变请求头的Content-Type为multipart/form-data;utf8的方式解决乱码,但是由于post请求使用的是angularjs的$http服务,在尝试强制更改请求头的时候引起了跨域异常,百度各种后无果,所以只能使用以下笨方法:前端使用encodeURI(str)方法将formdata中的所有涉及中文的表单数据转码,后端则使用URLDecoder.decode(str, "UTF-8")的方式解码(注:"UTF-8"应为大写).
service层很简单,使用$http服务请求后台,post请求,传入表单对象
function reg(dataform) { return $http.post(SYS_INFO.SERVER_PATH+'/userorder_app/regUser', dataform, { transformRequest: angular.identity, headers: {'Content-Type': undefined} }) .success(complete) .error(failed);
}这里要注意的是,因为是通过anjularjs的http请求来上传文件的,所以要让当前的request成为一个Multipart/form-data请求,anjularjs对于post和get请求默认的Content-Type header 是application/json。通过设置‘Content-Type’: undefined,这样浏览器不仅帮我们把Content-Type 设置为 multipart/form-data,还填充上当前的boundary,如果你手动设置为: ‘Content-Type’: multipart/form-data,后台会抛出异常:the current request boundary parameter is null。
ps:
通过设置 transformRequest: angular.identity ,anjularjs transformRequest function 将序列化我们的formdata object.
3.接收表单数据
后端使用的是springMVC,详细描述都在代码注释中
@RequestMapping(value = "/regUser", method = RequestMethod.POST)
@ResponseBody
public JSONObject addUser(HttpServletRequest request) throws IOException {
// 先实例化一个文件解析器(暂且先叫解析器,DefaultMultipartHttpServletRequest翻译名叫‘默认的多个请求’)
DefaultMultipartHttpServletRequest dmhsq = (DefaultMultipartHttpServletRequest) request;
//这个userOrder是我需要组装并且要持久化保存的用户对象
UserOrder userOrder = new UserOrder();
//获取所有表单数据 以下两行代码就涵盖了所有表单中的所有数据
Map map = dmhsq.getParameterMap();
//获取所有文件
Map<String, MultipartFile> fileMap = dmhsq.getFileMap();
//上传图片
//1.图片存储的路径
StringBuffer pre_save_path= new StringBuffer();
String path = System.getProperty("java.class.path");
path = path.substring(0, path.lastIndexOf("jboss-modules.jar"));//应用服务器的根路劲
for (String key : fileMap.keySet()) {
MultipartFile m = fileMap.get(key);
//2.图片名称
String originalFilename = new String(m.getOriginalFilename().getBytes("ISO-8859-1"));
//3.新的图片
File newfile = new File(path + "upload" + "/" + ((String[]) map.get("orderId"))[0] + "/" + originalFilename);
a //拼接需要保存的图片路劲以逗号隔开
pre_save_path.append("/upload/" + ((String[]) map.get("orderId"))[0] + "/" + originalFilename+",");
//4.保存图片的文件夹
File folder = new File(path + "upload" + "/" + ((String[]) map.get("orderId"))[0] + "/");
//5.看看文件夹存不存在 不存在就造一个
if (!folder.isDirectory()) {
folder.mkdirs();
}
//6.把新图片扔进去
m.transferTo(newfile);
}
//以下的就是一些业务逻辑 需要注意的是中文解码的那一块
userOrder.setVerifyPicture(pre_save_path.toString().substring(0,pre_save_path.toString().length()-1));//核实图片
//上面图片上传完了 下面该到注册信息了
//后端使用URLDecoder.decode(str, "UTF-8")的方式解码(注:"UTF-8"应为大写)
String name = ((String[]) map.get("name"))[0];
name = URLDecoder.decode(name, "UTF-8");
userOrder.setName(name);//姓名
userOrder.setUserAccount(((String[]) map.get("account"))[0]);//账号
userOrder.setPassword(((String[]) map.get("password"))[0]);//密码
//获取当前订单号的userorder对象
UserOrder u = userOrderService.findUserOrder(userOrder.getOrderId());
//查看这个账户存不存在
Boolean isExist = userService.hasAccountExist(userOrder.getUserAccount());
JSONObject returnObj = new JSONObject();
//如果都没有 就新创建一个userorder
if (u == null && !isExist) {
userOrderService.createUserOrder(userOrder);
returnObj.put("mes", "注册成功,即将为您跳转到登陆页面~");
returnObj.put("state", "success");
} else {
returnObj.put("mes", "注册失败,当前订单已注册或账户已存在!");
returnObj.put("state", "faild");
}
return returnObj;
}这里的DefaultMultipartHttpServletRequest文件解析器是个好东西,通过它能获取到表单上的所有文本数据和MultipartFile对象,这里不多解释,可以百度详情