feat: 适配新uploadFn,并修复小程序端使用PUT的错误判断

This commit is contained in:
Pan Qiancheng 2025-12-26 17:07:55 +08:00
parent 56b07dc4cb
commit 95b506b0eb
26 changed files with 325 additions and 307 deletions

7
es/types/Cos.d.ts vendored
View File

@ -1,11 +1,8 @@
import { EntityDict } from '../oak-app-domain';
import { BRC } from '..';
import { Cache } from 'oak-frontend-base/es/features/cache';
export type UploadFn = (file: File | string | Blob, name: string, // 文件的part name
uploadUrl: string, // 上传的url
formData: Record<string, any>, // 上传的其它part参数
autoInform?: boolean, // 上传成功是否会自动通知server若不会则需要前台显式通知
getPercent?: Function, uploadId?: string, method?: "POST" | "PUT") => Promise<any>;
import { UploadInterface } from 'oak-frontend-base/es/types/Upload';
export type UploadFn = UploadInterface['uploadFile'];
export type UploadToAspect = (file: File | string, name: string, // 文件的part name
aspectName: string, // 上传的aspect名
formData: Record<string, any>, // 上传的其它part参数

View File

@ -43,12 +43,20 @@ export default class ALiYun {
else {
let response;
try {
response = await uploadFn(file, 'file', uploadMeta.uploadHost, {
response = await uploadFn({
file: file,
name: 'file',
uploadUrl: uploadMeta.uploadHost,
formData: {
key: uploadMeta.key,
policy: uploadMeta.policy,
ossAccessKeyId: uploadMeta.accessKey,
signature: uploadMeta.signature,
}, true, getPercent, extraFile.id);
},
autoInform: true,
getPercent,
uploadId: extraFile.id,
});
}
catch (err) {
// 网络错误

View File

@ -1,5 +1,4 @@
import { sliceFile, cleanTempFiles } from '../files/slice';
import assert from 'assert';
import { sliceFile } from '../files/slice';
import { OakUploadException } from '../../types/Exception';
export function isAbortError(error) {
return error instanceof DOMException && error.name === 'AbortError';
@ -37,25 +36,33 @@ export async function chunkUpload(options) {
let lastError;
for (let attempt = 0; attempt <= retryTimes; attempt++) {
try {
const response = await uploadFn(chunk, 'file', part.uploadUrl, part.formData || {}, true, (percent) => {
// 更新每个分片的进度
updateChunkPercent(part.partNumber, percent);
}, `${extraFile.id}:${part.partNumber}`, "PUT");
// 验证上传是否成功
let isSuccess = false;
if (process.env.OAK_PLATFORM === 'wechatMp') {
if (response.errMsg === 'request:ok') {
const data = JSON.parse(response.data);
isSuccess = !!(data.status === 204 || data.status === 200);
}
let data;
if (chunk.type === 'getter') {
data = await chunk.getFile();
}
else {
isSuccess = !!(response.status === 200 || response.status === 204);
data = chunk;
}
const response = await uploadFn({
file: data,
name: 'file',
uploadUrl: part.uploadUrl,
formData: part.formData || {},
autoInform: true,
getPercent: (percent) => {
// 更新每个分片的进度
updateChunkPercent(part.partNumber, percent);
},
uploadId: `${extraFile.id}:${part.partNumber}`,
method: "PUT"
});
// 验证上传是否成功
let isSuccess = false;
isSuccess = !!(response.status === 200 || response.status === 204);
if (isSuccess) {
// 标记该分片已完成
part.etag = response.headers?.get("ETag") || response.headers?.get("etag") || response.headers?.get("eTag");
assert(part.etag, `无法获取分片 ${part.partNumber} 的 ETag`);
// // 标记该分片已完成
// part.etag = (response as Response).headers?.get("ETag") || response.headers?.get("etag") || response.headers?.get("eTag")
// assert(part.etag, `无法获取分片 ${part.partNumber} 的 ETag`);
return;
}
throw new OakUploadException(`分片 ${part.partNumber} 上传失败`);
@ -122,9 +129,5 @@ export async function chunkUpload(options) {
// if (onChunkSuccess) {
// await onChunkSuccess(chunkInfo);
// }
// 清理小程序环境下的临时文件
if (process.env.OAK_PLATFORM === 'wechatMp' && typeof file === 'string') {
await cleanTempFiles(chunks);
}
return;
}

View File

@ -30,12 +30,20 @@ export default class CTYun {
assert(extraFile.enableChunkedUpload !== true, '天翼云暂不支持分片上传');
let response;
try {
response = await uploadFn(file, 'file', uploadMeta.uploadHost, {
response = await uploadFn({
file: file,
name: 'file',
uploadUrl: uploadMeta.uploadHost,
formData: {
key: uploadMeta.key,
Policy: uploadMeta.policy,
AWSAccessKeyId: uploadMeta.accessKey,
signature: uploadMeta.signature,
}, true, getPercent, extraFile.id);
},
autoInform: true,
getPercent,
uploadId: extraFile.id,
});
}
catch (err) {
// 网络错误

View File

@ -30,10 +30,18 @@ export default class Qiniu {
assert(extraFile.enableChunkedUpload !== true, '暂不支持分片上传');
let response;
try {
response = await uploadFn(file, 'file', uploadMeta.uploadHost, {
response = await uploadFn({
file,
name: 'file',
uploadUrl: uploadMeta.uploadHost,
formData: {
key: uploadMeta.key,
token: uploadMeta.uploadToken,
}, true, getPercent, extraFile.id);
},
autoInform: true,
getPercent,
uploadId: extraFile.id,
});
}
catch (err) {
// 网络错误

View File

@ -46,22 +46,23 @@ export default class S3 {
let response;
try {
// S3 使用预签名 URL 直接上传,不需要额外的 formData
response = await uploadFn(file, 'file', uploadMeta.uploadUrl, {}, true, getPercent, extraFile.id, "PUT");
response = await uploadFn({
file,
name: 'file',
uploadUrl: uploadMeta.uploadUrl,
formData: {},
autoInform: true,
getPercent,
uploadId: extraFile.id,
method: "PUT",
isFilePath: true,
});
}
catch (err) {
throw new OakNetworkException('网络异常,请求失败');
}
let isSuccess = false;
if (process.env.OAK_PLATFORM === 'wechatMp') {
// 小程序端上传
if (response.errMsg === 'uploadFile:ok') {
const statusCode = response.statusCode;
isSuccess = statusCode === 200 || statusCode === 204;
}
}
else {
isSuccess = response.status === 200 || response.status === 204;
}
if (isSuccess) {
return;
}

View File

@ -31,7 +31,11 @@ export default class TencentYun {
assert(extraFile.enableChunkedUpload !== true, '暂不支持分片上传');
let response;
try {
response = await uploadFn(file, 'file', uploadMeta.uploadHost, {
response = await uploadFn({
file,
name: 'file',
uploadUrl: uploadMeta.uploadHost,
formData: {
key: uploadMeta.key,
policy: uploadMeta.policy,
signature: uploadMeta.signature,
@ -39,7 +43,11 @@ export default class TencentYun {
'q-ak': uploadMeta.accessKey,
'q-key-time': uploadMeta.keyTime,
'q-signature': uploadMeta.signature,
}, true, getPercent, extraFile.id);
},
autoInform: true,
getPercent,
uploadId: extraFile.id
});
}
catch (err) {
// 网络错误

View File

@ -1,3 +1,7 @@
export declare function sliceFile(file: string | File, chunkSize: number, partCount: number): Promise<(File | Blob | string)[]>;
export declare function sliceFileForMp(filePath: string, chunkSize: number, partCount: number): Promise<string[]>;
export type MpFileGetter = {
type: 'getter';
getFile: () => Promise<string | ArrayBuffer>;
};
export declare function sliceFile(file: string | File, chunkSize: number, partCount: number): Promise<(File | Blob | MpFileGetter)[]>;
export declare function sliceFileForMp(filePath: string, chunkSize: number, partCount: number): Promise<MpFileGetter[]>;
export declare function cleanTempFiles(tempFiles: string[]): Promise<void>;

View File

@ -42,34 +42,17 @@ export async function sliceFileForMp(filePath, chunkSize, partCount) {
const start = i * chunkSize;
const length = Math.min(chunkSize, fileSize - start);
try {
chunks.push({
type: 'getter',
getFile: async () => {
// 读取分片数据
const data = await new Promise((resolve, reject) => {
fs.readFile({
filePath: filePath,
encoding: 'binary',
position: start,
length: length,
success: (res) => resolve(res.data),
fail: reject
const data = fs.readFileSync(filePath, 'binary', start, length);
return data;
}
});
});
// 写入临时文件
const tempFilePath = `${tempDir}/chunk_${Date.now()}_${i}.temp`;
await new Promise((resolve, reject) => {
fs.writeFile({
filePath: tempFilePath,
encoding: 'binary',
data: data,
success: () => resolve(),
fail: reject
});
});
chunks.push(tempFilePath);
}
catch (err) {
console.error('分片读取失败:', err);
// 清理已创建的临时文件
await cleanTempFiles(chunks);
throw new OakUploadException(`分片 ${i + 1} 读取失败: ${err}`);
}
}

7
lib/types/Cos.d.ts vendored
View File

@ -1,11 +1,8 @@
import { EntityDict } from '../oak-app-domain';
import { BRC } from '..';
import { Cache } from 'oak-frontend-base/es/features/cache';
export type UploadFn = (file: File | string | Blob, name: string, // 文件的part name
uploadUrl: string, // 上传的url
formData: Record<string, any>, // 上传的其它part参数
autoInform?: boolean, // 上传成功是否会自动通知server若不会则需要前台显式通知
getPercent?: Function, uploadId?: string, method?: "POST" | "PUT") => Promise<any>;
import { UploadInterface } from 'oak-frontend-base/es/types/Upload';
export type UploadFn = UploadInterface['uploadFile'];
export type UploadToAspect = (file: File | string, name: string, // 文件的part name
aspectName: string, // 上传的aspect名
formData: Record<string, any>, // 上传的其它part参数

View File

@ -45,12 +45,20 @@ class ALiYun {
else {
let response;
try {
response = await uploadFn(file, 'file', uploadMeta.uploadHost, {
response = await uploadFn({
file: file,
name: 'file',
uploadUrl: uploadMeta.uploadHost,
formData: {
key: uploadMeta.key,
policy: uploadMeta.policy,
ossAccessKeyId: uploadMeta.accessKey,
signature: uploadMeta.signature,
}, true, getPercent, extraFile.id);
},
autoInform: true,
getPercent,
uploadId: extraFile.id,
});
}
catch (err) {
// 网络错误

View File

@ -2,9 +2,7 @@
Object.defineProperty(exports, "__esModule", { value: true });
exports.isAbortError = isAbortError;
exports.chunkUpload = chunkUpload;
const tslib_1 = require("tslib");
const slice_1 = require("../files/slice");
const assert_1 = tslib_1.__importDefault(require("assert"));
const Exception_1 = require("../../types/Exception");
function isAbortError(error) {
return error instanceof DOMException && error.name === 'AbortError';
@ -42,25 +40,33 @@ async function chunkUpload(options) {
let lastError;
for (let attempt = 0; attempt <= retryTimes; attempt++) {
try {
const response = await uploadFn(chunk, 'file', part.uploadUrl, part.formData || {}, true, (percent) => {
// 更新每个分片的进度
updateChunkPercent(part.partNumber, percent);
}, `${extraFile.id}:${part.partNumber}`, "PUT");
// 验证上传是否成功
let isSuccess = false;
if (process.env.OAK_PLATFORM === 'wechatMp') {
if (response.errMsg === 'request:ok') {
const data = JSON.parse(response.data);
isSuccess = !!(data.status === 204 || data.status === 200);
}
let data;
if (chunk.type === 'getter') {
data = await chunk.getFile();
}
else {
isSuccess = !!(response.status === 200 || response.status === 204);
data = chunk;
}
const response = await uploadFn({
file: data,
name: 'file',
uploadUrl: part.uploadUrl,
formData: part.formData || {},
autoInform: true,
getPercent: (percent) => {
// 更新每个分片的进度
updateChunkPercent(part.partNumber, percent);
},
uploadId: `${extraFile.id}:${part.partNumber}`,
method: "PUT"
});
// 验证上传是否成功
let isSuccess = false;
isSuccess = !!(response.status === 200 || response.status === 204);
if (isSuccess) {
// 标记该分片已完成
part.etag = response.headers?.get("ETag") || response.headers?.get("etag") || response.headers?.get("eTag");
(0, assert_1.default)(part.etag, `无法获取分片 ${part.partNumber} 的 ETag`);
// // 标记该分片已完成
// part.etag = (response as Response).headers?.get("ETag") || response.headers?.get("etag") || response.headers?.get("eTag")
// assert(part.etag, `无法获取分片 ${part.partNumber} 的 ETag`);
return;
}
throw new Exception_1.OakUploadException(`分片 ${part.partNumber} 上传失败`);
@ -127,9 +133,5 @@ async function chunkUpload(options) {
// if (onChunkSuccess) {
// await onChunkSuccess(chunkInfo);
// }
// 清理小程序环境下的临时文件
if (process.env.OAK_PLATFORM === 'wechatMp' && typeof file === 'string') {
await (0, slice_1.cleanTempFiles)(chunks);
}
return;
}

View File

@ -32,12 +32,20 @@ class CTYun {
(0, assert_1.assert)(extraFile.enableChunkedUpload !== true, '天翼云暂不支持分片上传');
let response;
try {
response = await uploadFn(file, 'file', uploadMeta.uploadHost, {
response = await uploadFn({
file: file,
name: 'file',
uploadUrl: uploadMeta.uploadHost,
formData: {
key: uploadMeta.key,
Policy: uploadMeta.policy,
AWSAccessKeyId: uploadMeta.accessKey,
signature: uploadMeta.signature,
}, true, getPercent, extraFile.id);
},
autoInform: true,
getPercent,
uploadId: extraFile.id,
});
}
catch (err) {
// 网络错误

View File

@ -32,10 +32,18 @@ class Qiniu {
(0, assert_1.assert)(extraFile.enableChunkedUpload !== true, '暂不支持分片上传');
let response;
try {
response = await uploadFn(file, 'file', uploadMeta.uploadHost, {
response = await uploadFn({
file,
name: 'file',
uploadUrl: uploadMeta.uploadHost,
formData: {
key: uploadMeta.key,
token: uploadMeta.uploadToken,
}, true, getPercent, extraFile.id);
},
autoInform: true,
getPercent,
uploadId: extraFile.id,
});
}
catch (err) {
// 网络错误

View File

@ -48,22 +48,23 @@ class S3 {
let response;
try {
// S3 使用预签名 URL 直接上传,不需要额外的 formData
response = await uploadFn(file, 'file', uploadMeta.uploadUrl, {}, true, getPercent, extraFile.id, "PUT");
response = await uploadFn({
file,
name: 'file',
uploadUrl: uploadMeta.uploadUrl,
formData: {},
autoInform: true,
getPercent,
uploadId: extraFile.id,
method: "PUT",
isFilePath: true,
});
}
catch (err) {
throw new Exception_2.OakNetworkException('网络异常,请求失败');
}
let isSuccess = false;
if (process.env.OAK_PLATFORM === 'wechatMp') {
// 小程序端上传
if (response.errMsg === 'uploadFile:ok') {
const statusCode = response.statusCode;
isSuccess = statusCode === 200 || statusCode === 204;
}
}
else {
isSuccess = response.status === 200 || response.status === 204;
}
if (isSuccess) {
return;
}

View File

@ -33,7 +33,11 @@ class TencentYun {
(0, assert_1.assert)(extraFile.enableChunkedUpload !== true, '暂不支持分片上传');
let response;
try {
response = await uploadFn(file, 'file', uploadMeta.uploadHost, {
response = await uploadFn({
file,
name: 'file',
uploadUrl: uploadMeta.uploadHost,
formData: {
key: uploadMeta.key,
policy: uploadMeta.policy,
signature: uploadMeta.signature,
@ -41,7 +45,11 @@ class TencentYun {
'q-ak': uploadMeta.accessKey,
'q-key-time': uploadMeta.keyTime,
'q-signature': uploadMeta.signature,
}, true, getPercent, extraFile.id);
},
autoInform: true,
getPercent,
uploadId: extraFile.id
});
}
catch (err) {
// 网络错误

View File

@ -1,3 +1,7 @@
export declare function sliceFile(file: string | File, chunkSize: number, partCount: number): Promise<(File | Blob | string)[]>;
export declare function sliceFileForMp(filePath: string, chunkSize: number, partCount: number): Promise<string[]>;
export type MpFileGetter = {
type: 'getter';
getFile: () => Promise<string | ArrayBuffer>;
};
export declare function sliceFile(file: string | File, chunkSize: number, partCount: number): Promise<(File | Blob | MpFileGetter)[]>;
export declare function sliceFileForMp(filePath: string, chunkSize: number, partCount: number): Promise<MpFileGetter[]>;
export declare function cleanTempFiles(tempFiles: string[]): Promise<void>;

View File

@ -48,34 +48,17 @@ async function sliceFileForMp(filePath, chunkSize, partCount) {
const start = i * chunkSize;
const length = Math.min(chunkSize, fileSize - start);
try {
chunks.push({
type: 'getter',
getFile: async () => {
// 读取分片数据
const data = await new Promise((resolve, reject) => {
fs.readFile({
filePath: filePath,
encoding: 'binary',
position: start,
length: length,
success: (res) => resolve(res.data),
fail: reject
const data = fs.readFileSync(filePath, 'binary', start, length);
return data;
}
});
});
// 写入临时文件
const tempFilePath = `${tempDir}/chunk_${Date.now()}_${i}.temp`;
await new Promise((resolve, reject) => {
fs.writeFile({
filePath: tempFilePath,
encoding: 'binary',
data: data,
success: () => resolve(),
fail: reject
});
});
chunks.push(tempFilePath);
}
catch (err) {
console.error('分片读取失败:', err);
// 清理已创建的临时文件
await cleanTempFiles(chunks);
throw new Exception_1.OakUploadException(`分片 ${i + 1} 读取失败: ${err}`);
}
}

View File

@ -1,17 +1,9 @@
import { EntityDict } from '../oak-app-domain';
import { BRC } from '..';
import { Cache } from 'oak-frontend-base/es/features/cache'
import { UploadInterface } from 'oak-frontend-base/es/types/Upload';
export type UploadFn = (
file: File | string | Blob,
name: string, // 文件的part name
uploadUrl: string, // 上传的url
formData: Record<string, any>, // 上传的其它part参数
autoInform?: boolean, // 上传成功是否会自动通知server若不会则需要前台显式通知
getPercent?: Function,
uploadId?: string,
method?: "POST" | "PUT" //默认POST
) => Promise<any>
export type UploadFn = UploadInterface['uploadFile']
export type UploadToAspect = (
file: File | string,

View File

@ -70,18 +70,20 @@ export default class ALiYun implements Cos<EntityDict> {
let response;
try {
response = await uploadFn(
file,
'file',
uploadMeta.uploadHost,
{
file: file,
name: 'file',
uploadUrl: uploadMeta.uploadHost,
formData: {
key: uploadMeta.key,
policy: uploadMeta.policy,
ossAccessKeyId: uploadMeta.accessKey,
signature: uploadMeta.signature,
},
true,
autoInform: true,
getPercent,
extraFile.id!,
uploadId: extraFile.id!,
}
);
} catch (err: any) {
// 网络错误

View File

@ -1,4 +1,4 @@
import { sliceFile, cleanTempFiles } from '../files/slice';
import { MpFileGetter, sliceFile, cleanTempFiles } from '../files/slice';
import { OpSchema } from '../../oak-app-domain/ExtraFile/Schema';
import { UploadFn } from "../../types/Cos";
import { EntityDict } from '../../oak-app-domain';
@ -59,39 +59,41 @@ export async function chunkUpload(
}, 500);
// 上传单个分片的函数,带重试
const uploadPart = async (part: typeof chunkInfo.parts[0], chunk: File | Blob | string) => {
const uploadPart = async (part: typeof chunkInfo.parts[0], chunk: File | Blob | MpFileGetter) => {
let lastError;
for (let attempt = 0; attempt <= retryTimes; attempt++) {
try {
let data: File | Blob | string;
if (chunk.type === 'getter') {
data = await (chunk as MpFileGetter).getFile() as string;
} else {
data = chunk as File | Blob;
}
const response = await uploadFn(
chunk,
'file',
part.uploadUrl,
part.formData || {},
true,
(percent: number) => {
{
file: data,
name: 'file',
uploadUrl: part.uploadUrl,
formData: part.formData || {},
autoInform: true,
getPercent: (percent: number) => {
// 更新每个分片的进度
updateChunkPercent(part.partNumber, percent);
},
`${extraFile.id}:${part.partNumber}`,
"PUT"
uploadId: `${extraFile.id}:${part.partNumber}`,
method: "PUT"
}
);
// 验证上传是否成功
let isSuccess = false;
if (process.env.OAK_PLATFORM === 'wechatMp') {
if (response.errMsg === 'request:ok') {
const data = JSON.parse(response.data);
isSuccess = !!(data.status === 204 || data.status === 200);
}
} else {
isSuccess = !!(response.status === 200 || response.status === 204);
}
if (isSuccess) {
// 标记该分片已完成
part.etag = (response as Response).headers?.get("ETag") || response.headers?.get("etag") || response.headers?.get("eTag")
assert(part.etag, `无法获取分片 ${part.partNumber} 的 ETag`);
// // 标记该分片已完成
// part.etag = (response as Response).headers?.get("ETag") || response.headers?.get("etag") || response.headers?.get("eTag")
// assert(part.etag, `无法获取分片 ${part.partNumber} 的 ETag`);
return;
}
@ -167,10 +169,5 @@ export async function chunkUpload(
// await onChunkSuccess(chunkInfo);
// }
// 清理小程序环境下的临时文件
if (process.env.OAK_PLATFORM === 'wechatMp' && typeof file === 'string') {
await cleanTempFiles(chunks as string[]);
}
return;
}

View File

@ -53,18 +53,20 @@ export default class CTYun implements Cos<EntityDict> {
let response;
try {
response = await uploadFn(
file,
'file',
uploadMeta.uploadHost,
{
file: file,
name: 'file',
uploadUrl: uploadMeta.uploadHost,
formData: {
key: uploadMeta.key,
Policy: uploadMeta.policy,
AWSAccessKeyId: uploadMeta.accessKey,
signature: uploadMeta.signature,
},
true,
autoInform: true,
getPercent,
extraFile.id,
uploadId: extraFile.id!,
}
);
} catch (err) {
// 网络错误

View File

@ -53,16 +53,18 @@ export default class Qiniu implements Cos<EntityDict> {
let response;
try {
response = await uploadFn(
file,
'file',
uploadMeta.uploadHost,
{
file,
name: 'file',
uploadUrl: uploadMeta.uploadHost,
formData: {
key: uploadMeta.key,
token: uploadMeta.uploadToken,
},
true,
autoInform: true,
getPercent,
extraFile.id!,
uploadId: extraFile.id!,
}
);
} catch (err) {
// 网络错误

View File

@ -75,29 +75,24 @@ export default class S3 implements Cos<EntityDict> {
try {
// S3 使用预签名 URL 直接上传,不需要额外的 formData
response = await uploadFn(
{
file,
'file',
uploadMeta.uploadUrl,
{},
true,
name: 'file',
uploadUrl: uploadMeta.uploadUrl,
formData: {},
autoInform: true,
getPercent,
extraFile.id,
"PUT"
uploadId: extraFile.id,
method: "PUT",
isFilePath: true,
}
);
} catch (err) {
throw new OakNetworkException('网络异常,请求失败');
}
let isSuccess = false;
if (process.env.OAK_PLATFORM === 'wechatMp') {
// 小程序端上传
if (response.errMsg === 'uploadFile:ok') {
const statusCode = response.statusCode;
isSuccess = statusCode === 200 || statusCode === 204;
}
} else {
isSuccess = response.status === 200 || response.status === 204;
}
if (isSuccess) {
return;

View File

@ -55,10 +55,11 @@ export default class TencentYun implements Cos<EntityDict> {
let response;
try {
response = await uploadFn(
file,
'file',
uploadMeta.uploadHost,
{
file,
name: 'file',
uploadUrl: uploadMeta.uploadHost,
formData: {
key: uploadMeta.key,
policy: uploadMeta.policy,
signature: uploadMeta.signature,
@ -67,9 +68,10 @@ export default class TencentYun implements Cos<EntityDict> {
'q-key-time': uploadMeta.keyTime,
'q-signature': uploadMeta.signature,
},
true,
autoInform: true,
getPercent,
extraFile.id!
uploadId: extraFile.id!
}
);
} catch (err) {
// 网络错误

View File

@ -1,8 +1,14 @@
import assert from "assert";
import { OakUploadException } from '../../types/Exception';
// 小程序专用类型避免下方writeFile报错
export type MpFileGetter = {
type: 'getter',
getFile: () => Promise<string | ArrayBuffer>
};
// 辅助方法:将文件切片
export async function sliceFile(file: string | File, chunkSize: number, partCount: number): Promise<(File | Blob | string)[]> {
export async function sliceFile(file: string | File, chunkSize: number, partCount: number): Promise<(File | Blob | MpFileGetter)[]> {
if (process.env.OAK_PLATFORM === 'wechatMp') {
// 微信小程序环境下file 可能是临时文件路径字符串
if (typeof file === 'string') {
@ -23,7 +29,7 @@ export async function sliceFile(file: string | File, chunkSize: number, partCoun
}
// 小程序环境下的文件分片
export async function sliceFileForMp(filePath: string, chunkSize: number, partCount: number): Promise<string[]> {
export async function sliceFileForMp(filePath: string, chunkSize: number, partCount: number): Promise<MpFileGetter[]> {
const fs = wx.getFileSystemManager();
const tempDir = `${wx.env.USER_DATA_PATH}/oak_upload_temp`;
@ -36,7 +42,7 @@ export async function sliceFileForMp(filePath: string, chunkSize: number, partCo
fs.mkdirSync(tempDir, false);
}
const chunks: string[] = [];
const chunks: MpFileGetter[] = [];
// 读取文件信息
const fileStat = fs.statSync(filePath);
@ -49,35 +55,16 @@ export async function sliceFileForMp(filePath: string, chunkSize: number, partCo
const length = Math.min(chunkSize, fileSize - start);
try {
chunks.push({
type: 'getter',
getFile: async () => {
// 读取分片数据
const data = await new Promise<string>((resolve, reject) => {
fs.readFile({
filePath: filePath,
encoding: 'binary',
position: start,
length: length,
success: (res) => resolve(res.data as string),
fail: reject
const data = fs.readFileSync(filePath, 'binary', start, length);
return data;
}
});
});
// 写入临时文件
const tempFilePath = `${tempDir}/chunk_${Date.now()}_${i}.temp`;
await new Promise<void>((resolve, reject) => {
fs.writeFile({
filePath: tempFilePath,
encoding: 'binary',
data: data,
success: () => resolve(),
fail: reject
});
});
chunks.push(tempFilePath);
} catch (err) {
console.error('分片读取失败:', err);
// 清理已创建的临时文件
await cleanTempFiles(chunks);
throw new OakUploadException(`分片 ${i + 1} 读取失败: ${err}`);
}
}