Skip to content

Commit

Permalink
feat: 채팅 기능 완성
Browse files Browse the repository at this point in the history
  • Loading branch information
sosow0212 committed May 27, 2024
1 parent d5c1e77 commit 89ad3cc
Show file tree
Hide file tree
Showing 28 changed files with 570 additions and 41 deletions.
53 changes: 53 additions & 0 deletions market-api/src/docs/asciidoc/chat.adoc
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
= Chat API 문서
:doctype: book
:icons: font
:source-highlighter: highlightjs
:toc: left
:toclevels: 3

== 사용자의 채팅 내역을 모두 반환한다. (GET /api/chats)

=== Request

include::{snippets}/chat-room-controller-test/find_all_my_chatting_rooms/request-headers.adoc[]
include::{snippets}/chat-room-controller-test/find_all_my_chatting_rooms/http-request.adoc[]

=== Response

include::{snippets}/chat-room-controller-test/find_all_my_chatting_rooms/response-fields.adoc[]
include::{snippets}/chat-room-controller-test/find_all_my_chatting_rooms/http-response.adoc[]

== 구매자가 채팅을 신청해서 채팅방을 생성한다. (POST /api/products/{productId}/chats)

=== Request

include::{snippets}/chat-room-controller-test/create_new_chatting_room/request-headers.adoc[]
include::{snippets}/chat-room-controller-test/create_new_chatting_room/path-parameters.adoc[]
include::{snippets}/chat-room-controller-test/create_new_chatting_room/request-fields.adoc[]
include::{snippets}/chat-room-controller-test/create_new_chatting_room/http-request.adoc[]

=== Response

include::{snippets}/chat-room-controller-test/create_new_chatting_room/response-headers.adoc[]
include::{snippets}/chat-room-controller-test/create_new_chatting_room/http-response.adoc[]

== 채팅방 내역을 반환한다 (GET /api/products/{productId}/chats/{chattingRoomId}?chatId=:&pageSize=:)

=== Request

include::{snippets}/chat-room-controller-test/find_chatting_history/request-headers.adoc[]
include::{snippets}/chat-room-controller-test/find_chatting_history/path-parameters.adoc[]
include::{snippets}/chat-room-controller-test/find_chatting_history/query-parameters.adoc[]
include::{snippets}/chat-room-controller-test/find_chatting_history/request-body.adoc[]
include::{snippets}/chat-room-controller-test/find_chatting_history/http-request.adoc[]

=== Response

include::{snippets}/chat-room-controller-test/find_chatting_history/response-fields.adoc[]
include::{snippets}/chat-room-controller-test/find_chatting_history/http-response.adoc[]

== 채팅 방법

- endpoint : `ws://localhost:8080/ws-stomp`
- subscribe url : `ws://localhost:8080/ws-stomp/sub/chats/{chattingRoomId}`
- publish url : `ws://localhost:8080/ws-stomp/pub/chats/{chattingRoomId}/messages`
2 changes: 2 additions & 0 deletions market-api/src/docs/asciidoc/index.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -20,3 +20,5 @@ include::member.adoc[]
include::product.adoc[]

include::voucher.adoc[]

include::chat.adoc[]
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package com.server.market.application.chat;

import com.server.market.domain.chat.ChatRepository;
import com.server.market.domain.chat.ChattingRoomRepository;
import com.server.market.domain.chat.dto.ChatHistoryResponse;
import com.server.market.domain.chat.dto.ChattingRoomSimpleResponse;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.List;

