diff --git a/.github/workflows/docker_publish.yml b/.github/workflows/docker_publish.yml
index 361795c..d2df9c4 100644
--- a/.github/workflows/docker_publish.yml
+++ b/.github/workflows/docker_publish.yml
@@ -33,6 +33,12 @@ jobs:
- name: Checkout Code
uses: actions/checkout@v7.0.1
+ - name: Checkout OpenSabre Framework
+ uses: actions/checkout@v7.0.1
+ with:
+ repository: opensabre/opensabre-framework
+ path: opensabre-framework
+
- name: Set up JDK
uses: actions/setup-java@v5.6.0
with:
@@ -40,6 +46,9 @@ jobs:
distribution: 'temurin'
cache: maven
+ - name: Install OpenSabre Framework Snapshot
+ run: mvn --batch-mode --no-transfer-progress -f opensabre-framework/pom.xml -DskipTests install
+
- name: Run Tests
run: mvn --batch-mode --no-transfer-progress verify
@@ -66,6 +75,12 @@ jobs:
- name: Checkout Code
uses: actions/checkout@v7.0.1
+ - name: Checkout OpenSabre Framework
+ uses: actions/checkout@v7.0.1
+ with:
+ repository: opensabre/opensabre-framework
+ path: opensabre-framework
+
- name: Set up JDK
uses: actions/setup-java@v5.6.0
with:
@@ -73,6 +88,9 @@ jobs:
distribution: 'temurin'
cache: maven
+ - name: Install OpenSabre Framework Snapshot
+ run: mvn --batch-mode --no-transfer-progress -f opensabre-framework/pom.xml -DskipTests install
+
- name: Resolve image tags
id: image
shell: bash
diff --git a/docs/development.md b/docs/development.md
index 4fb1600..b2b89dd 100644
--- a/docs/development.md
+++ b/docs/development.md
@@ -1,6 +1,6 @@
# 开发与运行
-1. 基于 `resources/db/` 初始化数据库;已有环境按版本顺序评估并执行 `migrations/`。
+1. 数据库变更写入 `resources/db/migration/mysql/`,由发布阶段的独立 Flyway 进程执行;应用 Pod 启动时不执行迁移。
2. 配置注册中心、数据库、缓存和通知渠道等依赖,再通过 Maven 启动。
3. 变更后按模块验证 API、数据持久化、权限和异常路径。
diff --git a/pom.xml b/pom.xml
index c2e52cb..84ee1e5 100644
--- a/pom.xml
+++ b/pom.xml
@@ -7,7 +7,7 @@
opensabre-base-dependencies
io.github.opensabre
- 1.1.2
+ 1.1.3-SNAPSHOT
base-sysadmin
@@ -45,6 +45,10 @@
+
+ io.github.opensabre
+ opensabre-starter-migration
+
io.github.opensabre
opensabre-starter-register
diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml
index 8e19a7f..ef6b56e 100644
--- a/src/main/resources/application.yml
+++ b/src/main/resources/application.yml
@@ -1,6 +1,9 @@
server:
port: ${SERVER_PORT:8020}
spring:
+ # Database changes are executed by the dedicated release migration process, never by app pods.
+ flyway:
+ enabled: false
application:
name: base-sysadmin
cloud:
diff --git a/src/main/resources/db/migration/mysql/V20260701_00__baseline.sql b/src/main/resources/db/migration/mysql/V20260701_00__baseline.sql
new file mode 100644
index 0000000..9e6f5dd
--- /dev/null
+++ b/src/main/resources/db/migration/mysql/V20260701_00__baseline.sql
@@ -0,0 +1,164 @@
+SET NAMES utf8;
+-- 审计日志表
+DROP TABLE IF EXISTS base_sys_audit_log;
+CREATE TABLE IF NOT EXISTS `base_sys_audit_log` (
+ `id` varchar(32) NOT NULL COMMENT '主键ID',
+ `operation_type` varchar(50) NOT NULL COMMENT '操作类型',
+ `operation_time` datetime NOT NULL COMMENT '操作时间',
+ `operator_username` varchar(100) NOT NULL COMMENT '操作人用户名',
+ `module` varchar(100) NOT NULL COMMENT '操作模块',
+ `description` varchar(500) COMMENT '操作描述',
+ `client_ip` varchar(50) COMMENT '操作IP地址',
+ `target_key` varchar(50) COMMENT '操作目标关键key',
+ `user_agent` varchar(500) COMMENT '用户代理',
+ `request_method` varchar(10) COMMENT '请求方法',
+ `request_url` varchar(500) COMMENT '请求URL',
+ `request` text COMMENT '请求参数',
+ `response` text COMMENT '操作结果',
+ `error_message` text COMMENT '错误信息',
+ `execution_time` bigint COMMENT '执行时间(毫秒)',
+ `created_by` varchar(100) NOT NULL COMMENT '创建人',
+ `created_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
+ `updated_by` varchar(100) NOT NULL COMMENT '更新人',
+ `updated_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
+ PRIMARY KEY (`id`),
+ KEY `idx_operator_username` (`operator_username`),
+ KEY `idx_operation_time` (`operation_time`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='审计日志表';
+
+DROP TABLE IF EXISTS base_sys_captcha_scene;
+CREATE TABLE IF NOT EXISTS `base_sys_captcha_scene` (
+ `id` varchar(32) NOT NULL COMMENT '主键ID',
+ `scene_code` varchar(64) NOT NULL COMMENT '场景编码',
+ `scene_name` varchar(128) NOT NULL COMMENT '场景名称',
+ `captcha_type` varchar(32) NOT NULL COMMENT '验证码类型',
+ `template_code` varchar(64) DEFAULT NULL COMMENT '消息模板编码',
+ `notification_template_id` varchar(64) DEFAULT NULL COMMENT '通知模板ID',
+ `description` varchar(255) DEFAULT NULL COMMENT '描述',
+ `captcha_length` int NOT NULL DEFAULT 4 COMMENT '验证码长度',
+ `captcha_expire_time` int NOT NULL DEFAULT 300 COMMENT '过期时间(秒)',
+ `captcha_attempts` int NOT NULL DEFAULT 3 COMMENT '最大尝试次数',
+ `min_interval` int NOT NULL DEFAULT 60 COMMENT '最小间隔(秒)',
+ `max_limit_count` int NOT NULL DEFAULT 100 COMMENT '单用户生成限制次数',
+ `enabled` tinyint(1) NOT NULL DEFAULT 1 COMMENT '是否启用',
+ `created_by` varchar(100) NOT NULL COMMENT '创建人',
+ `created_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
+ `updated_by` varchar(100) NOT NULL COMMENT '更新人',
+ `updated_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
+ PRIMARY KEY (`id`),
+ UNIQUE KEY `uk_scene_code` (`scene_code`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='验证码场景表';
+
+DROP TABLE IF EXISTS base_sys_ratelimit_scene;
+CREATE TABLE IF NOT EXISTS `base_sys_ratelimit_scene` (
+ `id` varchar(32) NOT NULL COMMENT '主键ID',
+ `scene_code` varchar(64) NOT NULL COMMENT '场景编码',
+ `scene_name` varchar(128) NOT NULL COMMENT '场景名称',
+ `algorithm` varchar(32) NOT NULL COMMENT '限次算法',
+ `dimensions` varchar(255) DEFAULT NULL COMMENT '维度代码,逗号分隔',
+ `key_prefix` varchar(100) DEFAULT NULL COMMENT '业务前缀',
+ `max_count` int NOT NULL DEFAULT 5 COMMENT '最大次数',
+ `period` int NOT NULL DEFAULT 60 COMMENT '时间窗口(秒)',
+ `enabled` tinyint(1) NOT NULL DEFAULT 1 COMMENT '是否启用',
+ `description` varchar(255) DEFAULT NULL COMMENT '描述',
+ `created_by` varchar(100) NOT NULL COMMENT '创建人',
+ `created_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
+ `updated_by` varchar(100) NOT NULL COMMENT '更新人',
+ `updated_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
+ PRIMARY KEY (`id`),
+ UNIQUE KEY `uk_scene_code` (`scene_code`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='限次场景表';
+
+DROP TABLE IF EXISTS base_sys_notification_record;
+DROP TABLE IF EXISTS base_sys_notification_template;
+DROP TABLE IF EXISTS base_sys_notification_scene;
+CREATE TABLE IF NOT EXISTS `base_sys_notification_scene` (
+ `id` varchar(32) NOT NULL COMMENT '主键ID',
+ `scene_code` varchar(64) NOT NULL COMMENT '场景编码',
+ `scene_name` varchar(128) NOT NULL COMMENT '场景名称',
+ `description` varchar(255) DEFAULT NULL COMMENT '描述',
+ `enabled` tinyint(1) NOT NULL DEFAULT 1 COMMENT '是否启用',
+ `created_by` varchar(100) NOT NULL COMMENT '创建人',
+ `created_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
+ `updated_by` varchar(100) NOT NULL COMMENT '更新人',
+ `updated_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
+ PRIMARY KEY (`id`),
+ UNIQUE KEY `uk_scene_code` (`scene_code`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='通知场景表';
+
+CREATE TABLE IF NOT EXISTS `base_sys_notification_template` (
+ `id` varchar(32) NOT NULL COMMENT '主键ID',
+ `scene_code` varchar(64) NOT NULL COMMENT '场景编码',
+ `channel` varchar(32) NOT NULL COMMENT '发送渠道',
+ `template_name` varchar(128) NOT NULL COMMENT '模板名称',
+ `title` varchar(255) DEFAULT NULL COMMENT '标题',
+ `content` text NOT NULL COMMENT '模板内容',
+ `param_schema` varchar(1000) DEFAULT NULL COMMENT '参数说明',
+ `sort` int NOT NULL DEFAULT 1 COMMENT '排序',
+ `enabled` tinyint(1) NOT NULL DEFAULT 1 COMMENT '是否启用',
+ `created_by` varchar(100) NOT NULL COMMENT '创建人',
+ `created_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
+ `updated_by` varchar(100) NOT NULL COMMENT '更新人',
+ `updated_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
+ PRIMARY KEY (`id`),
+ UNIQUE KEY `uk_scene_channel` (`scene_code`, `channel`),
+ KEY `idx_scene_enabled_sort` (`scene_code`, `enabled`, `sort`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='通知渠道模板表';
+
+CREATE TABLE IF NOT EXISTS `base_sys_notification_record` (
+ `id` varchar(32) NOT NULL COMMENT '主键ID',
+ `scene_code` varchar(64) NOT NULL COMMENT '场景编码',
+ `channel` varchar(32) NOT NULL COMMENT '发送渠道',
+ `target` varchar(255) NOT NULL COMMENT '发送目标',
+ `template_id` varchar(32) DEFAULT NULL COMMENT '模板ID',
+ `template_title` varchar(255) DEFAULT NULL COMMENT '标题快照',
+ `template_content` text COMMENT '内容快照',
+ `args_json` text COMMENT '参数JSON',
+ `status` varchar(32) NOT NULL COMMENT '发送状态',
+ `message_id` varchar(128) DEFAULT NULL COMMENT '消息ID',
+ `failure_reason` text COMMENT '失败原因',
+ `retry_count` int NOT NULL DEFAULT 0 COMMENT '重试次数',
+ `next_retry_time` datetime DEFAULT NULL COMMENT '下次重试时间',
+ `sent_time` datetime DEFAULT NULL COMMENT '发送时间',
+ `created_by` varchar(100) NOT NULL COMMENT '创建人',
+ `created_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
+ `updated_by` varchar(100) NOT NULL COMMENT '更新人',
+ `updated_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
+ PRIMARY KEY (`id`),
+ KEY `idx_scene_channel_status` (`scene_code`, `channel`, `status`),
+ KEY `idx_created_time` (`created_time`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='通知发送记录表';
+
+DROP TABLE IF EXISTS base_sys_dict_item;
+DROP TABLE IF EXISTS base_sys_dict_type;
+CREATE TABLE IF NOT EXISTS `base_sys_dict_type` (
+ `id` varchar(32) NOT NULL COMMENT '主键ID',
+ `name` varchar(128) NOT NULL COMMENT '字典名称',
+ `dict_code` varchar(64) NOT NULL COMMENT '字典编码',
+ `status` tinyint NOT NULL DEFAULT 1 COMMENT '状态(1:启用;0:禁用)',
+ `remark` varchar(255) DEFAULT NULL COMMENT '备注',
+ `created_by` varchar(100) NOT NULL COMMENT '创建人',
+ `created_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
+ `updated_by` varchar(100) NOT NULL COMMENT '更新人',
+ `updated_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
+ PRIMARY KEY (`id`),
+ UNIQUE KEY `uk_dict_code` (`dict_code`),
+ KEY `idx_status` (`status`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='字典类型表';
+
+CREATE TABLE IF NOT EXISTS `base_sys_dict_item` (
+ `id` varchar(32) NOT NULL COMMENT '主键ID',
+ `dict_code` varchar(64) NOT NULL COMMENT '字典编码',
+ `label` varchar(128) NOT NULL COMMENT '字典项标签',
+ `value` varchar(128) NOT NULL COMMENT '字典项值',
+ `status` tinyint NOT NULL DEFAULT 1 COMMENT '状态(1:启用;0:禁用)',
+ `sort` int NOT NULL DEFAULT 1 COMMENT '排序',
+ `tag_type` varchar(16) NOT NULL DEFAULT 'N' COMMENT '标签类型(N/P/S/W/I/D)',
+ `created_by` varchar(100) NOT NULL COMMENT '创建人',
+ `created_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
+ `updated_by` varchar(100) NOT NULL COMMENT '更新人',
+ `updated_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
+ PRIMARY KEY (`id`),
+ UNIQUE KEY `uk_dict_value` (`dict_code`, `value`),
+ KEY `idx_dict_status_sort` (`dict_code`, `status`, `sort`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='字典项表';
diff --git a/src/main/resources/db/migrations/V20260714_01__create_rate_limit_scene_table.sql b/src/main/resources/db/migration/mysql/V20260714_01__create_rate_limit_scene_table.sql
similarity index 98%
rename from src/main/resources/db/migrations/V20260714_01__create_rate_limit_scene_table.sql
rename to src/main/resources/db/migration/mysql/V20260714_01__create_rate_limit_scene_table.sql
index 4dc2145..c8732d9 100644
--- a/src/main/resources/db/migrations/V20260714_01__create_rate_limit_scene_table.sql
+++ b/src/main/resources/db/migration/mysql/V20260714_01__create_rate_limit_scene_table.sql
@@ -1,4 +1,3 @@
-USE os_base_sysadmin;
SET NAMES utf8mb4;
CREATE TABLE IF NOT EXISTS `base_sys_ratelimit_scene` (
diff --git a/src/main/resources/db/migrations/V20260714_02__seed_captcha_rate_limit_scenes.sql b/src/main/resources/db/migration/mysql/V20260714_02__seed_captcha_rate_limit_scenes.sql
similarity index 98%
rename from src/main/resources/db/migrations/V20260714_02__seed_captcha_rate_limit_scenes.sql
rename to src/main/resources/db/migration/mysql/V20260714_02__seed_captcha_rate_limit_scenes.sql
index a0066d2..c3623aa 100644
--- a/src/main/resources/db/migrations/V20260714_02__seed_captcha_rate_limit_scenes.sql
+++ b/src/main/resources/db/migration/mysql/V20260714_02__seed_captcha_rate_limit_scenes.sql
@@ -1,4 +1,3 @@
-USE os_base_sysadmin;
SET NAMES utf8mb4;
INSERT INTO base_sys_ratelimit_scene
diff --git a/src/main/resources/db/migration/mysql/V20260715_01__create_internal_message_tables.sql b/src/main/resources/db/migration/mysql/V20260715_01__create_internal_message_tables.sql
new file mode 100644
index 0000000..b4a37f3
--- /dev/null
+++ b/src/main/resources/db/migration/mysql/V20260715_01__create_internal_message_tables.sql
@@ -0,0 +1,36 @@
+SET NAMES utf8mb4;
+
+-- 站内信:消息主体与收件人快照。发布时由应用将目标用户名固化为收件人记录。
+CREATE TABLE IF NOT EXISTS `base_sys_internal_message` (
+ `id` varchar(32) NOT NULL COMMENT '主键ID',
+ `kind` varchar(32) NOT NULL COMMENT '消息类型:ANNOUNCEMENT/NOTIFICATION',
+ `title` varchar(255) NOT NULL COMMENT '标题',
+ `content` text NOT NULL COMMENT '富文本内容',
+ `level` varchar(16) NOT NULL DEFAULT 'L' COMMENT '消息等级',
+ `target_scope` varchar(32) NOT NULL COMMENT '目标范围',
+ `target_usernames` text NOT NULL COMMENT '草稿目标用户名快照,逗号分隔',
+ `target_url` varchar(500) DEFAULT NULL COMMENT '站内跳转地址',
+ `status` varchar(32) NOT NULL COMMENT '状态:DRAFT/PUBLISHED/REVOKED',
+ `publish_time` datetime DEFAULT NULL COMMENT '发布时间',
+ `expire_time` datetime DEFAULT NULL COMMENT '到期时间',
+ `created_by` varchar(100) NOT NULL COMMENT '创建人',
+ `created_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
+ `updated_by` varchar(100) NOT NULL COMMENT '更新人',
+ `updated_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
+ PRIMARY KEY (`id`),
+ KEY `idx_status_publish_time` (`status`, `publish_time`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='站内信主体表';
+
+CREATE TABLE IF NOT EXISTS `base_sys_internal_message_recipient` (
+ `id` varchar(32) NOT NULL COMMENT '主键ID',
+ `message_id` varchar(32) NOT NULL COMMENT '站内信ID',
+ `username` varchar(100) NOT NULL COMMENT '接收用户名',
+ `read_time` datetime DEFAULT NULL COMMENT '阅读时间',
+ `created_by` varchar(100) NOT NULL COMMENT '创建人',
+ `created_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
+ `updated_by` varchar(100) NOT NULL COMMENT '更新人',
+ `updated_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
+ PRIMARY KEY (`id`),
+ UNIQUE KEY `uk_message_username` (`message_id`, `username`),
+ KEY `idx_username_read_time` (`username`, `read_time`, `created_time`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='站内信收件人表';
diff --git a/src/main/resources/db/migrations/V20260715_03__create_usage_counter_minute_table.sql b/src/main/resources/db/migration/mysql/V20260715_03__create_usage_counter_minute_table.sql
similarity index 100%
rename from src/main/resources/db/migrations/V20260715_03__create_usage_counter_minute_table.sql
rename to src/main/resources/db/migration/mysql/V20260715_03__create_usage_counter_minute_table.sql
diff --git a/src/main/resources/db/migrations/V20260720_01__create_usage_scene_table.sql b/src/main/resources/db/migration/mysql/V20260720_01__create_usage_scene_table.sql
similarity index 100%
rename from src/main/resources/db/migrations/V20260720_01__create_usage_scene_table.sql
rename to src/main/resources/db/migration/mysql/V20260720_01__create_usage_scene_table.sql
diff --git a/src/main/resources/db/migrations/V20260720_02__repair_usage_scene_name_encoding.sql b/src/main/resources/db/migration/mysql/V20260720_02__repair_usage_scene_name_encoding.sql
similarity index 100%
rename from src/main/resources/db/migrations/V20260720_02__repair_usage_scene_name_encoding.sql
rename to src/main/resources/db/migration/mysql/V20260720_02__repair_usage_scene_name_encoding.sql
diff --git a/src/main/resources/db/migrations/V20260720_03__create_error_catalog_table.sql b/src/main/resources/db/migration/mysql/V20260720_03__create_error_catalog_table.sql
similarity index 100%
rename from src/main/resources/db/migrations/V20260720_03__create_error_catalog_table.sql
rename to src/main/resources/db/migration/mysql/V20260720_03__create_error_catalog_table.sql
diff --git a/src/main/resources/db/migrations/V20260726_01__add_error_catalog_ownership.sql b/src/main/resources/db/migration/mysql/V20260726_01__add_error_catalog_ownership.sql
similarity index 100%
rename from src/main/resources/db/migrations/V20260726_01__add_error_catalog_ownership.sql
rename to src/main/resources/db/migration/mysql/V20260726_01__add_error_catalog_ownership.sql
diff --git a/src/main/resources/db/migrations/V20260729_01__add_dictionary_ownership.sql b/src/main/resources/db/migration/mysql/V20260729_01__add_dictionary_ownership.sql
similarity index 100%
rename from src/main/resources/db/migrations/V20260729_01__add_dictionary_ownership.sql
rename to src/main/resources/db/migration/mysql/V20260729_01__add_dictionary_ownership.sql
diff --git a/src/main/resources/db/migrations/V20260818_01__use_millisecond_precision_for_audit_timestamps.sql b/src/main/resources/db/migration/mysql/V20260818_01__use_millisecond_precision_for_audit_timestamps.sql
similarity index 99%
rename from src/main/resources/db/migrations/V20260818_01__use_millisecond_precision_for_audit_timestamps.sql
rename to src/main/resources/db/migration/mysql/V20260818_01__use_millisecond_precision_for_audit_timestamps.sql
index 2c58722..6215092 100644
--- a/src/main/resources/db/migrations/V20260818_01__use_millisecond_precision_for_audit_timestamps.sql
+++ b/src/main/resources/db/migration/mysql/V20260818_01__use_millisecond_precision_for_audit_timestamps.sql
@@ -1,4 +1,3 @@
-USE os_base_sysadmin;
SET NAMES utf8mb4;
-- 此版本迁移仅执行一次。DATETIME 不进行时区转换;历史记录已截断的毫秒无法恢复。
diff --git a/src/main/resources/db/migrations/V20260829_01__seed_iqc_rule_category_dictionary.sql b/src/main/resources/db/migration/mysql/V20260829_01__seed_iqc_rule_category_dictionary.sql
similarity index 51%
rename from src/main/resources/db/migrations/V20260829_01__seed_iqc_rule_category_dictionary.sql
rename to src/main/resources/db/migration/mysql/V20260829_01__seed_iqc_rule_category_dictionary.sql
index 9c29fb6..33b754f 100644
--- a/src/main/resources/db/migrations/V20260829_01__seed_iqc_rule_category_dictionary.sql
+++ b/src/main/resources/db/migration/mysql/V20260829_01__seed_iqc_rule_category_dictionary.sql
@@ -33,3 +33,25 @@ ON DUPLICATE KEY UPDATE
`sort` = VALUES(`sort`),
`tag_type` = VALUES(`tag_type`),
`updated_by` = 'system';
+
+INSERT INTO `base_sys_dict_type`
+ (`id`, `name`, `dict_code`, `source_application`, `status`, `remark`, `created_by`, `updated_by`)
+VALUES
+ ('DICT_IQC_CONVERSATION_CHANNEL', '会话渠道', 'iqc_conversation_channel', 'iqc-platform', 1, 'IQC 会话来源渠道(系统枚举)', 'system', 'system'),
+ ('DICT_IQC_BUSINESS_TYPE', '业务类型', 'iqc_business_type', NULL, 1, 'IQC 业务类型(管理员可维护)', 'system', 'system')
+ON DUPLICATE KEY UPDATE
+ `name` = VALUES(`name`), `status` = 1, `remark` = VALUES(`remark`), `updated_by` = 'system';
+
+INSERT INTO `base_sys_dict_item`
+ (`id`, `dict_code`, `label`, `value`, `status`, `sort`, `tag_type`, `created_by`, `updated_by`)
+VALUES
+ ('DICT_IQC_CHANNEL_WEB', 'iqc_conversation_channel', '网页', 'WEB', 1, 10, 'P', 'system', 'system'),
+ ('DICT_IQC_CHANNEL_PHONE', 'iqc_conversation_channel', '电话', 'PHONE', 1, 20, 'S', 'system', 'system'),
+ ('DICT_IQC_CHANNEL_WECHAT', 'iqc_conversation_channel', '微信', 'WECHAT', 1, 30, 'P', 'system', 'system'),
+ ('DICT_IQC_CHANNEL_APP', 'iqc_conversation_channel', 'App', 'APP', 1, 40, 'S', 'system', 'system'),
+ ('DICT_IQC_CHANNEL_OTHER', 'iqc_conversation_channel', '其他', 'OTHER', 1, 99, 'N', 'system', 'system'),
+ ('DICT_IQC_BUSINESS_ORDER', 'iqc_business_type', '订单', 'ORDER', 1, 10, 'P', 'system', 'system'),
+ ('DICT_IQC_BUSINESS_TICKET', 'iqc_business_type', '工单', 'TICKET', 1, 20, 'W', 'system', 'system'),
+ ('DICT_IQC_BUSINESS_COMPLAINT', 'iqc_business_type', '投诉', 'COMPLAINT', 1, 30, 'D', 'system', 'system')
+ON DUPLICATE KEY UPDATE
+ `label` = VALUES(`label`), `status` = 1, `sort` = VALUES(`sort`), `tag_type` = VALUES(`tag_type`), `updated_by` = 'system';
diff --git a/src/main/resources/db/migration/mysql/V20260829_02__seed_iqc_rate_limit_scenes.sql b/src/main/resources/db/migration/mysql/V20260829_02__seed_iqc_rate_limit_scenes.sql
new file mode 100644
index 0000000..37f3838
--- /dev/null
+++ b/src/main/resources/db/migration/mysql/V20260829_02__seed_iqc_rate_limit_scenes.sql
@@ -0,0 +1,51 @@
+SET NAMES utf8mb4;
+
+-- IQC controllers declare named rate-limit scenes. Sysadmin requires every named scene
+-- to exist and deliberately does not fall back to annotation values for unknown names.
+INSERT INTO base_sys_ratelimit_scene
+ (id, scene_code, scene_name, algorithm, dimensions, key_prefix, max_count, period, enabled, description, created_by, updated_by)
+VALUES
+ ('RL_IQC_CONV_IMPORT', 'iqc-conversation-import', 'IQC 单文件会话导入', 'COUNTER', 'IP', 'iqc-conversation-import', 30, 60, 1, '单文件 TXT 会话导入限次', 'system', 'system'),
+ ('RL_IQC_CONV_BATCH_IMPORT', 'iqc-conversation-batch-import', 'IQC 批量会话导入', 'COUNTER', 'IP', 'iqc-conversation-batch-import', 10, 60, 1, '批量 TXT 会话导入限次', 'system', 'system'),
+ ('RL_IQC_CONV_ZIP_IMPORT', 'iqc-conversation-zip-import', 'IQC ZIP 会话导入', 'COUNTER', 'IP', 'iqc-conversation-zip-import', 10, 60, 1, 'ZIP 会话导入限次', 'system', 'system'),
+ ('RL_IQC_CONV_INGEST', 'iqc-conversation-ingest', 'IQC 单会话接口接入', 'COUNTER', 'IP', 'iqc-conversation-ingest', 120, 60, 1, '单会话接口接入限次', 'system', 'system'),
+ ('RL_IQC_CONV_INGEST_BATCH', 'iqc-conversation-ingest-batch', 'IQC 批量会话接口接入', 'COUNTER', 'IP', 'iqc-conversation-ingest-batch', 30, 60, 1, '批量会话接口接入限次', 'system', 'system'),
+ ('RL_IQC_CONV_QUERY', 'iqc-conversation-query', 'IQC 会话查询', 'COUNTER', 'IP', 'iqc-conversation-query', 60, 60, 1, '会话列表和详情查询限次', 'system', 'system'),
+ ('RL_IQC_TASK_QUERY', 'iqc-task-query', 'IQC 任务查询', 'COUNTER', 'IP', 'iqc-task-query', 60, 60, 1, '质检任务查询限次', 'system', 'system'),
+ ('RL_IQC_TASK_CREATE', 'iqc-task-create', 'IQC 任务创建', 'COUNTER', 'IP', 'iqc-task-create', 20, 60, 1, '质检任务创建限次', 'system', 'system'),
+ ('RL_IQC_TASK_RUN', 'iqc-task-run', 'IQC 任务执行', 'COUNTER', 'IP', 'iqc-task-run', 10, 60, 1, '质检任务执行限次', 'system', 'system'),
+ ('RL_IQC_RESULT_QUERY', 'iqc-result-query', 'IQC 结果查询', 'COUNTER', 'IP', 'iqc-result-query', 60, 60, 1, '质检结果查询限次', 'system', 'system'),
+ ('RL_IQC_RESULT_EXPORT', 'iqc-result-export', 'IQC 结果导出', 'COUNTER', 'IP', 'iqc-result-export', 10, 60, 1, '质检结果导出限次', 'system', 'system'),
+ ('RL_IQC_RESULT_FEEDBACK', 'iqc-result-feedback', 'IQC 结果反馈', 'COUNTER', 'IP', 'iqc-result-feedback', 60, 60, 1, '质检结果人工标注限次', 'system', 'system'),
+ ('RL_IQC_BOOTSTRAP', 'iqc-bootstrap', 'IQC 基础状态查询', 'COUNTER', 'IP', 'iqc-bootstrap', 60, 60, 1, 'IQC 基础状态查询限次', 'system', 'system'),
+ ('RL_IQC_DASHBOARD_QUERY', 'iqc-dashboard-query', 'IQC 总览查询', 'COUNTER', 'IP', 'iqc-dashboard-query', 30, 60, 1, '质检总览查询限次', 'system', 'system'),
+ ('RL_IQC_TEMPLATE_QUERY', 'iqc-template-query', 'IQC 模板查询', 'COUNTER', 'IP', 'iqc-template-query', 60, 60, 1, '质检模板查询限次', 'system', 'system'),
+ ('RL_IQC_TEMPLATE_MATERIALIZE', 'iqc-template-materialize', 'IQC 模板实例化', 'COUNTER', 'IP', 'iqc-template-materialize', 10, 60, 1, '从模板创建规则限次', 'system', 'system'),
+ ('RL_IQC_SETTINGS_QUERY', 'iqc-settings-query', 'IQC 设置查询', 'COUNTER', 'IP', 'iqc-settings-query', 60, 60, 1, 'IQC 设置查询限次', 'system', 'system'),
+ ('RL_IQC_DICTIONARY_QUERY', 'iqc-dictionary-query', 'IQC 字典查询', 'COUNTER', 'IP', 'iqc-dictionary-query', 60, 60, 1, 'IQC 字典选项查询限次', 'system', 'system'),
+ ('RL_IQC_AGENT_CREATE', 'iqc-agent-create', 'IQC Agent 创建', 'COUNTER', 'IP', 'iqc-agent-create', 20, 60, 1, 'IQC Agent 创建限次', 'system', 'system'),
+ ('RL_IQC_RULE_CREATE', 'iqc-rule-create', 'IQC 规则创建', 'COUNTER', 'IP', 'iqc-rule-create', 30, 60, 1, 'IQC 规则创建限次', 'system', 'system'),
+ ('RL_IQC_RULE_TEST', 'iqc-rule-test', 'IQC 规则测试', 'COUNTER', 'IP', 'iqc-rule-test', 60, 60, 1, 'IQC 规则测试限次', 'system', 'system'),
+ ('RL_IQC_SKILL_CREATE', 'iqc-skill-create', 'IQC Skill 创建', 'COUNTER', 'IP', 'iqc-skill-create', 20, 60, 1, 'IQC Skill 创建限次', 'system', 'system')
+ON DUPLICATE KEY UPDATE scene_name = VALUES(scene_name),
+ algorithm = VALUES(algorithm),
+ dimensions = VALUES(dimensions),
+ key_prefix = VALUES(key_prefix),
+ max_count = VALUES(max_count),
+ period = VALUES(period),
+ enabled = VALUES(enabled),
+ description = VALUES(description),
+ updated_time = CURRENT_TIMESTAMP(3),
+ updated_by = VALUES(updated_by);
+INSERT INTO base_sys_usage_scene
+ (id, object_type, object_id, usage_event, scene_name, source_app, enabled, description, created_by, updated_by)
+SELECT REPLACE(UUID(), '-', ''), 'RATE_LIMIT_SCENE', scene_code, 'RATE_LIMIT_CHECK',
+ CONCAT(scene_name, '限次检查'), 'iqc-platform', enabled, description, 'system', 'system'
+FROM base_sys_ratelimit_scene
+WHERE scene_code LIKE 'iqc-%'
+ON DUPLICATE KEY UPDATE scene_name = VALUES(scene_name),
+ source_app = VALUES(source_app),
+ enabled = VALUES(enabled),
+ description = VALUES(description),
+ updated_time = CURRENT_TIMESTAMP(3),
+ updated_by = VALUES(updated_by);