From 88bfe1d815545cab4fcbeae1afac441c3dcdb82e Mon Sep 17 00:00:00 2001 From: let5sne Date: Sun, 12 Jul 2026 21:14:53 +0800 Subject: [PATCH] feat(personal): add scanned PDF OCR schema --- .../personal/domain/PersonalAssistantDto.java | 13 ++++++- .../personal/PersonalSchemaContractTest.java | 18 ++++++++- .../sql/aihr_personal_knowledge_mysql8.sql | 39 +++++++++++++++++++ 3 files changed, 68 insertions(+), 2 deletions(-) diff --git a/backend/ruoyi-modules/ruoyi-aihr/src/main/java/org/dromara/aihr/personal/domain/PersonalAssistantDto.java b/backend/ruoyi-modules/ruoyi-aihr/src/main/java/org/dromara/aihr/personal/domain/PersonalAssistantDto.java index e131cedb..6d4200a7 100644 --- a/backend/ruoyi-modules/ruoyi-aihr/src/main/java/org/dromara/aihr/personal/domain/PersonalAssistantDto.java +++ b/backend/ruoyi-modules/ruoyi-aihr/src/main/java/org/dromara/aihr/personal/domain/PersonalAssistantDto.java @@ -45,7 +45,18 @@ public final class PersonalAssistantDto { public record ItemResponse(long id, String sourceType, String title, String originalUrl, String mimeType, long sizeBytes, String status, String errorCode, String errorMessage, String summary, - List tags, LocalDateTime capturedAt, LocalDateTime parsedAt) { + List tags, LocalDateTime capturedAt, LocalDateTime parsedAt, + OcrProgressResponse ocr) { + public ItemResponse(long id, String sourceType, String title, String originalUrl, String mimeType, + long sizeBytes, String status, String errorCode, String errorMessage, String summary, + List tags, LocalDateTime capturedAt, LocalDateTime parsedAt) { + this(id, sourceType, title, originalUrl, mimeType, sizeBytes, status, errorCode, errorMessage, summary, + tags, capturedAt, parsedAt, null); + } + } + + public record OcrProgressResponse(boolean required, String status, int totalPages, int processedPages, + int successPages, int failedPages, List failedPageNumbers) { } public record PageResponse(List rows, long total, int pageNum, int pageSize) { diff --git a/backend/ruoyi-modules/ruoyi-aihr/src/test/java/org/dromara/aihr/personal/PersonalSchemaContractTest.java b/backend/ruoyi-modules/ruoyi-aihr/src/test/java/org/dromara/aihr/personal/PersonalSchemaContractTest.java index 9b172703..0e52cba9 100644 --- a/backend/ruoyi-modules/ruoyi-aihr/src/test/java/org/dromara/aihr/personal/PersonalSchemaContractTest.java +++ b/backend/ruoyi-modules/ruoyi-aihr/src/test/java/org/dromara/aihr/personal/PersonalSchemaContractTest.java @@ -59,8 +59,10 @@ class PersonalSchemaContractTest { String session = tableDefinition(sql, "aihr_personal_chat_session"); String message = tableDefinition(sql, "aihr_personal_chat_message"); String cleanup = tableDefinition(sql, "aihr_personal_cleanup_job"); + String ocrJob = tableDefinition(sql, "aihr_personal_ocr_job"); + String ocrPage = tableDefinition(sql, "aihr_personal_ocr_page"); - for (String definition : new String[] {space, item, fragment, session, message, cleanup}) { + for (String definition : new String[] {space, item, fragment, session, message, cleanup, ocrJob, ocrPage}) { assertTrue(definition.contains("`owner_user_id` bigint not null"), "Every personal table must carry a non-null owner_user_id"); } @@ -123,6 +125,20 @@ class PersonalSchemaContractTest { assertTrue(cleanup.contains("unique key `uk_personal_cleanup_item` (`tenant_id`, `owner_user_id`, `item_id`)")); assertTrue(cleanup.contains("key `idx_personal_cleanup_status` (`status`, `update_time`)")); + assertTrue(ocrJob.contains("`total_pages` int not null")); + assertTrue(ocrJob.contains("`processed_pages` int not null default 0")); + assertTrue(ocrJob.contains("`success_pages` int not null default 0")); + assertTrue(ocrJob.contains("`failed_pages` int not null default 0")); + assertTrue(ocrJob.contains("`failed_page_numbers_json` json")); + assertTrue(ocrJob.contains("unique key `uk_personal_ocr_job_item` (`tenant_id`, `owner_user_id`, `item_id`)")); + assertTrue(ocrJob.contains("key `idx_personal_ocr_job_status` (`status`, `update_time`)")); + + assertTrue(ocrPage.contains("`page_number` int not null")); + assertTrue(ocrPage.contains("`ocr_text` longtext")); + assertTrue(ocrPage.contains("`attempt_count` int not null default 0")); + assertTrue(ocrPage.contains("unique key `uk_personal_ocr_page_number` (`tenant_id`, `owner_user_id`, `item_id`, `page_number`)")); + assertTrue(ocrPage.contains("key `idx_personal_ocr_page_status` (`status`, `update_time`)")); + assertFalse(sql.contains("alter table aihr_knowledge_fragment"), "Personal schema must not mutate enterprise knowledge tables"); assertFalse(sql.contains("alter table `aihr_knowledge_fragment`"), diff --git a/backend/script/sql/aihr_personal_knowledge_mysql8.sql b/backend/script/sql/aihr_personal_knowledge_mysql8.sql index 11b37049..8712d9ea 100644 --- a/backend/script/sql/aihr_personal_knowledge_mysql8.sql +++ b/backend/script/sql/aihr_personal_knowledge_mysql8.sql @@ -128,3 +128,42 @@ CREATE TABLE IF NOT EXISTS `aihr_personal_cleanup_job` ( UNIQUE KEY `uk_personal_cleanup_item` (`tenant_id`, `owner_user_id`, `item_id`), KEY `idx_personal_cleanup_status` (`status`, `update_time`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='个人AI助理资料清理任务'; + +CREATE TABLE IF NOT EXISTS `aihr_personal_ocr_job` ( + `id` bigint NOT NULL AUTO_INCREMENT COMMENT 'OCR任务ID', + `tenant_id` varchar(20) NOT NULL COMMENT '租户编号', + `owner_user_id` bigint NOT NULL COMMENT '资料所属用户ID', + `item_id` bigint NOT NULL COMMENT '资料ID', + `status` varchar(20) NOT NULL DEFAULT 'PENDING' COMMENT 'PENDING/RUNNING/RETRY/COMPLETED/FAILED', + `total_pages` int NOT NULL COMMENT 'PDF总页数', + `processed_pages` int NOT NULL DEFAULT 0 COMMENT '已处理页数', + `success_pages` int NOT NULL DEFAULT 0 COMMENT '识别成功页数', + `failed_pages` int NOT NULL DEFAULT 0 COMMENT '识别失败页数', + `failed_page_numbers_json` json COMMENT '识别失败页码JSON', + `next_page` int NOT NULL DEFAULT 1 COMMENT '下一待处理页码,从1开始', + `attempt_count` int NOT NULL DEFAULT 0 COMMENT '任务领取次数', + `last_error_code` varchar(80) DEFAULT NULL COMMENT '脱敏后的错误码', + `lease_until` 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`), + UNIQUE KEY `uk_personal_ocr_job_item` (`tenant_id`, `owner_user_id`, `item_id`), + KEY `idx_personal_ocr_job_status` (`status`, `update_time`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='个人AI助理扫描PDF OCR任务'; + +CREATE TABLE IF NOT EXISTS `aihr_personal_ocr_page` ( + `id` bigint NOT NULL AUTO_INCREMENT COMMENT 'OCR页记录ID', + `tenant_id` varchar(20) NOT NULL COMMENT '租户编号', + `owner_user_id` bigint NOT NULL COMMENT '资料所属用户ID', + `item_id` bigint NOT NULL COMMENT '资料ID', + `page_number` int NOT NULL COMMENT '页码,从1开始', + `status` varchar(20) NOT NULL DEFAULT 'PENDING' COMMENT 'PENDING/RUNNING/SUCCESS/FAILED', + `ocr_text` longtext COMMENT '页级OCR文本', + `attempt_count` int NOT NULL DEFAULT 0 COMMENT '识别尝试次数', + `last_error_code` varchar(80) 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`), + UNIQUE KEY `uk_personal_ocr_page_number` (`tenant_id`, `owner_user_id`, `item_id`, `page_number`), + KEY `idx_personal_ocr_page_status` (`status`, `update_time`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='个人AI助理扫描PDF OCR页结果';