|
@@ -0,0 +1,701 @@
|
|
|
|
+// 导入导出功能
|
|
|
|
+
|
|
|
|
+package api
|
|
|
|
+
|
|
|
|
+import (
|
|
|
|
+ "archive/zip"
|
|
|
|
+ "bytes"
|
|
|
|
+ "fmt"
|
|
|
|
+ "io"
|
|
|
|
+ "net/http"
|
|
|
|
+ "os"
|
|
|
|
+ "path"
|
|
|
|
+ "path/filepath"
|
|
|
|
+ "sku3dweb/db/model"
|
|
|
|
+ "sku3dweb/db/repo"
|
|
|
|
+ "sku3dweb/log"
|
|
|
|
+ "strconv"
|
|
|
|
+ "strings"
|
|
|
|
+ "time"
|
|
|
|
+
|
|
|
|
+ "github.com/gin-gonic/gin"
|
|
|
|
+ excelize "github.com/xuri/excelize/v2"
|
|
|
|
+ "go.mongodb.org/mongo-driver/bson/primitive"
|
|
|
|
+)
|
|
|
|
+
|
|
|
|
+// 固定Excel模板表头
|
|
|
|
+var fixedHeaders = []string{
|
|
|
|
+ "公司商品编号", "商品中文名", "商品英文名", "分类", "图片",
|
|
|
|
+ "单位包材毛重(KG)", "单位包材体积(CBM)", "长度(MM)", "门幅/宽(MM)", "厚度/高(MM)",
|
|
|
|
+ "备注", "首选供应商", "默认采购单价", "样品搜集人", "开发日期",
|
|
|
|
+ "出口属性", "内销属性", "内购属性", "委外属性",
|
|
|
|
+ "报关助记符", "报关商品编码", "报关商品中文名", "录入人名称", "适合的市场",
|
|
|
|
+ "供应商编号", "种类分类", "种类分类名称", "基布", "基布名称",
|
|
|
|
+ "表面工艺", "表面工艺名称", "产品克重(KG)", "运营周期", "商品单位体积",
|
|
|
|
+ "商品分类代码", "产品系列", "产品系列", "产品用途", "产品用途",
|
|
|
|
+ "样品编号", "留样册号", "原命名编号", "底布克重", "面布克重",
|
|
|
|
+ // "原始大图", "封面图", "商品图片",
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+func RegExcelRouter(router *GinRouter) {
|
|
|
|
+ // router.POSTJWT("/excel/import", ExcelImport)
|
|
|
|
+ // router.GETJWT("/excel/export", ExcelExport)
|
|
|
|
+ router.POSTJWT("/zip/import", ZipImport)
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+func ExcelImportWithImages(c *gin.Context, apictx *ApiSession, file io.Reader, goodsDir, textureDir string) (interface{}, error) {
|
|
|
|
+ // 读取Excel文件
|
|
|
|
+ xlsx, err := excelize.OpenReader(file)
|
|
|
|
+ if err != nil {
|
|
|
|
+ return nil, NewError("解析Excel文件失败")
|
|
|
|
+ }
|
|
|
|
+ defer func() {
|
|
|
|
+ if err := xlsx.Close(); err != nil {
|
|
|
|
+ log.Errorf("关闭Excel文件失败: %v", err)
|
|
|
|
+ }
|
|
|
|
+ }()
|
|
|
|
+
|
|
|
|
+ // 获取第一个sheet
|
|
|
|
+ sheetName := xlsx.GetSheetName(0)
|
|
|
|
+ rows, err := xlsx.GetRows(sheetName)
|
|
|
|
+ if err != nil {
|
|
|
|
+ return nil, NewError("读取Excel内容失败")
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ // 确保至少有表头和一行数据
|
|
|
|
+ if len(rows) < 2 {
|
|
|
|
+ return nil, NewError("Excel文件内容不足")
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ // 预热云函数
|
|
|
|
+ _, _ = QueryFassiImage("http://lymat.oss-cn-hangzhou.aliyuncs.com/images/1744072972845.png", 1, 0, 0)
|
|
|
|
+
|
|
|
|
+ // 获取分类配置
|
|
|
|
+ cat := []*model.Category{}
|
|
|
|
+ found, err := repo.RepoSeachDoc(apictx.CreateRepoCtx(), &repo.DocSearchOptions{
|
|
|
|
+ CollectName: repo.CollectionCategory,
|
|
|
|
+ Query: repo.Map{},
|
|
|
|
+ Project: []string{"name", "type", "children", "createTime"},
|
|
|
|
+ }, cat)
|
|
|
|
+ if err != nil || !found {
|
|
|
|
+ return nil, NewError("获取分类配置失败")
|
|
|
|
+ }
|
|
|
|
+ cates := cat[0].Children
|
|
|
|
+
|
|
|
|
+ // 创建导入结果记录
|
|
|
|
+ type ImportResult struct {
|
|
|
|
+ RowIndex int
|
|
|
|
+ Status string // "成功" 或 "失败"
|
|
|
|
+ ErrorMessage string
|
|
|
|
+ ImageID string
|
|
|
|
+ }
|
|
|
|
+ importResults := make([]ImportResult, 0, len(rows)-1)
|
|
|
|
+
|
|
|
|
+ // 根据模板解析每列数据
|
|
|
|
+ for i, row := range rows {
|
|
|
|
+ // 跳过表头
|
|
|
|
+ if i == 0 {
|
|
|
|
+ continue
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ // 创建导入结果记录
|
|
|
|
+ result := ImportResult{
|
|
|
|
+ RowIndex: i,
|
|
|
|
+ Status: "成功",
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ imageMat := model.MatImage{}
|
|
|
|
+ // 构建基础数据
|
|
|
|
+ imageMat.CusNum = row[0]
|
|
|
|
+ imageMat.NameCN = row[1]
|
|
|
|
+ imageMat.NameEN = row[2]
|
|
|
|
+ // 根据分类层级一的名字获取对应id,遍历一层获取对应数据
|
|
|
|
+ row3Cate := &model.Category{}
|
|
|
|
+ for _, cate := range cates {
|
|
|
|
+ if cate.Name == "商品分类" {
|
|
|
|
+ for _, c := range cate.Children {
|
|
|
|
+ if c.Name == row[3] {
|
|
|
|
+ // 记录该分类为其他自动做准备
|
|
|
|
+ row3Cate = c
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ if len(row3Cate.IdStr) <= 0 {
|
|
|
|
+ // 记录日志跳过
|
|
|
|
+ result.Status = "失败"
|
|
|
|
+ result.ErrorMessage = "商品分类未找到"
|
|
|
|
+ importResults = append(importResults, result)
|
|
|
|
+ continue
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ // 获取rowCate下的二级分类
|
|
|
|
+ var getRowCate2 = func(rowCate *model.Category, pName string, name string, cusNum string) *model.Category {
|
|
|
|
+ for _, cate := range rowCate.Children {
|
|
|
|
+ if cate.Name == pName {
|
|
|
|
+ for _, c := range cate.Children {
|
|
|
|
+ if c.Name == name {
|
|
|
|
+ if len(cusNum) > 0 {
|
|
|
|
+ if c.CusNum == cusNum {
|
|
|
|
+ return c
|
|
|
|
+ }
|
|
|
|
+ return nil
|
|
|
|
+ }
|
|
|
|
+ return c
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ }
|
|
|
|
+ return nil
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ imageMat.Categories = append(imageMat.Categories, row3Cate.IdStr)
|
|
|
|
+
|
|
|
|
+ // TODO 跳过图片 后面统一处理
|
|
|
|
+ var str2float64 = func(s string) float64 {
|
|
|
|
+ f, err := strconv.ParseFloat(s, 64)
|
|
|
|
+ if err != nil {
|
|
|
|
+ return 0 // 或 math.NaN() 表示无效值
|
|
|
|
+ }
|
|
|
|
+ return f
|
|
|
|
+ }
|
|
|
|
+ row5 := str2float64(row[5])
|
|
|
|
+ imageMat.PackageGrossWeight = &row5
|
|
|
|
+ row6 := str2float64(row[6])
|
|
|
|
+ imageMat.PackageVolume = &row6
|
|
|
|
+ row7 := str2float64(row[7])
|
|
|
|
+ imageMat.PhyHeight = &row7
|
|
|
|
+ row8 := str2float64(row[8])
|
|
|
|
+ imageMat.PhyWidth = &row8
|
|
|
|
+ row9 := str2float64(row[9])
|
|
|
|
+ imageMat.Thickness = &row9
|
|
|
|
+ imageMat.Remarks = row[10]
|
|
|
|
+ row11Cate := &model.Category{}
|
|
|
|
+ for _, cate := range cates {
|
|
|
|
+ if cate.Name == "首选供应商" {
|
|
|
|
+ for _, c := range cate.Children {
|
|
|
|
+ if c.Name == row[11] {
|
|
|
|
+ // 记录该分类为其他自动做准备
|
|
|
|
+ row11Cate = c
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ if len(row11Cate.IdStr) <= 0 {
|
|
|
|
+ // 记录日志跳过
|
|
|
|
+ result.Status = "失败"
|
|
|
|
+ result.ErrorMessage = "首选供应商未找到"
|
|
|
|
+ importResults = append(importResults, result)
|
|
|
|
+ continue
|
|
|
|
+ }
|
|
|
|
+ imageMat.Categories = append(imageMat.Categories, row11Cate.IdStr)
|
|
|
|
+
|
|
|
|
+ var str2int = func(s string) int {
|
|
|
|
+ f, err := strconv.Atoi(s)
|
|
|
|
+ if err != nil {
|
|
|
|
+ return 0 // 或 math.NaN() 表示无效值
|
|
|
|
+ }
|
|
|
|
+ return f
|
|
|
|
+ }
|
|
|
|
+ row12 := str2int(row[12])
|
|
|
|
+ imageMat.Price = &row12
|
|
|
|
+ staffName := row[14]
|
|
|
|
+ if len(staffName) > 0 {
|
|
|
|
+ staff := model.StaffUser{}
|
|
|
|
+ // 验证样品收集人是否预设
|
|
|
|
+ found, _ := repo.RepoSeachDoc(apictx.CreateRepoCtx(), &repo.DocSearchOptions{
|
|
|
|
+ CollectName: repo.CollectionStaffUser,
|
|
|
|
+ Query: repo.Map{"name": row[14]},
|
|
|
|
+ }, &staff)
|
|
|
|
+ if found {
|
|
|
|
+ imageMat.From = staffName
|
|
|
|
+ } else {
|
|
|
|
+ // 记录日志
|
|
|
|
+ result.Status = "失败"
|
|
|
|
+ result.ErrorMessage = "样品收集人未预设"
|
|
|
|
+ importResults = append(importResults, result)
|
|
|
|
+ continue
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ //开发日期
|
|
|
|
+ if len(row[15]) > 0 {
|
|
|
|
+ layout := "2006/1/2"
|
|
|
|
+ // 解析字符串
|
|
|
|
+ devTime, _ := time.Parse(layout, row[15])
|
|
|
|
+ imageMat.DevTime = &devTime
|
|
|
|
+ }
|
|
|
|
+ var str2bool = func(s string) *bool {
|
|
|
|
+ result := false
|
|
|
|
+ if s == "True" {
|
|
|
|
+ result = true
|
|
|
|
+ }
|
|
|
|
+ return &result
|
|
|
|
+ }
|
|
|
|
+ // 出口属性
|
|
|
|
+ imageMat.ExportProperty = str2bool(row[16])
|
|
|
|
+ // 内销属性
|
|
|
|
+ imageMat.DomesticProperty = str2bool(row[17])
|
|
|
|
+ // 内购属性
|
|
|
|
+ imageMat.InpurchaseProperty = str2bool(row[18])
|
|
|
|
+ // 委外属性
|
|
|
|
+ imageMat.OutsourcedProperty = str2bool(row[19])
|
|
|
|
+
|
|
|
|
+ // 报关助记
|
|
|
|
+ // 根据分类层级一的名字获取对应id,遍历一层获取对应数据
|
|
|
|
+ row20Cate := &model.Category{}
|
|
|
|
+ for _, cate := range cates {
|
|
|
|
+ if cate.Name == "报关助记符" {
|
|
|
|
+ for _, c := range cate.Children {
|
|
|
|
+ if c.Name == row[20] {
|
|
|
|
+ // 记录该分类为其他自动做准备
|
|
|
|
+ row20Cate = c
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ if len(row20Cate.IdStr) <= 0 {
|
|
|
|
+ // 记录日志跳过
|
|
|
|
+ result.Status = "失败"
|
|
|
|
+ result.ErrorMessage = "报关助记符未找到"
|
|
|
|
+ importResults = append(importResults, result)
|
|
|
|
+ continue
|
|
|
|
+ }
|
|
|
|
+ imageMat.Categories = append(imageMat.Categories, row20Cate.IdStr)
|
|
|
|
+ imageMat.TaxNameCN = row[22]
|
|
|
|
+ // 录入人
|
|
|
|
+ imageMat.UserId, _ = primitive.ObjectIDFromHex(apictx.User.ID)
|
|
|
|
+ imageMat.FitMarket = row[24]
|
|
|
|
+ // 供应商编号
|
|
|
|
+ imageMat.SupplierID = row[25]
|
|
|
|
+
|
|
|
|
+ // 种类分类
|
|
|
|
+ row26Cate := getRowCate2(row3Cate, "种类分类", row[27], row[26])
|
|
|
|
+ if row26Cate == nil {
|
|
|
|
+ // 没有找到对应分类
|
|
|
|
+ result.Status = "失败"
|
|
|
|
+ result.ErrorMessage = "种类分类未找到"
|
|
|
|
+ importResults = append(importResults, result)
|
|
|
|
+ continue
|
|
|
|
+ }
|
|
|
|
+ imageMat.Categories = append(imageMat.Categories, row26Cate.IdStr)
|
|
|
|
+
|
|
|
|
+ // 基布
|
|
|
|
+ row28Cate := getRowCate2(row3Cate, "基布", row[29], row[28])
|
|
|
|
+ if row28Cate == nil {
|
|
|
|
+ // 没有找到对应分类
|
|
|
|
+ result.Status = "失败"
|
|
|
|
+ result.ErrorMessage = "基布未找到"
|
|
|
|
+ importResults = append(importResults, result)
|
|
|
|
+ continue
|
|
|
|
+ }
|
|
|
|
+ imageMat.Categories = append(imageMat.Categories, row28Cate.IdStr)
|
|
|
|
+
|
|
|
|
+ // 表面工艺
|
|
|
|
+ row30Cate := getRowCate2(row3Cate, "表面工艺", row[31], row[30])
|
|
|
|
+ if row30Cate == nil {
|
|
|
|
+ // 没有找到对应分类
|
|
|
|
+ result.Status = "失败"
|
|
|
|
+ result.ErrorMessage = "表面工艺未找到"
|
|
|
|
+ importResults = append(importResults, result)
|
|
|
|
+ continue
|
|
|
|
+ }
|
|
|
|
+ imageMat.Categories = append(imageMat.Categories, row30Cate.IdStr)
|
|
|
|
+
|
|
|
|
+ // 产品克重
|
|
|
|
+ pw := str2float64(row[32])
|
|
|
|
+ imageMat.ProductWeight = &pw
|
|
|
|
+ // 运营周期
|
|
|
|
+ imageMat.OperationCycle = row[33]
|
|
|
|
+ // 商品单位体积
|
|
|
|
+ pv := str2float64(row[34])
|
|
|
|
+ imageMat.ProductVolume = &pv
|
|
|
|
+
|
|
|
|
+ // ??? 产品分类代码
|
|
|
|
+
|
|
|
|
+ // 产品系列
|
|
|
|
+ row36Cate := getRowCate2(row3Cate, "产品系列", row[37], row[36])
|
|
|
|
+ if row36Cate == nil {
|
|
|
|
+ // 没有找到对应分类
|
|
|
|
+ result.Status = "失败"
|
|
|
|
+ result.ErrorMessage = "产品系列未找到"
|
|
|
|
+ importResults = append(importResults, result)
|
|
|
|
+ continue
|
|
|
|
+ }
|
|
|
|
+ imageMat.Categories = append(imageMat.Categories, row36Cate.IdStr)
|
|
|
|
+
|
|
|
|
+ // 产品用途
|
|
|
|
+ row38Cate := getRowCate2(row3Cate, "产品用途", row[39], row[38])
|
|
|
|
+ if row38Cate == nil {
|
|
|
|
+ // 没有找到对应分类
|
|
|
|
+ result.Status = "失败"
|
|
|
|
+ result.ErrorMessage = "产品用途未找到"
|
|
|
|
+ importResults = append(importResults, result)
|
|
|
|
+ continue
|
|
|
|
+ }
|
|
|
|
+ imageMat.Categories = append(imageMat.Categories, row38Cate.IdStr)
|
|
|
|
+
|
|
|
|
+ // 样品编号
|
|
|
|
+ imageMat.SampleNumber = row[40]
|
|
|
|
+ // 留样册号
|
|
|
|
+ imageMat.CatalogNumber = row[41]
|
|
|
|
+ // 原命名编号
|
|
|
|
+ imageMat.OriginalNumber = row[42]
|
|
|
|
+ // 底布克重
|
|
|
|
+ bw := str2float64(row[43])
|
|
|
|
+ imageMat.BackingWeight = &bw
|
|
|
|
+ // 面布克重
|
|
|
|
+ sw := str2float64(row[44])
|
|
|
|
+ imageMat.SurfaceWeight = &sw
|
|
|
|
+
|
|
|
|
+ imageName := row[42] // 不包含后缀 .jpg .png .jpeg
|
|
|
|
+ // 根据图片名称检查goods和texture中是否存在,图片可能是.jpg .png .jpeg后缀
|
|
|
|
+ // 上传图片到oss 获取对应url,赋值到imageMat(Thumbnail, RawImage, ProductImage)
|
|
|
|
+
|
|
|
|
+ // 在goods目录中查找图片
|
|
|
|
+ goodsImagePath := findImageFile(goodsDir, imageName)
|
|
|
|
+ textureImagePath := findImageFile(textureDir, imageName)
|
|
|
|
+
|
|
|
|
+ // 如果找到了图片,上传到OSS
|
|
|
|
+ if goodsImagePath != "" {
|
|
|
|
+ // 上传原始图片作为RawImage
|
|
|
|
+ goodsImage, err := uploadLocalImage(goodsImagePath, "goods", apictx)
|
|
|
|
+ if err != nil {
|
|
|
|
+ log.Errorf("上传图片失败: %v", err)
|
|
|
|
+
|
|
|
|
+ } else {
|
|
|
|
+ imageMat.ProductImage = goodsImage
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ if textureImagePath != "" {
|
|
|
|
+ // 上传原始图片作为RawImage
|
|
|
|
+ textureImage, err := uploadLocalImage(textureImagePath, "texture", apictx)
|
|
|
|
+ if err != nil {
|
|
|
|
+ log.Errorf("上传图片失败: %v", err)
|
|
|
|
+
|
|
|
|
+ } else {
|
|
|
|
+ imageMat.RawImage = textureImage
|
|
|
|
+ imageMat.Thumbnail = textureImage
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ // 设置创建时间
|
|
|
|
+ imageMat.CreateTime = time.Now()
|
|
|
|
+ imageMat.UpdateTime = time.Now()
|
|
|
|
+
|
|
|
|
+ // 写入到数据库中并获取创建记录的数据库id
|
|
|
|
+ imgId, err := repo.RepoAddDoc(apictx.CreateRepoCtx(), repo.CollectionMatImages, &imageMat)
|
|
|
|
+ if err != nil {
|
|
|
|
+ log.Errorf("写入记录失败: %v", err)
|
|
|
|
+ result.Status = "失败"
|
|
|
|
+ result.ErrorMessage = "数据库写入失败: " + err.Error()
|
|
|
|
+ importResults = append(importResults, result)
|
|
|
|
+ continue
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ result.ImageID = imgId
|
|
|
|
+
|
|
|
|
+ // 如果texture存在,调用AddFassiImage创建图片特征和id的关联
|
|
|
|
+ if textureImagePath != "" && imageMat.RawImage != nil {
|
|
|
|
+ objId, err := primitive.ObjectIDFromHex(imgId)
|
|
|
|
+ if err != nil {
|
|
|
|
+ log.Errorf("转换ID失败: %v", err)
|
|
|
|
+
|
|
|
|
+ } else {
|
|
|
|
+ err = AddFassiImage(objId, imageMat.RawImage.Url)
|
|
|
|
+ if err != nil {
|
|
|
|
+ log.Errorf("创建Fassi图片关联失败: %v", err)
|
|
|
|
+
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ importResults = append(importResults, result)
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ // 创建一个新的Excel文件用于导出结果
|
|
|
|
+ resultExcel := excelize.NewFile()
|
|
|
|
+ sheet := "Sheet1"
|
|
|
|
+
|
|
|
|
+ // 复制原始表头并添加状态列
|
|
|
|
+ headers := append(rows[0], "导入状态", "失败原因", "数据库ID")
|
|
|
|
+ for i, header := range headers {
|
|
|
|
+ colName := string(rune('A' + i))
|
|
|
|
+ resultExcel.SetCellValue(sheet, colName+"1", header)
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ // 写入每行数据和导入结果
|
|
|
|
+ for i, row := range rows {
|
|
|
|
+ if i == 0 {
|
|
|
|
+ continue // 跳过表头
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ // 查找对应的导入结果
|
|
|
|
+ var result ImportResult
|
|
|
|
+ for _, r := range importResults {
|
|
|
|
+ if r.RowIndex == i {
|
|
|
|
+ result = r
|
|
|
|
+ break
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ // 写入原始数据
|
|
|
|
+ for j, cell := range row {
|
|
|
|
+ colName := string(rune('A' + j))
|
|
|
|
+ resultExcel.SetCellValue(sheet, colName+strconv.Itoa(i+1), cell)
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ // 添加状态和错误信息
|
|
|
|
+ statusCol := string(rune('A' + len(row)))
|
|
|
|
+ errorCol := string(rune('A' + len(row) + 1))
|
|
|
|
+ idCol := string(rune('A' + len(row) + 2))
|
|
|
|
+
|
|
|
|
+ resultExcel.SetCellValue(sheet, statusCol+strconv.Itoa(i+1), result.Status)
|
|
|
|
+ resultExcel.SetCellValue(sheet, errorCol+strconv.Itoa(i+1), result.ErrorMessage)
|
|
|
|
+ resultExcel.SetCellValue(sheet, idCol+strconv.Itoa(i+1), result.ImageID)
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ // 设置列宽
|
|
|
|
+ for i := range headers {
|
|
|
|
+ colName := string(rune('A' + i))
|
|
|
|
+ resultExcel.SetColWidth(sheet, colName, colName, 15)
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ // 保存到内存缓冲区
|
|
|
|
+ buffer := bytes.Buffer{}
|
|
|
|
+ if err := resultExcel.Write(&buffer); err != nil {
|
|
|
|
+ return nil, NewError("生成导入结果文件失败")
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ // 设置响应头,使浏览器下载文件
|
|
|
|
+ c.Header("Content-Description", "File Transfer")
|
|
|
|
+ c.Header("Content-Disposition", "attachment; filename=import_result_"+time.Now().Format("20060102150405")+".xlsx")
|
|
|
|
+ c.Data(http.StatusOK, "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", buffer.Bytes())
|
|
|
|
+
|
|
|
|
+ // 统计导入结果
|
|
|
|
+ successCount := 0
|
|
|
|
+ failCount := 0
|
|
|
|
+ partialCount := 0
|
|
|
|
+
|
|
|
|
+ for _, result := range importResults {
|
|
|
|
+ switch result.Status {
|
|
|
|
+ case "成功":
|
|
|
|
+ successCount++
|
|
|
|
+ case "失败":
|
|
|
|
+ failCount++
|
|
|
|
+ case "部分成功":
|
|
|
|
+ partialCount++
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ return map[string]interface{}{
|
|
|
|
+ "total": len(importResults),
|
|
|
|
+ "success": successCount,
|
|
|
|
+ "failed": failCount,
|
|
|
|
+ "partial": partialCount,
|
|
|
|
+ }, nil
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+// 新增ZIP文件导入处理函数
|
|
|
|
+func ZipImport(c *gin.Context, apictx *ApiSession) (interface{}, error) {
|
|
|
|
+ // 获取上传的文件
|
|
|
|
+ file, header, err := c.Request.FormFile("file")
|
|
|
|
+ if err != nil {
|
|
|
|
+ return nil, NewError("获取上传文件失败")
|
|
|
|
+ }
|
|
|
|
+ defer file.Close()
|
|
|
|
+
|
|
|
|
+ // 检查文件大小
|
|
|
|
+ if header.Size > 100*1024*1024 { // 限制100MB
|
|
|
|
+ return nil, NewError("上传文件过大,请控制在100MB以内")
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ // 检查文件扩展名
|
|
|
|
+ if !strings.HasSuffix(strings.ToLower(header.Filename), ".zip") {
|
|
|
|
+ return nil, NewError("只支持上传ZIP格式的文件")
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ // 创建临时目录存放解压文件
|
|
|
|
+ tempDir, err := os.MkdirTemp("", "sku3d_import_"+time.Now().Format("20060102150405"))
|
|
|
|
+ if err != nil {
|
|
|
|
+ return nil, NewError("创建临时目录失败")
|
|
|
|
+ }
|
|
|
|
+ defer os.RemoveAll(tempDir) // 确保处理完成后删除临时目录
|
|
|
|
+
|
|
|
|
+ // 保存上传的ZIP文件
|
|
|
|
+ zipFilePath := path.Join(tempDir, "upload.zip")
|
|
|
|
+ tempZipFile, err := os.Create(zipFilePath)
|
|
|
|
+ if err != nil {
|
|
|
|
+ return nil, NewError("创建临时文件失败")
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ // 将上传的文件内容写入临时文件
|
|
|
|
+ _, err = io.Copy(tempZipFile, file)
|
|
|
|
+ tempZipFile.Close()
|
|
|
|
+ if err != nil {
|
|
|
|
+ return nil, NewError("保存上传文件失败")
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ // 解压ZIP文件
|
|
|
|
+ extractDir := path.Join(tempDir, "extract")
|
|
|
|
+ err = os.MkdirAll(extractDir, 0755)
|
|
|
|
+ if err != nil {
|
|
|
|
+ return nil, NewError("创建解压目录失败")
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ err = unzipFile(zipFilePath, extractDir)
|
|
|
|
+ if err != nil {
|
|
|
|
+ return nil, NewError("解压文件失败: " + err.Error())
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ // 查找Excel文件
|
|
|
|
+ excelFilePath, err := findExcelFile(extractDir)
|
|
|
|
+ if err != nil {
|
|
|
|
+ return nil, NewError("未找到有效的Excel文件: " + err.Error())
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ // 打开Excel文件
|
|
|
|
+ excelFile, err := os.Open(excelFilePath)
|
|
|
|
+ if err != nil {
|
|
|
|
+ return nil, NewError("打开Excel文件失败")
|
|
|
|
+ }
|
|
|
|
+ defer excelFile.Close()
|
|
|
|
+
|
|
|
|
+ // 商品图片目录和纹理图片目录
|
|
|
|
+ goodsDir := path.Join(extractDir, "goods")
|
|
|
|
+ textureDir := path.Join(extractDir, "texture")
|
|
|
|
+
|
|
|
|
+ // 检查目录是否存在
|
|
|
|
+ if _, err := os.Stat(goodsDir); os.IsNotExist(err) {
|
|
|
|
+ log.Errorf("商品图片目录不存在: %s", goodsDir)
|
|
|
|
+ goodsDir = ""
|
|
|
|
+ }
|
|
|
|
+ if _, err := os.Stat(textureDir); os.IsNotExist(err) {
|
|
|
|
+ log.Errorf("纹理图片目录不存在: %s", textureDir)
|
|
|
|
+ textureDir = ""
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ // 传递给Excel导入函数处理,并指定图片目录
|
|
|
|
+ return ExcelImportWithImages(c, apictx, excelFile, goodsDir, textureDir)
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+// 解压ZIP文件到指定目录
|
|
|
|
+func unzipFile(zipFile, destDir string) error {
|
|
|
|
+ r, err := zip.OpenReader(zipFile)
|
|
|
|
+ if err != nil {
|
|
|
|
+ return err
|
|
|
|
+ }
|
|
|
|
+ defer r.Close()
|
|
|
|
+
|
|
|
|
+ for _, f := range r.File {
|
|
|
|
+ // 处理路径安全问题
|
|
|
|
+ filePath := filepath.Join(destDir, f.Name)
|
|
|
|
+ if !strings.HasPrefix(filePath, filepath.Clean(destDir)+string(os.PathSeparator)) {
|
|
|
|
+ return fmt.Errorf("非法的ZIP文件路径: %s", f.Name)
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ if f.FileInfo().IsDir() {
|
|
|
|
+ // 创建目录
|
|
|
|
+ if err := os.MkdirAll(filePath, os.ModePerm); err != nil {
|
|
|
|
+ return err
|
|
|
|
+ }
|
|
|
|
+ continue
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ // 确保父目录存在
|
|
|
|
+ if err := os.MkdirAll(filepath.Dir(filePath), os.ModePerm); err != nil {
|
|
|
|
+ return err
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ // 创建文件
|
|
|
|
+ outFile, err := os.OpenFile(filePath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, f.Mode())
|
|
|
|
+ if err != nil {
|
|
|
|
+ return err
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ // 打开压缩文件
|
|
|
|
+ rc, err := f.Open()
|
|
|
|
+ if err != nil {
|
|
|
|
+ outFile.Close()
|
|
|
|
+ return err
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ // 复制内容
|
|
|
|
+ _, err = io.Copy(outFile, rc)
|
|
|
|
+ outFile.Close()
|
|
|
|
+ rc.Close()
|
|
|
|
+ if err != nil {
|
|
|
|
+ return err
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ return nil
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+// 寻找目录中的Excel文件
|
|
|
|
+func findExcelFile(dir string) (string, error) {
|
|
|
|
+ var excelFiles []string
|
|
|
|
+ err := filepath.Walk(dir, func(path string, info os.FileInfo, err error) error {
|
|
|
|
+ if err != nil {
|
|
|
|
+ return err
|
|
|
|
+ }
|
|
|
|
+ if !info.IsDir() {
|
|
|
|
+ lowerPath := strings.ToLower(path)
|
|
|
|
+ if strings.HasSuffix(lowerPath, ".xlsx") || strings.HasSuffix(lowerPath, ".xls") {
|
|
|
|
+ excelFiles = append(excelFiles, path)
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ return nil
|
|
|
|
+ })
|
|
|
|
+
|
|
|
|
+ if err != nil {
|
|
|
|
+ return "", err
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ if len(excelFiles) == 0 {
|
|
|
|
+ return "", fmt.Errorf("未找到Excel文件")
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ // 返回第一个找到的Excel文件
|
|
|
|
+ return excelFiles[0], nil
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+// 获取图片文件路径
|
|
|
|
+func findImageFile(dir, originalName string) string {
|
|
|
|
+ if dir == "" || originalName == "" {
|
|
|
|
+ return ""
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ // 检查不同扩展名的图片文件
|
|
|
|
+ for _, ext := range []string{".png", ".jpg", ".jpeg"} {
|
|
|
|
+ filePath := filepath.Join(dir, originalName+ext)
|
|
|
|
+ if _, err := os.Stat(filePath); err == nil {
|
|
|
|
+ return filePath
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ return ""
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+// 上传本地图片到OSS
|
|
|
|
+func uploadLocalImage(filePath string, prefix string, apictx *ApiSession) (*model.OssType, error) {
|
|
|
|
+ // 获取ObsClient
|
|
|
|
+ obsClient, err := CreateObsClient()
|
|
|
|
+ if err != nil {
|
|
|
|
+ return nil, fmt.Errorf("创建ObsClient失败: %v", err)
|
|
|
|
+ }
|
|
|
|
+ defer obsClient.Close()
|
|
|
|
+
|
|
|
|
+ // 上传图片到OSS
|
|
|
|
+ bucketName := apictx.Svc.Conf.Obs.Bucket
|
|
|
|
+ ossPath := fmt.Sprintf("u/%s/%s", apictx.User.ID, prefix)
|
|
|
|
+ return UploadFile(obsClient, bucketName, filePath, ossPath), nil
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+// 工具函数 - 获取图片URL
|
|
|
|
+func getImageUrl(oss *model.OssType) string {
|
|
|
|
+ if oss == nil {
|
|
|
|
+ return ""
|
|
|
|
+ }
|
|
|
|
+ return oss.Url
|
|
|
|
+}
|