chenkun vor 1 Monat
Ursprung
Commit
dd170baa5e

+ 57 - 97
scripts/lq_oauth_db.sql

@@ -881,47 +881,32 @@ INSERT INTO `t_basis_of_preparation` VALUES ('3a2a4d64-a962-4319-a84b-f89a739a35
 INSERT INTO `t_basis_of_preparation` VALUES ('52e01c43-16a8-4005-9f61-936488130379', '建筑', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 'admin', '2026-01-15 09:35:39', NULL, '2026-01-15 09:35:39', NULL, NULL);
 
 -- ----------------------------
--- Table structure for t_document_main
--- ----------------------------
-DROP TABLE IF EXISTS `t_document_main`;
-CREATE TABLE `t_document_main`  (
-  `id` char(36) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL,
-  `title` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL,
-  `standard_no` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL,
-  `issuing_authority` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL,
-  `release_date` date NULL DEFAULT NULL,
-  `document_type` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL,
-  `professional_field` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL,
-  `validity` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT '现行',
-  `created_by` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL,
-  `created_time` datetime(0) NULL DEFAULT CURRENT_TIMESTAMP(0),
-  `updated_time` datetime(0) NULL DEFAULT CURRENT_TIMESTAMP(0) ON UPDATE CURRENT_TIMESTAMP(0),
-  `conversion_status` tinyint(0) NULL DEFAULT 0,
-  `conversion_progress` int(0) NULL DEFAULT 0,
-  `converted_file_name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL,
-  `conversion_error` text CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL,
-  `whether_to_enter` tinyint(0) NULL DEFAULT 0,
-  `source_type` enum('basis','work','job') CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL,
-  `source_id` char(36) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL,
-  `file_url` text CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL,
-  `file_extension` varchar(10) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL,
-  `content` text CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL,
-  `primary_category_id` int(0) NULL DEFAULT NULL,
-  `secondary_category_id` int(0) NULL DEFAULT NULL,
-  `year` int(0) NULL DEFAULT NULL,
-  `project_name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL,
-  `project_section` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL,
+-- Table structure for t_samp_document_main
+-- ----------------------------
+DROP TABLE IF EXISTS `t_samp_document_main`;
+CREATE TABLE `t_samp_document_main`  (
+  `id` varchar(36) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '主键',
+  `source_type` enum('basis','work','job') CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '所属类型',
+  `source_id` varchar(36) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '所属ID',
+  `title` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '文档名称',
+  `conversion_status` int(0) NOT NULL DEFAULT 0 COMMENT '状态: 0-待转换, 1-转换中, 2-完成, 3-失败',
+  `whether_to_enter` int(0) NOT NULL DEFAULT 0 COMMENT '是否入库: 0-未入库, 1-已入库',
+  `conversion_error` text CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL COMMENT '错误原因详情',
+  `file_url` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '文件URL',
+  `md_url` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT 'Markdown文件URL',
+  `json_url` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT 'JSON文件URL',
+  `file_extension` varchar(10) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '后缀名',
+  `created_by` varchar(36) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL COMMENT '创建人',
+  `created_time` datetime(0) NOT NULL DEFAULT CURRENT_TIMESTAMP(0) COMMENT '创建时间',
+  `updated_by` varchar(36) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL COMMENT '修改人',
+  `updated_time` datetime(0) NOT NULL DEFAULT CURRENT_TIMESTAMP(0) ON UPDATE CURRENT_TIMESTAMP(0) COMMENT '修改时间',
   PRIMARY KEY (`id`) USING BTREE,
   UNIQUE INDEX `idx_source_id_type`(`source_id`, `source_type`) USING BTREE
-) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci ROW_FORMAT = Dynamic;
+) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = '文档记录表' ROW_FORMAT = Dynamic;
 
 -- ----------------------------
--- Records of t_document_main
+-- Records of t_samp_document_main
 -- ----------------------------
-INSERT INTO `t_document_main` VALUES ('313339e5-f1b4-11f0-b5f2-128f048cee51', '方案', NULL, NULL, NULL, NULL, NULL, NULL, 'admin', '2026-01-15 09:47:47', '2026-01-15 10:58:48', 2, 100, '方案.md', NULL, 0, 'work', 'd4ef7acf-c28c-45cb-9214-0ddce85cf575', 'http://www.cdapm.com.cn/upload/%E5%BB%BA%E7%AD%91%E6%96%BD%E5%B7%A5%E6%A8%A1%E6%9D%BF%E5%AE%89%E5%85%A8%E6%8A%80%E6%9C%AF%E8%A7%84%E8%8C%83JGJ162-2008.pdf', '.pdf', '', NULL, NULL, 2026, NULL, NULL);
-INSERT INTO `t_document_main` VALUES ('39767b87-f1b4-11f0-b5f2-128f048cee51', '办公', NULL, NULL, NULL, NULL, NULL, NULL, 'admin', '2026-01-15 09:48:00', '2026-01-15 09:48:00', 0, 0, NULL, NULL, 0, 'job', '351b063f-eb20-4cbd-abf6-7a1097adbebf', 'http://www.cdapm.com.cn/upload/%E5%BB%BA%E7%AD%91%E6%96%BD%E5%B7%A5%E6%A8%A1%E6%9D%BF%E5%AE%89%E5%85%A8%E6%8A%80%E6%9C%AF%E8%A7%84%E8%8C%83JGJ162-2008.pdf', '.pdf', '', NULL, NULL, 2026, NULL, NULL);
-INSERT INTO `t_document_main` VALUES ('723b0bf8-f1b4-11f0-b5f2-128f048cee51', '新建 文本文档', NULL, NULL, NULL, NULL, NULL, NULL, 'admin', '2026-01-15 09:49:36', '2026-01-15 09:49:36', 0, 0, NULL, NULL, 0, 'basis', '3a2a4d64-a962-4319-a84b-f89a739a355f', 'http://192.168.91.15:19000/aidata/sampledata/uploads/20260115/193df254-05d2-469c-a407-a37c52b41c88.txt', '.txt', '', NULL, NULL, 2026, NULL, NULL);
-INSERT INTO `t_document_main` VALUES ('7f8020af-f1b2-11f0-b5f2-128f048cee51', '建筑', NULL, NULL, NULL, NULL, NULL, NULL, 'admin', '2026-01-15 09:35:39', '2026-01-15 09:47:02', 2, 100, '建筑.md', NULL, 0, 'basis', '52e01c43-16a8-4005-9f61-936488130379', 'http://www.cdapm.com.cn/upload/%E5%BB%BA%E7%AD%91%E6%96%BD%E5%B7%A5%E6%A8%A1%E6%9D%BF%E5%AE%89%E5%85%A8%E6%8A%80%E6%9C%AF%E8%A7%84%E8%8C%83JGJ162-2008.pdf', '.pdf', '', NULL, NULL, 2026, NULL, NULL);
 
 -- ----------------------------
 -- Table structure for t_job_of_preparation
@@ -1128,51 +1113,41 @@ INSERT INTO `users` VALUES ('zhangsan', 'zhangsan@qq.com', '143454545', 'sha256$
 INSERT INTO `users` VALUES ('admin', 'admin@example.com', NULL, 'sha256$fc7bcee8f0dd0566e809d1920b3524c7$149986dbf144e9aebc6a282959075db9a822012ab76813fb6b71509254b3c7ee', NULL, 1, 1, NULL, NULL, 0, NULL, 'ed6a79d3-0083-4d81-8b48-fc522f686f74', '2025-12-20 13:53:13', '2025-12-28 18:05:37', 0);
 
 -- ----------------------------
--- Triggers structure for table t_basis_of_preparation
+-- Triggers structure for table t_samp_standard_base_info
 -- ----------------------------
 DROP TRIGGER IF EXISTS `trg_basis_after_delete`;
 delimiter ;;
-CREATE TRIGGER `trg_basis_after_delete` AFTER DELETE ON `t_basis_of_preparation` FOR EACH ROW BEGIN
-                    DELETE FROM t_document_main WHERE source_id = OLD.id AND source_type = 'basis';
+CREATE TRIGGER `trg_basis_after_delete` AFTER DELETE ON `t_samp_standard_base_info` FOR EACH ROW BEGIN
+                    DELETE FROM t_samp_document_main WHERE source_id = OLD.id AND source_type = 'basis';
                 END
 ;;
 delimiter ;
 
 -- ----------------------------
--- Triggers structure for table t_basis_of_preparation
+-- Triggers structure for table t_samp_standard_base_info
 -- ----------------------------
 DROP TRIGGER IF EXISTS `trg_basis_after_insert`;
 delimiter ;;
-CREATE TRIGGER `trg_basis_after_insert` AFTER INSERT ON `t_basis_of_preparation` FOR EACH ROW BEGIN
-                    INSERT INTO t_document_main (
-                        id, title, standard_no, issuing_authority, release_date,   
-                        document_type, professional_field, validity, file_url, content,
-                        created_by, created_time, updated_time, source_type, source_id, whether_to_enter
+CREATE TRIGGER `trg_basis_after_insert` AFTER INSERT ON `t_samp_standard_base_info` FOR EACH ROW BEGIN
+                    INSERT INTO t_samp_document_main (
+                        id, title, file_url, created_by, created_time, updated_by, updated_time, source_type, source_id, whether_to_enter
                     ) VALUES (
-                        UUID(), NEW.chinese_name, NEW.standard_number, NEW.issuing_authority, NEW.release_date,
-                        NEW.document_type, NEW.professional_field, NEW.validity, NEW.file_url, NEW.content,
-                        NEW.created_by, NEW.created_time, NEW.updated_time, 'basis', NEW.id, 0
+                        UUID(), NEW.chinese_name, NEW.file_url, NEW.created_by, NEW.created_time, IFNULL(NEW.updated_by, NEW.created_by), NEW.updated_time, 'basis', NEW.id, 0
                     );
                 END
 ;;
 delimiter ;
 
 -- ----------------------------
--- Triggers structure for table t_basis_of_preparation
+-- Triggers structure for table t_samp_standard_base_info
 -- ----------------------------
 DROP TRIGGER IF EXISTS `trg_basis_after_update`;
 delimiter ;;
-CREATE TRIGGER `trg_basis_after_update` AFTER UPDATE ON `t_basis_of_preparation` FOR EACH ROW BEGIN
-                    UPDATE t_document_main SET
+CREATE TRIGGER `trg_basis_after_update` AFTER UPDATE ON `t_samp_standard_base_info` FOR EACH ROW BEGIN
+                    UPDATE t_samp_document_main SET
                         title = NEW.chinese_name,
-                        standard_no = NEW.standard_number,
-                        issuing_authority = NEW.issuing_authority,
-                        release_date = NEW.release_date,
-                        document_type = NEW.document_type,
-                        professional_field = NEW.professional_field,
-                        validity = NEW.validity,
                         file_url = NEW.file_url,
-                        content = NEW.content,
+                        updated_by = IFNULL(NEW.updated_by, NEW.created_by),
                         updated_time = NEW.updated_time
                     WHERE source_id = NEW.id AND source_type = 'basis';
                 END
@@ -1180,48 +1155,41 @@ CREATE TRIGGER `trg_basis_after_update` AFTER UPDATE ON `t_basis_of_preparation`
 delimiter ;
 
 -- ----------------------------
--- Triggers structure for table t_job_of_preparation
+-- Triggers structure for table t_samp_office_regulations
 -- ----------------------------
 DROP TRIGGER IF EXISTS `trg_job_after_delete`;
 delimiter ;;
-CREATE TRIGGER `trg_job_after_delete` AFTER DELETE ON `t_job_of_preparation` FOR EACH ROW BEGIN
-                    DELETE FROM t_document_main WHERE source_id = OLD.id AND source_type = 'job';
+CREATE TRIGGER `trg_job_after_delete` AFTER DELETE ON `t_samp_office_regulations` FOR EACH ROW BEGIN
+                    DELETE FROM t_samp_document_main WHERE source_id = OLD.id AND source_type = 'job';
                 END
 ;;
 delimiter ;
 
 -- ----------------------------
--- Triggers structure for table t_job_of_preparation
+-- Triggers structure for table t_samp_office_regulations
 -- ----------------------------
 DROP TRIGGER IF EXISTS `trg_job_after_insert`;
 delimiter ;;
-CREATE TRIGGER `trg_job_after_insert` AFTER INSERT ON `t_job_of_preparation` FOR EACH ROW BEGIN
-                    INSERT INTO t_document_main (
-                        id, title, issuing_authority, release_date, document_type, 
-                        file_url, content,
-                        created_by, created_time, updated_time, source_type, source_id, whether_to_enter
+CREATE TRIGGER `trg_job_after_insert` AFTER INSERT ON `t_samp_office_regulations` FOR EACH ROW BEGIN
+                    INSERT INTO t_samp_document_main (
+                        id, title, file_url, created_by, created_time, updated_by, updated_time, source_type, source_id, whether_to_enter
                     ) VALUES (
-                        UUID(), NEW.file_name, NEW.issuing_department, NEW.publish_date, NEW.document_type,
-                        NEW.file_url, NEW.content,
-                        NEW.created_by, NEW.created_time, NEW.updated_time, 'job', NEW.id, 0
+                        UUID(), NEW.file_name, NEW.file_url, NEW.created_by, NEW.created_time, IFNULL(NEW.updated_by, NEW.created_by), NEW.updated_time, 'job', NEW.id, 0
                     );
                 END
 ;;
 delimiter ;
 
 -- ----------------------------
--- Triggers structure for table t_job_of_preparation
+-- Triggers structure for table t_samp_office_regulations
 -- ----------------------------
 DROP TRIGGER IF EXISTS `trg_job_after_update`;
 delimiter ;;
-CREATE TRIGGER `trg_job_after_update` AFTER UPDATE ON `t_job_of_preparation` FOR EACH ROW BEGIN
-                    UPDATE t_document_main SET
+CREATE TRIGGER `trg_job_after_update` AFTER UPDATE ON `t_samp_office_regulations` FOR EACH ROW BEGIN
+                    UPDATE t_samp_document_main SET
                         title = NEW.file_name,
-                        issuing_authority = NEW.issuing_department,
-                        release_date = NEW.publish_date,
-                        document_type = NEW.document_type,
                         file_url = NEW.file_url,
-                        content = NEW.content,
+                        updated_by = IFNULL(NEW.updated_by, NEW.created_by),
                         updated_time = NEW.updated_time
                     WHERE source_id = NEW.id AND source_type = 'job';
                 END
@@ -1229,49 +1197,41 @@ CREATE TRIGGER `trg_job_after_update` AFTER UPDATE ON `t_job_of_preparation` FOR
 delimiter ;
 
 -- ----------------------------
--- Triggers structure for table t_work_of_preparation
+-- Triggers structure for table t_samp_construction_plan_base_info
 -- ----------------------------
 DROP TRIGGER IF EXISTS `trg_work_after_delete`;
 delimiter ;;
-CREATE TRIGGER `trg_work_after_delete` AFTER DELETE ON `t_work_of_preparation` FOR EACH ROW BEGIN
-                    DELETE FROM t_document_main WHERE source_id = OLD.id AND source_type = 'work';
+CREATE TRIGGER `trg_work_after_delete` AFTER DELETE ON `t_samp_construction_plan_base_info` FOR EACH ROW BEGIN
+                    DELETE FROM t_samp_document_main WHERE source_id = OLD.id AND source_type = 'work';
                 END
 ;;
 delimiter ;
 
 -- ----------------------------
--- Triggers structure for table t_work_of_preparation
+-- Triggers structure for table t_samp_construction_plan_base_info
 -- ----------------------------
 DROP TRIGGER IF EXISTS `trg_work_after_insert`;
 delimiter ;;
-CREATE TRIGGER `trg_work_after_insert` AFTER INSERT ON `t_work_of_preparation` FOR EACH ROW BEGIN
-                    INSERT INTO t_document_main (
-                        id, title, project_name, project_section, issuing_authority, release_date,
-                        file_url, content,
-                        created_by, created_time, updated_time, source_type, source_id, whether_to_enter
+CREATE TRIGGER `trg_work_after_insert` AFTER INSERT ON `t_samp_construction_plan_base_info` FOR EACH ROW BEGIN
+                    INSERT INTO t_samp_document_main (
+                        id, title, file_url, created_by, created_time, updated_by, updated_time, source_type, source_id, whether_to_enter
                     ) VALUES (
-                        UUID(), NEW.plan_name, NEW.project_name, NEW.project_section, NEW.compiling_unit, NEW.compiling_date,
-                        NEW.file_url, NEW.content,
-                        NEW.created_by, NEW.created_time, NEW.updated_time, 'work', NEW.id, 0
+                        UUID(), NEW.plan_name, NEW.file_url, NEW.created_by, NEW.created_time, IFNULL(NEW.updated_by, NEW.created_by), NEW.updated_time, 'work', NEW.id, 0
                     );
                 END
 ;;
 delimiter ;
 
 -- ----------------------------
--- Triggers structure for table t_work_of_preparation
+-- Triggers structure for table t_samp_construction_plan_base_info
 -- ----------------------------
 DROP TRIGGER IF EXISTS `trg_work_after_update`;
 delimiter ;;
-CREATE TRIGGER `trg_work_after_update` AFTER UPDATE ON `t_work_of_preparation` FOR EACH ROW BEGIN
-                    UPDATE t_document_main SET
+CREATE TRIGGER `trg_work_after_update` AFTER UPDATE ON `t_samp_construction_plan_base_info` FOR EACH ROW BEGIN
+                    UPDATE t_samp_document_main SET
                         title = NEW.plan_name,
-                        project_name = NEW.project_name,
-                        project_section = NEW.project_section,
-                        issuing_authority = NEW.compiling_unit,
-                        release_date = NEW.compiling_date,
                         file_url = NEW.file_url,
-                        content = NEW.content,
+                        updated_by = IFNULL(NEW.updated_by, NEW.created_by),
                         updated_time = NEW.updated_time
                     WHERE source_id = NEW.id AND source_type = 'work';
                 END

+ 16 - 34
scripts/lq_oauth_db_20260123.sql

@@ -318,46 +318,28 @@ INSERT INTO `t_samp_doc_category` VALUES (10, '人事通知', 3, 1, 0, 1, 'syste
 -- ----------------------------
 DROP TABLE IF EXISTS `t_samp_document_main`;
 CREATE TABLE `t_samp_document_main`  (
-  `id` char(36) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '文档ID',
-  `title` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '文档标题',
-  `standard_no` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '标准编号',
-  `issuing_authority` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '发布单位',
-  `release_date` date NULL DEFAULT NULL COMMENT '发布日期',
-  `document_type` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '文档类型',
-  `professional_field` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '专业领域',
-  `validity` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT '现行' COMMENT '时效性',
-  `conversion_status` tinyint(0) NULL DEFAULT 0 COMMENT '转换状态',
-  `conversion_progress` int(0) NULL DEFAULT 0 COMMENT '转换进度',
-  `converted_file_name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '转换后文件名',
-  `conversion_error` text CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL COMMENT '转换错误信息',
-  `whether_to_enter` tinyint(0) NULL DEFAULT 0 COMMENT '是否录入',
-  `source_type` enum('basis','work','job') CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '来源类型',
-  `source_id` char(36) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '来源ID',
-  `file_url` text CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL COMMENT '文件URL',
-  `json_url` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '转换后的 JSON 数据地址',
-  `file_extension` varchar(10) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '文件扩展名',
-  `content` text CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL COMMENT '文档内容',
-  `primary_category_id` int(0) NULL DEFAULT NULL COMMENT '主分类ID',
-  `secondary_category_id` int(0) NULL DEFAULT NULL COMMENT '次分类ID',
-  `year` int(0) NULL DEFAULT NULL COMMENT '年份',
-  `project_name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '项目名称',
-  `project_section` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '项目标段',
-  `created_by` varchar(36) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NULL DEFAULT NULL COMMENT '创建人',
-  `created_time` datetime(0) NULL DEFAULT CURRENT_TIMESTAMP(0) COMMENT '创建时间,默认当前时间',
-  `updated_by` varchar(36) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NULL DEFAULT NULL COMMENT '修改人',
-  `updated_time` datetime(0) NULL DEFAULT CURRENT_TIMESTAMP(0) ON UPDATE CURRENT_TIMESTAMP(0) COMMENT '修改时间,默认当前时间',
+  `id` varchar(36) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '主键',
+  `source_type` enum('basis','work','job') CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '所属类型',
+  `source_id` varchar(36) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '所属ID',
+  `title` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '文档名称',
+  `conversion_status` int(0) NOT NULL DEFAULT 0 COMMENT '状态: 0-待转换, 1-转换中, 2-完成, 3-失败',
+  `whether_to_enter` int(0) NOT NULL DEFAULT 0 COMMENT '是否入库: 0-未入库, 1-已入库',
+  `conversion_error` text CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL COMMENT '错误原因详情',
+  `file_url` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '文件URL',
+  `md_url` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT 'Markdown文件URL',
+  `json_url` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT 'JSON文件URL',
+  `file_extension` varchar(10) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '后缀名',
+  `created_by` varchar(36) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL COMMENT '创建人',
+  `created_time` datetime(0) NOT NULL DEFAULT CURRENT_TIMESTAMP(0) COMMENT '创建时间',
+  `updated_by` varchar(36) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL COMMENT '修改人',
+  `updated_time` datetime(0) NOT NULL DEFAULT CURRENT_TIMESTAMP(0) ON UPDATE CURRENT_TIMESTAMP(0) COMMENT '修改时间',
   PRIMARY KEY (`id`) USING BTREE,
   UNIQUE INDEX `idx_source_id_type`(`source_id`, `source_type`) USING BTREE
-) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = '文档表' ROW_FORMAT = Dynamic;
+) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = '文档记录表' ROW_FORMAT = Dynamic;
 
 -- ----------------------------
 -- Records of t_samp_document_main
 -- ----------------------------
-INSERT INTO `t_samp_document_main` VALUES ('313339e5-f1b4-11f0-b5f2-128f048cee51', '方案', NULL, NULL, NULL, NULL, NULL, NULL, 2, 100, 'http://192.168.91.15:19000/aidata/sampledata/converted/20260122/313339e5-f1b4-11f0-b5f2-128f048cee51.md', NULL, 0, 'work', 'd4ef7acf-c28c-45cb-9214-0ddce85cf575', 'http://www.cdapm.com.cn/upload/%E5%BB%BA%E7%AD%91%E6%96%BD%E5%B7%A5%E6%A8%A1%E6%9D%BF%E5%AE%89%E5%85%A8%E6%8A%80%E6%9C%AF%E8%A7%84%E8%8C%83JGJ162-2008.pdf', NULL, '.pdf', '', NULL, NULL, 2026, NULL, NULL, 'admin', '2026-01-15 09:47:47', 'system', '2026-01-22 14:30:22');
-INSERT INTO `t_samp_document_main` VALUES ('35da194c-f75a-11f0-aa4a-6ef9aeacc98a', '新建 文本文档', NULL, NULL, NULL, NULL, NULL, NULL, 0, 0, NULL, NULL, 0, 'basis', '4c93b6eb-5d48-4d57-bf3f-1cf47e1c55ea', 'http://192.168.91.15:19000/aidata/sampledata/uploads/20260122/a4d67bf7-0b07-4c6e-8bb8-6030f2331589.txt', NULL, NULL, '', NULL, NULL, 2026, NULL, NULL, 'admin', '2026-01-22 14:18:47', 'system', '2026-01-22 14:18:47');
-INSERT INTO `t_samp_document_main` VALUES ('39767b87-f1b4-11f0-b5f2-128f048cee51', '办公', NULL, NULL, NULL, NULL, NULL, NULL, 2, 100, 'http://test.com/test.pdf', NULL, 0, 'job', '351b063f-eb20-4cbd-abf6-7a1097adbebf', NULL, NULL, '.pdf', '', NULL, NULL, 2026, NULL, NULL, 'admin', '2026-01-15 09:48:00', 'system', '2026-01-22 15:56:00');
-INSERT INTO `t_samp_document_main` VALUES ('7f8020af-f1b2-11f0-b5f2-128f048cee51', '建筑', NULL, NULL, NULL, NULL, NULL, NULL, 2, 100, '建筑.md', NULL, 0, 'basis', '52e01c43-16a8-4005-9f61-936488130379', 'http://www.cdapm.com.cn/upload/%E5%BB%BA%E7%AD%91%E6%96%BD%E5%B7%A5%E6%A8%A1%E6%9D%BF%E5%AE%89%E5%85%A8%E6%8A%80%E6%9C%AF%E8%A7%84%E8%8C%83JGJ162-2008.pdf', NULL, '.pdf', '', NULL, NULL, 2026, NULL, NULL, 'admin', '2026-01-15 09:35:39', 'system', '2026-01-15 09:47:02');
-INSERT INTO `t_samp_document_main` VALUES ('d6c737ae-f759-11f0-aa4a-6ef9aeacc98a', '测试全流程上传文档', NULL, NULL, NULL, NULL, NULL, NULL, 0, 0, NULL, NULL, 0, 'basis', '55025214-b478-4110-8e2d-47617a9676a3', 'http://192.168.91.15:19000/aidata/sampledata/uploads/20260122/8bf35980-a2e6-418c-9d79-049256d024f6.txt', NULL, '.txt', '这是通过自动化脚本测试的上传文档内容摘要。', NULL, NULL, 2026, NULL, NULL, 'admin', '2026-01-22 14:16:07', 'system', '2026-01-22 14:16:07');
 
 -- ----------------------------
 -- Table structure for t_samp_knowledge_base

+ 72 - 70
scripts/miner_u.py

@@ -9,7 +9,7 @@ import io
 from datetime import datetime
 from pathlib import Path
 from urllib.parse import urlparse
-from minio import Minio
+from app.base.minio_connection import get_minio_manager
 
 # 配置日志
 logging.basicConfig(
@@ -24,44 +24,12 @@ sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..', 'src'))
 from app.core.config import config_handler
 
 # MinIO 配置
-MINIO_ENDPOINT = config_handler.get("admin_app", "MINIO_ENDPOINT", "192.168.91.15:19000")
-MINIO_ACCESS_KEY = config_handler.get("admin_app", "MINIO_ACCESS_KEY", "minioadmin")
-MINIO_SECRET_KEY = config_handler.get("admin_app", "MINIO_SECRET_KEY", "minioadmin")
-MINIO_BUCKET = config_handler.get("admin_app", "MINIO_BUCKET_NAME", "aidata")
-MINIO_USE_SSL = config_handler.get_bool("admin_app", "MINIO_USE_SSL", False)
-MINIO_BASE_PATH = config_handler.get("admin_app", "MINIO_BASE_PATH", "sampledata")
-
-def get_minio_client():
-    try:
-        return Minio(
-            MINIO_ENDPOINT,
-            access_key=MINIO_ACCESS_KEY,
-            secret_key=MINIO_SECRET_KEY,
-            secure=MINIO_USE_SSL
-        )
-    except Exception as e:
-        logger.error(f"MinIO client init failed: {e}")
-        return None
+minio_manager = get_minio_manager()
+MINIO_BASE_PATH = minio_manager.base_path
 
 def upload_to_minio(file_content, object_name, content_type="text/markdown"):
-    client = get_minio_client()
-    if not client:
-        return None
     try:
-        # 确保桶存在
-        if not client.bucket_exists(MINIO_BUCKET):
-            client.make_bucket(MINIO_BUCKET)
-        
-        # 上传文件
-        client.put_object(
-            MINIO_BUCKET,
-            object_name,
-            io.BytesIO(file_content),
-            len(file_content),
-            content_type=content_type
-        )
-        # 返回访问链接
-        return f"http://{MINIO_ENDPOINT}/{MINIO_BUCKET}/{object_name}"
+        return minio_manager.upload_file(file_content, object_name, content_type)
     except Exception as e:
         logger.error(f"Upload to MinIO failed: {e}")
         return None
@@ -98,7 +66,7 @@ def get_db_connection():
         logger.error(f"Database connection error: {e}")
         return None
 
-def update_db_status(doc_id, status=None, progress=None, error=None, converted_file_name=None):
+def update_db_status(doc_id, status=None, error=None, md_url=None, json_url=None):
     conn = get_db_connection()
     if not conn:
         return
@@ -109,15 +77,15 @@ def update_db_status(doc_id, status=None, progress=None, error=None, converted_f
             if status is not None:
                 updates.append("conversion_status = %s")
                 params.append(status)
-            if progress is not None:
-                updates.append("conversion_progress = %s")
-                params.append(progress)
             if error is not None:
                 updates.append("conversion_error = %s")
                 params.append(error)
-            if converted_file_name is not None:
-                updates.append("converted_file_name = %s")
-                params.append(converted_file_name)
+            if md_url is not None:
+                updates.append("md_url = %s")
+                params.append(md_url)
+            if json_url is not None:
+                updates.append("json_url = %s")
+                params.append(json_url)
             
             if not updates:
                 return
@@ -125,9 +93,36 @@ def update_db_status(doc_id, status=None, progress=None, error=None, converted_f
             # 同时更新修改时间
             updates.append("updated_time = NOW()")
                 
-            sql = f"UPDATE t_document_main SET {', '.join(updates)} WHERE id = %s"
+            sql = f"UPDATE t_samp_document_main SET {', '.join(updates)} WHERE id = %s"
             params.append(doc_id)
             cursor.execute(sql, params)
+            
+            # 如果更新了 json_url 或 md_url,同步更新到子表
+            if json_url is not None or md_url is not None:
+                try:
+                    cursor.execute("SELECT source_type, source_id FROM t_samp_document_main WHERE id = %s", (doc_id,))
+                    row = cursor.fetchone()
+                    if row and row[0] and row[1]:
+                        source_type, source_id = row[0], row[1]
+                        TABLE_MAP = {
+                            "basis": "t_samp_standard_base_info",
+                            "work": "t_samp_construction_plan_base_info",
+                            "job": "t_samp_office_regulations"
+                        }
+                        table_name = TABLE_MAP.get(source_type)
+                        if table_name:
+                            sub_updates = []
+                            sub_params = []
+                            if json_url is not None:
+                                sub_updates.append("json_url = %s")
+                                sub_params.append(json_url)
+                            
+                            if sub_updates:
+                                sub_sql = f"UPDATE {table_name} SET {', '.join(sub_updates)} WHERE id = %s"
+                                sub_params.append(source_id)
+                                cursor.execute(sub_sql, sub_params)
+                except Exception as e:
+                    logger.error(f"Sync URLs to sub-table failed: {e}")
     except Exception as e:
         logger.error(f"Update DB failed: {e}")
     finally:
@@ -151,7 +146,7 @@ def upload_files(file_data_list, upload_urls):
         if res.status_code != 200:
             raise RuntimeError(f"upload failed to {url}, status={res.status_code}")
 
-def poll_batch(batch_id, interval_sec=5, timeout_sec=1800):
+def poll_batch(doc_id, batch_id, interval_sec=5, timeout_sec=1800):
     deadline = time.time() + timeout_sec
     while True:
         r = requests.get(API_BATCH_RESULT.format(batch_id), headers=HEADERS, timeout=60)
@@ -172,7 +167,7 @@ def poll_batch(batch_id, interval_sec=5, timeout_sec=1800):
 def process_document(doc_id, chinese_name, file_url, out_dir):
     try:
         # 1. 更新状态:开始转换
-        update_db_status(doc_id, status=1, progress=10)
+        update_db_status(doc_id, status=1)
         
         # 2. 下载原始文件
         logger.info(f"Downloading {file_url}...")
@@ -190,24 +185,21 @@ def process_document(doc_id, chinese_name, file_url, out_dir):
             file_ext = ".pdf" # Default
             
         file_name = f"{chinese_name}{file_ext}"
-        update_db_status(doc_id, progress=30)
         
         # 3. 提交到 MinerU
         files_meta = [{"name": file_name, "data_id": doc_id}]
         batch_id, upload_urls = apply_upload_urls(files_meta)
-        upload_files([file_content], upload_urls)
         
-        update_db_status(doc_id, progress=50)
+        upload_files([file_content], upload_urls)
         
         # 4. 轮询结果
-        results = poll_batch(batch_id)
+        results = poll_batch(doc_id, batch_id)
         result = results[0]
         
         if result.get("state") == "done":
             zip_url = result.get("full_zip_url")
             if zip_url:
                 # 5. 下载并处理结果
-                update_db_status(doc_id, progress=80)
                 zip_resp = requests.get(zip_url, timeout=300)
                 zip_resp.raise_for_status()
                 
@@ -215,34 +207,44 @@ def process_document(doc_id, chinese_name, file_url, out_dir):
                 with zipfile.ZipFile(io.BytesIO(zip_resp.content)) as z:
                     # 查找 .md 文件
                     md_files = [f for f in z.namelist() if f.endswith(".md")]
+                    # 查找 .json 文件 (通常是 content_list.json)
+                    json_files = [f for f in z.namelist() if f.endswith(".json")]
+                    
+                    md_cloud_url = None
+                    json_cloud_url = None
+                    
                     if md_files:
                         md_content = z.read(md_files[0])
                         # 构造云端存储路径
-                        object_name = f"{MINIO_BASE_PATH}/converted/{datetime.now().strftime('%Y%m%d')}/{doc_id}.md"
+                        md_object_name = f"{MINIO_BASE_PATH}/converted/{datetime.now().strftime('%Y%m%d')}/{doc_id}.md"
                         # 上传到 MinIO
-                        cloud_url = upload_to_minio(md_content, object_name)
+                        md_cloud_url = upload_to_minio(md_content, md_object_name, content_type="text/markdown")
                         
-                        if cloud_url:
-                            logger.info(f"Uploaded converted file to {cloud_url}")
-                            update_db_status(doc_id, status=2, progress=100, converted_file_name=cloud_url)
-                            return True
-                        else:
-                            raise RuntimeError("Failed to upload converted file to MinIO")
-                    else:
-                        raise RuntimeError("No .md file found in the converted zip")
+                    if json_files:
+                        # 优先取 content_list.json
+                        json_file = next((f for f in json_files if "content_list" in f), json_files[0])
+                        json_content = z.read(json_file)
+                        # 构造云端存储路径
+                        json_object_name = f"{MINIO_BASE_PATH}/converted/{datetime.now().strftime('%Y%m%d')}/{doc_id}.json"
+                        # 上传到 MinIO
+                        json_cloud_url = upload_to_minio(json_content, json_object_name, content_type="application/json")
+                    
+                    # 6. 更新数据库
+                    update_db_status(doc_id, status=2, 
+                                    md_url=md_cloud_url, 
+                                    json_url=json_cloud_url)
+                    logger.info(f"[{doc_id}] Processed successfully. MD: {md_cloud_url}, JSON: {json_cloud_url}")
             else:
-                raise RuntimeError("No zip URL in result")
+                update_db_status(doc_id, status=3, error="Full ZIP URL not found")
         else:
-            err_msg = result.get("err_msg", "Unknown error")
-            raise RuntimeError(f"MinerU extraction failed: {err_msg}")
+            update_db_status(doc_id, status=3, error=result.get("err_msg", "Conversion failed"))
             
     except Exception as e:
-        logger.error(f"Process failed: {e}")
+        logger.exception(f"[{doc_id}] Error processing document: {e}")
         update_db_status(doc_id, status=3, error=str(e))
-        return False
 
 def main_cli(doc_id, out_dir=r"d:\UGit\MinerU"):
-    # 从数据库获取详细信息 - 直接从 t_document_main 获取
+    # 从数据库获取详细信息 - 直接从 t_samp_document_main 获取
     conn = get_db_connection()
     if not conn:
         logger.error("Database connection failed")
@@ -250,8 +252,8 @@ def main_cli(doc_id, out_dir=r"d:\UGit\MinerU"):
         
     try:
         with conn.cursor() as cursor:
-            # 优先从 t_document_main 获取 title 和 file_url
-            cursor.execute("SELECT title, file_url FROM t_document_main WHERE id = %s", (doc_id,))
+            # 优先从 t_samp_document_main 获取 title 和 file_url
+            cursor.execute("SELECT title, file_url FROM t_samp_document_main WHERE id = %s", (doc_id,))
             row = cursor.fetchone()
             if not row or not row[1]: # 如果主表没有 file_url,尝试从子表获取
                 if not row:
@@ -260,7 +262,7 @@ def main_cli(doc_id, out_dir=r"d:\UGit\MinerU"):
                 
                 title = row[0]
                 # 尝试从子表获取 (兼容旧数据)
-                cursor.execute("SELECT source_type, source_id FROM t_document_main WHERE id = %s", (doc_id,))
+                cursor.execute("SELECT source_type, source_id FROM t_samp_document_main WHERE id = %s", (doc_id,))
                 st_row = cursor.fetchone()
                 if st_row:
                     source_type, source_id = st_row

+ 5 - 0
src/app/base/__init__.py

@@ -19,6 +19,7 @@ from .milvus_connection import (
     init_milvus, 
     close_milvus
 )
+from .minio_connection import get_minio_manager, init_minio, MinioManager
 from .embedding_connection import get_embedding_model, get_embedding_config
 
 __all__ = [
@@ -41,6 +42,10 @@ __all__ = [
     "MilvusManager",
     "init_milvus",
     "close_milvus",
+    # MinIO
+    "get_minio_manager",
+    "init_minio",
+    "MinioManager",
     # Embedding
     "get_embedding_model",
     "get_embedding_config",

+ 95 - 0
src/app/base/minio_connection.py

@@ -0,0 +1,95 @@
+"""
+MinIO 存储连接管理
+"""
+import os
+import logging
+import uuid
+import io
+from datetime import datetime, timedelta
+from typing import Optional, Tuple, Dict, Any
+from minio import Minio
+
+# 导入配置
+from app.core.config import config_handler
+
+logger = logging.getLogger(__name__)
+
+_minio_manager = None
+
+class MinioManager:
+    """MinIO 管理器"""
+    
+    def __init__(self):
+        self.endpoint = config_handler.get("admin_app", "MINIO_ENDPOINT", "192.168.91.15:19000")
+        self.access_key = config_handler.get("admin_app", "MINIO_ACCESS_KEY", "minioadmin")
+        self.secret_key = config_handler.get("admin_app", "MINIO_SECRET_KEY", "minioadmin")
+        self.bucket_name = config_handler.get("admin_app", "MINIO_BUCKET_NAME", "lq-bucket")
+        self.secure = config_handler.get_bool("admin_app", "MINIO_USE_SSL", False)
+        self.base_path = config_handler.get("admin_app", "MINIO_BASE_PATH", "lqadmin")
+        
+        self._client = None
+        logger.info(f"初始化 MinIO 管理器: endpoint={self.endpoint}, bucket={self.bucket_name}")
+
+    @property
+    def client(self) -> Minio:
+        """获取 MinIO 客户端(延迟初始化)"""
+        if self._client is None:
+            try:
+                self._client = Minio(
+                    self.endpoint,
+                    access_key=self.access_key,
+                    secret_key=self.secret_key,
+                    secure=self.secure
+                )
+                # 确保桶存在
+                if not self._client.bucket_exists(self.bucket_name):
+                    self._client.make_bucket(self.bucket_name)
+                logger.info("MinIO 客户端初始化成功")
+            except Exception as e:
+                logger.error(f"MinIO 客户端初始化失败: {e}")
+                raise
+        return self._client
+
+    def get_upload_url(self, filename: str, content_type: str, expires_minutes: int = 15) -> Dict[str, Any]:
+        """生成预签名上传 URL"""
+        unique_id = str(uuid.uuid4())
+        ext = os.path.splitext(filename)[1]
+        object_name = f"{self.base_path}/uploads/{datetime.now().strftime('%Y%m%d')}/{unique_id}{ext}"
+        
+        # 生成预签名 URL (PUT)
+        upload_url = self.client.presigned_put_object(
+            self.bucket_name,
+            object_name,
+            expires=timedelta(minutes=expires_minutes)
+        )
+        
+        # 构造访问 URL
+        file_url = f"http://{self.endpoint}/{self.bucket_name}/{object_name}"
+        
+        return {
+            "upload_url": upload_url,
+            "file_url": file_url,
+            "object_name": object_name
+        }
+
+    def upload_file(self, file_content: bytes, object_name: str, content_type: str = "application/octet-stream") -> str:
+        """上传文件内容到 MinIO"""
+        self.client.put_object(
+            self.bucket_name,
+            object_name,
+            io.BytesIO(file_content),
+            len(file_content),
+            content_type=content_type
+        )
+        return f"http://{self.endpoint}/{self.bucket_name}/{object_name}"
+
+def get_minio_manager() -> MinioManager:
+    """获取 MinIO 管理器单例"""
+    global _minio_manager
+    if _minio_manager is None:
+        _minio_manager = MinioManager()
+    return _minio_manager
+
+def init_minio():
+    """初始化 MinIO"""
+    return get_minio_manager()

+ 16 - 23
src/app/sample/models/base_info.py

@@ -100,31 +100,24 @@ class TechnicalTermEntity(BaseModel):
 
 
 class DocumentMain(BaseModel):
-    """文档表模型"""
+    """文档记录表模型"""
     __tablename__ = "t_samp_document_main"
 
-    title = Column(String(255), nullable=False, comment="文档标题")
-    standard_no = Column(String(100), nullable=True, comment="标准编号")
-    issuing_authority = Column(String(255), nullable=True, comment="发布单位")
-    release_date = Column(Date, nullable=True, comment="发布日期")
-    document_type = Column(String(100), nullable=True, comment="文档类型")
-    professional_field = Column(String(100), nullable=True, comment="专业领域")
-    validity = Column(String(50), default="现行", comment="时效性")
-    conversion_status = Column(TINYINT, default=0, comment="转换状态")
-    conversion_progress = Column(Integer, default=0, comment="转换进度")
-    converted_file_name = Column(String(255), nullable=True, comment="转换后文件名")
-    conversion_error = Column(Text, nullable=True, comment="转换错误信息")
-    whether_to_enter = Column(TINYINT, default=0, comment="是否录入")
-    source_type = Column(Enum('basis', 'work', 'job'), nullable=False, comment="来源类型")
-    source_id = Column(CHAR(36), nullable=False, comment="来源ID")
-    file_url = Column(Text, nullable=True, comment="文件URL")
-    file_extension = Column(String(10), nullable=True, comment="文件扩展名")
-    content = Column(Text, nullable=True, comment="文档内容")
-    primary_category_id = Column(Integer, nullable=True, comment="主分类ID")
-    secondary_category_id = Column(Integer, nullable=True, comment="次分类ID")
-    year = Column(Integer, nullable=True, comment="年份")
-    project_name = Column(String(255), nullable=True, comment="项目名称")
-    project_section = Column(String(255), nullable=True, comment="项目标段")
+    id = Column(String(36), primary_key=True, comment="主键")
+    source_type = Column(Enum('basis', 'work', 'job'), nullable=False, comment="所属类型")
+    source_id = Column(String(36), nullable=False, comment="所属ID")
+    title = Column(String(255), nullable=False, comment="文档名称")
+    conversion_status = Column(Integer, nullable=False, default=0, comment="状态: 0-待转换, 1-转换中, 2-完成, 3-失败")
+    whether_to_enter = Column(Integer, nullable=False, default=0, comment="是否入库: 0-未入库, 1-已入库")
+    conversion_error = Column(Text, nullable=True, comment="错误原因详情")
+    file_url = Column(String(500), nullable=True, comment="文件URL")
+    md_url = Column(String(500), nullable=True, comment="Markdown文件URL")
+    json_url = Column(String(500), nullable=True, comment="JSON文件URL")
+    file_extension = Column(String(10), nullable=True, comment="后缀名")
+    created_by = Column(String(36), nullable=False, comment="创建人")
+    created_time = Column(DateTime, nullable=False, default=func.now(), comment="创建时间")
+    updated_by = Column(String(36), nullable=False, comment="修改人")
+    updated_time = Column(DateTime, nullable=False, default=func.now(), onupdate=func.now(), comment="修改时间")
 
     def __repr__(self):
         return f"<DocumentMain {self.title}>"

+ 1 - 0
src/app/sample/schemas/sample_schemas.py

@@ -43,6 +43,7 @@ class DocumentAdd(BaseModel):
     project_section: Optional[str] = None
     # 文件相关字段
     file_url: Optional[str] = None
+    json_url: Optional[str] = None
     file_extension: Optional[str] = None
 
 class DocumentListRequest(BaseModel):

+ 45 - 132
src/app/services/sample_service.py

@@ -2,13 +2,11 @@
 样本中心服务层
 从 sample_view.py 提取的SQL查询逻辑
 """
-import os
 import logging
 import uuid
 from typing import Optional, List, Dict, Any, Tuple
-from datetime import datetime, timezone, timedelta
-from minio import Minio
 from app.base.async_mysql_connection import get_db_connection
+from app.base.minio_connection import get_minio_manager
 from app.core.config import config_handler
 
 logger = logging.getLogger(__name__)
@@ -31,54 +29,14 @@ class SampleService:
     
     def __init__(self):
         """初始化服务"""
-        # 初始化 MinIO 客户端
-        try:
-            self.minio_endpoint = config_handler.get("admin_app", "MINIO_ENDPOINT", "192.168.91.15:19000")
-            self.minio_access_key = config_handler.get("admin_app", "MINIO_ACCESS_KEY", "minioadmin")
-            self.minio_secret_key = config_handler.get("admin_app", "MINIO_SECRET_KEY", "minioadmin")
-            self.minio_bucket = config_handler.get("admin_app", "MINIO_BUCKET_NAME", "lq-bucket")
-            self.minio_secure = config_handler.get_bool("admin_app", "MINIO_USE_SSL", False)
-            self.minio_base_path = config_handler.get("admin_app", "MINIO_BASE_PATH", "lqadmin")
-            
-            self.minio_client = Minio(
-                self.minio_endpoint,
-                access_key=self.minio_access_key,
-                secret_key=self.minio_secret_key,
-                secure=self.minio_secure
-            )
-            # 确保桶存在
-            if not self.minio_client.bucket_exists(self.minio_bucket):
-                self.minio_client.make_bucket(self.minio_bucket)
-        except Exception as e:
-            logger.error(f"MinIO 初始化失败: {e}")
-            self.minio_client = None
+        # 使用统一的 MinIO 管理器
+        self.minio_manager = get_minio_manager()
 
     async def get_upload_url(self, filename: str, content_type: str) -> Tuple[bool, str, Dict[str, Any]]:
         """获取 MinIO 预签名上传 URL"""
-        if not self.minio_client:
-            return False, "MinIO 服务不可用", {}
-            
         try:
-            unique_id = str(uuid.uuid4())
-            ext = os.path.splitext(filename)[1]
-            object_name = f"{self.minio_base_path}/uploads/{datetime.now().strftime('%Y%m%d')}/{unique_id}{ext}"
-            
-            # 生成预签名 URL (PUT)
-            upload_url = self.minio_client.presigned_put_object(
-                self.minio_bucket,
-                object_name,
-                expires=timedelta(minutes=15)
-            )
-            
-            # 构造访问 URL
-            # 如果是开发环境,可能需要处理 endpoint 转换,这里直接拼接
-            file_url = f"http://{self.minio_endpoint}/{self.minio_bucket}/{object_name}"
-            
-            return True, "成功获取上传链接", {
-                "upload_url": upload_url,
-                "file_url": file_url,
-                "object_name": object_name
-            }
+            data = self.minio_manager.get_upload_url(filename, content_type)
+            return True, "成功获取上传链接", data
         except Exception as e:
             logger.exception("生成上传链接失败")
             return False, f"生成上传链接失败: {str(e)}", {}
@@ -112,7 +70,9 @@ class SampleService:
                 docs = cursor.fetchall()
                 
                 for doc_row in docs:
-                    d_id, s_type, s_id = doc_row
+                    d_id = doc_row['id']
+                    s_type = doc_row['source_type']
+                    s_id = doc_row['source_id']
                     if s_type and s_id:
                         sub_table = get_table_name(s_type)
                         if sub_table:
@@ -120,9 +80,9 @@ class SampleService:
                             try:
                                 cursor.execute(sub_sql, (username, s_id))
                             except Exception as sub_e:
-                                logger.error(f"更新子表 {sub_table} 失败: {sub_e}")
+                                logger.error(f"更新子表 {sub_table} 入库状态失败: {sub_e}")
             except Exception as sync_e:
-                logger.error(f"同步更新子表失败: {sync_e}")
+                logger.error(f"同步更新子表入库状态失败: {sync_e}")
             
             conn.commit()
             
@@ -159,7 +119,8 @@ class SampleService:
                 docs = cursor.fetchall()
                 
                 for doc_row in docs:
-                    s_type, s_id = doc_row
+                    s_type = doc_row['source_type']
+                    s_id = doc_row['source_id']
                     if s_type and s_id:
                         sub_table = get_table_name(s_type)
                         if sub_table:
@@ -192,9 +153,6 @@ class SampleService:
         whether_to_enter: Optional[int] = None,
         keyword: Optional[str] = None,
         table_type: Optional[str] = None,
-        primary_category_id: Optional[str] = None,
-        secondary_category_id: Optional[str] = None,
-        year: Optional[int] = None,
         page: int = 1,
         size: int = 50
     ) -> Tuple[List[Dict[str, Any]], int, int, int]:
@@ -215,18 +173,9 @@ class SampleService:
             if whether_to_enter is not None:
                 where_clauses.append("whether_to_enter = %s")
                 params.append(whether_to_enter)
-            if primary_category_id:
-                where_clauses.append("primary_category_id = %s")
-                params.append(primary_category_id)
-            if secondary_category_id:
-                where_clauses.append("secondary_category_id = %s")
-                params.append(secondary_category_id)
-            if year:
-                where_clauses.append("year = %s")
-                params.append(year)
             if keyword:
-                where_clauses.append("(title LIKE %s OR content LIKE %s OR standard_no LIKE %s OR issuing_authority LIKE %s OR document_type LIKE %s)")
-                params.extend([f"%{keyword}%", f"%{keyword}%", f"%{keyword}%", f"%{keyword}%", f"%{keyword}%"])
+                where_clauses.append("title LIKE %s")
+                params.append(f"%{keyword}%")
             
             where_sql = " WHERE " + " AND ".join(where_clauses) if where_clauses else ""
             offset = (page - 1) * size
@@ -240,7 +189,7 @@ class SampleService:
             for row in cursor.fetchall():
                 item = row # DictCursor already returns dict
                 # 格式化时间
-                for key in ['created_time', 'updated_time', 'release_date']:
+                for key in ['created_time', 'updated_time']:
                     if item.get(key) and hasattr(item[key], 'isoformat'):
                         item[key] = item[key].isoformat()
                 items.append(item)
@@ -269,7 +218,7 @@ class SampleService:
             conn.close()
     
     async def get_document_detail(self, doc_id: str) -> Optional[Dict[str, Any]]:
-        """获取文档详情(关联查询子表)"""
+        """获取文档详情"""
         conn = get_db_connection()
         if not conn:
             return None
@@ -283,35 +232,8 @@ class SampleService:
             if not doc:
                 return None
             
-            # 查询子表
-            source_type = doc.get('source_type')
-            source_id = doc.get('source_id')
-            table_name = TABLE_MAP.get(source_type)
-            
-            if table_name and source_id:
-                cursor.execute(f"SELECT * FROM {table_name} WHERE id = %s", (source_id,))
-                sub_data = cursor.fetchone()
-                if sub_data:
-                    # 将子表字段映射到通用字段名
-                    if source_type == 'basis':
-                        doc['standard_no'] = sub_data.get('standard_number')
-                        doc['issuing_authority'] = sub_data.get('issuing_authority')
-                        doc['release_date'] = str(sub_data.get('release_date')) if sub_data.get('release_date') else None
-                        doc['document_type'] = sub_data.get('document_type')
-                        doc['professional_field'] = sub_data.get('professional_field')
-                        doc['validity'] = sub_data.get('validity')
-                    elif source_type == 'work':
-                        doc['project_name'] = sub_data.get('project_name')
-                        doc['project_section'] = sub_data.get('project_section')
-                        doc['issuing_authority'] = sub_data.get('compiling_unit')
-                        doc['release_date'] = str(sub_data.get('compiling_date')) if sub_data.get('compiling_date') else None
-                    elif source_type == 'job':
-                        doc['issuing_authority'] = sub_data.get('issuing_department')
-                        doc['document_type'] = sub_data.get('document_type')
-                        doc['release_date'] = str(sub_data.get('publish_date')) if sub_data.get('publish_date') else None
-            
             # 格式化时间
-            for key in ['created_time', 'updated_time', 'release_date']:
+            for key in ['created_time', 'updated_time']:
                 if doc.get(key) and hasattr(doc[key], 'isoformat'):
                     doc[key] = doc[key].isoformat()
             
@@ -356,9 +278,6 @@ class SampleService:
             table_name = TABLE_MAP.get(table_type, "t_basis_of_preparation")
             
             # 安全转换字段
-            p_cat_id = self._to_int(doc_data.get('primary_category_id'))
-            s_cat_id = self._to_int(doc_data.get('secondary_category_id'))
-            year = self._to_int(doc_data.get('year'))
             release_date = self._to_date(doc_data.get('release_date'))
             
             # 插入子表 (会触发数据库触发器自动向 t_samp_document_main 插入记录)
@@ -388,11 +307,9 @@ class SampleService:
             # 更新主表中触发器未处理的字段
             cursor.execute("""
                 UPDATE t_samp_document_main 
-                SET primary_category_id = %s, secondary_category_id = %s, year = %s,
-                    file_extension = %s, project_name = %s, project_section = %s
+                SET file_extension = %s
                 WHERE id = %s
-            """, (p_cat_id, s_cat_id, year, doc_data.get('file_extension'),
-                  doc_data.get('project_name'), doc_data.get('project_section'), doc_id))
+            """, (doc_data.get('file_extension'), doc_id))
             
             conn.commit()
             return True, "文档添加成功", doc_id
@@ -419,9 +336,6 @@ class SampleService:
             table_name = TABLE_MAP.get(table_type, "t_samp_standard_base_info")
             
             # 安全转换字段
-            p_cat_id = self._to_int(doc_data.get('primary_category_id'))
-            s_cat_id = self._to_int(doc_data.get('secondary_category_id'))
-            year = self._to_int(doc_data.get('year'))
             release_date = self._to_date(doc_data.get('release_date'))
             
             # 1. 更新子表 (会触发数据库触发器更新 t_samp_document_main 的基本字段)
@@ -444,12 +358,10 @@ class SampleService:
             # 2. 更新主表中触发器未处理的字段(或者被触发器覆盖的非子表字段)
             cursor.execute("""
                 UPDATE t_samp_document_main 
-                SET primary_category_id = %s, secondary_category_id = %s, year = %s,
-                    file_extension = %s, project_name = %s, project_section = %s,
+                SET file_extension = %s,
                     updated_by = %s, updated_time = NOW()
                 WHERE id = %s
-            """, (p_cat_id, s_cat_id, year, 
-                  doc_data.get('file_extension'), doc_data.get('project_name'), doc_data.get('project_section'),
+            """, (doc_data.get('file_extension'),
                   updater_id, doc_id))
 
             
@@ -596,7 +508,7 @@ class SampleService:
         try:
             cursor.execute("SELECT source_type FROM t_samp_document_main WHERE id = %s", (doc_id,))
             res = cursor.fetchone()
-            return res[0] if res else None
+            return res['source_type'] if res else None
         except Exception as e:
             logger.exception(f"获取文档source_type失败: {e}")
             return None
@@ -615,7 +527,7 @@ class SampleService:
         try:
             cursor.execute("SELECT title FROM t_samp_document_main WHERE id = %s", (doc_id,))
             res = cursor.fetchone()
-            return res[0] if res else "文档"
+            return res['title'] if res else "文档"
         except Exception as e:
             logger.exception(f"获取文档标题失败: {e}")
             return "文档"
@@ -623,16 +535,17 @@ class SampleService:
             cursor.close()
             conn.close()
     
-    async def update_conversion_progress(self, doc_id: str, status: int, progress: int, 
-                                        converted_file_name: Optional[str] = None,
+    async def update_conversion_progress(self, doc_id: str, status: int, 
+                                        md_url: Optional[str] = None,
+                                        json_url: Optional[str] = None,
                                         error_message: Optional[str] = None) -> bool:
         """更新文档转换进度
         
         Args:
             doc_id: 文档ID
             status: 转换状态 (0=未转换, 1=转换中, 2=已完成, 3=失败)
-            progress: 转换进度 (0-100)
-            converted_file_name: 转换后的文件名
+            md_url: Markdown文件URL
+            json_url: JSON文件URL
             error_message: 错误信息
         """
         conn = get_db_connection()
@@ -642,25 +555,25 @@ class SampleService:
         cursor = conn.cursor()
         
         try:
+            update_clauses = ["conversion_status = %s"]
+            params = [status]
+
             if error_message:
-                # 转换失败
-                cursor.execute(
-                    "UPDATE t_samp_document_main SET conversion_status = %s, conversion_error = %s WHERE id = %s",
-                    (status, error_message, doc_id)
-                )
-            elif converted_file_name:
-                # 转换完成
-                cursor.execute(
-                    "UPDATE t_samp_document_main SET conversion_status = %s, conversion_progress = %s, converted_file_name = %s WHERE id = %s",
-                    (status, progress, converted_file_name, doc_id)
-                )
-            else:
-                # 更新进度
-                cursor.execute(
-                    "UPDATE t_samp_document_main SET conversion_status = %s, conversion_progress = %s WHERE id = %s",
-                    (status, progress, doc_id)
-                )
+                update_clauses.append("conversion_error = %s")
+                params.append(error_message)
             
+            if md_url:
+                update_clauses.append("md_url = %s")
+                params.append(md_url)
+            
+            if json_url:
+                update_clauses.append("json_url = %s")
+                params.append(json_url)
+            
+            sql = f"UPDATE t_samp_document_main SET {', '.join(update_clauses)}, updated_time = NOW() WHERE id = %s"
+            params.append(doc_id)
+            
+            cursor.execute(sql, tuple(params))
             conn.commit()
             return True
         except Exception as e:

+ 5 - 13
src/views/sample_view.py

@@ -220,8 +220,10 @@ async def simulate_conversion(doc_id: str):
         # 4. 模拟完成 (100%)
         # 模拟云端存储地址
         converted_file_name = f"http://192.168.91.15:19000/aidata/sampledata/converted/simulated/{doc_id}.pdf"
+        json_url = f"http://192.168.91.15:19000/aidata/sampledata/converted/simulated/{doc_id}.json"
         await sample_service.update_conversion_progress(doc_id, status=2, progress=100, 
-                                                       converted_file_name=converted_file_name)
+                                                       converted_file_name=converted_file_name,
+                                                       json_url=json_url)
         
     except Exception as e:
         logger.exception("模拟转换出错")
@@ -287,6 +289,7 @@ async def add_document(doc: DocumentAdd, credentials: HTTPAuthorizationCredentia
             'secondary_category_id': doc.secondary_category_id,
             'year': doc.year,
             'file_url': doc.file_url,
+            'json_url': doc.json_url,
             'file_extension': doc.file_extension,
             'standard_no': doc.standard_no,
             'issuing_authority': doc.issuing_authority,
@@ -336,9 +339,6 @@ async def get_document_list(
     whether_to_enter: Optional[int] = None,
     keyword: Optional[str] = None,
     table_type: Optional[str] = None,
-    primary_category_id: Optional[str] = None,
-    secondary_category_id: Optional[str] = None,
-    year: Optional[int] = None,
     page: int = 1, 
     size: int = 50,
     credentials: HTTPAuthorizationCredentials = Depends(security)
@@ -354,9 +354,6 @@ async def get_document_list(
             whether_to_enter=whether_to_enter,
             keyword=keyword,
             table_type=table_type,
-            primary_category_id=primary_category_id,
-            secondary_category_id=secondary_category_id,
-            year=year,
             page=page,
             size=size
         )
@@ -403,6 +400,7 @@ async def edit_document(doc: DocumentAdd, credentials: HTTPAuthorizationCredenti
             'secondary_category_id': doc.secondary_category_id,
             'year': doc.year,
             'file_url': doc.file_url,
+            'json_url': doc.json_url,
             'file_extension': doc.file_extension,
             'standard_no': doc.standard_no if hasattr(doc, 'standard_no') else None,
             'issuing_authority': doc.issuing_authority if hasattr(doc, 'issuing_authority') else None,
@@ -605,9 +603,6 @@ async def get_secondary_categories(primaryId: str, credentials: HTTPAuthorizatio
 @router.get("/documents/search")
 async def search_documents(
     keyword: str, 
-    primaryCategoryId: Optional[str] = None,
-    secondaryCategoryId: Optional[str] = None,
-    year: Optional[int] = None,
     whether_to_enter: Optional[int] = None,
     table_type: Optional[str] = "basis",
     page: int = 1, 
@@ -616,9 +611,6 @@ async def search_documents(
 ):
     """关键词搜索文档,统一调用 get_document_list 以支持组合过滤"""
     return await get_document_list(
-        primary_category_id=primaryCategoryId,
-        secondary_category_id=secondaryCategoryId,
-        year=year,
         whether_to_enter=whether_to_enter,
         keyword=keyword,
         table_type=table_type,