diff --git a/batch-quartz/src/main/java/com/spring/common/advice/ResponseWrapper.java b/batch-quartz/src/main/java/com/spring/common/advice/ResponseWrapper.java index 32c4cc3..1793b86 100644 --- a/batch-quartz/src/main/java/com/spring/common/advice/ResponseWrapper.java +++ b/batch-quartz/src/main/java/com/spring/common/advice/ResponseWrapper.java @@ -18,7 +18,7 @@ import com.spring.common.error.GlobalExceptionHandler; import com.spring.infra.security.error.SecurityExceptionHandler; @RestControllerAdvice( - basePackages = "com.spring.domain.*.api", + basePackages = "com.spring.domain", basePackageClasses = { GlobalExceptionHandler.class, SecurityExceptionHandler.class } ) public class ResponseWrapper implements ResponseBodyAdvice { diff --git a/batch-quartz/src/main/java/com/spring/common/error/BizBaseException.java b/batch-quartz/src/main/java/com/spring/common/error/BizBaseException.java index bf1d28d..d9a6242 100644 --- a/batch-quartz/src/main/java/com/spring/common/error/BizBaseException.java +++ b/batch-quartz/src/main/java/com/spring/common/error/BizBaseException.java @@ -8,8 +8,8 @@ public class BizBaseException extends RuntimeException { private final ExceptionRule exceptionRule; public BizBaseException() { - super(ExceptionRule.BAD_REQUEST.getMessage()); - this.exceptionRule = ExceptionRule.BAD_REQUEST; + super(ExceptionRule.SYSTE_ERROR.getMessage()); + this.exceptionRule = ExceptionRule.SYSTE_ERROR; } public BizBaseException(ExceptionRule exceptionRule) { diff --git a/batch-quartz/src/main/java/com/spring/common/error/ExceptionRule.java b/batch-quartz/src/main/java/com/spring/common/error/ExceptionRule.java index e3e31b6..442bffd 100644 --- a/batch-quartz/src/main/java/com/spring/common/error/ExceptionRule.java +++ b/batch-quartz/src/main/java/com/spring/common/error/ExceptionRule.java @@ -9,6 +9,7 @@ import lombok.RequiredArgsConstructor; @RequiredArgsConstructor public enum ExceptionRule implements ErrorRule { + SYSTE_ERROR(HttpStatus.INTERNAL_SERVER_ERROR, "시스템 오류 입니다."), BAD_REQUEST(HttpStatus.BAD_REQUEST, "잘못된 요청입니다."), UNAUTHORIZED(HttpStatus.UNAUTHORIZED, "인증이 필요합니다."), FORBIDDEN(HttpStatus.FORBIDDEN, "접근이 금지되었습니다."), diff --git a/batch-quartz/src/main/java/com/spring/domain/schedule/api/ScheduleJobApi.java b/batch-quartz/src/main/java/com/spring/domain/schedule/api/ScheduleJobApi.java new file mode 100644 index 0000000..e83842d --- /dev/null +++ b/batch-quartz/src/main/java/com/spring/domain/schedule/api/ScheduleJobApi.java @@ -0,0 +1,66 @@ +package com.spring.domain.schedule.api; + +import java.util.List; + +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import com.spring.domain.schedule.dto.ReScheduleJobRequest; +import com.spring.domain.schedule.dto.ScheduleJobResponse; +import com.spring.domain.schedule.service.FindJobGroupService; +import com.spring.domain.schedule.service.FindScheduleJobService; +import com.spring.domain.schedule.service.ReScheduleJobService; +import com.spring.domain.schedule.service.ScheduleControlService; + +import lombok.RequiredArgsConstructor; + +@RequestMapping("/api/schedule") +@RestController +@RequiredArgsConstructor +public class ScheduleJobApi { + + private final FindScheduleJobService findScheduleJobService; + private final FindJobGroupService findJobGroupService; + private final ReScheduleJobService reScheduleJobService; + private final ScheduleControlService scheduleControlService; + + @GetMapping + public List getAllJobs() { + return findScheduleJobService.getAllJobs(); + } + + @GetMapping("/groups") + public List getJobGroups() { + return findJobGroupService.getJobGroups(); + } + + @GetMapping("/group/{groupName}/names") + public List getJobNamesByGroup(@PathVariable String groupName) { + return findJobGroupService.getJobNamesByGroup(groupName); + } + + @GetMapping("/pause/{groupName}/{jobName}") + public void pauseJob(@PathVariable String groupName, @PathVariable String jobName) { + scheduleControlService.pauseJob(groupName, jobName); + } + + @GetMapping("/resume/{groupName}/{jobName}") + public void resumeJob(@PathVariable String groupName, @PathVariable String jobName) { + scheduleControlService.resumeJob(groupName, jobName); + } + + @GetMapping("/trigger/{groupName}/{jobName}") + public void triggerJob(@PathVariable String groupName, @PathVariable String jobName) { + scheduleControlService.triggerJob(groupName, jobName); + } + + @PostMapping("/reschedule") + public boolean rescheduleJob(@RequestBody ReScheduleJobRequest request) { + return reScheduleJobService.rescheduleJob(request); + } + +} diff --git a/batch-quartz/src/main/java/com/spring/domain/schedule/dto/ReScheduleJobRequest.java b/batch-quartz/src/main/java/com/spring/domain/schedule/dto/ReScheduleJobRequest.java new file mode 100644 index 0000000..e8504fb --- /dev/null +++ b/batch-quartz/src/main/java/com/spring/domain/schedule/dto/ReScheduleJobRequest.java @@ -0,0 +1,14 @@ +package com.spring.domain.schedule.dto; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +@Getter +@RequiredArgsConstructor +public class ReScheduleJobRequest { + + private final String jobGroup; + private final String jobName; + private final String cronExpression; + +} diff --git a/batch-quartz/src/main/java/com/spring/domain/schedule/dto/ScheduleJobResponse.java b/batch-quartz/src/main/java/com/spring/domain/schedule/dto/ScheduleJobResponse.java new file mode 100644 index 0000000..6bdca23 --- /dev/null +++ b/batch-quartz/src/main/java/com/spring/domain/schedule/dto/ScheduleJobResponse.java @@ -0,0 +1,23 @@ +package com.spring.domain.schedule.dto; + +import java.time.LocalDateTime; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +@Getter +@RequiredArgsConstructor +public class ScheduleJobResponse { + + private final String name; // 작업의 이름 + private final String group; // 작업이 속한 그룹의 이름 + private final String description; // 작업에 대한 설명 (JobDetail에서 제공) + private final String schedule; // 다음 실행 시간 (Trigger에서 제공) + private final String status; // 현재 트리거의 상태 (TriggerState에서 제공) + private final String jobClass; // 작업의 클래스 이름 (JobDetail에서 제공) + private final String jobDataMap; // 작업에 대한 데이터 맵 (JobDetail에서 제공, 직렬화된 형태로 저장) + private final String triggerType; // 트리거의 유형 (Trigger의 구현 클래스 이름) + private final LocalDateTime nextFireTime; // 다음 실행 시간 (Trigger에서 제공) + private final LocalDateTime previousFireTime; // 이전 실행 시간 (Trigger에서 제공) + +} diff --git a/batch-quartz/src/main/java/com/spring/domain/schedule/service/FindJobGroupService.java b/batch-quartz/src/main/java/com/spring/domain/schedule/service/FindJobGroupService.java new file mode 100644 index 0000000..83e8354 --- /dev/null +++ b/batch-quartz/src/main/java/com/spring/domain/schedule/service/FindJobGroupService.java @@ -0,0 +1,40 @@ +package com.spring.domain.schedule.service; + +import java.util.Collections; +import java.util.List; +import java.util.stream.Collectors; + +import org.quartz.JobKey; +import org.quartz.Scheduler; +import org.quartz.SchedulerException; +import org.quartz.impl.matchers.GroupMatcher; +import org.springframework.stereotype.Service; + +import lombok.RequiredArgsConstructor; + +@Service +@RequiredArgsConstructor +public class FindJobGroupService { + + private final Scheduler scheduler; + + public List getJobGroups() { + try { + return scheduler.getJobGroupNames(); + } catch (SchedulerException e) { + return Collections.emptyList(); + } + } + + public List getJobNamesByGroup(String groupName) { + try { + return scheduler.getJobKeys(GroupMatcher.jobGroupEquals(groupName)) + .stream() + .map(JobKey::getName) + .collect(Collectors.toList()); + } catch (SchedulerException e) { + return Collections.emptyList(); + } + } + +} diff --git a/batch-quartz/src/main/java/com/spring/domain/schedule/service/FindScheduleJobService.java b/batch-quartz/src/main/java/com/spring/domain/schedule/service/FindScheduleJobService.java new file mode 100644 index 0000000..21dc831 --- /dev/null +++ b/batch-quartz/src/main/java/com/spring/domain/schedule/service/FindScheduleJobService.java @@ -0,0 +1,78 @@ +package com.spring.domain.schedule.service; + +import java.time.LocalDateTime; +import java.time.ZoneId; +import java.util.Collections; +import java.util.Date; +import java.util.List; +import java.util.Objects; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import org.quartz.JobDetail; +import org.quartz.JobKey; +import org.quartz.Scheduler; +import org.quartz.SchedulerException; +import org.quartz.Trigger; +import org.quartz.impl.matchers.GroupMatcher; +import org.springframework.stereotype.Service; + +import com.spring.domain.schedule.dto.ScheduleJobResponse; + +import lombok.RequiredArgsConstructor; + +@Service +@RequiredArgsConstructor +public class FindScheduleJobService { + + private final Scheduler scheduler; + + public List getAllJobs() { + try { + return scheduler.getJobGroupNames().stream() + .flatMap(groupName -> { + try { + return scheduler.getJobKeys(GroupMatcher.jobGroupEquals(groupName)).stream(); + } catch (SchedulerException e) { + return Stream.empty(); + } + }) + .map(jobKey -> { + try { + return createSchedule(jobKey); + } catch (SchedulerException e) { + return null; + } + }) + .filter(Objects::nonNull) + .collect(Collectors.toList()); + } catch (SchedulerException e) { + return Collections.emptyList(); + } + } + + private ScheduleJobResponse createSchedule(JobKey jobKey) throws SchedulerException { + JobDetail jobDetail = scheduler.getJobDetail(jobKey); + Trigger trigger = scheduler.getTriggersOfJob(jobKey).stream().findFirst().orElse(null); + return new ScheduleJobResponse( + jobKey.getName(), + jobKey.getGroup(), + jobDetail.getDescription(), + trigger != null ? trigger.getNextFireTime().toString() : "N/A", + trigger != null ? scheduler.getTriggerState(trigger.getKey()).name() : "UNKNOWN", + jobDetail.getJobClass().getName(), + jobDetail.getJobDataMap().toString(), + trigger != null ? trigger.getClass().getSimpleName() : "UNKNOWN", + trigger != null ? convertToLocalDateTime(trigger.getNextFireTime()) : null, + trigger != null ? convertToLocalDateTime(trigger.getPreviousFireTime()) : null + ); + } + + private LocalDateTime convertToLocalDateTime(Date date) { + if (date == null) return null; + return date.toInstant() + .atZone(ZoneId.systemDefault()) + .toLocalDateTime(); + } + +} diff --git a/batch-quartz/src/main/java/com/spring/domain/schedule/service/ReScheduleJobService.java b/batch-quartz/src/main/java/com/spring/domain/schedule/service/ReScheduleJobService.java new file mode 100644 index 0000000..28ccf49 --- /dev/null +++ b/batch-quartz/src/main/java/com/spring/domain/schedule/service/ReScheduleJobService.java @@ -0,0 +1,39 @@ +package com.spring.domain.schedule.service; + +import org.quartz.CronScheduleBuilder; +import org.quartz.Scheduler; +import org.quartz.SchedulerException; +import org.quartz.Trigger; +import org.quartz.TriggerBuilder; +import org.quartz.TriggerKey; +import org.springframework.stereotype.Service; + +import com.spring.domain.schedule.dto.ReScheduleJobRequest; + +import lombok.RequiredArgsConstructor; + +@Service +@RequiredArgsConstructor +public class ReScheduleJobService { + + private final Scheduler scheduler; + + public boolean rescheduleJob(ReScheduleJobRequest request) { + try { + TriggerKey triggerKey = TriggerKey.triggerKey(request.getJobName(), request.getJobGroup()); + Trigger oldTrigger = scheduler.getTrigger(triggerKey); + if (oldTrigger != null) { + Trigger newTrigger = TriggerBuilder.newTrigger() + .withIdentity(triggerKey) + .withSchedule(CronScheduleBuilder.cronSchedule(request.getCronExpression())) + .build(); + scheduler.rescheduleJob(oldTrigger.getKey(), newTrigger); + return true; + } + return false; + } catch (SchedulerException e) { + return false; + } + } + +} diff --git a/batch-quartz/src/main/java/com/spring/domain/schedule/service/ScheduleControlService.java b/batch-quartz/src/main/java/com/spring/domain/schedule/service/ScheduleControlService.java new file mode 100644 index 0000000..a5a84af --- /dev/null +++ b/batch-quartz/src/main/java/com/spring/domain/schedule/service/ScheduleControlService.java @@ -0,0 +1,45 @@ +package com.spring.domain.schedule.service; + +import org.quartz.JobKey; +import org.quartz.Scheduler; +import org.quartz.SchedulerException; +import org.springframework.stereotype.Service; + +import com.spring.common.error.BizBaseException; + +import lombok.RequiredArgsConstructor; + +@Service +@RequiredArgsConstructor +public class ScheduleControlService { + + private final Scheduler scheduler; + + public void pauseJob(String groupName, String jobName) { + try { + JobKey jobKey = new JobKey(jobName, groupName); + scheduler.pauseJob(jobKey); + } catch (SchedulerException e) { + throw new BizBaseException(); + } + } + + public void resumeJob(String groupName, String jobName) { + try { + JobKey jobKey = new JobKey(jobName, groupName); + scheduler.resumeJob(jobKey); + } catch (SchedulerException e) { + throw new BizBaseException(); + } + } + + public void triggerJob(String groupName, String jobName) { + try { + JobKey jobKey = new JobKey(jobName, groupName); + scheduler.triggerJob(jobKey); + } catch (SchedulerException e) { + throw new BizBaseException(); + } + } + +} diff --git a/batch-quartz/src/main/java/com/spring/infra/security/config/SecurityConfig.java b/batch-quartz/src/main/java/com/spring/infra/security/config/SecurityConfig.java index d7e87a6..080f7de 100644 --- a/batch-quartz/src/main/java/com/spring/infra/security/config/SecurityConfig.java +++ b/batch-quartz/src/main/java/com/spring/infra/security/config/SecurityConfig.java @@ -26,6 +26,7 @@ import org.springframework.security.web.authentication.UsernamePasswordAuthentic import com.fasterxml.jackson.databind.ObjectMapper; import com.spring.infra.security.filter.AuthenticationProcessingFilter; import com.spring.infra.security.filter.JwtAuthenticationFilter; +import com.spring.infra.security.filter.RedirectIfAuthenticatedFilter; import com.spring.infra.security.handler.SecurityAccessDeniedHandler; import com.spring.infra.security.handler.SecurityAuthenticationEntryPoint; import com.spring.infra.security.handler.SigninFailureHandler; @@ -94,6 +95,9 @@ public class SecurityConfig { .addFilterAfter( new JwtAuthenticationFilter(tokenService, List.of(PERMITTED_URI)), AuthenticationProcessingFilter.class + ).addFilterAfter( + new RedirectIfAuthenticatedFilter(), + JwtAuthenticationFilter.class ) .exceptionHandling(ex -> ex .authenticationEntryPoint(authenticationEntryPoint) diff --git a/batch-quartz/src/main/java/com/spring/infra/security/filter/RedirectIfAuthenticatedFilter.java b/batch-quartz/src/main/java/com/spring/infra/security/filter/RedirectIfAuthenticatedFilter.java new file mode 100644 index 0000000..42c5dc9 --- /dev/null +++ b/batch-quartz/src/main/java/com/spring/infra/security/filter/RedirectIfAuthenticatedFilter.java @@ -0,0 +1,32 @@ +package com.spring.infra.security.filter; + +import java.io.IOException; + +import javax.servlet.FilterChain; +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.springframework.lang.NonNull; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.web.filter.OncePerRequestFilter; + +public class RedirectIfAuthenticatedFilter extends OncePerRequestFilter { + + @Override + protected void doFilterInternal( + @NonNull HttpServletRequest request, + @NonNull HttpServletResponse response, + @NonNull FilterChain filterChain + ) throws ServletException, IOException { + String requestURI = request.getRequestURI(); + Authentication auth = SecurityContextHolder.getContext().getAuthentication(); + if (auth != null && auth.isAuthenticated() && "/".equals(requestURI)) { + response.sendRedirect("/main"); + return; + } + filterChain.doFilter(request, response); + } + +} diff --git a/batch-quartz/src/main/java/com/spring/infra/security/handler/HttpRequestEndpointChecker.java b/batch-quartz/src/main/java/com/spring/infra/security/handler/HttpRequestEndpointChecker.java new file mode 100644 index 0000000..7a1dded --- /dev/null +++ b/batch-quartz/src/main/java/com/spring/infra/security/handler/HttpRequestEndpointChecker.java @@ -0,0 +1,37 @@ +package com.spring.infra.security.handler; + +import javax.servlet.http.HttpServletRequest; + +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.stereotype.Component; +import org.springframework.web.method.HandlerMethod; +import org.springframework.web.servlet.HandlerExecutionChain; +import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping; +import org.springframework.web.util.ServletRequestPathUtils; + +import lombok.RequiredArgsConstructor; + +@Component +@RequiredArgsConstructor +public class HttpRequestEndpointChecker { + + @Qualifier("requestMappingHandlerMapping") + private final RequestMappingHandlerMapping requestMapping; + + public boolean isEndpointExist(HttpServletRequest request) { + try { + if (!ServletRequestPathUtils.hasParsedRequestPath(request)) { + ServletRequestPathUtils.parseAndCache(request); + } + HandlerExecutionChain handler = requestMapping.getHandler(request); + if (handler != null) { + HandlerMethod method = (HandlerMethod) handler.getHandler(); + if (method != null) return true; + } + } catch (Exception e) { + return false; + } + return false; + } + +} diff --git a/batch-quartz/src/main/java/com/spring/infra/security/handler/SecurityAuthenticationEntryPoint.java b/batch-quartz/src/main/java/com/spring/infra/security/handler/SecurityAuthenticationEntryPoint.java index 9d0ef34..09519b1 100644 --- a/batch-quartz/src/main/java/com/spring/infra/security/handler/SecurityAuthenticationEntryPoint.java +++ b/batch-quartz/src/main/java/com/spring/infra/security/handler/SecurityAuthenticationEntryPoint.java @@ -12,6 +12,8 @@ import org.springframework.security.web.AuthenticationEntryPoint; import org.springframework.stereotype.Component; import org.springframework.web.servlet.HandlerExceptionResolver; +import lombok.RequiredArgsConstructor; + /** * JWT 인증에서 인증되지 않은 접근을 처리하는 진입점 클래스입니다. * @@ -19,23 +21,27 @@ import org.springframework.web.servlet.HandlerExceptionResolver; * @version 1.0 */ @Component +@RequiredArgsConstructor public class SecurityAuthenticationEntryPoint implements AuthenticationEntryPoint { - private final HandlerExceptionResolver resolver; private static final String EXCEPTION_ATTRIBUTE = "exception"; - public SecurityAuthenticationEntryPoint(@Qualifier("handlerExceptionResolver") HandlerExceptionResolver resolver) { - this.resolver = resolver; - } + @Qualifier("handlerExceptionResolver") + private final HandlerExceptionResolver resolver; + private final HttpRequestEndpointChecker endpointChecker; @Override public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException, ServletException { - if (request.getAttribute(EXCEPTION_ATTRIBUTE) != null) { - resolver.resolveException(request, response, null, (Exception) request.getAttribute(EXCEPTION_ATTRIBUTE)); - return; + if (!endpointChecker.isEndpointExist(request)) { + response.sendError(HttpServletResponse.SC_NOT_FOUND); + } else { + if (request.getAttribute(EXCEPTION_ATTRIBUTE) != null) { + resolver.resolveException(request, response, null, (Exception) request.getAttribute(EXCEPTION_ATTRIBUTE)); + } else { + resolver.resolveException(request, response, null, authException); + } } - response.sendError(HttpServletResponse.SC_NOT_FOUND); } } diff --git a/batch-quartz/src/main/java/com/spring/infra/security/handler/SigninSuccessHandler.java b/batch-quartz/src/main/java/com/spring/infra/security/handler/SigninSuccessHandler.java index 7a2cc53..e359e1f 100644 --- a/batch-quartz/src/main/java/com/spring/infra/security/handler/SigninSuccessHandler.java +++ b/batch-quartz/src/main/java/com/spring/infra/security/handler/SigninSuccessHandler.java @@ -1,20 +1,22 @@ package com.spring.infra.security.handler; import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.util.Map; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; +import org.springframework.http.MediaType; import org.springframework.security.core.Authentication; -import org.springframework.security.web.DefaultRedirectStrategy; -import org.springframework.security.web.RedirectStrategy; import org.springframework.security.web.authentication.AuthenticationSuccessHandler; import org.springframework.security.web.savedrequest.HttpSessionRequestCache; import org.springframework.security.web.savedrequest.RequestCache; import org.springframework.security.web.savedrequest.SavedRequest; import org.springframework.stereotype.Component; +import com.fasterxml.jackson.databind.ObjectMapper; import com.spring.infra.security.jwt.JwtTokenService; import lombok.RequiredArgsConstructor; @@ -24,8 +26,8 @@ import lombok.RequiredArgsConstructor; public class SigninSuccessHandler implements AuthenticationSuccessHandler { private final JwtTokenService jwtTokenService; + private final ObjectMapper objectMapper; private RequestCache requestCache = new HttpSessionRequestCache(); - private RedirectStrategy redirectStrategy = new DefaultRedirectStrategy(); @Override public void onAuthenticationSuccess( @@ -35,14 +37,12 @@ public class SigninSuccessHandler implements AuthenticationSuccessHandler { ) throws IOException, ServletException { jwtTokenService.generateAccessToken(response, authentication); jwtTokenService.generateRefreshToken(response, authentication); - SavedRequest savedRequest = requestCache.getRequest(request, response); - if (savedRequest != null) { // 접근 권한 없는 경로 접근해서 스프링 시큐리티가 인터셉트해서 로그인폼으로 이동 후 로그인 성공한 경우 - redirectStrategy.sendRedirect(request, response, savedRequest.getRedirectUrl()); - } else { // 로그인 버튼 눌러서 로그인한 경우 기존에 있던 페이지로 리다이렉트 - String prevPage = String.valueOf(request.getSession().getAttribute("prevPage")); - redirectStrategy.sendRedirect(request, response, "null".equals(prevPage) ? "/main" : prevPage); - } + String targetUrl = (savedRequest != null) ? savedRequest.getRedirectUrl() : "/main"; + response.setContentType(MediaType.APPLICATION_JSON_VALUE); + response.setCharacterEncoding(StandardCharsets.UTF_8.name()); + Map responseBody = Map.of("status", true, "redirectUrl", targetUrl); + response.getWriter().write(objectMapper.writeValueAsString(responseBody)); } } diff --git a/batch-quartz/src/main/java/com/spring/web/controller/MainController.java b/batch-quartz/src/main/java/com/spring/web/controller/MainController.java index c34d576..54d1a67 100644 --- a/batch-quartz/src/main/java/com/spring/web/controller/MainController.java +++ b/batch-quartz/src/main/java/com/spring/web/controller/MainController.java @@ -1,6 +1,5 @@ package com.spring.web.controller; -import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; @@ -10,7 +9,6 @@ import org.springframework.web.bind.annotation.RequestMapping; public class MainController { @GetMapping - @PreAuthorize("hasRole('ROLE_ADMIN')") public String main() { return "pages/main/main"; } diff --git a/batch-quartz/src/main/java/com/spring/web/controller/SignController.java b/batch-quartz/src/main/java/com/spring/web/controller/SignController.java index 558e40b..83c78b7 100644 --- a/batch-quartz/src/main/java/com/spring/web/controller/SignController.java +++ b/batch-quartz/src/main/java/com/spring/web/controller/SignController.java @@ -1,15 +1,25 @@ package com.spring.web.controller; +import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Controller; +import org.springframework.ui.Model; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; @Controller @RequestMapping("/") public class SignController { + + @Value("${front.base.url}") + private String baseUrl; + + @Value("${front.base.timeout}") + private int timeout; @GetMapping - public String signIn() { + public String signIn(Model model) { + model.addAttribute("baseUrl", baseUrl); + model.addAttribute("timeout", timeout); return "pages/sign/sign-in"; } diff --git a/batch-quartz/src/main/resources/META-INF/additional-spring-configuration-metadata.json b/batch-quartz/src/main/resources/META-INF/additional-spring-configuration-metadata.json index e0e1d8a..3290422 100644 --- a/batch-quartz/src/main/resources/META-INF/additional-spring-configuration-metadata.json +++ b/batch-quartz/src/main/resources/META-INF/additional-spring-configuration-metadata.json @@ -153,5 +153,15 @@ "name": "spring.datasource.secondary.hikari.idle-timeout", "type": "java.lang.String", "description": "A description for 'spring.datasource.secondary.hikari.idle-timeout'" + }, + { + "name": "front.base.url", + "type": "java.lang.String", + "description": "A description for 'front.base.url'" + }, + { + "name": "front.base.timeout", + "type": "java.lang.String", + "description": "A description for 'front.base.timeout'" } ]} \ No newline at end of file diff --git a/batch-quartz/src/main/resources/application.yml b/batch-quartz/src/main/resources/application.yml index 8057837..6646193 100644 --- a/batch-quartz/src/main/resources/application.yml +++ b/batch-quartz/src/main/resources/application.yml @@ -95,4 +95,9 @@ jwt: expiration: 1 refresh-token: secret: bnhjdXMyLjAtcGxhdGZvcm0tcHJvamVjdC13aXRoLXNwcmluZy1ib290bnhjdF9zdHJvbmdfY29tcGxleF9zdHJvbmc= - expiration: 10080 \ No newline at end of file + expiration: 10080 + +front: + base: + url: http://localhost:8081 + timeout: 100000 \ No newline at end of file diff --git a/batch-quartz/src/main/resources/static/js/apis/schedule-api.js b/batch-quartz/src/main/resources/static/js/apis/schedule-api.js new file mode 100644 index 0000000..d842aac --- /dev/null +++ b/batch-quartz/src/main/resources/static/js/apis/schedule-api.js @@ -0,0 +1,31 @@ +import apiClient from '../common/axios-instance.js'; + +export const getAllJobs = async () => { + const response = await apiClient.get('/api/schedule'); + return response.data; +} + +export const getJobGroups = async () => { + const response = await apiClient.get('/api/schedule/groups'); + return response.data; +} + +export const getJobNamesByGroup = async (groupName) => { + const response = await apiClient.get(`/api/schedule/group/${groupName}/names`); + return response.data; +} + +export const pauseJob = async (groupName, jobName) => { + const response = await apiClient.post(`/api/schedule/pause/${groupName}/${jobName}`); + return response.data; +} + +export const resumeJob = async (groupName, jobName) => { + const response = await apiClient.post(`/api/schedule/resume/${groupName}/${jobName}`); + return response.data; +} + +export const triggerJob = async (groupName, jobName) => { + const response = await apiClient.post(`/api/schedule/trigger/${groupName}/${jobName}`); + return response.data; +}; \ No newline at end of file diff --git a/batch-quartz/src/main/resources/static/js/apis/user-api.js b/batch-quartz/src/main/resources/static/js/apis/sign-api.js similarity index 62% rename from batch-quartz/src/main/resources/static/js/apis/user-api.js rename to batch-quartz/src/main/resources/static/js/apis/sign-api.js index 9fe7a82..b85a11b 100644 --- a/batch-quartz/src/main/resources/static/js/apis/user-api.js +++ b/batch-quartz/src/main/resources/static/js/apis/sign-api.js @@ -1,11 +1,8 @@ import apiClient from '../common/axios-instance.js'; export const signIn = async (username, password) => { - try { - await apiClient.post('/sign-in', {username, password}); - } catch (error) { - console.error('Sign-in error:', error); - } + const response = await apiClient.post('/sign-in', {username, password}); + return response.data; }; export const signUp = async (loginId, password, userName) => { diff --git a/batch-quartz/src/main/resources/static/js/common/axios-instance.js b/batch-quartz/src/main/resources/static/js/common/axios-instance.js index c442081..48a1b73 100644 --- a/batch-quartz/src/main/resources/static/js/common/axios-instance.js +++ b/batch-quartz/src/main/resources/static/js/common/axios-instance.js @@ -1,7 +1,7 @@ // Axios apiClient 생성 const apiClient = axios.create({ - baseURL: 'http://localhost:8081', // 기본 URL 설정 - timeout: 100000000, // 요청 타임아웃 설정 (10초) + baseURL: BASE_URL, + timeout: TIME_OUT, headers: { 'Content-Type': 'application/json', } @@ -10,10 +10,6 @@ const apiClient = axios.create({ // 요청 인터셉터 추가 apiClient.interceptors.request.use( (config) => { - const token = getAccessToken(); - if (token) { - config.headers['Authorization'] = `Bearer ${token}`; - } return config; }, (error) => { @@ -27,61 +23,19 @@ apiClient.interceptors.response.use( return response; }, async (error) => { - const originalRequest = error.config; - - // 401 Unauthorized 에러 처리 - if (error.response && error.response.status === 401 && !originalRequest._retry) { - console.log("333333333333"); - originalRequest._retry = true; - - const refreshToken = getRefreshToken(); - console.log("refreshToken===="+refreshToken); - if (refreshToken) { - try { - const response = await axios.post(baseURL+'/auth/refresh', { - refreshToken: refreshToken, - }); - - const { accessToken, newRefreshToken } = response.data; - - // 새로 받은 토큰을 쿠키에 저장 - setAccessToken(accessToken); - if (newRefreshToken) { - setRefreshToken(newRefreshToken); - } - - // 갱신된 토큰으로 원래 요청 재시도 - originalRequest.headers['Authorization'] = `Bearer ${accessToken}`; - return apiClient(originalRequest); - } catch (refreshError) { - // refresh token이 만료된 경우 처리 (로그아웃 등) - console.error('Token refresh failed:', refreshError); - // 로그아웃 처리 또는 로그인 페이지로 리다이렉트 - } + if (error.response) { + const status = error.response.status; + const message = error.response.data.message; + if (message) { + alert(message); } + if (status == 401) { + location.href = "/"; + } + return new Promise(() => {}); } return Promise.reject(error); } ); -// 쿠키에서 accessToken 가져오기 -function getAccessToken() { - return Cookies.get('accessToken'); -} - -// 쿠키에서 refreshToken 가져오기 -function getRefreshToken() { - return Cookies.get('refreshToken'); -} - -// 쿠키에 accessToken 저장 -function setAccessToken(token) { - Cookies.set('accessToken', token, { secure: true, sameSite: 'strict' }); -} - -// 쿠키에 refreshToken 저장 -function setRefreshToken(token) { - Cookies.set('refreshToken', token, { secure: true, sameSite: 'strict' }); -} - export default apiClient; \ No newline at end of file diff --git a/batch-quartz/src/main/resources/static/js/pages/sign/sign-in.js b/batch-quartz/src/main/resources/static/js/pages/sign/sign-in.js index 3a94c7b..82fd13d 100644 --- a/batch-quartz/src/main/resources/static/js/pages/sign/sign-in.js +++ b/batch-quartz/src/main/resources/static/js/pages/sign/sign-in.js @@ -1,8 +1,13 @@ -import {signIn} from '../../apis/user-api.js'; +import {signIn} from '../../apis/sign-api.js'; document.getElementById('signinForm').addEventListener('submit', function(event) { event.preventDefault(); const username = document.getElementById('username').value; const password = document.getElementById('password').value; - signIn(username, password); + signIn(username, password) + .then(response => { + if (response.status) { + window.location.href = response.redirectUrl; + } + }); }); \ No newline at end of file diff --git a/batch-quartz/src/main/resources/static/js/pages/sign/sign-up.js b/batch-quartz/src/main/resources/static/js/pages/sign/sign-up.js index 5d2cfe7..6241cc7 100644 --- a/batch-quartz/src/main/resources/static/js/pages/sign/sign-up.js +++ b/batch-quartz/src/main/resources/static/js/pages/sign/sign-up.js @@ -1,4 +1,4 @@ -import {signUp} from '../../apis/user-api.js'; +import {signUp} from '../../apis/sign-api.js'; document.getElementById('signupForm').addEventListener('submit', function(event) { event.preventDefault(); diff --git a/batch-quartz/src/main/resources/templates/fragments/config.html b/batch-quartz/src/main/resources/templates/fragments/config.html index 2e4bad7..b0962ee 100644 --- a/batch-quartz/src/main/resources/templates/fragments/config.html +++ b/batch-quartz/src/main/resources/templates/fragments/config.html @@ -1,6 +1,10 @@ + \ No newline at end of file diff --git a/batch-quartz/src/main/resources/templates/pages/schedule/schedule-list.html b/batch-quartz/src/main/resources/templates/pages/schedule/schedule-list.html new file mode 100644 index 0000000..1c96bf6 --- /dev/null +++ b/batch-quartz/src/main/resources/templates/pages/schedule/schedule-list.html @@ -0,0 +1,38 @@ + + + + Schedule - List + + +
+

Manage Batch Jobs

+ + + + + + + + + + + + + + + + + + + +
Job NameGroupScheduleStatusActions
+ + +
+ +
+ + \ No newline at end of file diff --git a/batch-quartz/src/main/resources/templates/pages/sign/sign-up.html b/batch-quartz/src/main/resources/templates/pages/sign/sign-up.html index c3f10ab..4b62dc4 100644 --- a/batch-quartz/src/main/resources/templates/pages/sign/sign-up.html +++ b/batch-quartz/src/main/resources/templates/pages/sign/sign-up.html @@ -1,28 +1,28 @@ - - - - 회원가입 페이지 - - - - - - -
-

회원가입

-
-
- + + + 회원가입 페이지 + + +
+
+

회원가입

+ +
+ +
+
+ +
+
+ +
+ +
-
- -
-
- -
- - -
- + + + \ No newline at end of file