diff --git a/src/main/java/com/alist/api/modules/admin/schedule/AdminScheduleController.java b/src/main/java/com/alist/api/modules/admin/schedule/AdminScheduleController.java new file mode 100644 index 0000000..e29ddf7 --- /dev/null +++ b/src/main/java/com/alist/api/modules/admin/schedule/AdminScheduleController.java @@ -0,0 +1,143 @@ +package com.alist.api.modules.admin.schedule; + +import com.alist.api.common.response.ApiResponse; +import com.alist.api.common.response.ApiResponseCode; +import com.alist.api.modules.admin.schedule.form.*; +import com.alist.api.modules.admin.schedule.service.AdminScheduleService; +import com.alist.api.modules.admin.schedule.vo.*; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.validation.Valid; +import lombok.extern.slf4j.Slf4j; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; + +@Tag(name = "504. 스케줄 관리", description = "관리자 스케줄 작업 관리 API") +@Slf4j +@RestController +@RequestMapping("/admin/schedule") +public class AdminScheduleController { + private final AdminScheduleService adminScheduleService; + + public AdminScheduleController(AdminScheduleService adminScheduleService) { + this.adminScheduleService = adminScheduleService; + } + + @Operation( + summary = "스케줄 작업 목록 조회" + , description = "관리자가 스케줄 작업 목록을 검색 조건과 페이징으로 조회합니다." + ) + @GetMapping("/list") + public ResponseEntity> adminScheduleList( + @ModelAttribute AdminScheduleListForm adminScheduleListForm + ) { + AdminScheduleListVo adminScheduleListVo = adminScheduleService.selectAdminScheduleList(adminScheduleListForm.toDto()); + + if (adminScheduleListVo.getScheduleList() == null || adminScheduleListVo.getScheduleList().isEmpty()) { + return ApiResponse.entity(adminScheduleListVo, ApiResponseCode.CODE_2003); + } + + return ApiResponse.entity(adminScheduleListVo, ApiResponseCode.CODE_2001, "스케줄 작업"); + } + + @Operation( + summary = "스케줄 작업 상세 조회" + , description = "관리자가 스케줄 작업 PK 기준으로 상세 정보를 조회합니다." + ) + @GetMapping("/view") + public ResponseEntity> adminScheduleView( + @RequestParam Long scheduleJobIdx + ) { + AdminScheduleViewVo adminScheduleViewVo = adminScheduleService.selectAdminScheduleView(scheduleJobIdx); + + if (adminScheduleViewVo == null) { + return ApiResponse.entity((AdminScheduleViewVo) null, ApiResponseCode.CODE_2003); + } + + return ApiResponse.entity(adminScheduleViewVo, ApiResponseCode.CODE_2001, "스케줄 작업"); + } + + @Operation( + summary = "스케줄 작업 등록" + , description = "관리자가 스케줄 작업을 등록합니다. JOB_NAME과 JOB_GROUP이 중복되면 등록하지 않습니다." + ) + @PostMapping("/add") + public ResponseEntity> adminScheduleAdd( + @Valid @RequestBody AdminScheduleAddForm adminScheduleAddForm + ) { + AdminScheduleAddVo adminScheduleAddVo = adminScheduleService.insertAdminScheduleAdd(adminScheduleAddForm.toDto()); + + if (adminScheduleAddVo.getResultCode() == 401) { + return ApiResponse.entity(adminScheduleAddVo, ApiResponseCode.CODE_401); + } + + if (!adminScheduleAddVo.isProcessed()) { + return ApiResponse.entity(adminScheduleAddVo, ApiResponseCode.CODE_2004, "스케줄 작업"); + } + + return ApiResponse.entity(adminScheduleAddVo, ApiResponseCode.CODE_2002, "스케줄 작업"); + } + + @Operation( + summary = "스케줄 작업 수정" + , description = "관리자가 스케줄 작업 PK 기준으로 작업 코드, 크론식, 사용 여부 등 기본 정보를 수정합니다." + ) + @PutMapping("/modify") + public ResponseEntity> adminScheduleModify( + @Valid @RequestBody AdminScheduleModifyForm adminScheduleModifyForm + ) { + AdminScheduleModifyVo adminScheduleModifyVo = adminScheduleService.updateAdminScheduleModify(adminScheduleModifyForm.toDto()); + + if (adminScheduleModifyVo.getResultCode() == 401) { + return ApiResponse.entity(adminScheduleModifyVo, ApiResponseCode.CODE_401); + } + + if (adminScheduleModifyVo.getResultCode() == 2004) { + return ApiResponse.entity(adminScheduleModifyVo, ApiResponseCode.CODE_2004, "스케줄 작업"); + } + + if (!adminScheduleModifyVo.isProcessed()) { + return ApiResponse.entity(adminScheduleModifyVo, ApiResponseCode.CODE_2003); + } + + return ApiResponse.entity(adminScheduleModifyVo, ApiResponseCode.CODE_2005, "스케줄 작업 수정"); + } + + @Operation( + summary = "스케줄 작업 삭제" + , description = "관리자가 스케줄 작업 PK 기준으로 삭제 처리합니다. 실제 삭제가 아닌 DEL_YN 변경 방식입니다." + ) + @PatchMapping("/delete") + public ResponseEntity> adminScheduleDelete( + @Valid @RequestBody AdminScheduleDeleteForm adminScheduleDeleteForm + ) { + AdminScheduleDeleteVo adminScheduleDeleteVo = adminScheduleService.updateAdminScheduleDelete(adminScheduleDeleteForm.toDto()); + + if (adminScheduleDeleteVo.getResultCode() == 401) { + return ApiResponse.entity(adminScheduleDeleteVo, ApiResponseCode.CODE_401); + } + + if (!adminScheduleDeleteVo.isProcessed()) { + return ApiResponse.entity(adminScheduleDeleteVo, ApiResponseCode.CODE_2003); + } + + return ApiResponse.entity(adminScheduleDeleteVo, ApiResponseCode.CODE_2005, "스케줄 작업 삭제"); + } + + @Operation( + summary = "스케줄 작업 실행 로그 목록 조회" + , description = "관리자가 스케줄 작업 실행 로그를 검색 조건과 페이징으로 조회합니다." + ) + @GetMapping("/log/list") + public ResponseEntity> adminScheduleLogList( + @ModelAttribute AdminScheduleLogListForm adminScheduleLogListForm + ) { + AdminScheduleLogListVo adminScheduleLogListVo = adminScheduleService.selectAdminScheduleLogList(adminScheduleLogListForm.toDto()); + + if (adminScheduleLogListVo.getScheduleLogList() == null || adminScheduleLogListVo.getScheduleLogList().isEmpty()) { + return ApiResponse.entity(adminScheduleLogListVo, ApiResponseCode.CODE_2003); + } + + return ApiResponse.entity(adminScheduleLogListVo, ApiResponseCode.CODE_2001, "스케줄 작업 실행 로그"); + } +} diff --git a/src/main/java/com/alist/api/modules/admin/schedule/dto/AdminScheduleAddDto.java b/src/main/java/com/alist/api/modules/admin/schedule/dto/AdminScheduleAddDto.java new file mode 100644 index 0000000..2de263d --- /dev/null +++ b/src/main/java/com/alist/api/modules/admin/schedule/dto/AdminScheduleAddDto.java @@ -0,0 +1,19 @@ +package com.alist.api.modules.admin.schedule.dto; + +import lombok.Getter; +import lombok.Setter; + +@Getter +@Setter +public class AdminScheduleAddDto { + private Long scheduleJobIdx; + private String jobName; + private String jobGroup; + private String jobDescription; + private String cronExpr; + private String timezoneId; + private Integer maxRunningSeconds; + private String useYn; + private Integer createMember; + private Integer updateMember; +} diff --git a/src/main/java/com/alist/api/modules/admin/schedule/dto/AdminScheduleDeleteDto.java b/src/main/java/com/alist/api/modules/admin/schedule/dto/AdminScheduleDeleteDto.java new file mode 100644 index 0000000..b8deab8 --- /dev/null +++ b/src/main/java/com/alist/api/modules/admin/schedule/dto/AdminScheduleDeleteDto.java @@ -0,0 +1,11 @@ +package com.alist.api.modules.admin.schedule.dto; + +import lombok.Getter; +import lombok.Setter; + +@Getter +@Setter +public class AdminScheduleDeleteDto { + private Long scheduleJobIdx; + private Integer updateMember; +} diff --git a/src/main/java/com/alist/api/modules/admin/schedule/dto/AdminScheduleListDto.java b/src/main/java/com/alist/api/modules/admin/schedule/dto/AdminScheduleListDto.java new file mode 100644 index 0000000..5cbd2ab --- /dev/null +++ b/src/main/java/com/alist/api/modules/admin/schedule/dto/AdminScheduleListDto.java @@ -0,0 +1,14 @@ +package com.alist.api.modules.admin.schedule.dto; + +import com.alist.api.common.paging.PageRequest; +import lombok.Getter; +import lombok.Setter; + +@Getter +@Setter +public class AdminScheduleListDto extends PageRequest { + private String keyword; + private String jobGroup; + private Integer lastStatus; + private String useYn; +} diff --git a/src/main/java/com/alist/api/modules/admin/schedule/dto/AdminScheduleLogListDto.java b/src/main/java/com/alist/api/modules/admin/schedule/dto/AdminScheduleLogListDto.java new file mode 100644 index 0000000..54b939f --- /dev/null +++ b/src/main/java/com/alist/api/modules/admin/schedule/dto/AdminScheduleLogListDto.java @@ -0,0 +1,14 @@ +package com.alist.api.modules.admin.schedule.dto; + +import com.alist.api.common.paging.PageRequest; +import lombok.Getter; +import lombok.Setter; + +@Getter +@Setter +public class AdminScheduleLogListDto extends PageRequest { + private Long scheduleJobIdx; + private Integer status; + private String startDateFrom; + private String startDateTo; +} \ No newline at end of file diff --git a/src/main/java/com/alist/api/modules/admin/schedule/dto/AdminScheduleModifyDto.java b/src/main/java/com/alist/api/modules/admin/schedule/dto/AdminScheduleModifyDto.java new file mode 100644 index 0000000..e993434 --- /dev/null +++ b/src/main/java/com/alist/api/modules/admin/schedule/dto/AdminScheduleModifyDto.java @@ -0,0 +1,18 @@ +package com.alist.api.modules.admin.schedule.dto; + +import lombok.Getter; +import lombok.Setter; + +@Getter +@Setter +public class AdminScheduleModifyDto { + private Long scheduleJobIdx; + private String jobName; + private String jobGroup; + private String jobDescription; + private String cronExpr; + private String timezoneId; + private Integer maxRunningSeconds; + private String useYn; + private Integer updateMember; +} diff --git a/src/main/java/com/alist/api/modules/admin/schedule/form/AdminScheduleAddForm.java b/src/main/java/com/alist/api/modules/admin/schedule/form/AdminScheduleAddForm.java new file mode 100644 index 0000000..9c7d160 --- /dev/null +++ b/src/main/java/com/alist/api/modules/admin/schedule/form/AdminScheduleAddForm.java @@ -0,0 +1,57 @@ +package com.alist.api.modules.admin.schedule.form; + +import com.alist.api.modules.admin.schedule.dto.AdminScheduleAddDto; +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.Min; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.Pattern; +import jakarta.validation.constraints.Size; +import lombok.Getter; +import lombok.Setter; + +@Schema(description = "관리자 스케줄 작업 등록 요청") +@Getter +@Setter +public class AdminScheduleAddForm { + @NotBlank + @Size(max = 100) + @Schema(description = "작업 코드", example = "DAILY_LEARNING_SUMMARY", requiredMode = Schema.RequiredMode.REQUIRED) + private String jobName; + + @Size(max = 50) + @Schema(description = "작업 그룹. 미입력 시 DEFAULT", example = "DEFAULT") + private String jobGroup; + + @Size(max = 255) + @Schema(description = "작업 설명", example = "일별 학습 통계 집계") + private String jobDescription; + + @NotBlank + @Size(max = 100) + @Schema(description = "크론식", example = "0 0 2 * * *", requiredMode = Schema.RequiredMode.REQUIRED) + private String cronExpr; + + @Size(max = 50) + @Schema(description = "타임존. 미입력 시 Asia/Seoul", example = "Asia/Seoul") + private String timezoneId; + + @Min(1) + @Schema(description = "최대 실행 허용 시간(초). 미입력 시 1800", example = "1800") + private Integer maxRunningSeconds; + + @Pattern(regexp = "Y|N") + @Schema(description = "사용 여부. Y: 사용, N: 미사용. 미입력 시 Y", example = "Y") + private String useYn; + + public AdminScheduleAddDto toDto() { + AdminScheduleAddDto adminScheduleAddDto = new AdminScheduleAddDto(); + adminScheduleAddDto.setJobName(jobName.trim()); + adminScheduleAddDto.setJobGroup(jobGroup == null || jobGroup.trim().isEmpty() ? "DEFAULT" : jobGroup.trim()); + adminScheduleAddDto.setJobDescription(jobDescription == null ? null : jobDescription.trim()); + adminScheduleAddDto.setCronExpr(cronExpr.trim()); + adminScheduleAddDto.setTimezoneId(timezoneId == null || timezoneId.trim().isEmpty() ? "Asia/Seoul" : timezoneId.trim()); + adminScheduleAddDto.setMaxRunningSeconds(maxRunningSeconds == null ? 1800 : maxRunningSeconds); + adminScheduleAddDto.setUseYn(useYn == null || useYn.trim().isEmpty() ? "Y" : useYn.trim()); + return adminScheduleAddDto; + } +} diff --git a/src/main/java/com/alist/api/modules/admin/schedule/form/AdminScheduleDeleteForm.java b/src/main/java/com/alist/api/modules/admin/schedule/form/AdminScheduleDeleteForm.java new file mode 100644 index 0000000..8ab7d5b --- /dev/null +++ b/src/main/java/com/alist/api/modules/admin/schedule/form/AdminScheduleDeleteForm.java @@ -0,0 +1,22 @@ +package com.alist.api.modules.admin.schedule.form; + +import com.alist.api.modules.admin.schedule.dto.AdminScheduleDeleteDto; +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotNull; +import lombok.Getter; +import lombok.Setter; + +@Schema(description = "관리자 스케줄 작업 삭제 요청") +@Getter +@Setter +public class AdminScheduleDeleteForm { + @NotNull + @Schema(description = "스케줄 작업 PK", example = "9", requiredMode = Schema.RequiredMode.REQUIRED) + private Long scheduleJobIdx; + + public AdminScheduleDeleteDto toDto() { + AdminScheduleDeleteDto adminScheduleDeleteDto = new AdminScheduleDeleteDto(); + adminScheduleDeleteDto.setScheduleJobIdx(scheduleJobIdx); + return adminScheduleDeleteDto; + } +} diff --git a/src/main/java/com/alist/api/modules/admin/schedule/form/AdminScheduleListForm.java b/src/main/java/com/alist/api/modules/admin/schedule/form/AdminScheduleListForm.java new file mode 100644 index 0000000..ca6c752 --- /dev/null +++ b/src/main/java/com/alist/api/modules/admin/schedule/form/AdminScheduleListForm.java @@ -0,0 +1,35 @@ +package com.alist.api.modules.admin.schedule.form; + +import com.alist.api.common.paging.PageRequest; +import com.alist.api.modules.admin.schedule.dto.AdminScheduleListDto; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Getter; +import lombok.Setter; + +@Schema(description = "관리자 스케줄 작업 목록 검색 조건") +@Getter +@Setter +public class AdminScheduleListForm extends PageRequest { + @Schema(description = "검색어. 작업 코드, 작업 설명을 대상으로 검색합니다.", example = "DAILY") + private String keyword; + + @Schema(description = "작업 그룹", example = "DEFAULT") + private String jobGroup; + + @Schema(description = "마지막 실행 상태. 0: IDLE, 1: RUNNING, 2: SUCCESS, 3: SKIP, 4: FAIL", example = "2") + private Integer lastStatus; + + @Schema(description = "사용 여부. Y: 사용, N: 미사용", example = "Y") + private String useYn; + + public AdminScheduleListDto toDto() { + AdminScheduleListDto adminScheduleListDto = new AdminScheduleListDto(); + adminScheduleListDto.setPage(getPage()); + adminScheduleListDto.setSize(getSize()); + adminScheduleListDto.setKeyword(keyword == null ? null : keyword.trim()); + adminScheduleListDto.setJobGroup(jobGroup == null ? null : jobGroup.trim()); + adminScheduleListDto.setLastStatus(lastStatus); + adminScheduleListDto.setUseYn(useYn == null ? null : useYn.trim()); + return adminScheduleListDto; + } +} diff --git a/src/main/java/com/alist/api/modules/admin/schedule/form/AdminScheduleLogListForm.java b/src/main/java/com/alist/api/modules/admin/schedule/form/AdminScheduleLogListForm.java new file mode 100644 index 0000000..0d2546b --- /dev/null +++ b/src/main/java/com/alist/api/modules/admin/schedule/form/AdminScheduleLogListForm.java @@ -0,0 +1,36 @@ +package com.alist.api.modules.admin.schedule.form; + +import com.alist.api.common.paging.PageRequest; +import com.alist.api.modules.admin.schedule.dto.AdminScheduleLogListDto; +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotNull; +import lombok.Getter; +import lombok.Setter; + +@Schema(description = "관리자 스케줄 작업 실행 로그 목록 검색 조건") +@Getter +@Setter +public class AdminScheduleLogListForm extends PageRequest { + @Schema(description = "스케줄 작업 PK", example = "9", requiredMode = Schema.RequiredMode.REQUIRED) + private Long scheduleJobIdx; + + @Schema(description = "실행 상태. 1: RUNNING, 2: SUCCESS, 3: SKIP, 4: FAIL", example = "2") + private Integer status; + + @Schema(description = "실행 시작일 검색 시작값. yyyy-MM-dd 또는 yyyy-MM-dd HH:mm:ss", example = "2026-05-01") + private String startDateFrom; + + @Schema(description = "실행 시작일 검색 종료값. yyyy-MM-dd 또는 yyyy-MM-dd HH:mm:ss", example = "2026-05-28") + private String startDateTo; + + public AdminScheduleLogListDto toDto() { + AdminScheduleLogListDto adminScheduleLogListDto = new AdminScheduleLogListDto(); + adminScheduleLogListDto.setPage(getPage()); + adminScheduleLogListDto.setSize(getSize()); + adminScheduleLogListDto.setScheduleJobIdx(scheduleJobIdx); + adminScheduleLogListDto.setStatus(status); + adminScheduleLogListDto.setStartDateFrom(startDateFrom == null ? null : startDateFrom.trim()); + adminScheduleLogListDto.setStartDateTo(startDateTo == null ? null : startDateTo.trim()); + return adminScheduleLogListDto; + } +} diff --git a/src/main/java/com/alist/api/modules/admin/schedule/form/AdminScheduleModifyForm.java b/src/main/java/com/alist/api/modules/admin/schedule/form/AdminScheduleModifyForm.java new file mode 100644 index 0000000..60c294a --- /dev/null +++ b/src/main/java/com/alist/api/modules/admin/schedule/form/AdminScheduleModifyForm.java @@ -0,0 +1,59 @@ +package com.alist.api.modules.admin.schedule.form; + +import com.alist.api.modules.admin.schedule.dto.AdminScheduleModifyDto; +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.*; +import lombok.Getter; +import lombok.Setter; + +@Schema(description = "관리자 스케줄 작업 수정 요청") +@Getter +@Setter +public class AdminScheduleModifyForm { + @NotNull + @Schema(description = "스케줄 작업 PK", example = "9", requiredMode = Schema.RequiredMode.REQUIRED) + private Long scheduleJobIdx; + + @NotBlank + @Size(max = 100) + @Schema(description = "작업 코드", example = "DAILY_LEARNING_SUMMARY", requiredMode = Schema.RequiredMode.REQUIRED) + private String jobName; + + @Size(max = 50) + @Schema(description = "작업 그룹. 미입력 시 DEFAULT", example = "DEFAULT") + private String jobGroup; + + @Size(max = 255) + @Schema(description = "작업 설명", example = "일별 학습 통계 집계") + private String jobDescription; + + @NotBlank + @Size(max = 100) + @Schema(description = "크론식", example = "0 0 2 * * *", requiredMode = Schema.RequiredMode.REQUIRED) + private String cronExpr; + + @Size(max = 50) + @Schema(description = "타임존. 미입력 시 Asia/Seoul", example = "Asia/Seoul") + private String timezoneId; + + @Min(1) + @Schema(description = "최대 실행 허용 시간(초). 미입력 시 1800", example = "1800") + private Integer maxRunningSeconds; + + @Pattern(regexp = "Y|N") + @Schema(description = "사용 여부. Y: 사용, N: 미사용. 미입력 시 Y", example = "Y") + private String useYn; + + public AdminScheduleModifyDto toDto() { + AdminScheduleModifyDto adminScheduleModifyDto = new AdminScheduleModifyDto(); + adminScheduleModifyDto.setScheduleJobIdx(scheduleJobIdx); + adminScheduleModifyDto.setJobName(jobName.trim()); + adminScheduleModifyDto.setJobGroup(jobGroup == null || jobGroup.trim().isEmpty() ? "DEFAULT" : jobGroup.trim()); + adminScheduleModifyDto.setJobDescription(jobDescription == null ? null : jobDescription.trim()); + adminScheduleModifyDto.setCronExpr(cronExpr.trim()); + adminScheduleModifyDto.setTimezoneId(timezoneId == null || timezoneId.trim().isEmpty() ? "Asia/Seoul" : timezoneId.trim()); + adminScheduleModifyDto.setMaxRunningSeconds(maxRunningSeconds == null ? 1800 : maxRunningSeconds); + adminScheduleModifyDto.setUseYn(useYn == null || useYn.trim().isEmpty() ? "Y" : useYn.trim()); + return adminScheduleModifyDto; + } +} diff --git a/src/main/java/com/alist/api/modules/admin/schedule/mapper/AdminScheduleMapper.java b/src/main/java/com/alist/api/modules/admin/schedule/mapper/AdminScheduleMapper.java new file mode 100644 index 0000000..61d6fd3 --- /dev/null +++ b/src/main/java/com/alist/api/modules/admin/schedule/mapper/AdminScheduleMapper.java @@ -0,0 +1,32 @@ +package com.alist.api.modules.admin.schedule.mapper; + +import com.alist.api.modules.admin.schedule.dto.*; +import com.alist.api.modules.admin.schedule.vo.AdminScheduleLogVo; +import com.alist.api.modules.admin.schedule.vo.AdminScheduleViewVo; +import com.alist.api.modules.admin.schedule.vo.AdminScheduleVo; +import org.apache.ibatis.annotations.Mapper; + +import java.util.List; + +@Mapper +public interface AdminScheduleMapper { + int selectAdminScheduleDuplicateCount(AdminScheduleAddDto adminScheduleAddDto); + + int insertAdminScheduleAdd(AdminScheduleAddDto adminScheduleAddDto); + + int selectAdminScheduleCount(AdminScheduleListDto adminScheduleListDto); + + List selectAdminScheduleList(AdminScheduleListDto adminScheduleListDto); + + AdminScheduleViewVo selectAdminScheduleView(Long scheduleJobIdx); + + int selectAdminScheduleModifyDuplicateCount(AdminScheduleModifyDto adminScheduleModifyDto); + + int updateAdminScheduleModify(AdminScheduleModifyDto adminScheduleModifyDto); + + int updateAdminScheduleDelete(AdminScheduleDeleteDto adminScheduleDeleteDto); + + int selectAdminScheduleLogCount(AdminScheduleLogListDto adminScheduleLogListDto); + + List selectAdminScheduleLogList(AdminScheduleLogListDto adminScheduleLogListDto); +} diff --git a/src/main/java/com/alist/api/modules/admin/schedule/service/AdminScheduleService.java b/src/main/java/com/alist/api/modules/admin/schedule/service/AdminScheduleService.java new file mode 100644 index 0000000..3bc260b --- /dev/null +++ b/src/main/java/com/alist/api/modules/admin/schedule/service/AdminScheduleService.java @@ -0,0 +1,160 @@ +package com.alist.api.modules.admin.schedule.service; + +import com.alist.api.common.utils.SecurityUtil; +import com.alist.api.modules.admin.auth.mapper.AdminAuthMapper; +import com.alist.api.modules.admin.auth.vo.AdminAuthLoginVo; +import com.alist.api.modules.admin.schedule.dto.*; +import com.alist.api.modules.admin.schedule.mapper.AdminScheduleMapper; +import com.alist.api.modules.admin.schedule.vo.*; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.util.List; + +@Service +public class AdminScheduleService { + private final AdminScheduleMapper adminScheduleMapper; + private final AdminAuthMapper adminAuthMapper; + + public AdminScheduleService(AdminScheduleMapper adminScheduleMapper, AdminAuthMapper adminAuthMapper) { + this.adminScheduleMapper = adminScheduleMapper; + this.adminAuthMapper = adminAuthMapper; + } + + @Transactional(readOnly = true) + public AdminScheduleListVo selectAdminScheduleList(AdminScheduleListDto adminScheduleListDto) { + int totalCount = adminScheduleMapper.selectAdminScheduleCount(adminScheduleListDto); + List scheduleList = adminScheduleMapper.selectAdminScheduleList(adminScheduleListDto); + + AdminScheduleListVo adminScheduleListVo = new AdminScheduleListVo(); + adminScheduleListVo.setScheduleList(scheduleList); + adminScheduleListVo.setPaging(adminScheduleListDto, totalCount); + + return adminScheduleListVo; + } + + @Transactional(readOnly = true) + public AdminScheduleViewVo selectAdminScheduleView(Long scheduleJobIdx) { + return adminScheduleMapper.selectAdminScheduleView(scheduleJobIdx); + } + + @Transactional + public AdminScheduleAddVo insertAdminScheduleAdd(AdminScheduleAddDto adminScheduleAddDto) { + AdminScheduleAddVo adminScheduleAddVo = new AdminScheduleAddVo(); + + Integer loginUserTokenIdx = SecurityUtil.getLoginUserTokenIdx(); + + if (loginUserTokenIdx == null) { + adminScheduleAddVo.setResultCode(401); + adminScheduleAddVo.setProcessed(false); + return adminScheduleAddVo; + } + + AdminAuthLoginVo adminAuthLoginVo = adminAuthMapper.selectAdminTokenByUserTokenIdx(loginUserTokenIdx); + + if (adminAuthLoginVo == null || adminAuthLoginVo.getUserIdx() == null) { + adminScheduleAddVo.setResultCode(401); + adminScheduleAddVo.setProcessed(false); + return adminScheduleAddVo; + } + + adminScheduleAddDto.setCreateMember(adminAuthLoginVo.getUserIdx()); + adminScheduleAddDto.setUpdateMember(adminAuthLoginVo.getUserIdx()); + + int duplicateCount = adminScheduleMapper.selectAdminScheduleDuplicateCount(adminScheduleAddDto); + + if (duplicateCount > 0) { + adminScheduleAddVo.setResultCode(2004); + adminScheduleAddVo.setProcessed(false); + return adminScheduleAddVo; + } + + int insertCount = adminScheduleMapper.insertAdminScheduleAdd(adminScheduleAddDto); + + adminScheduleAddVo.setResultCode(2002); + adminScheduleAddVo.setProcessed(insertCount > 0); + adminScheduleAddVo.setScheduleJobIdx(adminScheduleAddDto.getScheduleJobIdx()); + + return adminScheduleAddVo; + } + + @Transactional + public AdminScheduleModifyVo updateAdminScheduleModify(AdminScheduleModifyDto adminScheduleModifyDto) { + AdminScheduleModifyVo adminScheduleModifyVo = new AdminScheduleModifyVo(); + + Integer loginUserTokenIdx = SecurityUtil.getLoginUserTokenIdx(); + + if (loginUserTokenIdx == null) { + adminScheduleModifyVo.setResultCode(401); + adminScheduleModifyVo.setProcessed(false); + return adminScheduleModifyVo; + } + + AdminAuthLoginVo adminAuthLoginVo = adminAuthMapper.selectAdminTokenByUserTokenIdx(loginUserTokenIdx); + + if (adminAuthLoginVo == null || adminAuthLoginVo.getUserIdx() == null) { + adminScheduleModifyVo.setResultCode(401); + adminScheduleModifyVo.setProcessed(false); + return adminScheduleModifyVo; + } + + adminScheduleModifyDto.setUpdateMember(adminAuthLoginVo.getUserIdx()); + + int duplicateCount = adminScheduleMapper.selectAdminScheduleModifyDuplicateCount(adminScheduleModifyDto); + + if (duplicateCount > 0) { + adminScheduleModifyVo.setResultCode(2004); + adminScheduleModifyVo.setProcessed(false); + return adminScheduleModifyVo; + } + + int updateCount = adminScheduleMapper.updateAdminScheduleModify(adminScheduleModifyDto); + + adminScheduleModifyVo.setResultCode(2005); + adminScheduleModifyVo.setProcessed(updateCount > 0); + + return adminScheduleModifyVo; + } + + @Transactional + public AdminScheduleDeleteVo updateAdminScheduleDelete(AdminScheduleDeleteDto adminScheduleDeleteDto) { + AdminScheduleDeleteVo adminScheduleDeleteVo = new AdminScheduleDeleteVo(); + + Integer loginUserTokenIdx = SecurityUtil.getLoginUserTokenIdx(); + + if (loginUserTokenIdx == null) { + adminScheduleDeleteVo.setResultCode(401); + adminScheduleDeleteVo.setProcessed(false); + return adminScheduleDeleteVo; + } + + AdminAuthLoginVo adminAuthLoginVo = adminAuthMapper.selectAdminTokenByUserTokenIdx(loginUserTokenIdx); + + if (adminAuthLoginVo == null || adminAuthLoginVo.getUserIdx() == null) { + adminScheduleDeleteVo.setResultCode(401); + adminScheduleDeleteVo.setProcessed(false); + return adminScheduleDeleteVo; + } + + adminScheduleDeleteDto.setUpdateMember(adminAuthLoginVo.getUserIdx()); + + int updateCount = adminScheduleMapper.updateAdminScheduleDelete(adminScheduleDeleteDto); + + adminScheduleDeleteVo.setResultCode(2005); + adminScheduleDeleteVo.setProcessed(updateCount > 0); + + return adminScheduleDeleteVo; + } + + @Transactional(readOnly = true) + public AdminScheduleLogListVo selectAdminScheduleLogList(AdminScheduleLogListDto adminScheduleLogListDto) { + int totalCount = adminScheduleMapper.selectAdminScheduleLogCount(adminScheduleLogListDto); + List scheduleLogList = adminScheduleMapper.selectAdminScheduleLogList(adminScheduleLogListDto); + + AdminScheduleLogListVo adminScheduleLogListVo = new AdminScheduleLogListVo(); + adminScheduleLogListVo.setScheduleLogList(scheduleLogList); + adminScheduleLogListVo.setPaging(adminScheduleLogListDto, totalCount); + + return adminScheduleLogListVo; + } +} diff --git a/src/main/java/com/alist/api/modules/admin/schedule/vo/AdminScheduleAddVo.java b/src/main/java/com/alist/api/modules/admin/schedule/vo/AdminScheduleAddVo.java new file mode 100644 index 0000000..67e3986 --- /dev/null +++ b/src/main/java/com/alist/api/modules/admin/schedule/vo/AdminScheduleAddVo.java @@ -0,0 +1,21 @@ +package com.alist.api.modules.admin.schedule.vo; + +import com.fasterxml.jackson.annotation.JsonIgnore; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Getter; +import lombok.Setter; + +@Schema(description = "관리자 스케줄 작업 등록 응답") +@Getter +@Setter +public class AdminScheduleAddVo { + @Schema(description = "스케줄 작업 등록 여부", example = "true") + private boolean processed; + + @Schema(description = "등록된 스케줄 작업 PK", example = "9") + private Long scheduleJobIdx; + + @JsonIgnore + @Schema(hidden = true) + private int resultCode; +} diff --git a/src/main/java/com/alist/api/modules/admin/schedule/vo/AdminScheduleDeleteVo.java b/src/main/java/com/alist/api/modules/admin/schedule/vo/AdminScheduleDeleteVo.java new file mode 100644 index 0000000..d913095 --- /dev/null +++ b/src/main/java/com/alist/api/modules/admin/schedule/vo/AdminScheduleDeleteVo.java @@ -0,0 +1,18 @@ +package com.alist.api.modules.admin.schedule.vo; + +import com.fasterxml.jackson.annotation.JsonIgnore; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Getter; +import lombok.Setter; + +@Schema(description = "관리자 스케줄 작업 삭제 응답") +@Getter +@Setter +public class AdminScheduleDeleteVo { + @Schema(description = "스케줄 작업 삭제 처리 여부", example = "true") + private boolean processed; + + @JsonIgnore + @Schema(hidden = true) + private int resultCode; +} diff --git a/src/main/java/com/alist/api/modules/admin/schedule/vo/AdminScheduleListVo.java b/src/main/java/com/alist/api/modules/admin/schedule/vo/AdminScheduleListVo.java new file mode 100644 index 0000000..54ee5f1 --- /dev/null +++ b/src/main/java/com/alist/api/modules/admin/schedule/vo/AdminScheduleListVo.java @@ -0,0 +1,16 @@ +package com.alist.api.modules.admin.schedule.vo; + +import com.alist.api.common.paging.PageResponse; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Getter; +import lombok.Setter; + +import java.util.List; + +@Schema(description = "관리자 스케줄 작업 목록 조회 응답") +@Getter +@Setter +public class AdminScheduleListVo extends PageResponse { + @Schema(description = "스케줄 작업 목록") + private List scheduleList; +} diff --git a/src/main/java/com/alist/api/modules/admin/schedule/vo/AdminScheduleLogListVo.java b/src/main/java/com/alist/api/modules/admin/schedule/vo/AdminScheduleLogListVo.java new file mode 100644 index 0000000..d71a92c --- /dev/null +++ b/src/main/java/com/alist/api/modules/admin/schedule/vo/AdminScheduleLogListVo.java @@ -0,0 +1,16 @@ +package com.alist.api.modules.admin.schedule.vo; + +import com.alist.api.common.paging.PageResponse; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Getter; +import lombok.Setter; + +import java.util.List; + +@Schema(description = "관리자 스케줄 작업 실행 로그 목록 조회 응답") +@Getter +@Setter +public class AdminScheduleLogListVo extends PageResponse { + @Schema(description = "스케줄 작업 실행 로그 목록") + private List scheduleLogList; +} diff --git a/src/main/java/com/alist/api/modules/admin/schedule/vo/AdminScheduleLogVo.java b/src/main/java/com/alist/api/modules/admin/schedule/vo/AdminScheduleLogVo.java new file mode 100644 index 0000000..019bbbe --- /dev/null +++ b/src/main/java/com/alist/api/modules/admin/schedule/vo/AdminScheduleLogVo.java @@ -0,0 +1,54 @@ +package com.alist.api.modules.admin.schedule.vo; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Getter; +import lombok.Setter; + +import java.time.LocalDateTime; + +@Schema(description = "관리자 스케줄 작업 실행 로그 목록 항목") +@Getter +@Setter +public class AdminScheduleLogVo { + @Schema(description = "스케줄 작업 로그 PK", example = "20185") + private Long scheduleJobLogIdx; + + @Schema(description = "스케줄 작업 PK", example = "9") + private Long scheduleJobIdx; + + @Schema(description = "1회 실행 식별자", example = "20260528093000-abc123") + private String runId; + + @Schema(description = "작업 코드", example = "DAILY_LEARNING_SUMMARY") + private String jobName; + + @Schema(description = "실행 시작 시각", example = "2026-05-28 09:30:00") + private LocalDateTime startDate; + + @Schema(description = "실행 종료 시각", example = "2026-05-28 09:30:12") + private LocalDateTime endDate; + + @Schema(description = "대상 건수", example = "100") + private Integer targetCount; + + @Schema(description = "성공 건수", example = "98") + private Integer successCount; + + @Schema(description = "실패 건수", example = "2") + private Integer failCount; + + @Schema(description = "요약 메시지", example = "일별 학습 통계 집계 완료") + private String message; + + @Schema(description = "오류 메시지", example = "timeout") + private String errorMessage; + + @Schema(description = "실행 서버", example = "api-01") + private String lockOwner; + + @Schema(description = "실행 상태. 1: RUNNING, 2: SUCCESS, 3: SKIP, 4: FAIL", example = "2") + private Integer status; + + @Schema(description = "기록 시각", example = "2026-05-28 09:30:00") + private LocalDateTime createDate; +} diff --git a/src/main/java/com/alist/api/modules/admin/schedule/vo/AdminScheduleModifyVo.java b/src/main/java/com/alist/api/modules/admin/schedule/vo/AdminScheduleModifyVo.java new file mode 100644 index 0000000..0743a96 --- /dev/null +++ b/src/main/java/com/alist/api/modules/admin/schedule/vo/AdminScheduleModifyVo.java @@ -0,0 +1,18 @@ +package com.alist.api.modules.admin.schedule.vo; + +import com.fasterxml.jackson.annotation.JsonIgnore; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Getter; +import lombok.Setter; + +@Schema(description = "관리자 스케줄 작업 수정 응답") +@Getter +@Setter +public class AdminScheduleModifyVo { + @Schema(description = "스케줄 작업 수정 여부", example = "true") + private boolean processed; + + @JsonIgnore + @Schema(hidden = true) + private int resultCode; +} diff --git a/src/main/java/com/alist/api/modules/admin/schedule/vo/AdminScheduleViewVo.java b/src/main/java/com/alist/api/modules/admin/schedule/vo/AdminScheduleViewVo.java new file mode 100644 index 0000000..dd39dac --- /dev/null +++ b/src/main/java/com/alist/api/modules/admin/schedule/vo/AdminScheduleViewVo.java @@ -0,0 +1,72 @@ +package com.alist.api.modules.admin.schedule.vo; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Getter; +import lombok.Setter; + +import java.time.LocalDateTime; + +@Schema(description = "관리자 스케줄 작업 상세 조회 응답") +@Getter +@Setter +public class AdminScheduleViewVo { + @Schema(description = "스케줄 작업 PK", example = "9") + private Long scheduleJobIdx; + + @Schema(description = "작업 코드", example = "DAILY_LEARNING_SUMMARY") + private String jobName; + + @Schema(description = "작업 그룹", example = "DEFAULT") + private String jobGroup; + + @Schema(description = "작업 설명", example = "일별 학습 통계 집계") + private String jobDescription; + + @Schema(description = "크론식", example = "0 0 2 * * *") + private String cronExpr; + + @Schema(description = "타임존", example = "Asia/Seoul") + private String timezoneId; + + @Schema(description = "최대 실행 허용 시간(초)", example = "1800") + private Integer maxRunningSeconds; + + @Schema(description = "마지막 실행 시작 시각", example = "2026-05-27 02:00:00") + private LocalDateTime lastRunDate; + + @Schema(description = "마지막 실행 종료 시각", example = "2026-05-27 02:00:10") + private LocalDateTime lastEndDate; + + @Schema(description = "마지막 성공 시각", example = "2026-05-27 02:00:10") + private LocalDateTime lastSuccessDate; + + @Schema(description = "마지막 실행 상태. 0: IDLE, 1: RUNNING, 2: SUCCESS, 3: SKIP, 4: FAIL", example = "2") + private Integer lastStatus; + + @Schema(description = "마지막 실행 메시지", example = "정상 처리") + private String lastMessage; + + @Schema(description = "실행 락 시각", example = "2026-05-27 02:00:00") + private LocalDateTime runLockDate; + + @Schema(description = "실행 락 소유자", example = "api-01") + private String runLockOwner; + + @Schema(description = "사용 여부. Y: 사용, N: 미사용", example = "Y") + private String useYn; + + @Schema(description = "삭제 여부. Y: 삭제, N: 정상", example = "N") + private String delYn; + + @Schema(description = "최초 생성일", example = "2026-05-27 10:00:00") + private LocalDateTime createDate; + + @Schema(description = "최초 생성자 회원 PK", example = "1") + private Integer createMember; + + @Schema(description = "마지막 수정일", example = "2026-05-27 10:00:00") + private LocalDateTime updateDate; + + @Schema(description = "마지막 수정자 회원 PK", example = "1") + private Integer updateMember; +} diff --git a/src/main/java/com/alist/api/modules/admin/schedule/vo/AdminScheduleVo.java b/src/main/java/com/alist/api/modules/admin/schedule/vo/AdminScheduleVo.java new file mode 100644 index 0000000..908b001 --- /dev/null +++ b/src/main/java/com/alist/api/modules/admin/schedule/vo/AdminScheduleVo.java @@ -0,0 +1,63 @@ +package com.alist.api.modules.admin.schedule.vo; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Getter; +import lombok.Setter; + +import java.time.LocalDateTime; + +@Schema(description = "관리자 스케줄 작업 목록 항목") +@Getter +@Setter +public class AdminScheduleVo { + @Schema(description = "스케줄 작업 PK", example = "9") + private Long scheduleJobIdx; + + @Schema(description = "작업 코드", example = "DAILY_LEARNING_SUMMARY") + private String jobName; + + @Schema(description = "작업 그룹", example = "DEFAULT") + private String jobGroup; + + @Schema(description = "작업 설명", example = "일별 학습 통계 집계") + private String jobDescription; + + @Schema(description = "크론식", example = "0 0 2 * * *") + private String cronExpr; + + @Schema(description = "타임존", example = "Asia/Seoul") + private String timezoneId; + + @Schema(description = "최대 실행 허용 시간(초)", example = "1800") + private Integer maxRunningSeconds; + + @Schema(description = "마지막 실행 시작 시각", example = "2026-05-27 02:00:00") + private LocalDateTime lastRunDate; + + @Schema(description = "마지막 실행 종료 시각", example = "2026-05-27 02:00:10") + private LocalDateTime lastEndDate; + + @Schema(description = "마지막 성공 시각", example = "2026-05-27 02:00:10") + private LocalDateTime lastSuccessDate; + + @Schema(description = "마지막 실행 상태. 0: IDLE, 1: RUNNING, 2: SUCCESS, 3: SKIP, 4: FAIL", example = "2") + private Integer lastStatus; + + @Schema(description = "마지막 실행 메시지", example = "정상 처리") + private String lastMessage; + + @Schema(description = "실행 락 시각", example = "2026-05-27 02:00:00") + private LocalDateTime runLockDate; + + @Schema(description = "실행 락 소유자", example = "api-01") + private String runLockOwner; + + @Schema(description = "사용 여부", example = "Y") + private String useYn; + + @Schema(description = "생성일", example = "2026-05-27 10:00:00") + private LocalDateTime createDate; + + @Schema(description = "수정일", example = "2026-05-27 10:00:00") + private LocalDateTime updateDate; +} diff --git a/src/main/resources/mapper/admin/schedule/AdminScheduleMapper.xml b/src/main/resources/mapper/admin/schedule/AdminScheduleMapper.xml new file mode 100644 index 0000000..0e52ac5 --- /dev/null +++ b/src/main/resources/mapper/admin/schedule/AdminScheduleMapper.xml @@ -0,0 +1,196 @@ + + + + + + FROM ALISTLMS.SCHEDULE_JOB ASJ + WHERE ASJ.DEL_YN = 'N' + + AND ( + ASJ.JOB_NAME LIKE CONCAT('%', #{keyword}, '%') + OR ASJ.JOB_DESCRIPTION LIKE CONCAT('%', #{keyword}, '%') + ) + + + AND ASJ.JOB_GROUP = #{jobGroup} + + + AND ASJ.LAST_STATUS = #{lastStatus} + + + AND ASJ.USE_YN = #{useYn} + + + + + + + + + + + + + + + FROM ALISTLMS.SCHEDULE_JOB_LOG ASJL + WHERE ASJL.SCHEDULE_JOB_IDX = #{scheduleJobIdx} + + AND ASJL.STATUS = #{status} + + + AND ASJL.START_DATE =]]> #{startDateFrom} + + + AND ASJL.START_DATE #{startDateTo} + + + + + + + + + /*AdminScheduleMapper.insertAdminScheduleAdd*/ + INSERT INTO ALISTLMS.SCHEDULE_JOB ( + JOB_NAME + , JOB_GROUP + , JOB_DESCRIPTION + , CRON_EXPR + , TIMEZONE_ID + , MAX_RUNNING_SECONDS + , LAST_STATUS + , USE_YN + , DEL_YN + , CREATE_MEMBER + , UPDATE_MEMBER + ) VALUES ( + #{jobName} + , #{jobGroup} + , #{jobDescription} + , #{cronExpr} + , #{timezoneId} + , #{maxRunningSeconds} + , 0 + , #{useYn} + , 'N' + , #{createMember} + , #{updateMember} + ) + + + + /*AdminScheduleMapper.updateAdminScheduleModify*/ + UPDATE ALISTLMS.SCHEDULE_JOB + SET JOB_NAME = #{jobName} + , JOB_GROUP = #{jobGroup} + , JOB_DESCRIPTION = #{jobDescription} + , CRON_EXPR = #{cronExpr} + , TIMEZONE_ID = #{timezoneId} + , MAX_RUNNING_SECONDS = #{maxRunningSeconds} + , USE_YN = #{useYn} + , UPDATE_MEMBER = #{updateMember} + WHERE SCHEDULE_JOB_IDX = #{scheduleJobIdx} + AND DEL_YN = 'N' + + + + /*AdminScheduleMapper.updateAdminScheduleDelete*/ + UPDATE ALISTLMS.SCHEDULE_JOB + SET DEL_YN = 'Y' + , UPDATE_MEMBER = #{updateMember} + WHERE SCHEDULE_JOB_IDX = #{scheduleJobIdx} + AND DEL_YN = 'N' + + \ No newline at end of file