친구 추가 요청,거절 및 리팩토링

2025. 1. 1. 18:55TIL

기존에 짬뽕되어있는 코드를 controller와 Service의 역할을 완전히 분리하도록 리팩토링 과정을 진행하였다.

FriendsController

더보기
package hjp.hjchat.domain.member.controller

import hjp.hjchat.domain.member.dto.FriendRequestDto
import hjp.hjchat.domain.member.dto.FriendShipDto
import hjp.hjchat.domain.member.service.FriendsService
import hjp.hjchat.infra.security.jwt.UserPrincipal
import org.springframework.http.HttpStatus
import org.springframework.http.ResponseEntity
import org.springframework.security.core.annotation.AuthenticationPrincipal
import org.springframework.web.bind.annotation.*

@RestController
@RequestMapping("/friends")
class FriendsController(
    private val friendsService: FriendsService,
) {

    @GetMapping("/get_list")
    fun getMyFriendsList(
        @AuthenticationPrincipal user: UserPrincipal
    ): ResponseEntity<List<FriendShipDto>>{
        return ResponseEntity.status(HttpStatus.OK).body(friendsService.getMyFriendsList(user))
    }

    @GetMapping("my_request")
    fun getSentFriendRequests(
        @AuthenticationPrincipal user: UserPrincipal,
    ):ResponseEntity<List<FriendShipDto>>{
        return ResponseEntity.ok(friendsService.getSentFriendRequests(user.memberId))
    }

    @GetMapping("/request")
    fun getReceivedFriendRequests(
        @AuthenticationPrincipal user: UserPrincipal
    ): ResponseEntity<List<FriendShipDto>> {
        val receivedRequests = friendsService.getReceivedFriendRequests(user.memberId)
        return ResponseEntity.ok(receivedRequests)
    }


    @PostMapping("/request")
    fun sendFriendRequest(
        @AuthenticationPrincipal user: UserPrincipal,
        @RequestBody request: FriendRequestDto
    ): ResponseEntity<FriendShipDto> {
        val friendship = friendsService.sendFriendRequest(user.memberId, request.friendId)
        return ResponseEntity.ok(friendship)
    }

    @PostMapping("/accept")
    fun acceptFriendRequest(
        @AuthenticationPrincipal user: UserPrincipal,
        @RequestBody request: FriendRequestDto
    ): ResponseEntity<FriendShipDto> {

        val friendship = friendsService.acceptFriendRequest(userId = user.memberId, senderId = request.friendId)

        return ResponseEntity.ok(friendship)
    }

    @PostMapping("/reject")
    fun rejectFriendRequest(
        @AuthenticationPrincipal user: UserPrincipal,
        @RequestBody request: FriendRequestDto
    ): ResponseEntity<Unit>{

        return ResponseEntity.ok(friendsService.rejectFriendRequest(user = user, senderId = request.friendId))
    }

}

 

FriendsService

더보기
package hjp.hjchat.domain.member.service

import com.fasterxml.jackson.databind.ObjectMapper
import hjp.hjchat.domain.member.dto.FriendNotificationDto
import hjp.hjchat.domain.member.dto.FriendShipDto
import hjp.hjchat.domain.member.dto.FriendshipStatus
import hjp.hjchat.domain.member.entity.FriendRequest
import hjp.hjchat.domain.member.entity.Friendship
import hjp.hjchat.domain.member.entity.RequestStatus
import hjp.hjchat.domain.member.entity.toResponse
import hjp.hjchat.domain.member.model.FriendRequestRepository
import hjp.hjchat.domain.member.model.FriendshipRepository
import hjp.hjchat.infra.security.jwt.UserPrincipal
import hjp.hjchat.infra.security.ouath.model.OAuthRepository
import org.springframework.kafka.core.KafkaTemplate
import org.springframework.messaging.simp.SimpMessagingTemplate
import org.springframework.stereotype.Service
import org.springframework.transaction.annotation.Transactional
import kotlin.jvm.optionals.getOrNull

