[O] Make handlers abstract

matching
Azalea 2024-12-27 16:03:02 -05:00
parent fa45891af4
commit ae6ff97b62
3 changed files with 119 additions and 114 deletions

View File

@ -1,123 +1,17 @@
package icu.samnyan.aqua.sega.chusan package icu.samnyan.aqua.sega.chusan
import ext.* import ext.*
import icu.samnyan.aqua.net.db.AquaUserServices
import icu.samnyan.aqua.net.utils.simpleDescribe
import icu.samnyan.aqua.sega.chusan.handler.*
import icu.samnyan.aqua.sega.chusan.model.Chu3Repos
import icu.samnyan.aqua.sega.chusan.model.request.UserCMissionResp import icu.samnyan.aqua.sega.chusan.model.request.UserCMissionResp
import icu.samnyan.aqua.sega.chusan.model.response.data.MatchingMemberInfo import icu.samnyan.aqua.sega.chusan.model.response.data.MatchingMemberInfo
import icu.samnyan.aqua.sega.chusan.model.response.data.MatchingWaitState import icu.samnyan.aqua.sega.chusan.model.response.data.MatchingWaitState
import icu.samnyan.aqua.sega.chusan.model.response.data.UserEmoney import icu.samnyan.aqua.sega.chusan.model.response.data.UserEmoney
import icu.samnyan.aqua.sega.chusan.model.userdata.UserCharge import icu.samnyan.aqua.sega.chusan.model.userdata.UserCharge
import icu.samnyan.aqua.sega.chusan.model.userdata.UserMusicDetail import icu.samnyan.aqua.sega.chusan.model.userdata.UserMusicDetail
import icu.samnyan.aqua.sega.general.BaseHandler
import icu.samnyan.aqua.sega.general.RequestContext
import icu.samnyan.aqua.sega.general.SpecialHandler
import icu.samnyan.aqua.sega.general.model.response.UserRecentRating import icu.samnyan.aqua.sega.general.model.response.UserRecentRating
import icu.samnyan.aqua.sega.general.toSpecial
import icu.samnyan.aqua.sega.util.jackson.BasicMapper
import icu.samnyan.aqua.sega.util.jackson.StringMapper
import icu.samnyan.aqua.spring.Metrics
import jakarta.servlet.http.HttpServletRequest
import org.slf4j.LoggerFactory
import org.springframework.web.bind.annotation.RestController
import java.time.LocalDateTime import java.time.LocalDateTime
import java.time.format.DateTimeFormatter import java.time.format.DateTimeFormatter
import kotlin.collections.set
import kotlin.reflect.full.declaredMemberProperties
/** val chusanInit: ChusanController.() -> Unit = {
* @author samnyan (privateamusement@protonmail.com)
*/
@Suppress("unused")
@RestController
@API(value = ["/g/chu3/{version}/ChuniServlet", "/g/chu3/{version}"])
class ChusanServletController(
val gameLogin: GameLoginHandler,
val upsertUserAll: UpsertUserAllHandler,
val cmUpsertUserGacha: CMUpsertUserGachaHandler,
val cmUpsertUserPrintSubtract: CMUpsertUserPrintSubtractHandler,
val cmUpsertUserPrintCancel: CMUpsertUserPrintCancelHandler,
val mapper: StringMapper,
val cmMapper: BasicMapper,
val db: Chu3Repos,
val us: AquaUserServices,
val versionHelper: ChusanVersionHelper,
val props: ChusanProps
) {
val log = LoggerFactory.getLogger(ChusanServletController::class.java)
// Below are code related to handling the handlers
val externalHandlers = mutableListOf("GameLoginApi", "UpsertUserAllApi",
"CMUpsertUserGachaApi", "CMUpsertUserPrintCancelApi", "CMUpsertUserPrintSubtractApi")
val noopEndpoint = setOf("UpsertClientBookkeepingApi", "UpsertClientDevelopApi", "UpsertClientErrorApi",
"UpsertClientSettingApi", "UpsertClientTestmodeApi", "CreateTokenApi", "RemoveTokenApi", "UpsertClientUploadApi",
"PrinterLoginApi", "PrinterLogoutApi", "Ping", "GameLogoutApi", "RemoveMatchingMemberApi")
// Fun!
val initH = mutableMapOf<String, SpecialHandler>()
infix operator fun String.invoke(fn: SpecialHandler) = initH.set("${this}Api", fn)
infix fun List<String>.all(fn: SpecialHandler) = forEach { it(fn) }
infix fun String.static(fn: () -> Any) = mapper.write(fn()).let { resp -> this { resp } }
val meow = init()
val members = this::class.declaredMemberProperties
val handlers: Map<String, SpecialHandler> = initH + externalHandlers.associateWith { api ->
val name = api.replace("Api", "").lowercase()
(members.find { it.name.lowercase() == name } ?: members.find { it.name.lowercase() == name.replace("cm", "") })
?.let { (it.call(this) as BaseHandler).toSpecial() }
?: throw IllegalArgumentException("Chu3: No handler found for $api")
}
@API("/{endpoint}", "/MatchingServer/{endpoint}")
fun handle(@PV endpoint: Str, @RB data: MutableMap<Str, Any>, @PV version: Str, req: HttpServletRequest): Any {
val ctx = RequestContext(req, data)
var api = endpoint
data["version"] = version
// Export version
if (api.endsWith("C3Exp")) {
api = api.removeSuffix("C3Exp")
data["c3exp"] = true
}
if (api !in noopEndpoint && !handlers.containsKey(api)) {
log.warn("Chu3 > $api not found")
return """{"returnCode":"1","apiName":"$api"}"""
}
// Only record the counter metrics if the API is known.
Metrics.counter("aquadx_chusan_api_call", "api" to api).increment()
if (api in noopEndpoint) {
log.info("Chu3 > $api no-op")
return """{"returnCode":"1"}"""
}
log.info("Chu3 < $api : ${data.toJson()}")
val map = if ("CM" in api) cmMapper else mapper
return try {
Metrics.timer("aquadx_chusan_api_latency", "api" to api).recordCallable {
handlers[api]!!(ctx).let { if (it is String) it else map.write(it) }.also {
if (api !in setOf("GetUserItemApi", "GetGameEventApi"))
log.info("Chu3 > $api : $it")
}
}
} catch (e: Exception) {
Metrics.counter(
"aquadx_chusan_api_error",
"api" to api, "error" to e.simpleDescribe()
).increment()
throw e
}
}
}
@Suppress("UNCHECKED_CAST")
fun ChusanServletController.init() {
// Stub handlers // Stub handlers
"GetGameRanking" { """{"type":"${data["type"]}","length":"0","gameRankingList":[]}""" } "GetGameRanking" { """{"type":"${data["type"]}","length":"0","gameRankingList":[]}""" }
"GetGameIdlist" { """{"type":"${data["type"]}","length":"0","gameIdlistList":[]}""" } "GetGameIdlist" { """{"type":"${data["type"]}","length":"0","gameIdlistList":[]}""" }
@ -183,7 +77,8 @@ fun ChusanServletController.init() {
"GetGameGachaCardById" { db.gameGachaCard.findAllByGachaId(parsing { data["gachaId"]!!.int }).let { "GetGameGachaCardById" { db.gameGachaCard.findAllByGachaId(parsing { data["gachaId"]!!.int }).let {
mapOf("gachaId" to it.size, "length" to it.size, "isPickup" to false, "gameGachaCardList" to it, mapOf("gachaId" to it.size, "length" to it.size, "isPickup" to false, "gameGachaCardList" to it,
"emissionList" to empty, "afterCalcList" to empty) "emissionList" to empty, "afterCalcList" to empty
)
} } } }
"GetUserCMission" { "GetUserCMission" {
@ -333,8 +228,8 @@ fun ChusanServletController.init() {
"maxCountCharacter" to 300, "maxCountCharacter" to 300,
"maxCountItem" to 300, "maxCountItem" to 300,
"maxCountMusic" to 300, "maxCountMusic" to 300,
"matchStartTime" to LocalDateTime.now().minusHours(1).format(fmt), "matchStartTime" to LocalDateTime.now().minusHours(5).format(fmt),
"matchEndTime" to LocalDateTime.now().plusHours(1).format(fmt), "matchEndTime" to LocalDateTime.now().plusHours(5).format(fmt),
"matchTimeLimit" to 10, "matchTimeLimit" to 10,
"matchErrorLimit" to 10, "matchErrorLimit" to 10,
"matchingUri" to addr, "matchingUri" to addr,
@ -372,4 +267,4 @@ fun ChusanServletController.init() {
val user = db.userData.findByCard_ExtId(uid)() ?: (400 - "User not found") val user = db.userData.findByCard_ExtId(uid)() ?: (400 - "User not found")
mapOf("userName" to user.userName, "level" to user.level, "medal" to user.medal, "lastDataVersion" to user.lastDataVersion, "isLogin" to false) mapOf("userName" to user.userName, "level" to user.level, "medal" to user.medal, "lastDataVersion" to user.lastDataVersion, "isLogin" to false)
} }
} }

View File

@ -0,0 +1,104 @@
package icu.samnyan.aqua.sega.chusan
import ext.*
import icu.samnyan.aqua.net.db.AquaUserServices
import icu.samnyan.aqua.net.utils.simpleDescribe
import icu.samnyan.aqua.sega.chusan.handler.*
import icu.samnyan.aqua.sega.chusan.model.Chu3Repos
import icu.samnyan.aqua.sega.general.*
import icu.samnyan.aqua.sega.util.jackson.BasicMapper
import icu.samnyan.aqua.sega.util.jackson.StringMapper
import icu.samnyan.aqua.spring.Metrics
import jakarta.servlet.http.HttpServletRequest
import org.slf4j.LoggerFactory
import org.springframework.web.bind.annotation.RestController
import kotlin.collections.set
import kotlin.reflect.full.declaredMemberProperties
/**
* @author samnyan (privateamusement@protonmail.com)
*/
@Suppress("unused")
@RestController
@API(value = ["/g/chu3/{version}/ChuniServlet", "/g/chu3/{version}"])
class ChusanController(
val gameLogin: GameLoginHandler,
val upsertUserAll: UpsertUserAllHandler,
val cmUpsertUserGacha: CMUpsertUserGachaHandler,
val cmUpsertUserPrintSubtract: CMUpsertUserPrintSubtractHandler,
val cmUpsertUserPrintCancel: CMUpsertUserPrintCancelHandler,
val mapper: StringMapper,
val cmMapper: BasicMapper,
val db: Chu3Repos,
val us: AquaUserServices,
val versionHelper: ChusanVersionHelper,
val props: ChusanProps
): MeowApi({ api, resp ->
if (resp is String) resp
else (if ("CM" in api) cmMapper else mapper).write(resp)
}) {
val log = LoggerFactory.getLogger(ChusanController::class.java)
// Below are code related to handling the handlers
val externalHandlers = mutableListOf("GameLoginApi", "UpsertUserAllApi",
"CMUpsertUserGachaApi", "CMUpsertUserPrintCancelApi", "CMUpsertUserPrintSubtractApi")
val noopEndpoint = setOf("UpsertClientBookkeepingApi", "UpsertClientDevelopApi", "UpsertClientErrorApi",
"UpsertClientSettingApi", "UpsertClientTestmodeApi", "CreateTokenApi", "RemoveTokenApi", "UpsertClientUploadApi",
"PrinterLoginApi", "PrinterLogoutApi", "Ping", "GameLogoutApi", "RemoveMatchingMemberApi")
init { chusanInit() }
val members = this::class.declaredMemberProperties
val handlers: Map<String, SpecialHandler> = initH + externalHandlers.associateWith { api ->
val name = api.replace("Api", "").lowercase()
(members.find { it.name.lowercase() == name } ?: members.find { it.name.lowercase() == name.replace("cm", "") })
?.let { (it.call(this) as BaseHandler).toSpecial() }
?: throw IllegalArgumentException("Chu3: No handler found for $api")
}
@API("/{endpoint}", "/MatchingServer/{endpoint}")
fun handle(@PV endpoint: Str, @RB data: MutableMap<Str, Any>, @PV version: Str, req: HttpServletRequest): Any {
val ctx = RequestContext(req, data)
var api = endpoint
data["version"] = version
// Export version
if (api.endsWith("C3Exp")) {
api = api.removeSuffix("C3Exp")
data["c3exp"] = true
}
if (api !in noopEndpoint && !handlers.containsKey(api)) {
log.warn("Chu3 > $api not found")
return """{"returnCode":"1","apiName":"$api"}"""
}
// Only record the counter metrics if the API is known.
Metrics.counter("aquadx_chusan_api_call", "api" to api).increment()
if (api in noopEndpoint) {
log.info("Chu3 > $api no-op")
return """{"returnCode":"1"}"""
}
log.info("Chu3 < $api : ${data.toJson()}")
return try {
Metrics.timer("aquadx_chusan_api_latency", "api" to api).recordCallable {
serialize(api, handlers[api]!!(ctx)).also {
if (api !in setOf("GetUserItemApi", "GetGameEventApi"))
log.info("Chu3 > $api : $it")
}
}
} catch (e: Exception) {
Metrics.counter(
"aquadx_chusan_api_error",
"api" to api, "error" to e.simpleDescribe()
).increment()
throw e
}
}
}

View File

@ -5,9 +5,7 @@ import ext.long
import ext.parsing import ext.parsing
import jakarta.servlet.http.HttpServletRequest import jakarta.servlet.http.HttpServletRequest
/**
* @author samnyan (privateamusement@protonmail.com)
*/
fun interface BaseHandler { fun interface BaseHandler {
@Throws(JsonProcessingException::class) @Throws(JsonProcessingException::class)
fun handle(request: Map<String, Any>): Any? fun handle(request: Map<String, Any>): Any?
@ -22,3 +20,11 @@ data class RequestContext(
typealias SpecialHandler = RequestContext.() -> Any? typealias SpecialHandler = RequestContext.() -> Any?
fun BaseHandler.toSpecial() = { ctx: RequestContext -> handle(ctx.data) } fun BaseHandler.toSpecial() = { ctx: RequestContext -> handle(ctx.data) }
// A very :3 way of declaring APIs
abstract class MeowApi(val serialize: (String, Any?) -> String) {
val initH = mutableMapOf<String, SpecialHandler>()
infix operator fun String.invoke(fn: SpecialHandler) = initH.set("${this}Api", fn)
infix fun List<String>.all(fn: SpecialHandler) = forEach { it(fn) }
infix fun String.static(fn: () -> Any) = serialize(this, fn()).let { resp -> this { resp } }
}