Skip to content

Commit 7bdd9b4

Browse files
authored
Merge pull request #46 from feat/chatRoom/1
채팅방 생성 및 채팅방 닫기
2 parents f8a3364 + cd52e3a commit 7bdd9b4

File tree

8 files changed

+241
-1
lines changed

8 files changed

+241
-1
lines changed
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
package org.dfbf.soundlink.domain.chat.controller;
2+
3+
import io.swagger.v3.oas.annotations.Operation;
4+
import io.swagger.v3.oas.annotations.tags.Tag;
5+
import lombok.RequiredArgsConstructor;
6+
import org.dfbf.soundlink.domain.chat.service.ChatRoomService;
7+
import org.dfbf.soundlink.global.exception.ResponseResult;
8+
import org.springframework.security.core.annotation.AuthenticationPrincipal;
9+
import org.springframework.web.bind.annotation.PostMapping;
10+
import org.springframework.web.bind.annotation.RequestMapping;
11+
import org.springframework.web.bind.annotation.RequestParam;
12+
import org.springframework.web.bind.annotation.RestController;
13+
14+
@RestController
15+
@RequiredArgsConstructor
16+
@RequestMapping("/api/chat/chatRoom")
17+
@Tag(name = "ChatRoom API", description = "채팅방 관련 API")
18+
public class ChatController {
19+
private final ChatRoomService chatRoomService;
20+
21+
@PostMapping("/create")
22+
@Operation(summary = "채팅요청 시 채팅방 생성", description = "requestId, responseId 값 확인")
23+
public ResponseResult create(@AuthenticationPrincipal Long userId, @RequestParam Long recordId) {
24+
return chatRoomService.createChatRoom(userId, recordId);
25+
}
26+
27+
@PostMapping("/close")
28+
@Operation(summary = "채팅방 닫기" , description="닫을 시 상태값 'close'변경, 레디스에서 삭제")
29+
public ResponseResult close(@AuthenticationPrincipal Long userId, Long chatRoomId) {
30+
return chatRoomService.closeChatRoom(userId, chatRoomId);
31+
}
32+
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
package org.dfbf.soundlink.domain.chat.dto;
2+
3+
4+
public record ChatReqDto (
5+
Long requestId,
6+
Long responseId
7+
){}
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
package org.dfbf.soundlink.domain.chat.entity;
2+
3+
import jakarta.persistence.*;
4+
import lombok.AccessLevel;
5+
import lombok.Builder;
6+
import lombok.Getter;
7+
import lombok.NoArgsConstructor;
8+
import org.dfbf.soundlink.domain.emotionRecord.entity.EmotionRecord;
9+
import org.dfbf.soundlink.domain.user.entity.User;
10+
import org.dfbf.soundlink.global.comm.enums.RoomStatus;
11+
import org.hibernate.annotations.CreationTimestamp;
12+
import org.hibernate.annotations.UpdateTimestamp;
13+
14+
import java.sql.Timestamp;
15+
16+
@Entity
17+
@Getter
18+
@NoArgsConstructor(access = AccessLevel.PROTECTED)
19+
public class ChatRoom {
20+
@Id
21+
@GeneratedValue(strategy = GenerationType.IDENTITY)
22+
@Column(name = "chat_room_id")
23+
private Long chatRoomId;
24+
25+
@Column(name = "start_time")
26+
private Timestamp startTime;
27+
28+
@Column(name = "end_time")
29+
private Timestamp endTime;
30+
31+
@ManyToOne
32+
@JoinColumn (name = "request_user_id")
33+
private User requestUserId;
34+
35+
@ManyToOne
36+
@JoinColumn(name = "record_id")
37+
private EmotionRecord recordId;
38+
39+
@Enumerated(EnumType.STRING)
40+
@Column(name = "status")
41+
private RoomStatus status;
42+
43+
@CreationTimestamp
44+
@Column(name = "created_at")
45+
private Timestamp createdAt;
46+
47+
@UpdateTimestamp
48+
@Column(name = "updated_at")
49+
private Timestamp updatedAt;
50+
51+
@Builder
52+
public ChatRoom(User requestUserId, EmotionRecord recordId, RoomStatus status,
53+
Timestamp startTime, Timestamp endTime) {
54+
this.requestUserId = requestUserId;
55+
this.recordId = recordId;
56+
this.status = status;
57+
this.startTime = startTime;
58+
this.endTime = endTime;
59+
}
60+
61+
//채팅방 상태 업데이트
62+
public void updateChatRoomStatus(RoomStatus status){
63+
this.status = status;
64+
if(status == RoomStatus.CLOSED){
65+
this.endTime = new Timestamp(System.currentTimeMillis());
66+
}
67+
}
68+
69+
}
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
package org.dfbf.soundlink.domain.chat.exception;
2+
3+
import org.dfbf.soundlink.global.exception.BusinessException;
4+
import org.dfbf.soundlink.global.exception.ErrorCode;
5+
6+
public class ChatRoomNotFoundException extends BusinessException {
7+
public ChatRoomNotFoundException() {super(ErrorCode.CHATROOM_NOT_FOUND); }
8+
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
package org.dfbf.soundlink.domain.chat.exception;
2+
3+
import org.dfbf.soundlink.global.exception.BusinessException;
4+
import org.dfbf.soundlink.global.exception.ErrorCode;
5+
6+
public class UnauthorizedAccessException extends BusinessException {
7+
public UnauthorizedAccessException() {
8+
super(ErrorCode.CHAT_UNAUTHORIZED);
9+
}
10+
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
package org.dfbf.soundlink.domain.chat.repository;
2+
3+
import org.dfbf.soundlink.domain.chat.entity.ChatRoom;
4+
import org.dfbf.soundlink.domain.emotionRecord.entity.EmotionRecord;
5+
import org.dfbf.soundlink.domain.user.entity.User;
6+
import org.springframework.data.jpa.repository.JpaRepository;
7+
8+
public interface ChatRoomRepository extends JpaRepository<ChatRoom, Long> {
9+
boolean existsByRequestUserIdAndRecordId(User requestUserId, EmotionRecord recordId);
10+
}
Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
package org.dfbf.soundlink.domain.chat.service;
2+
3+
import lombok.RequiredArgsConstructor;
4+
import org.dfbf.soundlink.domain.chat.dto.ChatReqDto;
5+
import org.dfbf.soundlink.domain.chat.entity.ChatRoom;
6+
import org.dfbf.soundlink.domain.chat.exception.ChatRoomNotFoundException;
7+
import org.dfbf.soundlink.domain.chat.exception.UnauthorizedAccessException;
8+
import org.dfbf.soundlink.domain.chat.repository.ChatRoomRepository;
9+
import org.dfbf.soundlink.domain.emotionRecord.entity.EmotionRecord;
10+
import org.dfbf.soundlink.domain.emotionRecord.exception.EmotionRecordNotFoundException;
11+
import org.dfbf.soundlink.domain.emotionRecord.exception.UserNotFoundException;
12+
import org.dfbf.soundlink.domain.emotionRecord.repository.EmotionRecordRepository;
13+
import org.dfbf.soundlink.domain.user.entity.User;
14+
import org.dfbf.soundlink.domain.user.repository.UserRepository;
15+
import org.dfbf.soundlink.global.comm.enums.RoomStatus;
16+
import org.dfbf.soundlink.global.exception.ErrorCode;
17+
import org.dfbf.soundlink.global.exception.ResponseResult;
18+
import org.springframework.dao.DataIntegrityViolationException;
19+
import org.springframework.data.redis.core.RedisTemplate;
20+
import org.springframework.security.core.annotation.AuthenticationPrincipal;
21+
import org.springframework.stereotype.Service;
22+
import org.springframework.transaction.annotation.Transactional;
23+
24+
import java.sql.Timestamp;
25+
26+
27+
@Service
28+
@RequiredArgsConstructor
29+
public class ChatRoomService {
30+
private final ChatRoomRepository chatRoomRepository;
31+
private final UserRepository userRepository;
32+
private final EmotionRecordRepository emotionRecordRepository;
33+
private final RedisTemplate<String, String> redisTemplate;
34+
35+
36+
@Transactional
37+
public ResponseResult createChatRoom(@AuthenticationPrincipal Long userId, Long recordId){
38+
try {
39+
//요청 보내는사람
40+
User requestUserId = userRepository.findById(userId)
41+
.orElseThrow(UserNotFoundException::new);
42+
43+
//감정기록 조회
44+
EmotionRecord emotionRecord = emotionRecordRepository.findById(recordId)
45+
.orElseThrow(EmotionRecordNotFoundException::new);
46+
//이미 존재하는 채팅방인지 확인
47+
if(chatRoomRepository.existsByRequestUserIdAndRecordId(requestUserId,emotionRecord)){
48+
return new ResponseResult(ErrorCode.CHAT_FAILED, "이미 존재하는 채팅방입니다.");
49+
}
50+
51+
Long responseUserId = emotionRecord.getUser().getUserId();
52+
53+
ChatRoom chatRoom = ChatRoom.builder()
54+
.requestUserId(requestUserId)
55+
.recordId(emotionRecord)
56+
.status(RoomStatus.WAITING) //상태 : 대기
57+
.startTime(new Timestamp(System.currentTimeMillis()))
58+
.endTime(null)
59+
.build();
60+
61+
//DB에 저장
62+
chatRoomRepository.save(chatRoom);
63+
64+
ChatReqDto chatReqDto = new ChatReqDto(userId, responseUserId);
65+
//레디스에 저장
66+
redisTemplate.opsForValue().set("Room::"+chatRoom.getChatRoomId(), String.valueOf(chatReqDto));
67+
68+
return new ResponseResult(ErrorCode.SUCCESS, chatRoom);
69+
} catch (Exception e) {
70+
return new ResponseResult(ErrorCode.INTERNAL_SERVER_ERROR, e.getMessage());
71+
}
72+
73+
}
74+
75+
//채팅방 닫기
76+
@Transactional
77+
public ResponseResult closeChatRoom(@AuthenticationPrincipal Long userId, Long chatRoomId) {
78+
try {
79+
ChatRoom chatRoom = chatRoomRepository.findById(chatRoomId)
80+
.orElseThrow(ChatRoomNotFoundException::new);
81+
82+
//요청자 또는 응답자가 아니면 예외 처리
83+
if(!chatRoom.getRequestUserId().getUserId().equals(userId) &&
84+
!chatRoom.getRecordId().getUser().getUserId().equals(userId)) {
85+
throw new UnauthorizedAccessException();//권한이 없을 경우 예외 발생
86+
}
87+
88+
chatRoom.updateChatRoomStatus(RoomStatus.CLOSED); //삳태 '닫기'로 변경
89+
chatRoomRepository.save(chatRoom);//DB에 저장
90+
91+
redisTemplate.delete("Room::"+chatRoomId);//레디스에서 삭제
92+
return new ResponseResult(ErrorCode.SUCCESS);
93+
} catch (Exception e) {
94+
return new ResponseResult(ErrorCode.INTERNAL_SERVER_ERROR, e.getMessage());
95+
}
96+
}
97+
98+
}

src/main/java/org/dfbf/soundlink/global/exception/ErrorCode.java

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,13 @@ public enum ErrorCode {
5656
KAKAOPAY_READY_ERROR(HttpStatus.INTERNAL_SERVER_ERROR, "카카오페이 결제 준비 에러"),
5757
KAKAOPAY_APPROVE_REDIS_ERROR(HttpStatus.INTERNAL_SERVER_ERROR, "TID가 REDIS 안에 없습니다."),
5858
KAKAOPAY_APPROVE_FEIGN_ERROR(HttpStatus.INTERNAL_SERVER_ERROR, "카카오페이 승인 API 통신 실패"),
59-
KAKAOPAY_APPROVE_ERROR(HttpStatus.INTERNAL_SERVER_ERROR, "카카오페이 결제 승인 에러");
59+
KAKAOPAY_APPROVE_ERROR(HttpStatus.INTERNAL_SERVER_ERROR, "카카오페이 결제 승인 에러"),
60+
61+
//채팅방 관련 에러
62+
CHAT_UNAUTHORIZED(HttpStatus.FORBIDDEN,"권한이 없습니다"),
63+
CHATROOM_NOT_FOUND(HttpStatus.NOT_FOUND,"채팅방을 찾을 수 없습니다."),
64+
CHAT_FAILED(HttpStatus.INTERNAL_SERVER_ERROR,"서버 내부 에러. 중복된 레코드가 존재합니다.");
65+
6066

6167
private final HttpStatus status;
6268
private final String message;

0 commit comments

Comments
 (0)