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,12 +1,14 @@
package com.debatetimer.config.sharing;

import com.debatetimer.config.CorsProperties;
import com.debatetimer.event.sharing.RoomSubscribeInterceptor;
import java.time.Duration;
import java.util.List;
import lombok.RequiredArgsConstructor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.messaging.handler.invocation.HandlerMethodArgumentResolver;
import org.springframework.messaging.simp.config.ChannelRegistration;
import org.springframework.messaging.simp.config.MessageBrokerRegistry;
import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;
import org.springframework.web.socket.config.annotation.EnableWebSocketMessageBroker;
Expand All @@ -26,12 +28,18 @@ public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {

private final CorsProperties corsProperties;
private final WebSocketAuthMemberResolver webSocketAuthMemberResolver;
private final RoomSubscribeInterceptor roomSubscribeInterceptor;

@Override
public void addArgumentResolvers(List<HandlerMethodArgumentResolver> resolvers) {
resolvers.add(webSocketAuthMemberResolver);
}

@Override
public void configureClientInboundChannel(ChannelRegistration registration) {
registration.interceptors(roomSubscribeInterceptor);
}

@Override
public void configureMessageBroker(MessageBrokerRegistry registry) {
registry.enableSimpleBroker("/room", "/chairman")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,6 @@ public SharingResponse share(
@DestinationVariable(value = "roomId") long roomId,
@Valid @Payload SharingRequest request
) {
return sharingService.share(request);
return sharingService.share(roomId, request);
}
}
3 changes: 3 additions & 0 deletions src/main/java/com/debatetimer/domain/sharing/TimerEvent.java
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,9 @@ public TimerEvent(
@Nullable TimerEventData timerEventData
) {
eventType.validateEventData(timerEventData);
if (eventType.isSync()) {
timerEventData.validateSyncable();
}
this.eventType = eventType;
this.timerEventData = timerEventData;
}
Expand Down
27 changes: 26 additions & 1 deletion src/main/java/com/debatetimer/domain/sharing/TimerEventData.java
Original file line number Diff line number Diff line change
Expand Up @@ -21,17 +21,32 @@ public class TimerEventData {

private final long remainingTime;

@Nullable
private final Boolean isRunning;

@Nullable
private final Long prosRemainingTime;

@Nullable
private final Long consRemainingTime;

public TimerEventData(
CustomizeBoxType timerType,
int sequence,
@Nullable Stance currentTeam,
long remainingTime
long remainingTime,
@Nullable Boolean isRunning,
@Nullable Long prosRemainingTime,
@Nullable Long consRemainingTime
) {
validateCurrentTeam(timerType, currentTeam);
this.timerType = timerType;
this.sequence = sequence;
this.currentTeam = currentTeam;
this.remainingTime = remainingTime;
this.isRunning = isRunning;
this.prosRemainingTime = prosRemainingTime;
this.consRemainingTime = consRemainingTime;
}

private void validateCurrentTeam(CustomizeBoxType timerType, Stance currentTeam) {
Expand All @@ -43,4 +58,14 @@ private void validateCurrentTeam(CustomizeBoxType timerType, Stance currentTeam)
throw new DTClientErrorException(ClientErrorCode.INVALID_NORMAL_TIMER_EVENT_DATA);
}
}

public void validateSyncable() {
if (isRunning == null) {
throw new DTClientErrorException(ClientErrorCode.INVALID_SYNC_TIMER_EVENT_DATA);
}

if (timerType.isTimeBased() && (prosRemainingTime == null || consRemainingTime == null)) {
throw new DTClientErrorException(ClientErrorCode.INVALID_SYNC_TIMER_EVENT_DATA);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ public enum TimerEventType {
PLAY(Objects::nonNull),
RESET(Objects::nonNull),
TEAM_SWITCH(Objects::nonNull),
SYNC(Objects::nonNull),
FINISHED(Objects::isNull),
;

Expand All @@ -25,4 +26,8 @@ public void validateEventData(Object eventData) {
throw new DTClientErrorException(ClientErrorCode.INVALID_TIMER_EVENT);
}
}

public boolean isSync() {
return this == SYNC;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,9 @@ public record SharingRequest(
@NotNull
TimerEventType eventType,

@Nullable
Long version,

@Valid
@Nullable
TimerEventInfoRequest data
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,15 +14,27 @@ public record TimerEventInfoRequest(
Stance currentTeam,

int sequence,
long remainingTime
long remainingTime,

@Nullable
Boolean isRunning,

@Nullable
Long prosRemainingTime,

@Nullable
Long consRemainingTime
) {

public TimerEventData toTimerEventInfo() {
return new TimerEventData(
timerType,
sequence,
currentTeam,
remainingTime
remainingTime,
isRunning,
prosRemainingTime,
consRemainingTime
);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,14 @@
public record SharingResponse(
TimerEventType eventType,

@Nullable
Long version,

@Nullable
TimerEventDataResponse data
) {

public SharingResponse(TimerEventType eventType) {
this(eventType, null);
this(eventType, null, null);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,15 +15,27 @@ public record TimerEventDataResponse(
Stance currentTeam,

int sequence,
long remainingTime
long remainingTime,

@Nullable
Boolean isRunning,

@Nullable
Long prosRemainingTime,

@Nullable
Long consRemainingTime
) {

public TimerEventDataResponse(TimerEventData timerEventInfo) {
this(
timerEventInfo.getTimerType(),
timerEventInfo.getCurrentTeam(),
timerEventInfo.getSequence(),
timerEventInfo.getRemainingTime()
timerEventInfo.getRemainingTime(),
timerEventInfo.getIsRunning(),
timerEventInfo.getProsRemainingTime(),
timerEventInfo.getConsRemainingTime()
);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
package com.debatetimer.event.sharing;

import com.debatetimer.domain.sharing.TimerEventType;
import com.debatetimer.dto.sharing.request.ChairmanSharingRequest;
import com.debatetimer.dto.sharing.response.SharingResponse;
import com.debatetimer.exception.custom.DTClientErrorException;
import com.debatetimer.exception.errorcode.ClientErrorCode;
import com.debatetimer.service.sharing.SharingRoomRegistry;
import org.springframework.context.annotation.Lazy;
import org.springframework.messaging.Message;
import org.springframework.messaging.MessageChannel;
import org.springframework.messaging.MessageHandler;
import org.springframework.messaging.simp.SimpMessageHeaderAccessor;
import org.springframework.messaging.simp.SimpMessageType;
import org.springframework.messaging.simp.SimpMessagingTemplate;
import org.springframework.messaging.simp.broker.SimpleBrokerMessageHandler;
import org.springframework.messaging.support.ExecutorChannelInterceptor;
import org.springframework.stereotype.Component;

/**
* simple broker가 청중의 룸 구독을 등록한 뒤에 후속 메시지를 보낸다.
* SessionSubscribeEvent는 구독 등록 전에 발행될 수 있어, 그 시점에 룸으로 보내면 새 청중이 받지 못할 수 있다.
*/
@Component
public class RoomSubscribeInterceptor implements ExecutorChannelInterceptor {

private static final String AUDIENCE_SUBSCRIBE_PREFIX = "/room/";
private static final String CHAIRMAN_CHANNEL_PREFIX = "/chairman/";

private final SimpMessagingTemplate messagingTemplate;
private final SharingRoomRegistry sharingRoomRegistry;

public RoomSubscribeInterceptor(
@Lazy SimpMessagingTemplate messagingTemplate,
SharingRoomRegistry sharingRoomRegistry
) {
this.messagingTemplate = messagingTemplate;
this.sharingRoomRegistry = sharingRoomRegistry;
}

@Override
public void afterMessageHandled(Message<?> message, MessageChannel channel, MessageHandler handler, Exception ex) {
if (ex != null || !(handler instanceof SimpleBrokerMessageHandler)) {
return;
}

SimpMessageHeaderAccessor accessor = SimpMessageHeaderAccessor.wrap(message);
String destination = accessor.getDestination();
if (accessor.getMessageType() != SimpMessageType.SUBSCRIBE || destination == null) {
return;
}

if (destination.startsWith(CHAIRMAN_CHANNEL_PREFIX)) {
handleChairmanSubscribe(destination);
return;
}

if (destination.startsWith(AUDIENCE_SUBSCRIBE_PREFIX)) {
handleAudienceSubscribe(destination);
}
}

/**
* 사회자의 구독은 공유 시작을 뜻하므로, 이전에 종료된 룸이라도 다시 진행 상태로 되돌린다.
*/
private void handleChairmanSubscribe(String destination) {
long roomId = parseRoomId(destination, CHAIRMAN_CHANNEL_PREFIX);
sharingRoomRegistry.reopen(roomId);
}

private void handleAudienceSubscribe(String destination) {
long roomId = parseRoomId(destination, AUDIENCE_SUBSCRIBE_PREFIX);
if (sharingRoomRegistry.isFinished(roomId)) {
messagingTemplate.convertAndSend(AUDIENCE_SUBSCRIBE_PREFIX + roomId,
new SharingResponse(TimerEventType.FINISHED));
Comment thread
coli-geonwoo marked this conversation as resolved.
Resolved
return;
}
messagingTemplate.convertAndSend(CHAIRMAN_CHANNEL_PREFIX + roomId, new ChairmanSharingRequest(roomId));
}

private long parseRoomId(String destination, String prefix) {
try {
String parsedRoomId = destination.substring(prefix.length());
return Long.parseLong(parsedRoomId);
} catch (NumberFormatException exception) {
throw new DTClientErrorException(ClientErrorCode.INVALID_ROOM_ID);
}
}
}

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,7 @@ public enum ClientErrorCode implements ResponseErrorCode {
INVALID_NORMAL_TIMER_EVENT_DATA(HttpStatus.BAD_REQUEST, "잘못된 일반 타이머 이벤트 데이터입니다"),
INVALID_TIME_BASED_TIMER_EVENT_DATA(HttpStatus.BAD_REQUEST, "잘못된 자유토론 타이머 이벤트 데이터입니다"),
INVALID_TIMER_EVENT(HttpStatus.BAD_REQUEST, "잘못된 타이머 이벤트 데이터입니다"),
INVALID_SYNC_TIMER_EVENT_DATA(HttpStatus.BAD_REQUEST, "잘못된 동기화 타이머 이벤트 데이터입니다"),
;

private final HttpStatus status;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package com.debatetimer.scheduler;

import com.debatetimer.service.sharing.SharingRoomRegistry;
import lombok.RequiredArgsConstructor;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;

@Component
@RequiredArgsConstructor
public class SharingRoomCleanupScheduler {

private static final long INTERVAL_MILLIS = 60 * 60 * 1000L;

private final SharingRoomRegistry sharingRoomRegistry;

@Scheduled(fixedRate = INTERVAL_MILLIS)
public void cleanupExpiredRooms() {
sharingRoomRegistry.removeExpired();
}
}
Loading
Loading