first push
This commit is contained in:
commit
13d533ece6
|
@ -0,0 +1,9 @@
|
||||||
|
package excel_export
|
||||||
|
|
||||||
|
type Config struct {
|
||||||
|
Data interface{}
|
||||||
|
FileName string
|
||||||
|
Ext Ext
|
||||||
|
SavePath string
|
||||||
|
Head []*FiledMapping
|
||||||
|
}
|
|
@ -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
|
||||||
|
)
|
|
@ -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
|
||||||
|
}
|
|
@ -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存放文件夹失败")
|
||||||
|
)
|
|
@ -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 {}
|
||||||
|
}
|
|
@ -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
|
||||||
|
)
|
|
@ -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
|
||||||
|
}
|
||||||
|
}
|
|
@ -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
|
||||||
|
}
|
|
@ -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"`
|
||||||
|
}
|
||||||
|
)
|
Loading…
Reference in New Issue