upload.go 6.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250
  1. package upload
  2. import (
  3. "fmt"
  4. "kpt-pasture/config"
  5. "kpt-pasture/http/middleware"
  6. "mime/multipart"
  7. "net/http"
  8. "os"
  9. "path/filepath"
  10. "strings"
  11. "time"
  12. "gitee.com/xuyiping_admin/pkg/apierr"
  13. "gitee.com/xuyiping_admin/pkg/xerr"
  14. "github.com/gin-gonic/gin"
  15. "github.com/xuri/excelize/v2"
  16. )
  17. func Photos(c *gin.Context) {
  18. form, err := c.MultipartForm()
  19. if err != nil {
  20. apierr.AbortBadRequest(c, http.StatusBadRequest, xerr.Customf("No multipartForm: %s", err.Error()))
  21. return
  22. }
  23. files := form.File["uploads"]
  24. // 验证文件数量
  25. if len(files) == 0 {
  26. apierr.AbortBadRequest(c, http.StatusBadRequest, xerr.Custom("No files selected"))
  27. return
  28. }
  29. res, err := middleware.BackendOperation(c).OpsService.Photos(c, files)
  30. if err != nil {
  31. apierr.AbortBadRequest(c, http.StatusBadRequest, err)
  32. return
  33. }
  34. c.JSON(http.StatusOK, gin.H{"code": http.StatusOK, "Msg": "ok", "data": res})
  35. }
  36. func Files2(c *gin.Context) {
  37. // 定义配置
  38. cf := ExcelImportConfig{
  39. AllowedExtensions: []string{".xlsx", ".xls"},
  40. RequiredHeaders: []string{"耳号", "牛舍名称", "性别", "出生日期", "胎次", "进场日期", "父号", "母号", "最近配种日期", "预产期",
  41. "配后天数", "累计配次", "最近妊检日期", "最近产犊日期", "产后天数", "品种", "出生重量", "断奶日期", "是否禁配", "体重", "体高", "流产日期", "流产原因",
  42. },
  43. MaxFileSize: 10 << 20,
  44. }
  45. // 实现处理逻辑
  46. ImportExcel(c, cf)
  47. }
  48. // ExcelImportConfig 导入配置
  49. type ExcelImportConfig struct {
  50. AllowedExtensions []string
  51. MaxFileSize int64
  52. RequiredHeaders []string // 必须包含的表头
  53. }
  54. // DefaultExcelImportConfig 默认配置
  55. var DefaultExcelImportConfig = ExcelImportConfig{
  56. AllowedExtensions: []string{".xlsx", ".xls"},
  57. MaxFileSize: 10 << 20, // 10MB
  58. }
  59. // ExcelHandler Excel处理接口
  60. type ExcelHandler interface {
  61. ProcessExcelData([][]string) error
  62. }
  63. // ImportExcel 导入Excel文件
  64. func ImportExcel(c *gin.Context, config ...ExcelImportConfig) {
  65. // 获取配置
  66. cfg := DefaultExcelImportConfig
  67. if len(config) > 0 {
  68. cfg = config[0]
  69. }
  70. // 1. 获取上传的文件
  71. file, err := c.FormFile("file")
  72. if err != nil {
  73. apierr.AbortBadRequest(c, http.StatusBadRequest, xerr.Customf("获取文件失败: %s", err.Error()))
  74. return
  75. }
  76. // 2. 验证文件基本属性
  77. if err = validateFile(file, cfg); err != nil {
  78. apierr.AbortBadRequest(c, http.StatusBadRequest, err)
  79. return
  80. }
  81. // 3. 保存文件到临时目录
  82. filePath, err := saveUploadedFile(c, file)
  83. if err != nil {
  84. apierr.AbortBadRequest(c, http.StatusInternalServerError, err)
  85. return
  86. }
  87. defer os.Remove(filePath) // 处理完成后删除临时文件
  88. // 4. 读取并验证Excel文件
  89. excelData, err := readExcelFile(filePath, filepath.Ext(file.Filename))
  90. if err != nil {
  91. apierr.AbortBadRequest(c, http.StatusBadRequest, err)
  92. return
  93. }
  94. // 5. 验证表头
  95. if err = validateHeaders(excelData[0], cfg.RequiredHeaders); err != nil {
  96. apierr.AbortBadRequest(c, http.StatusBadRequest, err)
  97. return
  98. }
  99. if err = middleware.BackendOperation(c).OpsService.ImportExcel(c, excelData); err != nil {
  100. apierr.AbortBadRequest(c, http.StatusBadRequest, err)
  101. return
  102. }
  103. c.JSON(http.StatusOK, gin.H{
  104. "code": http.StatusOK,
  105. "msg": "导入成功",
  106. "data": nil,
  107. })
  108. }
  109. // validateFile 验证文件基本属性
  110. func validateFile(file *multipart.FileHeader, cfg ExcelImportConfig) error {
  111. // 检查文件扩展名
  112. ext := strings.ToLower(filepath.Ext(file.Filename))
  113. validExt := false
  114. for _, allowedExt := range cfg.AllowedExtensions {
  115. if ext == allowedExt {
  116. validExt = true
  117. break
  118. }
  119. }
  120. if !validExt {
  121. return xerr.Customf("不支持的文件类型,仅支持: %v", cfg.AllowedExtensions)
  122. }
  123. // 检查文件大小
  124. if file.Size > cfg.MaxFileSize {
  125. return xerr.Customf("文件大小超过限制(最大%dMB)", cfg.MaxFileSize>>20)
  126. }
  127. return nil
  128. }
  129. // saveUploadedFile 保存上传的文件
  130. func saveUploadedFile(c *gin.Context, file *multipart.FileHeader) (string, error) {
  131. // 创建临时目录
  132. uploadDir := filepath.Join(config.WorkDir, "temp", "excel")
  133. if err := os.MkdirAll(uploadDir, 0755); err != nil {
  134. return "", xerr.Customf("创建临时目录失败: %s", err.Error())
  135. }
  136. // 生成唯一文件名
  137. filename := fmt.Sprintf("%d%s", time.Now().UnixNano(), filepath.Ext(file.Filename))
  138. filePath := filepath.Join(uploadDir, filename)
  139. // 保存文件
  140. if err := c.SaveUploadedFile(file, filePath); err != nil {
  141. return "", xerr.Customf("保存文件失败: %s", err.Error())
  142. }
  143. return filePath, nil
  144. }
  145. // readExcelFile 读取Excel文件
  146. func readExcelFile(filePath, ext string) ([][]string, error) {
  147. // 尝试使用excelize读取
  148. f, err := excelize.OpenFile(filePath)
  149. if err != nil {
  150. // 如果是xls格式且读取失败,尝试使用xls库
  151. if ext == ".xls" {
  152. return readXlsFile(filePath)
  153. }
  154. return nil, xerr.Customf("读取Excel文件失败: %s", err.Error())
  155. }
  156. defer f.Close()
  157. // 获取第一个工作表
  158. sheetName := f.GetSheetName(0)
  159. rows, err := f.GetRows(sheetName)
  160. if err != nil {
  161. return nil, xerr.Customf("读取工作表失败: %s", err.Error())
  162. }
  163. if len(rows) == 0 {
  164. return nil, xerr.Custom("Excel文件为空")
  165. }
  166. return rows, nil
  167. }
  168. // readXlsFile 使用xls库读取旧版Excel文件
  169. func readXlsFile(filePath string) ([][]string, error) {
  170. // 这里需要实现xls文件的读取逻辑
  171. // 可以使用第三方库如github.com/extrame/xls
  172. return nil, xerr.Custom("暂不支持.xls格式文件")
  173. }
  174. // validateHeaders 验证表头
  175. func validateHeaders(headers []string, requiredHeaders []string) error {
  176. if len(requiredHeaders) == 0 {
  177. return nil
  178. }
  179. headerMap := make(map[string]bool)
  180. for _, h := range headers {
  181. headerMap[strings.TrimSpace(h)] = true
  182. }
  183. var missingHeaders []string
  184. for _, req := range requiredHeaders {
  185. if !headerMap[req] {
  186. missingHeaders = append(missingHeaders, req)
  187. }
  188. }
  189. if len(missingHeaders) > 0 {
  190. return xerr.Customf("缺少必要表头: %v", missingHeaders)
  191. }
  192. return nil
  193. }
  194. func OssVideo(c *gin.Context) {
  195. filename := c.Param("name")
  196. videoPath := fmt.Sprintf("%s/files/video/%s", config.WorkDir, filename)
  197. // 打开视频文件
  198. file, err := os.Open(videoPath)
  199. if err != nil {
  200. c.JSON(http.StatusNotFound, gin.H{"error": "Video not found"})
  201. return
  202. }
  203. defer file.Close()
  204. fileInfo, err := file.Stat()
  205. if err != nil {
  206. c.JSON(http.StatusInternalServerError, gin.H{"error": "Could not get file info"})
  207. return
  208. }
  209. // 设置响应头
  210. c.Header("Content-Type", "video/mp4")
  211. c.Header("Content-Length", fmt.Sprintf("%d", fileInfo.Size()))
  212. // 流式传输文件内容
  213. http.ServeContent(c.Writer, c.Request, fileInfo.Name(), fileInfo.ModTime(), file)
  214. }