쌍용교육(JAVA)/SpringBoot

쌍용교육 -JSP수업 106일차 ch15SpringPage(17)

구 승 2024. 7. 17. 11:11

웹소켓 방식을 쓰는 채팅기능

kr.spring.websocket => SoctketHandler.java

SocketHandler.java
0.00MB

 

 

 

 

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{

새로운 값을 넣어주면 AppConfig 부분에 붉은 밑줄이 생기는데 impl처럼 add method 해주면 된다.

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>&times;</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");

이제 로그인을 한 상태로 웹을 껐다가 다시 실행을 해도 로그인 한 상태로 되어있다.