diff --git a/backend/ruoyi-modules/ruoyi-aihr/src/test/java/org/dromara/aihr/service/AihrKnowledgePlatformSchemaTest.java b/backend/ruoyi-modules/ruoyi-aihr/src/test/java/org/dromara/aihr/service/AihrKnowledgePlatformSchemaTest.java new file mode 100644 index 00000000..0c7cad70 --- /dev/null +++ b/backend/ruoyi-modules/ruoyi-aihr/src/test/java/org/dromara/aihr/service/AihrKnowledgePlatformSchemaTest.java @@ -0,0 +1,44 @@ +package org.dromara.aihr.service; + +import org.junit.jupiter.api.Tag; +import org.junit.jupiter.api.Test; + +import java.nio.file.Files; +import java.nio.file.Path; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +@Tag("dev") +class AihrKnowledgePlatformSchemaTest { + + @Test + void schemaDefinesSpacesAppsGrantsAndAuditWithoutPlainTokens() throws Exception { + String sql = Files.readString(repoRoot().resolve("backend/script/sql/aihr_knowledge_mysql8.sql")); + String migration = Files.readString(repoRoot().resolve( + "backend/script/sql/update/aihr_20260716_knowledge_space_platform_mysql8.sql")); + String schema = sql + migration; + + assertTrue(schema.contains("`code` varchar(100)")); + assertTrue(schema.contains("uk_aihr_knowledge_info_code")); + assertTrue(schema.contains("aihr_knowledge_space_grant")); + assertTrue(schema.contains("aihr_knowledge_app")); + assertTrue(schema.contains("aihr_knowledge_app_space")); + assertTrue(schema.contains("aihr_knowledge_query_log")); + assertTrue(schema.contains("space_codes_json")); + assertTrue(schema.contains("token_hash")); + assertFalse(schema.contains("token_plain")); + assertFalse(schema.contains("aihr_personal_")); + } + + private static Path repoRoot() { + Path current = Path.of("").toAbsolutePath(); + while (current != null && !Files.exists(current.resolve("backend/script/sql"))) { + current = current.getParent(); + } + if (current == null) { + throw new IllegalStateException("repository root not found"); + } + return current; + } +} diff --git a/backend/script/sql/aihr_knowledge_mysql8.sql b/backend/script/sql/aihr_knowledge_mysql8.sql index 4dfa9748..4634bfc0 100644 --- a/backend/script/sql/aihr_knowledge_mysql8.sql +++ b/backend/script/sql/aihr_knowledge_mysql8.sql @@ -5,6 +5,10 @@ CREATE TABLE IF NOT EXISTS `aihr_knowledge_info` ( `id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键', `tenant_id` varchar(20) DEFAULT '000000' COMMENT '租户编号', `name` varchar(100) NOT NULL COMMENT '知识库名称', + `code` varchar(100) NOT NULL COMMENT '租户内稳定空间编码', + `space_type` varchar(30) NOT NULL DEFAULT 'BUSINESS' COMMENT 'PUBLIC/BUSINESS/MANAGEMENT/EXTERNAL', + `sensitivity_level` varchar(30) NOT NULL DEFAULT 'INTERNAL' COMMENT 'PUBLIC/INTERNAL/CONFIDENTIAL', + `status` varchar(20) NOT NULL DEFAULT 'ACTIVE' COMMENT 'DRAFT/ACTIVE/DISABLED', `description` varchar(1000) DEFAULT NULL COMMENT '知识库描述', `separator` varchar(255) DEFAULT NULL COMMENT '知识分隔符', `overlap_char` int DEFAULT 120 COMMENT '重叠字符数', @@ -23,6 +27,7 @@ CREATE TABLE IF NOT EXISTS `aihr_knowledge_info` ( `update_time` datetime DEFAULT NULL COMMENT '更新时间', `remark` varchar(500) DEFAULT NULL COMMENT '备注', PRIMARY KEY (`id`), + UNIQUE KEY `uk_aihr_knowledge_info_code` (`tenant_id`, `code`), KEY `idx_aihr_knowledge_info_tenant` (`tenant_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='AI HR 知识库'; @@ -89,13 +94,13 @@ CREATE TABLE IF NOT EXISTS `aihr_knowledge_import_task` ( ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='AI HR 知识库导入任务'; INSERT INTO `aihr_knowledge_info` - (`id`, `tenant_id`, `name`, `description`, `separator`, `overlap_char`, `retrieve_limit`, `similarity_threshold`, `text_block_size`, `vector_model`, `embedding_model`, `enable_hybrid`, `hybrid_alpha`, `system_prompt`, `create_time`, `update_time`, `remark`) + (`id`, `tenant_id`, `name`, `code`, `description`, `separator`, `overlap_char`, `retrieve_limit`, `similarity_threshold`, `text_block_size`, `vector_model`, `embedding_model`, `enable_hybrid`, `hybrid_alpha`, `system_prompt`, `create_time`, `update_time`, `remark`) VALUES - (1001, '000000', '投诉处理 SOP', '业主投诉的接待、记录、派单、反馈和闭环确认。', '\n\n', 120, 5, 0.5, 800, NULL, NULL, 1, 1.0, '回答必须引用投诉处理 SOP 片段,不得编造未命中的金额、责任和时限。', NOW(), NOW(), 'seed:sop'), - (1002, '000000', '催缴沟通 SOP', '缴费提醒、费用争议回应、服务问题承接。', '\n\n', 120, 5, 0.5, 800, NULL, NULL, 1, 1.0, '回答必须区分服务整改与费用义务。', NOW(), NOW(), 'seed:sop'), - (1003, '000000', '报修跟进 SOP', '报修接单、派工、现场反馈、复核关闭。', '\n\n', 120, 5, 0.5, 800, NULL, NULL, 1, 1.0, '回答必须给出明确节点和责任人。', NOW(), NOW(), 'seed:sop'), - (1201, '000000', '增值服务需求识别 SOP', '基于业主真实需求介绍增值服务,明确适用范围、价格和取消规则。', '\n\n', 120, 5, 0.5, 800, NULL, NULL, 1, 1.0, '只能基于已确认的服务目录和价目口径回答,不得编造优惠或效果承诺。', NOW(), NOW(), 'seed:sop:brd-supplement'), - (1202, '000000', '日常服务回访 SOP', '服务完成后的满意度确认、遗留问题记录、责任人和回访节点。', '\n\n', 120, 5, 0.5, 800, NULL, NULL, 1, 1.0, '已完成不等于已闭环,回答必须包含满意度确认、责任人和下一次回访节点。', NOW(), NOW(), 'seed:sop:brd-supplement') + (1001, '000000', '投诉处理 SOP', 'legacy_1001', '业主投诉的接待、记录、派单、反馈和闭环确认。', '\n\n', 120, 5, 0.5, 800, NULL, NULL, 1, 1.0, '回答必须引用投诉处理 SOP 片段,不得编造未命中的金额、责任和时限。', NOW(), NOW(), 'seed:sop'), + (1002, '000000', '催缴沟通 SOP', 'legacy_1002', '缴费提醒、费用争议回应、服务问题承接。', '\n\n', 120, 5, 0.5, 800, NULL, NULL, 1, 1.0, '回答必须区分服务整改与费用义务。', NOW(), NOW(), 'seed:sop'), + (1003, '000000', '报修跟进 SOP', 'legacy_1003', '报修接单、派工、现场反馈、复核关闭。', '\n\n', 120, 5, 0.5, 800, NULL, NULL, 1, 1.0, '回答必须给出明确节点和责任人。', NOW(), NOW(), 'seed:sop'), + (1201, '000000', '增值服务需求识别 SOP', 'legacy_1201', '基于业主真实需求介绍增值服务,明确适用范围、价格和取消规则。', '\n\n', 120, 5, 0.5, 800, NULL, NULL, 1, 1.0, '只能基于已确认的服务目录和价目口径回答,不得编造优惠或效果承诺。', NOW(), NOW(), 'seed:sop:brd-supplement'), + (1202, '000000', '日常服务回访 SOP', 'legacy_1202', '服务完成后的满意度确认、遗留问题记录、责任人和回访节点。', '\n\n', 120, 5, 0.5, 800, NULL, NULL, 1, 1.0, '已完成不等于已闭环,回答必须包含满意度确认、责任人和下一次回访节点。', NOW(), NOW(), 'seed:sop:brd-supplement') ON DUPLICATE KEY UPDATE `description` = VALUES(`description`), `system_prompt` = VALUES(`system_prompt`), @@ -142,6 +147,7 @@ CREATE TABLE IF NOT EXISTS `aihr_knowledge_upload_item` ( `batch_id` varchar(64) NOT NULL COMMENT '上传批次', `file_name` varchar(255) NOT NULL COMMENT '文件名', `category` varchar(100) DEFAULT '' COMMENT '目标分类', + `space_codes_json` json DEFAULT NULL COMMENT '目标知识空间编码数组', `staging_path` varchar(500) NOT NULL COMMENT '暂存文件路径', `status` tinyint NOT NULL DEFAULT 0 COMMENT '0待处理 1处理中 2完成 3失败', `error` varchar(500) DEFAULT NULL COMMENT '失败原因', @@ -153,3 +159,74 @@ CREATE TABLE IF NOT EXISTS `aihr_knowledge_upload_item` ( KEY `idx_aihr_upload_item_batch` (`tenant_id`, `batch_id`, `id`), KEY `idx_aihr_upload_item_status` (`tenant_id`, `status`, `update_time`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='知识库批量上传队列'; + +CREATE TABLE IF NOT EXISTS `aihr_knowledge_space_grant` ( + `id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键', + `tenant_id` varchar(20) NOT NULL COMMENT '租户编号', + `knowledge_id` bigint NOT NULL COMMENT '知识空间ID', + `principal_type` varchar(20) NOT NULL COMMENT 'ROLE/USER', + `principal_value` varchar(100) NOT NULL COMMENT '角色编码或用户ID', + `permission` varchar(20) NOT NULL COMMENT 'READ/MANAGE', + `status` varchar(20) NOT NULL DEFAULT 'ACTIVE' COMMENT 'ACTIVE/DISABLED', + `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_space_grant` (`tenant_id`, `knowledge_id`, `principal_type`, `principal_value`, `permission`), + KEY `idx_aihr_space_grant_principal` (`tenant_id`, `principal_type`, `principal_value`, `status`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='知识空间主体授权'; + +CREATE TABLE IF NOT EXISTS `aihr_knowledge_app` ( + `id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键', + `tenant_id` varchar(20) NOT NULL COMMENT '租户编号', + `app_code` varchar(64) NOT NULL COMMENT '调用应用稳定编码', + `app_name` varchar(100) NOT NULL COMMENT '调用应用名称', + `auth_type` varchar(20) NOT NULL COMMENT 'SESSION/API_TOKEN', + `internal_client_key` varchar(64) DEFAULT NULL COMMENT '内部登录客户端key', + `token_hash` varchar(64) DEFAULT NULL COMMENT '外部令牌SHA-256', + `status` varchar(20) NOT NULL DEFAULT 'ACTIVE' COMMENT 'ACTIVE/DISABLED', + `rate_limit_per_minute` int NOT NULL DEFAULT 60 COMMENT '每分钟请求上限', + `expires_time` datetime DEFAULT NULL COMMENT '令牌到期时间', + `last_used_time` datetime DEFAULT NULL 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_app_code` (`app_code`), + UNIQUE KEY `uk_aihr_knowledge_app_client` (`tenant_id`, `auth_type`, `internal_client_key`), + KEY `idx_aihr_knowledge_app_tenant_status` (`tenant_id`, `status`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='知识问答调用应用'; + +CREATE TABLE IF NOT EXISTS `aihr_knowledge_app_space` ( + `id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键', + `tenant_id` varchar(20) NOT NULL COMMENT '租户编号', + `app_id` bigint NOT NULL COMMENT '调用应用ID', + `knowledge_id` bigint NOT NULL COMMENT '知识空间ID', + `create_by` bigint DEFAULT NULL COMMENT '创建者', + `create_time` datetime DEFAULT NULL COMMENT '创建时间', + PRIMARY KEY (`id`), + UNIQUE KEY `uk_aihr_app_space` (`tenant_id`, `app_id`, `knowledge_id`), + KEY `idx_aihr_app_space_knowledge` (`tenant_id`, `knowledge_id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='调用应用与知识空间授权'; + +CREATE TABLE IF NOT EXISTS `aihr_knowledge_query_log` ( + `id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键', + `request_id` varchar(64) NOT NULL COMMENT '请求追踪ID', + `tenant_id` varchar(20) NOT NULL COMMENT '租户编号', + `app_id` bigint NOT NULL COMMENT '调用应用ID', + `user_id` bigint DEFAULT NULL COMMENT '内部用户ID', + `question_hash` varchar(64) NOT NULL COMMENT '问题SHA-256', + `used_space_codes_json` json DEFAULT NULL COMMENT '实际使用空间编码', + `source_types_json` json DEFAULT NULL COMMENT '来源类型', + `status` varchar(30) NOT NULL COMMENT 'SUCCESS/NO_EVIDENCE/REJECTED/FAILED', + `latency_ms` bigint NOT NULL DEFAULT 0 COMMENT '处理耗时毫秒', + `model_name` varchar(100) DEFAULT NULL COMMENT '生成模型', + `prompt_version` varchar(100) DEFAULT NULL COMMENT '提示词版本', + `create_time` datetime NOT NULL COMMENT '创建时间', + PRIMARY KEY (`id`), + UNIQUE KEY `uk_aihr_query_request` (`request_id`), + KEY `idx_aihr_query_tenant_app` (`tenant_id`, `app_id`, `create_time`), + KEY `idx_aihr_query_status` (`tenant_id`, `status`, `create_time`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='知识问答最小化审计日志'; diff --git a/backend/script/sql/update/aihr_20260716_knowledge_space_platform_mysql8.sql b/backend/script/sql/update/aihr_20260716_knowledge_space_platform_mysql8.sql new file mode 100644 index 00000000..a2a4590a --- /dev/null +++ b/backend/script/sql/update/aihr_20260716_knowledge_space_platform_mysql8.sql @@ -0,0 +1,154 @@ +-- AIHR 多租户多知识空间统一问答平台迁移(MySQL 8.x)。 +-- 只补结构和 legacy 编码,不移动既有附件、片段或 OSS 数据;可重复执行。 + +SET @aihr_space_code_exists := ( + SELECT COUNT(*) FROM information_schema.COLUMNS + WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'aihr_knowledge_info' AND COLUMN_NAME = 'code' +); +SET @aihr_space_code_ddl := IF( + @aihr_space_code_exists = 0, + 'ALTER TABLE aihr_knowledge_info ADD COLUMN `code` varchar(100) DEFAULT NULL COMMENT ''租户内稳定空间编码'' AFTER `name`', + 'SELECT 1' +); +PREPARE aihr_space_code_stmt FROM @aihr_space_code_ddl; +EXECUTE aihr_space_code_stmt; +DEALLOCATE PREPARE aihr_space_code_stmt; + +UPDATE aihr_knowledge_info SET code = CONCAT('legacy_', id) WHERE code IS NULL OR code = ''; +ALTER TABLE aihr_knowledge_info MODIFY COLUMN `code` varchar(100) NOT NULL COMMENT '租户内稳定空间编码'; + +SET @aihr_space_type_exists := ( + SELECT COUNT(*) FROM information_schema.COLUMNS + WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'aihr_knowledge_info' AND COLUMN_NAME = 'space_type' +); +SET @aihr_space_type_ddl := IF( + @aihr_space_type_exists = 0, + 'ALTER TABLE aihr_knowledge_info ADD COLUMN `space_type` varchar(30) NOT NULL DEFAULT ''BUSINESS'' COMMENT ''PUBLIC/BUSINESS/MANAGEMENT/EXTERNAL'' AFTER `code`', + 'SELECT 1' +); +PREPARE aihr_space_type_stmt FROM @aihr_space_type_ddl; +EXECUTE aihr_space_type_stmt; +DEALLOCATE PREPARE aihr_space_type_stmt; + +SET @aihr_space_sensitivity_exists := ( + SELECT COUNT(*) FROM information_schema.COLUMNS + WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'aihr_knowledge_info' AND COLUMN_NAME = 'sensitivity_level' +); +SET @aihr_space_sensitivity_ddl := IF( + @aihr_space_sensitivity_exists = 0, + 'ALTER TABLE aihr_knowledge_info ADD COLUMN `sensitivity_level` varchar(30) NOT NULL DEFAULT ''INTERNAL'' COMMENT ''PUBLIC/INTERNAL/CONFIDENTIAL'' AFTER `space_type`', + 'SELECT 1' +); +PREPARE aihr_space_sensitivity_stmt FROM @aihr_space_sensitivity_ddl; +EXECUTE aihr_space_sensitivity_stmt; +DEALLOCATE PREPARE aihr_space_sensitivity_stmt; + +SET @aihr_space_status_exists := ( + SELECT COUNT(*) FROM information_schema.COLUMNS + WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'aihr_knowledge_info' AND COLUMN_NAME = 'status' +); +SET @aihr_space_status_ddl := IF( + @aihr_space_status_exists = 0, + 'ALTER TABLE aihr_knowledge_info ADD COLUMN `status` varchar(20) NOT NULL DEFAULT ''ACTIVE'' COMMENT ''DRAFT/ACTIVE/DISABLED'' AFTER `sensitivity_level`', + 'SELECT 1' +); +PREPARE aihr_space_status_stmt FROM @aihr_space_status_ddl; +EXECUTE aihr_space_status_stmt; +DEALLOCATE PREPARE aihr_space_status_stmt; + +SET @aihr_space_code_index_exists := ( + SELECT COUNT(*) FROM information_schema.STATISTICS + WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'aihr_knowledge_info' AND INDEX_NAME = 'uk_aihr_knowledge_info_code' +); +SET @aihr_space_code_index_ddl := IF( + @aihr_space_code_index_exists = 0, + 'ALTER TABLE aihr_knowledge_info ADD UNIQUE KEY `uk_aihr_knowledge_info_code` (`tenant_id`, `code`)', + 'SELECT 1' +); +PREPARE aihr_space_code_index_stmt FROM @aihr_space_code_index_ddl; +EXECUTE aihr_space_code_index_stmt; +DEALLOCATE PREPARE aihr_space_code_index_stmt; + +SET @aihr_upload_spaces_exists := ( + SELECT COUNT(*) FROM information_schema.COLUMNS + WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'aihr_knowledge_upload_item' AND COLUMN_NAME = 'space_codes_json' +); +SET @aihr_upload_spaces_ddl := IF( + @aihr_upload_spaces_exists = 0, + 'ALTER TABLE aihr_knowledge_upload_item ADD COLUMN `space_codes_json` json DEFAULT NULL COMMENT ''目标知识空间编码数组'' AFTER `category`', + 'SELECT 1' +); +PREPARE aihr_upload_spaces_stmt FROM @aihr_upload_spaces_ddl; +EXECUTE aihr_upload_spaces_stmt; +DEALLOCATE PREPARE aihr_upload_spaces_stmt; + +CREATE TABLE IF NOT EXISTS `aihr_knowledge_space_grant` ( + `id` bigint NOT NULL AUTO_INCREMENT, + `tenant_id` varchar(20) NOT NULL, + `knowledge_id` bigint NOT NULL, + `principal_type` varchar(20) NOT NULL, + `principal_value` varchar(100) NOT NULL, + `permission` varchar(20) NOT NULL, + `status` varchar(20) NOT NULL DEFAULT 'ACTIVE', + `create_by` bigint DEFAULT NULL, + `create_time` datetime DEFAULT NULL, + `update_by` bigint DEFAULT NULL, + `update_time` datetime DEFAULT NULL, + PRIMARY KEY (`id`), + UNIQUE KEY `uk_aihr_space_grant` (`tenant_id`, `knowledge_id`, `principal_type`, `principal_value`, `permission`), + KEY `idx_aihr_space_grant_principal` (`tenant_id`, `principal_type`, `principal_value`, `status`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='知识空间主体授权'; + +CREATE TABLE IF NOT EXISTS `aihr_knowledge_app` ( + `id` bigint NOT NULL AUTO_INCREMENT, + `tenant_id` varchar(20) NOT NULL, + `app_code` varchar(64) NOT NULL, + `app_name` varchar(100) NOT NULL, + `auth_type` varchar(20) NOT NULL, + `internal_client_key` varchar(64) DEFAULT NULL, + `token_hash` varchar(64) DEFAULT NULL, + `status` varchar(20) NOT NULL DEFAULT 'ACTIVE', + `rate_limit_per_minute` int NOT NULL DEFAULT 60, + `expires_time` datetime DEFAULT NULL, + `last_used_time` datetime DEFAULT NULL, + `create_by` bigint DEFAULT NULL, + `create_time` datetime DEFAULT NULL, + `update_by` bigint DEFAULT NULL, + `update_time` datetime DEFAULT NULL, + PRIMARY KEY (`id`), + UNIQUE KEY `uk_aihr_knowledge_app_code` (`app_code`), + UNIQUE KEY `uk_aihr_knowledge_app_client` (`tenant_id`, `auth_type`, `internal_client_key`), + KEY `idx_aihr_knowledge_app_tenant_status` (`tenant_id`, `status`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='知识问答调用应用'; + +CREATE TABLE IF NOT EXISTS `aihr_knowledge_app_space` ( + `id` bigint NOT NULL AUTO_INCREMENT, + `tenant_id` varchar(20) NOT NULL, + `app_id` bigint NOT NULL, + `knowledge_id` bigint NOT NULL, + `create_by` bigint DEFAULT NULL, + `create_time` datetime DEFAULT NULL, + PRIMARY KEY (`id`), + UNIQUE KEY `uk_aihr_app_space` (`tenant_id`, `app_id`, `knowledge_id`), + KEY `idx_aihr_app_space_knowledge` (`tenant_id`, `knowledge_id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='调用应用与知识空间授权'; + +CREATE TABLE IF NOT EXISTS `aihr_knowledge_query_log` ( + `id` bigint NOT NULL AUTO_INCREMENT, + `request_id` varchar(64) NOT NULL, + `tenant_id` varchar(20) NOT NULL, + `app_id` bigint NOT NULL, + `user_id` bigint DEFAULT NULL, + `question_hash` varchar(64) NOT NULL, + `used_space_codes_json` json DEFAULT NULL, + `source_types_json` json DEFAULT NULL, + `status` varchar(30) NOT NULL, + `latency_ms` bigint NOT NULL DEFAULT 0, + `model_name` varchar(100) DEFAULT NULL, + `prompt_version` varchar(100) DEFAULT NULL, + `create_time` datetime NOT NULL, + PRIMARY KEY (`id`), + UNIQUE KEY `uk_aihr_query_request` (`request_id`), + KEY `idx_aihr_query_tenant_app` (`tenant_id`, `app_id`, `create_time`), + KEY `idx_aihr_query_status` (`tenant_id`, `status`, `create_time`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='知识问答最小化审计日志';