feat: 유저 행동 데이터 기록 추가 - 콘텐츠에 댓글 쓰기
This commit is contained in:
parent
eb8c8c14e8
commit
ddcd54d3b9
|
@ -3,6 +3,9 @@ package kr.co.vividnext.sodalive.content.comment
|
||||||
import kr.co.vividnext.sodalive.common.ApiResponse
|
import kr.co.vividnext.sodalive.common.ApiResponse
|
||||||
import kr.co.vividnext.sodalive.common.SodaException
|
import kr.co.vividnext.sodalive.common.SodaException
|
||||||
import kr.co.vividnext.sodalive.member.Member
|
import kr.co.vividnext.sodalive.member.Member
|
||||||
|
import kr.co.vividnext.sodalive.member.MemberService
|
||||||
|
import kr.co.vividnext.sodalive.useraction.ActionType
|
||||||
|
import kr.co.vividnext.sodalive.useraction.UserActionService
|
||||||
import org.springframework.data.domain.Pageable
|
import org.springframework.data.domain.Pageable
|
||||||
import org.springframework.security.core.annotation.AuthenticationPrincipal
|
import org.springframework.security.core.annotation.AuthenticationPrincipal
|
||||||
import org.springframework.web.bind.annotation.GetMapping
|
import org.springframework.web.bind.annotation.GetMapping
|
||||||
|
@ -14,7 +17,11 @@ import org.springframework.web.bind.annotation.RequestParam
|
||||||
import org.springframework.web.bind.annotation.RestController
|
import org.springframework.web.bind.annotation.RestController
|
||||||
|
|
||||||
@RestController
|
@RestController
|
||||||
class AudioContentCommentController(private val service: AudioContentCommentService) {
|
class AudioContentCommentController(
|
||||||
|
private val service: AudioContentCommentService,
|
||||||
|
private val memberService: MemberService,
|
||||||
|
private val userActionService: UserActionService
|
||||||
|
) {
|
||||||
@PostMapping("/audio-content/comment")
|
@PostMapping("/audio-content/comment")
|
||||||
fun registerComment(
|
fun registerComment(
|
||||||
@RequestBody request: RegisterCommentRequest,
|
@RequestBody request: RegisterCommentRequest,
|
||||||
|
@ -22,7 +29,6 @@ class AudioContentCommentController(private val service: AudioContentCommentServ
|
||||||
) = run {
|
) = run {
|
||||||
if (member == null) throw SodaException("로그인 정보를 확인해주세요.")
|
if (member == null) throw SodaException("로그인 정보를 확인해주세요.")
|
||||||
|
|
||||||
ApiResponse.ok(
|
|
||||||
service.registerComment(
|
service.registerComment(
|
||||||
comment = request.comment,
|
comment = request.comment,
|
||||||
audioContentId = request.contentId,
|
audioContentId = request.contentId,
|
||||||
|
@ -30,7 +36,20 @@ class AudioContentCommentController(private val service: AudioContentCommentServ
|
||||||
isSecret = request.isSecret,
|
isSecret = request.isSecret,
|
||||||
member = member
|
member = member
|
||||||
)
|
)
|
||||||
|
|
||||||
|
try {
|
||||||
|
val memberId = member.id!!
|
||||||
|
val pushTokenList = memberService.getPushTokenList(recipient = memberId)
|
||||||
|
|
||||||
|
userActionService.recordAction(
|
||||||
|
memberId = member.id!!,
|
||||||
|
actionType = ActionType.CONTENT_COMMENT,
|
||||||
|
pushTokenList = pushTokenList
|
||||||
)
|
)
|
||||||
|
} catch (_: Exception) {
|
||||||
|
}
|
||||||
|
|
||||||
|
ApiResponse.ok(Unit, "")
|
||||||
}
|
}
|
||||||
|
|
||||||
@PutMapping("/audio-content/comment")
|
@PutMapping("/audio-content/comment")
|
||||||
|
|
|
@ -4,8 +4,6 @@ import com.google.firebase.messaging.AndroidConfig
|
||||||
import com.google.firebase.messaging.ApnsConfig
|
import com.google.firebase.messaging.ApnsConfig
|
||||||
import com.google.firebase.messaging.Aps
|
import com.google.firebase.messaging.Aps
|
||||||
import com.google.firebase.messaging.FirebaseMessaging
|
import com.google.firebase.messaging.FirebaseMessaging
|
||||||
import com.google.firebase.messaging.FirebaseMessagingException
|
|
||||||
import com.google.firebase.messaging.Message
|
|
||||||
import com.google.firebase.messaging.MessagingErrorCode
|
import com.google.firebase.messaging.MessagingErrorCode
|
||||||
import com.google.firebase.messaging.MulticastMessage
|
import com.google.firebase.messaging.MulticastMessage
|
||||||
import com.google.firebase.messaging.Notification
|
import com.google.firebase.messaging.Notification
|
||||||
|
@ -38,7 +36,7 @@ class FcmService(private val pushTokenService: PushTokenService) {
|
||||||
|
|
||||||
while (attempt <= maxAttempts && targets.isNotEmpty()) {
|
while (attempt <= maxAttempts && targets.isNotEmpty()) {
|
||||||
val multicastMessage = MulticastMessage.builder()
|
val multicastMessage = MulticastMessage.builder()
|
||||||
.addAllTokens(tokens)
|
.addAllTokens(targets)
|
||||||
|
|
||||||
multicastMessage.setAndroidConfig(
|
multicastMessage.setAndroidConfig(
|
||||||
AndroidConfig.builder()
|
AndroidConfig.builder()
|
||||||
|
@ -117,51 +115,69 @@ class FcmService(private val pushTokenService: PushTokenService) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun sendPointGranted(token: String, point: Int) {
|
fun sendPointGranted(tokens: List<String>, point: Int) {
|
||||||
|
if (tokens.isEmpty()) return
|
||||||
val data = mapOf(
|
val data = mapOf(
|
||||||
"type" to "POINT_GRANTED",
|
"type" to "POINT_GRANTED",
|
||||||
"point" to point.toString(),
|
"point" to point.toString(),
|
||||||
"message" to "${point}포인트가 지급되었습니다!"
|
"message" to "${point}포인트가 지급되었습니다!"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var targets = tokens
|
||||||
var attempts = 0
|
var attempts = 0
|
||||||
val maxAttempts = 3
|
val maxAttempts = 3
|
||||||
|
|
||||||
while (attempts < maxAttempts) {
|
while (attempts <= maxAttempts && targets.isNotEmpty()) {
|
||||||
try {
|
val multicastMessage = MulticastMessage.builder()
|
||||||
val message = Message.builder()
|
.addAllTokens(targets)
|
||||||
.setToken(token)
|
|
||||||
.putAllData(data)
|
.putAllData(data)
|
||||||
|
|
||||||
|
multicastMessage.setAndroidConfig(
|
||||||
|
AndroidConfig.builder()
|
||||||
|
.setPriority(AndroidConfig.Priority.HIGH)
|
||||||
.build()
|
.build()
|
||||||
|
)
|
||||||
|
|
||||||
val response = FirebaseMessaging.getInstance().send(message)
|
multicastMessage.setApnsConfig(
|
||||||
logger.info("[FCM] ✅ 성공 (attempt ${attempts + 1}): messageId=$response")
|
ApnsConfig.builder()
|
||||||
return // 성공 시 즉시 종료
|
.setAps(
|
||||||
} catch (e: FirebaseMessagingException) {
|
Aps.builder()
|
||||||
attempts++
|
.setSound("default")
|
||||||
|
.build()
|
||||||
|
)
|
||||||
|
.build()
|
||||||
|
)
|
||||||
|
|
||||||
// "registration-token-not-registered" 예외 코드 확인
|
val response = FirebaseMessaging.getInstance().sendEachForMulticast(multicastMessage.build())
|
||||||
if (e.messagingErrorCode == MessagingErrorCode.UNREGISTERED) {
|
val failedTokens = mutableListOf<String>()
|
||||||
logger.error("[FCM] ❌ 실패: 토큰이 등록되지 않음 (등록 해제됨) → 재시도 안함")
|
|
||||||
|
response.responses.forEachIndexed { index, res ->
|
||||||
|
if (!res.isSuccessful) {
|
||||||
|
val exception = res.exception
|
||||||
|
val token = targets[index]
|
||||||
|
|
||||||
|
if (exception?.messagingErrorCode == MessagingErrorCode.UNREGISTERED) {
|
||||||
|
logger.error("[FCM] ❌ UNREGISTERED → $token")
|
||||||
// DB에서 삭제
|
// DB에서 삭제
|
||||||
pushTokenService.unregisterInvalidToken(token)
|
pushTokenService.unregisterInvalidToken(token)
|
||||||
|
} else {
|
||||||
|
logger.error("[FCM] ❌ 실패: $token / ${exception?.messagingErrorCode}")
|
||||||
|
failedTokens.add(token)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (failedTokens.isEmpty()) {
|
||||||
|
logger.info("[FCM] ✅ 전체 전송 성공")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
logger.error("[FCM] ❌ 실패 (attempt $attempts): ${e.errorCode} - ${e.message}")
|
targets = failedTokens
|
||||||
|
|
||||||
if (attempts >= maxAttempts) {
|
|
||||||
logger.error("[FCM] ❌ 최종 실패: 전송 불가")
|
|
||||||
}
|
|
||||||
} catch (e: Exception) {
|
|
||||||
// Firebase 이외의 예외도 잡기
|
|
||||||
attempts++
|
attempts++
|
||||||
logger.error("[FCM] ❌ 실패 (attempt $attempts): ${e.message}")
|
}
|
||||||
|
|
||||||
if (attempts >= maxAttempts) {
|
if (targets.isNotEmpty()) {
|
||||||
logger.error("[FCM] ❌ 최종 실패: 알 수 없는 오류")
|
logger.error("[FCM] ❌ 최종 실패 대상 ${targets.size}명 → $targets")
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -66,7 +66,11 @@ class MemberController(
|
||||||
userActionService.recordAction(
|
userActionService.recordAction(
|
||||||
memberId = response.memberId,
|
memberId = response.memberId,
|
||||||
actionType = ActionType.SIGN_UP,
|
actionType = ActionType.SIGN_UP,
|
||||||
pushToken = request.pushToken
|
pushTokenList = if (request.pushToken != null) {
|
||||||
|
listOf(request.pushToken)
|
||||||
|
} else {
|
||||||
|
emptyList()
|
||||||
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
return ApiResponse.ok(message = "회원가입을 축하드립니다.", data = response.loginResponse)
|
return ApiResponse.ok(message = "회원가입을 축하드립니다.", data = response.loginResponse)
|
||||||
|
@ -355,7 +359,11 @@ class MemberController(
|
||||||
userActionService.recordAction(
|
userActionService.recordAction(
|
||||||
memberId = response.memberId,
|
memberId = response.memberId,
|
||||||
actionType = ActionType.SIGN_UP,
|
actionType = ActionType.SIGN_UP,
|
||||||
pushToken = request.pushToken
|
pushTokenList = if (request.pushToken != null) {
|
||||||
|
listOf(request.pushToken)
|
||||||
|
} else {
|
||||||
|
emptyList()
|
||||||
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -386,7 +394,11 @@ class MemberController(
|
||||||
userActionService.recordAction(
|
userActionService.recordAction(
|
||||||
memberId = response.memberId,
|
memberId = response.memberId,
|
||||||
actionType = ActionType.SIGN_UP,
|
actionType = ActionType.SIGN_UP,
|
||||||
pushToken = request.pushToken
|
pushTokenList = if (request.pushToken != null) {
|
||||||
|
listOf(request.pushToken)
|
||||||
|
} else {
|
||||||
|
emptyList()
|
||||||
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -64,6 +64,8 @@ interface MemberQueryRepository {
|
||||||
|
|
||||||
fun existsByNickname(nickname: String): Boolean
|
fun existsByNickname(nickname: String): Boolean
|
||||||
fun findNicknamesWithPrefix(prefix: String): List<String>
|
fun findNicknamesWithPrefix(prefix: String): List<String>
|
||||||
|
|
||||||
|
fun getPushTokenList(memberId: Long): List<String>
|
||||||
}
|
}
|
||||||
|
|
||||||
@Repository
|
@Repository
|
||||||
|
@ -507,4 +509,17 @@ class MemberQueryRepositoryImpl(
|
||||||
)
|
)
|
||||||
.fetch()
|
.fetch()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun getPushTokenList(memberId: Long): List<String> {
|
||||||
|
val where = member.isActive.isTrue
|
||||||
|
.and(member.email.notIn("admin@sodalive.net"))
|
||||||
|
.and(member.id.eq(memberId))
|
||||||
|
|
||||||
|
return queryFactory
|
||||||
|
.select(pushToken.token)
|
||||||
|
.from(member)
|
||||||
|
.innerJoin(pushToken).on(member.id.eq(pushToken.member.id))
|
||||||
|
.where(where)
|
||||||
|
.fetch()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -907,6 +907,12 @@ class MemberService(
|
||||||
return MemberResolveResult(member = member, isNew = true)
|
return MemberResolveResult(member = member, isNew = true)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun getPushTokenList(recipient: Long): List<String> {
|
||||||
|
return repository.getPushTokenList(recipient)
|
||||||
|
.toSet()
|
||||||
|
.toList()
|
||||||
|
}
|
||||||
|
|
||||||
private fun checkEmail(email: String) {
|
private fun checkEmail(email: String) {
|
||||||
val member = repository.findByEmail(email)
|
val member = repository.findByEmail(email)
|
||||||
|
|
||||||
|
|
|
@ -3,6 +3,7 @@ package kr.co.vividnext.sodalive.member.auth
|
||||||
import kr.co.vividnext.sodalive.common.ApiResponse
|
import kr.co.vividnext.sodalive.common.ApiResponse
|
||||||
import kr.co.vividnext.sodalive.common.SodaException
|
import kr.co.vividnext.sodalive.common.SodaException
|
||||||
import kr.co.vividnext.sodalive.member.Member
|
import kr.co.vividnext.sodalive.member.Member
|
||||||
|
import kr.co.vividnext.sodalive.member.MemberService
|
||||||
import kr.co.vividnext.sodalive.useraction.ActionType
|
import kr.co.vividnext.sodalive.useraction.ActionType
|
||||||
import kr.co.vividnext.sodalive.useraction.UserActionService
|
import kr.co.vividnext.sodalive.useraction.UserActionService
|
||||||
import org.springframework.security.core.annotation.AuthenticationPrincipal
|
import org.springframework.security.core.annotation.AuthenticationPrincipal
|
||||||
|
@ -15,6 +16,7 @@ import org.springframework.web.bind.annotation.RestController
|
||||||
@RequestMapping("/auth")
|
@RequestMapping("/auth")
|
||||||
class AuthController(
|
class AuthController(
|
||||||
private val service: AuthService,
|
private val service: AuthService,
|
||||||
|
private val memberService: MemberService,
|
||||||
private val userActionService: UserActionService
|
private val userActionService: UserActionService
|
||||||
) {
|
) {
|
||||||
@PostMapping
|
@PostMapping
|
||||||
|
@ -33,11 +35,16 @@ class AuthController(
|
||||||
|
|
||||||
val authResponse = service.authenticate(authenticateData, member.id!!)
|
val authResponse = service.authenticate(authenticateData, member.id!!)
|
||||||
|
|
||||||
|
try {
|
||||||
|
val memberId = member.id!!
|
||||||
|
val pushTokenList = memberService.getPushTokenList(recipient = memberId)
|
||||||
userActionService.recordAction(
|
userActionService.recordAction(
|
||||||
memberId = member.id!!,
|
memberId = member.id!!,
|
||||||
actionType = ActionType.USER_AUTHENTICATION,
|
actionType = ActionType.USER_AUTHENTICATION,
|
||||||
pushToken = member.pushToken
|
pushTokenList = pushTokenList
|
||||||
)
|
)
|
||||||
|
} catch (_: Exception) {
|
||||||
|
}
|
||||||
|
|
||||||
ApiResponse.ok(authResponse)
|
ApiResponse.ok(authResponse)
|
||||||
}
|
}
|
||||||
|
|
|
@ -24,7 +24,12 @@ class UserActionService(
|
||||||
|
|
||||||
private val coroutineScope = CoroutineScope(Dispatchers.IO)
|
private val coroutineScope = CoroutineScope(Dispatchers.IO)
|
||||||
|
|
||||||
fun recordAction(memberId: Long, actionType: ActionType, orderId: Long? = null, pushToken: String? = null) {
|
fun recordAction(
|
||||||
|
memberId: Long,
|
||||||
|
actionType: ActionType,
|
||||||
|
orderId: Long? = null,
|
||||||
|
pushTokenList: List<String> = emptyList()
|
||||||
|
) {
|
||||||
coroutineScope.launch {
|
coroutineScope.launch {
|
||||||
val now = LocalDateTime.now()
|
val now = LocalDateTime.now()
|
||||||
repository.save(UserActionLog(memberId, actionType))
|
repository.save(UserActionLog(memberId, actionType))
|
||||||
|
@ -73,8 +78,8 @@ class UserActionService(
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
if (pushToken != null) {
|
if (pushTokenList.isNotEmpty()) {
|
||||||
fcmService.sendPointGranted(pushToken, policy.pointAmount)
|
fcmService.sendPointGranted(pushTokenList, policy.pointAmount)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue