一、鸿蒙实现处理-压缩上传整体代码处理逻辑
- 转沙箱
- 压缩
- 获取凭证并上传文件
- 文件准备(拿到文件流)
- 获取上传凭证(调接口1拿到file_name和upload_url)
- 执行文件上传(向阶段2拿到的upload_url上传文件)
- 更新列表数据 (将阶段2获取到的file_name更新进去,最后提交表单时传参)
- 提交表单
// 处理视频/图片(转沙箱、压缩、调接口获取url、上传到OSS)
// index不是undefined时:修改
async handleFile(messageInfo: MessageInfo, index?: number) {
loadingInstance.show()
if (this.videoFlag) {
// 1视频处理
// 1)转沙箱
const file = uriToSandBox(messageInfo.sourceFilePath);
// 2)压缩
const fileUri = await this.videoToCompress(file.fileUri).catch(() => {
loadingInstance.hide()
showToast('视频压缩失败');
});
let stat = fs.statSync(fileUri as string);
if (stat.size / 1024 / 1024 > 50) {
loadingInstance.hide()
showToast('请拍摄50M以内的视频');
return;
}
// 3)获取上传凭证并上传
fileUri && this.fileUpload(fileUri, messageInfo, index)
} else {
// 2图片处理
const file = uriToSandBox(messageInfo.sourceFilePath);
// 1)转沙箱并压缩
const fileUri = await this.transferFile(messageInfo.sourceFilePath).catch(() => {
loadingInstance.hide()
});
// 2)获取上传凭证并上传
fileUri && this.fileUpload(fileUri, messageInfo, index)
}
}
二、压缩部分
实现思路:
图片压缩
视频压缩
代码:
// 优先压缩图片质量
async qualityPriorityCompress(sourcePixelMap: image.PixelMap, maxCompressedImageSize: number) {
let compressedImageData: ArrayBuffer =
await this.packing(sourcePixelMap, IMAGE_QUALITY_ZERO, this.afterCompressFmt);
// 先判断图片质量参数设置最低0能否满足目标大小。如果能满足目标大小,则直接使用packing二分图片质量。如果不满足,则质量参数固定为0,进行scale尺寸压缩
if (compressedImageData.byteLength <= maxCompressedImageSize * BYTE_CONVERSION) {
// 满足目标大小,直接使用packing二分
await this.packingImage(sourcePixelMap, compressedImageData, maxCompressedImageSize * BYTE_CONVERSION);
} else {
// 不满足目标大小,质量参数设置为0,再进行scale尺寸压缩
await this.scalePriorityCompress(sourcePixelMap, maxCompressedImageSize, IMAGE_QUALITY_ZERO);
}
// 更新显示压缩后的图片格式
this.showCompressFormat = this.afterCompressFmt;
}
// packing二分方式循环压缩
async packingImage(sourcePixelMap: image.PixelMap, compressedImageData: ArrayBuffer, maxCompressedImageByte: number) {
let imageQuality: number = 0;
const DICHOTOMY_ACCURACY = this.minBisectUnit;
// 图片质量参数范围为0-100,这里以minBisectUnit为最小二分单位创建用于packing二分图片质量参数的数组。
const packingArray: number[] = [];
// 性能知识点: 如果对图片压缩质量要求不高,建议调高minBisectUnit(对应'packing最小二分单位'),减少循环,提升packing压缩性能。
for (let i = 0; i <= 100; i += DICHOTOMY_ACCURACY) {
packingArray.push(i);
}
let left = 0; // 定义二分搜索范围的左边界
let right = packingArray.length - 1; // 定义二分搜索范围的右边界
const AFTER_COMPRESS_FMT = this.afterCompressFmt;
// 二分压缩图片
while (left <= right) {
const mid = Math.floor((left + right) / 2); // 定义二分搜索范围的中间位置
imageQuality = packingArray[mid]; // 获取二分中间位置的图片质量值
// 根据传入的图片质量参数进行packing压缩,返回压缩后的图片文件流数据。
compressedImageData = await this.packing(sourcePixelMap, imageQuality, AFTER_COMPRESS_FMT);
// 判断查找一个尽可能接近但不超过压缩目标的压缩大小
if (compressedImageData.byteLength <= maxCompressedImageByte) {
// 二分目标值在右半边,继续在更高的图片质量参数(即mid + 1)中搜索
left = mid + 1;
// 判断mid是否已经二分到最后,如果二分完了,退出
if (mid === packingArray.length - 1) {
break;
}
// 获取下一次二分的图片质量参数(mid+1)压缩的图片文件流数据
compressedImageData = await this.packing(sourcePixelMap, packingArray[mid + 1], AFTER_COMPRESS_FMT);
// 判断用下一次图片质量参数(mid+1)压缩的图片大小是否大于指定图片的压缩目标大小。如果大于,说明当前图片质量参数(mid)压缩出来的
// 图片大小最接近指定图片的压缩目标大小。传入当前图片质量参数mid,得到最终目标图片压缩数据。
if (compressedImageData.byteLength > maxCompressedImageByte) {
compressedImageData = await this.packing(sourcePixelMap, packingArray[mid], AFTER_COMPRESS_FMT);
break;
}
} else {
// 目标值不在当前范围的右半部分,将搜索范围的右边界向左移动,以缩小搜索范围并继续在下一次迭代中查找左半部分。
right = mid - 1;
}
}
// ...
}
三、OSS上传文件部分
方案图:


代码:
// 获取上传凭证并上传
// index不是undefined时:修改
private fileUpload = async (fileUrl: string, messageInfo: MessageInfo, index?: number) => {
let fileInfo: fs.File | null = null;
try {
// 阶段1:文件准备
fileInfo = await fs.open(fileUrl, fs.OpenMode.READ_ONLY);
const fileStat = await fs.stat(fileInfo.fd);
const fileData = new ArrayBuffer(fileStat.size);
await fs.read(fileInfo.fd, fileData);
// 阶段2:获取上传凭证(调接口1拿到file_name和upload_url)
const fileType = this.videoFlag ? 'mp4' : 'jpg';
const uploadInfo = await this.getUploadCredentials(fileType);
// 阶段3:执行文件上传(向阶段2拿到的upload_url上传文件)
await this.uploadFileToOSS(uploadInfo.upload_url, fileData, fileStat.size)
// 阶段4:更新列表数据 (将阶段2获取到的file_name更新进去,最后提交表单时传参)
this.updateMediaLists(uploadInfo.file_name, messageInfo, index);
return true;
} catch (error) {
showToast('文件上传失败,请重新上传')
return false;
} finally {
// 确保资源释放
if (fileInfo?.fd) {
await fs.close(fileInfo.fd);
}
loadingInstance.hide();
}
};