@Service
class FriendsService(
    private val oAuthRepository: OAuthRepository,
    private val friendshipRepository: FriendshipRepository,
    private val friendRequestRepository: FriendRequestRepository,
    private val kafkaTemplate: KafkaTemplate<String, String>,
    private val messagingTemplate: SimpMessagingTemplate,
    private val memberService: MemberService,
) {


    fun getMyFriendsList(user: UserPrincipal): List<FriendShipDto> {

        val friendList = friendshipRepository.findAllByUserId(user.memberId)
            ?: throw IllegalArgumentException("해당 ${user.memberId} ID의 데이터가 존재하지 않음")
        return friendList.map{ it.toResponse() }
    }

    @Transactional
    fun sendFriendRequest(userId: Long, friendId: Long): FriendShipDto {
        // 친구와 사용자 존재 확인
        val user = oAuthRepository.findById(userId)
            .orElseThrow { IllegalArgumentException("사용자를 찾을 수 없습니다.") }
        val friend = oAuthRepository.findById(friendId)
            .orElseThrow { IllegalArgumentException("친구를 찾을 수 없습니다.") }

        if (friendshipRepository.existsByUserAndFriend(user, friend)) {
            throw IllegalArgumentException("이미 친구 요청이 존재하거나 친구 관계입니다.")
        }
        if (friendshipRepository.existsByUserAndFriend(friend, user)) {
            throw IllegalArgumentException("이미 친구 요청이 존재하거나 친구 관계입니다.")
        }

        friendRequestRepository.save(
            FriendRequest(
                sender = user,
                receiver = friend,
                status = RequestStatus.PENDING
            )
        )

        // 친구 요청 생성
        val friendship = friendshipRepository.save(
            Friendship(
                user = user,
                friend = friend,
                status = FriendshipStatus.PENDING
            )
        )

        val event = mapOf(
            "type" to "REQUEST",
            "senderId" to userId,
            "receiverId" to friendId,
            "senderName" to user.userCode,
        )
        kafkaTemplate.send("friend-events", ObjectMapper().writeValueAsString(event))

        return FriendShipDto(
            userId = friendship.user.id,
            friendId = friendship.friend.id,
            status = friendship.status.toString(),
            senderName = friendship.friend.userName,
            friendCode = friendship.friend.userCode!!,
        )
    }

    @Transactional
    fun acceptFriendRequest(userId: Long, senderId: Long): FriendShipDto {
        // 친구 요청 찾기
        val friendRequest = friendRequestRepository.findBySenderIdAndReceiverId(senderId = senderId, receiverId = userId)
            ?: throw IllegalArgumentException("친구 요청을 찾을 수 없습니다.")

        val user = oAuthRepository.findById(userId).getOrNull()

        val sender = oAuthRepository.findById(senderId).getOrNull()

        val friendship = friendshipRepository.findByUserIdAndFriendId(userId = senderId, friendId = userId)
            ?: throw IllegalArgumentException("친구 요청을 찾을 수 없습니다.")

        friendship.status = FriendshipStatus.ACCEPTED
        friendshipRepository.save(friendship)
        friendshipRepository.save(
            Friendship(
                user = user!!,
                friend = sender!!,
                status = FriendshipStatus.ACCEPTED
            )
        )

        // 친구 요청 삭제
        friendRequestRepository.delete(friendRequest)

        // WebSocket 알림 전송
        messagingTemplate.convertAndSend("/topic/friend/${senderId}",
            FriendNotificationDto(type = "ACCEPT", senderName = user.userName)
        )

        return FriendShipDto(
            userId = friendship.user.id,
            friendId = friendship.friend.id,
            friendCode = friendship.friend.userCode!!,
            status = friendship.status.toString(),
            senderName = friendship.friend.userName
        )
    }

    @Transactional
    fun rejectFriendRequest(user: UserPrincipal, senderId: Long){

        val userInfo = memberService.getUserInfo(user)

        val friendRequest = friendRequestRepository.findBySenderIdAndReceiverId(senderId = senderId, receiverId = userInfo.userId)
            ?: throw IllegalArgumentException("친구 요청을 찾을 수 없습니다.")

        val friendship = friendshipRepository.findByUserIdAndFriendId(userId = senderId, friendId = userInfo.userId)
            ?: Friendship(user = friendRequest.receiver, friend = friendRequest.sender, status = FriendshipStatus.REJECTED)

        friendRequestRepository.delete(friendRequest)
        friendshipRepository.delete(friendship)

        messagingTemplate.convertAndSend("/topic/friend/${senderId}",
            FriendNotificationDto(type = "REJECT", senderName = userInfo.userName)
        )
    }


    fun getSentFriendRequests(userId: Long): List<FriendShipDto> {
        val requests = friendRequestRepository.findBySenderId(userId)
            ?: throw IllegalArgumentException("해당 유저를 찾을수 없음 ${userId}.")

        return requests.map {
            FriendShipDto(
                userId = userId,
                friendId = it.receiver.id,
                friendCode = it.receiver.userCode!!,
                status = it.status.toString(),
                senderName = it.receiver.userName
            )
        }
    }



    fun getReceivedFriendRequests(userId: Long): List<FriendShipDto> {
        val requests = friendRequestRepository.findByReceiverId(userId)
            ?: throw IllegalArgumentException("해당 유저를 찾을수 없음 ${userId}.")


        return requests.map {
            FriendShipDto(
                friendId =it.sender.id,
                friendCode = it.sender.userCode!!,
                userId = userId,
                status = it.status.toString(),
                senderName = it.sender.userName
            )
        }
    }

}

 

 

Kafka 메세지를 JSON 형태로 직렬화/역직렬화를 위한 DTO 생성

package hjp.hjchat.domain.member.dto

import com.fasterxml.jackson.annotation.JsonCreator
import com.fasterxml.jackson.annotation.JsonProperty

data class FriendEvent @JsonCreator constructor(
    @JsonProperty("type") val type: String,
    @JsonProperty("senderId") val senderId: Long,
    @JsonProperty("receiverId") val receiverId: Long,
    @JsonProperty("senderName") val senderName: String,
)

Kafka 메세지를 JSON 데이터로 보내거나 받을때 사용하기 위해서 작성

 

 

친구 추가 요청 시연 영상: https://www.youtube.com/watch?v=I-r_qeC9hv8

 

'TIL' 카테고리의 다른 글

S3을 이용한 프로필 사진 업로드 구현  (1) 2025.01.03
AWS S3 버킷 생성하기  (0) 2025.01.02
Apache Kafka 적용시키기  (0) 2024.12.31
Apache Kafka  (0) 2024.12.30
친구 목록 조회 기능 구현  (1) 2024.12.27