package com.eactive.eai.common.inflow; import java.time.Duration; import java.util.Arrays; import java.util.Calendar; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import org.apache.commons.lang3.StringUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import com.eactive.eai.common.exception.ExceptionUtil; import com.eactive.eai.common.lifecycle.Lifecycle; import com.eactive.eai.common.lifecycle.LifecycleException; import com.eactive.eai.common.lifecycle.LifecycleListener; import com.eactive.eai.common.lifecycle.LifecycleSupport; import com.eactive.eai.common.message.EAIMessage; import com.eactive.eai.common.server.EAIServerManager; import com.eactive.eai.common.util.ApplicationContextProvider; import com.eactive.eai.common.util.Logger; import com.eactive.eai.common.util.MessageUtil; import com.eactive.eai.message.service.InterfaceMapper; import com.ext.eai.common.stdmessage.STDMessageKeys; import io.github.bucket4j.Bandwidth; import io.github.bucket4j.Bucket4j; import io.github.bucket4j.local.LocalBucketBuilder; @Component public class InflowControlManager implements Lifecycle, Bucket { static Logger logger = Logger.getLogger(Logger.LOGGER_DEFAULT); public static final String QUOTA_TIMEUNIT_SECOND = "SEC"; public static final String QUOTA_TIMEUNIT_MINUTE = "MIN"; public static final String QUOTA_TIMEUNIT_HOUR = "HOU"; public static final String QUOTA_TIMEUNIT_DAY = "DAY"; public static final String QUOTA_TIMEUNIT_MONTH = "MON"; private Map adapterBucketList = new HashMap<>(); private Map interfaceBucketList = new HashMap<>(); private Map groupBucketList = new HashMap<>(); private Map interfaceToGroupMap = new HashMap<>(); private Map groupVoMap = new HashMap<>(); private boolean started; private LifecycleSupport lifecycle = new LifecycleSupport(this); @Autowired private InflowControlDAO inflowControlDAO; public static synchronized InflowControlManager getInstance() { return ApplicationContextProvider.getContext().getBean(InflowControlManager.class); } public static synchronized Bucket getBucket() { return ApplicationContextProvider.getContext().getBean(InflowControlManager.class); } /** * 유량제어 대상인 거래인지 체크한다.(사이트에 맞게 구성) * * @param eaiServerManager * @param eaiMessage * @return */ public static Boolean isTargetOfInflowControl(EAIServerManager eaiServerManager, EAIMessage eaiMessage) { InterfaceMapper mapper = eaiMessage.getMapper(); String inExDiv = MessageUtil.getInExDivision(mapper.getInExDivision(eaiMessage.getStandardMessage()), eaiMessage.getInternalExternalDvcd()); // 1|2 String returnType = mapper.getSendRecvDivision(eaiMessage.getStandardMessage()); // S|R if (STDMessageKeys.DIRECTION_IN.equals(inExDiv) && STDMessageKeys.SEND_RECV_CD_SEND.equals(returnType)) { return Boolean.valueOf(true); } return Boolean.valueOf(false); } public void start() throws LifecycleException { if (started) throw new LifecycleException("RECEAICMM201"); lifecycle.fireLifecycleEvent(STARTING_EVENT, this); try { init(); } catch (Exception e) { throw new LifecycleException(ExceptionUtil.getErrorCode(e, "RECEAICMM202")); } started = true; lifecycle.fireLifecycleEvent(STARTED_EVENT, this); } private void init() throws Exception { initAdapter(); initInterface(); initGroup(); } private void initAdapter() throws Exception { adapterBucketList = new HashMap<>(); List inflowAdapterVoList = inflowControlDAO.getInflowAdapterList(); for (InflowTargetVO inflowVo : inflowAdapterVoList) { if (InflowControlManager.isInflowTarget(inflowVo)) { CustomBucket bucket = makeBucketUsingInflowVO(inflowVo); if (bucket != null) { adapterBucketList.put(inflowVo.getName(), bucket); } } } } private void initInterface() throws Exception { interfaceBucketList = new HashMap<>(); List inflowInterfaceVoList = inflowControlDAO.getInflowInterfaceList(); for (InflowTargetVO inflowVo : inflowInterfaceVoList) { if (InflowControlManager.isInflowTarget(inflowVo)) { CustomBucket bucket = makeBucketUsingInflowVO(inflowVo); if (bucket != null) { interfaceBucketList.put(inflowVo.getName(), bucket); } } } } private void initGroup() throws Exception { groupBucketList = new HashMap<>(); interfaceToGroupMap = new HashMap<>(); groupVoMap = new HashMap<>(); List inflowGroupVoList = inflowControlDAO.getInflowGroupList(); for (InflowGroupVO groupVo : inflowGroupVoList) { if (isInflowGroupTarget(groupVo)) { CustomBucket bucket = makeBucketUsingGroupVO(groupVo); if (bucket != null) { groupBucketList.put(groupVo.getGroupId(), bucket); groupVoMap.put(groupVo.getGroupId(), groupVo); // 인터페이스 → 그룹 매핑 등록 for (String interfaceId : groupVo.getInterfaceList()) { interfaceToGroupMap.put(interfaceId, groupVo.getGroupId()); } } } } if (logger.isInfo()) { logger.info("InflowControlManager] initGroup completed. groups=" + groupBucketList.size() + ", mappings=" + interfaceToGroupMap.size()); } } public synchronized void reloadAdapter() throws Exception { if (logger.isWarn()) { logger.warn("InflowControlManager] reload all Started ..."); } initAdapter(); if (logger.isWarn()) { logger.warn("InflowControlManager] reload all finished ..."); } } public synchronized void reloadInterface() throws Exception { if (logger.isWarn()) { logger.warn("InflowControlManager] reload all Started ..."); } initInterface(); if (logger.isWarn()) { logger.warn("InflowControlManager] reload all finished ..."); } } public synchronized void reloadAdapter(String adapter) throws Exception { if (logger.isWarn()) { logger.warn("InflowControlManager] reload Started ..."); } Map map = inflowControlDAO.getInflowTargetByAdater(adapter); if (map != null) { Iterator it = map.keySet().iterator(); String key = ""; while (it.hasNext()) { key = it.next(); InflowTargetVO inflowTarget = map.get(key); if (InflowControlManager.isInflowTarget(inflowTarget)) { CustomBucket bucket = makeBucketUsingInflowVO(inflowTarget); if (bucket != null) { adapterBucketList.put(inflowTarget.getName(), bucket); } else { adapterBucketList.remove(inflowTarget.getName()); } } else { adapterBucketList.remove(inflowTarget.getName()); } } } if (logger.isWarn()) { logger.warn("InflowControlManager] reload finished ..."); } } public synchronized void reloadInterface(String inter) throws Exception { if (logger.isWarn()) { logger.warn("InflowControlManager] reload Started ..."); } Map map = inflowControlDAO.getInflowTargetByInterface(inter); if (map != null) { Iterator it = map.keySet().iterator(); String key = ""; while (it.hasNext()) { key = it.next(); InflowTargetVO inflowTarget = map.get(key); if (InflowControlManager.isInflowTarget(inflowTarget)) { CustomBucket bucket = makeBucketUsingInflowVO(inflowTarget); if (bucket != null) { interfaceBucketList.put(inflowTarget.getName(), bucket); } else { interfaceBucketList.remove(inflowTarget.getName()); } } else { interfaceBucketList.remove(inflowTarget.getName()); } } } if (logger.isWarn()) { logger.warn("InflowControlManager] reload finished ..."); } } public synchronized void reloadGroup() throws Exception { if (logger.isWarn()) { logger.warn("InflowControlManager] reloadGroup all Started ..."); } initGroup(); if (logger.isWarn()) { logger.warn("InflowControlManager] reloadGroup all finished ..."); } } public synchronized void reloadGroup(String groupId) throws Exception { if (logger.isWarn()) { logger.warn("InflowControlManager] reloadGroup Started ... groupId=" + groupId); } Map map = inflowControlDAO.getInflowTargetByGroup(groupId); if (map != null) { // 기존 그룹에 속한 인터페이스 매핑 제거 Iterator> mappingIt = interfaceToGroupMap.entrySet().iterator(); while (mappingIt.hasNext()) { Map.Entry entry = mappingIt.next(); if (groupId.equals(entry.getValue())) { mappingIt.remove(); } } Iterator it = map.keySet().iterator(); while (it.hasNext()) { String key = it.next(); InflowGroupVO groupVo = map.get(key); if (isInflowGroupTarget(groupVo)) { CustomBucket bucket = makeBucketUsingGroupVO(groupVo); if (bucket != null) { groupBucketList.put(groupVo.getGroupId(), bucket); groupVoMap.put(groupVo.getGroupId(), groupVo); // 인터페이스 → 그룹 매핑 재등록 for (String interfaceId : groupVo.getInterfaceList()) { interfaceToGroupMap.put(interfaceId, groupVo.getGroupId()); } } else { groupBucketList.remove(groupVo.getGroupId()); groupVoMap.remove(groupVo.getGroupId()); } } else { groupBucketList.remove(groupVo.getGroupId()); groupVoMap.remove(groupVo.getGroupId()); } } } if (logger.isWarn()) { logger.warn("InflowControlManager] reloadGroup finished ..."); } } public void stop() throws LifecycleException { // Validate and update our current component state if (!started) throw new LifecycleException("RECEAICMM203"); // Notify our interested LifecycleListeners lifecycle.fireLifecycleEvent(STOPING_EVENT, this); adapterBucketList.clear(); interfaceBucketList.clear(); groupBucketList.clear(); interfaceToGroupMap.clear(); groupVoMap.clear(); started = false; // Notify our interested LifecycleListeners lifecycle.fireLifecycleEvent(STOPPED_EVENT, this); } public void addLifecycleListener(LifecycleListener listener) { lifecycle.addLifecycleListener(listener); } public LifecycleListener[] findLifecycleListeners() { return lifecycle.findLifecycleListeners(); } public void removeLifecycleListener(LifecycleListener listener) { lifecycle.removeLifecycleListener(listener); } public boolean isStarted() { return this.started; } public String[] getAdapterAllKeys() { Iterator it = this.adapterBucketList.keySet().iterator(); String[] svcCd = new String[this.adapterBucketList.size()]; for (int i = 0; it.hasNext(); i++) { svcCd[i] = it.next(); } Arrays.sort(svcCd); return svcCd; } public String[] getInterfaceAllKeys() { Iterator it = this.interfaceBucketList.keySet().iterator(); String[] svcCd = new String[this.interfaceBucketList.size()]; for (int i = 0; it.hasNext(); i++) { svcCd[i] = it.next(); } Arrays.sort(svcCd); return svcCd; } public void removeAdapter(String adapter) { adapterBucketList.remove(adapter); } public void removeInterface(String inter) { interfaceBucketList.remove(inter); } public void removeGroup(String groupId) { groupBucketList.remove(groupId); groupVoMap.remove(groupId); // 해당 그룹에 속한 인터페이스 매핑도 제거 Iterator> it = interfaceToGroupMap.entrySet().iterator(); while (it.hasNext()) { Map.Entry entry = it.next(); if (groupId.equals(entry.getValue())) { it.remove(); } } } public String[] getGroupAllKeys() { Iterator it = this.groupBucketList.keySet().iterator(); String[] groupIds = new String[this.groupBucketList.size()]; for (int i = 0; it.hasNext(); i++) { groupIds[i] = it.next(); } Arrays.sort(groupIds); return groupIds; } private CustomBucket makeBucketUsingInflowVO(InflowTargetVO inflowTarget) { if (InflowControlManager.isInflowTarget(inflowTarget)) { LocalBucketBuilder builder = Bucket4j.builder(); if (inflowTarget.getThresholdPerSecond() > 0) { builder.addLimit(Bandwidth.simple(inflowTarget.getThresholdPerSecond(), Duration.ofSeconds(1))); } if (inflowTarget.getThreshold() > 0 && StringUtils.isNotBlank(inflowTarget.getThresholdTimeUnit())) { builder.addLimit(Bandwidth.simple(inflowTarget.getThreshold(), InflowControlManager.getPeriod(inflowTarget.getThresholdTimeUnit()))); } return new CustomBucket(builder.build(), inflowTarget); } return null; } private CustomBucket makeBucketUsingGroupVO(InflowGroupVO groupVo) { if (isInflowGroupTarget(groupVo)) { LocalBucketBuilder builder = Bucket4j.builder(); if (groupVo.getThresholdPerSecond() > 0) { builder.addLimit(Bandwidth.simple(groupVo.getThresholdPerSecond(), Duration.ofSeconds(1))); } if (groupVo.getThreshold() > 0 && StringUtils.isNotBlank(groupVo.getThresholdTimeUnit())) { builder.addLimit(Bandwidth.simple(groupVo.getThreshold(), InflowControlManager.getPeriod(groupVo.getThresholdTimeUnit()))); } // InflowTargetVO로 변환하여 CustomBucket 생성 InflowTargetVO targetVo = new InflowTargetVO(); targetVo.setName(groupVo.getGroupId()); targetVo.setThreshold(groupVo.getThreshold()); targetVo.setThresholdPerSecond(groupVo.getThresholdPerSecond()); targetVo.setThresholdTimeUnit(groupVo.getThresholdTimeUnit()); targetVo.setActivate(groupVo.isActivate()); return new CustomBucket(builder.build(), targetVo); } return null; } @Override public boolean isAdapterPass(String adapter) { CustomBucket b = adapterBucketList.get(adapter); if (b == null) return true; return b.getLocalBucket().tryConsume(1); } @Override public boolean isInterfacePass(String inter) { CustomBucket b = interfaceBucketList.get(inter); if (b == null) return true; return b.getLocalBucket().tryConsume(1); } @Override public InflowTargetVO getAdapterInflowThreashold(String adapter) { CustomBucket b = adapterBucketList.get(adapter); if (b == null) return null; return b.getInflowTargetVo(); } @Override public InflowTargetVO getInterfaceInflowThreashold(String inter) { CustomBucket b = interfaceBucketList.get(inter); if (b == null) return null; return b.getInflowTargetVo(); } @Override public InflowTargetVO getInflowThreashold(String inter) { return null; } public InflowTargetVO getAdapterInflow(String adapter) { return getAdapterInflowThreashold(adapter); } public InflowTargetVO getInterfaceInflow(String inter) { return getInterfaceInflowThreashold(inter); } @Override public boolean isGroupPass(String groupId) { CustomBucket b = groupBucketList.get(groupId); if (b == null) return true; return b.getLocalBucket().tryConsume(1); } @Override public InflowGroupVO getGroupInflowThreshold(String groupId) { return groupVoMap.get(groupId); } @Override public String getGroupIdByInterface(String interfaceId) { return interfaceToGroupMap.get(interfaceId); } public InflowGroupVO getGroupInflow(String groupId) { return getGroupInflowThreshold(groupId); } public static boolean isInflowTarget(InflowTargetVO inflowTarget) { if (inflowTarget == null || !inflowTarget.isActivate()) { return false; } return inflowTarget.getThresholdPerSecond() > 0 || (inflowTarget.getThreshold() > 0 && StringUtils .equalsAnyIgnoreCase(inflowTarget.getThresholdTimeUnit(), QUOTA_TIMEUNIT_DAY, QUOTA_TIMEUNIT_SECOND, QUOTA_TIMEUNIT_MINUTE, QUOTA_TIMEUNIT_HOUR, QUOTA_TIMEUNIT_DAY, QUOTA_TIMEUNIT_MONTH)); } public static boolean isInflowGroupTarget(InflowGroupVO groupVo) { if (groupVo == null || !groupVo.isActivate()) { return false; } return groupVo.getThresholdPerSecond() > 0 || (groupVo.getThreshold() > 0 && StringUtils .equalsAnyIgnoreCase(groupVo.getThresholdTimeUnit(), QUOTA_TIMEUNIT_DAY, QUOTA_TIMEUNIT_SECOND, QUOTA_TIMEUNIT_MINUTE, QUOTA_TIMEUNIT_HOUR, QUOTA_TIMEUNIT_DAY, QUOTA_TIMEUNIT_MONTH)); } public static Duration getPeriod(String timeUnit) { if (StringUtils.equalsIgnoreCase(timeUnit, QUOTA_TIMEUNIT_SECOND)) { return Duration.ofSeconds(1); } else if (StringUtils.equalsIgnoreCase(timeUnit, QUOTA_TIMEUNIT_MINUTE)) { return Duration.ofMinutes(1); } else if (StringUtils.equalsIgnoreCase(timeUnit, QUOTA_TIMEUNIT_HOUR)) { return Duration.ofHours(1); } else if (StringUtils.equalsIgnoreCase(timeUnit, QUOTA_TIMEUNIT_DAY)) { return Duration.ofDays(1); } else if (StringUtils.equalsIgnoreCase(timeUnit, QUOTA_TIMEUNIT_MONTH)) { Calendar calendar = Calendar.getInstance(); return Duration.ofDays(calendar.getActualMaximum(Calendar.DAY_OF_MONTH)); } else { return null; } } }