From 83488d27d1e9e04f28cf17eea1d9704efe6bec8c Mon Sep 17 00:00:00 2001 From: simonzhangsz Date: Tue, 28 May 2024 10:15:41 +0800 Subject: [PATCH] add: SMF CDR --- features/cdr/cdrevent.go | 238 +++++++++++++++++- lib/global/kits.go | 30 +++ lib/routes/routes.go | 9 +- src/modules/network_data/controller/smf.go | 80 ++++++ src/modules/network_data/model/cdr_event.go | 4 +- .../network_data/model/cdr_event_smf.go | 35 +++ src/modules/network_data/network_data.go | 16 ++ .../network_data/repository/cdr_event.go | 12 + .../network_data/repository/cdr_event.impl.go | 175 ++++++++++++- src/modules/network_data/service/cdr_event.go | 9 + .../network_data/service/cdr_event.impl.go | 29 +++ src/modules/ws/processor/cdr_connect.go | 18 ++ 12 files changed, 640 insertions(+), 15 deletions(-) create mode 100644 src/modules/network_data/controller/smf.go create mode 100644 src/modules/network_data/model/cdr_event_smf.go diff --git a/features/cdr/cdrevent.go b/features/cdr/cdrevent.go index 90437bd1..2f0efecd 100644 --- a/features/cdr/cdrevent.go +++ b/features/cdr/cdrevent.go @@ -4,6 +4,7 @@ import ( "encoding/json" "io" "net/http" + "time" "be.ems/lib/dborm" "be.ems/lib/global" @@ -14,13 +15,197 @@ import ( ) var ( - UriCDREvent = config.DefaultUriPrefix + "/cdrManagement/v1/elementType/{elementTypeValue}/objectType/cdrEvent" - UriCDRFile = config.DefaultUriPrefix + "/cdrManagement/v1/elementType/{elementTypeValue}/objectType/cdrFile" + UriIMSCDREvent = config.DefaultUriPrefix + "/cdrManagement/v1/elementType/ims/objectType/cdrEvent" + UriIMSCDRFile = config.DefaultUriPrefix + "/cdrManagement/v1/elementType/ims/objectType/cdrFile" + UriSMFCDREvent = config.DefaultUriPrefix + "/cdrManagement/v1/elementType/smf/objectType/cdrEvent" + UriSMFCDRFile = config.DefaultUriPrefix + "/cdrManagement/v1/elementType/smf/objectType/cdrFile" - CustomUriCDREvent = config.UriPrefix + "/cdrManagement/v1/elementType/{elementTypeValue}/objectType/cdrEvent" - CustomUriCDRFile = config.UriPrefix + "/cdrManagement/v1/elementType/{elementTypeValue}/objectType/cdrFile" + CustomUriIMSCDREvent = config.UriPrefix + "/cdrManagement/v1/elementType/ims/objectType/cdrEvent" + CustomUriIMSCDRFile = config.UriPrefix + "/cdrManagement/v1/elementType/ims/objectType/cdrFile" + CustomUriSMFCDREvent = config.UriPrefix + "/cdrManagement/v1/elementType/ims/objectType/cdrEvent" + CustomUriSMFCDRFile = config.UriPrefix + "/cdrManagement/v1/elementType/ims/objectType/cdrFile" ) +// SMF CDR +type CdrSubscriptionID struct { + SubscriptionIDType string `json:"subscriptionIDType"` + SubscriptionIDData string `json:"subscriptionIDData"` +} + +type CdrNetWorkFuctionInfomation struct { + NetworkFunctionality string `json:"networkFunctionality"` + NetworkFunctionName string `json:"networkFunctionName,omitempty"` + NetworkFunctionIPv4Address string `json:"networkFunctionIPv4Address,omitempty"` + NetworkFunctionIPv6Address string `json:"networkFunctionIPv6Address,omitempty"` +} + +type SMFTrigger string + +const ( + TimeThresholdReached SMFTrigger = "timeThresholdReached" + VolumeThresholdReached SMFTrigger = "volumeThresholdReached" + UnitThresholdReached SMFTrigger = "unitThresholdReached" + TimeQuotaExhausted SMFTrigger = "timeQuotaExhausted" + VolumeQuotaExhausted SMFTrigger = "volumeQuotaExhausted" + UnitQuotaExhausted SMFTrigger = "unitQuotaExhausted" + ExpiryOfQuotaValidityTime SMFTrigger = "expiryOfQuotaValidityTime" + ExpiryOfQuotaHoldingTime SMFTrigger = "expiryOfQuotaHoldingTime" + EndOfPDUSession SMFTrigger = "endOfPDUSession" +) + +type CdrSMFTrigger struct { + SMFTrigger SMFTrigger `json:"sMFTrigger"` +} + +type CdrQuotaManagementIndicator string + +// List of QuotaManagementIndicator +const ( + Cdr_QMI_ONLINE_CHARGING CdrQuotaManagementIndicator = "ONLINE_CHARGING" + Cdr_QMI_OFFLINE_CHARGING CdrQuotaManagementIndicator = "OFFLINE_CHARGING" + Cdr_QMI_QUOTA_MANAGEMENT_SUSPENDED CdrQuotaManagementIndicator = "QUOTA_MANAGEMENT_SUSPENDED" +) + +type CdrUsedUnitContainer struct { + ServiceIdentifier *int32 `json:"serviceIdentifier,omitempty"` + QuotaManagementIndicatorExt CdrQuotaManagementIndicator `json:"quotaManagementIndicatorExt,omitempty"` + Triggers []SMFTrigger `json:"triggers,omitempty"` + TriggerTimestamp *time.Time `json:"triggerTimestamp,omitempty"` + Time *uint32 `json:"time,omitempty"` + DataTotalVolume *uint64 `json:"dataTotalVolume,omitempty"` + DataVolumeUplink *uint64 `json:"dataVolumeUplink,omitempty"` + DataVolumeDownlink *uint64 `json:"dataVolumeDownlink,omitempty"` + ServiceSpecificUnits *uint64 `json:"serviceSpecificUnits,omitempty"` + EventTimeStamp *time.Time `json:"eventTimeStamp,omitempty"` + LocalSequenceNumber int32 `json:"localSequenceNumber"` + //PDUContainerInformation *PduContainerInformation `json:"pDUContainerInformation,omitempty"` + //NSPAContainerInformation *NspaContainerInformation `json:"nSPAContainerInformation,omitempty"` +} + +type CdrMultipleUnitUsage struct { + RatingGroup uint32 `json:"ratingGroup" yaml:"ratingGroup" bson:"ratingGroup" mapstructure:"RatingGroup"` + UsedUnitContainer []CdrUsedUnitContainer `json:"usedUnitContainer,omitempty" yaml:"usedUnitContainer" bson:"usedUnitContainer" mapstructure:"UsedUnitContainer"` + //UPFID string `json:"uPFID,omitempty" yaml:"uPFID" bson:"uPFID" mapstructure:"UPFID"` +} + +type CdrSubscriberEquipmentNumber struct { + SubscriberEquipmentNumberType string `json:"subscriberEquipmentNumberType"` + SubscriberEquipmentNumberData string `json:"subscriberEquipmentNumberData"` +} + +type CdrNetworkSliceInstanceID struct { + SST int32 `json:"sST"` + + SD string `json:"sD,omitempty"` +} + +type CdrPduAddress struct { + PDUIPv4Address string `json:"pDUIPv4Address,omitempty"` + PDUIPv6AddresswithPrefix string `json:"pDUIPv6AddresswithPrefix,omitempty"` + IPV4dynamicAddressFlag bool `json:"iPV4dynamicAddressFlag,omitempty"` + IPV6dynamicPrefixFlag bool `json:"iPv6dynamicPrefixFlag,omitempty"` +} + +type CdrArp struct { + PriorityLevel int32 `json:"priorityLevel"` + PreemptionCapability string `json:"preemptionCapability"` + PreemptionVulnerability string `json:"preemptionVulnerability"` +} + +type CdrAuthorizedQosInformation struct { + FiveQi int `json:"fiveQi"` + ARP *CdrArp `json:"aRP,omitempty"` + PriorityLevel *int32 `json:"priorityLevel,omitempty"` + AverWindow *int32 `json:"averWindow,omitempty"` + MaxDataBurstVol *int32 `json:"maxDataBurstVol,omitempty"` +} + +type CdrSubscribedDefaultQos struct { + FiveQi int32 `json:"fiveQi,omitempty"` + ARP CdrArp `json:"aRP,omitempty"` + PriorityLevel *int32 `json:"priorityLevel,omitempty"` +} + +type CdrSessionAmbr struct { + Uplink string `json:"uplink"` + + Downlink string `json:"downlink"` +} + +type CdrPDUSessionChargingInformation struct { + PDUSessionChargingID int32 `json:"pDUSessionChargingID"` + UserIdentifier string `json:"userIdentifier,omitempty"` // isdn + UserEquipmentInfo *CdrSubscriberEquipmentNumber `json:"userEquipmentInfo,omitempty"` // imei/imeisv + //UserLocationInfomation *UserLocation `json:"userLocationinfo,omitempty"` + UserRoamerInOut string `json:"userRoamerInOut,omitempty"` + PDUSessionId int32 `json:"pDUSessionId"` + NetworkSliceInstanceID *CdrNetworkSliceInstanceID `json:"networkSliceInstanceID,omitempty"` + //PDUType PduSessionType `json:"pDUType,omitempty"` + SSCMode string `json:"sSCMode,omitempty"` + DNNID string `json:"dNNID"` + SUPIPLMNIdentifier string `json:"sUPIPLMNIdentifier,omitempty"` + //ServingNetworkFunctionID *ServingNetworkFunctionId `json:"servingNetworkFunctionID,omitempty"` + //RATType RatType `json:"rATType,omitempty"` + DataNetworkNameIdentifier string `json:"dataNetworkNameIdentifier,omitempty"` + PDUAddress CdrPduAddress `json:"pDUAddress,omitempty"` + AuthorizedQoSInformation *CdrAuthorizedQosInformation `json:"authorizedQoSInformation,omitempty"` + UETimeZone string `json:"uETimeZone,omitempty"` + PDUSessionstartTime *time.Time `json:"pDUSessionstartTime,omitempty"` + PDUSessionstopTime *time.Time `json:"pDUSessionstopTime,omitempty"` + Diagnostics *int `json:"diagnostics,omitempty"` + ChargingCharacteristics string `json:"chargingCharacteristics,omitempty"` + ChChSelectionMode string `json:"chChSelectionMode,omitempty"` + ThreeGPPPSDataOffStatus string `json:"threeGPPPSDataOffStatus,omitempty"` + //RANSecondaryRATUsageReport *RanSecondaryRatUsageReport `json:"rANSecondaryRATUsageReport,omitempty"` + SubscribedQoSInformation *CdrSubscribedDefaultQos `json:"subscribedQoSInformation,omitempty"` + AuthorizedSessionAMBR *CdrSessionAmbr `json:"authorizedSessionAMBR,omitempty"` + SubscribedSessionAMBR *CdrSessionAmbr `json:"subscribedSessionAMBR,omitempty"` + ServingCNPLMNID string `json:"servingCNPLMNID,omitempty"` + DnnSelectionMode string `json:"dnnSelectionMode,omitempty"` + HomeProvidedChargingID int32 `json:"homeProvidedChargingID,omitempty"` + + //MAPDUNon3GPPUserLocationInfo *UserLocation `json:"mAPDUNon3GPPUserLocationInfo,omitempty" yaml:"mAPDUNon3GPPUserLocationInfo" bson:"mAPDUNon3GPPUserLocationInfo" mapstructure:"MAPDUNon3GPPUserLocationInfo"` + //PresenceReportingAreaInformation map[string]PresenceInfo `json:"presenceReportingAreaInformation,omitempty" yaml:"presenceReportingAreaInformation" bson:"presenceReportingAreaInformation" mapstructure:"PresenceReportingAreaInformation"` +} + +type CauseForRecordClosing string + +const ( + NormalRelease CauseForRecordClosing = "normalRelease" + PartialRecord CauseForRecordClosing = "partialRecord" + AbnormalRelease CauseForRecordClosing = "abnormalRelease" + CAMELInitCallRelease CauseForRecordClosing = "cAMELInitCallRelease" + VolumeLimit CauseForRecordClosing = "volumeLimit" + TimeLimit CauseForRecordClosing = "timeLimit" + ServingNodeChange CauseForRecordClosing = "servingNodeChange" + MaxChangeCond CauseForRecordClosing = "maxChangeCond" + ManagementIntervention CauseForRecordClosing = "managementIntervention" + IntraSGSNIntersystemChange CauseForRecordClosing = "intraSGSNIntersystemChange" + RATChange CauseForRecordClosing = "rATChange" + MSTimeZoneChange CauseForRecordClosing = "mSTimeZoneChange" + SGSNPLMNIDChange CauseForRecordClosing = "sGSNPLMNIDChange " + SGWChange CauseForRecordClosing = "sGWChange" + APNAMBRChange CauseForRecordClosing = "aPNAMBRChange" +) + +type ChargingRecord struct { + RecordType string `json:"recordType"` + ChargingID int `json:"chargingID"` + RecordingNetworkFunctionID string `json:"recordingNetworkFunctionID"` // UUID + SubscriberIdentifier CdrSubscriptionID `json:"subscriberIdentifier,omitempty"` + NFunctionConsumerInformation CdrNetWorkFuctionInfomation `json:"nFunctionConsumerInformation"` + Triggers []CdrSMFTrigger `json:"triggers,omitempty"` + ListOfMultipleUnitUsage []CdrMultipleUnitUsage `json:"listOfMultipleUnitUsage,omitempty"` + RecordOpeningTime string `json:"recordOpeningTime"` + Duration int `json:"duration"` + RecordSequenceNumber int `json:"recordSequenceNumber,omitempty"` + CauseForRecClosing CauseForRecordClosing `json:"causeForRecClosing"` + Diagnostics *int `json:"diagnostics,omitempty"` + LocalRecordSequenceNumber int `json:"localRecordSequenceNumber,omitempty"` + PDUSessionChargingInformation CdrPDUSessionChargingInformation `json:"pDUSessionChargingInformation,omitempty"` + InvocationTimestamp string `json:"invocationTimestamp,omitempty"` +} + type CDREvent struct { NeType string `json:"neType" xorm:"ne_type"` NeName string `json:"neName" xorm:"ne_name"` @@ -29,8 +214,8 @@ type CDREvent struct { CDR map[string]any `json:"CDR" xorm:"cdr_json"` } -func PostCDREventFromNF(w http.ResponseWriter, r *http.Request) { - log.Info("PostCDREventFromNF processing... ") +func PostCDREventFromIMS(w http.ResponseWriter, r *http.Request) { + log.Info("PostCDREventFromIMS processing... ") // body, err := io.ReadAll(io.LimitReader(r.Body, global.RequestBodyMaxLen)) body, err := io.ReadAll(io.LimitReader(r.Body, global.RequestBodyMaxLen)) @@ -49,9 +234,9 @@ func PostCDREventFromNF(w http.ResponseWriter, r *http.Request) { } log.Trace("cdrEvent:", cdrEvent) - affected, err := dborm.XormInsertTableOne("cdr_event", cdrEvent) + affected, err := dborm.XormInsertTableOne("cdr_event_ims", cdrEvent) if err != nil && affected <= 0 { - log.Error("Failed to insert cdr_event:", err) + log.Error("Failed to insert cdr_event_ims:", err) services.ResponseInternalServerError500ProcessError(w, err) return } @@ -65,3 +250,40 @@ func PostCDREventFromNF(w http.ResponseWriter, r *http.Request) { services.ResponseStatusOK204NoContent(w) } + +func PostCDREventFromSMF(w http.ResponseWriter, r *http.Request) { + log.Info("PostCDREventFromSMF processing... ") + + // body, err := io.ReadAll(io.LimitReader(r.Body, global.RequestBodyMaxLen)) + body, err := io.ReadAll(io.LimitReader(r.Body, global.RequestBodyMaxLen)) + if err != nil { + log.Error("Faile to io.ReadAll: ", err) + services.ResponseNotFound404UriNotExist(w, r) + return + } + + cdrEvent := new(CDREvent) + err = json.Unmarshal(body, &cdrEvent) + if cdrEvent.NeType == "" || err != nil { + log.Error("Failed to Unmarshal cdrEvent:", err) + services.ResponseInternalServerError500ProcessError(w, err) + return + } + log.Trace("cdrEvent:", cdrEvent) + + affected, err := dborm.XormInsertTableOne("cdr_event_smf", cdrEvent) + if err != nil && affected <= 0 { + log.Error("Failed to insert cdr_event_smf:", err) + services.ResponseInternalServerError500ProcessError(w, err) + return + } + + // 推送到ws订阅组 + // if v, ok := cdrEvent.CDR["recordType"]; ok { + // if v == "MOC" || v == "MTSM" { + // wsService.NewWSSendImpl.ByGroupID(wsService.GROUP_IMS_CDR, cdrEvent) + // } + // } + + services.ResponseStatusOK204NoContent(w) +} diff --git a/lib/global/kits.go b/lib/global/kits.go index 593f18c8..22091f08 100644 --- a/lib/global/kits.go +++ b/lib/global/kits.go @@ -696,3 +696,33 @@ func IsRpmOrDebPackage(filePath string) int { return fileType } + +func RecurseStructToMap(obj any) map[string]any { + out := make(map[string]any) + v := reflect.ValueOf(obj) + if v.Kind() == reflect.Ptr { + v = v.Elem() + } + + // 递归函数,用于处理嵌套结构体 + var recurse func(reflect.Value) any + recurse = func(value reflect.Value) any { + if value.Kind() == reflect.Struct { + nestedOut := make(map[string]any) + for i := 0; i < value.NumField(); i++ { + nestedOut[value.Type().Field(i).Name] = recurse(value.Field(i)) + } + return nestedOut + } else if value.Kind() == reflect.Ptr { + return recurse(value.Elem()) + } + return value.Interface() + } + + t := v.Type() + for i := 0; i < v.NumField(); i++ { + f := v.Field(i) + out[t.Field(i).Name] = recurse(f) + } + return out +} diff --git a/lib/routes/routes.go b/lib/routes/routes.go index bf95fe71..b5f342ae 100644 --- a/lib/routes/routes.go +++ b/lib/routes/routes.go @@ -300,8 +300,13 @@ func init() { Register("GET", ue.UriNSSFSubscriptions, ue.GetSubscriptionsFromNSSF, nil) Register("GET", ue.CustomUriNSSFSubscriptions, ue.GetSubscriptionsFromNSSF, nil) - Register("POST", cdr.UriCDREvent, cdr.PostCDREventFromNF, nil) - Register("POST", cdr.CustomUriCDREvent, cdr.PostCDREventFromNF, nil) + // ims cdr event + Register("POST", cdr.UriIMSCDREvent, cdr.PostCDREventFromIMS, nil) + Register("POST", cdr.CustomUriIMSCDREvent, cdr.PostCDREventFromIMS, nil) + + // smf cdr event + Register("POST", cdr.UriSMFCDREvent, cdr.PostCDREventFromSMF, nil) + Register("POST", cdr.CustomUriSMFCDREvent, cdr.PostCDREventFromSMF, nil) // UE event //Register("POST", event.UriUEEvent, event.PostUEEventFromAMF, nil) diff --git a/src/modules/network_data/controller/smf.go b/src/modules/network_data/controller/smf.go new file mode 100644 index 00000000..ee3ddb95 --- /dev/null +++ b/src/modules/network_data/controller/smf.go @@ -0,0 +1,80 @@ +package controller + +import ( + "strings" + + "be.ems/src/framework/i18n" + "be.ems/src/framework/utils/ctx" + "be.ems/src/framework/utils/parse" + "be.ems/src/framework/vo/result" + "be.ems/src/modules/network_data/model" + neDataService "be.ems/src/modules/network_data/service" + neService "be.ems/src/modules/network_element/service" + "github.com/gin-gonic/gin" +) + +// 实例化控制层 IMSController 结构体 +var NewSMFController = &SMFController{ + neInfoService: neService.NewNeInfoImpl, + cdrEventService: neDataService.NewSMFCDREventImpl, +} + +// 网元IMS +// +// PATH /ims +type SMFController struct { + // 网元信息服务 + neInfoService neService.INeInfo + // SMF CDR会话事件服务 + cdrEventService neDataService.SMFCDREvent +} + +// CDR会话列表 +// +// GET /cdr/list +func (s *SMFController) CDRList(c *gin.Context) { + language := ctx.AcceptLanguage(c) + var querys model.SMFCDREventQuery + if err := c.ShouldBindQuery(&querys); err != nil { + c.JSON(400, result.CodeMsg(400, i18n.TKey(language, "app.common.err400"))) + return + } + + // 查询网元获取IP + neInfo := s.neInfoService.SelectNeInfoByNeTypeAndNeID(querys.NeType, querys.NeID) + if neInfo.NeId != querys.NeID || neInfo.IP == "" { + c.JSON(200, result.ErrMsg(i18n.TKey(language, "app.common.noNEInfo"))) + return + } + querys.RmUID = neInfo.RmUID + + // 查询数据 + data := s.cdrEventService.SelectPage(querys) + c.JSON(200, result.Ok(data)) +} + +// CDR会话删除 +// +// DELETE /cdr/:cdrIds +func (s *SMFController) CDRRemove(c *gin.Context) { + language := ctx.AcceptLanguage(c) + cdrIds := c.Param("cdrIds") + if cdrIds == "" { + c.JSON(400, result.CodeMsg(400, i18n.TKey(language, "app.common.err400"))) + return + } + // 处理字符转id数组后去重 + ids := strings.Split(cdrIds, ",") + uniqueIDs := parse.RemoveDuplicates(ids) + if len(uniqueIDs) <= 0 { + c.JSON(200, result.Err(nil)) + return + } + rows, err := s.cdrEventService.DeleteByIds(uniqueIDs) + if err != nil { + c.JSON(200, result.ErrMsg(i18n.TKey(language, err.Error()))) + return + } + msg := i18n.TTemplate(language, "app.common.deleteSuccess", map[string]any{"num": rows}) + c.JSON(200, result.OkMsg(msg)) +} diff --git a/src/modules/network_data/model/cdr_event.go b/src/modules/network_data/model/cdr_event.go index f035ecff..ae49041f 100644 --- a/src/modules/network_data/model/cdr_event.go +++ b/src/modules/network_data/model/cdr_event.go @@ -2,7 +2,7 @@ package model import "time" -// CDREvent CDR会话对象 cdr_event +// CDREvent CDR会话对象 cdr_event_ims/cdr_event_smf type CDREvent struct { ID string `json:"id" gorm:"column:id;primaryKey;autoIncrement"` NeType string `json:"neType" gorm:"column:ne_type"` @@ -15,7 +15,7 @@ type CDREvent struct { // CDREventQuery CDR会话对象查询参数结构体 type CDREventQuery struct { - NeType string `json:"neType" form:"neType" binding:"required"` // 网元类型, 暂时支持IMS + NeType string `json:"neType" form:"neType" binding:"required"` // 网元类型 NeID string `json:"neId" form:"neId" binding:"required"` RmUID string `json:"rmUID" form:"rmUID"` RecordType string `json:"recordType" form:"recordType"` // 记录行为 MOC MTC MOSM MTSM diff --git a/src/modules/network_data/model/cdr_event_smf.go b/src/modules/network_data/model/cdr_event_smf.go new file mode 100644 index 00000000..0234f91c --- /dev/null +++ b/src/modules/network_data/model/cdr_event_smf.go @@ -0,0 +1,35 @@ +package model + +import "time" + +// CDREvent CDR会话对象 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"` +} + +type SMFCDREventQuery 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"` + SubscriberID string `json:"subscriberID" form:"subscriberID"` + StartTime string `json:"startTime" form:"startTime"` + EndTime string `json:"endTime" form:"endTime"` + SortField string `json:"sortField" form:"sortField" binding:"omitempty,oneof=timestamp"` // 排序字段,填写结果字段 + SortOrder string `json:"sortOrder" form:"sortOrder" binding:"omitempty,oneof=asc desc"` // 排序升降序,asc desc + PageNum int64 `json:"pageNum" form:"pageNum" binding:"required"` + PageSize int64 `json:"pageSize" form:"pageSize" binding:"required"` +} diff --git a/src/modules/network_data/network_data.go b/src/modules/network_data/network_data.go index 9c7bae73..8d56e692 100644 --- a/src/modules/network_data/network_data.go +++ b/src/modules/network_data/network_data.go @@ -57,6 +57,22 @@ func Setup(router *gin.Engine) { ) } + // 网元SMF + smfGroup := neDataGroup.Group("/smf") + { + // CDR会话事件列表 + smfGroup.GET("/cdr/list", + middleware.PreAuthorize(nil), + controller.NewSMFController.CDRList, + ) + // CDR会话删除 + smfGroup.DELETE("/cdr/:cdrIds", + middleware.PreAuthorize(nil), + collectlogs.OperateLog(collectlogs.OptionNew("log.operate.title.smfCDR", collectlogs.BUSINESS_TYPE_DELETE)), + controller.NewSMFController.CDRRemove, + ) + } + // 网元AMF amfGroup := neDataGroup.Group("/amf") { diff --git a/src/modules/network_data/repository/cdr_event.go b/src/modules/network_data/repository/cdr_event.go index cb3241c6..bd5c562e 100644 --- a/src/modules/network_data/repository/cdr_event.go +++ b/src/modules/network_data/repository/cdr_event.go @@ -13,3 +13,15 @@ type ICDREvent interface { // DeleteByIds 批量删除信息 DeleteByIds(cdrIds []string) int64 } + +// SMF CDR Event +type SMFCDREvent interface { + // SelectPage 根据条件分页查询 + SelectPage(querys model.SMFCDREventQuery) map[string]any + + // SelectByIds 通过ID查询 + SelectByIds(cdrIds []string) []model.CDREventSMF + + // DeleteByIds 批量删除信息 + DeleteByIds(cdrIds []string) int64 +} diff --git a/src/modules/network_data/repository/cdr_event.impl.go b/src/modules/network_data/repository/cdr_event.impl.go index 10e78400..c9903b8e 100644 --- a/src/modules/network_data/repository/cdr_event.impl.go +++ b/src/modules/network_data/repository/cdr_event.impl.go @@ -14,7 +14,7 @@ import ( // 实例化数据层 CDREventImpl 结构体 var NewCDREventImpl = &CDREventImpl{ - selectSql: `select id, ne_type, ne_name, rm_uid, timestamp, cdr_json, created_at from cdr_event`, + selectSql: `select id, ne_type, ne_name, rm_uid, timestamp, cdr_json, created_at from cdr_event_ims`, resultMap: map[string]string{ "id": "ID", @@ -35,6 +35,36 @@ type CDREventImpl struct { resultMap map[string]string } +// Instance of SMF CDREventImpl +var NewSMFCDREventImpl = &SMFCDREventImpl{ + 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", + }, +} + +// CDREventImpl CDR会话事件 数据层处理 +type SMFCDREventImpl struct { + // 查询视图对象SQL + selectSql string + // 结果字段与实体映射 + resultMap map[string]string +} + // convertResultRows 将结果记录转实体结果组 func (r *CDREventImpl) convertResultRows(rows []map[string]any) []model.CDREvent { arr := make([]model.CDREvent, 0) @@ -102,7 +132,7 @@ func (r *CDREventImpl) SelectPage(querys model.CDREventQuery) map[string]any { } // 查询数量 长度为0直接返回 - totalSql := "select count(1) as 'total' from cdr_event" + totalSql := "select count(1) as 'total' from cdr_event_ims" totalRows, err := datasource.RawDB("", totalSql+whereSql, params) if err != nil { logger.Errorf("total err => %v", err) @@ -164,7 +194,146 @@ func (r *CDREventImpl) SelectByIds(cdrIds []string) []model.CDREvent { // DeleteByIds 批量删除信息 func (r *CDREventImpl) DeleteByIds(cdrIds []string) int64 { placeholder := repo.KeyPlaceholderByQuery(len(cdrIds)) - sql := "delete from cdr_event where id in (" + placeholder + ")" + sql := "delete from cdr_event_ims where id in (" + placeholder + ")" + parameters := repo.ConvertIdsSlice(cdrIds) + results, err := datasource.ExecDB("", sql, parameters) + if err != nil { + logger.Errorf("delete err => %v", err) + return 0 + } + return results +} + +// convertResultRows 将结果记录转实体结果组 +func (r *SMFCDREventImpl) convertResultRows(rows []map[string]any) []model.CDREventSMF { + arr := make([]model.CDREventSMF, 0) + for _, row := range rows { + item := model.CDREventSMF{} + for key, value := range row { + if keyMapper, ok := r.resultMap[key]; ok { + repo.SetFieldValue(&item, keyMapper, value) + } + } + arr = append(arr, item) + } + return arr +} + +// SelectPage 根据条件分页查询 +func (r *SMFCDREventImpl) SelectPage(querys model.SMFCDREventQuery) map[string]any { + // 查询条件拼接 + var conditions []string + var params []any + if querys.NeType != "" { + conditions = append(conditions, "ne_type = ?") + params = append(params, querys.NeType) + } + if querys.RmUID != "" { + conditions = append(conditions, "rm_uid = ?") + params = append(params, querys.RmUID) + } + if querys.StartTime != "" { + conditions = append(conditions, "timestamp >= ?") + beginDate := date.ParseStrToDate(querys.StartTime, date.YYYY_MM_DD_HH_MM_SS) + params = append(params, beginDate.Unix()) + } + if querys.EndTime != "" { + conditions = append(conditions, "timestamp <= ?") + endDate := date.ParseStrToDate(querys.EndTime, date.YYYY_MM_DD_HH_MM_SS) + params = append(params, endDate.Unix()) + } + if querys.RecordType != "" { + conditions = append(conditions, "JSON_EXTRACT(cdr_json, '$.recordType') = ?") + params = append(params, querys.RecordType) + } + if querys.SubscriberID != "" { + 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 := "" + if len(conditions) > 0 { + whereSql += " where " + strings.Join(conditions, " and ") + } + + result := map[string]any{ + "total": 0, + "rows": []model.CDREventSMF{}, + } + + // 查询数量 长度为0直接返回 + totalSql := "select count(1) as 'total' from cdr_event_smf" + totalRows, err := datasource.RawDB("", totalSql+whereSql, params) + if err != nil { + logger.Errorf("total err => %v", err) + return result + } + total := parse.Number(totalRows[0]["total"]) + if total == 0 { + return result + } else { + result["total"] = total + } + + // 分页 + pageNum, pageSize := repo.PageNumSize(querys.PageNum, querys.PageSize) + pageSql := " limit ?,? " + params = append(params, pageNum*pageSize) + params = append(params, pageSize) + + // 排序 + orderSql := "" + if querys.SortField != "" { + sortSql := querys.SortField + if querys.SortOrder != "" { + if querys.SortOrder == "desc" { + sortSql += " desc " + } else { + sortSql += " asc " + } + } + orderSql = fmt.Sprintf(" order by id desc, %s ", sortSql) + } + + // 查询数据 + querySql := r.selectSql + whereSql + orderSql + pageSql + results, err := datasource.RawDB("", querySql, params) + if err != nil { + logger.Errorf("query err => %v", err) + } + + // 转换实体 + result["rows"] = r.convertResultRows(results) + return result +} + +// SelectByIds 通过ID查询 +func (r *SMFCDREventImpl) SelectByIds(cdrIds []string) []model.CDREventSMF { + placeholder := repo.KeyPlaceholderByQuery(len(cdrIds)) + querySql := r.selectSql + " where id in (" + placeholder + ")" + parameters := repo.ConvertIdsSlice(cdrIds) + results, err := datasource.RawDB("", querySql, parameters) + if err != nil { + logger.Errorf("query err => %v", err) + return []model.CDREventSMF{} + } + // 转换实体 + return r.convertResultRows(results) +} + +// DeleteByIds 批量删除信息 +func (r *SMFCDREventImpl) DeleteByIds(cdrIds []string) int64 { + placeholder := repo.KeyPlaceholderByQuery(len(cdrIds)) + sql := "delete from cdr_event_smf where id in (" + placeholder + ")" parameters := repo.ConvertIdsSlice(cdrIds) results, err := datasource.ExecDB("", sql, parameters) if err != nil { diff --git a/src/modules/network_data/service/cdr_event.go b/src/modules/network_data/service/cdr_event.go index 27e99177..b05234cd 100644 --- a/src/modules/network_data/service/cdr_event.go +++ b/src/modules/network_data/service/cdr_event.go @@ -10,3 +10,12 @@ type ICDREvent interface { // DeleteByIds 批量删除信息 DeleteByIds(cdrIds []string) (int64, error) } + +// CDR会话事件 服务层接口 +type SMFCDREvent interface { + // SelectPage 根据条件分页查询 + SelectPage(querys model.SMFCDREventQuery) map[string]any + + // DeleteByIds 批量删除信息 + DeleteByIds(cdrIds []string) (int64, error) +} diff --git a/src/modules/network_data/service/cdr_event.impl.go b/src/modules/network_data/service/cdr_event.impl.go index 89ac737e..fef4a13c 100644 --- a/src/modules/network_data/service/cdr_event.impl.go +++ b/src/modules/network_data/service/cdr_event.impl.go @@ -12,12 +12,21 @@ var NewCDREventImpl = &CDREventImpl{ cdrEventRepository: repository.NewCDREventImpl, } +var NewSMFCDREventImpl = &SMFCDREventImpl{ + cdrEventRepository: repository.NewSMFCDREventImpl, +} + // CDREventImpl CDR会话事件 服务层处理 type CDREventImpl struct { // CDR会话事件数据信息 cdrEventRepository repository.ICDREvent } +type SMFCDREventImpl struct { + // CDR会话事件数据信息 + cdrEventRepository repository.SMFCDREvent +} + // SelectPage 根据条件分页查询 func (r *CDREventImpl) SelectPage(querys model.CDREventQuery) map[string]any { return r.cdrEventRepository.SelectPage(querys) @@ -38,3 +47,23 @@ func (r *CDREventImpl) DeleteByIds(cdrIds []string) (int64, error) { // 删除信息失败! return 0, fmt.Errorf("delete fail") } + +func (r *SMFCDREventImpl) SelectPage(querys model.SMFCDREventQuery) map[string]any { + return r.cdrEventRepository.SelectPage(querys) +} + +// DeleteByIds 批量删除信息 +func (r *SMFCDREventImpl) DeleteByIds(cdrIds []string) (int64, error) { + // 检查是否存在 + ids := r.cdrEventRepository.SelectByIds(cdrIds) + if len(ids) <= 0 { + return 0, fmt.Errorf("not data") + } + + if len(ids) == len(cdrIds) { + rows := r.cdrEventRepository.DeleteByIds(cdrIds) + return rows, nil + } + // 删除信息失败! + return 0, fmt.Errorf("delete fail") +} diff --git a/src/modules/ws/processor/cdr_connect.go b/src/modules/ws/processor/cdr_connect.go index cc97f794..bd05465c 100644 --- a/src/modules/ws/processor/cdr_connect.go +++ b/src/modules/ws/processor/cdr_connect.go @@ -27,3 +27,21 @@ func GetCDRConnect(requestID string, data any) ([]byte, error) { })) return resultByte, err } + +// GetCDRConnect 获取CDR会话事件-SMF +func GetSMFCDRConnect(requestID string, data any) ([]byte, error) { + msgByte, _ := json.Marshal(data) + var query neDataModel.SMFCDREventQuery + err := json.Unmarshal(msgByte, &query) + if err != nil { + logger.Warnf("ws processor GetCDRConnect err: %s", err.Error()) + return nil, fmt.Errorf("query data structure error") + } + + dataMap := neDataService.NewSMFCDREventImpl.SelectPage(query) + resultByte, err := json.Marshal(result.Ok(map[string]any{ + "requestId": requestID, + "data": dataMap, + })) + return resultByte, err +}