Skip to content

Commit bea6f8f

Browse files
refactor(music-cache): 模糊匹配时按音质优先级自动选择最佳缓存 (#1009)
* Add environment variables for web and API configuration * Update API URL to use HTTPS * Update API URL in .env file * feat(electron): 优化音乐缓存服务的音质优先级和MD5验证机制 - 添加音质优先级映射表,支持多种音质等级排序 - 实现缓存元数据文件管理,存储MD5和文件大小信息 - 新增getMetaPath、readMeta、writeMeta等元数据操作方法 - 实现pickCandidates方法按音质优先级和修改时间选择候选缓存 - 重构hasCache方法支持按音质优先级自动选择最佳缓存 - 改进MD5验证逻辑,添加元数据缓存减少重复计算 - 优化下载完成后的元数据写入流程 * config: 移除环境变量配置文件 * fix(cache): 解决音乐缓存服务异常处理和元数据写入问题 - 添加了精确缓存检查和获取候选缓存的错误捕获及警告日志 - 将元数据写入改为异步执行避免阻塞主流程 - 添加了元数据写入失败的错误处理和警告日志 - 保持了文件重命名和缓存大小记录功能的正常执行 * Update electron/main/services/MusicCacheService.ts Co-authored-by: MoYingJi <moyingjiaw@outlook.com> * refactor(music-cache): 优化音乐缓存元数据存储机制 - 移除文件系统元数据文件读写,改用缓存服务存储 - 删除不再使用的 getMetaPath 方法 - 更新 readMeta 方法以从缓存中读取元数据 - 修改 writeMeta 方法将元数据写入缓存服务 - 调整 removeCacheWithMeta 方法使用缓存服务删除元数据 - 简化元数据键名生成逻辑 * fix(cache): 修复音质优先级匹配问题 - 将音质优先级映射中的键值从大写改为小写 - 修改getQualityWeight方法以支持大小写不敏感的音质匹配 - 确保音质比较时统一转换为小写进行匹配 * refactor(music-cache): 移除私有缓存文件删除方法并内联实现 - 删除了 private removeCacheFile 方法 - 将文件删除逻辑直接内联到调用处 - 保持相同的错误处理行为 * docs: 补充代码注释 * refactor(music): 简化音质优先级配置 - 移除注释中的枚举值兼容性说明 - 统一音质键名的字符串格式 - 删除冗余的音质选项如lossless、flac、exhigh等 - 保留核心音质等级配置 - 优化对象结构提高可读性 --------- Co-authored-by: MoYingJi <moyingjiaw@outlook.com>
1 parent 159f2e4 commit bea6f8f

1 file changed

Lines changed: 96 additions & 33 deletions

File tree

electron/main/services/MusicCacheService.ts

Lines changed: 96 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,19 @@ export class MusicCacheService {
1313
private static instance: MusicCacheService;
1414
private cacheService: CacheService;
1515
private downloadingTasks: Map<string, Promise<string>> = new Map();
16+
/** 音质优先级 */
17+
private readonly qualityPriority: Record<string, number> = {
18+
"master": 100,
19+
"dolby": 95,
20+
"spatial": 90,
21+
"surround": 85,
22+
"hi-res": 80,
23+
"sq": 70,
24+
"hq": 60,
25+
"mq": 50,
26+
"lq": 40,
27+
"standard": 40,
28+
};
1629

1730
private constructor() {
1831
this.cacheService = CacheService.getInstance();
@@ -34,6 +47,54 @@ export class MusicCacheService {
3447
return `${id}_${quality}.sc`;
3548
}
3649

50+
/**
51+
* 从缓存文件名中提取音质标识
52+
*/
53+
private getQualityFromKey(id: number | string, key: string): string | null {
54+
const prefix = `${id}_`;
55+
if (!key.startsWith(prefix) || !key.endsWith(".sc")) {
56+
return null;
57+
}
58+
return key.slice(prefix.length, -3);
59+
}
60+
61+
/**
62+
* 获取音质权重
63+
*/
64+
private getQualityWeight(quality: string): number {
65+
return this.qualityPriority[quality.toLowerCase()] ?? 0;
66+
}
67+
68+
/**
69+
* 按音质优先级和最近修改时间筛选候选缓存
70+
*/
71+
private async pickCandidates(
72+
id: number | string,
73+
): Promise<Array<{ filePath: string; quality: string }>> {
74+
const items = await this.cacheService.list("music");
75+
const result: Array<{ filePath: string; quality: string; weight: number; mtime: number }> = [];
76+
for (const item of items) {
77+
const quality = this.getQualityFromKey(id, item.key);
78+
if (!quality) {
79+
continue;
80+
}
81+
const filePath = this.cacheService.getFilePath("music", item.key);
82+
result.push({
83+
filePath,
84+
quality,
85+
weight: this.getQualityWeight(quality),
86+
mtime: item.mtime,
87+
});
88+
}
89+
result.sort((a, b) => {
90+
if (b.weight !== a.weight) {
91+
return b.weight - a.weight;
92+
}
93+
return b.mtime - a.mtime;
94+
});
95+
return result.map(({ filePath, quality }) => ({ filePath, quality }));
96+
}
97+
3798
/**
3899
* 计算文件 MD5
39100
*/
@@ -49,60 +110,62 @@ export class MusicCacheService {
49110

50111
/**
51112
* 检查缓存是否存在
52-
* 如果 quality 为 undefined,则返回任意一个匹配 id 的缓存(如果存在)
53-
* 如果提供了 expectedMD5,则会校验文件 MD5,不一致则删除缓存并返回 null
113+
* 指定 quality 时仅检查目标音质
114+
* 未指定 quality 时按音质优先级和最近修改时间选择候选缓存
115+
* 提供 expectedMD5 时会校验文件哈希,不一致则删除旧缓存
54116
*/
55117
public async hasCache(
56118
id: number | string,
57119
quality?: string,
58120
expectedMD5?: string,
59121
): Promise<string | null> {
60-
let filePath: string | null = null;
61-
62-
// 1. 精确查找:如果指定了音质,直接检查对应文件是否存在
122+
const candidates: Array<{ filePath: string; quality: string }> = [];
63123
if (quality) {
64124
const key = this.getCacheKey(id, quality);
65125
try {
66126
const p = this.cacheService.getFilePath("music", key);
67127
if (existsSync(p)) {
68-
filePath = p;
128+
candidates.push({ filePath: p, quality });
69129
}
70-
} catch {
71-
// ignore
130+
} catch (e) {
131+
cacheLog.warn(`[MusicCache] 检查精确缓存失败,ID: ${id}, 音质: ${quality}:`, e);
72132
}
73133
} else {
74-
// 2. 模糊查找:如果未指定音质,查找该 ID 下的任意缓存文件
75134
try {
76-
const items = await this.cacheService.list("music");
77-
// 查找以 id_ 开头且以 .sc 结尾的文件
78-
const prefix = `${id}_`;
79-
const match = items.find((item) => item.key.startsWith(prefix) && item.key.endsWith(".sc"));
80-
if (match) {
81-
filePath = this.cacheService.getFilePath("music", match.key);
82-
}
83-
} catch {
84-
// ignore
135+
candidates.push(...(await this.pickCandidates(id)));
136+
} catch (e) {
137+
cacheLog.warn(`[MusicCache] 获取候选缓存失败,ID: ${id}:`, e);
85138
}
86139
}
87140

88-
// 如果找到文件且需要校验 MD5
89-
if (filePath && expectedMD5) {
90-
try {
91-
const fileMD5 = await this.calculateMD5(filePath);
92-
if (fileMD5.toLowerCase() !== expectedMD5.toLowerCase()) {
93-
cacheLog.info(
94-
`[MusicCache] 缓存 MD5 不匹配,删除旧缓存。ID: ${id}, 期望: ${expectedMD5}, 实际: ${fileMD5}`,
95-
);
96-
await unlink(filePath).catch(() => {});
141+
for (const candidate of candidates) {
142+
const { filePath, quality: candidateQuality } = candidate;
143+
// 无需校验哈希时,命中即返回
144+
if (!expectedMD5) {
145+
return filePath;
146+
}
147+
try {
148+
const fileMD5 = await this.calculateMD5(filePath);
149+
if (fileMD5.toLowerCase() !== expectedMD5.toLowerCase()) {
150+
cacheLog.info(
151+
`[MusicCache] 缓存 MD5 不匹配,删除旧缓存。ID: ${id}, 音质: ${candidateQuality}, 期望: ${expectedMD5}, 实际: ${fileMD5}`,
152+
);
153+
await unlink(filePath).catch(() => {});
154+
if (quality) {
97155
return null;
98156
}
99-
} catch (error) {
100-
cacheLog.error(`[MusicCache] Failed to calculate MD5 for ${filePath}:`, error);
101-
return null;
102-
}
103-
}
157+
continue;
158+
}
159+
return filePath;
160+
} catch (error) {
161+
cacheLog.error(`[MusicCache] 校验缓存 MD5 失败,路径: ${filePath}:`, error);
162+
if (quality) {
163+
return null;
164+
}
165+
}
166+
}
104167

105-
return filePath;
168+
return null;
106169
}
107170

108171
/**

0 commit comments

Comments
 (0)