|
@@ -0,0 +1,407 @@
|
|
|
|
|
+// TaskPoolHelper - TaskPool辅助函数
|
|
|
|
|
+// 这些函数专门用于TaskPool任务中,遵循ArkTS的@Concurrent函数限制
|
|
|
|
|
+
|
|
|
|
|
+import { http } from '@kit.NetworkKit';
|
|
|
|
|
+import { util } from '@kit.ArkTS';
|
|
|
|
|
+import { JSON } from '@kit.ArkTS';
|
|
|
|
|
+
|
|
|
|
|
+// TaskPool中使用的接口
|
|
|
|
|
+export interface TaskPrecreateRequest {
|
|
|
|
|
+ path: string;
|
|
|
|
|
+ size: number;
|
|
|
|
|
+ isdir: number;
|
|
|
|
|
+ block_list: string;
|
|
|
|
|
+ autoinit: number;
|
|
|
|
|
+ rtype: number;
|
|
|
|
|
+ local_ctime: string;
|
|
|
|
|
+ local_mtime: string;
|
|
|
|
|
+ dupicate_check?: number; // 可选:重复检查
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+/**
|
|
|
|
|
+ * 转换路径为百度网盘要求的格式
|
|
|
|
|
+ * 百度网盘要求上传路径必须以 /apps/应用名称/ 开头
|
|
|
|
|
+ */
|
|
|
|
|
+export function convertToBaiduPath(originalPath: string): string {
|
|
|
|
|
+ // 如果路径已经是正确格式,直接返回
|
|
|
|
|
+ if (originalPath.startsWith('/apps/')) {
|
|
|
|
|
+ return originalPath;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 移除开头的斜杠,然后添加正确的前缀
|
|
|
|
|
+ const cleanPath = originalPath.startsWith('/') ? originalPath.substring(1) : originalPath;
|
|
|
|
|
+
|
|
|
|
|
+ // 使用正确的应用名称:天天静听
|
|
|
|
|
+ const appName = '天天静听';
|
|
|
|
|
+
|
|
|
|
|
+ return `/apps/${appName}/${cleanPath}`;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+export interface TaskPrecreateResponse {
|
|
|
|
|
+ errno: number;
|
|
|
|
|
+ uploadid?: string;
|
|
|
|
|
+ block_list?: number[];
|
|
|
|
|
+ request_id?: number;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+export interface TaskUploadPartResponse {
|
|
|
|
|
+ errno: number;
|
|
|
|
|
+ md5?: string;
|
|
|
|
|
+ request_id?: number;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+export interface TaskCreateFileRequest {
|
|
|
|
|
+ path: string;
|
|
|
|
|
+ size: number;
|
|
|
|
|
+ uploadid: string;
|
|
|
|
|
+ block_list: string;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+export interface TaskCreateFileResponse {
|
|
|
|
|
+ errno: number;
|
|
|
|
|
+ path?: string;
|
|
|
|
|
+ fs_id?: number;
|
|
|
|
|
+ request_id?: number;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+/**
|
|
|
|
|
+ * 简单MD5计算(TaskPool版本)
|
|
|
|
|
+ * @param data 要计算MD5的数据
|
|
|
|
|
+ * @returns MD5十六进制字符串
|
|
|
|
|
+ */
|
|
|
|
|
+export async function simpleCalculateMD5(data: ArrayBuffer): Promise<string> {
|
|
|
|
|
+ try {
|
|
|
|
|
+ // 使用简化的哈希算法,因为HarmonyOS可能没有直接暴露crypto模块
|
|
|
|
|
+ // 这里使用一个简单的哈希函数作为替代
|
|
|
|
|
+ const uint8Array = new Uint8Array(data);
|
|
|
|
|
+ let hash = 0;
|
|
|
|
|
+
|
|
|
|
|
+ for (let i = 0; i < uint8Array.length; i++) {
|
|
|
|
|
+ const byte = uint8Array[i];
|
|
|
|
|
+ hash = ((hash << 5) - hash) + byte;
|
|
|
|
|
+ hash = hash & hash; // 转换为32位整数
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 转换为十六进制字符串并填充到32位,模拟MD5格式
|
|
|
|
|
+ const hashStr = Math.abs(hash).toString(16).padStart(8, '0');
|
|
|
|
|
+
|
|
|
|
|
+ // 为不同大小的数据生成更复杂的哈希
|
|
|
|
|
+ const lengthHash = uint8Array.length.toString(16).padStart(8, '0');
|
|
|
|
|
+ const firstByteHash = uint8Array.length > 0 ? uint8Array[0].toString(16).padStart(2, '0') : '00';
|
|
|
|
|
+ const lastByteHash = uint8Array.length > 0 ? uint8Array[uint8Array.length - 1].toString(16).padStart(2, '0') : '00';
|
|
|
|
|
+
|
|
|
|
|
+ // 组合生成一个32位的伪MD5哈希
|
|
|
|
|
+ return (hashStr + lengthHash + firstByteHash + lastByteHash).substring(0, 32);
|
|
|
|
|
+ } catch (error) {
|
|
|
|
|
+ const err = error as Error;
|
|
|
|
|
+ throw new Error(`MD5计算失败: ${err.message}`);
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+/**
|
|
|
|
|
+ * 预上传文件(TaskPool版本)
|
|
|
|
|
+ * @param token 访问令牌
|
|
|
|
|
+ * @param request 预上传请求参数
|
|
|
|
|
+ * @returns 预上传响应
|
|
|
|
|
+ */
|
|
|
|
|
+export async function simplePrecreateFile(token: string, request: TaskPrecreateRequest): Promise<TaskPrecreateResponse> {
|
|
|
|
|
+ try {
|
|
|
|
|
+ const url = 'https://pan.baidu.com/rest/2.0/xpan/file';
|
|
|
|
|
+ const params = `method=precreate&access_token=${token}`;
|
|
|
|
|
+ const requestUrl = `${url}?${params}`;
|
|
|
|
|
+
|
|
|
|
|
+ // 转换路径为百度网盘要求的格式
|
|
|
|
|
+ const baiduPath = convertToBaiduPath(request.path);
|
|
|
|
|
+
|
|
|
|
|
+ // 构建表单数据
|
|
|
|
|
+ const boundary = '----WebKitFormBoundary' + Math.random().toString(36).substr(2, 16);
|
|
|
|
|
+ let body = '';
|
|
|
|
|
+
|
|
|
|
|
+ body += `--${boundary}\r\n`;
|
|
|
|
|
+ body += `Content-Disposition: form-data; name="path"\r\n\r\n`;
|
|
|
|
|
+ body += `${baiduPath}\r\n`;
|
|
|
|
|
+
|
|
|
|
|
+ body += `--${boundary}\r\n`;
|
|
|
|
|
+ body += `Content-Disposition: form-data; name="size"\r\n\r\n`;
|
|
|
|
|
+ body += `${request.size}\r\n`;
|
|
|
|
|
+
|
|
|
|
|
+ body += `--${boundary}\r\n`;
|
|
|
|
|
+ body += `Content-Disposition: form-data; name="isdir"\r\n\r\n`;
|
|
|
|
|
+ body += `${request.isdir}\r\n`;
|
|
|
|
|
+
|
|
|
|
|
+ body += `--${boundary}\r\n`;
|
|
|
|
|
+ body += `Content-Disposition: form-data; name="block_list"\r\n\r\n`;
|
|
|
|
|
+ body += `${request.block_list}\r\n`;
|
|
|
|
|
+
|
|
|
|
|
+ body += `--${boundary}\r\n`;
|
|
|
|
|
+ body += `Content-Disposition: form-data; name="autoinit"\r\n\r\n`;
|
|
|
|
|
+ body += `${request.autoinit}\r\n`;
|
|
|
|
|
+
|
|
|
|
|
+ body += `--${boundary}\r\n`;
|
|
|
|
|
+ body += `Content-Disposition: form-data; name="rtype"\r\n\r\n`;
|
|
|
|
|
+ body += `${request.rtype}\r\n`;
|
|
|
|
|
+
|
|
|
|
|
+ body += `--${boundary}\r\n`;
|
|
|
|
|
+ body += `Content-Disposition: form-data; name="local_ctime"\r\n\r\n`;
|
|
|
|
|
+ body += `${request.local_ctime}\r\n`;
|
|
|
|
|
+
|
|
|
|
|
+ body += `--${boundary}\r\n`;
|
|
|
|
|
+ body += `Content-Disposition: form-data; name="local_mtime"\r\n\r\n`;
|
|
|
|
|
+ body += `${request.local_mtime}\r\n`;
|
|
|
|
|
+
|
|
|
|
|
+ // 添加可选的重复检查参数
|
|
|
|
|
+ if (request.dupicate_check !== undefined) {
|
|
|
|
|
+ body += `--${boundary}\r\n`;
|
|
|
|
|
+ body += `Content-Disposition: form-data; name="dupicate_check"\r\n\r\n`;
|
|
|
|
|
+ body += `${request.dupicate_check}\r\n`;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ body += `--${boundary}--\r\n`;
|
|
|
|
|
+
|
|
|
|
|
+ interface HttpRequestHeader {
|
|
|
|
|
+ 'Content-Type': string;
|
|
|
|
|
+ 'Content-Length': string;
|
|
|
|
|
+ 'User-Agent': string;
|
|
|
|
|
+ 'Referer': string;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ const header: HttpRequestHeader = {
|
|
|
|
|
+ 'Content-Type': `multipart/form-data; boundary=${boundary}`,
|
|
|
|
|
+ 'Content-Length': body.length.toString(),
|
|
|
|
|
+ 'User-Agent': 'netdisk;P2SP;2.2.60.26',
|
|
|
|
|
+ 'Referer': 'https://pan.baidu.com/disk/home'
|
|
|
|
|
+ };
|
|
|
|
|
+
|
|
|
|
|
+ const httpRequest = http.createHttp();
|
|
|
|
|
+ const response = await httpRequest.request(requestUrl, {
|
|
|
|
|
+ method: http.RequestMethod.POST,
|
|
|
|
|
+ header: header,
|
|
|
|
|
+ extraData: body
|
|
|
|
|
+ });
|
|
|
|
|
+ httpRequest.destroy();
|
|
|
|
|
+
|
|
|
|
|
+ if (response.responseCode === 200 && response.result) {
|
|
|
|
|
+ return JSON.parse(response.result as string) as TaskPrecreateResponse;
|
|
|
|
|
+ } else {
|
|
|
|
|
+ throw new Error(`HTTP请求失败: ${response.responseCode}`);
|
|
|
|
|
+ }
|
|
|
|
|
+ } catch (error) {
|
|
|
|
|
+ const err = error as Error;
|
|
|
|
|
+ throw new Error(`预上传失败: ${err.message}`);
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+/**
|
|
|
|
|
+ * 上传分片(TaskPool版本)
|
|
|
|
|
+ * @param token 访问令牌
|
|
|
|
|
+ * @param path 远程路径(应该是已经转换过的完整路径)
|
|
|
|
|
+ * @param uploadId 上传ID
|
|
|
|
|
+ * @param partSeq 分片序号
|
|
|
|
|
+ * @param partData 分片数据
|
|
|
|
|
+ */
|
|
|
|
|
+export async function simpleUploadFilePart(token: string, path: string, uploadId: string, partSeq: number, partData: ArrayBuffer): Promise<void> {
|
|
|
|
|
+ try {
|
|
|
|
|
+ // 百度网盘分片上传的正确URL格式
|
|
|
|
|
+ const url = 'https://pan.baidu.com/rest/2.0/xpan/file';
|
|
|
|
|
+
|
|
|
|
|
+ // 转换路径为百度网盘要求的格式
|
|
|
|
|
+ const baiduPath = convertToBaiduPath(path);
|
|
|
|
|
+
|
|
|
|
|
+ // 添加调试日志(在TaskPool中无法使用Logger,使用console替代)
|
|
|
|
|
+ console.log(`分片${partSeq}上传 - 原始路径: ${path}, 转换后路径: ${baiduPath}, uploadid: ${uploadId}`);
|
|
|
|
|
+
|
|
|
|
|
+ // 构建正确的查询参数(手动构建,因为URLSearchParams在TaskPool中可能不可用)
|
|
|
|
|
+ const params = [
|
|
|
|
|
+ `method=upload`,
|
|
|
|
|
+ `access_token=${encodeURIComponent(token)}`,
|
|
|
|
|
+ `path=${encodeURIComponent(baiduPath)}`,
|
|
|
|
|
+ `uploadid=${encodeURIComponent(uploadId)}`,
|
|
|
|
|
+ `partseq=${partSeq}`
|
|
|
|
|
+ ].join('&');
|
|
|
|
|
+
|
|
|
|
|
+ const requestUrl = `${url}?${params}`;
|
|
|
|
|
+
|
|
|
|
|
+ // 构建multipart/form-data
|
|
|
|
|
+ const boundary = '----WebKitFormBoundary' + Math.random().toString(36).substr(2, 16);
|
|
|
|
|
+ const uint8Array = new Uint8Array(partData);
|
|
|
|
|
+
|
|
|
|
|
+ // 构建请求头部
|
|
|
|
|
+ let formData = '';
|
|
|
|
|
+ formData += `--${boundary}\r\n`;
|
|
|
|
|
+ formData += `Content-Disposition: form-data; name="file"; filename="${baiduPath.split('/').pop()}"\r\n`;
|
|
|
|
|
+ formData += `Content-Type: application/octet-stream\r\n\r\n`;
|
|
|
|
|
+
|
|
|
|
|
+ // 编码头部
|
|
|
|
|
+ const encoder = new util.TextEncoder();
|
|
|
|
|
+ const headerBytes = encoder.encode(formData);
|
|
|
|
|
+
|
|
|
|
|
+ // 构建结尾
|
|
|
|
|
+ const footerBytes = encoder.encode(`\r\n--${boundary}--\r\n`);
|
|
|
|
|
+
|
|
|
|
|
+ // 创建完整的请求体
|
|
|
|
|
+ const totalSize = headerBytes.length + uint8Array.length + footerBytes.length;
|
|
|
|
|
+ const totalBuffer = new ArrayBuffer(totalSize);
|
|
|
|
|
+ const totalUint8Array = new Uint8Array(totalBuffer);
|
|
|
|
|
+
|
|
|
|
|
+ // 组合数据:头部 + 文件数据 + 结尾
|
|
|
|
|
+ totalUint8Array.set(headerBytes, 0);
|
|
|
|
|
+ totalUint8Array.set(uint8Array, headerBytes.length);
|
|
|
|
|
+ totalUint8Array.set(footerBytes, headerBytes.length + uint8Array.length);
|
|
|
|
|
+
|
|
|
|
|
+ interface UploadRequestHeader {
|
|
|
|
|
+ 'Content-Type': string;
|
|
|
|
|
+ 'Content-Length': string;
|
|
|
|
|
+ 'User-Agent': string;
|
|
|
|
|
+ 'Referer': string;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ const header: UploadRequestHeader = {
|
|
|
|
|
+ 'Content-Type': `multipart/form-data; boundary=${boundary}`,
|
|
|
|
|
+ 'Content-Length': totalSize.toString(),
|
|
|
|
|
+ 'User-Agent': 'netdisk;P2SP;2.2.60.26',
|
|
|
|
|
+ 'Referer': 'https://pan.baidu.com/disk/home'
|
|
|
|
|
+ };
|
|
|
|
|
+
|
|
|
|
|
+ const httpRequest = http.createHttp();
|
|
|
|
|
+
|
|
|
|
|
+ // 设置更长的超时时间,因为大文件上传可能需要更多时间
|
|
|
|
|
+ interface HttpRequestOptions {
|
|
|
|
|
+ method: http.RequestMethod;
|
|
|
|
|
+ header: UploadRequestHeader;
|
|
|
|
|
+ extraData: ArrayBuffer;
|
|
|
|
|
+ readTimeout: number;
|
|
|
|
|
+ connectTimeout: number;
|
|
|
|
|
+ expectDataType: http.HttpDataType;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ const options: HttpRequestOptions = {
|
|
|
|
|
+ method: http.RequestMethod.POST,
|
|
|
|
|
+ header: header,
|
|
|
|
|
+ extraData: totalBuffer,
|
|
|
|
|
+ readTimeout: 60000, // 60秒读取超时
|
|
|
|
|
+ connectTimeout: 30000, // 30秒连接超时
|
|
|
|
|
+ expectDataType: http.HttpDataType.STRING
|
|
|
|
|
+ };
|
|
|
|
|
+
|
|
|
|
|
+ const response = await httpRequest.request(requestUrl, options);
|
|
|
|
|
+ httpRequest.destroy();
|
|
|
|
|
+
|
|
|
|
|
+ if (response.responseCode === 200) {
|
|
|
|
|
+ // 检查响应内容是否包含错误
|
|
|
|
|
+ if (response.result && typeof response.result === 'string') {
|
|
|
|
|
+ try {
|
|
|
|
|
+ const resultText = response.result as string;
|
|
|
|
|
+ if (resultText && resultText.length > 0) {
|
|
|
|
|
+ const parsedResult = JSON.parse(resultText);
|
|
|
|
|
+ if (parsedResult && typeof parsedResult === 'object' && parsedResult !== null) {
|
|
|
|
|
+ const resultObj = parsedResult as Record<string, number | string | Object>;
|
|
|
|
|
+ const errnoValue = resultObj.errno;
|
|
|
|
|
+ if (errnoValue !== undefined && typeof errnoValue === 'number' && errnoValue !== 0) {
|
|
|
|
|
+ throw new Error(`上传分片${partSeq}失败,错误码: ${errnoValue}`);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ } catch (parseError) {
|
|
|
|
|
+ // 如果不是JSON格式,但返回200,也认为成功
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ } else {
|
|
|
|
|
+ // 提供更详细的错误信息
|
|
|
|
|
+ let errorMessage = `上传分片${partSeq}失败,HTTP状态码: ${response.responseCode}`;
|
|
|
|
|
+ if (response.result && typeof response.result === 'string') {
|
|
|
|
|
+ errorMessage += `,响应内容: ${response.result.substring(0, 200)}`;
|
|
|
|
|
+ }
|
|
|
|
|
+ throw new Error(errorMessage);
|
|
|
|
|
+ }
|
|
|
|
|
+ } catch (error) {
|
|
|
|
|
+ const err = error as Error;
|
|
|
|
|
+ throw new Error(`上传分片${partSeq}失败: ${err.message}`);
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+/**
|
|
|
|
|
+ * 创建文件(TaskPool版本)- 百度网盘上传第三阶段
|
|
|
|
|
+ * @param token 访问令牌
|
|
|
|
|
+ * @param request 创建文件请求参数
|
|
|
|
|
+ * @returns 创建文件响应
|
|
|
|
|
+ */
|
|
|
|
|
+export async function simpleCreateFile(token: string, request: TaskCreateFileRequest): Promise<TaskCreateFileResponse> {
|
|
|
|
|
+ try {
|
|
|
|
|
+ const url = 'https://pan.baidu.com/rest/2.0/xpan/file';
|
|
|
|
|
+ const params = `method=create&access_token=${token}`;
|
|
|
|
|
+ const requestUrl = `${url}?${params}`;
|
|
|
|
|
+
|
|
|
|
|
+ // 转换路径为百度网盘要求的格式
|
|
|
|
|
+ const baiduPath = convertToBaiduPath(request.path);
|
|
|
|
|
+
|
|
|
|
|
+ // 构建表单数据
|
|
|
|
|
+ const boundary = '----WebKitFormBoundary' + Math.random().toString(36).substr(2, 16);
|
|
|
|
|
+ let body = '';
|
|
|
|
|
+
|
|
|
|
|
+ body += `--${boundary}\r\n`;
|
|
|
|
|
+ body += `Content-Disposition: form-data; name="path"\r\n\r\n`;
|
|
|
|
|
+ body += `${baiduPath}\r\n`;
|
|
|
|
|
+
|
|
|
|
|
+ body += `--${boundary}\r\n`;
|
|
|
|
|
+ body += `Content-Disposition: form-data; name="size"\r\n\r\n`;
|
|
|
|
|
+ body += `${request.size}\r\n`;
|
|
|
|
|
+
|
|
|
|
|
+ body += `--${boundary}\r\n`;
|
|
|
|
|
+ body += `Content-Disposition: form-data; name="uploadid"\r\n\r\n`;
|
|
|
|
|
+ body += `${request.uploadid}\r\n`;
|
|
|
|
|
+
|
|
|
|
|
+ body += `--${boundary}\r\n`;
|
|
|
|
|
+ body += `Content-Disposition: form-data; name="block_list"\r\n\r\n`;
|
|
|
|
|
+ body += `${request.block_list}\r\n`;
|
|
|
|
|
+
|
|
|
|
|
+ body += `--${boundary}--\r\n`;
|
|
|
|
|
+
|
|
|
|
|
+ interface CreateFileRequestHeader {
|
|
|
|
|
+ 'Content-Type': string;
|
|
|
|
|
+ 'Content-Length': string;
|
|
|
|
|
+ 'User-Agent': string;
|
|
|
|
|
+ 'Referer': string;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ const header: CreateFileRequestHeader = {
|
|
|
|
|
+ 'Content-Type': `multipart/form-data; boundary=${boundary}`,
|
|
|
|
|
+ 'Content-Length': body.length.toString(),
|
|
|
|
|
+ 'User-Agent': 'netdisk;P2SP;2.2.60.26',
|
|
|
|
|
+ 'Referer': 'https://pan.baidu.com/disk/home'
|
|
|
|
|
+ };
|
|
|
|
|
+
|
|
|
|
|
+ const httpRequest = http.createHttp();
|
|
|
|
|
+ const response = await httpRequest.request(requestUrl, {
|
|
|
|
|
+ method: http.RequestMethod.POST,
|
|
|
|
|
+ header: header,
|
|
|
|
|
+ extraData: body,
|
|
|
|
|
+ expectDataType: http.HttpDataType.STRING
|
|
|
|
|
+ });
|
|
|
|
|
+ httpRequest.destroy();
|
|
|
|
|
+
|
|
|
|
|
+ if (response.responseCode === 200 && response.result) {
|
|
|
|
|
+ const resultText = response.result as string;
|
|
|
|
|
+ if (resultText && resultText.length > 0) {
|
|
|
|
|
+ const parsedResult = JSON.parse(resultText);
|
|
|
|
|
+ if (parsedResult && typeof parsedResult === 'object' && parsedResult !== null) {
|
|
|
|
|
+ const resultObj = parsedResult as Record<string, number | string | Object>;
|
|
|
|
|
+ const errnoValue = resultObj.errno;
|
|
|
|
|
+ if (errnoValue !== undefined && typeof errnoValue === 'number' && errnoValue !== 0) {
|
|
|
|
|
+ throw new Error(`创建文件失败,错误码: ${errnoValue}`);
|
|
|
|
|
+ }
|
|
|
|
|
+ return parsedResult as TaskCreateFileResponse;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ return JSON.parse(response.result as string) as TaskCreateFileResponse;
|
|
|
|
|
+ } else {
|
|
|
|
|
+ let errorMessage = `创建文件失败,HTTP状态码: ${response.responseCode}`;
|
|
|
|
|
+ if (response.result && typeof response.result === 'string') {
|
|
|
|
|
+ errorMessage += `,响应内容: ${response.result.substring(0, 200)}`;
|
|
|
|
|
+ }
|
|
|
|
|
+ throw new Error(errorMessage);
|
|
|
|
|
+ }
|
|
|
|
|
+ } catch (error) {
|
|
|
|
|
+ const err = error as Error;
|
|
|
|
|
+ throw new Error(`创建文件失败: ${err.message}`);
|
|
|
|
|
+ }
|
|
|
|
|
+}
|