upload.go 6.9 KB

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