package com.snp.batch.global.controller; import com.snp.batch.global.dto.JobExecutionDto; import com.snp.batch.global.dto.JobLaunchRequest; import com.snp.batch.global.dto.ScheduleRequest; import com.snp.batch.global.dto.ScheduleResponse; import com.snp.batch.service.BatchService; import com.snp.batch.service.ScheduleService; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Parameter; import io.swagger.v3.oas.annotations.enums.Explode; import io.swagger.v3.oas.annotations.enums.ParameterIn; import io.swagger.v3.oas.annotations.enums.ParameterStyle; import io.swagger.v3.oas.annotations.responses.ApiResponse; import io.swagger.v3.oas.annotations.responses.ApiResponses; import io.swagger.v3.oas.annotations.tags.Tag; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springdoc.core.annotations.ParameterObject; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; import java.util.HashMap; import java.util.List; import java.util.Map; @Slf4j @RestController @RequestMapping("/api/batch") @RequiredArgsConstructor @Tag(name = "Batch Management API", description = "배치 작업 실행 및 스케줄 관리 API") public class BatchController { private final BatchService batchService; private final ScheduleService scheduleService; @Operation(summary = "배치 작업 실행", description = "지정된 배치 작업을 즉시 실행합니다. 쿼리 파라미터로 Job Parameters 전달 가능") @ApiResponses(value = { @ApiResponse(responseCode = "200", description = "작업 실행 성공"), @ApiResponse(responseCode = "500", description = "작업 실행 실패") }) @PostMapping("/jobs/{jobName}/execute") public ResponseEntity> executeJob( @Parameter(description = "실행할 배치 작업 이름", required = true, example = "sampleProductImportJob") @PathVariable String jobName, @Parameter(description = "Job Parameters (동적 파라미터)", required = false, example = "?param1=value1¶m2=value2") @RequestParam(required = false) Map params) { log.info("Received request to execute job: {} with params: {}", jobName, params); try { Long executionId = batchService.executeJob(jobName, params); return ResponseEntity.ok(Map.of( "success", true, "message", "Job started successfully", "executionId", executionId )); } catch (Exception e) { log.error("Error executing job: {}", jobName, e); return ResponseEntity.internalServerError().body(Map.of( "success", false, "message", "Failed to start job: " + e.getMessage() )); } } @Operation(summary = "배치 작업 실행", description = "지정된 배치 작업을 즉시 실행합니다. 쿼리 파라미터로 Job Parameters 전달 가능") @ApiResponses(value = { @ApiResponse(responseCode = "200", description = "작업 실행 성공"), @ApiResponse(responseCode = "500", description = "작업 실행 실패") }) @PostMapping("/jobs/{jobName}/executeJobTest") public ResponseEntity> executeJobTest( @Parameter( description = "실행할 배치 작업 이름", required = true,example = "sampleProductImportJob") @PathVariable String jobName, @ParameterObject JobLaunchRequest request ) { Map params = new HashMap<>(); if (request.getStartDate() != null) params.put("startDate", request.getStartDate()); if (request.getStopDate() != null) params.put("stopDate", request.getStopDate()); log.info("Executing job: {} with params: {}", jobName, params); try { Long executionId = batchService.executeJob(jobName, params); return ResponseEntity.ok(Map.of( "success", true, "message", "Job started successfully", "executionId", executionId )); } catch (Exception e) { log.error("Error executing job: {}", jobName, e); return ResponseEntity.internalServerError().body(Map.of( "success", false, "message", "Failed to start job: " + e.getMessage() )); } } @Operation(summary = "배치 작업 목록 조회", description = "등록된 모든 배치 작업 목록을 조회합니다") @ApiResponses(value = { @ApiResponse(responseCode = "200", description = "조회 성공") }) @GetMapping("/jobs") public ResponseEntity> listJobs() { log.info("Received request to list all jobs"); List jobs = batchService.listAllJobs(); return ResponseEntity.ok(jobs); } @Operation(summary = "배치 작업 실행 이력 조회", description = "특정 배치 작업의 실행 이력을 조회합니다") @ApiResponses(value = { @ApiResponse(responseCode = "200", description = "조회 성공") }) @GetMapping("/jobs/{jobName}/executions") public ResponseEntity> getJobExecutions( @Parameter(description = "배치 작업 이름", required = true, example = "sampleProductImportJob") @PathVariable String jobName) { log.info("Received request to get executions for job: {}", jobName); List executions = batchService.getJobExecutions(jobName); return ResponseEntity.ok(executions); } @GetMapping("/executions/{executionId}") public ResponseEntity getExecutionDetails(@PathVariable Long executionId) { log.info("Received request to get execution details for: {}", executionId); try { JobExecutionDto execution = batchService.getExecutionDetails(executionId); return ResponseEntity.ok(execution); } catch (Exception e) { log.error("Error getting execution details: {}", executionId, e); return ResponseEntity.notFound().build(); } } @GetMapping("/executions/{executionId}/detail") public ResponseEntity getExecutionDetailWithSteps(@PathVariable Long executionId) { log.info("Received request to get detailed execution for: {}", executionId); try { com.snp.batch.global.dto.JobExecutionDetailDto detail = batchService.getExecutionDetailWithSteps(executionId); return ResponseEntity.ok(detail); } catch (Exception e) { log.error("Error getting detailed execution: {}", executionId, e); return ResponseEntity.notFound().build(); } } @PostMapping("/executions/{executionId}/stop") public ResponseEntity> stopExecution(@PathVariable Long executionId) { log.info("Received request to stop execution: {}", executionId); try { batchService.stopExecution(executionId); return ResponseEntity.ok(Map.of( "success", true, "message", "Execution stop requested" )); } catch (Exception e) { log.error("Error stopping execution: {}", executionId, e); return ResponseEntity.internalServerError().body(Map.of( "success", false, "message", "Failed to stop execution: " + e.getMessage() )); } } @Operation(summary = "스케줄 목록 조회", description = "등록된 모든 스케줄을 조회합니다") @ApiResponses(value = { @ApiResponse(responseCode = "200", description = "조회 성공") }) @GetMapping("/schedules") public ResponseEntity> getSchedules() { log.info("Received request to get all schedules"); List schedules = scheduleService.getAllSchedules(); return ResponseEntity.ok(Map.of( "schedules", schedules, "count", schedules.size() )); } @GetMapping("/schedules/{jobName}") public ResponseEntity getSchedule(@PathVariable String jobName) { log.debug("Received request to get schedule for job: {}", jobName); try { ScheduleResponse schedule = scheduleService.getScheduleByJobName(jobName); return ResponseEntity.ok(schedule); } catch (IllegalArgumentException e) { // 스케줄이 없는 경우 - 정상적인 시나리오 (UI에서 존재 여부 확인용) log.debug("Schedule not found for job: {} (정상 - 존재 확인)", jobName); return ResponseEntity.notFound().build(); } catch (Exception e) { log.error("Error getting schedule for job: {}", jobName, e); return ResponseEntity.notFound().build(); } } @Operation(summary = "스케줄 생성", description = "새로운 배치 작업 스케줄을 등록합니다") @ApiResponses(value = { @ApiResponse(responseCode = "200", description = "생성 성공"), @ApiResponse(responseCode = "500", description = "생성 실패") }) @PostMapping("/schedules") public ResponseEntity> createSchedule( @Parameter(description = "스케줄 생성 요청 데이터", required = true) @RequestBody ScheduleRequest request) { log.info("Received request to create schedule for job: {}", request.getJobName()); try { ScheduleResponse schedule = scheduleService.createSchedule(request); return ResponseEntity.ok(Map.of( "success", true, "message", "Schedule created successfully", "data", schedule )); } catch (Exception e) { log.error("Error creating schedule for job: {}", request.getJobName(), e); return ResponseEntity.internalServerError().body(Map.of( "success", false, "message", "Failed to create schedule: " + e.getMessage() )); } } @PostMapping("/schedules/{jobName}/update") public ResponseEntity> updateSchedule( @PathVariable String jobName, @RequestBody Map request) { log.info("Received request to update schedule for job: {}", jobName); try { String cronExpression = request.get("cronExpression"); String description = request.get("description"); ScheduleResponse schedule = scheduleService.updateSchedule(jobName, cronExpression, description); return ResponseEntity.ok(Map.of( "success", true, "message", "Schedule updated successfully", "data", schedule )); } catch (Exception e) { log.error("Error updating schedule for job: {}", jobName, e); return ResponseEntity.internalServerError().body(Map.of( "success", false, "message", "Failed to update schedule: " + e.getMessage() )); } } @Operation(summary = "스케줄 삭제", description = "배치 작업 스케줄을 삭제합니다") @ApiResponses(value = { @ApiResponse(responseCode = "200", description = "삭제 성공"), @ApiResponse(responseCode = "500", description = "삭제 실패") }) @PostMapping("/schedules/{jobName}/delete") public ResponseEntity> deleteSchedule( @Parameter(description = "배치 작업 이름", required = true) @PathVariable String jobName) { log.info("Received request to delete schedule for job: {}", jobName); try { scheduleService.deleteSchedule(jobName); return ResponseEntity.ok(Map.of( "success", true, "message", "Schedule deleted successfully" )); } catch (Exception e) { log.error("Error deleting schedule for job: {}", jobName, e); return ResponseEntity.internalServerError().body(Map.of( "success", false, "message", "Failed to delete schedule: " + e.getMessage() )); } } @PostMapping("/schedules/{jobName}/toggle") public ResponseEntity> toggleSchedule( @PathVariable String jobName, @RequestBody Map request) { log.info("Received request to toggle schedule for job: {}", jobName); try { Boolean active = request.get("active"); ScheduleResponse schedule = scheduleService.toggleScheduleActive(jobName, active); return ResponseEntity.ok(Map.of( "success", true, "message", "Schedule toggled successfully", "data", schedule )); } catch (Exception e) { log.error("Error toggling schedule for job: {}", jobName, e); return ResponseEntity.internalServerError().body(Map.of( "success", false, "message", "Failed to toggle schedule: " + e.getMessage() )); } } @GetMapping("/timeline") public ResponseEntity getTimeline( @RequestParam String view, @RequestParam String date) { log.info("Received request to get timeline: view={}, date={}", view, date); try { com.snp.batch.global.dto.TimelineResponse timeline = batchService.getTimeline(view, date); return ResponseEntity.ok(timeline); } catch (Exception e) { log.error("Error getting timeline", e); return ResponseEntity.internalServerError().build(); } } @GetMapping("/dashboard") public ResponseEntity getDashboard() { log.info("Received request to get dashboard data"); try { com.snp.batch.global.dto.DashboardResponse dashboard = batchService.getDashboardData(); return ResponseEntity.ok(dashboard); } catch (Exception e) { log.error("Error getting dashboard data", e); return ResponseEntity.internalServerError().build(); } } @GetMapping("/timeline/period-executions") public ResponseEntity> getPeriodExecutions( @RequestParam String jobName, @RequestParam String view, @RequestParam String periodKey) { log.info("Received request to get period executions: jobName={}, view={}, periodKey={}", jobName, view, periodKey); try { List executions = batchService.getPeriodExecutions(jobName, view, periodKey); return ResponseEntity.ok(executions); } catch (Exception e) { log.error("Error getting period executions", e); return ResponseEntity.internalServerError().build(); } } }