WebSocket
관련 글
- Websocket
- Websocket + 부가기능 👈🏻
- Websocket (채팅 기록 json 파일 저장하기)
- Sse
- Sse 문제점
- Websocket + jwt
- Websocket test
- Jmh - 채팅 파일 refactoring
1편에 이어서 채팅기능을 구현했으므로 고객센터를 구현하기 위해 admin과 user를 구분하여 작성했다.
부가적인 기능 추가
- 해당 채팅방을 만든 사람 즉, 본인과 관리자만 해당 채팅방 이용 하게하기
- 관리자만 채팅 리스트 띄우기(그 외는 본인이 만든 채팅방만 띄워주게하기)
- 채팅이 완료되면 5분뒤에 채팅방 삭제 시키기
삭제 버튼을 누르면 5분뒤에 삭제를 하기 위해서 ChatMessage의 MessageType에 DELETE를 추가했다.
1
2
3
public enum MessageType {
JOIN, TALK, LEAVE, DELETE
}
1
stompClient.send("/app/chat/end-chat", {}, JSON.stringify({roomId: roomId, sender: username, type: 'DELETE'}))
전체코드는 해당 링크에 올려뒀다. github_Socket
ChatRoomController
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
@Controller
@RequiredArgsConstructor
public class ChatRoomController {
private final ChatServiceImpl chatService;
// 채팅 리스트 화면
@GetMapping("/room")
public String rooms(Model model) {
return "chat-list";
}
// 채팅방 입장 화면
@GetMapping("/room/enter/{roomId}")
public String roomDetail(Model model, @PathVariable String roomId){
model.addAttribute("roomId", roomId);
return "chat-room";
}
// 모든 채팅방 목록 반환
@GetMapping("/rooms")
@ResponseBody
public List<ChatRoomDto> room() {
return chatService.findAllRoom();
}
// 본인 채팅방
@GetMapping("/room/one/{nickname}")
@ResponseBody
public ChatRoomDto roomOne(@PathVariable String nickname) {
return chatService.roomOne(nickname);
}
// 채팅방 생성
@PostMapping("/room")
@ResponseBody
public ChatRoomDto createRoom(@RequestBody String nickname){
return chatService.createRoom(nickname);
}
// 완료된 채팅방 삭제하기
@DeleteMapping("/room/one/{roomId}")
@ResponseBody
public void deleteRoom(@PathVariable String roomId){
chatService.deleteRoom(roomId);
}
// 삭제 후 채팅방 재 접속 막기
@GetMapping("/room/{roomId}")
@ResponseBody
public boolean getRoomInfo(@PathVariable String roomId) {
return chatService.getRoomInfo(roomId);
}
}
Dto
ChatRoomDto
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
@Getter
@Setter
@ToString
@Builder
@AllArgsConstructor
public class ChatRoomDto {
private String roomId; // 채팅방 아이디
private String roomName; // 채팅방 이름
private String nickname;
public ChatRoomDto() {
}
public static ChatRoomDto create(String name) {
ChatRoomDto room = new ChatRoomDto();
room.roomId = UUID.randomUUID().toString();
room.roomName = name;
return room;
}
public static ChatRoomDto of (Socket socket){
return ChatRoomDto.builder()
.roomId(socket.getRoomId())
.nickname(socket.getNickname())
.build();
}
}
ChatRoomMap
1
2
3
4
5
6
7
8
9
10
11
@Getter
@Setter
public class ChatRoomMap {
private static ChatRoomMap chatRoomMap = new ChatRoomMap();
private Map<String, ChatRoomDto> chatRooms = new LinkedHashMap<>();
private ChatRoomMap(){}
public static ChatRoomMap getInstance(){
return chatRoomMap;
}
}
domain
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
@Getter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@Entity
@ToString
public class Socket {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "socket_id")
private Long idx;
@Column(nullable = false)
private String nickname;
@Column(nullable = false)
private String roomId;
public Socket(String roomId, String nickname){
this.roomId = roomId;
this.nickname = nickname;
}
}
socket 코드를 작성하면서 db에 최대한 저장안하려고 했는데
채팅방에 들어갈 유저를 특정 유저로 정해놓고 해당 채팅방을 들어가게 하려면 db가 필요할 것 같았다.
db 내용은 최소한의 column으로 username과 roomId 만 설정했다.
roomName은 username으로 자동 설정되므로 추가하지 않았다.
table 만들기
1
2
3
4
5
6
CREATE TABLE IF NOT EXISTS socket (
id bigint(5) NOT NULL AUTO_INCREMENT, # AUTO_INCREMENT : 자동으로 1씩 증가
room_id varchar(255) NOT NULL,
nickname varchar(255) NOT NULL,
PRIMARY KEY (socket_id)
);
Service
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
@Slf4j
@RequiredArgsConstructor
@Service
public class ChatServiceImpl {
private final ChatRepository chatRepository;
//채팅방 불러오기
public List<ChatRoomDto> findAllRoom() {
List<ChatRoomDto> chatRoomDtos = new ArrayList<>();
List<Socket> all = chatRepository.findAll();
try {
for(int i=0; i<all.size(); i++){
chatRoomDtos.add(ChatRoomDto.of(all.get(i)));
}
} catch (NullPointerException e) {
throw new RuntimeException("data 없음! ") ;
}
return chatRoomDtos;
}
//채팅방 하나 불러오기
public boolean getRoomInfo(String roomId) {
return chatRepository.existsByRoomId(roomId);
}
//채팅방 생성
public ChatRoomDto createRoom(String nickname) {
ChatRoomDto chatRoom = new ChatRoomDto();
if (!chatRepository.existsByNickname(nickname)) {
chatRoom = ChatRoomDto.create(nickname);
ChatRoomMap.getInstance().getChatRooms().put(chatRoom.getRoomId(), chatRoom);
Socket socket = new Socket(chatRoom.getRoomId(), nickname);
log.info("Service socket : " + socket);
chatRepository.save(socket);
return chatRoom;
}
else{
Optional<Socket> byNickname = chatRepository.findByNickname(nickname);
return ChatRoomDto.of(byNickname.get());
}
}
public ChatRoomDto roomOne(String nickname){
Optional<Socket> byNickname = chatRepository.findByNickname(nickname);
return ChatRoomDto.of(byNickname.get());
}
public void deleteRoom(String roomId) throws IllegalStateException{
Timer t = new Timer(true);
TimerTask task = new MyTimeTask(chatRepository, roomId);
t.schedule(task,300000);
log.info("5분뒤에 삭제 됩니다.");
}
}
5분뒤에 삭제되는 채팅방 구현
해당 코드는 단발성으로써 5분뒤에 삭제되는 로직이다.
Timer : 실제 타이머의 기능을 수행하는 클래스
TimerTask : “Timer” 클래스가 수행되어야 할 내용을 작성하는 클래스 → run method 재정의 필수
schedule()
은 타이머를 작동시키는 method이다.
Time
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@Slf4j
@RequiredArgsConstructor
public class MyTimeTask extends TimerTask {
private final ChatRepository chatRepository;
private String roomId;
public MyTimeTask(ChatRepository chatRepository, String roomId){
this.chatRepository = chatRepository;
this.roomId = roomId;
}
@Override
public void run() {
Socket socket = chatRepository.findByRoomId(roomId).orElseThrow(NullPointerException :: new);
chatRepository.delete(socket);
log.info("5분이 지나 삭제 되었습니다." + socket);
}
}
Repository
1
2
3
4
5
6
public interface ChatRepository extends JpaRepository<Socket, Long> {
boolean existsByNickname(String nickname);
Optional<Socket> findByNickname(String nickname);
boolean existsByRoomId(String roomId);
Optional<Socket> findByRoomId(String roomId);
}
채팅방 gif
왼쪽 : 일반 user || 오른쪽 : admin
reference
Java 를 활용한 Timer구현하기
[Spring]Springboot + websocket 채팅[1]
[Spring]Springboot + websocket 채팅[2]