Skip to content

Commit

Permalink
feat: 극락 통계 알림
Browse files Browse the repository at this point in the history
  • Loading branch information
DongGeon0908 committed Aug 18, 2024
1 parent fae691f commit 13eab71
Show file tree
Hide file tree
Showing 18 changed files with 207 additions and 14 deletions.
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package com.hero.alignlab.batch.job
package com.hero.alignlab.batch.posecount.job

import com.hero.alignlab.common.extension.executesOrNull
import com.hero.alignlab.config.database.TransactionTemplates
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
package com.hero.alignlab.batch.scheduler
package com.hero.alignlab.batch.posecount.scheduler

import com.hero.alignlab.batch.job.PoseCountUpdateJob
import com.hero.alignlab.batch.posecount.job.PoseCountUpdateJob
import com.hero.alignlab.common.extension.CoroutineExtension.retryOnError
import com.hero.alignlab.common.extension.resolveCancellation
import io.github.oshai.kotlinlogging.KotlinLogging
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
package com.hero.alignlab.batch.statistics.job

import com.hero.alignlab.client.discord.DiscordWebhookService
import com.hero.alignlab.client.discord.model.request.SendMessageRequest
import com.hero.alignlab.domain.discussion.infrastructure.DiscussionRepository
import com.hero.alignlab.domain.group.infrastructure.GroupRepository
import com.hero.alignlab.domain.group.infrastructure.GroupUserRepository
import com.hero.alignlab.domain.log.infrastructure.SystemActionLogRepository
import com.hero.alignlab.domain.notification.infrastructure.PoseNotificationRepository
import com.hero.alignlab.domain.pose.infrastructure.PoseSnapshotRepository
import com.hero.alignlab.domain.user.infrastructure.CredentialUserInfoRepository
import com.hero.alignlab.domain.user.infrastructure.OAuthUserInfoRepository
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.async
import kotlinx.coroutines.coroutineScope
import org.springframework.stereotype.Component
import java.time.LocalDateTime
import java.time.format.DateTimeFormatter

