fileUtil.ts 5.1 KB
import { getExcelData } from "./fileExport/export2Excel";
import { getWordData } from "./fileExport/word";
import { getPdfData } from "./fileExport/toPdf";

/**
 * 提取文件的扩展名
 * @param filename 原始文件名
 * @returns 文件扩展名(例如 ".jpg")
 */
function getFileExtension(filename: string): string {
  const lastDotIndex = filename.lastIndexOf(".");
  return lastDotIndex === -1 ? "" : filename.substring(lastDotIndex);
}

/**
 * 清理文件名,替换非字母数字字符为下划线并截断长度
 * @param filename 待处理的文件名(不含扩展名)
 * @param maxLength 文件名最大长度,默认50
 * @returns 清理后的文件名
 */
function cleanFileName(filename: string, maxLength: number = 50): string {
  const cleaned = filename
    .replace(/[^a-zA-Z0-9]/g, "_") // 非字母数字替换为下划线
    .replace(/_+/g, "_") // 合并连续下划线
    .replace(/^_+|_+$/g, "") // 去除首尾下划线
    .substring(0, maxLength); // 截断到指定长度
  return cleaned;
}

/**
 * 生成安全的随机字符串
 * @param length 字符串长度,默认8
 * @returns 随机字符串
 */
function generateRandomString(length: number = 8): string {
  const characters =
    "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
  let result = "";

  // 优先使用加密安全的随机数生成器
  if (typeof crypto !== "undefined" && crypto.getRandomValues) {
    const values = new Uint8Array(length);
    crypto.getRandomValues(values);
    for (let i = 0; i < length; i++) {
      result += characters[values[i] % characters.length];
    }
  } else {
    // 降级方案:使用 Math.random()
    for (let i = 0; i < length; i++) {
      result += characters[Math.floor(Math.random() * characters.length)];
    }
  }
  return result;
}

/**
 * 生成唯一文件名
 * @param originalFileName 原始文件名
 * @returns 格式为 [清理后的名称]_[时间戳]_[随机字符串].[扩展名]
 */
export function generateUniqueFileName(originalFileName: string): string {
  const ext = getFileExtension(originalFileName);
  const nameWithoutExt = originalFileName.slice(0, -ext.length);
  const cleanedName = cleanFileName(nameWithoutExt);
  const timestamp = Date.now().toString();
  const randomStr = generateRandomString(8);

  // 组合最终文件名
  const uniquePart = `${timestamp}_${randomStr}`;
  const newName = cleanedName ? `${cleanedName}_${uniquePart}` : uniquePart;
  return `${newName}${ext}`;
}

export async function getFileByUrl(url: string): Promise<File> {
  try {
    // 1. 发起网络请求获取图片数据
    const response = await fetch(url);
    if (!response.ok) {
      throw new Error(`HTTP error! Status: ${response.status}`);
    }
    // 2. 提取文件名(处理URL中的特殊字符)
    const parsedUrl = new URL(url);
    let filename = "image.png";
    // 移除文件名中的查询参数和哈希片段(如 "image.jpg?param=1#section" → "image.jpg")
    filename = filename.split(/[?#]/)[0];
    // 3. 获取 MIME 类型(优先使用响应头类型)
    const mimeType = response.headers.get("Content-Type") || "";
    // 4. 转换为 Blob 并生成 File 对象
    const blob = await response.blob();
    return new File([blob], filename, {
      type: mimeType || blob.type, // 双重保障类型
      lastModified: Date.now(),
    });
  } catch (error) {
    throw new Error("Failed to download image");
  }
}

export async function processChatFile(): Promise<{
  data: string;
  name: string;
}> {
  return new Promise((resolve, reject) => {
    // 创建隐藏的文件输入元素
    const fileInput = document.createElement("input");
    fileInput.type = "file";
    fileInput.accept = ".xlsx, .xls, .pdf, .docx, .doc";
    fileInput.multiple = false;
    fileInput.style.display = "none";

    // 文件类型与处理函数映射
    const fileHandlers: Record<string, (file: File) => Promise<any>> = {
      ".xlsx": getExcelData,
      ".xls": getExcelData,
      ".doc": getWordData,
      ".docx": getWordData,
      ".pdf": getPdfData,
    };

    fileInput.onchange = async (event: Event) => {
      try {
        const input = event.target as HTMLInputElement;
        const file = input.files?.[0];
        if (!file) throw new Error("No file selected");

        // 获取文件扩展名并校验类型
        const fileExt = file.name
          .toLowerCase()
          .slice(file.name.lastIndexOf("."));
        if (!Object.keys(fileHandlers).includes(fileExt)) {
          throw new Error("Unsupported file type");
        }

        // 处理文件内容
        const handler = fileHandlers[fileExt];
        const filedata = await handler(file);

        // 格式化返回数据
        const data = `'''filedata
                                ${file.name}
                                ${JSON.stringify(filedata)}
                                '''filedata`;
        resolve({ data: data, name: file.name });
      } catch (error) {
        reject(error instanceof Error ? error : new Error(String(error)));
      } finally {
        fileInput.remove();
      }
    };

    // 触发文件选择
    document.body.appendChild(fileInput);
    fileInput.click();
  });
}