StructuredDocumentController.java 7.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215
  1. package com.lingyue.document.controller;
  2. import com.lingyue.common.domain.AjaxResult;
  3. import com.lingyue.document.dto.StructuredDocumentDTO;
  4. import com.lingyue.document.entity.DocumentBlock;
  5. import com.lingyue.document.entity.DocumentBlock.TextElement;
  6. import com.lingyue.document.service.StructuredDocumentService;
  7. import io.swagger.v3.oas.annotations.Operation;
  8. import io.swagger.v3.oas.annotations.Parameter;
  9. import io.swagger.v3.oas.annotations.tags.Tag;
  10. import lombok.Data;
  11. import lombok.RequiredArgsConstructor;
  12. import lombok.extern.slf4j.Slf4j;
  13. import org.springframework.web.bind.annotation.*;
  14. import java.util.List;
  15. /**
  16. * 结构化文档控制器(参考飞书设计)
  17. * 提供编辑器所需的结构化文档和实体标注接口
  18. *
  19. * 核心设计:
  20. * - 文档由 Block 树组成
  21. * - 实体作为 TextElement 嵌入块中
  22. * - 编辑实体 = 修改块的 elements 数组
  23. *
  24. * @author lingyue
  25. * @since 2026-01-21
  26. */
  27. @Slf4j
  28. @RestController
  29. @RequestMapping("/api/v1/documents")
  30. @RequiredArgsConstructor
  31. @Tag(name = "结构化文档", description = "文档编辑器接口(参考飞书设计)")
  32. public class StructuredDocumentController {
  33. private final StructuredDocumentService structuredDocumentService;
  34. /**
  35. * 获取结构化文档
  36. */
  37. @GetMapping("/{documentId}/structured")
  38. @Operation(summary = "获取结构化文档", description = "获取带实体标注的结构化文档,用于编辑器渲染")
  39. public AjaxResult<?> getStructuredDocument(
  40. @Parameter(description = "文档ID", required = true)
  41. @PathVariable String documentId) {
  42. StructuredDocumentDTO document = structuredDocumentService.getStructuredDocument(documentId);
  43. if (document == null) {
  44. return AjaxResult.error("文档不存在: " + documentId);
  45. }
  46. return AjaxResult.success(document);
  47. }
  48. // ==================== 块操作 ====================
  49. /**
  50. * 更新块的元素
  51. */
  52. @PutMapping("/{documentId}/blocks/{blockId}/elements")
  53. @Operation(summary = "更新块元素", description = "更新块的 elements 数组")
  54. public AjaxResult<?> updateBlockElements(
  55. @PathVariable String documentId,
  56. @PathVariable String blockId,
  57. @RequestBody UpdateElementsRequest request) {
  58. try {
  59. structuredDocumentService.updateBlockElements(blockId, request.getElements());
  60. return AjaxResult.success("更新成功");
  61. } catch (Exception e) {
  62. log.error("更新块元素失败: blockId={}", blockId, e);
  63. return AjaxResult.error("更新失败: " + e.getMessage());
  64. }
  65. }
  66. /**
  67. * 创建新块
  68. */
  69. @PostMapping("/{documentId}/blocks")
  70. @Operation(summary = "创建块", description = "在文档中创建新块")
  71. public AjaxResult<?> createBlock(
  72. @PathVariable String documentId,
  73. @RequestBody CreateBlockRequest request) {
  74. try {
  75. DocumentBlock block = structuredDocumentService.createBlock(
  76. documentId,
  77. request.getParentId(),
  78. request.getIndex(),
  79. request.getBlockType(),
  80. request.getElements()
  81. );
  82. return AjaxResult.success("创建成功", block);
  83. } catch (Exception e) {
  84. log.error("创建块失败: documentId={}", documentId, e);
  85. return AjaxResult.error("创建失败: " + e.getMessage());
  86. }
  87. }
  88. /**
  89. * 删除块
  90. */
  91. @DeleteMapping("/{documentId}/blocks/{blockId}")
  92. @Operation(summary = "删除块", description = "删除指定块及其子块")
  93. public AjaxResult<?> deleteBlock(
  94. @PathVariable String documentId,
  95. @PathVariable String blockId) {
  96. structuredDocumentService.deleteBlock(blockId);
  97. return AjaxResult.success("删除成功");
  98. }
  99. // ==================== 实体操作 ====================
  100. /**
  101. * 标记实体(将文本转为实体)
  102. */
  103. @PostMapping("/{documentId}/blocks/{blockId}/mark-entity")
  104. @Operation(summary = "标记实体", description = "将块中的文本片段标记为实体")
  105. public AjaxResult<?> markEntity(
  106. @PathVariable String documentId,
  107. @PathVariable String blockId,
  108. @RequestBody MarkEntityRequest request) {
  109. try {
  110. String entityId = structuredDocumentService.markEntity(
  111. blockId,
  112. request.getElementIndex(),
  113. request.getStartOffset(),
  114. request.getEndOffset(),
  115. request.getEntityType()
  116. );
  117. return AjaxResult.success("标记成功", entityId);
  118. } catch (Exception e) {
  119. log.error("标记实体失败: blockId={}", blockId, e);
  120. return AjaxResult.error("标记失败: " + e.getMessage());
  121. }
  122. }
  123. /**
  124. * 取消实体标记(将实体还原为文本)
  125. */
  126. @DeleteMapping("/{documentId}/blocks/{blockId}/entities/{entityId}")
  127. @Operation(summary = "取消实体标记", description = "将实体还原为普通文本")
  128. public AjaxResult<?> unmarkEntity(
  129. @PathVariable String documentId,
  130. @PathVariable String blockId,
  131. @PathVariable String entityId) {
  132. structuredDocumentService.unmarkEntity(blockId, entityId);
  133. return AjaxResult.success("取消成功");
  134. }
  135. /**
  136. * 更新实体类型
  137. */
  138. @PutMapping("/{documentId}/blocks/{blockId}/entities/{entityId}")
  139. @Operation(summary = "更新实体", description = "更新实体类型")
  140. public AjaxResult<?> updateEntity(
  141. @PathVariable String documentId,
  142. @PathVariable String blockId,
  143. @PathVariable String entityId,
  144. @RequestBody UpdateEntityRequest request) {
  145. structuredDocumentService.updateEntityType(blockId, entityId, request.getEntityType());
  146. return AjaxResult.success("更新成功");
  147. }
  148. /**
  149. * 确认实体
  150. */
  151. @PostMapping("/{documentId}/blocks/{blockId}/entities/{entityId}/confirm")
  152. @Operation(summary = "确认实体", description = "确认实体标注正确")
  153. public AjaxResult<?> confirmEntity(
  154. @PathVariable String documentId,
  155. @PathVariable String blockId,
  156. @PathVariable String entityId) {
  157. structuredDocumentService.confirmEntity(blockId, entityId);
  158. return AjaxResult.success("确认成功");
  159. }
  160. // ==================== 请求 DTO ====================
  161. @Data
  162. public static class UpdateElementsRequest {
  163. private List<TextElement> elements;
  164. }
  165. @Data
  166. public static class CreateBlockRequest {
  167. private String parentId;
  168. private Integer index;
  169. private String blockType;
  170. private List<TextElement> elements;
  171. }
  172. @Data
  173. public static class MarkEntityRequest {
  174. /** 要标记的元素在 elements 数组中的索引 */
  175. private Integer elementIndex;
  176. /** 在该元素文本中的起始位置 */
  177. private Integer startOffset;
  178. /** 在该元素文本中的结束位置 */
  179. private Integer endOffset;
  180. /** 实体类型 */
  181. private String entityType;
  182. }
  183. @Data
  184. public static class UpdateEntityRequest {
  185. private String entityType;
  186. }
  187. }