Skip to content
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
Original file line number Diff line number Diff line change
@@ -1,35 +1,31 @@
package likelion.mlb.backendProject.domain.chat.bus;


import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import java.util.Map;
import java.util.UUID;
import lombok.RequiredArgsConstructor;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;

@Component
@RequiredArgsConstructor
public class ChatRedisPublisher {

private final StringRedisTemplate stringRedisTemplate;
private final ObjectMapper objectMapper;
// JVM 런타임 식별자를 사용하여 노드 구분 (재시작 시마다 변경됨)
private final String nodeId = System.getProperty("node.id",
java.lang.management.ManagementFactory.getRuntimeMXBean().getName());

public void publishToRoom(String roomId, Map<String, Object> payload) throws JsonProcessingException {
System.out.println("roomId = "+roomId);
System.out.println("payload = "+payload);
private final String nodeId = java.util.UUID.randomUUID().toString();

//payload.put("_src", nodeId()); // 루프 방지 태그
public void publishToRoom(UUID roomId, Map<String, Object> payload) {
try {
payload.put("_src", nodeId); // 루프 방지 태그
String json = objectMapper.writeValueAsString(payload);
System.out.println("publishToRoom = "+json);

stringRedisTemplate.convertAndSend(roomId, json);

stringRedisTemplate.convertAndSend(ChatChannels.roomChannel(roomId), json);
} catch (Exception ignore) {
}
}

public String nodeId() { return nodeId; }
public String nodeId() {
return nodeId;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -25,21 +25,11 @@ public void onMessage(Message message, byte[] pattern) {
@SuppressWarnings("unchecked")
Map<String, Object> payload = objectMapper.readValue(body, Map.class);

// ✅ 내 노드에서 보낸 메시지는 이미 전달했으므로 Redis를 통한 재전달 방지
// (ChatRedisPublisher에서 nodeId를 _src로 설정)
Object srcNodeId = payload.get("_src");
if (srcNodeId != null) {
// 현재 노드의 ID와 비교 (ChatRedisPublisher의 nodeId와 비교하려면 주입받아야 함)
// 여기서는 간단히 현재 JVM의 식별자로 비교
String currentNodeId = System.getProperty("node.id",
java.lang.management.ManagementFactory.getRuntimeMXBean().getName());
if (currentNodeId.equals(srcNodeId)) {
return; // 내가 보낸 메시지는 중복 전송 방지
}
}
// ❌ 내 노드에서 보낸 것도 브로커로 전달해야 함 (중복루프 없음)
// if (publisher.nodeId().equals(payload.get("_src"))) return;

String topic = ChatChannels.toTopic(channel);
// ✅ STOMP 브로커로 전송 (다른 노드에서 온 메시지만)
// ✅ STOMP 브로커로 전송
messagingTemplate.convertAndSend(topic, payload);

} catch (Exception e) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
package likelion.mlb.backendProject.domain.chat.controller;


import com.fasterxml.jackson.core.JsonProcessingException;
import java.security.Principal;
import java.util.Map;
import java.util.UUID;
Expand All @@ -16,7 +15,6 @@
import likelion.mlb.backendProject.domain.chat.entity.ChatMessage;
import likelion.mlb.backendProject.domain.chat.service.ChatMessageService;
import likelion.mlb.backendProject.global.security.dto.CustomUserDetails;
import org.springframework.transaction.annotation.Transactional;

@Controller
@RequiredArgsConstructor
Expand All @@ -28,10 +26,10 @@ public class ChatMessagingController {
private final ChatRedisPublisher chatRedisPublisher;


@Transactional
@MessageMapping("/chat/send")
public void send(ChatSendRequest req,
Principal principal) throws JsonProcessingException {
@MessageMapping("/chat/{roomId}/send")
public void send(@DestinationVariable UUID roomId,
ChatSendRequest req,
Principal principal) {

UUID userId = null;
if (principal instanceof Authentication auth
Expand All @@ -40,35 +38,24 @@ public void send(ChatSendRequest req,
}

// ✅ 방 멤버인지 권한 체크 (아니면 바로 거절)
if (userId == null || !membershipRepository.isMember(req.getRoomId(), userId)) {
if (userId == null || !membershipRepository.isMember(roomId, userId)) {
throw new org.springframework.messaging.MessagingException("Not a member of this chat room");
// 또는 그냥 return; // 조용히 무시하고 싶으면
}


System.out.println("------------/chat/{roomId}/send 시작 ");

// 안전장치: 메시지의 roomId는 URL의 roomId로 강제
ChatMessage saved = chatMessageService.saveUserMessage(req.getRoomId(), userId, req.getContent());
ChatMessage saved = chatMessageService.saveUserMessage(roomId, userId, req.getContent());

Map<String, Object> payload = Map.of(
"id", saved.getId().toString(),
"chatRoomId", req.getRoomId().toString(),
"chatRoomId", roomId.toString(),
"type", saved.getMessageType().name(),
"content", saved.getContent(),
"userId", userId != null ? userId.toString() : null,
"createdAt", saved.getCreatedAt().toString()
);


//System.out.println("------------받은메세지 payload"+payload.get(0).toString());

System.out.println("------------받은메세지"+req.getContent());

// ✅ 즉시 현재 노드의 클라이언트에게 전달
messagingTemplate.convertAndSend("/topic/chat/" + req.getRoomId(), payload);

// ✅ 다른 노드를 위해 Redis로도 전달
chatRedisPublisher.publishToRoom("chat." + req.getRoomId(), payload);
chatRedisPublisher.publishToRoom(roomId, new java.util.HashMap<>(payload));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ public ResponseEntity<Map<String, Object>> roomAlert(
"createdAt", saved.getCreatedAt().toString()
);
messagingTemplate.convertAndSend("/topic/chat/" + roomId, payload);
//chatRedisPublisher.publishToRoom(roomId, new java.util.HashMap<>(payload));
chatRedisPublisher.publishToRoom(roomId, new java.util.HashMap<>(payload));

return ResponseEntity.ok(Map.of("ok", true, "id", saved.getId().toString()));
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,11 @@
@Entity
@Table(name = "chat_read_state",
uniqueConstraints = @UniqueConstraint(columnNames = {"chat_room_id", "user_id"}))
@Getter @Setter @NoArgsConstructor @AllArgsConstructor @Builder
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class ChatReadState {

@Id
Expand All @@ -29,8 +33,12 @@ public class ChatReadState {

@PrePersist
void prePersist() {
if (id == null) id = UUID.randomUUID();
if (lastReadAt == null) lastReadAt = Instant.EPOCH; // 처음엔 1970-01-01
if (id == null) {
id = UUID.randomUUID();
}
if (lastReadAt == null) {
lastReadAt = Instant.EPOCH; // 처음엔 1970-01-01
}
}

public void mark(UUID messageId, Instant when) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ protected void doDispatch(UUID playerId,
"createdAt", saved.getCreatedAt().toString()
);
messagingTemplate.convertAndSend("/topic/chat/" + roomId, payload);
// chatRedisPublisher.publishToRoom(roomId, new java.util.HashMap<>(payload));
chatRedisPublisher.publishToRoom(roomId, new java.util.HashMap<>(payload));
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
package likelion.mlb.backendProject.domain.player.entity;

import jakarta.persistence.*;

import java.util.stream.Collectors;

import likelion.mlb.backendProject.domain.draft.dto.DraftRequest;
import likelion.mlb.backendProject.domain.player.cache.dto.PlayerDto;
import likelion.mlb.backendProject.domain.team.entity.Team;
Expand All @@ -15,7 +18,6 @@
import java.util.List;
import java.util.Map;
import java.util.UUID;
import java.util.stream.Collectors;

@Entity
@Table(name = "player")
Expand Down Expand Up @@ -141,7 +143,6 @@ public void updatePlayer(FplElement element,
String picUri = "https://resources.premierleague.com/premierleague25/photos/players/110x140/"
+ element.getCode() + ".png";
this.pic = picUri;
this.code = element.getCode();
this.status = element.getStatus();
this.fplId = element.getFplId();
this.news = element.getNews();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,23 +14,24 @@ public class OpenApiConfig {
@Bean
public OpenAPI openAPI() {
Info info = new Info()
.title("MLB Project API")
.version("v1.0.0")
.description("API documentation for the MLB project.");
.title("MLB Project API")
.version("v1.0.0")
.description("API documentation for the MLB project.");

// Security Scheme for JWT
String jwtSchemeName = "jwtAuth";
SecurityRequirement securityRequirement = new SecurityRequirement().addList(jwtSchemeName);
Components components = new Components()
.addSecuritySchemes(jwtSchemeName, new SecurityScheme()
.name(jwtSchemeName)
.type(SecurityScheme.Type.HTTP) // HTTP 방식
.scheme("bearer")
.bearerFormat("JWT"));
.addSecuritySchemes(jwtSchemeName, new SecurityScheme()
.name(jwtSchemeName)
.type(SecurityScheme.Type.HTTP) // HTTP 방식
.scheme("bearer")
.bearerFormat("JWT"));

return new OpenAPI()
.info(info)
.addSecurityItem(securityRequirement)
.components(components);
.info(info)
.addSecurityItem(securityRequirement)
.components(components);
}
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,6 @@ public RedisMessageListenerContainer redisMessageListenerContainer(RedisConnecti
RedisMessageListenerContainer container = new RedisMessageListenerContainer();
container.setConnectionFactory(connectionFactory);
container.addMessageListener(new MessageListenerAdapter(redisSubscriber), new PatternTopic("draft.*"));
container.addMessageListener(new MessageListenerAdapter(redisSubscriber), new PatternTopic("chat/*"));
return container;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -86,17 +86,7 @@ protected Principal determineUser(ServerHttpRequest request, WebSocketHandler ws
@Override
public void configureMessageBroker(MessageBrokerRegistry registry) {
registry.setApplicationDestinationPrefixes("/app");

// TaskScheduler 생성 및 초기화
// org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler scheduler =
// new org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler();
// scheduler.setPoolSize(1);
// scheduler.setThreadNamePrefix("websocket-heartbeat-");
// scheduler.initialize();

registry.enableSimpleBroker("/topic", "/queue");
// .setTaskScheduler(scheduler)
// .setHeartbeatValue(new long[]{10000, 10000}); // 하트비트 간격 조정
registry.setUserDestinationPrefix("/user");
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,7 @@ public void onMessage(Message message, byte[] pattern) {
String msgBody = new String(message.getBody());
DraftRequest draftRequest = objectMapper.readValue(msgBody, DraftRequest.class);

// simpMessagingTemplate.convertAndSend("/topic/draft." + draftRequest.getDraftId(), draftRequest);
simpMessagingTemplate.convertAndSend("/chat/" + draftRequest.getDraftId(), draftRequest);
simpMessagingTemplate.convertAndSend("/topic/draft." + draftRequest.getDraftId(), draftRequest);
} catch (Exception e) {

}
Expand Down