From 3a848f2fa94a4f8a1581611b37de4dbbe94bd661 Mon Sep 17 00:00:00 2001 From: DongGeon0908 Date: Tue, 30 Jul 2024 12:55:14 +0900 Subject: [PATCH] =?UTF-8?q?feat:=20pose=20snapshot=20=EC=B2=98=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- sql/DDL.sql | 29 ++++++++- .../extension/TransactionTemplateExtension.kt | 2 - .../PoseKeyPointSnapshotService.kt | 16 +++++ .../pose/application/PoseSnapshotFacade.kt | 36 +++++++++++ .../pose/application/PoseSnapshotService.kt | 16 +++++ .../pose/domain/PoseKeyPointSnapshot.kt | 30 +++++++++ .../domain/pose/domain/PosePosition.kt | 22 +++++++ .../domain/pose/domain/PoseSnapshot.kt | 20 ++++++ .../PoseKeyPointSnapshotRepository.kt | 10 +++ .../infrastructure/PoseSnapshotRepository.kt | 10 +++ .../domain/pose/model/PoseSnapshotModel.kt | 61 +++++++++++++++++++ .../pose/model/request/PoseSnapshotRequest.kt | 8 +++ .../model/response/PoseSnapshotResponse.kt | 20 ++++++ .../pose/resource/PoseSnapshotResource.kt | 27 ++++++++ .../event/listener/PoseSnapshotListener.kt | 38 ++++++++++++ .../com/hero/alignlab/event/model/Event.kt | 7 +++ 16 files changed, 349 insertions(+), 3 deletions(-) create mode 100644 src/main/kotlin/com/hero/alignlab/domain/pose/application/PoseKeyPointSnapshotService.kt create mode 100644 src/main/kotlin/com/hero/alignlab/domain/pose/application/PoseSnapshotFacade.kt create mode 100644 src/main/kotlin/com/hero/alignlab/domain/pose/application/PoseSnapshotService.kt create mode 100644 src/main/kotlin/com/hero/alignlab/domain/pose/domain/PoseKeyPointSnapshot.kt create mode 100644 src/main/kotlin/com/hero/alignlab/domain/pose/domain/PosePosition.kt create mode 100644 src/main/kotlin/com/hero/alignlab/domain/pose/domain/PoseSnapshot.kt create mode 100644 src/main/kotlin/com/hero/alignlab/domain/pose/infrastructure/PoseKeyPointSnapshotRepository.kt create mode 100644 src/main/kotlin/com/hero/alignlab/domain/pose/infrastructure/PoseSnapshotRepository.kt create mode 100644 src/main/kotlin/com/hero/alignlab/domain/pose/model/PoseSnapshotModel.kt create mode 100644 src/main/kotlin/com/hero/alignlab/domain/pose/model/request/PoseSnapshotRequest.kt create mode 100644 src/main/kotlin/com/hero/alignlab/domain/pose/model/response/PoseSnapshotResponse.kt create mode 100644 src/main/kotlin/com/hero/alignlab/domain/pose/resource/PoseSnapshotResource.kt create mode 100644 src/main/kotlin/com/hero/alignlab/event/listener/PoseSnapshotListener.kt diff --git a/sql/DDL.sql b/sql/DDL.sql index fdbbecc..b330304 100644 --- a/sql/DDL.sql +++ b/sql/DDL.sql @@ -59,4 +59,31 @@ CREATE TABLE `group_user` ) ENGINE=InnoDB AUTO_INCREMENT=200000 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='그룹 유저'; CREATE UNIQUE INDEX uidx__group_id__uid ON group_user (group_id, uid); -CREATE INDEX uidx__uid ON group_user (uid); +CREATE INDEX idx__uid ON group_user (uid); + +-- 포즈 스냅샵 +CREATE TABLE `pose_snapshot` +( + `id` bigint NOT NULL AUTO_INCREMENT COMMENT 'pose snapshot id', + `uid` bigint NOT NULL COMMENT 'uid', + `score` DECIMAL(20, 16) NOT NULL COMMENT '포즈 신뢰도 종합', + `created_at` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '생성일', + `modified_at` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '수정일', + PRIMARY KEY (`id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='포즈 스냅샷'; +CREATE INDEX idx__uid ON pose_snapshot (uid); + +-- 포즈 key point 스냅샷 +CREATE TABLE `pose_key_point_snapshot` +( + `id` bigint NOT NULL AUTO_INCREMENT COMMENT 'pose key point snapshot id', + `pose_snapshot_id` bigint NOT NULL COMMENT 'post snapshot id', + `position` VARCHAR(32) NOT NULL COMMENT '스냅샷 위치', + `x` DECIMAL(20, 16) NOT NULL COMMENT 'x 좌표', + `y` DECIMAL(20, 16) NOT NULL COMMENT 'y 좌표', + `confidence` DECIMAL(20, 16) NOT NULL COMMENT '신뢰도', + `created_at` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '생성일', + `modified_at` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '수정일', + PRIMARY KEY (`id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='포즈 key point'; +CREATE INDEX uidx__pose_snapshot_id__position ON pose_key_point_snapshot (pose_snapshot_id, position); diff --git a/src/main/kotlin/com/hero/alignlab/common/extension/TransactionTemplateExtension.kt b/src/main/kotlin/com/hero/alignlab/common/extension/TransactionTemplateExtension.kt index 0ff1acd..39bd460 100644 --- a/src/main/kotlin/com/hero/alignlab/common/extension/TransactionTemplateExtension.kt +++ b/src/main/kotlin/com/hero/alignlab/common/extension/TransactionTemplateExtension.kt @@ -27,7 +27,6 @@ suspend fun TransactionTemplate.coExecuteOrNull( } } - fun TransactionTemplate.executes( actions: TransactionCallback, ): RETURN { @@ -37,7 +36,6 @@ fun TransactionTemplate.executes( ?: throw FailToExecuteException(ErrorCode.FAIL_TO_TRANSACTION_TEMPLATE_EXECUTE_ERROR) } - fun TransactionTemplate.executesOrNull( actions: TransactionCallback, ): RETURN? { diff --git a/src/main/kotlin/com/hero/alignlab/domain/pose/application/PoseKeyPointSnapshotService.kt b/src/main/kotlin/com/hero/alignlab/domain/pose/application/PoseKeyPointSnapshotService.kt new file mode 100644 index 0000000..700fdc0 --- /dev/null +++ b/src/main/kotlin/com/hero/alignlab/domain/pose/application/PoseKeyPointSnapshotService.kt @@ -0,0 +1,16 @@ +package com.hero.alignlab.domain.pose.application + +import com.hero.alignlab.domain.pose.domain.PoseKeyPointSnapshot +import com.hero.alignlab.domain.pose.infrastructure.PoseKeyPointSnapshotRepository +import org.springframework.stereotype.Service +import org.springframework.transaction.annotation.Transactional + +@Service +class PoseKeyPointSnapshotService( + private val poseKeyPointSnapshotRepository: PoseKeyPointSnapshotRepository, +) { + @Transactional + fun saveAllSync(poseKeyPointSnapshots: List): List { + return poseKeyPointSnapshotRepository.saveAll(poseKeyPointSnapshots) + } +} diff --git a/src/main/kotlin/com/hero/alignlab/domain/pose/application/PoseSnapshotFacade.kt b/src/main/kotlin/com/hero/alignlab/domain/pose/application/PoseSnapshotFacade.kt new file mode 100644 index 0000000..0bf8175 --- /dev/null +++ b/src/main/kotlin/com/hero/alignlab/domain/pose/application/PoseSnapshotFacade.kt @@ -0,0 +1,36 @@ +package com.hero.alignlab.domain.pose.application + +import com.hero.alignlab.common.extension.executes +import com.hero.alignlab.config.database.TransactionTemplates +import com.hero.alignlab.domain.auth.model.AuthUser +import com.hero.alignlab.domain.pose.domain.PoseSnapshot +import com.hero.alignlab.domain.pose.model.request.PoseSnapshotRequest +import com.hero.alignlab.domain.pose.model.response.PoseSnapshotResponse +import com.hero.alignlab.event.model.LoadPoseSnapshot +import org.springframework.context.ApplicationEventPublisher +import org.springframework.stereotype.Service + +@Service +class PoseSnapshotFacade( + private val poseSnapshotService: PoseSnapshotService, + private val txTemplates: TransactionTemplates, + private val publisher: ApplicationEventPublisher, +) { + suspend fun loadPoseSnapshot(user: AuthUser, request: PoseSnapshotRequest): PoseSnapshotResponse { + val createdPoseSnapshot = txTemplates.writer.executes { + val createdPoseSnapshot = poseSnapshotService.saveSync( + PoseSnapshot( + uid = user.uid, + score = request.snapshot.score + ) + ) + + LoadPoseSnapshot(createdPoseSnapshot, request.snapshot.keypoints) + .run { publisher.publishEvent(this) } + + createdPoseSnapshot + } + + return PoseSnapshotResponse.from(createdPoseSnapshot) + } +} diff --git a/src/main/kotlin/com/hero/alignlab/domain/pose/application/PoseSnapshotService.kt b/src/main/kotlin/com/hero/alignlab/domain/pose/application/PoseSnapshotService.kt new file mode 100644 index 0000000..45ea313 --- /dev/null +++ b/src/main/kotlin/com/hero/alignlab/domain/pose/application/PoseSnapshotService.kt @@ -0,0 +1,16 @@ +package com.hero.alignlab.domain.pose.application + +import com.hero.alignlab.domain.pose.domain.PoseSnapshot +import com.hero.alignlab.domain.pose.infrastructure.PoseSnapshotRepository +import org.springframework.stereotype.Service +import org.springframework.transaction.annotation.Transactional + +@Service +class PoseSnapshotService( + private val poseSnapshotRepository: PoseSnapshotRepository, +) { + @Transactional + fun saveSync(poseSnapshot: PoseSnapshot): PoseSnapshot { + return poseSnapshotRepository.save(poseSnapshot) + } +} diff --git a/src/main/kotlin/com/hero/alignlab/domain/pose/domain/PoseKeyPointSnapshot.kt b/src/main/kotlin/com/hero/alignlab/domain/pose/domain/PoseKeyPointSnapshot.kt new file mode 100644 index 0000000..b098f78 --- /dev/null +++ b/src/main/kotlin/com/hero/alignlab/domain/pose/domain/PoseKeyPointSnapshot.kt @@ -0,0 +1,30 @@ +package com.hero.alignlab.domain.pose.domain + +import com.hero.alignlab.domain.common.domain.BaseEntity +import jakarta.persistence.* +import java.math.BigDecimal + +@Entity +@Table(name = "pose_key_point_snapshot") +class PoseKeyPointSnapshot( + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "id") + val id: Long = -1L, + + @Column(name = "pose_snapshot_id") + val poseSnapshotId: Long, + + @Column(name = "position") + @Enumerated(EnumType.STRING) + val position: PosePosition, + + @Column(name = "x") + val x: BigDecimal, + + @Column(name = "y") + val y: BigDecimal, + + @Column(name = "confidence") + val confidence: BigDecimal, +) : BaseEntity() diff --git a/src/main/kotlin/com/hero/alignlab/domain/pose/domain/PosePosition.kt b/src/main/kotlin/com/hero/alignlab/domain/pose/domain/PosePosition.kt new file mode 100644 index 0000000..d5bebea --- /dev/null +++ b/src/main/kotlin/com/hero/alignlab/domain/pose/domain/PosePosition.kt @@ -0,0 +1,22 @@ +package com.hero.alignlab.domain.pose.domain + +enum class PosePosition { + NOSE, + LEFT_EYE, + RIGHT_EYE, + LEFT_EAR, + RIGHT_EAR, + LEFT_SHOULDER, + RIGHT_SHOULDER, + LEFT_ELBOW, + RIGHT_ELBOW, + LEFT_WRIST, + RIGHT_WRIST, + LEFT_HIP, + RIGHT_HIP, + LEFT_KNEE, + RIGHT_KNEE, + LEFT_ANKLE, + RIGHT_ANKLE, + ; +} diff --git a/src/main/kotlin/com/hero/alignlab/domain/pose/domain/PoseSnapshot.kt b/src/main/kotlin/com/hero/alignlab/domain/pose/domain/PoseSnapshot.kt new file mode 100644 index 0000000..b394a8b --- /dev/null +++ b/src/main/kotlin/com/hero/alignlab/domain/pose/domain/PoseSnapshot.kt @@ -0,0 +1,20 @@ +package com.hero.alignlab.domain.pose.domain + +import com.hero.alignlab.domain.common.domain.BaseEntity +import jakarta.persistence.* +import java.math.BigDecimal + +@Entity +@Table(name = "pose_snapshot") +data class PoseSnapshot( + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "id") + val id: Long = -1, + + @Column(name = "uid") + val uid: Long, + + @Column(name = "score") + val score: BigDecimal, +) : BaseEntity() diff --git a/src/main/kotlin/com/hero/alignlab/domain/pose/infrastructure/PoseKeyPointSnapshotRepository.kt b/src/main/kotlin/com/hero/alignlab/domain/pose/infrastructure/PoseKeyPointSnapshotRepository.kt new file mode 100644 index 0000000..62ae373 --- /dev/null +++ b/src/main/kotlin/com/hero/alignlab/domain/pose/infrastructure/PoseKeyPointSnapshotRepository.kt @@ -0,0 +1,10 @@ +package com.hero.alignlab.domain.pose.infrastructure + +import com.hero.alignlab.domain.pose.domain.PoseKeyPointSnapshot +import org.springframework.data.jpa.repository.JpaRepository +import org.springframework.stereotype.Repository +import org.springframework.transaction.annotation.Transactional + +@Transactional(readOnly = true) +@Repository +interface PoseKeyPointSnapshotRepository : JpaRepository diff --git a/src/main/kotlin/com/hero/alignlab/domain/pose/infrastructure/PoseSnapshotRepository.kt b/src/main/kotlin/com/hero/alignlab/domain/pose/infrastructure/PoseSnapshotRepository.kt new file mode 100644 index 0000000..0c3a457 --- /dev/null +++ b/src/main/kotlin/com/hero/alignlab/domain/pose/infrastructure/PoseSnapshotRepository.kt @@ -0,0 +1,10 @@ +package com.hero.alignlab.domain.pose.infrastructure + +import com.hero.alignlab.domain.pose.domain.PoseSnapshot +import org.springframework.data.jpa.repository.JpaRepository +import org.springframework.stereotype.Repository +import org.springframework.transaction.annotation.Transactional + +@Transactional(readOnly = true) +@Repository +interface PoseSnapshotRepository : JpaRepository diff --git a/src/main/kotlin/com/hero/alignlab/domain/pose/model/PoseSnapshotModel.kt b/src/main/kotlin/com/hero/alignlab/domain/pose/model/PoseSnapshotModel.kt new file mode 100644 index 0000000..5dfbe2b --- /dev/null +++ b/src/main/kotlin/com/hero/alignlab/domain/pose/model/PoseSnapshotModel.kt @@ -0,0 +1,61 @@ +package com.hero.alignlab.domain.pose.model + +import com.fasterxml.jackson.databind.PropertyNamingStrategies +import com.fasterxml.jackson.databind.annotation.JsonNaming +import java.math.BigDecimal + +@JsonNaming(PropertyNamingStrategies.SnakeCaseStrategy::class) +data class PoseSnapshotModel( + val keypoints: List, + val score: BigDecimal, +) { + data class KeyPoint( + val y: BigDecimal, + val x: BigDecimal, + val name: PosePosition, + val confidence: BigDecimal + ) + + enum class PosePosition { + nose, + left_eye, + right_eye, + left_ear, + right_ear, + left_shoulder, + right_shoulder, + left_elbow, + right_elbow, + left_wrist, + right_wrist, + left_hip, + right_hip, + left_knee, + right_knee, + left_ankle, + right_ankle, + ; + + fun toPosition(): com.hero.alignlab.domain.pose.domain.PosePosition { + return when (this) { + nose -> com.hero.alignlab.domain.pose.domain.PosePosition.NOSE + left_eye -> com.hero.alignlab.domain.pose.domain.PosePosition.LEFT_EYE + right_eye -> com.hero.alignlab.domain.pose.domain.PosePosition.RIGHT_EYE + left_ear -> com.hero.alignlab.domain.pose.domain.PosePosition.LEFT_EAR + right_ear -> com.hero.alignlab.domain.pose.domain.PosePosition.RIGHT_EAR + left_shoulder -> com.hero.alignlab.domain.pose.domain.PosePosition.LEFT_SHOULDER + right_shoulder -> com.hero.alignlab.domain.pose.domain.PosePosition.RIGHT_SHOULDER + left_elbow -> com.hero.alignlab.domain.pose.domain.PosePosition.LEFT_ELBOW + right_elbow -> com.hero.alignlab.domain.pose.domain.PosePosition.RIGHT_ELBOW + left_wrist -> com.hero.alignlab.domain.pose.domain.PosePosition.LEFT_WRIST + right_wrist -> com.hero.alignlab.domain.pose.domain.PosePosition.RIGHT_WRIST + left_hip -> com.hero.alignlab.domain.pose.domain.PosePosition.LEFT_HIP + right_hip -> com.hero.alignlab.domain.pose.domain.PosePosition.RIGHT_HIP + left_knee -> com.hero.alignlab.domain.pose.domain.PosePosition.LEFT_KNEE + right_knee -> com.hero.alignlab.domain.pose.domain.PosePosition.RIGHT_KNEE + left_ankle -> com.hero.alignlab.domain.pose.domain.PosePosition.LEFT_ANKLE + right_ankle -> com.hero.alignlab.domain.pose.domain.PosePosition.RIGHT_ANKLE + } + } + } +} diff --git a/src/main/kotlin/com/hero/alignlab/domain/pose/model/request/PoseSnapshotRequest.kt b/src/main/kotlin/com/hero/alignlab/domain/pose/model/request/PoseSnapshotRequest.kt new file mode 100644 index 0000000..61fa855 --- /dev/null +++ b/src/main/kotlin/com/hero/alignlab/domain/pose/model/request/PoseSnapshotRequest.kt @@ -0,0 +1,8 @@ +package com.hero.alignlab.domain.pose.model.request + +import com.hero.alignlab.domain.pose.model.PoseSnapshotModel + +data class PoseSnapshotRequest( + /** 스냅샷 원천 데이터 */ + val snapshot: PoseSnapshotModel, +) diff --git a/src/main/kotlin/com/hero/alignlab/domain/pose/model/response/PoseSnapshotResponse.kt b/src/main/kotlin/com/hero/alignlab/domain/pose/model/response/PoseSnapshotResponse.kt new file mode 100644 index 0000000..46711b2 --- /dev/null +++ b/src/main/kotlin/com/hero/alignlab/domain/pose/model/response/PoseSnapshotResponse.kt @@ -0,0 +1,20 @@ +package com.hero.alignlab.domain.pose.model.response + +import com.hero.alignlab.domain.pose.domain.PoseSnapshot +import java.time.LocalDateTime + +data class PoseSnapshotResponse( + val id: Long, + val uid: Long, + val createdAt: LocalDateTime, +) { + companion object { + fun from(createdPoseSnapshot: PoseSnapshot): PoseSnapshotResponse { + return PoseSnapshotResponse( + id = createdPoseSnapshot.id, + uid = createdPoseSnapshot.uid, + createdAt = createdPoseSnapshot.createdAt + ) + } + } +} diff --git a/src/main/kotlin/com/hero/alignlab/domain/pose/resource/PoseSnapshotResource.kt b/src/main/kotlin/com/hero/alignlab/domain/pose/resource/PoseSnapshotResource.kt new file mode 100644 index 0000000..5a155b5 --- /dev/null +++ b/src/main/kotlin/com/hero/alignlab/domain/pose/resource/PoseSnapshotResource.kt @@ -0,0 +1,27 @@ +package com.hero.alignlab.domain.pose.resource + +import com.hero.alignlab.common.extension.wrapCreated +import com.hero.alignlab.domain.auth.model.AuthUser +import com.hero.alignlab.domain.pose.application.PoseSnapshotFacade +import com.hero.alignlab.domain.pose.model.request.PoseSnapshotRequest +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.PostMapping +import org.springframework.web.bind.annotation.RequestBody +import org.springframework.web.bind.annotation.RequestMapping +import org.springframework.web.bind.annotation.RestController + +@Tag(name = "Pose Snapshot API") +@RestController +@RequestMapping(produces = [MediaType.APPLICATION_JSON_VALUE]) +class PoseSnapshotResource( + private val poseSnapshotFacade: PoseSnapshotFacade, +) { + @Operation(summary = "post snapshot 저장") + @PostMapping("/api/v1/pose-snapshots") + suspend fun loadPoseSnapshot( + user: AuthUser, + @RequestBody request: PoseSnapshotRequest, + ) = poseSnapshotFacade.loadPoseSnapshot(user, request).wrapCreated() +} diff --git a/src/main/kotlin/com/hero/alignlab/event/listener/PoseSnapshotListener.kt b/src/main/kotlin/com/hero/alignlab/event/listener/PoseSnapshotListener.kt new file mode 100644 index 0000000..940c786 --- /dev/null +++ b/src/main/kotlin/com/hero/alignlab/event/listener/PoseSnapshotListener.kt @@ -0,0 +1,38 @@ +package com.hero.alignlab.event.listener + +import com.hero.alignlab.common.extension.executesOrNull +import com.hero.alignlab.config.database.TransactionTemplates +import com.hero.alignlab.domain.pose.application.PoseKeyPointSnapshotService +import com.hero.alignlab.domain.pose.domain.PoseKeyPointSnapshot +import com.hero.alignlab.event.model.LoadPoseSnapshot +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 + +@Component +class PoseSnapshotListener( + private val poseKeyPointSnapshotService: PoseKeyPointSnapshotService, + private val txTemplates: TransactionTemplates, +) { + @TransactionalEventListener + fun handle(event: LoadPoseSnapshot) { + CoroutineScope(Dispatchers.IO + Job()).launch { + val keyPoints = event.keyPoints.map { keyPoint -> + PoseKeyPointSnapshot( + poseSnapshotId = event.poseSnapshot.id, + position = keyPoint.name.toPosition(), + x = keyPoint.x, + y = keyPoint.y, + confidence = keyPoint.confidence + ) + } + + txTemplates.writer.executesOrNull { + poseKeyPointSnapshotService.saveAllSync(keyPoints) + } + } + } +} diff --git a/src/main/kotlin/com/hero/alignlab/event/model/Event.kt b/src/main/kotlin/com/hero/alignlab/event/model/Event.kt index 89943c1..a98781f 100644 --- a/src/main/kotlin/com/hero/alignlab/event/model/Event.kt +++ b/src/main/kotlin/com/hero/alignlab/event/model/Event.kt @@ -1,6 +1,8 @@ package com.hero.alignlab.event.model 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 import java.time.LocalDateTime sealed interface Event @@ -12,3 +14,8 @@ open class BaseEvent( data class CreateGroupEvent( val group: Group ) : BaseEvent() + +data class LoadPoseSnapshot( + val poseSnapshot: PoseSnapshot, + val keyPoints: List, +) : BaseEvent()