最近项目上有遇到需要在手机APP端嵌入HTML5页面并选取照片上传,网上有很多方式实现,原本使用了百度的webupload插件,但是需要依赖flash,所以我使用了H5自带的FileReader API来读取文件流转换成base64字符串,然后使用ajax无刷新方式上传到服务器。
HTML5页面编写
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html;charset=utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=Edge,chrome=1">
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0"/>
<script type="text/javascript" src="assets/aries/lib/jquery.js"></script>
<script type="text/javascript" src="js/json2/json2.js"></script>
<style>
.divImg{ border:1px solid #000; width:auto; height:auto;max-width: 100%;max-height: 100%}
.divImg img{width:100px; height:100px}
</style>
<script type="text/javascript">
var imgFiles = null;//用于存放base64字符串的数组
$(function(){
var input = $("#imgFile");
var result,div;
if(typeof FileReader==='undefined'){
input.setAttribute('disabled','disabled');
return alert("抱歉,你的浏览器不支持 FileReader");
}else{
$("#imgFile").bind('change',readFile);
}
function readFile(){
imgFiles = new Array();
for(var i=0;i<this.files.length;i++){
var imgName = $("#imgFile").val().toLocaleLowerCase();
if (!imgName.match(/.jpg|.jpeg|.gif|.png|.bmp/i)){ //判断上传文件格式
return alert("上传的图片格式不正确,请重新选择");
}
var reader = new FileReader();
reader.readAsDataURL(this.files[i]);
var picId = 0;
reader.onload = function(e){
imgFiles.push(this.result);//this.result就是图片转换后的base64字符串
result = '<div id="preview'+picId+'" class="divImg">![]('+this.result+')</div>';
div = document.createElement('div');
div.innerHTML = result;
document.getElementById('body').appendChild(div);//插入dom树
picId++;
}
}
}
$('#btnUpload').bind('click',uploadImg);
$('#btnReset').bind('click',reset);
})
/**
* 上传文件
*/
function uploadImg(){
for(var i=0;i<imgFiles.length;i++){
var base64 = imgFiles[i];
$.ajax({
url : 'http://192.168.1.102:8083/uploadFile',
type : 'post',
contentType: 'application/x-www-form-urlencoded; charset=utf-8',
async: false,
cache: false,
data : base64,
success : function(data){
console.log(data)
var result = JSON.parse(data);
if(result.isSuccess){
alert('第'+(i+1)+'张图片上传成功');
$('#preview'+i).remove();
}else{
alert('第'+(i+1)+'张图片上传失败:'+result.resultDesc);
}
}
})
}
reset();
}
/**
* 重置表单
*/
function reset(){
$('#imgFile').val('');
$('.divImg').each(function(index,domEle){
$(this).remove();
})
}
</script>
</head>
<body id="body">
<form id="imgForm" action="http://localhost:8083/uploadFile" method="post" enctype="multipart/form-data"></form>
<label>请选择一个图像文件:</label>
<input type="file" id="imgFile" name="imgFile" accept="image/*" multiple />
<button id="btnUpload">上传图片</button>
<button id="btnReset">重置</button>
</body>
</html>
服务端处理
后台使用原生的Java Servlet来接收处理
从HttpServletRequest中读取前端传来的InputStream并转换成字符串
/**
* 从HttpServletRequest中读取前端传来的InputStream并转换成base64字符串
*
* @param request
* @return
* @throws Exception
*/
private String getBase64Str(HttpServletRequest request) throws Exception {
try {
String imgStr = getRequestPayload(request);
if (imgStr == null || "".equals(imgStr)) {
return null;
}
/**
* 由于前端转过来的数据格式为data:image/jpeg;base64,图像base64字符串,因此需要处理一下
*/
final String subStr = "base64,";
String base64Str = imgStr.substring(imgStr.indexOf(subStr) + subStr.length(), imgStr.length());
return base64Str;
} catch (Exception ex) {
throw ex;
}
}
/**
* 从Request中读取二进制数据流,并转换成字符串
* @param request
* @return
* @throws Exception
*/
private String getRequestPayload(HttpServletRequest request) throws Exception{
StringBuilder sb = new StringBuilder();
BufferedReader reader = null;
try {
reader = request.getReader();
char[] buff = new char[1024];
int len;
while ((len = reader.read(buff)) != -1) {
sb.append(buff, 0, len);
}
} catch (IOException e) {
throw e;
}finally {
if(reader != null){
reader.close();
}
}
return sb.toString();
}
将base64字符串转换成图片并写入磁盘,最终完整的代码如下
import net.sf.json.JSONObject;
import org.apache.commons.io.IOUtils;
import sun.misc.BASE64Decoder;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.*;
/**
* Created by qianlong on 2017/1/12.
*/
public class FileUploadServlet extends HttpServlet {
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
service(req,resp);
}
public void service(HttpServletRequest req, HttpServletResponse res)
throws IOException, ServletException {
JSONObject result = new JSONObject();
res.setContentType("text/html;charset=UTF-8");
// Create path components to save the file
final String path = "/home/bluecoffee/upload";//保存路径可以从配置文件中读取
final PrintWriter writer = res.getWriter();
try {
String base64Str = this.getBase64Str(req);
if(base64Str == null || base64Str.equals("")){
result.put("isSuccess", false);
result.put("resultDesc", "请选择需要上传的图片");
writer.print(result.toString());
return;
}
System.out.println("base64Str=" + base64Str);
//生成一个新的文件名
String newImgName = path + File.separator + System.currentTimeMillis() + ".jpg";
boolean isWrite = this.writeImage(base64Str, newImgName);
if (isWrite) {
result.put("isSuccess", true);
result.put("resultDesc", "图片上传成功");
writer.print(result.toString());
}else{
result.put("isSuccess", false);
result.put("resultDesc", "写入磁盘文件失败");
writer.print(result.toString());
}
} catch (FileNotFoundException ex) {
ex.printStackTrace();
result.put("isSuccess", false);
result.put("resultDesc", "图片不存在:"+ex);
writer.print(result.toString());
} catch (Exception ex){
ex.printStackTrace();
result.put("isSuccess", false);
result.put("resultDesc", "图片上传失败:"+ex);
writer.print(result.toString());
}
}
/**
* 从HttpServletRequest中读取前端传来的InputStream并转换成真正的图片base64字符串
*
* @param request
* @return
* @throws Exception
*/
private String getBase64Str(HttpServletRequest request) throws Exception {
try {
String imgStr = getRequestPayload(request);
if (imgStr == null || "".equals(imgStr)) {
return null;
}
/**
* 由于前端转过来的数据格式为data:image/jpeg;base64,图像base64字符串,因此需要处理一下
*/
final String subStr = "base64,";
String base64Str = imgStr.substring(imgStr.indexOf(subStr) + subStr.length(), imgStr.length());
return base64Str;
} catch (Exception ex) {
throw ex;
}
}
/**
* 从Request中读取二进制数据流,并转换成字符串
* @param request
* @return
* @throws Exception
*/
private String getRequestPayload(HttpServletRequest request) throws Exception{
StringBuilder sb = new StringBuilder();
BufferedReader reader = null;
try {
reader = request.getReader();
char[] buff = new char[1024];
int len;
while ((len = reader.read(buff)) != -1) {
sb.append(buff, 0, len);
}
} catch (IOException e) {
throw e;
}finally {
if(reader != null){
reader.close();
}
}
return sb.toString();
}
/**
* 将base64字符串转换成图片并按新文件名写入磁盘,
* @param base64Str
* @param newImgName
* @return
* @throws Exception
*/
private boolean writeImage(String base64Str, String newImgName) throws Exception {
if (base64Str == null) // 图像数据为空
return false;
BASE64Decoder decoder = new BASE64Decoder();
OutputStream out = null;
try {
// Base64解码
byte[] bytes = decoder.decodeBuffer(base64Str);
for (int i = 0; i < bytes.length; ++i) {
if (bytes[i] < 0) {// 调整异常数据
bytes[i] += 256;
}
}
//将图片写入磁盘
out = new FileOutputStream(newImgName);
out.write(bytes);
return true;
} catch (Exception e) {
return false;
} finally {
if (out != null) {
out.flush();
out.close();
}
}
}
}
web.xml中配置
<servlet>
<servlet-name>FileUploadServlet</servlet-name>
<servlet-class>com.bluecoffee.web.servlets.FileUploadServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>FileUploadServlet</servlet-name>
<url-pattern>/pc/uploadFile</url-pattern>
</servlet-mapping>
<servlet-mapping>
<servlet-name>FileUploadServlet</servlet-name>
<url-pattern>/phone/uploadFile</url-pattern>
</servlet-mapping>
小结
该示例在iPhone的safari浏览器、小米手机的浏览器测试过,还需要在更多移动端测试。下一步准备加入在移动端压缩base64数据,提高传输效率。