因公司项目需要, 要修改一个手机端上传图片的一个功能, 原本的项目用的是 input 的 file 控件上传的, 虽然标注了可以多选, 但是在实际运用当中只有 iOS 手机可以实现多选, Android 手机并不支持多选, 甚至部分 Android 手机因为 input file 上 multiple="multiple" 属性连图片选择框都打开不了而这次的需求需要用户上传多张图片, 显然让用户一张图片一张图片上传满足不了需求, 我们需要多选上传
因为我们的项目是挂在微信企业号里面的, 所以我想到用微信企业号的 JS-SDK 的方法来选择图片, 可以直接绕开 file 控件的缺陷直接上代码:
- wx.chooseImage({
- count: 8, // 默认 9
- sizeType: ['original', 'compressed'], // 可以指定是原图还是压缩图, 默认二者都有
- sourceType: ['album', 'camera'], // 可以指定来源是相册还是相机, 默认二者都有
- success: function (res) {
- $(".previewimg").html("");// 每次选择图片完成后都清空之前已经添加的 html 节点
- var images = {
- localId: [],// 微信返回的本地 id 列表
- serverId: [],// 微信返回的服务器 id 列表
- };
- ioslocId = [];// 用于兼容 ios 的本地 id 列表 图片是 base64 格式的
- rows = "";// 声明一个空字符串用于保存循环出来的 html
- images.localId = images.localId.concat(res.localIds); // 返回选定照片的本地 ID 列表, localId 可以作为 img 标签的 src 属性显示图片
- localId = images.localId;
- try {
- for (var i = 0; i < images.localId.length; i++) {
- //alert(res.localIds[i]);
- wx.getLocalImgData({ // 循环调用 getLocalImgData
- localId: res.localIds[i], // 图片的 localID
- success: function (res) {
- var localData = res.localData; // localData 是图片的 base64 数据, 可以用 img 标签显示
- //alert(localData);
- if (window.__wxjs_is_wkwebview) { //ios
- localData = localData.replace('jgp', 'jpeg');//iOS 系统里面得到的数据, 类型为 image/jgp, 因此需要替换一下
- }
- else {
- localData = "data:image/jpeg;base64," + localData; //android
- }
- ioslocId.push(localData) // 把 base64 格式的图片添加到 ioslocId 数组里 这样该数组里的元素都是 base64 格式的
- rows = "";
- for (var j = 0; j < ioslocId.length; j++) {
- rows += '<img style="margin: 5px;"class="up_p"src="' + ioslocId[j] + '"> <input type="hidden"name="upimg'+ j +'" value="'+ ioslocId[j] +'" />';
- }
- $(".previewimg").html(rows);
- }, fail: function (e) {
- alert(e);
- }
- });
- }
- }
- catch (err) {
- alert(err) // 可执行
- }
- }
- });
这里注意到: 微信企业号 api 官网上: https://work.weixin.qq.com/api/doc#10029 / 获取本地图片接口
说: wx.getLocalImgData, 此接口仅在企业微信 iOS 支持, 要求版本为 2.4.6 及以上, 但是我们很显然的发现, Android 也可以用只是返回的 res.localData 做处理的不同而已
一开始我也不知道安卓手机获取的图片可以直接用 getLocalImgData 的返回值转 base64, 所以我想了很多其他办法, 也走了不少冤枉路, 比如我想用前端显示的图片转 base64, 用 canvas , 如下:
- function getBase64Image(img) {
- var canvas = document.createElement("canvas");
- canvas.width = img.width;
- canvas.height = img.height;
- var ctx = canvas.getContext("2d");
- ctx.drawImage(img, 0, 0, img.width, img.height);
- var dataURL = canvas.toDataURL("image/png");
- return dataURL
- // return dataURL.replace("data:image/png;base64,", "");
- }
但是很遗憾, 这里的 img 必须是同一域里的图片或者是允许跨域访问的图片地址, 而微信返回的是本地 ID, 即使安卓手机上这个 id 可以直接放在 img 的 src 上显示图片, 但不知是因为 id 问题还是因为这个地址不允许跨域访问导致用 canvas 转的 base64 是错的, 传到服务器端后不能保存成我们选择的图片所以这个方法肯定不行
于是经过他人的指点我想到用 wx.uploadImage 接口先上传到微信服务器, 然后再用微信提供的接口在服务器端将微信服务器的图片下载到我们自己的服务器
- wx.uploadImage({
- localId: '', // 需要上传的图片的本地 ID, 由 chooseImage 接口获得
- isShowProgressTips: 1, // 默认为 1, 显示进度提示
- success: function (res) {
- var serverId = res.serverId; // 返回图片的服务器端 ID
- }
- });
服务器端请求地址: https://qyapi.weixin.qq.com/cgi-bin/media/get?access_token=ACCESS_TOKEN&media_id=MEDIA_ID
此处获得的 media_id 即 serverId
这里上传到微信服务器必须一提的是: 上传图片只能一张一张图片上传, 上传下一张图片必须等上一张图片上传完成, 再进行下一张图片, 所以我立即想到写个递归上传即可, 如下:
- // 递归上传到微信服务器 add by torres cooooooooooooooool
- localId: [];
- function uploadImg(index) {
- if (index < 0) {
- return;
- }
- wx.uploadImage({
- localId: localId[index], // 需要上传的图片的本地 ID, 由 chooseImage 接口获得
- isShowProgressTips: 1, // 默认为 1, 显示进度提示
- success: function (res) {
- //res.serverId 返回图片的服务器端 ID
- try {
- var rows = '<input type="hidden"name="serverIdArr'+ index +'" value="'+ res.serverId +'" />';
- $(".previewimg").append(rows);
- } catch (e) {
- alert(e)
- }
- uploadImg(index - 1);
- }, fail: function (res) {
- alert("上传失败, msg:" + JSON.stringify(res));
- }
- });
- }
这样的写法可能有点 low, 我在网上看到另一种
- var syncUpload = function(localIds, index) {
- var localId = localIds.pop();
- wx.uploadImage({
- localId: localId,
- isShowProgressTips: 1,
- success: function(res) {
- // 其他对 serverId 做处理的代码
- try {
- //alert("上传下一张");
- var serverId = res.serverId; // 返回图片的服务器端 ID
- var rows = '<input type="hidden"name="serverIdArr'+ index +'" value="'+ res.serverId +'" />';
- $(".previewimg").append(rows);
- if (localIds.length > 0) {
- syncUpload(localIds, index - 1);
- }
- } catch(e) {
- alert(e)
- }
- },
- fail: function(res) {
- alert("上传失败, msg:" + JSON.stringify(res));
- }
- });
- };
两种方法差不多, 都能实现我想要的, 但是仅仅是在 iOS 很奇怪, 在 Android 机器上, 上传多张图片时并不能上传全部图片并返回 sercerId 并打印出 input 隐藏域在页面上 (或者说并不是所有图片上传都走了 success 方法, 但也没有走 fail 方法), 单张图片上传完全 OJBK 这就很让人郁闷了, 选择了三张图片上传微信服务器, 但是只返回了一个或两个 serverId, 十分不稳定, 有时候能全部上传, 有时候只能上传部分图片
这里也顺便贴出 C# 语言后台下载微信服务器端图片代码:
- string stUrl = string.Format("https://qyapi.weixin.qq.com/cgi-bin/media/get?access_token={0}&media_id={1}", access_token, serverIdArr[i]);
- HttpWebRequest req = (HttpWebRequest)HttpWebRequest.Create(stUrl);
- HttpWebResponse myResponse = (HttpWebResponse)req.GetResponse();
- string contentType = myResponse.ContentType;
- if (myResponse.ContentLength == 0 || (contentType != "image/jpeg" && contentType != "image/pjpeg" && contentType != "image/bmp" && contentType != "image/gif" && contentType != "image/png" && contentType != "image/x-png"))
- {
- htInfo["error"] += "第" + (i + 1) + "个文件上传的附件格式不对, 请重新上传!<br/>";
- continue;
- }
- int picUploadSize = Convert.ToInt32(ConfigurationManager.AppSettings["picUploadSize"]);
- if (myResponse.ContentLength > picUploadSize * 1024)
- {
- htInfo["error"] += string.Format("第" + (i + 1) + "个图片不能超过 {0}KB!<br/>", picUploadSize);
- continue;
- }
- req.Method = "GET";
- //req.ProtocolVersion = HttpVersion.Version10;
- string strpath = myResponse.ResponseUri.ToString();
- WebClient mywebclient = new WebClient();
- string extension = GetExtensionName(contentType); // 获取扩展名
- string imgName = DateTime.Now.ToString("yyyyMMddHHmmssffff") + extension;
- // 下载原图
- mywebclient.DownloadFile(strpath, savepath);
最终因为 Android 上传图片的问题这个方法还是行不通没有办法之下我只能在 wx.chooseImage 的 success 方法里显示选择图片的下面再加一个上传按钮, 让用户手动一个一个点击上传到微信服务器, 这样保证了每张都可以返回 serverId, 这么 low 的方法只能先用着了, 正当我要放弃继续探索其他方法的时候, 我在微信开发群里面问了一下我这个问题, 然后有好心的群友给了我建议说直接转 base64, 不用上传微信服务器时我就告诉了他我的疑问, 他给了我他的代码, 最终有了
localData = "data:image/jpeg;base64," + localData; //android
上面这行代码 , 苦苦追寻, 在埋头研究两天后终于实现了手机端选择多图上传的功能, 在用户操作上也不用区分 iOS 和 Android
以上的故事告诉我们: 不要轻言放弃, 这种方法不行就换一种再试试, 你会发现, 哪一种方式都 TM 不行啊!!! 我屮艸芔茻略略略略略 , 才不是呢! 你会发现总有一种适合你
有什么建议或意见可以请大家多多指点, 谢谢!
来源: https://www.cnblogs.com/wwfjcy479/p/8508265.html