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;
+ }
+}