package com.dms.mapper; import com.dms.model.DocumentEntity; import java.time.LocalDateTime; import java.util.List; import java.util.Map; import org.apache.ibatis.annotations.Insert; import org.apache.ibatis.annotations.Options; import org.apache.ibatis.annotations.Param; import org.apache.ibatis.annotations.Select; import org.apache.ibatis.annotations.Update; /** doc_document 表访问(按阶段逐步扩展)。 */ public interface DocumentMapper { /** 路径级联:同步挂载文档的 organization_name 快照(row_version+1)。 */ @Update("UPDATE doc_document SET organization_name = #{orgName}, " + "updated_by = #{actorId}, updated_at = #{now}, " + "row_version = row_version + 1 " + "WHERE organization_id = #{organizationId} AND is_deleted = 0") int syncOrganizationName(@Param("organizationId") long organizationId, @Param("orgName") String orgName, @Param("actorId") Long actorId, @Param("now") LocalDateTime now); // ------------------------------------------------------------ 基础读取(阶段5.2/5.3) @Select("SELECT * FROM doc_document WHERE id = #{id} AND is_deleted = 0") DocumentEntity findByIdNotDeleted(@Param("id") long id); /** 子方案权限源:root 主案(有效 MAIN)。 */ @Select("SELECT * FROM doc_document WHERE id = #{id} AND document_type = 'MAIN' " + "AND is_deleted = 0") DocumentEntity findActiveMainById(@Param("id") long id); /** 分类父节点检查:直挂有效主案数(_valid_parent / update_category)。 */ @Select("SELECT COUNT(*) FROM doc_document WHERE category_id = #{categoryId} " + "AND document_type = 'MAIN' AND is_deleted = 0") long countActiveMainByCategory(@Param("categoryId") long categoryId); /** 分类删除占用:直挂方案(MAIN+SUB_PLAN)数。 */ @Select("SELECT COUNT(*) FROM doc_document WHERE category_id = #{categoryId} " + "AND is_deleted = 0 AND document_type IN ('MAIN', 'SUB_PLAN')") long countActivePlansByCategory(@Param("categoryId") long categoryId); /** 分类路径级联:同步直挂方案快照(rv+1),返回受影响行数。 */ @Update("UPDATE doc_document SET category_name = #{categoryName}, " + "category_path = #{categoryPath}, updated_by = #{actorId}, " + "updated_at = #{now}, row_version = row_version + 1 " + "WHERE category_id = #{categoryId} AND is_deleted = 0 " + "AND document_type IN ('MAIN', 'SUB_PLAN')") int syncCategorySnapshot(@Param("categoryId") long categoryId, @Param("categoryName") String categoryName, @Param("categoryPath") String categoryPath, @Param("actorId") Long actorId, @Param("now") LocalDateTime now); // ------------------------------------------------------------ 密码锁(阶段5.3) /** 密码锁维护目标:锁定有效主案行。 */ @Select("SELECT * FROM doc_document WHERE id = #{id} AND document_type = 'MAIN' " + "AND is_deleted = 0 FOR UPDATE") DocumentEntity findActiveMainForUpdate(@Param("id") long id); /** 密码锁整体写入(哈希 + 7 勾选位 + rv+1)。 */ @Update("UPDATE doc_document SET password_hash = #{passwordHash}, " + "protect_view = #{protectView}, protect_download = #{protectDownload}, " + "protect_edit = #{protectEdit}, protect_delete = #{protectDelete}, " + "protect_config_permission = #{protectConfigPermission}, " + "protect_bind_attachment = #{protectBindAttachment}, " + "protect_unbind_attachment = #{protectUnbindAttachment}, " + "updated_by = #{updatedBy}, updated_at = #{updatedAt}, " + "row_version = row_version + 1 WHERE id = #{id}") int updatePasswordLock(DocumentEntity document); // ------------------------------------------------------------ 只读查询(阶段5.4) /** * 主案/子方案通用动态查询,对齐 document_query_service 的组合过滤: * types 必填;categoryIds/parentDocumentId/parentIds/titlePattern/contentPattern/ * securityLevels/orgPattern/tags(配tagsMatch)/securityLevel/status/updated范围可选; * orderBy 为服务层白名单校验后的排序子串,缺省不排序(对齐 Python 无 order_by 场景)。 */ @Select("") List listByFilters(Map filters); /** 对齐 _active_plan:详情目标(MAIN/SUB_PLAN)。 */ @Select("SELECT * FROM doc_document WHERE id = #{id} AND is_deleted = 0 " + "AND document_type IN ('MAIN', 'SUB_PLAN')") DocumentEntity findActivePlanById(@Param("id") long id); /** _record_view 锁定行。 */ @Select("SELECT * FROM doc_document WHERE id = #{id} AND is_deleted = 0 FOR UPDATE") DocumentEntity findNotDeletedForUpdate(@Param("id") long id); /** _record_view 查看计数自增。 */ @Update("UPDATE doc_document SET view_count = view_count + 1 WHERE id = #{id}") int incrementViewCount(@Param("id") long id); // ------------------------------------------------------------ 写操作(阶段5.5) /** 新建方案/附件(密码锁/计数等由库默认承担,对齐 Python session.add)。 */ @Insert("INSERT INTO doc_document (document_name, summary, document_type, " + "document_status, security_level, visibility_type, attachment_type, " + "category_id, category_name, category_path, organization_id, " + "organization_name, parent_document_id, root_document_id, tags, " + "original_file_name, file_relative_path, file_extension, mime_type, " + "file_size, file_hash, sort_no, content_text, content_extract_status, " + "content_extract_error, content_extracted_at, search_text, created_by, updated_by, " + "created_by_name, updated_by_name) VALUES (#{documentName}, #{summary}, " + "#{documentType}, #{documentStatus}, #{securityLevel}, #{visibilityType}, " + "#{attachmentType}, #{categoryId}, #{categoryName}, #{categoryPath}, " + "#{organizationId}, #{organizationName}, #{parentDocumentId}, " + "#{rootDocumentId}, #{tags}, #{originalFileName}, #{fileRelativePath}, " + "#{fileExtension}, #{mimeType}, #{fileSize}, #{fileHash}, #{sortNo}, " + "#{contentText}, #{contentExtractStatus}, #{contentExtractError}, " + "#{contentExtractedAt}, " + "#{searchText}, #{createdBy}, #{updatedBy}, #{createdByName}, " + "#{updatedByName})") @Options(useGeneratedKeys = true, keyProperty = "id") int insert(DocumentEntity document); /** 上传查重锁定:同名同类型未删(SUB_PLAN 限定父内,MAIN 全局),对齐 Python duplicate 条件。 */ @Select("") DocumentEntity findDuplicateForUpdate(@Param("name") String name, @Param("type") String type, @Param("parentId") Long parentId); /** 覆盖上传:全文件与元数据字段 + search_text + rv+1(document_name 不变)。 */ @Update("UPDATE doc_document SET summary = #{summary}, security_level = #{securityLevel}, " + "category_id = #{categoryId}, category_name = #{categoryName}, " + "category_path = #{categoryPath}, organization_id = #{organizationId}, " + "organization_name = #{organizationName}, tags = #{tags}, " + "original_file_name = #{originalFileName}, " + "file_relative_path = #{fileRelativePath}, file_extension = #{fileExtension}, " + "mime_type = #{mimeType}, file_size = #{fileSize}, file_hash = #{fileHash}, " + "content_text = #{contentText}, content_extract_status = #{contentExtractStatus}, " + "content_extract_error = #{contentExtractError}, " + "content_extracted_at = #{contentExtractedAt}, search_text = #{searchText}, " + "updated_by = #{updatedBy}, updated_by_name = #{updatedByName}, " + "updated_at = #{updatedAt}, row_version = row_version + 1 WHERE id = #{id}") int updateOverwrite(DocumentEntity document); /** 上传时密码锁写入:仅密码字段(rv 不递增,对齐 Python 上传后 apply_password_lock 语义)。 */ @Update("UPDATE doc_document SET password_hash = #{passwordHash}, " + "protect_view = #{protectView}, protect_download = #{protectDownload}, " + "protect_edit = #{protectEdit}, protect_delete = #{protectDelete}, " + "protect_config_permission = #{protectConfigPermission}, " + "protect_bind_attachment = #{protectBindAttachment}, " + "protect_unbind_attachment = #{protectUnbindAttachment} WHERE id = #{id}") int insertPasswordLock(DocumentEntity document); /** MAIN 换分类:子方案分类快照级联(rv+1)。 */ @Update("UPDATE doc_document SET category_id = #{categoryId}, " + "category_name = #{categoryName}, category_path = #{categoryPath}, " + "updated_by = #{updatedBy}, updated_by_name = #{updatedByName}, " + "updated_at = #{updatedAt}, row_version = row_version + 1 " + "WHERE parent_document_id = #{mainId} AND document_type = 'SUB_PLAN' " + "AND is_deleted = 0") int syncSubPlanCategory(@Param("mainId") long mainId, @Param("categoryId") Long categoryId, @Param("categoryName") String categoryName, @Param("categoryPath") String categoryPath, @Param("updatedBy") Long updatedBy, @Param("updatedByName") String updatedByName, @Param("updatedAt") LocalDateTime updatedAt); /** 新建主案排序末尾:全局 MAIN 最大 sort_no。 */ @Select("SELECT COALESCE(MAX(sort_no), 0) FROM doc_document " + "WHERE document_type = 'MAIN' AND parent_document_id IS NULL AND is_deleted = 0") int maxMainSortNo(); /** 新建子方案排序末尾:父内 SUB_PLAN 最大 sort_no。 */ @Select("SELECT COALESCE(MAX(sort_no), 0) FROM doc_document " + "WHERE document_type = 'SUB_PLAN' AND parent_document_id = #{parentId} " + "AND is_deleted = 0") int maxSubPlanSortNo(@Param("parentId") long parentId); /** 方案编辑:元数据字段 + search_text + rv+1(对齐 update_document)。 */ @Update("UPDATE doc_document SET document_name = #{documentName}, summary = #{summary}, " + "tags = #{tags}, category_id = #{categoryId}, category_name = #{categoryName}, " + "category_path = #{categoryPath}, organization_id = #{organizationId}, " + "organization_name = #{organizationName}, security_level = #{securityLevel}, " + "visibility_type = #{visibilityType}, search_text = #{searchText}, " + "updated_by = #{updatedBy}, updated_by_name = #{updatedByName}, " + "updated_at = #{updatedAt}, row_version = row_version + 1 WHERE id = #{id}") int updateMeta(DocumentEntity document); /** 附件编辑:元数据字段 + search_text + rv+1(对齐 update_attachment)。 */ @Update("UPDATE doc_document SET document_name = #{documentName}, " + "attachment_type = #{attachmentType}, summary = #{summary}, tags = #{tags}, " + "organization_id = #{organizationId}, organization_name = #{organizationName}, " + "search_text = #{searchText}, updated_by = #{updatedBy}, " + "updated_by_name = #{updatedByName}, updated_at = #{updatedAt}, " + "row_version = row_version + 1 WHERE id = #{id}") int updateAttachmentMeta(DocumentEntity document); /** 描述编辑:仅 description(rv+1,对齐 update_document_description)。 */ @Update("UPDATE doc_document SET description = #{description}, " + "updated_by = #{updatedBy}, updated_by_name = #{updatedByName}, " + "updated_at = #{updatedAt}, row_version = row_version + 1 WHERE id = #{id}") int updateDescription(DocumentEntity document); /** * re_extract_content:覆盖写回提取结果(含失败原因)并同步重建 search_text, * touch updated_by/name/at 与 rv+1。 */ @Update("UPDATE doc_document SET content_text = #{contentText}, " + "content_extract_status = #{contentExtractStatus}, " + "content_extract_error = #{contentExtractError}, " + "content_extracted_at = #{contentExtractedAt}, search_text = #{searchText}, " + "updated_by = #{updatedBy}, updated_by_name = #{updatedByName}, " + "updated_at = #{updatedAt}, row_version = row_version + 1 WHERE id = #{id}") int updateContentExtraction(DocumentEntity document); /** update_document 锁定行(MAIN/SUB_PLAN)。 */ @Select("SELECT * FROM doc_document WHERE id = #{id} " + "AND document_type IN ('MAIN', 'SUB_PLAN') AND is_deleted = 0 FOR UPDATE") DocumentEntity findPlanForUpdate(@Param("id") long id); /** update_attachment/delete_attachment 锁定行。 */ @Select("SELECT * FROM doc_document WHERE id = #{id} AND document_type = 'ATTACHMENT' " + "AND is_deleted = 0 FOR UPDATE") DocumentEntity findAttachmentForUpdate(@Param("id") long id); /** get_attachment 读取行(无锁)。 */ @Select("SELECT * FROM doc_document WHERE id = #{id} AND document_type = 'ATTACHMENT' " + "AND is_deleted = 0") DocumentEntity findActiveAttachmentById(@Param("id") long id); /** 对齐 session.get(Document, id):主键读取,不过滤任何状态。 */ @Select("SELECT * FROM doc_document WHERE id = #{id}") DocumentEntity findByIdNoFilter(@Param("id") long id); /** 新建子方案:父 child_count+1、rv+1、updated_by/by_name(不触碰 updated_at)。 */ @Update("UPDATE doc_document SET child_count = child_count + 1, " + "updated_by = #{updatedBy}, updated_by_name = #{updatedByName}, " + "row_version = row_version + 1 WHERE id = #{id}") int incrementChildCount(@Param("id") long id, @Param("updatedBy") Long updatedBy, @Param("updatedByName") String updatedByName); /** 删除子方案:父 child_count=GREATEST(0,-1)、rv+1、updated_*。 */ @Update("UPDATE doc_document SET child_count = GREATEST(0, child_count - 1), " + "updated_by = #{updatedBy}, updated_by_name = #{updatedByName}, " + "updated_at = #{updatedAt}, row_version = row_version + 1 WHERE id = #{id}") int decrementChildCount(@Param("id") long id, @Param("updatedBy") Long updatedBy, @Param("updatedByName") String updatedByName, @Param("updatedAt") LocalDateTime updatedAt); /** 删除主案:attachment_count 清零。 */ @Update("UPDATE doc_document SET attachment_count = 0 WHERE id = #{id}") int setAttachmentCountZero(@Param("id") long id); /** 删除主案前置:有效子方案计数(parent 或 root 指向主案)。 */ @Select("SELECT COUNT(*) FROM doc_document WHERE (parent_document_id = #{id} " + "OR root_document_id = #{id}) AND document_type = 'SUB_PLAN' AND is_deleted = 0") long countSubPlansByMain(@Param("id") long id); /** 软删文档:is_deleted、deleted_at、updated_by、updated_at、rv+1。 */ @Update("UPDATE doc_document SET is_deleted = 1, deleted_at = #{now}, " + "updated_at = #{now}, updated_by = #{actorId}, updated_by_name = #{actorName}, " + "row_version = row_version + 1 WHERE id = #{id}") int softDelete(@Param("id") long id, @Param("now") LocalDateTime now, @Param("actorId") Long actorId, @Param("actorName") String actorName); /** 重排:ids 集合行锁定(含软删行,对齐 Python select...in 不加 is_deleted 过滤)。 */ @Select("") List listForUpdateByIds(@Param("ids") List ids); /** 重排:兄弟全集锁定(MAIN 全局 / SUB_PLAN 父内)。 */ @Select("") List listSiblingsForOrder(@Param("parentId") Long parentId); /** 重排:仅写变化行(sort_no + updated_* + rv+1)。 */ @Update("UPDATE doc_document SET sort_no = #{sortNo}, updated_by = #{updatedBy}, " + "updated_by_name = #{updatedByName}, updated_at = #{updatedAt}, " + "row_version = row_version + 1 WHERE id = #{id}") int updateSortNo(@Param("id") long id, @Param("sortNo") int sortNo, @Param("updatedBy") Long updatedBy, @Param("updatedByName") String updatedByName, @Param("updatedAt") LocalDateTime updatedAt); // ------------------------------------------------------------ 下载/预览与挂载域(阶段5.6) /** 预览/下载入口:三类通用有效行(对齐 _document)。 */ @Select("SELECT * FROM doc_document WHERE id = #{id} AND is_deleted = 0 " + "AND document_type IN ('MAIN', 'SUB_PLAN', 'ATTACHMENT')") DocumentEntity findReadableById(@Param("id") long id); /** _record_download 查看计数同款:下载计数自增。 */ @Update("UPDATE doc_document SET download_count = download_count + 1 WHERE id = #{id}") int incrementDownloadCount(@Param("id") long id); /** 挂载/解挂后主案回写:attachment_count + rv+1 + updated_*。 */ @Update("UPDATE doc_document SET attachment_count = #{attachmentCount}, " + "row_version = row_version + 1, updated_by = #{updatedBy}, " + "updated_by_name = #{updatedByName}, updated_at = #{updatedAt} WHERE id = #{id}") int updateAttachmentCount(@Param("id") long id, @Param("attachmentCount") long attachmentCount, @Param("updatedBy") Long updatedBy, @Param("updatedByName") String updatedByName, @Param("updatedAt") LocalDateTime updatedAt); /** 挂载重排后主案元数据触碰:rv+1 + updated_*(不动 attachment_count)。 */ @Update("UPDATE doc_document SET row_version = row_version + 1, " + "updated_by = #{updatedBy}, updated_by_name = #{updatedByName}, " + "updated_at = #{updatedAt} WHERE id = #{id}") int bumpMeta(@Param("id") long id, @Param("updatedBy") Long updatedBy, @Param("updatedByName") String updatedByName, @Param("updatedAt") LocalDateTime updatedAt); /** * has_any_manage_action:任一"已发布主案"命中(密级×部门范围×查看权三来源)。 * scope 为 OWN/OTHER;集合为空时调用方传 [-1] 占位(对齐 Python * in_(空集)恒假、not_in(空集)恒真语义);canView=1 时矩阵行自身即查看来源。 */ @Select("") boolean existsManageHit(@Param("securityLevel") String securityLevel, @Param("scope") String scope, @Param("ownIds") List ownIds, @Param("ancestorIds") List ancestorIds, @Param("userId") long userId, @Param("canView") boolean canView); /** _current_version:挂载唯一键冲突兜底的当前版本读取。 */ @Select("SELECT row_version FROM doc_document WHERE id = #{id} AND is_deleted = 0") Integer currentRowVersion(@Param("id") long id); /** 权限保存:visibility + updated_* + rv+1(对齐 save_permissions 的文档 touch)。 */ @Update("UPDATE doc_document SET visibility_type = #{visibility}, " + "updated_by = #{updatedBy}, updated_by_name = #{updatedByName}, " + "updated_at = #{now}, row_version = row_version + 1 WHERE id = #{id}") int updateVisibility(@Param("id") long id, @Param("visibility") String visibility, @Param("updatedBy") Long updatedBy, @Param("updatedByName") String updatedByName, @Param("now") LocalDateTime now); // ------------------------------------------------------------ 回收站(阶段7,对齐 recycle_bin_service.py) /** * 回收站分页查询(对齐 list_recycle_bin_documents):软删 + deleted_at 非空 + * 类型白名单;可选 documentType/categoryId/keyword 四字段 LIKE/deletedFrom≥/deletedTo<。 * orderBy/idOrder 为服务层白名单校验后的 "列 方向" 子串。 */ @Select("") List listDeletedPage(@Param("f") Map filters); /** 回收站计数(与 listDeletedPage 同条件,无分页/排序)。 */ @Select("") long countDeletedPage(@Param("f") Map filters); /** 对齐 _restore_operation 行锁读:主键 FOR UPDATE,不过滤删除状态。 */ @Select("SELECT * FROM doc_document WHERE id = #{id} FOR UPDATE") DocumentEntity findByIdForUpdate(@Param("id") long id); /** 恢复后主案子案重数(AND 语义,区别于 countSubPlansByMain 的 OR 版)。 */ @Select("SELECT COUNT(*) FROM doc_document WHERE document_type = 'SUB_PLAN' " + "AND parent_document_id = #{mainId} AND root_document_id = #{mainId} " + "AND is_deleted = 0") long countActiveSubPlansUnderMain(@Param("mainId") long mainId); /** 主案恢复批次附件锁定(对齐 attachments 锁读:in + id 升序 FOR UPDATE,不过滤)。 */ @Select("") List listByIdsForUpdate(@Param("ids") java.util.Collection ids); /** 子方案恢复:继承主案分类快照(对齐 Python 对象赋值 flush)。 */ @Update("UPDATE doc_document SET category_id = #{categoryId}, " + "category_name = #{categoryName}, category_path = #{categoryPath} " + "WHERE id = #{id}") int updateCategorySnapshot(@Param("id") long id, @Param("categoryId") Long categoryId, @Param("categoryName") String categoryName, @Param("categoryPath") String categoryPath); /** 文档复活:is_deleted=0、deleted_at=NULL、updated_*、rv+1。 */ @Update("UPDATE doc_document SET is_deleted = 0, deleted_at = NULL, " + "updated_at = #{now}, updated_by = #{actorId}, " + "updated_by_name = #{actorName}, row_version = row_version + 1 " + "WHERE id = #{id}") int restoreDocument(@Param("id") long id, @Param("now") LocalDateTime now, @Param("actorId") Long actorId, @Param("actorName") String actorName); /** * 主案恢复后 child_count/attachment_count 重数(对齐 SQLAlchemy 仅变化时落库: * 值不变则 0 行受影响,不触碰 updated_*)。 */ @Update("UPDATE doc_document SET child_count = #{childCount}, " + "attachment_count = #{attachmentCount} WHERE id = #{id} " + "AND (child_count != #{childCount} OR attachment_count != #{attachmentCount})") int restoreMainCounts(@Param("id") long id, @Param("childCount") long childCount, @Param("attachmentCount") long attachmentCount); /** 子方案恢复后主案 touch:child_count 重数 + updated_* + rv+1(无条件)。 */ @Update("UPDATE doc_document SET child_count = #{childCount}, " + "updated_at = #{now}, updated_by = #{actorId}, " + "updated_by_name = #{actorName}, row_version = row_version + 1 " + "WHERE id = #{id}") int restoreParentCounts(@Param("id") long id, @Param("childCount") long childCount, @Param("now") LocalDateTime now, @Param("actorId") Long actorId, @Param("actorName") String actorName); }