Explorar el Código

百度网盘的上传文件的代码 目前有问题 就是分片0 第一次4M上传成功后 分片1上传 就返回文件已经存在

onecold hace 8 meses
padre
commit
81a0cd45d5

+ 277 - 0
entry/src/main/ets/common/network/BaiduPanClient.ets

@@ -356,6 +356,50 @@ export interface BaiduCreateFolderResponse {
   status?: number;
 }
 
+export interface BaiduPrecreateRequest {
+  path: string;
+  size: number;
+  isdir: number;
+  block_list: string;
+  autoinit: number;
+  rtype?: number;
+  uploadid?: string;
+  'content-md5'?: string;
+  'slice-md5'?: string;
+  local_ctime?: string;
+  local_mtime?: string;
+}
+
+export interface BaiduPrecreateResponse {
+  errno: number;
+  path?: string;
+  uploadid?: string;
+  return_type?: number;
+  block_list?: number[];
+  request_id?: number;
+}
+
+export interface BaiduUploadPartResponse {
+  errno: number;
+  md5?: string;
+  request_id?: number;
+}
+
+export interface BaiduUploadServerInfo {
+  server: string;
+}
+
+export interface BaiduUploadDomainResponse {
+  error_code: number;
+  error_msg?: string;
+  host?: string;
+  servers?: BaiduUploadServerInfo[];
+  quic_servers?: BaiduUploadServerInfo[];
+  bak_servers?: BaiduUploadServerInfo[];
+  request_id?: number;
+  expire?: number;
+}
+
 export async function createFolder(
   accessToken: string,
   path: string,
@@ -427,3 +471,236 @@ export async function createFolder(
     httpRequest.destroy();
   }
 }
