sync: 同步 v1.0.4 并脱敏配置与镜像
This commit is contained in:
@@ -0,0 +1,173 @@
|
||||
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
|
||||
}
|
||||
Reference in New Issue
Block a user