家人们,最近换了个公司还是前端开发,最近一个月的时间做了一个钉钉H5微应用。这个工作的主要任务就是将一个之前钉钉原生开发的小程序用Taro框架进行重构,为了实现某个跳转页面功能最后定为钉钉H5微应用。
这个项目属实给我搞得头大,因为钉钉对这钟多框架的支持度并不高,更不巧的是钉钉对H5的支持度更是不好😢。尤其体现在文件上传这块,当我们使用Taro.chooseImage时h5端会输出一个blob文件链接,格式是这种“blob:http://xxxxxxxxx”,可惜的是钉钉手机端内置浏览器无法对这种blob链接进行解析,而且在进行uploadFile网络请求时,我也是使用Taro的uploadAPI,这个api也不支持解析blob链接。其实我对文件上传功能也不是太熟悉,应为之前都是通过封装好的antd组件直接使用,自己这块也没有深入做过理解。下面我就从图片上传,图片回显两个方面,记录自己遇到的问题和自己的解决思路。
1. 图片上传
问题:当使用taro.uploadFile时传入后端的是一个blob链接,后端会返回“获取文件流失败”。
思路:其实图片上传也没什么难理解的,就是一个普通的网络请求,带着文件数据而已,taro.uploadFile这个api也是如此,只是封装了一下。所以不需要过分依赖这个api,直接发出一个axios请求也能完成功能,这块主要就是定位后端需要一个怎么样的文件数据。
这块我还有一个新的发现,我这个上传文件使用的是一个taroify的UploadFile组件,点击上传文件的框框,在html里面会新建一个type="file"的input框,并且把它定位到可见屏幕之外,点击了上传图片回显的框框会给这个input框一个自点击事件,然后真机端会自动跳转到选择图片页面。这个input的value值就是我们选择的file文件数据。经过测试file是无法直接传递到后端的我们需要将这个转换为formData数据,后端才能正常获取文件流。
问题:上传图片时如果图片大于1MB大小就会超时。
思路:网上相关问题和解决方法还是很多的,但是都是说联系测试同学帮忙修改nginx中的最大文件上传大小。我把这个问题反应给了负责人,负责人帮忙解决了😊,我看了一下修改后的代码,新建了一个配置文件“default.conf”,这块有点涉及我的知识盲区了,好在解决了,后续我再好好看看。
解决:
server {
listen 80;
server_name localhost;
client_max_body_size 10M;
gzip on;
gzip_min_length 1k;
gzip_comp_level 9;
#gzip_min_length 256;
gzip_proxied any;
gzip_vary on;
gzip_types text/plain application/x-javascript text/css application/xml text/javascript application/x-httpd-php application/javascript application/json;
#charset koi8-r;
access_log /var/log/nginx/host.access.log main;
#root /home/FFuser/front-sys;
#include /etc/nginx/mime.types;
2. 图片回显
问题:虽然图片能够成功上传了但是手机端有拍照和选择本地上传两种方法,但是当使用拍照时,手机竖屏拍照照片会顺时针旋转90°,横屏正常。
思路:家人们,真是头大了,这种问题怎么解决啊,服了。好在网上不乏相似问题,总结下来就是图片都有一个orientation属性,当or=6时就代表竖屏拍照,我们无法改变图片的or属性,只能知道or属性后给图片进行对应的翻转。比如判断or=6就给图片顺时针旋转90°,这个旋转需要使用canvas给图片进行重新绘制。这块还得感谢chatgpt,不然怎么东判断图片方向啊🤦♂️。
解决:
//判断照片方向
function getOrientation(file, callback?) {
var reader = new FileReader();
reader.onload = function (event) {
var view = new DataView(event.target.result);
var length = view.byteLength;
var offset = 2;
while (offset < length) {
var marker = view.getUint16(offset, false);
offset += 2;
if (marker == 0xffe1) {
if (view.getUint32((offset += 2), false) != 0x45786966) {
return callback(-1);
}
var little = view.getUint16((offset += 6), false) == 0x4949;
offset += view.getUint32(offset + 4, little);
var tags = view.getUint16(offset, little);
offset += 2;
for (var i = 0; i < tags; i++) {
if (view.getUint16(offset + i * 12, little) == 0x0112) {
return callback(view.getUint16(offset + i * 12 + 8, little));
}
}
} else if ((marker & 0xff00) != 0xff00) {
break;
} else {
offset += view.getUint16(offset, false);
}
}
return callback(-1);
};
reader.readAsArrayBuffer(file.slice(0, 64 * 1024));
}
//重新绘制图片
function rotateImage(file, orientation, callback) {
var img = new Image();
img.onload = function () {
var canvas = document.createElement("canvas");
var ctx = canvas.getContext("2d");
if (orientation === 6) {
canvas.width = img.height;
canvas.height = img.width;
} else {
canvas.width = img.width;
canvas.height = img.height;
}
// }
switch (orientation) {
case 6:
ctx.transform(0, 1, -1, 0, canvas.width, 0);
break;
default:
break;
}
ctx.drawImage(img, 0, 0);
canvas.toBlob(function (blob) {
callback(blob);
}, file.type);
};
img.src = URL.createObjectURL(file);
}
3. 图片数据格式转换
这次图片涉及到了大量的图片数据格式转换,基本有以下数据格式 base64、blob链接、blob数据、file数据、formData。下面我给出这几种数据格式如何相互转换。
// 1
// blob链接转换为formData
// formData后面跟着3个参数分别代表 数据类型、blob链接、formData文件名
var formData = new FormData();
formData.append("image", blob_res, file.name);
// 2
// blob链接转换为blob数据
var xhr = new XMLHttpRequest();
xhr.open('GET', 'blob:http://example.com/xxxxxxxx');
xhr.responseType = 'blob';
xhr.onload = function() {
var blobData = xhr.response;
// 处理blob数据
};
xhr.send();
// 3
// blob数据转换为blob链接
var blobData = new Blob(['Hello, world!'], { type: 'text/plain' });
var blobUrl = URL.createObjectURL(blobData);
console.log(blobUrl); // 输出blob链接
// 4
// file数据转换为formData
var formData = new FormData();
var file = document.querySelector('input[type=file]').files[0];
formData.append('file', file);
formData.append('name', '张三');
// 5
// blob数据转换为base64
function blobToBase64(blob, callback) {
var reader = new FileReader();
reader.readAsDataURL(blob);
reader.onload = function() {
var base64Data = reader.result;
callback(base64Data);
};
}
// 6
// base64转换为blob
function base64ToBlob(base64Data, contentType) {
contentType = contentType || '';
var binaryData = atob(base64Data.split(',')[1]);
var uint8Array = new Uint8Array(binaryData.length);
for (var i = 0; i < binaryData.length; i++) {
uint8Array[i] = binaryData.charCodeAt(i);
}
return new Blob([uint8Array], { type: contentType });
}
// 7
// formData转换为blob链接
var xhr = new XMLHttpRequest();
xhr.responseType = 'blob';
xhr.open('POST', '/submit');
xhr.onload = function() {
var blob = xhr.response;
var blobUrl = URL.createObjectURL(blob);
console.log(blobUrl);
};
xhr.send(formData);
总结
最后给出我上传图片的整段逻辑代码
const onUpload = () => {
const files_before = files;
Taro.chooseImage({
count: 1,
sourceType: ["camera", "album"],
//上传照片成功的回调
success: (res: any) => {
if (res.errMsg === "chooseImage:ok") {
//设置taroify的UploadFile组件的值,先让图片在组件中显示出来,主要是只有这样新建的input中才有文件file数据
setFiles([
{
url: res.tempFilePaths[0],
type: "image",
name: res.tempFilePaths[0],
},
...files,
]);
Taro.showLoading({
title: "上传中",
mask: true,
});
//获取新建input的dom对象,得到它的value数据(file数据)
const file = document.querySelector("#taroChooseImage").files[0];
//如果文件大于10M无法上传
if (file.size > 1024 * 1024 * 10) {
Taro.hideLoading();
Taro.showToast({
title: "图片大小超出10M, 请重新选择",
icon: "error",
});
setFiles(files);
return;
}
getOrientation(file, (res_or) => {
rotateImage(file, res_or, (blob_res) => {
//最后转换为formData数据
var formData = new FormData();
formData.append("image", blob_res, file.name);
setDisabled(true);
axios
.post(`${baseUrl}/file/v1/upload`, formData, {
headers: {
platform: "dtapp",
"Content-Type": "multipart/form-data",
Authorization: Taro.getStorageSync("key").token,
},
timeout: 20000,
})
.then(
(response) => {
Taro.hideLoading();
const { data } = response.data;
console.log("成功", data);
if (response.data.success === true) {
var entries = formData.entries();
var blobParts = [];
for (var entry of entries) {
blobParts.push(entry[1]);
}
var blob = new Blob(blobParts, {
type: blob_res.type,
});
// 将Blob对象转换为Blob URL
var blobUrl = URL.createObjectURL(blob);
const obj = {
url: blobUrl,
type: "image",
name: data.realFileName,
downloadUrl: data.downloadUrl,
fileName: data.fileName,
fileSuffix: data.fileSuffix,
realFileName: data.fileName,
realPath: data.realPath,
};
console.log("最后的文件参数", obj);
setFiles([obj, ...files_before]);
itemRef.current?.setValue([obj, ...files_before]);
} else if (response.data.success === false) {
Taro.showToast({
title: "上传图片失败",
icon: "error",
});
setFiles([...files_before]);
itemRef.current?.setValue([...files]);
}
setDisabled(false);
},
(err) => {
// alert(JSON.stringify(err));
Taro.hideLoading();
Taro.showToast({
title: "上传图片失败",
icon: "error",
});
setFiles([...files_before]);
setDisabled(false);
}
);
});
});
}
},
fail: () => {
Taro.showToast({
title: "上传图片失败",
icon: "error",
});
},
});
};