All checks were successful
构建并部署到测试环境(无 SSH) / build-and-deploy (push) Successful in 9m35s
- 新增不轮询清单 tb_card_polling_pause(迁移 000256 补写存量),所有改 enable_polling 的入口同事务写清单 - 卡、设备勾选批量开关轮询(单次 500,逐项结果,开启跳过风险停机与已销户)与 CSV 文件导入 - 订单套餐生效、改有效期后自动恢复轮询并按规则评估复机 - 不轮询清单查询与导出;卡、设备列表与导出增加是否轮询 - 失败告警停机被拒视图、导出与批量关闭轮询;开关轮询影响说明 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
227 lines
7.6 KiB
Go
227 lines
7.6 KiB
Go
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")
|
||
}
|