From fc70560f0598e85a155b7293e429e218d4895d4c Mon Sep 17 00:00:00 2001 From: Rinjae Date: Tue, 16 Jun 2026 09:11:06 +0900 Subject: [PATCH] =?UTF-8?q?=EA=B3=B5=EC=A7=80=EC=82=AC=ED=95=AD=20?= =?UTF-8?q?=EA=B4=80=EB=A6=AC=20-=20=EC=9E=A5=EC=95=A0/=EC=A0=90=EA=B2=80?= =?UTF-8?q?=20=EB=93=B1=EB=A1=9D=20UI=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - PortalNoticeUI: 장애/점검 필드 신설 (시작/종료/상태/영향API) - PortalNoticeManService: djb_apistatus_incident 연동 분기 - 등록 폼: 동적 유형별 UI + 영향 API 팝업 (showModal) Co-Authored-By: Claude Opus 4.7 --- .../portalnotice/portalNoticeManDetail.jsp | 310 ++++++++++++------ .../portalnotice/IncidentAffectedApiUI.java | 11 + .../portalnotice/PortalNoticeManService.java | 137 +++++++- .../onl/apim/portalnotice/PortalNoticeUI.java | 24 ++ .../portalnotice/PortalNoticeUIMapper.java | 4 +- 5 files changed, 392 insertions(+), 94 deletions(-) create mode 100644 src/main/java/com/eactive/eai/rms/onl/apim/portalnotice/IncidentAffectedApiUI.java diff --git a/WebContent/jsp/onl/apim/portalnotice/portalNoticeManDetail.jsp b/WebContent/jsp/onl/apim/portalnotice/portalNoticeManDetail.jsp index 6e74a8d..34ca6be 100644 --- a/WebContent/jsp/onl/apim/portalnotice/portalNoticeManDetail.jsp +++ b/WebContent/jsp/onl/apim/portalnotice/portalNoticeManDetail.jsp @@ -19,10 +19,23 @@ @@ -252,7 +339,7 @@ - +
@@ -270,9 +357,9 @@ - 게시유형 - -
+ 게시유형 * + +
@@ -286,7 +373,7 @@ 사용 - 고정여부 + 상단고정 고정 @@ -297,19 +384,58 @@ - + + 시작 * + + - --> + 종료 + + + + + + 상태 + +
+ +
+ + 이전 상태 + 없음 + + + 영향 API 선택 + +
+ + +
+ + + + + + + + + + + +
API IDAPI 명
선택된 영향 API가 없습니다.
+ +
- \ No newline at end of file + diff --git a/src/main/java/com/eactive/eai/rms/onl/apim/portalnotice/IncidentAffectedApiUI.java b/src/main/java/com/eactive/eai/rms/onl/apim/portalnotice/IncidentAffectedApiUI.java new file mode 100644 index 0000000..d38dcc7 --- /dev/null +++ b/src/main/java/com/eactive/eai/rms/onl/apim/portalnotice/IncidentAffectedApiUI.java @@ -0,0 +1,11 @@ +package com.eactive.eai.rms.onl.apim.portalnotice; + +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@NoArgsConstructor +public class IncidentAffectedApiUI { + private String apiId; + private String apiName; +} diff --git a/src/main/java/com/eactive/eai/rms/onl/apim/portalnotice/PortalNoticeManService.java b/src/main/java/com/eactive/eai/rms/onl/apim/portalnotice/PortalNoticeManService.java index 09bf0ad..3ec890b 100644 --- a/src/main/java/com/eactive/eai/rms/onl/apim/portalnotice/PortalNoticeManService.java +++ b/src/main/java/com/eactive/eai/rms/onl/apim/portalnotice/PortalNoticeManService.java @@ -1,5 +1,13 @@ package com.eactive.eai.rms.onl.apim.portalnotice; +import com.eactive.apim.portal.djb.apistatus.incident.entity.DjbApistatusIncident; +import com.eactive.apim.portal.djb.apistatus.incident.entity.DjbApistatusIncidentApi; +import com.eactive.apim.portal.djb.apistatus.incident.entity.DjbApistatusIncidentApiId; +import com.eactive.apim.portal.djb.apistatus.incident.entity.IncidentKind; +import com.eactive.apim.portal.djb.apistatus.incident.entity.IncidentState; +import com.eactive.apim.portal.djb.apistatus.incident.repository.DjbApistatusIncidentApiRepository; +import com.eactive.apim.portal.djb.apistatus.incident.repository.DjbApistatusIncidentRepository; +import com.eactive.apim.portal.djb.apistatus.incident.repository.DjbApistatusIncidentTimelineRepository; import com.eactive.apim.portal.file.entity.FileDetail; import com.eactive.apim.portal.file.entity.FileInfo; import com.eactive.apim.portal.file.service.FileService; @@ -23,7 +31,12 @@ import java.io.IOException; import java.io.UnsupportedEncodingException; import java.nio.charset.Charset; import java.nio.charset.StandardCharsets; +import java.time.LocalDateTime; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; import java.util.Optional; +import java.util.stream.Collectors; @Service @Transactional(transactionManager = "transactionManagerForEMS") @@ -32,6 +45,9 @@ public class PortalNoticeManService extends BaseService { private final PortalNoticeService portalNoticeService; private final PortalNoticeUIMapper portalNoticeUIMapper; private final FileService fileService; + private final DjbApistatusIncidentRepository incidentRepository; + private final DjbApistatusIncidentApiRepository incidentApiRepository; + private final DjbApistatusIncidentTimelineRepository incidentTimelineRepository; private String decodeString(String value) { if (ContainerUtil.get() == ContainerUtil.TOMCAT) { @@ -48,10 +64,27 @@ public class PortalNoticeManService extends BaseService { @Autowired public PortalNoticeManService(PortalNoticeService portalNoticeService, PortalNoticeUIMapper portalNoticeUIMapper, - FileService fileService){ + FileService fileService, + DjbApistatusIncidentRepository incidentRepository, + DjbApistatusIncidentApiRepository incidentApiRepository, + DjbApistatusIncidentTimelineRepository incidentTimelineRepository){ this.portalNoticeService = portalNoticeService; this.portalNoticeUIMapper = portalNoticeUIMapper; this.fileService = fileService; + this.incidentRepository = incidentRepository; + this.incidentApiRepository = incidentApiRepository; + this.incidentTimelineRepository = incidentTimelineRepository; + } + + private static boolean isIncidentKind(String noticeType) { + return PortalNoticeUI.NOTICE_TYPE_INCIDENT.equals(noticeType) + || PortalNoticeUI.NOTICE_TYPE_MAINTENANCE.equals(noticeType); + } + + private static IncidentKind toKind(String noticeType) { + if (PortalNoticeUI.NOTICE_TYPE_INCIDENT.equals(noticeType)) return IncidentKind.INCIDENT; + if (PortalNoticeUI.NOTICE_TYPE_MAINTENANCE.equals(noticeType)) return IncidentKind.MAINTENANCE; + return null; } @@ -89,9 +122,39 @@ public class PortalNoticeManService extends BaseService { PortalNoticeUI ui = portalNoticeUIMapper.toVo(portalNotice); setFileInfo(portalNotice, ui); + populateIncident(portalNotice, ui); return ui; } + private void populateIncident(PortalNotice portalNotice, PortalNoticeUI ui) { + if (!isIncidentKind(portalNotice.getNoticeType())) { + ui.setAffectedApis(Collections.emptyList()); + return; + } + Optional incidentOpt = incidentRepository.findByNoticeId(portalNotice.getId()); + if (!incidentOpt.isPresent()) { + ui.setAffectedApis(Collections.emptyList()); + return; + } + DjbApistatusIncident incident = incidentOpt.get(); + ui.setIncidentId(incident.getIncidentId()); + ui.setSummary(incident.getSummary()); + ui.setStartedAt(incident.getStartedAt()); + ui.setEndAt(incident.getEndAt()); + ui.setState(incident.getState() == null ? null : incident.getState().name()); + ui.setPreviousState(incident.getPreviousState() == null ? null : incident.getPreviousState().name()); + + List apis = incidentApiRepository + .findByIncidentIdOrderByApiId(incident.getIncidentId()).stream() + .map(api -> { + IncidentAffectedApiUI vo = new IncidentAffectedApiUI(); + vo.setApiId(api.getApiId()); + vo.setApiName(api.getApiName()); + return vo; + }).collect(Collectors.toList()); + ui.setAffectedApis(apis); + } + public void insert(PortalNoticeUI portalNoticeUI) throws IOException { //portalNoticeUI.setNoticeSubject(decodeString(portalNoticeUI.getNoticeSubject())); //portalNoticeUI.setNoticeDetail(decodeString(StringEscapeUtils.unescapeHtml(portalNoticeUI.getNoticeDetail()))); @@ -106,6 +169,61 @@ public class PortalNoticeManService extends BaseService { } portalNoticeService.save(portalNotice); + + if (isIncidentKind(portalNoticeUI.getNoticeType())) { + persistIncident(portalNoticeUI, portalNotice, true); + } + } + + private void persistIncident(PortalNoticeUI portalNoticeUI, PortalNotice portalNotice, boolean isCreate) { + IncidentKind kind = toKind(portalNoticeUI.getNoticeType()); + + DjbApistatusIncident incident = incidentRepository.findByNoticeId(portalNotice.getId()) + .orElseGet(DjbApistatusIncident::new); + + incident.setKind(kind); + incident.setTitle(portalNotice.getNoticeSubject()); + incident.setSummary(portalNoticeUI.getSummary()); + incident.setStartedAt(portalNoticeUI.getStartedAt() != null + ? portalNoticeUI.getStartedAt() : LocalDateTime.now()); + incident.setEndAt(portalNoticeUI.getEndAt()); + incident.setDetectedBy("MANUAL"); + incident.setNoticeId(portalNotice.getId()); + incident.setDraftYn("N"); + incident.setFixYn(StringUtils.defaultIfBlank(portalNotice.getFixYn(), "N")); + + if (kind == IncidentKind.INCIDENT) { + IncidentState newState = StringUtils.isNotBlank(portalNoticeUI.getState()) + ? IncidentState.valueOf(portalNoticeUI.getState()) + : IncidentState.INVESTIGATING; + if (!isCreate && incident.getState() != newState) { + incident.setPreviousState(incident.getState()); + } + incident.setState(newState); + } else { + incident.setState(null); + incident.setPreviousState(null); + } + + DjbApistatusIncident saved = incidentRepository.save(incident); + + // 영향 API 동기화 — 단순 전체 삭제 후 재삽입 + incidentApiRepository.deleteByIncidentId(saved.getIncidentId()); + List affected = portalNoticeUI.getAffectedApis(); + if (affected != null && !affected.isEmpty()) { + List rows = new ArrayList<>(); + for (IncidentAffectedApiUI src : affected) { + if (StringUtils.isBlank(src.getApiId())) continue; + DjbApistatusIncidentApi api = new DjbApistatusIncidentApi(); + api.setIncidentId(saved.getIncidentId()); + api.setApiId(src.getApiId()); + api.setApiName(src.getApiName()); + rows.add(api); + } + if (!rows.isEmpty()) { + incidentApiRepository.saveAll(rows); + } + } } private void handleFileUpload(PortalNoticeUI portalNoticeUI, PortalNotice portalNotice) throws IOException { @@ -129,6 +247,7 @@ public class PortalNoticeManService extends BaseService { portalNoticeUI.setNoticeDetail(StringEscapeUtils.unescapeHtml(portalNoticeUI.getNoticeDetail())); PortalNotice portalNotice = portalNoticeService.getById(portalNoticeUI.getId()); + String previousNoticeType = portalNotice.getNoticeType(); if (portalNoticeUI.getFiles() != null && !portalNoticeUI.getFiles().isEmpty()) { handleFileUpload(portalNoticeUI, portalNotice); @@ -139,11 +258,27 @@ public class PortalNoticeManService extends BaseService { portalNoticeUIMapper.updateToEntity(portalNoticeUI, portalNotice); portalNoticeService.save(portalNotice); + + if (isIncidentKind(portalNotice.getNoticeType())) { + persistIncident(portalNoticeUI, portalNotice, false); + } else if (isIncidentKind(previousNoticeType)) { + // 일반 공지로 유형 변경 → 기존 incident 정리 + deleteIncidentIfExists(portalNotice.getId()); + } + } + + private void deleteIncidentIfExists(String noticeId) { + incidentRepository.findByNoticeId(noticeId).ifPresent(incident -> { + incidentApiRepository.deleteByIncidentId(incident.getIncidentId()); + incidentTimelineRepository.deleteByIncidentId(incident.getIncidentId()); + incidentRepository.delete(incident); + }); } public void delete(String id) { PortalNotice portalNotice = portalNoticeService.getById(id); Optional.ofNullable(portalNotice.getFileId()).ifPresent(fileService::deleteFile); + deleteIncidentIfExists(id); portalNoticeService.deleteById(id); } diff --git a/src/main/java/com/eactive/eai/rms/onl/apim/portalnotice/PortalNoticeUI.java b/src/main/java/com/eactive/eai/rms/onl/apim/portalnotice/PortalNoticeUI.java index bded835..9d57de9 100644 --- a/src/main/java/com/eactive/eai/rms/onl/apim/portalnotice/PortalNoticeUI.java +++ b/src/main/java/com/eactive/eai/rms/onl/apim/portalnotice/PortalNoticeUI.java @@ -8,11 +8,16 @@ import org.springframework.format.annotation.DateTimeFormat; import org.springframework.web.multipart.MultipartFile; import java.time.LocalDateTime; +import java.util.List; @Data public class PortalNoticeUI { + public static final String NOTICE_TYPE_NORMAL = "1"; + public static final String NOTICE_TYPE_MAINTENANCE = "2"; + public static final String NOTICE_TYPE_INCIDENT = "3"; + private String id; private String noticeSubject; @@ -53,4 +58,23 @@ public class PortalNoticeUI { private boolean hasAttachment; // 첨부파일 여부 private boolean fileDeleted; // 파일 삭제 여부 + + // ───── 장애/점검(djb_apistatus_incident) 연동 필드 ───── + private Long incidentId; + + private String summary; + + @JsonFormat(pattern = "yyyy-MM-dd HH:mm") + @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm") + private LocalDateTime startedAt; + + @JsonFormat(pattern = "yyyy-MM-dd HH:mm") + @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm") + private LocalDateTime endAt; + + private String state; // INCIDENT 한정 (INVESTIGATING/IDENTIFIED/MONITORING/RESOLVED/CANCELED) + + private String previousState; // 직전 상태 + + private List affectedApis; } diff --git a/src/main/java/com/eactive/eai/rms/onl/apim/portalnotice/PortalNoticeUIMapper.java b/src/main/java/com/eactive/eai/rms/onl/apim/portalnotice/PortalNoticeUIMapper.java index d8fa0ed..02aa8c1 100644 --- a/src/main/java/com/eactive/eai/rms/onl/apim/portalnotice/PortalNoticeUIMapper.java +++ b/src/main/java/com/eactive/eai/rms/onl/apim/portalnotice/PortalNoticeUIMapper.java @@ -3,7 +3,9 @@ package com.eactive.eai.rms.onl.apim.portalnotice; import com.eactive.apim.portal.portalNotice.entity.PortalNotice; import org.mapstruct.*; -@Mapper(componentModel = "spring", unmappedTargetPolicy = ReportingPolicy.IGNORE) +@Mapper(componentModel = "spring", + unmappedTargetPolicy = ReportingPolicy.IGNORE, + unmappedSourcePolicy = ReportingPolicy.IGNORE) public interface PortalNoticeUIMapper { PortalNoticeUI toVo(PortalNotice entity);