feat: 不轮询清单与批量开关轮询
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>
This commit is contained in:
邹进
2026-10-26 03:07:20 +09:00
parent a4e3fe4d8f
commit 7ddb9312e3
45 changed files with 2250 additions and 50 deletions

View File

@@ -64,6 +64,8 @@ func CheckTypeName(checkType string) string {
return "实名检查"
case CheckTypePackage:
return "套餐检查"
case CheckTypeStopResume:
return "停复机"
case PollingAlertCheckTypeAll:
return "全部"
default:

View File

@@ -0,0 +1,132 @@
package constants
// 不轮询清单(tb_card_polling_pause,add-polling-pause-list-and-batch-toggle)。
// 不轮询清单状态。
const (
PollingPauseStatusClosed = 1 // 已关闭
PollingPauseStatusRestored = 2 // 已恢复
)
// PollingPauseStatusName 返回不轮询清单状态名称。
func PollingPauseStatusName(status int) string {
switch status {
case PollingPauseStatusClosed:
return "已关闭"
case PollingPauseStatusRestored:
return "已恢复"
default:
return ""
}
}
// 轮询开关来源(关闭来源与恢复来源共用)。
const (
PollingToggleSourceSingle = "single" // 单资产接口
PollingToggleSourceBatch = "batch" // 勾选批量
PollingToggleSourceImport = "import" // 文件导入
PollingToggleSourceRiskAuto = "risk_auto" // 风险停机或已销户自动关闭
PollingToggleSourceCascade = "cascade" // 设备级联
PollingToggleSourceOrder = "order" // 支付或下单生效
PollingToggleSourceExpiryChange = "expiry_change" // 改有效期
PollingToggleSourceAlert = "alert" // 停机被拒告警处理
PollingToggleSourceHistory = "history" // 历史数据(上线时补写)
)
// PollingToggleSources 返回全部轮询开关来源(筛选校验用)。返回新切片。
func PollingToggleSources() []string {
return []string{PollingToggleSourceSingle, PollingToggleSourceBatch, PollingToggleSourceImport, PollingToggleSourceRiskAuto,
PollingToggleSourceCascade, PollingToggleSourceOrder, PollingToggleSourceExpiryChange, PollingToggleSourceAlert, PollingToggleSourceHistory}
}
// PollingToggleSourceName 返回轮询开关来源名称。
func PollingToggleSourceName(source string) string {
switch source {
case PollingToggleSourceSingle:
return "单资产接口"
case PollingToggleSourceBatch:
return "勾选批量"
case PollingToggleSourceImport:
return "文件导入"
case PollingToggleSourceRiskAuto:
return "风险停机或已销户自动关闭"
case PollingToggleSourceCascade:
return "设备级联"
case PollingToggleSourceOrder:
return "支付或下单生效"
case PollingToggleSourceExpiryChange:
return "改有效期"
case PollingToggleSourceAlert:
return "停机被拒告警处理"
case PollingToggleSourceHistory:
return "历史数据"
default:
return ""
}
}
// PollingBatchMaxSize 是勾选批量开关轮询的单次上限。
const PollingBatchMaxSize = 500
// 批量与导入逐项结果状态。
const (
PollingToggleItemSuccess = 1 // 成功
PollingToggleItemSkipped = 2 // 跳过(已是目标状态、风险停机或已销户不能开启)
PollingToggleItemFailed = 3 // 失败
PollingToggleItemForbidden = 4 // 无权限或资产不存在
)
// PollingToggleItemStatusName 返回逐项结果状态名称。
func PollingToggleItemStatusName(status int) string {
switch status {
case PollingToggleItemSuccess:
return "成功"
case PollingToggleItemSkipped:
return "跳过"
case PollingToggleItemFailed:
return "失败"
case PollingToggleItemForbidden:
return "无权限或资产不存在"
default:
return ""
}
}
// PollingToggleImpactDisable 关闭轮询的影响说明(供前端确认框展示)。
var PollingToggleImpactDisable = []string{
"关闭后不再查询上游卡状态、流量与实名",
"流量用完、实名不满足时不自动停机;套餐激活、流量重置后不自动复机",
"套餐到期、退款后无可用套餐时仍会自动停机",
"订单套餐生效、后台修改有效期后资产有生效套餐时,会自动恢复轮询并按规则复机",
"人工停机与人工复机照常可用",
"设备关闭时,设备下所有卡同样适用",
}
// PollingToggleImpactEnable 开启轮询的影响说明。
var PollingToggleImpactEnable = []string{
"开启后按轮询配置查询上游卡状态、流量与实名",
"满足停机或复机条件时按规则自动停复机,人工停机不享有豁免",
"网关扩展状态为风险停机或已销户的卡不能开启轮询",
"设备开启时,设备下非风险停机、非已销户的卡同样开启",
}
// 轮询开关文件导入。
const (
// StoragePurposePollingStatusImport 轮询开关导入 CSV 上传用途。
StoragePurposePollingStatusImport = "polling_status_import"
// PollingStatusImportStoragePrefix 导入文件对象存储 Key 前缀。
PollingStatusImportStoragePrefix = "polling-status-imports"
// TaskTypePollingStatusImport 轮询开关文件导入 Worker 任务类型。
TaskTypePollingStatusImport = "polling:status_import"
// PollingStatusImportMaxRows 单个导入文件最大数据行数。
PollingStatusImportMaxRows = 50000
)
// 导出场景。
const (
ExportTaskScenePollingPause = "polling_pause"
ExportTaskScenePollingStopRefusedAlert = "polling_stop_refused_alert"
)
// PollingAlertViewStopRefused 是失败告警列表的"停机被拒"视图。
const PollingAlertViewStopRefused = "stop_refused"

View File

@@ -2,6 +2,7 @@ package queue
import (
"context"
iotCardSvc "github.com/break/junhong_cmp_fiber/internal/service/iot_card"
agentrechargeApp "github.com/break/junhong_cmp_fiber/internal/application/agentrecharge"
assetautorenewalApp "github.com/break/junhong_cmp_fiber/internal/application/assetautorenewal"
@@ -86,6 +87,10 @@ type WorkerServices struct {
OrderExpirer OrderExpirer // 订单超时取消服务(接口类型,避免循环依赖)
AssetPackageOrderCreator task.AssetPackageBatchOrderCreator // 批量订购复用后台单笔订单规则
DeviceBatchAllocator task.DeviceBatchAllocationExecutor // 设备CSV批量分配复用现有业务规则
// AssetPolling 是资产轮询开关服务:轮询开关文件导入与订单套餐生效后自动恢复轮询共用。
AssetPolling *pollingSvc.AssetPollingService
// IotCardPolling 是 AssetPolling 使用的卡服务,Worker 启动时注入轮询生命周期回调。
IotCardPolling *iotCardSvc.Service
}
// WorkerBootstrapResult Worker Bootstrap 结果

View File

@@ -36,7 +36,7 @@ func (s *Service) GenerateFileKey(purpose, fileName string) (string, error) {
if ext == "" {
ext = ".bin"
}
if (purpose == constants.StoragePurposeAssetPackageBatchOrder || purpose == constants.StoragePurposeDeviceBatchAllocation || purpose == constants.StoragePurposeShopBusinessOwnerImport || purpose == constants.StoragePurposePhoneAssetUnbindImport) && !strings.EqualFold(ext, ".csv") {
if (purpose == constants.StoragePurposeAssetPackageBatchOrder || purpose == constants.StoragePurposeDeviceBatchAllocation || purpose == constants.StoragePurposeShopBusinessOwnerImport || purpose == constants.StoragePurposePhoneAssetUnbindImport || purpose == constants.StoragePurposePollingStatusImport) && !strings.EqualFold(ext, ".csv") {
return "", errors.New(errors.CodeInvalidParam, "批量业务文件必须为CSV格式")
}

View File

@@ -25,4 +25,7 @@ var PurposeMappings = map[string]PurposeMapping{
constants.StoragePurposePhoneAssetUnbindImport: {
Prefix: constants.PhoneAssetUnbindImportStoragePrefix, ContentType: "text/csv",
},
constants.StoragePurposePollingStatusImport: {
Prefix: constants.PollingStatusImportStoragePrefix, ContentType: "text/csv",
},
}