first push

This commit is contained in:
renzhiyuan 2025-02-26 20:51:18 +08:00
commit 13d533ece6
9 changed files with 510 additions and 0 deletions

9
config.go Normal file
View File

@ -0,0 +1,9 @@
package excel_export
type Config struct {
Data interface{}
FileName string
Ext Ext
SavePath string
Head []*FiledMapping
}

17
constant.go Normal file
View File

@ -0,0 +1,17 @@
package excel_export
type Ext string
const (
Xlam Ext = ".xlam"
Xlsm Ext = ".xlsm"
Xlsx Ext = ".xlsx"
Xltm Ext = ".xltm"
)
const (
Err int8 = 0
Init int8 = 1
Running int8 = 2
Finish int8 = 3
)

217
export.go Normal file
View File

@ -0,0 +1,217 @@
package excel_export
import (
"encoding/json"
"fmt"
"gitea.cdlsxd.cn/self-tools/l_excel_export/pkg"
"gitea.cdlsxd.cn/self-tools/l_excel_export/types"
"time"
"gitea.cdlsxd.cn/self-tools/l_excel_export/export_err"
"github.com/xuri/excelize/v2"
"os"
)
type ExportExcel struct {
config *Config
password string
saveFunc func(file *excelize.File) (url string, err error)
sheetName string
logPath string
jobName string
task *types.Task
exportData [][]interface{}
header []interface{}
}
func NewExport(config *Config, opts ...Option) (*ExportExcel, error) {
export := &ExportExcel{
config: config,
}
for _, opt := range opts {
opt(export) // 应用选项
}
err := export.check()
return export, err
}
func (e *ExportExcel) Run() (taskId string, err error) {
err = e.init()
if err != nil {
return
}
f, err := e.getFile()
if err != nil {
return
}
go func(f *excelize.File) {
defer func() {
if r := recover(); r != nil {
f.Close()
fmt.Println(r)
}
}()
e.task.Status = Running
e.updateTask()
err = e.run(f)
if err != nil {
e.task.Status = Err
e.updateTask()
return
}
e.task.Ftime = time.Now().Format(time.DateTime)
e.finish(f)
}(f)
return e.task.TaskId, nil
}
func (e *ExportExcel) run(f *excelize.File) (err error) {
index, err := f.NewStreamWriter(e.getSheetName())
if err != nil {
return err
}
index.SetRow("A1", e.header)
if err != nil {
return err
}
count := len(e.exportData)
for i, v := range e.exportData {
cell, _ := excelize.CoordinatesToCellName(1, i+2)
index.SetRow(cell, v)
e.task.Process = i * 100 / count
e.updateTask()
}
if err = e.save(f); err != nil {
return err
}
return err
}
func (e *ExportExcel) finish(f *excelize.File) {
f.Close()
e.task.Status = Finish
e.task.Process = 100
e.updateTask()
}
func (e *ExportExcel) save(f *excelize.File) error {
e.task.Url = e.getUrl()
return f.SaveAs(e.task.Url)
}
func (e *ExportExcel) getUrl() string {
return fmt.Sprintf("%s/%s_%s%s", e.config.SavePath, e.config.FileName, e.task.TaskId, e.config.Ext)
}
func (e *ExportExcel) init() error {
e.task = &types.Task{
TaskId: pkg.CreateTaskId(),
Process: 0,
Url: "",
Ctime: time.Now().Format(time.DateTime),
Ftime: "",
Status: Init,
FileAddr: "",
}
err := e.updateTask()
if err != nil {
return err
}
dataMap := pkg.GetData(e.config.Data)
if len(dataMap) == 0 {
return export_err.ErrDataError
}
if len(e.config.Head) == 0 {
return export_err.ErrHeadNotSet
}
for _, v := range e.config.Head {
e.header = append(e.header, v.ColName)
}
//todo:这一步目的是为了保证数据与header未知一致但是在大数据量的时候存在性能问题
for _, v := range dataMap {
var (
slice []interface{}
)
for _, vv := range e.config.Head {
slice = append(slice, v[vv.FieldName])
}
e.exportData = append(e.exportData, slice)
}
//检测文件是否存在
if _, err = os.Stat(e.config.SavePath); os.IsNotExist(err) {
// 文件夹不存在,尝试创建
err = os.MkdirAll(e.config.SavePath, os.ModePerm)
if err != nil {
return export_err.ErrCreateExcelPathFail
}
} else if err != nil {
return export_err.ErrCheckExcelPathFail
}
return nil
}
func (e *ExportExcel) updateTask() error {
file, err := os.OpenFile(e.logFile(), os.O_RDWR|os.O_TRUNC|os.O_CREATE, 0766)
if err != nil {
return err
}
defer file.Close()
taskInfo, err := json.Marshal(e.task)
if err != nil {
return err
}
jsonInfo := string(taskInfo)
_, err = file.WriteString(jsonInfo)
if err != nil {
return err
}
return nil
}
func (e *ExportExcel) logFile() string {
return fmt.Sprintf("%s/%s", e.logPath, e.task.TaskId)
}
func (e *ExportExcel) getFile() (*excelize.File, error) {
f, err := excelize.OpenFile(e.path())
if err != nil {
f = excelize.NewFile()
}
return f, nil
}
func (e *ExportExcel) path() string {
return fmt.Sprintf("%s/%s.%s", e.config.SavePath, e.config.FileName, e.config.Ext)
}
func (e *ExportExcel) check() (err error) {
if len(e.jobName) == 0 {
e.jobName = time.Now().Format("default")
}
if e.config.SavePath == "" && e.saveFunc == nil {
return export_err.ErrNotSetSaveWay
}
if len(e.logPath) == 0 {
e.logPath, err = pkg.DefaultLogPath(e.jobName)
return
}
return
}
func (e *ExportExcel) getSheetName() string {
if len(e.sheetName) == 0 {
e.sheetName = "Sheet1"
}
return e.sheetName
}

