歌单
This commit is contained in:
@@ -358,6 +358,52 @@ export class D1Storage implements IStorage {
|
||||
}
|
||||
}
|
||||
|
||||
async batchSetMusicPlayRecords(userName: string, records: { key: string; record: any }[]): Promise<void> {
|
||||
if (records.length === 0) return;
|
||||
if (!this.db) return;
|
||||
|
||||
try {
|
||||
// 使用批量插入,D1 支持 batch 操作
|
||||
const statements = records.map(({ key, record }) =>
|
||||
this.db!
|
||||
.prepare(`
|
||||
INSERT INTO music_play_records (username, key, platform, song_id, name, artist, album, pic, play_time, duration, save_time)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||||
ON CONFLICT(username, key) DO UPDATE SET
|
||||
platform = excluded.platform,
|
||||
song_id = excluded.song_id,
|
||||
name = excluded.name,
|
||||
artist = excluded.artist,
|
||||
album = excluded.album,
|
||||
pic = excluded.pic,
|
||||
play_time = excluded.play_time,
|
||||
duration = excluded.duration,
|
||||
save_time = excluded.save_time
|
||||
`)
|
||||
.bind(
|
||||
userName,
|
||||
key,
|
||||
record.platform,
|
||||
record.id,
|
||||
record.name,
|
||||
record.artist,
|
||||
record.album || null,
|
||||
record.pic || null,
|
||||
record.play_time,
|
||||
record.duration,
|
||||
record.save_time
|
||||
)
|
||||
);
|
||||
|
||||
if (this.db.batch) {
|
||||
await this.db.batch(statements);
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('D1Storage.batchSetMusicPlayRecords error:', err);
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
|
||||
async getAllMusicPlayRecords(userName: string): Promise<{ [key: string]: any }> {
|
||||
try {
|
||||
const results = await this.db
|
||||
@@ -412,6 +458,261 @@ export class D1Storage implements IStorage {
|
||||
}
|
||||
}
|
||||
|
||||
// ==================== 音乐歌单相关 ====================
|
||||
|
||||
async createMusicPlaylist(userName: string, playlist: {
|
||||
id: string;
|
||||
name: string;
|
||||
description?: string;
|
||||
cover?: string;
|
||||
}): Promise<void> {
|
||||
try {
|
||||
const now = Date.now();
|
||||
await this.db
|
||||
.prepare(`
|
||||
INSERT INTO music_playlists (id, username, name, description, cover, created_at, updated_at)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?)
|
||||
`)
|
||||
.bind(
|
||||
playlist.id,
|
||||
userName,
|
||||
playlist.name,
|
||||
playlist.description || null,
|
||||
playlist.cover || null,
|
||||
now,
|
||||
now
|
||||
)
|
||||
.run();
|
||||
} catch (err) {
|
||||
console.error('D1Storage.createMusicPlaylist error:', err);
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
|
||||
async getMusicPlaylist(playlistId: string): Promise<any | null> {
|
||||
try {
|
||||
const result = await this.db
|
||||
.prepare('SELECT * FROM music_playlists WHERE id = ?')
|
||||
.bind(playlistId)
|
||||
.first();
|
||||
|
||||
if (!result) return null;
|
||||
|
||||
return {
|
||||
id: result.id,
|
||||
username: result.username,
|
||||
name: result.name,
|
||||
description: result.description || undefined,
|
||||
cover: result.cover || undefined,
|
||||
created_at: result.created_at,
|
||||
updated_at: result.updated_at,
|
||||
};
|
||||
} catch (err) {
|
||||
console.error('D1Storage.getMusicPlaylist error:', err);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
async getUserMusicPlaylists(userName: string): Promise<any[]> {
|
||||
try {
|
||||
const results = await this.db
|
||||
.prepare('SELECT * FROM music_playlists WHERE username = ? ORDER BY created_at DESC')
|
||||
.bind(userName)
|
||||
.all();
|
||||
|
||||
if (!results.results) return [];
|
||||
|
||||
return results.results.map((row) => ({
|
||||
id: row.id,
|
||||
username: row.username,
|
||||
name: row.name,
|
||||
description: row.description || undefined,
|
||||
cover: row.cover || undefined,
|
||||
created_at: row.created_at,
|
||||
updated_at: row.updated_at,
|
||||
}));
|
||||
} catch (err) {
|
||||
console.error('D1Storage.getUserMusicPlaylists error:', err);
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
async updateMusicPlaylist(playlistId: string, updates: {
|
||||
name?: string;
|
||||
description?: string;
|
||||
cover?: string;
|
||||
}): Promise<void> {
|
||||
try {
|
||||
const fields: string[] = [];
|
||||
const values: any[] = [];
|
||||
|
||||
if (updates.name !== undefined) {
|
||||
fields.push('name = ?');
|
||||
values.push(updates.name);
|
||||
}
|
||||
if (updates.description !== undefined) {
|
||||
fields.push('description = ?');
|
||||
values.push(updates.description || null);
|
||||
}
|
||||
if (updates.cover !== undefined) {
|
||||
fields.push('cover = ?');
|
||||
values.push(updates.cover || null);
|
||||
}
|
||||
|
||||
if (fields.length === 0) return;
|
||||
|
||||
fields.push('updated_at = ?');
|
||||
values.push(Date.now());
|
||||
values.push(playlistId);
|
||||
|
||||
await this.db
|
||||
.prepare(`UPDATE music_playlists SET ${fields.join(', ')} WHERE id = ?`)
|
||||
.bind(...values)
|
||||
.run();
|
||||
} catch (err) {
|
||||
console.error('D1Storage.updateMusicPlaylist error:', err);
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
|
||||
async deleteMusicPlaylist(playlistId: string): Promise<void> {
|
||||
try {
|
||||
// 由于设置了 ON DELETE CASCADE,删除歌单会自动删除关联的歌曲
|
||||
await this.db
|
||||
.prepare('DELETE FROM music_playlists WHERE id = ?')
|
||||
.bind(playlistId)
|
||||
.run();
|
||||
} catch (err) {
|
||||
console.error('D1Storage.deleteMusicPlaylist error:', err);
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
|
||||
async addSongToPlaylist(playlistId: string, song: {
|
||||
platform: string;
|
||||
id: string;
|
||||
name: string;
|
||||
artist: string;
|
||||
album?: string;
|
||||
pic?: string;
|
||||
duration: number;
|
||||
}): Promise<void> {
|
||||
try {
|
||||
const now = Date.now();
|
||||
|
||||
// 获取当前最大的 sort_order
|
||||
const maxOrderResult = await this.db
|
||||
.prepare('SELECT MAX(sort_order) as max_order FROM music_playlist_songs WHERE playlist_id = ?')
|
||||
.bind(playlistId)
|
||||
.first();
|
||||
|
||||
const nextOrder = (maxOrderResult?.max_order as number || 0) + 1;
|
||||
|
||||
await this.db
|
||||
.prepare(`
|
||||
INSERT INTO music_playlist_songs (
|
||||
playlist_id, platform, song_id, name, artist, album, pic, duration, added_at, sort_order
|
||||
)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||||
ON CONFLICT(playlist_id, platform, song_id) DO UPDATE SET
|
||||
name = excluded.name,
|
||||
artist = excluded.artist,
|
||||
album = excluded.album,
|
||||
pic = excluded.pic,
|
||||
duration = excluded.duration
|
||||
`)
|
||||
.bind(
|
||||
playlistId,
|
||||
song.platform,
|
||||
song.id,
|
||||
song.name,
|
||||
song.artist,
|
||||
song.album || null,
|
||||
song.pic || null,
|
||||
song.duration,
|
||||
now,
|
||||
nextOrder
|
||||
)
|
||||
.run();
|
||||
|
||||
// 更新歌单的 updated_at 和封面(如果是第一首歌)
|
||||
const songCount = await this.db
|
||||
.prepare('SELECT COUNT(*) as count FROM music_playlist_songs WHERE playlist_id = ?')
|
||||
.bind(playlistId)
|
||||
.first();
|
||||
|
||||
if ((songCount?.count as number) === 1 && song.pic) {
|
||||
await this.updateMusicPlaylist(playlistId, { cover: song.pic });
|
||||
} else {
|
||||
await this.db
|
||||
.prepare('UPDATE music_playlists SET updated_at = ? WHERE id = ?')
|
||||
.bind(Date.now(), playlistId)
|
||||
.run();
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('D1Storage.addSongToPlaylist error:', err);
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
|
||||
async removeSongFromPlaylist(playlistId: string, platform: string, songId: string): Promise<void> {
|
||||
try {
|
||||
await this.db
|
||||
.prepare('DELETE FROM music_playlist_songs WHERE playlist_id = ? AND platform = ? AND song_id = ?')
|
||||
.bind(playlistId, platform, songId)
|
||||
.run();
|
||||
|
||||
// 更新歌单的 updated_at
|
||||
await this.db
|
||||
.prepare('UPDATE music_playlists SET updated_at = ? WHERE id = ?')
|
||||
.bind(Date.now(), playlistId)
|
||||
.run();
|
||||
} catch (err) {
|
||||
console.error('D1Storage.removeSongFromPlaylist error:', err);
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
|
||||
async getPlaylistSongs(playlistId: string): Promise<any[]> {
|
||||
try {
|
||||
const results = await this.db
|
||||
.prepare('SELECT * FROM music_playlist_songs WHERE playlist_id = ? ORDER BY sort_order ASC')
|
||||
.bind(playlistId)
|
||||
.all();
|
||||
|
||||
if (!results.results) return [];
|
||||
|
||||
return results.results.map((row) => ({
|
||||
platform: row.platform,
|
||||
id: row.song_id,
|
||||
name: row.name,
|
||||
artist: row.artist,
|
||||
album: row.album || undefined,
|
||||
pic: row.pic || undefined,
|
||||
duration: row.duration,
|
||||
added_at: row.added_at,
|
||||
sort_order: row.sort_order,
|
||||
}));
|
||||
} catch (err) {
|
||||
console.error('D1Storage.getPlaylistSongs error:', err);
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
async isSongInPlaylist(playlistId: string, platform: string, songId: string): Promise<boolean> {
|
||||
try {
|
||||
const result = await this.db
|
||||
.prepare('SELECT 1 FROM music_playlist_songs WHERE playlist_id = ? AND platform = ? AND song_id = ? LIMIT 1')
|
||||
.bind(playlistId, platform, songId)
|
||||
.first();
|
||||
|
||||
return result !== null;
|
||||
} catch (err) {
|
||||
console.error('D1Storage.isSongInPlaylist error:', err);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// ==================== 辅助方法 ====================
|
||||
|
||||
private rowToPlayRecord(row: any): PlayRecord {
|
||||
|
||||
+103
@@ -211,6 +211,17 @@ export class DbManager {
|
||||
await this.storage.setMusicPlayRecord(userName, key, record);
|
||||
}
|
||||
|
||||
async batchSaveMusicPlayRecords(
|
||||
userName: string,
|
||||
records: Array<{ platform: string; id: string; record: MusicPlayRecord }>
|
||||
): Promise<void> {
|
||||
const batchRecords = records.map(({ platform, id, record }) => ({
|
||||
key: generateStorageKey(platform, id),
|
||||
record,
|
||||
}));
|
||||
await this.storage.batchSetMusicPlayRecords(userName, batchRecords);
|
||||
}
|
||||
|
||||
async getAllMusicPlayRecords(userName: string): Promise<{
|
||||
[key: string]: MusicPlayRecord;
|
||||
}> {
|
||||
@@ -230,6 +241,98 @@ export class DbManager {
|
||||
await this.storage.clearAllMusicPlayRecords(userName);
|
||||
}
|
||||
|
||||
// 音乐歌单相关方法
|
||||
async createMusicPlaylist(
|
||||
userName: string,
|
||||
playlist: {
|
||||
id: string;
|
||||
name: string;
|
||||
description?: string;
|
||||
cover?: string;
|
||||
}
|
||||
): Promise<void> {
|
||||
if (typeof (this.storage as any).createMusicPlaylist === 'function') {
|
||||
await (this.storage as any).createMusicPlaylist(userName, playlist);
|
||||
}
|
||||
}
|
||||
|
||||
async getMusicPlaylist(playlistId: string): Promise<any | null> {
|
||||
if (typeof (this.storage as any).getMusicPlaylist === 'function') {
|
||||
return (this.storage as any).getMusicPlaylist(playlistId);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
async getUserMusicPlaylists(userName: string): Promise<any[]> {
|
||||
if (typeof (this.storage as any).getUserMusicPlaylists === 'function') {
|
||||
return (this.storage as any).getUserMusicPlaylists(userName);
|
||||
}
|
||||
return [];
|
||||
}
|
||||
|
||||
async updateMusicPlaylist(
|
||||
playlistId: string,
|
||||
updates: {
|
||||
name?: string;
|
||||
description?: string;
|
||||
cover?: string;
|
||||
}
|
||||
): Promise<void> {
|
||||
if (typeof (this.storage as any).updateMusicPlaylist === 'function') {
|
||||
await (this.storage as any).updateMusicPlaylist(playlistId, updates);
|
||||
}
|
||||
}
|
||||
|
||||
async deleteMusicPlaylist(playlistId: string): Promise<void> {
|
||||
if (typeof (this.storage as any).deleteMusicPlaylist === 'function') {
|
||||
await (this.storage as any).deleteMusicPlaylist(playlistId);
|
||||
}
|
||||
}
|
||||
|
||||
async addSongToPlaylist(
|
||||
playlistId: string,
|
||||
song: {
|
||||
platform: string;
|
||||
id: string;
|
||||
name: string;
|
||||
artist: string;
|
||||
album?: string;
|
||||
pic?: string;
|
||||
duration: number;
|
||||
}
|
||||
): Promise<void> {
|
||||
if (typeof (this.storage as any).addSongToPlaylist === 'function') {
|
||||
await (this.storage as any).addSongToPlaylist(playlistId, song);
|
||||
}
|
||||
}
|
||||
|
||||
async removeSongFromPlaylist(
|
||||
playlistId: string,
|
||||
platform: string,
|
||||
songId: string
|
||||
): Promise<void> {
|
||||
if (typeof (this.storage as any).removeSongFromPlaylist === 'function') {
|
||||
await (this.storage as any).removeSongFromPlaylist(playlistId, platform, songId);
|
||||
}
|
||||
}
|
||||
|
||||
async getPlaylistSongs(playlistId: string): Promise<any[]> {
|
||||
if (typeof (this.storage as any).getPlaylistSongs === 'function') {
|
||||
return (this.storage as any).getPlaylistSongs(playlistId);
|
||||
}
|
||||
return [];
|
||||
}
|
||||
|
||||
async isSongInPlaylist(
|
||||
playlistId: string,
|
||||
platform: string,
|
||||
songId: string
|
||||
): Promise<boolean> {
|
||||
if (typeof (this.storage as any).isSongInPlaylist === 'function') {
|
||||
return (this.storage as any).isSongInPlaylist(playlistId, platform, songId);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
async verifyUser(userName: string, password: string): Promise<boolean> {
|
||||
return this.storage.verifyUser(userName, password);
|
||||
|
||||
@@ -537,6 +537,21 @@ export abstract class BaseRedisStorage implements IStorage {
|
||||
);
|
||||
}
|
||||
|
||||
async batchSetMusicPlayRecords(userName: string, records: { key: string; record: any }[]): Promise<void> {
|
||||
if (records.length === 0) return;
|
||||
|
||||
const hashKey = this.musicPlayRecordHashKey(userName);
|
||||
const data: Record<string, string> = {};
|
||||
|
||||
for (const { key, record } of records) {
|
||||
data[key] = JSON.stringify(record);
|
||||
}
|
||||
|
||||
await this.withRetry(() =>
|
||||
this.adapter.hSet(hashKey, data)
|
||||
);
|
||||
}
|
||||
|
||||
async getAllMusicPlayRecords(userName: string): Promise<Record<string, any>> {
|
||||
const hashData = await this.withRetry(() =>
|
||||
this.adapter.hGetAll(this.musicPlayRecordHashKey(userName))
|
||||
@@ -563,6 +578,219 @@ export abstract class BaseRedisStorage implements IStorage {
|
||||
);
|
||||
}
|
||||
|
||||
// ---------- 音乐歌单相关 ----------
|
||||
private musicPlaylistsKey(userName: string) {
|
||||
return `u:${userName}:music_playlists`;
|
||||
}
|
||||
|
||||
private musicPlaylistKey(playlistId: string) {
|
||||
return `music_playlist:${playlistId}`;
|
||||
}
|
||||
|
||||
private musicPlaylistSongsKey(playlistId: string) {
|
||||
return `music_playlist:${playlistId}:songs`;
|
||||
}
|
||||
|
||||
async createMusicPlaylist(userName: string, playlist: {
|
||||
id: string;
|
||||
name: string;
|
||||
description?: string;
|
||||
cover?: string;
|
||||
}): Promise<void> {
|
||||
const now = Date.now();
|
||||
const playlistData = {
|
||||
id: playlist.id,
|
||||
username: userName,
|
||||
name: playlist.name,
|
||||
description: playlist.description || '',
|
||||
cover: playlist.cover || '',
|
||||
created_at: now.toString(),
|
||||
updated_at: now.toString(),
|
||||
};
|
||||
|
||||
// 存储歌单信息
|
||||
await this.withRetry(() =>
|
||||
this.adapter.hSet(this.musicPlaylistKey(playlist.id), playlistData)
|
||||
);
|
||||
|
||||
// 添加到用户的歌单列表(使用 sorted set,按创建时间排序)
|
||||
await this.withRetry(() =>
|
||||
this.adapter.zAdd(this.musicPlaylistsKey(userName), {
|
||||
score: now,
|
||||
value: playlist.id,
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
async getMusicPlaylist(playlistId: string): Promise<any | null> {
|
||||
const data = await this.withRetry(() =>
|
||||
this.adapter.hGetAll(this.musicPlaylistKey(playlistId))
|
||||
);
|
||||
|
||||
if (!data || Object.keys(data).length === 0) return null;
|
||||
|
||||
return {
|
||||
id: data.id,
|
||||
username: data.username,
|
||||
name: data.name,
|
||||
description: data.description || undefined,
|
||||
cover: data.cover || undefined,
|
||||
created_at: parseInt(data.created_at, 10),
|
||||
updated_at: parseInt(data.updated_at, 10),
|
||||
};
|
||||
}
|
||||
|
||||
async getUserMusicPlaylists(userName: string): Promise<any[]> {
|
||||
// 获取用户的所有歌单ID(按创建时间倒序)
|
||||
const playlistIds = await this.withRetry(() =>
|
||||
this.adapter.zRange(this.musicPlaylistsKey(userName), 0, -1)
|
||||
);
|
||||
|
||||
if (!playlistIds || playlistIds.length === 0) return [];
|
||||
|
||||
// 获取每个歌单的详细信息
|
||||
const playlists = [];
|
||||
for (const id of playlistIds) {
|
||||
const playlist = await this.getMusicPlaylist(ensureString(id));
|
||||
if (playlist) {
|
||||
playlists.push(playlist);
|
||||
}
|
||||
}
|
||||
|
||||
// 按创建时间倒序排序
|
||||
return playlists.sort((a, b) => b.created_at - a.created_at);
|
||||
}
|
||||
|
||||
async updateMusicPlaylist(playlistId: string, updates: {
|
||||
name?: string;
|
||||
description?: string;
|
||||
cover?: string;
|
||||
}): Promise<void> {
|
||||
const updateData: Record<string, string> = {
|
||||
updated_at: Date.now().toString(),
|
||||
};
|
||||
|
||||
if (updates.name !== undefined) {
|
||||
updateData.name = updates.name;
|
||||
}
|
||||
if (updates.description !== undefined) {
|
||||
updateData.description = updates.description || '';
|
||||
}
|
||||
if (updates.cover !== undefined) {
|
||||
updateData.cover = updates.cover || '';
|
||||
}
|
||||
|
||||
await this.withRetry(() =>
|
||||
this.adapter.hSet(this.musicPlaylistKey(playlistId), updateData)
|
||||
);
|
||||
}
|
||||
|
||||
async deleteMusicPlaylist(playlistId: string): Promise<void> {
|
||||
// 获取歌单信息以获取用户名
|
||||
const playlist = await this.getMusicPlaylist(playlistId);
|
||||
if (!playlist) return;
|
||||
|
||||
// 从用户的歌单列表中移除
|
||||
await this.withRetry(() =>
|
||||
this.adapter.zRem(this.musicPlaylistsKey(playlist.username), playlistId)
|
||||
);
|
||||
|
||||
// 删除歌单信息
|
||||
await this.withRetry(() =>
|
||||
this.adapter.del(this.musicPlaylistKey(playlistId))
|
||||
);
|
||||
|
||||
// 删除歌单的歌曲列表
|
||||
await this.withRetry(() =>
|
||||
this.adapter.del(this.musicPlaylistSongsKey(playlistId))
|
||||
);
|
||||
}
|
||||
|
||||
async addSongToPlaylist(playlistId: string, song: {
|
||||
platform: string;
|
||||
id: string;
|
||||
name: string;
|
||||
artist: string;
|
||||
album?: string;
|
||||
pic?: string;
|
||||
duration: number;
|
||||
}): Promise<void> {
|
||||
const now = Date.now();
|
||||
const songKey = `${song.platform}+${song.id}`;
|
||||
|
||||
const songData = {
|
||||
platform: song.platform,
|
||||
id: song.id,
|
||||
name: song.name,
|
||||
artist: song.artist,
|
||||
album: song.album || '',
|
||||
pic: song.pic || '',
|
||||
duration: song.duration.toString(),
|
||||
added_at: now.toString(),
|
||||
};
|
||||
|
||||
// 添加歌曲到歌单(使用 hash 存储歌曲信息)
|
||||
await this.withRetry(() =>
|
||||
this.adapter.hSet(this.musicPlaylistSongsKey(playlistId), songKey, JSON.stringify(songData))
|
||||
);
|
||||
|
||||
// 更新歌单的 updated_at
|
||||
await this.updateMusicPlaylist(playlistId, {});
|
||||
|
||||
// 如果是第一首歌且有封面,更新歌单封面
|
||||
const songs = await this.getPlaylistSongs(playlistId);
|
||||
if (songs.length === 1 && song.pic) {
|
||||
await this.updateMusicPlaylist(playlistId, { cover: song.pic });
|
||||
}
|
||||
}
|
||||
|
||||
async removeSongFromPlaylist(playlistId: string, platform: string, songId: string): Promise<void> {
|
||||
const songKey = `${platform}+${songId}`;
|
||||
|
||||
await this.withRetry(() =>
|
||||
this.adapter.hDel(this.musicPlaylistSongsKey(playlistId), songKey)
|
||||
);
|
||||
|
||||
// 更新歌单的 updated_at
|
||||
await this.updateMusicPlaylist(playlistId, {});
|
||||
}
|
||||
|
||||
async getPlaylistSongs(playlistId: string): Promise<any[]> {
|
||||
const songsData = await this.withRetry(() =>
|
||||
this.adapter.hGetAll(this.musicPlaylistSongsKey(playlistId))
|
||||
);
|
||||
|
||||
if (!songsData || Object.keys(songsData).length === 0) return [];
|
||||
|
||||
const songs = [];
|
||||
for (const [, value] of Object.entries(songsData)) {
|
||||
if (value) {
|
||||
const song = JSON.parse(value);
|
||||
songs.push({
|
||||
platform: song.platform,
|
||||
id: song.id,
|
||||
name: song.name,
|
||||
artist: song.artist,
|
||||
album: song.album || undefined,
|
||||
pic: song.pic || undefined,
|
||||
duration: parseFloat(song.duration),
|
||||
added_at: parseInt(song.added_at, 10),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// 按添加时间排序
|
||||
return songs.sort((a, b) => a.added_at - b.added_at);
|
||||
}
|
||||
|
||||
async isSongInPlaylist(playlistId: string, platform: string, songId: string): Promise<boolean> {
|
||||
const songKey = `${platform}+${songId}`;
|
||||
const exists = await this.withRetry(() =>
|
||||
this.adapter.hGet(this.musicPlaylistSongsKey(playlistId), songKey)
|
||||
);
|
||||
return exists !== null;
|
||||
}
|
||||
|
||||
// ---------- 用户注册 / 登录(旧版本,保持兼容) ----------
|
||||
private userPwdKey(user: string) {
|
||||
return `u:${user}:pwd`;
|
||||
|
||||
@@ -55,6 +55,7 @@ export interface IStorage {
|
||||
// 音乐播放记录相关
|
||||
getMusicPlayRecord(userName: string, key: string): Promise<any | null>;
|
||||
setMusicPlayRecord(userName: string, key: string, record: any): Promise<void>;
|
||||
batchSetMusicPlayRecords(userName: string, records: { key: string; record: any }[]): Promise<void>;
|
||||
getAllMusicPlayRecords(userName: string): Promise<{ [key: string]: any }>;
|
||||
deleteMusicPlayRecord(userName: string, key: string): Promise<void>;
|
||||
clearAllMusicPlayRecords(userName: string): Promise<void>;
|
||||
|
||||
Reference in New Issue
Block a user