[+] Mai2 music ranking

pull/106/head
Menci 2025-01-12 03:53:57 +08:00
parent 65cc3095e2
commit 3d66b7e022
5 changed files with 106 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,71 @@
package icu.samnyan.aqua.sega.maimai2.handler
import com.querydsl.jpa.impl.JPAQueryFactory
import icu.samnyan.aqua.sega.general.BaseHandler
import icu.samnyan.aqua.sega.maimai2.model.Mai2Repos
import icu.samnyan.aqua.sega.maimai2.model.userdata.QMai2UserPlaylog
import org.slf4j.Logger
import org.slf4j.LoggerFactory
import org.springframework.scheduling.annotation.Scheduled
import org.springframework.stereotype.Component
import java.time.LocalDateTime
import java.time.format.DateTimeFormatter
/**
* @author samnyan (privateamusement@protonmail.com)
*/
@Component("Maimai2GetGameRankingHandler")
class GetGameRankingHandler(
private val repos: Mai2Repos,
private val queryFactory: JPAQueryFactory
) : BaseHandler {
private data class MusicRankingItem(val musicId: Int, val weight: Long)
private var musicRankingCache: Array<MusicRankingItem> = emptyArray()
init {
// To make sure the cache is initialized before the first request,
// not using `initialDelay = 0` in `@Scheduled`.
refreshMusicRankingCache()
}
@Scheduled(fixedDelay = 3600_000)
private fun refreshMusicRankingCache() {
val LOOK_BACK_DAYS: Long = 7
val QUREY_LIMIT: Long = 50
val qMai2Playlog = QMai2UserPlaylog.mai2UserPlaylog
val qPlaylog = QMai2UserPlaylog.mai2UserPlaylog
// 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 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(QUREY_LIMIT)
.fetch()
.map { MusicRankingItem(it.get(cMusicId)!!, it.get(cUserCount)!!) }
.toTypedArray()
logger.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.map { mapOf("id" to it.musicId, "point" to it.weight, "userName" to "") }
else -> emptyList()
}
)
companion object {
val logger: Logger = LoggerFactory.getLogger(GetGameRankingHandler::class.java)
}
}

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);