Skip to content

Commit

Permalink
🔨Refactor/#21 주석 추가, 불필요 코드 삭제, 예외처리, 쿠키 SameSite 설정 변경
Browse files Browse the repository at this point in the history
🔨Refactor/#21 주석 추가, 불필요 코드 삭제, 예외처리, 쿠키 SameSite 설정 변경
  • Loading branch information
dongkyeomjang authored Sep 28, 2024
2 parents 2b6d037 + ffca003 commit 08906ea
Show file tree
Hide file tree
Showing 19 changed files with 78 additions and 42 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
public class AuthController {
private final AuthService authService;

/* 미사용. 확장성을 위해 남겨놓음 */
@GetMapping("/auth/id-duplicate")
@Operation(summary = "아이디 중복 확인", description = "아이디 중복을 확인합니다.")
public ResponseDto<?> checkDuplicate(
Expand All @@ -34,6 +35,7 @@ public ResponseDto<?> checkDuplicate(
return ResponseDto.ok(authService.checkDuplicate(serialId));
}

/* 미사용. 확장성을 위해 남겨놓음 */
@PostMapping("/auth/sign-up")
@Operation(summary = "Default 회원가입", description = "Default 회원가입을 진행합니다.")
public void signUp(
Expand All @@ -42,6 +44,7 @@ public void signUp(
authService.signUp(authSignUpDto);
}

/* 리프레시토큰을 이용한 엑세스토큰 재발급. */
@PostMapping("/users/auth/reissue")
@Operation(summary = "Access 토큰 재발급", description = "Access 토큰을 재발급합니다.")
public ResponseDto<?> reissue(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,22 +15,28 @@
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.RequiredArgsConstructor;

import java.io.IOException;

@RequiredArgsConstructor
@RestController
@Tag(name = "손님 API", description = "손님 관련 API")
@Tag(name = "임시 유저용(수강자) API", description = "임시 유저(수강자) 관련 API. 수강자는 방에 입장할 때 임시로 생성되는 유저입니다.")
public class GuestController {
private final GuestService guestService;

@Operation(summary = "손님 아바타 생성하기", description = "손님에 대한 아바타를 생성합니다.")
/* 임시 User와 그 임시 유저와 연관관계를 맺는 Guest를 생성하고, 임시 User의 정보를 바탕으로 Jwt 토큰을 생성한 뒤 브라우저에 쿠키를 추가한다.
* 클라이언트로부터 전달받은 아바타 이미지는 Guest에 저장되며, 클라이언트로부터 전달받은 uuid와 맵핑되는 roomId를 찾아 반환한다.
* 이후 수강자의 모든 Http API 요청은 이 때 발급받은 access_token을 통해 이루어진다. */
@Operation(summary = "수강자 아바타 생성하기",
description = "수강자가 질문 방에서 사용할 아바타를 생성합니다." +
"클라이언트로부터 아바타를 넘겨받아, 이를 바탕으로 임시 유저를 생성하고," +
"Jwt 토큰을 생성한 뒤 클라이언트에 쿠키를 추가합니다" +
"또한, 클라이언트로부터 전달받은 uuid와 맵핑되는 roomId를 찾아 반환합니다.")
@PostMapping("/api/v1/rooms/avatar")
public ResponseDto<?> createGuestAvatar(HttpServletResponse response,
@RequestBody AvatarRequestDto avatarRequestDto) {
return ResponseDto.created(guestService.createAvatar(response, avatarRequestDto.uuid(), avatarRequestDto));
}

@Operation(summary = "방에 참가한 손님 전체조회", description = "현재 방에 참여하고 있는 손님의 id와 avatar를 전체 조회합니다.")
/* 클라이언트가 입장한 방에 참가한 수강자들을 전체 조회한다. 웹소켓에 접근하기 직전에 호출하여,
* 웹소켓을 통한 실시간 통신 이전까지의 수강생 현황(실시간으로 입장상태에있는)을 불러와, 질문방에서의 수강생관련 데이터 무결성을 보장한다.*/
@Operation(summary = "방에 참가한 수강생 전체조회", description = "현재 방에 참여하고 있는 수강생의 id와 avatar를 전체 조회합니다.")
@GetMapping("/api/v1/users/rooms/{room_id}/guests")
public ResponseDto<?> getGuests(@PathVariable("room_id") Long roomId) {
return ResponseDto.ok(guestService.getGuestsByRoomId(roomId));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,12 +20,19 @@
public class QuestionController {
private final QuestionService questionService;

/* 클라이언트가 입장한 방에 호출 시점까지 나왔던 모든 질문들을 조회한다. 웹소켓에 접근하기 직전에 호출하여,
* 웹소켓을 통한 실시간 통신 이전까지의 질문들을 불러와, 질문방에서의 질문 데이터 무결성을 보장한다.*/
@GetMapping("/api/v1/users/rooms/{roomId}/questions")
@Operation(summary = "질문 조회", description = "방에 입장할 때, 입장 직전까지 나왔던 해당 방의 질문들을 조회합니다.")
public ResponseDto<?> getQuestions(@PathVariable String roomId) {
return ResponseDto.ok(questionService.getQuestions(roomId));
}

/* 수강자가 웹소켓을 통해 강연자에게 질문을 전송한다. 질문은 모든 수강생 및 강연자가 실시간으로 확인 가능하다.
* type(웹소켓 응답 데이터의 타입. 이 경우 question), questionId(질문 아이디), title(질문 제목), content(질문 내용), avatarBase64(질문자의 아바타),
* sendTime(질문 전송 시점 -> 무관심 질문 사라지는 시간 계산), likeCount(공감 개수), status(질문 답변 여부)로 이루어진다.
* 1. 웹소켓의 세션 ID를 가져와 User와 맵핑하여 해당 수강자를 찾아낸다.
* 2. 수강자로부터 전달받은 데이터와 수강자 데이터를 합쳐 만들어진 Question 객체는 MongoDB에 저장된다.
* 3. 웹소켓을 통해 현재 질문방을 구독중인 모든 사용자에게 질문과 관련된 데이터를 실시간으로 전송한다. */
@MessageMapping("/rooms/{roomId}/questions")
@Operation(summary = "질문 보내기", description = "질문을 생성해서 저장하고, 해당 질문이 보내졌음을 다른 유저들이게 알립니다.")
public ResponseDto<?> sendQuestion(
Expand All @@ -37,7 +44,13 @@ public ResponseDto<?> sendQuestion(
questionService.sendQuestion(roomId, sessionId, questionRequestDto);
return ResponseDto.created(null);
}

/* 수강자가 웹소켓을 통해 특정 질문에 대해 공감을 표현한다. 이 과정에서는 중복 공감 방지를 위해 Redis가, 동시성 제어를 위해 RabbitMQ가 사용된다.
* type(웹소켓 응답 데이터의 타입. 이경우 like), questionId(질문 아이디), title(질문 제목), content(질문 내용), avatarBase64(질문자의 아바타),
* sendTime(이 때 sendTime은 갱신된다), likeCount(공감 개수), status(질문 답변 여부)로 이루어진다.
* 1. 웹소켓의 세션 ID를 가져와 User와 맵핑하여 해당 수강자를 찾아낸다.
* 2. Redis를 참조하여 해당 사용자의 공감 요청이 중복된 요청인지를 검사한다. 중복이라면 철회한다. 중복이 아니라면 ttl을 설정하여 이후 중복 요청을 방지한다.
* 3. RabbitMQ에 특정 질문에 대해 공감했음을 Enqueue한다.
* 4. RabiitMQ는 큐에 있는 데이터를 consume하여, 웹소켓을 통해 질문방에 있는 모든 사용자에게 해당 수강자의 공감과 관련된 데이터를 전송한다.*/
@MessageMapping("/rooms/{roomId}/questions/{questionId}/likes")
@Operation(summary = "질문 좋아요", description = "질문에 좋아요를 누르면 좋아요 수가 증가하고, 해당 질문에 좋아요를 눌렀음을 다른 유저들에게 알립니다.")
public ResponseDto<?> likeQuestion(
Expand All @@ -49,7 +62,11 @@ public ResponseDto<?> likeQuestion(
questionService.likeQuestion(roomId, questionId, sessionId);
return ResponseDto.ok(null);
}

/* 강연자가 웹소켓을 통해 특정 질문에 대해 답변했음을 모든 수강자들에게 전송한다.
* type(웹소켓 응답 데이터의 타입. 이 경우 check), questionId(질문 아이디), likeCount(공감 개수), status(질문 답변 여부)로 이루어진다.
* 1. 웹소켓의 세션 ID를 가져와 User와 맵핑하여 해당 강연자를 찾아낸다.
* 2. 해당 질문의 답변 여부를 true로 변경한다.
* 3. 웹소켓을 통해 현재 질문방을 구독중인 모든 사용자에게 해당 질문이 답변되었음을 실시간으로 전송한다.*/
@MessageMapping("/rooms/{roomId}/questions/{questionId}/checks")
@Operation(summary = "질문 답변 처리", description = "질문을 답변하면 해당 질문이 답변되었음을 다른 유저들에게 알립니다.")
public ResponseDto<?> checkQuestion(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,9 @@ public class RoomController {
private final RoomService roomService;
private final ReviewService reviewService;

/* 수강자가 웹소켓을 통해 질문 방에 참여한 모든 사용자들에게 실시간으로 본인이 참여했음을 알린다.
* 이는, 실시간 활성 사용자 수를 갱신하기 위해 사용되며, 다른 사용자들은 이를 통해 방에 참여한 사용자 수와 그들의 아바타를 확인할 수 있다.
* type(웹소켓 응답 데이터의 타입. 이 경우 in), guestId(수강자 아이디), avatarBase64(수강자 아바타), sendTime(입장 시각)으로 이루어진다. */
@MessageMapping("/rooms/{roomId}/in")
@Operation(summary = "방 입장", description = "방에 입장하고 다른 유저들에게 입장을 알립니다.")
public void enterRoom(
Expand All @@ -39,6 +42,8 @@ public void enterRoom(
roomService.enterRoom(roomId, sessionId);
}

/* 강연자가 웹소켓을 통해 질문 방에 참여한 모든 사용자들에게 실시간으로 세미나가 끝났음을 알리고 설문조사 url을 전송한다.
* type(웹소켓 응답 데이터의 타입. 이 경우 end), url(설문조사 url), sendTime(종료 시각)으로 이루어진다. */
@MessageMapping("/rooms/{roomId}/end")
@Operation(summary = "방 종료", description = "방을 종료하고 설문 조사 링크와 함께 다른 유저들에게 방 종료를 알립니다.")
public void endRoom(
Expand All @@ -52,29 +57,47 @@ public void endRoom(
roomService.endRoom(sessionId, roomId, endRoomRequestDto.url());
}

/* 강연자가 생성한 방 목록을 조회한다. 강연자의 강의실 관리에 사용될 데이터를 제공한다.
* 방 배열: id(강의실ID), name(방이름), date(강연날짜), location(강연장소), url(강의실 uuid), likeThreshold(명예의전당 등록 공감 수 기준) */
@GetMapping("/api/v1/users/rooms")
@Operation(summary = "방 목록 조회", description = "강연자가 생성한 방 목록을 조회합니다.")
public ResponseDto<?> getRooms(@UserId Long userId) {
List<RoomDto> rooms = roomService.getAllRooms(userId);
return ResponseDto.ok(rooms);
}

/* 강연자가 특정 방에 대한 상세정보를 조회한다. 해당 강의에 남겨진 질문들과, 수강생들이 남겨준 리뷰점수를 확인할 수 있다.
* id(강의실ID), name(방이름), date(강연날짜), location(강연장소), url(강의실 uuid), likeThreshold(명예의전당 등록 공감 수 기준) */
@GetMapping("/api/v1/users/rooms/{room_id}")
@Operation(summary = "방 상세 조회", description = "특정 방에 대한 상세 정보를 조회합니다.")
public ResponseDto<?> getRoom(@PathVariable("room_id") Long roomId) {
return ResponseDto.ok(roomService.getRoom(roomId));
}

/* 강연자가 방을 생성한다.
* 1. 클라이언트로부터 title(방제목), date(강연날짜), location(강연장소), likeThreshold(특별한 질문으로 간주할 공감 수 기준)을 입력받고
* 2. UUID를 발급하여 해당 방에 매핑시키고
* 3. 방을 생성한 뒤
* 4. 강연자에게 uuid를 반환한다. */
@PostMapping("/api/v1/users/rooms")
@Operation(summary = "방 생성", description = "강연자가 방을 생성합니다.")
public ResponseDto<?> createRoom(@UserId Long userId, @RequestBody RoomDto roomDto) {
return ResponseDto.created(roomService.createRoom(userId, roomDto));
}

/* 강연자가 특정 방에서 진행한 강연에 대한 리뷰와 그 강연에서 발생했던 모든 질문들을 조회한다.
* 질문 배열 + 각 리뷰 타입의 평균 점수 */
@GetMapping("/api/v1/users/rooms/{room_id}/reviews")
@Operation(summary = "리뷰 조회", description = "특정 방에 대한 리뷰를 조회합니다.")
public ResponseDto<?> getReview(@PathVariable("room_id") Long roomId) {
ReviewDto reviewDto = reviewService.getReviewByRoomId(roomId);
return ResponseDto.ok(reviewDto);
}

/* 수강자가 강연에 대한 리뷰 점수를 매긴다.
* 리뷰에는 총 5가지 타입이 있으며, 각 타입에 대해 1점~5점 사이의 점수를 매길 수 있다. */
@PostMapping("/api/v1/users/rooms/{room_id}/reviews")
@Operation(summary = "리뷰 생성", description = "특정 방에 대한 리뷰를 생성합니다.")
public ResponseDto<?> createReview(
@PathVariable("room_id") Long roomId,
@Valid @RequestBody ReviewCreateDto reviewCreateDto,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,11 @@ public class WebSocketEventListener {
private final RoomService roomService;
private final JwtUtil jwtUtil;

/* 웹소켓이 연결되고 나서 발생하는 이벤트.
* 웹소켓 연결 직후, native header로부터 Authorization의 값인 access_token을 추출한다.
* 만약 토큰이 비어있거나, 잘못된 토큰이 주어지면 에러 로그를 출력하고 소켓 연결이 끊긴다.
* 추출한 토큰으로부터 유저 정보인 userId를 가져와, 해당 유저 엔티티의 세션아이디를 웹소켓의 세션아이디로 업데이트한다.
* 추후 이 세션아이디는, 웹소켓 관련 요청 시 유저와의 맵핑을 위해 사용된다. */
@EventListener
public void handleWebSocketConnectListener(SessionConnectEvent event) {
StompHeaderAccessor headerAccessor = StompHeaderAccessor.wrap(event.getMessage());
Expand Down Expand Up @@ -66,6 +71,10 @@ public void handleWebSocketConnectListener(SessionConnectEvent event) {

}

/* 웹소켓 연결이 끊기고 나서 발생하는 이벤트
* 웹소켓 연결이 끝나고 나면, 연결이 종료된 User를 웹소켓 세션 아이디를 통해 맵핑하여 찾는다.
* 찾은 User가 Guest(수강자)인지 아닌지(강연자) 구분하고, 수강자라면 Guest와 User를 삭제한다. (임시 유저는 더미로 안남김)
* 강연자라면, 해당 엔티티의 sessionId만 null로 업데이트한다. (강연자는 소켓이 끊겼다고 삭제되면 안됨) */
@EventListener
public void handleWebSocketDisconnectListener(SessionDisconnectEvent event) {
StompHeaderAccessor headerAccessor = StompHeaderAccessor.wrap(event.getMessage());
Expand Down
1 change: 0 additions & 1 deletion src/main/java/com/gooaein/goojilgoojil/domain/Review.java
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
package com.gooaein.goojilgoojil.domain;

import com.gooaein.goojilgoojil.dto.response.ReviewDto;
import jakarta.persistence.*;
import lombok.*;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ public class Question {
private final byte[] avatarBase64; // 질문자 아바타
private Integer likeCount; // 좋아요 수
private String sendTime; // 질문 보낸 시간
private String status;
private String status; // 질문 답변 여부

@Builder
public Question(String roomId, String title, String content, byte[] avatarBase64, Integer likeCount, String status) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,10 @@
import jakarta.validation.constraints.NotNull;
import org.springframework.http.HttpStatus;
import org.springframework.lang.Nullable;
import org.springframework.validation.FieldError;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.MissingServletRequestParameterException;
import org.springframework.web.method.annotation.MethodArgumentTypeMismatchException;

import java.util.List;

@Schema(name = "ResponseDto", description = "API 응답 DTO")
public record ResponseDto<T>(@JsonIgnore HttpStatus httpStatus,
@Schema(name = "success", description = "API 호출 성공 여부")
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ public class RoomDto {
private Long id;
private String name;
private LocalDateTime date;
private String location; // location 필드 추가
private String location;
@JsonProperty("like_threshold")
private Integer likeThreshold;
private String url;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
package com.gooaein.goojilgoojil.dto.response;

import com.gooaein.goojilgoojil.domain.Review;
import com.gooaein.goojilgoojil.domain.Room;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ public enum ErrorCode {
ALREADY_LIKED_QUESTION(40008, HttpStatus.BAD_REQUEST, "이미 좋아요를 누르셨습니다."),
CANNOT_END_ROOM(40009, HttpStatus.BAD_REQUEST, "방 종료는 강연자만 가능합니다."),
CANNOT_CHECK_QUESTION(40010, HttpStatus.BAD_REQUEST, "질문 답변은 강연자만 가능합니다."),
CANNOT_CREATE_ROOM(40011, HttpStatus.BAD_REQUEST, "방 생성은 강연자만 가능합니다."),


// Access Denied Error
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,8 @@
package com.gooaein.goojilgoojil.service;

import com.gooaein.goojilgoojil.annotation.UserId;
import com.gooaein.goojilgoojil.domain.User;
import com.gooaein.goojilgoojil.dto.global.ResponseDto;
import com.gooaein.goojilgoojil.dto.request.AuthSignUpDto;
import com.gooaein.goojilgoojil.dto.request.OauthLoginDto;
import com.gooaein.goojilgoojil.dto.response.JwtTokenDto;
import com.gooaein.goojilgoojil.dto.type.ERole;
import com.gooaein.goojilgoojil.exception.CommonException;
import com.gooaein.goojilgoojil.exception.ErrorCode;
import com.gooaein.goojilgoojil.repository.UserRepository;
Expand All @@ -15,11 +11,6 @@
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;

import java.util.Optional;

@Service
@RequiredArgsConstructor
Expand Down
Original file line number Diff line number Diff line change
@@ -1,14 +1,10 @@
package com.gooaein.goojilgoojil.service;

import java.io.IOException;
import java.util.List;
import java.util.UUID;

import com.gooaein.goojilgoojil.dto.global.ResponseDto;
import com.gooaein.goojilgoojil.dto.response.RoomNumberDto;
import com.gooaein.goojilgoojil.security.info.AuthenticationResponse;
import com.gooaein.goojilgoojil.utility.CookieUtil;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
package com.gooaein.goojilgoojil.service;

import com.gooaein.goojilgoojil.domain.Guest;
import com.gooaein.goojilgoojil.domain.Like;
import com.gooaein.goojilgoojil.domain.Room;
import com.gooaein.goojilgoojil.domain.User;
import com.gooaein.goojilgoojil.domain.nosql.Question;
import com.gooaein.goojilgoojil.dto.request.LikeRequestDto;
Expand All @@ -20,7 +18,6 @@

import java.time.OffsetDateTime;
import java.util.List;
import java.util.Optional;
import java.util.concurrent.TimeUnit;

@RequiredArgsConstructor
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@

import com.gooaein.goojilgoojil.domain.Review;
import com.gooaein.goojilgoojil.domain.Room;
import com.gooaein.goojilgoojil.domain.nosql.Question;
import com.gooaein.goojilgoojil.dto.response.ReviewCreateDto;
import com.gooaein.goojilgoojil.dto.response.ReviewDto;
import com.gooaein.goojilgoojil.dto.response.ReviewQuestionsDto;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,8 @@ public void endRoom(String sessionId, String roomId, String url) {
public UUIDDto createRoom(Long userId, RoomDto roomDto) {
User user = userRepository.findById(userId)
.orElseThrow(() -> new CommonException(ErrorCode.NOT_FOUND_USER));
if (guestRepository.findById(userId).isPresent())
throw new CommonException(ErrorCode.CANNOT_CREATE_ROOM);
String generatedUrl = generateRandomString(12);

Room room = Room.builder()
Expand Down
Loading

0 comments on commit 08906ea

Please sign in to comment.