b_file.go 6.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239
  1. package internal
  2. import (
  3. "bytes"
  4. "context"
  5. "filesdk"
  6. "fmt"
  7. "io"
  8. "net/http"
  9. "net/url"
  10. "os"
  11. "path/filepath"
  12. "strings"
  13. "github.com/gogf/gf/v2/os/glog"
  14. "yx-dataset-server/app/schema"
  15. "yx-dataset-server/library/logger"
  16. "yx-dataset-server/library/minio"
  17. "yx-dataset-server/library/utils"
  18. )
  19. // NewFile 创建文件管理实例
  20. func NewFile() *File {
  21. return &File{}
  22. }
  23. // File 文件管理
  24. type File struct{}
  25. // Upload 上传文件
  26. func (a *File) Upload(ctx context.Context, r *http.Request, formKey, bucket string) (*schema.FileInfo, error) {
  27. file, header, err := r.FormFile(formKey)
  28. if err != nil {
  29. return nil, err
  30. }
  31. buff := new(bytes.Buffer)
  32. _, err = io.Copy(buff, file)
  33. if err != nil {
  34. return nil, err
  35. }
  36. headers := map[string]string{}
  37. headers["FILE-EXPIRE"] = utils.GetConfig("file_server.file-expire").String()
  38. result, err := filesdk.GetHandle().Upload(header.Filename, bucket, headers, buff.Bytes())
  39. if err != nil {
  40. glog.Debugf(ctx, "上传文件失败:%s", err.Error())
  41. return nil, err
  42. }
  43. err = filesdk.GetHandle().HandlePersistent(result.Hash)
  44. if err != nil {
  45. logger.Printf(ctx, "文件持久化设置失败:%s", err.Error())
  46. }
  47. info := &schema.FileInfo{
  48. URL: result.URL,
  49. Name: result.Name,
  50. Size: result.Size,
  51. MD5: result.Hash,
  52. }
  53. return info, nil
  54. }
  55. // Download 下载文件
  56. func (a *File) Download(ctx context.Context, fileUrl string) ([]byte, string, error) {
  57. Url, err := url.PathUnescape(fileUrl)
  58. if err != nil {
  59. return nil, "", err
  60. }
  61. stat, err := minio.GetClient().Stat(Url)
  62. if err != nil {
  63. fmt.Println("文件不存在")
  64. return nil, "", err
  65. }
  66. fileData, err := a.getSourceFileData(ctx, Url)
  67. if err != nil {
  68. return nil, "", err
  69. }
  70. return fileData, stat.ContentType, nil
  71. }
  72. func (a *File) getSourceFileData(ctx context.Context, file string) ([]byte, error) {
  73. var fileData []byte
  74. obj, err := minio.GetClient().Get(ctx, file)
  75. if err != nil {
  76. return nil, err
  77. }
  78. buf := new(bytes.Buffer)
  79. _, _ = io.Copy(buf, obj)
  80. _ = obj.Close()
  81. fileData = buf.Bytes()
  82. return fileData, nil
  83. }
  84. // 修正文件名,将半角 % 替换为全角 %(不替换的话文件将无法从浏览器中打开)
  85. func (a *File) getFileName(fileName string) string {
  86. return strings.ReplaceAll(fileName, "%", "%")
  87. }
  88. func (a *File) UploadFromTrainFolder(ctx context.Context, fileName, bucket string) (*schema.FileInfo, error) {
  89. // 1. 固定目标文件夹路径(和你的目录结构完全匹配)
  90. // 绝对路径兜底,避免程序运行目录错乱
  91. baseDir := "/opt/go/yx-dataset-server/file/产品知识"
  92. // 安全拼接完整路径,防止路径穿越漏洞
  93. fullFilePath := filepath.Join(baseDir, fileName)
  94. // 2. 打开本地文件(替代原 r.FormFile 表单取文件逻辑)
  95. localFile, err := os.Open(fullFilePath)
  96. if err != nil {
  97. return nil, fmt.Errorf("读取产品知识文件失败: %w", err)
  98. }
  99. // 函数结束自动关闭文件,防止句柄泄漏
  100. defer localFile.Close()
  101. // 3. 读取文件到缓冲区(完全保留你原有的内存拷贝逻辑)
  102. buff := new(bytes.Buffer)
  103. _, err = io.Copy(buff, localFile)
  104. if err != nil {
  105. return nil, fmt.Errorf("文件读取缓存失败: %w", err)
  106. }
  107. // 4. 原有SDK上传逻辑1:1保留
  108. headers := map[string]string{}
  109. headers["FILE-EXPIRE"] = utils.GetConfig("file_server.file-expire").String()
  110. result, err := filesdk.GetHandle().Upload(fileName, bucket, headers, buff.Bytes())
  111. if err != nil {
  112. glog.Debugf(ctx, "上传文件失败:%s", err.Error())
  113. return nil, err
  114. }
  115. // 5. 文件持久化逻辑完整保留
  116. err = filesdk.GetHandle().HandlePersistent(result.Hash)
  117. if err != nil {
  118. logger.Printf(ctx, "文件持久化设置失败:%s", err.Error())
  119. }
  120. // 6. 返回结构完全兼容原定义
  121. info := &schema.FileInfo{
  122. URL: result.URL,
  123. Name: result.Name,
  124. Size: result.Size,
  125. MD5: result.Hash,
  126. }
  127. return info, nil
  128. }
  129. func (a *File) UploadAllTrainFiles(ctx context.Context, path, bucket string) ([]*schema.FileInfo, error) {
  130. baseDir := fmt.Sprintf("/opt/go/yx-dataset-server/file/%s", path)
  131. fmt.Printf(fmt.Sprintf("正在读取【%s】目录下的所有文件...\n", path))
  132. // 读取目录内所有文件
  133. dirEntries, err := os.ReadDir(baseDir)
  134. if err != nil {
  135. return nil, fmt.Errorf("读取文件失败: %w", err)
  136. }
  137. var uploadResult []*schema.FileInfo
  138. fmt.Printf("开始上传文件...\n")
  139. for k, entry := range dirEntries {
  140. if entry.IsDir() {
  141. continue
  142. }
  143. fmt.Printf("正在处理第%d个文件:%s\n", k, entry.Name())
  144. // 调用上面的单文件上传方法
  145. fileInfo, err := a.UploadFromTrainFolder(ctx, entry.Name(), bucket)
  146. if err != nil {
  147. glog.Debugf(ctx, "文件【%s】上传跳过: %s", entry.Name(), err.Error())
  148. continue
  149. }
  150. uploadResult = append(uploadResult, fileInfo)
  151. }
  152. return uploadResult, nil
  153. }
  154. // UploadFromLocal 从本地文件路径上传文件
  155. func (a *File) UploadFromLocal(ctx context.Context, filePath string, bucket string) (*schema.FileInfo, error) {
  156. logger.Printf(ctx, "[文件上传] 开始上传本地文件: %s, bucket: %s", filePath, bucket)
  157. file, err := os.Open(filePath)
  158. if err != nil {
  159. logger.Printf(ctx, "[文件上传] 打开文件失败: %s, 错误: %s", filePath, err.Error())
  160. return nil, fmt.Errorf("打开文件失败: %s", err.Error())
  161. }
  162. defer file.Close()
  163. // 获取文件信息
  164. fileInfo, err := file.Stat()
  165. if err != nil {
  166. logger.Printf(ctx, "[文件上传] 获取文件信息失败: %s, 错误: %s", filePath, err.Error())
  167. return nil, fmt.Errorf("获取文件信息失败: %s", err.Error())
  168. }
  169. fileSize := fileInfo.Size()
  170. fileName := fileInfo.Name()
  171. logger.Printf(ctx, "[文件上传] 文件信息 - 名称: %s, 大小: %d bytes", fileName, fileSize)
  172. // 读取文件内容
  173. data, err := io.ReadAll(file)
  174. if err != nil {
  175. logger.Printf(ctx, "[文件上传] 读取文件内容失败: %s, 错误: %s", filePath, err.Error())
  176. return nil, fmt.Errorf("读取文件内容失败: %s", err.Error())
  177. }
  178. logger.Printf(ctx, "[文件上传] 文件读取完成: %s, 实际读取: %d bytes", filePath, len(data))
  179. headers := map[string]string{}
  180. headers["FILE-EXPIRE"] = utils.GetConfig("file_server.file-expire").String()
  181. logger.Printf(ctx, "[文件上传] 开始上传到Minio: %s", filePath)
  182. result, err := filesdk.GetHandle().Upload(fileName, bucket, headers, data)
  183. if err != nil {
  184. logger.Printf(ctx, "[文件上传] Minio上传失败: %s, 错误: %s", filePath, err.Error())
  185. glog.Debugf(ctx, "上传文件失败: %s", err.Error())
  186. return nil, err
  187. }
  188. logger.Printf(ctx, "[文件上传] Minio上传成功: %s -> %s, MD5: %s", filePath, result.URL, result.Hash)
  189. err = filesdk.GetHandle().HandlePersistent(result.Hash)
  190. if err != nil {
  191. logger.Printf(ctx, "[文件上传] 文件持久化设置失败: %s, 错误: %s", filePath, err.Error())
  192. }
  193. info := &schema.FileInfo{
  194. URL: result.URL,
  195. Name: result.Name,
  196. Size: result.Size,
  197. MD5: result.Hash,
  198. }
  199. logger.Printf(ctx, "[文件上传] 上传完成: %s", filePath)
  200. return info, nil
  201. }