import_controller.go 4.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171
  1. package controller
  2. import (
  3. "context"
  4. "log"
  5. "net/http"
  6. "os"
  7. "path/filepath"
  8. "strconv"
  9. "strings"
  10. "github.com/2930134478/AI-CS/backend/service"
  11. "github.com/gin-gonic/gin"
  12. )
  13. // ImportController 导入控制器
  14. type ImportController struct {
  15. importService *service.ImportService
  16. embeddingConfigService *service.EmbeddingConfigService
  17. users *service.UserService
  18. }
  19. // NewImportController 创建导入控制器实例
  20. func NewImportController(importService *service.ImportService, embeddingConfigService *service.EmbeddingConfigService, users *service.UserService) *ImportController {
  21. return &ImportController{
  22. importService: importService,
  23. embeddingConfigService: embeddingConfigService,
  24. users: users,
  25. }
  26. }
  27. func (c *ImportController) checkKBAccess(ctx *gin.Context) bool {
  28. userID := getUserIDFromHeader(ctx)
  29. if userID == 0 {
  30. // ⚠️ 修复:改为拒绝访问,而不是允许
  31. ctx.JSON(http.StatusUnauthorized, gin.H{"error": "未授权访问,请提供 X-User-Id 请求头"})
  32. return false
  33. }
  34. if err := c.embeddingConfigService.CheckKnowledgeBaseAccess(userID); err != nil {
  35. ctx.JSON(http.StatusForbidden, gin.H{"error": err.Error()})
  36. return false
  37. }
  38. return true
  39. }
  40. // ImportDocuments 批量导入文档(文件上传)
  41. func (c *ImportController) ImportDocuments(ctx *gin.Context) {
  42. if !requirePermission(ctx, c.users, string(service.PermKnowledge)) {
  43. return
  44. }
  45. if !c.checkKBAccess(ctx) {
  46. return
  47. }
  48. // 获取知识库 ID
  49. kbIDStr := ctx.PostForm("knowledge_base_id")
  50. if kbIDStr == "" {
  51. ctx.JSON(http.StatusBadRequest, gin.H{"error": "知识库 ID 不能为空"})
  52. return
  53. }
  54. kbID, err := strconv.ParseUint(kbIDStr, 10, 64)
  55. if err != nil || kbID == 0 {
  56. ctx.JSON(http.StatusBadRequest, gin.H{"error": "知识库 ID 不合法"})
  57. return
  58. }
  59. // 获取上传的文件
  60. form, err := ctx.MultipartForm()
  61. if err != nil {
  62. ctx.JSON(http.StatusBadRequest, gin.H{"error": "获取文件失败"})
  63. return
  64. }
  65. files := form.File["files"]
  66. if len(files) == 0 {
  67. ctx.JSON(http.StatusBadRequest, gin.H{"error": "未上传文件"})
  68. return
  69. }
  70. // ⚠️ 添加:文件类型验证
  71. allowedExts := map[string]bool{
  72. ".md": true,
  73. ".txt": true,
  74. ".pdf": true,
  75. ".doc": true,
  76. ".docx": true,
  77. }
  78. // 保存文件到临时目录
  79. filePaths := make([]string, 0, len(files))
  80. for _, file := range files {
  81. // ⚠️ 添加:验证文件类型
  82. ext := strings.ToLower(filepath.Ext(file.Filename))
  83. if !allowedExts[ext] {
  84. log.Printf("不支持的文件类型: %s (扩展名: %s)", file.Filename, ext)
  85. continue
  86. }
  87. // ⚠️ 添加:清理文件名,防止路径遍历攻击
  88. safeFilename := filepath.Base(file.Filename)
  89. safeFilename = strings.ReplaceAll(safeFilename, "..", "")
  90. safeFilename = strings.ReplaceAll(safeFilename, "/", "")
  91. safeFilename = strings.ReplaceAll(safeFilename, "\\", "")
  92. // 限制文件名长度
  93. if len(safeFilename) > 255 {
  94. safeFilename = safeFilename[:255]
  95. }
  96. // 保存文件
  97. filePath := "/tmp/" + safeFilename
  98. if err := ctx.SaveUploadedFile(file, filePath); err != nil {
  99. log.Printf("保存文件失败: %v", err)
  100. continue
  101. }
  102. filePaths = append(filePaths, filePath)
  103. }
  104. if len(filePaths) == 0 {
  105. ctx.JSON(http.StatusBadRequest, gin.H{"error": "没有有效的文件(所有文件都被拒绝或保存失败)"})
  106. return
  107. }
  108. // ⚠️ 添加:导入后清理临时文件
  109. defer func() {
  110. for _, path := range filePaths {
  111. if err := os.Remove(path); err != nil {
  112. log.Printf("清理临时文件失败: %v", err)
  113. }
  114. }
  115. }()
  116. // 导入文件
  117. result, err := c.importService.ImportFiles(context.Background(), uint(kbID), filePaths)
  118. if err != nil {
  119. log.Printf("导入文件失败: %v", err)
  120. ctx.JSON(http.StatusInternalServerError, gin.H{"error": "批量导入失败: " + err.Error()})
  121. return
  122. }
  123. result.Message = "导入完成"
  124. ctx.JSON(http.StatusOK, result)
  125. }
  126. // ImportFromURLs 批量导入文档(URL 爬取)
  127. func (c *ImportController) ImportFromURLs(ctx *gin.Context) {
  128. if !requirePermission(ctx, c.users, string(service.PermKnowledge)) {
  129. return
  130. }
  131. if !c.checkKBAccess(ctx) {
  132. return
  133. }
  134. var req struct {
  135. KnowledgeBaseID uint `json:"knowledge_base_id" binding:"required"`
  136. URLs []string `json:"urls" binding:"required"`
  137. }
  138. if err := ctx.ShouldBindJSON(&req); err != nil {
  139. ctx.JSON(http.StatusBadRequest, gin.H{"error": "请求参数错误: " + err.Error()})
  140. return
  141. }
  142. result, err := c.importService.ImportFromUrls(context.Background(), req.KnowledgeBaseID, req.URLs)
  143. if err != nil {
  144. log.Printf("导入 URL 失败: %v", err)
  145. ctx.JSON(http.StatusInternalServerError, gin.H{"error": "批量导入失败: " + err.Error()})
  146. return
  147. }
  148. result.Message = "导入完成"
  149. ctx.JSON(http.StatusOK, result)
  150. }