12
export_err/error.go Normal file
View File

@ -0,0 +1,12 @@
package export_err
import "errors"
var (
ErrNotSetSaveWay = errors.New("请设置文件保存路径SavePath或保存方式WithSaveFunc")
ErrFileNotExist = errors.New("文件不存在")
ErrDataError = errors.New("导出数据不存在或格式错误")
ErrHeadNotSet = errors.New("数据头Header为空")
ErrCreateExcelPathFail = errors.New("创建excel存放文件夹失败")
ErrCheckExcelPathFail = errors.New("检测excel存放文件夹失败")
)

63
export_test.go Normal file
View File

@ -0,0 +1,63 @@
package excel_export
import (
"fmt"
"github.com/xuri/excelize/v2"
"os"
"testing"
)
func TestExport(t *testing.T) {
f, err := excelize.OpenFile("./a.xlsx")
if err == os.ErrNotExist {
fmt.Println(111)
}
_ = fmt.Sprint(f, err)
}
type Order struct {
OrderNum string `json:"order_num"`
OrderID int `json:"order_id"`
CusNum string `json:"cus_num"`
}
func TestData(t *testing.T) {
var c = []interface{}{
Order{OrderNum: "aasdsad", OrderID: 1, CusNum: "hghghfg"},
Order{OrderNum: "vqewqewq", OrderID: 2, CusNum: "iuyiuyiyu"},
Order{OrderNum: "adfgf", OrderID: 3, CusNum: "ewewew"},
Order{OrderNum: "abbbb", OrderID: 4, CusNum: "xcxcxc"},
Order{OrderNum: "aqwewqeqw", OrderID: 5, CusNum: "fdfdfd"},
Order{OrderNum: "ahhhhhh", OrderID: 6, CusNum: "asdadsa"},
Order{OrderNum: "vvvvv", OrderID: 7, CusNum: "vcvcv"},
Order{OrderNum: "aaaa", OrderID: 8, CusNum: "asdasdwqewqe"},
Order{OrderNum: "wwww", OrderID: 9, CusNum: "bvbvbvbvbv"},
Order{OrderNum: "ffff", OrderID: 10, CusNum: "gfgfgf"},
Order{OrderNum: "tttt", OrderID: 11, CusNum: "zxczczxczxczx"},
Order{OrderNum: "hhhh", OrderID: 12, CusNum: "gjhfgjghjgh"},
Order{OrderNum: "bbvbv", OrderID: 13, CusNum: "ytytytuty"},
Order{OrderNum: "zcxzczx", OrderID: 14, CusNum: "rqwrqrqrqrqr"},
Order{OrderNum: "asdasd", OrderID: 15, CusNum: "asdzczxfaxc"},
}
out, err := NewExport(&Config{
FileName: "a",
Data: c,
Ext: Xlsx,
Head: []*FiledMapping{
{FieldName: "order_num", ColName: "订单编号"},
{FieldName: "order_id", ColName: "订单id"},
{FieldName: "cus_num", ColName: "顾客编号 "},
},
SavePath: "./path",
})
if err != nil {
panic(err)
}
task_id, err := out.Run()
if err != nil {
panic(err)
}
fmt.Println(task_id)
select {}
}

