From 60004ded55d19ad5902ffed312b24742003db4be Mon Sep 17 00:00:00 2001 From: TsMask <340112800@qq.com> Date: Mon, 15 Sep 2025 09:26:58 +0800 Subject: [PATCH] =?UTF-8?q?ref:=20v3=E5=8F=98=E6=9B=B4,=EF=BC=8C=E5=A4=87?= =?UTF-8?q?=E4=BB=BD=E7=BC=BA=E5=B0=91csv=EF=BC=8C=E8=8F=9C=E5=8D=95?= =?UTF-8?q?=E5=90=8C=E7=BA=A7=E6=A3=80=E6=9F=A5=E5=90=8D=E7=A7=B0=EF=BC=8C?= =?UTF-8?q?ims=E5=BF=99=E6=97=B6=E5=91=A8=E7=BB=9F=E8=AE=A1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- build/database/std/install/sys_menu.sql | 2 + local/omc.yaml | 2 +- .../ne_alarm_state_check_cmd.go | 6 +- src/modules/ne/service/ne_config_backup.go | 22 ++- src/modules/ne_data/service/kpi_report.go | 150 ++++++++++++++++++ src/modules/ne_data_nf/controller/ims.go | 58 +++++++ src/modules/ne_data_nf/ne_data_nf.go | 4 + src/modules/system/repository/sys_menu.go | 2 +- 8 files changed, 239 insertions(+), 7 deletions(-) diff --git a/build/database/std/install/sys_menu.sql b/build/database/std/install/sys_menu.sql index 78f9bce6..191e6e40 100644 --- a/build/database/std/install/sys_menu.sql +++ b/build/database/std/install/sys_menu.sql @@ -130,6 +130,7 @@ INSERT INTO `sys_menu` VALUES (2008, 'menu.ueUser.n3iwf', 6, 18, 'n3iwf', 'neDat INSERT INTO `sys_menu` VALUES (2009, 'menu.neData.pcfSub', 5, 24, 'pcf-sub', 'neData/pcf-sub/index', '1', '0', 'M', '1', '1', 'pcf:sub:index', 'icon-paixu', '0', 'system', 1728641403588, 'system', 1728641403588, '', '0', '1', 'pcf'); INSERT INTO `sys_menu` VALUES (2010, 'menu.neUser.nssf', 6, 19, 'nssf', 'neData/nssf-sub/index', '1', '0', 'M', '1', '1', 'nssf:sub:index', 'icon-daimayingyong', '0', 'system', 1728641403588, 'system', 1728641403588, '', '0', '1', 'nssf'); INSERT INTO `sys_menu` VALUES (2011, 'menu.neUser.nssfAmf', 6, 20, 'nssfAmf', 'neData/nssf-amf/index', '1', '0', 'M', '1', '1', 'nssf:sub:index', 'icon-paixu', '0', 'system', 1728641403588, 'system', 1728641403588, '', '0', '1', 'nssf'); +INSERT INTO `sys_menu` VALUES (2082, 'menu.neCore.info', 8, 6, 'info', 'neCore/coreInfo/index', '1', '1', 'M', '1', '1', 'neCore:info:index', 'icon-xiangmuchengyuan', '0', 'system', 1728641403588, 'system', 1728641403588, '', '0', '1', ''); INSERT INTO `sys_menu` VALUES (2083, 'menu.trace', 2083, 30, 'trace', '', '1', '0', 'D', '1', '1', '', 'icon-paixu', '0', 'system', 1728641403588, 'system', 1728641403588, 'menu.traceRemark', '0', '0', ''); INSERT INTO `sys_menu` VALUES (2084, 'menu.trace.task', 4, 40, 'task', 'ne/trace/task/index', '1', '0', 'M', '1', '1', 'traceManage:task:index', 'icon-chexiao', '0', 'system', 1728641403588, 'system', 1744453890548, 'menu.trace.taskRemark', '0', '0', ''); INSERT INTO `sys_menu` VALUES (2085, 'menu.trace.taskData', 2084, 41, 'task/inline/data', 'ne/trace/task/data', '1', '0', 'M', '0', '1', 'traceManage:task:data', '#', '0', 'system', 1728641403588, 'system', 1744453921381, '', '0', '0', ''); @@ -200,6 +201,7 @@ INSERT INTO `sys_menu` VALUES (2155, 'menu.common.delete', 2154, 1, '#', '', '1' INSERT INTO `sys_menu` VALUES (2156, 'menu.common.edit', 2154, 2, '#', '', '1', '1', 'B', '1', '1', 'ne:neConfigBackup:edit', '#', '0', 'system', 1728641403588, 'system', 1728641403588, '', '0', '0', ''); INSERT INTO `sys_menu` VALUES (2157, 'menu.dashboard.smscCDR', 2140, 9, 'smsc-cdr', 'neData/smsc-cdr/index', '1', '0', 'M', '1', '1', 'smsc:cdr:index', 'icon-paixu', '0', 'system', 1728641403588, 'system', 1728641403588, '', '0', '1', 'smsc'); INSERT INTO `sys_menu` VALUES (2158, 'menu.trace.pcapFile', 60, 12, 'pcap/inline/file', 'tool/pcap/file', '1', '0', 'M', '0', '1', 'traceManage:pcap:index', '#', '0', 'system', 1728641403588, 'system', 1728641403588, '', '0', '0', ''); +INSERT INTO `sys_menu` VALUES (2159, 'menu.neCore.overview', 8, 2, 'overview', 'neCore/overview/index', '1', '1', 'M', '1', '1', 'neCore:overview:index', 'icon-xiangmuchengyuan', '0', 'system', 1728641403588, 'system', 1728641403588, '', '0', '1', ''); INSERT INTO `sys_menu` VALUES (2160, 'menu.perf.kpic', 2099, 100, 'kpic', 'perfManage/kpic/index', '1', '0', 'M', '1', '1', 'perfManage:kpiCReport:index', 'icon-tubiaoku', '0', 'system', 1728641403588, 'system', 1728641403588, '', '0', '0', ''); INSERT INTO `sys_menu` VALUES (2161, 'menu.trace.taskHLR', 4, 45, 'taskHLR', 'ne/trace/task-hlr/index', '1', '0', 'M', '0', '1', 'traceManage:taskHLR:index', 'icon-chexiao', '0', 'system', 1728641403588, 'system', 1728641403588, '', '0', '0', ''); INSERT INTO `sys_menu` VALUES (2162, 'menu.trace.taskAnalyze', 2084, 42, 'task/inline/analyze', 'ne/trace/task/analyze', '1', '0', 'M', '0', '1', 'traceManage:task:analyze', '#', '0', 'system', 1728641403588, 'system', 1728641403588, '', '0', '0', ''); diff --git a/local/omc.yaml b/local/omc.yaml index 4cf87a57..0f6165e6 100644 --- a/local/omc.yaml +++ b/local/omc.yaml @@ -1,5 +1,5 @@ # Service Type Single Core Network (oc) / Multi Core Network (mc) / Tenant Core Network (tc) -serverType: "oc" +serverType: "mc" # login authentication, default true # serverLoginAuth: true # interface encryption, default false diff --git a/src/modules/crontask/processor/ne_alarm_state_check_cmd/ne_alarm_state_check_cmd.go b/src/modules/crontask/processor/ne_alarm_state_check_cmd/ne_alarm_state_check_cmd.go index c64e29e3..80ed7928 100644 --- a/src/modules/crontask/processor/ne_alarm_state_check_cmd/ne_alarm_state_check_cmd.go +++ b/src/modules/crontask/processor/ne_alarm_state_check_cmd/ne_alarm_state_check_cmd.go @@ -25,9 +25,9 @@ import ( ) var ( - triggerMax int64 = 3 // 阈值:连续触发次数大于该值才会产生告警 - triggerCount sync.Map // 阈值连续触发次数,存储每个事件的触发记录 - triggerWindow time.Duration = 5 * time.Second // 事件触发的时间窗口 + triggerMax int64 = 3 // 阈值:连续触发次数大于该值才会产生告警 + triggerCount sync.Map // 阈值连续触发次数,存储每个事件的触发记录 + triggerWindow time.Duration = 30 * time.Second // 事件触发的时间窗口 ) var NewProcessor = &NeAlarmStateCheckCMDProcessor{ diff --git a/src/modules/ne/service/ne_config_backup.go b/src/modules/ne/service/ne_config_backup.go index 0299aee7..8fcb6549 100644 --- a/src/modules/ne/service/ne_config_backup.go +++ b/src/modules/ne/service/ne_config_backup.go @@ -120,14 +120,23 @@ func (s NeConfigBackup) FileLocalToNe(neInfo model.NeInfo, localFile string) err sshClient.RunCMD(fmt.Sprintf("sudo mkdir -p /usr/local/etc/rtproxy && sudo cp -rf %s/rtproxy/* /usr/local/etc/rtproxy && sudo chmod 777 /usr/local/etc/rtproxy/rtproxy.conf", neDirTemp)) // iwf目录 sshClient.RunCMD(fmt.Sprintf("sudo mkdir -p /usr/local/etc/iwf && sudo cp -rf %s/iwf/* /usr/local/etc/iwf && sudo chmod 777 /usr/local/etc/iwf/*.yaml", neDirTemp)) + case "udm": + // udm目录 + chmodFile := "sudo chmod 777 /usr/local/etc/udm/*.yaml" + sshClient.RunCMD(fmt.Sprintf("sudo cp -rf %s/*.yaml /usr/local/etc/udm && %s", neDirTemp, chmodFile)) + // kvdb目录 + sshClient.RunCMD(fmt.Sprintf("sudo mkdir -p /usr/local/etc/kvdb && sudo cp -rf %s/kvdb/* /usr/local/etc/kvdb && sudo chmod 777 /usr/local/etc/kvdb/*.{rdb,conf}", neDirTemp)) case "smsc": chmodFile := "sudo chmod 777 /usr/local/etc/smsc/{*sys.conf,*conf.txt,conf/is41_operation.conf}" sshClient.RunCMD(fmt.Sprintf("sudo mkdir -p /usr/local/etc/smsc/conf && sudo cp -rf %s/* /usr/local/etc/smsc && %s", neDirTemp, chmodFile)) default: neEtcPath := fmt.Sprintf("/usr/local/etc/%s", neTypeLower) chmodFile := fmt.Sprintf("sudo chmod 777 %s/*.yaml", neEtcPath) + if neTypeLower == "amf" { + chmodFile = fmt.Sprintf("sudo chmod 777 %s/*.{yaml,csv}", neEtcPath) + } if neTypeLower == "mme" { - chmodFile = fmt.Sprintf("sudo chmod 777 %s/*.{yaml,conf}", neEtcPath) + chmodFile = fmt.Sprintf("sudo chmod 777 %s/*.{yaml,conf,csv}", neEtcPath) } sshClient.RunCMD(fmt.Sprintf("sudo cp -rf %s/* %s && %s", neDirTemp, neEtcPath, chmodFile)) } @@ -177,13 +186,22 @@ func (s NeConfigBackup) FileNeToLocal(neInfo model.NeInfo) (string, error) { sshClient.RunCMD(fmt.Sprintf("mkdir -p %s/rtproxy && sudo cp -rf /usr/local/etc/rtproxy/rtproxy.conf %s/rtproxy", neDirTemp, neDirTemp)) // iwf目录 sshClient.RunCMD(fmt.Sprintf("mkdir -p %s/iwf && sudo cp -rf /usr/local/etc/iwf/*.yaml %s/iwf", neDirTemp, neDirTemp)) + case "udm": + // udm目录 + sshClient.RunCMD(fmt.Sprintf("mkdir -p %s && sudo cp -rf /usr/local/etc/udm/*.yaml %s", neDirTemp, neDirTemp)) + // kvdb目录 + sshClient.RunCMD(fmt.Sprintf("mkdir -p %s/kvdb && sudo cp -rf /usr/local/etc/kvdb/*.{rdb,conf} %s/kvdb", neDirTemp, neDirTemp)) + sshClient.RunCMD(fmt.Sprintf("mkdir -p %s/kvdb && sudo cp -rf /usr/local/etc/kvdb/log %s/kvdb", neDirTemp, neDirTemp)) case "smsc": sshClient.RunCMD(fmt.Sprintf("mkdir -p %s && sudo cp -rf /usr/local/etc/smsc/{*.yaml,*.conf,*conf.txt} %s", neDirTemp, neDirTemp)) sshClient.RunCMD(fmt.Sprintf("sudo cp -rf /usr/local/etc/smsc/conf %s/conf", neDirTemp)) default: nePath := fmt.Sprintf("/usr/local/etc/%s/*.yaml", neTypeLower) + if neTypeLower == "amf" { + nePath = fmt.Sprintf("/usr/local/etc/%s/*.{yaml,csv}", neTypeLower) + } if neTypeLower == "mme" { - nePath = fmt.Sprintf("/usr/local/etc/%s/*.{yaml,conf}", neTypeLower) + nePath = fmt.Sprintf("/usr/local/etc/%s/*.{yaml,conf,csv}", neTypeLower) } sshClient.RunCMD(fmt.Sprintf("mkdir -p %s && sudo cp -rf %s %s", neDirTemp, nePath, neDirTemp)) } diff --git a/src/modules/ne_data/service/kpi_report.go b/src/modules/ne_data/service/kpi_report.go index af9cf992..a0a82178 100644 --- a/src/modules/ne_data/service/kpi_report.go +++ b/src/modules/ne_data/service/kpi_report.go @@ -349,3 +349,153 @@ func (r KpiReport) IMSBusyHour(coreUid string, neUid string, timestamp int64) [] } return data } + +// 定义结构体用于存储话务量值和对应的时间 +type TrafficData struct { + Time int64 `json:"time"` // 时间戳(毫秒) + Value float64 `json:"value"` // 话务量值 +} + +// IMSBusyWeek IMS忙时流量统计 周 +func (r KpiReport) IMSBusyWeek(coreUid string, neUid string, weekStart, weekEnd int64) map[string]any { + weekStartTime := time.UnixMilli(weekStart) + weekEndTime := time.UnixMilli(weekEnd) + + // 1. 获取一周内每小时的呼叫数据 + // 转换为毫秒级时间戳 + rows := r.kpiReportRepository.SelectIMS(coreUid, neUid, weekStartTime.UnixMilli(), weekEndTime.UnixMilli()) + + // 创建一个map来存储按时间段合并后的数据 + timeGroup := make(map[int64]map[string]int64) + // 遍历每个数据项 + for _, row := range rows { + // 将毫秒时间戳转换为小时级时间戳(保留到小时的起始毫秒) + timeHour := row.CreatedAt / 3600000 * 3600000 // 1小时 = 3600000毫秒 + + // 解析 JSON 字符串为 map + var kpiValues []map[string]any + err := json.Unmarshal([]byte(row.KpiValues), &kpiValues) + if err != nil { + continue + } + + var callAttempts, callCompletions int64 + for _, v := range kpiValues { + if k, ok := v["kpiId"]; ok { + if k == "SCSCF.06" { + callAttempts = parse.Number(v["value"]) + } + if k == "SCSCF.09" { + callCompletions = parse.Number(v["value"]) + } + } + } + // 合并到对应的小时段 + if _, exists := timeGroup[timeHour]; !exists { + timeGroup[timeHour] = map[string]int64{ + "callAttempts": 0, + "callCompletions": 0, + } + } + timeGroup[timeHour]["callAttempts"] += callAttempts + timeGroup[timeHour]["callCompletions"] += callCompletions + } + + // 时间组合输出 + data := make([]map[string]any, 0, len(timeGroup)) + for hour, sums := range timeGroup { + data = append(data, map[string]any{ + "timeGroup": fmt.Sprintf("%d", hour), + "callAttempts": sums["callAttempts"], + "callCompletions": sums["callCompletions"], + }) + } + + if len(data) == 0 { + return map[string]any{ + "busyHourAverageBHCA": 0, + "busyHourAverageBHCC": 0, + "topFourHoursBHCA": []float64{}, + "topFourHoursBHCC": []float64{}, + "totalHours": 0, + } + } + + // 2. 分离BHCA和BHCC数据,并按降序排序 + var bhcaData []TrafficData + var bhccData []TrafficData + + for _, row := range data { + // 获取时间戳 + timeValue := int64(0) + if t, ok := row["timeGroup"]; ok { + timeValue = parse.Number(t) + } + + // 处理BHCA数据 + if value, ok := row["callAttempts"]; ok { + bhcaVal := parse.Number(value) + bhcaData = append(bhcaData, TrafficData{ + Time: timeValue, + Value: float64(bhcaVal), + }) + } + + // 处理BHCC数据 + if value, ok := row["callCompletions"]; ok { + bhccVal := parse.Number(value) + bhccData = append(bhccData, TrafficData{ + Time: timeValue, + Value: float64(bhccVal), + }) + } + } + // 按降序排序(值大的在前) + sort.Slice(bhcaData, func(i, j int) bool { return bhcaData[i].Value > bhcaData[j].Value }) + sort.Slice(bhccData, func(i, j int) bool { return bhccData[i].Value > bhccData[j].Value }) + + // 3. 取前四个最高值并计算平均值 + topFourBHCA := getTopFourTrafficData(bhcaData) + topFourBHCC := getTopFourTrafficData(bhccData) + + avgBHCA := calculateTrafficDataAverage(topFourBHCA) + avgBHCC := calculateTrafficDataAverage(topFourBHCC) + + // 4. 返回结果 + return map[string]any{ + "busyHourAverageBHCA": avgBHCA, + "busyHourAverageBHCC": avgBHCC, + "topFourHoursBHCA": topFourBHCA, + "topFourHoursBHCC": topFourBHCC, + "totalHours": len(data), + } +} + +// 辅助函数:获取前四个最高值的TrafficData +func getTopFourTrafficData(data []TrafficData) []TrafficData { + if len(data) == 0 { + return []TrafficData{} + } + + // 最多取前四个值 + maxCount := 4 + if len(data) < maxCount { + maxCount = len(data) + } + + return data[:maxCount] +} + +// 辅助函数:计算TrafficData的平均值 +func calculateTrafficDataAverage(data []TrafficData) float64 { + if len(data) == 0 { + return 0 + } + + var sum float64 = 0 + for _, v := range data { + sum += v.Value + } + + return sum / float64(len(data)) +} diff --git a/src/modules/ne_data_nf/controller/ims.go b/src/modules/ne_data_nf/controller/ims.go index 7e25ad0f..a7df9b73 100644 --- a/src/modules/ne_data_nf/controller/ims.go +++ b/src/modules/ne_data_nf/controller/ims.go @@ -298,3 +298,61 @@ func (s IMSController) KPIBusyHour(c *gin.Context) { data := s.kpiReportService.IMSBusyHour(neInfo.CoreUID, neInfo.NeUID, query.Timestamp) c.JSON(200, resp.OkData(data)) } + +// KPI 忙时统计 周 +// +// GET /kpi/busy-week +// +// @Tags network_data/ims +// @Accept json +// @Produce json +// @Param neId query string true "NE ID" default(001) +// @Param timestamp query int64 false "timestamp" +// @Success 200 {object} object "Response Results" +// @Security TokenAuth +// @Summary Busy week statistics +// @Description Busy week statistics +// @Router /neData/ims/kpi/busy-week [get] +func (s IMSController) KPIBusyWeek(c *gin.Context) { + language := reqctx.AcceptLanguage(c) + var query struct { + CoreUID string `form:"coreUid" binding:"required"` // 核心网唯一标识 + NeUID string `form:"neUid" binding:"required"` // 网元唯一标识 + WeekStart int64 `form:"weekStart" binding:"required"` // 时间戳毫秒 年月日 + WeekEnd int64 `form:"weekEnd" binding:"required"` // 时间戳毫秒 年月日 + } + if err := c.ShouldBindQuery(&query); err != nil { + errMsgs := fmt.Sprintf("bind err: %s", resp.FormatBindError(err)) + c.JSON(422, resp.CodeMsg(resp.CODE_PARAM_PARSER, errMsgs)) + return + } + if query.WeekStart < 1e12 || query.WeekStart > 1e13 { + c.JSON(422, resp.CodeMsg(resp.CODE_PARAM_CHEACK, "weekStart format is ms")) + return + } + if query.WeekEnd < 1e12 || query.WeekEnd > 1e13 { + c.JSON(422, resp.CodeMsg(resp.CODE_PARAM_CHEACK, "weekEnd format is ms")) + return + } + if query.WeekEnd < query.WeekStart || query.WeekEnd == query.WeekStart { + c.JSON(422, resp.CodeMsg(resp.CODE_PARAM_CHEACK, "weekEnd must be greater than weekStart and not equal to weekStart")) + return + } + // 计算周差 + weekDiff := query.WeekEnd - query.WeekStart + // 周差是否7天 + if weekDiff-7*24*60*60*1000 != -1000 { + c.JSON(422, resp.CodeMsg(resp.CODE_PARAM_CHEACK, "weekEnd must be 7 days after weekStart")) + return + } + + // 查询网元获取IP + neInfo := s.neInfoService.FindByCoreUidAndNeUid(query.CoreUID, query.NeUID) + if neInfo.CoreUID != query.CoreUID || neInfo.NeUID != query.NeUID { + c.JSON(200, resp.ErrMsg(i18n.TKey(language, "app.common.noNEInfo"))) + return + } + + data := s.kpiReportService.IMSBusyWeek(neInfo.CoreUID, neInfo.NeUID, query.WeekStart, query.WeekEnd) + c.JSON(200, resp.OkData(data)) +} diff --git a/src/modules/ne_data_nf/ne_data_nf.go b/src/modules/ne_data_nf/ne_data_nf.go index 9399b799..d1d54fef 100644 --- a/src/modules/ne_data_nf/ne_data_nf.go +++ b/src/modules/ne_data_nf/ne_data_nf.go @@ -45,6 +45,10 @@ func Setup(router *gin.Engine) { middleware.AuthorizeUser(nil), controller.NewIMS.KPIBusyHour, ) + imsGroup.GET("/kpi/busy-week", + middleware.AuthorizeUser(nil), + controller.NewIMS.KPIBusyWeek, + ) } // 网元SMSC diff --git a/src/modules/system/repository/sys_menu.go b/src/modules/system/repository/sys_menu.go index 146655dc..c1ea5ed0 100644 --- a/src/modules/system/repository/sys_menu.go +++ b/src/modules/system/repository/sys_menu.go @@ -167,7 +167,7 @@ func (r SysMenu) CheckUnique(sysMenu model.SysMenu) int64 { tx := db.DB("").Model(&model.SysMenu{}) tx = tx.Where("del_flag = '0'") // 查询条件拼接 - if sysMenu.ParentId <= 0 { + if sysMenu.ParentId > 0 { tx = tx.Where("parent_id = ?", sysMenu.ParentId) } if sysMenu.MenuName != "" {