[+] Mai2 music ranking

pull/108/head
Menci 2025-01-12 03:53:57 +08:00 committed by Azalea
parent 528960940c
commit 03b452e426
5 changed files with 104 additions and 1 deletions

View File

@ -13,6 +13,7 @@ plugins {
kotlin("plugin.jpa") version ktVer
kotlin("plugin.serialization") version ktVer
kotlin("plugin.allopen") version ktVer
kotlin("kapt") version ktVer
id("io.freefair.lombok") version "8.6"
id("org.springframework.boot") version "3.2.3"
id("com.github.ben-manes.versions") version "0.51.0"
@ -55,6 +56,8 @@ dependencies {
runtimeOnly("org.xerial:sqlite-jdbc:3.45.2.0")
implementation("org.hibernate.orm:hibernate-core:6.4.4.Final")
implementation("org.hibernate.orm:hibernate-community-dialects:6.4.4.Final")
implementation("io.github.openfeign.querydsl:querydsl-jpa:6.10.1")
kapt("io.github.openfeign.querydsl:querydsl-apt:6.10.1:jpa")
// JSR305 for nullable
implementation("com.google.code.findbugs:jsr305:3.0.2")
@ -122,6 +125,11 @@ hibernate {
}
}
kapt {
includeCompileClasspath = false
keepJavacAnnotationProcessors = true
}
allOpen {
annotation("jakarta.persistence.Entity")
annotation("jakarta.persistence.MappedSuperclass")
@ -153,3 +161,9 @@ tasks.withType<Javadoc> {
tasks.getByName<Jar>("jar") {
enabled = false
}
sourceSets {
main {
java.srcDir("${layout.buildDirectory.get()}/generated/source/kapt/main")
}
}

View File

@ -33,6 +33,7 @@ class Maimai2ServletController(
val getUserFavoriteItem: GetUserFavoriteItemHandler,
val getUserRivalMusic: GetUserRivalMusicHandler,
val getUserCharacter: GetUserCharacterHandler,
val getGameRanking: GetGameRankingHandler,
val repos: Mai2Repos
) {
companion object {
@ -222,7 +223,6 @@ class Maimai2ServletController(
val getUserIntimate = UserReqHandler { _, uid -> mapOf("userId" to uid, "length" to 0, "userIntimateList" to empty) }
val getTransferFriend = UserReqHandler { _, uid -> mapOf("userId" to uid, "transferFriendList" to empty) }
val getGameNgMusicId = BaseHandler { mapOf("length" to 0, "musicIdList" to empty) }
val getGameRanking = BaseHandler { mapOf("type" to it["type"].toString(), "gameRankingList" to empty) }
val getGameTournamentInfo = BaseHandler { mapOf("length" to 0, "gameTournamentInfoList" to empty) }
val getGameKaleidxScope = BaseHandler { mapOf("gameKaleidxScopeList" to empty) }
val getUserKaleidxScope = UserReqHandler { _, uid -> mapOf("userId" to uid, "userKaleidxScopeList" to empty) }

View File

@ -0,0 +1,69 @@
package icu.samnyan.aqua.sega.maimai2.handler
import com.querydsl.jpa.impl.JPAQueryFactory
import ext.logger
import ext.thread
import icu.samnyan.aqua.sega.general.BaseHandler
import icu.samnyan.aqua.sega.maimai2.model.userdata.QMai2UserPlaylog
import org.springframework.scheduling.annotation.Scheduled
import org.springframework.stereotype.Component
import java.time.LocalDateTime
import java.time.format.DateTimeFormatter
import kotlin.concurrent.Volatile
/**
* @author samnyan (privateamusement@protonmail.com)
*/
@Component("Maimai2GetGameRankingHandler")
class GetGameRankingHandler(
private val queryFactory: JPAQueryFactory
) : BaseHandler {
private data class MusicRankingItem(val id: Int, val point: Long, val userName: String = "")
@Volatile
private var musicRankingCache: List<MusicRankingItem> = emptyList()
init {
// To make sure the cache is initialized before the first request,
// not using `initialDelay = 0` in `@Scheduled`.
thread { refreshMusicRankingCache() }
}
@Scheduled(fixedDelay = 3600_000)
private fun refreshMusicRankingCache() {
// Get the play count of each music in the last N days
val queryAfter = LocalDateTime.now().minusDays(LOOK_BACK_DAYS)
val formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")
val queryAfterStr = queryAfter.format(formatter)
val qPlaylog = QMai2UserPlaylog.mai2UserPlaylog
val cMusicId = qPlaylog.musicId
val cUserCount = qPlaylog.user.id.countDistinct()
musicRankingCache = queryFactory
.select(cMusicId, cUserCount)
.from(qPlaylog)
.where(qPlaylog.userPlayDate.stringValue().goe(queryAfterStr))
.groupBy(cMusicId)
.orderBy(cUserCount.desc())
.limit(QUERY_LIMIT)
.fetch()
.map { MusicRankingItem(it.get(cMusicId)!!, it.get(cUserCount)!!) }
log.info("Refreshed music ranking cache: ${musicRankingCache.size} items")
}
override fun handle(request: Map<String, Any>): Any = mapOf(
"type" to request["type"],
"gameRankingList" to when(request["type"]) {
1 -> musicRankingCache
else -> emptyList()
}
)
companion object {
val log = logger()
const val LOOK_BACK_DAYS: Long = 7
const val QUERY_LIMIT: Long = 50
}
}

View File

@ -0,0 +1,18 @@
package icu.samnyan.aqua.spring
import com.querydsl.jpa.impl.JPAQueryFactory
import jakarta.persistence.EntityManager
import jakarta.persistence.PersistenceContext
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
@Configuration
class QuerydslConfig {
@PersistenceContext
private lateinit var entityManager: EntityManager
@Bean
fun jpaQueryFactory(): JPAQueryFactory {
return JPAQueryFactory(entityManager)
}
}

View File

@ -0,0 +1,2 @@
CREATE INDEX idx_play_date_music_user
ON maimai2_user_playlog (user_play_date, music_id, user_id);