先说一下遇到的问题,
1.刚开始在开发阶段直接前台用ajax调接口上传图片没问题,可是到了正式服务器部署时,问题出现了。好像是无法跨域(可能是服务器域名化了的原因),但是后台接口方法中已经添加了允许跨域请求的代码:
//跨域请求
response.setHeader("Access-Control-Allow-Origin", "*");
2.于是,调整方案,前台 ajax 上传文件提交到后台,在后台调用接口来完成文件上传,代码写完测试时又发现问题了,大一点的图片传到后台时接收不到,部分android手机上传的图片后台也接收不到。
最终的解决方案:
前台使用fetch发起请求,并且将图片转换成 base64字符串再提交到后台,后台解析成图片调用接口完成图片上传。
注意:部分Android手机出现点击图片上传按钮无响应的情况,经排查是下面这段代码的问题。
<input class="weui_uploader_input" name="file" type="file" id="up_file" accept="image/jpg,image/jpeg,image/png,image/gif" multiple />
部分Android手机不支持 H5的multiple 属性,去掉即可(去掉之后在选取图片时一次只能选择一张)
一、前端上传图片:
前端使用 weui 来选取图片。
<div class="weui_uploader z_photo1">
<div class="weui_uploader_bd">
<ul class="weui_uploader_files" id="img2x">
</ul>
<div class="weui_uploader_input_wrp">
<input class="weui_uploader_input" name="file" type="file" id="up_file" accept="image/jpg,image/jpeg,image/png,image/gif" multiple />
</div>
</div>
</div>
js部分,通过fetch将选取到的图片发给后台处理
$(function() {
// 允许上传的图片类型
var allowTypes = ['image/jpg', 'image/jpeg', 'image/png', 'image/gif'];
// 1024KB*10),也就是 10MB
var maxSize = 1024 * 10 * 1024;
// 图片最大宽度
var maxWidth = 9000;
// 最大上传图片数量
var maxCount = 6;
$('#up_file').on('change',
function(event) {
var files = event.target.files;
//console.log(files);
//console.log("--files.length--",files.length);
// 如果没有选中文件,直接返回
if (files.length === 0) {
return;
}
for (var i = 0,
len = files.length; i < len; i++) {
var file = files[i];
var reader = new FileReader();
// 如果类型不在允许的类型范围内
if (allowTypes.indexOf(file.type) === -1) {
$.weui.alert({
text: '该类型不允许上传'
});
continue;
}
if (file.size > maxSize) {
$.weui.alert({
text: '图片太大,不允许上传'
});
continue;
}
if ($('.weui_uploader_file').length >= maxCount) {
$.weui.alert({
text: '最多只能上传' + maxCount + '张图片'
});
return;
}
//console.log(file.name);
var _canvas;
reader.onload = function(e) {
//console.log('--render.onload-');
var img = new Image();
img.onload = function() {
//console.log('--img.onload-');
// 不要超出最大宽度
var w = Math.min(maxWidth, img.width);
// 高度按比例计算
var h = img.height * (w / img.width);
var canvas = document.createElement('canvas');
var ctx = canvas.getContext('2d');
// 设置 canvas 的宽度和高度
canvas.width = w;
canvas.height = h;
ctx.drawImage(img, 0, 0, w, h);
var base64 = canvas.toDataURL('image/png');
// 插入到预览区
var progress = 0;
function uploading() {
}
};
var formData = new FormData();
formData.append('file', e.target.result);
formData.append('busId', "${pkId}");
formData.append('fileName', file.name);
fetch("${ctx}/wechat/eventReport/uploadFile", {
method: 'POST',
headers: {
},
body: formData,
}).then((response) = >response.json()).then((responseData) = >{
//console.log(responseData);
if (responseData.success) {
var imgNames = responseData.data;
//这里是将图片上传之后,后台返回的图片的存放路径存到了imgArr数据组// 中,方便前台页面上回显上传的图片。
for (var i = 0; i < imgNames.length; i++) {
var image = imgNames[i];
if (image) {
imgArr.push(image);
}
}
//这个就是让图片回显在页面上的函数,每个人的处理方式都不一样,就不贴出来了。
refreshImg();
} else {
layer.msg('上传失败!', {
time: 2000
});
}
}).
catch((error) = >{
console.error('error', error);
layer.msg('上传失败!', {
time: 2000
});
});
};
reader.readAsDataURL(file);
}
});
})
二、后台处理图片
先将base64字符串转为图片。这里使用 restTemplate来请求接口,所以需要将图片文件先转为 FileSystemResource类型,
只能先生成一个 FileSystemResource 类型的文件,上传成功之后在删除即可。
@RequestMapping(value = "/uploadFile",method = RequestMethod.POST)
@ResponseBody
public Map<String,Object> uploadV(HttpServletRequest request,HttpServletResponse response,String file,String fileName,String busId) {
Map<String,Object> map = new HashMap<String,Object>();
List<String> resList = new ArrayList<String>();
try{
//file = file.substring("data:image/png;base64,".length(),file.length());
file = file.substring(file.indexOf("base64,")+7,file.length());
String tempFileName = UUID.randomUUID().toString().replaceAll("-", "")+fileName.substring(fileName.lastIndexOf("."));
//在项目的webapp文件夹下新建一个tempFile文件夹,用来存放生成的临时文件
String tempFilePath = request.getSession().getServletContext().getRealPath("/") +"tempFile/" + tempFileName;
Base64Utils.Base64ToImage(file, tempFilePath);
FileSystemResource resource = new FileSystemResource(tempFilePath);
MultiValueMap<String, Object> param = new LinkedMultiValueMap<>();
param.add("file", resource);
param.add("busId", busId);
//Constants.fileUploadUrl 是上传图片的接口的url
String string = restTemplate.postForObject(Constants.fileUploadUrl, param, String.class);
//System.out.println(string);
new File(tempFilePath).delete();
JSONObject json = JSONObject.fromObject(string);
if(json.getBoolean("success")){
resList.add(json.getString("data"));
}
map.put("data", resList);
map.put("success", true);
} catch(Exception e){
map.put("success", false);
e.printStackTrace();
}
return map;
}
将base64字符串转为图片的工具类:
package *****;
import java.io.FileOutputStream;
import java.io.OutputStream;
import org.hibernate.internal.util.StringHelper;
import Decoder.BASE64Decoder;
public class Base64Utils {
/**
* base64字符串转换成图片
* @param imgStr base64字符串
* @param imgFilePath 图片存放路径
* @return
*
* @author ZHANGJL
* @dateTime 2018-02-23 14:42:17
*/
public static boolean Base64ToImage(String imgStr,String imgFilePath) { // 对字节数组字符串进行Base64解码并生成图片
if (StringHelper.isEmpty(imgStr)) // 图像数据为空
return false;
BASE64Decoder decoder = new BASE64Decoder();
try {
// Base64解码
byte[] b = decoder.decodeBuffer(imgStr);
for (int i = 0; i < b.length; ++i) {
if (b[i] < 0) {// 调整异常数据
b[i] += 256;
}
}
OutputStream out = new FileOutputStream(imgFilePath);
out.write(b);
out.flush();
out.close();
return true;
} catch (Exception e) {
return false;
}
}
}
三、后台接口处理上传过来的图片
上传文件接口,这里 List<MultipartFile> files 中就是接收到的文件了,请自行处理,代码省略了。
@RequestMapping(value = "/hzz/uploadv", method = RequestMethod.POST)
@ResponseBody
public Object handleFileUploadV(HttpServletRequest request, HttpServletResponse response) {
//跨域请求
response.addHeader("Access-Control-Allow-Origin", "*");//http://localhost:8086
List<MultipartFile> files = ((MultipartHttpServletRequest) request).getFiles("file");
}