공지사항 관리 - 장애 점검 등록 UI 추가
eapim-admin CI / build (push) Has been cancelled

This commit is contained in:
Rinjae
2026-06-16 14:55:39 +09:00
parent cfafccc34d
commit 6b6e2863cd
5 changed files with 393 additions and 94 deletions
@@ -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;
}
@@ -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<DjbApistatusIncident> 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<IncidentAffectedApiUI> 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<IncidentAffectedApiUI> affected = portalNoticeUI.getAffectedApis();
if (affected != null && !affected.isEmpty()) {
List<DjbApistatusIncidentApi> 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);
}
@@ -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,24 @@ public class PortalNoticeUI {
private boolean hasAttachment; // 첨부파일 여부
private boolean fileDeleted; // 파일 삭제 여부
// ───── 장애/점검(djb_apistatus_incident) 연동 필드 ─────
private Long incidentId;
private String summary;
// HTML <input type="datetime-local"> 는 'T' 구분자 형식 (예: 2026-06-16T10:29).
@JsonFormat(pattern = "yyyy-MM-dd'T'HH:mm")
@DateTimeFormat(pattern = "yyyy-MM-dd'T'HH:mm")
private LocalDateTime startedAt;
@JsonFormat(pattern = "yyyy-MM-dd'T'HH:mm")
@DateTimeFormat(pattern = "yyyy-MM-dd'T'HH:mm")
private LocalDateTime endAt;
private String state; // INCIDENT 한정 (INVESTIGATING/IDENTIFIED/MONITORING/RESOLVED/CANCELED)
private String previousState; // 직전 상태
private List<IncidentAffectedApiUI> affectedApis;
}
@@ -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);