新增 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。
137 lines
12 KiB
Go
137 lines
12 KiB
Go
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:未通知(无有效业务员))"`
|
||
}
|