@lynker-desktop/electron-sdk 0.0.9-alpha.55 → 0.0.9-alpha.57
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/esm/main/resource-cache.d.ts +41 -3
- package/esm/main/resource-cache.d.ts.map +1 -1
- package/esm/main/resource-cache.js +186 -89
- package/esm/main/resource-cache.js.map +1 -1
- package/main/resource-cache.d.ts +41 -3
- package/main/resource-cache.d.ts.map +1 -1
- package/main/resource-cache.js +186 -89
- package/main/resource-cache.js.map +1 -1
- package/package.json +6 -5
|
@@ -11,6 +11,39 @@ export interface ResourceCacheOptions {
|
|
|
11
11
|
/** 允许缓存的资源来源,支持null/数组/函数 */
|
|
12
12
|
allowedOrigins?: null | string[] | ((url: string) => boolean);
|
|
13
13
|
}
|
|
14
|
+
/**
|
|
15
|
+
* 下载进度回调函数类型
|
|
16
|
+
*/
|
|
17
|
+
export type DownloadProgressCallback = (progress: {
|
|
18
|
+
url: string;
|
|
19
|
+
downloaded: number;
|
|
20
|
+
total: number;
|
|
21
|
+
percentage: number;
|
|
22
|
+
speed: number;
|
|
23
|
+
}) => void;
|
|
24
|
+
/**
|
|
25
|
+
* 缓存进度回调函数类型
|
|
26
|
+
*/
|
|
27
|
+
export type CacheProgressCallback = (progress: {
|
|
28
|
+
current: number;
|
|
29
|
+
total: number;
|
|
30
|
+
url: string;
|
|
31
|
+
success: boolean;
|
|
32
|
+
result?: {
|
|
33
|
+
filePath?: string;
|
|
34
|
+
hostPath?: string;
|
|
35
|
+
mimeType?: string;
|
|
36
|
+
size?: number;
|
|
37
|
+
error?: string;
|
|
38
|
+
};
|
|
39
|
+
percentage: number;
|
|
40
|
+
downloadProgress?: {
|
|
41
|
+
downloaded: number;
|
|
42
|
+
total: number;
|
|
43
|
+
percentage: number;
|
|
44
|
+
speed: number;
|
|
45
|
+
};
|
|
46
|
+
}) => void;
|
|
14
47
|
/**
|
|
15
48
|
* 资源缓存类:拦截并缓存静态资源,提升加载性能
|
|
16
49
|
*/
|
|
@@ -72,9 +105,10 @@ export declare class ResourceCache {
|
|
|
72
105
|
* @param url 资源URL
|
|
73
106
|
* @param filePath 本地缓存路径
|
|
74
107
|
* @param redirectCount 当前重定向次数(内部使用)
|
|
108
|
+
* @param onProgress 下载进度回调函数(可选)
|
|
75
109
|
* @returns Promise<void> 下载完成或失败
|
|
76
110
|
*/
|
|
77
|
-
downloadResourceAsync(url: string, filePath: string, redirectCount?: number): Promise<void>;
|
|
111
|
+
downloadResourceAsync(url: string, filePath: string, redirectCount?: number, onProgress?: DownloadProgressCallback): Promise<void>;
|
|
78
112
|
/**
|
|
79
113
|
* 下载资源到本地缓存(同步版本,不返回 Promise,用于拦截器)
|
|
80
114
|
* @param url 资源URL
|
|
@@ -103,10 +137,12 @@ export declare class ResourceCache {
|
|
|
103
137
|
* 手动缓存指定 URL 的资源
|
|
104
138
|
* @param url 要缓存的资源 URL(支持普通 URL 和 base64 data URL)
|
|
105
139
|
* @param force 是否强制重新下载,即使缓存有效(默认 false)
|
|
140
|
+
* @param ignoreOrigin 是否忽略来源检查(默认 false)
|
|
141
|
+
* @param onDownloadProgress 下载进度回调函数(可选)
|
|
106
142
|
* @returns Promise<{ filePath: string, hostPath: string, mimeType: string, size: number }> 返回缓存文件路径、主机路径、MIME 类型和文件大小
|
|
107
143
|
* @throws 如果 URL 不匹配缓存规则或来源不允许,会抛出错误
|
|
108
144
|
*/
|
|
109
|
-
cacheUrl(url: string, force?: boolean, ignoreOrigin?: boolean): Promise<{
|
|
145
|
+
cacheUrl(url: string, force?: boolean, ignoreOrigin?: boolean, onDownloadProgress?: DownloadProgressCallback): Promise<{
|
|
110
146
|
filePath: string;
|
|
111
147
|
hostPath: string;
|
|
112
148
|
mimeType: string;
|
|
@@ -116,9 +152,11 @@ export declare class ResourceCache {
|
|
|
116
152
|
* 批量缓存多个 URL 的资源
|
|
117
153
|
* @param urls 要缓存的资源 URL 数组
|
|
118
154
|
* @param force 是否强制重新下载,即使缓存有效(默认 false)
|
|
155
|
+
* @param ignoreOrigin 是否忽略来源检查(默认 false)
|
|
156
|
+
* @param onProgress 进度回调函数(可选)
|
|
119
157
|
* @returns Promise<Array<{ url: string, success: boolean, filePath?: string, hostPath?: string, mimeType?: string, size?: number, error?: string }>> 返回每个 URL 的缓存结果
|
|
120
158
|
*/
|
|
121
|
-
addCacheUrls(urls: string[], force?: boolean, ignoreOrigin?: boolean): Promise<Array<{
|
|
159
|
+
addCacheUrls(urls: string[], force?: boolean, ignoreOrigin?: boolean, onProgress?: CacheProgressCallback): Promise<Array<{
|
|
122
160
|
url: string;
|
|
123
161
|
success: boolean;
|
|
124
162
|
filePath?: string;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"resource-cache.d.ts","sourceRoot":"","sources":["../../src/main/resource-cache.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"resource-cache.d.ts","sourceRoot":"","sources":["../../src/main/resource-cache.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,15 +1,10 @@
|
|
|
1
1
|
import fs from 'node:fs';
|
|
2
2
|
import path from 'node:path';
|
|
3
|
-
import http from 'node:http';
|
|
4
|
-
import https from 'node:https';
|
|
5
3
|
import md5 from 'md5';
|
|
6
|
-
import ipc__default from '@lynker-desktop/electron-ipc/main';
|
|
7
4
|
import mime from 'mime-types';
|
|
5
|
+
import { ipcMain } from 'electron';
|
|
6
|
+
import { DownloaderHelper } from 'node-downloader-helper';
|
|
8
7
|
|
|
9
|
-
/**
|
|
10
|
-
* HTTP 重定向状态码
|
|
11
|
-
*/
|
|
12
|
-
const REDIRECT_STATUS_CODES = [301, 302, 307, 308];
|
|
13
8
|
/**
|
|
14
9
|
* 默认文件扩展名
|
|
15
10
|
*/
|
|
@@ -76,14 +71,30 @@ class ResourceCache {
|
|
|
76
71
|
this._cleanOldCache().catch(err => {
|
|
77
72
|
console.log('初始化时清理过期缓存失败:', err);
|
|
78
73
|
});
|
|
79
|
-
|
|
74
|
+
ipcMain.handle('core:cache', async (event, options) => {
|
|
80
75
|
try {
|
|
81
76
|
switch (options.method) {
|
|
82
77
|
case 'clear':
|
|
83
78
|
return await this.clearCache();
|
|
84
79
|
case 'add': {
|
|
80
|
+
const { id } = options;
|
|
81
|
+
const sender = event.sender;
|
|
85
82
|
const urls = Array.isArray(options.urls) ? options.urls : options.urls ? [options.urls] : [];
|
|
86
|
-
const data = await this.addCacheUrls(urls, options.force ?? false, true)
|
|
83
|
+
const data = await this.addCacheUrls(urls, options.force ?? false, true, (data) => {
|
|
84
|
+
try {
|
|
85
|
+
if (sender) {
|
|
86
|
+
if (typeof sender?.send === 'function') {
|
|
87
|
+
sender?.send?.(`core:cache:progress`, {
|
|
88
|
+
id,
|
|
89
|
+
data
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
catch (error) {
|
|
95
|
+
console.log('发送缓存进度回调失败:', error);
|
|
96
|
+
}
|
|
97
|
+
});
|
|
87
98
|
return Array.isArray(options.urls) ? data : data[0];
|
|
88
99
|
}
|
|
89
100
|
case 'delete': {
|
|
@@ -243,9 +254,10 @@ class ResourceCache {
|
|
|
243
254
|
* @param url 资源URL
|
|
244
255
|
* @param filePath 本地缓存路径
|
|
245
256
|
* @param redirectCount 当前重定向次数(内部使用)
|
|
257
|
+
* @param onProgress 下载进度回调函数(可选)
|
|
246
258
|
* @returns Promise<void> 下载完成或失败
|
|
247
259
|
*/
|
|
248
|
-
downloadResourceAsync(url, filePath, redirectCount = 0) {
|
|
260
|
+
downloadResourceAsync(url, filePath, redirectCount = 0, onProgress) {
|
|
249
261
|
// 检查是否正在下载,避免重复下载
|
|
250
262
|
if (this._downloadingUrls.has(url)) {
|
|
251
263
|
return Promise.reject(new Error(`资源正在下载中: ${url}`));
|
|
@@ -257,77 +269,81 @@ class ResourceCache {
|
|
|
257
269
|
this._downloadingUrls.add(url);
|
|
258
270
|
return new Promise((resolve, reject) => {
|
|
259
271
|
const tempFilePath = `${filePath}.cache`;
|
|
260
|
-
const
|
|
261
|
-
const
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
272
|
+
const downloadDir = path.dirname(tempFilePath);
|
|
273
|
+
const downloadFileName = path.basename(tempFilePath);
|
|
274
|
+
// 确保目录存在
|
|
275
|
+
if (!fs.existsSync(downloadDir)) {
|
|
276
|
+
fs.mkdirSync(downloadDir, { recursive: true });
|
|
277
|
+
}
|
|
278
|
+
// 创建下载器实例
|
|
279
|
+
const dl = new DownloaderHelper(url, downloadDir, {
|
|
280
|
+
fileName: downloadFileName,
|
|
281
|
+
retry: { maxRetries: 3, delay: 1000 },
|
|
282
|
+
// 超时设置:10 分钟(600000 毫秒)
|
|
283
|
+
// 对于视频等大文件,需要更长的超时时间
|
|
284
|
+
// 如果 10 分钟内没有任何数据传输,才会超时
|
|
285
|
+
timeout: 600000,
|
|
286
|
+
override: true, // 覆盖已存在的文件
|
|
287
|
+
httpRequestOptions: {
|
|
288
|
+
// 允许重定向
|
|
289
|
+
followRedirect: true,
|
|
290
|
+
maxRedirects: MAX_REDIRECTS
|
|
267
291
|
}
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
const location = res.headers.location;
|
|
281
|
-
if (location) {
|
|
282
|
-
request.destroy();
|
|
283
|
-
file.close(() => {
|
|
284
|
-
// 异步删除临时文件,不阻塞
|
|
285
|
-
fs.promises.unlink(tempFilePath).catch(() => {
|
|
286
|
-
// 忽略删除失败
|
|
287
|
-
});
|
|
292
|
+
});
|
|
293
|
+
// 监听下载进度
|
|
294
|
+
if (onProgress) {
|
|
295
|
+
dl.on('progress', (stats) => {
|
|
296
|
+
try {
|
|
297
|
+
// stats 包含: progress (百分比), downloaded (已下载字节), total (总字节), speed (速度)
|
|
298
|
+
onProgress({
|
|
299
|
+
url,
|
|
300
|
+
downloaded: stats.downloaded || 0,
|
|
301
|
+
total: stats.total || 0,
|
|
302
|
+
percentage: stats.progress !== undefined ? Math.round(stats.progress) : -1,
|
|
303
|
+
speed: stats.speed || 0
|
|
288
304
|
});
|
|
289
|
-
this._downloadingUrls.delete(url);
|
|
290
|
-
// 递归处理重定向,增加重定向计数
|
|
291
|
-
this.downloadResourceAsync(location, filePath, redirectCount + 1).then(resolve).catch(reject);
|
|
292
|
-
return;
|
|
293
305
|
}
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
if (
|
|
306
|
-
|
|
306
|
+
catch (error) {
|
|
307
|
+
// 忽略回调中的错误,避免影响下载
|
|
308
|
+
console.error('进度回调执行失败:', error);
|
|
309
|
+
}
|
|
310
|
+
});
|
|
311
|
+
}
|
|
312
|
+
// 监听下载完成
|
|
313
|
+
dl.on('end', () => {
|
|
314
|
+
this._downloadingUrls.delete(url);
|
|
315
|
+
// 将临时文件重命名为最终文件
|
|
316
|
+
fs.rename(tempFilePath, filePath, (renameErr) => {
|
|
317
|
+
if (renameErr) {
|
|
318
|
+
reject(new Error(`缓存文件重命名失败 from ${tempFilePath} to ${filePath}: ${renameErr.message}`));
|
|
319
|
+
}
|
|
320
|
+
else {
|
|
321
|
+
resolve();
|
|
307
322
|
}
|
|
308
|
-
fs.rename(tempFilePath, filePath, (renameErr) => {
|
|
309
|
-
this._downloadingUrls.delete(url);
|
|
310
|
-
if (renameErr) {
|
|
311
|
-
cleanupAndAbort(`缓存文件重命名失败 from ${tempFilePath} to ${filePath}`, renameErr);
|
|
312
|
-
}
|
|
313
|
-
else {
|
|
314
|
-
resolve();
|
|
315
|
-
}
|
|
316
|
-
});
|
|
317
323
|
});
|
|
318
324
|
});
|
|
319
|
-
|
|
320
|
-
|
|
325
|
+
// 监听下载错误
|
|
326
|
+
dl.on('error', (err) => {
|
|
327
|
+
this._downloadingUrls.delete(url);
|
|
328
|
+
// 清理临时文件
|
|
329
|
+
fs.promises.unlink(tempFilePath).catch(() => {
|
|
330
|
+
// 忽略删除失败
|
|
331
|
+
});
|
|
332
|
+
reject(err instanceof Error ? err : new Error(`下载失败: ${url}`));
|
|
321
333
|
});
|
|
322
|
-
|
|
323
|
-
|
|
334
|
+
// 监听下载停止(取消)
|
|
335
|
+
dl.on('stop', () => {
|
|
336
|
+
this._downloadingUrls.delete(url);
|
|
337
|
+
// 清理临时文件
|
|
338
|
+
fs.promises.unlink(tempFilePath).catch(() => {
|
|
339
|
+
// 忽略删除失败
|
|
340
|
+
});
|
|
341
|
+
reject(new Error(`下载已停止: ${url}`));
|
|
324
342
|
});
|
|
325
|
-
//
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
request.on('close', () => {
|
|
330
|
-
clearTimeout(timeout);
|
|
343
|
+
// 开始下载
|
|
344
|
+
dl.start().catch((err) => {
|
|
345
|
+
this._downloadingUrls.delete(url);
|
|
346
|
+
reject(err instanceof Error ? err : new Error(`启动下载失败: ${url}`));
|
|
331
347
|
});
|
|
332
348
|
});
|
|
333
349
|
}
|
|
@@ -418,10 +434,12 @@ class ResourceCache {
|
|
|
418
434
|
* 手动缓存指定 URL 的资源
|
|
419
435
|
* @param url 要缓存的资源 URL(支持普通 URL 和 base64 data URL)
|
|
420
436
|
* @param force 是否强制重新下载,即使缓存有效(默认 false)
|
|
437
|
+
* @param ignoreOrigin 是否忽略来源检查(默认 false)
|
|
438
|
+
* @param onDownloadProgress 下载进度回调函数(可选)
|
|
421
439
|
* @returns Promise<{ filePath: string, hostPath: string, mimeType: string, size: number }> 返回缓存文件路径、主机路径、MIME 类型和文件大小
|
|
422
440
|
* @throws 如果 URL 不匹配缓存规则或来源不允许,会抛出错误
|
|
423
441
|
*/
|
|
424
|
-
async cacheUrl(url, force = false, ignoreOrigin = false) {
|
|
442
|
+
async cacheUrl(url, force = false, ignoreOrigin = false, onDownloadProgress) {
|
|
425
443
|
// 检查是否是 base64 data URL
|
|
426
444
|
const base64Info = this._isBase64DataUrl(url);
|
|
427
445
|
if (base64Info.isBase64) {
|
|
@@ -452,7 +470,7 @@ class ResourceCache {
|
|
|
452
470
|
};
|
|
453
471
|
}
|
|
454
472
|
// 处理普通 URL
|
|
455
|
-
const shouldCache = this._getMatchFunction();
|
|
473
|
+
const shouldCache = ignoreOrigin ? () => true : this._getMatchFunction();
|
|
456
474
|
const isAllowedOrigin = ignoreOrigin ? () => true : this._getOriginAllowFunction();
|
|
457
475
|
// 检查是否匹配缓存规则
|
|
458
476
|
if (!shouldCache(url)) {
|
|
@@ -477,7 +495,7 @@ class ResourceCache {
|
|
|
477
495
|
};
|
|
478
496
|
}
|
|
479
497
|
// 下载资源
|
|
480
|
-
await this.downloadResourceAsync(url, cachePath.filePath);
|
|
498
|
+
await this.downloadResourceAsync(url, cachePath.filePath, 0, onDownloadProgress);
|
|
481
499
|
// 获取文件大小
|
|
482
500
|
const stats = await fs.promises.stat(cachePath.filePath);
|
|
483
501
|
return {
|
|
@@ -490,30 +508,109 @@ class ResourceCache {
|
|
|
490
508
|
* 批量缓存多个 URL 的资源
|
|
491
509
|
* @param urls 要缓存的资源 URL 数组
|
|
492
510
|
* @param force 是否强制重新下载,即使缓存有效(默认 false)
|
|
511
|
+
* @param ignoreOrigin 是否忽略来源检查(默认 false)
|
|
512
|
+
* @param onProgress 进度回调函数(可选)
|
|
493
513
|
* @returns Promise<Array<{ url: string, success: boolean, filePath?: string, hostPath?: string, mimeType?: string, size?: number, error?: string }>> 返回每个 URL 的缓存结果
|
|
494
514
|
*/
|
|
495
|
-
async addCacheUrls(urls, force = false, ignoreOrigin = false) {
|
|
496
|
-
const
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
515
|
+
async addCacheUrls(urls, force = false, ignoreOrigin = false, onProgress) {
|
|
516
|
+
const total = urls.length;
|
|
517
|
+
const results = [];
|
|
518
|
+
let completed = 0;
|
|
519
|
+
// 存储每个 URL 的下载进度
|
|
520
|
+
const downloadProgressMap = new Map();
|
|
521
|
+
// 进度回调的包装函数,确保线程安全
|
|
522
|
+
const reportProgress = (url, success, result) => {
|
|
523
|
+
if (onProgress) {
|
|
524
|
+
completed++;
|
|
525
|
+
const downloadProgress = downloadProgressMap.get(url);
|
|
526
|
+
onProgress({
|
|
527
|
+
current: completed,
|
|
528
|
+
total,
|
|
529
|
+
url,
|
|
530
|
+
success,
|
|
531
|
+
result,
|
|
532
|
+
percentage: Math.round((completed / total) * 100),
|
|
533
|
+
downloadProgress: downloadProgress ? {
|
|
534
|
+
downloaded: downloadProgress.downloaded,
|
|
535
|
+
total: downloadProgress.total,
|
|
536
|
+
percentage: downloadProgress.percentage,
|
|
537
|
+
speed: downloadProgress.speed
|
|
538
|
+
} : undefined
|
|
539
|
+
});
|
|
540
|
+
}
|
|
541
|
+
};
|
|
542
|
+
// 并行处理所有 URL,但跟踪每个的完成状态
|
|
543
|
+
const promises = urls.map(async (url, index) => {
|
|
544
|
+
// 为每个 URL 创建下载进度回调
|
|
545
|
+
const onDownloadProgress = onProgress
|
|
546
|
+
? (progress) => {
|
|
547
|
+
// 更新下载进度
|
|
548
|
+
downloadProgressMap.set(url, {
|
|
549
|
+
downloaded: progress.downloaded,
|
|
550
|
+
total: progress.total,
|
|
551
|
+
percentage: progress.percentage,
|
|
552
|
+
speed: progress.speed
|
|
553
|
+
});
|
|
554
|
+
// 实时报告下载进度(不增加 completed 计数)
|
|
555
|
+
if (onProgress) {
|
|
556
|
+
onProgress({
|
|
557
|
+
current: completed,
|
|
558
|
+
total,
|
|
559
|
+
url,
|
|
560
|
+
success: true, // 下载中视为进行中
|
|
561
|
+
result: undefined,
|
|
562
|
+
percentage: Math.round((completed / total) * 100),
|
|
563
|
+
downloadProgress: {
|
|
564
|
+
downloaded: progress.downloaded,
|
|
565
|
+
total: progress.total,
|
|
566
|
+
percentage: progress.percentage,
|
|
567
|
+
speed: progress.speed
|
|
568
|
+
}
|
|
569
|
+
});
|
|
570
|
+
}
|
|
571
|
+
}
|
|
572
|
+
: undefined;
|
|
573
|
+
try {
|
|
574
|
+
const result = await this.cacheUrl(url, force, ignoreOrigin, onDownloadProgress);
|
|
575
|
+
const item = {
|
|
501
576
|
url,
|
|
502
577
|
success: true,
|
|
503
|
-
filePath: result.
|
|
504
|
-
hostPath: result.
|
|
505
|
-
mimeType: result.
|
|
506
|
-
size: result.
|
|
578
|
+
filePath: result.filePath,
|
|
579
|
+
hostPath: result.hostPath,
|
|
580
|
+
mimeType: result.mimeType,
|
|
581
|
+
size: result.size
|
|
507
582
|
};
|
|
583
|
+
results[index] = item;
|
|
584
|
+
// 清除下载进度(已完成)
|
|
585
|
+
downloadProgressMap.delete(url);
|
|
586
|
+
// 调用进度回调
|
|
587
|
+
reportProgress(url, true, {
|
|
588
|
+
filePath: result.filePath,
|
|
589
|
+
hostPath: result.hostPath,
|
|
590
|
+
mimeType: result.mimeType,
|
|
591
|
+
size: result.size
|
|
592
|
+
});
|
|
593
|
+
return item;
|
|
508
594
|
}
|
|
509
|
-
|
|
510
|
-
|
|
595
|
+
catch (error) {
|
|
596
|
+
const item = {
|
|
511
597
|
url,
|
|
512
598
|
success: false,
|
|
513
|
-
error:
|
|
599
|
+
error: error instanceof Error ? error.message : '未知错误'
|
|
514
600
|
};
|
|
601
|
+
results[index] = item;
|
|
602
|
+
// 清除下载进度(失败)
|
|
603
|
+
downloadProgressMap.delete(url);
|
|
604
|
+
// 调用进度回调
|
|
605
|
+
reportProgress(url, false, {
|
|
606
|
+
error: item.error
|
|
607
|
+
});
|
|
608
|
+
return item;
|
|
515
609
|
}
|
|
516
610
|
});
|
|
611
|
+
// 等待所有请求完成
|
|
612
|
+
await Promise.allSettled(promises);
|
|
613
|
+
return results;
|
|
517
614
|
}
|
|
518
615
|
/**
|
|
519
616
|
* 删除多个 URL 的资源
|