Files
junhong_cmp_fiber/internal/model/dto/package_traffic_alert_dto.go
break d5bcda94fe feat(套餐真流量预警): AUG26-004 真流量预警规则、达量扫描通知与导出
新增 000228 迁移:规则表 tb_package_traffic_alert_rule(每套餐商品至多一条,无软删除,package_id
非部分唯一约束)、达量预警快照表 tb_package_traffic_alert(以主套餐使用记录 + 阈值快照为唯一键,
触发时冻结用量、额度、比例、阈值、到期时间、归属与资产快照),并为 tb_package_usage 新增扫描
范围部分索引 idx_package_usage_alert_scope;down 在预警表存在数据时阻断回滚。

新增规则维护接口 GET/POST/PUT /api/admin/package-traffic-alert-rules(仅超级管理员与平台账号):
创建校验套餐存在且真流量额度大于零,阈值为 1%~100% 的两位小数;修改只影响后续扫描,不回填也
不改写既有预警快照;全部写操作记录操作者、前后值与时间。

新增每日 06:00(Asia/Shanghai)扫描任务 package:traffic:alert:scan,与套餐临期扫描共用 data_cleanup
队列:按资产汇总当前有效套餐的真流量,分子取使用记录真已用量、分母取使用记录真总量快照,命中
主套餐规则阈值时在同一事务创建预警与可靠通知事件;重复执行以唯一冲突视为已处理,不重复投递,
不建停机锁、不调用运营商。

新增预警列表、详情与异步导出 GET /api/admin/package-traffic-alerts、GET /api/admin/package-traffic-alerts/:id、
POST /api/admin/package-traffic-alerts/export,列表与详情一律读冻结快照;新增通知类型
package.traffic.alert 与受控目标 package_traffic_alert_detail,目标解析仅对超级管理员与平台账号
返回可跳转,越权与不存在统一按资源不可见处理。

同步 OpenAPI(cmd/gendocs、cmd/api/docs.go、pkg/openapi/handlers.go)、审计动作与资源注册、上下文
健康检查证据;归档变更并同步 package-traffic-alert 主 Spec。
2026-09-16 17:05:55 +08:00

