[api] admin/notice/~

공지사항 api 추가
This commit is contained in:
2026-06-25 17:42:41 +09:00
parent f6df6bc7cb
commit 9a1a1a02c9
22 changed files with 1226 additions and 0 deletions
@@ -0,0 +1,139 @@
package com.alist.api.modules.admin.notice;
import com.alist.api.common.response.ApiResponse;
import com.alist.api.common.response.ApiResponseCode;
import com.alist.api.modules.admin.notice.form.AdminNoticeAddForm;
import com.alist.api.modules.admin.notice.form.AdminNoticeDeleteForm;
import com.alist.api.modules.admin.notice.form.AdminNoticeListForm;
import com.alist.api.modules.admin.notice.form.AdminNoticeModifyForm;
import com.alist.api.modules.admin.notice.service.AdminNoticeService;
import com.alist.api.modules.admin.notice.vo.AdminNoticeAddVo;
import com.alist.api.modules.admin.notice.vo.AdminNoticeDeleteVo;
import com.alist.api.modules.admin.notice.vo.AdminNoticeListVo;
import com.alist.api.modules.admin.notice.vo.AdminNoticeModifyVo;
import com.alist.api.modules.admin.notice.vo.AdminNoticeViewVo;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.validation.Valid;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PatchMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
@Tag(name = "505. 공지사항 관리", description = "관리자 공지사항 관리 API")
@RestController
@RequestMapping("/admin/notice")
public class AdminNoticeController {
private final AdminNoticeService adminNoticeService;
public AdminNoticeController(AdminNoticeService adminNoticeService) {
this.adminNoticeService = adminNoticeService;
}
@Operation(
summary = "공지사항 목록 조회"
, description = "관리자가 공지사항 목록을 검색 조건과 페이징으로 조회합니다."
)
@GetMapping("/list")
public ResponseEntity<ApiResponse<AdminNoticeListVo>> adminNoticeList(
@ModelAttribute AdminNoticeListForm adminNoticeListForm
) {
AdminNoticeListVo adminNoticeListVo = adminNoticeService.selectAdminNoticeList(adminNoticeListForm.toDto());
if (adminNoticeListVo.getNoticeList() == null || adminNoticeListVo.getNoticeList().isEmpty()) {
return ApiResponse.entity(adminNoticeListVo, ApiResponseCode.CODE_2003);
}
return ApiResponse.entity(adminNoticeListVo, ApiResponseCode.CODE_2001, "공지사항");
}
@Operation(
summary = "공지사항 상세 조회"
, description = "관리자가 공지사항 PK 기준으로 상세 정보를 조회합니다."
)
@GetMapping("/view")
public ResponseEntity<ApiResponse<AdminNoticeViewVo>> adminNoticeView(
@RequestParam Long noticeIdx
) {
AdminNoticeViewVo adminNoticeViewVo = adminNoticeService.selectAdminNoticeView(noticeIdx);
if (adminNoticeViewVo == null) {
return ApiResponse.entity((AdminNoticeViewVo) null, ApiResponseCode.CODE_2003);
}
return ApiResponse.entity(adminNoticeViewVo, ApiResponseCode.CODE_2001, "공지사항");
}
@Operation(
summary = "공지사항 등록"
, description = "관리자가 공지사항을 등록합니다. 캠퍼스 대상 공지는 campusIdxList를 함께 저장합니다."
)
@PostMapping("/add")
public ResponseEntity<ApiResponse<AdminNoticeAddVo>> adminNoticeAdd(
@Valid @RequestBody AdminNoticeAddForm adminNoticeAddForm
) {
AdminNoticeAddVo adminNoticeAddVo = adminNoticeService.insertAdminNoticeAdd(adminNoticeAddForm.toDto());
if (adminNoticeAddVo.getResultCode() == 401) {
return ApiResponse.entity(adminNoticeAddVo, ApiResponseCode.CODE_401);
}
if (adminNoticeAddVo.getResultCode() == 4001) {
return ApiResponse.entity(adminNoticeAddVo, ApiResponseCode.CODE_4001);
}
return ApiResponse.entity(adminNoticeAddVo, ApiResponseCode.CODE_2002, "공지사항");
}
@Operation(
summary = "공지사항 수정"
, description = "관리자가 공지사항 PK 기준으로 제목, 내용, 노출 범위, 첨부파일 정보를 수정합니다."
)
@PutMapping("/modify")
public ResponseEntity<ApiResponse<AdminNoticeModifyVo>> adminNoticeModify(
@Valid @RequestBody AdminNoticeModifyForm adminNoticeModifyForm
) {
AdminNoticeModifyVo adminNoticeModifyVo = adminNoticeService.updateAdminNoticeModify(adminNoticeModifyForm.toDto());
if (adminNoticeModifyVo.getResultCode() == 401) {
return ApiResponse.entity(adminNoticeModifyVo, ApiResponseCode.CODE_401);
}
if (adminNoticeModifyVo.getResultCode() == 4001) {
return ApiResponse.entity(adminNoticeModifyVo, ApiResponseCode.CODE_4001);
}
if (!adminNoticeModifyVo.isProcessed()) {
return ApiResponse.entity(adminNoticeModifyVo, ApiResponseCode.CODE_2003);
}
return ApiResponse.entity(adminNoticeModifyVo, ApiResponseCode.CODE_2005, "공지사항 수정");
}
@Operation(
summary = "공지사항 삭제"
, description = "관리자가 공지사항 PK 기준으로 삭제 처리합니다. 실제 삭제가 아닌 DEL_YN 변경 방식입니다."
)
@PatchMapping("/delete")
public ResponseEntity<ApiResponse<AdminNoticeDeleteVo>> adminNoticeDelete(
@Valid @RequestBody AdminNoticeDeleteForm adminNoticeDeleteForm
) {
AdminNoticeDeleteVo adminNoticeDeleteVo = adminNoticeService.updateAdminNoticeDelete(adminNoticeDeleteForm.toDto());
if (adminNoticeDeleteVo.getResultCode() == 401) {
return ApiResponse.entity(adminNoticeDeleteVo, ApiResponseCode.CODE_401);
}
if (!adminNoticeDeleteVo.isProcessed()) {
return ApiResponse.entity(adminNoticeDeleteVo, ApiResponseCode.CODE_2003);
}
return ApiResponse.entity(adminNoticeDeleteVo, ApiResponseCode.CODE_2005, "공지사항 삭제");
}
}
@@ -0,0 +1,24 @@
package com.alist.api.modules.admin.notice.dto;
import lombok.Getter;
import lombok.Setter;
import java.util.List;
@Getter
@Setter
public class AdminNoticeAddDto {
private Long noticeIdx;
private String noticeType;
private String targetScope;
private List<Long> campusIdxList;
private String title;
private String content;
private String pinYn;
private String startDate;
private String endDate;
private String useYn;
private List<AdminNoticeFileDto> fileList;
private Integer createMember;
private Integer updateMember;
}
@@ -0,0 +1,12 @@
package com.alist.api.modules.admin.notice.dto;
import lombok.Getter;
import lombok.Setter;
@Getter
@Setter
public class AdminNoticeCampusDto {
private Long noticeIdx;
private Long campusIdx;
private Integer createMember;
}
@@ -0,0 +1,11 @@
package com.alist.api.modules.admin.notice.dto;
import lombok.Getter;
import lombok.Setter;
@Getter
@Setter
public class AdminNoticeDeleteDto {
private Long noticeIdx;
private Integer updateMember;
}
@@ -0,0 +1,18 @@
package com.alist.api.modules.admin.notice.dto;
import lombok.Getter;
import lombok.Setter;
@Getter
@Setter
public class AdminNoticeFileDto {
private Long noticeIdx;
private String fileOriginalName;
private String fileSaveName;
private String filePath;
private Long fileSize;
private String fileExt;
private Integer sortOrder;
private Integer createMember;
private Integer updateMember;
}
@@ -0,0 +1,16 @@
package com.alist.api.modules.admin.notice.dto;
import com.alist.api.common.paging.PageRequest;
import lombok.Getter;
import lombok.Setter;
@Getter
@Setter
public class AdminNoticeListDto extends PageRequest {
private String keyword;
private String noticeType;
private String targetScope;
private Long campusIdx;
private String pinYn;
private String useYn;
}
@@ -0,0 +1,24 @@
package com.alist.api.modules.admin.notice.dto;
import lombok.Getter;
import lombok.Setter;
import java.util.List;
@Getter
@Setter
public class AdminNoticeModifyDto {
private Long noticeIdx;
private String noticeType;
private String targetScope;
private List<Long> campusIdxList;
private String title;
private String content;
private String pinYn;
private String startDate;
private String endDate;
private String useYn;
private List<AdminNoticeFileDto> fileList;
private List<Long> deleteFileIdxList;
private Integer updateMember;
}
@@ -0,0 +1,72 @@
package com.alist.api.modules.admin.notice.form;
import com.alist.api.modules.admin.notice.dto.AdminNoticeAddDto;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.Valid;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.Pattern;
import jakarta.validation.constraints.Size;
import lombok.Getter;
import lombok.Setter;
import java.util.List;
@Schema(description = "관리자 공지사항 등록 요청")
@Getter
@Setter
public class AdminNoticeAddForm {
@NotBlank
@Size(max = 30)
@Schema(description = "공지 유형. GENERAL 일반, SYSTEM 시스템, EVENT 이벤트", example = "GENERAL", requiredMode = Schema.RequiredMode.REQUIRED)
private String noticeType;
@NotBlank
@Size(max = 30)
@Schema(description = "노출 범위. ALL 전체, ADMIN 관리자, TEACHER 교사, STUDENT 학생, CAMPUS 캠퍼스", example = "CAMPUS", requiredMode = Schema.RequiredMode.REQUIRED)
private String targetScope;
@Schema(description = "캠퍼스 IDX 목록. targetScope가 CAMPUS일 때 사용", example = "[1, 2, 3]")
private List<Long> campusIdxList;
@NotBlank
@Size(max = 200)
@Schema(description = "제목", example = "서비스 점검 안내", requiredMode = Schema.RequiredMode.REQUIRED)
private String title;
@NotBlank
@Schema(description = "내용", example = "서비스 점검이 예정되어 있습니다.", requiredMode = Schema.RequiredMode.REQUIRED)
private String content;
@Pattern(regexp = "Y|N")
@Schema(description = "상단 고정 여부. Y: 고정, N: 미고정. 미입력 시 N", example = "N")
private String pinYn;
@Schema(description = "노출 시작일", example = "2026-07-01 00:00:00")
private String startDate;
@Schema(description = "노출 종료일", example = "2026-07-31 23:59:59")
private String endDate;
@Pattern(regexp = "Y|N")
@Schema(description = "사용 여부. Y: 사용, N: 미사용. 미입력 시 Y", example = "Y")
private String useYn;
@Valid
@Schema(description = "첨부파일 목록")
private List<AdminNoticeFileForm> fileList;
public AdminNoticeAddDto toDto() {
AdminNoticeAddDto adminNoticeAddDto = new AdminNoticeAddDto();
adminNoticeAddDto.setNoticeType(noticeType.trim());
adminNoticeAddDto.setTargetScope(targetScope.trim());
adminNoticeAddDto.setCampusIdxList(campusIdxList);
adminNoticeAddDto.setTitle(title.trim());
adminNoticeAddDto.setContent(content.trim());
adminNoticeAddDto.setPinYn(pinYn == null || pinYn.trim().isEmpty() ? "N" : pinYn.trim());
adminNoticeAddDto.setStartDate(startDate == null || startDate.trim().isEmpty() ? null : startDate.trim());
adminNoticeAddDto.setEndDate(endDate == null || endDate.trim().isEmpty() ? null : endDate.trim());
adminNoticeAddDto.setUseYn(useYn == null || useYn.trim().isEmpty() ? "Y" : useYn.trim());
adminNoticeAddDto.setFileList(fileList == null ? null : fileList.stream().map(AdminNoticeFileForm::toDto).toList());
return adminNoticeAddDto;
}
}
@@ -0,0 +1,22 @@
package com.alist.api.modules.admin.notice.form;
import com.alist.api.modules.admin.notice.dto.AdminNoticeDeleteDto;
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 AdminNoticeDeleteForm {
@NotNull
@Schema(description = "공지사항 PK", example = "1", requiredMode = Schema.RequiredMode.REQUIRED)
private Long noticeIdx;
public AdminNoticeDeleteDto toDto() {
AdminNoticeDeleteDto adminNoticeDeleteDto = new AdminNoticeDeleteDto();
adminNoticeDeleteDto.setNoticeIdx(noticeIdx);
return adminNoticeDeleteDto;
}
}
@@ -0,0 +1,52 @@
package com.alist.api.modules.admin.notice.form;
import com.alist.api.modules.admin.notice.dto.AdminNoticeFileDto;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.Min;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.Size;
import lombok.Getter;
import lombok.Setter;
@Schema(description = "관리자 공지사항 첨부파일 요청")
@Getter
@Setter
public class AdminNoticeFileForm {
@NotBlank
@Size(max = 255)
@Schema(description = "원본 파일명", example = "notice.pdf", requiredMode = Schema.RequiredMode.REQUIRED)
private String fileOriginalName;
@NotBlank
@Size(max = 255)
@Schema(description = "저장 파일명", example = "20260701120000_notice.pdf", requiredMode = Schema.RequiredMode.REQUIRED)
private String fileSaveName;
@NotBlank
@Size(max = 500)
@Schema(description = "파일 경로", example = "/uploads/notice/2026/07/01/20260701120000_notice.pdf", requiredMode = Schema.RequiredMode.REQUIRED)
private String filePath;
@Min(0)
@Schema(description = "파일 크기", example = "2048")
private Long fileSize;
@Size(max = 20)
@Schema(description = "파일 확장자", example = "pdf")
private String fileExt;
@Min(0)
@Schema(description = "정렬 순서. 미입력 시 0", example = "0")
private Integer sortOrder;
public AdminNoticeFileDto toDto() {
AdminNoticeFileDto adminNoticeFileDto = new AdminNoticeFileDto();
adminNoticeFileDto.setFileOriginalName(fileOriginalName.trim());
adminNoticeFileDto.setFileSaveName(fileSaveName.trim());
adminNoticeFileDto.setFilePath(filePath.trim());
adminNoticeFileDto.setFileSize(fileSize);
adminNoticeFileDto.setFileExt(fileExt == null ? null : fileExt.trim());
adminNoticeFileDto.setSortOrder(sortOrder == null ? 0 : sortOrder);
return adminNoticeFileDto;
}
}
@@ -0,0 +1,43 @@
package com.alist.api.modules.admin.notice.form;
import com.alist.api.common.paging.PageRequest;
import com.alist.api.modules.admin.notice.dto.AdminNoticeListDto;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Getter;
import lombok.Setter;
@Schema(description = "관리자 공지사항 목록 검색 조건")
@Getter
@Setter
public class AdminNoticeListForm extends PageRequest {
@Schema(description = "검색어. 제목, 내용을 대상으로 검색합니다.", example = "점검")
private String keyword;
@Schema(description = "공지 유형. GENERAL 일반, SYSTEM 시스템, EVENT 이벤트", example = "GENERAL")
private String noticeType;
@Schema(description = "노출 범위. ALL 전체, ADMIN 관리자, TEACHER 교사, STUDENT 학생, CAMPUS 캠퍼스", example = "CAMPUS")
private String targetScope;
@Schema(description = "캠퍼스 IDX. 특정 캠퍼스 대상 공지를 검색할 때 사용", example = "1")
private Long campusIdx;
@Schema(description = "상단 고정 여부. Y: 고정, N: 미고정", example = "Y")
private String pinYn;
@Schema(description = "사용 여부. Y: 사용, N: 미사용", example = "Y")
private String useYn;
public AdminNoticeListDto toDto() {
AdminNoticeListDto adminNoticeListDto = new AdminNoticeListDto();
adminNoticeListDto.setPage(getPage());
adminNoticeListDto.setSize(getSize());
adminNoticeListDto.setKeyword(keyword == null ? null : keyword.trim());
adminNoticeListDto.setNoticeType(noticeType == null ? null : noticeType.trim());
adminNoticeListDto.setTargetScope(targetScope == null ? null : targetScope.trim());
adminNoticeListDto.setCampusIdx(campusIdx);
adminNoticeListDto.setPinYn(pinYn == null ? null : pinYn.trim());
adminNoticeListDto.setUseYn(useYn == null ? null : useYn.trim());
return adminNoticeListDto;
}
}
@@ -0,0 +1,82 @@
package com.alist.api.modules.admin.notice.form;
import com.alist.api.modules.admin.notice.dto.AdminNoticeModifyDto;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.Valid;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Pattern;
import jakarta.validation.constraints.Size;
import lombok.Getter;
import lombok.Setter;
import java.util.List;
@Schema(description = "관리자 공지사항 수정 요청")
@Getter
@Setter
public class AdminNoticeModifyForm {
@NotNull
@Schema(description = "공지사항 PK", example = "1", requiredMode = Schema.RequiredMode.REQUIRED)
private Long noticeIdx;
@NotBlank
@Size(max = 30)
@Schema(description = "공지 유형. GENERAL 일반, SYSTEM 시스템, EVENT 이벤트", example = "GENERAL", requiredMode = Schema.RequiredMode.REQUIRED)
private String noticeType;
@NotBlank
@Size(max = 30)
@Schema(description = "노출 범위. ALL 전체, ADMIN 관리자, TEACHER 교사, STUDENT 학생, CAMPUS 캠퍼스", example = "CAMPUS", requiredMode = Schema.RequiredMode.REQUIRED)
private String targetScope;
@Schema(description = "캠퍼스 IDX 목록. targetScope가 CAMPUS일 때 사용", example = "[1, 2, 3]")
private List<Long> campusIdxList;
@NotBlank
@Size(max = 200)
@Schema(description = "제목", example = "서비스 점검 안내", requiredMode = Schema.RequiredMode.REQUIRED)
private String title;
@NotBlank
@Schema(description = "내용", example = "서비스 점검이 예정되어 있습니다.", requiredMode = Schema.RequiredMode.REQUIRED)
private String content;
@Pattern(regexp = "Y|N")
@Schema(description = "상단 고정 여부. Y: 고정, N: 미고정. 미입력 시 N", example = "N")
private String pinYn;
@Schema(description = "노출 시작일", example = "2026-07-01 00:00:00")
private String startDate;
@Schema(description = "노출 종료일", example = "2026-07-31 23:59:59")
private String endDate;
@Pattern(regexp = "Y|N")
@Schema(description = "사용 여부. Y: 사용, N: 미사용. 미입력 시 Y", example = "Y")
private String useYn;
@Valid
@Schema(description = "신규 첨부파일 목록. 기존 첨부파일은 유지되고 전달된 파일만 추가됩니다.")
private List<AdminNoticeFileForm> fileList;
@Schema(description = "삭제할 기존 첨부파일 PK 목록", example = "[1, 2]")
private List<Long> deleteFileIdxList;
public AdminNoticeModifyDto toDto() {
AdminNoticeModifyDto adminNoticeModifyDto = new AdminNoticeModifyDto();
adminNoticeModifyDto.setNoticeIdx(noticeIdx);
adminNoticeModifyDto.setNoticeType(noticeType.trim());
adminNoticeModifyDto.setTargetScope(targetScope.trim());
adminNoticeModifyDto.setCampusIdxList(campusIdxList);
adminNoticeModifyDto.setTitle(title.trim());
adminNoticeModifyDto.setContent(content.trim());
adminNoticeModifyDto.setPinYn(pinYn == null || pinYn.trim().isEmpty() ? "N" : pinYn.trim());
adminNoticeModifyDto.setStartDate(startDate == null || startDate.trim().isEmpty() ? null : startDate.trim());
adminNoticeModifyDto.setEndDate(endDate == null || endDate.trim().isEmpty() ? null : endDate.trim());
adminNoticeModifyDto.setUseYn(useYn == null || useYn.trim().isEmpty() ? "Y" : useYn.trim());
adminNoticeModifyDto.setFileList(fileList == null ? null : fileList.stream().map(AdminNoticeFileForm::toDto).toList());
adminNoticeModifyDto.setDeleteFileIdxList(deleteFileIdxList);
return adminNoticeModifyDto;
}
}
@@ -0,0 +1,48 @@
package com.alist.api.modules.admin.notice.mapper;
import com.alist.api.modules.admin.notice.dto.AdminNoticeAddDto;
import com.alist.api.modules.admin.notice.dto.AdminNoticeCampusDto;
import com.alist.api.modules.admin.notice.dto.AdminNoticeDeleteDto;
import com.alist.api.modules.admin.notice.dto.AdminNoticeFileDto;
import com.alist.api.modules.admin.notice.dto.AdminNoticeListDto;
import com.alist.api.modules.admin.notice.dto.AdminNoticeModifyDto;
import com.alist.api.modules.admin.notice.vo.AdminNoticeFileVo;
import com.alist.api.modules.admin.notice.vo.AdminNoticeViewVo;
import com.alist.api.modules.admin.notice.vo.AdminNoticeVo;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import java.util.List;
@Mapper
public interface AdminNoticeMapper {
int selectAdminNoticeCount(AdminNoticeListDto adminNoticeListDto);
List<AdminNoticeVo> selectAdminNoticeList(AdminNoticeListDto adminNoticeListDto);
AdminNoticeViewVo selectAdminNoticeView(Long noticeIdx);
List<Long> selectAdminNoticeCampusIdxList(Long noticeIdx);
List<AdminNoticeFileVo> selectAdminNoticeFileList(Long noticeIdx);
int insertAdminNoticeAdd(AdminNoticeAddDto adminNoticeAddDto);
int insertAdminNoticeCampusAdd(AdminNoticeCampusDto adminNoticeCampusDto);
int insertAdminNoticeFileAdd(AdminNoticeFileDto adminNoticeFileDto);
int updateAdminNoticeModify(AdminNoticeModifyDto adminNoticeModifyDto);
int updateAdminNoticeDelete(AdminNoticeDeleteDto adminNoticeDeleteDto);
int deleteAdminNoticeCampusByNoticeIdx(Long noticeIdx);
int updateAdminNoticeFileDeleteByNoticeIdx(@Param("noticeIdx") Long noticeIdx, @Param("updateMember") Integer updateMember);
int updateAdminNoticeFileDeleteByNoticeFileIdxList(
@Param("noticeIdx") Long noticeIdx
, @Param("noticeFileIdxList") List<Long> noticeFileIdxList
, @Param("updateMember") Integer updateMember
);
}
@@ -0,0 +1,213 @@
package com.alist.api.modules.admin.notice.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.notice.dto.AdminNoticeAddDto;
import com.alist.api.modules.admin.notice.dto.AdminNoticeCampusDto;
import com.alist.api.modules.admin.notice.dto.AdminNoticeDeleteDto;
import com.alist.api.modules.admin.notice.dto.AdminNoticeFileDto;
import com.alist.api.modules.admin.notice.dto.AdminNoticeListDto;
import com.alist.api.modules.admin.notice.dto.AdminNoticeModifyDto;
import com.alist.api.modules.admin.notice.mapper.AdminNoticeMapper;
import com.alist.api.modules.admin.notice.vo.AdminNoticeAddVo;
import com.alist.api.modules.admin.notice.vo.AdminNoticeDeleteVo;
import com.alist.api.modules.admin.notice.vo.AdminNoticeFileVo;
import com.alist.api.modules.admin.notice.vo.AdminNoticeListVo;
import com.alist.api.modules.admin.notice.vo.AdminNoticeModifyVo;
import com.alist.api.modules.admin.notice.vo.AdminNoticeViewVo;
import com.alist.api.modules.admin.notice.vo.AdminNoticeVo;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.List;
@Service
public class AdminNoticeService {
private final AdminNoticeMapper adminNoticeMapper;
private final AdminAuthMapper adminAuthMapper;
public AdminNoticeService(AdminNoticeMapper adminNoticeMapper, AdminAuthMapper adminAuthMapper) {
this.adminNoticeMapper = adminNoticeMapper;
this.adminAuthMapper = adminAuthMapper;
}
@Transactional(readOnly = true)
public AdminNoticeListVo selectAdminNoticeList(AdminNoticeListDto adminNoticeListDto) {
int totalCount = adminNoticeMapper.selectAdminNoticeCount(adminNoticeListDto);
List<AdminNoticeVo> noticeList = adminNoticeMapper.selectAdminNoticeList(adminNoticeListDto);
AdminNoticeListVo adminNoticeListVo = new AdminNoticeListVo();
adminNoticeListVo.setNoticeList(noticeList);
adminNoticeListVo.setPaging(adminNoticeListDto, totalCount);
return adminNoticeListVo;
}
@Transactional(readOnly = true)
public AdminNoticeViewVo selectAdminNoticeView(Long noticeIdx) {
AdminNoticeViewVo adminNoticeViewVo = adminNoticeMapper.selectAdminNoticeView(noticeIdx);
if (adminNoticeViewVo == null) {
return null;
}
List<Long> campusIdxList = adminNoticeMapper.selectAdminNoticeCampusIdxList(noticeIdx);
List<AdminNoticeFileVo> fileList = adminNoticeMapper.selectAdminNoticeFileList(noticeIdx);
adminNoticeViewVo.setCampusIdxList(campusIdxList);
adminNoticeViewVo.setFileList(fileList);
return adminNoticeViewVo;
}
@Transactional
public AdminNoticeAddVo insertAdminNoticeAdd(AdminNoticeAddDto adminNoticeAddDto) {
AdminNoticeAddVo adminNoticeAddVo = new AdminNoticeAddVo();
Integer adminUserIdx = getLoginAdminUserIdx();
if (adminUserIdx == null) {
adminNoticeAddVo.setResultCode(401);
adminNoticeAddVo.setProcessed(false);
return adminNoticeAddVo;
}
if (!isValidCampusScope(adminNoticeAddDto.getTargetScope(), adminNoticeAddDto.getCampusIdxList())) {
adminNoticeAddVo.setResultCode(4001);
adminNoticeAddVo.setProcessed(false);
return adminNoticeAddVo;
}
adminNoticeAddDto.setCreateMember(adminUserIdx);
adminNoticeAddDto.setUpdateMember(adminUserIdx);
int insertCount = adminNoticeMapper.insertAdminNoticeAdd(adminNoticeAddDto);
insertCampusList(adminNoticeAddDto.getNoticeIdx(), adminNoticeAddDto.getTargetScope(), adminNoticeAddDto.getCampusIdxList(), adminUserIdx);
insertFileList(adminNoticeAddDto.getNoticeIdx(), adminNoticeAddDto.getFileList(), adminUserIdx);
adminNoticeAddVo.setResultCode(2002);
adminNoticeAddVo.setProcessed(insertCount > 0);
adminNoticeAddVo.setNoticeIdx(adminNoticeAddDto.getNoticeIdx());
return adminNoticeAddVo;
}
@Transactional
public AdminNoticeModifyVo updateAdminNoticeModify(AdminNoticeModifyDto adminNoticeModifyDto) {
AdminNoticeModifyVo adminNoticeModifyVo = new AdminNoticeModifyVo();
Integer adminUserIdx = getLoginAdminUserIdx();
if (adminUserIdx == null) {
adminNoticeModifyVo.setResultCode(401);
adminNoticeModifyVo.setProcessed(false);
return adminNoticeModifyVo;
}
if (!isValidCampusScope(adminNoticeModifyDto.getTargetScope(), adminNoticeModifyDto.getCampusIdxList())) {
adminNoticeModifyVo.setResultCode(4001);
adminNoticeModifyVo.setProcessed(false);
return adminNoticeModifyVo;
}
adminNoticeModifyDto.setUpdateMember(adminUserIdx);
int updateCount = adminNoticeMapper.updateAdminNoticeModify(adminNoticeModifyDto);
if (updateCount > 0) {
adminNoticeMapper.deleteAdminNoticeCampusByNoticeIdx(adminNoticeModifyDto.getNoticeIdx());
deleteFileList(adminNoticeModifyDto.getNoticeIdx(), adminNoticeModifyDto.getDeleteFileIdxList(), adminUserIdx);
insertCampusList(adminNoticeModifyDto.getNoticeIdx(), adminNoticeModifyDto.getTargetScope(), adminNoticeModifyDto.getCampusIdxList(), adminUserIdx);
insertFileList(adminNoticeModifyDto.getNoticeIdx(), adminNoticeModifyDto.getFileList(), adminUserIdx);
}
adminNoticeModifyVo.setResultCode(2005);
adminNoticeModifyVo.setProcessed(updateCount > 0);
return adminNoticeModifyVo;
}
@Transactional
public AdminNoticeDeleteVo updateAdminNoticeDelete(AdminNoticeDeleteDto adminNoticeDeleteDto) {
AdminNoticeDeleteVo adminNoticeDeleteVo = new AdminNoticeDeleteVo();
Integer adminUserIdx = getLoginAdminUserIdx();
if (adminUserIdx == null) {
adminNoticeDeleteVo.setResultCode(401);
adminNoticeDeleteVo.setProcessed(false);
return adminNoticeDeleteVo;
}
adminNoticeDeleteDto.setUpdateMember(adminUserIdx);
int updateCount = adminNoticeMapper.updateAdminNoticeDelete(adminNoticeDeleteDto);
if (updateCount > 0) {
adminNoticeMapper.updateAdminNoticeFileDeleteByNoticeIdx(adminNoticeDeleteDto.getNoticeIdx(), adminUserIdx);
}
adminNoticeDeleteVo.setResultCode(2005);
adminNoticeDeleteVo.setProcessed(updateCount > 0);
return adminNoticeDeleteVo;
}
private Integer getLoginAdminUserIdx() {
Integer loginUserTokenIdx = SecurityUtil.getLoginUserTokenIdx();
if (loginUserTokenIdx == null) {
return null;
}
AdminAuthLoginVo adminAuthLoginVo = adminAuthMapper.selectAdminTokenByUserTokenIdx(loginUserTokenIdx);
if (adminAuthLoginVo == null || adminAuthLoginVo.getUserIdx() == null) {
return null;
}
return adminAuthLoginVo.getUserIdx();
}
private boolean isValidCampusScope(String targetScope, List<Long> campusIdxList) {
if (!"CAMPUS".equals(targetScope)) {
return true;
}
return campusIdxList != null && !campusIdxList.isEmpty();
}
private void insertCampusList(Long noticeIdx, String targetScope, List<Long> campusIdxList, Integer adminUserIdx) {
if (!"CAMPUS".equals(targetScope) || campusIdxList == null) {
return;
}
for (Long campusIdx : campusIdxList) {
AdminNoticeCampusDto adminNoticeCampusDto = new AdminNoticeCampusDto();
adminNoticeCampusDto.setNoticeIdx(noticeIdx);
adminNoticeCampusDto.setCampusIdx(campusIdx);
adminNoticeCampusDto.setCreateMember(adminUserIdx);
adminNoticeMapper.insertAdminNoticeCampusAdd(adminNoticeCampusDto);
}
}
private void insertFileList(Long noticeIdx, List<AdminNoticeFileDto> fileList, Integer adminUserIdx) {
if (fileList == null || fileList.isEmpty()) {
return;
}
for (AdminNoticeFileDto adminNoticeFileDto : fileList) {
adminNoticeFileDto.setNoticeIdx(noticeIdx);
adminNoticeFileDto.setCreateMember(adminUserIdx);
adminNoticeFileDto.setUpdateMember(adminUserIdx);
adminNoticeMapper.insertAdminNoticeFileAdd(adminNoticeFileDto);
}
}
private void deleteFileList(Long noticeIdx, List<Long> deleteFileIdxList, Integer adminUserIdx) {
if (deleteFileIdxList == null || deleteFileIdxList.isEmpty()) {
return;
}
adminNoticeMapper.updateAdminNoticeFileDeleteByNoticeFileIdxList(noticeIdx, deleteFileIdxList, adminUserIdx);
}
}
@@ -0,0 +1,21 @@
package com.alist.api.modules.admin.notice.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 AdminNoticeAddVo {
@Schema(description = "공지사항 등록 여부", example = "true")
private boolean processed;
@Schema(description = "등록된 공지사항 PK", example = "1")
private Long noticeIdx;
@JsonIgnore
@Schema(hidden = true)
private int resultCode;
}
@@ -0,0 +1,18 @@
package com.alist.api.modules.admin.notice.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 AdminNoticeDeleteVo {
@Schema(description = "공지사항 삭제 처리 여부", example = "true")
private boolean processed;
@JsonIgnore
@Schema(hidden = true)
private int resultCode;
}
@@ -0,0 +1,39 @@
package com.alist.api.modules.admin.notice.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 AdminNoticeFileVo {
@Schema(description = "공지사항 첨부파일 PK", example = "1")
private Long noticeFileIdx;
@Schema(description = "공지사항 PK", example = "1")
private Long noticeIdx;
@Schema(description = "원본 파일명", example = "notice.pdf")
private String fileOriginalName;
@Schema(description = "저장 파일명", example = "20260701120000_notice.pdf")
private String fileSaveName;
@Schema(description = "파일 경로", example = "/uploads/notice/2026/07/01/20260701120000_notice.pdf")
private String filePath;
@Schema(description = "파일 크기", example = "2048")
private Long fileSize;
@Schema(description = "파일 확장자", example = "pdf")
private String fileExt;
@Schema(description = "정렬 순서", example = "0")
private Integer sortOrder;
@Schema(description = "생성일", example = "2026-07-01 10:00:00")
private LocalDateTime createDate;
}
@@ -0,0 +1,16 @@
package com.alist.api.modules.admin.notice.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 AdminNoticeListVo extends PageResponse {
@Schema(description = "공지사항 목록")
private List<AdminNoticeVo> noticeList;
}
@@ -0,0 +1,18 @@
package com.alist.api.modules.admin.notice.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 AdminNoticeModifyVo {
@Schema(description = "공지사항 수정 여부", example = "true")
private boolean processed;
@JsonIgnore
@Schema(hidden = true)
private int resultCode;
}
@@ -0,0 +1,64 @@
package com.alist.api.modules.admin.notice.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Getter;
import lombok.Setter;
import java.time.LocalDateTime;
import java.util.List;
@Schema(description = "관리자 공지사항 상세 조회 응답")
@Getter
@Setter
public class AdminNoticeViewVo {
@Schema(description = "공지사항 PK", example = "1")
private Long noticeIdx;
@Schema(description = "공지 유형", example = "GENERAL")
private String noticeType;
@Schema(description = "노출 범위", example = "CAMPUS")
private String targetScope;
@Schema(description = "캠퍼스 IDX 목록", example = "[1, 2, 3]")
private List<Long> campusIdxList;
@Schema(description = "제목", example = "서비스 점검 안내")
private String title;
@Schema(description = "내용", example = "서비스 점검이 예정되어 있습니다.")
private String content;
@Schema(description = "상단 고정 여부", example = "N")
private String pinYn;
@Schema(description = "노출 시작일", example = "2026-07-01 00:00:00")
private LocalDateTime startDate;
@Schema(description = "노출 종료일", example = "2026-07-31 23:59:59")
private LocalDateTime endDate;
@Schema(description = "조회수", example = "0")
private Integer viewCount;
@Schema(description = "사용 여부", example = "Y")
private String useYn;
@Schema(description = "삭제 여부", example = "N")
private String delYn;
@Schema(description = "첨부파일 목록")
private List<AdminNoticeFileVo> fileList;
@Schema(description = "생성일", example = "2026-07-01 10:00:00")
private LocalDateTime createDate;
@Schema(description = "생성자 회원 PK", example = "1")
private Integer createMember;
@Schema(description = "수정일", example = "2026-07-01 10:00:00")
private LocalDateTime updateDate;
@Schema(description = "수정자 회원 PK", example = "1")
private Integer updateMember;
}
@@ -0,0 +1,45 @@
package com.alist.api.modules.admin.notice.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 AdminNoticeVo {
@Schema(description = "공지사항 PK", example = "1")
private Long noticeIdx;
@Schema(description = "공지 유형", example = "GENERAL")
private String noticeType;
@Schema(description = "노출 범위", example = "CAMPUS")
private String targetScope;
@Schema(description = "제목", example = "서비스 점검 안내")
private String title;
@Schema(description = "상단 고정 여부", example = "N")
private String pinYn;
@Schema(description = "노출 시작일", example = "2026-07-01 00:00:00")
private LocalDateTime startDate;
@Schema(description = "노출 종료일", example = "2026-07-31 23:59:59")
private LocalDateTime endDate;
@Schema(description = "조회수", example = "0")
private Integer viewCount;
@Schema(description = "사용 여부", example = "Y")
private String useYn;
@Schema(description = "생성일", example = "2026-07-01 10:00:00")
private LocalDateTime createDate;
@Schema(description = "수정일", example = "2026-07-01 10:00:00")
private LocalDateTime updateDate;
}
@@ -0,0 +1,229 @@
<?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.notice.mapper.AdminNoticeMapper">
<sql id="adminNoticeWhere">
FROM ALISTLMS.NOTICE N
WHERE N.DEL_YN = 'N'
<if test="keyword != null and keyword != ''">
AND (
N.TITLE LIKE CONCAT('%', #{keyword}, '%')
OR N.CONTENT LIKE CONCAT('%', #{keyword}, '%')
)
</if>
<if test="noticeType != null and noticeType != ''">
AND N.NOTICE_TYPE = #{noticeType}
</if>
<if test="targetScope != null and targetScope != ''">
AND N.TARGET_SCOPE = #{targetScope}
</if>
<if test="campusIdx != null">
AND EXISTS (
SELECT 1
FROM ALISTLMS.NOTICE_CAMPUS NC
WHERE NC.NOTICE_IDX = N.NOTICE_IDX
AND NC.CAMPUS_IDX = #{campusIdx}
)
</if>
<if test="pinYn != null and pinYn != ''">
AND N.PIN_YN = #{pinYn}
</if>
<if test="useYn != null and useYn != ''">
AND N.USE_YN = #{useYn}
</if>
</sql>
<select id="selectAdminNoticeCount" resultType="int">
/*AdminNoticeMapper.selectAdminNoticeCount*/
SELECT COUNT(*)
<include refid="adminNoticeWhere"/>
</select>
<select id="selectAdminNoticeList" resultType="com.alist.api.modules.admin.notice.vo.AdminNoticeVo">
/*AdminNoticeMapper.selectAdminNoticeList*/
SELECT N.NOTICE_IDX
, N.NOTICE_TYPE
, N.TARGET_SCOPE
, N.TITLE
, N.PIN_YN
, N.START_DATE
, N.END_DATE
, N.VIEW_COUNT
, N.USE_YN
, N.CREATE_DATE
, N.UPDATE_DATE
<include refid="adminNoticeWhere"/>
ORDER BY N.PIN_YN DESC, N.NOTICE_IDX DESC
LIMIT #{limit}
OFFSET #{offset}
</select>
<select id="selectAdminNoticeView" resultType="com.alist.api.modules.admin.notice.vo.AdminNoticeViewVo">
/*AdminNoticeMapper.selectAdminNoticeView*/
SELECT N.NOTICE_IDX
, N.NOTICE_TYPE
, N.TARGET_SCOPE
, N.TITLE
, N.CONTENT
, N.PIN_YN
, N.START_DATE
, N.END_DATE
, N.VIEW_COUNT
, N.USE_YN
, N.DEL_YN
, N.CREATE_DATE
, N.CREATE_MEMBER
, N.UPDATE_DATE
, N.UPDATE_MEMBER
FROM ALISTLMS.NOTICE N
WHERE N.NOTICE_IDX = #{noticeIdx}
AND N.DEL_YN = 'N'
</select>
<select id="selectAdminNoticeCampusIdxList" resultType="long">
/*AdminNoticeMapper.selectAdminNoticeCampusIdxList*/
SELECT NC.CAMPUS_IDX
FROM ALISTLMS.NOTICE_CAMPUS NC
WHERE NC.NOTICE_IDX = #{noticeIdx}
ORDER BY NC.CAMPUS_IDX
</select>
<select id="selectAdminNoticeFileList" resultType="com.alist.api.modules.admin.notice.vo.AdminNoticeFileVo">
/*AdminNoticeMapper.selectAdminNoticeFileList*/
SELECT NF.NOTICE_FILE_IDX
, NF.NOTICE_IDX
, NF.FILE_ORIGINAL_NAME
, NF.FILE_SAVE_NAME
, NF.FILE_PATH
, NF.FILE_SIZE
, NF.FILE_EXT
, NF.SORT_ORDER
, NF.CREATE_DATE
FROM ALISTLMS.NOTICE_FILE NF
WHERE NF.NOTICE_IDX = #{noticeIdx}
AND NF.DEL_YN = 'N'
ORDER BY NF.SORT_ORDER ASC, NF.NOTICE_FILE_IDX ASC
</select>
<insert id="insertAdminNoticeAdd" parameterType="com.alist.api.modules.admin.notice.dto.AdminNoticeAddDto" useGeneratedKeys="true" keyProperty="noticeIdx">
/*AdminNoticeMapper.insertAdminNoticeAdd*/
INSERT INTO ALISTLMS.NOTICE (
NOTICE_TYPE
, TARGET_SCOPE
, TITLE
, CONTENT
, PIN_YN
, START_DATE
, END_DATE
, USE_YN
, DEL_YN
, CREATE_MEMBER
, UPDATE_MEMBER
) VALUES (
#{noticeType}
, #{targetScope}
, #{title}
, #{content}
, #{pinYn}
, #{startDate}
, #{endDate}
, #{useYn}
, 'N'
, #{createMember}
, #{updateMember}
)
</insert>
<insert id="insertAdminNoticeCampusAdd" parameterType="com.alist.api.modules.admin.notice.dto.AdminNoticeCampusDto">
/*AdminNoticeMapper.insertAdminNoticeCampusAdd*/
INSERT INTO ALISTLMS.NOTICE_CAMPUS (
NOTICE_IDX
, CAMPUS_IDX
, CREATE_MEMBER
) VALUES (
#{noticeIdx}
, #{campusIdx}
, #{createMember}
)
</insert>
<insert id="insertAdminNoticeFileAdd" parameterType="com.alist.api.modules.admin.notice.dto.AdminNoticeFileDto">
/*AdminNoticeMapper.insertAdminNoticeFileAdd*/
INSERT INTO ALISTLMS.NOTICE_FILE (
NOTICE_IDX
, FILE_ORIGINAL_NAME
, FILE_SAVE_NAME
, FILE_PATH
, FILE_SIZE
, FILE_EXT
, SORT_ORDER
, DEL_YN
, CREATE_MEMBER
, UPDATE_MEMBER
) VALUES (
#{noticeIdx}
, #{fileOriginalName}
, #{fileSaveName}
, #{filePath}
, #{fileSize}
, #{fileExt}
, #{sortOrder}
, 'N'
, #{createMember}
, #{updateMember}
)
</insert>
<update id="updateAdminNoticeModify" parameterType="com.alist.api.modules.admin.notice.dto.AdminNoticeModifyDto">
/*AdminNoticeMapper.updateAdminNoticeModify*/
UPDATE ALISTLMS.NOTICE
SET NOTICE_TYPE = #{noticeType}
, TARGET_SCOPE = #{targetScope}
, TITLE = #{title}
, CONTENT = #{content}
, PIN_YN = #{pinYn}
, START_DATE = #{startDate}
, END_DATE = #{endDate}
, USE_YN = #{useYn}
, UPDATE_MEMBER = #{updateMember}
WHERE NOTICE_IDX = #{noticeIdx}
AND DEL_YN = 'N'
</update>
<update id="updateAdminNoticeDelete" parameterType="com.alist.api.modules.admin.notice.dto.AdminNoticeDeleteDto">
/*AdminNoticeMapper.updateAdminNoticeDelete*/
UPDATE ALISTLMS.NOTICE
SET DEL_YN = 'Y'
, UPDATE_MEMBER = #{updateMember}
WHERE NOTICE_IDX = #{noticeIdx}
AND DEL_YN = 'N'
</update>
<delete id="deleteAdminNoticeCampusByNoticeIdx">
/*AdminNoticeMapper.deleteAdminNoticeCampusByNoticeIdx*/
DELETE FROM ALISTLMS.NOTICE_CAMPUS
WHERE NOTICE_IDX = #{noticeIdx}
</delete>
<update id="updateAdminNoticeFileDeleteByNoticeIdx">
/*AdminNoticeMapper.updateAdminNoticeFileDeleteByNoticeIdx*/
UPDATE ALISTLMS.NOTICE_FILE
SET DEL_YN = 'Y'
, UPDATE_MEMBER = #{updateMember}
WHERE NOTICE_IDX = #{noticeIdx}
AND DEL_YN = 'N'
</update>
<update id="updateAdminNoticeFileDeleteByNoticeFileIdxList">
/*AdminNoticeMapper.updateAdminNoticeFileDeleteByNoticeFileIdxList*/
UPDATE ALISTLMS.NOTICE_FILE
SET DEL_YN = 'Y'
, UPDATE_MEMBER = #{updateMember}
WHERE NOTICE_IDX = #{noticeIdx}
AND DEL_YN = 'N'
AND NOTICE_FILE_IDX IN
<foreach collection="noticeFileIdxList" item="noticeFileIdx" open="(" separator="," close=")">
#{noticeFileIdx}
</foreach>
</update>
</mapper>