From 7e89339ed434db40fb6d0c20f4e6bdc60f789ab6 Mon Sep 17 00:00:00 2001 From: FhRh Date: Tue, 10 Dec 2024 21:53:09 +0900 Subject: [PATCH] =?UTF-8?q?feat(UserActivityManager)=20:=20DAU,=20MAU?= =?UTF-8?q?=EA=B3=84=EC=82=B0=20=EB=B0=8F=20export?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 로그인시 계정 없음에 대한 에러코드를 정의하였습니다. - 로그인시 하루 기준으로 중복되지 않는 사용자에 대해 카운트하고, metricRegistry를 통해 export 합니다. --- .../kotlin/com/wap/wabi/WabiApplication.kt | 2 + .../com/wap/wabi/auth/admin/entity/Admin.java | 13 ++++++ .../wabi/auth/admin/service/AdminService.kt | 11 ++++- .../auth/admin/util/UserActivityManager.kt | 42 +++++++++++++++++++ .../com/wap/wabi/exception/ErrorCode.kt | 1 + 5 files changed, 67 insertions(+), 2 deletions(-) create mode 100644 wabi/src/main/kotlin/com/wap/wabi/auth/admin/util/UserActivityManager.kt diff --git a/wabi/src/main/kotlin/com/wap/wabi/WabiApplication.kt b/wabi/src/main/kotlin/com/wap/wabi/WabiApplication.kt index ef2759f..594ffd4 100644 --- a/wabi/src/main/kotlin/com/wap/wabi/WabiApplication.kt +++ b/wabi/src/main/kotlin/com/wap/wabi/WabiApplication.kt @@ -4,10 +4,12 @@ import jakarta.annotation.PostConstruct import org.springframework.boot.autoconfigure.SpringBootApplication import org.springframework.boot.runApplication import org.springframework.data.jpa.repository.config.EnableJpaAuditing +import org.springframework.scheduling.annotation.EnableScheduling import java.util.TimeZone @SpringBootApplication @EnableJpaAuditing +@EnableScheduling class WabiApplication { @PostConstruct fun started() { diff --git a/wabi/src/main/kotlin/com/wap/wabi/auth/admin/entity/Admin.java b/wabi/src/main/kotlin/com/wap/wabi/auth/admin/entity/Admin.java index d33a627..8cb665a 100644 --- a/wabi/src/main/kotlin/com/wap/wabi/auth/admin/entity/Admin.java +++ b/wabi/src/main/kotlin/com/wap/wabi/auth/admin/entity/Admin.java @@ -116,4 +116,17 @@ public Admin build() { return new Admin(this); } } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + Admin admin = (Admin) o; + return id.equals(admin.id); + } + + @Override + public int hashCode() { + return id.hashCode(); + } } diff --git a/wabi/src/main/kotlin/com/wap/wabi/auth/admin/service/AdminService.kt b/wabi/src/main/kotlin/com/wap/wabi/auth/admin/service/AdminService.kt index c067aec..003ca74 100644 --- a/wabi/src/main/kotlin/com/wap/wabi/auth/admin/service/AdminService.kt +++ b/wabi/src/main/kotlin/com/wap/wabi/auth/admin/service/AdminService.kt @@ -7,6 +7,7 @@ import com.wap.wabi.auth.admin.payload.response.AdminLoginResponse import com.wap.wabi.auth.admin.repository.AdminRefreshTokenRepository import com.wap.wabi.auth.admin.repository.AdminRepository import com.wap.wabi.auth.admin.util.AdminValidator +import com.wap.wabi.auth.admin.util.UserActivityManager import com.wap.wabi.auth.jwt.JwtTokenProvider import com.wap.wabi.exception.ErrorCode import com.wap.wabi.exception.RestApiException @@ -20,7 +21,8 @@ class AdminService( private val adminRefreshTokenRepository: AdminRefreshTokenRepository, private val tokenProvider: JwtTokenProvider, private val adminValidator: AdminValidator, - private val encoder: PasswordEncoder + private val encoder: PasswordEncoder, + private val userActivityManager: UserActivityManager ) { @Transactional fun registerAdmin(adminRegisterRequest: AdminRegisterRequest) { @@ -36,7 +38,11 @@ class AdminService( adminValidator.validateLogin(adminLoginRequest) val admin = adminRepository.findByName(adminLoginRequest.name) ?.takeIf { admin -> - encoder.matches(adminLoginRequest.password, admin.get().password) + try { + encoder.matches(adminLoginRequest.password, admin.get().password) + } catch (e: Exception) { + throw RestApiException(ErrorCode.BAD_REQUEST_ADMIN_LOGIN) + } } ?: throw RestApiException(ErrorCode.BAD_REQUEST_NOT_EXIST_ADMIN) val refreshToken = tokenProvider.createRefreshToken() @@ -54,6 +60,7 @@ class AdminService( ) val accessToken = tokenProvider.createAccessToken("${admin.get().username}:${admin.get().role}") + userActivityManager.updateUserActivity(admin.get()) return AdminLoginResponse( name = admin.get().username, role = admin.get().role, diff --git a/wabi/src/main/kotlin/com/wap/wabi/auth/admin/util/UserActivityManager.kt b/wabi/src/main/kotlin/com/wap/wabi/auth/admin/util/UserActivityManager.kt new file mode 100644 index 0000000..ed45cba --- /dev/null +++ b/wabi/src/main/kotlin/com/wap/wabi/auth/admin/util/UserActivityManager.kt @@ -0,0 +1,42 @@ +package com.wap.wabi.auth.admin.util + +import com.wap.wabi.auth.admin.entity.Admin +import io.micrometer.core.instrument.Gauge +import io.micrometer.core.instrument.Metrics +import org.slf4j.LoggerFactory +import org.springframework.scheduling.annotation.Scheduled +import org.springframework.stereotype.Component + + +@Component +class UserActivityManager { + private var dauCounter = 0 + private var mauCounter = 0 + + private val activeUsersToday = HashSet() + + private val logger = LoggerFactory.getLogger(UserActivityManager::class.java) + + fun updateUserActivity(admin: Admin) { + if (activeUsersToday.add(admin)) { + dauCounter++ + mauCounter++ + } + + Gauge.builder("user_activity_dau") { dauCounter }.register(Metrics.globalRegistry) + Gauge.builder("user_activity_mau") { mauCounter }.register(Metrics.globalRegistry) + } + + @Scheduled(cron = "0 0 0 * * ?") + fun resetDailyActiveUser() { + dauCounter = 0 + activeUsersToday.clear() + logger.info("Reset daily active user") + } + + @Scheduled(cron = "0 0 0 1 * ?") + fun resetMonthlyActiveUser() { + mauCounter = 0 + logger.info("Reset monthly active user") + } +} diff --git a/wabi/src/main/kotlin/com/wap/wabi/exception/ErrorCode.kt b/wabi/src/main/kotlin/com/wap/wabi/exception/ErrorCode.kt index 2a95c5a..1ae478c 100644 --- a/wabi/src/main/kotlin/com/wap/wabi/exception/ErrorCode.kt +++ b/wabi/src/main/kotlin/com/wap/wabi/exception/ErrorCode.kt @@ -45,4 +45,5 @@ enum class ErrorCode( BAD_REQUEST_ADMIN_NAME(HttpStatus.BAD_REQUEST, "800-3", "이름이 형식에 맞지 않습니다."), BAD_REQUEST_EXIST_ADMIN(HttpStatus.BAD_REQUEST, "800-4", "이미 존재하는 어드민입니다."), BAD_REQUEST_NOT_EXIST_ADMIN(HttpStatus.BAD_REQUEST, "800-5", "존재하지 않는 어드민입니다."), + BAD_REQUEST_ADMIN_LOGIN(HttpStatus.BAD_REQUEST, "800-6", "일치하는 계정이 없습니다."), }