Skip to content

Commit

Permalink
feat: discord 알림 발송 기능 고도화
Browse files Browse the repository at this point in the history
  • Loading branch information
DongGeon0908 committed Sep 24, 2024
1 parent 20802e6 commit f0ec85a
Show file tree
Hide file tree
Showing 14 changed files with 155 additions and 27 deletions.
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package com.hero.alignlab.batch.statistics.job

import com.hero.alignlab.client.discord.DiscordWebhookService
import com.hero.alignlab.client.discord.config.DiscordWebhookClientConfig
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
Expand Down Expand Up @@ -113,10 +114,6 @@ class HeroStatisticsJob(

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

val text = countActiveUserTop3.await().joinToString("\n") {
"- uid: ${it.uid} / count: ${it.count}"
}

val message = """
**$title [${fromDate.format(formatter)} ~ ${toDate.format(formatter)}]**
Expand Down Expand Up @@ -145,7 +142,10 @@ class HeroStatisticsJob(
**이용자 현황**
- ${countActiveUserTop3.await().joinToString(" | ") { "uid : ${it.uid} count : ${it.count}" }}
""".trimIndent()
discordWebhookService.sendMessage(SendMessageRequest(message))
discordWebhookService.sendMessage(
channel = DiscordWebhookClientConfig.Config.Channel.STATISTICS,
request = SendMessageRequest(message)
)
}
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package com.hero.alignlab.client.discord

