사실 이미 좀 진행했던 건데 안듣고 내 프로젝트 하다가 오늘은 제대로 들었다.
그래서 중간부터 서술하는 점 이해 바란다.
<script>
// 채팅 메세지 읽기 (read)
// 클라이언트가 받은 메세지의 번호를 입력해야함
// --> 메세지 가져오기 요청시에 필요한 부분만 잘라서 가져올 수 있다.
let Chat__lastLoadedId = 0;
function Chat__loadMore() {
fetchGet("/chat/messages", {
fromId: Chat__lastLoadedId
})
.then(body => {
console.log('body :' + body);
console.log('body.data : ' + body.data);
console.log('body.data.chatMessages : ' + body.data.chatMessages);
Chat__drawMessages(body.data.chatMessages);
});
}
const Chat_elMessageUl = document.querySelector('.chat__message-ul');
function Chat__drawMessages(messages) {
if (messages.length > 0) {
// 메세지를 그리기 전에 Chat__lastLoadedUuid 변수를 갱신합니다.
Chat__lastLoadedId = messages[messages.length - 1].id;
}
// 메세지를 그리기 전에 Chat__lastLoadedUuid 변수를 갱신합니다.
Chat__lastLoadedId = messages[messages.length - 1].id;
console.log(Chat__lastLoadedId);
messages.forEach((message) => {
Chat_elMessageUl
.insertAdjacentHTML(
"afterBegin",
`<li>${message.authorName} : ${message.content}</li>`
);
});
// Chat__loadMore(); 즉시실행
setTimeout(Chat__loadMore, 500); // 0.5초 뒤에 실행
}
// 최초에 한번 불러오기
Chat__loadMore();
</script>
이런 식으로 하면 폴링 방식으로 0.5초마다 채팅방을 갱신한다.
찔러보는 것은 나쁜건 아니지만 에너지 소모가 심하다. -> 무분별한 호출 야기를 방지하는 것이 목표.
그래서 이걸 SSE 방식으로 바꾼다. (아쉬운 부분을 수정.)
SSE
- 브라우저 notify
- 새 데이터가 들어온 것을 인식
새 데이터가 들어온 것을 인식하는 것이 핵심.
const sse = new EventSource("/sse/connect");
sse.addEventListener('chat__messageAdded', e => {
Chat__loadMore();
});
// 최초에 한번 불러오기
Chat__loadMore();
해당코드를 맨 뒤에 작성해준다.'chat__messageAdded'
이 명령어를 감지하면 갱신한다.
그래서 이 명령어를 담당할 어떤 무언가를 또 만들어 주어야한다.
Java에서 Ut 클래스 생성해주고 아래와 같은 코드를 작성.
public class Ut {
public static <K, V> Map<K, V> mapOf(Object... args) {
Map<K, V> map = new LinkedHashMap<>();
int size = args.length / 2;
for (int i = 0; i < size; i++) {
int keyIndex = i * 2;
int valueIndex = keyIndex + 1;
K key = (K) args[keyIndex];
V value = (V) args[valueIndex];
map.put(key, value);
}
return map;
}
}
SseEmitters클래스와 SseController도 생성.
그리고 컨트롤러에 SseEmitters
클래스를 불러온다.
@Component
@Slf4j
public class SseEmitters {
private final List<SseEmitter> emitters = new CopyOnWriteArrayList<>();
public SseEmitter add(SseEmitter emitter) {
this.emitters.add(emitter);
emitter.onCompletion(() -> {
this.emitters.remove(emitter);
});
emitter.onTimeout(() -> {
emitter.complete();
});
return emitter;
}
public void noti(String eventName) {
noti(eventName, Ut.mapOf());
}
public void noti(String eventName, Map<String, Object> data) {
emitters.forEach(emitter -> {
try {
emitter.send(
SseEmitter.event()
.name(eventName)
.data(data)
);
} catch (ClientAbortException e) {
} catch (IOException e) {
throw new RuntimeException(e);
}
});
}
}
@Controller
@RequestMapping("/sse")
@RequiredArgsConstructor
public class SseController {
private final SseEmitters sseEmitters;
@GetMapping(value = "/connect", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
public ResponseEntity<SseEmitter> connect() {
SseEmitter emitter = new SseEmitter();
sseEmitters.add(emitter);
try {
emitter.send(SseEmitter.event()
.name("connect")
.data("connected!"));
} catch (IOException e) {
throw new RuntimeException(e);
}
return ResponseEntity.ok(emitter);
}
}
이후 write메서드 안쪽에 해당 코드를 불러와서 'chat__messageAdded'
write메서드가 실행될 때 저걸 알아먹게 해준다.
@PostMapping("/writeMessage")
@ResponseBody
public RsData<writeMessageResponse> writeMessage(@RequestBody writeMessageRequest req) {
ChatMessage message = new ChatMessage(req.authorName, req.content);
chatMessages.add(message);
sseEmitters.noti("chat__messageAdded");
return new RsData<>("S-1", "메세지가 작성됨", new writeMessageResponse(message.getId()) );
}
이렇게 하면 채팅을 쳐서 데이터가 갱신될 때 자동으로 화면에 바로바로 갱신된다!
'Coding History' 카테고리의 다른 글
리액트 (템플릿 리터럴, 컴포넌트 리팩토링, onChange) (1) | 2024.09.24 |
---|---|
리액트! (JS Filter) (1) | 2024.09.24 |
VO, DTO, Entity의 차이점 (0) | 2024.09.09 |
OAuto2 가 무엇인가? (1) | 2024.09.03 |
국비 지원 IT(웹앱개발) 취업반 강의 58일차 (Spring) (4) | 2024.08.28 |