친구 추가 요청,거절 및 리팩토링
2025. 1. 1. 18:55ㆍTIL
기존에 짬뽕되어있는 코드를 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 |