最近的业务需要配合前端提供一个上传图片的接口,前端是一个Html5的页面,使用的上传方式和原有上传方式不太一样,因此特别记录一下。
第一种,页面使用表单提交,后台使用commons-fileupload.jar来接收。
其实这种方法就是传统的表单提交方式,前端页面如下:
<form id="form" action="http://localhost:8080/emission/SaveImage" method="post" enctype="multipart/form-data">
<input class="m_camera" type="file" accept="image/*" name="file_img" capture="camera" id="cameraInput" >
<input type="hidden" value="123455788" name="orderid" />
<input type="hidden" value="" name="hidden" id="j_thumb" />
<!-- input type="submit" value="submit" name="sbt" id="sbt" -->
</form>
JS中获得图片压缩一下,然后放到表单中传过来,其实这个东西本来是个很简单的业务,但由于其中的两行JS代码的误会,导致这个东西搞了很久,这两行代码时这样的
var r = i.toDataURL("image/jpeg", .8);
document.getElementById("j_thumb").value = r.substr(22)
看起来是给”j_thumb“这个input赋值,然后提交到页面,实际上并不是这样的。当我在后台使用commons-fileupload插件解析request时,在 ”j_thumb“这个对象中,并没有看到对应的文件,直到最后把整个request输出出来,才发现原来这个东西是在file_img这个对象中传过来的,可能是因为浏览器自动处理吧,把图片放到了这个type为file的标签中。得到这个对象后就简单很多了。直接读取一下解析后的文件流,这里没有尝试判断是否文本域的方法,如何使用判断文本域的方法来处理图片,请参见前文:微软云+Servlet实现图片上传接口 所需jar包commons-fileupload也可以在前文中找到。
以下是后台接收图片的代码:
Data.writeLog(Global.errPath1, "访问到SaveImage+test\r\n");
DiskFileItemFactory factory = new DiskFileItemFactory();
factory.setSizeThreshold(8192);
factory.setRepository(tempPath);
ServletFileUpload upload = new ServletFileUpload(factory);
upload.setSizeMax(1000000 * 20);
String orderid = "";
String suffix = "";
try {
/* 解析request 取得表单域迭代器 */
FileItemIterator fii = upload.getItemIterator(request);
/* 遍历迭代器 ,这里也可以使用while循环*/
for (final FileItemIterator itemIterator = fii; itemIterator.hasNext();) {
final FileItemStream item = itemIterator.next();
/*直接根据对象name进行筛选*/
if ("orderid".equals(item.getFieldName())) {
/*读取request的输入流*/
BufferedReader responseReader = new BufferedReader(
new InputStreamReader(item.openStream(), "UTF-8"));
String readLine;
StringBuffer responseSb = new StringBuffer();
while ((readLine = responseReader.readLine()) != null) {
responseSb.append(readLine);
}
/*获得表单文字域*/
orderid = responseSb.toString();
}
if ("file_img".equals(item.getFieldName())) {
/*截取name获得文件后缀名*/
String fileName = item.getName();
int index = fileName.indexOf(".");
suffix = fileName.substring(index, fileName.length());
/*从request流中获得输入流*/
BufferedInputStream inputStream = new BufferedInputStream(item.openStream());
/*定义输出流*/
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
/*复制输入流到输出流*/
Streams.copy(inputStream, outputStream, true);
/*生成文件图片*/
File picFile = new File(
"D:\\feng\\perl-work\\zzq\\emission\\imageUpload\\" + directory + "\\" + saveName + suffix);
if (picFile.getParentFile() != null && !picFile.getParentFile().exists()) {
picFile.getParentFile().mkdirs();
}
if (!picFile.exists()) {
picFile.createNewFile();
}
/*写入文件并关闭流*/
OutputStream fileOutputStream = new FileOutputStream(picFile);
outputStream.writeTo(fileOutputStream);
inputStream.close();
outputStream.close();
fileOutputStream.close();
}
}
} catch (Exception e) {
message.put("code", "-1");
message.put("message", "接收文件失败" + e.getMessage());
message.put("id", request.getParameter("orderid"));
writer.println(new JSONObject(message).toString());
request.setAttribute("orderId", orderid);
}
这种范式的一个问题就是对app不太友好,因为表单提交就意味着url跳转,只能在服务器端的网页上使用这种上传图片的方法。
第二种,使用ajax异步调用图片存储接口
ajax也是现在很流行图片上传方法,即将图片处理成二进制文本,然后作为字符串传输给后台。前台代码如下:
function saveImage(txt) {
var id = "";
alert(txt)
$.ajax({
url: "http://localhost:8080/emission/SaveImage2",
type: "post",
data: {
orderId: 39,
file_img: txt
},
success: function(response) {
alert(response);
var dataobj = eval("(" + response + ")")
}
})
}
txt即为处理过的二进制文本图片。
后端接收代码如下:
String orderid = request.getParameter("orderId");
String image = request.getParameter("file_img");
/*进行base64解码*/
byte[] images = Base64.decodeBase64(image);
File picFile = new File(
"D:\\feng\\perl-work\\zzq\\emission\\imageUpload\\" + directory + "\\" + saveName + ".png");
if (picFile.getParentFile() != null && !picFile.getParentFile().exists()) {
picFile.getParentFile().mkdirs();
}
if (!picFile.exists()) {
picFile.createNewFile();
}
/*将bytes写入文件*/
FileOutputStream fos = new FileOutputStream(picFile);
fos.write(images);
fos.close();
由于前台有一个简单的压缩算法,使用了canvas对象的toDataURL方法,会将图片进行base64编码,因此后台接收的时候需要将对应的字符串进行解码。
小结:其实压缩图片上传是个很常见的业务,但是这其中的细节确实值得了解。最开始我们使用接口的形式,但是由于前后端不在一个服务器上,js调用的时候出现了跨域的问题。当我们使用jsonp传递参数试图解决这个问题的时候,由于图片压缩完是一个很长的字符串,因此可能超出了jsonp的限制,传输失败。这时候我们转用表单提交图片。由于对H5方法不熟悉,并且JS代码中压缩完的图片赋值给了一个hidden标签,对后台解析造成了很大的影响。事后推测,应该是浏览器对enctype=”multipart/form-data”形式的表单有自动的封装定义,即使将文件形式字符传给了hidden域,最终完成传递工作的还是type=“file”类型的input,而发现这个问题足足用了两天的时间,可以说是一个比较深刻的教训了。