Skip to content

Commit

Permalink
Merge pull request #52 from Nawabali-project/feature/34/cicdNotification
Browse files Browse the repository at this point in the history
웹소켓 + stomp 채팅 기능
  • Loading branch information
minnieming authored Apr 7, 2024
2 parents 2ba7344 + 9cf0833 commit 16182ca
Show file tree
Hide file tree
Showing 16 changed files with 648 additions and 2 deletions.
12 changes: 12 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -41,3 +41,15 @@ out/
/src/main/resources/application.properties
src/main/resources/application.properties

/src/main/generated/com/nawabali/nawabali/constant/QAddress.java
/src/main/generated/com/nawabali/nawabali/domain/QBookMark.java
/src/main/generated/com/nawabali/nawabali/domain/QChat_ChatMessage.java
/src/main/generated/com/nawabali/nawabali/domain/QChat_ChatRoom.java
/src/main/generated/com/nawabali/nawabali/domain/QComment.java
/src/main/generated/com/nawabali/nawabali/domain/QLike.java
/src/main/generated/com/nawabali/nawabali/domain/QPost.java
/src/main/generated/com/nawabali/nawabali/domain/image/QPostImage.java
/src/main/generated/com/nawabali/nawabali/domain/image/QProfileImage.java
/src/main/generated/com/nawabali/nawabali/domain/QTimeStamp.java
/src/main/generated/com/nawabali/nawabali/constant/QTown.java
/src/main/generated/com/nawabali/nawabali/domain/QUser.java
13 changes: 13 additions & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,19 @@ dependencies {
//REDIS
implementation 'org.springframework.boot:spring-boot-starter-data-redis:3.2.0'

// webSoket
implementation 'org.springframework.boot:spring-boot-starter-websocket'
implementation 'org.springframework.boot:spring-boot-starter-freemarker'
implementation 'org.springframework.boot:spring-boot-devtools'
implementation 'org.webjars.bower:bootstrap:4.3.1'
implementation 'org.webjars.bower:vue:2.5.16'
implementation 'org.webjars.bower:axios:0.17.1'
implementation 'org.webjars:sockjs-client:1.1.2'
implementation 'org.webjars:stomp-websocket:2.3.3-1'
implementation 'com.google.code.gson:gson:2.8.0'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
implementation 'org.springframework.boot:spring-boot-starter-thymeleaf'

implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
implementation 'org.springframework.boot:spring-boot-starter-security'
implementation 'org.springframework.boot:spring-boot-starter-web'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,8 @@ public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Excepti
.requestMatchers("/api-test").permitAll()
.requestMatchers("/users/check-nickname").permitAll()
.requestMatchers("/email-verification").permitAll()
.requestMatchers("/ws-stomp/**").permitAll()
.requestMatchers("/chat/**").permitAll()
.anyRequest().authenticated() // 그 외 모든 요청 인증처리
);

Expand Down
24 changes: 24 additions & 0 deletions src/main/java/com/nawabali/nawabali/config/WebSocketConfig.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package com.nawabali.nawabali.config;

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;

@Configuration
@EnableWebSocketMessageBroker
public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {

@Override
public void configureMessageBroker(MessageBrokerRegistry config) {
config.enableSimpleBroker("/sub");
config.setApplicationDestinationPrefixes("/pub");
}

@Override // 웹소켓 핸드셰이크 커넥션
public void registerStompEndpoints(StompEndpointRegistry registry) {
registry.addEndpoint("/ws-stomp").setAllowedOriginPatterns("*")
.withSockJS();
}
}
58 changes: 58 additions & 0 deletions src/main/java/com/nawabali/nawabali/controller/ChatController.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
package com.nawabali.nawabali.controller;

import com.nawabali.nawabali.domain.Chat;
import com.nawabali.nawabali.domain.User;
import com.nawabali.nawabali.dto.ChatDto;
import com.nawabali.nawabali.exception.CustomException;
import com.nawabali.nawabali.exception.ErrorCode;
import com.nawabali.nawabali.repository.ChatMessageRepository;
import com.nawabali.nawabali.repository.ChatRoomRepository;
import com.nawabali.nawabali.repository.UserRepository;
import com.nawabali.nawabali.security.UserDetailsImpl;
import com.nawabali.nawabali.service.ChatMessageService;
import lombok.RequiredArgsConstructor;
import org.springframework.messaging.handler.annotation.MessageMapping;
import org.springframework.messaging.simp.SimpMessageSendingOperations;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;

