l_excel_export/export.go

329 lines
6.6 KiB
Go
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package excel_export
import (
"encoding/json"
"fmt"
"gitea.cdlsxd.cn/self-tools/l_excel_export/export_err"
"gitea.cdlsxd.cn/self-tools/l_excel_export/pkg"
"gitea.cdlsxd.cn/self-tools/l_excel_export/types"
"github.com/xuri/excelize/v2"
"io"
"math"
"net/http"
"os"
"sort"
"time"
)
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(opts ...Option) *ExportExcel {
e := &ExportExcel{}
for _, opt := range opts {
opt(e) // 应用选项
}
e.initJobName()
return e
}
func (e *ExportExcel) Run(config *Config) (taskId string, err error) {
err = e.init(config)
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) TaskInfo(task_id string) (task *types.Task, err error) {
e.logPath, err = pkg.DefaultLogPath(e.jobName)
if err != nil {
return nil, err
}
e.task = &types.Task{
TaskId: task_id,
}
taskInfo, err := os.ReadFile(e.logFile())
if err != nil {
return nil, err
}
_ = json.Unmarshal(taskInfo, e.task)
return e.task, nil
}
func (e *ExportExcel) DownloadExcel(w http.ResponseWriter, task_id string) (err error) {
task, err := e.TaskInfo(task_id)
if err != nil {
return err
}
_, exist := os.Stat(task.Url)
if exist != nil {
return fmt.Errorf("文件不存在")
}
file, err := os.Open(task.Url)
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")
// 打开为下载
w.Header().Set("Content-Type", "application/octet-stream")
w.Header().Set("Content-Disposition", "attachment; filename="+task.Url)
// 将结果写入
_, err = w.Write(payload)
return
}
func (e *ExportExcel) TaskHis(page int, num int) (res *types.ResPage, err error) {
var data []*types.Task
e.logPath, err = pkg.DefaultLogPath(e.jobName)
if err != nil {
return res, err
}
entries := e.SortLogFileWithStatus()
count := len(entries)
begin := (page - 1) * num
entEnd := begin + num
if count < entEnd {
entEnd = count
}
data = entries[begin:entEnd]
return &types.ResPage{
Page: page,
Limit: num,
Total: count,
Data: data,
LastPage: int(math.Ceil(float64(count) / float64(num))),
}, 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(config *Config) error {
e.config = config
err := e.check()
if err != nil {
return err
}
e.task = &types.Task{
TaskId: pkg.CreateTaskId(),
Process: 0,
Url: "",
Ctime: time.Now().Format(time.DateTime),
Ftime: "",
Status: Init,
}
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 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) initJobName() {
if len(e.jobName) == 0 {
e.jobName = "default"
}
}
func (e *ExportExcel) getSheetName() string {
if len(e.sheetName) == 0 {
e.sheetName = "Sheet1"
}
return e.sheetName
}
func (e *ExportExcel) SortLogFileWithStatus() (fileInfoList []*types.Task) {
// 获取目录中的文件信息
d, _ := os.Open(e.logPath)
defer d.Close()
files, _ := d.ReadDir(0)
// 填充切片
for _, file := range files {
fileName := file.Name()
bytes, _ := os.ReadFile(e.logPath + "/" + fileName)
var info types.Task
_ = json.Unmarshal(bytes, &info)
fileInfoList = append(fileInfoList, &info)
}
// 根据修改时间对切片进行排序
sort.Slice(fileInfoList, func(i, j int) bool {
return fileInfoList[i].Ctime >= fileInfoList[j].Ctime
})
return fileInfoList
}