@Component
class HeroStatisticsJob(
private val discordWebhookService: DiscordWebhookService,

/** repository */
private val groupRepository: GroupRepository,
private val groupUserRepository: GroupUserRepository,
private val discussionRepository: DiscussionRepository,
private val imageRepository: GroupRepository,
private val systemActionLogRepository: SystemActionLogRepository,
private val poseNotificationRepository: PoseNotificationRepository,
private val poseSnapshotRepository: PoseSnapshotRepository,
private val credentialUserInfoRepository: CredentialUserInfoRepository,
private val oAuthUserInfoRepository: OAuthUserInfoRepository,
private val userInfoRepository: CredentialUserInfoRepository,
) {
suspend fun sendHeroStatistics(title: String, fromDate: LocalDateTime, toDate: LocalDateTime) {
coroutineScope {
val groupCount = async(Dispatchers.IO) {
groupRepository.countByCreatedAtBetween(fromDate, toDate)
}.await()
val groupUserCount = async(Dispatchers.IO) {
groupUserRepository.countByCreatedAtBetween(fromDate, toDate)
}.await()
val discussionCount = async(Dispatchers.IO) {
discussionRepository.countByCreatedAtBetween(fromDate, toDate)
}.await()
val syslogCount = async(Dispatchers.IO) {
systemActionLogRepository.countByCreatedAtBetween(fromDate, toDate)
}.await()
val poseNotificationCount = async(Dispatchers.IO) {
poseNotificationRepository.countByCreatedAtBetween(fromDate, toDate)
}.await()
val poseSnapshotCount = async(Dispatchers.IO) {
poseSnapshotRepository.countByCreatedAtBetween(fromDate, toDate)
}.await()
val credentialUserInfoCount = async(Dispatchers.IO) {
credentialUserInfoRepository.countByCreatedAtBetween(fromDate, toDate)
}.await()
val oAuthUserInfoCount = async(Dispatchers.IO) {
oAuthUserInfoRepository.countByCreatedAtBetween(fromDate, toDate)
}.await()
val userInfoCount = async(Dispatchers.IO) {
userInfoRepository.countByCreatedAtBetween(fromDate, toDate)
}.await()
val imageCount = async(Dispatchers.IO) {
imageRepository.countByCreatedAtBetween(fromDate, toDate)
}.await()

val formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")

val message = """
$title
- targetDate: ${fromDate.format(formatter)} ~ ${toDate.format(formatter)}
- 그룹 생성수 : $groupCount
- 그룹 유저 생성수 : $groupUserCount
- 문의하기 생성수 : $discussionCount
- api 호출량 : $syslogCount
- 포즈 알림 설정수 : $poseNotificationCount
- 포즈 스냅샷 생성수 : $poseSnapshotCount
- 일반 회원가입수 : $credentialUserInfoCount
- OAuth 회원가입수 : $oAuthUserInfoCount
- 유저 생성수 : $userInfoCount
- 이미지 생성수 : $imageCount
""".trimIndent()

discordWebhookService.sendMessage(SendMessageRequest(message))
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
package com.hero.alignlab.batch.statistics.scheduler

import com.hero.alignlab.batch.statistics.job.HeroStatisticsJob
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.launch
import org.springframework.scheduling.annotation.Scheduled
import org.springframework.stereotype.Component
import java.time.LocalDateTime

@Component
class HeroStatisticsScheduler(
private val heroStatisticsJob: HeroStatisticsJob
) {
@Scheduled(cron = "0 0 0/1 * * *")
fun dailyRunJob() {
CoroutineScope(Dispatchers.IO + Job()).launch {
val toDate = LocalDateTime.now()
val fromDate = LocalDateTime.now().minusHours(1)

heroStatisticsJob.sendHeroStatistics(
title = "극락통계 1시간 단위 [$fromDate ~ $toDate]",
fromDate = fromDate,
toDate = toDate
)
}
}

@Scheduled(cron = "0 0 9 * * *")
fun runDailySummary() {
CoroutineScope(Dispatchers.IO + Job()).launch {
val toDate = LocalDateTime.now()
val fromDate = LocalDateTime.now().minusDays(1)

heroStatisticsJob.sendHeroStatistics(
title = "극락통계 1일 단위 [$fromDate ~ $toDate]",
fromDate = fromDate,
toDate = toDate
)
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package com.hero.alignlab.client.discord

import com.hero.alignlab.client.discord.client.DiscordWebhookClient
import com.hero.alignlab.client.discord.model.request.SendMessageRequest
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import org.springframework.stereotype.Service

@Service
class DiscordWebhookService(
private val discordWebhookClient: DiscordWebhookClient,
) {
suspend fun sendMessage(request: SendMessageRequest) {
withContext(Dispatchers.IO) {
discordWebhookClient.sendMessage(request)
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import com.hero.alignlab.client.discord.model.request.SendMessageRequest
import com.hero.alignlab.client.discord.model.response.GetWebhookWithTokenResponse

interface DiscordWebhookClient {
suspend fun getWebhookWithToken(id: Int): GetWebhookWithTokenResponse
suspend fun getWebhookWithToken(): GetWebhookWithTokenResponse

suspend fun sendMessage(id: Int, request: SendMessageRequest)
suspend fun sendMessage(request: SendMessageRequest)
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,13 @@ import org.springframework.web.reactive.function.client.WebClient
class SuspendableDiscordWebhookClient(
client: WebClient,
) : DiscordWebhookClient, SuspendableClient(client) {
override suspend fun getWebhookWithToken(id: Int): GetWebhookWithTokenResponse {
override suspend fun getWebhookWithToken(): GetWebhookWithTokenResponse {
return client
.get()
.request()
}

override suspend fun sendMessage(id: Int, request: SendMessageRequest) {
override suspend fun sendMessage(request: SendMessageRequest) {
return client
.post()
.bodyValue(request)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.hero.alignlab.domain.dev.resource

import com.hero.alignlab.batch.statistics.job.HeroStatisticsJob
import com.hero.alignlab.client.discord.client.DiscordWebhookClient
import com.hero.alignlab.client.discord.model.request.SendMessageRequest
import com.hero.alignlab.config.dev.DevResourceCheckConfig.Companion.devResource
Expand All @@ -8,20 +9,32 @@ import io.swagger.v3.oas.annotations.Operation
import io.swagger.v3.oas.annotations.tags.Tag
import org.springframework.http.MediaType
import org.springframework.web.bind.annotation.*
import java.time.LocalDateTime

@Tag(name = DEV_TAG)
@RestController
@RequestMapping(produces = [MediaType.APPLICATION_JSON_VALUE])
class DevDiscordWebhookResource(
private val discordWebhookClient: DiscordWebhookClient,
private val statisticsJob: HeroStatisticsJob,
) {
@Operation(summary = "discord webhook test")
@PostMapping("/api/dev/v1/discord-webhooks/{id}")
suspend fun sendMessage(
@PathVariable id: Int,
@RequestHeader("X-HERO-DEV-TOKEN") token: String,
@RequestParam message: String,
) = devResource(token) {
discordWebhookClient.sendMessage(id, SendMessageRequest(message))
discordWebhookClient.sendMessage(SendMessageRequest(message))
}

@Operation(summary = "discord webhook daily noti")
@PostMapping("/api/dev/v1/discord-webhooks/daily-noti")
suspend fun sendDailyNoti(
@RequestHeader("X-HERO-DEV-TOKEN") token: String,
) = devResource(token) {
val toDate = LocalDateTime.now()
val fromDate = LocalDateTime.now().minusHours(1)

statisticsJob.sendHeroStatistics("극락통계 테스트", fromDate, toDate)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,10 @@ import com.hero.alignlab.domain.discussion.domain.Discussion
import org.springframework.data.jpa.repository.JpaRepository
import org.springframework.stereotype.Repository
import org.springframework.transaction.annotation.Transactional
import java.time.LocalDateTime

@Transactional(readOnly = true)
@Repository
interface DiscussionRepository : JpaRepository<Discussion, Long>
interface DiscussionRepository : JpaRepository<Discussion, Long> {
fun countByCreatedAtBetween(startAt: LocalDateTime, endAt: LocalDateTime): Long
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,14 @@ import com.hero.alignlab.domain.group.domain.Group
import org.springframework.data.jpa.repository.JpaRepository
import org.springframework.stereotype.Repository
import org.springframework.transaction.annotation.Transactional
import java.time.LocalDateTime

@Repository
@Transactional(readOnly = true)
interface GroupRepository : JpaRepository<Group, Long> {
fun existsByName(name: String): Boolean

fun findByIdAndOwnerUid(id: Long, ownerUid: Long): Group?

fun countByCreatedAtBetween(startAt: LocalDateTime, endAt: LocalDateTime): Long
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import org.springframework.data.domain.Pageable
import org.springframework.data.jpa.repository.JpaRepository
import org.springframework.stereotype.Repository
import org.springframework.transaction.annotation.Transactional
import java.time.LocalDateTime

@Transactional(readOnly = true)
@Repository
Expand All @@ -23,4 +24,6 @@ interface GroupUserRepository : JpaRepository<GroupUser, Long> {
fun findAllByGroupId(groupId: Long, pageable: Pageable): Page<GroupUser>

fun existsByUid(uid: Long): Boolean

fun countByCreatedAtBetween(startAt: LocalDateTime, endAt: LocalDateTime): Long
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,10 @@ import com.hero.alignlab.domain.image.domain.ImageMetadata
import org.springframework.data.jpa.repository.JpaRepository
import org.springframework.stereotype.Repository
import org.springframework.transaction.annotation.Transactional
import java.time.LocalDateTime

@Transactional(readOnly = true)
@Repository
interface ImageMetadataRepository : JpaRepository<ImageMetadata, Long>
interface ImageMetadataRepository : JpaRepository<ImageMetadata, Long> {
fun countByCreatedAtBetween(startAt: LocalDateTime, endAt: LocalDateTime): Long
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,10 @@ import com.hero.alignlab.domain.log.domain.SystemActionLog
import org.springframework.data.jpa.repository.JpaRepository
import org.springframework.stereotype.Repository
import org.springframework.transaction.annotation.Transactional
import java.time.LocalDateTime

@Transactional(readOnly = true)
@Repository
interface SystemActionLogRepository : JpaRepository<SystemActionLog, Long>
interface SystemActionLogRepository : JpaRepository<SystemActionLog, Long> {
fun countByCreatedAtBetween(startAt: LocalDateTime, endAt: LocalDateTime): Long
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,13 @@ package com.hero.alignlab.domain.notification.infrastructure
import com.hero.alignlab.domain.notification.domain.PoseNotification
import org.springframework.data.jpa.repository.JpaRepository
import org.springframework.transaction.annotation.Transactional
import java.time.LocalDateTime

@Transactional(readOnly = true)
interface PoseNotificationRepository : JpaRepository<PoseNotification, Long> {
fun findByUidAndIsActive(uid: Long, isActive: Boolean): PoseNotification?

fun findByUid(uid: Long): PoseNotification?

fun countByCreatedAtBetween(startAt: LocalDateTime, endAt: LocalDateTime): Long
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,13 @@ import org.springframework.data.jpa.repository.support.QuerydslRepositorySupport
import org.springframework.stereotype.Repository
import org.springframework.transaction.annotation.Transactional
import java.time.LocalDate
import java.time.LocalDateTime

@Transactional(readOnly = true)
@Repository
interface PoseSnapshotRepository : JpaRepository<PoseSnapshot, Long>, PoseSnapshotQRepository
interface PoseSnapshotRepository : JpaRepository<PoseSnapshot, Long>, PoseSnapshotQRepository {
fun countByCreatedAtBetween(startAt: LocalDateTime, endAt: LocalDateTime): Long
}

@Transactional(readOnly = true)
interface PoseSnapshotQRepository {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,14 @@ import com.hero.alignlab.domain.user.domain.CredentialUserInfo
import org.springframework.data.jpa.repository.JpaRepository
import org.springframework.stereotype.Repository
import org.springframework.transaction.annotation.Transactional
import java.time.LocalDateTime

@Transactional(readOnly = true)
@Repository
interface CredentialUserInfoRepository : JpaRepository<CredentialUserInfo, Long> {
fun existsByUsername(username: String): Boolean

fun findByUsernameAndPassword(username: String, password: EncryptData): CredentialUserInfo?

fun countByCreatedAtBetween(startAt: LocalDateTime, endAt: LocalDateTime): Long
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import com.hero.alignlab.domain.user.domain.OAuthUserInfo
import org.springframework.data.jpa.repository.JpaRepository
import org.springframework.stereotype.Repository
import org.springframework.transaction.annotation.Transactional
import java.time.LocalDateTime

@Transactional(readOnly = true)
@Repository
Expand All @@ -14,4 +15,6 @@ interface OAuthUserInfoRepository : JpaRepository<OAuthUserInfo, Long> {
fun deleteByOauthIdAndProvider(oauthId: String, provider: OAuthProvider)

fun findByProviderAndOauthId(provider: OAuthProvider, oauthId: String): OAuthUserInfo?

fun countByCreatedAtBetween(startAt: LocalDateTime, endAt: LocalDateTime): Long
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,13 @@ import org.springframework.data.jpa.repository.JpaRepository
import org.springframework.data.jpa.repository.support.QuerydslRepositorySupport
import org.springframework.stereotype.Repository
import org.springframework.transaction.annotation.Transactional
import java.time.LocalDateTime

@Transactional(readOnly = true)
@Repository
interface UserInfoRepository : JpaRepository<UserInfo, Long>, UserInfoQRepository
interface UserInfoRepository : JpaRepository<UserInfo, Long>, UserInfoQRepository {
fun countByCreatedAtBetween(startAt: LocalDateTime, endAt: LocalDateTime): Long
}

@Transactional(readOnly = true)
interface UserInfoQRepository {
Expand Down

0 comments on commit 13eab71

Please sign in to comment.