WorkCamera/uniapp/WorkCameraf/common/cosUpload.js
2026-01-05 21:20:55 +08:00

342 lines
8.8 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/**
* COS 上传服务
* 实现腾讯云 COS 直传功能
*/
import { get, post } from "./request";
import { getBaseUrl } from "./server";
/**
* 获取预签名 URL
* @param {Object} params - 请求参数
* @param {Date} params.recordTime - 记录时间
* @param {string} params.deptName - 部门名称
* @param {string} params.content - 工作内容
* @param {Array<string>} params.workers - 工作人员列表
* @param {string} params.fileExt - 文件扩展名,默认 .jpg
* @param {number} params.imageCount - 图片数量
* @returns {Promise<Object>} - 返回预签名 URL 信息
*/
export const getUploadUrls = async (params) => {
const baseUrl = getBaseUrl();
const url = baseUrl + "api/cos/getUploadUrls";
const requestData = {
RecordTime: params.recordTime instanceof Date
? params.recordTime.toISOString()
: params.recordTime,
DeptName: params.deptName || "",
Content: params.content || "",
Workers: params.workers || [],
FileExt: params.fileExt || ".jpg",
ImageCount: params.imageCount || 1
};
console.log("获取预签名 URL 请求:", url, requestData);
const res = await post(url, requestData);
console.log("获取预签名 URL 响应:", res);
if (res.code !== 200) {
throw new Error(res.msg || "获取预签名 URL 失败");
}
return res.data;
};
/**
* 直传图片到 COS使用 PUT 方法)
* @param {string} uploadUrl - 预签名上传 URL
* @param {string} filePath - 本地文件路径
* @param {number} retryCount - 重试次数,默认 3
* @returns {Promise<boolean>} - 上传是否成功
*/
export const uploadToCos = async (uploadUrl, filePath, retryCount = 3) => {
let lastError = null;
for (let i = 0; i < retryCount; i++) {
try {
console.log(`COS 上传尝试 ${i + 1}/${retryCount}:`, uploadUrl);
let result = false;
// #ifdef H5
// H5 环境使用 fetch API
result = await uploadToCosH5(uploadUrl, filePath);
// #endif
// #ifndef H5
// App/小程序环境使用 uni.uploadFile
result = await uploadToCosApp(uploadUrl, filePath);
// #endif
if (result) {
console.log("COS 上传成功");
return true;
}
} catch (error) {
console.error(`COS 上传失败 (尝试 ${i + 1}):`, error);
lastError = error;
}
}
throw lastError || new Error("COS 上传失败,已重试 " + retryCount + " 次");
};
/**
* H5 环境上传到 COS
* @param {string} uploadUrl - 预签名上传 URL
* @param {string} filePath - 本地文件路径blob URL 或 base64
* @returns {Promise<boolean>}
*/
const uploadToCosH5 = async (uploadUrl, filePath) => {
// 获取文件 Blob
let blob;
if (filePath.startsWith("blob:")) {
// blob URL
const response = await fetch(filePath);
blob = await response.blob();
} else if (filePath.startsWith("data:")) {
// base64 数据
const response = await fetch(filePath);
blob = await response.blob();
} else {
// 普通 URL
const response = await fetch(filePath);
blob = await response.blob();
}
// 使用 PUT 方法上传
const response = await fetch(uploadUrl, {
method: "PUT",
body: blob,
headers: {
"Content-Type": blob.type || "image/jpeg"
}
});
if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
}
return true;
};
/**
* App/小程序环境上传到 COS
* @param {string} uploadUrl - 预签名上传 URL
* @param {string} filePath - 本地文件路径
* @returns {Promise<boolean>}
*/
const uploadToCosApp = (uploadUrl, filePath) => {
return new Promise((resolve, reject) => {
// 先读取文件内容
uni.getFileSystemManager().readFile({
filePath: filePath,
success: (fileRes) => {
// 使用 uni.request 发送 PUT 请求
uni.request({
url: uploadUrl,
method: "PUT",
data: fileRes.data,
header: {
"Content-Type": "image/jpeg"
},
success: (res) => {
if (res.statusCode >= 200 && res.statusCode < 300) {
resolve(true);
} else {
reject(new Error(`HTTP ${res.statusCode}`));
}
},
fail: (err) => {
reject(err);
}
});
},
fail: (err) => {
// 如果 getFileSystemManager 不可用,尝试使用 uploadFile
uni.uploadFile({
url: uploadUrl,
filePath: filePath,
name: "file",
header: {
"Content-Type": "image/jpeg"
},
success: (res) => {
if (res.statusCode >= 200 && res.statusCode < 300) {
resolve(true);
} else {
reject(new Error(`HTTP ${res.statusCode}`));
}
},
fail: (uploadErr) => {
reject(uploadErr);
}
});
}
});
});
};
/**
* 并发上传多张图片到 COS
* @param {Array<Object>} uploadTasks - 上传任务列表
* @param {string} uploadTasks[].filePath - 本地文件路径
* @param {Object} uploadTasks[].uploadUrls - 预签名 URL 对象
* @param {number} concurrency - 最大并发数,默认 3
* @param {Function} onProgress - 进度回调 (completed, total)
* @returns {Promise<Array<Object>>} - 返回上传结果列表
*/
export const uploadImagesToCos = async (uploadTasks, concurrency = 3, onProgress = null) => {
const results = [];
let completed = 0;
const total = uploadTasks.length;
// 创建任务队列
const queue = [...uploadTasks];
const executing = [];
const executeTask = async (task, index) => {
try {
// 上传到 Daily 目录
if (task.uploadUrls.daily) {
await uploadToCos(task.uploadUrls.daily, task.filePath);
}
// 上传到 Workers 目录(每个工人一个目录)
if (task.uploadUrls.workers) {
for (const workerName in task.uploadUrls.workers) {
const workerUrl = task.uploadUrls.workers[workerName];
await uploadToCos(workerUrl, task.filePath);
}
}
// 上传到 Content 目录
if (task.uploadUrls.content) {
await uploadToCos(task.uploadUrls.content, task.filePath);
}
// 上传到 Dept 目录
if (task.uploadUrls.dept) {
await uploadToCos(task.uploadUrls.dept, task.filePath);
}
results[index] = {
success: true,
accessUrl: task.accessUrl
};
} catch (error) {
console.error(`图片 ${index + 1} 上传失败:`, error);
results[index] = {
success: false,
error: error.message
};
} finally {
completed++;
if (onProgress) {
onProgress(completed, total);
}
}
};
// 并发控制
for (let i = 0; i < queue.length; i++) {
const task = queue[i];
const promise = executeTask(task, i);
executing.push(promise);
if (executing.length >= concurrency) {
await Promise.race(executing);
// 移除已完成的 Promise
for (let j = executing.length - 1; j >= 0; j--) {
// Promise.race 不会告诉我们哪个完成了,所以我们需要检查
// 这里简化处理,等待所有当前执行的任务完成
}
}
}
// 等待所有任务完成
await Promise.all(executing);
return results;
};
/**
* 批量上传图片到 COS完整流程
* @param {Array<string>} filePaths - 本地文件路径列表
* @param {Object} recordInfo - 记录信息
* @param {Date} recordInfo.recordTime - 记录时间
* @param {string} recordInfo.deptName - 部门名称
* @param {string} recordInfo.content - 工作内容
* @param {Array<string>} recordInfo.workers - 工作人员列表
* @param {Function} onProgress - 进度回调 (stage, completed, total)
* @returns {Promise<Array<string>>} - 返回上传成功的 COS URL 列表
*/
export const batchUploadToCos = async (filePaths, recordInfo, onProgress = null) => {
if (!filePaths || filePaths.length === 0) {
throw new Error("没有要上传的图片");
}
// 1. 获取预签名 URL
if (onProgress) {
onProgress("getting_urls", 0, filePaths.length);
}
const urlsResponse = await getUploadUrls({
recordTime: recordInfo.recordTime,
deptName: recordInfo.deptName,
content: recordInfo.content,
workers: recordInfo.workers,
fileExt: ".jpg",
imageCount: filePaths.length
});
if (!urlsResponse || !urlsResponse.images || urlsResponse.images.length === 0) {
throw new Error("获取预签名 URL 失败");
}
// 2. 构建上传任务
const uploadTasks = filePaths.map((filePath, index) => {
const imageInfo = urlsResponse.images[index];
return {
filePath: filePath,
uploadUrls: imageInfo.uploadUrls,
accessUrl: imageInfo.accessUrl
};
});
// 3. 并发上传
const uploadResults = await uploadImagesToCos(
uploadTasks,
3, // 最大并发数
(completed, total) => {
if (onProgress) {
onProgress("uploading", completed, total);
}
}
);
// 4. 收集成功上传的 URL
const successUrls = [];
const failedIndexes = [];
uploadResults.forEach((result, index) => {
if (result.success) {
successUrls.push(result.accessUrl);
} else {
failedIndexes.push(index);
}
});
if (failedIndexes.length > 0) {
console.warn(`${failedIndexes.length} 张图片上传失败:`, failedIndexes);
}
if (successUrls.length === 0) {
throw new Error("所有图片上传失败");
}
return successUrls;
};