first push
This commit is contained in:
commit
38bc172e13
|
@ -0,0 +1,235 @@
|
|||
异步excel导入
|
||||
|
||||
|
||||
|
||||
## 安装
|
||||
|
||||
```bash
|
||||
$ go get -u gitea.cdlsxd.cn/self-tools/l_excel_import
|
||||
```
|
||||
|
||||
|
||||
## 使用
|
||||
```go
|
||||
importExcel, err := excel.NewImportExcel("goods_import",
|
||||
excel.WithHeader([]string{"条码", "分类名称", "货品名称", "货品编号", "商品货号", "品牌", "单位", "规格参数", "货品说明", "保质期", "保质期单位", "链接", "货品图片", "电商销售价格", "销售价", "供应商报价", "税率", "默认供应商", "默认存放仓库", "备注", "长", "宽", "高", "重量"}),
|
||||
excel.WithTrimFiled([]string{"分类名称", "默认供应商", "默认存放仓库", "货品编号", "商品货号"}),
|
||||
excel.WithFiledRegex(map[string]*excel.Regex{
|
||||
"货品编号": {
|
||||
Rule: "[\\u4e00-\\u9fa5]",
|
||||
MatchBool: true,
|
||||
Desc: "不能包含中文",
|
||||
},
|
||||
}),
|
||||
excel.WithSpeedMod(true),
|
||||
excel.WithRowPicHandle(func(pic *excel.Pic) (url string, err2 error) {
|
||||
ossConf := s.c.GetOss()
|
||||
businessMap, exist := ossConf.BusinessMap["goods"]
|
||||
if !exist {
|
||||
return "", errors.New("oss配置不存在")
|
||||
}
|
||||
//"physicalGoodsSystems/images/goods"
|
||||
url = s.GoodsBiz.OssPathFile(businessMap.Folder, pic.PicInfos.Format.AltText, pic.PicInfos.Extension)
|
||||
// 判断endpoint字段中是否存在internal的字段,如果存在则替换为internal
|
||||
urlEndpoint := businessMap.Endpoint
|
||||
if strings.Contains(businessMap.Endpoint, "-internal") {
|
||||
urlEndpoint = strings.Replace(businessMap.Endpoint, "-internal", "", 1)
|
||||
}
|
||||
return fmt.Sprintf("https://%s.%s/%s", businessMap.Bucket, urlEndpoint, url), oss.NewOss(ossConf.AccessKeyId, ossConf.AccessKeySecret).UploadBase64Byte(
|
||||
ctx,
|
||||
businessMap.Endpoint,
|
||||
businessMap.Bucket,
|
||||
url,
|
||||
pic.PicInfos.File)
|
||||
}),
|
||||
excel.WithDeleteOssHandle(func(ObjectName []string) (err error) {
|
||||
ossConf := s.c.GetOss()
|
||||
businessMap, exist := ossConf.BusinessMap["goods"]
|
||||
if !exist {
|
||||
return errors.New("oss配置不存在")
|
||||
}
|
||||
return oss.NewOss(ossConf.AccessKeyId, ossConf.AccessKeySecret).DeleteBatch(ctx, businessMap.Endpoint, businessMap.Bucket, ObjectName)
|
||||
}),
|
||||
excel.WithGetFileObject(func(fileObjectUrl string) (body io.ReadCloser, err error) {
|
||||
ossConf := s.c.GetOss()
|
||||
businessMap, exist := ossConf.BusinessMap["goods"]
|
||||
if !exist {
|
||||
return nil, errors.New("oss配置不存在")
|
||||
}
|
||||
return oss.NewOss(ossConf.AccessKeyId, ossConf.AccessKeySecret).GetObject(ctx, businessMap.Endpoint, businessMap.Bucket, fileObjectUrl)
|
||||
}),
|
||||
).Init(ctx, func(excel *excel.ImportExcel, rows []map[string]string) {
|
||||
var sellByData int
|
||||
//创建子上下文,防止父ctx取消导致无法请求rpc
|
||||
subCtx, cancel := context.WithCancel(context.Background())
|
||||
//协程结束后释放掉子上下文
|
||||
defer cancel()
|
||||
for k, v := range rows {
|
||||
var (
|
||||
supId int32
|
||||
WareHouseId int32
|
||||
CateId int32
|
||||
webPrice float64
|
||||
price float64
|
||||
costPrice float64
|
||||
)
|
||||
//这里用中文作key是为了方便,如果用英文key或者数字作key,需要做映射关系,而且还要考虑key可能是任意类型,而且开发的时候出现excel插入删除也很麻烦
|
||||
//分类
|
||||
if len(v["分类名称"]) > 30 {
|
||||
excel.AddErr("条码过长,请检查", k+1, v)
|
||||
continue
|
||||
}
|
||||
if v["分类名称"] != "" {
|
||||
if _, exist := cateMap[v["分类名称"]]; !exist {
|
||||
excel.AddErr("商品分类不存在", k+1, v)
|
||||
continue
|
||||
}
|
||||
CateId = cateMap[v["分类名称"]].Id
|
||||
}
|
||||
//供应商
|
||||
if v["默认供应商"] != "" {
|
||||
if _, exist := supplierMap[v["默认供应商"]]; !exist {
|
||||
excel.AddErr("供应商不存在", k+1, v)
|
||||
continue
|
||||
}
|
||||
supId = supplierMap[v["默认供应商"]].Id
|
||||
}
|
||||
//仓库
|
||||
if v["默认存放仓库"] != "" {
|
||||
if _, exist := wareHouseMap[v["默认存放仓库"]]; !exist {
|
||||
excel.AddErr("仓库不存在", k+1, v)
|
||||
continue
|
||||
}
|
||||
WareHouseId = int32(wareHouseMap[v["默认存放仓库"]].Id)
|
||||
}
|
||||
//电商价
|
||||
if v["电商销售价格"] != "" {
|
||||
webPrice, err = strconv.ParseFloat(v["电商销售价格"], 64)
|
||||
if err != nil {
|
||||
excel.AddErr("电商销售价格格式不正确", k+1, v)
|
||||
continue
|
||||
}
|
||||
}
|
||||
if v["销售价"] != "" {
|
||||
price, err = strconv.ParseFloat(v["销售价"], 64)
|
||||
if err != nil {
|
||||
excel.AddErr("销售价格式不正确", k+1, v)
|
||||
continue
|
||||
}
|
||||
}
|
||||
if v["供应商报价"] != "" {
|
||||
costPrice, err = strconv.ParseFloat(v["供应商报价"], 64)
|
||||
if err != nil {
|
||||
excel.AddErr("供应商报价格式不正确", k+1, v)
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
//保质期
|
||||
if v["保质期"] == "" {
|
||||
sellByData = 0
|
||||
} else {
|
||||
sellByData, err = strconv.Atoi(v["保质期"])
|
||||
if err != nil {
|
||||
excel.AddErr("保质期格式不正确", k+1, v)
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
//税率
|
||||
tax := util.RemovePercentSignsWithBuilder(v["税率"])
|
||||
taxRate, err := strconv.ParseFloat(tax, 64)
|
||||
if err != nil {
|
||||
excel.AddErr("税率格式不正确", k+1, v)
|
||||
continue
|
||||
}
|
||||
//"条码", "分类名称", "货品名称", "货品编号", "品牌", "单位", "规格参数", "货品说明", "保质期", "保质期单位", "链接", "货品图片", "电商销售价格", "代发含税运价格", "税率", "默认供应商", "默认存放仓库", "备注"}
|
||||
goodsInfo := &pb.AddGoodsReqs{
|
||||
//商品标题
|
||||
Title: v["货品名称"],
|
||||
//商品品牌
|
||||
Brand: v["品牌"],
|
||||
//商品简介、卖点
|
||||
Introduction: v["货品说明"],
|
||||
|
||||
//商品编码
|
||||
GoodsNum: v["货品编号"],
|
||||
//商品货号
|
||||
GoodsCode: v["商品货号"],
|
||||
//商品条形码
|
||||
GoodsBarCode: v["条码"],
|
||||
//是否组合商品
|
||||
IsComposeGoods: 2,
|
||||
//市场价,单位分
|
||||
Price: float32(price),
|
||||
//单位
|
||||
Unit: v["单位"],
|
||||
//保质期
|
||||
SellByDate: int32(sellByData),
|
||||
//保质期单位
|
||||
SellByDateUnit: v["保质期单位"],
|
||||
//外部平台链接
|
||||
ExternalUrl: v["链接"],
|
||||
//电商平台价格
|
||||
ExternalPrice: float32(webPrice),
|
||||
//销售价
|
||||
SalesPrice: float32(price),
|
||||
//税率
|
||||
TaxRate: float32(taxRate),
|
||||
//商品参数
|
||||
GoodsAttributes: v["规格参数"],
|
||||
//商品说明
|
||||
GoodsIllustration: v["货品说明"],
|
||||
//备注
|
||||
Remark: v["备注"],
|
||||
Status: pojo.STATUS_ENABLE,
|
||||
IsHot: 2,
|
||||
}
|
||||
|
||||
goodsAdd, err := goods.NewGoodsBiz(types.ToTmplConf(s.c)).Add(subCtx, goodsInfo)
|
||||
if err != nil {
|
||||
excel.AddErr(fmt.Sprintf("添加商品失败:%s", err), k+1, v)
|
||||
continue
|
||||
}
|
||||
if CateId != 0 {
|
||||
_, err = goods.NewGoodsCateGoryRelationBiz(types.ToTmplConf(s.c)).Add(subCtx, &api.AddGoodsCategoryRelationReqs{
|
||||
GoodsId: goodsAdd.Id,
|
||||
CategoryIds: []int32{CateId},
|
||||
})
|
||||
if err != nil {
|
||||
excel.AddErr(fmt.Sprintf("添加商品分类失败:%s", err), k+1, v)
|
||||
continue
|
||||
}
|
||||
}
|
||||
if WareHouseId != 0 && supId != 0 && costPrice != 0 {
|
||||
_, err = goods.NewGoodsSupplierRelationBiz(types.ToTmplConf(s.c)).Add(subCtx, &pb.AddGoodsSupplierRelationReqs{
|
||||
SupplierId: supId,
|
||||
GoodsId: goodsAdd.Id,
|
||||
WarehouseId: WareHouseId,
|
||||
IsDefaultWarehouse: pojo.IS_DEFAULT_WAREHOUSE,
|
||||
SupplierGoodsPrice: float32(costPrice),
|
||||
Sort: 1,
|
||||
})
|
||||
if err != nil {
|
||||
excel.AddErr(fmt.Sprintf("添加供应商商品关系失败:%s", err), k+1, v)
|
||||
continue
|
||||
}
|
||||
}
|
||||
if v["货品图片"] != "" { //商品图片
|
||||
for key, media := range strings.Split(v["货品图片"], ",") {
|
||||
_, _err := goods.NewGoodsMediaBiz(types.ToTmplConf(s.c)).Add(subCtx, &pb.AddGoodsMediaReqs{
|
||||
GoodsId: goodsAdd.Id,
|
||||
Url: media,
|
||||
Sort: int32(key + 1),
|
||||
Type: pojo.GOODS_MEDIA_TYPE_IMAGE,
|
||||
})
|
||||
if _err != nil {
|
||||
excel.AddErr(fmt.Sprintf("添加商品图片失败:%s", err), k+1, v)
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
excel.Next()
|
||||
}
|
||||
})
|
||||
```
|
|
@ -0,0 +1,44 @@
|
|||
package excel_import
|
||||
|
||||
import "github.com/xuri/excelize/v2"
|
||||
|
||||
type CreateExcel struct {
|
||||
rowIndex int
|
||||
file *excelize.File
|
||||
sw *excelize.StreamWriter
|
||||
Rows [][]interface{} `json:"rows"`
|
||||
Header []interface{} `json:"header"`
|
||||
Path string `json:"path"`
|
||||
FileName string `json:"file_name"`
|
||||
ErrFileUrls []string `json:"err_file_urls"`
|
||||
}
|
||||
|
||||
func (c *CreateExcel) Init() (err error) {
|
||||
c.file = excelize.NewFile()
|
||||
c.sw, err = c.file.NewStreamWriter("Sheet1")
|
||||
err = c.WriteHeader()
|
||||
return err
|
||||
}
|
||||
|
||||
func (c *CreateExcel) WriteHeader() error {
|
||||
return c.Write(c.Header)
|
||||
}
|
||||
|
||||
func (c *CreateExcel) Write(values []interface{}) error {
|
||||
|
||||
cell, err := excelize.CoordinatesToCellName(1, c.rowIndex+1)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = c.sw.SetRow(cell, values)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
c.rowIndex++
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *CreateExcel) Save() error {
|
||||
return c.file.SaveAs(c.Path + c.FileName)
|
||||
}
|
|
@ -0,0 +1,158 @@
|
|||
package excel_import
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/bytedance/sonic"
|
||||
"os"
|
||||
"regexp"
|
||||
"sort"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
func importLogPath(jobName string) (string, error) {
|
||||
path, err := os.Getwd()
|
||||
path = fmt.Sprintf("%s/%s/%s", path, "log/import", jobName)
|
||||
err = CheckDir(path)
|
||||
return path, err
|
||||
}
|
||||
|
||||
func ImportLogPathByOrder(jobName string) (string, error) {
|
||||
path, err := os.Getwd()
|
||||
path = fmt.Sprintf("%s/%s/%s/excel/", path, "log/import", jobName)
|
||||
err = CheckDir(path)
|
||||
return path, err
|
||||
}
|
||||
|
||||
func tempFile(jobName string) string {
|
||||
path, _ := os.Getwd()
|
||||
path = fmt.Sprintf("%s/%s/%s.xlsx", path, "docs/import_temp", jobName)
|
||||
|
||||
return path
|
||||
}
|
||||
|
||||
func CheckDir(path string) error {
|
||||
// 判断目录是否存在
|
||||
if _, err := os.Stat(path); os.IsNotExist(err) {
|
||||
// 如果目录不存在,则创建它
|
||||
err = os.MkdirAll(path, os.ModePerm)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
} else if err != nil {
|
||||
// 如果Stat返回了其他错误(比如权限问题)
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func SortFileWithStatus(dir string) []FileInfoStatus {
|
||||
|
||||
// 获取目录中的文件信息
|
||||
d, _ := os.Open(dir)
|
||||
defer d.Close()
|
||||
files, _ := d.ReadDir(0)
|
||||
|
||||
var fileInfoList []FileInfoStatus
|
||||
|
||||
// 填充切片
|
||||
for _, file := range files {
|
||||
fileName := file.Name()
|
||||
fileInfo, _ := file.Info()
|
||||
|
||||
bytes, _ := os.ReadFile(dir + "/" + fileName)
|
||||
var info Task
|
||||
_ = sonic.Unmarshal(bytes, &info)
|
||||
times, _ := time.Parse(time.DateTime, info.Ctime)
|
||||
fileInfoList = append(fileInfoList, FileInfoStatus{FileInfo: fileInfo, Status: info.Status, Time: times})
|
||||
}
|
||||
|
||||
// 根据修改时间对切片进行排序
|
||||
|
||||
sort.Slice(fileInfoList, func(i, j int) bool {
|
||||
return fileInfoList[i].Time.After(fileInfoList[j].Time)
|
||||
})
|
||||
return fileInfoList
|
||||
}
|
||||
|
||||
// SortHeader 根据SortSlice排序,
|
||||
// 这里这样设计主要是考虑在实际开发中可能会频繁的出现excel表里面插入或者删除字段,导致之后的导入数据的具体v[n]也需要跟着改的情况
|
||||
// HeaderMap决定了v[n]和excel表里面的字段名之间的映射关系
|
||||
// SortSlice决定了导入导出数据的顺序,里面的元素可以是HeaderMap里面的key,也可以是HeaderMap里面的value,这个取决于HeaderMap是否为nil
|
||||
// HeaderMap可以为nil,如果为nil,则SortSlice里面的元素就是excel表里面的字段名,如果HeaderMap不为nil,则SortSlice里面的元素则是经过HeaderMap映射之后的值
|
||||
// 严格来说这里的SortSlice应该是一个interface{}类型的切片,这里使用了string是为了方便
|
||||
//***最愚蠢的事情就是为了强迫症而强迫症
|
||||
//func SortHeader(header *Header) []string {
|
||||
// if header.HeaderMap == nil {
|
||||
// return header.SortSlice
|
||||
// }
|
||||
// var sortSlice []string
|
||||
// for _, v := range header.SortSlice {
|
||||
// if _, exist := header.HeaderMap[v]; exist {
|
||||
// sortSlice = append(sortSlice, header.HeaderMap[v])
|
||||
// }
|
||||
// }
|
||||
// return sortSlice
|
||||
//}
|
||||
|
||||
func ExchangeRows(oldRows [][]string, setHeader []string) (rows []map[string]string) {
|
||||
oldRowsHeader := oldRows[0]
|
||||
oldRowsMap := make(map[string]int, len(oldRowsHeader))
|
||||
for index, header := range oldRowsHeader {
|
||||
oldRowsMap[header] = index
|
||||
}
|
||||
for _, oldRow := range oldRows {
|
||||
newRow := make(map[string]string, len(setHeader))
|
||||
lenOldRow := len(oldRow)
|
||||
for _, header := range setHeader {
|
||||
point, exist := oldRowsMap[header]
|
||||
if !exist || point >= lenOldRow {
|
||||
newRow[header] = ""
|
||||
} else {
|
||||
newRow[header] = oldRow[point]
|
||||
}
|
||||
}
|
||||
rows = append(rows, newRow)
|
||||
}
|
||||
return rows
|
||||
}
|
||||
|
||||
func ExchangeRowWithMap(oldRow map[string]string, setHeader []string) (rows []string) {
|
||||
|
||||
for _, header := range setHeader {
|
||||
rows = append(rows, oldRow[header])
|
||||
}
|
||||
return rows
|
||||
}
|
||||
|
||||
func Ter[T any](cond bool, a, b T) T {
|
||||
if cond {
|
||||
return a
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
||||
func RegexMatch(str string, pattern string) bool {
|
||||
matched, err := regexp.MatchString(pattern, str)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
return matched
|
||||
}
|
||||
|
||||
// IsExcelFormat 检查是否为 Excel 相关格式
|
||||
func IsExcelFormat(fileObjectUrl string) bool {
|
||||
// 支持的扩展名
|
||||
allowedExtensions := []string{".xls", ".xlsx", ".csv"}
|
||||
|
||||
// 转为小写,避免大小写问题
|
||||
fileObjectUrl = strings.ToLower(fileObjectUrl)
|
||||
|
||||
// 遍历匹配扩展名
|
||||
for _, ext := range allowedExtensions {
|
||||
if strings.HasSuffix(fileObjectUrl, ext) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
|
@ -0,0 +1,36 @@
|
|||
module gitea.cdlsxd.cn/self-tools/l_excel_import
|
||||
|
||||
go 1.23.6
|
||||
|
||||
require (
|
||||
github.com/bytedance/sonic v1.12.9
|
||||
github.com/go-kratos/kratos/v2 v2.8.3
|
||||
github.com/xuri/excelize/v2 v2.9.0
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/bytedance/sonic/loader v0.2.2 // indirect
|
||||
github.com/cloudwego/base64x v0.1.5 // indirect
|
||||
github.com/go-kratos/aegis v0.2.0 // indirect
|
||||
github.com/go-playground/form/v4 v4.2.0 // indirect
|
||||
github.com/golang/protobuf v1.5.4 // indirect
|
||||
github.com/google/uuid v1.4.0 // indirect
|
||||
github.com/gorilla/mux v1.8.1 // indirect
|
||||
github.com/klauspost/cpuid/v2 v2.0.9 // indirect
|
||||
github.com/kr/text v0.2.0 // indirect
|
||||
github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 // indirect
|
||||
github.com/richardlehane/mscfb v1.0.4 // indirect
|
||||
github.com/richardlehane/msoleps v1.0.4 // indirect
|
||||
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
|
||||
github.com/xuri/efp v0.0.0-20240408161823-9ad904a10d6d // indirect
|
||||
github.com/xuri/nfp v0.0.0-20240318013403-ab9948c2c4a7 // indirect
|
||||
golang.org/x/arch v0.0.0-20210923205945-b76863e36670 // indirect
|
||||
golang.org/x/crypto v0.28.0 // indirect
|
||||
golang.org/x/net v0.30.0 // indirect
|
||||
golang.org/x/sys v0.26.0 // indirect
|
||||
golang.org/x/text v0.19.0 // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20240102182953-50ed04b92917 // indirect
|
||||
google.golang.org/grpc v1.61.1 // indirect
|
||||
google.golang.org/protobuf v1.33.0 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
)
|
|
@ -0,0 +1,658 @@
|
|||
package excel_import
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"github.com/bytedance/sonic"
|
||||
"github.com/go-kratos/kratos/v2/log"
|
||||
"github.com/go-kratos/kratos/v2/transport/http"
|
||||
"github.com/xuri/excelize/v2"
|
||||
"io"
|
||||
"math"
|
||||
urlnet "net/url"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
)
|
||||
|
||||
type ImportExcel struct {
|
||||
Ctx context.Context
|
||||
basePath string
|
||||
task *Task
|
||||
errRowsExporter *CreateExcel
|
||||
rowCount int
|
||||
importRowsCount int32
|
||||
RowPicCells []string //图片所在单元格
|
||||
PicSaveHandle func(pic *Pic) (url string, err error) //存储图片文件
|
||||
DeleteOssHandle func(ObjectName []string) (err error) //删除OSS图片
|
||||
GetFileObject func(fileObjectUrl string) (body io.ReadCloser, err error)
|
||||
Rows []map[string]string
|
||||
TrimFiled []string //需要进行去掉空格操作的字段
|
||||
RegexFiledMap map[string]*Regex
|
||||
JobName string //任务类型,区分不同业务
|
||||
TaskId string //自动生成
|
||||
SpeedMod bool //是否开启加速
|
||||
SliceLen uint //加速模式下,并行切片长度,默认为100
|
||||
Header []string //头部标题对标
|
||||
HandleFunc func(excel *ImportExcel, rows []map[string]string)
|
||||
FileObjectUrl string
|
||||
DownloadUrl string
|
||||
}
|
||||
|
||||
type checkOption struct {
|
||||
TrimCheck bool
|
||||
RegexCheck bool
|
||||
}
|
||||
|
||||
func NewImportExcel(jobName string, opts ...Option) *ImportExcel {
|
||||
Import := &ImportExcel{
|
||||
|
||||
JobName: jobName,
|
||||
errRowsExporter: &CreateExcel{}, //错误行导出
|
||||
task: &Task{}, //任务
|
||||
}
|
||||
for _, opt := range opts {
|
||||
opt(Import) // 应用选项
|
||||
}
|
||||
return Import
|
||||
}
|
||||
|
||||
func (i *ImportExcel) Init(ctx http.Context, handleFunc func(excel *ImportExcel, row []map[string]string)) (*ImportExcel, error) {
|
||||
i.Ctx = ctx
|
||||
basePath, err := importLogPath(i.JobName)
|
||||
i.basePath = basePath
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
err = CheckDir(i.taskFile())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
err = CheckDir(i.importLogFile())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
err = CheckDir(i.errExcelPath())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
i.HandleFunc = handleFunc
|
||||
i.TaskId = i.createTaskId()
|
||||
err = i.GetRows(ctx.Request())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
i.errRowsExporter.FileName = fmt.Sprintf("%s.xlsx", i.TaskId)
|
||||
i.errRowsExporter.Path = i.errExcelPath()
|
||||
|
||||
return i, nil
|
||||
}
|
||||
|
||||
func (i *ImportExcel) Run() (err error) {
|
||||
//创建任务
|
||||
i.task = &Task{
|
||||
TaskId: i.TaskId,
|
||||
Ctime: time.Now().Format(time.DateTime),
|
||||
Status: TaskStatusInit,
|
||||
}
|
||||
err = i.updateTask()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
i.rowCount = len(i.Rows) - 1
|
||||
err = i.filedCheck()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if i.SpeedMod {
|
||||
if i.SliceLen == 0 {
|
||||
i.SliceLen = 100
|
||||
}
|
||||
//从第二行开始读取
|
||||
for j := 1; j <= len(i.Rows[1:]); j += int(i.SliceLen) {
|
||||
if j+int(i.SliceLen) > len(i.Rows) {
|
||||
i.SliceLen = uint(len(i.Rows) - j)
|
||||
}
|
||||
//****HandleFunc需要创建子上下文来防止ctx被取消导致上下文丢失**** ChildCtx,cancel:=context.WithCancel(context.BackGround()) defer cancel()
|
||||
go i.HandleFunc(i, i.Rows[j:j+int(i.SliceLen)])
|
||||
}
|
||||
} else {
|
||||
i.HandleFunc(i, i.Rows[1:])
|
||||
}
|
||||
i.task.Status = TaskStatusFinish
|
||||
i.updateTask()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (i *ImportExcel) filedCheck() (err error) {
|
||||
var (
|
||||
checkOptions checkOption
|
||||
errMsg []string
|
||||
)
|
||||
if len(i.TrimFiled) >= 0 {
|
||||
checkOptions.TrimCheck = true
|
||||
}
|
||||
if len(i.RegexFiledMap) >= 0 {
|
||||
checkOptions.RegexCheck = true
|
||||
}
|
||||
|
||||
//去掉空格
|
||||
trimKeysMap := make(map[string]struct{})
|
||||
for _, key := range i.TrimFiled {
|
||||
trimKeysMap[key] = struct{}{}
|
||||
}
|
||||
trimValue := func(value string) string {
|
||||
return strings.TrimSpace(value)
|
||||
}
|
||||
|
||||
for line, dataMap := range i.Rows[1:] {
|
||||
for key, value := range dataMap {
|
||||
//去掉空格
|
||||
if checkOptions.TrimCheck {
|
||||
if _, exists := trimKeysMap[key]; exists {
|
||||
value = trimValue(value)
|
||||
}
|
||||
}
|
||||
//正则匹配
|
||||
if checkOptions.RegexCheck {
|
||||
if _, exists := i.RegexFiledMap[key]; exists {
|
||||
match := RegexMatch(i.RegexFiledMap[key].Rule, value)
|
||||
if match == i.RegexFiledMap[key].MatchBool {
|
||||
errMsg = append(errMsg, fmt.Sprintf("第%d行,字段:%s,值:%s,格式错误:%s", line+2, key, value, i.RegexFiledMap[key].Desc))
|
||||
}
|
||||
}
|
||||
}
|
||||
dataMap[key] = value
|
||||
}
|
||||
}
|
||||
if len(errMsg) > 0 {
|
||||
return fmt.Errorf(strings.Join(errMsg, "\n"))
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (i *ImportExcel) GetRows(request *http.Request) (err error) {
|
||||
err = i.getRowsFromHttp(request)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if i.Rows == nil || len(i.Rows) == 0 {
|
||||
return fmt.Errorf("未获取到导入数据或导入数据为空")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (i *ImportExcel) GetTaskInfo(taskId string) (*TaskResp, error) {
|
||||
var (
|
||||
info *Task
|
||||
)
|
||||
i.TaskId = taskId
|
||||
basePath, err := importLogPath(i.JobName)
|
||||
i.basePath = basePath
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
taskInfo, _ := os.ReadFile(i.taskFile())
|
||||
_ = sonic.Unmarshal(taskInfo, &info)
|
||||
if info == nil {
|
||||
// 兼容,提供默认值,类似:{"task_id":"1734576235708222000","process":100,"ctime":"2024-12-19 10:44:03","ftime":"2024-12-19 10:44:03","status":2}
|
||||
info = &Task{
|
||||
TaskId: taskId,
|
||||
Process: 0,
|
||||
Status: TaskStatusInit,
|
||||
Ctime: time.Now().Format(time.DateTime),
|
||||
Ftime: time.Now().Format(time.DateTime),
|
||||
}
|
||||
}
|
||||
if info.Process >= 95 {
|
||||
info.Process = 100
|
||||
}
|
||||
|
||||
return &TaskResp{
|
||||
Task: info,
|
||||
ImportErrLog: i.importLog(),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (i *ImportExcel) importLog() []*ImportLog {
|
||||
var logs = []*ImportLog{}
|
||||
_, err := os.Stat(i.importLogFile())
|
||||
if err != nil {
|
||||
return logs
|
||||
}
|
||||
taskLog, err := os.ReadFile(i.importLogFile())
|
||||
if err != nil {
|
||||
return logs
|
||||
}
|
||||
logList := strings.Split(string(taskLog), "\n")
|
||||
for _, v := range logList {
|
||||
if v == "" {
|
||||
continue
|
||||
}
|
||||
var log *ImportLog
|
||||
err = sonic.Unmarshal([]byte(v), &log)
|
||||
if err != nil {
|
||||
return logs
|
||||
}
|
||||
logs = append(logs, log)
|
||||
}
|
||||
return logs
|
||||
}
|
||||
|
||||
func (i *ImportExcel) GetExcel(taskId string) string {
|
||||
i.TaskId = taskId
|
||||
basePath, _ := importLogPath(i.JobName)
|
||||
i.basePath = basePath
|
||||
return i.errExcelFile()
|
||||
}
|
||||
|
||||
func (i *ImportExcel) DownloadExcel(ctx http.Context) (err error) {
|
||||
taskId := ctx.Query().Get("task_id")
|
||||
addr := i.GetExcel(taskId)
|
||||
_, exist := os.Stat(addr)
|
||||
if exist != nil {
|
||||
return fmt.Errorf("文件不存在")
|
||||
}
|
||||
|
||||
file, err := os.Open(addr)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer file.Close()
|
||||
payload, err := io.ReadAll(file)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// 设置HTTP响应头
|
||||
// 打开为预览
|
||||
//ctx.Response().Header().Set("Content-Type", "image/png")
|
||||
// 打开为下载
|
||||
ctx.Response().Header().Set("Content-Type", "application/octet-stream")
|
||||
ctx.Response().Header().Set("Content-Disposition", "attachment; filename="+taskId+".xlsx")
|
||||
// 将结果写入
|
||||
_, err = ctx.Response().Write(payload)
|
||||
return
|
||||
}
|
||||
|
||||
func (i *ImportExcel) DownloadExcelTemp(ctx http.Context) (err error) {
|
||||
|
||||
file, err := os.Open(i.DownloadUrl)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer file.Close()
|
||||
payload, err := io.ReadAll(file)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// 设置HTTP响应头
|
||||
// 打开为预览
|
||||
//ctx.Response().Header().Set("Content-Type", "image/png")
|
||||
// 打开为下载
|
||||
ctx.Response().Header().Set("Content-Type", "application/octet-stream")
|
||||
ctx.Response().Header().Set("Content-Disposition", "attachment; filename="+i.JobName+".xlsx")
|
||||
// 将结果写入
|
||||
_, err = ctx.Response().Write(payload)
|
||||
return
|
||||
}
|
||||
|
||||
func (i *ImportExcel) TaskInfo(ctx http.Context) (err error) {
|
||||
taskId := ctx.Query().Get("task_id")
|
||||
importExcelInfo, err := i.GetTaskInfo(taskId)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
response := make(map[string]interface{}, 1)
|
||||
|
||||
response["data"] = importExcelInfo
|
||||
return ctx.Result(200, response)
|
||||
}
|
||||
|
||||
func (i *ImportExcel) TaskHis(ctx http.Context) (err error) {
|
||||
var (
|
||||
data []map[string]interface{}
|
||||
res ResPage
|
||||
)
|
||||
|
||||
page := ctx.Query().Get("page")
|
||||
num := ctx.Query().Get("limit")
|
||||
|
||||
path := i.taskPath()
|
||||
|
||||
entries := SortFileWithStatus(path)
|
||||
count := len(entries)
|
||||
pageInt, _ := strconv.ParseInt(page, 10, 64)
|
||||
numInt, _ := strconv.ParseInt(num, 10, 64)
|
||||
begin := (pageInt - 1) * numInt
|
||||
entEnd := begin + numInt
|
||||
if count < int(entEnd) {
|
||||
entEnd = int64(count)
|
||||
}
|
||||
entries = entries[begin:entEnd]
|
||||
for _, entry := range entries {
|
||||
if entry.FileInfo == nil {
|
||||
break
|
||||
}
|
||||
info := make(map[string]interface{})
|
||||
if entry.IsDir() {
|
||||
continue
|
||||
}
|
||||
info["task_id"] = entry.Name()
|
||||
file := fmt.Sprintf("%s/%s", path, entry.Name())
|
||||
bytes, _ := os.ReadFile(file)
|
||||
_ = sonic.Unmarshal(bytes, &info)
|
||||
fileOs, _ := os.Stat(file)
|
||||
info["update_time"] = fileOs.ModTime().Format(time.DateTime)
|
||||
data = append(data, info)
|
||||
}
|
||||
res = ResPage{
|
||||
Page: int(pageInt),
|
||||
Limit: int(numInt),
|
||||
Total: count,
|
||||
Data: data,
|
||||
LastPage: int(math.Ceil(float64(count) / float64(numInt))),
|
||||
}
|
||||
response := make(map[string]interface{}, 1)
|
||||
|
||||
response["data"] = res
|
||||
return ctx.Result(200, response)
|
||||
}
|
||||
|
||||
func (i *ImportExcel) getRowsFromHttp(r *http.Request) (err error) {
|
||||
// 定义一个file文件
|
||||
var file io.ReadCloser
|
||||
// 获取上传的文件
|
||||
// 获取post请求的参数
|
||||
fileObjectUrl := r.FormValue("fileObjectUrl")
|
||||
if i.GetFileObject != nil && fileObjectUrl != "" {
|
||||
// 判断fileObjectUrl 是否为excel相关得格式
|
||||
if !IsExcelFormat(fileObjectUrl) {
|
||||
return fmt.Errorf("文件格式错误, 不是excel")
|
||||
}
|
||||
file, err = i.GetFileObject(fileObjectUrl)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// 记录excel地址,用于删除文件
|
||||
i.FileObjectUrl = fileObjectUrl
|
||||
} else {
|
||||
file, _, err = r.FormFile("file")
|
||||
speed_mode := r.PostForm.Get("speed_mode")
|
||||
if strings.EqualFold(speed_mode, "true") {
|
||||
i.SpeedMod = true
|
||||
}
|
||||
if err != nil {
|
||||
return fmt.Errorf("未找到导入文件: %w", err)
|
||||
}
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
// 解析Excel文件
|
||||
f, err := excelize.OpenReader(file)
|
||||
if err != nil {
|
||||
return fmt.Errorf("解析excel文件失败: %w", err)
|
||||
}
|
||||
defer f.Close()
|
||||
// 获取第一个工作表的名称
|
||||
sheetNames := f.GetSheetList()
|
||||
if len(sheetNames) == 0 {
|
||||
return fmt.Errorf("无效的excel,未获取到对应的sheet")
|
||||
}
|
||||
rows, err := f.GetRows(sheetNames[0])
|
||||
if err != nil {
|
||||
return fmt.Errorf("excel内未找到数据: %w", err)
|
||||
}
|
||||
|
||||
if i.PicSaveHandle != nil {
|
||||
i.RowPicCells, err = f.GetPictureCells(sheetNames[0])
|
||||
if err != nil {
|
||||
return fmt.Errorf("excel内图片获取失败: %w", err)
|
||||
}
|
||||
// 遍历所有的图片
|
||||
var picList []*Pic
|
||||
for _, cell := range i.RowPicCells {
|
||||
if cell == "N1" {
|
||||
continue
|
||||
}
|
||||
pics, _err := f.GetPictures(sheetNames[0], cell)
|
||||
if _err != nil {
|
||||
return fmt.Errorf("excel内未找到图片: %w", _err)
|
||||
}
|
||||
if len(pics) > 0 {
|
||||
for _, v := range pics {
|
||||
if v.File != nil {
|
||||
picList = append(picList, &Pic{
|
||||
PicInfos: v,
|
||||
Cell: cell,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
fmt.Printf("开始上传OSS,时间:%s \n", time.Now().Format(time.DateTime))
|
||||
if len(picList) > 0 {
|
||||
var (
|
||||
cellUrlMap = make(map[string][]string)
|
||||
wg = sync.WaitGroup{}
|
||||
mu sync.Mutex
|
||||
)
|
||||
wg.Add(len(picList))
|
||||
for _, pic := range picList {
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
fmt.Printf("图片上传失败,cell:%s,url:%s,err:%s", pic.Cell, pic.Url, r)
|
||||
}
|
||||
}()
|
||||
url, _err := i.PicSaveHandle(pic)
|
||||
mu.Lock()
|
||||
if _err != nil {
|
||||
log.Error(fmt.Sprintf("图片上传失败,cell:%s,url:%s,err:%s", pic.Cell, pic.Url, _err.Error()))
|
||||
}
|
||||
cellUrlMap[pic.Cell] = append(cellUrlMap[pic.Cell], url)
|
||||
mu.Unlock()
|
||||
}()
|
||||
}
|
||||
wg.Wait()
|
||||
fmt.Printf("结束上传OSS,时间:%s \n", time.Now().Format(time.DateTime))
|
||||
if len(cellUrlMap) > 0 {
|
||||
for cell, cellPic := range cellUrlMap {
|
||||
err = f.SetCellValue(sheetNames[0], cell, strings.Join(cellPic, ","))
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
fmt.Printf("os--暂无图片,时间:%s \n", time.Now().Format(time.DateTime))
|
||||
//从新获取一次
|
||||
rows, err = f.GetRows(sheetNames[0])
|
||||
if err != nil {
|
||||
return fmt.Errorf("excel内未找到数据: %w", err)
|
||||
}
|
||||
}
|
||||
// 获取所有行
|
||||
|
||||
if len(rows) == 0 {
|
||||
return fmt.Errorf("无效的excel,未获取到对应的记录数据")
|
||||
}
|
||||
|
||||
if len(i.Header) > 0 || i.Header != nil {
|
||||
|
||||
i.Rows = ExchangeRows(rows, i.Header)
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func (i *ImportExcel) getRowsFromFile(filePath string) (err error) {
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func (i *ImportExcel) updateTask() error {
|
||||
|
||||
file, err := os.OpenFile(i.taskFile(), os.O_RDWR|os.O_TRUNC|os.O_CREATE, 0766)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
taskInfo, err := sonic.Marshal(i.task)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
jsonInfo := string(taskInfo)
|
||||
_, err = file.WriteString(jsonInfo)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
func (i *ImportExcel) AddErr(failReason string, key int, row map[string]string) {
|
||||
|
||||
_ = i.updateImportLog(&ImportLog{
|
||||
FailReason: failReason,
|
||||
Line: key,
|
||||
})
|
||||
|
||||
var interfaces []interface{}
|
||||
rowSort := ExchangeRowWithMap(row, i.Header)
|
||||
for _, v := range rowSort {
|
||||
interfaces = append(interfaces, v)
|
||||
}
|
||||
interfaces = append(interfaces, failReason)
|
||||
i.errRowsExporter.Rows = append(i.errRowsExporter.Rows, interfaces)
|
||||
i.errRowsExporter.ErrFileUrls = append(i.errRowsExporter.ErrFileUrls, row["货品图片"])
|
||||
i.Next()
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (i *ImportExcel) Next() (err error) {
|
||||
atomic.AddInt32(&i.importRowsCount, 1)
|
||||
i.task.Process = int(i.importRowsCount * 100 / int32(i.rowCount))
|
||||
i.updateTask()
|
||||
if int(i.importRowsCount) == i.rowCount {
|
||||
i.task.Process = 100
|
||||
i.task.Status = TaskStatusCreateFailExcel
|
||||
err = i.updateTask()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if len(i.errRowsExporter.Rows) > 0 {
|
||||
headerInterfaces := make([]interface{}, len(i.Header)+1)
|
||||
for index, header := range i.Header {
|
||||
headerInterfaces[index] = header
|
||||
}
|
||||
headerInterfaces[len(headerInterfaces)-1] = "失败原因"
|
||||
i.errRowsExporter.Header = headerInterfaces
|
||||
err = i.errRowsExporter.Init()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
for _, v := range i.errRowsExporter.Rows {
|
||||
_ = i.errRowsExporter.Write(v)
|
||||
}
|
||||
i.errRowsExporter.Save()
|
||||
}
|
||||
// 删除已经上传到OSS的图片
|
||||
if len(i.errRowsExporter.ErrFileUrls) > 0 {
|
||||
var errFileObjectName []string
|
||||
for _, url := range i.errRowsExporter.ErrFileUrls {
|
||||
if url == "" {
|
||||
continue
|
||||
}
|
||||
ossUrl := strings.Split(url, ",")
|
||||
for _, v := range ossUrl {
|
||||
parsedURL, err := urlnet.Parse(v)
|
||||
if err != nil {
|
||||
fmt.Printf("解析URL失败: %v\n", err)
|
||||
continue
|
||||
}
|
||||
// 去掉path前面得/
|
||||
parsedURL.Path = strings.TrimPrefix(parsedURL.Path, "/")
|
||||
errFileObjectName = append(errFileObjectName, parsedURL.Path)
|
||||
}
|
||||
}
|
||||
// 批量删除OSS文件
|
||||
if len(errFileObjectName) > 0 {
|
||||
if i.DeleteOssHandle != nil {
|
||||
err = i.DeleteOssHandle(errFileObjectName)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if i.FileObjectUrl != "" {
|
||||
// 批量删除OSS文件
|
||||
if i.DeleteOssHandle != nil {
|
||||
err = i.DeleteOssHandle([]string{i.FileObjectUrl})
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
i.task.Status = TaskStatusRunning
|
||||
i.task.Ftime = time.Now().Format(time.DateTime)
|
||||
i.updateTask()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (i *ImportExcel) CreateFailExcel() {
|
||||
i.importRowsCount++
|
||||
}
|
||||
|
||||
func (i *ImportExcel) updateImportLog(importLog *ImportLog) error {
|
||||
file, err := os.OpenFile(i.importLogFile(), os.O_APPEND|os.O_WRONLY|os.O_CREATE, 0766)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer file.Close()
|
||||
logInfo, err := sonic.Marshal(importLog)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
jsonInfo := string(logInfo)
|
||||
_, err = file.WriteString(jsonInfo + "\n")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (i *ImportExcel) taskFile() string {
|
||||
return i.basePath + "/task/" + i.TaskId
|
||||
}
|
||||
|
||||
func (i *ImportExcel) taskPath() string {
|
||||
if i.basePath == "" {
|
||||
basePath, _ := importLogPath(i.JobName)
|
||||
i.basePath = basePath
|
||||
}
|
||||
return i.basePath + "/task/"
|
||||
}
|
||||
|
||||
func (i *ImportExcel) importLogFile() string {
|
||||
return i.basePath + "/import/" + i.TaskId
|
||||
}
|
||||
|
||||
func (i *ImportExcel) errExcelPath() string {
|
||||
return i.basePath + "/excel/"
|
||||
}
|
||||
|
||||
func (i *ImportExcel) errExcelFile() string {
|
||||
return i.errExcelPath() + i.TaskId + ".xlsx"
|
||||
}
|
||||
|
||||
func (i *ImportExcel) createTaskId() string {
|
||||
return fmt.Sprintf("%d", time.Now().UnixNano())
|
||||
}
|
|
@ -0,0 +1,17 @@
|
|||
package excel_import
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestCut(t *testing.T) {
|
||||
data := [][]string{{"a", "b", "c"}, {"d", "e", "f"}, {"d", "e", "f"}, {"d", "e", "f"}, {"d", "e", "f"}}
|
||||
|
||||
fmt.Println(data[1:])
|
||||
}
|
||||
|
||||
func TestTaskInfo(t *testing.T) {
|
||||
NewImportExcel("goods_import").GetTaskInfo("1727160398921095702")
|
||||
|
||||
}
|
|
@ -0,0 +1,72 @@
|
|||
package excel_import
|
||||
|
||||
import "io"
|
||||
|
||||
type (
|
||||
Option func(*ImportExcel)
|
||||
Regex struct {
|
||||
Rule string //匹配正则
|
||||
MatchBool bool //匹配结果为true抛错还是匹配为false抛错
|
||||
Desc string //抛错内容
|
||||
}
|
||||
)
|
||||
|
||||
func WithSpeedMod(speedMod bool) Option {
|
||||
return func(s *ImportExcel) {
|
||||
s.SpeedMod = speedMod
|
||||
}
|
||||
}
|
||||
|
||||
func WithSliceLen(sliceLen uint) Option {
|
||||
return func(s *ImportExcel) {
|
||||
s.SliceLen = sliceLen
|
||||
}
|
||||
}
|
||||
|
||||
func WithHeader(header []string) Option {
|
||||
return func(s *ImportExcel) {
|
||||
s.Header = header
|
||||
}
|
||||
}
|
||||
|
||||
func WithTaskId(taskId string) Option {
|
||||
return func(s *ImportExcel) {
|
||||
s.TaskId = taskId
|
||||
}
|
||||
}
|
||||
|
||||
func WithRowPicHandle(picSaveHandle func(pic *Pic) (url string, err error)) Option {
|
||||
return func(s *ImportExcel) {
|
||||
s.PicSaveHandle = picSaveHandle
|
||||
}
|
||||
}
|
||||
|
||||
func WithDeleteOssHandle(deleteOssHandle func(ObjectName []string) (err error)) Option {
|
||||
return func(s *ImportExcel) {
|
||||
s.DeleteOssHandle = deleteOssHandle
|
||||
}
|
||||
}
|
||||
|
||||
func WithGetFileObject(getFileObject func(fileObjectUrl string) (body io.ReadCloser, err error)) Option {
|
||||
return func(s *ImportExcel) {
|
||||
s.GetFileObject = getFileObject
|
||||
}
|
||||
}
|
||||
|
||||
func WithTrimFiled(trimFiledMap []string) Option {
|
||||
return func(s *ImportExcel) {
|
||||
s.TrimFiled = trimFiledMap
|
||||
}
|
||||
}
|
||||
|
||||
func WithFiledRegex(regexFiledMap map[string]*Regex) Option {
|
||||
return func(s *ImportExcel) {
|
||||
s.RegexFiledMap = regexFiledMap
|
||||
}
|
||||
}
|
||||
|
||||
func WithExcelDownLoadUrl(url string) Option {
|
||||
return func(s *ImportExcel) {
|
||||
s.DownloadUrl = url
|
||||
}
|
||||
}
|
|
@ -0,0 +1,80 @@
|
|||
package excel_import
|
||||
|
||||
import (
|
||||
"github.com/xuri/excelize/v2"
|
||||
"io/fs"
|
||||
"time"
|
||||
)
|
||||
|
||||
type (
|
||||
Task struct {
|
||||
TaskId string `json:"task_id"`
|
||||
Process int `json:"process"`
|
||||
Ctime string `json:"ctime"`
|
||||
Ftime string `json:"ftime"`
|
||||
Status int `json:"status"`
|
||||
}
|
||||
|
||||
ImportLog struct {
|
||||
FailReason string `json:"fail_reason"`
|
||||
Line int `json:"line"`
|
||||
}
|
||||
|
||||
RowInfo struct {
|
||||
Row []string
|
||||
}
|
||||
|
||||
TaskResp struct {
|
||||
Task *Task `json:"task"`
|
||||
ImportErrLog []*ImportLog `json:"log"`
|
||||
}
|
||||
|
||||
FileInfoStatus struct {
|
||||
fs.FileInfo
|
||||
Status int
|
||||
Time time.Time
|
||||
}
|
||||
|
||||
ResPage struct {
|
||||
Page int `json:"current_page"`
|
||||
Limit int `json:"per_page"`
|
||||
Total int `json:"total"`
|
||||
LastPage int `json:"last_page"`
|
||||
Data []map[string]interface{} `json:"data"`
|
||||
}
|
||||
|
||||
PicList struct {
|
||||
PicInfos []excelize.Picture
|
||||
Url []string
|
||||
}
|
||||
|
||||
Pic struct {
|
||||
PicInfos excelize.Picture
|
||||
Url string
|
||||
Cell string
|
||||
}
|
||||
|
||||
//Header struct {
|
||||
// HeaderMap map[string]string
|
||||
// SortSlice []string
|
||||
//}
|
||||
)
|
||||
|
||||
const (
|
||||
TaskStatusInit = iota + 1 // 初始化
|
||||
TaskStatusRunning
|
||||
TaskStatusCreateFailExcel
|
||||
TaskStatusFinish
|
||||
)
|
||||
|
||||
const (
|
||||
Rows_From_Request = iota + 1
|
||||
Rows_From_File
|
||||
Rows_From_Rows
|
||||
)
|
||||
|
||||
var fromWays = map[int]string{
|
||||
Rows_From_Request: "request",
|
||||
Rows_From_File: "file",
|
||||
Rows_From_Rows: "rows",
|
||||
}
|
Loading…
Reference in New Issue