Files
junhong_cmp_fiber/internal/exporter/polling_pause_scene.go
邹进 7ddb9312e3
All checks were successful
构建并部署到测试环境(无 SSH) / build-and-deploy (push) Successful in 9m35s
feat: 不轮询清单与批量开关轮询
- 新增不轮询清单 tb_card_polling_pause(迁移 000256 补写存量),所有改 enable_polling 的入口同事务写清单
- 卡、设备勾选批量开关轮询(单次 500,逐项结果,开启跳过风险停机与已销户)与 CSV 文件导入
- 订单套餐生效、改有效期后自动恢复轮询并按规则评估复机
- 不轮询清单查询与导出;卡、设备列表与导出增加是否轮询
- 失败告警停机被拒视图、导出与批量关闭轮询;开关轮询影响说明

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-26 03:07:20 +09:00

227 lines
7.6 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package exporter
import (
"context"
"strconv"
"time"
"gorm.io/gorm"
"github.com/break/junhong_cmp_fiber/internal/query/pollingalert"
pausequery "github.com/break/junhong_cmp_fiber/internal/query/pollingpause"
"github.com/break/junhong_cmp_fiber/pkg/constants"
)
// 不轮询清单与停机被拒告警导出(add-polling-pause-list-and-batch-toggle),仅超级管理员与平台账号。
var pollingPauseHeaders = []string{
"资产类型", "资产ID", "ICCID或IMEI", "运营商账户", "当前是否轮询", "网关扩展状态", "状态", "关闭来源", "关闭原因",
"关闭人", "关闭时间", "导入任务ID", "恢复来源", "恢复原因", "恢复人", "恢复时间",
}
// PollingPauseDataSource 不轮询清单导出数据源。
type PollingPauseDataSource struct {
query *pausequery.Query
}
// NewPollingPauseDataSource 创建不轮询清单导出数据源。
func NewPollingPauseDataSource(db *gorm.DB) *PollingPauseDataSource {
return &PollingPauseDataSource{query: pausequery.NewQuery(db)}
}
// Scene 返回导出场景编码。
func (s *PollingPauseDataSource) Scene() string { return constants.ExportTaskScenePollingPause }
func (s *PollingPauseDataSource) filter(params ExportParams) (pausequery.Filter, error) {
if err := pausequery.EnsureAllowed(params.UserType); err != nil {
return pausequery.Filter{}, err
}
filter := pausequery.Filter{}
filter.AssetType, _ = filterString(params.Filters, "asset_type")
filter.Source, _ = filterString(params.Filters, "source")
filter.Identifier, _ = filterString(params.Filters, "identifier")
if id, ok := filterUint(params.Filters, "carrier_id"); ok && id > 0 {
filter.CarrierID = &id
}
if id, ok := filterUint(params.Filters, "operator_id"); ok && id > 0 {
filter.OperatorID = &id
}
if status, ok := filterInt(params.Filters, "status"); ok && status > 0 {
filter.Status = &status
}
if t, ok := filterTime(params.Filters, "start_time"); ok {
filter.StartTime = &t
}
if t, ok := filterTime(params.Filters, "end_time"); ok {
filter.EndTime = &t
}
return filter, nil
}
// Count 统计导出行数。
func (s *PollingPauseDataSource) Count(ctx context.Context, params ExportParams) (int, error) {
filter, err := s.filter(params)
if err != nil {
return 0, err
}
total, err := s.query.Count(ctx, filter)
return int(total), err
}
// Headers 返回导出表头。
func (s *PollingPauseDataSource) Headers(ctx context.Context, params ExportParams) ([]string, error) {
if err := pausequery.EnsureAllowed(params.UserType); err != nil {
return nil, err
}
return pollingPauseHeaders, nil
}
// Fetch 按 offset/limit 返回导出行。
func (s *PollingPauseDataSource) Fetch(ctx context.Context, params ExportParams, offset, limit int) ([][]string, error) {
filter, err := s.filter(params)
if err != nil {
return nil, err
}
rows, err := s.query.Page(ctx, filter, offset, limit)
if err != nil {
return nil, err
}
result := make([][]string, 0, len(rows))
for _, row := range rows {
assetType := "卡"
if row.AssetType == constants.AssetTypeDevice {
assetType = "设备"
}
importID := ""
if row.ImportTaskID > 0 {
importID = strconv.FormatUint(uint64(row.ImportTaskID), 10)
}
result = append(result, []string{
assetType, strconv.FormatUint(uint64(row.AssetID), 10), row.Identifier, row.CarrierName, yesNo(row.EnablePolling), row.GatewayExtend,
constants.PollingPauseStatusName(row.Status), constants.PollingToggleSourceName(row.CloseSource), row.CloseReason,
operatorName(row.ClosedBy, row.ClosedByName), formatExportTime(&row.ClosedAt), importID,
constants.PollingToggleSourceName(row.RestoreSource), row.RestoreReason, operatorName(row.RestoredBy, row.RestoredByName),
formatExportTime(row.RestoredAt),
})
}
return result, nil
}
var stopRefusedHeaders = []string{
"告警ID", "ICCID", "运营商账户", "所属设备ID", "网络状态", "有生效套餐", "停机原因", "错误类型", "上游返回信息",
"首次失败时间", "最近失败时间", "失败次数", "是否轮询", "处理状态", "处理人", "处理时间", "处理备注",
}
// StopRefusedAlertDataSource 停机被拒告警导出数据源。
type StopRefusedAlertDataSource struct {
query *pollingalert.Query
threshold func(ctx context.Context) int
}
// NewStopRefusedAlertDataSource 创建停机被拒告警导出数据源;threshold 为空时取默认阈值。
func NewStopRefusedAlertDataSource(db *gorm.DB, threshold func(ctx context.Context) int) *StopRefusedAlertDataSource {
return &StopRefusedAlertDataSource{query: pollingalert.NewQuery(db), threshold: threshold}
}
// Scene 返回导出场景编码。
func (s *StopRefusedAlertDataSource) Scene() string {
return constants.ExportTaskScenePollingStopRefusedAlert
}
func (s *StopRefusedAlertDataSource) filter(ctx context.Context, params ExportParams) (pollingalert.StopRefusedFilter, int, error) {
if err := pausequery.EnsureAllowed(params.UserType); err != nil {
return pollingalert.StopRefusedFilter{}, 0, err
}
filter := pollingalert.StopRefusedFilter{}
filter.ICCID, _ = filterString(params.Filters, "iccid")
if id, ok := filterUint(params.Filters, "carrier_id"); ok && id > 0 {
filter.CarrierID = &id
}
if status, ok := filterInt(params.Filters, "status"); ok && status > 0 {
filter.Status = &status
}
if t, ok := filterTime(params.Filters, "start_time"); ok {
filter.StartTime = &t
}
if t, ok := filterTime(params.Filters, "end_time"); ok {
filter.EndTime = &t
}
threshold := 0
if s.threshold != nil {
threshold = s.threshold(ctx)
}
return filter, threshold, nil
}
// Count 统计导出行数。
func (s *StopRefusedAlertDataSource) Count(ctx context.Context, params ExportParams) (int, error) {
filter, threshold, err := s.filter(ctx, params)
if err != nil {
return 0, err
}
total, err := s.query.Count(ctx, filter, threshold)
return int(total), err
}
// Headers 返回导出表头。
func (s *StopRefusedAlertDataSource) Headers(ctx context.Context, params ExportParams) ([]string, error) {
if err := pausequery.EnsureAllowed(params.UserType); err != nil {
return nil, err
}
return stopRefusedHeaders, nil
}
// Fetch 按 offset/limit 返回导出行。
func (s *StopRefusedAlertDataSource) Fetch(ctx context.Context, params ExportParams, offset, limit int) ([][]string, error) {
filter, threshold, err := s.filter(ctx, params)
if err != nil {
return nil, err
}
rows, err := s.query.Page(ctx, filter, threshold, offset, limit)
if err != nil {
return nil, err
}
result := make([][]string, 0, len(rows))
for _, row := range rows {
device := ""
if row.DeviceID > 0 {
device = strconv.FormatUint(uint64(row.DeviceID), 10)
}
network := "停机"
if row.NetworkStatus == constants.NetworkStatusOnline {
network = "开机"
}
result = append(result, []string{
strconv.FormatUint(uint64(row.ID), 10), row.ICCID, row.CarrierName, device, network, yesNo(row.HasPackage), row.StopReason,
constants.GatewayErrorClassName(row.ErrorCategory), row.LastError, formatExportTime(row.FirstFailedAt), formatExportTime(row.LastFailedAt),
strconv.Itoa(row.FailCount), yesNo(row.EnablePolling), constants.PollingAlertStatusName(row.Status),
operatorName(row.HandledBy, row.HandledByName), formatExportTime(row.HandledAt), row.HandleRemark,
})
}
return result, nil
}
func yesNo(v bool) string {
if v {
return "是"
}
return "否"
}
func operatorName(id uint, name string) string {
if id == 0 {
return "系统"
}
if name == "" {
return strconv.FormatUint(uint64(id), 10)
}
return name
}
func formatExportTime(t *time.Time) string {
if t == nil || t.IsZero() {
return ""
}
return t.In(time.FixedZone("CST", 8*3600)).Format("2006-01-02 15:04:05")
}