Files
junhong_cmp_fiber/internal/service/asset_audit/operation_content.go
Break 016e7bee79
Some checks failed
构建并部署到测试环境(无 SSH) / build-and-deploy (push) Has been cancelled
修改过期时间,以及修改套餐已用量
2026-06-12 10:41:24 +08:00

407 lines
11 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 asset_audit
import (
"strings"
)
var snapshotFieldSet = map[string]struct{}{
"id": {},
"virtual_no": {},
"device_virtual_no": {},
"iccid": {},
"imei": {},
"sn": {},
"msisdn": {},
"shop_id": {},
"status": {},
"series_id": {},
"enable_polling": {},
"realname_policy": {},
"real_name_status": {},
"network_status": {},
"stop_reason": {},
"deleted": {},
"asset_status": {},
}
// normalizeOperationContent 将原始 before/after 数据归一化为“操作内容”。
// 兼容历史数据:
// 1. 优先读取 operation_content
// 2. 若 before 仅有资产快照而 after 是操作内容,则按字段名回填 before
// 3. 若 after 为空则按 before 补齐,保证前端有稳定结构。
func normalizeOperationContent(operationType string, beforeData, afterData map[string]any) (map[string]any, map[string]any) {
beforeRaw := extractOperationContent(beforeData)
afterRaw := extractOperationContent(afterData)
before := trimNonOperationKeys(beforeRaw)
after := trimNonOperationKeys(afterRaw)
snapshot := extractAssetSnapshot(beforeData, afterData)
// 历史数据可能把扁平资产快照直接写到 before_data。
// 这种情况下 before 不是“操作内容”,需要按 after 字段从快照回填。
if isLikelySnapshotInsteadOfOperation(before, after, operationType) {
if len(snapshot) == 0 {
snapshot = cloneMap(beforeRaw)
}
before = deriveBeforeFromSnapshot(after, snapshot)
}
if len(before) == 0 && len(after) > 0 {
before = deriveBeforeFromSnapshot(after, snapshot)
}
if len(after) == 0 && len(before) > 0 {
after = cloneMap(before)
}
return before, after
}
// WrapOperationContent 将操作内容包装为统一结构写入 before_data/after_data。
// 写入格式:
//
// {
// "operation_content": {...},
// "asset_snapshot": {...} // 可选
// }
func WrapOperationContent(beforeOperation, afterOperation, snapshot map[string]any) (map[string]any, map[string]any) {
before := cloneMap(beforeOperation)
after := cloneMap(afterOperation)
if len(before) == 0 && len(after) > 0 {
before = deriveBeforeFromSnapshot(after, snapshot)
}
if len(after) == 0 && len(before) > 0 {
after = cloneMap(before)
}
beforeWrapped := map[string]any{
"operation_content": before,
}
afterWrapped := map[string]any{
"operation_content": after,
}
if len(snapshot) > 0 {
beforeWrapped["asset_snapshot"] = cloneMap(snapshot)
afterWrapped["asset_snapshot"] = cloneMap(snapshot)
}
return beforeWrapped, afterWrapped
}
func extractOperationContent(data map[string]any) map[string]any {
if len(data) == 0 {
return nil
}
if v, ok := data["operation_content"]; ok {
if m, ok := toMap(v); ok {
return cloneMap(m)
}
}
return cloneMap(data)
}
func extractAssetSnapshot(beforeData, afterData map[string]any) map[string]any {
candidates := []map[string]any{beforeData, afterData}
for _, data := range candidates {
if len(data) == 0 {
continue
}
if v, ok := data["asset_snapshot"]; ok {
if m, ok := toMap(v); ok {
return cloneMap(m)
}
}
if v, ok := data["device"]; ok {
if m, ok := toMap(v); ok {
return cloneMap(m)
}
}
if v, ok := data["card"]; ok {
if m, ok := toMap(v); ok {
return cloneMap(m)
}
}
if isLikelyAssetSnapshot(data) {
return cloneMap(data)
}
}
return nil
}
func trimNonOperationKeys(in map[string]any) map[string]any {
if len(in) == 0 {
return nil
}
out := cloneMap(in)
delete(out, "operation_content")
delete(out, "asset_snapshot")
delete(out, "operation_context")
delete(out, "device")
delete(out, "card")
delete(out, "cards")
if len(out) == 0 {
return nil
}
return out
}
func deriveBeforeFromSnapshot(after map[string]any, snapshot map[string]any) map[string]any {
if len(after) == 0 {
return nil
}
out := make(map[string]any, len(after))
for key := range after {
if val, ok := snapshot[key]; ok {
out[key] = val
continue
}
if alias := snapshotAlias(key); alias != "" {
if val, ok := snapshot[alias]; ok {
out[key] = val
continue
}
}
out[key] = nil
}
return out
}
func snapshotAlias(operationField string) string {
switch operationField {
case "target_shop_id", "to_shop_id", "source_shop_id", "from_shop_id":
return "shop_id"
case "new_status":
return "status"
case "new_series_id":
return "series_id"
case "new_realname_policy":
return "realname_policy"
case "new_enable_polling":
return "enable_polling"
case "new_switch_mode":
return "switch_mode"
default:
return ""
}
}
func cloneMap(in map[string]any) map[string]any {
if len(in) == 0 {
return nil
}
out := make(map[string]any, len(in))
for k, v := range in {
out[k] = v
}
return out
}
func toMap(v any) (map[string]any, bool) {
switch val := v.(type) {
case map[string]any:
return val, true
default:
return nil, false
}
}
func isLikelyAssetSnapshot(data map[string]any) bool {
if len(data) == 0 {
return false
}
snapshotCount := 0
for key := range data {
if _, ok := snapshotFieldSet[key]; !ok {
return false
}
snapshotCount++
}
return snapshotCount >= 2
}
func isLikelySnapshotInsteadOfOperation(before, after map[string]any, operationType string) bool {
if len(before) == 0 || len(after) == 0 {
return false
}
if !isLikelyAssetSnapshot(before) {
return false
}
fieldDesc := getOperationFieldDesc(operationType)
for key := range after {
if _, ok := fieldDesc[key]; ok {
return true
}
if _, ok := snapshotFieldSet[key]; !ok {
return true
}
}
return false
}
func getOperationFieldDesc(operationType string) map[string]string {
common := map[string]string{
"batch_total": "批量总数",
"success_count": "成功数量",
"fail_count": "失败数量",
"failed_items": "失败明细",
"reason": "原因说明",
}
switch strings.TrimSpace(operationType) {
case "card_allocate", "device_allocate":
return mergeFieldDesc(common, map[string]string{
"target_shop_id": "目标店铺ID",
"target_shop_name": "目标店铺名称",
"to_shop_id": "目标店铺ID",
"to_shop_name": "目标店铺名称",
"device_ids": "设备ID列表",
"device_virtual_nos": "设备虚拟号列表",
"card_ids": "卡ID列表",
"iccids": "卡ICCID列表",
"selection_type": "选择方式",
"new_status": "变更后状态",
})
case "card_recall", "device_recall":
return mergeFieldDesc(common, map[string]string{
"source_shop_id": "来源店铺ID",
"source_shop_name": "来源店铺名称",
"target_shop": "回收后归属店铺ID空表示平台库存",
"target_shop_name": "回收后归属店铺名称",
"device_ids": "设备ID列表",
"device_virtual_nos": "设备虚拟号列表",
"card_ids": "卡ID列表",
"iccids": "卡ICCID列表",
"selection_type": "选择方式",
"new_status": "变更后状态",
})
case "device_bind_card":
return map[string]string{
"binding_id": "绑定记录ID",
"slot_position": "插槽位置",
"device_id": "设备ID",
"device_virtual_no": "设备虚拟号",
"device_imei": "设备IMEI",
"iot_card_id": "卡ID",
"iccid": "卡ICCID",
}
case "device_unbind_card":
return map[string]string{
"binding_id": "绑定记录ID",
"device_id": "设备ID",
"device_virtual_no": "设备虚拟号",
"device_imei": "设备IMEI",
"iot_card_id": "卡ID",
"iccid": "卡ICCID",
"unbind": "是否执行解绑",
}
case "device_stop", "device_start":
return mergeFieldDesc(common, map[string]string{
"skip_count": "跳过数量",
"failed_items": "失败明细含ICCID与原因",
})
case "device_speed_limit":
return map[string]string{
"speed_limit": "限速值KB/s",
}
case "device_set_wifi":
return map[string]string{
"imei": "设备 IMEI",
"ssid": "WiFi 名称",
"password": "WiFi 密码(脱敏)",
"enabled": "WiFi 开关状态",
}
case "device_switch_card":
return map[string]string{
"device_virtual_no": "设备虚拟号",
"device_imei": "设备IMEI",
"target_iccid": "目标ICCID",
}
case "device_switch_mode":
return map[string]string{
"switch_mode": "切卡模式0自动/1手动",
}
case "device_reboot":
return map[string]string{
"action": "设备重启动作",
}
case "device_reset":
return map[string]string{
"action": "设备恢复出厂动作",
}
case "asset_polling_status", "card_polling_status":
return mergeFieldDesc(common, map[string]string{
"enable_polling": "轮询开关true启用/false禁用",
"card_ids": "卡ID列表",
"iccids": "卡ICCID列表",
})
case "asset_realname_policy", "card_realname_policy":
return map[string]string{
"realname_policy": "实名策略none/before_order/after_order",
}
case "asset_package_expires_at":
return map[string]string{
"package_usage_id": "套餐使用记录ID",
"expires_at": "套餐过期时间",
"status": "套餐状态",
"status_name": "套餐状态名称",
}
case "asset_package_usage":
return map[string]string{
"package_usage_id": "套餐使用记录ID",
"data_usage_mb": "套餐真实已用量(MB)",
"virtual_used_mb": "套餐展示已用量(MB)",
"real_total_mb": "套餐真实总量(MB)",
"virtual_total_mb": "套餐业务停机阈值(MB)",
"status": "套餐状态",
"status_name": "套餐状态名称",
"enable_virtual_data": "是否启用虚流量",
}
case "card_realname_status":
return map[string]string{
"real_name_status": "实名状态0未实名/1已实名",
}
case "asset_deactivate", "card_delete", "card_batch_delete", "device_delete":
return mergeFieldDesc(common, map[string]string{
"asset_status": "资产状态",
"deleted": "是否删除",
"iccids": "卡ICCID列表",
"device_virtual_nos": "设备虚拟号列表",
"device_imei": "设备IMEI",
})
case "device_import_task_create", "iot_card_import_task_create":
return map[string]string{
"batch_no": "批次号",
"file_key": "文件存储键",
"realname_policy": "实名策略",
"carrier_id": "运营商ID仅卡导入",
"card_category": "卡类型(仅卡导入)",
}
default:
return mergeFieldDesc(common, map[string]string{
"card_id": "卡ID",
"iccid": "卡ICCID",
"iccids": "卡ICCID列表",
"device_id": "设备ID",
"device_virtual_no": "设备虚拟号",
"device_virtual_nos": "设备虚拟号列表",
"device_imei": "设备IMEI",
"device_sn": "设备SN",
"shop_name": "店铺名称",
"target_shop_name": "目标店铺名称",
"source_shop_name": "来源店铺名称",
})
}
}
func mergeFieldDesc(base, extra map[string]string) map[string]string {
out := make(map[string]string, len(base)+len(extra))
for k, v := range base {
out[k] = v
}
for k, v := range extra {
out[k] = v
}
return out
}