package upload import ( "fmt" "kpt-pasture/config" "kpt-pasture/http/middleware" "mime/multipart" "net/http" "os" "path/filepath" "strings" "time" "github.com/xuri/excelize/v2" operationPb "gitee.com/xuyiping_admin/go_proto/proto/go/backend/operation" "gitee.com/xuyiping_admin/pkg/ginutil" "gitee.com/xuyiping_admin/pkg/apierr" "gitee.com/xuyiping_admin/pkg/xerr" "github.com/gin-gonic/gin" ) func Photos(c *gin.Context) { form, err := c.MultipartForm() if err != nil { apierr.AbortBadRequest(c, http.StatusBadRequest, xerr.Customf("No multipartForm: %s", err.Error())) return } files := form.File["uploads"] // 验证文件数量 if len(files) == 0 { apierr.AbortBadRequest(c, http.StatusBadRequest, xerr.Custom("No files selected")) return } res, err := middleware.BackendOperation(c).OpsService.Photos(c, files) if err != nil { apierr.AbortBadRequest(c, http.StatusBadRequest, err) return } c.JSON(http.StatusOK, gin.H{"code": http.StatusOK, "Msg": "ok", "data": res}) } func Files(c *gin.Context) { // 1. 获取上传的文件 file, err := c.FormFile("file") if err != nil { apierr.AbortBadRequest(c, http.StatusBadRequest, xerr.Customf("获取文件失败: %s", err.Error())) return } cf := GetExcelImportConfig(c.GetHeader("farmid")) // 2. 验证文件基本属性 if err = validateFile(file, cf); err != nil { apierr.AbortBadRequest(c, http.StatusBadRequest, err) return } // 3. 保存文件到临时目录 filePath, err := saveUploadedFile(c, file) if err != nil { apierr.AbortBadRequest(c, http.StatusInternalServerError, err) return } defer os.Remove(filePath) // 处理完成后删除临时文件 // 4. 读取并验证Excel文件 excelData, err := readExcelFile(filePath, filepath.Ext(file.Filename)) if err != nil { apierr.AbortBadRequest(c, http.StatusInternalServerError, err) return } // 5. 验证表头 if err = validateHeaders(excelData[0], cf.RequiredHeaders); err != nil { apierr.AbortBadRequest(c, http.StatusInternalServerError, err) return } // 实现处理逻辑 if err = middleware.BackendOperation(c).OpsService.ImportExcel2(c, excelData, cf.RequiredHeaders); err != nil { apierr.AbortBadRequest(c, http.StatusBadRequest, err) return } ginutil.JSONResp(c, &operationPb.CommonOK{ Code: http.StatusOK, Msg: "ok", Data: &operationPb.Success{Success: true}, }) } // ExcelImportConfig 导入配置 type ExcelImportConfig struct { AllowedExtensions []string MaxFileSize int64 RequiredHeaders []string // 必须包含的表头 HeadOfField []string } // readExcelFile 读取Excel文件 func readExcelFile(filePath, ext string) ([][]string, error) { // 尝试使用excelize读取 f, err := excelize.OpenFile(filePath) if err != nil { // 如果是xls格式且读取失败,尝试使用xls库 if ext == ".xls" { return readXlsFile(filePath) } return nil, xerr.Customf("读取Excel文件失败: %s", err.Error()) } defer f.Close() // 获取第一个工作表 sheetName := f.GetSheetName(0) rows, err := f.GetRows(sheetName) if err != nil { return nil, xerr.Customf("读取工作表失败: %s", err.Error()) } if len(rows) == 0 { return nil, xerr.Custom("Excel文件为空") } return rows, nil } // readXlsFile 使用xls库读取旧版Excel文件 func readXlsFile(filePath string) ([][]string, error) { // 这里需要实现xls文件的读取逻辑 // 可以使用第三方库如github.com/extrame/xls return nil, xerr.Custom("暂不支持.xls格式文件") } func GetExcelImportConfig(farmId string) *ExcelImportConfig { switch farmId { case "e50cd455dec93edf3d8135abbc770680": return &ExcelImportConfig{ AllowedExtensions: []string{".xlsx", ".xls"}, RequiredHeaders: []string{"", "", "耳号", "牛舍名称", "性别", "出生日期", "胎次", "进场日期", "父号", "母号", "最近配种日期", "预产期", "配后天数", "累计配次", "最近妊检日期", "最近产犊日期", "产后天数", "品种", "出生重量", "断奶日期", "是否禁配", "体重", "体高", "流产日期", "流产原因"}, HeadOfField: []string{"", "", "earNumber", "penName", "sex", "birthAt", "lact", "admissionAt", "fatherNumber", "motherNumber", "lastMatingAt", "", "matingAge", "allMatingTimes", "lastPregnantCheckAt", "lastCalvingAt", "lactationAge", "cowKind", "birthWeight", "weaningAt", "isForbiddenMating", "currentWeight", "currenWtHeight", "lastAbortionAt", ""}, MaxFileSize: 10 << 20, } default: return nil } } // ExcelHandler Excel处理接口 type ExcelHandler interface { ProcessExcelData([][]string) error } // validateFile 验证文件基本属性 func validateFile(file *multipart.FileHeader, cfg *ExcelImportConfig) error { // 检查文件扩展名 ext := strings.ToLower(filepath.Ext(file.Filename)) validExt := false for _, allowedExt := range cfg.AllowedExtensions { if ext == allowedExt { validExt = true break } } if !validExt { return xerr.Customf("不支持的文件类型,仅支持: %v", cfg.AllowedExtensions) } // 检查文件大小 if file.Size > cfg.MaxFileSize { return xerr.Customf("文件大小超过限制(最大%dMB)", cfg.MaxFileSize>>20) } return nil } // saveUploadedFile 保存上传的文件 func saveUploadedFile(c *gin.Context, file *multipart.FileHeader) (string, error) { // 创建临时目录 uploadDir := filepath.Join(config.WorkDir, "temp", "excel") if err := os.MkdirAll(uploadDir, 0755); err != nil { return "", xerr.Customf("创建临时目录失败: %s", err.Error()) } // 生成唯一文件名 filename := fmt.Sprintf("%d%s", time.Now().UnixNano(), filepath.Ext(file.Filename)) filePath := filepath.Join(uploadDir, filename) // 保存文件 if err := c.SaveUploadedFile(file, filePath); err != nil { return "", xerr.Customf("保存文件失败: %s", err.Error()) } return filePath, nil } // validateHeaders 验证表头 func validateHeaders(headers []string, requiredHeaders []string) error { if len(requiredHeaders) == 0 { return nil } headerMap := make(map[string]bool) for _, h := range headers { headerMap[strings.TrimSpace(h)] = true } var missingHeaders []string for _, req := range requiredHeaders { if req == "" { continue } if !headerMap[req] { missingHeaders = append(missingHeaders, req) } } if len(missingHeaders) > 0 { return xerr.Customf("缺少必要表头: %v", missingHeaders) } return nil } func OssVideo(c *gin.Context) { filename := c.Param("name") videoPath := fmt.Sprintf("%s/files/video/%s", config.WorkDir, filename) // 打开视频文件 file, err := os.Open(videoPath) if err != nil { c.JSON(http.StatusNotFound, gin.H{"error": "Video not found"}) return } defer file.Close() fileInfo, err := file.Stat() if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": "Could not get file info"}) return } // 设置响应头 c.Header("Content-Type", "video/mp4") c.Header("Content-Length", fmt.Sprintf("%d", fileInfo.Size())) // 流式传输文件内容 http.ServeContent(c.Writer, c.Request, fileInfo.Name(), fileInfo.ModTime(), file) }