Skip to content

Commit

Permalink
✨Feature/#8 STOMP를 사용하는 웹소켓 기반의 서비스 구현
Browse files Browse the repository at this point in the history
✨Feature/#8 STOMP를 사용하는 웹소켓 기반의 서비스 구현
  • Loading branch information
dongkyeomjang authored Sep 26, 2024
2 parents 6c6ab66 + 5af863b commit 39053dc
Show file tree
Hide file tree
Showing 33 changed files with 917 additions and 9 deletions.
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package com.gooaein.goojilgoojil.config;

import com.gooaein.goojilgoojil.intercepter.pre.CustomHandshakeInterceptor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.messaging.simp.config.MessageBrokerRegistry;
import org.springframework.web.socket.config.annotation.EnableWebSocketMessageBroker;
Expand All @@ -9,11 +11,12 @@
@Configuration
@EnableWebSocketMessageBroker
public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {

@Autowired
private CustomHandshakeInterceptor customHandshakeInterceptor;
@Override
public void registerStompEndpoints(StompEndpointRegistry registry) {
registry.addEndpoint(("/ws-connection")).setAllowedOrigins("*"); //처음 핸드쉐이킹
registry.addEndpoint("/ws-connection").setAllowedOrigins("*").withSockJS();
registry.addEndpoint(("/ws-connection")).setAllowedOriginPatterns("*").addInterceptors(customHandshakeInterceptor); //처음 핸드쉐이킹
registry.addEndpoint("/ws-connection").setAllowedOrigins("*").addInterceptors(customHandshakeInterceptor).withSockJS();
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,17 @@ public class Constants {
"/api/v1/no-auth/**", "api/v1/oauth/login",
"/api/v1/auth/sign-up",
"/api/v1/auth/id-duplicate",
"/api/v1/rooms/{roomId}/avartar",
"/api/v1/rooms/{roomId}/questions",
"/api/v1/rooms/{roomId}/guests",
"/api/v1/rooms/{roomId}/reviews",
"/api-docs.html",
"/api-docs/**",
"/swagger-resources/**",
"/v3/api-docs/**",
"/swagger-ui/**"
"/swagger-ui/**",
"/ws-connection/**",
"/ws-connection"
);

public static List<String> USERS_URLS = List.of(
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package com.gooaein.goojilgoojil.controller;

import com.gooaein.goojilgoojil.dto.global.ResponseDto;
import com.gooaein.goojilgoojil.service.GuestService;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.RestController;

@RequiredArgsConstructor
@RestController
public class GuestController {
private final GuestService guestService;

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
package com.gooaein.goojilgoojil.controller;

import com.gooaein.goojilgoojil.dto.global.ResponseDto;
import com.gooaein.goojilgoojil.dto.request.QuestionRequestDto;
import com.gooaein.goojilgoojil.service.QuestionService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.RequiredArgsConstructor;
import org.springframework.messaging.handler.annotation.DestinationVariable;
import org.springframework.messaging.handler.annotation.MessageMapping;
import org.springframework.messaging.handler.annotation.Payload;
import org.springframework.messaging.simp.SimpMessageHeaderAccessor;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;

@RequiredArgsConstructor
@RestController
@Tag(name = "질문", description = "질문 관련 API")
public class QuestionController {
private final QuestionService questionService;

@GetMapping("/api/v1/rooms/{roomId}/questions")
@Operation(summary = "질문 조회", description = "방에 입장할 때, 입장 직전까지 나왔던 해당 방의 질문들을 조회합니다.")
public ResponseDto<?> getQuestions(@PathVariable String roomId) {
return ResponseDto.ok(questionService.getQuestions(roomId));
}

@MessageMapping("/rooms/{roomId}/questions")
@Operation(summary = "질문 보내기", description = "질문을 생성해서 저장하고, 해당 질문이 보내졌음을 다른 유저들이게 알립니다.")
public ResponseDto<?> sendQuestion(
@DestinationVariable String roomId,
@Payload QuestionRequestDto questionRequestDto,
SimpMessageHeaderAccessor headerAccessor) {
// WebSocket 세션 ID 가져오기
String sessionId = headerAccessor.getSessionId();
questionService.sendQuestion(roomId, sessionId, questionRequestDto);
return ResponseDto.created(null);
}

@MessageMapping("/rooms/{roomId}/questions/{questionId}/likes")
@Operation(summary = "질문 좋아요", description = "질문에 좋아요를 누르면 좋아요 수가 증가하고, 해당 질문에 좋아요를 눌렀음을 다른 유저들에게 알립니다.")
public ResponseDto<?> likeQuestion(
@DestinationVariable String roomId,
@DestinationVariable String questionId,
SimpMessageHeaderAccessor headerAccessor) {
// WebSocket 세션 ID 가져오기
String sessionId = headerAccessor.getSessionId();
questionService.likeQuestion(roomId, questionId, sessionId);
return ResponseDto.ok(null);
}

@MessageMapping("/rooms/{roomId}/questions/{questionId}/checks")
@Operation(summary = "질문 답변 처리", description = "질문을 답변하면 해당 질문이 답변되었음을 다른 유저들에게 알립니다.")
public ResponseDto<?> checkQuestion(
@DestinationVariable String roomId,
@DestinationVariable String questionId,
SimpMessageHeaderAccessor headerAccessor) {
// WebSocket 세션 ID 가져오기
String sessionId = headerAccessor.getSessionId();
questionService.checkQuestion(roomId, questionId, sessionId);
return ResponseDto.ok(null);
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
package com.gooaein.goojilgoojil.controller;

import com.gooaein.goojilgoojil.dto.request.EndRoomRequestDto;
import com.gooaein.goojilgoojil.service.RoomService;
import io.swagger.v3.oas.annotations.Operation;
import lombok.RequiredArgsConstructor;
import org.springframework.messaging.handler.annotation.DestinationVariable;
import org.springframework.messaging.handler.annotation.MessageMapping;
import org.springframework.messaging.handler.annotation.Payload;
import org.springframework.messaging.simp.SimpMessageHeaderAccessor;
import org.springframework.web.bind.annotation.RestController;

@RequiredArgsConstructor
@RestController
public class RoomController {
private final RoomService roomService;

@MessageMapping("/rooms/{roomId}/in")
@Operation(summary = "방 입장", description = "방에 입장하고 다른 유저들에게 입장을 알립니다.")
public void enterRoom(
@DestinationVariable String roomId,
SimpMessageHeaderAccessor headerAccessor) {

// WebSocket 세션 ID 가져오기
String sessionId = headerAccessor.getSessionId();

roomService.enterRoom(roomId, sessionId);
}

@MessageMapping("/rooms/{roomId}/end")
@Operation(summary = "방 종료", description = "방을 종료하고 설문 조사 링크와 함께 다른 유저들에게 방 종료를 알립니다.")
public void endRoom(
@DestinationVariable String roomId,
@Payload EndRoomRequestDto endRoomRequestDto,
SimpMessageHeaderAccessor headerAccessor) {

// WebSocket 세션 ID 가져오기
String sessionId = headerAccessor.getSessionId();

roomService.endRoom(sessionId, roomId, endRoomRequestDto.url());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
package com.gooaein.goojilgoojil.controller;

import com.gooaein.goojilgoojil.exception.CommonException;
import com.gooaein.goojilgoojil.service.RoomService;
import com.gooaein.goojilgoojil.service.UserService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.event.EventListener;
import org.springframework.messaging.simp.stomp.StompHeaderAccessor;
import org.springframework.stereotype.Component;

import org.springframework.web.socket.messaging.SessionConnectEvent;
import org.springframework.web.socket.messaging.SessionDisconnectEvent;

@Component
@RequiredArgsConstructor
@Slf4j
public class WebSocketEventListener {

private final UserService userService;
private final RoomService roomService;
@EventListener
public void handleWebSocketConnectListener(SessionConnectEvent event) {
StompHeaderAccessor headerAccessor = StompHeaderAccessor.wrap(event.getMessage());

// WebSocket 세션 ID 가져오기
String sessionId = headerAccessor.getSessionId();

// SecurityContext에서 JwtUserInfo 가져오기
Long userId = (Long) headerAccessor.getSessionAttributes().get("userId");

if (userId != null) {
userService.updateSessionId(userId, sessionId);
} else {
log.error("WebSocket 세션에 인증 정보가 없습니다.");
}
}

@EventListener
public void handleWebSocketDisconnectListener(SessionDisconnectEvent event) {
StompHeaderAccessor headerAccessor = StompHeaderAccessor.wrap(event.getMessage());

// WebSocket 세션 ID 가져오기
String sessionId = headerAccessor.getSessionId();

// SecurityContext에서 JwtUserInfo 가져오기
Long userId = (Long) headerAccessor.getSessionAttributes().get("userId");
try {
if (userId != null) {
if (userService.isGuest(userId)) { // 게스트일 경우 유저 및 게스트 자체를 삭제
roomService.exitRoom(sessionId);
} else { // 방장일경우 세션만 삭제
userService.updateSessionId(userId, null);
}
} else {
log.error("WebSocket 세션에 인증 정보가 없습니다.");
}
} catch (CommonException e) {
log.error("handleApiException() in GlobalExceptionHandler throw CommonException : {}", e.getMessage());
}
}
}

36 changes: 36 additions & 0 deletions src/main/java/com/gooaein/goojilgoojil/domain/Guest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package com.gooaein.goojilgoojil.domain;

import jakarta.persistence.*;
import lombok.AccessLevel;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;

@NoArgsConstructor(access = AccessLevel.PROTECTED)
@Getter
@Entity
@Table(name = "guests")
public class Guest {
@Id
@Column(name = "guest_id")
private Long id;

@OneToOne(fetch = FetchType.LAZY)
@MapsId
@JoinColumn(name = "guest_id")
private User user;

@JoinColumn(name = "room_id")
@ManyToOne
private Room room;

@Column(name = "avartar_base64")
private String avartarBase64;

@Builder
public Guest(User user, Room room, String avartarBase64) {
this.user = user;
this.room = room;
this.avartarBase64 = avartarBase64;
}
}
30 changes: 30 additions & 0 deletions src/main/java/com/gooaein/goojilgoojil/domain/Like.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package com.gooaein.goojilgoojil.domain;

import jakarta.persistence.*;
import lombok.AccessLevel;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;

@Entity
@Getter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@Table(name = "likes")
public class Like {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;

@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "user_id")
private User user;

@Column(name = "question_id")
private String questionId;

@Builder
public Like(User user, String questionId) {
this.user = user;
this.questionId = questionId;
}
}
49 changes: 49 additions & 0 deletions src/main/java/com/gooaein/goojilgoojil/domain/Review.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
package com.gooaein.goojilgoojil.domain;

import com.gooaein.goojilgoojil.dto.response.ReviewDto;
import jakarta.persistence.*;
import lombok.AccessLevel;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;

@Entity
@Getter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@Table(name = "reviews")
public class Review {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "review_id", nullable = false)
private Long id;

@Column(name = "type1", nullable = false)
private Integer type1;

@Column(name = "type2", nullable = false)
private Integer type2;

@Column(name = "type3", nullable = false)
private Integer type3;

@Column(name = "type4", nullable = false)
private Integer type4;

@Column(name = "type5", nullable = false)
private Integer type5;

@JoinColumn(name = "room_id")
@ManyToOne
private Room room;

@Builder
public Review(ReviewDto reviewDto) {
this.type1 = type1;
this.type2 = type2;
this.type3 = type3;
this.type4 = type4;
this.type5 = type5;
this.room = room;
}

}
41 changes: 41 additions & 0 deletions src/main/java/com/gooaein/goojilgoojil/domain/Room.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
package com.gooaein.goojilgoojil.domain;

import jakarta.persistence.*;
import lombok.AccessLevel;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;

import java.time.LocalDateTime;

@Entity
@Getter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@Table(name = "rooms")
public class Room {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "room_id", nullable = false)
private Long id;

@Column(name = "room_name", nullable = false)
private String name;

@Column(name = "sub_name", nullable = false)
private String subName;

@Column(name = "date", nullable = false)
private LocalDateTime date;

@Column(name = "url", nullable = false)
private String url;

@Builder
public Room(String name, String subName, LocalDateTime date, String url) {
this.name = name;
this.subName = subName;
this.date = date;
this.url = url;
}

}
Loading

0 comments on commit 39053dc

Please sign in to comment.