Files
junhong_cmp_fiber/internal/query/pollingalert/stop_refused.go
邹进 226a15573a
All checks were successful
构建并部署到测试环境(无 SSH) / build-and-deploy (push) Successful in 1h20m10s
fix: 观测序列与拒绝停机告警读取 ICCID 时显式指定列名
GORM 默认把字段 ICCID 映射成 icc_id:
- 观测序列设备换卡的本地预期判定读不到当前卡 ICCID,永远判不成立,只能靠调网关重试到上限
- 拒绝停机告警列表 ICCID 一直为空
补列名测试防回归。
2026-11-07 17:39:46 +09:00

123 lines
4.7 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 pollingalert 是失败告警"停机被拒"视图的只读查询,供后台列表与导出共用(add-polling-pause-list-and-batch-toggle)。
package pollingalert
import (
"context"
"time"
"gorm.io/gorm"
"github.com/break/junhong_cmp_fiber/pkg/constants"
"github.com/break/junhong_cmp_fiber/pkg/errors"
)
// StopRefusedFilter 是停机被拒视图的附加筛选。
type StopRefusedFilter struct {
CarrierID *uint
Status *int
ICCID string
StartTime *time.Time
EndTime *time.Time
}
// StopRefusedRow 是停机被拒告警与卡当前状态的投影。
type StopRefusedRow struct {
ID uint
CardID uint
ICCID string `gorm:"column:iccid"` // 显式列名:GORM 默认映射成 icc_id
CarrierID uint
CarrierName string
DeviceID uint
NetworkStatus int
StopReason string
EnablePolling bool
HasPackage bool
ErrorCategory string
FailCount int
FirstFailedAt *time.Time
LastFailedAt *time.Time
LastError string
Status int
HandledBy uint
HandledByName string
HandledAt *time.Time
HandleRemark string
}
// ApplyStopRefused 在告警查询上叠加"停机被拒"视图条件:停复机方向为停机,且错误类型为终态或业务错误、或停机失败次数达到阈值。
// 停复机方向告警的最近错误以 "stop: " 开头(见停复机指令执行器)。
func ApplyStopRefused(query *gorm.DB, table string, threshold int) *gorm.DB {
if threshold <= 0 {
threshold = 5
}
prefix := ""
if table != "" {
prefix = table + "."
}
return query.Where(prefix+"alert_level = ? AND "+prefix+"check_type = ? AND "+prefix+"last_error LIKE ?",
constants.PollingAlertLevelCard, constants.CheckTypeStopResume, constants.StopResumeActionStop+":%").
Where("("+prefix+"error_category IN ? OR "+prefix+"fail_count >= ?)",
[]string{constants.GatewayClassTerminal, constants.GatewayClassBusiness}, threshold)
}
// Query 停机被拒视图查询。
type Query struct {
db *gorm.DB
}
// NewQuery 创建停机被拒视图查询。
func NewQuery(db *gorm.DB) *Query {
return &Query{db: db}
}
func (q *Query) base(ctx context.Context, filter StopRefusedFilter, threshold int) *gorm.DB {
query := ApplyStopRefused(q.db.WithContext(ctx).Table("tb_polling_failure_alert AS a"), "a", threshold)
if filter.CarrierID != nil {
query = query.Where("a.carrier_id = ?", *filter.CarrierID)
}
if filter.Status != nil {
query = query.Where("a.status = ?", *filter.Status)
}
if filter.ICCID != "" {
query = query.Where("a.iccid = ?", filter.ICCID)
}
if filter.StartTime != nil {
query = query.Where("a.last_failed_at >= ?", *filter.StartTime)
}
if filter.EndTime != nil {
query = query.Where("a.last_failed_at <= ?", *filter.EndTime)
}
return query
}
// Count 统计停机被拒告警数。
func (q *Query) Count(ctx context.Context, filter StopRefusedFilter, threshold int) (int64, error) {
var total int64
if err := q.base(ctx, filter, threshold).Count(&total).Error; err != nil {
return 0, errors.Wrap(errors.CodeDatabaseError, err, "统计停机被拒告警失败")
}
return total, nil
}
// Page 返回一页停机被拒告警,附带所属设备、网络状态、停机原因、轮询开关、是否有生效套餐与处理人。
func (q *Query) Page(ctx context.Context, filter StopRefusedFilter, threshold, offset, limit int) ([]StopRefusedRow, error) {
var rows []StopRefusedRow
err := q.base(ctx, filter, threshold).
Select(`a.id, a.card_id, a.iccid, a.carrier_id, COALESCE(ca.carrier_name, '') AS carrier_name,
COALESCE(b.device_id, 0) AS device_id, COALESCE(c.network_status, 0) AS network_status,
COALESCE(c.stop_reason, '') AS stop_reason, COALESCE(c.enable_polling, FALSE) AS enable_polling,
EXISTS (SELECT 1 FROM tb_package_usage u WHERE u.status = ? AND u.master_usage_id IS NULL AND u.deleted_at IS NULL
AND ((b.device_id IS NOT NULL AND u.device_id = b.device_id) OR (b.device_id IS NULL AND u.iot_card_id = a.card_id))) AS has_package,
a.error_category, a.fail_count, a.first_failed_at, a.last_failed_at, a.last_error, a.status,
a.handled_by, COALESCE(ac.username, '') AS handled_by_name, a.handled_at, a.handle_remark`, constants.PackageUsageStatusActive).
Joins("LEFT JOIN tb_iot_card c ON c.id = a.card_id").
Joins("LEFT JOIN tb_device_sim_binding b ON b.iot_card_id = a.card_id AND b.bind_status = ? AND b.deleted_at IS NULL", constants.BindStatusBound).
Joins("LEFT JOIN tb_carrier ca ON ca.id = a.carrier_id").
Joins("LEFT JOIN tb_account ac ON ac.id = a.handled_by").
Order("a.last_failed_at DESC NULLS LAST, a.id DESC").Offset(offset).Limit(limit).Scan(&rows).Error
if err != nil {
return nil, errors.Wrap(errors.CodeDatabaseError, err, "查询停机被拒告警失败")
}
return rows, nil
}