+
+/**
+ * 获取百度网盘上传域名
+ */
+export async function getUploadDomain(
+  accessToken: string,
+  path: string,
+  uploadId: string
+): Promise<string> {
+  if (!accessToken) {
+    throw new Error('缺少百度网盘access_token');
+  }
+  if (!path) {
+    throw new Error('缺少文件路径');
+  }
+  if (!uploadId) {
+    throw new Error('缺少上传ID');
+  }
+
+  const payload = await httpGet('https://d.pcs.baidu.com/rest/2.0/pcs/file', [
+    { key: 'method', value: 'locateupload' },
+    { key: 'appid', value: 250528 },
+    { key: 'access_token', value: accessToken },
+    { key: 'path', value: path },
+    { key: 'uploadid', value: uploadId },
+    { key: 'upload_version', value: '2.0' }
+  ]);
+
+  const parsed = await parseJson<BaiduUploadDomainResponse>(payload);
+  if (parsed.error_code !== 0) {
+    throw new Error(`获取上传域名失败 error_code=${parsed.error_code}, error_msg=${parsed.error_msg}`);
+  }
+
+  // 优先使用HTTPS服务器
+  if (parsed.servers && parsed.servers.length > 0) {
+    for (let i = 0; i < parsed.servers.length; i++) {
+      const server = parsed.servers[i];
+      if (server.server && server.server.startsWith('https://')) {
+        return server.server;
+      }
+    }
+    // 如果没有HTTPS,返回第一个
+    if (parsed.servers[0].server) {
+      return parsed.servers[0].server;
+    }
+  }
+
+  throw new Error('未获取到可用的上传服务器');
+}
+
+/**
+ * 百度网盘预上传
+ */
+export async function precreateFile(
+  accessToken: string,
+  request: BaiduPrecreateRequest
+): Promise<BaiduPrecreateResponse> {
+  if (!accessToken) {
+    throw new Error('缺少百度网盘access_token');
+  }
+
+  const requestUrl = `${BaiduConstants.PAN_BASE}/rest/2.0/xpan/file?method=precreate&access_token=${accessToken}`;
+
+  // 构建请求体
+  const bodyParams: string[] = [
+    `path=${encodeURIComponent(request.path)}`,
+    `size=${request.size}`,
+    `isdir=${request.isdir}`,
+    `autoinit=${request.autoinit}`,
+    `block_list=${request.block_list}`
+  ];
+
+  if (request.rtype !== undefined) {
+    bodyParams.push(`rtype=${request.rtype}`);
+  }
+  if (request.uploadid) {
+    bodyParams.push(`uploadid=${request.uploadid}`);
+  }
+  // 注意:由于ArkTS限制,暂时不使用带连字符的属性访问
+  // 在实际使用中,这些属性是可选的,不影响基本功能
+
+  const body = bodyParams.join('&');
+  const httpRequest = http.createHttp();
+  const options: http.HttpRequestOptions = {
+    method: http.RequestMethod.POST,
+    connectTimeout: 10000,
+    readTimeout: 10000,
+    expectDataType: http.HttpDataType.STRING,
+    header: {
+      'User-Agent': BaiduConstants.USER_AGENT,
+      'Content-Type': 'application/x-www-form-urlencoded'
+    },
+    extraData: body
+  };
+
+  try {
+    Logger.info(TAG, `百度网盘预上传请求: ${requestUrl}`);
+    Logger.info(TAG, `百度网盘预上传参数: ${body}`);
+
+    const response = await httpRequest.request(requestUrl, options);
+    const payload = parseResponseBody(response);
+    Logger.info(TAG, `百度网盘预上传响应: ${payload}`);
+
+    const parsed = await parseJson<BaiduPrecreateResponse>(payload);
+    if (parsed.errno !== 0) {
+      let errorMessage = `预上传失败 errno=${parsed.errno}`;
+      switch (parsed.errno) {
+        case -7:
+          errorMessage = '文件或目录名错误或无权访问';
+          break;
+        case -10:
+          errorMessage = '云端容量不足';
+          break;
+        default:
+          errorMessage = `预上传失败 errno=${parsed.errno}`;
+      }
+      throw new Error(errorMessage);
+    }
+
+    if (!parsed.uploadid) {
+      throw new Error('预上传失败:未获取到uploadid');
+    }
+
+    return parsed;
+  } finally {
+    httpRequest.destroy();
+  }
+}
+
+/**
+ * 上传百度网盘文件分片
+ */
+export async function uploadFilePart(
+  uploadServer: string,
+  accessToken: string,
+  path: string,
+  uploadId: string,
+  partSeq: number,
+  fileData: ArrayBuffer
+): Promise<BaiduUploadPartResponse> {
+  if (!uploadServer) {
+    throw new Error('缺少上传服务器地址');
+  }
+  if (!accessToken) {
+    throw new Error('缺少百度网盘access_token');
+  }
+  if (!path) {
+    throw new Error('缺少文件路径');
+  }
+  if (!uploadId) {
+    throw new Error('缺少上传ID');
+  }
+  if (fileData.byteLength === 0) {
+    throw new Error('文件数据不能为空');
+  }
+
+  const requestUrl = `${uploadServer}/rest/2.0/pcs/superfile2?method=upload&access_token=${accessToken}&type=tmpfile&path=${encodeURIComponent(path)}&uploadid=${uploadId}&partseq=${partSeq}`;
+
+  const httpRequest = http.createHttp();
+  const options: http.HttpRequestOptions = {
+    method: http.RequestMethod.POST,
+    connectTimeout: 30000,
+    readTimeout: 60000,
+    expectDataType: http.HttpDataType.STRING,
+    header: {
+      'User-Agent': BaiduConstants.USER_AGENT,
+      'Content-Type': 'multipart/form-data'
+    },
+    extraData: fileData
+  };
+
+  try {
+    Logger.info(TAG, `百度网盘分片上传请求: ${requestUrl}`);
+    Logger.info(TAG, `分片序号: ${partSeq}, 数据大小: ${fileData.byteLength} 字节`);
+
+    const response = await httpRequest.request(requestUrl, options);
+    const payload = parseResponseBody(response);
+    Logger.info(TAG, `百度网盘分片上传响应: ${payload}`);
+
+    const parsed = await parseJson<BaiduUploadPartResponse>(payload);
+    if (parsed.errno !== 0) {
+      let errorMessage = `分片上传失败 errno=${parsed.errno}`;
+      switch (parsed.errno) {
+        case 31024:
+          errorMessage = '没有申请上传权限';
+          break;
+        case 31299:
+          errorMessage = '第一个分片的大小小于4MB';
+          break;
+        case 31364:
+          errorMessage = '超出分片大小限制';
+          break;
+        case 31363:
+          errorMessage = '分片缺失';
+          break;
+        default:
+          errorMessage = `分片上传失败 errno=${parsed.errno}`;
+      }
+      throw new Error(errorMessage);
+    }
+
+    return parsed;
+  } finally {
+    httpRequest.destroy();
+  }
+}
+
+/**
+ * MD5 计算函数(简化版本,实际项目中应使用更安全的实现)
+ */
+export async function calculateMD5(data: ArrayBuffer): Promise<string> {
+  // 由于ArkTS限制,返回一个基于数据内容的模拟MD5值
+  // 实际项目中需要集成真正的MD5库
+  if (!data || data.byteLength === 0) {
+    return 'd41d8cd98f00b204e9800998ecf8427e'; // 空数据的MD5
+  }
+
+  // 简化的哈希算法(仅用于演示,不是真正的MD5)
+  const dataArray = new Uint8Array(data);
+  let hash = 0;
+  for (let i = 0; i < dataArray.length; i++) {
+    const char = dataArray[i];
+    hash = ((hash << 5) - hash) + char;
+    hash = hash & 0xffffffff; // 转换为32位整数
+  }
+
+  // 转换为16进制字符串并补全到32位
+  let hashStr = Math.abs(hash).toString(16);
+  while (hashStr.length < 32) {
+    hashStr = '0' + hashStr;
+  }
+  return hashStr.substring(0, 32).toLowerCase();
+}

