feat(personal): add isolated personal knowledge schema
This commit is contained in:
+50
@@ -0,0 +1,50 @@
|
||||
package org.dromara.aihr.personal;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.Tag;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.Locale;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
@Tag("dev")
|
||||
class PersonalSchemaContractTest {
|
||||
|
||||
private static final String SCHEMA_FILE = "aihr_personal_knowledge_mysql8.sql";
|
||||
|
||||
@Test
|
||||
void personalKnowledgeSchemaIsOwnerScopedAndIndependent() throws IOException {
|
||||
Path sqlPath = locateSqlDirectory().resolve(SCHEMA_FILE);
|
||||
assertTrue(Files.isRegularFile(sqlPath), "Missing personal knowledge schema: " + sqlPath);
|
||||
|
||||
String sql = Files.readString(sqlPath).toLowerCase(Locale.ROOT);
|
||||
assertTrue(sql.contains("create table if not exists `aihr_personal_space`"));
|
||||
assertTrue(sql.contains("create table if not exists `aihr_personal_item`"));
|
||||
assertTrue(sql.contains("create table if not exists `aihr_personal_fragment`"));
|
||||
assertTrue(sql.contains("create table if not exists `aihr_personal_chat_session`"));
|
||||
assertTrue(sql.contains("create table if not exists `aihr_personal_chat_message`"));
|
||||
assertTrue(sql.contains("`owner_user_id`"), "Personal records must be owner scoped");
|
||||
assertTrue(sql.contains("fulltext key"), "Personal fragments must provide full-text search");
|
||||
assertFalse(sql.contains("alter table aihr_knowledge_fragment"),
|
||||
"Personal schema must not mutate enterprise knowledge tables");
|
||||
assertFalse(sql.contains("alter table `aihr_knowledge_fragment`"),
|
||||
"Personal schema must not mutate enterprise knowledge tables");
|
||||
}
|
||||
|
||||
private Path locateSqlDirectory() {
|
||||
Path current = Path.of("").toAbsolutePath().normalize();
|
||||
while (current != null) {
|
||||
Path candidate = current.resolve("backend/script/sql");
|
||||
if (Files.isDirectory(candidate)) {
|
||||
return candidate;
|
||||
}
|
||||
current = current.getParent();
|
||||
}
|
||||
throw new IllegalStateException("Unable to locate backend/script/sql from "
|
||||
+ Path.of("").toAbsolutePath());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,96 @@
|
||||
-- 个人 AI 助理独立知识空间。
|
||||
-- 个人知识仅按 tenant_id + owner_user_id 隔离,不修改或复用企业 aihr_knowledge_* 表。
|
||||
|
||||
CREATE TABLE IF NOT EXISTS `aihr_personal_space` (
|
||||
`id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键',
|
||||
`tenant_id` varchar(20) NOT NULL DEFAULT '000000' COMMENT '租户编号',
|
||||
`owner_user_id` bigint NOT NULL COMMENT '个人空间所属用户ID',
|
||||
`owner_ext_party_id` varchar(100) DEFAULT NULL COMMENT '外部组织主体ID',
|
||||
`status` varchar(20) NOT NULL DEFAULT 'ACTIVE' COMMENT '空间状态',
|
||||
`quota_bytes` bigint NOT NULL DEFAULT 524288000 COMMENT '空间配额,默认500MB',
|
||||
`used_bytes` bigint NOT NULL DEFAULT 0 COMMENT '已使用字节数',
|
||||
`item_count` int NOT NULL DEFAULT 0 COMMENT '资料数量',
|
||||
`create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
|
||||
`update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
|
||||
PRIMARY KEY (`id`),
|
||||
UNIQUE KEY `uk_aihr_personal_space_owner` (`tenant_id`, `owner_user_id`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='个人AI助理知识空间';
|
||||
|
||||
CREATE TABLE IF NOT EXISTS `aihr_personal_item` (
|
||||
`id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键',
|
||||
`tenant_id` varchar(20) NOT NULL DEFAULT '000000' COMMENT '租户编号',
|
||||
`owner_user_id` bigint NOT NULL COMMENT '资料所属用户ID',
|
||||
`owner_ext_party_id` varchar(100) DEFAULT NULL COMMENT '外部组织主体ID',
|
||||
`space_id` bigint NOT NULL COMMENT '个人空间ID',
|
||||
`source_type` varchar(30) NOT NULL COMMENT '来源类型: text/file/url',
|
||||
`title` varchar(500) NOT NULL COMMENT '资料标题',
|
||||
`source_url` varchar(2000) DEFAULT NULL COMMENT '原始网址',
|
||||
`oss_id` bigint DEFAULT NULL COMMENT '对象存储ID',
|
||||
`oss_url` varchar(2000) DEFAULT NULL COMMENT '对象存储访问地址',
|
||||
`mime_type` varchar(255) DEFAULT NULL COMMENT 'MIME类型',
|
||||
`size_bytes` bigint NOT NULL DEFAULT 0 COMMENT '资料大小',
|
||||
`content_hash` varchar(128) DEFAULT NULL COMMENT '内容摘要哈希',
|
||||
`status` varchar(30) NOT NULL DEFAULT 'PENDING' COMMENT '处理状态',
|
||||
`attempt_count` int NOT NULL DEFAULT 0 COMMENT '处理尝试次数',
|
||||
`last_error` varchar(1000) DEFAULT NULL COMMENT '最后错误信息',
|
||||
`summary` text COMMENT '资料摘要',
|
||||
`tags_json` longtext COMMENT '标签JSON',
|
||||
`captured_time` datetime DEFAULT NULL COMMENT '采集完成时间',
|
||||
`parsed_time` datetime DEFAULT NULL COMMENT '解析完成时间',
|
||||
`deleted_time` datetime DEFAULT NULL COMMENT '软删除时间',
|
||||
`create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
|
||||
`update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
|
||||
PRIMARY KEY (`id`),
|
||||
KEY `idx_aihr_personal_item_owner_status` (`tenant_id`, `owner_user_id`, `status`, `update_time`),
|
||||
KEY `idx_aihr_personal_item_space` (`space_id`, `id`),
|
||||
KEY `idx_aihr_personal_item_hash` (`tenant_id`, `owner_user_id`, `content_hash`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='个人AI助理资料';
|
||||
|
||||
CREATE TABLE IF NOT EXISTS `aihr_personal_fragment` (
|
||||
`id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键',
|
||||
`tenant_id` varchar(20) NOT NULL DEFAULT '000000' COMMENT '租户编号',
|
||||
`owner_user_id` bigint NOT NULL COMMENT '片段所属用户ID',
|
||||
`owner_ext_party_id` varchar(100) DEFAULT NULL COMMENT '外部组织主体ID',
|
||||
`item_id` bigint NOT NULL COMMENT '个人资料ID',
|
||||
`fragment_index` int NOT NULL COMMENT '片段序号',
|
||||
`content` text NOT NULL COMMENT '片段内容',
|
||||
`token_count` int NOT NULL DEFAULT 0 COMMENT '估算Token数',
|
||||
`embedding_model` varchar(100) DEFAULT NULL COMMENT '向量模型',
|
||||
`embedding_json` longtext COMMENT '向量JSON',
|
||||
`create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
|
||||
PRIMARY KEY (`id`),
|
||||
UNIQUE KEY `uk_aihr_personal_fragment_item_idx` (`item_id`, `fragment_index`),
|
||||
KEY `idx_aihr_personal_fragment_owner` (`tenant_id`, `owner_user_id`, `item_id`),
|
||||
FULLTEXT KEY `ft_aihr_personal_fragment_content` (`content`) WITH PARSER ngram
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='个人AI助理知识片段';
|
||||
|
||||
CREATE TABLE IF NOT EXISTS `aihr_personal_chat_session` (
|
||||
`id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键',
|
||||
`tenant_id` varchar(20) NOT NULL DEFAULT '000000' COMMENT '租户编号',
|
||||
`owner_user_id` bigint NOT NULL COMMENT '会话所属用户ID',
|
||||
`owner_ext_party_id` varchar(100) DEFAULT NULL COMMENT '外部组织主体ID',
|
||||
`title` varchar(255) NOT NULL DEFAULT '新对话' COMMENT '会话标题',
|
||||
`status` varchar(20) NOT NULL DEFAULT 'ACTIVE' COMMENT '会话状态',
|
||||
`create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
|
||||
`update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
|
||||
PRIMARY KEY (`id`),
|
||||
KEY `idx_aihr_personal_chat_session_owner` (`tenant_id`, `owner_user_id`, `status`, `update_time`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='个人AI助理对话会话';
|
||||
|
||||
CREATE TABLE IF NOT EXISTS `aihr_personal_chat_message` (
|
||||
`id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键',
|
||||
`tenant_id` varchar(20) NOT NULL DEFAULT '000000' COMMENT '租户编号',
|
||||
`owner_user_id` bigint NOT NULL COMMENT '消息所属用户ID',
|
||||
`owner_ext_party_id` varchar(100) DEFAULT NULL COMMENT '外部组织主体ID',
|
||||
`session_id` bigint NOT NULL COMMENT '对话会话ID',
|
||||
`role` varchar(20) NOT NULL COMMENT '消息角色: user/assistant/system',
|
||||
`content` longtext NOT NULL COMMENT '消息内容',
|
||||
`citations_json` longtext COMMENT '引用来源JSON',
|
||||
`input_tokens` int NOT NULL DEFAULT 0 COMMENT '输入Token数',
|
||||
`output_tokens` int NOT NULL DEFAULT 0 COMMENT '输出Token数',
|
||||
`model_name` varchar(100) DEFAULT NULL COMMENT '模型名称',
|
||||
`create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
|
||||
PRIMARY KEY (`id`),
|
||||
KEY `idx_aihr_personal_chat_message_session` (`session_id`, `id`),
|
||||
KEY `idx_aihr_personal_chat_message_owner` (`tenant_id`, `owner_user_id`, `create_time`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='个人AI助理对话消息';
|
||||
@@ -20,6 +20,7 @@ docker exec -i wygj-mysql mysql -uroot -proot --default-character-set=utf8mb4 ry
|
||||
docker exec -i wygj-mysql mysql -uroot -proot --default-character-set=utf8mb4 ry-vue < "$ROOT_DIR/backend/script/sql/ry_job.sql"
|
||||
docker exec -i wygj-mysql mysql -uroot -proot --default-character-set=utf8mb4 ry-vue < "$ROOT_DIR/backend/script/sql/ry_workflow.sql"
|
||||
docker exec -i wygj-mysql mysql -uroot -proot --default-character-set=utf8mb4 ry-vue < "$ROOT_DIR/backend/script/sql/aihr_knowledge_mysql8.sql"
|
||||
docker exec -i wygj-mysql mysql -uroot -proot --default-character-set=utf8mb4 ry-vue < "$ROOT_DIR/backend/script/sql/aihr_personal_knowledge_mysql8.sql"
|
||||
docker exec -i wygj-mysql mysql -uroot -proot --default-character-set=utf8mb4 ry-vue < "$ROOT_DIR/backend/script/sql/aihr_model_mysql8.sql"
|
||||
docker exec -i wygj-mysql mysql -uroot -proot --default-character-set=utf8mb4 ry-vue < "$ROOT_DIR/backend/script/sql/aihr_practice_mysql8.sql"
|
||||
docker exec -i wygj-mysql mysql -uroot -proot --default-character-set=utf8mb4 ry-vue < "$ROOT_DIR/backend/script/sql/aihr_interview_result_mysql8.sql"
|
||||
|
||||
Reference in New Issue
Block a user