diff --git a/sql/DDL.sql b/sql/DDL.sql index 319a6fb..496d93e 100644 --- a/sql/DDL.sql +++ b/sql/DDL.sql @@ -88,6 +88,7 @@ CREATE TABLE `group_user_score` ) ENGINE=InnoDB AUTO_INCREMENT=200000 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='그룹 유저 스코어'; CREATE UNIQUE INDEX uidx__group_user_id ON group_user_score (group_user_id); CREATE INDEX idx__group_id__group_user_id ON group_user_score (group_id, group_user_id); +CREATE INDEX idx__uid ON group_user_score (uid); -- 포즈 스냅샵 CREATE TABLE `pose_snapshot` diff --git a/src/main/kotlin/com/hero/alignlab/domain/group/application/GroupUserScoreService.kt b/src/main/kotlin/com/hero/alignlab/domain/group/application/GroupUserScoreService.kt index 137c783..9e3f189 100644 --- a/src/main/kotlin/com/hero/alignlab/domain/group/application/GroupUserScoreService.kt +++ b/src/main/kotlin/com/hero/alignlab/domain/group/application/GroupUserScoreService.kt @@ -5,6 +5,7 @@ import com.hero.alignlab.domain.group.infrastructure.GroupUserScoreRepository import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext import org.springframework.stereotype.Service +import org.springframework.transaction.annotation.Transactional @Service class GroupUserScoreService( @@ -15,4 +16,30 @@ class GroupUserScoreService( groupUserScoreRepository.findAllByGroupId(groupId) } } + + suspend fun findByUidOrNull(uid: Long): GroupUserScore? { + return withContext(Dispatchers.IO) { + groupUserScoreRepository.findByUid(uid) + } + } + + suspend fun findAllByGroupUserIdIn(groupdUserIds: List): List { + return withContext(Dispatchers.IO) { + groupUserScoreRepository.findAllByGroupUserIdIn(groupdUserIds) + } + } + + @Transactional + fun saveSync(groupUserScore: GroupUserScore): GroupUserScore { + return groupUserScoreRepository.save(groupUserScore) + } + + @Transactional + fun saveAllSync(groupUserScores: List): List { + return groupUserScoreRepository.saveAll(groupUserScores) + } + + fun findAllByGroupIdAndUidsSync(groupId: Long, uids: Set): List { + return groupUserScoreRepository.findAllByGroupIdAndUidIn(groupId, uids) + } } diff --git a/src/main/kotlin/com/hero/alignlab/domain/group/application/GroupUserService.kt b/src/main/kotlin/com/hero/alignlab/domain/group/application/GroupUserService.kt index bfd1c8f..ffea215 100644 --- a/src/main/kotlin/com/hero/alignlab/domain/group/application/GroupUserService.kt +++ b/src/main/kotlin/com/hero/alignlab/domain/group/application/GroupUserService.kt @@ -26,7 +26,7 @@ class GroupUserService( suspend fun findAllByUid(uid: Long): List { return withContext(Dispatchers.IO) { - findAllByUid(uid) + findAllByUidSync(uid) } } diff --git a/src/main/kotlin/com/hero/alignlab/domain/group/infrastructure/GroupUserScoreRepository.kt b/src/main/kotlin/com/hero/alignlab/domain/group/infrastructure/GroupUserScoreRepository.kt index c630f38..18a7ecf 100644 --- a/src/main/kotlin/com/hero/alignlab/domain/group/infrastructure/GroupUserScoreRepository.kt +++ b/src/main/kotlin/com/hero/alignlab/domain/group/infrastructure/GroupUserScoreRepository.kt @@ -9,4 +9,10 @@ import org.springframework.transaction.annotation.Transactional @Repository interface GroupUserScoreRepository : JpaRepository { fun findAllByGroupId(groupId: Long): List + + fun findByUid(uid: Long): GroupUserScore? + + fun findAllByGroupUserIdIn(groupUserIds: List): List + + fun findAllByGroupIdAndUidIn(groupId: Long, uids: Set): List } diff --git a/src/main/kotlin/com/hero/alignlab/domain/pose/domain/vo/PoseType.kt b/src/main/kotlin/com/hero/alignlab/domain/pose/domain/vo/PoseType.kt index 24f32b9..d771a08 100644 --- a/src/main/kotlin/com/hero/alignlab/domain/pose/domain/vo/PoseType.kt +++ b/src/main/kotlin/com/hero/alignlab/domain/pose/domain/vo/PoseType.kt @@ -16,4 +16,8 @@ enum class PoseType(val nameKor: String) { @JsonEnumDefaultValue UNKNOWN("예외 타입"), ; + + companion object { + val BAD_POSE = setOf(TURTLE_NECK, SHOULDER_TWIST, CHIN_UTP, TURTLE_NECK) + } } diff --git a/src/main/kotlin/com/hero/alignlab/event/listener/PoseSnapshotListener.kt b/src/main/kotlin/com/hero/alignlab/event/listener/PoseSnapshotListener.kt index 9a163b5..88b6305 100644 --- a/src/main/kotlin/com/hero/alignlab/event/listener/PoseSnapshotListener.kt +++ b/src/main/kotlin/com/hero/alignlab/event/listener/PoseSnapshotListener.kt @@ -2,11 +2,15 @@ package com.hero.alignlab.event.listener import com.hero.alignlab.common.extension.coExecuteOrNull import com.hero.alignlab.config.database.TransactionTemplates +import com.hero.alignlab.domain.group.application.GroupUserScoreService +import com.hero.alignlab.domain.group.application.GroupUserService +import com.hero.alignlab.domain.group.domain.GroupUserScore import com.hero.alignlab.domain.pose.application.PoseCountService import com.hero.alignlab.domain.pose.application.PoseKeyPointSnapshotService import com.hero.alignlab.domain.pose.domain.PoseCount import com.hero.alignlab.domain.pose.domain.PoseKeyPointSnapshot import com.hero.alignlab.domain.pose.domain.vo.PoseTotalCount +import com.hero.alignlab.domain.pose.domain.vo.PoseType.Companion.BAD_POSE import com.hero.alignlab.event.model.LoadPoseSnapshot import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers @@ -19,6 +23,8 @@ import org.springframework.transaction.event.TransactionalEventListener class PoseSnapshotListener( private val poseKeyPointSnapshotService: PoseKeyPointSnapshotService, private val poseCountService: PoseCountService, + private val groupUserScoreService: GroupUserScoreService, + private val groupUserService: GroupUserService, private val txTemplates: TransactionTemplates, ) { @TransactionalEventListener @@ -52,9 +58,28 @@ class PoseSnapshotListener( date = targetDate ) + val score = poseCount.totalCount.count + .filter { (key, _) -> key in BAD_POSE } + .values + .sum() + + val groupUsers = groupUserService.findAllByUid(event.poseSnapshot.uid) + val groupUserScore = groupUserScoreService.findAllByGroupUserIdIn(groupUsers.map { it.id }) + .associateBy { it.groupUserId } + + val needToUpdateScores = groupUsers.map { groupUser -> + groupUserScore[groupUser.id] ?: GroupUserScore( + groupId = groupUser.groupId, + groupUserId = groupUser.id, + uid = groupUser.uid, + score = score + ) + } + txTemplates.writer.coExecuteOrNull { poseKeyPointSnapshotService.bulkSave(keyPoints) poseCountService.saveSync(poseCount) + groupUserScoreService.saveAllSync(needToUpdateScores) } } } diff --git a/src/main/kotlin/com/hero/alignlab/ws/handler/ReactiveConcurrentUserWebSocketHandler.kt b/src/main/kotlin/com/hero/alignlab/ws/handler/ReactiveConcurrentUserWebSocketHandler.kt index 5fc6a27..61e7d3c 100644 --- a/src/main/kotlin/com/hero/alignlab/ws/handler/ReactiveConcurrentUserWebSocketHandler.kt +++ b/src/main/kotlin/com/hero/alignlab/ws/handler/ReactiveConcurrentUserWebSocketHandler.kt @@ -4,6 +4,7 @@ import com.hero.alignlab.common.extension.mapper import com.hero.alignlab.domain.auth.application.AuthFacade import com.hero.alignlab.domain.auth.model.AUTH_TOKEN_KEY import com.hero.alignlab.domain.auth.model.AuthUserToken +import com.hero.alignlab.domain.group.application.GroupUserScoreService import com.hero.alignlab.domain.group.application.GroupUserService import com.hero.alignlab.domain.user.application.UserInfoService import com.hero.alignlab.exception.ErrorCode @@ -27,6 +28,7 @@ class ReactiveConcurrentUserWebSocketHandler( private val authFacade: AuthFacade, private val userInfoService: UserInfoService, private val groupUserService: GroupUserService, + private val groupUserScoreService: GroupUserScoreService, ) : WebSocketHandler { private val logger = KotlinLogging.logger { } @@ -78,9 +80,11 @@ class ReactiveConcurrentUserWebSocketHandler( val userInfoByUid = userInfoService.findAllByIdsSync(uids.toList()).associateBy { it.id } val groupUserss = groupUserService.findAllByGroupIdAndUidsSync(groupId, userInfoByUid.keys) .associateBy { it.uid } + val groupUserScores = groupUserScoreService.findAllByGroupIdAndUidsSync(groupId, userInfoByUid.keys) + .associateBy { it.uid } uidBySession.forEach { (_, websocketSession) -> - val message = ConcurrentMessage.of(groupId, userInfoByUid, groupUserss) + val message = ConcurrentMessage.of(groupId, userInfoByUid, groupUserss, groupUserScores) .run { mapper.writeValueAsString(this) } websocketSession @@ -130,8 +134,10 @@ class ReactiveConcurrentUserWebSocketHandler( private suspend fun sendUpdatedGroupStatus(groupId: Long, uidBySession: MutableMap) { val userInfoByUid = userInfoService.findAllByIds(uidBySession.keys.toList()).associateBy { it.id } val groupUsers = groupUserService.findAllByGroupIdAndUids(groupId, userInfoByUid.keys).associateBy { it.uid } + val groupUserScores = groupUserScoreService.findAllByGroupIdAndUidsSync(groupId, userInfoByUid.keys) + .associateBy { it.uid } - val message = ConcurrentMessage.of(groupId, userInfoByUid, groupUsers) + val message = ConcurrentMessage.of(groupId, userInfoByUid, groupUsers, groupUserScores) .run { mapper.writeValueAsString(this) } uidBySession.forEach { (_, websocketSession) -> diff --git a/src/main/kotlin/com/hero/alignlab/ws/model/ConcurrentMessage.kt b/src/main/kotlin/com/hero/alignlab/ws/model/ConcurrentMessage.kt index 4dc7458..dbaa321 100644 --- a/src/main/kotlin/com/hero/alignlab/ws/model/ConcurrentMessage.kt +++ b/src/main/kotlin/com/hero/alignlab/ws/model/ConcurrentMessage.kt @@ -1,8 +1,10 @@ package com.hero.alignlab.ws.model import com.hero.alignlab.domain.group.domain.GroupUser +import com.hero.alignlab.domain.group.domain.GroupUserScore import com.hero.alignlab.domain.user.domain.UserInfo import java.time.LocalDateTime +import java.util.concurrent.atomic.AtomicInteger data class ConcurrentMessage( val timestamp: LocalDateTime = LocalDateTime.now(), @@ -21,8 +23,11 @@ data class ConcurrentMessage( fun of( groupId: Long, userInfoByUid: Map, - groupUserss: Map + groupUserss: Map, + groupUserSocres: Map ): ConcurrentMessage { + val rank = AtomicInteger(1) + return ConcurrentMessage( groupId = groupId, groupUsers = userInfoByUid.mapNotNull { (uid, info) -> @@ -32,9 +37,8 @@ data class ConcurrentMessage( groupUserId = groupUSer.id, uid = uid, nickname = info.nickname, - // 더미 데이터 - rank = 1, - score = 1 + rank = rank.getAndIncrement(), + score = groupUserSocres[uid]?.score ?: 0, ) } )