From c139bbad94ea8ed4bff02122d1992eee5dfdfcfd Mon Sep 17 00:00:00 2001 From: TsMask <340112800@qq.com> Date: Sat, 31 Aug 2024 14:36:01 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E5=BC=95=E5=85=A5features=E7=9A=84gin?= =?UTF-8?q?=E8=B7=AF=E7=94=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- features/cdr/cdrevent.go | 284 +++------------------ features/cm/ne.go | 2 +- features/event/event.go | 12 +- features/features.go | 20 ++ features/file/file.go | 57 +---- features/file/model.go | 85 ------- features/file/service.go | 207 ---------------- features/fm/alarm.go | 62 ++--- features/lm/file_export/controller.go | 142 +++++++++++ features/lm/file_export/model.go | 30 +++ features/lm/file_export/route.go | 41 ++++ features/lm/service.go | 18 ++ features/pm/kpi_c_report/controller.go | 327 +++++++++++++++++++++++++ features/pm/kpi_c_report/model.go | 73 ++++++ features/pm/kpi_c_report/route.go | 44 ++++ features/pm/kpi_c_title/controller.go | 198 +++++++++++++++ features/pm/kpi_c_title/model.go | 30 +++ features/pm/kpi_c_title/route.go | 40 +++ features/pm/performance.go | 101 ++++++-- features/pm/service.go | 20 ++ features/state/getstate.go | 10 +- features/state/state_linux.go | 8 +- features/state/state_windows.go | 8 +- features/state/sysinfo.go | 49 +--- lib/dborm/dbgorm.go | 161 ++++++++++++ lib/eval/evaluate.go | 111 +++++++++ lib/file/file.go | 170 ++----------- lib/file/file_linux.go | 63 +++++ lib/file/file_windows.go | 61 +++++ lib/oauth/oauth.go | 27 -- lib/routes/routes.go | 15 +- lib/services/response.go | 35 +++ omc/omc.go | 78 ++++++ 33 files changed, 1678 insertions(+), 911 deletions(-) create mode 100644 features/features.go delete mode 100644 features/file/model.go delete mode 100644 features/file/service.go create mode 100644 features/lm/file_export/controller.go create mode 100644 features/lm/file_export/model.go create mode 100644 features/lm/file_export/route.go create mode 100644 features/lm/service.go create mode 100644 features/pm/kpi_c_report/controller.go create mode 100644 features/pm/kpi_c_report/model.go create mode 100644 features/pm/kpi_c_report/route.go create mode 100644 features/pm/kpi_c_title/controller.go create mode 100644 features/pm/kpi_c_title/model.go create mode 100644 features/pm/kpi_c_title/route.go create mode 100644 features/pm/service.go create mode 100644 lib/dborm/dbgorm.go create mode 100644 lib/eval/evaluate.go create mode 100644 lib/file/file_linux.go create mode 100644 lib/file/file_windows.go create mode 100644 lib/services/response.go diff --git a/features/cdr/cdrevent.go b/features/cdr/cdrevent.go index 73fbe7d..a5ea06a 100644 --- a/features/cdr/cdrevent.go +++ b/features/cdr/cdrevent.go @@ -1,211 +1,28 @@ package cdr import ( - "encoding/json" - "io" + "fmt" "net/http" - "time" + "strings" + "nms_cxy/lib/core/ctx" "nms_cxy/lib/dborm" - "nms_cxy/lib/global" "nms_cxy/lib/log" "nms_cxy/lib/services" "nms_cxy/omc/config" + neService "nms_cxy/src/modules/network_element/service" wsService "nms_cxy/src/modules/ws/service" ) var ( - 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" + UriCDREvent = config.DefaultUriPrefix + "/cdrManagement/v1/elementType/{elementTypeValue}/objectType/cdrEvent" + UriCDRFile = config.DefaultUriPrefix + "/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" + CustomUriCDREvent = config.UriPrefix + "/cdrManagement/v1/elementType/{elementTypeValue}/objectType/cdrEvent" + CustomUriCDRFile = config.UriPrefix + "/cdrManagement/v1/elementType/{elementTypeValue}/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"` -} - +// CDREvent CDR数据表格结构体 type CDREvent struct { NeType string `json:"neType" xorm:"ne_type"` NeName string `json:"neName" xorm:"ne_name"` @@ -214,72 +31,45 @@ type CDREvent struct { CDR map[string]any `json:"CDR" xorm:"cdr_json"` } -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)) - 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) +// PostCDREventFrom 接收CDR数据请求 +func PostCDREventFrom(w http.ResponseWriter, r *http.Request) { + log.Info("PostCDREventFrom processing... ") + neType := ctx.GetParam(r, "elementTypeValue") + var cdrEvent CDREvent + if err := ctx.ShouldBindJSON(r, &cdrEvent); err != nil { services.ResponseInternalServerError500ProcessError(w, err) return } - log.Trace("cdrEvent:", cdrEvent) - affected, err := dborm.XormInsertTableOne("cdr_event_ims", cdrEvent) + neTypeLower := strings.ToLower(cdrEvent.NeType) + if neType == "" || neType != neTypeLower { + services.ResponseInternalServerError500ProcessError(w, fmt.Errorf("inconsistent network element types")) + return + } + + tableName := fmt.Sprintf("cdr_event_%s", neTypeLower) + affected, err := dborm.XormInsertTableOne(tableName, cdrEvent) if err != nil && affected <= 0 { - log.Error("Failed to insert cdr_event_ims:", err) + log.Error("Failed to insert "+tableName, 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) + // 发送到匹配的网元 + neInfo := neService.NewNeInfoImpl.SelectNeInfoByRmuid(cdrEvent.RmUID) + if neInfo.RmUID == cdrEvent.RmUID { + // 推送到ws订阅组 + switch neInfo.NeType { + case "IMS": + if v, ok := cdrEvent.CDR["recordType"]; ok && (v == "MOC" || v == "MTSM") { + wsService.NewWSSendImpl.ByGroupID(wsService.GROUP_IMS_CDR+neInfo.NeId, cdrEvent) + } + case "SMF": + wsService.NewWSSendImpl.ByGroupID(wsService.GROUP_SMF_CDR+neInfo.NeId, cdrEvent) + case "SMSC": + wsService.NewWSSendImpl.ByGroupID(wsService.GROUP_SMSC_CDR+neInfo.NeId, cdrEvent) } } 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订阅组 - wsService.NewWSSendImpl.ByGroupID(wsService.GROUP_SMF_CDR, cdrEvent) - - services.ResponseStatusOK204NoContent(w) -} diff --git a/features/cm/ne.go b/features/cm/ne.go index a409022..88fbcbf 100644 --- a/features/cm/ne.go +++ b/features/cm/ne.go @@ -862,7 +862,7 @@ func PostNeServiceAction(w http.ResponseWriter, r *http.Request) { // send 204 to fe firstly services.ResponseStatusOK204NoContent(w) //actionCmd := fmt.Sprintf("sudo %s/bin/omcsvc.sh %s", config.GetYamlConfig().NE.OmcDir, action) - actionCmd := fmt.Sprintf("sudo systemctl %s omc", action) + actionCmd := fmt.Sprintf("sudo systemctl %s restagent", action) go RunSSHCmd(sshHost, actionCmd) return // cmd := exec.Command("ssh", sshHost, actionCmd) diff --git a/features/event/event.go b/features/event/event.go index df43aff..6b8662d 100644 --- a/features/event/event.go +++ b/features/event/event.go @@ -14,6 +14,7 @@ import ( "nms_cxy/lib/log" "nms_cxy/lib/services" "nms_cxy/omc/config" + neService "nms_cxy/src/modules/network_element/service" wsService "nms_cxy/src/modules/ws/service" "github.com/gin-gonic/gin" @@ -72,6 +73,7 @@ func PostUEEventFromAMF(c *gin.Context) { return } + // AMF没有RmUID,直接推送 // 推送到ws订阅组 wsService.NewWSSendImpl.ByGroupID(wsService.GROUP_AMF_UE, ueEvent) @@ -97,9 +99,13 @@ func PostUEEvent(w http.ResponseWriter, r *http.Request) { return } - // 推送到ws订阅组 - if ueEvent.NeType == "MME" { - wsService.NewWSSendImpl.ByGroupID(wsService.GROUP_MME_UE, ueEvent) + // 发送到匹配的网元 + neInfo := neService.NewNeInfoImpl.SelectNeInfoByRmuid(ueEvent.RmUID) + if neInfo.RmUID == ueEvent.RmUID { + // 推送到ws订阅组 + if ueEvent.NeType == "MME" { + wsService.NewWSSendImpl.ByGroupID(wsService.GROUP_MME_UE+neInfo.NeId, ueEvent) + } } services.ResponseStatusOK204NoContent(w) diff --git a/features/features.go b/features/features.go new file mode 100644 index 0000000..dc2e0c4 --- /dev/null +++ b/features/features.go @@ -0,0 +1,20 @@ +package features + +import ( + "nms_cxy/features/lm" + "nms_cxy/features/pm" + "nms_cxy/lib/log" + + "github.com/gin-gonic/gin" +) + +func InitServiceEngine(r *gin.Engine) { + log.Info("======init feature group gin.Engine") + + // featuresGroup := r.Group("/") + // 注册 各个features 模块的路由 + pm.InitSubServiceRoute(r) + lm.InitSubServiceRoute(r) + + // return featuresGroup +} diff --git a/features/file/file.go b/features/file/file.go index 603257c..e102c05 100644 --- a/features/file/file.go +++ b/features/file/file.go @@ -1,32 +1,20 @@ package file import ( - "fmt" "net/http" "path/filepath" - "nms_cxy/lib/core/ctx" - "nms_cxy/lib/dborm" - "nms_cxy/lib/file" "nms_cxy/lib/log" "nms_cxy/lib/services" "nms_cxy/omc/config" "github.com/gorilla/mux" - "github.com/shirou/gopsutil/disk" ) var ( // parameter config management - UriFile = config.DefaultUriPrefix + "/fileManagement/{apiVersion}/{location}/file" - + UriFile = config.DefaultUriPrefix + "/fileManagement/{apiVersion}/{location}/file" CustomUriFile = config.UriPrefix + "/fileManagement/{apiVersion}/{location}/file" - - // 获取磁盘列表 - UriDiskList = config.DefaultUriPrefix + "/fileManagement/{apiVersion}/files/diskList" - - // 获取文件列表 - UriListFiles = config.DefaultUriPrefix + "/fileManagement/{apiVersion}/files/listFiles" ) // func init() { @@ -152,46 +140,3 @@ func DeleteFile(w http.ResponseWriter, r *http.Request) { services.ResponseStatusOK204NoContent(w) return } - -// 磁盘列表 -func DiskList(w http.ResponseWriter, r *http.Request) { - disks := make([]map[string]string, 0) - - partitions, err := disk.Partitions(false) - if err != nil { - services.ResponseWithJson(w, 200, disks) - } - - for _, partition := range partitions { - usage, err := disk.Usage(partition.Mountpoint) - if err != nil { - continue - } - disks = append(disks, map[string]string{ - "size": file.FormatFileSize(float64(usage.Total)), - "used": file.FormatFileSize(float64(usage.Used)), - "avail": file.FormatFileSize(float64(usage.Free)), - "pcent": fmt.Sprintf("%.1f%%", usage.UsedPercent), - "target": partition.Device, - }) - } - services.ResponseWithJson(w, 200, disks) -} - -// 获取文件列表 /files/search -func ListFiles(w http.ResponseWriter, r *http.Request) { - // json 請求參數獲取 - var bodyArgs FileOption - err := ctx.ShouldBindJSON(r, &bodyArgs) - if err != nil || dborm.DbClient.XEngine == nil { - services.ResponseErrorWithJson(w, 400, err.Error()) - return - } - - files, err := GetFileList(bodyArgs) - if err != nil { - services.ResponseErrorWithJson(w, 400, err.Error()) - return - } - services.ResponseWithJson(w, 200, files) -} diff --git a/features/file/model.go b/features/file/model.go deleted file mode 100644 index 1de351f..0000000 --- a/features/file/model.go +++ /dev/null @@ -1,85 +0,0 @@ -package file - -import ( - "bufio" - "fmt" - "io/fs" - "os" - "os/exec" - "time" - - "github.com/spf13/afero" -) - -type FileOption struct { - Path string `json:"path"` - Search string `json:"search"` - ContainSub bool `json:"containSub"` - Expand bool `json:"expand"` - Dir bool `json:"dir"` - ShowHidden bool `json:"showHidden"` - Page int `json:"page"` - PageSize int `json:"pageSize"` -} - -type FileInfo struct { - Fs afero.Fs `json:"-"` - Path string `json:"path"` - Name string `json:"name"` - Extension string `json:"extension"` - Content string `json:"content"` - Size int64 `json:"size"` - IsDir bool `json:"isDir"` - IsSymlink bool `json:"isSymlink"` - IsHidden bool `json:"isHidden"` - LinkPath string `json:"linkPath"` - Type string `json:"type"` - Mode string `json:"mode"` - MimeType string `json:"mimeType"` - UpdateTime time.Time `json:"updateTime"` - ModTime time.Time `json:"modTime"` - FileMode os.FileMode `json:"-"` - Items []*FileInfo `json:"items"` - ItemTotal int `json:"itemTotal"` -} - -func (f *FileInfo) search(search string, count int) (files []FileSearchInfo, total int, err error) { - cmd := exec.Command("find", f.Path, "-name", fmt.Sprintf("*%s*", search)) - output, err := cmd.StdoutPipe() - if err != nil { - return - } - if err = cmd.Start(); err != nil { - return - } - defer func() { - _ = cmd.Wait() - _ = cmd.Process.Kill() - }() - - scanner := bufio.NewScanner(output) - for scanner.Scan() { - line := scanner.Text() - info, err := os.Stat(line) - if err != nil { - continue - } - total++ - if total > count { - continue - } - files = append(files, FileSearchInfo{ - Path: line, - FileInfo: info, - }) - } - if err = scanner.Err(); err != nil { - return - } - return -} - -type FileSearchInfo struct { - Path string `json:"path"` - fs.FileInfo -} diff --git a/features/file/service.go b/features/file/service.go deleted file mode 100644 index e105c3f..0000000 --- a/features/file/service.go +++ /dev/null @@ -1,207 +0,0 @@ -package file - -import ( - "errors" - "fmt" - "os" - "path" - "path/filepath" - "strings" - - "nms_cxy/lib/file" - - "github.com/spf13/afero" -) - -// 获取文件列表 -func GetFileList(op FileOption) (FileInfo, error) { - var fileInfo FileInfo - if _, err := os.Stat(op.Path); err != nil && os.IsNotExist(err) { - return fileInfo, nil - } - info, err := NewFileInfo(op) - if err != nil { - return fileInfo, err - } - fileInfo = *info - return fileInfo, nil -} - -func NewFileInfo(op FileOption) (*FileInfo, error) { - var appFs = afero.NewOsFs() - - info, err := appFs.Stat(op.Path) - if err != nil { - return nil, err - } - - fileInfo := &FileInfo{ - Fs: appFs, - Path: op.Path, - Name: info.Name(), - IsDir: info.IsDir(), - FileMode: info.Mode(), - ModTime: info.ModTime(), - Size: info.Size(), - IsSymlink: file.IsSymlink(info.Mode()), - Extension: filepath.Ext(info.Name()), - IsHidden: file.IsHidden(op.Path), - Mode: fmt.Sprintf("%04o", info.Mode().Perm()), - MimeType: file.GetMimeType(op.Path), - } - if fileInfo.IsSymlink { - fileInfo.LinkPath = file.GetSymlink(op.Path) - } - if op.Expand { - if fileInfo.IsDir { - if err := listChildren(fileInfo, op.Dir, op.ShowHidden, op.ContainSub, op.Search, op.Page, op.PageSize); err != nil { - return nil, err - } - return fileInfo, nil - } else { - if err := getContent(fileInfo); err != nil { - return nil, err - } - } - } - return fileInfo, nil -} - -func listChildren(f *FileInfo, dir, showHidden, containSub bool, search string, page, pageSize int) error { - afs := &afero.Afero{Fs: f.Fs} - var ( - files []FileSearchInfo - err error - total int - ) - - if search != "" && containSub { - files, total, err = f.search(search, page*pageSize) - if err != nil { - return err - } - } else { - dirFiles, err := afs.ReadDir(f.Path) - if err != nil { - return err - } - for _, file := range dirFiles { - files = append(files, FileSearchInfo{ - Path: f.Path, - FileInfo: file, - }) - } - } - - var items []*FileInfo - for _, df := range files { - if dir && !df.IsDir() { - continue - } - name := df.Name() - fPath := path.Join(df.Path, df.Name()) - if search != "" { - if containSub { - fPath = df.Path - name = strings.TrimPrefix(strings.TrimPrefix(fPath, f.Path), "/") - } else { - lowerName := strings.ToLower(name) - lowerSearch := strings.ToLower(search) - if !strings.Contains(lowerName, lowerSearch) { - continue - } - } - } - if !showHidden && file.IsHidden(name) { - continue - } - f.ItemTotal++ - isSymlink, isInvalidLink := false, false - if file.IsSymlink(df.Mode()) { - isSymlink = true - info, err := f.Fs.Stat(fPath) - if err == nil { - df.FileInfo = info - } else { - isInvalidLink = true - } - } - - fileInfo := &FileInfo{ - Fs: f.Fs, - Name: name, - Size: df.Size(), - ModTime: df.ModTime(), - FileMode: df.Mode(), - IsDir: df.IsDir(), - IsSymlink: isSymlink, - IsHidden: file.IsHidden(fPath), - Extension: filepath.Ext(name), - Path: fPath, - Mode: fmt.Sprintf("%04o", df.Mode().Perm()), - } - - if isSymlink { - fileInfo.LinkPath = file.GetSymlink(fPath) - } - if df.Size() > 0 { - fileInfo.MimeType = file.GetMimeType(fPath) - } - if isInvalidLink { - fileInfo.Type = "invalid_link" - } - items = append(items, fileInfo) - } - if containSub { - f.ItemTotal = total - } - start := (page - 1) * pageSize - end := pageSize + start - var result []*FileInfo - if start < 0 || start > f.ItemTotal || end < 0 || start > end { - result = items - } else { - if end > f.ItemTotal { - result = items[start:] - } else { - result = items[start:end] - } - } - - f.Items = result - return nil -} - -func getContent(f *FileInfo) error { - if f.Size <= 10*1024*1024 { - afs := &afero.Afero{Fs: f.Fs} - cByte, err := afs.ReadFile(f.Path) - if err != nil { - return nil - } - if len(cByte) > 0 && detectBinary(cByte) { - return errors.New("ErrFileCanNotRead") - } - f.Content = string(cByte) - return nil - } else { - return errors.New("ErrFileCanNotRead") - } -} - -func detectBinary(buf []byte) bool { - whiteByte := 0 - n := 1024 - if len(buf) < 1024 { - n = len(buf) - } - for i := 0; i < n; i++ { - if (buf[i] >= 0x20) || buf[i] == 9 || buf[i] == 10 || buf[i] == 13 { - whiteByte++ - } else if buf[i] <= 6 || (buf[i] >= 14 && buf[i] <= 31) { - return true - } - } - - return whiteByte < 1 -} diff --git a/features/fm/alarm.go b/features/fm/alarm.go index 398b11c..c5a9717 100644 --- a/features/fm/alarm.go +++ b/features/fm/alarm.go @@ -16,14 +16,11 @@ import ( "nms_cxy/lib/services" "nms_cxy/omc/config" - "nms_cxy/src/framework/utils/date" - neDataModel "nms_cxy/src/modules/network_data/model" - nmsCXYService "nms_cxy/src/modules/nms_cxy/service" + "xorm.io/xorm" "github.com/go-resty/resty/v2" _ "github.com/go-sql-driver/mysql" "github.com/gorilla/mux" - "xorm.io/xorm" ) const ( @@ -288,8 +285,6 @@ func PostAlarmFromNF(w http.ResponseWriter, r *http.Request) { services.ResponseInternalServerError500DatabaseOperationFailed(w) continue } - // 推送Kafka - ParseAlarmDataPushKafka(alarmData) } else { affected, err := session.Where("ne_type=? and ne_id=? and alarm_id=? and alarm_status=1", alarmData.NeType, alarmData.NeId, alarmData.AlarmId). Cols("alarm_status", "clear_type", "clear_time"). @@ -299,8 +294,6 @@ func PostAlarmFromNF(w http.ResponseWriter, r *http.Request) { services.ResponseInternalServerError500DatabaseOperationFailed(w) continue } - // 推送Kafka - ParseAlarmDataPushKafka(alarmData) } log.Trace("alarmData:", alarmData) var currentSeq string @@ -328,7 +321,7 @@ func PostAlarmFromNF(w http.ResponseWriter, r *http.Request) { alarmLog.AlarmCode = alarmData.AlarmCode alarmLog.AlarmStatus = alarmData.AlarmStatus alarmLog.EventTime = eventTime - log.Debug("alarmLog:", alarmLog) + log.Trace("alarmLog:", alarmLog) affected, err := session.Insert(alarmLog) if err != nil && affected <= 0 { @@ -361,8 +354,6 @@ func PostAlarmFromNF(w http.ResponseWriter, r *http.Request) { } } session.Commit() - // 推送Kafka - ParseAlarmDataPushKafka(alarmData) // for alarm forward time format alarmData.EventTime = eventTime } else { @@ -444,8 +435,8 @@ func PostAlarmFromNF(w http.ResponseWriter, r *http.Request) { if IsNeedToAckAlarm(valueJson, &alarmData) { SetAlarmAckInfo(valueJson, &alarmData) } - log.Debug("alarmData:", alarmData) - if alarmData.OrigSeverity == "Event" && config.GetYamlConfig().Alarm.SplitEventAlarm { + log.Trace("alarmData:", alarmData) + if (alarmData.OrigSeverity == "Event" || alarmData.OrigSeverity == "5") && config.GetYamlConfig().Alarm.SplitEventAlarm { affected, err := xEngine.Table("alarm_event").InsertOne(alarmData) if err != nil && affected <= 0 { log.Error("Failed to insert alarm_event:", err) @@ -475,8 +466,6 @@ func PostAlarmFromNF(w http.ResponseWriter, r *http.Request) { log.Error("Failed to insert alarm_log:", err) } session.Commit() - // 推送Kafka - ParseAlarmDataPushKafka(alarmData) } if config.GetYamlConfig().Alarm.ForwardAlarm { if err = AlarmEmailForward(&alarmData); err != nil { @@ -749,14 +738,28 @@ func GetAlarmFromNF(w http.ResponseWriter, r *http.Request) { } } + evenTime := global.GetFmtTimeString(time.RFC3339, alarmData.EventTime, time.DateTime) alarmData.ObjectUid = alarmData.NeId alarmData.ObjectType = "VNFM" - alarmData.EventTime = global.GetFmtTimeString(time.RFC3339, alarmData.EventTime, time.DateTime) + alarmData.EventTime = evenTime if IsNeedToAckAlarm(valueJson, &alarmData) { SetAlarmAckInfo(valueJson, &alarmData) } log.Trace("alarmData:", alarmData) - affected, err := session.Insert(alarmData) + var affected int64 + if (alarmData.OrigSeverity == "Event" || alarmData.OrigSeverity == "5") && config.GetYamlConfig().Alarm.SplitEventAlarm { + affected, err = session.Table("alarm_event").InsertOne(alarmData) + if err != nil && affected <= 0 { + log.Error("Failed to insert alarm_event:", err) + continue + } + } else { + affected, err = session.Table("alarm").Insert(alarmData) + if err != nil && affected <= 0 { + log.Error("Failed to insert alarm:", err) + continue + } + } if err == nil && affected > 0 { alarmLog := new(AlarmLog) alarmLog.NeType = alarmData.NeType @@ -765,8 +768,8 @@ func GetAlarmFromNF(w http.ResponseWriter, r *http.Request) { alarmLog.AlarmId = alarmData.AlarmId alarmLog.AlarmCode = alarmData.AlarmCode alarmLog.AlarmStatus = alarmData.AlarmStatus - alarmLog.EventTime = global.GetFmtTimeString(time.RFC3339, alarmData.EventTime, time.DateTime) - log.Debug("alarmLog:", alarmLog) + alarmLog.EventTime = evenTime + log.Trace("alarmLog:", alarmLog) affected, err = session.Insert(alarmLog) if err != nil && affected <= 0 { log.Error("Failed to insert data:", err) @@ -789,24 +792,3 @@ func GetAlarmFromNF(w http.ResponseWriter, r *http.Request) { } services.ResponseStatusOK204NoContent(w) } - -// ParseAlarmDataPushKafka 处理告警数据后推送kafka -func ParseAlarmDataPushKafka(alarmData Alarm) { - neAlarm := neDataModel.Alarm{ - AlarmSeq: fmt.Sprint(alarmData.AlarmSeq), - AlarmTitle: alarmData.AlarmTitle, - AlarmStatus: fmt.Sprint(alarmData.AlarmStatus), - AlarmType: alarmData.AlarmType, - OrigSeverity: alarmData.OrigSeverity, - EventTime: date.ParseStrToDate(alarmData.EventTime, date.YYYY_MM_DD_HH_MM_SS), - ID: alarmData.AlarmId, - AlarmCode: fmt.Sprint(alarmData.AlarmCode), - SpecificProblem: alarmData.SpecificProblem, - ObjectUid: alarmData.ObjectUid, - NeName: alarmData.NeName, - AlarmId: alarmData.ObjectUid, - ObjectName: alarmData.ObjectName, - AddInfo: alarmData.AddInfo, - } - nmsCXYService.NewAlarmImpl.KafkaPush(neAlarm) -} diff --git a/features/lm/file_export/controller.go b/features/lm/file_export/controller.go new file mode 100644 index 0000000..df40f11 --- /dev/null +++ b/features/lm/file_export/controller.go @@ -0,0 +1,142 @@ +package file_export + +import ( + "encoding/json" + "net/http" + "os" + "path/filepath" + + "nms_cxy/lib/file" + "nms_cxy/lib/log" + "nms_cxy/lib/services" + "nms_cxy/src/framework/datasource" + "nms_cxy/src/framework/i18n" + "nms_cxy/src/framework/utils/ctx" + + "github.com/gin-gonic/gin" +) + +type SysJobResponse struct { + SysJob + TableName string `json:"tableName"` + TableDisplay string `json:"tableDisplay"` + FilePath string `json:"filePath"` +} + +type TargetParams struct { + Duration int `json:"duration"` + TableName string `json:"tableName"` + Columns string `json:"columns"` // exported column name of time string + TimeCol string `json:"timeCol"` // time stamp of column name + TimeUnit string `json:"timeUnit"` // timestamp unit: second/micro/milli + Extras string `json:"extras"` // extras condition for where + FilePath string `json:"filePath"` // file path +} + +func (m *SysJob) GetFileExportTable(c *gin.Context) { + var results []SysJob + + err := datasource.DefaultDB().Table(m.TableName()).Where("invoke_target=? and status=1", INVOKE_FILE_EXPORT). + Find(&results).Error + if err != nil { + c.JSON(http.StatusInternalServerError, services.ErrResp(err.Error())) + return + } + language := ctx.AcceptLanguage(c) + var response []SysJobResponse + for _, job := range results { + var params TargetParams + if err := json.Unmarshal([]byte(job.TargetParams), ¶ms); err != nil { + c.JSON(http.StatusInternalServerError, services.ErrResp(err.Error())) + return + } + TableDisplay := i18n.TKey(language, "table."+params.TableName) + if TableDisplay == "" { + TableDisplay = params.TableName + } + response = append(response, SysJobResponse{ + SysJob: job, + TableName: params.TableName, + TableDisplay: TableDisplay, + FilePath: params.FilePath, + }) + } + c.JSON(http.StatusOK, services.DataResp(response)) +} + +func (m *FileExport) GetFileList(c *gin.Context) { + var querys FileExportQuery + + if err := c.ShouldBindQuery(&querys); err != nil { + c.JSON(http.StatusBadRequest, services.ErrResp(err.Error())) + return + } + + files, err := file.GetFileInfo(querys.Path, querys.Suffix) + if err != nil { + log.Error(err) + c.JSON(http.StatusInternalServerError, services.ErrResp(err.Error())) + } + + // split files list + lenNum := int64(len(files)) + start := (querys.PageNum - 1) * querys.PageSize + end := start + querys.PageSize + var splitList []file.FileInfo + if start >= lenNum { + splitList = []file.FileInfo{} + } else if end >= lenNum { + splitList = files[start:] + } else { + splitList = files[start:end] + } + total := len(files) + c.JSON(http.StatusOK, services.TotalDataResp(splitList, total)) +} + +func (m *FileExport) Total(c *gin.Context) { + dir := c.Query("path") + + fileCount, dirCount, err := file.GetFileAndDirCount(dir) + if err != nil { + log.Error(err) + c.JSON(http.StatusInternalServerError, services.ErrResp(err.Error())) + return + } + total := fileCount + dirCount + c.JSON(http.StatusOK, services.TotalResp(int64(total))) +} + +func (m *FileExport) DownloadHandler(c *gin.Context) { + dir := c.Query("path") + fileName := c.Param("fileName") + filePath := filepath.Join(dir, fileName) + + file, err := os.Open(filePath) + if err != nil { + c.JSON(http.StatusInternalServerError, services.ErrResp(err.Error())) + return + } + defer file.Close() + + if _, err := os.Stat(filePath); os.IsNotExist(err) { + c.JSON(http.StatusNotFound, services.ErrResp(err.Error())) + return + } + + c.Header("Content-Disposition", "attachment; filename="+fileName) + c.Header("Content-Type", "application/octet-stream") + c.File(filePath) +} + +func (m *FileExport) Delete(c *gin.Context) { + fileName := c.Param("fileName") + dir := c.Query("path") + filePath := filepath.Join(dir, fileName) + + if err := os.Remove(filePath); err != nil { + c.JSON(http.StatusInternalServerError, services.ErrResp(err.Error())) + return + } + c.JSON(http.StatusNoContent, nil) // 204 No Content +} diff --git a/features/lm/file_export/model.go b/features/lm/file_export/model.go new file mode 100644 index 0000000..8fefa8c --- /dev/null +++ b/features/lm/file_export/model.go @@ -0,0 +1,30 @@ +package file_export + +import ( + "nms_cxy/lib/file" +) + +const ( + INVOKE_FILE_EXPORT = "exportTable" +) + +type SysJob struct { + JobID int64 `gorm:"column:job_id;primary_key;auto_increment" json:"job_id"` //任务ID + InvokeTarget string `gorm:"column:invoke_target" json:"invoke_target"` //调用目标字符串 + TargetParams string `gorm:"column:target_params;type:json" json:"target_params,omitempty"` //调用目标传入参数 +} + +func (m *SysJob) TableName() string { + return "sys_job" +} + +type FileExport struct { + file.FileInfo +} + +type FileExportQuery struct { + Path string `form:"path" binding:"required"` + Suffix string `form:"suffix"` + PageNum int64 `form:"pageNum" binding:"required"` + PageSize int64 `form:"pageSize" binding:"required"` +} diff --git a/features/lm/file_export/route.go b/features/lm/file_export/route.go new file mode 100644 index 0000000..3b0b72d --- /dev/null +++ b/features/lm/file_export/route.go @@ -0,0 +1,41 @@ +package file_export + +import ( + "nms_cxy/src/framework/middleware" + + "github.com/gin-gonic/gin" +) + +// Register Routes for file_export +func Register(r *gin.RouterGroup) { + + lmTable := r.Group("/table") + { + var m *SysJob + lmTable.GET("/list", + middleware.PreAuthorize(nil), + m.GetFileExportTable, + ) + + } + lmFile := r.Group("/file") + { + var f *FileExport + lmFile.GET("/list", + middleware.PreAuthorize(nil), + f.GetFileList, + ) + lmFile.GET("/total", + middleware.PreAuthorize(nil), + f.Total, + ) + lmFile.GET("/:fileName", + middleware.PreAuthorize(nil), + f.DownloadHandler, + ) + lmFile.DELETE("/:fileName", + middleware.PreAuthorize(nil), + f.Delete, + ) + } +} diff --git a/features/lm/service.go b/features/lm/service.go new file mode 100644 index 0000000..a3ef349 --- /dev/null +++ b/features/lm/service.go @@ -0,0 +1,18 @@ +// log management package + +package lm + +import ( + "nms_cxy/features/lm/file_export" + "nms_cxy/lib/log" + + "github.com/gin-gonic/gin" +) + +func InitSubServiceRoute(r *gin.Engine) { + log.Info("======init Log management group gin.Engine") + + lmGroup := r.Group("/lm") + // register sub modules routes + file_export.Register(lmGroup) +} diff --git a/features/pm/kpi_c_report/controller.go b/features/pm/kpi_c_report/controller.go new file mode 100644 index 0000000..bc46a80 --- /dev/null +++ b/features/pm/kpi_c_report/controller.go @@ -0,0 +1,327 @@ +package kpi_c_report + +import ( + "fmt" + "net/http" + "strings" + + "nms_cxy/lib/dborm" + "nms_cxy/lib/services" + + "github.com/gin-gonic/gin" +) + +func (k *KpiCReport) Get(c *gin.Context) { + var reports []KpiCReport + var conditions []string + var params []any + + var querys KpiCReportQuery + if err := c.ShouldBindQuery(&querys); err != nil { + c.JSON(http.StatusBadRequest, services.ErrResp(err.Error())) + return + } + + // construct condition to get + if querys.NeType != "" { + conditions = append(conditions, "ne_type = ?") + params = append(params, strings.ToUpper(querys.NeType)) + } else { + c.JSON(http.StatusBadRequest, services.ErrResp("Not found NE type")) + return + } + tableName := TableName() + "_" + strings.ToLower(querys.NeType) + dbg := dborm.DefaultDB().Table(tableName) + + if querys.NeID != "" { + conditions = append(conditions, "rm_uid = (select n.rm_uid from ne_info n where n.ne_type=? and n.ne_id=? and n.status=1)") + params = append(params, strings.ToUpper(querys.NeType), querys.NeID) + } else { + c.JSON(http.StatusBadRequest, services.ErrResp("Not found required parameter NE ID")) + return + } + if querys.StartTime != "" { + conditions = append(conditions, "created_at >= ?") + params = append(params, querys.StartTime) + } + if querys.EndTime != "" { + conditions = append(conditions, "created_at <= ?") + params = append(params, querys.EndTime) + } + + whereSql := "" + if len(conditions) > 0 { + whereSql += strings.Join(conditions, " and ") + dbg = dbg.Where(whereSql, params...) + } + // page number and size + if pageSize := querys.PageSize; pageSize > 0 { + dbg = dbg.Limit(pageSize) + if pageNum := querys.PageNum; pageNum > 0 { + dbg = dbg.Offset((pageNum - 1) * pageSize) + } + } + + // order by + if sortField, sortOrder := querys.SortField, querys.SortOrder; sortField != "" && sortOrder != "" { + orderBy := fmt.Sprintf("%s %s", sortField, sortOrder) + dbg = dbg.Order(orderBy) + } + + //err := dborm.DefaultDB().Table(tableName).Where(whereSql, params...).Find(&reports).Error + err := dbg.Find(&reports).Error + if err != nil { + c.JSON(http.StatusInternalServerError, services.ErrResp(err.Error())) + return + } + c.JSON(http.StatusOK, services.DataResp(reports)) +} + +func (k *KpiCReport) GetReport2FE(c *gin.Context) { + var results []KpiCReport + var conditions []string + var params []any + + var querys KpiCReportQuery + if err := c.ShouldBindQuery(&querys); err != nil { + c.JSON(http.StatusBadRequest, services.ErrResp(err.Error())) + return + } + + // construct condition to get + if querys.NeType != "" { + conditions = append(conditions, "ne_type = ?") + params = append(params, strings.ToUpper(querys.NeType)) + } else { + c.JSON(http.StatusBadRequest, services.ErrResp("Not found required parameter NE type")) + return + } + tableName := TableName() + "_" + strings.ToLower(querys.NeType) + dbg := dborm.DefaultDB().Table(tableName) + + if querys.NeID != "" { + conditions = append(conditions, "rm_uid = (select n.rm_uid from ne_info n where n.ne_type=? and n.ne_id=? and n.status=1)") + params = append(params, querys.NeType, querys.NeID) + } else { + c.JSON(http.StatusBadRequest, services.ErrResp("Not found required parameter NE ID")) + return + } + if querys.StartTime != "" { + conditions = append(conditions, "created_at >= ?") + params = append(params, querys.StartTime) + } + if querys.EndTime != "" { + conditions = append(conditions, "created_at <= ?") + params = append(params, querys.EndTime) + } + + whereSql := "" + if len(conditions) > 0 { + whereSql += strings.Join(conditions, " and ") + dbg = dbg.Where(whereSql, params...) + } + // page number and size + if pageSize := querys.PageSize; pageSize > 0 { + dbg = dbg.Limit(pageSize) + if pageNum := querys.PageNum; pageNum > 0 { + dbg = dbg.Offset((pageNum - 1) * pageSize) + } + } + + // order by + if sortField, sortOrder := querys.SortField, querys.SortOrder; sortField != "" && sortOrder != "" { + orderBy := fmt.Sprintf("%s %s", sortField, sortOrder) + dbg = dbg.Order(orderBy) + } + + //err := dborm.DefaultDB().Table(tableName).Where(whereSql, params...).Find(&reports).Error + err := dbg.Find(&results).Error + if err != nil { + c.JSON(http.StatusInternalServerError, services.ErrResp(err.Error())) + return + } + + reports := []map[string]any{} + for _, r := range results { + report := map[string]any{ + // kip_id ... + "neType": *r.NeType, + "neId": querys.NeID, + "neName": *r.NeName, + "rmUID": *r.RmUID, + "startIndex": r.Index, + "timeGroup": r.Date[:10] + " " + *r.StartTime, + "createdAt": r.CreatedAt, + "granularity": r.Granularity, + } + + for _, k := range r.KpiValues { + report[k.KPIID] = k.Value + } + reports = append(reports, report) + } + c.JSON(http.StatusOK, services.DataResp(reports)) +} + +func (k *KpiCReport) GetTotalList(c *gin.Context) { + var reports []KpiCReport + var conditions []string + var params []any + + var querys KpiCReportQuery + if err := c.ShouldBindQuery(&querys); err != nil { + c.JSON(http.StatusBadRequest, services.ErrResp(err.Error())) + return + } + + // construct condition to get + if querys.NeType != "" { + conditions = append(conditions, "ne_type = ?") + params = append(params, strings.ToUpper(querys.NeType)) + } else { + c.JSON(http.StatusBadRequest, services.ErrResp("Not found NE type")) + return + } + tableName := TableName() + "_" + strings.ToLower(querys.NeType) + dbg := dborm.DefaultDB().Table(tableName) + + if querys.StartTime != "" { + conditions = append(conditions, "created_at >= ?") + params = append(params, querys.StartTime) + } + if querys.EndTime != "" { + conditions = append(conditions, "created_at <= ?") + params = append(params, querys.EndTime) + } + + whereSql := "" + if len(conditions) > 0 { + whereSql += strings.Join(conditions, " and ") + dbg = dbg.Where(whereSql, params...) + } + + // get total number + var total int64 = 0 + err := dbg.Count(&total).Error + if err != nil { + c.JSON(http.StatusInternalServerError, services.ErrResp(err.Error())) + return + } + + // page number and size + if pageSize := querys.PageSize; pageSize > 0 { + dbg = dbg.Limit(pageSize) + if pageNum := querys.PageNum; pageNum > 0 { + dbg = dbg.Offset((pageNum - 1) * pageSize) + } + } + + // order by + if sortField, sortOrder := querys.SortField, querys.SortOrder; sortField != "" && sortOrder != "" { + orderBy := fmt.Sprintf("%s %s", sortField, sortOrder) + dbg = dbg.Order(orderBy) + } + + //err := dborm.DefaultDB().Table(tableName).Where(whereSql, params...).Find(&reports).Error + err = dbg.Find(&reports).Error + if err != nil { + c.JSON(http.StatusInternalServerError, services.ErrResp(err.Error())) + return + } + + c.JSON(http.StatusOK, services.TotalDataResp(reports, total)) +} + +func (k *KpiCReport) Total(c *gin.Context) { + var conditions []string + var params []any + + var querys KpiCReportQuery + if err := c.ShouldBindQuery(&querys); err != nil { + c.JSON(http.StatusBadRequest, services.ErrResp(err.Error())) + return + } + + // construct condition to get + if querys.NeType != "" { + conditions = append(conditions, "ne_type = ?") + params = append(params, strings.ToUpper(querys.NeType)) + } else { + c.JSON(http.StatusBadRequest, services.ErrResp("Not found NE type")) + return + } + tableName := TableName() + "_" + strings.ToLower(querys.NeType) + dbg := dborm.DefaultDB().Table(tableName) + + if querys.StartTime != "" { + conditions = append(conditions, "created_at >= ?") + params = append(params, querys.StartTime) + } + if querys.EndTime != "" { + conditions = append(conditions, "created_at <= ?") + params = append(params, querys.EndTime) + } + + whereSql := "" + if len(conditions) > 0 { + whereSql += strings.Join(conditions, " and ") + dbg = dbg.Where(whereSql, params...) + } + var total int64 = 0 + err := dbg.Count(&total).Error + if err != nil { + c.JSON(http.StatusInternalServerError, services.ErrResp(err.Error())) + return + } + + c.JSON(http.StatusOK, services.TotalResp(total)) +} + +func (k *KpiCReport) Post(c *gin.Context) { + var report KpiCReport + + if err := c.ShouldBindJSON(&report); err != nil { + c.JSON(http.StatusBadRequest, services.ErrResp(err.Error())) + return + } + if err := dborm.DefaultDB().Create(&report).Error; err != nil { + c.JSON(http.StatusInternalServerError, services.ErrResp(err.Error())) + return + } + c.JSON(http.StatusCreated, services.DataResp(report)) +} + +func (k *KpiCReport) Put(c *gin.Context) { + var report KpiCReport + id := c.Param("id") + + if err := dborm.DefaultDB().First(&report, id).Error; err != nil { + c.JSON(http.StatusNotFound, services.ErrResp("KPI report not found")) + return + } + + if err := c.ShouldBindJSON(&report); err != nil { + c.JSON(http.StatusBadRequest, services.ErrResp(err.Error())) + return + } + dborm.DefaultDB().Save(&report) + c.JSON(http.StatusOK, services.DataResp(report)) +} + +func (k *KpiCReport) Delete(c *gin.Context) { + id := c.Param("id") + + if err := dborm.DefaultDB().Delete(&KpiCReport{}, id).Error; err != nil { + c.JSON(http.StatusNotFound, services.ErrResp("KPI report not found")) + return + } + + c.JSON(http.StatusNoContent, nil) // 204 No Content +} + +func InsertKpiCReport(neType string, report KpiCReport) { + tableName := TableName() + "_" + strings.ToLower(neType) + if err := dborm.DefaultDB().Table(tableName).Create(&report).Error; err != nil { + return + } +} diff --git a/features/pm/kpi_c_report/model.go b/features/pm/kpi_c_report/model.go new file mode 100644 index 0000000..627040a --- /dev/null +++ b/features/pm/kpi_c_report/model.go @@ -0,0 +1,73 @@ +package kpi_c_report + +import ( + "database/sql/driver" + "encoding/json" + "fmt" + "time" +) + +type KpiCVal struct { + KPIID string `json:"kpi_id" gorm:"column:kpi_id"` + Value float64 `json:"value" gorm:"column:value"` + Err string `json:"err" gorm:"column:err"` +} + +type KpiCValues []KpiCVal + +type KpiCReport struct { + ID int `gorm:"column:id;primary_key;auto_increment" json:"id"` + NeType *string `gorm:"column:ne_type;default:NULL" json:"neType,omitempty"` + NeName *string `gorm:"column:ne_name;default:" json:"neName,omitempty"` + RmUID *string `gorm:"column:rm_uid;default:NULL" json:"rmUid,omitempty"` + Date string `gorm:"column:date" json:"date"` // time.Time `gorm:"column:date" json:"date"` + StartTime *string `gorm:"column:start_time;default:NULL" json:"startTime,omitempty"` + EndTime *string `gorm:"column:end_time;default:NULL" json:"endTime,omitempty"` + Index int16 `gorm:"column:index" json:"index"` + Granularity *int8 `gorm:"column:granularity;default:60" json:"granularity,omitempty"` //Time granualarity: 5/10/.../60/300 (second) + KpiValues KpiCValues `gorm:"column:kpi_values;type:json" json:"kpiValues,omitempty"` + CreatedAt *time.Time `gorm:"column:created_at;default:current_timestamp()" json:"createdAt,omitempty"` +} + +type KpiCReportQuery struct { + NeType string `json:"neType" form:"neType" binding:"required"` + NeID string `json:"neId" form:"neId" binding:"required"` + RmUID string `json:"rmUID" form:"rmUID"` + StartTime string `json:"startTime" form:"startTime"` + EndTime string `json:"endTime" form:"endTime"` + TenantName string `json:"tenantName" form:"tenantName"` + UserName string `json:"userName" form:"userName"` + SortField string `json:"sortField" form:"sortField" binding:"omitempty,oneof=created_at"` // 排序字段,填写结果字段 + SortOrder string `json:"sortOrder" form:"sortOrder" binding:"omitempty,oneof=asc desc"` // 排序升降序,asc desc + PageNum int `json:"pageNum" form:"pageNum"` + PageSize int `json:"pageSize" form:"pageSize"` +} + +type KpiCReport2FE struct { + NeType string `json:"neType" gorm:"column:ne_type"` + NeId string `json:"neId"` + NeName string `json:"neName" gorm:"column:ne_name"` + RmUID string `json:"rmUid" gorm:"column:rm_uid"` + TimeGroup string `json:"timeGroup"` + StartIndex int16 `json:"startIndex" gorm:"column:index"` + Granularity int8 `json:"granularity" gorm:"column:granularity"` + TenantID string `json:"tenantID" gorm:"column:tenant_id"` +} + +func TableName() string { + return "kpi_c_report" +} + +// 将 KpiCValues 转换为 JSON 字节 +func (k KpiCValues) Value() (driver.Value, error) { + return json.Marshal(k) +} + +// 从字节中扫描 KpiCValues +func (k *KpiCValues) Scan(value interface{}) error { + b, ok := value.([]byte) + if !ok { + return fmt.Errorf("failed to scan value: %v", value) + } + return json.Unmarshal(b, k) +} diff --git a/features/pm/kpi_c_report/route.go b/features/pm/kpi_c_report/route.go new file mode 100644 index 0000000..ee7b2b3 --- /dev/null +++ b/features/pm/kpi_c_report/route.go @@ -0,0 +1,44 @@ +package kpi_c_report + +import ( + "nms_cxy/src/framework/middleware" + + "github.com/gin-gonic/gin" +) + +// Register Routes for kpi_c_report +func Register(r *gin.RouterGroup) { + + pmKPIC := r.Group("/kpiC") + { + var k *KpiCReport + pmKPIC.GET("/report", + middleware.PreAuthorize(nil), + k.GetReport2FE, + ) + pmKPIC.GET("/report/list", + middleware.PreAuthorize(nil), + k.Get, + ) + pmKPIC.GET("/report/totalList", + middleware.PreAuthorize(nil), + k.Total, + ) + pmKPIC.GET("/report/total", + middleware.PreAuthorize(nil), + k.Total, + ) + pmKPIC.POST("/report", + middleware.PreAuthorize(nil), + k.Post, + ) + pmKPIC.PUT("/report/:id", + middleware.PreAuthorize(nil), + k.Put, + ) + pmKPIC.DELETE("/report/:id", + middleware.PreAuthorize(nil), + k.Delete, + ) + } +} diff --git a/features/pm/kpi_c_title/controller.go b/features/pm/kpi_c_title/controller.go new file mode 100644 index 0000000..959ac79 --- /dev/null +++ b/features/pm/kpi_c_title/controller.go @@ -0,0 +1,198 @@ +package kpi_c_title + +import ( + "fmt" + "net/http" + "strings" + + "nms_cxy/lib/dborm" + "nms_cxy/lib/services" + + "github.com/gin-gonic/gin" +) + +func (k *KpiCTitle) GetToalList(c *gin.Context) { + var titles []KpiCTitle + var conditions []string + var params []any + + var querys KpiCTitleQuery + if err := c.ShouldBindQuery(&querys); err != nil { + c.JSON(http.StatusBadRequest, services.ErrResp(err.Error())) + return + } + + dbg := dborm.DefaultDB().Table(k.TableName()) + // construct condition to get + if neType := querys.NeType; neType != "" { + conditions = append(conditions, "ne_type = ?") + params = append(params, strings.ToUpper(neType)) + } + if status := querys.Status; status != "" { + conditions = append(conditions, "status = ?") + params = append(params, status) + } + whereSql := "" + if len(conditions) > 0 { + whereSql += strings.Join(conditions, " and ") + dbg = dbg.Where(whereSql, params...) + } + + // Get total number + var total int64 = 0 + if err := dbg.Count(&total).Error; err != nil { + c.JSON(http.StatusInternalServerError, services.ErrResp(err.Error())) + return + } + + // page number and size + if pageSize := querys.PageSize; pageSize > 0 { + dbg = dbg.Limit(pageSize) + if pageNum := querys.PageNum; pageNum > 0 { + dbg = dbg.Offset((pageNum - 1) * pageSize) + } + } + + // order by + if sortField, sortOrder := querys.SortField, querys.SortOrder; sortField != "" && sortOrder != "" { + orderBy := fmt.Sprintf("%s %s", sortField, sortOrder) + dbg = dbg.Order(orderBy) + } + if err := dbg.Find(&titles).Error; err != nil { + c.JSON(http.StatusInternalServerError, services.ErrResp(err.Error())) + return + } + + c.JSON(http.StatusOK, services.TotalDataResp(titles, total)) + //c.JSON(http.StatusOK, titles) +} + +func (k *KpiCTitle) Get(c *gin.Context) { + var titles []KpiCTitle + var conditions []string + var params []any + + // construct condition to get + if neType := c.Query("neType"); neType != "" { + conditions = append(conditions, "ne_type = ?") + params = append(params, strings.ToUpper(neType)) + } + if status := c.Query("status"); status != "" { + conditions = append(conditions, "status = ?") + params = append(params, status) + } + whereSql := "" + if len(conditions) > 0 { + whereSql += strings.Join(conditions, " and ") + } + if err := dborm.DefaultDB().Where(whereSql, params...).Find(&titles).Error; err != nil { + c.JSON(http.StatusInternalServerError, services.ErrResp(err.Error())) + return + } + + c.JSON(http.StatusOK, services.DataResp(titles)) + //c.JSON(http.StatusOK, titles) +} + +func (k *KpiCTitle) Total(c *gin.Context) { + var conditions []string + var params []any + + // construct condition to get + if neType := c.Query("neType"); neType != "" { + conditions = append(conditions, "ne_type = ?") + params = append(params, strings.ToUpper(neType)) + } + if status := c.Query("status"); status != "" { + conditions = append(conditions, "status = ?") + params = append(params, status) + } + whereSql := "" + if len(conditions) > 0 { + whereSql += strings.Join(conditions, " and ") + } + var total int64 = 0 + if err := dborm.DefaultDB().Table(k.TableName()).Where(whereSql, params...).Count(&total).Error; err != nil { + c.JSON(http.StatusInternalServerError, services.ErrResp(err.Error())) + return + } + + c.JSON(http.StatusOK, services.TotalResp(total)) +} + +func (k *KpiCTitle) Post(c *gin.Context) { + var title KpiCTitle + + if err := c.ShouldBindJSON(&title); err != nil { + c.JSON(http.StatusBadRequest, services.ErrResp(err.Error())) + return + } + result := dborm.DefaultDB().Where("ne_type=? and kpi_id=?", title.NeType, title.KpiID).First(&title) + if result.RowsAffected > 0 { + c.JSON(http.StatusInternalServerError, services.ErrResp("target kpiC title already exist")) + return + } + if err := dborm.DefaultDB().Create(&title).Error; err != nil { + c.JSON(http.StatusInternalServerError, services.ErrResp(err.Error())) + return + } + kpiCReportTable := "kpi_c_report_" + strings.ToLower(*title.NeType) + if !dborm.DefaultDB().Migrator().HasTable(kpiCReportTable) { + // clone table "kpi_c_report" to "kpi_c_report_{neType}" + sql := fmt.Sprintf("CREATE TABLE IF NOT EXISTS %s AS SELECT * FROM %s WHERE 1=0", kpiCReportTable, "kpi_c_report") + if _, err := dborm.ExecSQL(sql, nil); err != nil { + c.JSON(http.StatusInternalServerError, services.ErrResp(err.Error())) + return + } + sql = fmt.Sprintf("ALTER TABLE %s MODIFY COLUMN `id` int(11) NOT NULL AUTO_INCREMENT FIRST,ADD PRIMARY KEY IF NOT EXISTS (`id`)", kpiCReportTable) + if _, err := dborm.ExecSQL(sql, nil); err != nil { + c.JSON(http.StatusInternalServerError, services.ErrResp(err.Error())) + return + } + sql = fmt.Sprintf("ALTER TABLE %s ADD INDEX IF NOT EXISTS `idx_timestamp`(`created_at`) USING BTREE, ADD INDEX IF NOT EXISTS `idx_uid_datetime`(`rm_uid`, `date`, `start_time`) USING BTREE", kpiCReportTable) + if _, err := dborm.ExecSQL(sql, nil); err != nil { + c.JSON(http.StatusInternalServerError, services.ErrResp(err.Error())) + return + } + } + c.JSON(http.StatusCreated, services.DataResp(title)) +} + +func (k *KpiCTitle) Put(c *gin.Context) { + var title KpiCTitle + id := c.Param("id") + + if err := dborm.DefaultDB().First(&title, id).Error; err != nil { + c.JSON(http.StatusNotFound, services.ErrResp("KPIC Title not found")) + return + } + + if err := c.ShouldBindJSON(&title); err != nil { + c.JSON(http.StatusBadRequest, services.ErrResp(err.Error())) + return + } + dborm.DefaultDB().Save(&title) + + c.JSON(http.StatusOK, services.DataResp(title)) +} + +func (k *KpiCTitle) Delete(c *gin.Context) { + id := c.Param("id") + + if err := dborm.DefaultDB().Delete(&KpiCTitle{}, id).Error; err != nil { + c.JSON(http.StatusNotFound, services.ErrResp("KPIC Title not found")) + return + } + + c.JSON(http.StatusNoContent, nil) // 204 No Content +} + +func GetActiveKPICList(neType string) []KpiCTitle { + k := new([]KpiCTitle) + + err := dborm.DefaultDB().Where("`ne_type` = ? and `status` = 'Active'", neType).Find(&k).Error + if err != nil { + return nil + } + return *k +} diff --git a/features/pm/kpi_c_title/model.go b/features/pm/kpi_c_title/model.go new file mode 100644 index 0000000..77bb942 --- /dev/null +++ b/features/pm/kpi_c_title/model.go @@ -0,0 +1,30 @@ +package kpi_c_title + +import "time" + +type KpiCTitle struct { + ID int `gorm:"column:id;primary_key;auto_increment" json:"id"` + NeType *string `gorm:"column:ne_type;default:NULL," json:"neType,omitempty"` + KpiID *string `gorm:"column:kpi_id;default:NULL," json:"kpiId,omitempty"` + Title *string `gorm:"column:title;default:NULL," json:"title,omitempty"` + Expression *string `gorm:"column:expression;default:NULL," json:"expression,omitempty"` + Status string `gorm:"column:status;default:'Active'" json:"status"` + Unit *string `gorm:"column:unit" json:"unit,omitempty"` + Description *string `gorm:"column:description;default:NULL," json:"description,omitempty"` + CreatedBy *string `gorm:"column:created_by;default:NULL," json:"createdBy,omitempty"` + UpdatedAt *time.Time `gorm:"column:updated_at;default:current_timestamp()," json:"updatedAt,omitempty"` +} + +type KpiCTitleQuery struct { + ID int `json:"id" form:"id"` + NeType string `json:"neType" form:"neType"` + Status string `json:"status" form:"status"` + SortField string `json:"sortField" form:"sortField" binding:"omitempty,oneof=created_at"` // 排序字段,填写结果字段 + SortOrder string `json:"sortOrder" form:"sortOrder" binding:"omitempty,oneof=asc desc"` // 排序升降序,asc desc + PageNum int `json:"pageNum" form:"pageNum"` + PageSize int `json:"pageSize" form:"pageSize"` +} + +func (k *KpiCTitle) TableName() string { + return "kpi_c_title" +} diff --git a/features/pm/kpi_c_title/route.go b/features/pm/kpi_c_title/route.go new file mode 100644 index 0000000..196c064 --- /dev/null +++ b/features/pm/kpi_c_title/route.go @@ -0,0 +1,40 @@ +package kpi_c_title + +import ( + "nms_cxy/src/framework/middleware" + + "github.com/gin-gonic/gin" +) + +// Register Routes for kpi_c_title +func Register(r *gin.RouterGroup) { + + pmKPIC := r.Group("/kpiC") + { + var k *KpiCTitle + pmKPIC.GET("/title", + middleware.PreAuthorize(nil), + k.Get, + ) + pmKPIC.GET("/title/total", + middleware.PreAuthorize(nil), + k.Total, + ) + pmKPIC.GET("/title/totalList", + middleware.PreAuthorize(nil), + k.GetToalList, + ) + pmKPIC.POST("/title", + middleware.PreAuthorize(nil), + k.Post, + ) + pmKPIC.PUT("/title/:id", + middleware.PreAuthorize(nil), + k.Put, + ) + pmKPIC.DELETE("/title/:id", + middleware.PreAuthorize(nil), + k.Delete, + ) + } +} diff --git a/features/pm/performance.go b/features/pm/performance.go index 1e77b1a..917815b 100644 --- a/features/pm/performance.go +++ b/features/pm/performance.go @@ -2,7 +2,6 @@ package pm import ( "encoding/json" - "errors" "fmt" "io" "math" @@ -11,19 +10,22 @@ import ( "strings" "time" + "nms_cxy/features/pm/kpi_c_report" + "nms_cxy/features/pm/kpi_c_title" "nms_cxy/lib/dborm" + evaluate "nms_cxy/lib/eval" "nms_cxy/lib/global" "nms_cxy/lib/log" "nms_cxy/lib/services" "nms_cxy/omc/config" - "xorm.io/xorm" - + neService "nms_cxy/src/modules/network_element/service" wsService "nms_cxy/src/modules/ws/service" "github.com/go-resty/resty/v2" _ "github.com/go-sql-driver/mysql" "github.com/gorilla/mux" + "xorm.io/xorm" ) type Response struct { @@ -228,14 +230,6 @@ func PostKPIReportFromNF(w http.ResponseWriter, r *http.Request) { granularity = int8(seconds) } - // 黄金指标事件对象 - kpiEvent := map[string]any{ - // kip_id ... - "neType": kpiReport.Task.NE.NeType, - "neName": kpiReport.Task.NE.NEName, - "startIndex": kpiIndex, - "timeGroup": startTime, - } // insert into new kpi_report_xxx table kpiData := new(KpiData) kpiData.Date = startTime @@ -250,6 +244,19 @@ func PostKPIReportFromNF(w http.ResponseWriter, r *http.Request) { kpiData.RmUid = kpiReport.Task.NE.RmUID kpiVal := new(KPIVal) kpiData.CreatedAt = time.Now().UnixMilli() + + // 黄金指标事件对象 + kpiEvent := map[string]any{ + // kip_id ... + "neType": kpiReport.Task.NE.NeType, + "neName": kpiReport.Task.NE.NEName, + "rmUID": kpiReport.Task.NE.RmUID, + "startIndex": kpiIndex, + "timeGroup": kpiData.CreatedAt, + } + + // for custom kpi + kpiValMap := map[string]any{} for _, k := range kpiReport.Task.NE.KPIs { kpiEvent[k.KPIID] = k.Value // kip_id @@ -257,7 +264,9 @@ func PostKPIReportFromNF(w http.ResponseWriter, r *http.Request) { kpiVal.Value = int64(k.Value) kpiVal.Err = k.Err kpiData.KPIValues = append(kpiData.KPIValues, *kpiVal) + kpiValMap[k.KPIID] = k.Value } + kpiValMap["granularity"] = kpiData.Granularity // insert kpi_report table, no session tableName := "kpi_report_" + strings.ToLower(kpiReport.Task.NE.NeType) @@ -268,15 +277,67 @@ func PostKPIReportFromNF(w http.ResponseWriter, r *http.Request) { return } - // 推送到ws订阅组 - wsService.NewWSSendImpl.ByGroupID(wsService.GROUP_KPI, kpiEvent) - if kpiReport.Task.NE.NeType == "UPF" { - wsService.NewWSSendImpl.ByGroupID(wsService.GROUP_KPI_UPF, kpiEvent) + report := kpi_c_report.KpiCReport{ + NeType: &kpiData.NEType, + NeName: &kpiData.NEName, + RmUID: &kpiData.RmUid, + Date: kpiData.Date, + StartTime: &kpiData.StartTime, + EndTime: &kpiData.EndTime, + Index: int16(kpiData.Index), + Granularity: &kpiData.Granularity, + } + + // 发送到匹配的网元 + neInfo := neService.NewNeInfoImpl.SelectNeInfoByRmuid(kpiData.RmUid) + // custom kpi report to FE + kpiCEvent := map[string]any{ + // kip_id ... + "neType": kpiData.NEType, + "neId": neInfo.NeId, + "neName": kpiData.NEName, + "rmUID": kpiData.RmUid, + "startIndex": kpiData.Index, + "timeGroup": kpiData.Date[:10] + " " + kpiData.StartTime, + "createdAt": kpiData.CreatedAt, + "granularity": kpiData.Granularity, + } + kpiCList := kpi_c_title.GetActiveKPICList(kpiData.NEType) + for _, k := range kpiCList { + result, err := evaluate.CalcExpr(*k.Expression, kpiValMap) + kpiCVal := new(kpi_c_report.KpiCVal) + kpiCVal.KPIID = *k.KpiID + if err != nil { + kpiCVal.Value = 0.0 + kpiCVal.Err = err.Error() + } else { + kpiCVal.Value = result + } + + report.KpiValues = append(report.KpiValues, *kpiCVal) + + // set KPIC event kpiid and value + kpiCEvent[kpiCVal.KPIID] = kpiCVal.Value + } + + // KPI自定义指标入库 + kpi_c_report.InsertKpiCReport(kpiData.NEType, report) + + if neInfo.RmUID == kpiData.RmUid { + // 推送到ws订阅组 + wsService.NewWSSendImpl.ByGroupID(fmt.Sprintf("%s%s_%s", wsService.GROUP_KPI, neInfo.NeType, neInfo.NeId), kpiEvent) + // 推送自定义KPI到ws订阅组 + wsService.NewWSSendImpl.ByGroupID(fmt.Sprintf("%s%s_%s", wsService.GROUP_KPI_C, neInfo.NeType, neInfo.NeId), kpiCEvent) + if neInfo.NeType == "UPF" { + // 推送标识为:12_RMUID, exp: 12_4400HXUPF001 + wsService.NewWSSendImpl.ByGroupID(wsService.GROUP_KPI_UPF+kpiReport.Task.NE.RmUID, kpiEvent) + } } services.ResponseStatusOK204NoContent(w) } +// PostGoldKPIFromNF 已废弃 // post kpi report from NEs, insert insto gold_kpi table, discard... func PostGoldKPIFromNF(w http.ResponseWriter, r *http.Request) { log.Debug("PostKPIReportFromNF processing... ") @@ -324,6 +385,7 @@ func PostGoldKPIFromNF(w http.ResponseWriter, r *http.Request) { // kip_id ... "neType": goldKpi.NEType, "neName": goldKpi.NEName, + "rmUID": goldKpi.RmUid, "startIndex": goldKpi.Index, "timeGroup": goldKpi.StartTime, } @@ -641,7 +703,7 @@ func PostMeasureTaskToNF(w http.ResponseWriter, r *http.Request) { return } if neInfo == nil { - err := errors.New(fmt.Sprintf("not found target NE neType=%s, neId=%s", neType, neId)) + err := fmt.Errorf("not found target NE neType=%s, neId=%s", neType, neId) log.Error(err) services.ResponseInternalServerError500ProcessError(w, err) return @@ -694,7 +756,7 @@ func PostMeasureTaskToNF(w http.ResponseWriter, r *http.Request) { return } default: - err = errors.New(fmt.Sprintf("measure task status must be inactive id=%d", id)) + err = fmt.Errorf("measure task status must be inactive id=%d", id) log.Error("Unable to active measure task:", err) services.ResponseInternalServerError500ProcessError(w, err) return @@ -721,7 +783,7 @@ func PostMeasureTaskToNF(w http.ResponseWriter, r *http.Request) { services.TransportResponse(w, response.StatusCode(), response.Body()) return } else { - err = errors.New(fmt.Sprintf("failed to active measure task, NF return error status=%v", response.Status())) + err = fmt.Errorf("failed to active measure task, NF return error status=%v", response.Status()) log.Error("Unable to active measure task:", err) services.ResponseInternalServerError500ProcessError(w, err) return @@ -934,8 +996,6 @@ func PatchMeasureTaskToNF(w http.ResponseWriter, r *http.Request) { return } if neInfo == nil { - em := errors.New("Not found NE info in database") - log.Error(em) taskInfo := new(dborm.MeasureTask) taskInfo.Status = dborm.MeasureTaskStatusInactive affected, err := dborm.XormUpdateTableById(id, dborm.TableNameMeasureTask, taskInfo) @@ -988,7 +1048,6 @@ func PatchMeasureTaskToNF(w http.ResponseWriter, r *http.Request) { } services.ResponseWithJson(w, response.StatusCode(), respMsg) - return } type Measurement struct { diff --git a/features/pm/service.go b/features/pm/service.go new file mode 100644 index 0000000..bbd0bed --- /dev/null +++ b/features/pm/service.go @@ -0,0 +1,20 @@ +package pm + +import ( + "nms_cxy/features/pm/kpi_c_report" + "nms_cxy/features/pm/kpi_c_title" + "nms_cxy/lib/log" + + "github.com/gin-gonic/gin" +) + +func InitSubServiceRoute(r *gin.Engine) { + log.Info("======init PM group gin.Engine") + + pmGroup := r.Group("/pm") + // register sub modules routes + kpi_c_title.Register(pmGroup) + kpi_c_report.Register(pmGroup) + + // return featuresGroup +} diff --git a/features/state/getstate.go b/features/state/getstate.go index 9bb7bb7..263d023 100644 --- a/features/state/getstate.go +++ b/features/state/getstate.go @@ -9,7 +9,7 @@ import ( "strings" "time" - "github.com/shirou/gopsutil/v3/net" + "github.com/shirou/gopsutil/v4/net" "github.com/go-resty/resty/v2" "github.com/gorilla/mux" @@ -506,11 +506,11 @@ func GetOneSysinfoFromNF(w http.ResponseWriter, r *http.Request) { if systemState.HostName != "" { hostName = systemState.HostName } - osInfo := "Linux 5gc 4.15.0-29-generic #31-Ubuntu SMP Tue Jul 17 15:39:52 UTC 2018 x86_64 GNU/Linux" + osInfo := "Linux 5gc 4.15.0-29-generic SMP Tue Jul 17 15:39:52 UTC 2018 x86_64 GNU/Linux" if systemState.OsInfo != "" { osInfo = systemState.OsInfo } - dbInfo := "kvdb v1.0.1" + dbInfo := "db v1.4.15" if systemState.OsInfo != "" { dbInfo = systemState.DbInfo } @@ -652,11 +652,11 @@ func GetAllSysinfoFromNF(w http.ResponseWriter, r *http.Request) { if systemState.HostName != "" { hostName = systemState.HostName } - osInfo := "Linux 5gc 4.15.0-29-generic #31-Ubuntu SMP Tue Jul 17 15:39:52 UTC 2018 x86_64 GNU/Linux" + osInfo := "Linux 5gc 4.15.0-29-generic SMP Tue Jul 17 15:39:52 UTC 2018 x86_64 GNU/Linux" if systemState.OsInfo != "" { osInfo = systemState.OsInfo } - dbInfo := "kvdb v1.0.1" + dbInfo := "db v1.4.15" if systemState.OsInfo != "" { dbInfo = systemState.DbInfo } diff --git a/features/state/state_linux.go b/features/state/state_linux.go index 54fae4a..91ccd19 100644 --- a/features/state/state_linux.go +++ b/features/state/state_linux.go @@ -13,10 +13,10 @@ import ( "nms_cxy/lib/log" - "github.com/shirou/gopsutil/v3/cpu" - "github.com/shirou/gopsutil/v3/disk" - "github.com/shirou/gopsutil/v3/mem" - "github.com/shirou/gopsutil/v3/process" + "github.com/shirou/gopsutil/v4/cpu" + "github.com/shirou/gopsutil/v4/disk" + "github.com/shirou/gopsutil/v4/mem" + "github.com/shirou/gopsutil/v4/process" ) type SysInfo struct { diff --git a/features/state/state_windows.go b/features/state/state_windows.go index ef37c5c..3ddf484 100644 --- a/features/state/state_windows.go +++ b/features/state/state_windows.go @@ -11,10 +11,10 @@ import ( "syscall" "time" - "github.com/shirou/gopsutil/v3/cpu" - "github.com/shirou/gopsutil/v3/disk" - "github.com/shirou/gopsutil/v3/mem" - "github.com/shirou/gopsutil/v3/process" + "github.com/shirou/gopsutil/v4/cpu" + "github.com/shirou/gopsutil/v4/disk" + "github.com/shirou/gopsutil/v4/mem" + "github.com/shirou/gopsutil/v4/process" ) type SysInfo struct { diff --git a/features/state/sysinfo.go b/features/state/sysinfo.go index 83b3f23..ba65ca9 100644 --- a/features/state/sysinfo.go +++ b/features/state/sysinfo.go @@ -3,55 +3,10 @@ package state import ( "nms_cxy/lib/log" - "github.com/shirou/gopsutil/cpu" - "github.com/shirou/gopsutil/disk" - "github.com/shirou/gopsutil/host" - "github.com/shirou/gopsutil/mem" + "github.com/shirou/gopsutil/v4/cpu" + "github.com/shirou/gopsutil/v4/mem" ) -func getSystemInfo() { - // 获取主机信息 - hostInfo, err := host.Info() - if err != nil { - log.Errorf("Failed to get host info: %v", err) - return - } - log.Tracef("Host info: %+v", hostInfo) - - // 获取CPU信息 - cpuInfo, err := cpu.Info() - if err != nil { - log.Errorf("Failed to get CPU info: %v", err) - return - } - log.Tracef("CPU info: %+v", cpuInfo) - - // 获取内存信息 - memInfo, err := mem.VirtualMemory() - if err != nil { - log.Errorf("Failed to get memory info: %v", err) - return - } - log.Tracef("Memory info: %+v", memInfo) - - // 获取磁盘分区信息 - diskPartitions, err := disk.Partitions(true) - if err != nil { - log.Errorf("Failed to get disk partitions: %v", err) - return - } - log.Tracef("Disk partitions: %+v", diskPartitions) - for _, partition := range diskPartitions { - // 获取每个磁盘分区的使用情况 - usage, err := disk.Usage(partition.Mountpoint) - if err != nil { - log.Errorf("Failed to get disk usage for %s: %v", partition.Mountpoint, err) - continue - } - log.Tracef("%s usage: %+v", partition.Mountpoint, usage) - } -} - func getCpuNumber() int { // 获取CPU信息 cpuInfo, err := cpu.Info() diff --git a/lib/dborm/dbgorm.go b/lib/dborm/dbgorm.go new file mode 100644 index 0000000..95f794b --- /dev/null +++ b/lib/dborm/dbgorm.go @@ -0,0 +1,161 @@ +package dborm + +import ( + "fmt" + "log" + "os" + "regexp" + "time" + + "gorm.io/driver/mysql" + "gorm.io/gorm" + "gorm.io/gorm/logger" +) + +// 数据库连接实例 +var dbgEngine *gorm.DB + +// 载入连接日志配置 +func loadLogger() logger.Interface { + newLogger := logger.New( + log.New(os.Stdout, "[GORM] ", log.LstdFlags), // 将日志输出到控制台 + logger.Config{ + SlowThreshold: time.Second, // Slow SQL 阈值 + LogLevel: logger.Info, // 日志级别 Silent不输出任何日志 + ParameterizedQueries: false, // 参数化查询SQL 用实际值带入?的执行语句 + Colorful: false, // 彩色日志输出 + }, + ) + return newLogger +} + +// 连接数据库实例 +func InitGormConnect(dbType, dbUser, dbPassword, dbHost, dbPort, dbName, dbParam, dbLogging any) error { + var dialector gorm.Dialector + switch dbType { + case "mysql": + dsn := fmt.Sprintf("%s:%s@tcp(%s:%s)/%s?%s", + dbUser, + dbPassword, + dbHost, + dbPort, + dbName, + dbParam, + ) + dialector = mysql.Open(dsn) + default: + err := fmt.Errorf("invalid type: %s", dbType) + return err + } + opts := &gorm.Config{} + // 是否需要日志输出 + if dbLogging.(bool) { + opts.Logger = loadLogger() + } + // 创建连接 + db, err := gorm.Open(dialector, opts) + if err != nil { + log.Fatalf("failed to open: %s", err) + return err + } + // 获取底层 SQL 数据库连接 + sqlDB, err := db.DB() + if err != nil { + log.Fatalf("failed to connect DB pool: %v", err) + return err + } + // 测试数据库连接 + err = sqlDB.Ping() + if err != nil { + log.Fatalf("failed to ping database: %v", err) + return err + } + dbgEngine = db + return nil +} + +// 关闭数据库实例 +func Close() { + sqlDB, err := dbgEngine.DB() + if err != nil { + log.Fatalf("failed to connect pool: %s", err) + } + if err := sqlDB.Close(); err != nil { + log.Fatalf("failed to close: %s", err) + } +} + +// 获取默认数据源 +func DefaultDB() *gorm.DB { + return dbgEngine +} + +// RawSQL 原生查询语句 +func RawSQL(sql string, parameters []any) ([]map[string]any, error) { + // 数据源 + db := DefaultDB() + + // 使用正则表达式替换连续的空白字符为单个空格 + fmtSql := regexp.MustCompile(`\s+`).ReplaceAllString(sql, " ") + + // logger.Infof("sql=> %v", fmtSql) + // logger.Infof("parameters=> %v", parameters) + + // 查询结果 + var rows []map[string]any + res := db.Raw(fmtSql, parameters...).Scan(&rows) + if res.Error != nil { + return nil, res.Error + } + return rows, nil +} + +// ExecSQL 原生执行语句 +func ExecSQL(sql string, parameters []any) (int64, error) { + // 数据源 + db := DefaultDB() + + // 使用正则表达式替换连续的空白字符为单个空格 + fmtSql := regexp.MustCompile(`\s+`).ReplaceAllString(sql, " ") + // 执行结果 + res := db.Exec(fmtSql, parameters...) + if res.Error != nil { + return 0, res.Error + } + return res.RowsAffected, nil +} + +func CloneTable(srcTable, dstTable string) error { + // 获取表 A 的结构信息 + var columns []gorm.ColumnType + dbMigrator := dbgEngine.Migrator() + columns, err := dbMigrator.ColumnTypes(srcTable) + if err != nil { + return fmt.Errorf("failed to ColumnTypes, %v", err) + } + + // 创建表 destination table + err = dbMigrator.CreateTable(dstTable) + if err != nil { + return fmt.Errorf("failed to CreateTable, %v", err) + } + // 复制表 src 的字段到表 dst + for _, column := range columns { + err = dbMigrator.AddColumn(dstTable, column.Name()) + if err != nil { + return fmt.Errorf("failed to AddColumn, %v", err) + } + } + + // 复制表 src 的主键和索引到表 dst + err = dbMigrator.CreateConstraint(dstTable, "PRIMARY") + if err != nil { + return fmt.Errorf("failed to AddColumn, %v", err) + } + + err = dbMigrator.CreateConstraint(dstTable, "INDEX") + if err != nil { + return fmt.Errorf("failed to AddColumn, %v", err) + } + return nil +} diff --git a/lib/eval/evaluate.go b/lib/eval/evaluate.go new file mode 100644 index 0000000..d0ff37e --- /dev/null +++ b/lib/eval/evaluate.go @@ -0,0 +1,111 @@ +package evaluate + +import ( + "fmt" + "go/ast" + "go/parser" + "go/token" + "regexp" + "strconv" + "strings" +) + +// Parse and caculate expression +func CalcExpr(expr string, paramValues map[string]any) (float64, error) { + // match parameter with '' + re := regexp.MustCompile(`'([^']+)'`) + matches := re.FindAllStringSubmatch(expr, -1) + + // replace to value + for _, match := range matches { + paramName := match[1] + value, exists := paramValues[paramName] + if !exists { + return 0, fmt.Errorf("parameter '%s' not found", paramName) + } + + expr = strings.Replace(expr, match[0], fmt.Sprintf("%v", value), 1) + } + + // expression to evaluate + result, err := evalExpr(expr) + return result, err +} + +// eval 解析和计算表达式 +func evalExpr(expr string) (float64, error) { + //fset := token.NewFileSet() + node, err := parser.ParseExpr(expr) + if err != nil { + return 0, err + } + return evalNode(node) +} + +// EvaluateExpr 解析并计算给定的表达式 +func EvalExpr(expr string, values map[string]any) (float64, error) { + // 解析表达式 + node, err := parser.ParseExpr(expr) + if err != nil { + return 0, err + } + + // 遍历 AST 并替换变量 + ast.Inspect(node, func(n ast.Node) bool { + if ident, ok := n.(*ast.Ident); ok { + if val, ok := values[ident.Name]; ok { + // 替换标识符为对应值 + ident.Name = fmt.Sprintf("%v", val) + } + } + return true + }) + + // 计算表达式 + return evalNode(node) +} + +// eval 递归计算 AST 节点 +func evalNode(node ast.Node) (float64, error) { + var result float64 + + switch n := node.(type) { + case *ast.BinaryExpr: + left, err := evalNode(n.X) + if err != nil { + return 0, err + } + right, err := evalNode(n.Y) + if err != nil { + return 0, err + } + switch n.Op { + case token.ADD: + result = left + right + case token.SUB: + result = left - right + case token.MUL: + result = left * right + case token.QUO: + result = left / right + } + case *ast.BasicLit: + var err error + result, err = strconv.ParseFloat(n.Value, 64) + if err != nil { + return 0, err + } + case *ast.Ident: + val, err := strconv.ParseFloat(n.Name, 64) + if err != nil { + return 0, fmt.Errorf("unsupported expression: %s", n.Name) + } + result = val + case *ast.ParenExpr: + return evalNode(n.X) // 递归评估括号中的表达式 + default: + return 0, fmt.Errorf("unsupported expression: %T", n) + } + + return result, nil +} diff --git a/lib/file/file.go b/lib/file/file.go index a03e354..7bc2a57 100644 --- a/lib/file/file.go +++ b/lib/file/file.go @@ -1,161 +1,27 @@ package file import ( - "fmt" - "net/http" "os" + "path/filepath" ) -// const ( -// //经过测试,linux下,延时需要大于100ms -// TIME_DELAY_AFTER_WRITE = 200 -// ) +func GetFileAndDirCount(dir string) (int, int, error) { + var fileCount, dirCount int -// type Response struct { -// Data []string `json:"data"` -// } + err := filepath.Walk(dir, func(path string, info os.FileInfo, err error) error { + if err != nil { + return err + } + if path == dir { + return nil // 跳过当前目录 + } + if info.IsDir() { + dirCount++ + } else { + fileCount++ + } + return nil + }) -// type MMLRequest struct { -// MML []string `json:"mml"` -// } - -// func GetFile(w http.ResponseWriter, r *http.Request) { -// log.Debug("PostMMLToNF processing... ") - -// vars := mux.Vars(r) -// neType := vars["elementTypeValue"] -// params := r.URL.Query() -// neId := params["ne_id"] -// log.Debug("neType:", neType, "neId", neId) - -// neInfo := new(dborm.NeInfo) -// var err error -// if len(neId) == 0 { -// log.Error("ne_id NOT FOUND") -// services.ResponseBadRequest400WrongParamValue(w) -// return -// } -// neInfo, err = dborm.XormGetNeInfo(neType, neId[0]) -// if err != nil { -// log.Error("dborm.XormGetNeInfo is failed:", err) -// services.ResponseInternalServerError500DatabaseOperationFailed(w) -// return -// } - -// var buf [8192]byte -// var n int -// var mmlResult []string - -// if neInfo != nil { -// hostMML := fmt.Sprintf("%s:%d", neInfo.Ip, config.GetYamlConfig().MML.Port) -// conn, err := net.Dial("tcp", hostMML) -// if err != nil { -// errMsg := fmt.Sprintf("Failed to dial %s: %v", hostMML, err) -// log.Error(errMsg) -// mmlResult = append(mmlResult, errMsg) -// response := Response{mmlResult} -// services.ResponseWithJson(w, http.StatusOK, response) -// return -// } - -// loginStr := fmt.Sprintf("%s\n%s\n", config.GetYamlConfig().MML.User, config.GetYamlConfig().MML.Password) -// n, err = conn.Write([]byte(loginStr)) -// if err != nil { -// log.Errorf("Error: %s", err.Error()) -// return -// } - -// time.Sleep(time.Millisecond * TIME_DELAY_AFTER_WRITE) - -// n, err = conn.Read(buf[0:]) -// if err != nil { -// log.Errorf("Error: %s", err.Error()) -// return -// } -// log.Debug(string(buf[0:n])) - -// body, err := io.ReadAll(io.LimitReader(r.Body, global.RequestBodyMaxLen)) -// if err != nil { -// log.Error("io.ReadAll is failed:", err) -// services.ResponseNotFound404UriNotExist(w, r) -// return -// } -// log.Debug("Body:", string(body)) - -// mmlRequest := new(MMLRequest) -// _ = json.Unmarshal(body, mmlRequest) - -// for _, mml := range mmlRequest.MML { -// mmlCommand := fmt.Sprintf("%s\n", mml) -// log.Debug("mml command:", mmlCommand) -// n, err = conn.Write([]byte(mmlCommand)) -// if err != nil { -// log.Errorf("Error: %s", err.Error()) -// return -// } -// time.Sleep(time.Millisecond * TIME_DELAY_AFTER_WRITE) - -// n, err = conn.Read(buf[0:]) -// if err != nil { -// log.Errorf("Error: %s", err.Error()) -// return -// } -// log.Debug(string(buf[0 : n-len(neType)-2])) -// mmlResult = append(mmlResult, string(buf[0:n-len(neType)-2])) -// } -// } - -// response := Response{mmlResult} -// services.ResponseWithJson(w, http.StatusOK, response) -// } - -// 格式文件大小单位 -func FormatFileSize(fileSize float64) (size string) { - if fileSize < 1024 { - return fmt.Sprintf("%.2fB", fileSize/float64(1)) - } else if fileSize < (1024 * 1024) { - return fmt.Sprintf("%.2fKB", fileSize/float64(1024)) - } else if fileSize < (1024 * 1024 * 1024) { - return fmt.Sprintf("%.2fMB", fileSize/float64(1024*1024)) - } else if fileSize < (1024 * 1024 * 1024 * 1024) { - return fmt.Sprintf("%.2fGB", fileSize/float64(1024*1024*1024)) - } else if fileSize < (1024 * 1024 * 1024 * 1024 * 1024) { - return fmt.Sprintf("%.2fTB", fileSize/float64(1024*1024*1024*1024)) - } else { - return fmt.Sprintf("%.2fEB", fileSize/float64(1024*1024*1024*1024*1024)) - } -} - -func IsSymlink(mode os.FileMode) bool { - return mode&os.ModeSymlink != 0 -} - -const dotCharacter = 46 - -func IsHidden(path string) bool { - return path[0] == dotCharacter -} - -func GetMimeType(path string) string { - file, err := os.Open(path) - if err != nil { - return "" - } - defer file.Close() - - buffer := make([]byte, 512) - _, err = file.Read(buffer) - if err != nil { - return "" - } - mimeType := http.DetectContentType(buffer) - return mimeType -} - -func GetSymlink(path string) string { - linkPath, err := os.Readlink(path) - if err != nil { - return "" - } - return linkPath + return fileCount, dirCount, err } diff --git a/lib/file/file_linux.go b/lib/file/file_linux.go new file mode 100644 index 0000000..9e459ef --- /dev/null +++ b/lib/file/file_linux.go @@ -0,0 +1,63 @@ +//go:build linux +// +build linux + +package file + +import ( + "fmt" + "os" + "path/filepath" + "syscall" +) + +type FileInfo struct { + FileType string `json:"fileType"` // 文件类型 + FileMode string `json:"fileMode"` // 文件的权限 + LinkCount int64 `json:"linkCount"` // 硬链接数目 + Owner string `json:"owner"` // 所属用户 + Group string `json:"group"` // 所属组 + Size int64 `json:"size"` // 文件的大小 + ModifiedTime int64 `json:"modifiedTime"` // 最后修改时间,单位为秒 + FileName string `json:"fileName"` // 文件的名称 +} + +func GetFileInfo(dir, suffix string) ([]FileInfo, error) { + var files []FileInfo + + err := filepath.Walk(dir, func(path string, info os.FileInfo, err error) error { + if err != nil { + return err + } + + if path == dir { + return nil // 跳过当前目录 + } + + fileType := "file" + if info.IsDir() { + fileType = "directory" + } else if info.Mode()&os.ModeSymlink != 0 { + fileType = "symlink" + } + + // check if match suffix + if (suffix != "" && filepath.Ext(path) == suffix) || suffix == "" { + fileInfo := FileInfo{ + FileType: fileType, + FileMode: info.Mode().String(), + LinkCount: int64(info.Sys().(*syscall.Stat_t).Nlink), + Owner: fmt.Sprintf("%d", info.Sys().(*syscall.Stat_t).Uid), + Group: fmt.Sprintf("%d", info.Sys().(*syscall.Stat_t).Gid), + Size: info.Size(), + ModifiedTime: info.ModTime().Unix(), + FileName: info.Name(), + } + files = append(files, fileInfo) + } + return nil + }) + if err != nil { + return nil, err + } + return files, nil +} diff --git a/lib/file/file_windows.go b/lib/file/file_windows.go new file mode 100644 index 0000000..5371d44 --- /dev/null +++ b/lib/file/file_windows.go @@ -0,0 +1,61 @@ +//go:build windows +// +build windows + +package file + +import ( + "os" + "path/filepath" +) + +type FileInfo struct { + FileType string `json:"fileType"` // 文件类型 + FileMode string `json:"fileMode"` // 文件的权限 + LinkCount int64 `json:"linkCount"` // 硬链接数目 + Owner string `json:"owner"` // 所属用户 + Group string `json:"group"` // 所属组 + Size int64 `json:"size"` // 文件的大小 + ModifiedTime int64 `json:"modifiedTime"` // 最后修改时间,单位为秒 + FileName string `json:"fileName"` // 文件的名称 +} + +func GetFileInfo(dir, suffix string) ([]FileInfo, error) { + var files []FileInfo + + err := filepath.Walk(dir, func(path string, info os.FileInfo, err error) error { + if err != nil { + return err + } + + if path == dir { + return nil // 跳过当前目录 + } + + fileType := "file" + if info.IsDir() { + fileType = "directory" + } else if info.Mode()&os.ModeSymlink != 0 { + fileType = "symlink" + } + + // check if match suffix + if (suffix != "" && filepath.Ext(path) == suffix) || suffix == "" { + fileInfo := FileInfo{ + FileType: fileType, + FileMode: info.Mode().String(), + LinkCount: 0, + Owner: "N/A", + Group: "N/A", + Size: info.Size(), + ModifiedTime: info.ModTime().Unix(), + FileName: info.Name(), + } + files = append(files, fileInfo) + } + return nil + }) + if err != nil { + return nil, err + } + return files, nil +} diff --git a/lib/oauth/oauth.go b/lib/oauth/oauth.go index a2019b2..f59454e 100644 --- a/lib/oauth/oauth.go +++ b/lib/oauth/oauth.go @@ -4,7 +4,6 @@ import ( "crypto/sha256" "crypto/sha512" "encoding/hex" - "fmt" "math/rand" "net/http" "strings" @@ -12,35 +11,9 @@ import ( "nms_cxy/lib/log" - "github.com/dgrijalva/jwt-go" "golang.org/x/crypto/bcrypt" ) -// GenToken 生成Token值 -func GenToken(mapClaims jwt.MapClaims) (string, error) { - token := jwt.NewWithClaims(jwt.SigningMethodHS256, mapClaims) - var nowDate = time.Now() - var secret = fmt.Sprintf("%v%v", nowDate, "xxxx") - return token.SignedString([]byte(secret)) -} - -// GenerateToken 生成Token值 -func GenerateToken(mapClaims jwt.MapClaims, key string) (string, error) { - token := jwt.NewWithClaims(jwt.SigningMethodHS256, mapClaims) - return token.SignedString([]byte(key)) -} - -// ParseToken: "解析token" -func ParseToken(token string, secret string) (string, error) { - claim, err := jwt.Parse(token, func(token *jwt.Token) (interface{}, error) { - return []byte(secret), nil - }) - if err != nil { - return "", err - } - return claim.Claims.(jwt.MapClaims)["cmd"].(string), nil -} - func RandAccessToken(n int) (ret string) { allString := "52661fbd-6b84-4fc2-aa1e-17879a5c6c9b" ret = "" diff --git a/lib/routes/routes.go b/lib/routes/routes.go index 83e938f..61c76a0 100644 --- a/lib/routes/routes.go +++ b/lib/routes/routes.go @@ -279,24 +279,15 @@ func init() { Register("GET", ue.UriNSSFSubscriptions, ue.GetSubscriptionsFromNSSF, nil) Register("GET", ue.CustomUriNSSFSubscriptions, ue.GetSubscriptionsFromNSSF, 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) + // cdr event + Register("POST", cdr.UriCDREvent, cdr.PostCDREventFrom, nil) + Register("POST", cdr.CustomUriCDREvent, cdr.PostCDREventFrom, nil) // UE event 上报的UE事件 Register("POST", event.UriUEEvent, event.PostUEEvent, nil) - // UE event AMF上报的UE事件, 无前缀给到Gin处理 //Register("POST", event.UriUEEvent, event.PostUEEventFromAMF, nil) - // 文件资源 - Register("GET", file.UriDiskList, file.DiskList, nil) - Register("POST", file.UriListFiles, file.ListFiles, nil) - // 数据库连接情况 Register("GET", dbrest.UriDbConnection, dbrest.DbConnection, nil) Register("GET", dbrest.CustomUriDbConnection, dbrest.DbConnection, nil) diff --git a/lib/services/response.go b/lib/services/response.go new file mode 100644 index 0000000..93f2442 --- /dev/null +++ b/lib/services/response.go @@ -0,0 +1,35 @@ +package services + +const ( + CODE_FAIL = 0 + CODE_SUCC = 1 +) + +func ErrResp(msg string) map[string]any { + return map[string]any{"code": CODE_FAIL, "message": msg} +} + +func DataResp(data any) map[string]any { + return map[string]any{"code": CODE_SUCC, "data": data} +} + +func SuccMessageResp() map[string]any { + return map[string]any{"code": CODE_SUCC, "message": "success"} +} + +func TotalResp(total int64) map[string]any { + return map[string]any{"code": CODE_SUCC, "total": total} +} + +func TotalDataResp(data any, total any) map[string]any { + return map[string]any{"code": CODE_SUCC, "data": data, "total": total} +} + +func SuccResp(va map[string]any) map[string]any { + resp := make(map[string]any) + resp["code"] = CODE_SUCC + for k, v := range va { + resp[k] = v + } + return resp +} diff --git a/omc/omc.go b/omc/omc.go index a7c5f84..742a72d 100644 --- a/omc/omc.go +++ b/omc/omc.go @@ -11,6 +11,7 @@ import ( _ "net/http/pprof" + "nms_cxy/features" "nms_cxy/features/dbrest" "nms_cxy/features/event" "nms_cxy/features/fm" @@ -30,6 +31,48 @@ import ( "golang.org/x/net/http2/h2c" ) +// const defaultConfigFile = "./etc/restconf.yaml" + +// func init() { +// cfile := flag.String("c", defaultConfigFile, "config file") +// pv := flag.Bool("v", false, "print version") +// ph := flag.Bool("h", false, "print help") + +// flag.Parse() +// if *pv { +// fmt.Printf("OMC restagent version: %s\n%s\n%s\n\n", global.Version, global.BuildTime, global.GoVer) +// os.Exit(0) +// } +// if *ph { +// flag.Usage() +// os.Exit(0) +// } + +// config.ReadConfig(*cfile) +// config.UriPrefix = config.GetYamlConfig().OMC.UriPrefix +// //fmt.Println(config.UriPrefix) +// } + +// func listenIPv6(ipv6 string, port int) { +// // +// addr := &net.TCPAddr{ +// IP: net.ParseIP(ipv6), +// Port: port, +// } + +// listener, err := net.ListenTCP("tcp6", addr) +// if err != nil { +// fmt.Println("Failed to listen:", err) +// return +// } + +// server := &http.Server{} +// err = server.Serve(listener) +// if err != nil { +// fmt.Println("Failed to serve:", err) +// } +// } + func HttpListen(addr string, router http.Handler) { // 创建HTTP服务器 h2s := &http2.Server{ @@ -40,6 +83,12 @@ func HttpListen(addr string, router http.Handler) { Handler: h2c.NewHandler(router, h2s), } + // // support http 2.0 server + // err := http2.ConfigureServer(server, &http2.Server{}) + // if err != nil { + // fmt.Println("ConfigureServer err:", err) + // os.Exit(11) + // } err := server.ListenAndServe() if err != nil { fmt.Println("ListenAndServe err:", err) @@ -165,6 +214,12 @@ func main() { fmt.Println("dborm.initDbClient err:", err) os.Exit(4) } + err = dborm.InitGormConnect(conf.Database.Type, conf.Database.User, conf.Database.Password, + conf.Database.Host, conf.Database.Port, conf.Database.Name, conf.Database.ConnParam, true) + if err != nil { + fmt.Println("dborm.InitGormConnect err:", err) + os.Exit(4) + } err = fm.InitDbClient(conf.Database.Type, conf.Database.User, conf.Database.Password, conf.Database.Host, conf.Database.Port, conf.Database.Name, conf.Database.ConnParam) if err != nil { @@ -204,6 +259,10 @@ func main() { // AMF上报的UE事件, 无前缀,暂时特殊处理 app.POST(event.UriUEEventAMF, event.PostUEEventFromAMF) + // register feature service gin.Engine + features.InitServiceEngine(app) + + // var listenLocalhost bool = false for _, rest := range conf.Rest { // ipv4 goroutines if rest.IPv4 != "" { @@ -214,6 +273,16 @@ func main() { go HttpListen(listen, app) } } + // if rest.IPv4 != "0.0.0.0" && !listenLocalhost { + // listenLocalhost = true + // // 默认启动localhost侦听 + // listenLocal := "127.0.0.1" + ":" + strconv.Itoa(int(rest.Port)) + // if strings.ToLower(rest.Scheme) == "https" { + // go HttpListenTLS(listenLocal, rest.CaFile, rest.CertFile, rest.KeyFile, rest.ClientAuthType, app) + // } else { + // go HttpListen(listenLocal, app) + // } + // } // ipv6 goroutines if rest.IPv6 != "" { listenv6 := "[" + rest.IPv6 + "]" + ":" + strconv.Itoa(int(rest.Port)) @@ -224,6 +293,15 @@ func main() { } } + // if rest.IPv6 != "::" { + // // 默认启动localhost侦听 + // listenv6Local := "[" + "::1" + "]" + ":" + strconv.Itoa(int(rest.Port)) + // if strings.ToLower(rest.Scheme) == "https" { + // go HttpListenTLS(listenv6Local, rest.CaFile, rest.CertFile, rest.KeyFile, app) + // } else { + // go HttpListen(listenv6Local, app) + // } + // } } if conf.WebServer.Enabled {