package com.lingyue.document.controller; import com.baomidou.mybatisplus.core.metadata.IPage; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.lingyue.document.entity.Document; import com.lingyue.document.entity.DocumentElement; import com.lingyue.document.service.DocumentService; import com.lingyue.document.service.DocumentElementService; import com.lingyue.common.domain.AjaxResult; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Parameter; import io.swagger.v3.oas.annotations.tags.Tag; import jakarta.validation.Valid; import jakarta.validation.constraints.NotEmpty; import lombok.Data; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.web.bind.annotation.*; import java.util.ArrayList; import java.util.List; import java.util.Map; /** * 文档控制器 * 提供文档的 CRUD 操作 * * @author lingyue * @since 2026-01-21 */ @Slf4j @RestController @RequestMapping("/api/v1/documents") @RequiredArgsConstructor @Tag(name = "文档管理", description = "文档 CRUD 接口") public class DocumentController { private final DocumentService documentService; private final DocumentElementService documentElementService; /** * 获取文档列表(分页) */ @GetMapping @Operation(summary = "获取文档列表", description = "分页获取用户的文档列表") public AjaxResult> getDocuments( @Parameter(description = "用户ID", required = true) @RequestParam String userId, @Parameter(description = "页码,从1开始") @RequestParam(defaultValue = "1") Integer page, @Parameter(description = "每页数量") @RequestParam(defaultValue = "20") Integer size, @Parameter(description = "状态筛选") @RequestParam(required = false) String status, @Parameter(description = "关键词搜索") @RequestParam(required = false) String keyword) { log.info("获取文档列表: userId={}, page={}, size={}", userId, page, size); Page pageParam = new Page<>(page, size); IPage result = documentService.getDocumentsByUserId(userId, pageParam, status, keyword); return AjaxResult.success(result); } /** * 获取文档详情 */ @GetMapping("/{documentId}") @Operation(summary = "获取文档详情", description = "根据文档ID获取详情") public AjaxResult getDocument( @Parameter(description = "文档ID", required = true) @PathVariable String documentId) { Document document = documentService.getDocumentById(documentId); if (document == null) { return AjaxResult.error("文档不存在: " + documentId); } return AjaxResult.success(document); } /** * 获取文档提取的文本内容 */ @GetMapping("/{documentId}/text") @Operation(summary = "获取文档文本", description = "获取文档解析后的文本内容") public AjaxResult getDocumentText( @Parameter(description = "文档ID", required = true) @PathVariable String documentId) { try { String text = documentService.getDocumentText(documentId); if (text == null) { return AjaxResult.error("文档文本不存在或尚未解析完成"); } DocumentTextResponse response = new DocumentTextResponse(); response.setDocumentId(documentId); response.setText(text); response.setLength(text.length()); return AjaxResult.success(response); } catch (Exception e) { log.error("获取文档文本失败: documentId={}", documentId, e); return AjaxResult.error("获取文档文本失败: " + e.getMessage()); } } /** * 获取文档解析状态 */ @GetMapping("/{documentId}/parse-status") @Operation(summary = "获取解析状态", description = "获取文档的解析进度和状态") public AjaxResult getParseStatus( @Parameter(description = "文档ID", required = true) @PathVariable String documentId) { Document document = documentService.getDocumentById(documentId); if (document == null) { return AjaxResult.error("文档不存在: " + documentId); } ParseStatusResponse response = new ParseStatusResponse(); response.setDocumentId(documentId); response.setStatus(document.getParseStatus()); response.setProgress(document.getParseProgress()); response.setError(document.getParseError()); response.setStartedAt(document.getParseStartedAt()); response.setCompletedAt(document.getParseCompletedAt()); return AjaxResult.success(response); } /** * 更新文档信息 */ @PutMapping("/{documentId}") @Operation(summary = "更新文档", description = "更新文档的名称、状态等信息") public AjaxResult updateDocument( @Parameter(description = "文档ID", required = true) @PathVariable String documentId, @Valid @RequestBody UpdateDocumentRequest request) { try { Document document = documentService.updateDocument(documentId, request.getName(), request.getStatus(), request.getMetadata()); log.info("更新文档成功: documentId={}", documentId); return AjaxResult.success("更新成功", document); } catch (Exception e) { log.error("更新文档失败: documentId={}", documentId, e); return AjaxResult.error("更新文档失败: " + e.getMessage()); } } /** * 删除文档(级联删除所有关联数据) */ @DeleteMapping("/{documentId}") @Operation(summary = "删除文档", description = "删除指定文档及其所有关联数据(图节点、向量、结构化元素等)") public AjaxResult deleteDocument( @Parameter(description = "文档ID", required = true) @PathVariable String documentId) { try { documentService.deleteDocumentCascade(documentId); log.info("删除文档成功: documentId={}", documentId); return AjaxResult.success("删除成功"); } catch (Exception e) { log.error("删除文档失败: documentId={}", documentId, e); return AjaxResult.error("删除文档失败: " + e.getMessage()); } } /** * 批量删除文档 */ @PostMapping("/batch-delete") @Operation(summary = "批量删除文档", description = "批量删除多个文档及其关联数据") public AjaxResult batchDeleteDocuments( @Valid @RequestBody BatchDeleteRequest request) { List successIds = new ArrayList<>(); List failedIds = new ArrayList<>(); for (String documentId : request.getDocumentIds()) { try { documentService.deleteDocumentCascade(documentId); successIds.add(documentId); } catch (Exception e) { log.error("批量删除文档失败: documentId={}, error={}", documentId, e.getMessage()); failedIds.add(documentId); } } BatchDeleteResponse response = new BatchDeleteResponse(); response.setSuccessIds(successIds); response.setFailedIds(failedIds); response.setSuccessCount(successIds.size()); response.setFailedCount(failedIds.size()); if (failedIds.isEmpty()) { return AjaxResult.success("批量删除成功", response); } else if (successIds.isEmpty()) { return AjaxResult.error("批量删除全部失败", response); } else { return AjaxResult.success("批量删除部分成功", response); } } /** * 获取文档的结构化内容 * 包含段落、图片、表格,按原始顺序排列 */ @GetMapping("/{documentId}/elements") @Operation(summary = "获取文档结构化内容", description = "获取文档的段落、图片、表格等结构化元素,保持原始排版顺序") public AjaxResult getDocumentElements( @Parameter(description = "文档ID", required = true) @PathVariable String documentId) { List elements = documentElementService.getElementsByDocumentId(documentId); if (elements.isEmpty()) { return AjaxResult.error("文档尚未进行结构化解析或无内容"); } DocumentElementsResponse response = new DocumentElementsResponse(); response.setDocumentId(documentId); response.setElements(elements); response.setStats(documentElementService.getElementStats(documentId)); return AjaxResult.success(response); } /** * 获取文档中的所有图片 */ @GetMapping("/{documentId}/images") @Operation(summary = "获取文档图片列表", description = "获取文档中所有图片的信息和URL") public AjaxResult getDocumentImages( @Parameter(description = "文档ID", required = true) @PathVariable String documentId) { List images = documentElementService.getImagesByDocumentId(documentId); return AjaxResult.success(images); } /** * 获取文档中的所有表格 */ @GetMapping("/{documentId}/tables") @Operation(summary = "获取文档表格列表", description = "获取文档中所有表格的数据") public AjaxResult getDocumentTables( @Parameter(description = "文档ID", required = true) @PathVariable String documentId) { List tables = documentElementService.getTablesByDocumentId(documentId); return AjaxResult.success(tables); } // ==================== 请求/响应 DTO ==================== @Data public static class DocumentTextResponse { private String documentId; private String text; private Integer length; } @Data public static class ParseStatusResponse { private String documentId; private String status; private Integer progress; private String error; private java.util.Date startedAt; private java.util.Date completedAt; } @Data public static class DocumentElementsResponse { private String documentId; private List elements; private Map stats; } @Data public static class UpdateDocumentRequest { private String name; // 文档名称 private String status; // 状态(可选) private Object metadata; // 元数据(可选) } @Data public static class BatchDeleteRequest { @NotEmpty(message = "文档ID列表不能为空") private List documentIds; } @Data public static class BatchDeleteResponse { private List successIds; private List failedIds; private int successCount; private int failedCount; } }