329 lines
6.6 KiB
Go
329 lines
6.6 KiB
Go
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
|
||
}
|