chore: 并入 iotlcm-app 历史到 app/(原提交 f40bda9)
This commit is contained in:
@@ -0,0 +1,303 @@
|
||||
package executionlog
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/gogf/gf/v2/database/gdb"
|
||||
"github.com/gogf/gf/v2/errors/gerror"
|
||||
"github.com/gogf/gf/v2/frame/g"
|
||||
"github.com/gogf/gf/v2/util/gconv"
|
||||
"regexp"
|
||||
"slices"
|
||||
"strings"
|
||||
"unicode/utf8"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-app/internal/consts"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-app/internal/dao"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-app/internal/library/allocationunits"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-app/internal/library/batchmetadata"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-app/internal/library/contexts"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-app/internal/library/xgorm/handler"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-app/internal/model"
|
||||
)
|
||||
|
||||
// 办理结果只允许发起人查看;通用批次日志沿用控制台的数据范围。
|
||||
type ownerOnlyKey struct{}
|
||||
|
||||
func scope(ctx context.Context) (*gdb.Model, error) {
|
||||
if contexts.GetUserId(ctx) <= 0 || strings.TrimSpace(contexts.GetTenantId(ctx)) == "" {
|
||||
return nil, gerror.New("请先登录")
|
||||
}
|
||||
return scopedModel(ctx, dao.BatchMain.Ctx(ctx))
|
||||
}
|
||||
|
||||
func scopedModel(ctx context.Context, mod *gdb.Model) (*gdb.Model, error) {
|
||||
user, tenant := contexts.GetUserId(ctx), strings.TrimSpace(contexts.GetTenantId(ctx))
|
||||
if user <= 0 || tenant == "" {
|
||||
return nil, gerror.New("请先登录")
|
||||
}
|
||||
mod = mod.Where("tenant_id", tenant)
|
||||
if ownerOnly, _ := ctx.Value(ownerOnlyKey{}).(bool); ownerOnly {
|
||||
return mod.Where("created_by", user), nil
|
||||
}
|
||||
if contexts.IsSuperAdmin(ctx) {
|
||||
return mod, nil
|
||||
}
|
||||
// 菜单范围由认证中间件读取当前数据库后写入,优先于登录时的角色快照。
|
||||
roleId := gconv.Int64(contexts.GetDataValue(ctx, "currentRoleId"))
|
||||
dataScope := gconv.String(contexts.GetDataValue(ctx, "currentRoleDataScope"))
|
||||
if roleId > 0 && dataScope != "" && len(contexts.GetRoles(ctx)) == 0 {
|
||||
identity := *contexts.GetUser(ctx)
|
||||
identity.Roles = []model.Role{{RoleId: roleId, DataScope: dataScope}}
|
||||
copied := *contexts.Get(ctx)
|
||||
copied.User = &identity
|
||||
ctx = contexts.Set(ctx, &copied)
|
||||
mod = mod.Ctx(ctx)
|
||||
}
|
||||
if len(contexts.GetRoles(ctx)) == 0 {
|
||||
return mod.Where("1 = 0"), nil
|
||||
}
|
||||
validScopes := []string{
|
||||
consts.SysRoleDataScopeAll, consts.SysRoleDataScopeCustom,
|
||||
consts.SysRoleDataScopeDept, consts.SysRoleDataScopeDeptAndBelow,
|
||||
consts.SysRoleDataScopePersonal, consts.SysRoleDataScopeDeptAndBelowOrPersonal,
|
||||
consts.SysRoleDataScopeOrgAndNextLevel, consts.SysRoleDataScopeOrgNextLevel,
|
||||
}
|
||||
if roleId > 0 && dataScope != "" {
|
||||
if !slices.Contains(validScopes, dataScope) {
|
||||
return mod.Where("1 = 0"), nil
|
||||
}
|
||||
} else {
|
||||
for _, role := range contexts.GetRoles(ctx) {
|
||||
if !slices.Contains(validScopes, role.DataScope) {
|
||||
return mod.Where("1 = 0"), nil
|
||||
}
|
||||
}
|
||||
}
|
||||
// 先生成创建部门/创建人的过滤条件,再选择返回字段,避免 DTO 缺少权限字段导致漏过滤。
|
||||
return handler.FilterAuth(mod.Fields("created_dept", "created_by")), nil
|
||||
}
|
||||
|
||||
func dbError(ctx context.Context, err error) error {
|
||||
g.Log().Error(ctx, "execution log query failed", err)
|
||||
return gerror.New("执行日志查询失败,请稍后重试")
|
||||
}
|
||||
|
||||
func paging(page, size int) (int, int) {
|
||||
if page < 1 {
|
||||
page = 1
|
||||
}
|
||||
if size < 1 {
|
||||
size = 20
|
||||
}
|
||||
if size > 100 {
|
||||
size = 100
|
||||
}
|
||||
return page, size
|
||||
}
|
||||
|
||||
func normalize(item *model.AppExecutionLogItem) {
|
||||
item.SourceChannel = batchmetadata.Channel(item.SourceChannel)
|
||||
if !batchmetadata.ValidMode(item.ExecutionMode) {
|
||||
item.ExecutionMode = batchmetadata.Unknown
|
||||
}
|
||||
item.FailReason = safeReason(item.FailReason)
|
||||
}
|
||||
|
||||
// Do not expose internal network addresses, credentials or stack traces to mobile clients.
|
||||
var publicErrorCode = regexp.MustCompile("^[A-Za-z0-9_.-]{1,64}$")
|
||||
|
||||
func safeReason(value string) string {
|
||||
value = strings.TrimSpace(value)
|
||||
if value == "" {
|
||||
return ""
|
||||
}
|
||||
lower := strings.ToLower(value)
|
||||
for _, marker := range []string{"token", "secret", "password", "authorization", "api_key", "apikey", "http:", "https:", "rpc", "sql", "tcp", "panic", "goroutine", "stack", "/src/", "/app/", "dial ", "127.0.", "192." + "168.", "10.", "172.", "sk-", "mysql", "redis"} {
|
||||
if strings.Contains(lower, marker) {
|
||||
return "执行失败,请凭批次号联系客服核查"
|
||||
}
|
||||
}
|
||||
if strings.ContainsAny(value, "\r\n") || utf8.RuneCountInString(value) > 180 {
|
||||
return "执行失败,请凭批次号联系客服核查"
|
||||
}
|
||||
return value
|
||||
}
|
||||
|
||||
func List(ctx context.Context, in *model.AppExecutionLogListParam) ([]*model.AppExecutionLogItem, int, error) {
|
||||
mod, err := scope(ctx)
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
if in == nil {
|
||||
in = &model.AppExecutionLogListParam{}
|
||||
}
|
||||
c := dao.BatchMain.Columns()
|
||||
mod = mod.Fields(model.AppExecutionLogItem{})
|
||||
if n := strings.TrimSpace(in.BatchNo); n != "" {
|
||||
mod = mod.Where(c.BatchNo, n)
|
||||
}
|
||||
if in.SourceChannel != "" {
|
||||
mod = mod.Where(c.SourceChannel, in.SourceChannel)
|
||||
}
|
||||
if in.ExecutionMode != nil {
|
||||
mod = mod.Where(c.ExecutionMode, *in.ExecutionMode)
|
||||
}
|
||||
if in.SourceType != nil {
|
||||
mod = mod.Where(c.SourceType, *in.SourceType)
|
||||
}
|
||||
if in.BatchAsyncStatus != nil {
|
||||
mod = mod.Where(c.BatchAsyncStatus, *in.BatchAsyncStatus)
|
||||
}
|
||||
// Split allocation children are execution details, not a second user submission.
|
||||
// Allocation children are execution details of the parent submission. The
|
||||
// source_batch_no relation is authoritative; batch_info is only descriptive.
|
||||
mod = mod.Where("NOT (batch_type = ? AND COALESCE(source_batch_no, '') <> '' AND source_batch_no <> batch_no)", consts.BatchTypeAllocate)
|
||||
page, size := paging(in.Page, in.PageSize)
|
||||
items := make([]*model.AppExecutionLogItem, 0)
|
||||
total := 0
|
||||
if err = mod.Page(page, size).OrderDesc(c.Id).ScanAndCount(&items, &total, false); err != nil {
|
||||
return nil, 0, dbError(ctx, err)
|
||||
}
|
||||
unitFields := []*allocationunits.Fields{}
|
||||
for _, item := range items {
|
||||
normalize(item)
|
||||
if item.BatchType == consts.BatchTypeAllocate {
|
||||
item.Fields = allocationunits.Resolve(item.UnitInfo, item.StoredSourceDeptId, item.StoredTargetDeptId)
|
||||
unitFields = append(unitFields, &item.Fields)
|
||||
}
|
||||
}
|
||||
if err := allocationunits.Fill(ctx, contexts.GetTenantId(ctx), unitFields); err != nil {
|
||||
return nil, 0, dbError(ctx, err)
|
||||
}
|
||||
return items, total, nil
|
||||
}
|
||||
|
||||
func ensureAllocateParent(ctx context.Context, batchNo string) error {
|
||||
mod, err := scope(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
columns := dao.BatchMain.Columns()
|
||||
count, err := mod.Where(columns.BatchNo, strings.TrimSpace(batchNo)).Where("(batch_type = ? OR (batch_type = ? AND JSON_EXTRACT(batch_info, '$.targetBatchType') = ?))", consts.BatchTypeAllocate, consts.BatchTypePrepare, consts.BatchTypeAllocate).Where("COALESCE(source_batch_no, '') = ''").Count()
|
||||
if err != nil {
|
||||
return dbError(ctx, err)
|
||||
}
|
||||
if count != 1 {
|
||||
return gerror.New("划拨批次不存在或无权查看")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
func AllocateSummary(ctx context.Context, batchNo string) (*model.AppExecutionLogItem, error) {
|
||||
ctx = context.WithValue(ctx, ownerOnlyKey{}, true)
|
||||
if err := ensureAllocateParent(ctx, batchNo); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return Summary(ctx, batchNo)
|
||||
}
|
||||
func AllocateDetails(ctx context.Context, in *model.AppExecutionLogDetailParam) ([]*model.AppExecutionLogDetail, int, error) {
|
||||
ctx = context.WithValue(ctx, ownerOnlyKey{}, true)
|
||||
if in == nil {
|
||||
return nil, 0, gerror.New("批次号不能为空")
|
||||
}
|
||||
if err := ensureAllocateParent(ctx, in.BatchNo); err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
return Details(ctx, in)
|
||||
}
|
||||
|
||||
func Summary(ctx context.Context, batchNo string) (*model.AppExecutionLogItem, error) {
|
||||
mod, err := scope(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if strings.TrimSpace(batchNo) == "" {
|
||||
return nil, gerror.New("批次号不能为空")
|
||||
}
|
||||
var item *model.AppExecutionLogItem
|
||||
if err = mod.Fields(model.AppExecutionLogItem{}).
|
||||
Where(dao.BatchMain.Columns().BatchNo, batchNo).
|
||||
Where("NOT (batch_type = ? AND COALESCE(source_batch_no, '') <> '' AND source_batch_no <> batch_no)", consts.BatchTypeAllocate).
|
||||
Scan(&item); err != nil {
|
||||
return nil, dbError(ctx, err)
|
||||
}
|
||||
if item == nil {
|
||||
return nil, gerror.New("执行日志不存在或无权查看")
|
||||
}
|
||||
normalize(item)
|
||||
if item.BatchType == consts.BatchTypeAllocate {
|
||||
item.Fields = allocationunits.Resolve(item.UnitInfo, item.StoredSourceDeptId, item.StoredTargetDeptId)
|
||||
if err := allocationunits.Fill(ctx, contexts.GetTenantId(ctx), []*allocationunits.Fields{&item.Fields}); err != nil {
|
||||
return nil, dbError(ctx, err)
|
||||
}
|
||||
}
|
||||
return item, nil
|
||||
}
|
||||
|
||||
func Details(ctx context.Context, in *model.AppExecutionLogDetailParam) ([]*model.AppExecutionLogDetail, int, error) {
|
||||
if in == nil {
|
||||
return nil, 0, gerror.New("批次号不能为空")
|
||||
}
|
||||
parent, err := Summary(ctx, in.BatchNo)
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
c := dao.BatchDetail.Columns()
|
||||
mod, err := scopedModel(ctx, dao.BatchDetail.Ctx(ctx))
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
mod = mod.Fields(model.AppExecutionLogDetail{})
|
||||
if parent.BatchType == consts.BatchTypeAllocate {
|
||||
// 先按当前菜单的数据范围解析子批次,再用批次号索引查询明细。
|
||||
var children []struct {
|
||||
BatchNo string `orm:"batch_no"`
|
||||
}
|
||||
mainColumns := dao.BatchMain.Columns()
|
||||
childrenMod, err := scope(ctx)
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
if err := childrenMod.Fields(mainColumns.BatchNo).
|
||||
Where(mainColumns.SourceBatchNo, in.BatchNo).Where(mainColumns.BatchType, parent.BatchType).
|
||||
Where(mainColumns.BatchNo+" <> ?", in.BatchNo).Scan(&children); err != nil {
|
||||
return nil, 0, dbError(ctx, err)
|
||||
}
|
||||
batchNos := []string{in.BatchNo}
|
||||
for _, child := range children {
|
||||
batchNos = append(batchNos, child.BatchNo)
|
||||
}
|
||||
mod = mod.WhereIn(c.BatchNo, batchNos)
|
||||
} else {
|
||||
mod = mod.Where(c.BatchNo, in.BatchNo)
|
||||
}
|
||||
if in.DetailStatus != nil {
|
||||
mod = mod.Where(c.DetailStatus, *in.DetailStatus)
|
||||
}
|
||||
page, size := paging(in.Page, in.PageSize)
|
||||
items := make([]*model.AppExecutionLogDetail, 0)
|
||||
total := 0
|
||||
if err = mod.OrderAsc(c.Id).Page(page, size).ScanAndCount(&items, &total, false); err != nil {
|
||||
return nil, 0, dbError(ctx, err)
|
||||
}
|
||||
unitFields := []*allocationunits.Fields{}
|
||||
for index, item := range items {
|
||||
if parent.BatchType == consts.BatchTypeAllocate {
|
||||
item.Fields = allocationunits.Resolve(item.UnitInfo, parent.SourceDeptId, parent.TargetDeptId)
|
||||
if item.SourceDeptName == "" && item.SourceDeptId == parent.SourceDeptId {
|
||||
item.SourceDeptName = parent.SourceDeptName
|
||||
}
|
||||
if item.TargetDeptName == "" && item.TargetDeptId == parent.TargetDeptId {
|
||||
item.TargetDeptName = parent.TargetDeptName
|
||||
}
|
||||
unitFields = append(unitFields, &item.Fields)
|
||||
}
|
||||
item.RowNo = (page-1)*size + index + 1
|
||||
item.ErrorReason = safeReason(item.ErrorReason)
|
||||
if item.ErrorCode != "" && !publicErrorCode.MatchString(item.ErrorCode) {
|
||||
item.ErrorCode = "EXECUTION_FAILED"
|
||||
}
|
||||
}
|
||||
if err := allocationunits.Fill(ctx, contexts.GetTenantId(ctx), unitFields); err != nil {
|
||||
return nil, 0, dbError(ctx, err)
|
||||
}
|
||||
return items, total, nil
|
||||
}
|
||||
Reference in New Issue
Block a user