+ 44 - 5
entry/src/main/ets/common/util/FileManager.ets

@@ -117,18 +117,57 @@ class FileManagerClass {
     }
   }
 
-  // 读取文件内容为ArrayBuffer
+  // 读取文件内容为ArrayBuffer(优化版本,避免阻塞主线程)
   public async readFileToArrayBuffer(filePath: string): Promise<ArrayBuffer> {
     try {
       if (!await this.isExist(filePath)) {
         throw new Error(`文件不存在: ${filePath}`);
       }
-      const file = fileIo.openSync(filePath, fileIo.OpenMode.READ_ONLY);
+
+      // 对于大文件,分批读取以避免阻塞主线程
       const stat = await fileIo.stat(filePath);
-      const buffer = new ArrayBuffer(stat.size);
-      await fileIo.read(file.fd, buffer);
+      const fileSize = stat.size;
+
+      // 如果文件小于10MB,直接读取
+      if (fileSize <= 10 * 1024 * 1024) {
+        const file = fileIo.openSync(filePath, fileIo.OpenMode.READ_ONLY);
+        const buffer = new ArrayBuffer(fileSize);
+        await fileIo.read(file.fd, buffer);
+        fileIo.closeSync(file);
+        Logger.info(TAG, `读取文件成功: ${filePath}, 大小: ${fileSize}`);
+        return buffer;
+      }
+
+      // 对于大文件,分块读取
+      const CHUNK_SIZE = 2 * 1024 * 1024; // 2MB
+      const file = fileIo.openSync(filePath, fileIo.OpenMode.READ_ONLY);
+      const buffer = new ArrayBuffer(fileSize);
+      const uint8Array = new Uint8Array(buffer);
+      let offset = 0;
+
+      while (offset < fileSize) {
+        const chunkSize = Math.min(CHUNK_SIZE, fileSize - offset);
+        const chunkBuffer = new ArrayBuffer(chunkSize);
+
+        // 读取一块数据
+        await fileIo.read(file.fd, chunkBuffer);
+
+        // 复制到主buffer
+        const chunkUint8Array = new Uint8Array(chunkBuffer);
+        uint8Array.set(chunkUint8Array, offset);
+
+        offset += chunkSize;
+
+        // 每读取一块后让出控制权
+        if (offset < fileSize) {
+          await new Promise<void>(resolve => {
+            setTimeout(resolve, 0);
+          });
+        }
+      }
+
       fileIo.closeSync(file);
-      Logger.info(TAG, `读取文件成功: ${filePath}, 大小: ${stat.size}`);
+      Logger.info(TAG, `读取大文件成功: ${filePath}, 大小: ${fileSize}`);
       return buffer;
     } catch (err) {
       const error = err as Error;

+ 269 - 5
entry/src/main/ets/common/util/RemoteDriveManager.ets

@@ -23,11 +23,152 @@ import { WebDavUrlUtil } from './WebDavUrlUtil';
 import MediaTable from './MediaTable';
 import { FtpClient, FileInfo as FtpEntryInfo, StringEncoding, AccessOptions } from '@liuzhosoft/ftp4h';
 import { BaiduConstants } from '../constants/BaiduConstants';
-import { appendAccessTokenToDlink, BaiduListEntry, BaiduFileMeta, buildAudioStreamingUrl, createFolder, deleteFiles as deleteBaiduFiles, ensureAudioStreamReady, fetchFileMetas as fetchBaiduFileMetas, listDirectory as listBaiduDirectory, refreshAccessToken as refreshBaiduAccessToken } from '../network/BaiduPanClient';
+import { appendAccessTokenToDlink, BaiduListEntry, BaiduFileMeta, buildAudioStreamingUrl, calculateMD5, createFolder, deleteFiles as deleteBaiduFiles, ensureAudioStreamReady, fetchFileMetas as fetchBaiduFileMetas, getUploadDomain, listDirectory as listBaiduDirectory, precreateFile, refreshAccessToken as refreshBaiduAccessToken, uploadFilePart } from '../network/BaiduPanClient';
 import { ServerLogUtil } from './ServerLogUtil';
+import { taskpool } from '@kit.ArkTS';
+import { fileIo } from '@kit.CoreFileKit';
+import { util } from '@kit.ArkTS';
+import { http } from '@kit.NetworkKit';
+import { JSON } from '@kit.ArkTS';
+import { simpleCalculateMD5, simplePrecreateFile, simpleUploadFilePart, simpleCreateFile, TaskPrecreateRequest, TaskPrecreateResponse, TaskUploadPartResponse, TaskCreateFileRequest, TaskCreateFileResponse, convertToBaiduPath } from './TaskPoolHelper';
 
 const TAG = 'heanup RemoteDriveManager';
 
+// 百度网盘上传TaskPool任务接口(使用前缀避免名称冲突)
+interface TaskBaiduUploadParams {
+  filePath: string;
+  accessToken: string;
+  remotePath: string;
+  songName: string;
+  songId: string;
+}
+
+interface TaskBaiduUploadResult {
+  success: boolean;
+  error?: string;
+  filePath?: string;
+  remotePath?: string;
+  songId?: string;
+  uploadId?: string;
+  progress?: number;
+}
+
+/**
+ * 百度网盘文件上传TaskPool任务
+ * 注意:TaskPool任务函数不能访问外部变量,必须使用纯函数
+ */
+@Concurrent
+async function executeBaiduUpload(params: TaskBaiduUploadParams): Promise<TaskBaiduUploadResult> {
+  const filePath = params.filePath;
+  const accessToken = params.accessToken;
+  const remotePath = params.remotePath;
+  const songName = params.songName;
+  const songId = params.songId;
+
+  try {
+    // 1. 读取文件数据
+    const file = fileIo.openSync(filePath, fileIo.OpenMode.READ_ONLY);
+    const stat = await fileIo.stat(filePath);
+    const fileSize = stat.size;
+    const fileData = new ArrayBuffer(fileSize);
+    await fileIo.read(file.fd, fileData);
+    fileIo.closeSync(file);
+
+    if (fileSize === 0) {
+      throw new Error('文件大小为0');
+    }
+
+    // 2. 计算分片信息
+    const PART_SIZE = 4 * 1024 * 1024; // 4MB分片大小
+    const totalParts = Math.ceil(fileSize / PART_SIZE);
+
+    // 3. 计算分片MD5列表
+    const blockMd5List: string[] = [];
+    for (let i = 0; i < totalParts; i++) {
+      const start = i * PART_SIZE;
+      const end = Math.min(start + PART_SIZE, fileSize);
+      const partData = fileData.slice(start, end);
+      // 使用导入的MD5计算函数
+      const md5 = await simpleCalculateMD5(partData);
+      blockMd5List.push(md5);
+    }
+
+    // 4. 预上传(第一阶段)
+    const precreateRequest: TaskPrecreateRequest = {
+      path: remotePath,
+      size: fileSize,
+      isdir: 0,
+      block_list: JSON.stringify(blockMd5List),
+      autoinit: 1,
+      rtype: 1,
+      local_ctime: Math.floor(Date.now() / 1000).toString(),
+      local_mtime: Math.floor(Date.now() / 1000).toString(),
+      dupicate_check: 0 // 不检查重复文件
+    };
+
+    const precreateResponse = await simplePrecreateFile(accessToken, precreateRequest);
+    if (!precreateResponse.uploadid) {
+      throw new Error('预上传失败:未获取到uploadid');
+    }
+
+    // 5. 分片上传
+    const partsToUpload = precreateResponse.block_list || [];
+    Logger.info(`预上传返回block_list: ${JSON.stringify(partsToUpload)}, errno: ${precreateResponse.errno}`);
+
+    // 如果block_list为空,说明所有分片都已存在,直接跳过分片上传
+    if (partsToUpload.length === 0) {
+      Logger.info( `所有分片已存在,跳过分片上传阶段`);
+    } else {
+      Logger.info( `需要上传的分片列表: ${JSON.stringify(partsToUpload)}, 总计${partsToUpload.length}个分片`);
+
+      for (let i = 0; i < partsToUpload.length; i++) {
+        const partSeq = partsToUpload[i];
+        const start = partSeq * PART_SIZE;
+        const end = Math.min(start + PART_SIZE, fileSize);
+        const partData = fileData.slice(start, end);
+
+        Logger.info( `开始上传分片${partSeq}, 大小: ${partData.byteLength}字节`);
+        await simpleUploadFilePart(accessToken, remotePath, precreateResponse.uploadid!, partSeq, partData);
+        Logger.info(`分片${partSeq}上传完成`);
+      }
+    }
+
+    // 6. 创建文件(第三阶段)
+    const createFileRequest: TaskCreateFileRequest = {
+      path: remotePath,
+      size: fileSize,
+      uploadid: precreateResponse.uploadid!,
+      block_list: JSON.stringify(blockMd5List)
+    };
+
+    const createFileResponse = await simpleCreateFile(accessToken, createFileRequest);
+    if (!createFileResponse.path) {
+      throw new Error('创建文件失败:未获取到文件路径');
+    }
+
+    return {
+      success: true,
+      filePath: filePath,
+      remotePath: createFileResponse.path,
+      songId: songId,
+      uploadId: precreateResponse.uploadid,
+      progress: 100
+    };
+
+  } catch (error) {
+    const err = error as Error;
+    return {
+      success: false,
+      error: err.message,
+      filePath: filePath,
+      remotePath: remotePath,
+      songId: songId,
+      progress: 0
+    };
+  }
+}
+
+
 export interface BreadcrumbItem {
   label: string;
   path: string;
@@ -2302,7 +2443,12 @@ export class RemoteDriveManager {
     this.notifyObservers(RemoteDriveManagerStates.SetCurrentUploadTask);
 
     try {
-      await this.uploadSingleFile(task);
+      if(this.currentAccount.webType==RemoteDriveType.WebDav){
+        await this.uploadSingleFile(task);
+      }else if(this.currentAccount.webType==RemoteDriveType.Baidu){
+        await this.uploadBaiduFile(task);
+      }
+
       
       // 上传成功,移到完成队列
       Logger.info(TAG, `任务成功,移至完成队列: ${task.song.name}`);
@@ -2601,7 +2747,7 @@ export class RemoteDriveManager {
       const err = error as Error;
       const endTime = Date.now();
       const duration = ((endTime - startTime) / 1000).toFixed(2);
-      
+
       // 详细错误日志
       Logger.error(TAG, '========== 上传任务失败 ==========');
       Logger.error(TAG, `文件名: ${song.name}`);
@@ -2614,15 +2760,133 @@ export class RemoteDriveManager {
       Logger.error(TAG, `错误堆栈: ${err.stack || '无堆栈信息'}`);
       Logger.error(TAG, `重试次数: ${task.retryCount || 0}`);
       Logger.error(TAG, '====================================');
-      
+
       // 根据错误类型进行分类处理
       this.handleUploadError(err, task);
-      
+
+      // 重新抛出错误以便上层处理
+      throw err;
+    }
+  }
+
+  /**
+   * 上传百度网盘文件(使用TaskPool避免主线程阻塞)
+   * @param task 上传任务
+   */
+  private async uploadBaiduFile(task: TransferTask): Promise<void> {
+    const song = task.song;
+    const account = task.account;
+    const startTime = Date.now();
+
+    // 详细日志:百度网盘上传开始
+    Logger.info(TAG, '========== 百度网盘上传任务开始 ==========');
+    Logger.info(TAG, `文件名: ${song.name}`);
+    Logger.info(TAG, `文件路径: ${song.filePath}`);
+    Logger.info(TAG, `文件大小: ${song.videoSize || 0} 字节`);
+    Logger.info(TAG, `目标账户: ${account.name} (ID: ${account.id})`);
+    Logger.info(TAG, `重试次数: ${task.retryCount || 0}`);
+
+    try {
+      // 检查网络是否允许上传
+      Logger.info(TAG, '检查网络连接状态...');
+      const networkAllowed = await this.checkNetworkAllowed();
+      if (!networkAllowed) {
+        const errorMsg = '当前网络不允许上传,请连接WiFi或在设置中允许移动网络上传';
+        Logger.error(TAG, `网络检查失败: ${errorMsg}`);
+        throw new Error(errorMsg);
+      }
+      Logger.info(TAG, '网络检查通过');
+
+      this.notifyObservers(RemoteDriveManagerStates.UploadStart);
+
+      // 获取访问令牌
+      const accessToken = await this.ensureBaiduAccessToken(account);
+      if (!accessToken) {
+        throw new Error('百度网盘授权已过期,请重新登录');
+      }
+
+      // 构建上传路径
+      const uploadPath = task.customUploadPath || account.uploadFilePath || '/apps/ttmusic';
+      const normalizedPath = this.normalizeFullPath(uploadPath);
+      const fileName = song.fileName || song.name || `upload-${Date.now()}`;
+      const remotePath = `${normalizedPath === '/' ? '' : normalizedPath}/${fileName}`;
+
+      Logger.info(TAG, '---------- 百度网盘路径信息 ----------');
+      Logger.info(TAG, `上传基础路径: ${uploadPath}`);
+      Logger.info(TAG, `规范化后的路径: ${normalizedPath}`);
+      Logger.info(TAG, `文件名: ${fileName}`);
+      Logger.info(TAG, `最终远程路径: ${remotePath}`);
+      Logger.info(TAG, '------------------------------');
+
+      // 使用TaskPool执行上传任务
+      const taskId = `baidu-upload-${song.id || Date.now()}`;
+      Logger.info(TAG, '创建TaskPool上传任务...');
+
+    // 转换路径为百度网盘要求的格式
+    const baiduRemotePath = convertToBaiduPath(remotePath);
+    Logger.info(TAG, `原始路径: ${remotePath}, 转换后路径: ${baiduRemotePath}`);
+
+    const taskParams: TaskBaiduUploadParams = {
+      filePath: song.filePath,
+      accessToken: accessToken,
+      remotePath: baiduRemotePath,
+      songName: song.name,
+      songId: song.id || `file-${Date.now()}`
+    };
+
+    const uploadTask: taskpool.Task = new taskpool.Task(executeBaiduUpload, taskParams);
+
+      // 提交任务到TaskPool
+      Logger.info(TAG, '提交上传任务到TaskPool...');
+      const result = await taskpool.execute(uploadTask) as TaskBaiduUploadResult;
+
+      // 检查上传结果
+      if (!result.success) {
+        const errorMessage = result.error || '上传失败';
+        throw new Error(errorMessage);
+      }
+
+      // 上传成功
+      const endTime = Date.now();
+      const duration = ((endTime - startTime) / 1000).toFixed(2);
+
+      Logger.info(TAG, '========== 百度网盘上传任务成功 ==========');
+      Logger.info(TAG, `文件名: ${song.name}`);
+      Logger.info(TAG, `文件路径: ${result.filePath || song.filePath}`);
+      Logger.info(TAG, `目标路径: ${result.remotePath || remotePath}`);
+      Logger.info(TAG, `UploadId: ${result.uploadId || 'unknown'}`);
+      Logger.info(TAG, `耗时: ${duration} 秒`);
+      Logger.info(TAG, '====================================');
+
+      // 更新上传进度到100%
+      this.notifyObservers(RemoteDriveManagerStates.UploadProgress);
+
+    } catch (error) {
+      const err = error as Error;
+      const endTime = Date.now();
+      const duration = ((endTime - startTime) / 1000).toFixed(2);
+
+      // 详细错误日志
+      Logger.error(TAG, '========== 百度网盘上传任务失败 ==========');
+      Logger.error(TAG, `文件名: ${song.name}`);
+      Logger.error(TAG, `文件路径: ${song.filePath}`);
+      Logger.error(TAG, `目标账户: ${account.name} (ID: ${account.id})`);
+      Logger.error(TAG, `耗时: ${duration} 秒`);
+      Logger.error(TAG, `错误信息: ${err.message}`);
+      Logger.error(TAG, `错误堆栈: ${err.stack || '无堆栈信息'}`);
+      Logger.error(TAG, `重试次数: ${task.retryCount || 0}`);
+      Logger.error(TAG, '====================================');
+
+      // 根据错误类型进行分类处理
+      this.handleUploadError(err, task);
+
       // 重新抛出错误以便上层处理
       throw err;
     }
   }
+
   
+    
   /**
    * 获取错误类型
    * @param error 错误对象

+ 2 - 2
entry/src/main/ets/view/LocalMusic.ets

@@ -9249,7 +9249,7 @@ export struct LocalMusic {
       TransitionEffect.opacity(1),
       TransitionEffect.OPACITY
     ))
-    .visualEffect(!this.isMusicBGCover&&deviceInfo.sdkApiVersion>=20&&this.bgController?
+    .visualEffect(StrUtil.isEmpty(this.cover)&&deviceInfo.sdkApiVersion>=20&&this.bgController?
       new hdsEffect.HdsEffectBuilder()
         .shaderEffect({
           effectType: hdsEffect.EffectType.UV_BACKGROUND_FLOW_LIGHT,
@@ -9282,7 +9282,7 @@ export struct LocalMusic {
               duration: 500,
               curve: Curve.Sharp
             }, () => {
-              this.scaleValueImage = Math.min(1, Math.max(0.5, 1 - event.offsetY / 600));
+              this.scaleValueImage = Math.min(1, Math.max(0.4, 1 - event.offsetY / 520));
               console.info('onecold scaleValueImage:', this.scaleValueImage)
 
               this.scaleValueText =Math.min(1, Math.max(0.6, 1 - event.offsetY / 700));