137 lines
12 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 dto
import "time"
// CreatePackageTrafficAlertRuleRequest 创建套餐真流量预警规则请求。
// 每个套餐商品至多一条当前规则;创建即校验商品真流量额度大于零。
type CreatePackageTrafficAlertRuleRequest struct {
PackageID uint `json:"package_id" validate:"required,min=1" required:"true" minimum:"1" description:"套餐商品ID必须存在且真流量额度大于零"`
ThresholdPercent float64 `json:"threshold_percent" validate:"required,gt=0" required:"true" description:"真流量预警阈值百分比,取值 1 至 100允许两位小数"`
Enabled *bool `json:"enabled" description:"是否启用(默认 true停用后扫描不再创建新预警"`
Remark string `json:"remark" validate:"omitempty,max=500" maxLength:"500" description:"备注,最多 500 字符"`
}
// UpdatePackageTrafficAlertRuleRequest 修改套餐真流量预警规则请求。
// 只允许修改阈值、启停与备注;修改不回填既有预警,也不改写已冻结的预警快照。
type UpdatePackageTrafficAlertRuleRequest struct {
ThresholdPercent *float64 `json:"threshold_percent" validate:"omitempty,gt=0" description:"新的真流量预警阈值百分比,取值 1 至 100允许两位小数"`
Enabled *bool `json:"enabled" description:"是否启用;停用后扫描不再创建新预警,既有预警保留"`
Remark *string `json:"remark" validate:"omitempty,max=500" maxLength:"500" description:"备注,最多 500 字符"`
}
// UpdatePackageTrafficAlertRuleParams 修改预警规则的路径参数与请求体(用于文档生成)。
type UpdatePackageTrafficAlertRuleParams struct {
ID uint `path:"id" description:"预警规则ID" required:"true"`
UpdatePackageTrafficAlertRuleRequest
}
// ListPackageTrafficAlertRuleRequest 预警规则分页查询参数。
type ListPackageTrafficAlertRuleRequest struct {
PackageID *uint `json:"package_id" query:"package_id" validate:"omitempty,gt=0" description:"按套餐商品ID过滤"`
Enabled *bool `json:"enabled" query:"enabled" description:"按启用状态过滤;不传时查询全部"`
Page int `json:"page" query:"page" validate:"omitempty,min=1" minimum:"1" description:"页码,默认 1"`
PageSize int `json:"page_size" query:"page_size" validate:"omitempty,min=1,max=100" minimum:"1" maximum:"100" description:"每页数量,默认 20最大 100"`
}
// PackageTrafficAlertRuleItem 预警规则列表项。
type PackageTrafficAlertRuleItem struct {
ID uint `json:"id" description:"预警规则ID"`
PackageID uint `json:"package_id" description:"套餐商品ID"`
PackageName string `json:"package_name" description:"套餐名称"`
RealDataMB int64 `json:"real_data_mb" description:"套餐商品当前真流量额度(MB),仅用于配置校验展示,不作为预警分母"`
ThresholdPercent float64 `json:"threshold_percent" description:"真流量预警阈值百分比"`
Enabled bool `json:"enabled" description:"是否启用"`
EnabledName string `json:"enabled_name" description:"启用状态名称(中文)"`
Remark string `json:"remark" description:"备注"`
UpdatedAt time.Time `json:"updated_at" description:"最近更新时间"`
}
// PackageTrafficAlertRuleListResponse 预警规则分页响应。
type PackageTrafficAlertRuleListResponse struct {
Items []PackageTrafficAlertRuleItem `json:"items" description:"预警规则列表"`
Total int64 `json:"total" description:"符合条件的规则总数"`
Page int `json:"page" description:"当前页码"`
Size int `json:"size" description:"每页数量"`
}
// ListPackageTrafficAlertRequest 套餐真流量达量预警分页查询参数。
// 时间范围为带时区的 RFC3339 秒级闭区间,按触发时间筛选,任一端可省略。
type ListPackageTrafficAlertRequest struct {
PackageID *uint `json:"package_id" query:"package_id" validate:"omitempty,gt=0" description:"按阈值来源套餐商品ID过滤"`
ShopID *uint `json:"shop_id" query:"shop_id" validate:"omitempty,gt=0" description:"按触发时所属店铺ID过滤"`
BusinessOwnerAccountID *uint `json:"business_owner_account_id" query:"business_owner_account_id" validate:"omitempty,gt=0" description:"按触发时店铺业务员账号ID过滤"`
AssetType string `json:"asset_type" query:"asset_type" validate:"omitempty,oneof=iot_card device" enum:"iot_card,device" description:"资产类型 (iot_card:物联网卡, device:设备)"`
AssetIdentifier string `json:"asset_identifier" query:"asset_identifier" validate:"omitempty,max=100" maxLength:"100" description:"资产或卡标识关键词,匹配资产标识、卡标识与对应标识符快照"`
ThresholdPercent *float64 `json:"threshold_percent" query:"threshold_percent" description:"按触发阈值快照精确过滤,允许两位小数"`
StartTime *time.Time `json:"start_time" query:"start_time" description:"触发时间起始RFC3339含该时刻"`
EndTime *time.Time `json:"end_time" query:"end_time" description:"触发时间截止RFC3339含该时刻"`
NotificationStatus *int `json:"notification_status" query:"notification_status" enum:"1,2,3,4,5" description:"通知投递结果过滤 (1:已通知, 2:待投递, 3:投递失败, 4:未通知(接收人已失效), 5:未通知(无有效业务员))"`
Page int `json:"page" query:"page" validate:"omitempty,min=1" minimum:"1" description:"页码,默认 1"`
PageSize int `json:"page_size" query:"page_size" validate:"omitempty,min=1,max=100" minimum:"1" maximum:"100" description:"每页数量,默认 20最大 100"`
}
// PackageTrafficAlertItem 套餐真流量达量预警列表项。
// 资产、套餐、用量、阈值、到期时间与触发时归属均为触发快照;用户组按快照业务员账号实时推导。
type PackageTrafficAlertItem struct {
ID uint `json:"id" description:"预警ID"`
PackageUsageID uint `json:"package_usage_id" description:"主套餐使用记录ID"`
PackageID uint `json:"package_id" description:"阈值来源套餐商品ID"`
PackageName string `json:"package_name" description:"套餐名称快照"`
AssetType string `json:"asset_type" description:"资产类型 (iot_card:物联网卡, device:设备)"`
AssetID uint `json:"asset_id" description:"资产ID"`
AssetIdentifier string `json:"asset_identifier" description:"资产标识快照(卡为 ICCID设备为虚拟号/IMEI/SN"`
CardIdentifier string `json:"card_identifier" description:"卡标识快照(卡资产为自身 ICCID设备资产为触发时绑定卡 ICCID"`
CounterpartIdentifier string `json:"counterpart_identifier" description:"对应标识符快照(卡→触发时绑定设备标识,设备→触发时绑定卡 ICCID"`
DeviceType string `json:"device_type" description:"设备类型快照"`
DeviceModel string `json:"device_model" description:"设备型号快照"`
UsedMB int64 `json:"used_mb" description:"触发时真已用量汇总快照(MB)"`
LimitMB int64 `json:"limit_mb" description:"触发时真总量快照汇总(MB)"`
UsagePercent float64 `json:"usage_percent" description:"触发时汇总比例快照(%),可能大于 100"`
ThresholdPercent float64 `json:"threshold_percent" description:"触发阈值快照(%)"`
ExpiresAt *time.Time `json:"expires_at" description:"主套餐到期时间快照,无法推算时为 null"`
DaysRemaining *int `json:"days_remaining" description:"按到期时间快照推算的剩余上海自然日天数,负数表示已过期;无到期时间时为 null"`
TriggeredAt time.Time `json:"triggered_at" description:"触发时间"`
ShopID *uint `json:"shop_id" description:"触发时所属店铺ID快照平台库存为 null"`
ShopName string `json:"shop_name" description:"触发时所属店铺名称快照"`
BusinessOwnerAccountID *uint `json:"business_owner_account_id" description:"触发时店铺业务员账号ID快照无有效业务员时为 null"`
BusinessOwnerName string `json:"business_owner_name" description:"触发时业务员名称快照"`
BusinessUserGroupNames []string `json:"business_user_group_names" description:"按快照业务员账号实时推导的业务用户组名称,可能为空"`
NotificationStatus int `json:"notification_status" description:"通知投递结果 (1:已通知, 2:待投递, 3:投递失败, 4:未通知(接收人已失效), 5:未通知(无有效业务员))"`
NotificationStatusName string `json:"notification_status_name" description:"通知投递结果名称(中文)"`
NotificationDeliveredAt *time.Time `json:"notification_delivered_at" description:"通知事件投递完成时间,未完成时为 null"`
NotificationReadAt *time.Time `json:"notification_read_at,omitempty" description:"接收人首次已读时间,仅详情返回"`
NotificationExpiresAt *time.Time `json:"notification_expires_at,omitempty" description:"通知展示期结束时间,仅详情返回"`
ShopChangedSinceTrigger bool `json:"shop_changed_since_trigger,omitempty" description:"资产当前归属店铺是否已不同于触发快照,仅详情返回"`
OwnerChangedSinceTrigger bool `json:"owner_changed_since_trigger,omitempty" description:"店铺当前业务员是否已不同于触发快照,仅详情返回"`
}
// PackageTrafficAlertDetailResponse 套餐真流量达量预警详情响应。
type PackageTrafficAlertDetailResponse struct {
PackageTrafficAlertItem
NotificationEventID string `json:"notification_event_id,omitempty" description:"可靠通知事件ID无有效业务员时为空"`
NotificationSummary string `json:"notification_summary,omitempty" description:"通知投递补充说明,例如接收人已失效或未生成通知"`
}
// PackageTrafficAlertListResponse 套餐真流量达量预警分页响应。
type PackageTrafficAlertListResponse struct {
Items []PackageTrafficAlertItem `json:"items" description:"预警列表"`
Total int64 `json:"total" description:"符合条件的预警总数"`
Page int `json:"page" description:"当前页码"`
Size int `json:"size" description:"每页数量"`
}
// ExportPackageTrafficAlertRequest 套餐真流量达量预警导出请求。
// 筛选与列表一致,创建时冻结操作者、筛选、时间范围与可见资产范围。
type ExportPackageTrafficAlertRequest struct {
Format string `json:"format" validate:"required,oneof=xlsx csv" required:"true" enum:"xlsx,csv" description:"导出格式 (xlsx:Excel, csv:CSV)"`
PackageID *uint `json:"package_id" validate:"omitempty,gt=0" description:"按阈值来源套餐商品ID过滤"`
ShopID *uint `json:"shop_id" validate:"omitempty,gt=0" description:"按触发时所属店铺ID过滤"`
BusinessOwnerAccountID *uint `json:"business_owner_account_id" validate:"omitempty,gt=0" description:"按触发时店铺业务员账号ID过滤"`
AssetType string `json:"asset_type" validate:"omitempty,oneof=iot_card device" enum:"iot_card,device" description:"资产类型 (iot_card:物联网卡, device:设备)"`
AssetIdentifier string `json:"asset_identifier" validate:"omitempty,max=100" maxLength:"100" description:"资产或卡标识关键词,匹配资产标识、卡标识与对应标识符快照"`
ThresholdPercent *float64 `json:"threshold_percent" description:"按触发阈值快照精确过滤,允许两位小数"`
StartTime *time.Time `json:"start_time" description:"触发时间起始RFC3339含该时刻"`
EndTime *time.Time `json:"end_time" description:"触发时间截止RFC3339含该时刻"`
NotificationStatus *int `json:"notification_status" enum:"1,2,3,4,5" description:"通知投递结果过滤 (1:已通知, 2:待投递, 3:投递失败, 4:未通知(接收人已失效), 5:未通知(无有效业务员))"`
}