You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

277 lines
5.8 KiB
Go

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

package export
import (
"context"
"math"
"os"
"sync"
)
const (
MaxRow = 1000000 // excel 最大行数
)
// RowData 单条数据类型
type RowData map[string]interface{}
// AfterFinishCustomize 完成后自定义处理函数
type AfterFinishCustomize func(fileName, fullPath string) error
type Task interface {
GetTotal() int64 //获取导出的总条数
GetBaseTask() *BaseTask //获取基础任务结构
WriteData(data []RowData) (err error) //写入数据
FileClose() //关闭文件
GetFilename() string //获取文件名称
GetFullPath() string //获取文件存储路径
}
// Done 执行导出任务,虽然现在没有错误
func Done(et Task) {
//没有数据导出的时候直接任务结束
t := et.GetBaseTask()
if et.GetTotal() == 0 {
t.taskEnd(nil)
return
}
//监听步进完成
go t.listenNotifySp()
//监听数据加入
go t.listenAppendData()
//监听任务情况
go func() {
var taskErr error
select {
case <-t.finish: //正常完成触发
break
case <-t.ctx.Done(): //超时触发
break
case notifyErr, ok := <-t.err: //执行过程出现异常
if ok {
taskErr = notifyErr
}
}
//任务结束后
t.taskEnd(taskErr)
}()
return
}
type BaseTask struct {
ctx context.Context //上下文
cancel context.CancelFunc //关闭上下文
err chan error //错误通知,必须要有错误才通知
fr int64 //当前写入的行数
finish chan bool //导出完成标识
ap chan []RowData //需要导出的数据
reply chan Reply //处理结果通道,文件导出完成后,会通知该通道返回处理的结果,调用AfterFinish()方法可以获取到通知
ti Task //任务接口(ti=>taskInterface)
fp *FinishPart //使用完成部分通讯
pe *Proceed //从某一条数据之后继续导出
}
type Proceed struct {
l *sync.Mutex //写入进度锁
filterNum int64 //先过滤多少条数据之后才开始计算
nft int64 //当前已经过滤了多少条数据
begin int64 //到当写入条数到这个点的时候才开始写入文件
}
type PartDone func(spn, nfp float64)
// FinishPart 完成部分导出的结构体
type FinishPart struct {
l *sync.Mutex //写入进度锁
spn float64 //把整个导出文件分成多少份
sp int64 //每多少条后计算一次速率
nfp float64 //当前已导出的份数
notifySp chan float64 //当前进度 / 分割数的值
fn PartDone //进度完成要做什么操作
}
// Reply 处理结果结构体
type Reply struct {
Error error
Filename string
FilePath string
Fr int64
}
// Init 初始化基础任务
func (t *BaseTask) Init(ctx context.Context, ti Task, opts ...TaskOption) {
//设置的数据
for _, opt := range opts {
opt(t)
}
//初始化任务值
t.err = make(chan error, 1)
t.finish = make(chan bool, 1)
t.reply = make(chan Reply, 1)
t.ap = make(chan []RowData, 1)
t.ctx, t.cancel = context.WithCancel(ctx)
t.ti = ti
if t.HasFp() {
t.fp.sp = int64(math.Ceil(float64(ti.GetTotal()) / t.fp.spn))
}
}
// GetBaseTask 获取基础任务结构
func (t *BaseTask) GetBaseTask() *BaseTask {
return t
}
// 监听加入的数据
func (t *BaseTask) listenAppendData() {
for {
select {
case data, ok := <-t.ap:
if !ok {
return
}
err := t.ti.WriteData(data)
if err != nil {
t.err <- err
return
}
case <-t.ctx.Done():
return
}
}
}
// CheckFinish 查看任务有没有完成
func (t *BaseTask) CheckFinish() {
// 写入总数已经达到总条数,关闭文件和删除任务
total := t.ti.GetTotal()
if t.fr >= total || t.fr >= MaxRow {
t.finish <- true
} else if t.HasFp() && t.fr%t.fp.sp == 0 { //达到完成部分导出
t.notifySp()
}
}
// 获取步进通知
func (t *BaseTask) listenNotifySp() {
if t.fp == nil {
return
}
for {
select {
case nfp, ok := <-t.fp.notifySp:
if !ok {
return
}
t.fp.fn(t.fp.spn, nfp)
case <-t.ctx.Done():
return
}
}
}
// 需要通知部分完成
func (t *BaseTask) notifySp() {
t.fp.l.Lock()
defer t.fp.l.Unlock()
t.fp.nfp++
t.fp.notifySp <- t.fp.nfp
}
// AppendData 追加任务导出数据
func (t *BaseTask) AppendData(data []RowData) {
t.ap <- data
}
// 任务结束后
func (t *BaseTask) taskEnd(err error) {
//结束其他进行中的协程任务
t.cancel()
//关闭文件资源占用
t.ti.FileClose()
//通知结构体
reply := Reply{
Filename: t.ti.GetFilename(),
FilePath: t.ti.GetFullPath(),
Fr: t.GetFr(),
}
//有异常的时候处理
if err != nil {
//删除文件,防止服务器积压
_ = os.Remove(t.ti.GetFullPath())
reply.Error = err
}
//通知任务回调
t.reply <- reply
}
// CanWriteRow 是否可以写入数据
func (t *BaseTask) CanWriteRow() bool {
if t.pe == nil {
return true
}
t.pe.l.Lock()
defer t.pe.l.Unlock()
//先处理一下要过滤的数据
if t.pe.filterNum != 0 && t.pe.filterNum >= t.pe.nft {
t.fr--
t.pe.nft++
return false
}
//看看不是已经到要写入的那一行
if t.pe.begin <= t.fr {
return true
}
return false
}
// GetReply 获取任务结果
func (t *BaseTask) GetReply() <-chan Reply {
return t.reply
}
// HasFp 是否存在进度推送
func (t *BaseTask) HasFp() bool {
return t.fp != nil
}
// FrAdd 写入了一条数据
func (t *BaseTask) FrAdd() {
t.fr++
}
// GetFr 获取写入文件的行数
func (t *BaseTask) GetFr() int64 {
return t.fr
}
// ManualEnd 手动结束,有错误传入的时候是
func (t *BaseTask) ManualEnd(err error) {
if err != nil {
t.err <- err
return
}
t.finish <- true
}
// Release 任务结束后释放
func (t *BaseTask) Release() {
close(t.reply)
}