Feat: 웹소켓 설정
This commit is contained in:
parent
c4ba971a53
commit
ba219fa208
@ -69,6 +69,9 @@ dependencies {
|
|||||||
testImplementation 'org.junit.jupiter:junit-jupiter:5.7.1'
|
testImplementation 'org.junit.jupiter:junit-jupiter:5.7.1'
|
||||||
testImplementation 'org.mockito:mockito-core:3.9.0'
|
testImplementation 'org.mockito:mockito-core:3.9.0'
|
||||||
testImplementation 'org.mockito:mockito-junit-jupiter:3.9.0'
|
testImplementation 'org.mockito:mockito-junit-jupiter:3.9.0'
|
||||||
|
|
||||||
|
// WebSocket
|
||||||
|
implementation 'org.springframework.boot:spring-boot-starter-websocket'
|
||||||
}
|
}
|
||||||
|
|
||||||
tasks.named('test') {
|
tasks.named('test') {
|
||||||
|
@ -74,6 +74,17 @@ public class ProjectController {
|
|||||||
return SuccessResponse.of(project);
|
return SuccessResponse.of(project);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Operation(summary = "프로젝트 모델 학습", description = "프로젝트 모델을 학습시킵니다..")
|
||||||
|
@SwaggerApiSuccess(description = "프로젝트 모델이 성공적으로 학습됩니다.")
|
||||||
|
@SwaggerApiError({ErrorCode.EMPTY_REQUEST_PARAMETER, ErrorCode.SERVER_ERROR})
|
||||||
|
@DeleteMapping("/projects/{project_id}/train")
|
||||||
|
public BaseResponse<Void> trainModel(
|
||||||
|
@CurrentUser final Integer memberId,
|
||||||
|
@PathVariable("project_id") final Integer projectId) {
|
||||||
|
projectService.train(memberId, projectId);
|
||||||
|
return SuccessResponse.empty();
|
||||||
|
}
|
||||||
|
|
||||||
@Operation(summary = "프로젝트 삭제", description = "프로젝트를 삭제합니다.")
|
@Operation(summary = "프로젝트 삭제", description = "프로젝트를 삭제합니다.")
|
||||||
@SwaggerApiSuccess(description = "프로젝트를 성공적으로 삭제합니다.")
|
@SwaggerApiSuccess(description = "프로젝트를 성공적으로 삭제합니다.")
|
||||||
@SwaggerApiError({ErrorCode.PROJECT_NOT_FOUND, ErrorCode.PARTICIPANT_UNAUTHORIZED, ErrorCode.SERVER_ERROR})
|
@SwaggerApiError({ErrorCode.PROJECT_NOT_FOUND, ErrorCode.PARTICIPANT_UNAUTHORIZED, ErrorCode.SERVER_ERROR})
|
||||||
@ -120,4 +131,6 @@ public class ProjectController {
|
|||||||
return SuccessResponse.empty();
|
return SuccessResponse.empty();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -114,6 +114,14 @@ public class ProjectService {
|
|||||||
participantRepository.delete(participant);
|
participantRepository.delete(participant);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void train(final Integer memberId,final Integer projectId) {
|
||||||
|
// 멤버 권한 체크
|
||||||
|
|
||||||
|
// 레디스 train 테이블에 존재하는지 확인
|
||||||
|
|
||||||
|
// AI서버와 웹 소켓 연결
|
||||||
|
}
|
||||||
|
|
||||||
private Workspace getWorkspace(final Integer memberId, final Integer workspaceId) {
|
private Workspace getWorkspace(final Integer memberId, final Integer workspaceId) {
|
||||||
return workspaceRepository.findByMemberIdAndId(memberId, workspaceId)
|
return workspaceRepository.findByMemberIdAndId(memberId, workspaceId)
|
||||||
.orElseThrow(() -> new CustomException(ErrorCode.WORKSPACE_NOT_FOUND));
|
.orElseThrow(() -> new CustomException(ErrorCode.WORKSPACE_NOT_FOUND));
|
||||||
@ -152,5 +160,6 @@ public class ProjectService {
|
|||||||
throw new CustomException(ErrorCode.PARTICIPANT_BAD_REQUEST);
|
throw new CustomException(ErrorCode.PARTICIPANT_BAD_REQUEST);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -15,7 +15,7 @@ public class CorsMvcConfig implements WebMvcConfigurer {
|
|||||||
public void addCorsMappings(CorsRegistry registry) {
|
public void addCorsMappings(CorsRegistry registry) {
|
||||||
registry.addMapping("/**")
|
registry.addMapping("/**")
|
||||||
.exposedHeaders("Set-Cookie")
|
.exposedHeaders("Set-Cookie")
|
||||||
.allowedOrigins(frontend) // application.yml에서 가져온 값 사용
|
.allowedOrigins(frontend, "http://localhost:5173") // application.yml에서 가져온 값 사용
|
||||||
.allowCredentials(true);
|
.allowCredentials(true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -6,6 +6,10 @@ import org.springframework.context.annotation.Configuration;
|
|||||||
import org.springframework.data.redis.connection.RedisConnectionFactory;
|
import org.springframework.data.redis.connection.RedisConnectionFactory;
|
||||||
import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory;
|
import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory;
|
||||||
import org.springframework.data.redis.core.RedisTemplate;
|
import org.springframework.data.redis.core.RedisTemplate;
|
||||||
|
import org.springframework.data.redis.listener.ChannelTopic;
|
||||||
|
import org.springframework.data.redis.listener.PatternTopic;
|
||||||
|
import org.springframework.data.redis.listener.RedisMessageListenerContainer;
|
||||||
|
import org.springframework.data.redis.listener.adapter.MessageListenerAdapter;
|
||||||
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
|
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
|
||||||
import org.springframework.data.redis.serializer.StringRedisSerializer;
|
import org.springframework.data.redis.serializer.StringRedisSerializer;
|
||||||
|
|
||||||
@ -42,4 +46,19 @@ public class RedisConfig {
|
|||||||
|
|
||||||
return redisTemplate;
|
return redisTemplate;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public RedisMessageListenerContainer redisContainer(RedisConnectionFactory connectionFactory,
|
||||||
|
MessageListenerAdapter listenerAdapter) {
|
||||||
|
|
||||||
|
RedisMessageListenerContainer container = new RedisMessageListenerContainer();
|
||||||
|
container.setConnectionFactory(connectionFactory);
|
||||||
|
container.addMessageListener(listenerAdapter, new ChannelTopic("/ai/train"));
|
||||||
|
return container;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public MessageListenerAdapter listenerAdapter(MessageListenerAdapter listenerAdapter) {
|
||||||
|
return new MessageListenerAdapter(listenerAdapter, "onMessage");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -48,7 +48,7 @@ public class SecurityConfig {
|
|||||||
.formLogin((auth) -> auth.disable());
|
.formLogin((auth) -> auth.disable());
|
||||||
|
|
||||||
// 세션 설정 비활성화
|
// 세션 설정 비활성화
|
||||||
http.sessionManagement((session)->session
|
http.sessionManagement((session) -> session
|
||||||
.sessionCreationPolicy(SessionCreationPolicy.STATELESS));
|
.sessionCreationPolicy(SessionCreationPolicy.STATELESS));
|
||||||
|
|
||||||
// CORS 설정
|
// CORS 설정
|
||||||
@ -63,7 +63,7 @@ public class SecurityConfig {
|
|||||||
|
|
||||||
// 경로별 인가 작업
|
// 경로별 인가 작업
|
||||||
http
|
http
|
||||||
.authorizeHttpRequests(auth->auth
|
.authorizeHttpRequests(auth -> auth
|
||||||
.requestMatchers("/swagger", "/swagger-ui.html", "/swagger-ui/**", "/api-docs", "/api-docs/**", "/v3/api-docs/**").permitAll()
|
.requestMatchers("/swagger", "/swagger-ui.html", "/swagger-ui/**", "/api-docs", "/api-docs/**", "/v3/api-docs/**").permitAll()
|
||||||
.requestMatchers("/api/auth/reissue").permitAll()
|
.requestMatchers("/api/auth/reissue").permitAll()
|
||||||
.anyRequest().authenticated()
|
.anyRequest().authenticated()
|
||||||
@ -80,9 +80,6 @@ public class SecurityConfig {
|
|||||||
);
|
);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// JWT 필터 추가
|
// JWT 필터 추가
|
||||||
http
|
http
|
||||||
.addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class);
|
.addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class);
|
||||||
@ -93,8 +90,8 @@ public class SecurityConfig {
|
|||||||
public CorsConfigurationSource corsConfigurationSource() {
|
public CorsConfigurationSource corsConfigurationSource() {
|
||||||
CorsConfiguration configuration = new CorsConfiguration();
|
CorsConfiguration configuration = new CorsConfiguration();
|
||||||
configuration.setAllowCredentials(true);
|
configuration.setAllowCredentials(true);
|
||||||
configuration.setAllowedOrigins(List.of(frontend)); // 프론트엔드 URL 사용
|
configuration.setAllowedOrigins(List.of(frontend, "http://localhost:5173")); // 프론트엔드 URL 사용
|
||||||
configuration.setAllowedMethods(List.of("GET","POST","PUT","PATCH","DELETE","OPTIONS"));
|
configuration.setAllowedMethods(List.of("GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS"));
|
||||||
configuration.setAllowedHeaders(List.of("*"));
|
configuration.setAllowedHeaders(List.of("*"));
|
||||||
configuration.setMaxAge(3600L);
|
configuration.setMaxAge(3600L);
|
||||||
|
|
||||||
|
@ -0,0 +1,25 @@
|
|||||||
|
package com.worlabel.global.config;
|
||||||
|
|
||||||
|
import com.worlabel.global.handler.CustomWebSocketHandler;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import org.springframework.context.annotation.Bean;
|
||||||
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
import org.springframework.web.socket.config.annotation.EnableWebSocket;
|
||||||
|
import org.springframework.web.socket.config.annotation.WebSocketConfigurer;
|
||||||
|
import org.springframework.web.socket.config.annotation.WebSocketHandlerRegistry;
|
||||||
|
|
||||||
|
@Configuration
|
||||||
|
@EnableWebSocket
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class WebSocketConfig implements WebSocketConfigurer {
|
||||||
|
|
||||||
|
private final CustomWebSocketHandler webSocketHandler;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
|
||||||
|
registry
|
||||||
|
.addHandler(webSocketHandler, "/ws")
|
||||||
|
.setAllowedOrigins("*");
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,35 @@
|
|||||||
|
package com.worlabel.global.handler;
|
||||||
|
|
||||||
|
import com.worlabel.global.service.RedisMessageSubscriber;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import org.springframework.data.redis.core.RedisTemplate;
|
||||||
|
import org.springframework.lang.NonNull;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
import org.springframework.web.socket.CloseStatus;
|
||||||
|
import org.springframework.web.socket.TextMessage;
|
||||||
|
import org.springframework.web.socket.WebSocketSession;
|
||||||
|
import org.springframework.web.socket.handler.TextWebSocketHandler;
|
||||||
|
|
||||||
|
@Component
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class CustomWebSocketHandler extends TextWebSocketHandler {
|
||||||
|
|
||||||
|
private final RedisTemplate<String , Object> redisTemplate;
|
||||||
|
private final RedisMessageSubscriber redisMessageSubscriber;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void afterConnectionEstablished(@NonNull WebSocketSession session) {
|
||||||
|
redisMessageSubscriber.addSession(session);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void afterConnectionClosed(@NonNull WebSocketSession session,@NonNull CloseStatus status) throws Exception {
|
||||||
|
redisMessageSubscriber.removeSession(session);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void handleTextMessage(@NonNull WebSocketSession session, TextMessage message) {
|
||||||
|
// Redis 메시지 발행
|
||||||
|
redisTemplate.convertAndSend("/ai/train", message.getPayload());
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,35 @@
|
|||||||
|
package com.worlabel.global.service;
|
||||||
|
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
import org.springframework.web.socket.TextMessage;
|
||||||
|
import org.springframework.web.socket.WebSocketSession;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.concurrent.CopyOnWriteArraySet;
|
||||||
|
|
||||||
|
@Slf4j
|
||||||
|
@Service
|
||||||
|
public class RedisMessageSubscriber {
|
||||||
|
|
||||||
|
private final Set<WebSocketSession> sessions = new CopyOnWriteArraySet<>();
|
||||||
|
|
||||||
|
public void addSession(WebSocketSession session) {
|
||||||
|
sessions.add(session);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void removeSession(WebSocketSession session) {
|
||||||
|
sessions.remove(session);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void onMessage(String message) {
|
||||||
|
for (WebSocketSession session : sessions) {
|
||||||
|
try {
|
||||||
|
session.sendMessage(new TextMessage(message));
|
||||||
|
} catch (IOException e) {
|
||||||
|
log.debug("", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user