Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,11 @@

import com.market.saessag.domain.chat.dto.ChatMessageResponse;
import com.market.saessag.domain.chat.service.ChatMessageService;
import com.market.saessag.domain.user.dto.SignInResponse;
import com.market.saessag.global.response.ApiResponse;
import com.market.saessag.global.response.SuccessCode;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpSession;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.*;

Expand Down Expand Up @@ -39,8 +41,12 @@ public ApiResponse<List<ChatMessageResponse>> searchMessages(

// 읽음 처리
@PostMapping("/{roomId}/mark-read")
public ApiResponse<Void> markMessageAsRead(@PathVariable Long roomId, HttpServletRequest request) { //user 세션으로 바꿀 것
chatMessageService.markMessagesAsRead(roomId, request);
public ApiResponse<Void> markMessageAsRead(@PathVariable Long roomId, HttpServletRequest request) {
// 변경된 세션 처리 통합한 후 수정하겠습니다!
HttpSession session = request.getSession();
SignInResponse userSession = (SignInResponse) session.getAttribute("userProfile");

chatMessageService.markMessagesAsRead(roomId, userSession);
return ApiResponse.success();
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,12 @@
import com.market.saessag.domain.chat.service.ChatFileService;
import com.market.saessag.domain.chat.service.ChatMessageService;
import com.market.saessag.domain.chat.service.ChatSubscriptionService;
import com.market.saessag.domain.user.dto.SignInResponse;
import com.market.saessag.global.response.ApiResponse;
import com.market.saessag.global.response.SuccessCode;
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.handler.annotation.SendTo;
import org.springframework.messaging.handler.annotation.*;
import org.springframework.messaging.simp.SimpMessageHeaderAccessor;
import org.springframework.stereotype.Controller;

import java.util.List;
Expand All @@ -28,11 +27,14 @@ public class ChatMessageWebSocketController {
// 메시지 전송
@MessageMapping("/chat/{roomId}/sendMessage")
@SendTo("/topic/chat/{roomId}")
public ApiResponse<ChatMessageResponse> sendMessage(@DestinationVariable Long roomId, @Payload ChatMessageRequest message) {
ChatMessageResponse savedMessage = chatMessageService.saveMessage(roomId, message);
public ApiResponse<ChatMessageResponse> sendMessage(@DestinationVariable Long roomId, @Payload ChatMessageRequest message,
SimpMessageHeaderAccessor headerAccessor) {

// 추후에 세션 정보로 읽음 처리 꼭 할 것
// 현재 임시로 메시지 보낸 후 메시지 읽음 API 사용하여 자신의 메시지 읽음 처리하도록 하는 중
ChatMessageResponse savedMessage = chatMessageService.saveMessage(roomId, message, headerAccessor);

//메시지 읽음 처리
SignInResponse user = (SignInResponse) headerAccessor.getSessionAttributes().get("userProfile");
chatMessageService.markMessagesAsRead(roomId, user);

//오프라인 구독자들에게 메시지 전송
chatSubscriptionService.sendToOffSubscriber(savedMessage, roomId);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
package com.market.saessag.domain.chat.controller;

import com.market.saessag.domain.chat.dto.ChatRoomCreateResponse;
import com.market.saessag.domain.chat.dto.ChatRoomRequest;
import com.market.saessag.domain.chat.dto.ChatRoomResponse;
import com.market.saessag.domain.chat.service.ChatRoomService;
import com.market.saessag.global.response.ApiResponse;
import com.market.saessag.global.response.SuccessCode;
import jakarta.servlet.http.HttpServletRequest;
import lombok.RequiredArgsConstructor;
import org.springframework.data.util.Pair;
import org.springframework.web.bind.annotation.*;

import java.util.List;
Expand All @@ -19,10 +21,10 @@ public class ChatRoomController {

// 방 생성
@PostMapping()
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

엔드포인트를 지정하지 않은 이유가 있을까요?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

/api/chat/room 엔드포인트에 POST 요청을 보내면 방 생성 엔드포인트로 괜찮을거라 생각했었는데 다시 보니 /create 등으로 지정하는게 더 좋을 것 같습니다!

public ApiResponse<ChatRoomResponse> createChatRoom(@RequestBody ChatRoomRequest request) {
ChatRoomResponse chatRoom = chatRoomService.createOrGetChatRoom(request.getProductId(), request.getBuyerId(), request.getSellerId());
public ApiResponse<ChatRoomCreateResponse> createChatRoom(@RequestBody ChatRoomRequest request) {
ChatRoomCreateResponse chatRoom = chatRoomService.createOrGetChatRoom(request.getProductId(), request.getBuyerId(), request.getSellerId());

return ApiResponse.success(SuccessCode.ROOM_CREATED, chatRoom);
return ApiResponse.success(chatRoom);
}

// 특정 유저가 현재 속해있는 채팅방 리스트 반환
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@
@Getter
@Builder
public class ChatMessageRequest {
private Long senderId;
private String content;
private LocalDateTime timeStamp;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package com.market.saessag.domain.chat.dto;

import com.market.saessag.global.response.SuccessCode;
import lombok.Builder;
import lombok.Getter;

@Getter
@Builder
public class ChatRoomCreateResponse {
private SuccessCode successCode;
private ChatRoomResponse chatRoomResponse;
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
import lombok.RequiredArgsConstructor;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.messaging.simp.SimpMessageHeaderAccessor;
import org.springframework.stereotype.Service;

import java.time.LocalDateTime;
Expand All @@ -35,11 +36,12 @@ public class ChatMessageService {
private final UserRepository userRepository;
private final ChatMessageReadRepository chatMessageReadRepository;

public ChatMessageResponse saveMessage(Long roomId, ChatMessageRequest message) {
public ChatMessageResponse saveMessage(Long roomId, ChatMessageRequest message, SimpMessageHeaderAccessor headerAccessor) {
ChatRoom chatRoom = chatRoomRepository.findById(roomId)
.orElseThrow(() -> new CustomException(ErrorCode.ROOM_NOT_FOUND));

User sender = userRepository.findById(message.getSenderId()) //세션으로 변경
SignInResponse user = (SignInResponse) headerAccessor.getSessionAttributes().get("userProfile");
User sender = userRepository.findById(user.getId())
.orElseThrow(() -> new CustomException(ErrorCode.USER_NOT_FOUND));

ChatMessage newMessage = ChatMessage.builder()
Expand Down Expand Up @@ -110,11 +112,17 @@ public List<ChatMessageResponse> searchMessages(Long roomId, String keyword, Htt

// 읽음 처리
@Transactional
public void markMessagesAsRead(Long roomId, HttpServletRequest httpRequest) {
public void markMessagesAsRead(Long roomId, SignInResponse userSession) {
ChatRoom chatRoom = chatRoomRepository.findById(roomId)
.orElseThrow(() -> new CustomException(ErrorCode.ROOM_NOT_FOUND));

User user = getUserFromSession(httpRequest);

if (userSession == null) {
throw new CustomException(ErrorCode.UNAUTHORIZED);
}

User user = userRepository.findById(userSession.getId())
.orElseThrow(() -> new CustomException(ErrorCode.USER_NOT_FOUND));

List<ChatMessage> unreadMessages = chatMessageRepository.findByChatRoom(chatRoom);

Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.market.saessag.domain.chat.service;

import com.market.saessag.domain.chat.dto.ChatRoomCreateResponse;
import com.market.saessag.domain.chat.dto.ChatRoomResponse;
import com.market.saessag.domain.chat.entity.ChatMessage;
import com.market.saessag.domain.chat.entity.ChatRoom;
Expand All @@ -14,14 +15,17 @@
import com.market.saessag.domain.user.repository.UserRepository;
import com.market.saessag.global.exception.CustomException;
import com.market.saessag.global.exception.ErrorCode;
import com.market.saessag.global.response.SuccessCode;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpSession;
import jakarta.transaction.Transactional;
import lombok.RequiredArgsConstructor;
import org.springframework.data.util.Pair;
import org.springframework.stereotype.Service;

import java.time.LocalDateTime;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;

@Service
Expand All @@ -34,7 +38,7 @@ public class ChatRoomService {
private final ChatSubscriptionRepository chatSubscriptionRepository;

//방 생성 (방 존재 시 채팅방 반환)
public ChatRoomResponse createOrGetChatRoom(Long productId, Long buyerId, Long sellerId) {
public ChatRoomCreateResponse createOrGetChatRoom(Long productId, Long buyerId, Long sellerId) {
Product product = productRepository.findById(productId)
.orElseThrow(() -> new CustomException(ErrorCode.PRODUCT_NOT_FOUND));

Expand All @@ -44,21 +48,34 @@ public ChatRoomResponse createOrGetChatRoom(Long productId, Long buyerId, Long s
User seller = userRepository.findById(sellerId)
.orElseThrow(() -> new IllegalArgumentException("판매자를 찾을 수 없습니다."));

ChatRoom chatRoom = chatRoomRepository.findByProductAndBuyerAndSeller(product, buyer, seller)
.orElseGet(() -> {
ChatRoom newRoom = ChatRoom.builder()
.product(product)
.buyer(buyer)
.seller(seller)
.build();
Optional<ChatRoom> chatRoom = chatRoomRepository.findByProductAndBuyerAndSeller(product, buyer, seller);

return chatRoomRepository.save(newRoom);
});
// 새로운 채팅방 생성하는 경우
if (chatRoom.isEmpty()) {
ChatRoom newRoom = ChatRoom.builder()
.product(product)
.buyer(buyer)
.seller(seller)
.build();

chatRoomRepository.save(newRoom);

// 구독 목록에 추가
subscribeUserToChatRoom(buyer, newRoom);
subscribeUserToChatRoom(seller, newRoom);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

새로운 Room을 생성하고, 판매자와 구매자를 추가한다.
라는 부분은 별도의 메소드나 클래스로 관리하는 편이 좋지 않을까 하는 생각도 드는데, 어떻게 생각하실까요?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

방을 생성하는 로직이 이 부분 말고 쓰이는 곳이 없어서 따로 분리하여 관리는 안 했는데, createNewRoom() 같은 메서드로 분리하면 해당 로직을 읽기 편해질 것 같다고 생각합니다!


return ChatRoomCreateResponse.builder()
.successCode(SuccessCode.ROOM_CREATED)
.chatRoomResponse(chatRoomResponseEntity(newRoom))
.build();
}

subscribeUserToChatRoom(buyer, chatRoom);
subscribeUserToChatRoom(seller, chatRoom);
// 기존 채팅방 불러오는 경우

return chatRoomResponseEntity(chatRoom);
return ChatRoomCreateResponse.builder()
.successCode(SuccessCode.ROOM_FETCHED)
.chatRoomResponse(chatRoomResponseEntity(chatRoom.get()))
.build();
}

// 유저의 모든 채팅방 반환
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
package com.market.saessag.global.config;

import com.market.saessag.global.interceptor.WebSocketHandShakeInterceptor;
import org.springframework.context.annotation.Configuration;
import org.springframework.messaging.simp.config.MessageBrokerRegistry;
import org.springframework.web.socket.config.annotation.EnableWebSocketMessageBroker;
import org.springframework.web.socket.config.annotation.StompEndpointRegistry;
import org.springframework.web.socket.config.annotation.WebSocketMessageBrokerConfigurer;
import org.springframework.web.socket.server.support.HttpSessionHandshakeInterceptor;

@Configuration
@EnableWebSocketMessageBroker
Expand All @@ -13,7 +15,8 @@ public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {
public void registerStompEndpoints(StompEndpointRegistry registry) {
//WebSocket 엔드포인트 설정 (ws://localhost:8080/ws)
registry.addEndpoint("/ws")
.setAllowedOrigins("*");
.setAllowedOrigins("*")
.addInterceptors(new WebSocketHandShakeInterceptor());
}

@Override
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package com.market.saessag.global.interceptor;

import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpSession;
import org.springframework.http.server.ServerHttpRequest;
import org.springframework.http.server.ServerHttpResponse;
import org.springframework.http.server.ServletServerHttpRequest;
import org.springframework.web.socket.WebSocketHandler;
import org.springframework.web.socket.server.HandshakeInterceptor;

import java.util.Map;

public class WebSocketHandShakeInterceptor implements HandshakeInterceptor {

@Override
public boolean beforeHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler wsHandler, Map<String, Object> attributes) throws Exception {
if (request instanceof ServletServerHttpRequest) {
ServletServerHttpRequest servletRequest = (ServletServerHttpRequest) request;
HttpSession session = servletRequest.getServletRequest().getSession(false);

if (session != null) {
// 세션 정보 유저 정보에서 가져와 WebSocket 세션에 저장
attributes.put("userProfile", session.getAttribute("userProfile"));
}
}
return true;
}

@Override
public void afterHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler wsHandler, Exception exception) {

}
}
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ public enum SuccessCode {
EMAIL_VERIFIED(HttpStatus.OK, "이메일 인증이 완료되었습니다"),
SIGNUP_COMPLETED(HttpStatus.CREATED, "회원가입이 완료되었습니다"),
ROOM_CREATED(HttpStatus.OK,"방이 성공적으로 생성되었습니다."),
ROOM_FETCHED(HttpStatus.OK,"기존 채팅방을 불러왔습니다."),
DATA_FETCHED(HttpStatus.OK, "데이터 조회에 성공했습니다.");

private final HttpStatus httpStatus;
Expand Down