package app import ( "context" "strings" "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/xgorm/handler" "git.5iot.com/iotlcm-public/iotlcm-app/internal/model" "git.5iot.com/iotlcm-public/iotlcm-app/internal/model/entity" "github.com/gogf/gf/v2/database/gdb" "github.com/gogf/gf/v2/errors/gerror" "github.com/gogf/gf/v2/os/gtime" ) const appCardListMaxPageSize = 100 // 处理首页免登录公共查卡,只返回卡基础信息,不开放绑卡、重启等业务能力。 func (s *sAppCard) PublicQuery(ctx context.Context, param *model.AppCardQueryParam) (res *model.AppCardModel, err error) { card, err := s.findPublicCard(ctx, param.TenantId, param.CardValue) if err != nil { return nil, err } if card == nil { return s.publicQueryUpstream(ctx, param.CardValue) } return s.publicCardRowToModel(ctx, card, param.CardValue), nil } // 查询当前登录用户可见卡片。终端用户走 t_sim_terminal 关联表, // 企业用户沿用管理平台数据权限查询持卡列表。 func (s *sAppCard) List(ctx context.Context, param *model.AppCardListParam) (items []*model.AppCardModel, total int, err error) { user, err := appCurrentUser(ctx) if err != nil { return nil, 0, err } mod := s.visibleCardModel(ctx, user) if param.AllocateOnly { if s.isTerminalUser(ctx, user) || user.DeptId <= 0 { return nil, 0, gerror.New("当前账号不能划拨卡片") } if err := appEnsureFeatureAccess(ctx, "card:allocate"); err != nil { return nil, 0, err } mod = mod.Where(dao.TSimCardInfo.Table()+"."+dao.TSimCardInfo.Columns().DeptId, user.DeptId) } mod = s.applyCardFilters(mod, param) if param.Page <= 0 { param.Page = 1 } if param.PageSize <= 0 { param.PageSize = 10 } if param.PageSize > appCardListMaxPageSize { param.PageSize = appCardListMaxPageSize } var rows []*appCardQueryRow includeTotal := param.IncludeTotal == nil || *param.IncludeTotal queryPageSize := param.PageSize if !includeTotal { queryPageSize++ } query := mod.OrderDesc(dao.TSimCardInfo.Table()+"."+dao.TSimCardInfo.Columns().Id). Page(param.Page, queryPageSize) if includeTotal { err = query.ScanAndCount(&rows, &total, false) } else { err = query.Scan(&rows) if len(rows) > param.PageSize { rows = rows[:param.PageSize] } } if err != nil { return nil, 0, gerror.Wrap(err, "查询卡列表失败,请稍后重试") } if err = s.attachCardChannelInfoRows(ctx, rows); err != nil { return nil, 0, err } ownerType := s.ownerType(ctx, user) items = make([]*model.AppCardModel, 0, len(rows)) for _, row := range rows { items = append(items, s.cardRowToModel(ctx, row, user, ownerType, true)) } return items, total, nil } // 登录后先查用户可见卡;查不到时回退公共查卡,保证登录后仍能查看公共卡详情。 func (s *sAppCard) Detail(ctx context.Context, param *model.AppCardDetailParam) (res *model.AppCardModel, err error) { user, err := appCurrentUser(ctx) if err != nil { return nil, err } card, err := s.findVisibleCard(ctx, user, param.CardValue) if err != nil { return nil, err } if card != nil { model := s.attachYesterdayDeptFlow(ctx, s.cardRowToModel(ctx, card, user, s.ownerType(ctx, user), true)) return s.attachAppCardRealtimeSession(ctx, card, model), nil } card, err = s.findPublicCard(ctx, user.TenantId, param.CardValue) if err != nil { return nil, err } if card == nil { return s.publicQueryUpstream(ctx, param.CardValue) } if err = s.attachCardChannelInfo(ctx, card); err != nil { return nil, err } return s.publicCardRowToModel(ctx, card, param.CardValue), nil } func (s *sAppCard) publicQueryUpstream(ctx context.Context, cardValue string) (*model.AppCardModel, error) { provider, err := newAppSimUpstreamProvider(ctx) if err != nil { return nil, appErrUpstreamUnavailable(err) } info, err := provider.QuerySimBasicInfo(ctx, appSimBasicInfoInputFromCardValue(cardValue)) if err != nil { return nil, appErrUpstreamUnavailable(err) } card := s.upstreamPublicCardToModel(info, cardValue) if card == nil { return nil, gerror.New("卡信息不存在,请确认查询号码是否正确") } return card, nil } func (s *sAppCard) attachYesterdayDeptFlow(ctx context.Context, card *model.AppCardModel) *model.AppCardModel { if card == nil || card.DeptId <= 0 || strings.TrimSpace(card.TenantId) == "" { return card } statDate := gtime.Now().AddDate(0, 0, -1).Format("Y-m-d") var daily *entity.TDeptFlowDaily _ = dao.TDeptFlowDaily.Ctx(ctx). Where(dao.TDeptFlowDaily.Columns().TenantId, card.TenantId). Where(dao.TDeptFlowDaily.Columns().DeptId, card.DeptId). Where(dao.TDeptFlowDaily.Columns().StatDate, statDate). Limit(1). Scan(&daily) if daily == nil || daily.Id == 0 { return card } card.YesterdayFlowStatDate = statDate if daily.StatDate != nil && !daily.StatDate.IsZero() { card.YesterdayFlowStatDate = daily.StatDate.Format("Y-m-d") } card.YesterdayDeptUsedFlowMb = daily.TotalUsedFlowMb card.YesterdayDeptSms = daily.TotalSms card.YesterdayDeptVoiceMin = daily.TotalVoiceMin return card } func (s *sAppCard) findPublicCard(ctx context.Context, tenantId string, cardValue string) (*appCardQueryRow, error) { mod := s.cardQueryBase(ctx, false) tenantId = strings.TrimSpace(tenantId) if tenantId == "" { tenantId = consts.DefaultSystemTenantCode } mod = mod.Where(dao.TSimCardInfo.Table()+"."+dao.TSimCardInfo.Columns().TenantId, tenantId) var row *appCardQueryRow err := mod. Where(appCardValueCondition(), appCardValueArgs(cardValue)...). Limit(1). Scan(&row) if err != nil { return nil, err } if err = s.attachCardChannelInfo(ctx, row); err != nil { return nil, err } return row, nil } func (s *sAppCard) findOpenCard(ctx context.Context, tenantId string, cardValue string) (*appCardQueryRow, error) { if strings.TrimSpace(cardValue) == "" { return nil, gerror.New("请输入ICCID、MSISDN、IMSI或IMEI") } cardTable := dao.TSimCardInfo.Table() mod := s.cardQueryBase(ctx, false) if tenantId = strings.TrimSpace(tenantId); tenantId != "" { mod = mod.Where(cardTable+"."+dao.TSimCardInfo.Columns().TenantId, tenantId) } var row *appCardQueryRow err := mod. Where(appCardValueCondition(), appCardValueArgs(cardValue)...). OrderDesc(cardTable + "." + dao.TSimCardInfo.Columns().Id). Limit(1). Scan(&row) if err != nil { return nil, err } if err = s.attachCardChannelInfo(ctx, row); err != nil { return nil, err } return row, nil } func (s *sAppCard) findCardByValue(ctx context.Context, tenantId string, cardValue string) (*entity.TSimCardInfo, error) { if strings.TrimSpace(cardValue) == "" { return nil, gerror.New("请输入ICCID、MSISDN、IMSI或IMEI") } var card *entity.TSimCardInfo err := dao.TSimCardInfo.Ctx(ctx). Where(dao.TSimCardInfo.Columns().TenantId, tenantId). Where(appCardValueCondition(), appCardValueArgs(cardValue)...). Limit(1). Scan(&card) if err != nil { return nil, err } return card, nil } func (s *sAppCard) findVisibleCard(ctx context.Context, user *entity.SysUser, cardValue string) (*appCardQueryRow, error) { var row *appCardQueryRow err := s.visibleCardModel(ctx, user). Where(appCardValueCondition(), appCardValueArgs(cardValue)...). Limit(1). Scan(&row) if err != nil { return nil, err } if err = s.attachCardChannelInfo(ctx, row); err != nil { return nil, err } return row, nil } // findDiagnosisCard 查询诊断卡片。企业用户诊断只隔离租户,不套用部门数据范围; // 终端用户仍沿用已绑定卡范围,避免终端账号读取其他用户的卡片。 func (s *sAppCard) findDiagnosisCard(ctx context.Context, user *entity.SysUser, cardValue string) (*appCardQueryRow, error) { if user == nil || strings.TrimSpace(user.TenantId) == "" { return nil, nil } if s.isTerminalUser(ctx, user) { return s.findVisibleCard(ctx, user, cardValue) } cardTable := dao.TSimCardInfo.Table() var row *appCardQueryRow err := s.cardQueryBase(ctx, false). Where(cardTable+"."+dao.TSimCardInfo.Columns().TenantId, strings.TrimSpace(user.TenantId)). Where(appCardValueCondition(), appCardValueArgs(cardValue)...). OrderDesc(cardTable + "." + dao.TSimCardInfo.Columns().Id). Limit(1). Scan(&row) if err != nil { return nil, err } if err = s.attachCardChannelInfo(ctx, row); err != nil { return nil, err } return row, nil } func (s *sAppCard) visibleCardModel(ctx context.Context, user *entity.SysUser) *gdb.Model { cardTable := dao.TSimCardInfo.Table() if s.isTerminalUser(ctx, user) { bindTable := dao.TSimTerminal.Table() packageAlias := "terminal_package" categoryAlias := "terminal_category" packageJoinTable := dao.TSimFlowPackageInfo.Table() + " " + packageAlias categoryJoinTable := dao.TSimPackageCategory.Table() + " " + categoryAlias return s.cardQueryBase(ctx, false). InnerJoin(bindTable, bindTable+"."+dao.TSimTerminal.Columns().Iccid+"="+cardTable+"."+dao.TSimCardInfo.Columns().Iccid). LeftJoin(packageJoinTable, packageAlias+"."+dao.TSimFlowPackageInfo.Columns().PackageCode+"="+bindTable+"."+appSimTerminalCurrentPackageCodeColumn+" AND "+packageAlias+"."+dao.TSimFlowPackageInfo.Columns().TenantId+"="+bindTable+"."+dao.TSimTerminal.Columns().TenantId). LeftJoin(categoryJoinTable, categoryAlias+"."+dao.TSimPackageCategory.Columns().CategoryCode+"="+packageAlias+"."+dao.TSimFlowPackageInfo.Columns().CategoryCode+" AND "+categoryAlias+"."+dao.TSimPackageCategory.Columns().TenantId+"="+packageAlias+"."+dao.TSimFlowPackageInfo.Columns().TenantId). Where(cardTable+"."+dao.TSimCardInfo.Columns().TenantId, user.TenantId). Where(bindTable+"."+dao.TSimTerminal.Columns().TenantId, user.TenantId). Where(bindTable+"."+dao.TSimTerminal.Columns().TerminalId, user.UserId). Fields(bindTable + "." + dao.TSimTerminal.Columns().Id + " as terminal_bind_id"). Fields(bindTable + "." + dao.TSimTerminal.Columns().TerminalId + " as terminal_user_id"). Fields(bindTable + "." + dao.TSimTerminal.Columns().TerminalName + " as terminalName"). Fields(bindTable + "." + appSimTerminalDeptIdColumn + " as terminal_dept_id"). Fields(bindTable + "." + appSimTerminalCurrentPackageCodeColumn + " as terminal_current_package_code"). Fields(packageAlias + "." + dao.TSimFlowPackageInfo.Columns().PackageName + " as terminal_package_name"). Fields(packageAlias + "." + dao.TSimFlowPackageInfo.Columns().PackageType + " as terminal_package_type"). Fields(packageAlias + "." + dao.TSimFlowPackageInfo.Columns().CategoryCode + " as terminal_category_code"). Fields(categoryAlias + "." + dao.TSimPackageCategory.Columns().CategoryName + " as terminal_category_name") } return s.enterpriseVisibleCardModel(ctx, user) } func (s *sAppCard) enterpriseVisibleCardModel(ctx context.Context, user *entity.SysUser) *gdb.Model { cardTable := dao.TSimCardInfo.Table() if user == nil || user.DeptId <= 0 { return s.cardQueryBase(ctx, false).Where("1=0") } mod := s.cardQueryBase(ctx, false). Where(cardTable+"."+dao.TSimCardInfo.Columns().TenantId, user.TenantId) return appApplyEnterpriseDataScope( ctx, user, mod, cardTable+"."+dao.TSimCardInfo.Columns().DeptId, cardTable+"."+dao.TSimCardInfo.Columns().CreatedBy, func(legacyMod *gdb.Model) *gdb.Model { return appLegacyEnterpriseDeptAndSubScope(ctx, user, legacyMod, cardTable+"."+dao.TSimCardInfo.Columns().DeptId) }, ) } // 统一卡查询基础 JOIN,其中 current_package_code 对应套餐表 package_code。 // 卡片展示和公共当前套餐详情都依赖这个关联关系。 func (s *sAppCard) cardQueryBase(ctx context.Context, useAuth bool) *gdb.Model { cardTable := dao.TSimCardInfo.Table() packageTable := dao.TSimFlowPackageInfo.Table() categoryTable := dao.TSimPackageCategory.Table() ownerDeptAlias := "owner_dept" ownerDeptJoinTable := dao.SysDept.Table() + " " + ownerDeptAlias mod := dao.TSimCardInfo.Ctx(ctx). FieldsPrefix(cardTable, entity.TSimCardInfo{}). Fields(packageTable+"."+dao.TSimFlowPackageInfo.Columns().PackageName+" as packageName"). Fields(packageTable+"."+dao.TSimFlowPackageInfo.Columns().PackageType+" as packageType"). Fields(packageTable+"."+dao.TSimFlowPackageInfo.Columns().CategoryCode+" as categoryCode"). Fields(categoryTable+"."+dao.TSimPackageCategory.Columns().CategoryName+" as categoryName"). Fields(ownerDeptAlias+"."+dao.SysDept.Columns().DeptName+" as ownerDeptName"). Fields(ownerDeptAlias+"."+dao.SysDept.Columns().DeptKey+" as ownerDeptKey"). LeftJoin(packageTable, packageTable+"."+dao.TSimFlowPackageInfo.Columns().PackageCode+"="+cardTable+"."+dao.TSimCardInfo.Columns().CurrentPackageCode+" AND "+packageTable+"."+dao.TSimFlowPackageInfo.Columns().TenantId+"="+cardTable+"."+dao.TSimCardInfo.Columns().TenantId). LeftJoin(categoryTable, categoryTable+"."+dao.TSimPackageCategory.Columns().CategoryCode+"="+packageTable+"."+dao.TSimFlowPackageInfo.Columns().CategoryCode+" AND "+categoryTable+"."+dao.TSimPackageCategory.Columns().TenantId+"="+packageTable+"."+dao.TSimFlowPackageInfo.Columns().TenantId). LeftJoin(ownerDeptJoinTable, ownerDeptAlias+"."+dao.SysDept.Columns().DeptId+"="+cardTable+"."+dao.TSimCardInfo.Columns().DeptId+" AND "+ownerDeptAlias+"."+dao.SysDept.Columns().TenantId+"="+cardTable+"."+dao.TSimCardInfo.Columns().TenantId) if useAuth { mod = handler.Model(mod, &handler.Option{FilterAuth: true, FilterTenant: true}) } return mod } func (s *sAppCard) applyCardFilters(mod *gdb.Model, param *model.AppCardListParam) *gdb.Model { if strings.TrimSpace(param.CardValue) != "" { if param.AllocateOnly { // 划拨列表支持号码片段检索,详情与执行提交仍按完整号码定位。 value := "%" + strings.TrimSpace(param.CardValue) + "%" columns := dao.TSimCardInfo.Columns() table := dao.TSimCardInfo.Table() + "." mod = mod.Where("("+table+columns.Iccid+" LIKE ? OR "+table+columns.Msisdn+" LIKE ? OR "+table+columns.Imsi+" LIKE ? OR "+table+columns.Imei+" LIKE ?)", value, value, value, value) } else { mod = mod.Where(appCardValueCondition(), appCardValueArgs(param.CardValue)...) } } if strings.TrimSpace(param.CategoryCode) != "" { mod = mod.Where(dao.TSimFlowPackageInfo.Table()+"."+dao.TSimFlowPackageInfo.Columns().CategoryCode, strings.TrimSpace(param.CategoryCode)) } switch strings.TrimSpace(param.Status) { case "normal": mod = mod.Where(dao.TSimCardInfo.Table()+"."+dao.TSimCardInfo.Columns().BossCardStatus, 2). WhereNot(dao.TSimCardInfo.Table()+"."+dao.TSimCardInfo.Columns().PackageStatus, 3) case "low-flow": mod = mod.Where(dao.TSimCardInfo.Table()+"."+dao.TSimCardInfo.Columns().PackageTotalFlowMb+">?", 0). Where("("+dao.TSimCardInfo.Table()+"."+dao.TSimCardInfo.Columns().PackageTotalFlowMb+"-"+dao.TSimCardInfo.Table()+"."+dao.TSimCardInfo.Columns().UsedFlowMb+")/"+dao.TSimCardInfo.Table()+"."+dao.TSimCardInfo.Columns().PackageTotalFlowMb+" <= ?", 0.2) case "expiring": mod = mod.Where(dao.TSimCardInfo.Table()+"."+dao.TSimCardInfo.Columns().FlowPkgEndAt+" IS NOT NULL"). Where(dao.TSimCardInfo.Table()+"."+dao.TSimCardInfo.Columns().FlowPkgEndAt+" >= ?", gtime.Now()). Where(dao.TSimCardInfo.Table()+"."+dao.TSimCardInfo.Columns().FlowPkgEndAt+" <= ?", gtime.Now().AddDate(0, 0, 30)) case "error": mod = mod.Where( "("+dao.TSimCardInfo.Table()+"."+dao.TSimCardInfo.Columns().PackageStatus+" = ? OR "+dao.TSimCardInfo.Table()+"."+dao.TSimCardInfo.Columns().BossCardStatus+" IN(?, ?, ?, ?) OR "+dao.TSimCardInfo.Table()+"."+dao.TSimCardInfo.Columns().BossCardStatus+" NOT IN(?, ?, ?, ?, ?, ?, ?, ?))", consts.PackageStatusExpired, 0, consts.V3ApiSimBossCardStatusStopped, 8, 9, 0, consts.V3ApiSimBossCardStatusPendingActivate, consts.V3ApiSimBossCardStatusActivated, consts.V3ApiSimBossCardStatusStopped, 6, 7, 8, 9, ) } return mod }