feat(config-service): split config-service from iam for plugin/layout config

- new NestJS service on port 3011/gRPC 50059 (ADR-026)
- owns 6 config_ tables (plugin/role-mapping/role-layout/layout-tpl/user-override/outbox)
- GraphQL Federation 2 subgraph with DataLoader + RouterAuthGuard
- gRPC ConfigService + admin REST CRUD + user REST API
- three-layer merge: registry.defaultProps + roleMapping.widget_props + userOverride.props
- Redis cache with 5min TTL
- registered in apollo-router supergraph + docker-compose + port-allocation

Implements M3 of v2.1 migration plan.
This commit is contained in:
SpecialX
2026-07-15 02:13:03 +08:00
parent 163bff6666
commit 1a5fa78fa6
44 changed files with 3538 additions and 33 deletions

View File

@@ -40,3 +40,8 @@ subgraphs:
routing_url: http://data-ana:3006/graphql
schema:
subgraph_url: http://data-ana:3006/graphql
config:
routing_url: http://config-service:3011/graphql
schema:
subgraph_url: http://config-service:3011/graphql

View File

@@ -294,6 +294,33 @@ services:
depends_on:
redis:
condition: service_started
config-service:
build:
context: ../services/config-service
container_name: edu-config-service
profiles: ["p3", "p4", "p5", "p6"]
restart: unless-stopped
environment:
DATABASE_URL: mysql://edu:${MYSQL_PASSWORD:-changeme}@edu-mysql:3306/next_edu_cloud
REDIS_URL: redis://edu-redis:6379
KAFKA_BROKERS: kafka:29092
ROUTER_AUTH_SECRET: ${ROUTER_AUTH_SECRET:-dev-router-secret}
DEV_MODE: ${DEV_MODE:-false}
PORT: "3011"
GRPC_PORT: "50059"
ports:
- "3011:3011"
- "50059:50059"
depends_on:
mysql:
condition: service_healthy
redis:
condition: service_started
healthcheck:
test: ["CMD", "wget", "--spider", "-q", "http://localhost:3011/healthz"]
interval: 15s
timeout: 5s
retries: 5
apollo-router:
build:
context: ./apollo-router
@@ -313,6 +340,7 @@ services:
- msg
- ai
- data-ana
- config-service
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:8088/health"]
interval: 15s

View File

