package com.lingyue.document.controller; import com.lingyue.common.domain.AjaxResult; import com.lingyue.document.dto.StructuredDocumentDTO; import com.lingyue.document.entity.DocumentBlock; import com.lingyue.document.entity.DocumentBlock.TextElement; import com.lingyue.document.service.StructuredDocumentService; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Parameter; import io.swagger.v3.oas.annotations.tags.Tag; import lombok.Data; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.web.bind.annotation.*; import java.util.List; /** * 结构化文档控制器(参考飞书设计) * 提供编辑器所需的结构化文档和实体标注接口 * * 核心设计: * - 文档由 Block 树组成 * - 实体作为 TextElement 嵌入块中 * - 编辑实体 = 修改块的 elements 数组 * * @author lingyue * @since 2026-01-21 */ @Slf4j @RestController @RequestMapping("/api/v1/documents") @RequiredArgsConstructor @Tag(name = "结构化文档", description = "文档编辑器接口(参考飞书设计)") public class StructuredDocumentController { private final StructuredDocumentService structuredDocumentService; /** * 获取结构化文档 */ @GetMapping("/{documentId}/structured") @Operation(summary = "获取结构化文档", description = "获取带实体标注的结构化文档,用于编辑器渲染") public AjaxResult getStructuredDocument( @Parameter(description = "文档ID", required = true) @PathVariable String documentId) { StructuredDocumentDTO document = structuredDocumentService.getStructuredDocument(documentId); if (document == null) { return AjaxResult.error("文档不存在: " + documentId); } return AjaxResult.success(document); } // ==================== 块操作 ==================== /** * 更新块的元素 */ @PutMapping("/{documentId}/blocks/{blockId}/elements") @Operation(summary = "更新块元素", description = "更新块的 elements 数组") public AjaxResult updateBlockElements( @PathVariable String documentId, @PathVariable String blockId, @RequestBody UpdateElementsRequest request) { try { structuredDocumentService.updateBlockElements(blockId, request.getElements()); return AjaxResult.success("更新成功"); } catch (Exception e) { log.error("更新块元素失败: blockId={}", blockId, e); return AjaxResult.error("更新失败: " + e.getMessage()); } } /** * 创建新块 */ @PostMapping("/{documentId}/blocks") @Operation(summary = "创建块", description = "在文档中创建新块") public AjaxResult createBlock( @PathVariable String documentId, @RequestBody CreateBlockRequest request) { try { DocumentBlock block = structuredDocumentService.createBlock( documentId, request.getParentId(), request.getIndex(), request.getBlockType(), request.getElements() ); return AjaxResult.success("创建成功", block); } catch (Exception e) { log.error("创建块失败: documentId={}", documentId, e); return AjaxResult.error("创建失败: " + e.getMessage()); } } /** * 删除块 */ @DeleteMapping("/{documentId}/blocks/{blockId}") @Operation(summary = "删除块", description = "删除指定块及其子块") public AjaxResult deleteBlock( @PathVariable String documentId, @PathVariable String blockId) { structuredDocumentService.deleteBlock(blockId); return AjaxResult.success("删除成功"); } // ==================== 实体操作 ==================== /** * 标记实体(将文本转为实体) */ @PostMapping("/{documentId}/blocks/{blockId}/mark-entity") @Operation(summary = "标记实体", description = "将块中的文本片段标记为实体") public AjaxResult markEntity( @PathVariable String documentId, @PathVariable String blockId, @RequestBody MarkEntityRequest request) { try { String entityId = structuredDocumentService.markEntity( blockId, request.getElementIndex(), request.getStartOffset(), request.getEndOffset(), request.getEntityType() ); return AjaxResult.success("标记成功", entityId); } catch (Exception e) { log.error("标记实体失败: blockId={}", blockId, e); return AjaxResult.error("标记失败: " + e.getMessage()); } } /** * 取消实体标记(将实体还原为文本) */ @DeleteMapping("/{documentId}/blocks/{blockId}/entities/{entityId}") @Operation(summary = "取消实体标记", description = "将实体还原为普通文本") public AjaxResult unmarkEntity( @PathVariable String documentId, @PathVariable String blockId, @PathVariable String entityId) { structuredDocumentService.unmarkEntity(blockId, entityId); return AjaxResult.success("取消成功"); } /** * 更新实体类型 */ @PutMapping("/{documentId}/blocks/{blockId}/entities/{entityId}") @Operation(summary = "更新实体", description = "更新实体类型") public AjaxResult updateEntity( @PathVariable String documentId, @PathVariable String blockId, @PathVariable String entityId, @RequestBody UpdateEntityRequest request) { structuredDocumentService.updateEntityType(blockId, entityId, request.getEntityType()); return AjaxResult.success("更新成功"); } /** * 确认实体 */ @PostMapping("/{documentId}/blocks/{blockId}/entities/{entityId}/confirm") @Operation(summary = "确认实体", description = "确认实体标注正确") public AjaxResult confirmEntity( @PathVariable String documentId, @PathVariable String blockId, @PathVariable String entityId) { structuredDocumentService.confirmEntity(blockId, entityId); return AjaxResult.success("确认成功"); } // ==================== 请求 DTO ==================== @Data public static class UpdateElementsRequest { private List elements; } @Data public static class CreateBlockRequest { private String parentId; private Integer index; private String blockType; private List elements; } @Data public static class MarkEntityRequest { /** 要标记的元素在 elements 数组中的索引 */ private Integer elementIndex; /** 在该元素文本中的起始位置 */ private Integer startOffset; /** 在该元素文本中的结束位置 */ private Integer endOffset; /** 实体类型 */ private String entityType; } @Data public static class UpdateEntityRequest { private String entityType; } }