diff --git a/WebContent/WEB-INF/authserver-servlet.xml b/WebContent/WEB-INF/authserver-servlet.xml index 4eb1843..1342774 100644 --- a/WebContent/WEB-INF/authserver-servlet.xml +++ b/WebContent/WEB-INF/authserver-servlet.xml @@ -21,6 +21,8 @@ base-package="com.eactive.eai.authserver" /> + diff --git a/kjb-docs/inflow/inflow_control_group_ddl.sql b/kjb-docs/inflow/inflow_control_group_ddl.sql new file mode 100644 index 0000000..685024c --- /dev/null +++ b/kjb-docs/inflow/inflow_control_group_ddl.sql @@ -0,0 +1,51 @@ +-- ============================================ +-- 유량제어 그룹 테이블 DDL +-- ============================================ + +-- ============================================ +-- Oracle +-- ============================================ + +-- 유량제어 그룹 테이블 +CREATE TABLE inflow_control_group ( + group_id VARCHAR2(36) NOT NULL, -- UUID + group_name VARCHAR2(100), + threshold NUMBER(10), + threshold_per_second NUMBER(10), + threshold_time_unit VARCHAR2(12), + use_yn VARCHAR2(1) DEFAULT '1', + modified_by VARCHAR2(50), + modified_at TIMESTAMP, + CONSTRAINT pk_inflow_control_group PRIMARY KEY (group_id) +); + +COMMENT ON TABLE inflow_control_group IS '유량제어 그룹'; +COMMENT ON COLUMN inflow_control_group.group_id IS '그룹 ID (UUID)'; +COMMENT ON COLUMN inflow_control_group.group_name IS '그룹명'; +COMMENT ON COLUMN inflow_control_group.threshold IS '임계치'; +COMMENT ON COLUMN inflow_control_group.threshold_per_second IS '초당 임계치'; +COMMENT ON COLUMN inflow_control_group.threshold_time_unit IS '임계치 TimeUnit (SEC, MIN, HOU, DAY, MON)'; +COMMENT ON COLUMN inflow_control_group.use_yn IS '사용여부 (1:사용, 0:미사용)'; +COMMENT ON COLUMN inflow_control_group.modified_by IS '수정자'; +COMMENT ON COLUMN inflow_control_group.modified_at IS '수정일시'; + +-- 그룹-인터페이스 매핑 테이블 +CREATE TABLE inflow_control_group_mapping ( + interface_id VARCHAR2(100) NOT NULL, + group_id VARCHAR2(50) NOT NULL, + use_yn VARCHAR2(1) DEFAULT '1', + modified_by VARCHAR2(50), + modified_at TIMESTAMP, + CONSTRAINT pk_inflow_ctrl_grp_mapping PRIMARY KEY (interface_id) +); + +COMMENT ON TABLE inflow_control_group_mapping IS '유량제어 그룹 매핑'; +COMMENT ON COLUMN inflow_control_group_mapping.interface_id IS '인터페이스 ID'; +COMMENT ON COLUMN inflow_control_group_mapping.group_id IS '그룹 ID'; +COMMENT ON COLUMN inflow_control_group_mapping.use_yn IS '사용여부 (1:사용, 0:미사용)'; +COMMENT ON COLUMN inflow_control_group_mapping.modified_by IS '수정자'; +COMMENT ON COLUMN inflow_control_group_mapping.modified_at IS '수정일시'; + +-- 인덱스 +CREATE INDEX idx_inflow_ctrl_grp_map_grp ON inflow_control_group_mapping (group_id); + diff --git a/src/main/java/com/eactive/eai/manage/inflow/GroupBucketStatusDTO.java b/src/main/java/com/eactive/eai/manage/inflow/GroupBucketStatusDTO.java new file mode 100644 index 0000000..64dd221 --- /dev/null +++ b/src/main/java/com/eactive/eai/manage/inflow/GroupBucketStatusDTO.java @@ -0,0 +1,101 @@ +package com.eactive.eai.manage.inflow; + +import java.util.List; + +public class GroupBucketStatusDTO { + private String groupId; + private String groupName; + private boolean activate; + private List buckets; + + public static class BucketInfo { + private String type; // "perSecond" | "threshold" + private long capacity; // 최대 토큰 수 + private long availableTokens; // 현재 사용 가능한 토큰 수 + private String timeUnit; // threshold인 경우 시간 단위 + private double usagePercent; // 사용률 (100 - availableTokens/capacity * 100) + + public BucketInfo() {} + + public BucketInfo(String type, long capacity, long availableTokens, String timeUnit) { + this.type = type; + this.capacity = capacity; + this.availableTokens = availableTokens; + this.timeUnit = timeUnit; + this.usagePercent = capacity > 0 ? + Math.round((1.0 - (double) availableTokens / capacity) * 10000) / 100.0 : 0; + } + + public String getType() { + return type; + } + + public void setType(String type) { + this.type = type; + } + + public long getCapacity() { + return capacity; + } + + public void setCapacity(long capacity) { + this.capacity = capacity; + } + + public long getAvailableTokens() { + return availableTokens; + } + + public void setAvailableTokens(long availableTokens) { + this.availableTokens = availableTokens; + } + + public String getTimeUnit() { + return timeUnit; + } + + public void setTimeUnit(String timeUnit) { + this.timeUnit = timeUnit; + } + + public double getUsagePercent() { + return usagePercent; + } + + public void setUsagePercent(double usagePercent) { + this.usagePercent = usagePercent; + } + } + + public String getGroupId() { + return groupId; + } + + public void setGroupId(String groupId) { + this.groupId = groupId; + } + + public String getGroupName() { + return groupName; + } + + public void setGroupName(String groupName) { + this.groupName = groupName; + } + + public boolean isActivate() { + return activate; + } + + public void setActivate(boolean activate) { + this.activate = activate; + } + + public List getBuckets() { + return buckets; + } + + public void setBuckets(List buckets) { + this.buckets = buckets; + } +} diff --git a/src/main/java/com/eactive/eai/manage/inflow/InflowGroupBucketController.java b/src/main/java/com/eactive/eai/manage/inflow/InflowGroupBucketController.java new file mode 100644 index 0000000..befff94 --- /dev/null +++ b/src/main/java/com/eactive/eai/manage/inflow/InflowGroupBucketController.java @@ -0,0 +1,56 @@ +package com.eactive.eai.manage.inflow; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +@RestController +@RequestMapping("/manage/inflow/group") +public class InflowGroupBucketController { + + @Autowired + private InflowGroupBucketService bucketService; + + /** + * 특정 그룹의 버킷 상태 조회 + * GET /manage/inflow/group/{groupId}/bucket-status + */ + @GetMapping("/{groupId}/bucket-status") + public ResponseEntity getGroupBucketStatus(@PathVariable String groupId) { + GroupBucketStatusDTO status = bucketService.getGroupBucketStatus(groupId); + + if (status == null) { + Map error = new HashMap<>(); + error.put("success", false); + error.put("message", "그룹을 찾을 수 없습니다: " + groupId); + return ResponseEntity.ok(error); + } + + Map result = new HashMap<>(); + result.put("success", true); + result.put("data", status); + return ResponseEntity.ok(result); + } + + /** + * 모든 그룹의 버킷 상태 조회 + * GET /manage/inflow/group/bucket-status + */ + @GetMapping("/bucket-status") + public ResponseEntity getAllGroupBucketStatus() { + List statusList = bucketService.getAllGroupBucketStatus(); + + Map result = new HashMap<>(); + result.put("success", true); + result.put("data", statusList); + result.put("count", statusList.size()); + return ResponseEntity.ok(result); + } +} diff --git a/src/main/java/com/eactive/eai/manage/inflow/InflowGroupBucketService.java b/src/main/java/com/eactive/eai/manage/inflow/InflowGroupBucketService.java new file mode 100644 index 0000000..1bb4d6f --- /dev/null +++ b/src/main/java/com/eactive/eai/manage/inflow/InflowGroupBucketService.java @@ -0,0 +1,105 @@ +package com.eactive.eai.manage.inflow; + +import java.lang.reflect.Field; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +import org.springframework.stereotype.Service; + +import com.eactive.eai.common.inflow.CustomGroupBucket; +import com.eactive.eai.common.inflow.InflowControlManager; +import com.eactive.eai.common.inflow.InflowGroupVO; + +@Service +public class InflowGroupBucketService { + + public GroupBucketStatusDTO getGroupBucketStatus(String groupId) { + InflowControlManager manager = InflowControlManager.getInstance(); + + InflowGroupVO groupVo = manager.getGroupInflowThreshold(groupId); + if (groupVo == null) { + return null; + } + + GroupBucketStatusDTO dto = new GroupBucketStatusDTO(); + dto.setGroupId(groupId); + dto.setGroupName(groupVo.getGroupName()); + dto.setActivate(groupVo.isActivate()); + + List buckets = new ArrayList<>(); + + // CustomGroupBucket에서 직접 토큰 조회 + CustomGroupBucket customGroupBucket = getCustomGroupBucket(groupId); + + // perSecond bucket + if (groupVo.getThresholdPerSecond() > 0) { + long capacity = groupVo.getThresholdPerSecond(); + long available = capacity; + if (customGroupBucket != null) { + long tokens = customGroupBucket.getPerSecondAvailableTokens(); + if (tokens >= 0) { + available = Math.min(tokens, capacity); + } + } + buckets.add(new GroupBucketStatusDTO.BucketInfo( + "perSecond", + capacity, + available, + "SEC" + )); + } + + // threshold bucket + if (groupVo.getThreshold() > 0) { + long capacity = groupVo.getThreshold(); + long available = capacity; + if (customGroupBucket != null) { + long tokens = customGroupBucket.getThresholdAvailableTokens(); + if (tokens >= 0) { + available = Math.min(tokens, capacity); + } + } + buckets.add(new GroupBucketStatusDTO.BucketInfo( + "threshold", + capacity, + available, + groupVo.getThresholdTimeUnit() + )); + } + + dto.setBuckets(buckets); + return dto; + } + + /** + * 그룹 버킷 조회 (리플렉션 사용) + */ + public CustomGroupBucket getCustomGroupBucket(String groupId) { + try { + InflowControlManager manager = InflowControlManager.getInstance(); + Field field = InflowControlManager.class.getDeclaredField("groupBucketList"); + field.setAccessible(true); + @SuppressWarnings("unchecked") + Map groupBucketList = + (Map) field.get(manager); + return groupBucketList.get(groupId); + } catch (Exception e) { + return null; + } + } + + public List getAllGroupBucketStatus() { + InflowControlManager manager = InflowControlManager.getInstance(); + String[] groupIds = manager.getGroupAllKeys(); + + List result = new ArrayList<>(); + for (String groupId : groupIds) { + GroupBucketStatusDTO dto = getGroupBucketStatus(groupId); + if (dto != null) { + result.add(dto); + } + } + return result; + } +}