DocumentController.java 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328
  1. package com.lingyue.document.controller;
  2. import com.baomidou.mybatisplus.core.metadata.IPage;
  3. import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
  4. import com.lingyue.document.entity.Document;
  5. import com.lingyue.document.entity.DocumentElement;
  6. import com.lingyue.document.service.DocumentService;
  7. import com.lingyue.document.service.DocumentElementService;
  8. import com.lingyue.common.domain.AjaxResult;
  9. import io.swagger.v3.oas.annotations.Operation;
  10. import io.swagger.v3.oas.annotations.Parameter;
  11. import io.swagger.v3.oas.annotations.tags.Tag;
  12. import jakarta.validation.Valid;
  13. import jakarta.validation.constraints.NotEmpty;
  14. import lombok.Data;
  15. import lombok.RequiredArgsConstructor;
  16. import lombok.extern.slf4j.Slf4j;
  17. import org.springframework.web.bind.annotation.*;
  18. import java.util.ArrayList;
  19. import java.util.List;
  20. import java.util.Map;
  21. /**
  22. * 文档控制器
  23. * 提供文档的 CRUD 操作
  24. *
  25. * @author lingyue
  26. * @since 2026-01-21
  27. */
  28. @Slf4j
  29. @RestController
  30. @RequestMapping("/api/v1/documents")
  31. @RequiredArgsConstructor
  32. @Tag(name = "文档管理", description = "文档 CRUD 接口")
  33. public class DocumentController {
  34. private final DocumentService documentService;
  35. private final DocumentElementService documentElementService;
  36. /**
  37. * 获取文档列表(分页)
  38. */
  39. @GetMapping
  40. @Operation(summary = "获取文档列表", description = "分页获取用户的文档列表")
  41. public AjaxResult<IPage<Document>> getDocuments(
  42. @Parameter(description = "用户ID", required = true)
  43. @RequestParam String userId,
  44. @Parameter(description = "页码,从1开始")
  45. @RequestParam(defaultValue = "1") Integer page,
  46. @Parameter(description = "每页数量")
  47. @RequestParam(defaultValue = "20") Integer size,
  48. @Parameter(description = "状态筛选")
  49. @RequestParam(required = false) String status,
  50. @Parameter(description = "关键词搜索")
  51. @RequestParam(required = false) String keyword) {
  52. log.info("获取文档列表: userId={}, page={}, size={}", userId, page, size);
  53. Page<Document> pageParam = new Page<>(page, size);
  54. IPage<Document> result = documentService.getDocumentsByUserId(userId, pageParam, status, keyword);
  55. return AjaxResult.success(result);
  56. }
  57. /**
  58. * 获取文档详情
  59. */
  60. @GetMapping("/{documentId}")
  61. @Operation(summary = "获取文档详情", description = "根据文档ID获取详情")
  62. public AjaxResult<?> getDocument(
  63. @Parameter(description = "文档ID", required = true)
  64. @PathVariable String documentId) {
  65. Document document = documentService.getDocumentById(documentId);
  66. if (document == null) {
  67. return AjaxResult.error("文档不存在: " + documentId);
  68. }
  69. return AjaxResult.success(document);
  70. }
  71. /**
  72. * 获取文档提取的文本内容
  73. */
  74. @GetMapping("/{documentId}/text")
  75. @Operation(summary = "获取文档文本", description = "获取文档解析后的文本内容")
  76. public AjaxResult<?> getDocumentText(
  77. @Parameter(description = "文档ID", required = true)
  78. @PathVariable String documentId) {
  79. try {
  80. String text = documentService.getDocumentText(documentId);
  81. if (text == null) {
  82. return AjaxResult.error("文档文本不存在或尚未解析完成");
  83. }
  84. DocumentTextResponse response = new DocumentTextResponse();
  85. response.setDocumentId(documentId);
  86. response.setText(text);
  87. response.setLength(text.length());
  88. return AjaxResult.success(response);
  89. } catch (Exception e) {
  90. log.error("获取文档文本失败: documentId={}", documentId, e);
  91. return AjaxResult.error("获取文档文本失败: " + e.getMessage());
  92. }
  93. }
  94. /**
  95. * 获取文档解析状态
  96. */
  97. @GetMapping("/{documentId}/parse-status")
  98. @Operation(summary = "获取解析状态", description = "获取文档的解析进度和状态")
  99. public AjaxResult<?> getParseStatus(
  100. @Parameter(description = "文档ID", required = true)
  101. @PathVariable String documentId) {
  102. Document document = documentService.getDocumentById(documentId);
  103. if (document == null) {
  104. return AjaxResult.error("文档不存在: " + documentId);
  105. }
  106. ParseStatusResponse response = new ParseStatusResponse();
  107. response.setDocumentId(documentId);
  108. response.setStatus(document.getStatus());
  109. response.setProgress(null);
  110. response.setError(null);
  111. response.setStartedAt(null);
  112. response.setCompletedAt(null);
  113. return AjaxResult.success(response);
  114. }
  115. /**
  116. * 更新文档信息
  117. */
  118. @PutMapping("/{documentId}")
  119. @Operation(summary = "更新文档", description = "更新文档的名称、状态等信息")
  120. public AjaxResult<?> updateDocument(
  121. @Parameter(description = "文档ID", required = true)
  122. @PathVariable String documentId,
  123. @Valid @RequestBody UpdateDocumentRequest request) {
  124. try {
  125. Document document = documentService.updateDocument(documentId, request.getName(),
  126. request.getStatus(), request.getMetadata());
  127. log.info("更新文档成功: documentId={}", documentId);
  128. return AjaxResult.success("更新成功", document);
  129. } catch (Exception e) {
  130. log.error("更新文档失败: documentId={}", documentId, e);
  131. return AjaxResult.error("更新文档失败: " + e.getMessage());
  132. }
  133. }
  134. /**
  135. * 删除文档(级联删除所有关联数据)
  136. */
  137. @DeleteMapping("/{documentId}")
  138. @Operation(summary = "删除文档", description = "删除指定文档及其所有关联数据(图节点、向量、结构化元素等)")
  139. public AjaxResult<?> deleteDocument(
  140. @Parameter(description = "文档ID", required = true)
  141. @PathVariable String documentId) {
  142. try {
  143. documentService.deleteDocumentCascade(documentId);
  144. log.info("删除文档成功: documentId={}", documentId);
  145. return AjaxResult.success("删除成功");
  146. } catch (Exception e) {
  147. log.error("删除文档失败: documentId={}", documentId, e);
  148. return AjaxResult.error("删除文档失败: " + e.getMessage());
  149. }
  150. }
  151. /**
  152. * 批量删除文档
  153. */
  154. @PostMapping("/batch-delete")
  155. @Operation(summary = "批量删除文档", description = "批量删除多个文档及其关联数据")
  156. public AjaxResult<?> batchDeleteDocuments(
  157. @Valid @RequestBody BatchDeleteRequest request) {
  158. List<String> successIds = new ArrayList<>();
  159. List<String> failedIds = new ArrayList<>();
  160. for (String documentId : request.getDocumentIds()) {
  161. try {
  162. documentService.deleteDocumentCascade(documentId);
  163. successIds.add(documentId);
  164. } catch (Exception e) {
  165. log.error("批量删除文档失败: documentId={}, error={}", documentId, e.getMessage());
  166. failedIds.add(documentId);
  167. }
  168. }
  169. BatchDeleteResponse response = new BatchDeleteResponse();
  170. response.setSuccessIds(successIds);
  171. response.setFailedIds(failedIds);
  172. response.setSuccessCount(successIds.size());
  173. response.setFailedCount(failedIds.size());
  174. if (failedIds.isEmpty()) {
  175. return AjaxResult.success("批量删除成功", response);
  176. } else if (successIds.isEmpty()) {
  177. return AjaxResult.error("批量删除全部失败", response);
  178. } else {
  179. return AjaxResult.success("批量删除部分成功", response);
  180. }
  181. }
  182. /**
  183. * 获取文档的结构化内容
  184. * 包含段落、图片、表格,按原始顺序排列
  185. */
  186. @GetMapping("/{documentId}/elements")
  187. @Operation(summary = "获取文档结构化内容", description = "获取文档的段落、图片、表格等结构化元素,保持原始排版顺序")
  188. public AjaxResult<?> getDocumentElements(
  189. @Parameter(description = "文档ID", required = true)
  190. @PathVariable String documentId) {
  191. List<DocumentElement> elements = documentElementService.getElementsByDocumentId(documentId);
  192. if (elements.isEmpty()) {
  193. return AjaxResult.error("文档尚未进行结构化解析或无内容");
  194. }
  195. DocumentElementsResponse response = new DocumentElementsResponse();
  196. response.setDocumentId(documentId);
  197. response.setElements(elements);
  198. response.setStats(documentElementService.getElementStats(documentId));
  199. return AjaxResult.success(response);
  200. }
  201. /**
  202. * 获取文档中的所有图片
  203. */
  204. @GetMapping("/{documentId}/images")
  205. @Operation(summary = "获取文档图片列表", description = "获取文档中所有图片的信息和URL")
  206. public AjaxResult<?> getDocumentImages(
  207. @Parameter(description = "文档ID", required = true)
  208. @PathVariable String documentId) {
  209. List<DocumentElement> images = documentElementService.getImagesByDocumentId(documentId);
  210. return AjaxResult.success(images);
  211. }
  212. /**
  213. * 获取文档中的所有表格
  214. */
  215. @GetMapping("/{documentId}/tables")
  216. @Operation(summary = "获取文档表格列表", description = "获取文档中所有表格的数据")
  217. public AjaxResult<?> getDocumentTables(
  218. @Parameter(description = "文档ID", required = true)
  219. @PathVariable String documentId) {
  220. List<DocumentElement> tables = documentElementService.getTablesByDocumentId(documentId);
  221. return AjaxResult.success(tables);
  222. }
  223. /**
  224. * 获取文档目录
  225. */
  226. @GetMapping("/{documentId}/toc")
  227. @Operation(summary = "获取文档目录", description = "获取文档中的目录结构")
  228. public AjaxResult<?> getDocumentToc(
  229. @Parameter(description = "文档ID", required = true)
  230. @PathVariable String documentId) {
  231. List<TocItemResponse> tocItems = documentElementService.getTocByDocumentId(documentId);
  232. return AjaxResult.success(tocItems);
  233. }
  234. // ==================== 请求/响应 DTO ====================
  235. @Data
  236. public static class DocumentTextResponse {
  237. private String documentId;
  238. private String text;
  239. private Integer length;
  240. }
  241. @Data
  242. public static class ParseStatusResponse {
  243. private String documentId;
  244. private String status;
  245. private Integer progress;
  246. private String error;
  247. private java.util.Date startedAt;
  248. private java.util.Date completedAt;
  249. }
  250. @Data
  251. public static class DocumentElementsResponse {
  252. private String documentId;
  253. private List<DocumentElement> elements;
  254. private Map<String, Object> stats;
  255. }
  256. @Data
  257. public static class UpdateDocumentRequest {
  258. private String name; // 文档名称
  259. private String status; // 状态(可选)
  260. private Object metadata; // 元数据(可选)
  261. }
  262. @Data
  263. public static class BatchDeleteRequest {
  264. @NotEmpty(message = "文档ID列表不能为空")
  265. private List<String> documentIds;
  266. }
  267. @Data
  268. public static class BatchDeleteResponse {
  269. private List<String> successIds;
  270. private List<String> failedIds;
  271. private int successCount;
  272. private int failedCount;
  273. }
  274. @Data
  275. public static class TocItemResponse {
  276. private Integer index; // 目录项序号
  277. private Integer level; // 层级 (1, 2, 3...)
  278. private String title; // 标题文本
  279. private String pageNum; // 页码
  280. private String anchorId; // 锚点ID(用于跳转)
  281. }
  282. }