[api] 스케줄 관리 작업

This commit is contained in:
2026-05-28 10:30:10 +09:00
parent cbea6bc2af
commit b0646a0d3d
22 changed files with 1094 additions and 0 deletions
@@ -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<ApiResponse<AdminScheduleListVo>> 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<ApiResponse<AdminScheduleViewVo>> 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<ApiResponse<AdminScheduleAddVo>> 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<ApiResponse<AdminScheduleModifyVo>> 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<ApiResponse<AdminScheduleDeleteVo>> 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<ApiResponse<AdminScheduleLogListVo>> 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, "스케줄 작업 실행 로그");
}
}
@@ -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;
}
@@ -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;
}
@@ -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;
}
@@ -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;
}
@@ -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;
}
@@ -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;
}
}
@@ -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;
}
}
@@ -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;
}
}
@@ -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;
}
}
@@ -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;
}
}
@@ -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<AdminScheduleVo> selectAdminScheduleList(AdminScheduleListDto adminScheduleListDto);
AdminScheduleViewVo selectAdminScheduleView(Long scheduleJobIdx);
int selectAdminScheduleModifyDuplicateCount(AdminScheduleModifyDto adminScheduleModifyDto);
int updateAdminScheduleModify(AdminScheduleModifyDto adminScheduleModifyDto);
int updateAdminScheduleDelete(AdminScheduleDeleteDto adminScheduleDeleteDto);
int selectAdminScheduleLogCount(AdminScheduleLogListDto adminScheduleLogListDto);
List<AdminScheduleLogVo> selectAdminScheduleLogList(AdminScheduleLogListDto adminScheduleLogListDto);
}
@@ -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<AdminScheduleVo> 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<AdminScheduleLogVo> scheduleLogList = adminScheduleMapper.selectAdminScheduleLogList(adminScheduleLogListDto);
AdminScheduleLogListVo adminScheduleLogListVo = new AdminScheduleLogListVo();
adminScheduleLogListVo.setScheduleLogList(scheduleLogList);
adminScheduleLogListVo.setPaging(adminScheduleLogListDto, totalCount);
return adminScheduleLogListVo;
}
}
@@ -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;
}
@@ -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;
}
@@ -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<AdminScheduleVo> scheduleList;
}
@@ -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<AdminScheduleLogVo> scheduleLogList;
}
@@ -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;
}
@@ -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;
}
@@ -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;
}
@@ -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;
}
@@ -0,0 +1,196 @@
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.alist.api.modules.admin.schedule.mapper.AdminScheduleMapper">
<sql id="adminScheduleWhere">
FROM ALISTLMS.SCHEDULE_JOB ASJ
WHERE ASJ.DEL_YN = 'N'
<if test="keyword != null and keyword != ''">
AND (
ASJ.JOB_NAME LIKE CONCAT('%', #{keyword}, '%')
OR ASJ.JOB_DESCRIPTION LIKE CONCAT('%', #{keyword}, '%')
)
</if>
<if test="jobGroup != null and jobGroup != ''">
AND ASJ.JOB_GROUP = #{jobGroup}
</if>
<if test="lastStatus != null">
AND ASJ.LAST_STATUS = #{lastStatus}
</if>
<if test="useYn != null and useYn != ''">
AND ASJ.USE_YN = #{useYn}
</if>
</sql>
<select id="selectAdminScheduleCount" resultType="int">
/*AdminScheduleMapper.selectAdminScheduleCount*/
SELECT COUNT(*)
<include refid="adminScheduleWhere"/>
</select>
<select id="selectAdminScheduleList" resultType="com.alist.api.modules.admin.schedule.vo.AdminScheduleVo">
/*AdminScheduleMapper.selectAdminScheduleList*/
SELECT ASJ.SCHEDULE_JOB_IDX
, ASJ.JOB_NAME
, ASJ.JOB_GROUP
, ASJ.JOB_DESCRIPTION
, ASJ.CRON_EXPR
, ASJ.TIMEZONE_ID
, ASJ.MAX_RUNNING_SECONDS
, ASJ.LAST_RUN_DATE
, ASJ.LAST_END_DATE
, ASJ.LAST_SUCCESS_DATE
, ASJ.LAST_STATUS
, ASJ.LAST_MESSAGE
, ASJ.RUN_LOCK_DATE
, ASJ.RUN_LOCK_OWNER
, ASJ.USE_YN
, ASJ.CREATE_DATE
, ASJ.UPDATE_DATE
<include refid="adminScheduleWhere"/>
ORDER BY ASJ.SCHEDULE_JOB_IDX DESC
LIMIT #{limit}
OFFSET #{offset}
</select>
<select id="selectAdminScheduleView" resultType="com.alist.api.modules.admin.schedule.vo.AdminScheduleViewVo">
/*AdminScheduleMapper.selectAdminScheduleView*/
SELECT ASJ.SCHEDULE_JOB_IDX
, ASJ.JOB_NAME
, ASJ.JOB_GROUP
, ASJ.JOB_DESCRIPTION
, ASJ.CRON_EXPR
, ASJ.TIMEZONE_ID
, ASJ.MAX_RUNNING_SECONDS
, ASJ.LAST_RUN_DATE
, ASJ.LAST_END_DATE
, ASJ.LAST_SUCCESS_DATE
, ASJ.LAST_STATUS
, ASJ.LAST_MESSAGE
, ASJ.RUN_LOCK_DATE
, ASJ.RUN_LOCK_OWNER
, ASJ.USE_YN
, ASJ.DEL_YN
, ASJ.CREATE_DATE
, ASJ.CREATE_MEMBER
, ASJ.UPDATE_DATE
, ASJ.UPDATE_MEMBER
FROM ALISTLMS.SCHEDULE_JOB ASJ
WHERE ASJ.SCHEDULE_JOB_IDX = #{scheduleJobIdx}
AND ASJ.DEL_YN = 'N'
</select>
<select id="selectAdminScheduleDuplicateCount" resultType="int">
/*AdminScheduleMapper.selectAdminScheduleDuplicateCount*/
SELECT COUNT(*)
FROM ALISTLMS.SCHEDULE_JOB ASJ
WHERE ASJ.DEL_YN = 'N'
AND ASJ.JOB_NAME = #{jobName}
AND ASJ.JOB_GROUP = #{jobGroup}
</select>
<select id="selectAdminScheduleModifyDuplicateCount" resultType="int">
/*AdminScheduleMapper.selectAdminScheduleModifyDuplicateCount*/
SELECT COUNT(*)
FROM ALISTLMS.SCHEDULE_JOB ASJ
WHERE ASJ.DEL_YN = 'N'
AND ASJ.JOB_NAME = #{jobName}
AND ASJ.JOB_GROUP = #{jobGroup}
AND ASJ.SCHEDULE_JOB_IDX != #{scheduleJobIdx}
</select>
<sql id="adminScheduleLogWhere">
FROM ALISTLMS.SCHEDULE_JOB_LOG ASJL
WHERE ASJL.SCHEDULE_JOB_IDX = #{scheduleJobIdx}
<if test="status != null">
AND ASJL.STATUS = #{status}
</if>
<if test="startDateFrom != null and startDateFrom != ''">
AND ASJL.START_DATE <![CDATA[>=]]> #{startDateFrom}
</if>
<if test="startDateTo != null and startDateTo != ''">
AND ASJL.START_DATE <![CDATA[<=]]> #{startDateTo}
</if>
</sql>
<select id="selectAdminScheduleLogCount" resultType="int">
/*AdminScheduleMapper.selectAdminScheduleLogCount*/
SELECT COUNT(*)
<include refid="adminScheduleLogWhere"/>
</select>
<select id="selectAdminScheduleLogList" resultType="com.alist.api.modules.admin.schedule.vo.AdminScheduleLogVo">
/*AdminScheduleMapper.selectAdminScheduleLogList*/
SELECT ASJL.SCHEDULE_JOB_LOG_IDX
, ASJL.SCHEDULE_JOB_IDX
, ASJL.RUN_ID
, ASJL.JOB_NAME
, ASJL.START_DATE
, ASJL.END_DATE
, ASJL.TARGET_COUNT
, ASJL.SUCCESS_COUNT
, ASJL.FAIL_COUNT
, ASJL.MESSAGE
, ASJL.ERROR_MESSAGE
, ASJL.LOCK_OWNER
, ASJL.STATUS
, ASJL.CREATE_DATE
<include refid="adminScheduleLogWhere"/>
ORDER BY ASJL.SCHEDULE_JOB_LOG_IDX DESC
LIMIT #{limit}
OFFSET #{offset}
</select>
<insert id="insertAdminScheduleAdd" parameterType="com.alist.api.modules.admin.schedule.dto.AdminScheduleAddDto" useGeneratedKeys="true" keyProperty="scheduleJobIdx">
/*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}
)
</insert>
<update id="updateAdminScheduleModify" parameterType="com.alist.api.modules.admin.schedule.dto.AdminScheduleModifyDto">
/*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'
</update>
<update id="updateAdminScheduleDelete" parameterType="com.alist.api.modules.admin.schedule.dto.AdminScheduleDeleteDto">
/*AdminScheduleMapper.updateAdminScheduleDelete*/
UPDATE ALISTLMS.SCHEDULE_JOB
SET DEL_YN = 'Y'
, UPDATE_MEMBER = #{updateMember}
WHERE SCHEDULE_JOB_IDX = #{scheduleJobIdx}
AND DEL_YN = 'N'
</update>
</mapper>