16
go.mod Normal file
View File

@ -0,0 +1,16 @@
module gitea.cdlsxd.cn/self-tools/l_excel_export
go 1.22.2
require github.com/xuri/excelize/v2 v2.9.0
require (
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/xuri/efp v0.0.0-20240408161823-9ad904a10d6d // indirect
github.com/xuri/nfp v0.0.0-20240318013403-ab9948c2c4a7 // indirect
golang.org/x/crypto v0.28.0 // indirect
golang.org/x/net v0.30.0 // indirect
golang.org/x/text v0.19.0 // indirect
)

47
option.go Normal file
View File

@ -0,0 +1,47 @@
package excel_export
import "github.com/xuri/excelize/v2"
type (
Option func(export *ExportExcel)
FiledMapping struct {
FieldName string `json:"filed_name"` //原字段
ColName string `json:"col_name"` //excel列名
}
)
// WithPassword excel打开密码
func WithPassword(password string) Option {
return func(s *ExportExcel) {
s.password = password
}
}
// WithSaveFunc 抛出file文件交由外部处理
func WithSaveFunc(saveFunc func(file *excelize.File) (url string, err error)) Option {
return func(s *ExportExcel) {
s.saveFunc = saveFunc
}
}
// WithSheet excel的sheet名称
func WithSheet(sheetName string) Option {
return func(s *ExportExcel) {
s.sheetName = sheetName
}
}
// WithLogPath 日志保存文件
func WithLogPath(logPath string) Option {
return func(s *ExportExcel) {
s.logPath = logPath
}
}
// WithJobName 任务名称
func WithJobName(name string) Option {
return func(s *ExportExcel) {
s.jobName = name
}
}

116
pkg/pkg.go Normal file
View File

@ -0,0 +1,116 @@
package pkg
import (
"encoding/json"
"fmt"
"os"
"reflect"
"strings"
"time"
)
func DefaultLogPath(jobName string) (string, error) {
path, err := os.Getwd()
path = fmt.Sprintf("%s/%s/%s", path, "log/export", jobName)
err = CheckDir(path)
return path, err
}
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 CreateTaskId() string {
return fmt.Sprintf("%d", time.Now().UnixNano())
}
// toMap 将结构体转换为map[string]interface{}
// StructToMap 将一个struct转换为map[string]interface{}
func StructToMap(obj interface{}) map[string]interface{} {
// 获取obj的类型
val := reflect.ValueOf(obj)
if val.Kind() == reflect.Ptr {
val = val.Elem()
}
// 确保obj是一个struct
if val.Kind() != reflect.Struct {
return nil
}
// 创建一个map来保存结果
data := make(map[string]interface{})
// 遍历struct的字段
for i := 0; i < val.NumField(); i++ {
// 获取字段的类型和值
valueField := val.Field(i)
typeField := val.Type().Field(i)
jsonTag := typeField.Tag.Get("json")
if idx := strings.Index(jsonTag, ","); idx != -1 {
// 如果有逗号,则取逗号之前的部分
jsonTag = jsonTag[:idx]
}
// 忽略未导出的字段(字段名首字母小写)
if !typeField.IsExported() {
continue
}
// 将字段名和值添加到map中
data[jsonTag] = valueField.Interface()
}
return data
}
func GetData(data interface{}) (out []map[string]interface{}) {
switch data.(type) {
case []map[string]interface{}:
for _, item := range data.([]map[string]interface{}) {
out = append(out, item)
}
case []map[string]string:
for _, maps := range data.([]map[string]string) {
newMap := make(map[string]interface{})
for key, item := range maps {
newMap[key] = item
}
out = append(out, newMap)
}
case []interface{}:
for _, item := range data.([]interface{}) {
dataMap := StructToMap(item)
if dataMap == nil {
return nil
}
out = append(out, dataMap)
}
case string:
err := json.Unmarshal([]byte(data.(string)), &out)
if err != nil {
return nil
}
case []byte:
err := json.Unmarshal(data.([]byte), &out)
if err != nil {
return nil
}
default:
return nil
}
return
}

13
types/types.go Normal file
View File

@ -0,0 +1,13 @@
package types
type (
Task struct {
TaskId string `json:"task_id"`
Url string `json:"url"`
Process int `json:"process"`
Ctime string `json:"ctime"`
Ftime string `json:"ftime"`
Status int8 `json:"status"`
FileAddr string `json:"file_addr"`
}
)