Skip to content

Commit

Permalink
Merge pull request #9 from 28th-meetup/feat/chat
Browse files Browse the repository at this point in the history
[Feat] 채팅 기능 추가
  • Loading branch information
summit45 authored Nov 2, 2023
2 parents c6afe1d + 3ed215e commit c197f00
Show file tree
Hide file tree
Showing 30 changed files with 887 additions and 7 deletions.
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

0 comments on commit c197f00

Please sign in to comment.