일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
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 |
- Firebase
- Unity3d
- 유니티
- 구현
- 스파르타내일배움캠프TIL
- Inventory
- 언리얼엔진
- 워크플로
- 프로그래머스
- Unity
- UnrealEngine
- 포톤
- unityui
- Unity2D
- 해시
- UE4
- c#
- FSM
- 유클리드호제법
- BFS
- 스택
- QueryDSL
- 내일배움캠프
- 스파르타내일배움캠프
- 알고리즘
- 문자열
- C++
- 이분탐색
- 순열
- Photon
- Today
- Total
개발 낙서장
[TIL] 내일배움캠프 68일차 - SSE로 알림 기능 구현 본문
오늘의 학습 키워드📚
SSE를 사용해 클라이언트가 채팅방에 대한 변경 사항을 즉각 조회할 수 있도록 했다.
우선 임시로 채팅방이 생성될 경우 클라이언트가 알 수 있도록 콘솔 로그를 찍어봤다.
JavaScript 부분
클라이언트에서 서버로 SSE 연결 요청을 하는데 EventSource 객체를 통해 연결하고 서버가 보내주는 데이터를 받을 수 있다.
// SSE 이벤트 구독
const eventSource = new EventSource(
`http://localhost:8080/api/chat-rooms/sse?userId=` + localStorage.getItem('userId')
);
eventSource.addEventListener('createChatRoom', event => {
let data = JSON.parse(event.data);
console.log(data.userName + ' ' + data.chatRoomId + ' ' + data.chatRoomName + ' ' + data.description);
})
클라이언트에서 SSE URL로 연결을 하고
서버에서 name 값으로 데이터를 보내면 클라이언트에서 해당 name 값으로 온 이벤트를 받아 사용할 수 있다.
Controller 부분
SseEmitter를 생성해 추가하는 부분은 Service 부분으로 넘겼다.
Controller에서는 SSE와 연결하는 URL만 설정해주었다.
@GetMapping("/api/chat-rooms/sse")
public SseEmitter subscribeChatRoomChanges(@RequestParam(name = "userId") Long userId) {
return chatRoomService.subscribeChatRoomChanges(userId);
}
Service 부분
SseEmitter는 Map으로 관리해 UserId로 찾을 수 있도록 했다.
구독 요청이 들어오면 userId로 등록을 하고 연결이 끝나거나 해제될 경우 삭제하는 방식으로 했다.
@Service
@RequiredArgsConstructor
public class ChatRoomService {
private final ChatRoomRepository chatRoomRepository;
private final MemberRepository memberRepository;
private final Map<Long, SseEmitter> chatRoomEmitters = new ConcurrentHashMap<>();
public GetChatRoomResponse createChatRoom(CreateChatRoomRequest request, User user) {
StringBuilder sb = new StringBuilder();
int i = 0;
for (i = 0; i < request.getChatRoomTags().size() - 1; i++) {
sb.append(request.getChatRoomTags().get(i)).append(",");
}
sb.append(request.getChatRoomTags().get(i));
ChatRoom chatRoom = chatRoomRepository.save(
ChatRoom.builder()
.chatRoomName(request.getChatRoomName())
.description(request.getDescription())
.coverImage(request.getCoverImage())
.chatRoomTag(sb.toString())
.user(user)
.build()
);
memberRepository.save(
Member.builder().chatRoomId(chatRoom.getChatRoomId()).userId(user.getUserId()).build());
GetChatRoomResponse response = new GetChatRoomResponse(user.getUserName(), chatRoom);
sendChatRoomChanges(chatRoom.getChatRoomId(), "createChatRoom", response);
return response;
}
public SseEmitter subscribeChatRoomChanges(Long userId) {
SseEmitter sseEmitter = new SseEmitter(Long.MAX_VALUE);
chatRoomEmitters.put(userId, sseEmitter);
sseEmitter.onCompletion(() -> chatRoomEmitters.remove(userId));
sseEmitter.onTimeout(() -> chatRoomEmitters.remove(userId));
return sseEmitter;
}
public void sendChatRoomChanges(Long chatRoomId, String eventName, Object data) {
chatRoomRepository.findById(chatRoomId).ifPresent(chatRoom -> {
for (Long userId : chatRoomEmitters.keySet()) {
SseEmitter sseEmitter = chatRoomEmitters.get(userId);
try {
sseEmitter.send(SseEmitter.event().name(eventName).data(data));
} catch (IOException e) {
chatRoomEmitters.remove(userId);
}
}
});
}
}
우선 채팅방을 생성할 때만 알림이 가도록 설정했다.
채팅 방에 변화가 생길 경우 sendChatRoomChanges 메소드를 호출하면 구독한 클라이언트들에게 데이터가 전송되는 방식이다.
구분하는 방법은 name 값을 설정해 클라이언트에서 해당 name 값으로 이벤트 리스너를 등록하면 된다.
넘어온 데이터를 활용할 수 있으니 채팅방이 추가되면 새로 출력해주고 수정되면 값을 바꿔주는 등의 작업을 해주면 될 것 같다.
또한 다른 알림 기능(사용자를 채팅으로 태그한다거나 채팅방에 초대된다거나 등)도 구현할 수 있을 것 같다.
트러블 슈팅
SSE 또한 헤더에 데이터를 담는 부분을 따로 구현해줘야 했는데 처음엔 JWT로 유저 정보를 담아 보내려고 했다.
EventSource에서는 기본적으로 헤더 전달이 없어서 다른 라이브러리를 사용해야 했다.
https://www.npmjs.com/package/event-source-polyfill
위 사이트를 참고해 event-source-polyfill을 설치하고 사용을 했는데 자바 스크립트 모듈 에러가 발생했다.
import { NativeEventSource, EventSourcePolyfill } from 'event-source-polyfill';
const EventSource = NativeEventSource || EventSourcePolyfill;
사이트에 나와있는 대로 js 상단에 EventSource를 정의하고 사용하려 했지만 모듈을 import 할 수 없다는 에러가 계속 발생했다.
구글링을 해봐도 다른 사람들은 다 저렇게 쓰면 헤더를 보낼 수 있다고 해서 고민을 했는데 결국 헤더는 포기하고 userId를 직접 보내주기로 했다.
.done(function (res, status, xhr) {
const token = xhr.getResponseHeader('Authorization');
Cookies.set('Authorization', token, {path: '/'})
$.ajaxPrefilter(function (options, originalOptions, jqXHR) {
jqXHR.setRequestHeader('Authorization', token);
});
localStorage.setItem('userId', res.userId);
localStorage.setItem('email', res.email);
localStorage.setItem('userName', res.userName);
// 로그인 성공 시 Todo 페이지로 이동
window.location.href = host;
})
로그인 부분 스크립트의 일부인데 로그인에 성공할 경우 필요한 유저 정보를 브라우저의 localStorage에 담아 활용했다.
오늘의 회고💬
SSE 헤더 에러 때문에 시간을 상당히 허비했는데 그래도 구현해서 다행이라고 생각한다.
내일의 계획📜
SSE 기능을 다 끝내면 우선 내가 맡은 기본 기능은 완료된다!
'Java > Sparta' 카테고리의 다른 글
[TIL] 내일배움캠프 70일차 - 리액트 (0) | 2024.04.05 |
---|---|
[TIL] 내일배움캠프 69일차 - li 동적 수정 (0) | 2024.04.04 |
[TIL] 내일배움캠프 67일차 - SSE (0) | 2024.04.02 |
[TIL] 내일배움캠프 66일차 - JPQL concat (1) | 2024.04.01 |
[TIL] 내일배움캠프 65일차 - STOMP JWT 인증 (0) | 2024.03.29 |