| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250 | package uploadimport (	"fmt"	"kpt-pasture/config"	"kpt-pasture/http/middleware"	"mime/multipart"	"net/http"	"os"	"path/filepath"	"strings"	"time"	"gitee.com/xuyiping_admin/pkg/apierr"	"gitee.com/xuyiping_admin/pkg/xerr"	"github.com/gin-gonic/gin"	"github.com/xuri/excelize/v2")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 Files2(c *gin.Context) {	// 定义配置	cf := ExcelImportConfig{		AllowedExtensions: []string{".xlsx", ".xls"},		RequiredHeaders: []string{"耳号", "牛舍名称", "性别", "出生日期", "胎次", "进场日期", "父号", "母号", "最近配种日期", "预产期",			"配后天数", "累计配次", "最近妊检日期", "最近产犊日期", "产后天数", "品种", "出生重量", "断奶日期", "是否禁配", "体重", "体高", "流产日期", "流产原因",		},		MaxFileSize: 10 << 20,	}	// 实现处理逻辑	ImportExcel(c, cf)}// ExcelImportConfig 导入配置type ExcelImportConfig struct {	AllowedExtensions []string	MaxFileSize       int64	RequiredHeaders   []string // 必须包含的表头}// DefaultExcelImportConfig 默认配置var DefaultExcelImportConfig = ExcelImportConfig{	AllowedExtensions: []string{".xlsx", ".xls"},	MaxFileSize:       10 << 20, // 10MB}// ExcelHandler Excel处理接口type ExcelHandler interface {	ProcessExcelData([][]string) error}// ImportExcel 导入Excel文件func ImportExcel(c *gin.Context, config ...ExcelImportConfig) {	// 获取配置	cfg := DefaultExcelImportConfig	if len(config) > 0 {		cfg = config[0]	}	// 1. 获取上传的文件	file, err := c.FormFile("file")	if err != nil {		apierr.AbortBadRequest(c, http.StatusBadRequest, xerr.Customf("获取文件失败: %s", err.Error()))		return	}	// 2. 验证文件基本属性	if err = validateFile(file, cfg); 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.StatusBadRequest, err)		return	}	// 5. 验证表头	if err = validateHeaders(excelData[0], cfg.RequiredHeaders); err != nil {		apierr.AbortBadRequest(c, http.StatusBadRequest, err)		return	}	if err = middleware.BackendOperation(c).OpsService.ImportExcel(c, excelData); err != nil {		apierr.AbortBadRequest(c, http.StatusBadRequest, err)		return	}	c.JSON(http.StatusOK, gin.H{		"code": http.StatusOK,		"msg":  "导入成功",		"data": nil,	})}// 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}// 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格式文件")}// 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 !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)}
 |