[+] 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.jpa") version ktVer
kotlin("plugin.serialization") version ktVer kotlin("plugin.serialization") version ktVer
kotlin("plugin.allopen") version ktVer kotlin("plugin.allopen") version ktVer
kotlin("kapt") version ktVer
id("io.freefair.lombok") version "8.6" id("io.freefair.lombok") version "8.6"
id("org.springframework.boot") version "3.2.3" id("org.springframework.boot") version "3.2.3"
id("com.github.ben-manes.versions") version "0.51.0" id("com.github.ben-manes.versions") version "0.51.0"
@ -55,6 +56,8 @@ dependencies {
runtimeOnly("org.xerial:sqlite-jdbc:3.45.2.0") runtimeOnly("org.xerial:sqlite-jdbc:3.45.2.0")
implementation("org.hibernate.orm:hibernate-core:6.4.4.Final") implementation("org.hibernate.orm:hibernate-core:6.4.4.Final")
implementation("org.hibernate.orm:hibernate-community-dialects: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 // JSR305 for nullable
implementation("com.google.code.findbugs:jsr305:3.0.2") implementation("com.google.code.findbugs:jsr305:3.0.2")
@ -122,6 +125,11 @@ hibernate {
} }
} }
kapt {
includeCompileClasspath = false
keepJavacAnnotationProcessors = true
}
allOpen { allOpen {
annotation("jakarta.persistence.Entity") annotation("jakarta.persistence.Entity")
annotation("jakarta.persistence.MappedSuperclass") annotation("jakarta.persistence.MappedSuperclass")
@ -153,3 +161,9 @@ tasks.withType<Javadoc> {
tasks.getByName<Jar>("jar") { tasks.getByName<Jar>("jar") {
enabled = false 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 getUserFavoriteItem: GetUserFavoriteItemHandler,
val getUserRivalMusic: GetUserRivalMusicHandler, val getUserRivalMusic: GetUserRivalMusicHandler,
val getUserCharacter: GetUserCharacterHandler, val getUserCharacter: GetUserCharacterHandler,
val getGameRanking: GetGameRankingHandler,
val repos: Mai2Repos val repos: Mai2Repos
) { ) {
companion object { companion object {
@ -222,7 +223,6 @@ class Maimai2ServletController(
val getUserIntimate = UserReqHandler { _, uid -> mapOf("userId" to uid, "length" to 0, "userIntimateList" to empty) } 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 getTransferFriend = UserReqHandler { _, uid -> mapOf("userId" to uid, "transferFriendList" to empty) }
val getGameNgMusicId = BaseHandler { mapOf("length" to 0, "musicIdList" 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 getGameTournamentInfo = BaseHandler { mapOf("length" to 0, "gameTournamentInfoList" to empty) }
val getGameKaleidxScope = BaseHandler { mapOf("gameKaleidxScopeList" to empty) } val getGameKaleidxScope = BaseHandler { mapOf("gameKaleidxScopeList" to empty) }
val getUserKaleidxScope = UserReqHandler { _, uid -> mapOf("userId" to uid, "userKaleidxScopeList" 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);