From ba219fa20801c610ce2de56021cec6a363d3ab3e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B9=80=EC=9A=A9=EC=88=98?= Date: Wed, 11 Sep 2024 17:15:06 +0900 Subject: [PATCH 1/5] =?UTF-8?q?Feat:=20=EC=9B=B9=EC=86=8C=EC=BC=93=20?= =?UTF-8?q?=EC=84=A4=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/build.gradle | 3 ++ .../project/controller/ProjectController.java | 13 +++++++ .../project/service/ProjectService.java | 9 +++++ .../worlabel/global/config/CorsMvcConfig.java | 2 +- .../worlabel/global/config/RedisConfig.java | 19 ++++++++++ .../global/config/SecurityConfig.java | 23 ++++++------ .../global/config/WebSocketConfig.java | 25 +++++++++++++ .../handler/CustomWebSocketHandler.java | 35 +++++++++++++++++++ .../service/RedisMessageSubscriber.java | 35 +++++++++++++++++++ 9 files changed, 150 insertions(+), 14 deletions(-) create mode 100644 backend/src/main/java/com/worlabel/global/config/WebSocketConfig.java create mode 100644 backend/src/main/java/com/worlabel/global/handler/CustomWebSocketHandler.java create mode 100644 backend/src/main/java/com/worlabel/global/service/RedisMessageSubscriber.java diff --git a/backend/build.gradle b/backend/build.gradle index 0c0b514..14778a9 100644 --- a/backend/build.gradle +++ b/backend/build.gradle @@ -69,6 +69,9 @@ dependencies { testImplementation 'org.junit.jupiter:junit-jupiter:5.7.1' testImplementation 'org.mockito:mockito-core:3.9.0' testImplementation 'org.mockito:mockito-junit-jupiter:3.9.0' + + // WebSocket + implementation 'org.springframework.boot:spring-boot-starter-websocket' } tasks.named('test') { diff --git a/backend/src/main/java/com/worlabel/domain/project/controller/ProjectController.java b/backend/src/main/java/com/worlabel/domain/project/controller/ProjectController.java index da3701b..152883d 100644 --- a/backend/src/main/java/com/worlabel/domain/project/controller/ProjectController.java +++ b/backend/src/main/java/com/worlabel/domain/project/controller/ProjectController.java @@ -74,6 +74,17 @@ public class ProjectController { return SuccessResponse.of(project); } + @Operation(summary = "프로젝트 모델 학습", description = "프로젝트 모델을 학습시킵니다..") + @SwaggerApiSuccess(description = "프로젝트 모델이 성공적으로 학습됩니다.") + @SwaggerApiError({ErrorCode.EMPTY_REQUEST_PARAMETER, ErrorCode.SERVER_ERROR}) + @DeleteMapping("/projects/{project_id}/train") + public BaseResponse trainModel( + @CurrentUser final Integer memberId, + @PathVariable("project_id") final Integer projectId) { + projectService.train(memberId, projectId); + return SuccessResponse.empty(); + } + @Operation(summary = "프로젝트 삭제", description = "프로젝트를 삭제합니다.") @SwaggerApiSuccess(description = "프로젝트를 성공적으로 삭제합니다.") @SwaggerApiError({ErrorCode.PROJECT_NOT_FOUND, ErrorCode.PARTICIPANT_UNAUTHORIZED, ErrorCode.SERVER_ERROR}) @@ -120,4 +131,6 @@ public class ProjectController { return SuccessResponse.empty(); } + + } diff --git a/backend/src/main/java/com/worlabel/domain/project/service/ProjectService.java b/backend/src/main/java/com/worlabel/domain/project/service/ProjectService.java index 3efbaa2..43fde3b 100644 --- a/backend/src/main/java/com/worlabel/domain/project/service/ProjectService.java +++ b/backend/src/main/java/com/worlabel/domain/project/service/ProjectService.java @@ -114,6 +114,14 @@ public class ProjectService { participantRepository.delete(participant); } + public void train(final Integer memberId,final Integer projectId) { + // 멤버 권한 체크 + + // 레디스 train 테이블에 존재하는지 확인 + + // AI서버와 웹 소켓 연결 + } + private Workspace getWorkspace(final Integer memberId, final Integer workspaceId) { return workspaceRepository.findByMemberIdAndId(memberId, workspaceId) .orElseThrow(() -> new CustomException(ErrorCode.WORKSPACE_NOT_FOUND)); @@ -152,5 +160,6 @@ public class ProjectService { throw new CustomException(ErrorCode.PARTICIPANT_BAD_REQUEST); } } + } diff --git a/backend/src/main/java/com/worlabel/global/config/CorsMvcConfig.java b/backend/src/main/java/com/worlabel/global/config/CorsMvcConfig.java index d138275..add5b16 100644 --- a/backend/src/main/java/com/worlabel/global/config/CorsMvcConfig.java +++ b/backend/src/main/java/com/worlabel/global/config/CorsMvcConfig.java @@ -15,7 +15,7 @@ public class CorsMvcConfig implements WebMvcConfigurer { public void addCorsMappings(CorsRegistry registry) { registry.addMapping("/**") .exposedHeaders("Set-Cookie") - .allowedOrigins(frontend) // application.yml에서 가져온 값 사용 + .allowedOrigins(frontend, "http://localhost:5173") // application.yml에서 가져온 값 사용 .allowCredentials(true); } } diff --git a/backend/src/main/java/com/worlabel/global/config/RedisConfig.java b/backend/src/main/java/com/worlabel/global/config/RedisConfig.java index c546bdc..3c0cde3 100644 --- a/backend/src/main/java/com/worlabel/global/config/RedisConfig.java +++ b/backend/src/main/java/com/worlabel/global/config/RedisConfig.java @@ -6,6 +6,10 @@ import org.springframework.context.annotation.Configuration; import org.springframework.data.redis.connection.RedisConnectionFactory; import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory; 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.StringRedisSerializer; @@ -42,4 +46,19 @@ public class RedisConfig { 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"); + } } diff --git a/backend/src/main/java/com/worlabel/global/config/SecurityConfig.java b/backend/src/main/java/com/worlabel/global/config/SecurityConfig.java index 9c37c6d..5fbeb33 100644 --- a/backend/src/main/java/com/worlabel/global/config/SecurityConfig.java +++ b/backend/src/main/java/com/worlabel/global/config/SecurityConfig.java @@ -48,7 +48,7 @@ public class SecurityConfig { .formLogin((auth) -> auth.disable()); // 세션 설정 비활성화 - http.sessionManagement((session)->session + http.sessionManagement((session) -> session .sessionCreationPolicy(SessionCreationPolicy.STATELESS)); // CORS 설정 @@ -57,16 +57,16 @@ public class SecurityConfig { http .exceptionHandling(configurer -> configurer - .authenticationEntryPoint(authenticationEntryPoint) - .accessDeniedHandler(authenticationDeniedHandler) - ); + .authenticationEntryPoint(authenticationEntryPoint) + .accessDeniedHandler(authenticationDeniedHandler) + ); // 경로별 인가 작업 http - .authorizeHttpRequests(auth->auth + .authorizeHttpRequests(auth -> auth .requestMatchers("/swagger", "/swagger-ui.html", "/swagger-ui/**", "/api-docs", "/api-docs/**", "/v3/api-docs/**").permitAll() - .requestMatchers("/api/auth/reissue").permitAll() - .anyRequest().authenticated() + .requestMatchers("/api/auth/reissue").permitAll() + .anyRequest().authenticated() // .anyRequest().permitAll() ); @@ -74,15 +74,12 @@ public class SecurityConfig { http .oauth2Login(oauth2 -> oauth2 .authorizationEndpoint(authorization -> authorization.baseUri("/api/login/oauth2/authorization")) - .redirectionEndpoint(redirection -> redirection.baseUri("/api/login/oauth2/code/*")) + .redirectionEndpoint(redirection -> redirection.baseUri("/api/login/oauth2/code/*")) .userInfoEndpoint(userInfo -> userInfo.userService(customOAuth2UserService)) .successHandler(oAuth2SuccessHandler) ); - - - // JWT 필터 추가 http .addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class); @@ -93,8 +90,8 @@ public class SecurityConfig { public CorsConfigurationSource corsConfigurationSource() { CorsConfiguration configuration = new CorsConfiguration(); configuration.setAllowCredentials(true); - configuration.setAllowedOrigins(List.of(frontend)); // 프론트엔드 URL 사용 - configuration.setAllowedMethods(List.of("GET","POST","PUT","PATCH","DELETE","OPTIONS")); + configuration.setAllowedOrigins(List.of(frontend, "http://localhost:5173")); // 프론트엔드 URL 사용 + configuration.setAllowedMethods(List.of("GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS")); configuration.setAllowedHeaders(List.of("*")); configuration.setMaxAge(3600L); diff --git a/backend/src/main/java/com/worlabel/global/config/WebSocketConfig.java b/backend/src/main/java/com/worlabel/global/config/WebSocketConfig.java new file mode 100644 index 0000000..432a474 --- /dev/null +++ b/backend/src/main/java/com/worlabel/global/config/WebSocketConfig.java @@ -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("*"); + } + +} diff --git a/backend/src/main/java/com/worlabel/global/handler/CustomWebSocketHandler.java b/backend/src/main/java/com/worlabel/global/handler/CustomWebSocketHandler.java new file mode 100644 index 0000000..712ab26 --- /dev/null +++ b/backend/src/main/java/com/worlabel/global/handler/CustomWebSocketHandler.java @@ -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 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()); + } +} diff --git a/backend/src/main/java/com/worlabel/global/service/RedisMessageSubscriber.java b/backend/src/main/java/com/worlabel/global/service/RedisMessageSubscriber.java new file mode 100644 index 0000000..79621cd --- /dev/null +++ b/backend/src/main/java/com/worlabel/global/service/RedisMessageSubscriber.java @@ -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 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); + } + } + } +} \ No newline at end of file From cb976ff556ede0466952794298dd9cc72fb6cbd9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B9=80=EC=9A=A9=EC=88=98?= Date: Wed, 11 Sep 2024 18:00:06 +0900 Subject: [PATCH 2/5] =?UTF-8?q?Feat:=20WebSocket=20=ED=99=98=EA=B2=BD?= =?UTF-8?q?=EC=84=A4=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../project/service/ProjectService.java | 3 +++ .../worlabel/global/config/CorsMvcConfig.java | 2 +- .../worlabel/global/config/RedisConfig.java | 1 - .../global/config/WebSocketConfig.java | 1 - .../global/handler/AIWebSocketClient.java | 19 +++++++++++++++++++ .../handler/CustomWebSocketHandler.java | 13 +++++++++---- .../service/WebSocketMessageSubscriber.java | 16 ++++++++++++++++ 7 files changed, 48 insertions(+), 7 deletions(-) create mode 100644 backend/src/main/java/com/worlabel/global/handler/AIWebSocketClient.java create mode 100644 backend/src/main/java/com/worlabel/global/service/WebSocketMessageSubscriber.java diff --git a/backend/src/main/java/com/worlabel/domain/project/service/ProjectService.java b/backend/src/main/java/com/worlabel/domain/project/service/ProjectService.java index 43fde3b..76e24aa 100644 --- a/backend/src/main/java/com/worlabel/domain/project/service/ProjectService.java +++ b/backend/src/main/java/com/worlabel/domain/project/service/ProjectService.java @@ -120,6 +120,9 @@ public class ProjectService { // 레디스 train 테이블에 존재하는지 확인 // AI서버와 웹 소켓 연결 + + // 웹 소켓 연결 후 AI에서 받은 메시지를 다른 곳에 전달 + } private Workspace getWorkspace(final Integer memberId, final Integer workspaceId) { diff --git a/backend/src/main/java/com/worlabel/global/config/CorsMvcConfig.java b/backend/src/main/java/com/worlabel/global/config/CorsMvcConfig.java index add5b16..d138275 100644 --- a/backend/src/main/java/com/worlabel/global/config/CorsMvcConfig.java +++ b/backend/src/main/java/com/worlabel/global/config/CorsMvcConfig.java @@ -15,7 +15,7 @@ public class CorsMvcConfig implements WebMvcConfigurer { public void addCorsMappings(CorsRegistry registry) { registry.addMapping("/**") .exposedHeaders("Set-Cookie") - .allowedOrigins(frontend, "http://localhost:5173") // application.yml에서 가져온 값 사용 + .allowedOrigins(frontend) // application.yml에서 가져온 값 사용 .allowCredentials(true); } } diff --git a/backend/src/main/java/com/worlabel/global/config/RedisConfig.java b/backend/src/main/java/com/worlabel/global/config/RedisConfig.java index 3c0cde3..1765495 100644 --- a/backend/src/main/java/com/worlabel/global/config/RedisConfig.java +++ b/backend/src/main/java/com/worlabel/global/config/RedisConfig.java @@ -50,7 +50,6 @@ public class RedisConfig { @Bean public RedisMessageListenerContainer redisContainer(RedisConnectionFactory connectionFactory, MessageListenerAdapter listenerAdapter) { - RedisMessageListenerContainer container = new RedisMessageListenerContainer(); container.setConnectionFactory(connectionFactory); container.addMessageListener(listenerAdapter, new ChannelTopic("/ai/train")); diff --git a/backend/src/main/java/com/worlabel/global/config/WebSocketConfig.java b/backend/src/main/java/com/worlabel/global/config/WebSocketConfig.java index 432a474..99c8f51 100644 --- a/backend/src/main/java/com/worlabel/global/config/WebSocketConfig.java +++ b/backend/src/main/java/com/worlabel/global/config/WebSocketConfig.java @@ -2,7 +2,6 @@ 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; diff --git a/backend/src/main/java/com/worlabel/global/handler/AIWebSocketClient.java b/backend/src/main/java/com/worlabel/global/handler/AIWebSocketClient.java new file mode 100644 index 0000000..10d24a2 --- /dev/null +++ b/backend/src/main/java/com/worlabel/global/handler/AIWebSocketClient.java @@ -0,0 +1,19 @@ +package com.worlabel.global.handler; + +import org.springframework.stereotype.Component; +import org.springframework.web.socket.WebSocketSession; +import org.springframework.web.socket.client.standard.StandardWebSocketClient; +import org.springframework.web.socket.handler.TextWebSocketHandler; + +@Component +public class AIWebSocketClient extends TextWebSocketHandler { + + private WebSocketSession session; + + public void connectToAIServer(){ + StandardWebSocketClient client = new StandardWebSocketClient(); + try{ + session = client.doHandshake() + } + } +} diff --git a/backend/src/main/java/com/worlabel/global/handler/CustomWebSocketHandler.java b/backend/src/main/java/com/worlabel/global/handler/CustomWebSocketHandler.java index 712ab26..408587e 100644 --- a/backend/src/main/java/com/worlabel/global/handler/CustomWebSocketHandler.java +++ b/backend/src/main/java/com/worlabel/global/handler/CustomWebSocketHandler.java @@ -2,6 +2,7 @@ package com.worlabel.global.handler; import com.worlabel.global.service.RedisMessageSubscriber; import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.lang.NonNull; import org.springframework.stereotype.Component; @@ -10,25 +11,29 @@ import org.springframework.web.socket.TextMessage; import org.springframework.web.socket.WebSocketSession; import org.springframework.web.socket.handler.TextWebSocketHandler; +@Slf4j @Component @RequiredArgsConstructor public class CustomWebSocketHandler extends TextWebSocketHandler { private final RedisTemplate redisTemplate; - private final RedisMessageSubscriber redisMessageSubscriber; +// private final RedisMessageSubscriber redisMessageSubscriber; @Override public void afterConnectionEstablished(@NonNull WebSocketSession session) { - redisMessageSubscriber.addSession(session); + log.debug("연결 성공 - afterConnectionEstablished"); +// redisMessageSubscriber.addSession(session); } @Override - public void afterConnectionClosed(@NonNull WebSocketSession session,@NonNull CloseStatus status) throws Exception { - redisMessageSubscriber.removeSession(session); + public void afterConnectionClosed(@NonNull WebSocketSession session,@NonNull CloseStatus status) { + log.debug("연결 해제 - afterConnectionClosed"); +// redisMessageSubscriber.removeSession(session); } @Override protected void handleTextMessage(@NonNull WebSocketSession session, TextMessage message) { + log.debug("메세지 받음 - handleTextMessage : {}",message.getPayload()); // Redis 메시지 발행 redisTemplate.convertAndSend("/ai/train", message.getPayload()); } diff --git a/backend/src/main/java/com/worlabel/global/service/WebSocketMessageSubscriber.java b/backend/src/main/java/com/worlabel/global/service/WebSocketMessageSubscriber.java new file mode 100644 index 0000000..125b963 --- /dev/null +++ b/backend/src/main/java/com/worlabel/global/service/WebSocketMessageSubscriber.java @@ -0,0 +1,16 @@ +package com.worlabel.global.service; + +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; + +@Slf4j +@Service +@RequiredArgsConstructor +public class WebSocketMessageSubscriber { + + public void onMessage(String message) { + // Redis 메세지를 수신하여 WebSocket 클라이언트에 전달 하기 + log.debug("수신 - {} 이곳에서 클라이언트에게 전달",message); + } +} From c7d613365bd35fd2f0bb70e340905b979e8449af Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B9=80=EC=9A=A9=EC=88=98?= Date: Thu, 12 Sep 2024 00:54:30 +0900 Subject: [PATCH 3/5] =?UTF-8?q?Feat:=20Fast=20API=20=EC=97=B0=EA=B2=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../label/controller/LabelController.java | 1 + .../domain/progress/ProgressController.java | 19 +++++ .../project/controller/ProjectController.java | 6 +- .../domain/project/dto/RequestDto.java | 29 ++++++++ .../project/service/ProjectService.java | 38 ++++++++-- .../worlabel/global/config/AsyncConfig.java | 10 +++ .../global/config/CustomWebSocketConfig1.java | 4 + .../worlabel/global/config/RedisConfig.java | 18 ----- .../global/config/SecurityConfig.java | 2 +- .../global/config/WebSocketConfig.java | 30 ++++---- .../filter/JwtAuthenticationFilter.java | 1 + .../global/handler/AIWebSocketClient.java | 19 ----- .../handler/CustomWebSocketHandler.java | 28 +------ .../global/service/AIWebSocketClient.java | 74 +++++++++++++++++++ .../service/RedisMessageSubscriber.java | 35 --------- .../service/WebSocketMessageSubscriber.java | 16 ---- 16 files changed, 197 insertions(+), 133 deletions(-) create mode 100644 backend/src/main/java/com/worlabel/domain/progress/ProgressController.java create mode 100644 backend/src/main/java/com/worlabel/domain/project/dto/RequestDto.java create mode 100644 backend/src/main/java/com/worlabel/global/config/AsyncConfig.java create mode 100644 backend/src/main/java/com/worlabel/global/config/CustomWebSocketConfig1.java delete mode 100644 backend/src/main/java/com/worlabel/global/handler/AIWebSocketClient.java create mode 100644 backend/src/main/java/com/worlabel/global/service/AIWebSocketClient.java delete mode 100644 backend/src/main/java/com/worlabel/global/service/RedisMessageSubscriber.java delete mode 100644 backend/src/main/java/com/worlabel/global/service/WebSocketMessageSubscriber.java diff --git a/backend/src/main/java/com/worlabel/domain/label/controller/LabelController.java b/backend/src/main/java/com/worlabel/domain/label/controller/LabelController.java index da1ad65..dfde93f 100644 --- a/backend/src/main/java/com/worlabel/domain/label/controller/LabelController.java +++ b/backend/src/main/java/com/worlabel/domain/label/controller/LabelController.java @@ -26,6 +26,7 @@ public class LabelController { private final LabelService labelService; + @Operation(summary = "프로젝트 단위 오토레이블링", description = "해당 프로젝트 이미지를 오토레이블링합니다.") @SwaggerApiSuccess(description = "해당 프로젝트가 오토 레이블링 됩니다.") @SwaggerApiError({ErrorCode.EMPTY_REQUEST_PARAMETER, ErrorCode.SERVER_ERROR}) diff --git a/backend/src/main/java/com/worlabel/domain/progress/ProgressController.java b/backend/src/main/java/com/worlabel/domain/progress/ProgressController.java new file mode 100644 index 0000000..bce5218 --- /dev/null +++ b/backend/src/main/java/com/worlabel/domain/progress/ProgressController.java @@ -0,0 +1,19 @@ +package com.worlabel.domain.progress; + +import lombok.extern.slf4j.Slf4j; +import org.springframework.messaging.handler.annotation.MessageMapping; +import org.springframework.messaging.handler.annotation.SendTo; +import org.springframework.stereotype.Controller; + +@Slf4j +@Controller +public class ProgressController { + + @MessageMapping("/ai/train/progress") + @SendTo("/topic/progress") + public String handleTrainingProgress(String message) { + // FastAPI에서 전송한 학습 진행 상황 메시지를 처리하고 클라이언트로 전달 + log.debug("Received message: {}", message); + return message; + } +} diff --git a/backend/src/main/java/com/worlabel/domain/project/controller/ProjectController.java b/backend/src/main/java/com/worlabel/domain/project/controller/ProjectController.java index 152883d..2583fd6 100644 --- a/backend/src/main/java/com/worlabel/domain/project/controller/ProjectController.java +++ b/backend/src/main/java/com/worlabel/domain/project/controller/ProjectController.java @@ -16,10 +16,12 @@ import io.swagger.v3.oas.annotations.Parameter; import io.swagger.v3.oas.annotations.tags.Tag; import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; import org.springframework.web.bind.annotation.*; import java.util.List; +@Slf4j @Tag(name = "프로젝트 관련 API") @RestController @RequestMapping("/api") @@ -77,10 +79,12 @@ public class ProjectController { @Operation(summary = "프로젝트 모델 학습", description = "프로젝트 모델을 학습시킵니다..") @SwaggerApiSuccess(description = "프로젝트 모델이 성공적으로 학습됩니다.") @SwaggerApiError({ErrorCode.EMPTY_REQUEST_PARAMETER, ErrorCode.SERVER_ERROR}) - @DeleteMapping("/projects/{project_id}/train") + @PostMapping("/projects/{project_id}/train") public BaseResponse trainModel( @CurrentUser final Integer memberId, @PathVariable("project_id") final Integer projectId) { + log.debug("훈련 요청 "); + projectService.train(memberId, projectId); return SuccessResponse.empty(); } diff --git a/backend/src/main/java/com/worlabel/domain/project/dto/RequestDto.java b/backend/src/main/java/com/worlabel/domain/project/dto/RequestDto.java new file mode 100644 index 0000000..92fe3b5 --- /dev/null +++ b/backend/src/main/java/com/worlabel/domain/project/dto/RequestDto.java @@ -0,0 +1,29 @@ +package com.worlabel.domain.project.dto; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; + +import java.util.List; + +public class RequestDto { + + @Data + public class TrainDataInfo { + private String imageUrl; + } + + @Data + public static class TrainRequest { + @JsonProperty("project_id") + private int projectId; + + @JsonProperty("data") + private List data; +// private int seed; // Optional +// private float ratio; // Default = 0.8 +// private int epochs; // Default = 50 +// private float batch; // Default = -1 + + // Getters and Setters + } +} diff --git a/backend/src/main/java/com/worlabel/domain/project/service/ProjectService.java b/backend/src/main/java/com/worlabel/domain/project/service/ProjectService.java index 76e24aa..f483701 100644 --- a/backend/src/main/java/com/worlabel/domain/project/service/ProjectService.java +++ b/backend/src/main/java/com/worlabel/domain/project/service/ProjectService.java @@ -8,6 +8,8 @@ import com.worlabel.domain.participant.entity.WorkspaceParticipant; import com.worlabel.domain.participant.entity.dto.ParticipantRequest; import com.worlabel.domain.participant.repository.ParticipantRepository; import com.worlabel.domain.participant.repository.WorkspaceParticipantRepository; +import com.worlabel.domain.project.dto.RequestDto; +import com.worlabel.domain.project.dto.RequestDto.TrainRequest; import com.worlabel.domain.project.entity.Project; import com.worlabel.domain.project.entity.dto.ProjectRequest; import com.worlabel.domain.project.entity.dto.ProjectResponse; @@ -16,14 +18,26 @@ import com.worlabel.domain.workspace.entity.Workspace; import com.worlabel.domain.workspace.repository.WorkspaceRepository; import com.worlabel.global.exception.CustomException; import com.worlabel.global.exception.ErrorCode; +import com.worlabel.global.service.AIWebSocketClient; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Value; +import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.http.ResponseEntity; +import org.springframework.scheduling.annotation.Async; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; +import org.springframework.web.client.RestTemplate; +import org.springframework.web.socket.TextMessage; +import org.springframework.web.socket.WebSocketHttpHeaders; +import org.springframework.web.socket.client.standard.StandardWebSocketClient; +import org.springframework.web.socket.handler.TextWebSocketHandler; +import org.springframework.web.util.UriComponentsBuilder; +import java.net.URI; import java.util.List; import java.util.Objects; +import java.util.concurrent.CompletableFuture; @Slf4j @Service @@ -36,7 +50,7 @@ public class ProjectService { private final ParticipantRepository participantRepository; private final MemberRepository memberRepository; private final WorkspaceParticipantRepository workspaceParticipantRepository; - + private final RestTemplate restTemplate; public ProjectResponse createProject(final Integer memberId, final Integer workspaceId, final ProjectRequest projectRequest) { Workspace workspace = getWorkspace(memberId, workspaceId); @@ -114,15 +128,29 @@ public class ProjectService { participantRepository.delete(participant); } - public void train(final Integer memberId,final Integer projectId) { + @Async + public CompletableFuture train(final Integer memberId, final Integer projectId) { // 멤버 권한 체크 - + // 레디스 train 테이블에 존재하는지 확인 - // AI서버와 웹 소켓 연결 + // FastAPI 서버로 학습 요청을 전송 + String url = "http://localhost:8000/api/detection/train"; - // 웹 소켓 연결 후 AI에서 받은 메시지를 다른 곳에 전달 + TrainRequest trainRequest = new TrainRequest(); + trainRequest.setProjectId(projectId); + trainRequest.setData(List.of()); + // FastAPI 서버로 POST 요청 전송 + try { + ResponseEntity result = restTemplate.postForEntity(url, trainRequest, String.class); + log.debug("응답 결과 {} ",result); + System.out.println("FastAPI 서버에 학습 요청을 성공적으로 전송했습니다. Project ID: " + projectId); + } catch (Exception e) { + System.err.println("FastAPI 서버에 학습 요청을 전송하는 중 오류가 발생했습니다: " + e.getMessage()); + } + + return CompletableFuture.completedFuture(null); } private Workspace getWorkspace(final Integer memberId, final Integer workspaceId) { diff --git a/backend/src/main/java/com/worlabel/global/config/AsyncConfig.java b/backend/src/main/java/com/worlabel/global/config/AsyncConfig.java new file mode 100644 index 0000000..422172f --- /dev/null +++ b/backend/src/main/java/com/worlabel/global/config/AsyncConfig.java @@ -0,0 +1,10 @@ +package com.worlabel.global.config; + +import org.springframework.context.annotation.Configuration; +import org.springframework.scheduling.annotation.EnableAsync; + +@EnableAsync +@Configuration +public class AsyncConfig { + +} diff --git a/backend/src/main/java/com/worlabel/global/config/CustomWebSocketConfig1.java b/backend/src/main/java/com/worlabel/global/config/CustomWebSocketConfig1.java new file mode 100644 index 0000000..456d455 --- /dev/null +++ b/backend/src/main/java/com/worlabel/global/config/CustomWebSocketConfig1.java @@ -0,0 +1,4 @@ +package com.worlabel.global.config; + +public class CustomWebSocketConfig1 { +} diff --git a/backend/src/main/java/com/worlabel/global/config/RedisConfig.java b/backend/src/main/java/com/worlabel/global/config/RedisConfig.java index 1765495..c546bdc 100644 --- a/backend/src/main/java/com/worlabel/global/config/RedisConfig.java +++ b/backend/src/main/java/com/worlabel/global/config/RedisConfig.java @@ -6,10 +6,6 @@ import org.springframework.context.annotation.Configuration; import org.springframework.data.redis.connection.RedisConnectionFactory; import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory; 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.StringRedisSerializer; @@ -46,18 +42,4 @@ public class RedisConfig { 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"); - } } diff --git a/backend/src/main/java/com/worlabel/global/config/SecurityConfig.java b/backend/src/main/java/com/worlabel/global/config/SecurityConfig.java index 5fbeb33..4ec098c 100644 --- a/backend/src/main/java/com/worlabel/global/config/SecurityConfig.java +++ b/backend/src/main/java/com/worlabel/global/config/SecurityConfig.java @@ -64,7 +64,7 @@ public class SecurityConfig { // 경로별 인가 작업 http .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/**", "/ws/**").permitAll() .requestMatchers("/api/auth/reissue").permitAll() .anyRequest().authenticated() // .anyRequest().permitAll() diff --git a/backend/src/main/java/com/worlabel/global/config/WebSocketConfig.java b/backend/src/main/java/com/worlabel/global/config/WebSocketConfig.java index 99c8f51..2ec8464 100644 --- a/backend/src/main/java/com/worlabel/global/config/WebSocketConfig.java +++ b/backend/src/main/java/com/worlabel/global/config/WebSocketConfig.java @@ -1,24 +1,26 @@ package com.worlabel.global.config; -import com.worlabel.global.handler.CustomWebSocketHandler; -import lombok.RequiredArgsConstructor; 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; +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 -@EnableWebSocket -@RequiredArgsConstructor -public class WebSocketConfig implements WebSocketConfigurer { - - private final CustomWebSocketHandler webSocketHandler; +@EnableWebSocketMessageBroker +public class WebSocketConfig implements WebSocketMessageBrokerConfigurer { @Override - public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) { - registry - .addHandler(webSocketHandler, "/ws") - .setAllowedOrigins("*"); + public void configureMessageBroker(MessageBrokerRegistry config) { + // 메시지 브로커 설정: 클라이언트가 구독할 수 있는 경로 지정 + config.enableSimpleBroker("/topic"); + // 클라이언트가 메시지를 보낼 때 사용하는 경로의 접두사 + config.setApplicationDestinationPrefixes("/app"); } + @Override + public void registerStompEndpoints(StompEndpointRegistry registry) { + registry.addEndpoint("/ws") + .setAllowedOrigins("*"); + } } diff --git a/backend/src/main/java/com/worlabel/global/filter/JwtAuthenticationFilter.java b/backend/src/main/java/com/worlabel/global/filter/JwtAuthenticationFilter.java index eb582e9..d5c3d22 100644 --- a/backend/src/main/java/com/worlabel/global/filter/JwtAuthenticationFilter.java +++ b/backend/src/main/java/com/worlabel/global/filter/JwtAuthenticationFilter.java @@ -30,6 +30,7 @@ public class JwtAuthenticationFilter extends OncePerRequestFilter { @Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { + log.debug(request.getRequestURI()); String token = resolveToken(request); log.debug("token {}", token); try { diff --git a/backend/src/main/java/com/worlabel/global/handler/AIWebSocketClient.java b/backend/src/main/java/com/worlabel/global/handler/AIWebSocketClient.java deleted file mode 100644 index 10d24a2..0000000 --- a/backend/src/main/java/com/worlabel/global/handler/AIWebSocketClient.java +++ /dev/null @@ -1,19 +0,0 @@ -package com.worlabel.global.handler; - -import org.springframework.stereotype.Component; -import org.springframework.web.socket.WebSocketSession; -import org.springframework.web.socket.client.standard.StandardWebSocketClient; -import org.springframework.web.socket.handler.TextWebSocketHandler; - -@Component -public class AIWebSocketClient extends TextWebSocketHandler { - - private WebSocketSession session; - - public void connectToAIServer(){ - StandardWebSocketClient client = new StandardWebSocketClient(); - try{ - session = client.doHandshake() - } - } -} diff --git a/backend/src/main/java/com/worlabel/global/handler/CustomWebSocketHandler.java b/backend/src/main/java/com/worlabel/global/handler/CustomWebSocketHandler.java index 408587e..b970c8e 100644 --- a/backend/src/main/java/com/worlabel/global/handler/CustomWebSocketHandler.java +++ b/backend/src/main/java/com/worlabel/global/handler/CustomWebSocketHandler.java @@ -1,40 +1,20 @@ package com.worlabel.global.handler; -import com.worlabel.global.service.RedisMessageSubscriber; -import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; -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; @Slf4j @Component -@RequiredArgsConstructor public class CustomWebSocketHandler extends TextWebSocketHandler { - private final RedisTemplate redisTemplate; -// private final RedisMessageSubscriber redisMessageSubscriber; - @Override - public void afterConnectionEstablished(@NonNull WebSocketSession session) { - log.debug("연결 성공 - afterConnectionEstablished"); -// redisMessageSubscriber.addSession(session); - } + protected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception { + // FastAPI에서 받은 메세지 + log.debug("FastAPI로 부터 받은 메세지 : {}", message.getPayload()); - @Override - public void afterConnectionClosed(@NonNull WebSocketSession session,@NonNull CloseStatus status) { - log.debug("연결 해제 - afterConnectionClosed"); -// redisMessageSubscriber.removeSession(session); - } - - @Override - protected void handleTextMessage(@NonNull WebSocketSession session, TextMessage message) { - log.debug("메세지 받음 - handleTextMessage : {}",message.getPayload()); - // Redis 메시지 발행 - redisTemplate.convertAndSend("/ai/train", message.getPayload()); + // 클라이언트로 보내기 } } diff --git a/backend/src/main/java/com/worlabel/global/service/AIWebSocketClient.java b/backend/src/main/java/com/worlabel/global/service/AIWebSocketClient.java new file mode 100644 index 0000000..adff64a --- /dev/null +++ b/backend/src/main/java/com/worlabel/global/service/AIWebSocketClient.java @@ -0,0 +1,74 @@ +package com.worlabel.global.service; + +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; +import org.springframework.web.socket.TextMessage; +import org.springframework.web.socket.WebSocketHttpHeaders; +import org.springframework.web.socket.WebSocketSession; +import org.springframework.web.socket.client.standard.StandardWebSocketClient; +import org.springframework.web.socket.handler.TextWebSocketHandler; + +import java.net.URI; + +@Slf4j +@Component +@RequiredArgsConstructor +public class AIWebSocketClient { + + private WebSocketSession session; + + // WebSocket 연결 설정 + public void connect(String url) { + try { + StandardWebSocketClient client = new StandardWebSocketClient(); + WebSocketHttpHeaders headers = new WebSocketHttpHeaders(); + client.doHandshake(new WebSocketHandler(), headers, URI.create(url)).get(); + log.info("Connected to WebSocket at {}", url); + } catch (Exception e) { + log.error("Failed to connect to WebSocket: {}", e.getMessage()); + } + } + + // WebSocket 메시지 전송 + public void sendMessage(String message) { + try { + if (session != null && session.isOpen()) { + session.sendMessage(new TextMessage(message)); + log.info("Sent message: {}", message); + } else { + log.warn("WebSocket session is not open. Unable to send message."); + } + } catch (Exception e) { + log.error("Failed to send message: {}", e.getMessage()); + } + } + + + // WebSocket 연결 종료 + public void close() { + try { + if (session != null && session.isOpen()) { + session.close(); + log.info("WebSocket connection closed."); + } + } catch (Exception e) { + log.error("Failed to close WebSocket session: {}", e.getMessage()); + } + } + + // WebSocket 핸들러 정의 + private class WebSocketHandler extends TextWebSocketHandler { + @Override + public void afterConnectionEstablished(WebSocketSession session) throws Exception { + AIWebSocketClient.this.session = session; + log.info("WebSocket connection established."); + } + + @Override + protected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception { + log.info("Received message: {}", message.getPayload()); + // 여기서 메시지를 처리하는 로직을 추가할 수 있습니다. + } + } +} diff --git a/backend/src/main/java/com/worlabel/global/service/RedisMessageSubscriber.java b/backend/src/main/java/com/worlabel/global/service/RedisMessageSubscriber.java deleted file mode 100644 index 79621cd..0000000 --- a/backend/src/main/java/com/worlabel/global/service/RedisMessageSubscriber.java +++ /dev/null @@ -1,35 +0,0 @@ -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 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); - } - } - } -} \ No newline at end of file diff --git a/backend/src/main/java/com/worlabel/global/service/WebSocketMessageSubscriber.java b/backend/src/main/java/com/worlabel/global/service/WebSocketMessageSubscriber.java deleted file mode 100644 index 125b963..0000000 --- a/backend/src/main/java/com/worlabel/global/service/WebSocketMessageSubscriber.java +++ /dev/null @@ -1,16 +0,0 @@ -package com.worlabel.global.service; - -import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; -import org.springframework.stereotype.Service; - -@Slf4j -@Service -@RequiredArgsConstructor -public class WebSocketMessageSubscriber { - - public void onMessage(String message) { - // Redis 메세지를 수신하여 WebSocket 클라이언트에 전달 하기 - log.debug("수신 - {} 이곳에서 클라이언트에게 전달",message); - } -} From 6c46b64ce2b59eaf2c667e11b76e67a88e0c9d29 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B9=80=EC=9A=A9=EC=88=98?= Date: Thu, 12 Sep 2024 17:03:10 +0900 Subject: [PATCH 4/5] =?UTF-8?q?Feat:=20WebSocket=20=EC=97=B0=EA=B2=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../worlabel/domain/image/entity/Image.java | 7 +-- .../domain/image/entity/LabelStatus.java | 2 +- .../image/repository/ImageRepository.java | 6 +-- .../label/controller/LabelController.java | 4 -- .../worlabel/domain/label/entity/Label.java | 11 ++--- .../label/repository/LabelRepository.java | 2 + .../domain/label/service/LabelService.java | 28 ++++++------ .../repository/ParticipantRepository.java | 2 + .../domain/progress/ProgressController.java | 23 ++++++++++ .../project/controller/ProjectController.java | 2 - .../project/service/ProjectService.java | 43 ++++++++++--------- .../global/service/S3UploadService.java | 5 +-- 12 files changed, 73 insertions(+), 62 deletions(-) diff --git a/backend/src/main/java/com/worlabel/domain/image/entity/Image.java b/backend/src/main/java/com/worlabel/domain/image/entity/Image.java index 12cceb1..b2002af 100644 --- a/backend/src/main/java/com/worlabel/domain/image/entity/Image.java +++ b/backend/src/main/java/com/worlabel/domain/image/entity/Image.java @@ -9,9 +9,6 @@ import lombok.AccessLevel; import lombok.Getter; import lombok.NoArgsConstructor; -import java.util.ArrayList; -import java.util.List; - @Getter @Entity @Table(name = "project_image") @@ -49,7 +46,7 @@ public class Image extends BaseEntity { */ @Column(name = "status", nullable = false) @Enumerated(EnumType.STRING) - private LabelStatus status = LabelStatus.PENDING; + private LabelStatus status = LabelStatus.Pending; /** * 속한 폴더 @@ -62,7 +59,7 @@ public class Image extends BaseEntity { /** * 이미지에 연결된 레이블 */ - @OneToOne(mappedBy = "image", cascade = CascadeType.ALL, orphanRemoval = true) + @OneToOne(mappedBy = "image", fetch = FetchType.LAZY, cascade = CascadeType.ALL, orphanRemoval = true) private Label label; private Image(final String imageTitle, final String imageUrl, final Integer order, final Folder folder) { diff --git a/backend/src/main/java/com/worlabel/domain/image/entity/LabelStatus.java b/backend/src/main/java/com/worlabel/domain/image/entity/LabelStatus.java index 93f66a7..f3253a0 100644 --- a/backend/src/main/java/com/worlabel/domain/image/entity/LabelStatus.java +++ b/backend/src/main/java/com/worlabel/domain/image/entity/LabelStatus.java @@ -4,7 +4,7 @@ import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonValue; public enum LabelStatus { - PENDING, + Pending, IN_PROGRESS, NEED_REVIEW, COMPLETED; diff --git a/backend/src/main/java/com/worlabel/domain/image/repository/ImageRepository.java b/backend/src/main/java/com/worlabel/domain/image/repository/ImageRepository.java index 2e70391..ddb86b3 100644 --- a/backend/src/main/java/com/worlabel/domain/image/repository/ImageRepository.java +++ b/backend/src/main/java/com/worlabel/domain/image/repository/ImageRepository.java @@ -12,10 +12,8 @@ public interface ImageRepository extends JpaRepository { Optional findByIdAndFolderId(Long imageId, Integer folderId); - // TODO: N + 1 @Query("select i from Image i " + - "join fetch i.folder f " + - "join fetch f.project p " + - "where p.id = :projectId") + "join fetch i.label l " + + "where i.folder.project.id = :projectId") List findImagesByProjectId(@Param("projectId") Integer projectId); } diff --git a/backend/src/main/java/com/worlabel/domain/label/controller/LabelController.java b/backend/src/main/java/com/worlabel/domain/label/controller/LabelController.java index dfde93f..ac7b67b 100644 --- a/backend/src/main/java/com/worlabel/domain/label/controller/LabelController.java +++ b/backend/src/main/java/com/worlabel/domain/label/controller/LabelController.java @@ -26,7 +26,6 @@ public class LabelController { private final LabelService labelService; - @Operation(summary = "프로젝트 단위 오토레이블링", description = "해당 프로젝트 이미지를 오토레이블링합니다.") @SwaggerApiSuccess(description = "해당 프로젝트가 오토 레이블링 됩니다.") @SwaggerApiError({ErrorCode.EMPTY_REQUEST_PARAMETER, ErrorCode.SERVER_ERROR}) @@ -51,7 +50,4 @@ public class LabelController { labelService.save(imageId); return SuccessResponse.empty(); } - - - } diff --git a/backend/src/main/java/com/worlabel/domain/label/entity/Label.java b/backend/src/main/java/com/worlabel/domain/label/entity/Label.java index a4f7aff..124e190 100644 --- a/backend/src/main/java/com/worlabel/domain/label/entity/Label.java +++ b/backend/src/main/java/com/worlabel/domain/label/entity/Label.java @@ -34,14 +34,6 @@ public class Label extends BaseEntity { @JoinColumn(name = "image_id") private Image image; - /** - * 속한 카테고리 - * TODO: 한 레이블 카테고리에 속한걸 찾는데에 Json파일에 담기 때문에 카테고리는 Label Entity에 없어도 될 것 같음 - */ -// @ManyToOne(fetch = FetchType.LAZY) -// @JoinColumn(name = "label_category_id") -// private LabelCategory labelCategory; - public static Label of(String jsonUrl, Image image) { Label label = new Label(); label.url = jsonUrl; @@ -49,4 +41,7 @@ public class Label extends BaseEntity { return label; } + public void changeUrl(String newUrl){ + + } } diff --git a/backend/src/main/java/com/worlabel/domain/label/repository/LabelRepository.java b/backend/src/main/java/com/worlabel/domain/label/repository/LabelRepository.java index 1f84962..cdf4641 100644 --- a/backend/src/main/java/com/worlabel/domain/label/repository/LabelRepository.java +++ b/backend/src/main/java/com/worlabel/domain/label/repository/LabelRepository.java @@ -8,4 +8,6 @@ import java.util.Optional; public interface LabelRepository extends JpaRepository { Optional