@@ -733,3 +733,84 @@ CREATE TABLE IF NOT EXISTS `msg_announcement_reads` (
UNIQUE KEY `uniq_announcement_user` (`announcement_id`, `user_id`),
INDEX `idx_msg_announcement_reads_user` (`user_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
-- ============================================================
-- 6. Config 服务services/config-service/src/config-config/config.schema.ts
-- v2.1 M3 / ADR-026从 iam 拆分出插件配置 + 布局 + 用户偏好职责
-- ============================================================
-- 6.1 插件注册表系统级admin 维护;内置插件由构建脚本同步)
CREATE TABLE IF NOT EXISTS `config_plugin_registry` (
`plugin_id` VARCHAR(100) NOT NULL,
`category` VARCHAR(50) NOT NULL,
`version` VARCHAR(50) NULL,
`display_name` VARCHAR(200) NOT NULL,
`description` TEXT NULL,
`required_roles` JSON NULL,
`default_slot` VARCHAR(50) NULL,
`default_size` JSON NULL,
`default_props` JSON NULL,
`props_schema` JSON NULL,
`is_builtin` BOOLEAN NOT NULL DEFAULT TRUE,
`is_active` BOOLEAN NOT NULL DEFAULT TRUE,
`package_url` TEXT NULL,
`created_at` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
`updated_at` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (`plugin_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
-- 6.2 角色-插件映射admin 配置角色可用插件集 + 角色 Layout 默认)
CREATE TABLE IF NOT EXISTS `config_role_plugin_mapping` (
`role` VARCHAR(50) NOT NULL,
`plugin_id` VARCHAR(100) NOT NULL,
`slot` VARCHAR(50) NULL,
`sort_order` INT NOT NULL DEFAULT 0,
`is_enabled` BOOLEAN NOT NULL DEFAULT TRUE,
`widget_props` JSON NULL,
INDEX `idx_config_role_plugin_pk` (`role`, `plugin_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
-- 6.3 角色 Layout 默认admin 配置角色默认模板)
CREATE TABLE IF NOT EXISTS `config_role_layout_default` (
`role` VARCHAR(50) NOT NULL,
`layout_id` VARCHAR(50) NOT NULL,
`slot_overrides` JSON NULL,
PRIMARY KEY (`role`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
-- 6.4 Layout 模板(系统预置 5 种不可改classic/focus/split/triple/canvas
CREATE TABLE IF NOT EXISTS `config_layout_templates` (
`layout_id` VARCHAR(50) NOT NULL,
`display_name` VARCHAR(200) NOT NULL,
`description` TEXT NULL,
`available_slots` JSON NULL,
`layout_schema` JSON NULL,
`is_active` BOOLEAN NOT NULL DEFAULT TRUE,
PRIMARY KEY (`layout_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
-- 6.5 用户布局覆盖(用户自定义)
CREATE TABLE IF NOT EXISTS `config_user_layout_override` (
`user_id` VARCHAR(36) NOT NULL,
`active_layout` VARCHAR(50) NULL,
`slot_overrides` JSON NULL,
`plugin_placements` JSON NULL,
`hidden_plugins` JSON NULL,
`updated_at` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (`user_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
-- 6.6 Config Outbox 表v2.1 ADR-032业务写 outboxDebezium 监听 binlog 投递 Kafka
CREATE TABLE IF NOT EXISTS `config_outbox` (
`event_id` CHAR(36) NOT NULL,
`aggregate_type` VARCHAR(50) NOT NULL,
`aggregate_id` VARCHAR(100) NOT NULL,
`event_type` VARCHAR(100) NOT NULL,
`payload` JSON NOT NULL,
`created_at` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
`published_at` TIMESTAMP NULL,
`retry_count` INT NOT NULL DEFAULT 0,
`next_retry_at` TIMESTAMP NULL,
PRIMARY KEY (`event_id`),
INDEX `idx_outbox_unpublished` (`published_at`, `next_retry_at`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

View File

@@ -37,21 +37,22 @@
## 3. NestJS BFF + 业务服务3000-3099
| 服务 | HTTP 端口 | gRPC 端口 | 说明 | 阶段 |
| ------------- | --------- | --------- | ----------------------------------------------- | ------- |
| apollo-router | 3000 | — | v2.1 GraphQL 聚合层(替代 BFF 手动聚合) | P3 |
| iam | 3002 | 50052 | P2 启用 gRPC serverI1 裁决2026-07-09 修正) | P2 ✅ |
| teacher-bff | 3003 | — | BFF 不暴露 gRPC对下游走 gRPC | P2 ✅ |
| core-edu | 3004 | 50053 | 含原 classes 服务(合并后) | P3 |
| content | 3005 | 50054 | Neo4j + ES | P4 |
| data-ana | 3006 | 50055 | HTTP 保留作 Gateway 直连降级gRPC 为 P4 主入口 | P4 |
| msg | 3007 | 50056 | 通知中心 | P5 |
| ai | 3008 | 50058 | LLM 网关 | P5 |
| student-bff | 3009 | — | BFF 不暴露 gRPC | P3 |
| parent-bff | 3010 | — | BFF 不暴露 gRPC | P4 |
| ~~classes~~ | ~~3001~~ | ~~—~~ | 已合并入 core-eduP3 | P1 历史 |
| 服务 | HTTP 端口 | gRPC 端口 | 说明 | 阶段 |
| -------------- | --------- | --------- | ----------------------------------------------- | ------- |
| apollo-router | 3000 | — | v2.1 GraphQL 聚合层(替代 BFF 手动聚合) | P3 |
| iam | 3002 | 50052 | P2 启用 gRPC serverI1 裁决2026-07-09 修正) | P2 ✅ |
| teacher-bff | 3003 | — | BFF 不暴露 gRPC对下游走 gRPC | P2 ✅ |
| core-edu | 3004 | 50053 | 含原 classes 服务(合并后) | P3 |
| content | 3005 | 50054 | Neo4j + ES | P4 |
| data-ana | 3006 | 50055 | HTTP 保留作 Gateway 直连降级gRPC 为 P4 主入口 | P4 |
| msg | 3007 | 50056 | 通知中心 | P5 |
| ai | 3008 | 50058 | LLM 网关 | P5 |
| student-bff | 3009 | — | BFF 不暴露 gRPC | P3 |
| parent-bff | 3010 | — | BFF 不暴露 gRPC | P4 |
| config-service | 3011 | 50059 | v2.1 M3 从 iam 拆分ADR-026 | P3 |
| ~~classes~~ | ~~3001~~ | ~~—~~ | 已合并入 core-eduP3 | P1 历史 |
> **端口空闲**3011-3099 预留扩展。
> **端口空闲**3012-3099 预留扩展。
---
@@ -82,9 +83,10 @@
| data-ana | 50055 | AnalyticsService | P4 |
| msg | 50056 | NotificationService / NotificationPreferenceService / NotificationTemplateService | P5 |
| ai | 50058 | AiService | P5 |
| config-service | 50059 | ConfigService | P3 |
| ~~push-gateway~~ | ~~50057~~ | ~~PushService~~ | 豁免(见 §2 |
> **端口空闲**50051、50057、50059-50099 预留扩展。
> **端口空闲**50051、50057、50060-50099 预留扩展。
> **push-gateway 50057**:原计划预留,因 gRPC 豁免释放。50058 让给 ai 服务。
---
@@ -116,12 +118,13 @@
## 7. 变更记录
| 日期 | 变更 | 决策者 |
| ---------- | ------------------------------------------------------ | ------ |
| 2026-07-09 | 初始创建,登记全部 15 服务 + 基础设施端口 | coord |
| 2026-07-09 | 仲裁 admin-portal 3003 → 4003MF 配置 3000 → 4000 | coord |
| 2026-07-09 | 仲裁 push-gateway 豁免 gRPC释放 5005750058 让给 ai | coord |
| 2026-07-09 | classes 3001 标记为历史(已合并入 core-edu | coord |
| 日期 | 变更 | 决策者 |
| ---------- | --------------------------------------------------------- | ------ |
| 2026-07-09 | 初始创建,登记全部 15 服务 + 基础设施端口 | coord |
| 2026-07-09 | 仲裁 admin-portal 3003 → 4003MF 配置 3000 → 4000 | coord |
| 2026-07-09 | 仲裁 push-gateway 豁免 gRPC释放 5005750058 让给 ai | coord |
| 2026-07-09 | classes 3001 标记为历史(已合并入 core-edu | coord |
| 2026-07-14 | M3 新增 config-service3011/50059ADR-026 从 iam 拆分) | coord |
---