웹소켓 방식을 쓰는 채팅기능
kr.spring.websocket => SoctketHandler.java
package kr.spring.websocket;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import org.springframework.web.socket.CloseStatus;
import org.springframework.web.socket.TextMessage;
import org.springframework.web.socket.WebSocketSession;
import org.springframework.web.socket.handler.TextWebSocketHandler;
import lombok.extern.slf4j.Slf4j;
@Slf4j
public class SocketHandler extends TextWebSocketHandler {
private Map<String, WebSocketSession> users = new ConcurrentHashMap<String, WebSocketSession>();
/*
* 클라이언트가 연결되면, 클라이언트와 관련된 WebSocketSession을 users 맵에 저장한다. 이 users 맵은
* 채팅 메시지를 연결된 전체 클라이언트에 전달할 때 사용
*/
@Override
public void afterConnectionEstablished(
WebSocketSession session) throws Exception {
log.debug(session.getId() + " 연결 됨");
users.put(session.getId(), session);
}
/*
* 클라이언트와의 연결이 종료되면, 클라이언트에 해당하는 WebSocketSession을 users 맵에서 제거한다.
*/
@Override
public void afterConnectionClosed(
WebSocketSession session, CloseStatus status) throws Exception {
log.debug(session.getId() + " 연결 종료됨");
users.remove(session.getId());
}
/*
* 클라이언트가 전송한 메시지를 users 맵에 보관한 전체 WebSocketSession에 다시 전달한다. 클라이언트는
* 메시지를 수신하면 채팅 영역에 보여주도록 구현, 특정 클라이언트가 채팅 메시지를 서버에 보내면 전체 클라이언트는
* 다시 그 메시지를 받아서 화면에 보여주게 된다.
*/
@Override
protected void handleTextMessage(
WebSocketSession session, TextMessage message) throws Exception {
log.debug(session.getId() + "로부터 메시지 수신: " + message.getPayload());
log.debug("[접속 user 수 : " + users.values().size()+"]");
for (WebSocketSession s : users.values()) {
s.sendMessage(message);
log.debug(s.getId() + "에 메시지 발송: " + message.getPayload());
}
}
@Override
public void handleTransportError(
WebSocketSession session, Throwable exception) throws Exception {
log.debug(session.getId() + " 익셉션 발생: " + exception.toString());
}
}
실제 메시지를 소켓쪽에 보내면 내용이 데이터베이스에 저장되지않음. 실시간 방식임.
내가 하는건 데이터베이스에 저장되는거라 실시간 방식이라 보기는 어렵다고 생각.
kr.spring.config => AppConfig
웹소켓을 쓰기위해 설정이 필요
//자바코드 기반 설정 클래스
@Configuration
@EnableWebSocket
public class AppConfig implements WebMvcConfigurer, WebSocketConfigurer{
add method 해준 메소드의 값을 넣어준다.
message-ws는 식별자명으로 js에서 호출해줄 때 사용
//웹소켓 세팅
@Override
public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
registry.addHandler(new SocketHandler(), "message-ws").setAllowedOrigins("*");
}
pom.xml 에는 미리 웹소켓을 사용할 수 있게 값을 넣어놔줬음
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-websocket</artifactId>
</dependency>
message.talk.js
맨아래
selectMsg();
부분을 삭제. 채팅방 생성시에 에러가 나기 때문
/*--------------------
* 웹소켓 연결
*--------------------*/
function connectWebSocket(){
message_socket = new WebSocket('ws://localhost:8000/message-ws'); //message-ws 는 아까 appConfig에서 정해둔 식별자명
message_socket.onopen = function(evt){
console.log('채팅페이지 접속 :'+ $('#talkDetail').length);
if($('#talkDetail').length==1){
message_socket.send('msg');
}
};
//서버로부터 메시지를 받으면 호출되는 함수 지정
message_socket.onmessage = function(evt){
//메시지 읽기
let data = evt.data;
if($('#talkDetail').length==1 && data.substring(0,3)=='msg'){
selectMsg();
}
};
message_socket.onclose=function(evt){
//소켓이 종료된 후 보과적인 작성이 있을 경우 명시
console.long('chat close');
};
}
전체코드
수정 부분이 너무많아서 전체로 올림
$(function(){
let message_socket; //웹소켓 식별자
/*--------------------
* 채팅 회원 저장
*--------------------*/
let member_list = []; //채팅 회원을 저장하는 배열
//채팅방 멤버를 저장하는 배열에 정보 셋팅
//채팅방 또는 채팅 페이지를 인식해서 채팅방 멤버를 초기 셋팅
if($('#talkWrite').length>0){ //채팅방 생성 페이지
member_list = [$('#user').attr('data-id')];
console.log(member_list);
}else if($('#talkDetail').length>0){
connectWebSocket(); //웹소켓 생성
member_list = $('#chat_member').text().split(',');
}
/*--------------------
* 웹소켓 연결
*--------------------*/
function connectWebSocket(){
message_socket = new WebSocket('ws://localhost:8000/message-ws'); //message-ws 는 아까 appConfig에서 정해둔 식별자명
message_socket.onopen = function(evt){
console.log('채팅페이지 접속 :'+ $('#talkDetail').length);
if($('#talkDetail').length==1){
message_socket.send('msg');
}
};
//서버로부터 메시지를 받으면 호출되는 함수 지정
message_socket.onmessage = function(evt){
//메시지 읽기
let data = evt.data;
if($('#talkDetail').length==1 && data.substring(0,3)=='msg'){
selectMsg();
}
};
message_socket.onclose=function(evt){
//소켓이 종료된 후 보과적인 작성이 있을 경우 명시
console.long('chat close');
};
}
/*--------------------
* 채팅방 생성하기
*--------------------*/
//회원 정보 검색
$('#member_search').keyup(function(){
if($('#member_search').val().trim()==''){
$('#search_area').empty();
return;
}
//서버와 통신
$.ajax({
url:'memberSearchAjax',
type:'get',
data:{id:$('#member_search').val()},
dataType:'json',
success:function(param){
if(param.result == 'logout'){
$('#member_search').attr('disabled',true); //disabled 가 true이면 비활성화
$('#member_search').val('로그인해야 회원 검색이 가능합니다.'); //비활성화 상태라는걸 알려주는 값을 전달
}else if(param.result == 'success'){
$('#search_area').empty();
$(param.member).each(function(index,item){
//채팅방 개설자의 아이디와 동일한 아이디와
//이미 member_list에 저장된 아이디 제외
if(!member_list.includes(item.id)){
let output = '';
output += '<li data-num="'+item.mem_num+'">';
output += item.id;
output += '</li>';
$('#search_area').append(output);
}
});
}else{
alert('회원 검색 오류 발생')
}
},
error:function(){
alert('네트워크 오류 발생');
}
})
});
//검색된 회원 선택하기
$(document).on('click','#search_area li',function(){
let id =$(this).text(); //선택한 아이디
let mem_num = $(this).attr('data-num'); //선택한 회원번호
member_list.push(id);
//선택한 id를 화면에 표시
let choice_id = '<span class="member-span" data-id="'+id+'">';
choice_id += '<input type="hidden" name="members" value="'+mem_num+'">';
choice_id += id+'<sup>×</sup></span>'
$('#talk_member').append(choice_id);
$('#member_search').val('');
$('#search_area').empty(); //ul태그 초기화
if($('#name_checked').is(':checked')){ //채팅방 이름 자동설정 (check표시가 되어있을 때)
makeRoom_name();
}
});
//선택한 채팅방 멤버 삭제하기
$(document).on('click','.member-span',function(){
let id = $(this).attr('data-id');
//채팅 멤버가 저장된 배열에서 삭제할 멤버의 id 제거
member_list.splice(member_list.indexOf(id),1);
$(this).remove();
if($('#name_checked').is(':checked')){ //채팅방 이름 자동설정 (check표시가 되어있을 때)
makeRoom_name();
} //span은 후손선택자
if($('#talk_member span').length==0){
$('#name_span').text('');
$('#basic_name').val('');
}
});
//채팅방 이름 생성 방식 정하기(자동/수동)
$('#name_checked').click(function(){
if($('#name_checked').is(':checked')){//채팅방 이름 자동 생성
$('#basic_name').attr('type','hidden');
if(member_list.length > 1){
makeRoom_name();
}
}else{//채팅방 이름 수동 생성
$('#basic_name').attr('type','text');
$('#name_span').text(''); //채팅방 이름 표시 텍스트 초기화
}
});
// 채팅방 이름 생성
function makeRoom_name(){
let name = '';
$.each(member_list,function(index,item){
if(index>0) name += ',';
name += item;
});
if(name.length>55){
name = name.substring(0,55) + '...';
}
$('#basic_name').val(name);
$('#name_span').text(name);
}
//채팅방 생성 전송
$('#talk_form').submit(function(){
if(member_list.length<=1){//이미 배열에 현재 로그인한 유저(채팅방 개설자)가 기본 등록되어 있어서 로그인한 유저 포함 최소 2명이 되어야 채팅 가능
alert('채팅에 참여할 회원을 검색하세요!');
$('#member_search').focus();
return false;
}
});
/*--------------------
* 채팅하기
*--------------------*/
//메시지 입력후 enter 이벤트 처리
$('#message').keydown(function(event){
if(event.keyCode == 13 && !event.shiftKey){
$('#detail_form').trigger('submit');
}
});
//채팅 메시지 등록
$('#detail_form').submit(function(event){
if($('#message').val().trim()==''){
alert('메시지를 입력하세요');
$('#message').val('').focus(); //공백을 제거하고 포커스를 맞추기
return false;
}
if($('#message').val().length>1333){
alert('메시지를 1333자 까지만 입력가능합니다.');
return false;
}
//입력한 데이터를 읽어오기
let form_data = $(this).serialize();
//서버와 통신
$.ajax({
url:'../talk/writeTalk',
type:'post',
data:form_data,
dataType:'json',
success:function(param){
if(param.result == 'logout'){
alert('로그인 해야 작성 할 수 있습니다.');
message_socket.close();
}else if(param.result=='success'){
//폼 초기화
$('#message').val('').focus();
message_socket.send('msg');
}else{
alert('채팅 메시지 등록 오류 발생');
message_socket.close();
}
},
error:function(){
alert('네트워크 오류 발생');
message_socket.close();
}
});//end of ajax
//기본 이벤트 제거
event.preventDefault();
});//end of 채팅 메시지 등록
//채팅 데이터 읽기
function selectMsg(){
$.ajax({
url:'../talk/talkDetailAjax',
type:'get',
data:{talkroom_num:$('#talkroom_num').val()},
dataType:'json',
success:function(param){
if(param.result == 'logout'){
alert('로그인 후 사용하세요!');
message_socket.close();
}else if(param.result == 'success'){
//메시지 표시 UI 초기화
$('#chatting_message').empty();
let chat_date='';
$(param.list).each(function(index,item){
let output = '';
//날짜 추출
if(chat_date != item.chat_date.split(' ')[0]){
chat_date = item.chat_date.split(' ')[0];
output += '<div class="date-position"><span>'+chat_date+'</span></div>';
}
if(item.message.indexOf('@{member}@')>=0){//멤버등록/탈퇴 메시지 처리
//신규, 탈퇴 회원 메시지
output += '<div class="member-message">';
output += item.message.substring(0,item.message.indexOf('@{member}@'));
output += '</div>';
}else{
//멤버등록/탈퇴 메시지가 아닌 일반 메시지
if(item.mem_num == param.user_num){
output += '<div class="from-position">'+item.id;
output += '<div>';
}else{
output += '<div class="to-position">';
output += '<div class="space-photo">';
output += '<img src="../member/viewProfile?mem_num='+ item.mem_num +'" width="40" height="40" class="my-photo">';
output += '</div><div class="space-message">';
output += item.id;
}
output += '<div class="item">';
output += item.read_count + ' <span>' + item.message.replace(/\r\n/g,'<br>').replace(/\r/g,'<br>').replace(/\n/g,'<br>') + '</span>';
//시간 추출
output += '<div class="align-right">' + item.chat_date.split(' ')[1] + '</div>';
output += '</div>';
output += '</div><div class="space-clear"></div>';
output += '</div>';
}
//문서 객체에 추가
$('#chatting_message').append(output);
//스크롤을 하단에 위치시킴
$('#chatting_message').scrollTop($("#chatting_message")[0].scrollHeight);
});
}else{
alert('채팅 메시지 읽기 오류 발생');
message_socket.close();
}
},
error:function(){
alert('네트워크 오류 발생');
message_socket.close();
}
});
}
});
같은 서버에서만 작동되기 때문에 내 컴퓨터로만 가능하며, 크롬과 엣지로 나눠서 실행해야 다른 사용자로 인식한다.
자동로그인 처리 기능
MemberController
로그인 부분
// ==== 자동로그인 체크 시작 ====//
Boolean autoLogin = memberVO.getAuto()!=null && memberVO.getAuto().equals("on");
if(autoLogin) {
//자동로그인을 체크한 경우
String au_id = member.getAu_id();
if(au_id==null) {
//자동로그인 체크 식별값 생성
au_id = UUID.randomUUID().toString();
log.debug("<<au_id>>:"+au_id);
member.setAu_id(au_id);
memberService.updateAu_id(member.getAu_id(), member.getMem_num());
}
Cookie auto_cookie = new Cookie("au-log",au_id);
auto_cookie.setMaxAge(60*60*24*7); //쿠키의 유효기간은 1주일 (자동 로그인 기간을 1주일) -1주일동안 로그인을 안하면 풀린다.
auto_cookie.setPath("/");
response.addCookie(auto_cookie);
}
// ==== 자동로그인 체크 끝 ====//
로그아웃 부분
추가로 메서드 값에
HttpServletResponse response
를 넣어줘야됨.
// ==== 자동로그인 시작 ====//
//클라이언트 쿠키 처리
Cookie auto_cookie = new Cookie("au-log","");
auto_cookie.setMaxAge(0); //쿠키삭제
auto_cookie.setPath("/");
response.addCookie(auto_cookie);
// ==== 자동로그인 끝 ====//
memberMapper.java
//자동 로그인 처리
@Update("UPDATE spmember_detail SET au_id=#{au_id} WHERE mem_num=#{mem_num}")
public void updateAu_id(String au_id,Long mem_num);
@Select("SELECT m.mem_num,m.id,m.auth,d.au_id,d.passwd,m.nick_name,d.email FROM spmember m JOIN spmember_detail d ON m.mem_num=d.mem_num WHERE d.au_id=#{au_id}")
public MemberVO selectAu_id(String au_id);
@Update("UPDATE spmember_detail SET au_id='' WHERE mem_num=#{mem_num}")
public void deleteAu_id(Long mem_num);
MemberServiceImpl
@Override
public void updateAu_id(String au_id, Long mem_num) {
memberMapper.updateAu_id(au_id, mem_num);
}
@Override
public MemberVO selectAu_id(String au_id) {
return memberMapper.selectAu_id(au_id);
}
@Override
public void deleteAu_id(Long mem_num) {
memberMapper.deleteAu_id(mem_num);
}
kr.spring.interceptor =>AutoLogionCheckInterceptor.java
AutoLoginCheckInterceptor.java
0.00MB
package kr.spring.interceptor;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.servlet.HandlerInterceptor;
import kr.spring.member.service.MemberService;
import kr.spring.member.vo.MemberVO;
import lombok.extern.slf4j.Slf4j;
@Slf4j
public class AutoLoginCheckInterceptor implements HandlerInterceptor{
@Autowired
MemberService memberService;
@Override
public boolean preHandle(HttpServletRequest request,
HttpServletResponse response,
Object handler)throws Exception{
log.debug("<<AutoLoginCheckInterceptor 진입>>");
HttpSession session = request.getSession();
MemberVO user = (MemberVO)session.getAttribute("user");
if(user==null) {
Cookie now_cookie = findCookie(request.getCookies(),"au-log");
if(now_cookie!=null) {
MemberVO memberVO = memberService.selectAu_id(
now_cookie.getValue());
log.debug("<<자동로그인 여부 체크 MemberVO>> : " + memberVO);
if(memberVO != null && memberVO.getAuth()>=2) {
//일반회원부터 자동로그인 처리
session.setAttribute("user", memberVO);
log.debug("<<자동로그인 성공>>");
}
}
}
return true;
}
private Cookie findCookie(Cookie[] cookies, String name) {
if(cookies == null || cookies.length == 0) {
return null;
}else {
for(int i=0;i<cookies.length;i++) {
String cookie_name = cookies[i].getName();
if(cookie_name.equals(name)) {
return cookies[i];
}
}
return null;
}
}
}
kr.spring.config => AppConfig
private AutoLoginCheckInterceptor autoLoginCheck;
@Bean
public AutoLoginCheckInterceptor interceptor() {
autoLoginCheck = new AutoLoginCheckInterceptor();
return autoLoginCheck;
}
@Override
public void addInterceptors(InterceptorRegistry registry) {
//AutoLoginCheckInterceptor 설정
registry.addInterceptor(autoLoginCheck)
.addPathPatterns("/**")
.excludePathPatterns("/images/**") //excludePathPatterns는 등록을 안한다.
.excludePathPatterns("/images_upload/**")
.excludePathPatterns("/upload/**")
.excludePathPatterns("/css/**")
.excludePathPatterns("/js/**")
.excludePathPatterns("/member/login")
.excludePathPatterns("/member/logout");
이제 로그인을 한 상태로 웹을 껐다가 다시 실행을 해도 로그인 한 상태로 되어있다.
'쌍용교육(JAVA) > SpringBoot' 카테고리의 다른 글
교육 때 진행한 프로젝트 연결 (0) | 2024.07.31 |
---|---|
쌍용교육 -JSP수업 105일차 ch15SpringPage(16) (0) | 2024.07.16 |
쌍용교육 -JSP수업 104일차 ch15SpringPage(15) (0) | 2024.07.15 |
쌍용교육 -JSP수업 103일차 ch15SpringPage(14) (0) | 2024.07.12 |
쌍용교육 -JSP수업 102일차 ch15SpringPage(13) (0) | 2024.07.11 |