174 lines
6.8 KiB
Go
174 lines
6.8 KiB
Go
package exportcenter
|
|
|
|
import (
|
|
"context"
|
|
"time"
|
|
|
|
"github.com/gogf/gf/v2/database/gdb"
|
|
"github.com/gogf/gf/v2/encoding/gjson"
|
|
"github.com/gogf/gf/v2/errors/gerror"
|
|
"github.com/gogf/gf/v2/frame/g"
|
|
"github.com/gogf/gf/v2/util/gconv"
|
|
|
|
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/consts"
|
|
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/dao"
|
|
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/library/batchimport"
|
|
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/library/batchmetadata"
|
|
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/library/contexts"
|
|
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/model"
|
|
)
|
|
|
|
func snapshot(ctx context.Context) (*model.ExportIdentitySnapshot, error) {
|
|
user := contexts.GetUser(ctx)
|
|
if user == nil || user.ID <= 0 {
|
|
return nil, gerror.New("登录状态已失效,请重新登录")
|
|
}
|
|
snap := &model.ExportIdentitySnapshot{
|
|
UserId: user.ID,
|
|
DeptId: user.DeptId,
|
|
TenantId: user.TenantId,
|
|
Roles: user.Roles,
|
|
AccessCodeList: user.AccessCodeList,
|
|
}
|
|
if v := contexts.GetDataValue(ctx, "currentRoleId"); v != nil {
|
|
snap.CurrentRoleId = gconv.String(v)
|
|
}
|
|
if v := contexts.GetDataValue(ctx, "currentRoleDataScope"); v != nil {
|
|
snap.CurrentRoleDataScope = gconv.String(v)
|
|
}
|
|
return snap, nil
|
|
}
|
|
|
|
// Submit 提交一次导出任务:预校验行数、落任务与执行日志、投递后台执行。
|
|
func Submit(ctx context.Context, req *Request) (res *SubmitResult, err error) {
|
|
if req == nil || req.Tags == nil || req.Fetch == nil {
|
|
return nil, gerror.New("导出配置不完整")
|
|
}
|
|
if req.BizKey == "" || req.BizName == "" {
|
|
return nil, gerror.New("导出业务标识不能为空")
|
|
}
|
|
|
|
// 提交前占用执行/排队容量,避免高峰期无限创建等待执行的 goroutine。
|
|
select {
|
|
case slots <- struct{}{}:
|
|
default:
|
|
return nil, gerror.New("导出任务排队已满,请稍后重试")
|
|
}
|
|
ownershipTransferred := false
|
|
defer func() {
|
|
if !ownershipTransferred {
|
|
<-slots
|
|
}
|
|
}()
|
|
|
|
snap, err := snapshot(ctx)
|
|
if err != nil {
|
|
return nil, failSubmit(ctx, "snapshot", req.BizKey, err)
|
|
}
|
|
|
|
// 预校验:只取 1 行,拿到筛选条件下的总行数。
|
|
// 业务侧可能返回 -1 表示「提交阶段还无法确定条数」(如按号段/按公司圈卡,
|
|
// 需要后台实际查库才知道命中多少),此时放行,最终条数以实际写出为准。
|
|
// total=0 也放行:空结果同样要产出文件(至少带表头与失败原因列),
|
|
// 让用户能拿到「查了但没命中」的凭据,而不是只看到一句报错。
|
|
_, total, err := req.Fetch(ctx, 1, 1)
|
|
if err != nil {
|
|
return nil, failSubmit(ctx, "count", req.BizKey, gerror.Wrap(err, "统计导出数据失败"))
|
|
}
|
|
if total > MaxRows {
|
|
return nil, gerror.Newf("本次导出 %d 行,超过单次上限 %d 行,请缩小筛选范围", total, MaxRows)
|
|
}
|
|
// 未知条数不写入任务记录,避免 -1 出现在列表与执行日志里。
|
|
recordedTotal := total
|
|
if recordedTotal < 0 {
|
|
recordedTotal = 0
|
|
}
|
|
|
|
taskNo, err := batchimport.NextBatchNo(consts.BatchNoPrefixExport)
|
|
if err != nil {
|
|
return nil, failSubmit(ctx, "batch_no", req.BizKey, gerror.Wrap(err, "生成任务编号失败"))
|
|
}
|
|
|
|
// 列清单直接影响导出内容,留痕便于排查「导出列与页面不一致」。
|
|
if len(req.Columns) == 0 {
|
|
req.Columns = columnsFromRequest(ctx)
|
|
}
|
|
g.Log().Infof(ctx, "[exportcenter] 提交导出 biz=%s user=%d columns=%d", req.BizKey, snap.UserId, len(req.Columns))
|
|
|
|
now := time.Now()
|
|
expire := now.AddDate(0, 0, ExpireDays)
|
|
fileName := taskNo + ".xlsx"
|
|
|
|
batchInfo := gjson.New(g.Map{
|
|
"exportBizKey": req.BizKey,
|
|
"exportBizName": req.BizName,
|
|
"permCode": req.PermCode,
|
|
})
|
|
|
|
var taskId int64
|
|
err = g.DB().Transaction(ctx, func(ctx context.Context, tx gdb.TX) error {
|
|
id, insertErr := dao.TExportTask.Ctx(ctx).TX(tx).Data(g.Map{
|
|
dao.TExportTask.Columns().TaskNo: taskNo,
|
|
dao.TExportTask.Columns().BizKey: req.BizKey,
|
|
dao.TExportTask.Columns().BizName: req.BizName,
|
|
dao.TExportTask.Columns().PermCode: req.PermCode,
|
|
dao.TExportTask.Columns().QueryParams: gjson.New(req.QueryParam),
|
|
dao.TExportTask.Columns().IdentitySnapshot: gjson.New(snap),
|
|
dao.TExportTask.Columns().Status: consts.DownloadStatusPending,
|
|
dao.TExportTask.Columns().TotalCount: recordedTotal,
|
|
dao.TExportTask.Columns().FileName: fileName,
|
|
dao.TExportTask.Columns().OwnerUserId: snap.UserId,
|
|
dao.TExportTask.Columns().DeptId: snap.DeptId,
|
|
dao.TExportTask.Columns().TenantId: snap.TenantId,
|
|
dao.TExportTask.Columns().CreatedDept: snap.DeptId,
|
|
dao.TExportTask.Columns().CreatedBy: snap.UserId,
|
|
dao.TExportTask.Columns().CreatedAt: now,
|
|
dao.TExportTask.Columns().ExpireTime: expire,
|
|
}).InsertAndGetId()
|
|
if insertErr != nil {
|
|
return gerror.Wrap(insertErr, "创建导出任务失败")
|
|
}
|
|
taskId = id
|
|
|
|
_, insertErr = dao.BatchMain.Ctx(ctx).TX(tx).Data(g.Map{
|
|
dao.BatchMain.Columns().BatchNo: taskNo,
|
|
dao.BatchMain.Columns().BatchType: consts.BatchTypeExport,
|
|
dao.BatchMain.Columns().BatchStatus: consts.BatchStatusNormal,
|
|
dao.BatchMain.Columns().BatchAsyncStatus: consts.BatchAsyncStatusCreated,
|
|
dao.BatchMain.Columns().SourceChannel: batchmetadata.Console,
|
|
dao.BatchMain.Columns().ExecutionMode: batchmetadata.Bulk,
|
|
dao.BatchMain.Columns().RunResult: consts.BatchRunResultPending,
|
|
dao.BatchMain.Columns().TotalCount: recordedTotal,
|
|
dao.BatchMain.Columns().BatchInfo: batchInfo,
|
|
dao.BatchMain.Columns().TenantId: snap.TenantId,
|
|
dao.BatchMain.Columns().CreatedDept: snap.DeptId,
|
|
// dept_id 必须与 created_dept 一致。
|
|
// FilterAuth 对 batch_main 优先用 dept_id 过滤,留空(NUL)会让提交人自己也看不到
|
|
// 这条执行日志(只有全部数据权限的管理员能看到),表现为「导出了但日志里没有」。
|
|
dao.BatchMain.Columns().DeptId: snap.DeptId,
|
|
dao.BatchMain.Columns().CreatedBy: snap.UserId,
|
|
dao.BatchMain.Columns().CreatedAt: now,
|
|
}).Insert()
|
|
if insertErr != nil {
|
|
return gerror.Wrap(insertErr, "写导出执行日志失败")
|
|
}
|
|
return nil
|
|
})
|
|
if err != nil {
|
|
return nil, failSubmit(ctx, "persist", req.BizKey, err)
|
|
}
|
|
|
|
go run(taskNo, req)
|
|
ownershipTransferred = true
|
|
|
|
return &SubmitResult{TaskId: taskId, TaskNo: taskNo, Total: total}, nil
|
|
}
|
|
|
|
// failSubmit 统一记录提交失败的环节与原因。
|
|
// 框架会把未带错误码的 error 统一转成「内部服务器错误」,真实原因只能从日志看,
|
|
// 所以这里必须把 step 打出来,否则线上无法定位。
|
|
func failSubmit(ctx context.Context, step, bizKey string, err error) error {
|
|
g.Log().Errorf(ctx, "[exportcenter] 提交导出失败 step=%s biz=%s err=%+v", step, bizKey, err)
|
|
return err
|
|
}
|