feat(aihr): 重构运营后台与租户知识治理

- 重构运营总览、侧栏和内容运营工作台\n- 增加大喇叭、成果审核与运营统计链路\n- 补齐租户、知识空间、分类和知识维护闭环\n- 加固标签页租户上下文、停用租户写保护与迁移预检
This commit is contained in:
2026-07-23 01:24:22 +08:00
parent 333c365c6f
commit 163b08ecfe
94 changed files with 8610 additions and 1114 deletions
+23 -2
View File
@@ -35,6 +35,7 @@ CREATE TABLE IF NOT EXISTS `aihr_knowledge_attach` (
`id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键',
`tenant_id` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT '000000' COMMENT '租户编号',
`knowledge_id` bigint NOT NULL COMMENT '知识库ID',
`category_id` bigint DEFAULT NULL COMMENT '知识空间内分类ID',
`oss_id` bigint DEFAULT NULL COMMENT '对象存储ID',
`doc_id` varchar(32) DEFAULT NULL COMMENT '文档ID',
`name` varchar(500) DEFAULT NULL COMMENT '附件名称',
@@ -48,9 +49,27 @@ CREATE TABLE IF NOT EXISTS `aihr_knowledge_attach` (
`remark` varchar(500) DEFAULT NULL COMMENT '备注',
PRIMARY KEY (`id`),
UNIQUE KEY `uk_aihr_knowledge_attach_name` (`knowledge_id`, `name`),
KEY `idx_aihr_knowledge_attach_doc` (`doc_id`)
KEY `idx_aihr_knowledge_attach_doc` (`doc_id`),
KEY `idx_aihr_knowledge_attach_category` (`tenant_id`, `knowledge_id`, `category_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='AI HR 知识库附件';
CREATE TABLE IF NOT EXISTS `aihr_knowledge_category` (
`id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键',
`tenant_id` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '租户编号',
`knowledge_id` bigint NOT NULL COMMENT '知识空间ID',
`code` varchar(64) NOT NULL COMMENT '空间内稳定分类编码',
`name` varchar(100) NOT NULL COMMENT '分类名称',
`status` varchar(20) NOT NULL DEFAULT 'ACTIVE' COMMENT 'ACTIVE/DISABLED',
`sort_order` int NOT NULL DEFAULT 0 COMMENT '排序,越小越靠前',
`create_by` bigint DEFAULT NULL COMMENT '创建者',
`create_time` datetime DEFAULT NULL COMMENT '创建时间',
`update_by` bigint DEFAULT NULL COMMENT '更新者',
`update_time` datetime DEFAULT NULL COMMENT '更新时间',
PRIMARY KEY (`id`),
UNIQUE KEY `uk_aihr_knowledge_category_code` (`tenant_id`, `knowledge_id`, `code`),
KEY `idx_aihr_knowledge_category_space` (`tenant_id`, `knowledge_id`, `status`, `sort_order`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='知识空间内容分类';
CREATE TABLE IF NOT EXISTS `aihr_knowledge_fragment` (
`id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键',
`tenant_id` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT '000000' COMMENT '租户编号',
@@ -269,6 +288,7 @@ CREATE TABLE IF NOT EXISTS `aihr_knowledge_conversation` (
`user_id` bigint NOT NULL COMMENT '内部用户ID',
`conversation_id` varchar(64) NOT NULL COMMENT '客户端随机会话ID',
`project_code` varchar(100) DEFAULT NULL COMMENT '当前项目编码;多项目会话严格隔离',
`broadcast_message_id` bigint DEFAULT NULL COMMENT '绑定的公司消息ID;只存可信引用,不存消息正文',
`version` bigint NOT NULL DEFAULT 0 COMMENT '乐观并发版本',
`context_json` json DEFAULT NULL COMMENT '最近六轮脱敏短期上下文',
`expires_time` datetime NOT NULL COMMENT '不活跃过期时间',
@@ -276,7 +296,8 @@ CREATE TABLE IF NOT EXISTS `aihr_knowledge_conversation` (
`update_time` datetime NOT NULL COMMENT '更新时间',
PRIMARY KEY (`id`),
UNIQUE KEY `uk_aihr_knowledge_conversation` (`tenant_id`, `app_id`, `user_id`, `conversation_id`),
KEY `idx_aihr_knowledge_conversation_expire` (`tenant_id`, `expires_time`)
KEY `idx_aihr_knowledge_conversation_expire` (`tenant_id`, `expires_time`),
KEY `idx_aihr_knowledge_conversation_broadcast` (`tenant_id`, `broadcast_message_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='知识问答短期会话';
CREATE TABLE IF NOT EXISTS `aihr_knowledge_admin_audit` (
@@ -0,0 +1,183 @@
-- 银城大喇叭最小消息与阅读审计:仅租户公开已发布消息,不复用 sys_notice。
-- 可重复执行;不包含定向目标、附件、AI 摘要或问答上下文。
CREATE TABLE IF NOT EXISTS `aihr_broadcast_message` (
`id` bigint NOT NULL AUTO_INCREMENT COMMENT '消息ID',
`tenant_id` varchar(20) NOT NULL COMMENT '租户编号',
`title` varchar(200) NOT NULL COMMENT '消息标题',
`content` text NOT NULL COMMENT '消息正文',
`status` varchar(20) NOT NULL DEFAULT 'PUBLISHED' COMMENT 'PUBLISHED/WITHDRAWN',
`published_by` bigint NOT NULL COMMENT '发布人用户ID',
`published_time` datetime NOT NULL COMMENT '发布时间',
`publish_request_key` varchar(100) DEFAULT NULL COMMENT '发布幂等键',
`publish_request_hash` char(64) DEFAULT NULL COMMENT '发布载荷SHA-256',
`withdrawn_by` bigint DEFAULT NULL COMMENT '撤回人用户ID',
`withdrawn_time` datetime DEFAULT NULL COMMENT '撤回时间',
`withdraw_reason` varchar(500) DEFAULT NULL COMMENT '撤回原因',
`create_time` datetime NOT NULL COMMENT '创建时间',
`update_time` datetime NOT NULL COMMENT '更新时间',
PRIMARY KEY (`id`),
UNIQUE KEY `uk_aihr_broadcast_message_publish_request` (`tenant_id`, `published_by`, `publish_request_key`),
KEY `idx_aihr_broadcast_message_public` (`tenant_id`, `status`, `published_time`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='银城大喇叭租户公开消息';
-- 兼容已执行过 B0 建表的环境:历史消息没有请求键或撤回原因,保留 NULL。
SET @has_broadcast_publish_request_key := (
SELECT COUNT(*) FROM information_schema.COLUMNS
WHERE table_schema = DATABASE() AND table_name = 'aihr_broadcast_message'
AND column_name = 'publish_request_key'
);
SET @broadcast_ddl := IF(
@has_broadcast_publish_request_key = 0,
'ALTER TABLE aihr_broadcast_message ADD COLUMN publish_request_key varchar(100) DEFAULT NULL COMMENT ''发布幂等键'' AFTER published_time',
'SELECT 1'
);
PREPARE aihr_broadcast_stmt FROM @broadcast_ddl; EXECUTE aihr_broadcast_stmt; DEALLOCATE PREPARE aihr_broadcast_stmt;
SET @has_broadcast_publish_request_hash := (
SELECT COUNT(*) FROM information_schema.COLUMNS
WHERE table_schema = DATABASE() AND table_name = 'aihr_broadcast_message'
AND column_name = 'publish_request_hash'
);
SET @broadcast_ddl := IF(
@has_broadcast_publish_request_hash = 0,
'ALTER TABLE aihr_broadcast_message ADD COLUMN publish_request_hash char(64) DEFAULT NULL COMMENT ''发布载荷SHA-256'' AFTER publish_request_key',
'SELECT 1'
);
PREPARE aihr_broadcast_stmt FROM @broadcast_ddl; EXECUTE aihr_broadcast_stmt; DEALLOCATE PREPARE aihr_broadcast_stmt;
SET @has_broadcast_withdraw_reason := (
SELECT COUNT(*) FROM information_schema.COLUMNS
WHERE table_schema = DATABASE() AND table_name = 'aihr_broadcast_message'
AND column_name = 'withdraw_reason'
);
SET @broadcast_ddl := IF(
@has_broadcast_withdraw_reason = 0,
'ALTER TABLE aihr_broadcast_message ADD COLUMN withdraw_reason varchar(500) DEFAULT NULL COMMENT ''撤回原因'' AFTER withdrawn_time',
'SELECT 1'
);
PREPARE aihr_broadcast_stmt FROM @broadcast_ddl; EXECUTE aihr_broadcast_stmt; DEALLOCATE PREPARE aihr_broadcast_stmt;
SET @has_broadcast_publish_request_index := (
SELECT COUNT(*) FROM information_schema.STATISTICS
WHERE table_schema = DATABASE() AND table_name = 'aihr_broadcast_message'
AND index_name = 'uk_aihr_broadcast_message_publish_request'
);
SET @broadcast_publish_request_index_valid := (
SELECT COUNT(*) FROM (
SELECT index_name
FROM information_schema.STATISTICS
WHERE table_schema = DATABASE() AND table_name = 'aihr_broadcast_message'
AND index_name = 'uk_aihr_broadcast_message_publish_request'
GROUP BY index_name
HAVING MIN(non_unique) = 0
AND COUNT(*) = 3
AND SUM(sub_part IS NOT NULL) = 0
AND GROUP_CONCAT(column_name ORDER BY seq_in_index SEPARATOR ',') =
'tenant_id,published_by,publish_request_key'
) valid_broadcast_publish_request_index
);
SET @broadcast_ddl := CASE
WHEN @broadcast_publish_request_index_valid > 0 THEN 'SELECT 1'
WHEN @has_broadcast_publish_request_index > 0 THEN
'ALTER TABLE aihr_broadcast_message DROP INDEX uk_aihr_broadcast_message_publish_request, ADD UNIQUE KEY uk_aihr_broadcast_message_publish_request (tenant_id, published_by, publish_request_key)'
ELSE
'ALTER TABLE aihr_broadcast_message ADD UNIQUE KEY uk_aihr_broadcast_message_publish_request (tenant_id, published_by, publish_request_key)'
END;
PREPARE aihr_broadcast_stmt FROM @broadcast_ddl; EXECUTE aihr_broadcast_stmt; DEALLOCATE PREPARE aihr_broadcast_stmt;
-- 只追加首发快照;B1 没有修订入口,应用代码不会更新或删除版本记录。
CREATE TABLE IF NOT EXISTS `aihr_broadcast_version` (
`id` bigint NOT NULL AUTO_INCREMENT COMMENT '消息版本ID',
`tenant_id` varchar(20) NOT NULL COMMENT '租户编号',
`message_id` bigint NOT NULL COMMENT '消息ID',
`version` int NOT NULL COMMENT '消息版本,首版为1',
`title` varchar(200) NOT NULL COMMENT '消息标题快照',
`content` text NOT NULL COMMENT '消息正文快照',
`published_by` bigint NOT NULL COMMENT '发布人用户ID',
`published_time` datetime NOT NULL COMMENT '发布时间',
`create_time` datetime NOT NULL COMMENT '快照创建时间',
PRIMARY KEY (`id`),
UNIQUE KEY `uk_aihr_broadcast_version` (`tenant_id`, `message_id`, `version`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='银城大喇叭消息不可变版本快照';
CREATE TABLE IF NOT EXISTS `aihr_broadcast_read` (
`id` bigint NOT NULL AUTO_INCREMENT COMMENT '阅读记录ID',
`tenant_id` varchar(20) NOT NULL COMMENT '租户编号',
`message_id` bigint NOT NULL COMMENT '消息ID',
`user_id` bigint NOT NULL COMMENT '阅读用户ID',
`first_read_time` datetime NOT NULL COMMENT '首次阅读时间',
`create_time` datetime NOT NULL COMMENT '创建时间',
`update_time` datetime NOT NULL COMMENT '更新时间',
PRIMARY KEY (`id`),
UNIQUE KEY `uk_aihr_broadcast_read` (`tenant_id`, `message_id`, `user_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='银城大喇叭阅读审计';
-- 与租户事实表保持同一排序规则,兼容历史库 general_ci 与新 MySQL 8 库 0900_ai_ci。
SET @broadcast_target_collation := (
SELECT collation_name FROM information_schema.columns
WHERE table_schema = DATABASE() AND table_name = 'aihr_knowledge_info' AND column_name = 'tenant_id'
LIMIT 1
);
SET @broadcast_target_collation := COALESCE(@broadcast_target_collation, @@collation_database);
SET @broadcast_message_collation := (
SELECT table_collation FROM information_schema.tables
WHERE table_schema = DATABASE() AND table_name = 'aihr_broadcast_message'
LIMIT 1
);
SET @broadcast_ddl := IF(
@broadcast_message_collation = @broadcast_target_collation,
'SELECT 1',
CONCAT('ALTER TABLE aihr_broadcast_message CONVERT TO CHARACTER SET utf8mb4 COLLATE ', @broadcast_target_collation)
);
PREPARE aihr_broadcast_stmt FROM @broadcast_ddl; EXECUTE aihr_broadcast_stmt; DEALLOCATE PREPARE aihr_broadcast_stmt;
SET @broadcast_version_collation := (
SELECT table_collation FROM information_schema.tables
WHERE table_schema = DATABASE() AND table_name = 'aihr_broadcast_version'
LIMIT 1
);
SET @broadcast_ddl := IF(
@broadcast_version_collation = @broadcast_target_collation,
'SELECT 1',
CONCAT('ALTER TABLE aihr_broadcast_version CONVERT TO CHARACTER SET utf8mb4 COLLATE ', @broadcast_target_collation)
);
PREPARE aihr_broadcast_stmt FROM @broadcast_ddl; EXECUTE aihr_broadcast_stmt; DEALLOCATE PREPARE aihr_broadcast_stmt;
SET @broadcast_read_collation := (
SELECT table_collation FROM information_schema.tables
WHERE table_schema = DATABASE() AND table_name = 'aihr_broadcast_read'
LIMIT 1
);
SET @broadcast_ddl := IF(
@broadcast_read_collation = @broadcast_target_collation,
'SELECT 1',
CONCAT('ALTER TABLE aihr_broadcast_read CONVERT TO CHARACTER SET utf8mb4 COLLATE ', @broadcast_target_collation)
);
PREPARE aihr_broadcast_stmt FROM @broadcast_ddl; EXECUTE aihr_broadcast_stmt; DEALLOCATE PREPARE aihr_broadcast_stmt;
-- 兼容已执行过 B0 建表的环境:原有消息只补一次 v1 快照。
INSERT INTO aihr_broadcast_version (
tenant_id, message_id, `version`, title, content, published_by, published_time, create_time
)
SELECT m.tenant_id, m.id, 1, m.title, m.content, m.published_by, m.published_time, m.create_time
FROM aihr_broadcast_message m
WHERE NOT EXISTS (
SELECT 1
FROM aihr_broadcast_version v
WHERE v.tenant_id = m.tenant_id
AND v.message_id = m.id
AND v.`version` = 1
);
SET @broadcast_target_collation := NULL;
SET @broadcast_message_collation := NULL;
SET @broadcast_version_collation := NULL;
SET @broadcast_read_collation := NULL;
SET @has_broadcast_publish_request_key := NULL;
SET @has_broadcast_publish_request_hash := NULL;
SET @has_broadcast_withdraw_reason := NULL;
SET @has_broadcast_publish_request_index := NULL;
SET @broadcast_publish_request_index_valid := NULL;
SET @broadcast_ddl := NULL;
@@ -0,0 +1,38 @@
-- 银城大喇叭消息追问上下文:会话只绑定消息 ID,绝不保存正文到 context_json 或问答审计。
-- 可重复执行;消息有效性由应用层按当前员工、租户和 PUBLISHED 状态逐轮重新校验。
SET @has_broadcast_question_context_column := (
SELECT COUNT(*)
FROM information_schema.COLUMNS
WHERE table_schema = DATABASE()
AND table_name = 'aihr_knowledge_conversation'
AND column_name = 'broadcast_message_id'
);
SET @broadcast_question_context_ddl := IF(
@has_broadcast_question_context_column = 0,
'ALTER TABLE aihr_knowledge_conversation ADD COLUMN broadcast_message_id bigint DEFAULT NULL COMMENT ''绑定的公司消息ID;只存可信引用,不存消息正文'' AFTER project_code',
'SELECT 1'
);
PREPARE aihr_broadcast_question_context_stmt FROM @broadcast_question_context_ddl;
EXECUTE aihr_broadcast_question_context_stmt;
DEALLOCATE PREPARE aihr_broadcast_question_context_stmt;
SET @has_broadcast_question_context_index := (
SELECT COUNT(*)
FROM information_schema.STATISTICS
WHERE table_schema = DATABASE()
AND table_name = 'aihr_knowledge_conversation'
AND index_name = 'idx_aihr_knowledge_conversation_broadcast'
);
SET @broadcast_question_context_ddl := IF(
@has_broadcast_question_context_index = 0,
'ALTER TABLE aihr_knowledge_conversation ADD KEY idx_aihr_knowledge_conversation_broadcast (tenant_id, broadcast_message_id)',
'SELECT 1'
);
PREPARE aihr_broadcast_question_context_stmt FROM @broadcast_question_context_ddl;
EXECUTE aihr_broadcast_question_context_stmt;
DEALLOCATE PREPARE aihr_broadcast_question_context_stmt;
SET @has_broadcast_question_context_column := NULL;
SET @has_broadcast_question_context_index := NULL;
SET @broadcast_question_context_ddl := NULL;
@@ -0,0 +1,80 @@
-- AIHR 知识空间内分类迁移(MySQL 8.x)。
-- 分类是租户 + 知识空间内的内容目录,不扩大空间授权或调用应用的检索范围;可重复执行。
CREATE TABLE IF NOT EXISTS `aihr_knowledge_category` (
`id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键',
`tenant_id` varchar(20) NOT NULL COMMENT '租户编号',
`knowledge_id` bigint NOT NULL COMMENT '知识空间ID',
`code` varchar(64) NOT NULL COMMENT '空间内稳定分类编码',
`name` varchar(100) NOT NULL COMMENT '分类名称',
`status` varchar(20) NOT NULL DEFAULT 'ACTIVE' COMMENT 'ACTIVE/DISABLED',
`sort_order` int NOT NULL DEFAULT 0 COMMENT '排序,越小越靠前',
`create_by` bigint DEFAULT NULL COMMENT '创建者',
`create_time` datetime DEFAULT NULL COMMENT '创建时间',
`update_by` bigint DEFAULT NULL COMMENT '更新者',
`update_time` datetime DEFAULT NULL COMMENT '更新时间',
PRIMARY KEY (`id`),
UNIQUE KEY `uk_aihr_knowledge_category_code` (`tenant_id`, `knowledge_id`, `code`),
KEY `idx_aihr_knowledge_category_space` (`tenant_id`, `knowledge_id`, `status`, `sort_order`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='知识空间内容分类';
SET @aihr_attach_table_exists := (
SELECT COUNT(*) FROM information_schema.TABLES
WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'aihr_knowledge_attach'
);
SET @aihr_attach_category_exists := (
SELECT COUNT(*) FROM information_schema.COLUMNS
WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'aihr_knowledge_attach' AND COLUMN_NAME = 'category_id'
);
SET @aihr_attach_category_ddl := IF(
@aihr_attach_table_exists = 1 AND @aihr_attach_category_exists = 0,
'ALTER TABLE `aihr_knowledge_attach` ADD COLUMN `category_id` bigint DEFAULT NULL COMMENT ''知识空间内分类ID'' AFTER `knowledge_id`',
'SELECT 1'
);
PREPARE aihr_attach_category_stmt FROM @aihr_attach_category_ddl;
EXECUTE aihr_attach_category_stmt;
DEALLOCATE PREPARE aihr_attach_category_stmt;
SET @aihr_attach_category_index_exists := (
SELECT COUNT(*) FROM information_schema.STATISTICS
WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'aihr_knowledge_attach'
AND INDEX_NAME = 'idx_aihr_knowledge_attach_category'
);
SET @aihr_attach_category_index_ddl := IF(
@aihr_attach_table_exists = 1 AND @aihr_attach_category_index_exists = 0,
'ALTER TABLE `aihr_knowledge_attach` ADD KEY `idx_aihr_knowledge_attach_category` (`tenant_id`, `knowledge_id`, `category_id`)',
'SELECT 1'
);
PREPARE aihr_attach_category_index_stmt FROM @aihr_attach_category_index_ddl;
EXECUTE aihr_attach_category_index_stmt;
DEALLOCATE PREPARE aihr_attach_category_index_stmt;
-- 与知识空间 tenant_id 的实际排序规则对齐,兼容历史库 general_ci 与新 MySQL 8 库 0900_ai_ci。
SET @aihr_category_target_collation := (
SELECT collation_name FROM information_schema.COLUMNS
WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'aihr_knowledge_info' AND COLUMN_NAME = 'tenant_id'
LIMIT 1
);
SET @aihr_category_target_collation := COALESCE(@aihr_category_target_collation, @@collation_database);
SET @aihr_category_table_collation := (
SELECT table_collation FROM information_schema.TABLES
WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'aihr_knowledge_category'
LIMIT 1
);
SET @aihr_category_collation_ddl := IF(
@aihr_category_table_collation = @aihr_category_target_collation,
'SELECT 1',
CONCAT('ALTER TABLE `aihr_knowledge_category` CONVERT TO CHARACTER SET utf8mb4 COLLATE ', @aihr_category_target_collation)
);
PREPARE aihr_category_collation_stmt FROM @aihr_category_collation_ddl;
EXECUTE aihr_category_collation_stmt;
DEALLOCATE PREPARE aihr_category_collation_stmt;
SET @aihr_attach_table_exists := NULL;
SET @aihr_attach_category_exists := NULL;
SET @aihr_attach_category_ddl := NULL;
SET @aihr_attach_category_index_exists := NULL;
SET @aihr_attach_category_index_ddl := NULL;
SET @aihr_category_target_collation := NULL;
SET @aihr_category_table_collation := NULL;
SET @aihr_category_collation_ddl := NULL;