fix(open-api): schedules 的缓存/Redis 分支补齐 voteCount,消除分支间字段不一致

上一步只给数据库分支补了 voteCount,但实测该接口通常命中缓存分支,
导致 voteCount 依然缺失。抽出 buildVoteCountMap() 供三条分支共用,
保证无论缓存命中与否,输出结构一致。
This commit is contained in:
2026-09-26 14:30:02 +08:00
parent 12dac95e1b
commit 39375c23f2
+40 -20
View File
@@ -21,6 +21,32 @@ import {
type VisibilitySettingsLike type VisibilitySettingsLike
} from '~~/server/utils/schedule-visibility' } from '~~/server/utils/schedule-visibility'
/**
* 批量统计歌曲票数。
*
* 该接口有缓存分支和数据库分支两条路径,此前字段互不一致
* (序列号只在缓存分支有、票数只在数据库分支有)。统一由本函数提供票数,
* 保证把缓存开关、Redis 开关怎么组合,输出结构都一致。
*/
async function buildVoteCountMap(songIds: number[]): Promise<Map<number, number>> {
const map = new Map<number, number>()
if (songIds.length === 0) return map
const rows = await db
.select({
songId: votes.songId,
count: sql`count(*)`
})
.from(votes)
.where(inArray(votes.songId, songIds))
.groupBy(votes.songId)
rows.forEach((row) => {
map.set(row.songId, Number(row.count))
})
return map
}
export default defineEventHandler(async (event) => { export default defineEventHandler(async (event) => {
try { try {
const query = getQuery(event) const query = getQuery(event)
@@ -85,6 +111,10 @@ export default defineEventHandler(async (event) => {
} }
filteredSchedules = applyVisibilityFilter(filteredSchedules) filteredSchedules = applyVisibilityFilter(filteredSchedules)
const voteCounts = await buildVoteCountMap(
filteredSchedules.map((s: any) => s.song?.id).filter(Boolean)
)
// 转换为开放API格式 // 转换为开放API格式
const result = { const result = {
success: true, success: true,
@@ -101,7 +131,8 @@ export default defineEventHandler(async (event) => {
semester: schedule.song.semester, semester: schedule.song.semester,
cover: schedule.song.cover, cover: schedule.song.cover,
musicPlatform: schedule.song.musicPlatform, musicPlatform: schedule.song.musicPlatform,
musicId: schedule.song.musicId musicId: schedule.song.musicId,
voteCount: voteCounts.get(schedule.song.id) || 0
}, },
playTime: schedule.playTime playTime: schedule.playTime
? { ? {
@@ -141,6 +172,11 @@ export default defineEventHandler(async (event) => {
if (redisData) { if (redisData) {
const filteredRedisData = applyVisibilityFilter(redisData) const filteredRedisData = applyVisibilityFilter(redisData)
const voteCounts = await buildVoteCountMap(
filteredRedisData.map((s: any) => s.song?.id).filter(Boolean)
)
// 转换为开放API格式 // 转换为开放API格式
const result = { const result = {
success: true, success: true,
@@ -157,7 +193,8 @@ export default defineEventHandler(async (event) => {
semester: schedule.song.semester, semester: schedule.song.semester,
cover: schedule.song.cover, cover: schedule.song.cover,
musicPlatform: schedule.song.musicPlatform, musicPlatform: schedule.song.musicPlatform,
musicId: schedule.song.musicId musicId: schedule.song.musicId,
voteCount: voteCounts.get(schedule.song.id) || 0
}, },
playTime: schedule.playTime playTime: schedule.playTime
? { ? {
@@ -322,24 +359,7 @@ export default defineEventHandler(async (event) => {
}) })
} }
// 统计每首歌的票数。 const voteCountsMap = await buildVoteCountMap(songIds)
// 该接口的缓存分支(本文件上方)会返回 voteCount,数据库分支此前漏了,
// 导致两条分支字段不一致,这里补齐。
const voteCountsMap = new Map<number, number>()
if (songIds.length > 0) {
const voteCountsData = await db
.select({
songId: votes.songId,
count: sql`count(*)`
})
.from(votes)
.where(inArray(votes.songId, songIds))
.groupBy(votes.songId)
voteCountsData.forEach((row) => {
voteCountsMap.set(row.songId, Number(row.count))
})
}
// 格式化数据 // 格式化数据
const formattedSchedules = schedulesData.map((schedule) => { const formattedSchedules = schedulesData.map((schedule) => {