diff --git a/src/modules/network_data/controller/ims.go b/src/modules/network_data/controller/ims.go index 4116c137..feef3813 100644 --- a/src/modules/network_data/controller/ims.go +++ b/src/modules/network_data/controller/ims.go @@ -136,7 +136,7 @@ func (s *IMSController) CDRExport(c *gin.Context) { var cdrJSON map[string]interface{} err := json.Unmarshal([]byte(row.CDRJSONStr), &cdrJSON) if err != nil { - logger.Warnf("UEExport Error parsing JSON: %s", err.Error()) + logger.Warnf("CDRExport Error parsing JSON: %s", err.Error()) continue } // 记录类型 diff --git a/src/modules/network_data/controller/smf.go b/src/modules/network_data/controller/smf.go index a40e6680..2e033386 100644 --- a/src/modules/network_data/controller/smf.go +++ b/src/modules/network_data/controller/smf.go @@ -1,11 +1,14 @@ package controller import ( + "encoding/json" "fmt" + "strconv" "strings" "time" "be.ems/src/framework/i18n" + "be.ems/src/framework/logger" "be.ems/src/framework/utils/ctx" "be.ems/src/framework/utils/file" "be.ems/src/framework/utils/parse" @@ -14,6 +17,7 @@ import ( neDataService "be.ems/src/modules/network_data/service" neService "be.ems/src/modules/network_element/service" "github.com/gin-gonic/gin" + "github.com/gin-gonic/gin/binding" ) // 实例化控制层 SMFController 结构体 @@ -88,11 +92,17 @@ func (s *SMFController) CDRRemove(c *gin.Context) { func (s *SMFController) CDRExport(c *gin.Context) { language := ctx.AcceptLanguage(c) // 查询结果,根据查询条件结果,单页最大值限制 - // querys := ctx.BodyJSONMap(c) var querys model.CDREventSMFQuery - + if err := c.ShouldBindBodyWith(&querys, binding.JSON); err != nil { + c.JSON(400, result.CodeMsg(400, i18n.TKey(language, "app.common.err400"))) + return + } + // 限制导出数据集 + if querys.PageSize > 10000 { + querys.PageSize = 10000 + } data := s.cdrEventService.SelectPage(querys) - if data["total"].(int64) == 0 { + if parse.Number(data["total"]) == 0 { // 导出数据记录为空 c.JSON(200, result.ErrMsg(i18n.TKey(language, "app.common.exportEmpty"))) return @@ -100,63 +110,142 @@ func (s *SMFController) CDRExport(c *gin.Context) { rows := data["rows"].([]model.CDREventSMF) // 导出文件名称 - fileName := fmt.Sprintf("user_export_%d_%d.xlsx", len(rows), time.Now().UnixMilli()) + fileName := fmt.Sprintf("smf_cdr_event_export_%d_%d.xlsx", len(rows), time.Now().UnixMilli()) // 第一行表头标题 headerCells := map[string]string{ - "A1": i18n.TKey(language, "user.export.id"), - "B1": i18n.TKey(language, "user.export.name"), - "C1": i18n.TKey(language, "user.export.nick"), - "D1": i18n.TKey(language, "user.export.role"), - "E1": i18n.TKey(language, "user.export.deptName"), - "F1": i18n.TKey(language, "user.export.loginIP"), - "G1": i18n.TKey(language, "user.export.loginDate"), - "H1": i18n.TKey(language, "user.export.status"), - // "F1": i18n.TKey(language, "user.export.sex"), - // "E1": i18n.TKey(language, "user.export.phone"), - // "D1": i18n.TKey(language, "user.export.email"), - // "I1": i18n.TKey(language, "user.export.deptID"), - // "K1": i18n.TKey(language, "user.export.deptLeader"), + "A1": "ID", + "B1": "Charging ID", + "C1": "Subscriber ID Data", + "D1": "Subscriber ID Type", + "E1": "Data Volume Uplink", + "F1": "Data Volume Downlink", + "G1": "Data Total Volume", + "H1": "Duration", + "I1": "Invocation Time", + "J1": "PDU Session Charging Information", } - // 读取用户性别字典数据 - // dictSysUserSex := s.sysDictDataService.SelectDictDataByType("sys_user_sex") // 从第二行开始的数据 dataCells := make([]map[string]any, 0) - // for i, row := range rows { - // idx := strconv.Itoa(i + 2) - // // 用户性别 - // // sysUserSex := row.Sex - // // for _, v := range dictSysUserSex { - // // if row.Sex == v.DictValue { - // // sysUserSex = i18n.TKey(language, v.DictLabel) - // // break - // // } - // // } - // // 帐号状态 - // statusValue := i18n.TKey(language, "dictData.disable") - // if row.Status == "1" { - // statusValue = i18n.TKey(language, "dictData.normal") - // } - // // 用户角色, 默认导出首个 - // userRole := "" - // if len(row.Roles) > 0 { - // userRole = i18n.TKey(language, row.Roles[0].RoleName) - // } - // dataCells = append(dataCells, map[string]any{ - // "A" + idx: row.UserID, - // "B" + idx: row.UserName, - // "C" + idx: row.NickName, - // "D" + idx: userRole, - // "E" + idx: row.Dept.DeptName, - // "F" + idx: row.LoginIP, - // "G" + idx: date.ParseDateToStr(row.LoginDate, date.YYYY_MM_DD_HH_MM_SS), - // "H" + idx: statusValue, - // // "E" + idx: row.PhoneNumber, - // // "F" + idx: sysUserSex, - // // "D" + idx: row.Email, - // // "I" + idx: row.Dept.DeptID, - // // "K" + idx: row.Dept.Leader, - // }) - // } + for i, row := range rows { + idx := strconv.Itoa(i + 2) + // 解析 JSON 字符串为 map + var cdrJSON map[string]interface{} + err := json.Unmarshal([]byte(row.CDRJSONStr), &cdrJSON) + if err != nil { + logger.Warnf("CDRExport Error parsing JSON: %s", err.Error()) + continue + } + // 计费ID + chargingID := "" + if v, ok := cdrJSON["chargingID"]; ok && v != nil { + chargingID = fmt.Sprint(parse.Number(v)) + } + // 订阅 ID 类型 + subscriptionIDType := "-" + // 订阅 ID 数据 + subscriptionIDData := "-" + if v, ok := cdrJSON["subscriberIdentifier"]; ok && v != nil { + if sub, subOk := v.(map[string]any); subOk && sub != nil { + subscriptionIDType = sub["subscriptionIDType"].(string) + subscriptionIDData = sub["subscriptionIDData"].(string) + } + } + // 数据量上行链路 + dataVolumeUplink := []string{} + // 数据量下行链路 + dataVolumeDownlink := []string{} + // 数据总量 + dataTotalVolume := []string{} + if v, ok := cdrJSON["listOfMultipleUnitUsage"]; ok && v != nil { + usageList := v.([]any) + if len(usageList) > 0 { + for _, used := range usageList { + usedUnit := used.(map[string]any) + usedUnitList := usedUnit["usedUnitContainer"].([]any) + if len(usedUnitList) > 0 { + for _, data := range usedUnitList { + udata := data.(map[string]any) + if dup, dupOk := udata["dataVolumeUplink"]; dupOk { + dataVolumeUplink = append(dataVolumeUplink, fmt.Sprint(parse.Number(dup))) + } + if ddown, ddownOk := udata["dataVolumeDownlink"]; ddownOk { + dataVolumeDownlink = append(dataVolumeDownlink, fmt.Sprint(parse.Number(ddown))) + } + if dt, dtOk := udata["dataTotalVolume"]; dtOk { + dataTotalVolume = append(dataTotalVolume, fmt.Sprint(parse.Number(dt))) + } + } + } + } + } + } + // 时长 + duration := "-" + if v, ok := cdrJSON["duration"]; ok && v != nil { + duration = fmt.Sprint(parse.Number(v)) + } + // 调用时间 + invocationTimestamp := "" + if v, ok := cdrJSON["invocationTimestamp"]; ok && v != nil { + invocationTimestamp = v.(string) + } + // 记录打开时间 + pduSessionChargingInformation := "" + if v, ok := cdrJSON["pDUSessionChargingInformation"]; ok && v != nil { + pduInfo := v.(map[string]any) + + User_Identifier := "" + if v, ok := pduInfo["userIdentifier"]; ok && v != nil { + User_Identifier = v.(string) + } + SSC_Mode := "" + if v, ok := pduInfo["sSCMode"]; ok && v != nil { + SSC_Mode = v.(string) + } + RAT_Type := "" + if v, ok := pduInfo["rATType"]; ok && v != nil { + RAT_Type = v.(string) + } + DNN_ID := "" + if v, ok := pduInfo["dNNID"]; ok && v != nil { + DNN_ID = v.(string) + } + PDU_Type := "" + if v, ok := pduInfo["pDUType"]; ok && v != nil { + PDU_Type = v.(string) + } + PDU_IPv4 := "" + PDU_IPv6 := "" + if v, ok := pduInfo["pDUAddress"]; ok && v != nil { + pDUAddress := v.(map[string]any) + if addr, ok := pDUAddress["pDUIPv4Address"]; ok && addr != nil { + PDU_IPv4 = addr.(string) + } + if addr, ok := pDUAddress["pDUIPv6AddresswithPrefix"]; ok && addr != nil { + PDU_IPv6 = addr.(string) + } + } + + pduSessionChargingInformation = fmt.Sprintf(`User Identifier: %s +SSC Mode: %s RAT Type: %s DNN ID: %s +PDU Type: %s +PDU IPv4 Address: %s +PDU IPv6 Addres Swith Prefix: %s`, User_Identifier, SSC_Mode, RAT_Type, DNN_ID, PDU_Type, PDU_IPv4, PDU_IPv6) + } + + dataCells = append(dataCells, map[string]any{ + "A" + idx: row.ID, + "B" + idx: chargingID, + "C" + idx: subscriptionIDData, + "D" + idx: subscriptionIDType, + "E" + idx: strings.Join(dataVolumeUplink, ","), + "F" + idx: strings.Join(dataVolumeDownlink, ","), + "G" + idx: strings.Join(dataTotalVolume, ","), + "H" + idx: duration, + "I" + idx: invocationTimestamp, + "J" + idx: pduSessionChargingInformation, + }) + } // 导出数据表格 saveFilePath, err := file.WriteSheet(headerCells, dataCells, fileName, "") diff --git a/src/modules/network_data/model/cdr_event_smf.go b/src/modules/network_data/model/cdr_event_smf.go index e1b6f243..259c1025 100644 --- a/src/modules/network_data/model/cdr_event_smf.go +++ b/src/modules/network_data/model/cdr_event_smf.go @@ -4,20 +4,24 @@ import "time" // CDREventSMF CDR会话对象SMF cdr_event_smf type CDREventSMF struct { - ID string `json:"id" gorm:"column:id;primaryKey;autoIncrement"` - NeType string `json:"neType" gorm:"column:ne_type"` - NeName string `json:"neName" gorm:"column:ne_name"` - RmUID string `json:"rmUID" gorm:"column:rm_uid"` - Timestamp int64 `json:"timestamp" gorm:"column:timestamp"` - RecordType string `json:"recordType" gorm:"column:record_type"` - ChargingID string `json:"chargingID" gorm:"column:charging_id"` - SubscriberID string `json:"subscriberID" gorm:"column:subscriber_id"` - Duration string `json:"duration" gorm:"column:duration"` - DataVolumeUplink string `json:"dataVolumeUplink" gorm:"column:data_volume_uplink"` - DataVolumeDownlink string `json:"dataVolumeDownlink" gorm:"column:data_volume_downlink"` - DataTotalVolume string `json:"dataTotalVolume" gorm:"column:data_total_volume"` - PDUAddress string `json:"pduAddress" gorm:"column:pdu_address"` - CreatedAt time.Time `json:"createdAt" gorm:"column:created_at;default:CURRENT_TIMESTAMP"` + ID string `json:"id" gorm:"column:id;primaryKey;autoIncrement"` + NeType string `json:"neType" gorm:"column:ne_type"` + NeName string `json:"neName" gorm:"column:ne_name"` + RmUID string `json:"rmUID" gorm:"column:rm_uid"` + Timestamp int64 `json:"timestamp" gorm:"column:timestamp"` + CDRJSONStr string `json:"cdrJSON" gorm:"column:cdr_json"` + CreatedAt time.Time `json:"createdAt" gorm:"column:created_at;default:CURRENT_TIMESTAMP"` + + // ====== 非数据库字段属性 ====== + + // RecordType string `json:"recordType" gorm:"column:record_type"` + // ChargingID string `json:"chargingID" gorm:"column:charging_id"` + // SubscriberID string `json:"subscriberID" gorm:"column:subscriber_id"` + // Duration string `json:"duration" gorm:"column:duration"` + // DataVolumeUplink string `json:"dataVolumeUplink" gorm:"column:data_volume_uplink"` + // DataVolumeDownlink string `json:"dataVolumeDownlink" gorm:"column:data_volume_downlink"` + // DataTotalVolume string `json:"dataTotalVolume" gorm:"column:data_total_volume"` + // PDUAddress string `json:"pduAddress" gorm:"column:pdu_address"` } // CDREventSMFQuery CDR会话对象SMF查询参数结构体 @@ -25,7 +29,7 @@ type CDREventSMFQuery struct { NeType string `json:"neType" form:"neType" binding:"required"` // SMF NeID string `json:"neId" form:"neId" binding:"required"` RmUID string `json:"rmUID" form:"rmUID"` - RecordType string `json:"recordType" form:"recordType"` + RecordType string `json:"recordType" form:"recordType"` // 暂时没用到 SubscriberID string `json:"subscriberID" form:"subscriberID"` StartTime string `json:"startTime" form:"startTime"` EndTime string `json:"endTime" form:"endTime"` diff --git a/src/modules/network_data/repository/cdr_event_smf.impl.go b/src/modules/network_data/repository/cdr_event_smf.impl.go index 501a7ae8..818f1f25 100644 --- a/src/modules/network_data/repository/cdr_event_smf.impl.go +++ b/src/modules/network_data/repository/cdr_event_smf.impl.go @@ -13,23 +13,31 @@ import ( // 实例化数据层 CDREventSMFImpl 结构体 var NewCDREventSMFImpl = &CDREventSMFImpl{ - selectSql: `select id, ne_type, ne_name, rm_uid, timestamp, JSON_EXTRACT(cdr_json, '$.recordType') AS record_type, JSON_EXTRACT(cdr_json, '$.chargingID') AS charging_id, JSON_EXTRACT(cdr_json, '$.subscriberIdentifier.subscriptionIDData') AS subscriber_id, JSON_EXTRACT(cdr_json, '$.duration') AS duration, JSON_EXTRACT(cdr_json, '$.listOfMultipleUnitUsage[*].usedUnitContainer[*].dataVolumeUplink') AS data_volume_uplink, JSON_EXTRACT(cdr_json, '$.listOfMultipleUnitUsage[*].usedUnitContainer[*].dataVolumeDownlink') AS data_volume_downlink, JSON_EXTRACT(cdr_json, '$.listOfMultipleUnitUsage[*].usedUnitContainer[*].dataTotalVolume') AS data_total_volume, JSON_EXTRACT(cdr_json, '$.pDUSessionChargingInformation.pDUAddress') AS pdu_address, created_at from cdr_event_smf`, + selectSql: `select id, ne_type, ne_name, rm_uid, timestamp, cdr_json, created_at from cdr_event_smf`, + // selectSql: `select id, ne_type, ne_name, rm_uid, timestamp, JSON_EXTRACT(cdr_json, '$.recordType') AS record_type, JSON_EXTRACT(cdr_json, '$.chargingID') AS charging_id, JSON_EXTRACT(cdr_json, '$.subscriberIdentifier.subscriptionIDData') AS subscriber_id, JSON_EXTRACT(cdr_json, '$.duration') AS duration, JSON_EXTRACT(cdr_json, '$.listOfMultipleUnitUsage[*].usedUnitContainer[*].dataVolumeUplink') AS data_volume_uplink, JSON_EXTRACT(cdr_json, '$.listOfMultipleUnitUsage[*].usedUnitContainer[*].dataVolumeDownlink') AS data_volume_downlink, JSON_EXTRACT(cdr_json, '$.listOfMultipleUnitUsage[*].usedUnitContainer[*].dataTotalVolume') AS data_total_volume, JSON_EXTRACT(cdr_json, '$.pDUSessionChargingInformation.pDUAddress') AS pdu_address, created_at from cdr_event_smf`, resultMap: map[string]string{ - "id": "ID", - "ne_type": "NeType", - "ne_name": "NeName", - "rm_uid": "RmUID", - "timestamp": "Timestamp", - "record_type": "RecordType", - "charging_id": "ChargingID", - "subscriber_id": "SubscriberID", - "duration": "Duration", - "data_volume_uplink": "DataVolumeUplink", - "data_volume_downlink": "DataVolumeDownlink", - "data_total_volume": "DataTotalVolume", - "pdu_address": "PDUAddress", - "created_at": "CreatedAt", + "id": "ID", + "ne_type": "NeType", + "ne_name": "NeName", + "rm_uid": "RmUID", + "timestamp": "Timestamp", + "cdr_json": "CDRJSONStr", + "created_at": "CreatedAt", + // "id": "ID", + // "ne_type": "NeType", + // "ne_name": "NeName", + // "rm_uid": "RmUID", + // "timestamp": "Timestamp", + // "record_type": "RecordType", + // "charging_id": "ChargingID", + // "subscriber_id": "SubscriberID", + // "duration": "Duration", + // "data_volume_uplink": "DataVolumeUplink", + // "data_volume_downlink": "DataVolumeDownlink", + // "data_total_volume": "DataTotalVolume", + // "pdu_address": "PDUAddress", + // "created_at": "CreatedAt", }, } @@ -91,14 +99,6 @@ func (r *CDREventSMFImpl) SelectPage(querys model.CDREventSMFQuery) map[string]a conditions = append(conditions, "JSON_EXTRACT(cdr_json, '$.subscriberIdentifier.subscriptionIDData') = ?") params = append(params, querys.SubscriberID) } - // if querys.RecordType != "" { - // recordTypes := strings.Split(querys.RecordType, ",") - // placeholder := repo.KeyPlaceholderByQuery(len(recordTypes)) - // conditions = append(conditions, fmt.Sprintf("JSON_EXTRACT(cdr_json, '$.recordType') in (%s)", placeholder)) - // for _, recordType := range recordTypes { - // params = append(params, recordType) - // } - // } // 构建查询条件语句 whereSql := ""