fileUtil.ts
5.1 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
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();
});
}