• Spring Boot Web Socket 실시간 알림

    2022. 2. 21.

    by. 순일

     프로젝트로 SNS를 개발하던 도중 실시간 알림 기능이 필요하게 되어 구글링을 통해 알아보던 중 좋은 블로그를 접하게 되었고 해당 블로그를 참고하여 개발을 성공할 수 있었다.

     

     먼저 웹은 HTTP/HTTPS 방식으로 동작하기 때문에 2가지의 특성을 가지고 있는데 첫 번째는 Stateless이고, 두 번째는 Connectionless이다. Stateless는 클라이언트와 서버가 통신이 끝나면 상태 정보를 유지하지 않는다는 특징이 있고, Connectionless는 클라이언트와 서버가 요청 응답을 종료하면 접속을 끊는다는 특징이 있다. 그래서 실시간으로 통신하기는 제한이 있었고 이를 WebSocket을 이용해 해결했으며, WebSocket은 클라이언트와 서버가 지속적으로 연결된 TCP라인을 통해 실시간으로 데이터를 주고받을 수 있도록 해준다.

    통신이 끝나면 상태 정보를 유지하지 않는다

    참고 블로그 : https://velog.io/@skyepodium/vue-spring-boot-stomp-%EC%9B%B9%EC%86%8C%EC%BC%93

     

     위의 블로그를 참고하면 간단한 채팅 기능을 구현할 수 있는데, 해당 부분에선 모든 유저가 알림 데이터를 받아서 걸러줘야 하는데 특정 유저에게만 데이터를 보내주고 싶어 구글링을 하던 중 DestinationVariable에 대해 알게 되었고 이를 이용해 해결했다.

     

     파일은 총 6가지이며 먼저 파일을 작성하기 앞서 build.gradle 파일에 dependencies 안에 websocket관련 선언을 추가해주도록 한다.

    implementation 'org.springframework.boot:spring-boot-starter-websocket' // websocket

     

    1.  WebSocketConfig

     해당 파일은 소켓을 연결하기 위해 접근하는 부분을 설정해주는 파일로 2가지를 설정해 주는데 첫 번째는 클라이언트에서 알림 메시지(내용)를 보내줄 때 받는 url을 지정하는 부분이고, 두 번째는 websocket connection을 연결하게 될 때 cors를 허용하도록 해주는 부분이다.

    @Configuration
    @EnableWebSocketMessageBroker
    public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {
    
        @Override
        // 클라이언트에서 메시지를 보내면 받는 url 지정하는 부분
        public void configureMessageBroker(MessageBrokerRegistry config) {
            config.enableSimpleBroker("/alarm/receive");
        }
    
        @Override
        // connection을 맺을때 CORS 허용합니다.
        public void registerStompEndpoints(StompEndpointRegistry registry) {
            registry.addEndpoint("/")
                    .setAllowedOrigins("http://localhost:5500")
                    .withSockJS();
        }
    }

     

    2. AlarmController

     알림은 /alarm/send/{no}로 입력받게 되고 알림을 DB 저장하고 저장 완료되면 /alarm/receive/{no}으로 메시지를 보내주는 방식 {no}는 알림이기 때문에 특정 유저에게만 보내주기 위해서 no를 지정해주었다. 

    @RestController
    public class AlarmController {
    	
    	@Autowired
    	AlarmService alarmService;
    	
        @MessageMapping("/alarm/send/{no}") // 메시지 받을 url 주소
        @SendTo("/alarm/receive/{no}") // 메시지를 해당 url 로 반환
     	
        public AlarmDto insertAlram(@DestinationVariable int no, @RequestParam AlarmDto alarmDto) { //pathvriable 친구
            System.out.println(no);
            AlarmDto dto = alarmService.insertAlram(alarmDto);
            if(dto != null) {
            	return dto;
            }else {
            	throw new ResponseStatusException(HttpStatus.FORBIDDEN, "다시 시도해주세요");
            }
        }
    }

     

    3. AlarmService(인터페이스), AlarmServiceImpl(클래스)

     인터페이스를 먼저 생성해주고, 클래스에서는 이를 상속받아서 사용한다. 알림이 들어오게 되면 데이터베이스에 저장하기 위해 추가 정보들을 넣어주는데 배포 서버 기준으로 date가 저장되기 때문에 서울로 따로 지정해서 알림 등록 날짜를 같이 넣어주었다. 결과를 비교하는 부분에서  >=으로 하는 이유 데이터베이스 insert 부분에서 중복이 있으면 제거하고 다시 insert 하는 REPLACE into 때문에 성공 시 리턴 값이 2회로 리턴되기 때문에 이상으로 지정했다.

    public interface AlarmService {
    	public AlarmDto insertAlram(AlarmDto alarmDto);
    }
    @Service
    public class AlarmServiceImpl implements AlarmService {
    	
    	@Autowired
    	AlarmDao alarmDao;
    	
    	private static final int SUCCESS = 1;
    	
    	@Override
    	public AlarmDto insertAlram(AlarmDto alarmDto) {
    		Date date = new Date();
    		TimeZone timeZone;
    		timeZone = TimeZone.getTimeZone("Asia/Seoul"); // 배포 서버기준이기 때문에 서울로 지정
    		SimpleDateFormat format = new SimpleDateFormat ( "yyyy-MM-dd HH:mm:ss");
    		format.setTimeZone(timeZone); // 필요료하는 형식으로 데이터 변경
    		alarmDto.setDate(format.format(date));
    		int result = alarmDao.insertAlram(alarmDto);
    		if(result >= SUCCESS) // >= 으로 하는 이유 insert 부분에서 중복이 있으면 제거하고 다시 insert하기 때문에 2회로 리턴됨
    			return alarmDto;
    		return null;
    	}
    }

     

    4. AlarmDao

     프로젝트에 데이터 베이스 접근 부분은 Mybatis를 사용했기 때문에 Dao 클래스를 만들어 준다.

    @Mapper
    public interface AlarmDao {
    	public int insertAlram(AlarmDto alarmDto);
    }

     

    5. AlarmDto

     데이터베이스에 저장하기 위해 Dto 클래스를 이용하여 데이터를 저장 반환해 준다. 다음 프로젝트부턴 Lombok 사용해서 더 코드를 간단히 해서 사용해보아야겠다.

    public class AlarmDto {
    	private int no;
    	private int sender;
    	private String senderNickname;
    	private String senderImg;
    	private int receiver;
    	private int feedno;
    	private int commentno;
    	private int pickno;
    	private String date;
    	private int type;
    	private int readcheck;
    	public int getNo() {
    		return no;
    	}
    	public void setNo(int no) {
    		this.no = no;
    	}
    	public int getSender() {
    		return sender;
    	}
    	public void setSender(int sender) {
    		this.sender = sender;
    	}
    	public String getSenderNickname() {
    		return senderNickname;
    	}
    	public void setSenderNickname(String senderNickname) {
    		this.senderNickname = senderNickname;
    	}
    	public String getSenderImg() {
    		return senderImg;
    	}
    	public void setSenderImg(String senderImg) {
    		this.senderImg = senderImg;
    	}
    	public int getReceiver() {
    		return receiver;
    	}
    	public void setReceiver(int receiver) {
    		this.receiver = receiver;
    	}
    	public int getFeedno() {
    		return feedno;
    	}
    	public void setFeedno(int feedno) {
    		this.feedno = feedno;
    	}
    	public int getCommentno() {
    		return commentno;
    	}
    	public void setCommentno(int commentno) {
    		this.commentno = commentno;
    	}
    	public int getPickno() {
    		return pickno;
    	}
    	public void setPickno(int pickno) {
    		this.pickno = pickno;
    	}
    	public String getDate() {
    		return date;
    	}
    	public void setDate(String date) {
    		this.date = date;
    	}
    	public int getType() {
    		return type;
    	}
    	public void setType(int type) {
    		this.type = type;
    	}
    	public int getReadcheck() {
    		return readcheck;
    	}
    	public void setReadcheck(int readcheck) {
    		this.readcheck = readcheck;
    	}
    	public AlarmDto() {}
    	public AlarmDto(int no, int sender, String senderNickname, String senderImg, int receiver, int feedno,
    			int commentno, int pickno, String date, int type, int readcheck) {
    		super();
    		this.no = no;
    		this.sender = sender;
    		this.senderNickname = senderNickname;
    		this.senderImg = senderImg;
    		this.receiver = receiver;
    		this.feedno = feedno;
    		this.commentno = commentno;
    		this.pickno = pickno;
    		this.date = date;
    		this.type = type;
    		this.readcheck = readcheck;
    	}
    	@Override
    	public String toString() {
    		return "AlarmDto [no=" + no + ", sender=" + sender + ", senderNickname=" + senderNickname + ", senderImg="
    				+ senderImg + ", receiver=" + receiver + ", feedno=" + feedno + ", commentno=" + commentno + ", pickno="
    				+ pickno + ", date=" + date + ", type=" + type + ", readcheck=" + readcheck + "]";
    	}
    }

     

    6. alarm_query.xml

    <insert id="insertAlram"
    		parameterType="com.ssafy.project.EmotionPlanet.Dto.AlarmDto">
    		REPLACE into alarm(sender, receiver, feedno,commentno, pickno, date, type, readcheck)
    		values
    		(#{sender},#{receiver},#{feedno},#{commentno}, #{pickno}, #{date} ,#{type}, 0)
    		<selectKey keyProperty="no" resultType="int" order="AFTER">
    			SELECT LAST_INSERT_ID()
    		</selectKey>
    </insert>

     

    Vue Web Socket 실시간 알림 

     

    Vue Web Socket 실시간 알림

    Spring Boot Web Socket 실시간 알림  프로젝트로 SNS를 개발하던 도중 실시간 알림 기능이 필요하게 되어 구글링을 통해 알아보던 중 좋은 블로그를 접하게 되었고 해당 블로그를 참고하여 개발을 성

    soonil.tistory.com

     

    728x90

    댓글