@RequiredArgsConstructor
@Transactional(readOnly = true)
@Service
public class ChatRoomQueryService {

private final ChatRepository chatRepository;
private final ChattingRoomRepository chattingRoomRepository;

public List<ChattingRoomSimpleResponse> findAllMyChats(final Long authId) {
return chattingRoomRepository.findMyChattingRooms(authId);
}

public List<ChatHistoryResponse> findChattingHistoryByChatId(final Long authId, final Long chattingRoomId, final Long chatId, final Integer pageSize) {
return chatRepository.findChattingHistoryByChatId(authId, chattingRoomId, chatId, pageSize);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,19 +3,28 @@
import com.server.market.application.chat.dto.ChatMessageRequest;
import com.server.market.domain.chat.Chat;
import com.server.market.domain.chat.ChatRepository;
import com.server.market.domain.chat.ChattingRoom;
import com.server.market.domain.chat.ChattingRoomRepository;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

@RequiredArgsConstructor
@Transactional(readOnly = true)
@Service
public class ChatRoomService {

private final ChattingRoomRepository chattingRoomRepository;
private final ChatRepository chatRepository;

// public Chat saveMessage(final Long chatRoomId, final ChatMessageRequest chatMessageRequest) {
// return chatRepository.save(Chat.builder()
// .chatRoomId(chatRoomId)
// .message(chatMessageRequest.message())
// .build());
// }
@Transactional
public ChattingRoom createChattingRoomByBuyer(final Long authMember, final Long productId, final Long sellerId) {
ChattingRoom chattingRoom = ChattingRoom.createNewChattingRoom(productId, authMember, sellerId);
return chattingRoomRepository.save(chattingRoom);
}

@Transactional
public Chat chat(final Long chatRoomId, final ChatMessageRequest chattingRequest) {
return chatRepository.save(Chat.of(chatRoomId, chattingRequest.senderId(), chattingRequest.message()));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@

public record ChatMessageRequest(
Long senderId,
String senderName,
String message
) {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package com.server.market.application.chat.dto;

import jakarta.validation.constraints.NotNull;

public record ChattingRoomCreateRequest(
@NotNull(message = "판매자 id가 들어와야 합니다.")
Long sellerId
) {
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,8 @@ public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {
@Override
public void registerStompEndpoints(StompEndpointRegistry registry) {
registry.addEndpoint("/ws-stomp")
.setAllowedOrigins("*");
.setAllowedOrigins("*")
.addInterceptors();
}

@Override
Expand Down
14 changes: 14 additions & 0 deletions market-api/src/main/java/com/server/market/domain/chat/Chat.java
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package com.server.market.domain.chat;

import com.server.global.domain.BaseEntity;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
Expand All @@ -27,8 +28,21 @@ public class Chat extends BaseEntity {
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;

@Column(nullable = false)
private Long chatRoomId;

@Column(nullable = false)
private Long senderId;

@Lob
@Column(nullable = false)
private String message;

public static Chat of(final Long chatId, final Long senderId, final String message) {
return Chat.builder()
.chatRoomId(chatId)
.senderId(senderId)
.message(message)
.build();
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,12 @@
package com.server.market.domain.chat;

import com.server.market.domain.chat.dto.ChatHistoryResponse;

import java.util.List;

public interface ChatRepository {

Chat save(Chat chat);

List<ChatHistoryResponse> findChattingHistoryByChatId(Long authId, Long chattingRoomId, Long chatId, Integer pageSize);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
package com.server.market.domain.chat;

import com.server.global.domain.BaseEntity;
import com.server.market.domain.chat.vo.ChattingStatus;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.EnumType;
import jakarta.persistence.Enumerated;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import jakarta.persistence.Table;
import lombok.AccessLevel;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.NoArgsConstructor;

@Getter
@Builder
@EqualsAndHashCode(of = "id", callSuper = false)
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@AllArgsConstructor(access = AccessLevel.PRIVATE)
@Entity
@Table(name = "chatting_room")
public class ChattingRoom extends BaseEntity {

@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;

@Column(nullable = false)
private Long productId;

@Column(nullable = false)
private Long buyerId;

@Column(nullable = false)
private Long sellerId;

@Column(nullable = false)
@Enumerated(EnumType.STRING)
private ChattingStatus chattingStatus;

public static ChattingRoom createNewChattingRoom(final Long productId, final Long buyerId, final Long sellerId) {
return ChattingRoom.builder()
.productId(productId)
.buyerId(buyerId)
.sellerId(sellerId)
.chattingStatus(ChattingStatus.PROCESS)
.build();
}

public void done() {
this.chattingStatus = ChattingStatus.DONE;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package com.server.market.domain.chat;

import com.server.market.domain.chat.dto.ChattingRoomSimpleResponse;

import java.util.List;

public interface ChattingRoomRepository {

ChattingRoom save(ChattingRoom chattingRoom);

List<ChattingRoomSimpleResponse> findMyChattingRooms(Long authId);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package com.server.market.domain.chat.dto;

import java.time.LocalDateTime;

public record ChatHistoryResponse(
Long chatRoomId,
Long chattingId,
Long senderId,
String senderNickname,
String message,
Boolean isSendByMe,
LocalDateTime sendTime
) {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package com.server.market.domain.chat.dto;

import java.time.LocalDateTime;

public record ChattingRoomSimpleResponse(
String productName,
Long productId,
Long chattingRoomId,
Long sellerId,
String sellerNickname,
LocalDateTime lastChattingTime
) {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package com.server.market.domain.chat.vo;

public enum ChattingStatus {

PROCESS,
DONE
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
package com.server.market.infrastructure.chat;

import com.querydsl.core.types.dsl.BooleanExpression;
import com.querydsl.jpa.impl.JPAQueryFactory;
import com.server.market.domain.chat.dto.ChatHistoryResponse;
import com.server.market.domain.chat.dto.ChattingRoomSimpleResponse;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Repository;

import java.util.List;

import static com.querydsl.core.types.Projections.constructor;
import static com.server.market.domain.chat.QChat.chat;
import static com.server.market.domain.chat.QChattingRoom.chattingRoom;
import static com.server.market.domain.product.QProduct.product;
import static com.server.member.domain.member.QMember.member;

@RequiredArgsConstructor
@Repository
public class ChatQueryRepository {

private final JPAQueryFactory jpaQueryFactory;

public List<ChattingRoomSimpleResponse> findMyChattingRooms(final Long authId) {
return jpaQueryFactory.select(constructor(ChattingRoomSimpleResponse.class,
product.description.title,
product.id,
chattingRoom.id,
chattingRoom.sellerId,
member.nickname,
chattingRoom.createdAt
)).from(chattingRoom)
.join(product).on(chattingRoom.productId.eq(product.id))
.join(member).on(chattingRoom.sellerId.eq(member.id))
.where(chattingRoom.buyerId.eq(authId))
.fetch();
}

public List<ChatHistoryResponse> findChattingHistoryByChatId(final Long authId, final Long chattingRoomId, final Long chatId, final Integer pageSize) {
return jpaQueryFactory.select(constructor(ChatHistoryResponse.class,
chat.chatRoomId,
chat.id,
chat.senderId,
member.nickname, // join
chat.message,
chat.senderId.eq(authId), // 보낸 사람이 인증된 사용자인지 여부
chat.createdAt
)).from(chat)
.leftJoin(member).on(member.id.eq(chat.senderId)) // 이 부분이 잘못됨
.where(
chat.chatRoomId.eq(chattingRoomId),
ltChatId(chatId)
)
.orderBy(chat.createdAt.desc())
.limit(pageSize)
.fetch();
}

private BooleanExpression ltChatId(final Long chatId) {
if (chatId == null) {
return null;
}

return chat.id.lt(chatId);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,26 @@

import com.server.market.domain.chat.Chat;
import com.server.market.domain.chat.ChatRepository;
import com.server.market.domain.chat.dto.ChatHistoryResponse;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Repository;

import java.util.List;

@RequiredArgsConstructor
@Repository
public class ChatRepositoryImpl implements ChatRepository {

private final ChatJpaRepository chatJpaRepository;
private final ChatQueryRepository chatQueryRepository;

@Override
public Chat save(final Chat chat) {
return chatJpaRepository.save(chat);
}

@Override
public List<ChatHistoryResponse> findChattingHistoryByChatId(final Long authId, final Long chattingRoomId, final Long chatId, final Integer pageSize) {
return chatQueryRepository.findChattingHistoryByChatId(authId, chattingRoomId, chatId, pageSize);
}
}
Loading

0 comments on commit 89ad3cc

Please sign in to comment.