Merge remote-tracking branch 'origin/master' into master
Resolve conflict in PortalNoticeManService.java import block: union both sides — keep Arrays (HEAD) and LocalDateTime/ArrayList/Collections (origin/master), all of which are used in the merged body. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -254,6 +254,13 @@ public interface MonitoringContext {
|
||||
|
||||
// 이중 로그인 허용여부
|
||||
public static final String RMS_DUAL_LOGIN_ENABLED = "rms.DUAL_LOGIN_ENABLED";
|
||||
|
||||
// 자동 로그아웃 타임아웃 (단위: 분, 기본값: 10)
|
||||
public static final String RMS_AUTO_LOGOUT_TIMEOUT = "rms.auto.logout.timeout";
|
||||
|
||||
// 웹훅 재전송 설정
|
||||
public static final String API_WEBHOOK_RETRY_COUNT = "api.webhook.retry_count";
|
||||
public static final String API_WEBHOOK_RETRY_TIME = "api.webhook.retry_time";
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -617,6 +617,8 @@ public class MainController implements InterceptorSkipController {
|
||||
menuId = menuId == null ? "" : menuId;
|
||||
model.addAttribute("mainPage", mainPage);
|
||||
model.addAttribute("menuId", menuId);
|
||||
model.addAttribute("autoLogoutTimeout",
|
||||
monitoringContext.getIntProperty(MonitoringContext.RMS_AUTO_LOGOUT_TIMEOUT, 10));
|
||||
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug("CURRENT SERVICE TYPE : " + service);
|
||||
|
||||
@@ -35,6 +35,7 @@ public class MonitoringPropertyManService extends BaseService {
|
||||
|
||||
public MonitoringPropertyGroupUI selectDetail(String prptyGroupName) {
|
||||
MonitoringPropertyGroup propertyGroup = propertyGroupService.getById(prptyGroupName);
|
||||
propertyGroup.getMonitoringProperties().sort(Comparator.comparing(p -> p.getId().getPrptyName()));
|
||||
return propertyGroupMapper.toVo(propertyGroup);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,13 +1,11 @@
|
||||
package com.eactive.eai.rms.common.scheduler;
|
||||
|
||||
import com.eactive.eai.rms.common.base.BaseService;
|
||||
import com.eactive.eai.rms.common.scheduler.ui.JobInfoMapper;
|
||||
import com.eactive.eai.rms.common.scheduler.ui.JobInfoUI;
|
||||
import com.eactive.eai.rms.common.scheduler.ui.ScheduleInfo;
|
||||
import com.eactive.eai.rms.common.scheduler.ui.SchedulerUISearch;
|
||||
import com.eactive.eai.rms.data.entity.man.monitoringCode.service.MonitoringCodeService;
|
||||
import com.eactive.eai.rms.data.entity.man.scheduler.JobHistoryService;
|
||||
import com.eactive.eai.rms.data.entity.man.scheduler.JobInfoService;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.StreamSupport;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.quartz.JobDetail;
|
||||
import org.quartz.JobKey;
|
||||
@@ -20,9 +18,14 @@ import org.springframework.data.domain.Pageable;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.StreamSupport;
|
||||
import com.eactive.eai.rms.common.base.BaseService;
|
||||
import com.eactive.eai.rms.common.scheduler.ui.JobInfoMapper;
|
||||
import com.eactive.eai.rms.common.scheduler.ui.JobInfoUI;
|
||||
import com.eactive.eai.rms.common.scheduler.ui.ScheduleInfo;
|
||||
import com.eactive.eai.rms.common.scheduler.ui.SchedulerUISearch;
|
||||
import com.eactive.eai.rms.data.entity.man.monitoringCode.service.MonitoringCodeService;
|
||||
import com.eactive.eai.rms.data.entity.man.scheduler.JobHistoryService;
|
||||
import com.eactive.eai.rms.data.entity.man.scheduler.JobInfoService;
|
||||
|
||||
@Service("schedulerService")
|
||||
@Transactional(transactionManager = "transactionManagerForEMS")
|
||||
@@ -122,10 +125,10 @@ public class SchedulerManService extends BaseService {
|
||||
|
||||
List<? extends Trigger> triggersOfJob = scheduler.getTriggersOfJob(new JobKey(jobName, Scheduler.DEFAULT_GROUP));
|
||||
if(triggersOfJob.size() > 0) {
|
||||
result.setStartTime(String.valueOf(triggersOfJob.get(0).getStartTime()));
|
||||
result.setEndTime(String.valueOf(triggersOfJob.get(0).getEndTime()));
|
||||
result.setPreviousFireTime(String.valueOf(triggersOfJob.get(0).getPreviousFireTime()));
|
||||
result.setNextFireTime(String.valueOf(triggersOfJob.get(0).getNextFireTime()));
|
||||
result.setStartTime(dateFormat(triggersOfJob.get(0).getStartTime()));
|
||||
result.setEndTime(dateFormat(triggersOfJob.get(0).getEndTime()));
|
||||
result.setPreviousFireTime(dateFormat(triggersOfJob.get(0).getPreviousFireTime()));
|
||||
result.setNextFireTime(dateFormat(triggersOfJob.get(0).getNextFireTime()));
|
||||
}
|
||||
|
||||
} catch (Exception e) {
|
||||
@@ -150,5 +153,14 @@ public class SchedulerManService extends BaseService {
|
||||
public void delete(String jobName) {
|
||||
jobInfoService.deleteById(jobName);
|
||||
}
|
||||
|
||||
private String dateFormat(Date date) {
|
||||
if (date == null) {
|
||||
return "";
|
||||
} else {
|
||||
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
|
||||
return sdf.format(date);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package com.eactive.eai.rms.common.util;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.util.List;
|
||||
|
||||
import org.apache.log4j.Logger;
|
||||
@@ -191,6 +192,20 @@ public class StringUtils
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
public static String toString(Object val) {
|
||||
return val != null ? val.toString() : null;
|
||||
}
|
||||
|
||||
public static Long toLong(Object val) {
|
||||
return val != null ? ((Number) val).longValue() : null;
|
||||
}
|
||||
|
||||
public static BigDecimal toDecimal(Object val) {
|
||||
if (val == null) return null;
|
||||
if (val instanceof BigDecimal) return (BigDecimal) val;
|
||||
return new BigDecimal(val.toString());
|
||||
}
|
||||
//
|
||||
// /**
|
||||
// * - "" 나 Null 을 입력받아 SPACE 로 변환한다.
|
||||
|
||||
@@ -107,6 +107,17 @@ public class UserInfoService extends
|
||||
}).collect(Collectors.toList());
|
||||
}
|
||||
|
||||
public List<UserInfo> findAllByRoleId(String roleId) {
|
||||
QUserInfo qUserInfo = QUserInfo.userInfo;
|
||||
QUserRole qUserRole = QUserRole.userRole;
|
||||
|
||||
return getJPAQueryFactory()
|
||||
.selectFrom(qUserInfo)
|
||||
.join(qUserRole).on(qUserInfo.userid.eq(qUserRole.id.userId))
|
||||
.where(qUserRole.id.roleId.eq(roleId))
|
||||
.fetch();
|
||||
}
|
||||
|
||||
public Page<UserInfo> findByUsername(Pageable pageable, String username) {
|
||||
QUserInfo qUserInfo = QUserInfo.userInfo;
|
||||
BooleanExpression predicate = qUserInfo.isNotNull();
|
||||
|
||||
+28
@@ -0,0 +1,28 @@
|
||||
package com.eactive.eai.rms.data.entity.onl.apim.portalinquiry;
|
||||
|
||||
import com.eactive.apim.portal.qna.entity.InquiryComment;
|
||||
import com.eactive.eai.data.jpa.BaseRepository;
|
||||
import com.eactive.eai.rms.data.EMSDataSource;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
@EMSDataSource
|
||||
public interface PortalInquiryCommentRepository extends BaseRepository<InquiryComment, String> {
|
||||
|
||||
long countByInquiry_Id(String inquiryId);
|
||||
|
||||
long countByInquiry_IdAndDelYn(String inquiryId, String delYn);
|
||||
|
||||
List<InquiryComment> findByInquiry_IdAndDelYnOrderByCreatedDateAsc(String inquiryId, String delYn);
|
||||
|
||||
Optional<InquiryComment> findTopByInquiry_IdAndDelYnOrderByCreatedDateDesc(
|
||||
String inquiryId, String delYn);
|
||||
|
||||
Optional<InquiryComment> findTopByInquiry_IdAndAdminYnAndDelYnOrderByCreatedDateDesc(
|
||||
String inquiryId, String adminYn, String delYn);
|
||||
|
||||
boolean existsByInquiry_IdAndAdminYnAndDelYnAndCreatedDateAfter(
|
||||
String inquiryId, String adminYn, String delYn, LocalDateTime after);
|
||||
}
|
||||
+31
@@ -0,0 +1,31 @@
|
||||
package com.eactive.eai.rms.data.entity.onl.apim.portalinquiry;
|
||||
|
||||
import com.eactive.apim.portal.qna.entity.InquiryComment;
|
||||
import com.eactive.eai.rms.data.jpa.AbstractEMSDataSerivce;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Service
|
||||
@Transactional(transactionManager = "transactionManagerForEMS")
|
||||
public class PortalInquiryCommentService extends AbstractEMSDataSerivce<InquiryComment, String, PortalInquiryCommentRepository> {
|
||||
|
||||
// AbstractEMSDataSerivce의 repository 필드가 BaseRepository<T,I>로 고정되어
|
||||
// 커스텀 메서드를 호출할 수 없으므로 별도 필드로 직접 주입
|
||||
@Autowired
|
||||
private PortalInquiryCommentRepository inquiryCommentRepository;
|
||||
|
||||
public long countByInquiryId(String inquiryId) {
|
||||
return inquiryCommentRepository.countByInquiry_Id(inquiryId);
|
||||
}
|
||||
|
||||
public long countActiveByInquiryId(String inquiryId) {
|
||||
return inquiryCommentRepository.countByInquiry_IdAndDelYn(inquiryId, "N");
|
||||
}
|
||||
|
||||
public List<InquiryComment> findByInquiryId(String inquiryId) {
|
||||
return inquiryCommentRepository.findByInquiry_IdAndDelYnOrderByCreatedDateAsc(inquiryId, "N");
|
||||
}
|
||||
}
|
||||
+3
@@ -4,7 +4,10 @@ import com.eactive.apim.portal.qna.entity.Inquiry;
|
||||
import com.eactive.eai.data.jpa.BaseRepository;
|
||||
import com.eactive.eai.rms.data.EMSDataSource;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@EMSDataSource
|
||||
public interface PortalInquiryRepository extends BaseRepository<Inquiry, String> {
|
||||
|
||||
List<Inquiry> findByInquiryStatus(String inquiryStatus);
|
||||
}
|
||||
|
||||
+18
-6
@@ -1,20 +1,28 @@
|
||||
package com.eactive.eai.rms.data.entity.onl.apim.portalinquiry;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.data.domain.Page;
|
||||
import org.springframework.data.domain.Pageable;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import com.eactive.apim.portal.qna.entity.Inquiry;
|
||||
import com.eactive.apim.portal.qna.entity.QInquiry;
|
||||
import com.eactive.eai.rms.data.jpa.AbstractEMSDataSerivce;
|
||||
import com.querydsl.core.BooleanBuilder;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.springframework.data.domain.Page;
|
||||
import org.springframework.data.domain.Pageable;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
import java.util.List;
|
||||
|
||||
@Service
|
||||
@Transactional(transactionManager = "transactionManagerForEMS")
|
||||
public class PortalInquiryService extends AbstractEMSDataSerivce<Inquiry, String, PortalInquiryRepository> {
|
||||
|
||||
// AbstractEMSDataSerivce의 repository 필드가 BaseRepository<T,I>로 고정되어
|
||||
// 커스텀 메서드를 호출할 수 없으므로 별도 필드로 직접 주입
|
||||
@Autowired
|
||||
private PortalInquiryRepository portalInquiryRepository;
|
||||
|
||||
public Page<Inquiry> findAll(Pageable pageable, PortalInquirySearch portalInquirySearch) {
|
||||
QInquiry qInquiry = QInquiry.inquiry;
|
||||
|
||||
@@ -58,4 +66,8 @@ public class PortalInquiryService extends AbstractEMSDataSerivce<Inquiry, String
|
||||
repository.deleteAll(inquiries);
|
||||
}
|
||||
}
|
||||
|
||||
public List<Inquiry> findByInquiryStatus(String inquiryStatus) {
|
||||
return portalInquiryRepository.findByInquiryStatus(inquiryStatus);
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
package com.eactive.eai.rms.data.ext.djb.apistatus;
|
||||
package com.eactive.eai.rms.data.entity.onl.djb.apistatus;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
+2
-1
@@ -1,7 +1,8 @@
|
||||
package com.eactive.eai.rms.data.ext.djb.apistatus;
|
||||
package com.eactive.eai.rms.data.entity.onl.djb.apistatus;
|
||||
|
||||
public interface ApiStatusEvent {
|
||||
String getEaisvcname();
|
||||
String getEaisvcdesc();
|
||||
String getPrevStatusCode();
|
||||
String getEvent();
|
||||
}
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
package com.eactive.eai.rms.data.ext.djb.inflow;
|
||||
package com.eactive.eai.rms.data.entity.onl.djb.inflow;
|
||||
|
||||
public interface InflowTokenInsufficient {
|
||||
String getEaisvcname();
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
package com.eactive.eai.rms.data.ext.djb.inflow;
|
||||
package com.eactive.eai.rms.data.entity.onl.djb.inflow;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
package com.eactive.eai.rms.data.ext.djb.inflow;
|
||||
package com.eactive.eai.rms.data.entity.onl.djb.inflow;
|
||||
|
||||
import java.io.Serializable;
|
||||
import javax.persistence.Column;
|
||||
@@ -0,0 +1,33 @@
|
||||
package com.eactive.eai.rms.data.entity.onl.djb.webhook;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.NoArgsConstructor;
|
||||
import org.hibernate.annotations.Comment;
|
||||
|
||||
import javax.persistence.Column;
|
||||
import javax.persistence.Entity;
|
||||
import javax.persistence.Id;
|
||||
import javax.persistence.Table;
|
||||
|
||||
@Entity
|
||||
@Table(name = "PTL_WEBHOOK_REQ")
|
||||
@Getter
|
||||
@NoArgsConstructor
|
||||
public class WebhookReq {
|
||||
|
||||
@Id
|
||||
@Column(name = "ID", length = 36, nullable = false)
|
||||
private String id;
|
||||
|
||||
@Column(name = "ORG_ID", length = 100)
|
||||
@Comment("법인 ID")
|
||||
private String orgId;
|
||||
|
||||
@Column(name = "TARGET_URL", length = 500, nullable = false)
|
||||
@Comment("웹훅 수신 URL")
|
||||
private String targetUrl;
|
||||
|
||||
@Column(name = "SECRET", length = 200)
|
||||
@Comment("HMAC 서명용 Secret Key")
|
||||
private String secret;
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
package com.eactive.eai.rms.data.entity.onl.djb.webhook;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
import javax.persistence.EmbeddedId;
|
||||
import javax.persistence.Entity;
|
||||
import javax.persistence.Table;
|
||||
import java.io.Serializable;
|
||||
|
||||
@Entity
|
||||
@Table(name = "PTL_WEBHOOK_REQ_API")
|
||||
@Getter
|
||||
@NoArgsConstructor
|
||||
public class WebhookReqApi implements Serializable {
|
||||
|
||||
@EmbeddedId
|
||||
private WebhookReqApiId id;
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
package com.eactive.eai.rms.data.entity.onl.djb.webhook;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
import org.hibernate.annotations.Comment;
|
||||
|
||||
import javax.persistence.Column;
|
||||
import javax.persistence.Embeddable;
|
||||
import java.io.Serializable;
|
||||
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@Embeddable
|
||||
public class WebhookReqApiId implements Serializable {
|
||||
|
||||
@Column(name = "WEBHOOK_REQ_ID", nullable = false, length = 36)
|
||||
@Comment("웹훅 요청 ID")
|
||||
private String webhookReqId;
|
||||
|
||||
@Column(name = "API_ID", nullable = false, length = 100)
|
||||
@Comment("API ID")
|
||||
private String apiId;
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
package com.eactive.eai.rms.data.entity.onl.djb.webhook;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
import javax.persistence.EmbeddedId;
|
||||
import javax.persistence.Entity;
|
||||
import javax.persistence.Table;
|
||||
import java.io.Serializable;
|
||||
|
||||
@Entity
|
||||
@Table(name = "PTL_WEBHOOK_REQ_EVENT")
|
||||
@Getter
|
||||
@NoArgsConstructor
|
||||
public class WebhookReqEvent implements Serializable {
|
||||
|
||||
@EmbeddedId
|
||||
private WebhookReqEventId id;
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
package com.eactive.eai.rms.data.entity.onl.djb.webhook;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
import org.hibernate.annotations.Comment;
|
||||
|
||||
import javax.persistence.Column;
|
||||
import javax.persistence.Embeddable;
|
||||
import java.io.Serializable;
|
||||
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@Embeddable
|
||||
public class WebhookReqEventId implements Serializable {
|
||||
|
||||
@Column(name = "WEBHOOK_REQ_ID", nullable = false, length = 36)
|
||||
@Comment("웹훅 요청 ID")
|
||||
private String webhookReqId;
|
||||
|
||||
@Column(name = "EVENT_TYPE", nullable = false, length = 50)
|
||||
@Comment("이벤트 유형 (CONTROL_START, ERROR_START 등)")
|
||||
private String eventType;
|
||||
}
|
||||
+4
-1
@@ -1,4 +1,4 @@
|
||||
package com.eactive.eai.rms.data.ext.djb.webhook;
|
||||
package com.eactive.eai.rms.data.entity.onl.djb.webhook;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.NoArgsConstructor;
|
||||
@@ -42,6 +42,9 @@ public class WebhookSendLog {
|
||||
@Column(name = "STATUS_CODE")
|
||||
private Integer statusCode;
|
||||
|
||||
@Column(name = "ORG_ID")
|
||||
private String orgId;
|
||||
|
||||
@Lob
|
||||
@Column(name = "RESPONSE_BODY")
|
||||
private String responseBody;
|
||||
+43
-80
@@ -1,9 +1,6 @@
|
||||
package com.eactive.eai.rms.data.entity.onl.inflow;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import javax.persistence.EntityManager;
|
||||
@@ -18,6 +15,7 @@ import org.springframework.stereotype.Service;
|
||||
|
||||
import com.eactive.apim.portal.app.entity.QCredential;
|
||||
import com.eactive.eai.data.entity.onl.adapter.QAdapterGroup;
|
||||
import com.eactive.eai.data.entity.onl.authserver.QClientEntity;
|
||||
import com.eactive.eai.data.entity.onl.inflow.InflowControl;
|
||||
import com.eactive.eai.data.entity.onl.inflow.InflowControlId;
|
||||
import com.eactive.eai.data.entity.onl.inflow.QInflowControl;
|
||||
@@ -29,7 +27,6 @@ import com.eactive.eai.rms.onl.manage.inflow.history.InflowControlHistoryManUISe
|
||||
import com.eactive.eai.rms.onl.manage.inflow.inflow.InflowControlManMapper;
|
||||
import com.querydsl.core.BooleanBuilder;
|
||||
import com.querydsl.core.Tuple;
|
||||
import com.querydsl.core.types.OrderSpecifier;
|
||||
import com.querydsl.jpa.impl.JPAQuery;
|
||||
import com.querydsl.jpa.impl.JPAQueryFactory;
|
||||
|
||||
@@ -186,101 +183,67 @@ public class InflowControlService extends AbstractDataService<InflowControl, Inf
|
||||
}
|
||||
|
||||
public InflowControlServiceDto findByIdForClient(String clientId) {
|
||||
QCredential qCredential = QCredential.credential;
|
||||
JPAQueryFactory jpaQueryFactory = new JPAQueryFactory(entityManager);
|
||||
QInflowControl qInflowControl = QInflowControl.inflowControl;
|
||||
QClientEntity qClientEntity = QClientEntity.clientEntity;
|
||||
|
||||
// EMSADM: Credential 조회
|
||||
Tuple credTuple = new JPAQueryFactory(entityManagerForEMS)
|
||||
.select(qCredential.clientid, qCredential.clientname)
|
||||
.from(qCredential)
|
||||
.where(qCredential.clientid.eq(clientId))
|
||||
Tuple tuple = jpaQueryFactory
|
||||
.select(qInflowControl, qClientEntity.clientid, qClientEntity.clientname)
|
||||
.from(qClientEntity)
|
||||
.leftJoin(qInflowControl)
|
||||
.on(qInflowControl.id.type.eq(CLIENT_TYPE_INFLOW)
|
||||
.and(qInflowControl.id.name.eq(qClientEntity.clientid)))
|
||||
.where(qClientEntity.clientid.eq(clientId))
|
||||
.fetchOne();
|
||||
|
||||
// AGWADM: InflowControl 조회
|
||||
InflowControl inflowControl = new JPAQueryFactory(entityManager)
|
||||
.selectFrom(qInflowControl)
|
||||
.where(qInflowControl.id.type.eq(CLIENT_TYPE_INFLOW)
|
||||
.and(qInflowControl.id.name.eq(clientId)))
|
||||
.fetchOne();
|
||||
|
||||
InflowControlServiceDto dto;
|
||||
if (inflowControl != null) {
|
||||
dto = mapper.toDto(inflowControl);
|
||||
} else {
|
||||
dto = new InflowControlServiceDto();
|
||||
dto.setName(clientId);
|
||||
dto.setType(CLIENT_TYPE_INFLOW);
|
||||
}
|
||||
if (credTuple != null) {
|
||||
dto.setDesc(credTuple.get(qCredential.clientname));
|
||||
}
|
||||
return dto;
|
||||
return toDto(qClientEntity, qInflowControl, tuple);
|
||||
}
|
||||
|
||||
public Page<InflowControlServiceDto> findAllForClient(Pageable pageable, String searchName) {
|
||||
QCredential qCredential = QCredential.credential;
|
||||
|
||||
JPAQueryFactory jpaQueryFactory = new JPAQueryFactory(entityManager);
|
||||
QInflowControl qInflowControl = QInflowControl.inflowControl;
|
||||
QClientEntity qClientEntity = QClientEntity.clientEntity;
|
||||
|
||||
// EMSADM: Credential 목록 및 건수 조회
|
||||
JPAQueryFactory emsFactory = new JPAQueryFactory(entityManagerForEMS);
|
||||
BooleanBuilder credPredicate = new BooleanBuilder();
|
||||
if (!StringUtils.isEmpty(searchName)) {
|
||||
credPredicate.and(qCredential.clientname.contains(searchName));
|
||||
}
|
||||
|
||||
long totalCount = emsFactory
|
||||
.select(qCredential.clientid.count())
|
||||
.from(qCredential)
|
||||
.where(credPredicate)
|
||||
.fetchOne();
|
||||
|
||||
List<Tuple> credList = emsFactory
|
||||
.select(qCredential.clientid, qCredential.clientname)
|
||||
.from(qCredential)
|
||||
.where(credPredicate)
|
||||
.orderBy(qCredential.clientname.asc())
|
||||
List<Tuple> tupleList = jpaQueryFactory
|
||||
.select(qInflowControl, qClientEntity.clientid, qClientEntity.clientname)
|
||||
.from(qClientEntity)
|
||||
.leftJoin(qInflowControl)
|
||||
.on(qInflowControl.id.type.eq(CLIENT_TYPE_INFLOW)
|
||||
.and(qInflowControl.id.name.eq(qClientEntity.clientid)))
|
||||
.where(qClientEntity.clientname.contains(searchName))
|
||||
.orderBy(qClientEntity.clientname.asc())
|
||||
.offset(pageable.getOffset())
|
||||
.limit(pageable.getPageSize())
|
||||
.fetch();
|
||||
|
||||
if (credList.isEmpty()) {
|
||||
return new PageImpl<>(Collections.emptyList(), pageable, totalCount);
|
||||
}
|
||||
|
||||
// AGWADM: 해당 clientId의 InflowControl 일괄 조회
|
||||
List<String> clientIds = credList.stream()
|
||||
.map(t -> t.get(qCredential.clientid))
|
||||
.collect(Collectors.toList());
|
||||
|
||||
Map<String, InflowControl> inflowMap = new JPAQueryFactory(entityManager)
|
||||
.selectFrom(qInflowControl)
|
||||
.where(qInflowControl.id.type.eq(CLIENT_TYPE_INFLOW)
|
||||
.and(qInflowControl.id.name.in(clientIds)))
|
||||
.fetch()
|
||||
List<InflowControlServiceDto> dtoList = tupleList
|
||||
.stream()
|
||||
.collect(Collectors.toMap(ic -> ic.getId().getName(), ic -> ic));
|
||||
|
||||
// Java에서 병합
|
||||
List<InflowControlServiceDto> dtoList = credList.stream()
|
||||
.map(tuple -> {
|
||||
String clientId = tuple.get(qCredential.clientid);
|
||||
InflowControl inflowControl = inflowMap.get(clientId);
|
||||
InflowControlServiceDto dto;
|
||||
if (inflowControl != null) {
|
||||
dto = mapper.toDto(inflowControl);
|
||||
} else {
|
||||
dto = new InflowControlServiceDto();
|
||||
dto.setName(clientId);
|
||||
dto.setType(CLIENT_TYPE_INFLOW);
|
||||
}
|
||||
dto.setDesc(tuple.get(qCredential.clientname));
|
||||
return dto;
|
||||
})
|
||||
.map(tuple -> toDto(qClientEntity, qInflowControl, tuple))
|
||||
.collect(Collectors.toList());
|
||||
|
||||
long totalCount = jpaQueryFactory
|
||||
.select(qClientEntity.clientname.count())
|
||||
.from(qClientEntity)
|
||||
.where(qClientEntity.clientname.contains(searchName))
|
||||
.fetchOne();
|
||||
return new PageImpl<>(dtoList, pageable, totalCount);
|
||||
}
|
||||
|
||||
private InflowControlServiceDto toDto(QClientEntity qClientEntity, QInflowControl qInflowControl,
|
||||
Tuple tuple) {
|
||||
InflowControlServiceDto dto = null;
|
||||
if (tuple.get(qInflowControl) != null)
|
||||
dto = mapper.toDto(tuple.get(qInflowControl));
|
||||
else {
|
||||
dto = new InflowControlServiceDto();
|
||||
dto.setName(tuple.get(qClientEntity.clientid));
|
||||
dto.setType(CLIENT_TYPE_INFLOW);
|
||||
}
|
||||
dto.setDesc(tuple.get(qClientEntity.clientname));
|
||||
return dto;
|
||||
}
|
||||
|
||||
public Page<Tuple> selectLogList(Pageable pageable, InflowControlHistoryManUISearch uiSearch) {
|
||||
QInflowControlLog qlog = QInflowControlLog.inflowControlLog;
|
||||
QInflowControlGroup qgroup = QInflowControlGroup.inflowControlGroup;
|
||||
|
||||
+4
-4
@@ -120,11 +120,11 @@ public class ApiStatsDayService
|
||||
q.seq900BizErrCnt.sum().as("seq900BizErrCnt"),
|
||||
q.minRespTime.min().castToNum(BigDecimal.class).as("minRespTime"),
|
||||
q.maxRespTime.max().castToNum(BigDecimal.class).as("maxRespTime"),
|
||||
// 가중 평균: SUM(avg * successCnt) / SUM(successCnt)
|
||||
// 가중 평균: SUM(avg * totalCnt) / SUM(totalCnt)
|
||||
Expressions.cases()
|
||||
.when(q.successCnt.sum().gt(0))
|
||||
.then(q.avgRespTime.multiply(q.successCnt).sum()
|
||||
.divide(q.successCnt.sum()).castToNum(BigDecimal.class))
|
||||
.when(q.totalCnt.sum().gt(0))
|
||||
.then(q.avgRespTime.multiply(q.totalCnt).sum()
|
||||
.divide(q.totalCnt.sum()).castToNum(BigDecimal.class))
|
||||
.otherwise((BigDecimal) null)
|
||||
.as("avgRespTime")))
|
||||
.from(q)
|
||||
|
||||
+6
-7
@@ -211,11 +211,10 @@ public class ApiStatsHourService
|
||||
q.seq900BizErrCnt.sum().as("seq900BizErrCnt"),
|
||||
q.minRespTime.min().castToNum(BigDecimal.class).as("minRespTime"),
|
||||
q.maxRespTime.max().castToNum(BigDecimal.class).as("maxRespTime"),
|
||||
// 가중 평균: SUM(avg * successCnt) / SUM(successCnt)
|
||||
Expressions.cases()
|
||||
.when(q.successCnt.sum().gt(0))
|
||||
.then(q.avgRespTime.multiply(q.successCnt).sum()
|
||||
.divide(q.successCnt.sum()).castToNum(BigDecimal.class))
|
||||
.when(q.totalCnt.sum().gt(0))
|
||||
.then(q.avgRespTime.multiply(q.totalCnt).sum()
|
||||
.divide(q.totalCnt.sum()).castToNum(BigDecimal.class))
|
||||
.otherwise((BigDecimal) null)
|
||||
.as("avgRespTime")))
|
||||
.from(q)
|
||||
@@ -293,9 +292,9 @@ public class ApiStatsHourService
|
||||
q.minRespTime.min().castToNum(BigDecimal.class).as("minRespTime"),
|
||||
q.maxRespTime.max().castToNum(BigDecimal.class).as("maxRespTime"),
|
||||
Expressions.cases()
|
||||
.when(q.successCnt.sum().gt(0))
|
||||
.then(q.avgRespTime.multiply(q.successCnt).sum()
|
||||
.divide(q.successCnt.sum()).castToNum(BigDecimal.class))
|
||||
.when(q.totalCnt.sum().gt(0))
|
||||
.then(q.avgRespTime.multiply(q.totalCnt).sum()
|
||||
.divide(q.totalCnt.sum()).castToNum(BigDecimal.class))
|
||||
.otherwise((BigDecimal) null)
|
||||
.as("avgRespTime")))
|
||||
.from(q)
|
||||
|
||||
+4
-4
@@ -214,11 +214,11 @@ public class ApiStatsMinuteService
|
||||
q.seq900BizErrCnt.sum().as("seq900BizErrCnt"),
|
||||
q.minRespTime.min().castToNum(BigDecimal.class).as("minRespTime"),
|
||||
q.maxRespTime.max().castToNum(BigDecimal.class).as("maxRespTime"),
|
||||
// 가중 평균: SUM(avg * successCnt) / SUM(successCnt)
|
||||
// 가중 평균: SUM(avg * totalCnt) / SUM(totalCnt)
|
||||
Expressions.cases()
|
||||
.when(q.successCnt.sum().gt(0))
|
||||
.then(q.avgRespTime.multiply(q.successCnt).sum()
|
||||
.divide(q.successCnt.sum()).castToNum(BigDecimal.class))
|
||||
.when(q.totalCnt.sum().gt(0))
|
||||
.then(q.avgRespTime.multiply(q.totalCnt).sum()
|
||||
.divide(q.totalCnt.sum()).castToNum(BigDecimal.class))
|
||||
.otherwise((BigDecimal) null)
|
||||
.as("avgRespTime")))
|
||||
.from(q)
|
||||
|
||||
+7
-7
@@ -120,11 +120,11 @@ public class ApiStatsMonthService
|
||||
q.seq900BizErrCnt.sum().as("seq900BizErrCnt"),
|
||||
q.minRespTime.min().castToNum(BigDecimal.class).as("minRespTime"),
|
||||
q.maxRespTime.max().castToNum(BigDecimal.class).as("maxRespTime"),
|
||||
// 가중 평균: SUM(avg * successCnt) / SUM(successCnt)
|
||||
// 가중 평균: SUM(avg * totalCnt) / SUM(totalCnt)
|
||||
Expressions.cases()
|
||||
.when(q.successCnt.sum().gt(0))
|
||||
.then(q.avgRespTime.multiply(q.successCnt).sum()
|
||||
.divide(q.successCnt.sum()).castToNum(BigDecimal.class))
|
||||
.when(q.totalCnt.sum().gt(0))
|
||||
.then(q.avgRespTime.multiply(q.totalCnt).sum()
|
||||
.divide(q.totalCnt.sum()).castToNum(BigDecimal.class))
|
||||
.otherwise((BigDecimal) null)
|
||||
.as("avgRespTime")))
|
||||
.from(q)
|
||||
@@ -172,9 +172,9 @@ public class ApiStatsMonthService
|
||||
q.minRespTime.min().castToNum(BigDecimal.class).as("minRespTime"),
|
||||
q.maxRespTime.max().castToNum(BigDecimal.class).as("maxRespTime"),
|
||||
Expressions.cases()
|
||||
.when(q.successCnt.sum().gt(0))
|
||||
.then(q.avgRespTime.multiply(q.successCnt).sum()
|
||||
.divide(q.successCnt.sum()).castToNum(BigDecimal.class))
|
||||
.when(q.totalCnt.sum().gt(0))
|
||||
.then(q.avgRespTime.multiply(q.totalCnt).sum()
|
||||
.divide(q.totalCnt.sum()).castToNum(BigDecimal.class))
|
||||
.otherwise((BigDecimal) null)
|
||||
.as("avgRespTime")))
|
||||
.from(q)
|
||||
|
||||
+7
-7
@@ -117,11 +117,11 @@ public class ApiStatsYearService
|
||||
q.seq900BizErrCnt.sum().as("seq900BizErrCnt"),
|
||||
q.minRespTime.min().castToNum(BigDecimal.class).as("minRespTime"),
|
||||
q.maxRespTime.max().castToNum(BigDecimal.class).as("maxRespTime"),
|
||||
// 가중 평균: SUM(avg * successCnt) / SUM(successCnt)
|
||||
// 가중 평균: SUM(avg * totalCnt) / SUM(totalCnt)
|
||||
Expressions.cases()
|
||||
.when(q.successCnt.sum().gt(0))
|
||||
.then(q.avgRespTime.multiply(q.successCnt).sum()
|
||||
.divide(q.successCnt.sum()).castToNum(BigDecimal.class))
|
||||
.when(q.totalCnt.sum().gt(0))
|
||||
.then(q.avgRespTime.multiply(q.totalCnt).sum()
|
||||
.divide(q.totalCnt.sum()).castToNum(BigDecimal.class))
|
||||
.otherwise((BigDecimal) null)
|
||||
.as("avgRespTime")))
|
||||
.from(q)
|
||||
@@ -169,9 +169,9 @@ public class ApiStatsYearService
|
||||
q.minRespTime.min().castToNum(BigDecimal.class).as("minRespTime"),
|
||||
q.maxRespTime.max().castToNum(BigDecimal.class).as("maxRespTime"),
|
||||
Expressions.cases()
|
||||
.when(q.successCnt.sum().gt(0))
|
||||
.then(q.avgRespTime.multiply(q.successCnt).sum()
|
||||
.divide(q.successCnt.sum()).castToNum(BigDecimal.class))
|
||||
.when(q.totalCnt.sum().gt(0))
|
||||
.then(q.avgRespTime.multiply(q.totalCnt).sum()
|
||||
.divide(q.totalCnt.sum()).castToNum(BigDecimal.class))
|
||||
.otherwise((BigDecimal) null)
|
||||
.as("avgRespTime")))
|
||||
.from(q)
|
||||
|
||||
+8
-1
@@ -5,7 +5,9 @@ import java.util.List;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.springframework.data.domain.Page;
|
||||
import org.springframework.data.domain.PageRequest;
|
||||
import org.springframework.data.domain.Pageable;
|
||||
import org.springframework.data.domain.Sort;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
@@ -42,7 +44,12 @@ public class ExtendedColumnDefinitionService
|
||||
predicate = predicate.and(qExtendedColumnDefinition.columnDesc.containsIgnoreCase(searchColumnDesc));
|
||||
}
|
||||
|
||||
return repository.findAll(predicate, pageable);
|
||||
Pageable orderedPageable = PageRequest.of(
|
||||
pageable.getPageNumber(),
|
||||
pageable.getPageSize(),
|
||||
Sort.by(Sort.Order.asc("orderSeq"), Sort.Order.asc("columnName")));
|
||||
|
||||
return repository.findAll(predicate, orderedPageable);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,69 +0,0 @@
|
||||
package com.eactive.eai.rms.data.ext.djb.apistatus;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.ApplicationEventPublisher;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import com.eactive.eai.rms.ext.djb.event.ApiStatusChangedEvent;
|
||||
|
||||
@Service
|
||||
@Transactional
|
||||
public class ApiStatusService {
|
||||
|
||||
private static final Logger log = LoggerFactory.getLogger(ApiStatusService.class);
|
||||
|
||||
@Autowired
|
||||
private ApiStatusRepository apiStatusRepository;
|
||||
|
||||
@Autowired
|
||||
private ApplicationEventPublisher eventPublisher;
|
||||
|
||||
|
||||
|
||||
public void updateApiStatus(HashMap<String, String> param) {
|
||||
|
||||
List<ApiStatusEvent> list = apiStatusRepository.findApiStatusEvents(
|
||||
Integer.parseInt(param.get("errorRate")),
|
||||
Integer.parseInt(param.get("errorRangeMinute")),
|
||||
Integer.parseInt(param.get("delayRangeMinute")),
|
||||
Integer.parseInt(param.get("delayAvgRespTime"))
|
||||
);
|
||||
|
||||
for (ApiStatusEvent event : list) {
|
||||
String newStatusCode = resolveStatusCode(event.getEvent());
|
||||
if (newStatusCode == null) continue;
|
||||
|
||||
ApiStatus apiStatus = apiStatusRepository.findById(event.getEaisvcname()).orElse(new ApiStatus());
|
||||
|
||||
apiStatus.setEaisvcname(event.getEaisvcname());
|
||||
apiStatus.setStatusCode(newStatusCode);
|
||||
|
||||
apiStatusRepository.save(apiStatus); // PK 있으면 UPDATE, 없으면 INSERT
|
||||
log.debug("API 상태 변경: {}-{} {} → {}", event.getEaisvcname(), event.getEaisvcdesc(), event.getEvent(), newStatusCode);
|
||||
|
||||
eventPublisher.publishEvent(ApiStatusChangedEvent.from(event)); // 트랜잭션 커밋 후 리스너 실행
|
||||
log.debug("이벤트 전파");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private String resolveStatusCode(String event) {
|
||||
switch (event) {
|
||||
case "CONTROL_START": return "C"; //점검
|
||||
case "CONTROL_END": return "N"; //정상
|
||||
case "ERROR_START": return "E"; //장애
|
||||
case "ERROR_END": return "N"; //정상
|
||||
case "DELAY_START": return "D"; //지연
|
||||
case "DELAY_END": return "N"; //정상
|
||||
default:
|
||||
log.warn("알 수 없는 이벤트: {}", event);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
package com.eactive.eai.rms.ext.djb.apistatus;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
@Service
|
||||
@Transactional
|
||||
public class ApiStatusDetectionService {
|
||||
|
||||
private static final Logger log = LoggerFactory.getLogger(ApiStatusDetectionService.class);
|
||||
|
||||
/**
|
||||
* 이벤트 감지
|
||||
* @param event "CONTROL_START" : 점검시작
|
||||
"CONTROL_END" : 점검종료
|
||||
"ERROR_START" : 장애시작
|
||||
"ERROR_END" : 장애종료
|
||||
"DELAY_START" : 지연시작
|
||||
"DELAY_END" : 지연종료
|
||||
* @param apiIds API IDs
|
||||
*/
|
||||
public void detect(String event, List<String> apiIds) {
|
||||
log.debug("이벤트 감지: {} {}", event, apiIds);
|
||||
}
|
||||
|
||||
// API FSM 내 아직 장애로 남아있는 API 목록
|
||||
public void remainDownApiIds() {
|
||||
|
||||
}
|
||||
|
||||
|
||||
// 정상 동작 - detect()에서 일괄 처리 (복구 = 점검종료, 장애종료, 지연종료)
|
||||
// public void recovered(String event, String[] apiIds) {
|
||||
//
|
||||
// }
|
||||
|
||||
}
|
||||
+12
-8
@@ -1,10 +1,12 @@
|
||||
package com.eactive.eai.rms.data.ext.djb.apistatus;
|
||||
package com.eactive.eai.rms.ext.djb.apistatus;
|
||||
import java.util.List;
|
||||
|
||||
import org.springframework.data.jpa.repository.Query;
|
||||
import org.springframework.data.repository.query.Param;
|
||||
|
||||
import com.eactive.eai.data.jpa.BaseRepository;
|
||||
import com.eactive.eai.rms.data.entity.onl.djb.apistatus.ApiStatus;
|
||||
import com.eactive.eai.rms.data.entity.onl.djb.apistatus.ApiStatusEvent;
|
||||
|
||||
public interface ApiStatusRepository extends BaseRepository<ApiStatus, String> {
|
||||
|
||||
@@ -22,9 +24,11 @@ public interface ApiStatusRepository extends BaseRepository<ApiStatus, String> {
|
||||
@Query(nativeQuery = true, value =
|
||||
" SELECT EAISVCNAME"
|
||||
+ " , (SELECT EAISVCDESC FROM TSEAIHE01 WHERE A.EAISVCNAME = EAISVCNAME) AS EAISVCDESC"
|
||||
+ " , PREV_STATUS_CODE "
|
||||
+ " , EVENT"
|
||||
+ " FROM ("
|
||||
+ " SELECT EAISVCNAME"
|
||||
+ " , STATUS_CODE AS PREV_STATUS_CODE "
|
||||
+ " , CASE WHEN STATUS_CODE != 'C' AND CTRL_YN = 'Y' THEN 'CONTROL_START'"
|
||||
+ " WHEN STATUS_CODE = 'C' AND CTRL_YN = 'N' THEN 'CONTROL_END'"
|
||||
+ " WHEN STATUS_CODE IN ('N','D') AND ERR_YN = 'Y' THEN 'ERROR_START'"
|
||||
@@ -34,14 +38,14 @@ public interface ApiStatusRepository extends BaseRepository<ApiStatus, String> {
|
||||
+ " ELSE 'STAY' END AS EVENT"
|
||||
+ " FROM ("
|
||||
+ " SELECT A.EAISVCNAME"
|
||||
+ " , NVL(B.STATUS_CODE, '1') AS STATUS_CODE"
|
||||
+ " , NVL((SELECT 'Y' FROM TSEAITI01"
|
||||
+ " WHERE (TO_CHAR(SYSDATE,'HH24MI') BETWEEN SUBSTR(EAICTRLDSTICCTNT,16,4) AND SUBSTR(EAICTRLDSTICCTNT,21,4)"
|
||||
+ " OR SUBSTR(EAICTRLDSTICCTNT,16,9) = '0000|0000')"
|
||||
+ " AND A.EAISVCNAME = EAICTRLNAME),'N') AS CTRL_YN"
|
||||
+ " , NVL(B.STATUS_CODE, 'N') AS STATUS_CODE"
|
||||
+ " , NVL(( SELECT MAX('Y') FROM EMSAPP.DJB_APISTATUS_INCIDENT_API X"
|
||||
+ " WHERE EXISTS (SELECT 1 FROM EMSAPP.DJB_APISTATUS_INCIDENT Y WHERE X.INCIDENT_ID = Y.INCIDENT_ID "
|
||||
+ " AND SYSTIMESTAMP BETWEEN STARTED_AT AND END_AT) "
|
||||
+ " AND A.EAISVCNAME = X.API_ID),'N') AS CTRL_YN "
|
||||
+ " , (SELECT CASE WHEN TOTAL = 0 THEN 'X'"
|
||||
+ " WHEN (TOTAL - SUCCESS) * 100 / TOTAL > :errorRate THEN 'Y'"
|
||||
+ " ELSE 'N' END"
|
||||
+ " WHEN (TOTAL - SUCCESS) * 100 / TOTAL > :errorRate THEN 'Y'"
|
||||
+ " ELSE 'N' END"
|
||||
+ " FROM (SELECT SUM(SUCCESS_CNT + TIMEOUT_CNT + SYSTEM_ERR_CNT + BIZ_ERR_CNT) AS TOTAL"
|
||||
+ " , SUM(SUCCESS_CNT) AS SUCCESS"
|
||||
+ " FROM API_STATS_MINUTE"
|
||||
@@ -0,0 +1,133 @@
|
||||
package com.eactive.eai.rms.ext.djb.apistatus;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import com.eactive.apim.portal.template.entity.MessageCode;
|
||||
import com.eactive.eai.rms.data.entity.man.role.Role;
|
||||
import com.eactive.eai.rms.data.entity.onl.djb.apistatus.ApiStatus;
|
||||
import com.eactive.eai.rms.data.entity.onl.djb.apistatus.ApiStatusEvent;
|
||||
import com.eactive.eai.rms.ext.djb.ums.UmsManager;
|
||||
|
||||
|
||||
@Service
|
||||
@Transactional
|
||||
public class ApiStatusService {
|
||||
|
||||
private static final Logger log = LoggerFactory.getLogger(ApiStatusService.class);
|
||||
|
||||
@Autowired
|
||||
private ApiStatusRepository apiStatusRepository;
|
||||
|
||||
@Autowired
|
||||
private UmsManager ums;
|
||||
|
||||
@Autowired
|
||||
private ApiStatusDetectionService apiStatusDectionService;
|
||||
|
||||
|
||||
public void updateApiStatus(HashMap<String, String> param) {
|
||||
|
||||
List<ApiStatusEvent> list = apiStatusRepository.findApiStatusEvents(
|
||||
Integer.parseInt(param.get("errorRate")),
|
||||
Integer.parseInt(param.get("errorRangeMinute")),
|
||||
Integer.parseInt(param.get("delayRangeMinute")),
|
||||
Integer.parseInt(param.get("delayAvgRespTime"))
|
||||
);
|
||||
|
||||
HashMap<String, List<String>> eventMap = new HashMap<>();
|
||||
|
||||
|
||||
for (ApiStatusEvent event : list) {
|
||||
String newStatusCode = resolveStatusCode(event.getEvent());
|
||||
if (newStatusCode == null) continue;
|
||||
|
||||
ApiStatus apiStatus = apiStatusRepository.findById(event.getEaisvcname()).orElse(new ApiStatus());
|
||||
|
||||
apiStatus.setEaisvcname(event.getEaisvcname());
|
||||
apiStatus.setStatusCode(newStatusCode);
|
||||
|
||||
apiStatusRepository.save(apiStatus); // PK 있으면 UPDATE, 없으면 INSERT
|
||||
log.debug("API 상태 변경: {}-{} {} → {}", event.getEaisvcname(), event.getEaisvcdesc(), event.getEvent(), newStatusCode);
|
||||
|
||||
|
||||
//이벤트별로 api분리 저장
|
||||
List<String> apis = (List<String>)eventMap.get(event.getEvent());
|
||||
if (apis == null) {
|
||||
apis = new ArrayList<String>();
|
||||
apis.add(event.getEaisvcname());
|
||||
eventMap.put(event.getEvent(), apis);
|
||||
} else {
|
||||
apis.add(event.getEaisvcname());
|
||||
}
|
||||
}
|
||||
|
||||
//이벤트별로 ums, webhook을 발송한다
|
||||
for (Map.Entry<String, List<String>> entry : eventMap.entrySet()) {
|
||||
String event = entry.getKey();
|
||||
List<String> apiIds = entry.getValue();
|
||||
|
||||
String message = getSwingChatMessage(event, apiIds);
|
||||
ums.sendMessenger("api-monitor", MessageCode.API_STATUS_CHANGED, message);
|
||||
|
||||
//제휴사 웹훅 발송
|
||||
ums.sendWebhook(event, apiIds);
|
||||
|
||||
//개발자포탈 API상태모니터링 화면을 위한 처리
|
||||
apiStatusDectionService.detect(event, apiIds);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private String resolveStatusCode(String event) {
|
||||
switch (event) {
|
||||
case "CONTROL_START": return "C"; //점검
|
||||
case "CONTROL_END": return "N"; //정상
|
||||
case "ERROR_START": return "E"; //장애
|
||||
case "ERROR_END": return "N"; //정상
|
||||
case "DELAY_START": return "D"; //지연
|
||||
case "DELAY_END": return "N"; //정상
|
||||
default:
|
||||
log.warn("알 수 없는 이벤트: {}", event);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private String getSwingChatMessage(String event, List<String> apiIds) {
|
||||
String message = "";
|
||||
switch (event) {
|
||||
case "CONTROL_START":
|
||||
message = "API 서비스 점검이 시작되었습니다"; //점검
|
||||
break;
|
||||
case "CONTROL_END":
|
||||
message = "API 서비스 점검이 완료되었습니다"; //정상
|
||||
break;
|
||||
case "ERROR_START":
|
||||
message = "API 서비스 장애가 발생하였습니다"; //장애
|
||||
break;
|
||||
case "ERROR_END":
|
||||
message = "API 서비스 장애가 복구되었습니다"; //정상
|
||||
break;
|
||||
case "DELAY_START":
|
||||
message = "API 서비스 지연이 발생하였습니다"; //지연
|
||||
break;
|
||||
case "DELAY_END":
|
||||
message = "API 서비스 지연이 복구되었습니다"; //정상
|
||||
break;
|
||||
default:
|
||||
message = "";
|
||||
}
|
||||
|
||||
return message + "\n- " + String.join(",", apiIds);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,25 +0,0 @@
|
||||
package com.eactive.eai.rms.ext.djb.async;
|
||||
|
||||
import java.util.concurrent.Executor;
|
||||
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.scheduling.annotation.EnableAsync;
|
||||
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
|
||||
|
||||
@Configuration
|
||||
@EnableAsync
|
||||
public class AsyncConfig {
|
||||
|
||||
@Bean("asyncExecutor")
|
||||
public Executor asyncExecutor() {
|
||||
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
|
||||
executor.setCorePoolSize(2);
|
||||
executor.setMaxPoolSize(5);
|
||||
executor.setQueueCapacity(20);
|
||||
executor.setThreadNamePrefix("async-");
|
||||
executor.initialize();
|
||||
return executor;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,40 +0,0 @@
|
||||
package com.eactive.eai.rms.ext.djb.async;
|
||||
|
||||
import org.springframework.context.event.EventListener;
|
||||
import org.springframework.scheduling.annotation.Async;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.transaction.event.TransactionPhase;
|
||||
import org.springframework.transaction.event.TransactionalEventListener;
|
||||
|
||||
import com.eactive.eai.rms.ext.djb.event.ApiStatusChangedEvent;
|
||||
import com.eactive.eai.rms.ext.djb.event.InflowTokenInsufficientEvent;
|
||||
import com.eactive.eai.rms.ext.djb.swing.service.SwingSendManager;
|
||||
import com.eactive.eai.rms.ext.djb.webhook.service.WebhookSendManager;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
@Slf4j
|
||||
@Component
|
||||
@RequiredArgsConstructor
|
||||
public class AsyncEventListener {
|
||||
|
||||
private final WebhookSendManager webhookSendManager;
|
||||
|
||||
private final SwingSendManager swingSendManager;
|
||||
|
||||
@Async("asyncExecutor")
|
||||
@TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT)
|
||||
public void onApiStatusChanged(ApiStatusChangedEvent event) {
|
||||
log.debug("API 상태 변경 이벤트 수신: {} {}", event.getEaisvcname(), event.getEvent());
|
||||
webhookSendManager.send(event.getEaisvcname(), event.getEvent());
|
||||
swingSendManager.send(null, 0, 0);
|
||||
}
|
||||
|
||||
@Async("asyncExecutor")
|
||||
@EventListener // 트랜잭션 write 없이 read만 하므로 TransactionalEventListener 불필요
|
||||
public void onInflowTokenFail(InflowTokenInsufficientEvent event) {
|
||||
log.debug("유량제어 토큰획득 실패 이벤트 수신: {} {}건", event.getEaisvcname(), event.getCnt());
|
||||
swingSendManager.send(event.getEaisvcname(), event.getCnt(), event.getRangeMinute());
|
||||
}
|
||||
}
|
||||
@@ -1,18 +0,0 @@
|
||||
package com.eactive.eai.rms.ext.djb.event;
|
||||
|
||||
import com.eactive.eai.rms.data.ext.djb.apistatus.ApiStatusEvent;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
|
||||
@Getter
|
||||
@RequiredArgsConstructor
|
||||
public class ApiStatusChangedEvent {
|
||||
private final String eaisvcname;
|
||||
private final String eaisvcdesc;
|
||||
private final String event;
|
||||
|
||||
public static ApiStatusChangedEvent from(ApiStatusEvent source) {
|
||||
return new ApiStatusChangedEvent(source.getEaisvcname(), source.getEaisvcdesc(), source.getEvent());
|
||||
}
|
||||
}
|
||||
@@ -1,19 +0,0 @@
|
||||
package com.eactive.eai.rms.ext.djb.event;
|
||||
|
||||
import com.eactive.eai.rms.data.ext.djb.inflow.InflowTokenInsufficient;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
|
||||
@Getter
|
||||
@RequiredArgsConstructor
|
||||
public class InflowTokenInsufficientEvent {
|
||||
private final String eaisvcname;
|
||||
private final String eaisvcdesc;
|
||||
private final long cnt;
|
||||
private final long rangeMinute;
|
||||
|
||||
public static InflowTokenInsufficientEvent from(InflowTokenInsufficient summary, long rangeMinute) {
|
||||
return new InflowTokenInsufficientEvent(summary.getEaisvcname(), summary.getEaisvcdesc(), summary.getCnt(), rangeMinute);
|
||||
}
|
||||
}
|
||||
+4
-1
@@ -1,9 +1,12 @@
|
||||
package com.eactive.eai.rms.data.ext.djb.inflow;
|
||||
package com.eactive.eai.rms.ext.djb.inflow;
|
||||
|
||||
import java.util.List;
|
||||
import org.springframework.data.jpa.repository.Query;
|
||||
import org.springframework.data.repository.query.Param;
|
||||
import com.eactive.eai.data.jpa.BaseRepository;
|
||||
import com.eactive.eai.rms.data.entity.onl.djb.inflow.InflowTokenInsufficient;
|
||||
import com.eactive.eai.rms.data.entity.onl.djb.inflow.InflowTokenInsufficientLog;
|
||||
import com.eactive.eai.rms.data.entity.onl.djb.inflow.InflowTokenInsufficientLogId;
|
||||
|
||||
public interface InflowTokenRepository extends BaseRepository<InflowTokenInsufficientLog, InflowTokenInsufficientLogId> {
|
||||
|
||||
+9
-7
@@ -1,15 +1,17 @@
|
||||
package com.eactive.eai.rms.data.ext.djb.inflow;
|
||||
package com.eactive.eai.rms.ext.djb.inflow;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.ApplicationEventPublisher;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import com.eactive.eai.rms.ext.djb.event.InflowTokenInsufficientEvent;
|
||||
import com.eactive.apim.portal.template.entity.MessageCode;
|
||||
import com.eactive.eai.rms.data.entity.man.role.Role;
|
||||
import com.eactive.eai.rms.data.entity.onl.djb.inflow.InflowTokenInsufficient;
|
||||
import com.eactive.eai.rms.ext.djb.ums.UmsManager;
|
||||
|
||||
|
||||
@Service
|
||||
@@ -22,16 +24,16 @@ public class InflowTokenService {
|
||||
private InflowTokenRepository inflowTokenRepository;
|
||||
|
||||
@Autowired
|
||||
private ApplicationEventPublisher eventPublisher;
|
||||
private UmsManager ums;
|
||||
|
||||
public void checkRecentFails(long rangeMinute) {
|
||||
List<InflowTokenInsufficient> rows = inflowTokenRepository.countTokenInsufficient(rangeMinute);
|
||||
|
||||
for (InflowTokenInsufficient info : rows) {
|
||||
log.debug("유량제어 토큰 획득 실패: {}-{} 최근 {}분 동안 {}건", info.getEaisvcname(), info.getEaisvcdesc(), rangeMinute, info.getCnt());
|
||||
|
||||
//내부직원 알림 발송
|
||||
eventPublisher.publishEvent(InflowTokenInsufficientEvent.from(info, rangeMinute));
|
||||
String message = "유량제어 토큰 획득 실패하였습니다 \n" + info.getEaisvcname();
|
||||
|
||||
ums.sendMessenger("api-monitor", MessageCode.INFLOW_TOKEN_FAILED, message);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,143 @@
|
||||
package com.eactive.eai.rms.ext.djb.job;
|
||||
|
||||
import java.time.LocalDate;
|
||||
import java.time.LocalTime;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
|
||||
import javax.persistence.EntityManager;
|
||||
import javax.persistence.PersistenceContext;
|
||||
|
||||
import org.quartz.Job;
|
||||
import org.quartz.JobExecutionContext;
|
||||
import org.quartz.JobExecutionException;
|
||||
import org.quartz.SchedulerException;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import com.eactive.eai.rms.common.datasource.DataSourceContextHolder;
|
||||
import com.eactive.eai.rms.common.datasource.DataSourceType;
|
||||
import com.eactive.eai.rms.common.datasource.DataSourceTypeManager;
|
||||
import com.eactive.eai.rms.common.util.CommonUtil;
|
||||
import com.eactive.eai.rms.data.entity.onl.kjb.statistics.ApiStatsHourService;
|
||||
import com.eactive.eai.rms.data.entity.onl.kjb.statistics.QApiStatsHour;
|
||||
|
||||
/**
|
||||
* API 로그 테이블을 시간별 통계로 집계하는 배치 작업
|
||||
* TSEAILGXX → API_STATS_HOUR
|
||||
* 실행 주기: 매시 00:10 (당일 데이터 집계)
|
||||
*/
|
||||
@Component
|
||||
public class ApiStatsHourlyAggregationJob implements Job {
|
||||
private static final Logger log = LoggerFactory.getLogger(ApiStatsHourlyAggregationJob.class);
|
||||
|
||||
@PersistenceContext
|
||||
private EntityManager entityManager;
|
||||
|
||||
private ApiStatsHourService apiStatsHourService;
|
||||
|
||||
@Autowired
|
||||
public void setApiStatsHourService(ApiStatsHourService apiStatsHourService) {
|
||||
this.apiStatsHourService = apiStatsHourService;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute(JobExecutionContext context) throws JobExecutionException {
|
||||
ApplicationContext appContext = null;
|
||||
final ApiStatsHourlyAggregationJob selfJob;
|
||||
try {
|
||||
appContext = (ApplicationContext) context.getScheduler().getContext().get("applicationContext");
|
||||
selfJob = appContext.getBean(ApiStatsHourlyAggregationJob.class);
|
||||
} catch (SchedulerException e) {
|
||||
log.error("applicationContext get module error", e);
|
||||
return;
|
||||
}
|
||||
|
||||
DataSourceType dataType = DataSourceTypeManager.getDataSourceType(DataSourceTypeManager.APIGW);
|
||||
DataSourceContextHolder.setDataSourceType(dataType);
|
||||
try {
|
||||
LocalDate targetDate = LocalDate.now();
|
||||
if (LocalTime.now().isBefore(LocalTime.of(1, 0))) {
|
||||
targetDate = targetDate.minusDays(1);
|
||||
}
|
||||
selfJob.executeManual(targetDate);
|
||||
} catch (Exception e) {
|
||||
throw new JobExecutionException(e);
|
||||
} finally {
|
||||
DataSourceContextHolder.clearDataSourceType();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 수동 실행 메서드 (날짜 지정)
|
||||
*/
|
||||
@Transactional
|
||||
public int executeManual(LocalDate targetDate) {
|
||||
long startTime = System.currentTimeMillis();
|
||||
log.info("=== 시간별 통계 집계 작업 시작 === 대상: {}", targetDate);
|
||||
|
||||
String searchDate = targetDate.format(DateTimeFormatter.ofPattern("yyyyMMdd"));
|
||||
String logTableName = CommonUtil.getLogTable(searchDate, true);
|
||||
|
||||
// 1. 기존 데이터 삭제
|
||||
QApiStatsHour q = QApiStatsHour.apiStatsHour;
|
||||
long deletedCount = apiStatsHourService.getJPAQueryFactory()
|
||||
.delete(q)
|
||||
.where(q.statTime.goe(targetDate.atStartOfDay())
|
||||
.and(q.statTime.lt(targetDate.plusDays(1).atStartOfDay())))
|
||||
.execute();
|
||||
log.info("기존 데이터 삭제: {} 건", deletedCount);
|
||||
|
||||
// 2. 집계 및 데이터 입력
|
||||
/**
|
||||
* EAISVCSERNO로 그룹핑하여 1행으로 집계를 한 다음 (한거래 안에서 adapter가 여러개 있을 경우 seq=100의 adapter를 기준)
|
||||
* 총건수 : EAISVCSERNO로 그룹핑한 총 건수
|
||||
* 성공건수 : seq = 400 and EAIERRCD = null 인 건수
|
||||
* Timeout : EAIERRCD in (공통코드 CODEGROUP = 'ERRCODE_TIMEOUT') 인 건수
|
||||
* 시스템오류 : (seq400 = null or EAIERRCD is not null) and 공통코드(ERRCODE_TIMEOUT)에 정의되지 않은 errorcode 인 건수
|
||||
*/
|
||||
String sql =
|
||||
"INSERT INTO API_STATS_HOUR" +
|
||||
" (STAT_TIME, API_NAME, GW_INSTANCE_ID, BIZ_DIV_CODE, CLIENT_ID" +
|
||||
" , INBOUND_ADAPTER, OUTBOUND_ADAPTER" +
|
||||
", TOTAL_CNT, SUCCESS_CNT, TIMEOUT_CNT, SYSTEM_ERR_CNT, BIZ_ERR_CNT" +
|
||||
", SEQ900_TIMEOUT_CNT, SEQ900_SYSTEM_ERR_CNT, SEQ900_BIZ_ERR_CNT" +
|
||||
", AVG_RESP_TIME, MIN_RESP_TIME, MAX_RESP_TIME, P50_RESP_TIME, P95_RESP_TIME)" +
|
||||
" SELECT TO_TIMESTAMP(SUBSTR(DT,1,10) || '0000', 'YYYYMMDDHH24MISS')" +
|
||||
" , API_NAME, GW_INSTANCE_ID, BIZ_DIV_CODE, NVL(A.CLIENT_ID, 'NONE')" +
|
||||
" , INBOUND_ADAPTER, OUTBOUND_ADAPTER" +
|
||||
" , COUNT(EAISVCSERNO)" +
|
||||
" , SUM(CASE WHEN A.ERROR_CODE IS NULL AND E400 IS NOT NULL THEN 1 ELSE 0 END)" +
|
||||
" , SUM(CASE WHEN B.CODE IS NOT NULL THEN 1 ELSE 0 END)" +
|
||||
" , SUM(CASE WHEN (A.E400 IS NULL OR A.ERROR_CODE IS NOT NULL) AND B.CODE IS NULL THEN 1 ELSE 0 END)" +
|
||||
" , 0, 0, 0, 0" +
|
||||
" , TRUNC(AVG(RESP_TIME)), MIN(RESP_TIME), MAX(RESP_TIME), 0, 0" +
|
||||
" FROM (" +
|
||||
" SELECT EAISVCSERNO" +
|
||||
" , MAX(EAISVCNAME) AS API_NAME, MAX(EAISEVRINSTNCNAME) AS GW_INSTANCE_ID" +
|
||||
" , MAX(EAIBZWKDSTCD) AS BIZ_DIV_CODE, MAX(CLIENTID) AS CLIENT_ID" +
|
||||
" , MAX(CASE WHEN LOGPRCSSSERNO = '100' THEN GSTATSYSADPTRBZWKGROUPNAME ELSE '' END) AS INBOUND_ADAPTER" +
|
||||
" , MAX(CASE WHEN LOGPRCSSSERNO = '100' THEN PSVSYSADPTRBZWKGROUPNAME ELSE '' END) AS OUTBOUND_ADAPTER" +
|
||||
" , MIN(MSGDPSTYMS) AS DT" +
|
||||
" , MAX(MSGPRCSSYMS) - MIN(MSGDPSTYMS) AS RESP_TIME" +
|
||||
" , MAX(CASE WHEN LOGPRCSSSERNO = '400' THEN LOGPRCSSSERNO ELSE '' END) AS E400" +
|
||||
" , MAX(EAIERRCD) AS ERROR_CODE" +
|
||||
" FROM " + logTableName +
|
||||
" WHERE MSGDPSTYMS LIKE :searchDate || '%'" +
|
||||
" GROUP BY EAISVCSERNO" +
|
||||
" ) A LEFT OUTER JOIN TSEAICM20 B ON A.ERROR_CODE = B.CODE AND B.CODEGROUP = 'ERRCODE_TIMEOUT' AND B.USEYN = 'Y' " +
|
||||
" GROUP BY SUBSTR(DT,1,10), API_NAME, GW_INSTANCE_ID, BIZ_DIV_CODE, NVL(A.CLIENT_ID, 'NONE')" +
|
||||
" , INBOUND_ADAPTER, OUTBOUND_ADAPTER";
|
||||
|
||||
int savedCount = entityManager.createNativeQuery(sql)
|
||||
.setParameter("searchDate", searchDate)
|
||||
.executeUpdate();
|
||||
|
||||
long elapsedTime = System.currentTimeMillis() - startTime;
|
||||
log.info("=== 집계 완료 === (처리 건수: {}, 소요 시간: {}ms)", savedCount, elapsedTime);
|
||||
return savedCount;
|
||||
}
|
||||
}
|
||||
@@ -17,7 +17,7 @@ import com.eactive.eai.rms.common.context.MonitoringContext;
|
||||
import com.eactive.eai.rms.common.datasource.DataSourceContextHolder;
|
||||
import com.eactive.eai.rms.common.datasource.DataSourceTypeManager;
|
||||
import com.eactive.eai.rms.common.util.CommonUtil;
|
||||
import com.eactive.eai.rms.data.ext.djb.apistatus.ApiStatusService;
|
||||
import com.eactive.eai.rms.ext.djb.apistatus.ApiStatusService;
|
||||
import com.eactive.eai.rms.onl.common.util.DateUtil;
|
||||
|
||||
/**
|
||||
@@ -88,11 +88,11 @@ public class ApiStatusMonitorJob implements Job {
|
||||
monitoringContext = (MonitoringContext) appContext.getBean("monitoringContext");
|
||||
ApiStatusService apiStatusUpdateService = appContext.getBean(ApiStatusService.class);
|
||||
|
||||
|
||||
DataSourceContextHolder.setDataSourceType(
|
||||
DataSourceTypeManager.getDataSourceType(DataSourceTypeManager.APIGW));
|
||||
try {
|
||||
apiStatusUpdateService.updateApiStatus(param);
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("ApiStatusUpdateJob execution failed", e);
|
||||
throw new JobExecutionException(e);
|
||||
|
||||
@@ -18,7 +18,7 @@ import com.eactive.eai.rms.common.context.MonitoringContext;
|
||||
import com.eactive.eai.rms.common.datasource.DataSourceContextHolder;
|
||||
import com.eactive.eai.rms.common.datasource.DataSourceTypeManager;
|
||||
import com.eactive.eai.rms.common.util.CommonUtil;
|
||||
import com.eactive.eai.rms.data.ext.djb.inflow.InflowTokenService;
|
||||
import com.eactive.eai.rms.ext.djb.inflow.InflowTokenService;
|
||||
import com.eactive.eai.rms.onl.common.util.DateUtil;
|
||||
|
||||
/**
|
||||
|
||||
@@ -0,0 +1,73 @@
|
||||
package com.eactive.eai.rms.ext.djb.job;
|
||||
|
||||
import com.eactive.eai.rms.common.datasource.DataSourceContextHolder;
|
||||
import com.eactive.eai.rms.common.datasource.DataSourceTypeManager;
|
||||
import com.eactive.eai.rms.common.util.CommonUtil;
|
||||
import com.eactive.eai.rms.onl.apim.portalInquiry.PortalInquiryClosingService;
|
||||
import com.eactive.eai.rms.onl.common.util.DateUtil;
|
||||
import org.quartz.DisallowConcurrentExecution;
|
||||
import org.quartz.Job;
|
||||
import org.quartz.JobExecutionContext;
|
||||
import org.quartz.JobExecutionException;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
|
||||
/**
|
||||
* 매일 00:05 실행 - RESPONDED 상태 문의글 자동 종료 배치
|
||||
*
|
||||
* <p>종료 조건 (기본 7일):</p>
|
||||
* <ul>
|
||||
* <li>답변완료 후 댓글이 7일간 없는 경우</li>
|
||||
* <li>관리자 댓글 등록 후 7일간 포탈사용자 댓글이 없는 경우</li>
|
||||
* </ul>
|
||||
*
|
||||
* <p>Cron: 0 5 0 * * ?</p>
|
||||
*
|
||||
* <p>Job 파라미터 (JobDataMap):</p>
|
||||
* <ul>
|
||||
* <li>{@value #KEY_INQUIRY_COMMENT_CLOSING_DAY}: 종료 기준 일수 (기본값: 7)</li>
|
||||
* </ul>
|
||||
*/
|
||||
@DisallowConcurrentExecution
|
||||
public class PortalInquiryClosingJob implements Job {
|
||||
|
||||
private static final Logger log = LoggerFactory.getLogger(PortalInquiryClosingJob.class);
|
||||
|
||||
/** Job 파라미터 키: 댓글종료 기간(일) */
|
||||
public static final String KEY_INQUIRY_COMMENT_CLOSING_DAY = "inquiry.comment.closing_day";
|
||||
|
||||
private static final int DEFAULT_CLOSING_DAYS = 7;
|
||||
|
||||
@Autowired
|
||||
private PortalInquiryClosingService inquiryCommentClosingService;
|
||||
|
||||
@Override
|
||||
public void execute(JobExecutionContext context) throws JobExecutionException {
|
||||
log.info("*** START PortalInquiryCommentClosingService run({})", CommonUtil.getToday("yyyy-MM-dd HH:mm"));
|
||||
|
||||
int closingDays = DEFAULT_CLOSING_DAYS;
|
||||
String closingDayParam = context.getMergedJobDataMap().getString(KEY_INQUIRY_COMMENT_CLOSING_DAY);
|
||||
if (closingDayParam != null) {
|
||||
try {
|
||||
closingDays = Integer.parseInt(closingDayParam);
|
||||
} catch (NumberFormatException e) {
|
||||
log.warn("잘못된 {} 파라미터 값: '{}', 기본값 {}일 사용", KEY_INQUIRY_COMMENT_CLOSING_DAY, closingDayParam, DEFAULT_CLOSING_DAYS);
|
||||
}
|
||||
}
|
||||
|
||||
DataSourceContextHolder.setDataSourceType(
|
||||
DataSourceTypeManager.getDataSourceType(DataSourceTypeManager.APIGW));
|
||||
try {
|
||||
int closedCount = inquiryCommentClosingService.closeExpiredInquiries(closingDays);
|
||||
log.info("PortalInquiryCommentClosingService 완료: {}건 CLOSED 처리", closedCount);
|
||||
} catch (Exception e) {
|
||||
log.error("PortalInquiryCommentClosingService execution failed", e);
|
||||
throw new JobExecutionException(e);
|
||||
} finally {
|
||||
DataSourceContextHolder.clearDataSourceType();
|
||||
}
|
||||
|
||||
log.info("*** END PortalInquiryCommentClosingService run({})", DateUtil.getDateTime("yyyy-MM-dd HH:mm"));
|
||||
}
|
||||
}
|
||||
@@ -1,29 +1,20 @@
|
||||
package com.eactive.eai.rms.ext.djb.statistics;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.time.LocalDate;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.springframework.data.domain.Page;
|
||||
import org.springframework.data.domain.PageRequest;
|
||||
import org.springframework.data.domain.Pageable;
|
||||
import org.springframework.data.domain.Sort;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.stereotype.Controller;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
|
||||
import com.eactive.eai.rms.common.vo.GridResponse;
|
||||
import com.eactive.eai.rms.data.entity.onl.kjb.statistics.ApiStatsDay;
|
||||
import com.eactive.eai.rms.data.entity.onl.kjb.statistics.ApiStatsDayService;
|
||||
import com.eactive.ext.kjb.statistics.mapping.ApiStatsUIMapper;
|
||||
import com.eactive.ext.kjb.statistics.service.ApiStatsExcelExportService;
|
||||
import com.eactive.ext.kjb.statistics.ui.ApiStatsChartUI;
|
||||
import com.eactive.ext.kjb.statistics.ui.ApiStatsSearch;
|
||||
import com.eactive.ext.kjb.statistics.ui.ApiStatsUI;
|
||||
|
||||
@@ -35,37 +26,29 @@ import lombok.extern.slf4j.Slf4j;
|
||||
@RequiredArgsConstructor
|
||||
public class ApiUseStatsController {
|
||||
|
||||
private final ApiStatsDayService service;
|
||||
private final ApiStatsUIMapper mapper;
|
||||
private final ApiStatsExcelExportService excelExportService;
|
||||
private final ApiUseStatsService service;
|
||||
private final ApiUseStatsExcelExportService excelExportService;
|
||||
|
||||
@GetMapping(value = "/onl/djb/statistics/apiUseStatsMan.view")
|
||||
@GetMapping(value = "/onl/kjb/statistics/apiUseStatsMan.view")
|
||||
public String view() {
|
||||
return "/onl/djb/statistics/apiUseStatsMan";
|
||||
return "/onl/kjb/statistics/apiUseStatsMan";
|
||||
}
|
||||
|
||||
@PostMapping(value = "/onl/djb/statistics/apiUseStatsMan.json", params = "cmd=LIST")
|
||||
@PostMapping(value = "/onl/kjb/statistics/apiUseStatsMan.json", params = "cmd=LIST")
|
||||
public ResponseEntity<GridResponse<ApiStatsUI>> selectList(ApiStatsSearch search, Pageable pageable) {
|
||||
Pageable sortedPageable = PageRequest.of(
|
||||
pageable.getPageNumber(),
|
||||
pageable.getPageSize(),
|
||||
Sort.by(Sort.Order.desc("statTime")));
|
||||
|
||||
Page<ApiStatsDay> page = service.selectList(search, sortedPageable);
|
||||
Page<ApiStatsUI> uiPage = page.map(mapper::toVo);
|
||||
|
||||
return ResponseEntity.ok(new GridResponse<>(uiPage));
|
||||
Page<ApiStatsUI> page = service.selectList(search, pageable);
|
||||
return ResponseEntity.ok(new GridResponse<>(page));
|
||||
}
|
||||
|
||||
@PostMapping(value = "/onl/djb/statistics/apiStatsDayMan.json", params = "cmd=EXCEL_EXPORT")
|
||||
@PostMapping(value = "/onl/kjb/statistics/apiUseStatsMan.json", params = "cmd=EXCEL_EXPORT")
|
||||
public void exportToExcel(ApiStatsSearch search, HttpServletResponse response) throws IOException {
|
||||
log.info("Excel export started - search: {}", search);
|
||||
|
||||
try {
|
||||
List<ApiStatsDay> dataList = service.selectListForExcel(search);
|
||||
log.info("Data retrieved - count: {}", dataList.size());
|
||||
List<ApiStatsUI> uiList = service.selectList(search);
|
||||
log.info("Data retrieved - count: {}", uiList.size());
|
||||
|
||||
if (dataList.isEmpty()) {
|
||||
if (uiList.isEmpty()) {
|
||||
log.warn("No data found for export");
|
||||
response.setStatus(HttpServletResponse.SC_NO_CONTENT);
|
||||
response.setContentType("application/json; charset=UTF-8");
|
||||
@@ -74,11 +57,6 @@ public class ApiUseStatsController {
|
||||
return;
|
||||
}
|
||||
|
||||
List<ApiStatsUI> uiList = dataList.stream()
|
||||
.map(mapper::toVo)
|
||||
.collect(Collectors.toList());
|
||||
log.info("Data converted to UI list - count: {}", uiList.size());
|
||||
|
||||
String fileName = generateFileName(search);
|
||||
log.info("Generating Excel file: {}", fileName);
|
||||
|
||||
@@ -101,10 +79,7 @@ public class ApiUseStatsController {
|
||||
}
|
||||
|
||||
private String generateFileName(ApiStatsSearch search) {
|
||||
String timeRange = StringUtils.isNotBlank(search.getSearchStartDateTime())
|
||||
? search.getSearchStartDateTime()
|
||||
: LocalDate.now().format(DateTimeFormatter.ofPattern("yyyyMMdd"));
|
||||
return "API사용현황_" + timeRange + ".xlsx";
|
||||
return "API사용현황_" + LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyyMMddHHmmss")) + ".xlsx";
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+256
@@ -0,0 +1,256 @@
|
||||
package com.eactive.eai.rms.ext.djb.statistics;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.math.BigDecimal;
|
||||
import java.net.URLEncoder;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.List;
|
||||
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
import org.apache.poi.ss.usermodel.BorderStyle;
|
||||
import org.apache.poi.ss.usermodel.Cell;
|
||||
import org.apache.poi.ss.usermodel.CellStyle;
|
||||
import org.apache.poi.ss.usermodel.FillPatternType;
|
||||
import org.apache.poi.ss.usermodel.Font;
|
||||
import org.apache.poi.ss.usermodel.HorizontalAlignment;
|
||||
import org.apache.poi.ss.usermodel.IndexedColors;
|
||||
import org.apache.poi.ss.usermodel.Row;
|
||||
import org.apache.poi.ss.usermodel.Sheet;
|
||||
import org.apache.poi.ss.usermodel.Workbook;
|
||||
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import com.eactive.ext.kjb.statistics.ui.ApiStatsUI;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
/**
|
||||
* API 통계 Excel Export 서비스
|
||||
* Hour/Minute 통계 데이터를 Excel 파일로 생성
|
||||
*/
|
||||
@Slf4j
|
||||
@Service
|
||||
public class ApiUseStatsExcelExportService {
|
||||
|
||||
private static final String[] COLUMN_HEADERS = {
|
||||
"구분", "총건수", "성공", "성공율(%)", "실패율(%)", "Timeout", "시스템오류",
|
||||
"평균응답(ms)", "최소응답(ms)", "최대응답(ms)"
|
||||
};
|
||||
|
||||
/**
|
||||
* API 통계 데이터를 Excel 파일로 생성하여 HTTP 응답으로 전송
|
||||
*
|
||||
* @param dataList API 통계 데이터 리스트
|
||||
* @param fileName 다운로드 파일명
|
||||
* @param response HTTP 응답 객체
|
||||
* @throws IOException 파일 생성 실패 시
|
||||
*/
|
||||
public void exportApiStats(List<ApiStatsUI> dataList, String fileName, HttpServletResponse response)
|
||||
throws IOException {
|
||||
|
||||
log.info("Starting Excel export - rows: {}, filename: {}", dataList.size(), fileName);
|
||||
|
||||
try (Workbook workbook = new XSSFWorkbook()) {
|
||||
Sheet sheet = workbook.createSheet("API통계");
|
||||
log.debug("Excel sheet created");
|
||||
|
||||
// 스타일 생성
|
||||
CellStyle headerStyle = createHeaderStyle(workbook);
|
||||
CellStyle stringStyle = createStringStyle(workbook);
|
||||
CellStyle numberStyle = createNumberStyle(workbook);
|
||||
CellStyle decimalStyle = createDecimalStyle(workbook);
|
||||
log.debug("Excel styles created");
|
||||
|
||||
// 헤더 행 생성
|
||||
createHeaderRow(sheet, headerStyle);
|
||||
log.debug("Header row created");
|
||||
|
||||
// 데이터 행 생성
|
||||
int rowNum = 1;
|
||||
for (ApiStatsUI data : dataList) {
|
||||
createDataRow(sheet, rowNum++, data, stringStyle, numberStyle, decimalStyle);
|
||||
}
|
||||
log.info("Data rows created - count: {}", dataList.size());
|
||||
|
||||
// 컬럼 너비 자동 조정
|
||||
autoSizeColumns(sheet);
|
||||
log.debug("Column widths adjusted");
|
||||
|
||||
// HTTP 응답 설정
|
||||
setHttpResponse(response, fileName, workbook);
|
||||
log.info("Excel file sent to response");
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("Error creating Excel file", e);
|
||||
throw new IOException("Excel file creation failed: " + e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 헤더 스타일 생성 (회색 배경, 볼드, 테두리)
|
||||
*/
|
||||
private CellStyle createHeaderStyle(Workbook workbook) {
|
||||
CellStyle style = workbook.createCellStyle();
|
||||
|
||||
// 배경색
|
||||
style.setFillForegroundColor(IndexedColors.GREY_25_PERCENT.getIndex());
|
||||
style.setFillPattern(FillPatternType.SOLID_FOREGROUND);
|
||||
|
||||
// 테두리
|
||||
style.setBorderTop(BorderStyle.THIN);
|
||||
style.setBorderBottom(BorderStyle.THIN);
|
||||
style.setBorderLeft(BorderStyle.THIN);
|
||||
style.setBorderRight(BorderStyle.THIN);
|
||||
|
||||
// 정렬
|
||||
style.setAlignment(HorizontalAlignment.CENTER);
|
||||
|
||||
// 폰트 (볼드)
|
||||
Font font = workbook.createFont();
|
||||
font.setBold(true);
|
||||
style.setFont(font);
|
||||
|
||||
return style;
|
||||
}
|
||||
|
||||
/**
|
||||
* 문자열 데이터 스타일 생성 (기본 테두리)
|
||||
*/
|
||||
private CellStyle createStringStyle(Workbook workbook) {
|
||||
CellStyle style = workbook.createCellStyle();
|
||||
style.setBorderTop(BorderStyle.THIN);
|
||||
style.setBorderBottom(BorderStyle.THIN);
|
||||
style.setBorderLeft(BorderStyle.THIN);
|
||||
style.setBorderRight(BorderStyle.THIN);
|
||||
return style;
|
||||
}
|
||||
|
||||
/**
|
||||
* 숫자 데이터 스타일 생성 (우측정렬 + 쉼표 구분)
|
||||
*/
|
||||
private CellStyle createNumberStyle(Workbook workbook) {
|
||||
CellStyle style = workbook.createCellStyle();
|
||||
style.setBorderTop(BorderStyle.THIN);
|
||||
style.setBorderBottom(BorderStyle.THIN);
|
||||
style.setBorderLeft(BorderStyle.THIN);
|
||||
style.setBorderRight(BorderStyle.THIN);
|
||||
style.setAlignment(HorizontalAlignment.RIGHT);
|
||||
style.setDataFormat(workbook.createDataFormat().getFormat("#,##0"));
|
||||
return style;
|
||||
}
|
||||
|
||||
/**
|
||||
* 소수점 데이터 스타일 생성 (우측정렬 + 소수점 2자리)
|
||||
*/
|
||||
private CellStyle createDecimalStyle(Workbook workbook) {
|
||||
CellStyle style = workbook.createCellStyle();
|
||||
style.setBorderTop(BorderStyle.THIN);
|
||||
style.setBorderBottom(BorderStyle.THIN);
|
||||
style.setBorderLeft(BorderStyle.THIN);
|
||||
style.setBorderRight(BorderStyle.THIN);
|
||||
style.setAlignment(HorizontalAlignment.RIGHT);
|
||||
style.setDataFormat(workbook.createDataFormat().getFormat("0.00"));
|
||||
return style;
|
||||
}
|
||||
|
||||
/**
|
||||
* 헤더 행 생성
|
||||
*/
|
||||
private void createHeaderRow(Sheet sheet, CellStyle headerStyle) {
|
||||
Row headerRow = sheet.createRow(0);
|
||||
|
||||
for (int i = 0; i < COLUMN_HEADERS.length; i++) {
|
||||
Cell cell = headerRow.createCell(i);
|
||||
cell.setCellValue(COLUMN_HEADERS[i]);
|
||||
cell.setCellStyle(headerStyle);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 데이터 행 생성
|
||||
*/
|
||||
private void createDataRow(Sheet sheet, int rowNum, ApiStatsUI data,
|
||||
CellStyle stringStyle, CellStyle numberStyle, CellStyle decimalStyle) {
|
||||
|
||||
Row row = sheet.createRow(rowNum);
|
||||
int colNum = 0;
|
||||
|
||||
createStringCell(row, colNum++, data.getOrgName(), stringStyle);
|
||||
createLongCell(row, colNum++, data.getTotalCnt(), numberStyle);
|
||||
createLongCell(row, colNum++, data.getSuccessCnt(), numberStyle);
|
||||
createDecimalCell(row, colNum++, data.getSuccessRate(), decimalStyle);
|
||||
createDecimalCell(row, colNum++, data.getFailRate(), decimalStyle);
|
||||
createLongCell(row, colNum++, data.getTimeoutCnt(), numberStyle);
|
||||
createLongCell(row, colNum++, data.getSystemErrCnt(), numberStyle);
|
||||
createDecimalCell(row, colNum++, data.getAvgRespTime(), numberStyle);
|
||||
createDecimalCell(row, colNum++, data.getMinRespTime(), numberStyle);
|
||||
createDecimalCell(row, colNum++, data.getMaxRespTime(), numberStyle);
|
||||
}
|
||||
|
||||
/**
|
||||
* 문자열 셀 생성
|
||||
*/
|
||||
private void createStringCell(Row row, int colNum, String value, CellStyle style) {
|
||||
Cell cell = row.createCell(colNum);
|
||||
cell.setCellValue(value != null ? value : "");
|
||||
cell.setCellStyle(style);
|
||||
}
|
||||
|
||||
/**
|
||||
* Long 타입 숫자 셀 생성
|
||||
*/
|
||||
private void createLongCell(Row row, int colNum, Long value, CellStyle style) {
|
||||
Cell cell = row.createCell(colNum);
|
||||
if (value != null) {
|
||||
cell.setCellValue(value.doubleValue());
|
||||
} else {
|
||||
cell.setCellValue(0);
|
||||
}
|
||||
cell.setCellStyle(style);
|
||||
}
|
||||
|
||||
/**
|
||||
* BigDecimal 타입 소수점 셀 생성
|
||||
*/
|
||||
private void createDecimalCell(Row row, int colNum, BigDecimal value, CellStyle style) {
|
||||
Cell cell = row.createCell(colNum);
|
||||
if (value != null) {
|
||||
cell.setCellValue(value.doubleValue());
|
||||
} else {
|
||||
cell.setCellValue(0.0);
|
||||
}
|
||||
cell.setCellStyle(style);
|
||||
}
|
||||
|
||||
/**
|
||||
* 컬럼 너비 자동 조정
|
||||
*/
|
||||
private void autoSizeColumns(Sheet sheet) {
|
||||
for (int i = 0; i < COLUMN_HEADERS.length; i++) {
|
||||
sheet.autoSizeColumn(i);
|
||||
// 한글 문자 고려하여 약간 여유 공간 추가
|
||||
sheet.setColumnWidth(i, sheet.getColumnWidth(i) + 512);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* HTTP 응답 설정 및 파일 전송
|
||||
*/
|
||||
private void setHttpResponse(HttpServletResponse response, String fileName, Workbook workbook)
|
||||
throws IOException {
|
||||
|
||||
// Content-Type 설정
|
||||
response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
|
||||
|
||||
// Content-Disposition 설정 (파일명 UTF-8 인코딩)
|
||||
String encodedFileName = URLEncoder.encode(fileName, StandardCharsets.UTF_8.toString())
|
||||
.replaceAll("\\+", "%20");
|
||||
response.setHeader("Content-Disposition",
|
||||
"attachment; filename=\"" + encodedFileName + "\"; filename*=UTF-8''" + encodedFileName);
|
||||
|
||||
// Workbook을 응답 스트림으로 전송
|
||||
workbook.write(response.getOutputStream());
|
||||
response.getOutputStream().flush();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
package com.eactive.eai.rms.ext.djb.statistics;
|
||||
|
||||
import com.eactive.eai.data.jpa.BaseRepository;
|
||||
import com.eactive.eai.rms.data.entity.onl.kjb.statistics.ApiStatsDay;
|
||||
import com.eactive.eai.rms.data.entity.onl.kjb.statistics.ApiStatsDayId;
|
||||
|
||||
/**
|
||||
* API 사용현황 Repository
|
||||
*/
|
||||
interface ApiUseStatsRepository extends BaseRepository<ApiStatsDay, ApiStatsDayId> {
|
||||
|
||||
}
|
||||
@@ -0,0 +1,209 @@
|
||||
package com.eactive.eai.rms.ext.djb.statistics;
|
||||
|
||||
import java.math.RoundingMode;
|
||||
import java.time.LocalDate;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import javax.persistence.EntityManager;
|
||||
import javax.persistence.PersistenceContext;
|
||||
import javax.persistence.Query;
|
||||
|
||||
import org.springframework.data.domain.Page;
|
||||
import org.springframework.data.domain.PageImpl;
|
||||
import org.springframework.data.domain.Pageable;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import com.eactive.eai.data.jpa.AbstractDataService;
|
||||
import com.eactive.eai.rms.common.util.StringUtils;
|
||||
import com.eactive.eai.rms.data.entity.onl.kjb.statistics.ApiStatsDay;
|
||||
import com.eactive.eai.rms.data.entity.onl.kjb.statistics.ApiStatsDayId;
|
||||
import com.eactive.ext.kjb.statistics.ui.ApiStatsSearch;
|
||||
import com.eactive.ext.kjb.statistics.ui.ApiStatsUI;
|
||||
|
||||
@Service
|
||||
@Transactional
|
||||
public class ApiUseStatsService
|
||||
extends AbstractDataService<ApiStatsDay, ApiStatsDayId, ApiUseStatsRepository> {
|
||||
|
||||
private static final int MAX_DAYS = 31;
|
||||
|
||||
@PersistenceContext
|
||||
private EntityManager entityManager;
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public Page<ApiStatsUI> selectList(ApiStatsSearch search, Pageable pageable) {
|
||||
Query dataQuery = getDataQuery(search);
|
||||
dataQuery.setFirstResult((int) pageable.getOffset());
|
||||
dataQuery.setMaxResults(pageable.getPageSize());
|
||||
|
||||
List<Object[]> rows = dataQuery.getResultList();
|
||||
List<ApiStatsUI> results = rows.stream()
|
||||
.map(this::toVO)
|
||||
.collect(Collectors.toList());
|
||||
|
||||
long records = rows.size() > 0 ? StringUtils.toLong(rows.get(0)[0]) : 0;
|
||||
|
||||
return new PageImpl<>(results, pageable, records);
|
||||
}
|
||||
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public List<ApiStatsUI> selectList(ApiStatsSearch search) {
|
||||
Query dataQuery = getDataQuery(search);
|
||||
List<Object[]> rows = dataQuery.getResultList();
|
||||
return rows.stream()
|
||||
.map(this::toVO)
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
|
||||
private Query getDataQuery(ApiStatsSearch search) {
|
||||
|
||||
StringBuilder where = buildNativeWhere(search);
|
||||
|
||||
String dataSql = "";
|
||||
if ("ORG".equals(search.getSearchType())) {
|
||||
dataSql = "SELECT COUNT(*) OVER () AS RECORDS "
|
||||
+ ", B.ORGNAME"
|
||||
+ ", SUM(A.TOTAL_CNT)"
|
||||
+ ", SUM(A.SUCCESS_CNT)"
|
||||
+ ", CASE WHEN SUM(A.TOTAL_CNT) > 0 THEN SUM(A.SUCCESS_CNT) * 100 / SUM(A.TOTAL_CNT) ELSE NULL END"
|
||||
+ ", CASE WHEN SUM(A.TOTAL_CNT) > 0 THEN (SUM(A.TOTAL_CNT) - SUM(A.SUCCESS_CNT)) * 100 / SUM(A.TOTAL_CNT) ELSE NULL END"
|
||||
+ ", SUM(A.TIMEOUT_CNT)"
|
||||
+ ", SUM(A.SYSTEM_ERR_CNT)"
|
||||
+ ", CASE WHEN SUM(A.TOTAL_CNT) > 0 THEN TRUNC(SUM(A.TOTAL_CNT * A.AVG_RESP_TIME) / SUM(A.TOTAL_CNT)) ELSE NULL END"
|
||||
+ ", MIN(A.MIN_RESP_TIME)"
|
||||
+ ", MAX(A.MAX_RESP_TIME)"
|
||||
+ " FROM API_STATS_DAY A"
|
||||
+ " LEFT OUTER JOIN TSEAIAU01 B ON A.CLIENT_ID = B.CLIENTID"
|
||||
+ " LEFT OUTER JOIN TSEAIHE01 C ON A.API_NAME = C.EAISVCNAME "
|
||||
+ where
|
||||
+ " GROUP BY B.ORGNAME"
|
||||
+ " ORDER BY ORGNAME";
|
||||
} else if ("API".equals(search.getSearchType())) {
|
||||
dataSql = "SELECT COUNT(*) OVER () AS RECORDS "
|
||||
+ ", C.EAISVCDESC"
|
||||
+ ", SUM(A.TOTAL_CNT)"
|
||||
+ ", SUM(A.SUCCESS_CNT)"
|
||||
+ ", CASE WHEN SUM(A.TOTAL_CNT) > 0 THEN SUM(A.SUCCESS_CNT) * 100 / SUM(A.TOTAL_CNT) ELSE NULL END"
|
||||
+ ", CASE WHEN SUM(A.TOTAL_CNT) > 0 THEN (SUM(A.TOTAL_CNT) - SUM(A.SUCCESS_CNT)) * 100 / SUM(A.TOTAL_CNT) ELSE NULL END"
|
||||
+ ", SUM(A.TIMEOUT_CNT)"
|
||||
+ ", SUM(A.SYSTEM_ERR_CNT)"
|
||||
+ ", CASE WHEN SUM(A.TOTAL_CNT) > 0 THEN TRUNC(SUM(A.TOTAL_CNT * A.AVG_RESP_TIME) / SUM(A.TOTAL_CNT)) ELSE NULL END"
|
||||
+ ", MIN(A.MIN_RESP_TIME)"
|
||||
+ ", MAX(A.MAX_RESP_TIME)"
|
||||
+ " FROM API_STATS_DAY A "
|
||||
+ " LEFT OUTER JOIN TSEAIAU01 B ON A.CLIENT_ID = B.CLIENTID"
|
||||
+ " LEFT OUTER JOIN TSEAIHE01 C ON A.API_NAME = C.EAISVCNAME "
|
||||
+ where
|
||||
+ " GROUP BY C.EAISVCDESC"
|
||||
+ " ORDER BY EAISVCDESC";
|
||||
} else if ("DATE".equals(search.getSearchType())) {
|
||||
dataSql = "SELECT COUNT(*) OVER () AS RECORDS "
|
||||
+ ", TO_CHAR(STAT_TIME,'YYYY-MM-DD') AS STAT_TIME "
|
||||
+ ", SUM(A.TOTAL_CNT)"
|
||||
+ ", SUM(A.SUCCESS_CNT)"
|
||||
+ ", CASE WHEN SUM(A.TOTAL_CNT) > 0 THEN SUM(A.SUCCESS_CNT) * 100 / SUM(A.TOTAL_CNT) ELSE NULL END"
|
||||
+ ", CASE WHEN SUM(A.TOTAL_CNT) > 0 THEN (SUM(A.TOTAL_CNT) - SUM(A.SUCCESS_CNT)) * 100 / SUM(A.TOTAL_CNT) ELSE NULL END"
|
||||
+ ", SUM(A.TIMEOUT_CNT)"
|
||||
+ ", SUM(A.SYSTEM_ERR_CNT)"
|
||||
+ ", CASE WHEN SUM(A.TOTAL_CNT) > 0 THEN TRUNC(SUM(A.TOTAL_CNT * A.AVG_RESP_TIME) / SUM(A.TOTAL_CNT)) ELSE NULL END"
|
||||
+ ", MIN(A.MIN_RESP_TIME)"
|
||||
+ ", MAX(A.MAX_RESP_TIME)"
|
||||
+ " FROM API_STATS_DAY A "
|
||||
+ " LEFT OUTER JOIN TSEAIAU01 B ON A.CLIENT_ID = B.CLIENTID"
|
||||
+ " LEFT OUTER JOIN TSEAIHE01 C ON A.API_NAME = C.EAISVCNAME "
|
||||
+ where
|
||||
+ " GROUP BY TO_CHAR(STAT_TIME,'YYYY-MM-DD')"
|
||||
+ " ORDER BY STAT_TIME";
|
||||
}
|
||||
|
||||
Query dataQuery = entityManager.createNativeQuery(dataSql);
|
||||
if (search.getParsedStartDate() != null) {
|
||||
dataQuery.setParameter("searchStartDate", search.getParsedStartDate());
|
||||
dataQuery.setParameter("searchEndDate", search.getParsedEndDate());
|
||||
}
|
||||
if (StringUtils.isNotBlank(search.getSearchOrgName())) {
|
||||
dataQuery.setParameter("searchOrgName", "%" + search.getSearchOrgName().toUpperCase() + "%");
|
||||
}
|
||||
if (StringUtils.isNotBlank(search.getSearchApiName())) {
|
||||
dataQuery.setParameter("searchApiName", "%" + search.getSearchApiName().toUpperCase() + "%");
|
||||
}
|
||||
return dataQuery;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Native Query 결과를 ApiStatsUI로 변환
|
||||
*/
|
||||
private ApiStatsUI toVO(Object[] row) {
|
||||
ApiStatsUI vo = new ApiStatsUI();
|
||||
vo.setOrgName(StringUtils.toString(row[1]));
|
||||
vo.setTotalCnt(StringUtils.toLong(row[2]));
|
||||
vo.setSuccessCnt(StringUtils.toLong(row[3]));
|
||||
vo.setSuccessRate(StringUtils.toDecimal(row[4]).setScale(2, RoundingMode.HALF_UP));
|
||||
vo.setFailRate(StringUtils.toDecimal(row[5]).setScale(2, RoundingMode.HALF_UP));
|
||||
vo.setTimeoutCnt(StringUtils.toLong(row[6]));
|
||||
vo.setSystemErrCnt(StringUtils.toLong(row[7]));
|
||||
vo.setAvgRespTime(StringUtils.toDecimal(row[8]));
|
||||
vo.setMinRespTime(StringUtils.toDecimal(row[9]));
|
||||
vo.setMaxRespTime(StringUtils.toDecimal(row[10]));
|
||||
return vo;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* native SQL용 동적 WHERE 절 생성
|
||||
*/
|
||||
private StringBuilder buildNativeWhere(ApiStatsSearch search) {
|
||||
StringBuilder where = new StringBuilder(" WHERE 1=1");
|
||||
|
||||
calculateDateRange(search);
|
||||
|
||||
if (search.getParsedStartDate() != null) {
|
||||
where.append(" AND A.STAT_TIME >= :searchStartDate ");
|
||||
where.append(" AND A.STAT_TIME <= :searchEndDate ");
|
||||
}
|
||||
|
||||
if (StringUtils.isNotBlank(search.getSearchOrgName())) {
|
||||
where.append(" AND UPPER(B.ORGNAME) LIKE :searchOrgName ");
|
||||
}
|
||||
|
||||
if (StringUtils.isNotBlank(search.getSearchApiName())) {
|
||||
where.append(" AND UPPER(C.EAISVCDESC) LIKE :searchApiName ");
|
||||
}
|
||||
|
||||
return where;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 조회 기간 계산 (최대 31일 제한)
|
||||
*/
|
||||
private void calculateDateRange(ApiStatsSearch search) {
|
||||
if (StringUtils.isBlank(search.getSearchStartDateTime())) {
|
||||
return;
|
||||
}
|
||||
|
||||
LocalDate startDate = LocalDate.parse(search.getSearchStartDateTime(),
|
||||
DateTimeFormatter.ofPattern("yyyyMMdd"));
|
||||
LocalDate endDate;
|
||||
|
||||
if (StringUtils.isNotBlank(search.getSearchEndDateTime())) {
|
||||
endDate = LocalDate.parse(search.getSearchEndDateTime(),
|
||||
DateTimeFormatter.ofPattern("yyyyMMdd"));
|
||||
if (java.time.Period.between(startDate, endDate).getDays() > MAX_DAYS) {
|
||||
endDate = startDate.plusDays(MAX_DAYS);
|
||||
}
|
||||
} else {
|
||||
endDate = startDate.plusDays(MAX_DAYS);
|
||||
}
|
||||
|
||||
search.setParsedStartDate(startDate);
|
||||
search.setParsedEndDate(endDate);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,17 +0,0 @@
|
||||
package com.eactive.eai.rms.ext.djb.swing.service;
|
||||
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
@Slf4j
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class SwingSendManager {
|
||||
|
||||
public void send(String eaisvcname, long cnt, long rangeMinute) {
|
||||
log.info("[Swing] 발송 준비: {} 최근 {}분 {}건", eaisvcname, rangeMinute, cnt);
|
||||
// TODO: 알림 발송 로직
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,89 @@
|
||||
package com.eactive.eai.rms.ext.djb.ums;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.scheduling.annotation.Async;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import com.eactive.apim.portal.template.entity.MessageCode;
|
||||
import com.eactive.apim.portal.template.service.MessageHandlerService;
|
||||
import com.eactive.apim.portal.template.service.MessageRecipient;
|
||||
import com.eactive.apim.portal.user.entity.UserInfo;
|
||||
import com.eactive.eai.rms.data.entity.man.user.UserInfoService;
|
||||
import com.eactive.eai.rms.ext.djb.webhook.dto.WebhookSendRequest;
|
||||
import com.eactive.eai.rms.ext.djb.webhook.service.WebhookService;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
|
||||
@Component
|
||||
@RequiredArgsConstructor
|
||||
public class UmsManager {
|
||||
|
||||
private static final Logger log = LoggerFactory.getLogger(UmsManager.class);
|
||||
|
||||
private final MessageHandlerService messageHandlerService;
|
||||
private final UserInfoService userInfoService;
|
||||
private final WebhookService webhookService;
|
||||
|
||||
/**
|
||||
* role 역할을 가진 내부직원에게 메신저(Swing chat) 발송
|
||||
* @param role
|
||||
* @param messageCode
|
||||
* @param message
|
||||
* @return
|
||||
*/
|
||||
public void sendMessenger(String roleId, MessageCode messageCode, String message) {
|
||||
|
||||
List<UserInfo> users = userInfoService.findAllByRoleId(roleId);
|
||||
if (users.isEmpty()) {
|
||||
log.info("sendMessenger: no users found for role '{}'", roleId);
|
||||
return;
|
||||
}
|
||||
|
||||
for (UserInfo user : users) {
|
||||
MessageRecipient recipient = MessageRecipient.builder()
|
||||
.userId(user.getUserid())
|
||||
.username(user.getUsername())
|
||||
.build();
|
||||
|
||||
HashMap<String, Object> params = new HashMap<>();
|
||||
params.put("message", message);
|
||||
|
||||
messageHandlerService.publishEvent(messageCode, recipient, params);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 내부직원에게 메신저 발송
|
||||
* @param user
|
||||
* @param messageCode
|
||||
* @param message
|
||||
*/
|
||||
public void sendMessenger(UserInfo user, MessageCode messageCode, String message) {
|
||||
MessageRecipient recipient = MessageRecipient.builder()
|
||||
.userId(user.getUserid())
|
||||
.username(user.getUsername())
|
||||
.build();
|
||||
|
||||
HashMap<String, Object> params = new HashMap<>();
|
||||
params.put("message", message);
|
||||
|
||||
messageHandlerService.publishEvent(messageCode, recipient, params);
|
||||
}
|
||||
|
||||
/**
|
||||
* event를 구독중인 제휴사에게 웹훅 발송 (비동기)
|
||||
* @param event
|
||||
* @param apiIds
|
||||
*/
|
||||
@Async("webhookExecutor")
|
||||
public void sendWebhook(String event, List<String> apiIds) {
|
||||
List<WebhookSendRequest> list = webhookService.findSendList(event, apiIds);
|
||||
for (WebhookSendRequest req : list) {
|
||||
webhookService.send(req);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
package com.eactive.eai.rms.ext.djb.ums.event;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import com.eactive.apim.portal.template.entity.MessageCode;
|
||||
import com.eactive.apim.portal.template.service.MessageEventHandler;
|
||||
import com.eactive.apim.portal.template.service.MessageRecipient;
|
||||
import com.eactive.apim.portal.template.service.MessageSendEvent;
|
||||
|
||||
@Component
|
||||
public class ApiStatusChangedEventHandler implements MessageEventHandler {
|
||||
|
||||
@Override
|
||||
public MessageCode getKey() {
|
||||
return MessageCode.API_STATUS_CHANGED;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean allowAdditionalRecipients() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDisplayName() {
|
||||
return "API 상태 변화";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDescription() {
|
||||
return "<div>사용 가능 변수: </div><br/>"
|
||||
+ "<div> - message 알림 메시지 </div><br/>";
|
||||
}
|
||||
|
||||
@Override
|
||||
public MessageSendEvent createEvent(Object source, MessageRecipient recipient, Map<String, Object> params) {
|
||||
Map<String, String> eventParams = new HashMap<>();
|
||||
eventParams.put("message", (String) params.get("message"));
|
||||
return new MessageSendEvent(source, MessageCode.API_STATUS_CHANGED, recipient, eventParams);
|
||||
}
|
||||
}
|
||||
+43
@@ -0,0 +1,43 @@
|
||||
package com.eactive.eai.rms.ext.djb.ums.event;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import com.eactive.apim.portal.template.entity.MessageCode;
|
||||
import com.eactive.apim.portal.template.service.MessageEventHandler;
|
||||
import com.eactive.apim.portal.template.service.MessageRecipient;
|
||||
import com.eactive.apim.portal.template.service.MessageSendEvent;
|
||||
|
||||
@Component
|
||||
public class InflowTokenFailedEventHandler implements MessageEventHandler {
|
||||
|
||||
@Override
|
||||
public MessageCode getKey() {
|
||||
return MessageCode.INFLOW_TOKEN_FAILED;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean allowAdditionalRecipients() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDisplayName() {
|
||||
return "유량제어 토큰 획득 실패";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDescription() {
|
||||
return "<div>사용 가능 변수: </div><br/>"
|
||||
+ "<div> - message 알림 메시지 </div><br/>";
|
||||
}
|
||||
|
||||
@Override
|
||||
public MessageSendEvent createEvent(Object source, MessageRecipient recipient, Map<String, Object> params) {
|
||||
Map<String, String> eventParams = new HashMap<>();
|
||||
eventParams.put("message", (String) params.get("message"));
|
||||
return new MessageSendEvent(source, MessageCode.INFLOW_TOKEN_FAILED, recipient, eventParams);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,344 @@
|
||||
package com.eactive.eai.rms.ext.djb.ums.service;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.InputStreamReader;
|
||||
import java.io.OutputStream;
|
||||
import java.net.HttpURLConnection;
|
||||
import java.net.URL;
|
||||
import java.nio.charset.Charset;
|
||||
import java.security.SecureRandom;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import com.eactive.apim.portal.portalproperty.service.PortalPropertyService;
|
||||
import com.eactive.apim.portal.template.entity.MessageRequest;
|
||||
import com.eactive.eai.common.util.SystemUtil;
|
||||
import com.eactive.eai.rms.data.entity.man.monitoringProperty.service.MonitoringPropertyService;
|
||||
import com.eactive.eai.rms.ext.djb.ums.vo.EmailDataVO;
|
||||
import com.eactive.eai.rms.ext.djb.ums.vo.SmsDataVO;
|
||||
import com.eactive.eai.rms.ext.djb.ums.vo.StdHeadVO;
|
||||
import com.eactive.eai.rms.ext.djb.ums.vo.SwingCommonVO;
|
||||
import com.eactive.eai.rms.ext.djb.ums.vo.SwingDataVO;
|
||||
import com.eactive.eai.rms.ext.djb.ums.vo.SwingReceiverDataVO;
|
||||
import com.google.gson.Gson;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
@Slf4j
|
||||
@Service
|
||||
public class UmsService {
|
||||
|
||||
private static final Gson gson = new Gson();
|
||||
|
||||
private static final String SYS_DVCD = "OPA";
|
||||
private final MonitoringPropertyService propertyService;
|
||||
|
||||
public UmsService(MonitoringPropertyService propertyService) {
|
||||
this.propertyService = propertyService;
|
||||
}
|
||||
|
||||
public boolean send(MessageRequest messageRequest) throws Exception {
|
||||
if (messageRequest == null) {
|
||||
throw new IllegalArgumentException("messageRequest is null");
|
||||
}
|
||||
|
||||
this.httpConnection(messageRequest);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
private void httpConnection(MessageRequest messageRequest) throws Exception {
|
||||
HttpURLConnection conn = null;
|
||||
BufferedReader br = null;
|
||||
log.debug("<<<HTTP Connection>>>:" + messageRequest.toString());
|
||||
try {
|
||||
String host = propertyService.getPropertyValue("Monitoring", "djb.ums."+messageRequest.getMessageType().toLowerCase()+".url", "");
|
||||
|
||||
URL url = new URL(host);
|
||||
int timeoutValue = 5 * 1000; // 타임아웃 설정을 위한 값 (단위: ms)
|
||||
// Charset charset = Charset.forName("UTF-8");
|
||||
Charset charset = Charset.forName("MS949");
|
||||
|
||||
StringBuffer sb = null;
|
||||
String bodyData = this.makeBodyData(messageRequest);
|
||||
|
||||
String responseData = "";
|
||||
String method = bodyData.isEmpty() ? "GET" : "POST";
|
||||
conn = (HttpURLConnection) url.openConnection();
|
||||
conn.setRequestMethod(method); // 전송방식
|
||||
// conn.setRequestProperty("Content-Type", "application/json; charset=UTF-8");
|
||||
conn.setRequestProperty("Content-Type", "application/json; charset=MS949");
|
||||
conn.setConnectTimeout(timeoutValue); // 연결 타임아웃 설정(5초)
|
||||
conn.setReadTimeout(timeoutValue); // 읽기 타임아웃 설정(5초)
|
||||
conn.setDoOutput(true);
|
||||
|
||||
// POst 방식인 경우에만
|
||||
if (method.equals("POST")) {
|
||||
OutputStream os = conn.getOutputStream();
|
||||
byte requestData[] = bodyData.getBytes(charset);
|
||||
os.write(requestData);
|
||||
os.close();
|
||||
}
|
||||
|
||||
if (log.isDebugEnabled()) {
|
||||
log.debug("getContentType();" + conn.getContentType()); // 응답 콘텐츠 유형 구하기
|
||||
log.debug("getResponsecode();" + conn.getResponseCode()); // 응답 코드 구하기
|
||||
log.debug("getResponseMessage():" + conn.getResponseMessage()); // 응답 메세지 구하기
|
||||
}
|
||||
|
||||
// http 요청 후 응답 받은 데이타를 버퍼에 쌓는다
|
||||
if (conn.getResponseCode() >= 200 && conn.getResponseCode() <= 300) {
|
||||
br = new BufferedReader(new InputStreamReader(conn.getInputStream(), charset));
|
||||
} else {
|
||||
br = new BufferedReader(new InputStreamReader(conn.getErrorStream(), charset));
|
||||
}
|
||||
|
||||
String inputLine;
|
||||
sb = new StringBuffer();
|
||||
while ((inputLine = br.readLine()) != null) {
|
||||
sb.append(inputLine);
|
||||
}
|
||||
|
||||
responseData = sb.toString();
|
||||
} finally {
|
||||
// http 요청 및 응답 완료 후 BufferedReader를 닫는다
|
||||
if (br != null) {
|
||||
br.close();
|
||||
}
|
||||
if (conn != null) {
|
||||
conn.disconnect();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
private String makeBodyData(MessageRequest messageRequest) {
|
||||
// 리턴값
|
||||
String rtnVal = "";
|
||||
|
||||
// guID 및 날짜 시간 정보
|
||||
String jsonData[] = this.makeStdGuidInfo();
|
||||
|
||||
// 표준 헤더
|
||||
StdHeadVO stdHeadVO = new StdHeadVO();
|
||||
stdHeadVO.setNxgn_stnd_idfr("JERA");
|
||||
stdHeadVO.setGuid(jsonData[2]);
|
||||
stdHeadVO.setGuid_prgs_no("1");
|
||||
stdHeadVO.setStnd_mesg_ver("R10");
|
||||
stdHeadVO.setOrtr_guid(jsonData[2]);
|
||||
stdHeadVO.setSect_ecrpt_yn("N");
|
||||
stdHeadVO.setFrst_trnm_ipad(propertyService.getPropertyValue("Monitoring", "djb.ums.was_ip_address", ""));
|
||||
stdHeadVO.setFrst_trnm_ipv4_addr(propertyService.getPropertyValue("Monitoring", "djb.ums.was_ip_address", ""));
|
||||
stdHeadVO.setFrst_trnm_mac(propertyService.getPropertyValue("Monitoring", "djb.ums.was_mac_address", ""));
|
||||
stdHeadVO.setFrst_mesg_dman_dt(jsonData[0]);
|
||||
stdHeadVO.setFrst_mesg_dman_time(jsonData[1]);
|
||||
stdHeadVO.setFrst_trnm_sys_dvcd(SYS_DVCD);
|
||||
stdHeadVO.setTrnm_sys_dvcd(SYS_DVCD);
|
||||
stdHeadVO.setDman_rspn_dvcd("S"); // 요청응답구분코드 S:요청 R:응답
|
||||
stdHeadVO.setSys_env_dvcd(SystemUtil.getSysOperEvirnDstcd()); // D:개발 T:테스트 P:운영
|
||||
stdHeadVO.setTx_tycd("O"); // O:온라인 B:배치
|
||||
stdHeadVO.setSynz_dvcd("S"); // S:동기 A:비동기
|
||||
stdHeadVO.setChnl_tycd(""); // 채널유형코드
|
||||
stdHeadVO.setHmab_dvcd("1"); // 내외구분코드 1:내부 2:외부(타발)
|
||||
stdHeadVO.setIf_id(messageRequest.getEaiInterfaceId()); // 인터페이스ID
|
||||
stdHeadVO.setTx_id(messageRequest.getServiceId()); // 거래ID
|
||||
|
||||
if ("MESSENGER".equals(messageRequest.getMessageType()) ) {
|
||||
|
||||
// 스윙챗 알림 공용
|
||||
SwingCommonVO swiComVO = new SwingCommonVO();
|
||||
swiComVO.setClientld(propertyService.getPropertyValue("Monitoring", "djb.ums.messenger.client_id", ""));
|
||||
swiComVO.setClientSecret(propertyService.getPropertyValue("Monitoring", "djb.ums.messenger.client_secret", ""));
|
||||
swiComVO.setCompanyCode("CB");
|
||||
swiComVO.setRequestUniqueKey(this.genrateRandomStringNumber26());
|
||||
|
||||
// 스윙챗 알림 데이터
|
||||
SwingDataVO swiDataVO = new SwingDataVO();
|
||||
swiDataVO.setNotiCode("CNO_FEP_HMP_0002");
|
||||
swiDataVO.setNotiTitle("알림");
|
||||
swiDataVO.setNotiContent(messageRequest.getMessage());
|
||||
|
||||
// 스윙챗 알림 데이터
|
||||
List<SwingReceiverDataVO> swiReDataVOList = new ArrayList<SwingReceiverDataVO>();
|
||||
SwingReceiverDataVO receiver = new SwingReceiverDataVO();
|
||||
receiver.setCode(messageRequest.getMessengerId());
|
||||
swiReDataVOList.add(receiver);
|
||||
|
||||
swiDataVO.setReceiverInfo(swiReDataVOList);
|
||||
|
||||
String header = gson.toJson(stdHeadVO);
|
||||
String common = gson.toJson(swiComVO);
|
||||
String data = gson.toJson(swiDataVO);
|
||||
|
||||
// 헤더 + 데이터
|
||||
rtnVal = "{\"HEAD\":" + header + " ,\r\n" + "\"DATA\":\r\n" + " [" + "{\r\n"
|
||||
+ " \"data_dvcd\":\"IO\", \r\n" + " \"BIZ_DATA\":\r\n" + " {\r\n"
|
||||
+ " \"common\":\r\n" + common + ",\r\n" + " \"data\":" + data
|
||||
+ " }\r\n" + " }\r\n" + " ]\r\n" + "}";
|
||||
|
||||
}
|
||||
else if ("SMS".equals(messageRequest.getMessageType())
|
||||
|| "KAKAO".equals(messageRequest.getMessageType())) {
|
||||
|
||||
// Sms 데이터
|
||||
SmsDataVO dataVO = new SmsDataVO();
|
||||
dataVO.setSnd_dman_dt(jsonData[0]);
|
||||
dataVO.setSnd_dman_id("");
|
||||
dataVO.setSnd_dvcd("EAI");
|
||||
dataVO.setMsg_titl("");
|
||||
dataVO.setMsg_ctnt(messageRequest.getMessage());
|
||||
dataVO.setRecvr_no(messageRequest.getPhone());
|
||||
dataVO.setSndn_no("15880079"); //발신번호
|
||||
dataVO.setSnd_empno(""); //요청자
|
||||
dataVO.setSnd_emp_dprm_cd(""); //요청부서코드
|
||||
if ("SMS".equals(messageRequest.getMessageType())) {
|
||||
dataVO.setLtrs_snd_tycd("1"); // 1:SMS 2:LMS/MMS
|
||||
dataVO.setMsg_snd_tycd("0000"); // 알림톡 메세지 종류 0000:SMS, 1000:LMS, 1008:알림톡, ....
|
||||
} else if ("KAKAO".equals(messageRequest.getMessageType())) {
|
||||
dataVO.setLtrs_snd_tycd("2"); // 1:SMS 2:LMS/MMS
|
||||
dataVO.setMsg_snd_tycd("1008"); // 알림톡 메세지 종류 0000:SMS, 1000:LMS, 1008:알림톡, ....
|
||||
dataVO.setAlmtk_snd_prfl_key(""); // 플러스친구아이디
|
||||
dataVO.setAlmtk_tmplt_cd(""); // 템플릿코드
|
||||
dataVO.setAlmtk_err_ltrs_snd_yn("Y"); // 오류자문자발송여부
|
||||
dataVO.setAlmtk_btn_trnm_tycd("2"); // 카카오버튼전송방식 1-format string 2-JSON 3-XML
|
||||
}
|
||||
dataVO.setSms_trnm_bzwk_dvcd(""); //업무구분코드
|
||||
dataVO.setSms_bzwk_dcls_dvcd(""); //업무세분코드
|
||||
dataVO.setSms_sys_dvcd("83"); //시스템코드
|
||||
dataVO.setTx_dt(jsonData[0]); //거래일자
|
||||
dataVO.setTx_tktm(jsonData[1].substring(0,6)); //거래시간
|
||||
|
||||
List<SmsDataVO> dataList = new ArrayList<SmsDataVO>();
|
||||
dataList.add(dataVO);
|
||||
|
||||
String header = gson.toJson(stdHeadVO);
|
||||
String data = gson.toJson(dataList);
|
||||
|
||||
// 헤더 + 데이터
|
||||
rtnVal = "{\"HEAD\":" + header + " ,\r\n" + "\"DATA\":\r\n" + " [" + "{\r\n"
|
||||
+ " \"data_dvcd\":\"IO\", \r\n" + " \"BIZ_DATA\":\r\n" + " {\r\n"
|
||||
+ " \"ums_if_data_ncnt\":1, \r\n"
|
||||
+ " \"ums_if_data\":" + data
|
||||
+ " }\r\n" + " }\r\n" + " ]\r\n" + "}";
|
||||
|
||||
}
|
||||
else if ("EMAIL".equals(messageRequest.getMessageType())) {
|
||||
|
||||
// 이메일 데이터
|
||||
EmailDataVO dataVO = new EmailDataVO();
|
||||
dataVO.setUms_evnt_id(""); // UMS이벤트ID(필수)
|
||||
dataVO.setSnd_dman_dt(jsonData[0]);
|
||||
dataVO.setSnd_dman_id("");
|
||||
dataVO.setSnd_dman_sno("1");
|
||||
dataVO.setEmail_titl(messageRequest.getSubject()); // 메일제목
|
||||
dataVO.setCstno(""); // 고객번호(필수)
|
||||
dataVO.setSendr_emaddr(""); // 발신자 이메일
|
||||
dataVO.setSendr_nm(""); // 발신자 이름
|
||||
dataVO.setSndbk_emaddr(""); // 리턴 이메일
|
||||
dataVO.setRecvr_emaddr(messageRequest.getEmail()); // 수신자 이메일(필수)
|
||||
dataVO.setRecvr_nm(messageRequest.getUsername()); // 수신자명(필수)
|
||||
dataVO.setTmplt_mpng_info(messageRequest.getMessage()); // 탬플릿매핑정보
|
||||
dataVO.setTmplt_mpng_rptt_info(""); // 탬플릿매핑반복정보
|
||||
dataVO.setSnd_empno(""); // 발신자ID(필수)
|
||||
dataVO.setSnd_emp_dprm_cd(""); // 발신자부서코드(필수)
|
||||
dataVO.setBzwk_cd(""); // 업무구분코드
|
||||
dataVO.setSub_bzwk_cd(""); // 업무세부코드
|
||||
dataVO.setSys_dvcd(SYS_DVCD);
|
||||
dataVO.setTx_dt(jsonData[0]); //거래일자
|
||||
dataVO.setTx_tktm(jsonData[1].substring(0,6)); //거래시간
|
||||
|
||||
List<EmailDataVO> dataList = new ArrayList<EmailDataVO>();
|
||||
dataList.add(dataVO);
|
||||
|
||||
String header = gson.toJson(stdHeadVO);
|
||||
String data = gson.toJson(dataList);
|
||||
|
||||
// 헤더 + 데이터
|
||||
rtnVal = "{\"HEAD\":" + header + " ,\r\n" + "\"DATA\":\r\n" + " [" + "{\r\n"
|
||||
+ " \"data_dvcd\":\"IO\", \r\n" + " \"BIZ_DATA\":\r\n" + " {\r\n"
|
||||
+ " \"ums_if_data_ncnt\":1, \r\n"
|
||||
+ " \"ums_if_data\":" + data
|
||||
+ " }\r\n" + " }\r\n" + " ]\r\n" + "}";
|
||||
}
|
||||
|
||||
log.debug(rtnVal);
|
||||
|
||||
return rtnVal;
|
||||
}
|
||||
|
||||
|
||||
private String[] makeStdGuidInfo() {
|
||||
// 리턴값
|
||||
String rtnVal[] = new String[3];
|
||||
|
||||
// 현재시간
|
||||
Date currentDate = new Date();
|
||||
|
||||
SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMddHHmmssSSS");
|
||||
String formattedDate = sdf.format(currentDate);
|
||||
|
||||
rtnVal[0] = formattedDate.substring(0,8);
|
||||
rtnVal[1] = formattedDate.substring(8);
|
||||
|
||||
// 랜던값
|
||||
String random20 = this.genrateRandomStringNumber20();
|
||||
|
||||
rtnVal[2] = formattedDate + SYS_DVCD + random20;
|
||||
|
||||
return rtnVal;
|
||||
}
|
||||
|
||||
|
||||
private String genrateRandomStringNumber20() {
|
||||
String rtnVal= "";
|
||||
|
||||
StringBuilder buffer= new StringBuilder(10);
|
||||
|
||||
// 랜덤값
|
||||
String characters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
|
||||
|
||||
// 랜덤객체 생성
|
||||
SecureRandom random = new SecureRandom();
|
||||
|
||||
// 반복문
|
||||
for (int i= 0;i< 10;i++) {
|
||||
// 랜덤수
|
||||
int randomIndex = random.nextInt(characters.length());
|
||||
|
||||
// 생성된 랜덤값
|
||||
char randomChar = characters.charAt(randomIndex);
|
||||
|
||||
// 랜덤값 저장
|
||||
buffer.append(randomChar);
|
||||
}
|
||||
|
||||
// 랜덤객체 재생성
|
||||
random = new SecureRandom();
|
||||
|
||||
// 난수 10개 생성
|
||||
int random10 = (int) (Math.pow(10, 9) + random.nextDouble() * Math.pow(10, 9));
|
||||
|
||||
// 문자 숫자 조합(10) + 숫자(10)
|
||||
rtnVal = buffer.toString() + Integer.toString(random10);
|
||||
|
||||
//리턴
|
||||
return rtnVal;
|
||||
}
|
||||
|
||||
private String genrateRandomStringNumber26() {
|
||||
StringBuilder buffer = new StringBuilder(26);
|
||||
String characters = "abcdefghijklmnopqrstuvwxyzABCDEFGHJKLMNOPQRSTUVWXYZ0123456789";
|
||||
SecureRandom random = new SecureRandom();
|
||||
for (int i = 0; i < 26; i++) {
|
||||
buffer.append(characters.charAt(random.nextInt(characters.length())));
|
||||
}
|
||||
return buffer.toString();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
package com.eactive.eai.rms.ext.djb.ums.vo;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
public class EmailDataVO {
|
||||
|
||||
/** UMS이벤트ID */
|
||||
private String ums_evnt_id;
|
||||
|
||||
/** 발송요청일자 */
|
||||
private String snd_dman_dt;
|
||||
|
||||
/** UUID */
|
||||
private String snd_dman_id;
|
||||
|
||||
/** 발송요청순번 */
|
||||
private String snd_dman_sno;
|
||||
|
||||
/** 메일제목 */
|
||||
private String email_titl;
|
||||
|
||||
/** 고객번호 */
|
||||
private String cstno;
|
||||
|
||||
/** 발신자 이메일 */
|
||||
private String sendr_emaddr;
|
||||
|
||||
/** 발신자 이름 */
|
||||
private String sendr_nm;
|
||||
|
||||
/** 리턴 이메일 */
|
||||
private String sndbk_emaddr;
|
||||
|
||||
/** 수신자 이메일 */
|
||||
private String recvr_emaddr;
|
||||
|
||||
/** 수신자명 */
|
||||
private String recvr_nm;
|
||||
|
||||
/** 탬플릿매핑정보 */
|
||||
private String tmplt_mpng_info;
|
||||
|
||||
/** 탬플릿매핑반복정보 */
|
||||
private String tmplt_mpng_rptt_info;
|
||||
|
||||
/** 발신자ID */
|
||||
private String snd_empno;
|
||||
|
||||
/** 발신자부서코드 */
|
||||
private String snd_emp_dprm_cd;
|
||||
|
||||
/** 업무구분코드 */
|
||||
private String bzwk_cd;
|
||||
|
||||
/** 업무세부코드 */
|
||||
private String sub_bzwk_cd;
|
||||
|
||||
/** 시스템코드 */
|
||||
private String sys_dvcd;
|
||||
|
||||
/** 거래일자 */
|
||||
private String tx_dt;
|
||||
|
||||
/** 거래시간 */
|
||||
private String tx_tktm;
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
package com.eactive.eai.rms.ext.djb.ums.vo;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
public class SmsDataVO {
|
||||
|
||||
/** 발송요청일자 */
|
||||
private String snd_dman_dt;
|
||||
|
||||
/** UUID */
|
||||
private String snd_dman_id;
|
||||
|
||||
/** 발송구분 */
|
||||
private String snd_dvcd;
|
||||
|
||||
/** 발송타입 */
|
||||
private String ltrs_snd_tycd;
|
||||
|
||||
/** LMS 제목 */
|
||||
private String msg_titl;
|
||||
|
||||
/** 메세지 */
|
||||
private String msg_ctnt;
|
||||
|
||||
/** 수신자번호 */
|
||||
private String recvr_no;
|
||||
|
||||
/** 발신번호 */
|
||||
private String sndn_no;
|
||||
|
||||
/** 요청자(직원번호) */
|
||||
private String snd_empno;
|
||||
|
||||
/** 요청부서코드 */
|
||||
private String snd_emp_dprm_cd;
|
||||
|
||||
/** 알림톡 메시지 종류 */
|
||||
private String msg_snd_tycd;
|
||||
|
||||
/** 플러스친구아이디 */
|
||||
private String almtk_snd_prfl_key;
|
||||
|
||||
/** 템플릿코드 */
|
||||
private String almtk_tmplt_cd;
|
||||
|
||||
/** 오류자문자발송여부 */
|
||||
private String almtk_err_ltrs_snd_yn;
|
||||
|
||||
/** 카카오톡전송방식 */
|
||||
private String almtk_btn_trnm_tycd;
|
||||
|
||||
/** 업무구분 코드 */
|
||||
private String sms_trnm_bzwk_dvcd;
|
||||
|
||||
/** 업무세부코드 */
|
||||
private String sms_bzwk_dcls_dvcd;
|
||||
|
||||
/** 시스템코드 */
|
||||
private String sms_sys_dvcd;
|
||||
|
||||
/** 거래일자 */
|
||||
private String tx_dt;
|
||||
|
||||
/** 거래시간 */
|
||||
private String tx_tktm;
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
package com.eactive.eai.rms.ext.djb.ums.vo;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
public class StdHeadVO {
|
||||
|
||||
private String nxgn_stnd_idfr;
|
||||
|
||||
private String guid;
|
||||
|
||||
private String guid_prgs_no;
|
||||
|
||||
private String stnd_mesg_ver;
|
||||
|
||||
private String ortr_guid;
|
||||
|
||||
private String sect_ecrpt_yn;
|
||||
|
||||
private String frst_trnm_ipad;
|
||||
|
||||
private String frst_trnm_ipv4_addr;
|
||||
|
||||
private String frst_trnm_mac;
|
||||
|
||||
private String frst_mesg_dman_dt;
|
||||
|
||||
private String frst_mesg_dman_time;
|
||||
|
||||
private String frst_trnm_sys_dvcd;
|
||||
|
||||
private String trnm_sys_dvcd;
|
||||
|
||||
private String dman_rspn_dvcd;
|
||||
|
||||
private String sys_env_dvcd;
|
||||
|
||||
private String tx_tycd;
|
||||
|
||||
private String synz_dvcd;
|
||||
|
||||
private String tx_id;
|
||||
|
||||
private String osdch_dvcd;
|
||||
|
||||
private String chnl_tycd;
|
||||
|
||||
private String if_id;
|
||||
|
||||
private String hmab_dvcd;
|
||||
|
||||
private String tx_brcd;
|
||||
|
||||
private String tlrno;
|
||||
|
||||
private String insl_brcd;
|
||||
|
||||
private String blng_brcd;
|
||||
|
||||
private String mgmt_brcd;
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
package com.eactive.eai.rms.ext.djb.ums.vo;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
public class SwingCommonVO {
|
||||
|
||||
/** 클라이업트 D */
|
||||
private String clientld;
|
||||
|
||||
/** 클라이언트 시크릿 */
|
||||
private String clientSecret;
|
||||
|
||||
/** 회사코드 */
|
||||
private String companyCode;
|
||||
|
||||
/** 요청고유키 */
|
||||
private String requestUniqueKey;
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
package com.eactive.eai.rms.ext.djb.ums.vo;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
public class SwingDataVO {
|
||||
|
||||
/** 알림코드 */
|
||||
private String notiCode;
|
||||
|
||||
/** 알림제목 */
|
||||
private String notiTitle;
|
||||
|
||||
/** 알림내용 */
|
||||
private String notiContent;
|
||||
|
||||
/** 수신자정보 */
|
||||
private List<SwingReceiverDataVO> receiverInfo;
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
package com.eactive.eai.rms.ext.djb.ums.vo;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
public class SwingReceiverDataVO {
|
||||
|
||||
/** 알림코드 */
|
||||
private String name;
|
||||
|
||||
/** 알림제목 */
|
||||
private String type;
|
||||
|
||||
/** 알림내용 */
|
||||
private String companyCode;
|
||||
|
||||
/** 수신자정보 */
|
||||
private String code;
|
||||
|
||||
/** 상품명 */
|
||||
private String prdNm;
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
package com.eactive.eai.rms.ext.djb.webhook.config;
|
||||
|
||||
import java.util.concurrent.Executor;
|
||||
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.scheduling.annotation.EnableAsync;
|
||||
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
|
||||
|
||||
@Configuration
|
||||
@EnableAsync
|
||||
public class WebhookAsyncConfig {
|
||||
|
||||
@Bean(name = "webhookExecutor")
|
||||
public Executor webhookExecutor() {
|
||||
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
|
||||
executor.setCorePoolSize(2);
|
||||
executor.setMaxPoolSize(10);
|
||||
executor.setQueueCapacity(100);
|
||||
executor.setThreadNamePrefix("webhook-");
|
||||
executor.setWaitForTasksToCompleteOnShutdown(true);
|
||||
executor.setAwaitTerminationSeconds(30);
|
||||
executor.initialize();
|
||||
return executor;
|
||||
}
|
||||
}
|
||||
+5
-9
@@ -11,7 +11,7 @@ import org.springframework.web.bind.annotation.RequestHeader;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import com.eactive.eai.rms.data.ext.djb.webhook.WebhookSendLog;
|
||||
import com.eactive.eai.rms.data.entity.onl.djb.webhook.WebhookSendLog;
|
||||
import com.eactive.eai.rms.ext.djb.webhook.dto.WebhookSendRequest;
|
||||
import com.eactive.eai.rms.ext.djb.webhook.service.WebhookReceiveService;
|
||||
import com.eactive.eai.rms.ext.djb.webhook.service.WebhookService;
|
||||
@@ -38,16 +38,12 @@ public class WebhookSendController {
|
||||
public ResponseEntity<Map<String, Object>> sendWebhook(
|
||||
@RequestBody WebhookSendRequest request) {
|
||||
|
||||
WebhookSendLog result = webhookService.send(
|
||||
request.getTargetUrl(),
|
||||
request.getEventType(),
|
||||
request.getData()
|
||||
);
|
||||
webhookService.send(request);
|
||||
|
||||
Map<String, Object> response = new HashMap<>();
|
||||
response.put("logId", result.getId());
|
||||
response.put("success", result.getSuccess());
|
||||
response.put("statusCode", result.getStatusCode());
|
||||
// response.put("logId", result.getId());
|
||||
// response.put("success", result.getSuccess());
|
||||
// response.put("statusCode", result.getStatusCode());
|
||||
|
||||
return ResponseEntity.ok(response);
|
||||
}
|
||||
|
||||
@@ -6,8 +6,9 @@ import lombok.Setter;
|
||||
@Getter
|
||||
@Setter
|
||||
public class WebhookSendRequest {
|
||||
|
||||
private String orgId;
|
||||
private String targetUrl;
|
||||
private String eventType;
|
||||
private String secret;
|
||||
private Object data;
|
||||
}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
package com.eactive.eai.rms.ext.djb.webhook.repository;
|
||||
|
||||
import com.eactive.eai.rms.data.EMSDataSource;
|
||||
import com.eactive.eai.rms.data.entity.onl.djb.webhook.WebhookReq;
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
import org.springframework.stereotype.Repository;
|
||||
|
||||
@Repository
|
||||
@EMSDataSource
|
||||
public interface WebhookReqRepository extends JpaRepository<WebhookReq, String> {
|
||||
}
|
||||
+1
-1
@@ -10,7 +10,7 @@ import org.springframework.data.repository.query.Param;
|
||||
import org.springframework.stereotype.Repository;
|
||||
|
||||
import com.eactive.eai.rms.data.EMSDataSource;
|
||||
import com.eactive.eai.rms.data.ext.djb.webhook.WebhookSendLog;
|
||||
import com.eactive.eai.rms.data.entity.onl.djb.webhook.WebhookSendLog;
|
||||
|
||||
@Repository
|
||||
@EMSDataSource
|
||||
|
||||
@@ -1,17 +0,0 @@
|
||||
package com.eactive.eai.rms.ext.djb.webhook.service;
|
||||
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
@Slf4j
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class WebhookSendManager {
|
||||
|
||||
public void send(String eaisvcname, String eventType) {
|
||||
log.info("[Webhook] 발송 준비: {} {}", eaisvcname, eventType);
|
||||
// TODO: PTL_WEBHOOK_REQ 조인 조회 후 WebhookService.send() 호출
|
||||
}
|
||||
}
|
||||
@@ -2,12 +2,16 @@ package com.eactive.eai.rms.ext.djb.webhook.service;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.ArrayList;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import javax.crypto.Mac;
|
||||
import javax.crypto.spec.SecretKeySpec;
|
||||
import javax.persistence.EntityManager;
|
||||
import javax.persistence.PersistenceContext;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.http.HttpEntity;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.MediaType;
|
||||
@@ -18,10 +22,17 @@ import org.springframework.web.client.HttpClientErrorException;
|
||||
import org.springframework.web.client.HttpServerErrorException;
|
||||
import org.springframework.web.client.RestTemplate;
|
||||
|
||||
import com.eactive.eai.rms.data.ext.djb.webhook.WebhookSendLog;
|
||||
import com.eactive.eai.rms.common.context.MonitoringContext;
|
||||
import com.eactive.eai.rms.data.entity.onl.djb.webhook.QWebhookReq;
|
||||
import com.eactive.eai.rms.data.entity.onl.djb.webhook.QWebhookReqApi;
|
||||
import com.eactive.eai.rms.data.entity.onl.djb.webhook.QWebhookReqEvent;
|
||||
import com.eactive.eai.rms.data.entity.onl.djb.webhook.WebhookSendLog;
|
||||
import com.eactive.eai.rms.ext.djb.webhook.dto.WebhookPayload;
|
||||
import com.eactive.eai.rms.ext.djb.webhook.dto.WebhookSendRequest;
|
||||
import com.eactive.eai.rms.ext.djb.webhook.repository.WebhookSendLogRepository;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.querydsl.core.Tuple;
|
||||
import com.querydsl.jpa.impl.JPAQueryFactory;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
@@ -34,19 +45,63 @@ public class WebhookService {
|
||||
private final WebhookSendLogRepository sendLogRepository;
|
||||
private final ObjectMapper objectMapper;
|
||||
private final RestTemplate restTemplate;
|
||||
private final MonitoringContext monitoringContext;
|
||||
|
||||
|
||||
private String secretKey = "HW8JtFpkPmQqsVmr0Rb81P4qDypaNIVbGf3ZNMMPfyerhOHshoKABLaMBo1HA4BldTxhw1NjYmVnoPu9IIV7cyRXjecL3b2UctyR7DnVaJausltZLLr8Qm4Bzs4wRmgf";
|
||||
@PersistenceContext(unitName = "entityManagerFactoryForEMS")
|
||||
private EntityManager entityManager;
|
||||
|
||||
private static final String HMAC_ALGORITHM = "HmacSHA256";
|
||||
private static final int MAX_RETRY = 3;
|
||||
private static final int DEFAULT_RETRY_COUNT = 3;
|
||||
private static final int DEFAULT_RETRY_TIME = 10000;
|
||||
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public List<WebhookSendRequest> findSendList(String event, List<String> apiIds) {
|
||||
QWebhookReq req = QWebhookReq.webhookReq;
|
||||
QWebhookReqEvent evt = QWebhookReqEvent.webhookReqEvent;
|
||||
QWebhookReqApi api = QWebhookReqApi.webhookReqApi;
|
||||
|
||||
List<Tuple> tuples = new JPAQueryFactory(entityManager)
|
||||
.select(req.orgId, req.targetUrl, req.secret, api.id.apiId)
|
||||
.from(req)
|
||||
.join(evt).on(req.id.eq(evt.id.webhookReqId))
|
||||
.join(api).on(req.id.eq(api.id.webhookReqId))
|
||||
.where(evt.id.eventType.eq(event)
|
||||
.and(api.id.apiId.in(apiIds)))
|
||||
.fetch();
|
||||
|
||||
// orgId + targetUrl + secret 기준으로 그룹핑, apiId를 data(List)로 집계
|
||||
Map<String, WebhookSendRequest> resultMap = new LinkedHashMap<>();
|
||||
for (Tuple t : tuples) {
|
||||
String key = t.get(req.orgId) + "|" + t.get(req.targetUrl);
|
||||
resultMap.computeIfAbsent(key, k -> {
|
||||
WebhookSendRequest wr = new WebhookSendRequest();
|
||||
wr.setOrgId(t.get(req.orgId));
|
||||
wr.setTargetUrl(t.get(req.targetUrl));
|
||||
wr.setSecret(t.get(req.secret));
|
||||
wr.setEventType(event);
|
||||
wr.setData(new ArrayList<String>());
|
||||
return wr;
|
||||
});
|
||||
((List<String>) resultMap.get(key).getData()).add(t.get(api.id.apiId));
|
||||
}
|
||||
|
||||
return new ArrayList<>(resultMap.values());
|
||||
}
|
||||
|
||||
/**
|
||||
* 웹훅 발송 메인 메서드
|
||||
*/
|
||||
@Transactional
|
||||
public WebhookSendLog send(String targetUrl, String eventType, Object data) {
|
||||
public void send(WebhookSendRequest req) {
|
||||
|
||||
String targetUrl = req.getTargetUrl();
|
||||
String secret = req.getSecret();
|
||||
String eventType = req.getEventType();
|
||||
Object data = req.getData();
|
||||
|
||||
WebhookSendLog sendLog = new WebhookSendLog();
|
||||
sendLog.setOrgId(req.getOrgId());
|
||||
sendLog.setTargetUrl(targetUrl);
|
||||
sendLog.setEventType(eventType);
|
||||
|
||||
@@ -57,7 +112,7 @@ public class WebhookService {
|
||||
sendLog.setPayload(payloadJson);
|
||||
|
||||
// 2. Secret Key로 HMAC-SHA256 서명 생성
|
||||
String signature = generateSignature(payloadJson);
|
||||
String signature = generateSignature(secret, payloadJson);
|
||||
sendLog.setSignature(signature);
|
||||
|
||||
// 3. HTTP 요청 헤더 구성
|
||||
@@ -67,18 +122,20 @@ public class WebhookService {
|
||||
headers.set("X-Webhook-Event", eventType);
|
||||
headers.set("X-Webhook-Timestamp", String.valueOf(webhookPayload.getTimestamp()));
|
||||
|
||||
// 4. 발송
|
||||
// 4. 발송 (재시도 포함)
|
||||
int retryCount = monitoringContext.getIntProperty(MonitoringContext.API_WEBHOOK_RETRY_COUNT, DEFAULT_RETRY_COUNT);
|
||||
int retryTime = monitoringContext.getIntProperty(MonitoringContext.API_WEBHOOK_RETRY_TIME, DEFAULT_RETRY_TIME);
|
||||
sendLog.setSentAt(LocalDateTime.now());
|
||||
HttpEntity<String> request = new HttpEntity<>(payloadJson, headers);
|
||||
ResponseEntity<String> response = restTemplate.postForEntity(targetUrl, request, String.class);
|
||||
HttpEntity<String> httpRequest = new HttpEntity<>(payloadJson, headers);
|
||||
ResponseEntity<String> response = sendWithRetry(targetUrl, httpRequest, sendLog, retryCount, retryTime);
|
||||
|
||||
// 5. 성공 로그
|
||||
sendLog.setStatusCode(response.getStatusCode().value());
|
||||
sendLog.setResponseBody(response.getBody());
|
||||
sendLog.setSuccess(response.getStatusCode().is2xxSuccessful());
|
||||
|
||||
log.info("[Webhook] 발송 성공 - eventType: {}, url: {}, status: {}",
|
||||
eventType, targetUrl, response.getStatusCode());
|
||||
log.info("[Webhook] 발송 성공 - eventType: {}, url: {}, status: {}, retryCount: {}",
|
||||
eventType, targetUrl, response.getStatusCode(), sendLog.getRetryCount());
|
||||
|
||||
} catch (HttpClientErrorException | HttpServerErrorException e) {
|
||||
sendLog.setStatusCode(e.getStatusCode().value());
|
||||
@@ -95,40 +152,46 @@ public class WebhookService {
|
||||
eventType, targetUrl, e.getMessage());
|
||||
}
|
||||
|
||||
return sendLogRepository.save(sendLog);
|
||||
sendLogRepository.save(sendLog);
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* 실패 건 재시도
|
||||
* 재시도 포함 HTTP 발송
|
||||
* - 4xx: 클라이언트 오류이므로 즉시 throw (재시도 불필요)
|
||||
* - 5xx / 네트워크 오류: 최대 RETRY_COUNT회까지 exponential backoff 재시도 (1s → 2s → 4s)
|
||||
*/
|
||||
@Transactional
|
||||
public void retryFailedWebhooks() {
|
||||
List<WebhookSendLog> failedLogs =
|
||||
sendLogRepository.findFailedLogs(MAX_RETRY);
|
||||
|
||||
for (WebhookSendLog failedLog : failedLogs) {
|
||||
private ResponseEntity<String> sendWithRetry(String targetUrl, HttpEntity<String> request, WebhookSendLog sendLog, int retryCount, int retryTime) throws Exception {
|
||||
Exception lastException = null;
|
||||
for (int attempt = 0; attempt <= retryCount; attempt++) {
|
||||
try {
|
||||
log.info("[Webhook] 재시도 - id: {}, retry: {}", failedLog.getId(), failedLog.getRetryCount());
|
||||
failedLog.setRetryCount(failedLog.getRetryCount() + 1);
|
||||
sendLogRepository.save(failedLog);
|
||||
|
||||
send(failedLog.getTargetUrl(), failedLog.getEventType(),
|
||||
objectMapper.readValue(failedLog.getPayload(), Object.class));
|
||||
|
||||
if (attempt > 0) {
|
||||
long delayMs = retryTime * (1L << (attempt - 1));
|
||||
log.info("[Webhook] 재시도 {}/{} - url: {}, delay: {}ms", attempt, retryCount, targetUrl, delayMs);
|
||||
Thread.sleep(delayMs);
|
||||
sendLog.setRetryCount(attempt);
|
||||
}
|
||||
return restTemplate.postForEntity(targetUrl, request, String.class);
|
||||
} catch (HttpClientErrorException e) {
|
||||
throw e; // 4xx는 재시도 불필요
|
||||
} catch (Exception e) {
|
||||
log.error("[Webhook] 재시도 실패 - id: {}", failedLog.getId(), e);
|
||||
lastException = e;
|
||||
log.warn("[Webhook] 발송 실패 (attempt {}/{}) - url: {}, error: {}", attempt, retryCount, targetUrl, e.getMessage());
|
||||
}
|
||||
}
|
||||
throw lastException;
|
||||
}
|
||||
|
||||
/**
|
||||
* HMAC-SHA256 서명 생성
|
||||
* Java 17 미만은 HexFormat 미지원이므로 직접 변환
|
||||
*/
|
||||
private String generateSignature(String payload) throws Exception {
|
||||
private String generateSignature(String secret, String payload) throws Exception {
|
||||
Mac mac = Mac.getInstance(HMAC_ALGORITHM);
|
||||
SecretKeySpec keySpec = new SecretKeySpec(
|
||||
secretKey.getBytes(StandardCharsets.UTF_8), HMAC_ALGORITHM);
|
||||
secret.getBytes(StandardCharsets.UTF_8), HMAC_ALGORITHM);
|
||||
mac.init(keySpec);
|
||||
byte[] hash = mac.doFinal(payload.getBytes(StandardCharsets.UTF_8));
|
||||
return bytesToHex(hash);
|
||||
|
||||
@@ -14,6 +14,7 @@ public class PortalApprovalAuthorizer implements ApprovalAuthorizer {
|
||||
return approval.getApprovers().stream()
|
||||
.filter(this::isCurrentApprover)
|
||||
.map(this::getApproverId)
|
||||
.filter(java.util.Objects::nonNull)
|
||||
.anyMatch(id -> id.equals(approverId));
|
||||
}
|
||||
|
||||
@@ -22,6 +23,6 @@ public class PortalApprovalAuthorizer implements ApprovalAuthorizer {
|
||||
}
|
||||
|
||||
private String getApproverId(Approver approver) {
|
||||
return approver.getUser().getId();
|
||||
return approver.getUser() != null ? approver.getUser().getId() : null;
|
||||
}
|
||||
}
|
||||
|
||||
+56
-2
@@ -13,10 +13,16 @@ import org.springframework.data.domain.Sort;
|
||||
import org.springframework.data.web.SortDefault;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.stereotype.Controller;
|
||||
import org.springframework.ui.Model;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
@Controller
|
||||
@RequiredArgsConstructor
|
||||
public class PortalApprovalManController extends BaseAnnotationController {
|
||||
@@ -24,8 +30,8 @@ public class PortalApprovalManController extends BaseAnnotationController {
|
||||
private final PortalApprovalManService portalApprovalManService;
|
||||
|
||||
@GetMapping(value = "/onl/apim/approval/portalApprovalMan.view")
|
||||
public void view() {
|
||||
// view
|
||||
public void view(Model model) {
|
||||
model.addAttribute("hardDeleteEnabled", portalApprovalManService.isHardDeleteEnabled());
|
||||
}
|
||||
|
||||
@GetMapping(value = "/onl/apim/approval/portalApprovalMan.view", params = "cmd=DETAIL")
|
||||
@@ -76,4 +82,52 @@ public class PortalApprovalManController extends BaseAnnotationController {
|
||||
portalApprovalManService.redeploy(id);
|
||||
return ResponseEntity.ok().build();
|
||||
}
|
||||
|
||||
@PostMapping(value = "/onl/apim/approval/portalApprovalMan.json", params = "cmd=HARD_DELETE_MULTIPLE")
|
||||
public ResponseEntity<Map<String, Object>> hardDeleteMultiple(String ids) {
|
||||
Map<String, Object> resultMap = new HashMap<>();
|
||||
try {
|
||||
if (StringUtils.isBlank(ids)) {
|
||||
resultMap.put("status", "fail");
|
||||
resultMap.put("message", "삭제할 승인 요청을 선택해주세요.");
|
||||
return ResponseEntity.ok(resultMap);
|
||||
}
|
||||
|
||||
String[] idArray = ids.split(",");
|
||||
int successCount = 0;
|
||||
List<String> failMessages = new ArrayList<>();
|
||||
|
||||
for (String id : idArray) {
|
||||
if (StringUtils.isNotBlank(id.trim())) {
|
||||
try {
|
||||
portalApprovalManService.hardDelete(id.trim());
|
||||
successCount++;
|
||||
} catch (BizException e) {
|
||||
failMessages.add(e.getMessage());
|
||||
} catch (Exception e) {
|
||||
failMessages.add(id.trim() + ": " + e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (failMessages.isEmpty()) {
|
||||
resultMap.put("status", "success");
|
||||
resultMap.put("message", successCount + "건의 승인 요청이 완전삭제되었습니다.");
|
||||
} else {
|
||||
resultMap.put("status", "fail");
|
||||
resultMap.put("message", "완전삭제 실패: " + String.join(", ", failMessages)
|
||||
+ (successCount > 0 ? " (" + successCount + "건 성공)" : ""));
|
||||
}
|
||||
} catch (Exception e) {
|
||||
resultMap.put("status", "fail");
|
||||
resultMap.put("message", "완전삭제 중 오류가 발생했습니다: " + e.getMessage());
|
||||
}
|
||||
return ResponseEntity.ok(resultMap);
|
||||
}
|
||||
|
||||
@PostMapping(value = "/onl/apim/approval/portalApprovalMan.json", params = "cmd=UPDATE")
|
||||
public ResponseEntity<Void> update(String id, String expectEndDate) {
|
||||
portalApprovalManService.update(id, expectEndDate);
|
||||
return ResponseEntity.ok().build();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ import com.eactive.apim.portal.apprequest.repository.AppRequestRepository;
|
||||
import com.eactive.apim.portal.approval.entity.Approval;
|
||||
import com.eactive.apim.portal.approval.entity.ApprovalType;
|
||||
import com.eactive.apim.portal.approval.statemachine.*;
|
||||
import com.eactive.apim.portal.portalproperty.service.PortalPropertyService;
|
||||
import com.eactive.apim.portal.portaluser.entity.PortalUser;
|
||||
import com.eactive.apim.portal.portaluser.repository.PortalUserRepository;
|
||||
import com.eactive.eai.data.entity.onl.apim.apigroup.ApiGroup;
|
||||
@@ -25,6 +26,7 @@ import com.eactive.eai.rms.onl.apim.portalorg.PortalOrgManService;
|
||||
import com.eactive.eai.rms.onl.apim.portaluser.PortalUserManService;
|
||||
import com.eactive.eai.rms.onl.apim.portaluser.PortalUserUI;
|
||||
import com.eactive.eai.rms.common.acl.user.ui.UserUI;
|
||||
import com.eactive.eai.rms.onl.common.exception.BizException;
|
||||
import com.eactive.eai.rms.onl.transaction.apim.ApiInterfaceService;
|
||||
import com.eactive.eai.rms.onl.transaction.apim.mapping.ApiSpecUIMapper;
|
||||
import com.eactive.eai.rms.onl.transaction.apim.ui.ApiInterfaceUI;
|
||||
@@ -36,6 +38,8 @@ import org.springframework.data.domain.Pageable;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import javax.persistence.EntityManager;
|
||||
import javax.persistence.PersistenceContext;
|
||||
import java.util.*;
|
||||
|
||||
@Service
|
||||
@@ -43,6 +47,9 @@ import java.util.*;
|
||||
@RequiredArgsConstructor
|
||||
public class PortalApprovalManService extends BaseService {
|
||||
|
||||
private static final String PORTAL_PROPERTY_GROUP = "Portal";
|
||||
private static final String HARD_DELETE_FLAG_KEY = "approval.hard-delete.enabled";
|
||||
|
||||
private final ApprovalService approvalService;
|
||||
private final ApprovalStateMachine approvalStateMachine;
|
||||
private final AppRequestRepository appRequestRepository;
|
||||
@@ -59,6 +66,10 @@ public class PortalApprovalManService extends BaseService {
|
||||
private final ApiGroupUIMapper apiGroupUIMapper;
|
||||
private final PortalApprovalAuthorizer portalApprovalAuthorizer;
|
||||
private final PortalOrgManService portalOrgManService;
|
||||
private final PortalPropertyService portalPropertyService;
|
||||
|
||||
@PersistenceContext(unitName = "entityManagerFactoryForEMS")
|
||||
private EntityManager entityManager;
|
||||
|
||||
public Page<PortalApprovalUI> selectList(Pageable pageable, PortalApprovalUISearch portalApprovalUISearch) {
|
||||
Page<Approval> approval = approvalService.findAll(pageable, portalApprovalUISearch);
|
||||
@@ -76,6 +87,16 @@ public class PortalApprovalManService extends BaseService {
|
||||
* 마스킹된 상세 정보 조회
|
||||
*/
|
||||
public PortalApprovalUI selectDetail(String id) {
|
||||
//2026.06.05 관리자가 승인요청을 조회시 심사중으로 상태 변경
|
||||
//APP 유형이고 REQUESTED 상태인 경우 관리자 조회 시 PROCESSING으로 변경
|
||||
approvalService.findById(id).ifPresent(approval -> {
|
||||
if (approval.getApprovalType().equals(ApprovalType.APP)
|
||||
&& approval.getApprovalStatus() instanceof RequestedState) {
|
||||
approval.setApprovalStatus(new ProcessingState());
|
||||
approvalService.save(approval);
|
||||
}
|
||||
});
|
||||
|
||||
PortalApprovalUI result = getDetailWithMaskOption(id, false);
|
||||
if (result == null) {
|
||||
throw new IllegalArgumentException("승인 정보를 찾을 수 없습니다. ID: " + id);
|
||||
@@ -320,6 +341,13 @@ public class PortalApprovalManService extends BaseService {
|
||||
sendEvent(id, ApprovalEvent.DENY, options);
|
||||
}
|
||||
|
||||
public void update(String id, String expectEndDate) {
|
||||
Approval approval = approvalService.findById(id)
|
||||
.orElseThrow(() -> new IllegalArgumentException("승인 정보를 찾을 수 없습니다. ID: " + id));
|
||||
approval.setExpectEndDate(expectEndDate);
|
||||
approvalService.save(approval);
|
||||
}
|
||||
|
||||
public void redeploy(String id) {
|
||||
Map options = new HashMap<>();
|
||||
Approval approval = approvalService.findById(id).orElseThrow(() -> new IllegalArgumentException("승인 정보를 찾을 수 없습니다. ID: " + id));
|
||||
@@ -330,6 +358,28 @@ public class PortalApprovalManService extends BaseService {
|
||||
|
||||
}
|
||||
|
||||
public boolean isHardDeleteEnabled() {
|
||||
Map<String, String> properties = portalPropertyService.getPortalPropertiesAsMap(PORTAL_PROPERTY_GROUP);
|
||||
return Boolean.parseBoolean(properties.getOrDefault(HARD_DELETE_FLAG_KEY, "false"));
|
||||
}
|
||||
|
||||
public void hardDelete(String id) {
|
||||
if (!isHardDeleteEnabled()) {
|
||||
throw new BizException("승인 요청 완전삭제 기능이 비활성화되어 있습니다. (PTL_PROPERTY: " + HARD_DELETE_FLAG_KEY + ")");
|
||||
}
|
||||
// Approval.approvers 가 EAGER + Approver.user 가 NotFound 처리 없음 → 사라진 UserInfo 로 인해
|
||||
// findById/deleteById 가 hydration 단계에서 실패하는 row 가 존재. 네이티브 SQL 로 우회.
|
||||
entityManager.createNativeQuery("DELETE FROM PTL_APPROVER WHERE APPROVAL_ID = :id")
|
||||
.setParameter("id", id)
|
||||
.executeUpdate();
|
||||
int affected = entityManager.createNativeQuery("DELETE FROM PTL_APPROVAL WHERE ID = :id")
|
||||
.setParameter("id", id)
|
||||
.executeUpdate();
|
||||
if (affected == 0) {
|
||||
throw new BizException("승인 정보를 찾을 수 없습니다. ID: " + id);
|
||||
}
|
||||
}
|
||||
|
||||
public void sendEvent(String id, ApprovalEvent event, Map<String, Object> options) {
|
||||
approvalService.findById(id).ifPresent(approval -> {
|
||||
options.put("authorizer", portalApprovalAuthorizer);
|
||||
|
||||
@@ -38,6 +38,8 @@ public class PortalApprovalUI {
|
||||
@DateTimeFormat(pattern = "yyyyMMddHHmmssSSS")
|
||||
private String approvalDate;
|
||||
|
||||
private String expectEndDate;
|
||||
|
||||
public String getApprovalTypeDescription() {
|
||||
return approvalType != null ? approvalType.getDescription() : "";
|
||||
}
|
||||
|
||||
+10
-17
@@ -6,7 +6,6 @@ import com.eactive.apim.portal.apprequest.repository.AppRequestRepository;
|
||||
import com.eactive.apim.portal.approval.entity.Approval;
|
||||
import com.eactive.apim.portal.approval.service.ApprovalDeployException;
|
||||
import com.eactive.apim.portal.approval.statemachine.listener.ApprovalListener;
|
||||
import com.eactive.apim.portal.portalproperty.service.PortalPropertyService;
|
||||
import com.eactive.apim.portal.template.entity.MessageCode;
|
||||
import com.eactive.apim.portal.template.service.MessageRecipient;
|
||||
import com.eactive.apim.portal.template.service.MessageSendService;
|
||||
@@ -19,10 +18,8 @@ import com.eactive.eai.rms.onl.apim.approval.RandomStringGenerator;
|
||||
import com.eactive.eai.rms.onl.apim.approval.credential.CredentialManService;
|
||||
import com.eactive.eai.rms.onl.apim.approval.credential.CredentialUI;
|
||||
import com.eactive.eai.rms.onl.apim.approval.credential.CredentialUIMapper;
|
||||
import com.eactive.eai.rms.onl.apim.portaluser.PortalUserUI;
|
||||
import com.eactive.eai.rms.onl.common.service.AgentUtilService;
|
||||
import com.eactive.eai.rms.onl.manage.authserver.client.ClientManService;
|
||||
import com.eactive.eai.rms.onl.manage.authserver.client.ClientUI;
|
||||
import com.eactive.eai.rms.onl.transaction.apim.ui.ApiSpecInfoUI;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
@@ -35,22 +32,12 @@ import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.apache.commons.lang.StringUtils;
|
||||
import org.apache.http.client.HttpClient;
|
||||
import org.apache.http.client.config.RequestConfig;
|
||||
import org.apache.http.impl.client.HttpClientBuilder;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.annotation.Qualifier;
|
||||
import org.springframework.http.HttpEntity;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.HttpMethod;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
import org.springframework.web.client.RestTemplate;
|
||||
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
@@ -116,9 +103,15 @@ public class PortalAppApprovalListener implements ApprovalListener {
|
||||
}
|
||||
|
||||
private void sendApprovalResult(AppRequest appRequest) {
|
||||
com.eactive.apim.portal.portaluser.entity.PortalUser requester =
|
||||
appRequest.getApproval() != null ? appRequest.getApproval().getRequester() : null;
|
||||
if (requester == null) {
|
||||
logger.warn("승인 결과 통지 생략 - 요청자 정보 없음. appRequestId: {}", appRequest.getId());
|
||||
return;
|
||||
}
|
||||
MessageRecipient recipient = new MessageRecipient();
|
||||
recipient.setPhone(appRequest.getApproval().getRequester().getMobileNumber());
|
||||
recipient.setUserId(appRequest.getApproval().getRequester().getEmailAddr());
|
||||
recipient.setPhone(requester.getMobileNumber());
|
||||
recipient.setUserId(requester.getEmailAddr());
|
||||
Map<String, String> params = new HashMap<>();
|
||||
params.put("apiKey", appRequest.getClientName());
|
||||
messageSendService.sendMessage(MessageCode.APP_REGISTER_APPROVED, recipient, params);
|
||||
@@ -165,8 +158,8 @@ public class PortalAppApprovalListener implements ApprovalListener {
|
||||
credentialUI.setClientname(appRequest.getClientName());
|
||||
credentialUI.setScope("api");
|
||||
credentialUI.setGranttypes("client_credentials");
|
||||
credentialUI.setOrgid(appRequest.getOrg().getId());
|
||||
credentialUI.setOrgname(appRequest.getOrg().getOrgName());
|
||||
credentialUI.setOrgid(appRequest.getOrg() != null ? appRequest.getOrg().getId() : null);
|
||||
credentialUI.setOrgname(appRequest.getOrg() != null ? appRequest.getOrg().getOrgName() : null);
|
||||
credentialUI.setAllowedips(appRequest.getIpWhitelist());
|
||||
credentialUI.setRedirecturi(appRequest.getCallbackUrl());
|
||||
credentialUI.setAppIconFileId(appRequest.getAppIconFileId());
|
||||
|
||||
+78
@@ -0,0 +1,78 @@
|
||||
package com.eactive.eai.rms.onl.apim.portalInquiry;
|
||||
|
||||
import com.eactive.apim.portal.qna.entity.Inquiry;
|
||||
import com.eactive.apim.portal.qna.entity.InquiryComment;
|
||||
import com.eactive.eai.rms.data.entity.onl.apim.portalinquiry.PortalInquiryCommentRepository;
|
||||
import com.eactive.eai.rms.data.entity.onl.apim.portalinquiry.PortalInquiryCommentService;
|
||||
import com.eactive.eai.rms.data.entity.onl.apim.portalinquiry.PortalInquiryService;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
/**
|
||||
* RESPONDED 상태 문의글 중 아래 조건에 해당하는 경우 CLOSED로 변경한다.
|
||||
* - 조건 A: 활성 댓글이 없고 답변완료일(responseDate)로부터 N일 경과
|
||||
* - 조건 B: 마지막 관리자 댓글(adminYn=Y) 이후 N일간 포탈사용자(adminYn=N) 댓글 없음
|
||||
*/
|
||||
@Service
|
||||
@Transactional(transactionManager = "transactionManagerForEMS")
|
||||
@Slf4j
|
||||
@RequiredArgsConstructor
|
||||
public class PortalInquiryClosingService {
|
||||
|
||||
private static final String RESPONDED = "RESPONDED";
|
||||
private static final String CLOSED = "CLOSED";
|
||||
|
||||
private final PortalInquiryService portalInquiryService;
|
||||
private final PortalInquiryCommentService portalInquiryCommentService;
|
||||
private final PortalInquiryCommentRepository inquiryCommentRepository;
|
||||
|
||||
public int closeExpiredInquiries(int closingDays) {
|
||||
LocalDateTime threshold = LocalDateTime.now().minusDays(closingDays);
|
||||
List<Inquiry> respondedInquiries = portalInquiryService.findByInquiryStatus(RESPONDED);
|
||||
|
||||
int closedCount = 0;
|
||||
for (Inquiry inquiry : respondedInquiries) {
|
||||
if (shouldClose(inquiry, threshold)) {
|
||||
inquiry.setInquiryStatus(CLOSED);
|
||||
portalInquiryService.save(inquiry);
|
||||
closedCount++;
|
||||
log.info("문의글 CLOSED 처리: id={}", inquiry.getId());
|
||||
}
|
||||
}
|
||||
log.info("InquiryCommentClosingService: 총 {}건 CLOSED 처리 (기준={}일)", closedCount, closingDays);
|
||||
return closedCount;
|
||||
}
|
||||
|
||||
private boolean shouldClose(Inquiry inquiry, LocalDateTime threshold) {
|
||||
long activeCommentCount = portalInquiryCommentService.countActiveByInquiryId(inquiry.getId());
|
||||
|
||||
// 댓글 없음 → 답변완료일 기준 N일 경과 여부만 확인
|
||||
if (activeCommentCount == 0) {
|
||||
LocalDateTime responseDate = inquiry.getResponseDate();
|
||||
return responseDate != null && !responseDate.isAfter(threshold);
|
||||
}
|
||||
|
||||
// 가장 최근 활성 댓글 확인
|
||||
Optional<InquiryComment> lastComment = inquiryCommentRepository
|
||||
.findTopByInquiry_IdAndDelYnOrderByCreatedDateDesc(inquiry.getId(), "N");
|
||||
|
||||
if (!lastComment.isPresent()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 포탈사용자(adminYn != 'Y')가 마지막 댓글 작성자 → CLOSED 불가
|
||||
if (!"Y".equals(lastComment.get().getAdminYn())) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 관리자가 마지막 댓글 작성자 → N일 경과 여부 확인
|
||||
LocalDateTime lastAdminDate = lastComment.get().getCreatedDate();
|
||||
return lastAdminDate != null && !lastAdminDate.isAfter(threshold);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
package com.eactive.eai.rms.onl.apim.portalInquiry;
|
||||
|
||||
import com.eactive.apim.portal.file.entity.FileInfo;
|
||||
import com.eactive.eai.data.converter.LocalDateTimeFormatters;
|
||||
import com.eactive.eai.rms.onl.apim.portaluser.PortalUserUI;
|
||||
import com.fasterxml.jackson.annotation.JsonFormat;
|
||||
import lombok.Data;
|
||||
import org.springframework.format.annotation.DateTimeFormat;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.List;
|
||||
|
||||
|
||||
@Data
|
||||
public class PortalInquiryCommentUI {
|
||||
|
||||
private String id;
|
||||
|
||||
private String inquiryId;
|
||||
|
||||
private String commentDetail;
|
||||
|
||||
private String delYn;
|
||||
|
||||
private String createdBy;
|
||||
|
||||
private String writerName;
|
||||
|
||||
private String lastModifiedBy;
|
||||
|
||||
@JsonFormat(pattern = LocalDateTimeFormatters.FORMAT_YYYYMMDDHHMMSS_14)
|
||||
@DateTimeFormat(pattern = "yyyyMMddHHmmss")
|
||||
private LocalDateTime createdDate;
|
||||
|
||||
@JsonFormat(pattern = LocalDateTimeFormatters.FORMAT_YYYYMMDDHHMMSS_14)
|
||||
@DateTimeFormat(pattern = "yyyyMMddHHmmss")
|
||||
private LocalDateTime lastModifiedDate;
|
||||
|
||||
}
|
||||
+17
@@ -0,0 +1,17 @@
|
||||
package com.eactive.eai.rms.onl.apim.portalInquiry;
|
||||
|
||||
import com.eactive.apim.portal.qna.entity.InquiryComment;
|
||||
import org.mapstruct.Mapper;
|
||||
import org.mapstruct.Mapping;
|
||||
import org.mapstruct.ReportingPolicy;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Mapper(componentModel = "spring", unmappedTargetPolicy = ReportingPolicy.IGNORE)
|
||||
public interface PortalInquiryCommentUIMapper {
|
||||
|
||||
@Mapping(target = "inquiryId", source = "inquiry.id")
|
||||
PortalInquiryCommentUI toVo(InquiryComment entity);
|
||||
|
||||
List<PortalInquiryCommentUI> toVoList(List<InquiryComment> entities);
|
||||
}
|
||||
+18
@@ -73,5 +73,23 @@ public class PortalInquiryManController extends BaseAnnotationController {
|
||||
portalInquiryManService.deleteBulk(ids);
|
||||
return ResponseEntity.ok().build();
|
||||
}
|
||||
|
||||
@PostMapping(value = "/onl/apim/portalinquiry/portalInquiryMan.json", params = "cmd=LIST_COMMENT")
|
||||
public ResponseEntity<List<PortalInquiryCommentUI>> selectCommentList(String id) {
|
||||
return ResponseEntity.ok(portalInquiryManService.selectCommentList(id));
|
||||
}
|
||||
|
||||
@PostMapping(value = "/onl/apim/portalinquiry/portalInquiryMan.json", params = "cmd=INSERT_COMMENT")
|
||||
public ResponseEntity<String> insertComment(HttpServletRequest request, PortalInquiryCommentUI portalInquiryCommentUI) {
|
||||
String userid = SessionManager.getUserId(request);
|
||||
portalInquiryManService.insertComment(portalInquiryCommentUI, userid);
|
||||
return ResponseEntity.ok().build();
|
||||
}
|
||||
|
||||
@PostMapping(value = "/onl/apim/portalinquiry/portalInquiryMan.json", params = "cmd=DELETE_COMMENT")
|
||||
public ResponseEntity<Void> deleteComment(String id) {
|
||||
portalInquiryManService.deleteComment(id);
|
||||
return ResponseEntity.ok().build();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -4,10 +4,14 @@ import com.eactive.apim.portal.file.entity.FileInfo;
|
||||
import com.eactive.apim.portal.file.service.FileService;
|
||||
import com.eactive.apim.portal.portaluser.entity.PortalUser;
|
||||
import com.eactive.apim.portal.portaluser.entity.PortalUserEnums;
|
||||
import com.eactive.apim.portal.portaluser.repository.PortalUserRepository;
|
||||
import com.eactive.apim.portal.qna.entity.Inquiry;
|
||||
import com.eactive.apim.portal.qna.entity.InquiryComment;
|
||||
import com.eactive.apim.portal.user.entity.UserInfo;
|
||||
import com.eactive.eai.rms.common.base.BaseService;
|
||||
import com.eactive.eai.rms.common.exception.IntegrationException;
|
||||
import com.eactive.eai.rms.data.entity.man.user.UserInfoService;
|
||||
import com.eactive.eai.rms.data.entity.onl.apim.portalinquiry.PortalInquiryCommentService;
|
||||
import com.eactive.eai.rms.data.entity.onl.apim.portalinquiry.PortalInquirySearch;
|
||||
import com.eactive.eai.rms.data.entity.onl.apim.portalinquiry.PortalInquiryService;
|
||||
import com.eactive.eai.rms.onl.apim.masking.MaskingUtils;
|
||||
@@ -22,27 +26,38 @@ import lombok.extern.slf4j.Slf4j;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
@Service
|
||||
@Transactional(transactionManager = "transactionManagerForEMS")
|
||||
@Slf4j
|
||||
public class PortalInquiryManService extends BaseService {
|
||||
private final PortalInquiryService portalInquiryService;
|
||||
private final PortalInquiryCommentService portalInquiryCommentService;
|
||||
private final PortalInquiryUIMapper portalInquiryUIMapper;
|
||||
private final PortalInquiryCommentUIMapper portalInquiryCommentUIMapper;
|
||||
private final UserInfoService userInfoService;
|
||||
private final PortalUserRepository portalUserRepository;
|
||||
private final FileService fileService;
|
||||
|
||||
public static final String PENDING = "PENDING";
|
||||
public static final String RESPONDED = "RESPONDED";
|
||||
public static final String CLOSED = "CLOSED";
|
||||
|
||||
|
||||
public PortalInquiryManService(PortalInquiryService portalInquiryService,
|
||||
PortalInquiryCommentService portalInquiryCommentService,
|
||||
PortalInquiryUIMapper portalInquiryUIMapper,
|
||||
PortalInquiryCommentUIMapper portalInquiryCommentUIMapper,
|
||||
UserInfoService userInfoService,
|
||||
PortalUserRepository portalUserRepository,
|
||||
FileService fileService){
|
||||
this.portalInquiryService = portalInquiryService;
|
||||
this.portalInquiryCommentService = portalInquiryCommentService;
|
||||
this.portalInquiryUIMapper = portalInquiryUIMapper;
|
||||
this.portalInquiryCommentUIMapper = portalInquiryCommentUIMapper;
|
||||
this.userInfoService = userInfoService;
|
||||
this.portalUserRepository = portalUserRepository;
|
||||
this.fileService = fileService;
|
||||
}
|
||||
|
||||
@@ -55,6 +70,7 @@ public class PortalInquiryManService extends BaseService {
|
||||
if (ui.getInquirer() != null) {
|
||||
maskPortalInquiryUI(ui);
|
||||
}
|
||||
ui.setCommentCount((int) portalInquiryCommentService.countActiveByInquiryId(inquiry.getId()));
|
||||
return setInquiryAttachmentStatus(ui);
|
||||
});
|
||||
}
|
||||
@@ -141,6 +157,7 @@ public class PortalInquiryManService extends BaseService {
|
||||
UserInfo userinfo = userInfoService.getById(userId);
|
||||
|
||||
Inquiry inquiry = portalInquiryService.getById(portalInquiryUI.getId());
|
||||
validateNotClosed(inquiry);
|
||||
// 답변 대기중 상태일 경우만 답변완료로 상태 변경
|
||||
if(inquiry.getInquiryStatus().equals(PENDING)) {
|
||||
inquiry.setInquiryStatus(RESPONDED);
|
||||
@@ -161,5 +178,53 @@ public class PortalInquiryManService extends BaseService {
|
||||
List<String> idList = Arrays.asList(ids.split(","));
|
||||
portalInquiryService.deleteByIds(idList);
|
||||
}
|
||||
|
||||
public List<PortalInquiryCommentUI> selectCommentList(String inquiryId) {
|
||||
List<InquiryComment> comments = portalInquiryCommentService.findByInquiryId(inquiryId);
|
||||
List<PortalInquiryCommentUI> result = portalInquiryCommentUIMapper.toVoList(comments);
|
||||
result.forEach(ui -> ui.setWriterName(resolveWriterName(ui.getCreatedBy())));
|
||||
return result;
|
||||
}
|
||||
|
||||
private String resolveWriterName(String userId) {
|
||||
if (StringUtils.isBlank(userId)) return "";
|
||||
// 직원(내부 사용자) 여부 먼저 확인 → 이름 그대로 반환
|
||||
Optional<UserInfo> staff = userInfoService.findById(userId);
|
||||
if (staff.isPresent()) {
|
||||
return staff.get().getUsername();
|
||||
}
|
||||
// 포털 회원이면 이름 마스킹 처리 (김수민 → 김수*)
|
||||
Optional<PortalUser> portalUser = portalUserRepository.findById(userId);
|
||||
if (portalUser.isPresent()) {
|
||||
return MaskingUtils.maskName(portalUser.get().getUserName());
|
||||
}
|
||||
return userId;
|
||||
}
|
||||
|
||||
public void insertComment(PortalInquiryCommentUI commentUI, String userId) {
|
||||
Inquiry inquiry = portalInquiryService.getById(commentUI.getInquiryId());
|
||||
validateNotClosed(inquiry);
|
||||
|
||||
InquiryComment comment = new InquiryComment();
|
||||
comment.setInquiry(inquiry);
|
||||
comment.setCommentDetail(commentUI.getCommentDetail());
|
||||
comment.setDelYn("N");
|
||||
comment.setAdminYn("Y");
|
||||
|
||||
portalInquiryCommentService.save(comment);
|
||||
}
|
||||
|
||||
public void deleteComment(String commentId) {
|
||||
InquiryComment comment = portalInquiryCommentService.getById(commentId);
|
||||
validateNotClosed(comment.getInquiry());
|
||||
comment.setDelYn("Y");
|
||||
portalInquiryCommentService.save(comment);
|
||||
}
|
||||
|
||||
private void validateNotClosed(Inquiry inquiry) {
|
||||
if (CLOSED.equals(inquiry.getInquiryStatus())) {
|
||||
throw new IntegrationException("답변종료되어 수정할 수 없습니다.");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -55,4 +55,6 @@ public class PortalInquiryUI {
|
||||
private LocalDateTime lastModifiedDate;
|
||||
|
||||
private String orgName;
|
||||
|
||||
private int commentCount;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
+135
-1
@@ -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,9 +31,13 @@ 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.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@Service
|
||||
@Transactional(transactionManager = "transactionManagerForEMS")
|
||||
@@ -34,6 +46,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) {
|
||||
@@ -50,10 +65,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;
|
||||
}
|
||||
|
||||
|
||||
@@ -91,9 +123,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())));
|
||||
@@ -108,6 +170,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 {
|
||||
@@ -131,6 +248,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);
|
||||
@@ -141,11 +259,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);
|
||||
|
||||
@@ -27,6 +27,8 @@ import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
|
||||
import javax.persistence.EntityManager;
|
||||
import javax.persistence.PersistenceContext;
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.Charset;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
@@ -48,6 +50,9 @@ public class PortalOrgManService extends BaseService {
|
||||
private final PortalUserCorpManagerUIMapper portalUserCorpManagerUIMapper;
|
||||
private final FileService fileService;
|
||||
|
||||
@PersistenceContext(unitName = "entityManagerFactoryForEMS")
|
||||
private EntityManager entityManager;
|
||||
|
||||
public Page<PortalOrgUI> selectList(Pageable pageable, PortalOrgUISearch portalOrgUISearch) {
|
||||
Page<PortalOrg> portalOrg = portalOrgService.findAll(pageable, portalOrgUISearch);
|
||||
return portalOrg.map(entity -> convertToUIWithMaskOption(entity, true));
|
||||
@@ -146,19 +151,37 @@ public class PortalOrgManService extends BaseService {
|
||||
throw new BizException("삭제(탈퇴) 상태인 법인만 완전삭제할 수 있습니다.");
|
||||
}
|
||||
|
||||
List<String> dependencies = new ArrayList<>();
|
||||
// 사용자(PortalUser)는 별도 화면에서 정리해야 하므로 cascade 하지 않고 차단
|
||||
long userCount = portalUserService.countByOrgId(id);
|
||||
if (userCount > 0) dependencies.add("사용자 " + userCount + "건");
|
||||
long appRequestCount = portalOrgService.countAppRequestByOrgId(id);
|
||||
if (appRequestCount > 0) dependencies.add("앱 신청 " + appRequestCount + "건");
|
||||
|
||||
if (!dependencies.isEmpty()) {
|
||||
throw new BizException("의존 데이터가 존재하여 완전삭제할 수 없습니다: " + String.join(", ", dependencies));
|
||||
if (userCount > 0) {
|
||||
throw new BizException("의존 데이터가 존재하여 완전삭제할 수 없습니다: 사용자 " + userCount + "건");
|
||||
}
|
||||
|
||||
// 앱 신청(AppRequest) + 연결된 승인(Approval/Approver) 은 cascade 삭제
|
||||
cascadeDeleteAppRequestsByOrg(id);
|
||||
|
||||
portalOrgService.deleteById(id);
|
||||
}
|
||||
|
||||
private void cascadeDeleteAppRequestsByOrg(String orgId) {
|
||||
// 1) 해당 org 의 app_request 가 참조하는 approval 의 approver 제거
|
||||
entityManager.createNativeQuery(
|
||||
"DELETE FROM PTL_APPROVER WHERE APPROVAL_ID IN " +
|
||||
"(SELECT APPROVAL_ID FROM PTL_APP_REQUEST WHERE ORG_ID = :orgId AND APPROVAL_ID IS NOT NULL)"
|
||||
).setParameter("orgId", orgId).executeUpdate();
|
||||
|
||||
// 2) approval 제거
|
||||
entityManager.createNativeQuery(
|
||||
"DELETE FROM PTL_APPROVAL WHERE ID IN " +
|
||||
"(SELECT APPROVAL_ID FROM PTL_APP_REQUEST WHERE ORG_ID = :orgId AND APPROVAL_ID IS NOT NULL)"
|
||||
).setParameter("orgId", orgId).executeUpdate();
|
||||
|
||||
// 3) app_request 제거
|
||||
entityManager.createNativeQuery(
|
||||
"DELETE FROM PTL_APP_REQUEST WHERE ORG_ID = :orgId"
|
||||
).setParameter("orgId", orgId).executeUpdate();
|
||||
}
|
||||
|
||||
public Map<String, Object> selectDetailWithUsers(String id) {
|
||||
return getDetailWithUsers(id, true);
|
||||
}
|
||||
|
||||
@@ -1,19 +1,5 @@
|
||||
package com.eactive.eai.rms.onl.common.service.ums;
|
||||
|
||||
import com.eactive.apim.portal.template.entity.MessageRequest;
|
||||
import com.eactive.apim.portal.template.repository.MessageRequestRepository;
|
||||
import com.eactive.ext.kjb.ums.KjbEmailSendModule;
|
||||
import com.eactive.ext.kjb.ums.KjbUmsService;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.annotation.Qualifier;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.PlatformTransactionManager;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
import org.springframework.transaction.support.TransactionTemplate;
|
||||
|
||||
import javax.persistence.EntityManager;
|
||||
import javax.persistence.PersistenceContext;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.LinkedBlockingQueue;
|
||||
@@ -21,6 +7,23 @@ import java.util.concurrent.ThreadPoolExecutor;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import javax.annotation.PostConstruct;
|
||||
import javax.persistence.EntityManager;
|
||||
import javax.persistence.PersistenceContext;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.annotation.Qualifier;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.PlatformTransactionManager;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
import org.springframework.transaction.support.TransactionTemplate;
|
||||
|
||||
import com.eactive.apim.portal.template.entity.MessageRequest;
|
||||
import com.eactive.apim.portal.template.repository.MessageRequestRepository;
|
||||
import com.eactive.eai.rms.common.datasource.DataSourceContextHolder;
|
||||
import com.eactive.eai.rms.common.datasource.DataSourceTypeManager;
|
||||
import com.eactive.eai.rms.ext.djb.ums.service.UmsService;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
|
||||
@Slf4j
|
||||
@@ -40,13 +43,17 @@ public class UmsDispatchService {
|
||||
private EntityManager entityManager;
|
||||
|
||||
private final MessageRequestRepository messageRequestRepository;
|
||||
|
||||
private final UmsService umsService;
|
||||
|
||||
@Autowired
|
||||
public UmsDispatchService(
|
||||
@Qualifier("transactionManagerForEMS") PlatformTransactionManager transactionManager,
|
||||
MessageRequestRepository messageRequestRepository
|
||||
MessageRequestRepository messageRequestRepository,
|
||||
UmsService umsService
|
||||
) {
|
||||
this.messageRequestRepository = messageRequestRepository;
|
||||
this.umsService = umsService;
|
||||
|
||||
this.executorService = new ThreadPoolExecutor(
|
||||
CORE_POOL_SIZE,
|
||||
@@ -67,15 +74,8 @@ public class UmsDispatchService {
|
||||
// KjbEmailSendModule.getInstance().setRepository(messageRequestRepository);
|
||||
log.info("UmsDispatchService initialized - KjbUmsService and KjbEmailSendModule ready via singleton");
|
||||
}
|
||||
|
||||
|
||||
// private KjbUmsService getUmsService() {
|
||||
// return KjbUmsService.getInstance();
|
||||
// }
|
||||
//
|
||||
// private KjbEmailSendModule getEmailModule() {
|
||||
// return KjbEmailSendModule.getInstance();
|
||||
// }
|
||||
//
|
||||
@SuppressWarnings("unchecked")
|
||||
@Transactional(transactionManager = "transactionManagerForEMS")
|
||||
public void processMessages() {
|
||||
@@ -112,7 +112,7 @@ public class UmsDispatchService {
|
||||
|
||||
log.info("Starting to submit {} messages for processing", messagesToProcess.size());
|
||||
for (MessageRequest message : messagesToProcess) {
|
||||
//submitMessageTask(message);
|
||||
submitMessageTask(message);
|
||||
}
|
||||
} else {
|
||||
log.info("No pending messages found");
|
||||
@@ -123,66 +123,42 @@ public class UmsDispatchService {
|
||||
}
|
||||
|
||||
|
||||
// private void submitMessageTask(MessageRequest message) {
|
||||
// int queueSize = executorService.getQueue().size();
|
||||
// log.debug("Current queue size before submission: {}", queueSize);
|
||||
//
|
||||
// executorService.submit(() -> {
|
||||
// try {
|
||||
// transactionTemplate.execute(status -> {
|
||||
// try {
|
||||
// // 광주은행에서는 사용하지 않는 메소드
|
||||
//// processMessage(message);
|
||||
//
|
||||
// if (KjbUmsService.isAllowChannel(message)) {
|
||||
// getUmsService().send(message);
|
||||
// }
|
||||
//
|
||||
// if (KjbEmailSendModule.isAllowChannel(message)) {
|
||||
// getEmailModule().sendEmail(message);
|
||||
// }
|
||||
//
|
||||
// return null;
|
||||
// } catch (Exception e) {
|
||||
// log.error("Failed to process message: {} - Error: {}",
|
||||
// message.getId(), e.getMessage(), e);
|
||||
// updateMessageStatus(message, "FAILED");
|
||||
// status.setRollbackOnly();
|
||||
// return null;
|
||||
// }
|
||||
// });
|
||||
// } catch (Exception e) {
|
||||
// log.error("Transaction execution failed for message: {} - Error: {}",
|
||||
// message.getId(), e.getMessage(), e);
|
||||
// }
|
||||
// });
|
||||
// }
|
||||
private void submitMessageTask(MessageRequest message) {
|
||||
int queueSize = executorService.getQueue().size();
|
||||
log.debug("Current queue size before submission: {}", queueSize);
|
||||
|
||||
/**
|
||||
* kbank 방식으로 광주은행에서는 사용하지 않음
|
||||
* @param message
|
||||
* @throws IOException
|
||||
*/
|
||||
// @Deprecated
|
||||
// public void processMessage(MessageRequest message) throws IOException {
|
||||
// try {
|
||||
// String standardMessage = messageFactory.createMessage(message);
|
||||
// log.debug("Processing message ID: {} - Type: {}", message.getId(), message.getMessageType());
|
||||
//
|
||||
// umsSender.sendMessage(standardMessage);
|
||||
// updateMessageStatus(message, "SENT");
|
||||
// log.debug("Successfully processed and sent message ID: {}", message.getId());
|
||||
// } catch (Exception e) {
|
||||
// log.error("Message processing failed for ID: {} - Error: {}",
|
||||
// message.getId(), e.getMessage(), e);
|
||||
// updateMessageStatus(message, "FAILED");
|
||||
// throw e;
|
||||
// }
|
||||
// }
|
||||
executorService.submit(() -> {
|
||||
try {
|
||||
transactionTemplate.execute(status -> {
|
||||
try {
|
||||
umsService.send(message);
|
||||
updateMessageStatus(message, "SENT");
|
||||
return null;
|
||||
} catch (Exception e) {
|
||||
// checked exception을 RuntimeException으로 감싸 전파 → TransactionTemplate이 rollback 후 re-throw
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
});
|
||||
} catch (Exception e) {
|
||||
log.error("Failed to process message: {} - Error: {}", message.getId(), e.getMessage(), e);
|
||||
try {
|
||||
transactionTemplate.execute(s -> {
|
||||
updateMessageStatus(message, "FAILED");
|
||||
return null;
|
||||
});
|
||||
} catch (Exception ex) {
|
||||
log.error("Failed to update FAIL status for message: {}", message.getId(), ex);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
|
||||
public void updateMessageStatus(MessageRequest message, String status) {
|
||||
message.setRequestStatus(status);
|
||||
message.setSentDate(LocalDateTime.now());
|
||||
entityManager.merge(message);
|
||||
entityManager.flush();
|
||||
}
|
||||
}
|
||||
@@ -1,79 +0,0 @@
|
||||
package com.eactive.eai.rms.onl.common.util;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.apache.log4j.Logger;
|
||||
|
||||
import com.eactive.eai.rms.onl.common.service.OnlAgentUtilServiceImpl;
|
||||
|
||||
import io.kubernetes.client.openapi.ApiClient;
|
||||
import io.kubernetes.client.openapi.Configuration;
|
||||
import io.kubernetes.client.openapi.apis.CoreV1Api;
|
||||
import io.kubernetes.client.openapi.models.V1EndpointAddress;
|
||||
import io.kubernetes.client.openapi.models.V1EndpointSubset;
|
||||
import io.kubernetes.client.openapi.models.V1Endpoints;
|
||||
import io.kubernetes.client.util.ClientBuilder;
|
||||
|
||||
public class K8sUtil {
|
||||
private static final Logger logger = Logger.getLogger(K8sUtil.class);
|
||||
|
||||
public static List<Map<String, String>> getServerInfoByK8sUrl(String namespace, String serviceName, int port, String extUrl) throws Exception {
|
||||
|
||||
List<Map<String, String>> resultList = getServerInfoByK8sApi(namespace,serviceName);
|
||||
|
||||
for (Map<String,String> map : resultList) {
|
||||
String EAISEVRINSTNCNAME = map.get("EAISEVRINSTNCNAME");
|
||||
String EAISEVRIP = map.get("EAISEVRIP");
|
||||
Map<String, String> serverInfo = new HashMap<>();
|
||||
|
||||
serverInfo.put(OnlAgentUtilServiceImpl.FIELD_URL, new StringBuilder().append("http://" + EAISEVRIP + ":"+String.valueOf(port) + extUrl).toString());
|
||||
serverInfo.put(OnlAgentUtilServiceImpl.FIELD_INST_NAME, EAISEVRINSTNCNAME);
|
||||
}
|
||||
|
||||
return resultList;
|
||||
}
|
||||
public static List<Map<String, String>> getServerInfoByK8sApi(String namespace, String serviceName) throws Exception {
|
||||
|
||||
logger.info("K8sAgentUtilService getServerInfoByK8sApi start.");
|
||||
|
||||
List<Map<String, String>> resultList = new ArrayList<>();
|
||||
|
||||
try {
|
||||
ApiClient client = ClientBuilder.cluster().build();
|
||||
Configuration.setDefaultApiClient(client);
|
||||
|
||||
if (StringUtils.isBlank(namespace)) {
|
||||
new RuntimeException("Can't find kubernetes_namespace in Engine Property");
|
||||
}
|
||||
if (StringUtils.isBlank(serviceName)) {
|
||||
new RuntimeException("Can't find kubernetes_servicename in Engine Property");
|
||||
}
|
||||
|
||||
logger.info("getServerInfoByK8sApi Codes [ namespace: " + namespace + ", serviceName:" + serviceName + " ]");
|
||||
|
||||
V1Endpoints endpoints = new CoreV1Api().readNamespacedEndpoints(serviceName, namespace, null);
|
||||
|
||||
for (V1EndpointSubset subset : endpoints.getSubsets()) {
|
||||
for (V1EndpointAddress address : subset.getAddresses()) {
|
||||
Map<String, String> serverInfo = new HashMap<>();
|
||||
serverInfo.put("EAISEVRINSTNCNAME", address.getTargetRef().getName());
|
||||
serverInfo.put("EAISEVRIP", address.getIp());
|
||||
resultList.add(serverInfo);
|
||||
}
|
||||
}
|
||||
|
||||
logger.info("getServerInfoByK8sApi resultList [ " + resultList.toString() + " ]");
|
||||
|
||||
} catch (Exception e) {
|
||||
logger.error(e);
|
||||
throw new RuntimeException("K8S Mode로 AgentUtilService를 기동중 실패하였습니다.", e);
|
||||
}
|
||||
|
||||
return resultList;
|
||||
}
|
||||
|
||||
}
|
||||
+8
@@ -85,4 +85,12 @@ public class InflowAdapterControlManController extends OnlBaseAnnotationControll
|
||||
return new ModelAndView("jsonView", resultMap);
|
||||
}
|
||||
|
||||
@RequestMapping(value = "/onl/admin/inflow/inflowAdapterControlMan.json", params = "cmd=LIST_BUCKET_STATUS")
|
||||
public ModelAndView getBucketStatus(HttpServletRequest request, HttpServletResponse response, String adapterId) {
|
||||
List<Map<String, Object>> statusList = service.getAdapterBucketStatus(adapterId);
|
||||
Map<String, Object> resultMap = new HashMap<>();
|
||||
resultMap.put("servers", statusList);
|
||||
return new ModelAndView("jsonView", resultMap);
|
||||
}
|
||||
|
||||
}
|
||||
+8
@@ -83,4 +83,12 @@ public class InflowClientControlManController extends OnlBaseAnnotationControlle
|
||||
return new ModelAndView("jsonView", resultMap);
|
||||
}
|
||||
|
||||
@RequestMapping(value = "/onl/admin/inflow/inflowClientControlMan.json", params = "cmd=LIST_BUCKET_STATUS")
|
||||
public ModelAndView getBucketStatus(HttpServletRequest request, HttpServletResponse response, String clientId) {
|
||||
List<Map<String, Object>> statusList = service.getClientBucketStatus(clientId);
|
||||
Map<String, Object> resultMap = new HashMap<>();
|
||||
resultMap.put("servers", statusList);
|
||||
return new ModelAndView("jsonView", resultMap);
|
||||
}
|
||||
|
||||
}
|
||||
+75
@@ -1,12 +1,21 @@
|
||||
package com.eactive.eai.rms.onl.manage.inflow.inflow;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.annotation.Qualifier;
|
||||
import org.springframework.data.domain.Page;
|
||||
import org.springframework.data.domain.Pageable;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.web.client.RestTemplate;
|
||||
|
||||
import com.eactive.eai.data.entity.onl.inflow.InflowControl;
|
||||
import com.eactive.eai.data.entity.onl.inflow.InflowControlId;
|
||||
@@ -14,10 +23,13 @@ import com.eactive.eai.rms.common.base.BaseService;
|
||||
import com.eactive.eai.rms.common.combo.ComboService;
|
||||
import com.eactive.eai.rms.data.entity.onl.inflow.InflowControlService;
|
||||
import com.eactive.eai.rms.data.entity.onl.inflow.InflowControlServiceDto;
|
||||
import com.eactive.eai.rms.onl.common.service.EaiServerInfoService;
|
||||
|
||||
@Service
|
||||
public class InflowControlManService extends BaseService {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(InflowControlManService.class);
|
||||
|
||||
private static final String ADAPTER_TYPE_INFLOW = "01";
|
||||
private static final String INTERFACE_TYPE_INFLOW = "02";
|
||||
private static final String CLIENT_TYPE_INFLOW = "03";
|
||||
@@ -32,6 +44,11 @@ public class InflowControlManService extends BaseService {
|
||||
@Autowired
|
||||
private InflowControlManMapper mapper;
|
||||
|
||||
@Autowired
|
||||
private EaiServerInfoService eaiServerInfoService;
|
||||
|
||||
private RestTemplate restTemplate = new RestTemplate();
|
||||
|
||||
// 어댑터 유량제어
|
||||
public Page<InflowControlManUI> selectAdapterList(Pageable pageable, String searchName) {
|
||||
Page<InflowControlServiceDto> dtoList = service.findAllForAdapter(pageable, searchName);
|
||||
@@ -123,6 +140,64 @@ public class InflowControlManService extends BaseService {
|
||||
InflowControlId id = toId(CLIENT_TYPE_INFLOW, name);
|
||||
service.findById(id).ifPresent(service::delete);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public List<Map<String, Object>> getClientBucketStatus(String clientId) {
|
||||
return getBucketStatus("client", clientId, "클라이언트가 로드되지 않음", "클라이언트 버킷 상태 조회 실패");
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public List<Map<String, Object>> getAdapterBucketStatus(String adapterId) {
|
||||
return getBucketStatus("adapter", adapterId, "어댑터가 로드되지 않음", "어댑터 버킷 상태 조회 실패");
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public List<Map<String, Object>> getInterfaceBucketStatus(String interfaceId) {
|
||||
return getBucketStatus("interface", interfaceId, "인터페이스가 로드되지 않음", "인터페이스 버킷 상태 조회 실패");
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private List<Map<String, Object>> getBucketStatus(String target, String targetId, String noDataMessage, String logPrefix) {
|
||||
List<Map<String, Object>> result = new ArrayList<>();
|
||||
List<Map<String, String>> serverList = eaiServerInfoService.getEaiServerIpList();
|
||||
|
||||
for (Map<String, String> server : serverList) {
|
||||
String serverName = server.get("EAISVRINSTNM");
|
||||
String serverIp = server.get("EAISVRIP");
|
||||
String serverPort = server.get("WEBSERVERPORT");
|
||||
|
||||
if (StringUtils.isEmpty(serverPort)) {
|
||||
serverPort = server.get("EAISVRLSNPORT");
|
||||
}
|
||||
|
||||
Map<String, Object> serverStatus = new HashMap<>();
|
||||
serverStatus.put("serverName", serverName);
|
||||
serverStatus.put("serverIp", serverIp);
|
||||
serverStatus.put("serverPort", serverPort);
|
||||
|
||||
try {
|
||||
String url = String.format("http://%s:%s/manage/inflow/%s/%s/bucket-status",
|
||||
serverIp, serverPort, target, targetId);
|
||||
ResponseEntity<Map> response = restTemplate.getForEntity(url, Map.class);
|
||||
|
||||
if (response.getBody() != null && Boolean.TRUE.equals(response.getBody().get("success"))) {
|
||||
serverStatus.put("status", "online");
|
||||
serverStatus.put("data", response.getBody().get("data"));
|
||||
} else {
|
||||
serverStatus.put("status", "no_data");
|
||||
serverStatus.put("message", noDataMessage);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
logger.warn("{} - server: {}, error: {}", logPrefix, serverName, e.getMessage());
|
||||
serverStatus.put("status", "offline");
|
||||
serverStatus.put("message", "서버 연결 실패");
|
||||
}
|
||||
|
||||
result.add(serverStatus);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
private InflowControlId toId(InflowControlManUI ui) {
|
||||
|
||||
+8
@@ -83,4 +83,12 @@ public class InflowInterfaceControlManController extends OnlBaseAnnotationContro
|
||||
return new ModelAndView("jsonView", resultMap);
|
||||
}
|
||||
|
||||
@RequestMapping(value = "/onl/admin/inflow/inflowInterfaceControlMan.json", params = "cmd=LIST_BUCKET_STATUS")
|
||||
public ModelAndView getBucketStatus(HttpServletRequest request, HttpServletResponse response, String interfaceId) {
|
||||
List<Map<String, Object>> statusList = service.getInterfaceBucketStatus(interfaceId);
|
||||
Map<String, Object> resultMap = new HashMap<>();
|
||||
resultMap.put("servers", statusList);
|
||||
return new ModelAndView("jsonView", resultMap);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -565,7 +565,7 @@ public class ApiInterfaceService extends OnlBaseService {
|
||||
restOption.setMethod(outboundHttpMethod);
|
||||
restOption.setExtraPath(outboundRestPath);
|
||||
|
||||
String pathType = outboundRestPath.matches(".*\\{[_a-zA-Z]+\\}.*") ? "variableUrlRequest" : "simpleRequest";
|
||||
String pathType = outboundRestPath.matches(".*\\{.+\\}.*") ? "variableUrlRequest" : "simpleRequest";
|
||||
restOption.setType(pathType);
|
||||
|
||||
if (StringUtils.isNotBlank(contentType)) {
|
||||
|
||||
+1
-1
@@ -400,7 +400,7 @@ public class SimpleInterfaceService extends OnlBaseService {
|
||||
jsonObject.put("method", outboundHttpMethod);
|
||||
jsonObject.put("extraPath", outboundRestPath);
|
||||
|
||||
String pathType = outboundRestPath.matches(".*\\{[a-zA-Z]+\\}.*") ? "variableUrlRequest" : "simpleRequest";
|
||||
String pathType = outboundRestPath.matches(".*\\{.+\\}.*") ? "variableUrlRequest" : "simpleRequest";
|
||||
jsonObject.put("type", pathType);
|
||||
restOption = jsonObject.toString();
|
||||
return restOption;
|
||||
|
||||
@@ -15,6 +15,7 @@ import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
|
||||
import com.eactive.eai.rms.ext.djb.job.ApiStatsHourlyAggregationJob;
|
||||
import com.eactive.ext.kjb.statistics.job.DailyToMonthlyAggregationJob;
|
||||
import com.eactive.ext.kjb.statistics.job.HourlyToDailyAggregationJob;
|
||||
import com.eactive.ext.kjb.statistics.job.MonthlyToYearlyAggregationJob;
|
||||
@@ -33,12 +34,62 @@ public class ApiStatsAggregationController {
|
||||
private final HourlyToDailyAggregationJob hourlyToDailyAggregationJob;
|
||||
private final DailyToMonthlyAggregationJob dailyToMonthlyAggregationJob;
|
||||
private final MonthlyToYearlyAggregationJob monthlyToYearlyAggregationJob;
|
||||
|
||||
private final ApiStatsHourlyAggregationJob apiStatsHourlyAggregationJob;
|
||||
|
||||
@GetMapping(value = "/onl/kjb/statistics/apiStatsAggregationMan.view")
|
||||
public String view() {
|
||||
return "/onl/kjb/statistics/apiStatsAggregationMan";
|
||||
}
|
||||
|
||||
/**
|
||||
* 거래로그→시간별 통계 집계 수동 실행
|
||||
* @param targetDate 집계 대상 날짜 (yyyyMMdd 형식, 예: 20250120)
|
||||
* @return 처리 결과
|
||||
*/
|
||||
@PostMapping(value = "/onl/kjb/statistics/apiStatsAggregationMan.json", params = "cmd=AGGREGATION_HOUR")
|
||||
public ResponseEntity<Map<String, Object>> executeAggregationHourly(
|
||||
@RequestParam(required = false) String targetDate) {
|
||||
|
||||
Map<String, Object> result = new HashMap<>();
|
||||
|
||||
try {
|
||||
// 날짜 입력 검증
|
||||
if (targetDate == null || targetDate.trim().isEmpty()) {
|
||||
log.error("targetDate 미입력");
|
||||
result.put("success", false);
|
||||
result.put("message", "대상 날짜를 입력하세요. (예: 20250120)");
|
||||
return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(result);
|
||||
}
|
||||
|
||||
LocalDate date;
|
||||
try {
|
||||
date = LocalDate.parse(targetDate, DateTimeFormatter.ofPattern("yyyyMMdd"));
|
||||
} catch (DateTimeParseException e) {
|
||||
log.error("잘못된 날짜 형식: {}", targetDate);
|
||||
result.put("success", false);
|
||||
result.put("message", "잘못된 날짜 형식입니다. yyyyMMdd 형식으로 입력하세요. (예: 20250120)");
|
||||
return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(result);
|
||||
}
|
||||
|
||||
log.info("거래로그→시간별 통계 집계 수동 실행 시작. targetDate: {}", date);
|
||||
int count = apiStatsHourlyAggregationJob.executeManual(date);
|
||||
|
||||
result.put("success", true);
|
||||
result.put("message", "거래로그→시간별 통계 집계가 완료되었습니다.");
|
||||
result.put("targetDate", date.format(DateTimeFormatter.ofPattern("yyyyMMdd")));
|
||||
result.put("processedCount", count);
|
||||
|
||||
return ResponseEntity.ok(result);
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("거래로그→시간별 통계 집계 실행 중 오류 발생", e);
|
||||
result.put("success", false);
|
||||
result.put("message", "집계 실행 중 오류가 발생했습니다: " + e.getMessage());
|
||||
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(result);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 시간별→일별 통계 집계 수동 실행
|
||||
* @param targetDate 집계 대상 날짜 (yyyyMMdd 형식, 예: 20250120)
|
||||
|
||||
@@ -138,9 +138,9 @@ public class DailyToMonthlyAggregationJob implements Job {
|
||||
qd.seq900SystemErrCnt.sum().as("seq900SystemErrCnt"),
|
||||
qd.seq900BizErrCnt.sum().as("seq900BizErrCnt"),
|
||||
Expressions.cases()
|
||||
.when(qd.successCnt.sum().gt(0))
|
||||
.then(qd.avgRespTime.multiply(qd.successCnt).sum()
|
||||
.divide(qd.successCnt.sum()).castToNum(BigDecimal.class))
|
||||
.when(qd.totalCnt.sum().gt(0))
|
||||
.then(qd.avgRespTime.multiply(qd.totalCnt).sum()
|
||||
.divide(qd.totalCnt.sum()).castToNum(BigDecimal.class))
|
||||
.otherwise((BigDecimal) null)
|
||||
.as("avgRespTime"),
|
||||
qd.minRespTime.min().castToNum(BigDecimal.class).as("minRespTime"),
|
||||
|
||||
@@ -133,9 +133,9 @@ public class HourlyToDailyAggregationJob implements Job {
|
||||
qh.seq900SystemErrCnt.sum().as("seq900SystemErrCnt"),
|
||||
qh.seq900BizErrCnt.sum().as("seq900BizErrCnt"),
|
||||
Expressions.cases()
|
||||
.when(qh.successCnt.sum().gt(0))
|
||||
.then(qh.avgRespTime.multiply(qh.successCnt).sum()
|
||||
.divide(qh.successCnt.sum()).castToNum(BigDecimal.class))
|
||||
.when(qh.totalCnt.sum().gt(0))
|
||||
.then(qh.avgRespTime.multiply(qh.totalCnt).sum()
|
||||
.divide(qh.totalCnt.sum()).castToNum(BigDecimal.class))
|
||||
.otherwise((BigDecimal) null)
|
||||
.as("avgRespTime"),
|
||||
qh.minRespTime.min().castToNum(BigDecimal.class).as("minRespTime"),
|
||||
|
||||
@@ -136,9 +136,9 @@ public class MonthlyToYearlyAggregationJob implements Job {
|
||||
qm.seq900SystemErrCnt.sum().as("seq900SystemErrCnt"),
|
||||
qm.seq900BizErrCnt.sum().as("seq900BizErrCnt"),
|
||||
Expressions.cases()
|
||||
.when(qm.successCnt.sum().gt(0))
|
||||
.then(qm.avgRespTime.multiply(qm.successCnt).sum()
|
||||
.divide(qm.successCnt.sum()).castToNum(BigDecimal.class))
|
||||
.when(qm.totalCnt.sum().gt(0))
|
||||
.then(qm.avgRespTime.multiply(qm.totalCnt).sum()
|
||||
.divide(qm.totalCnt.sum()).castToNum(BigDecimal.class))
|
||||
.otherwise((BigDecimal) null)
|
||||
.as("avgRespTime"),
|
||||
qm.minRespTime.min().castToNum(BigDecimal.class).as("minRespTime"),
|
||||
|
||||
+4
-9
@@ -36,9 +36,8 @@ public class ApiStatsExcelExportService {
|
||||
private static final String[] COLUMN_HEADERS = {
|
||||
"통계시간", "API명", "인스턴스", "업무구분", "클라이언트ID",
|
||||
"Inbound Adapter", "Outbound Adapter",
|
||||
"총건수", "성공건수", "Timeout건수", "시스템오류건수", "업무오류건수",
|
||||
"Seq900 Timeout", "Seq900 시스템오류", "Seq900 업무오류",
|
||||
"평균응답시간", "최소응답시간", "최대응답시간", "P50응답시간", "P95응답시간"
|
||||
"총건수", "성공건수", "Timeout건수", "시스템오류건수",
|
||||
"평균응답시간", "최소응답시간", "최대응답시간"
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -193,17 +192,13 @@ public class ApiStatsExcelExportService {
|
||||
createLongCell(row, colNum++, data.getSuccessCnt(), numberStyle);
|
||||
createLongCell(row, colNum++, data.getTimeoutCnt(), numberStyle);
|
||||
createLongCell(row, colNum++, data.getSystemErrCnt(), numberStyle);
|
||||
createLongCell(row, colNum++, data.getBizErrCnt(), numberStyle);
|
||||
createLongCell(row, colNum++, data.getSeq900TimeoutCnt(), numberStyle);
|
||||
createLongCell(row, colNum++, data.getSeq900SystemErrCnt(), numberStyle);
|
||||
createLongCell(row, colNum++, data.getSeq900BizErrCnt(), numberStyle);
|
||||
|
||||
|
||||
// 소수점 컬럼 (15-19)
|
||||
createDecimalCell(row, colNum++, data.getAvgRespTime(), decimalStyle);
|
||||
createDecimalCell(row, colNum++, data.getMinRespTime(), decimalStyle);
|
||||
createDecimalCell(row, colNum++, data.getMaxRespTime(), decimalStyle);
|
||||
createDecimalCell(row, colNum++, data.getP50RespTime(), decimalStyle);
|
||||
createDecimalCell(row, colNum++, data.getP95RespTime(), decimalStyle);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -18,6 +18,8 @@ public class ApiStatsSearch {
|
||||
private String searchClientId;
|
||||
private String searchInboundAdapter;
|
||||
private String searchOutboundAdapter;
|
||||
private String searchOrgName;
|
||||
private String searchType;
|
||||
|
||||
/** 파싱된 시작시간 */
|
||||
private LocalDateTime parsedStartDateTime;
|
||||
|
||||
@@ -14,6 +14,7 @@ public class ApiStatsUI {
|
||||
private String gwInstanceId;
|
||||
private String bizDivCode;
|
||||
private String clientId;
|
||||
private String orgName;
|
||||
private String inboundAdapter;
|
||||
private String outboundAdapter;
|
||||
|
||||
@@ -22,15 +23,13 @@ public class ApiStatsUI {
|
||||
private Long successCnt;
|
||||
private Long timeoutCnt;
|
||||
private Long systemErrCnt;
|
||||
private Long bizErrCnt;
|
||||
private Long seq900TimeoutCnt;
|
||||
private Long seq900SystemErrCnt;
|
||||
private Long seq900BizErrCnt;
|
||||
|
||||
// 응답시간 메트릭
|
||||
private BigDecimal avgRespTime;
|
||||
private BigDecimal minRespTime;
|
||||
private BigDecimal maxRespTime;
|
||||
private BigDecimal p50RespTime;
|
||||
private BigDecimal p95RespTime;
|
||||
|
||||
//성공율,실패율
|
||||
private BigDecimal successRate;
|
||||
private BigDecimal failRate;
|
||||
}
|
||||
|
||||
@@ -23,7 +23,6 @@
|
||||
FLOVRSEVRNAME AS "FAILOVERSVRNM",
|
||||
HOSTNAME AS "HOSTNAME"
|
||||
FROM $schemaId$.TSEAIBP03
|
||||
WHERE ROWNUM = 1
|
||||
</statement>
|
||||
|
||||
</sqlMap>
|
||||
@@ -5,13 +5,20 @@
|
||||
# \uC790\uB3D9 \uD56B\uC2A4\uC651 \uD65C\uC131\uD654
|
||||
autoHotswap=true
|
||||
|
||||
# \uB85C\uAE45 \uB808\uBCA8 (TRACE, DEBUG, INFO, WARNING, ERROR)
|
||||
LOGGER.level=INFO
|
||||
# \uB85C\uADF8 \uB808\uBCA8 (TRACE, DEBUG, INFO, WARNING, ERROR)
|
||||
LOGGER=INFO
|
||||
|
||||
# \uB85C\uADF8 \uD30C\uC77C \uACBD\uB85C (\uC9C0\uC815 \uC2DC \uCF58\uC194 \uB300\uC2E0 \uD30C\uC77C\uB85C \uCD9C\uB825)
|
||||
LOGFILE=/Users/rinjae/eactive/djb-eapim/djb-eapim/eapim-admin/logs/hotswap-agent.log
|
||||
LOGFILE.append=true
|
||||
|
||||
# \uC2DC\uAC01 \uD3EC\uB9F7
|
||||
LOGGER_DATETIME_FORMAT=yyyy-MM-dd HH:mm:ss.SSS
|
||||
|
||||
# ============================================================
|
||||
# \uBA85\uC2DC\uC801 \uD50C\uB7EC\uADF8\uC778 \uBE44\uD65C\uC131\uD654 (\uC27C\uD45C \uAD6C\uBD84)
|
||||
# ============================================================
|
||||
disabledPlugins=org.hotswap.agent.plugin.ibatis.IBatisPlugin,org.hotswap.agent.plugin.mybatis.MyBatisPlugin
|
||||
disabledPlugins=IBatis,MyBatis
|
||||
|
||||
# ============================================================
|
||||
# Core Plugin \uD65C\uC131\uD654
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
|
||||
<property name="LOG_PATTERN" value="[%d{yyyy-MM-dd HH:mm:ss.SSS}] [%-5level] \\(%F:%L\\) %-20M -%msg%n" />
|
||||
<property name="PREFIX" value="${inst.Type:-ems}" />
|
||||
<property name="LOG_HOME" value="/Log/App/eapim/${inst.Name:-devSvr00}" />
|
||||
<property name="LOG_HOME" value="/logs/eapim/${inst.Name:-emsSvr00}" />
|
||||
<property name="BACKUP_HOME" value="${LOG_HOME}/backup/" />
|
||||
<property name="LOG_LEVEL" value="${LOGBACK_LOG_LEVEL:-DEBUG}" />
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
|
||||
<property name="LOG_PATTERN" value="[%d{yyyy-MM-dd HH:mm:ss.SSS}] [%-5level] \\(%F:%L\\) %-20M -%msg%n" />
|
||||
<property name="PREFIX" value="${inst.Type:-ems}" />
|
||||
<property name="LOG_HOME" value="/Log/App/eapim/${inst.Name:-devSvr00}" />
|
||||
<property name="LOG_HOME" value="/logs/eapim/${inst.Name:-devSvr00}" />
|
||||
<property name="BACKUP_HOME" value="${LOG_HOME}/backup/" />
|
||||
<property name="LOG_LEVEL" value="${LOGBACK_LOG_LEVEL:-DEBUG}" />
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
|
||||
<property name="LOG_PATTERN" value="[%d{yyyy-MM-dd HH:mm:ss.SSS}] [%-5level] \\(%F:%L\\) %-20M -%msg%n" />
|
||||
<property name="PREFIX" value="${inst.Type:-ems}" />
|
||||
<property name="LOG_HOME" value="/Log/App/eapim/${inst.Name:-emsSvr00}" />
|
||||
<property name="LOG_HOME" value="/logs/eapim/${inst.Name:-emsSvr00}" />
|
||||
<property name="BACKUP_HOME" value="${LOG_HOME}/backup/" />
|
||||
<property name="LOG_LEVEL" value="${LOGBACK_LOG_LEVEL:-INFO}" />
|
||||
|
||||
|
||||
Reference in New Issue
Block a user