upload.go 7.0 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. "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(c.GetHeader("farmid"))
  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); 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(farmId string) *ExcelImportConfig {
  117. switch farmId {
  118. case "e50cd455dec93edf3d8135abbc770680":
  119. return &ExcelImportConfig{
  120. AllowedExtensions: []string{".xlsx", ".xls"},
  121. RequiredHeaders: []string{"", "", "耳号", "牛舍名称", "性别", "出生日期", "胎次", "进场日期", "父号", "母号", "最近配种日期", "预产期", "配后天数",
  122. "累计配次", "最近妊检日期", "最近产犊日期", "产后天数", "品种", "出生重量", "断奶日期", "是否禁配", "体重", "体高", "流产日期", "流产原因"},
  123. HeadOfField: []string{"", "", "earNumber", "penName", "sex", "birthAt", "lact", "admissionAt", "fatherNumber", "motherNumber",
  124. "lastMatingAt", "", "matingAge", "allMatingTimes", "lastPregnantCheckAt", "lastCalvingAt", "lactationAge",
  125. "cowKind", "birthWeight", "weaningAt", "isForbiddenMating", "currentWeight", "currenWtHeight", "lastAbortionAt", ""},
  126. MaxFileSize: 10 << 20,
  127. }
  128. default:
  129. return nil
  130. }
  131. }
  132. // ExcelHandler Excel处理接口
  133. type ExcelHandler interface {
  134. ProcessExcelData([][]string) error
  135. }
  136. // validateFile 验证文件基本属性
  137. func validateFile(file *multipart.FileHeader, cfg *ExcelImportConfig) error {
  138. // 检查文件扩展名
  139. ext := strings.ToLower(filepath.Ext(file.Filename))
  140. validExt := false
  141. for _, allowedExt := range cfg.AllowedExtensions {
  142. if ext == allowedExt {
  143. validExt = true
  144. break
  145. }
  146. }
  147. if !validExt {
  148. return xerr.Customf("不支持的文件类型,仅支持: %v", cfg.AllowedExtensions)
  149. }
  150. // 检查文件大小
  151. if file.Size > cfg.MaxFileSize {
  152. return xerr.Customf("文件大小超过限制(最大%dMB)", cfg.MaxFileSize>>20)
  153. }
  154. return nil
  155. }
  156. // saveUploadedFile 保存上传的文件
  157. func saveUploadedFile(c *gin.Context, file *multipart.FileHeader) (string, error) {
  158. // 创建临时目录
  159. uploadDir := filepath.Join(config.WorkDir, "temp", "excel")
  160. if err := os.MkdirAll(uploadDir, 0755); err != nil {
  161. return "", xerr.Customf("创建临时目录失败: %s", err.Error())
  162. }
  163. // 生成唯一文件名
  164. filename := fmt.Sprintf("%d%s", time.Now().UnixNano(), filepath.Ext(file.Filename))
  165. filePath := filepath.Join(uploadDir, filename)
  166. // 保存文件
  167. if err := c.SaveUploadedFile(file, filePath); err != nil {
  168. return "", xerr.Customf("保存文件失败: %s", err.Error())
  169. }
  170. return filePath, nil
  171. }
  172. // validateHeaders 验证表头
  173. func validateHeaders(headers []string, requiredHeaders []string) error {
  174. if len(requiredHeaders) == 0 {
  175. return nil
  176. }
  177. headerMap := make(map[string]bool)
  178. for _, h := range headers {
  179. headerMap[strings.TrimSpace(h)] = true
  180. }
  181. var missingHeaders []string
  182. for _, req := range requiredHeaders {
  183. if req == "" {
  184. continue
  185. }
  186. if !headerMap[req] {
  187. missingHeaders = append(missingHeaders, req)
  188. }
  189. }
  190. if len(missingHeaders) > 0 {
  191. return xerr.Customf("缺少必要表头: %v", missingHeaders)
  192. }
  193. return nil
  194. }
  195. func OssVideo(c *gin.Context) {
  196. filename := c.Param("name")
  197. videoPath := fmt.Sprintf("%s/files/video/%s", config.WorkDir, filename)
  198. // 打开视频文件
  199. file, err := os.Open(videoPath)
  200. if err != nil {
  201. c.JSON(http.StatusNotFound, gin.H{"error": "Video not found"})
  202. return
  203. }
  204. defer file.Close()
  205. fileInfo, err := file.Stat()
  206. if err != nil {
  207. c.JSON(http.StatusInternalServerError, gin.H{"error": "Could not get file info"})
  208. return
  209. }
  210. // 设置响应头
  211. c.Header("Content-Type", "video/mp4")
  212. c.Header("Content-Length", fmt.Sprintf("%d", fileInfo.Size()))
  213. // 流式传输文件内容
  214. http.ServeContent(c.Writer, c.Request, fileInfo.Name(), fileInfo.ModTime(), file)
  215. }