import java.time.LocalDateTime;
import java.util.List;

@RequiredArgsConstructor
@Controller
public class ChatController {

private final SimpMessageSendingOperations messagingTemplate;
private final ChatMessageService chatMessageService;
private final ChatMessageRepository chatMessageRepository;
private final UserRepository userRepository;
private final ChatRoomRepository chatRoomRepository;

@MessageMapping("/chat/message")
public void message(ChatDto.ChatMessageDto message) {
if (ChatDto.ChatMessageDto.MessageType.ENTER.equals(message.getType()))
message.setMessage(message.getSender() + "님이 입장하셨습니다.");

// User userOptional = userRepository.findById(message.getUserId())
// .orElseThrow(()-> new CustomException(ErrorCode.FORBIDDEN_CHATMESSAGE));

Chat.ChatRoom chatRoom = chatRoomRepository.findById(message.getRoomId())
.orElseThrow(()-> new CustomException(ErrorCode.FORBIDDEN_CHATMESSAGE));

Chat.ChatMessage chatMessage = Chat.ChatMessage.builder()
.type(message.getType())
.sender(message.getSender())
.message(message.getMessage())
.createdAt(LocalDateTime.now())
// .user(userOptional)
.chatRoom(chatRoom)
.build();

chatMessageRepository.save(chatMessage);

messagingTemplate.convertAndSend("/sub/chat/room/" + message.getRoomId(), message);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
package com.nawabali.nawabali.controller;

import com.nawabali.nawabali.dto.ChatDto;
import com.nawabali.nawabali.repository.ChatMessageRepository;
import com.nawabali.nawabali.repository.ChatRoomRepository;
import com.nawabali.nawabali.service.ChatMessageService;
import com.nawabali.nawabali.service.ChatRoomService;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.*;

import java.util.List;

@RequiredArgsConstructor
@Controller
@RequestMapping("/chat")
public class ChatRoomController {

private final ChatRoomService chatRoomService;
private final ChatRoomRepository chatRoomRepository;
private final ChatMessageService chatMessageService;

// 채팅 리스트 화면
@GetMapping("/room")
public String rooms(Model model) {
return "/chat/room";
}

// 모든 채팅방 목록 반환
@GetMapping("/rooms")
@ResponseBody
public List<ChatDto.ChatRoomDto> room() {

return chatRoomService.room();

// return chatRoomRepository.findAllRoom();

}
// 채팅방 생성
@PostMapping("/room")
@ResponseBody
public ChatDto.ChatRoomDto createRoom(@RequestParam String name) {

return chatRoomService.createRoom(name);

// return chatRoomRepository.createChatRoom(name);
}

// 채팅방 입장 화면
@GetMapping("/room/enter/{roomId}")
public String roomDetail(Model model, @PathVariable String roomId) {
model.addAttribute("roomId", roomId);
return "/chat/roomdetail";
}

// 특정 채팅방 조회
@GetMapping("/room/found")
@ResponseBody
public List<ChatDto.ChatRoomDto> roomInfo(@RequestParam String name) {
return chatRoomService.roomInfo(name);
// return chatRoomRepository.findRoomById(roomId);
}

// ChatController는 웹소켓 endpoint를 담당해서 일반적인 http요청을 처리하지 않아 이곳으로 옮겨 놓음.
// @GetMapping("/room/{roomId}/message")
// public List<ChatDto.ChatMessageDto> loadMessage (@PathVariable Long roomId) {
// return chatMessageService.loadMessage(roomId);
// }
}
78 changes: 78 additions & 0 deletions src/main/java/com/nawabali/nawabali/domain/Chat.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
package com.nawabali.nawabali.domain;

import com.nawabali.nawabali.dto.ChatDto;
import jakarta.persistence.*;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.extern.slf4j.Slf4j;

import java.time.LocalDateTime;

public class Chat {

@Entity
@Getter
@Builder(toBuilder = true)
@NoArgsConstructor
@AllArgsConstructor
@Table(name = "chatMessage")
@Slf4j(topic = "chat 로그")
public static class ChatMessage {

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

@Column (nullable = false)
private ChatDto.ChatMessageDto.MessageType type; // 메세지 타입

@Column (nullable = false)
private String sender; // 메시지 보낸사람

@Column (nullable = false)
private String message; // 메시지

@Column (nullable = false)
private LocalDateTime createdAt;

@ManyToOne
@JoinColumn (name = "user_id")
private User user;

@ManyToOne
@JoinColumn (name = "room_id")
private ChatRoom chatRoom;

private ChatMessage (User user) {
this.sender = user.getNickname();
}

}

@Entity
@Getter
@Builder(toBuilder = true)
@NoArgsConstructor
@AllArgsConstructor
@Table(name = "chatRoom")
@Slf4j(topic = "chat 로그")
public static class ChatRoom {

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

@Column(nullable = false)
private String roomNumber;

@Column (nullable = false)
private String name;


// public ChatRoom () {
// this.Id = UUID.randomUUID().toString();
// }
}
}
45 changes: 45 additions & 0 deletions src/main/java/com/nawabali/nawabali/dto/ChatDto.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
package com.nawabali.nawabali.dto;

import lombok.*;
import org.springframework.stereotype.Component;

import java.time.LocalDateTime;

public class ChatDto {

@Getter
@Setter
@Builder
public static class ChatMessageDto { // 채팅 메세지를 주고받기 위한 DTO
// 메시지 타입 : 입장, 채팅
public enum MessageType {
ENTER, TALK
}
private Long id;
private MessageType type; // 메시지 타입
private Long roomId; // 방번호
private Long userId;
private String sender; // 메시지 보낸사람
private String message; // 메시지
private LocalDateTime createdAt;
}

@Getter
// @Setter
@Builder
@Component
@NoArgsConstructor
@AllArgsConstructor
public static class ChatRoomDto {
private Long roomId;
private String name;
private String roomNumber;

// public static ChatRoom create(String name) {
// ChatRoom chatRoom = new ChatRoom();
// chatRoom.roomId = UUID.randomUUID().toString();
// chatRoom.name = name;
// return chatRoom;
// }
} // pub/sub 방식으로 구독자 관리 / 발송의 구현이 되므로 간소화 됐다.
}
3 changes: 2 additions & 1 deletion src/main/java/com/nawabali/nawabali/exception/ErrorCode.java
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ public enum ErrorCode {

// 403 Forbidden : 클라이언트는 콘텐츠에 접근할 권리를 가지고 있지 않다
FORBIDDEN_MEMBER(FORBIDDEN,"본인의 게시물이 아닙니다."),
FORBIDDEN_CHATMESSAGE(FORBIDDEN, "본인의 채팅이 아닙니다."),

// 404 NOT_FOUND: 잘못된 리소스 접근
REFRESH_TOKEN_NOT_FOUND(NOT_FOUND, "로그아웃 된 사용자입니다"),
Expand All @@ -43,7 +44,7 @@ public enum ErrorCode {
USER_NOT_FOUND(NOT_FOUND, "해당 사용자를 찾을 수 없습니다."),
PROFILEIMAGE_NOT_FOUND(NOT_FOUND, "해당 프로필이미지를 찾을 수 없습니다."),
LIKE_NOT_FOUND(NOT_FOUND, "해당 좋아요를 찾을 수 없습니다."),

CHATROOM_NOT_FOUND(NOT_FOUND, "해당 채팅방을 찾을 수 없습니다."),


// 409 CONFLICT: 중복된 리소스 (요청이 현재 서버 상태와 충돌될 때)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package com.nawabali.nawabali.repository;

import com.nawabali.nawabali.domain.Chat;
import org.springframework.data.jpa.repository.JpaRepository;

import java.util.List;
import java.util.Optional;

public interface ChatMessageRepository extends JpaRepository<Chat.ChatMessage, Long> {
Optional<List<Chat.ChatMessage>> findByChatRoomIdAndUserId(Long roomId, Long userId);

// Optional <List<ChatDto.ChatMessageDto>> findByRoomIdAndUserIdOrderByCreatedAtDesc(Long roomId, Long id);
// Optional<List<ChatDto.ChatMessageDto>> findByChatRoom_IdAndUser_IdOrderByCreatedAtDesc(Long chatRoomId, Long userId);



}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package com.nawabali.nawabali.repository;

import com.nawabali.nawabali.domain.Chat;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;

import java.util.List;
import java.util.Optional;

@Repository
public interface ChatRoomRepository extends JpaRepository<Chat.ChatRoom, Long> {

Optional<List<Chat.ChatRoom>> findByNameContainingIgnoreCase(String name);

}
Loading

0 comments on commit 16182ca

Please sign in to comment.