Skip to content

Commit

Permalink
feat: pose snapshot 처리
Browse files Browse the repository at this point in the history
  • Loading branch information
DongGeon0908 committed Jul 30, 2024
1 parent 303093d commit 3a848f2
Show file tree
Hide file tree
Showing 16 changed files with 349 additions and 3 deletions.
29 changes: 28 additions & 1 deletion sql/DDL.sql
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,6 @@ suspend fun <RETURN> TransactionTemplate.coExecuteOrNull(
}
}


fun <RETURN> TransactionTemplate.executes(
actions: TransactionCallback<RETURN>,
): RETURN {
Expand All @@ -37,7 +36,6 @@ fun <RETURN> TransactionTemplate.executes(
?: throw FailToExecuteException(ErrorCode.FAIL_TO_TRANSACTION_TEMPLATE_EXECUTE_ERROR)
}


fun <RETURN> TransactionTemplate.executesOrNull(
actions: TransactionCallback<RETURN>,
): RETURN? {
Expand Down
Original file line number Diff line number Diff line change
@@ -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<PoseKeyPointSnapshot>): List<PoseKeyPointSnapshot> {
return poseKeyPointSnapshotRepository.saveAll(poseKeyPointSnapshots)
}
}
Original file line number Diff line number Diff line change
@@ -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)
}
}
Original file line number Diff line number Diff line change
@@ -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)
}
}
Original file line number Diff line number Diff line change
@@ -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()
Original file line number Diff line number Diff line change
@@ -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,
;
}
Original file line number Diff line number Diff line change
@@ -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()
Original file line number Diff line number Diff line change
@@ -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<PoseKeyPointSnapshot, Long>
Original file line number Diff line number Diff line change
@@ -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<PoseSnapshot, Long>
Original file line number Diff line number Diff line change
@@ -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<KeyPoint>,
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
}
}
}
}
Original file line number Diff line number Diff line change
@@ -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,
)
Original file line number Diff line number Diff line change
@@ -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
)
}
}
}
Original file line number Diff line number Diff line change
@@ -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()
}
Original file line number Diff line number Diff line change
@@ -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)
}
}
}
}
7 changes: 7 additions & 0 deletions src/main/kotlin/com/hero/alignlab/event/model/Event.kt
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -12,3 +14,8 @@ open class BaseEvent(
data class CreateGroupEvent(
val group: Group
) : BaseEvent()

data class LoadPoseSnapshot(
val poseSnapshot: PoseSnapshot,
val keyPoints: List<KeyPoint>,
) : BaseEvent()

0 comments on commit 3a848f2

Please sign in to comment.