Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Feat] 채팅 기능 추가 #9

Merged
merged 5 commits into from
Nov 2, 2023
Merged
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
Binary file removed .DS_Store
Binary file not shown.
5 changes: 3 additions & 2 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -36,5 +36,6 @@ out/
### VS Code ###
.vscode/

### JRebel ###
rebel.xml
### JREBEL ###
rebel.xml

4 changes: 4 additions & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -40,12 +40,16 @@ dependencies {
implementation 'javax.validation:validation-api:2.0.1.Final'
//websocket
implementation 'org.springframework.boot:spring-boot-starter-websocket'
implementation 'org.webjars:sockjs-client:1.1.2'
implementation 'org.webjars:stomp-websocket:2.3.3-1'
//swagger (spring 3.x버전이라 springfox 적용불가, springdoc 중에서 호환되는 종속성 사용)
implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.0.2'
//mysql (spring 3.x)
runtimeOnly 'com.mysql:mysql-connector-j'
//redis
implementation 'org.springframework.boot:spring-boot-starter-data-redis'
//embedded redis - https://mvnrepository.com/artifact/it.ozimov/embedded-redis
// implementation 'it.ozimov:embedded-redis:0.7.2'
//lombok
compileOnly 'org.projectlombok:lombok'
annotationProcessor 'org.projectlombok:lombok'
Expand Down
Binary file removed src/main/java/com/kusitms/jipbap/.DS_Store
Binary file not shown.
2 changes: 2 additions & 0 deletions src/main/java/com/kusitms/jipbap/JipbapApplication.java
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@

@EnableScheduling
@EnableJpaAuditing
//@EnableWebSocketMessageBroker
//@EnableRedisRepositories
@SpringBootApplication
public class JipbapApplication {

Expand Down
58 changes: 58 additions & 0 deletions src/main/java/com/kusitms/jipbap/chat/config/RedisConfig.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
package com.kusitms.jipbap.chat.config;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.fasterxml.jackson.datatype.jdk8.Jdk8Module;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import com.kusitms.jipbap.chat.domain.dto.MessageDto;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.listener.RedisMessageListenerContainer;
import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;

@Configuration
public class RedisConfig {

@Bean
public ObjectMapper objectMapper() {
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
objectMapper.registerModules(new JavaTimeModule(), new Jdk8Module());
return objectMapper;
}

// redis 연결, redis 의 pub/sub 기능을 이용하기 위해 pub/sub 메시지를 처리하는 MessageListener 설정(등록)
@Bean
public RedisMessageListenerContainer redisMessageListener(RedisConnectionFactory connectionFactory) { // 1.
RedisMessageListenerContainer container = new RedisMessageListenerContainer();
container.setConnectionFactory(connectionFactory); // 2.

return container;
}

// Redis 데이터베이스와의 상호작용을 위한 RedisTemplate 을 설정. JSON 형식으로 담기 위해 직렬화
@Bean
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory connectionFactory) {
RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
redisTemplate.setConnectionFactory(connectionFactory);
redisTemplate.setKeySerializer(new StringRedisSerializer()); // Key Serializer
redisTemplate.setValueSerializer(new Jackson2JsonRedisSerializer<>(String.class)); // Value Serializer

return redisTemplate;
}

// Redis 에 메시지 내역을 저장하기 위한 RedisTemplate을 설정
@Bean
public RedisTemplate<String, MessageDto> redisTemplateMessage(RedisConnectionFactory connectionFactory) {
RedisTemplate<String, MessageDto> redisTemplateMessage = new RedisTemplate<>();
redisTemplateMessage.setConnectionFactory(connectionFactory);
redisTemplateMessage.setKeySerializer(new StringRedisSerializer()); // Key Serializer
redisTemplateMessage.setValueSerializer(new GenericJackson2JsonRedisSerializer(objectMapper())); // Value Serializer

return redisTemplateMessage;
}
}
26 changes: 26 additions & 0 deletions src/main/java/com/kusitms/jipbap/chat/config/WebSockConfig.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package com.kusitms.jipbap.chat.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 WebSockConfig implements WebSocketMessageBrokerConfigurer {

@Override
public void configureMessageBroker(MessageBrokerRegistry config) {
config.enableSimpleBroker("/sub"); //message broker가 subscriber들에게 메세지를 전달할 url (구독 요청)
config.setApplicationDestinationPrefixes("/pub"); //클라이언트가 서버로 메세지를 보낼 url 접두사 지정 (발행 요청)
}

@Override
public void registerStompEndpoints(StompEndpointRegistry registry) {
registry.addEndpoint("/ws-stomp") //websocket 연결 endpoint
.setAllowedOriginPatterns("*");
// .withSockJS(); //제거해야 endpoint 연결 성공
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package com.kusitms.jipbap.chat.controller;


import com.kusitms.jipbap.chat.domain.dto.MessageDto;
import com.kusitms.jipbap.chat.service.RoomService;
import com.kusitms.jipbap.chat.service.MessageService;
import com.kusitms.jipbap.chat.service.RedisPublisher;
import com.kusitms.jipbap.common.response.CommonResponse;
import lombok.RequiredArgsConstructor;
import org.springframework.messaging.handler.annotation.MessageMapping;
import org.springframework.web.bind.annotation.*;

import java.util.List;

@RestController
@RequiredArgsConstructor
public class MessageController {
private final RedisPublisher redisPublisher;
private final RoomService roomService;
private final MessageService messageService;

// 대화 & 대화 저장
@MessageMapping("/message")
public void message(MessageDto messageDto) {
// 클라이언트의 채팅방(topic) 입장, 대화를 위해 리스너와 연동
roomService.enterMessageRoom(messageDto.getRoomId());

// Websocket 에 발행된 메시지를 redis 로 발행. 해당 채팅방을 구독한 클라이언트에게 메시지가 실시간 전송됨 (1:N, 1:1 에서 사용 가능)
redisPublisher.publish(roomService.getTopic(messageDto.getRoomId()), messageDto);

// DB & Redis 에 대화 저장
messageService.saveMessage(messageDto);
}

// 대화 내역 조회
@GetMapping("/chat/room/{roomId}/message")
public CommonResponse<List<MessageDto>> loadMessage(@PathVariable String roomId) {
return new CommonResponse<>(messageService.loadMessage(roomId));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
package com.kusitms.jipbap.chat.controller;

import com.kusitms.jipbap.chat.domain.dto.MessageRequestDto;
import com.kusitms.jipbap.chat.domain.dto.MessageResponseDto;
import com.kusitms.jipbap.chat.domain.dto.RoomDto;
import com.kusitms.jipbap.chat.service.RoomService;
import com.kusitms.jipbap.common.response.CommonResponse;
import com.kusitms.jipbap.security.Auth;
import com.kusitms.jipbap.security.AuthInfo;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.*;

import java.util.List;

@RestController
@RequestMapping("/chat")
@RequiredArgsConstructor
public class RoomController {
private final RoomService roomService;

// 채팅방 생성
@PostMapping("/room")
public CommonResponse<MessageResponseDto> createRoom(@RequestBody MessageRequestDto messageRequestDto, @Auth AuthInfo authInfo) {
return new CommonResponse<>(roomService.createRoom(messageRequestDto, authInfo.getEmail()));
}

// 사용자 관련 채팅방 전체 조회
@GetMapping("/rooms")
public CommonResponse<List<MessageResponseDto>> findAllRoomByUser(@Auth AuthInfo authInfo) {
return new CommonResponse<>(roomService.findAllRoomByUser(authInfo.getEmail()));
}

// 사용자 관련 채팅방 선택 조회
@GetMapping("/room/{roomId}")
public CommonResponse<RoomDto> findRoom(@PathVariable String roomId, @Auth AuthInfo authInfo) {
return new CommonResponse<>(roomService.findRoom(roomId, authInfo.getEmail()));
}

// 채팅방 삭제
@DeleteMapping("/room/{id}")
public CommonResponse<String> deleteRoom(@PathVariable Long id, @Auth AuthInfo authInfo) {
roomService.deleteRoom(id, authInfo.getEmail());
return new CommonResponse<>("채팅방 삭제 완료");
}
}
26 changes: 26 additions & 0 deletions src/main/java/com/kusitms/jipbap/chat/domain/dto/MessageDto.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package com.kusitms.jipbap.chat.domain.dto;

import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import com.kusitms.jipbap.chat.domain.entity.Message;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;

@Getter
@Setter
@NoArgsConstructor
@JsonIgnoreProperties(ignoreUnknown = true)
public class MessageDto {
private String senderName;
private String roomId;
private String message;
private String sentTime;

// 대화 조회
public MessageDto(Message message) {
this.senderName = message.getSenderName();
this.roomId = message.getRoom().getRoomId();
this.message = message.getMessage();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package com.kusitms.jipbap.chat.domain.dto;

import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import lombok.Getter;
import lombok.NoArgsConstructor;

@Getter
@NoArgsConstructor
@JsonIgnoreProperties(ignoreUnknown =true)
public class MessageRequestDto {
private Long receiverId; // 메세지 수신자
private String receiverName; // 수신자 이름 (채팅방명으로 쓰임)
private Long storeId; // 1:1 채팅하기를 시작한 가게 정보
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
package com.kusitms.jipbap.chat.domain.dto;

import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.kusitms.jipbap.chat.domain.entity.Room;
import lombok.Getter;
import lombok.NoArgsConstructor;

@Getter
@NoArgsConstructor
@JsonInclude(JsonInclude.Include.NON_NULL)
@JsonIgnoreProperties(ignoreUnknown =true)
public class MessageResponseDto {
private Long id;
private String roomName;
private String sender;
private String roomId;
private String receiver;
private String message;
private String createdAt;

// 채팅방 생성
public MessageResponseDto(Room room) {
this.id = room.getId();
this.roomName = room.getRoomName();
this.sender = room.getSenderName();
this.roomId = room.getRoomId();
this.receiver = room.getReceiverName();
}

// 사용자 관련 채팅방 전체 조회
public MessageResponseDto(Long id, String roomName, String roomId, String sender, String receiver) {
this.id = id;
this.roomName = roomName;
this.sender = sender;
this.roomId = roomId;
this.receiver = receiver;
}

public MessageResponseDto(String roomId) {
this.roomId = roomId;
}

public void setLatestMessageContent(String message) {
this.message = message;
}

public void setLatestMessageCreatedAt(String createdAt) {
this.createdAt = createdAt;
}
}
42 changes: 42 additions & 0 deletions src/main/java/com/kusitms/jipbap/chat/domain/dto/RoomDto.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
package com.kusitms.jipbap.chat.domain.dto;

import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.kusitms.jipbap.user.User;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.Setter;

import java.io.Serializable;
import java.util.UUID;

@Getter
@Setter
@AllArgsConstructor
@JsonInclude(JsonInclude.Include.NON_NULL)
@JsonIgnoreProperties(ignoreUnknown =true)
public class RoomDto implements Serializable {

private static final long serialVersionUID = 298188411555580374L; // Redis 에 저장되는 객체들이 직렬화가 가능하도록

private Long id;
private String roomName; // 채팅방 제목
private String roomId;
private String senderName; // 메시지 송신자 이름
private String receiverName; // 메시지 수신자 이름

// 채팅방 생성
private RoomDto() {

}
public static RoomDto create(MessageRequestDto messageRequestDto, User user) {
RoomDto roomDto = new RoomDto();
roomDto.roomName = messageRequestDto.getReceiverName();
roomDto.roomId = UUID.randomUUID().toString();
roomDto.senderName = user.getUsername();
roomDto.receiverName = messageRequestDto.getReceiverName();

return roomDto;
}

}
37 changes: 37 additions & 0 deletions src/main/java/com/kusitms/jipbap/chat/domain/entity/Message.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package com.kusitms.jipbap.chat.domain.entity;

import com.fasterxml.jackson.annotation.JsonInclude;
import com.kusitms.jipbap.common.entity.DateEntity;
import jakarta.persistence.*;
import lombok.*;

@Entity
@Getter
@Table(name = "tb_message")
@NoArgsConstructor
@JsonInclude(JsonInclude.Include.NON_NULL)
public class Message extends DateEntity {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;

private String senderName;

private String receiverName;

private String message;

private String sentTime;

// 1.
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "room_id")
private Room room;

// 대화 저장
public Message(String senderName, Room room, String message) {
this.senderName = senderName;
this.room = room;
this.message = message;
}
}
Loading
Loading