import com.hero.alignlab.client.discord.client.DiscordWebhookClient
import com.hero.alignlab.client.discord.config.DiscordWebhookClientConfig
import com.hero.alignlab.client.discord.model.request.SendMessageRequest
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
Expand All @@ -10,9 +11,12 @@ import org.springframework.stereotype.Service
class DiscordWebhookService(
private val discordWebhookClient: DiscordWebhookClient,
) {
suspend fun sendMessage(request: SendMessageRequest) {
suspend fun sendMessage(
channel: DiscordWebhookClientConfig.Config.Channel,
request: SendMessageRequest
) {
withContext(Dispatchers.IO) {
discordWebhookClient.sendMessage(request)
discordWebhookClient.sendMessage(channel, request)
}
}
}
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
package com.hero.alignlab.client.discord.client

import com.hero.alignlab.client.discord.config.DiscordWebhookClientConfig
import com.hero.alignlab.client.discord.model.request.SendMessageRequest
import com.hero.alignlab.client.discord.model.response.GetWebhookWithTokenResponse

interface DiscordWebhookClient {
suspend fun getWebhookWithToken(): GetWebhookWithTokenResponse
suspend fun getWebhookWithToken(channel: DiscordWebhookClientConfig.Config.Channel): GetWebhookWithTokenResponse

suspend fun sendMessage(request: SendMessageRequest)
suspend fun sendMessage(channel: DiscordWebhookClientConfig.Config.Channel, request: SendMessageRequest)
}
Original file line number Diff line number Diff line change
@@ -1,23 +1,34 @@
package com.hero.alignlab.client.discord.client

import com.hero.alignlab.client.discord.config.DiscordWebhookClientConfig.Config.Channel
import com.hero.alignlab.client.discord.config.DiscordWebhookClientConfig.Config.Token
import com.hero.alignlab.client.discord.model.request.SendMessageRequest
import com.hero.alignlab.client.discord.model.response.GetWebhookWithTokenResponse
import com.hero.alignlab.client.kakao.SuspendableClient
import com.hero.alignlab.exception.ErrorCode
import com.hero.alignlab.exception.NotFoundException
import org.springframework.web.reactive.function.client.WebClient

class SuspendableDiscordWebhookClient(
client: WebClient,
private val targets: Map<Channel, Token>,
) : DiscordWebhookClient, SuspendableClient(client) {
override suspend fun getWebhookWithToken(): GetWebhookWithTokenResponse {
override suspend fun getWebhookWithToken(channel: Channel): GetWebhookWithTokenResponse {
return client
.get()
.uri(resolveTargetToken(channel))
.request()
}

override suspend fun sendMessage(request: SendMessageRequest) {
override suspend fun sendMessage(channel: Channel, request: SendMessageRequest) {
return client
.post()
.uri(resolveTargetToken(channel))
.bodyValue(request)
.request()
}

private fun resolveTargetToken(channel: Channel): String {
return targets[channel]?.token ?: throw NotFoundException(ErrorCode.NOT_FOUND_TARGET_TOKEN)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -34,11 +34,21 @@ class DiscordWebhookClientConfig {

val webclient = WebClientFactory.generate(discordWebhookConfig.url)

return SuspendableDiscordWebhookClient(webclient)
return SuspendableDiscordWebhookClient(webclient, discordWebhookConfig.channels)
}

data class Config(
@field:NotBlank
var url: String = "",
)
var channels: Map<Channel, Token> = emptyMap()
) {
data class Token(
@field:NotBlank
var token: String = "",
)

enum class Channel {
STATISTICS, DISCUSSION;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -85,4 +85,45 @@ class DevDeleteService(
discussionRepository.deleteAllByUid(uid)
discussionCommentRepository.deleteAllByUid(uid)
}

@Transactional
fun deleteAllWithoutUser(uid: Long) {
/*
/** 유저 정보 삭제 */
credentialUserInfoRepository.deleteAllByUid(uid)
oauthUserInfoRepository.deleteAllByUid(uid)
userInfoRepository.deleteById(uid)
*/

/** pose layout 데이터 삭제 */
val poseLayoutIds = poseLayoutRepository.findAllByUid(uid).map { it.id }

poseLayoutRepository.deleteAllByUid(uid)
poseLayoutPointRepository.deleteAllByPoseLayoutIdIn(poseLayoutIds)

poseCountRepository.deleteAllByUid(uid)
val poseSnapshotIds = poseSnapshotRepository.findAllByUid(uid)
.map { it.id }

poseSnapshotRepository.deleteAllByUid(uid)
poseKeyPointSnapshotRepository.deleteAllByPoseSnapshotIdIn(poseSnapshotIds)

/** pose noti 제거 */
poseNotificationRepository.deleteAllByUid(uid)

/** syslog */
systemActionLogRepository.deleteAllByUid(uid)

/** img */
imageMetadataRepository.deleteAllByUid(uid)

/** group */
groupRepository.deleteAllByOwnerUid(uid)
groupUserRepository.deleteAllByUid(uid)
groupUserScoreRepository.deleteAllByUid(uid)

/** discussion */
discussionRepository.deleteAllByUid(uid)
discussionCommentRepository.deleteAllByUid(uid)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,6 @@ class DevDeleteResource(
dev: DevAuthUser,
@PathVariable uid: Long,
) {
devDeleteService.deleteAll(uid)
devDeleteService.deleteAllWithoutUser(uid)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ 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.config.DiscordWebhookClientConfig
import com.hero.alignlab.client.discord.model.request.SendMessageRequest
import com.hero.alignlab.config.swagger.SwaggerTag.DEV_TAG
import com.hero.alignlab.domain.auth.model.DevAuthUser
Expand All @@ -25,9 +26,10 @@ class DevDiscordWebhookResource(
@PostMapping("/api/dev/v1/discord-webhooks/{id}")
suspend fun sendMessage(
dev: DevAuthUser,
@RequestParam channel: DiscordWebhookClientConfig.Config.Channel,
@RequestParam message: String,
) {
discordWebhookClient.sendMessage(SendMessageRequest(message))
discordWebhookClient.sendMessage(channel, SendMessageRequest(message))
}

@Operation(summary = "discord webhook daily noti")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,15 @@ import com.hero.alignlab.domain.discussion.domain.Discussion
import com.hero.alignlab.domain.discussion.infrastructure.DiscussionRepository
import com.hero.alignlab.domain.discussion.model.request.DiscussionRequest
import com.hero.alignlab.domain.discussion.model.response.DiscussionResponse
import com.hero.alignlab.event.model.DiscussionEvent
import org.springframework.context.ApplicationEventPublisher
import org.springframework.stereotype.Service

@Service
class DiscussionService(
private val discussionRepository: DiscussionRepository,
private val txTemplates: TransactionTemplates,
private val publisher: ApplicationEventPublisher,
) {
suspend fun create(user: AuthUser, request: DiscussionRequest): DiscussionResponse {
val discussion = Discussion(
Expand All @@ -24,7 +27,11 @@ class DiscussionService(
)

val createdDiscussion = txTemplates.writer.coExecute {
discussionRepository.save(discussion)
val discussion = discussionRepository.save(discussion)

publisher.publishEvent(DiscussionEvent(discussion))

discussion
}

return DiscussionResponse.from(createdDiscussion)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
package com.hero.alignlab.event.listener

import com.hero.alignlab.client.discord.client.DiscordWebhookClient
import com.hero.alignlab.client.discord.config.DiscordWebhookClientConfig
import com.hero.alignlab.client.discord.model.request.SendMessageRequest
import com.hero.alignlab.domain.user.application.UserInfoService
import com.hero.alignlab.event.model.DiscussionEvent
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.launch
import org.springframework.stereotype.Component
import org.springframework.transaction.event.TransactionalEventListener
import java.time.LocalDateTime
import java.time.format.DateTimeFormatter

@Component
class DiscussionEventListener(
private val discordWebhookClient: DiscordWebhookClient,
private val userInfoService: UserInfoService,
) {
@TransactionalEventListener
fun handle(event: DiscussionEvent) {
CoroutineScope(Dispatchers.IO + Job()).launch {
val formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")

val userInfo = userInfoService.getUserInfo(event.discussion.uid)

val message = """
**문의하기 [${LocalDateTime.now().format(formatter)}]**
- 유저명 : ${userInfo.nickname}
- 이메일 : ${event.discussion.email ?: "없음"}
- type : ${event.discussion.type}
- title : ${event.discussion.title}
- content : ${event.discussion.content}
""".trimIndent()

discordWebhookClient.sendMessage(
channel = DiscordWebhookClientConfig.Config.Channel.DISCUSSION,
request = SendMessageRequest(message)
)
}
}
}
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
package com.hero.alignlab.event.listener

import com.hero.alignlab.domain.dev.application.DevDeleteService
import com.hero.alignlab.domain.group.application.GroupFacade
import com.hero.alignlab.domain.group.infrastructure.GroupRepository
import com.hero.alignlab.domain.notification.infrastructure.PoseNotificationRepository
import com.hero.alignlab.domain.pose.infrastructure.*
import com.hero.alignlab.event.model.WithdrawEvent
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
Expand All @@ -16,14 +15,7 @@ class WithdrawEventListener(
/** 탈퇴 회원이 그룹장인 경우 승계 작업 필요. */
private val groupFacade: GroupFacade,
private val groupRepository: GroupRepository,

/** 자세 데이터, 탈퇴회원이 있더라도 문제 없음. */
private val poseCountRepository: PoseCountRepository,
private val poseSnapshotRepository: PoseSnapshotRepository,
private val poseKeyPointSnapshotRepository: PoseKeyPointSnapshotRepository,
private val poseLayoutRepository: PoseLayoutRepository,
private val poseLayoutPointRepository: PoseLayoutPointRepository,
private val poseNotificationRepository: PoseNotificationRepository,
private val devDeleteService: DevDeleteService,
) {
/**
* 현상황에서 그룹 승계 작업만 원활히 진행되면, 그외 데이터의 경우 탈퇴로 인한 문제는 발생하지 않는다.
Expand All @@ -37,6 +29,8 @@ class WithdrawEventListener(
if (group != null) {
groupFacade.withdraw(group.id, event.uid)
}

devDeleteService.deleteAll(event.uid)
}
}
}
5 changes: 5 additions & 0 deletions src/main/kotlin/com/hero/alignlab/event/model/Event.kt
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package com.hero.alignlab.event.model
import com.hero.alignlab.common.extension.mapper
import com.hero.alignlab.common.extension.remoteIp
import com.hero.alignlab.domain.auth.model.AUTH_TOKEN_KEY
import com.hero.alignlab.domain.discussion.domain.Discussion
import com.hero.alignlab.domain.group.domain.Group
import com.hero.alignlab.domain.pose.domain.PoseSnapshot
import com.hero.alignlab.domain.pose.model.PoseSnapshotModel.KeyPoint
Expand Down Expand Up @@ -62,3 +63,7 @@ data class SystemActionLogEvent(
data class WithdrawEvent(
val uid: Long,
) : BaseEvent()

data class DiscussionEvent(
val discussion: Discussion,
) : BaseEvent()
3 changes: 3 additions & 0 deletions src/main/kotlin/com/hero/alignlab/exception/ErrorCode.kt
Original file line number Diff line number Diff line change
Expand Up @@ -59,5 +59,8 @@ enum class ErrorCode(val status: HttpStatus, val description: String) {

/** Pose Layout Error Code */
NOT_FOUND_POSE_LAYOUT_ERROR(HttpStatus.NOT_FOUND, "포즈 레이아웃을 찾을 수 없습니다."),

/** discord */
NOT_FOUND_TARGET_TOKEN(HttpStatus.NOT_FOUND, "디스코드 발송 토큰이 없습니다."),
;
}
7 changes: 6 additions & 1 deletion src/main/resources/config/application.yml
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,12 @@ client:
unlink-path: /v1/user/unlink
discord:
webhook:
url:
url: https://discord.com/api/webhooks
channels:
STATISTICS:
token:
DISCUSSION:
token:

encrypt:
key:
Expand Down

0 comments on commit f0ec85a

Please sign in to comment.