From e6013789d1cccee48e9f70ca21af61e30fa4af8e Mon Sep 17 00:00:00 2001 From: TsMask <340112800@qq.com> Date: Wed, 24 Jan 2024 11:51:27 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=96=B0=E5=A2=9E=E7=BD=91=E5=85=83?= =?UTF-8?q?=E6=95=B0=E6=8D=AE=E6=A8=A1=E5=9D=97?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/app.go | 5 +- .../network_data/controller/cdr_event.go | 51 +++++++ .../network_data/controller/perf_kpi.go | 80 +++++++++++ src/modules/network_data/model/cdr_event.go | 27 ++++ src/modules/network_data/model/perf_kpi.go | 23 +++ src/modules/network_data/network_data.go | 39 +++++ .../network_data/repository/cdr_event.go | 9 ++ .../network_data/repository/cdr_event.impl.go | 133 ++++++++++++++++++ .../network_data/repository/perf_kpi.go | 12 ++ .../network_data/repository/perf_kpi.impl.go | 90 ++++++++++++ src/modules/network_data/service/cdr_event.go | 9 ++ .../network_data/service/cdr_event.impl.go | 22 +++ src/modules/network_data/service/perf_kpi.go | 12 ++ .../network_data/service/perf_kpi.impl.go | 38 +++++ 14 files changed, 549 insertions(+), 1 deletion(-) create mode 100644 src/modules/network_data/controller/cdr_event.go create mode 100644 src/modules/network_data/controller/perf_kpi.go create mode 100644 src/modules/network_data/model/cdr_event.go create mode 100644 src/modules/network_data/model/perf_kpi.go create mode 100644 src/modules/network_data/network_data.go create mode 100644 src/modules/network_data/repository/cdr_event.go create mode 100644 src/modules/network_data/repository/cdr_event.impl.go create mode 100644 src/modules/network_data/repository/perf_kpi.go create mode 100644 src/modules/network_data/repository/perf_kpi.impl.go create mode 100644 src/modules/network_data/service/cdr_event.go create mode 100644 src/modules/network_data/service/cdr_event.impl.go create mode 100644 src/modules/network_data/service/perf_kpi.go create mode 100644 src/modules/network_data/service/perf_kpi.impl.go diff --git a/src/app.go b/src/app.go index 713fe00c..939df23c 100644 --- a/src/app.go +++ b/src/app.go @@ -12,6 +12,7 @@ import ( "ems.agt/src/modules/common" "ems.agt/src/modules/crontask" "ems.agt/src/modules/monitor" + networkdata "ems.agt/src/modules/network_data" networkelement "ems.agt/src/modules/network_element" "ems.agt/src/modules/system" "ems.agt/src/modules/trace" @@ -118,8 +119,10 @@ func initModulesRoute(app *gin.Engine) { common.Setup(app) // 系统模块 system.Setup(app) - // 网元模块 + // 网元功能模块 networkelement.Setup(app) + // 网元数据模块 + networkdata.Setup(app) // 跟踪模块 trace.Setup(app) // 图表模块 diff --git a/src/modules/network_data/controller/cdr_event.go b/src/modules/network_data/controller/cdr_event.go new file mode 100644 index 00000000..648147ed --- /dev/null +++ b/src/modules/network_data/controller/cdr_event.go @@ -0,0 +1,51 @@ +package controller + +import ( + "ems.agt/src/framework/i18n" + "ems.agt/src/framework/utils/ctx" + "ems.agt/src/framework/vo/result" + "ems.agt/src/modules/network_data/model" + neDataService "ems.agt/src/modules/network_data/service" + neService "ems.agt/src/modules/network_element/service" + "github.com/gin-gonic/gin" +) + +// 实例化控制层 PerfKPIController 结构体 +var NewCDREventController = &CDREventController{ + neInfoService: neService.NewNeInfoImpl, + cdrEventService: neDataService.NewCDREventImpl, +} + +// CDR会话事件 +// +// PATH /cdr +type CDREventController struct { + // 网元信息服务 + neInfoService neService.INeInfo + // CDR会话事件服务 + cdrEventService neDataService.ICDREvent +} + +// CDR会话列表 +// +// GET /list +func (s *CDREventController) List(c *gin.Context) { + language := ctx.AcceptLanguage(c) + var querys model.CDREventQuery + if err := c.ShouldBindQuery(&querys); err != nil { + c.JSON(400, result.CodeMsg(400, i18n.TKey(language, "app.common.err400"))) + return + } + + // 查询网元获取IP + neInfo := s.neInfoService.SelectNeInfoByNeTypeAndNeID(querys.NeType, querys.NeID) + if neInfo.NeId != querys.NeID || neInfo.IP == "" { + c.JSON(200, result.ErrMsg(i18n.TKey(language, "app.common.noNEInfo"))) + return + } + querys.RmUID = neInfo.RmUID + + // 查询数据 + data := s.cdrEventService.SelectPage(querys) + c.JSON(200, result.Ok(data)) +} diff --git a/src/modules/network_data/controller/perf_kpi.go b/src/modules/network_data/controller/perf_kpi.go new file mode 100644 index 00000000..996c51e2 --- /dev/null +++ b/src/modules/network_data/controller/perf_kpi.go @@ -0,0 +1,80 @@ +package controller + +import ( + "ems.agt/lib/core/utils/date" + "ems.agt/src/framework/i18n" + "ems.agt/src/framework/utils/ctx" + "ems.agt/src/framework/vo/result" + neDataService "ems.agt/src/modules/network_data/service" + "ems.agt/src/modules/network_element/model" + neService "ems.agt/src/modules/network_element/service" + "github.com/gin-gonic/gin" +) + +// 实例化控制层 PerfKPIController 结构体 +var NewPerfKPIController = &PerfKPIController{ + neInfoService: neService.NewNeInfoImpl, + perfKPIService: neDataService.NewPerfKPIImpl, +} + +// 性能统计 +// +// PATH /kpi +type PerfKPIController struct { + // 网元信息服务 + neInfoService neService.INeInfo + // 统计信息服务 + perfKPIService neDataService.IPerfKPI +} + +// 获取统计数据 +// +// GET /data +func (s *PerfKPIController) GoldKPI(c *gin.Context) { + language := ctx.AcceptLanguage(c) + var querys model.GoldKPIQuery + if err := c.ShouldBindQuery(&querys); err != nil { + c.JSON(400, result.CodeMsg(400, i18n.TKey(language, "app.common.err400"))) + return + } + + // 时间格式校验 + startTime := date.ParseStrToDate(querys.StartTime, date.YYYY_MM_DD_HH_MM_SS) + if startTime.IsZero() { + c.JSON(400, result.CodeMsg(400, i18n.TKey(language, "app.common.err400"))) + return + } + endTime := date.ParseStrToDate(querys.EndTime, date.YYYY_MM_DD_HH_MM_SS) + if endTime.IsZero() { + c.JSON(400, result.CodeMsg(400, i18n.TKey(language, "app.common.err400"))) + return + } + + // 查询网元获取IP + neInfo := s.neInfoService.SelectNeInfoByNeTypeAndNeID(querys.NeType, querys.NeID) + if neInfo.NeId != querys.NeID || neInfo.IP == "" { + c.JSON(200, result.ErrMsg(i18n.TKey(language, "app.common.noNEInfo"))) + return + } + querys.RmUID = neInfo.RmUID + + // 查询数据 + kpiData := s.perfKPIService.SelectGoldKPI(querys) + c.JSON(200, result.OkData(kpiData)) +} + +// 获取统计标题 +// +// GET /title +func (s *PerfKPIController) Title(c *gin.Context) { + language := ctx.AcceptLanguage(c) + neType := c.Query("neType") + if neType == "" { + c.JSON(400, result.CodeMsg(400, i18n.TKey(language, "app.common.err400"))) + return + } + + kpiTitles := s.perfKPIService.SelectGoldKPITitle(neType) + + c.JSON(200, result.OkData(kpiTitles)) +} diff --git a/src/modules/network_data/model/cdr_event.go b/src/modules/network_data/model/cdr_event.go new file mode 100644 index 00000000..7b2381ae --- /dev/null +++ b/src/modules/network_data/model/cdr_event.go @@ -0,0 +1,27 @@ +package model + +import "time" + +// CDREvent CDR会话对象 cdr_event +type CDREvent struct { + ID string `json:"id" gorm:"column:id;primaryKey;autoIncrement"` + NeType string `json:"neType" gorm:"column:ne_type"` + NeName string `json:"neName" gorm:"column:ne_name"` + RmUID string `json:"rmUID" gorm:"column:rm_uid"` + Timestamp int64 `json:"timestamp" gorm:"column:timestamp"` + CDRJSONStr string `json:"cdrJson" gorm:"column:cdr_json"` + CreatedAt time.Time `json:"createdAt" gorm:"column:created_at;default:CURRENT_TIMESTAMP"` +} + +// CDREventQuery CDR会话对象查询参数结构体 +type CDREventQuery struct { + NeType string `form:"neType" binding:"required"` + NeID string `form:"neId" binding:"required"` + RmUID string `form:"rmUID"` + StartTime string `form:"startTime" ` + EndTime string `form:"endTime"` + SortField string `form:"sortField" binding:"omitempty,oneof=timestamp"` + SortOrder string `form:"sortOrder" binding:"omitempty,oneof=asc desc"` + PageNum int64 `form:"pageNum" binding:"required"` + PageSize string `form:"pageSize" binding:"required"` +} diff --git a/src/modules/network_data/model/perf_kpi.go b/src/modules/network_data/model/perf_kpi.go new file mode 100644 index 00000000..108d81f2 --- /dev/null +++ b/src/modules/network_data/model/perf_kpi.go @@ -0,0 +1,23 @@ +package model + +// GoldKPITitle 黄金指标标题信息对象 kpi_title +type GoldKPITitle struct { + ID string `json:"id" gorm:"column:id;primaryKey;autoIncrement"` + NeType string `json:"neType" gorm:"column:ne_type"` + KPIID string `json:"kpiId" gorm:"column:kpi_id"` + TitleJson string `json:"titleJson" gorm:"column:title_json"` + CnTitle string `json:"cnTitle" gorm:"column:cn_title"` + EnTitle string `json:"enTitle" gorm:"column:en_title"` +} + +// GoldKPIQuery 黄金指标查询参数结构体 +type GoldKPIQuery struct { + NeType string `form:"neType" binding:"required"` + NeID string `form:"neId" binding:"required"` + StartTime string `form:"startTime" binding:"required"` + EndTime string `form:"endTime" binding:"required"` + Interval int64 `form:"interval" binding:"required"` + RmUID string `form:"rmUID"` + SortField string `form:"sortField" binding:"omitempty,oneof=timeGroup"` + SortOrder string `form:"sortOrder" binding:"omitempty,oneof=asc desc"` +} diff --git a/src/modules/network_data/network_data.go b/src/modules/network_data/network_data.go new file mode 100644 index 00000000..48773cce --- /dev/null +++ b/src/modules/network_data/network_data.go @@ -0,0 +1,39 @@ +package networkdata + +import ( + "ems.agt/src/framework/logger" + "ems.agt/src/framework/middleware" + "ems.agt/src/modules/network_data/controller" + + "github.com/gin-gonic/gin" +) + +// 模块路由注册 +func Setup(router *gin.Engine) { + logger.Infof("开始加载 ====> network_data 模块路由") + + neDataGroup := router.Group("/neData") + + // 性能统计信息 + kpiGroup := neDataGroup.Group("/kpi") + { + kpiGroup.GET("/title", + middleware.PreAuthorize(nil), + controller.NewPerfKPIController.Title, + ) + kpiGroup.GET("/data", + middleware.PreAuthorize(nil), + controller.NewPerfKPIController.GoldKPI, + ) + } + + // CDR会话事件信息 + cdrGroup := neDataGroup.Group("/cdr") + { + cdrGroup.GET("/list", + middleware.PreAuthorize(nil), + controller.NewCDREventController.List, + ) + } + +} diff --git a/src/modules/network_data/repository/cdr_event.go b/src/modules/network_data/repository/cdr_event.go new file mode 100644 index 00000000..0df96d95 --- /dev/null +++ b/src/modules/network_data/repository/cdr_event.go @@ -0,0 +1,9 @@ +package repository + +import "ems.agt/src/modules/network_data/model" + +// CDR会话事件 数据层接口 +type ICDREvent interface { + // SelectPage 根据条件分页查询 + SelectPage(querys model.CDREventQuery) map[string]any +} diff --git a/src/modules/network_data/repository/cdr_event.impl.go b/src/modules/network_data/repository/cdr_event.impl.go new file mode 100644 index 00000000..d12c34fc --- /dev/null +++ b/src/modules/network_data/repository/cdr_event.impl.go @@ -0,0 +1,133 @@ +package repository + +import ( + "fmt" + "strings" + + "ems.agt/src/framework/datasource" + "ems.agt/src/framework/logger" + "ems.agt/src/framework/utils/date" + "ems.agt/src/framework/utils/parse" + "ems.agt/src/framework/utils/repo" + "ems.agt/src/modules/network_data/model" +) + +// 实例化数据层 PerfKPIImpl 结构体 +var NewCDREventImpl = &CDREventImpl{ + selectSql: `select id, ne_type, ne_name, rm_uid, timestamp, cdr_json ,created_at from cdr_event`, + + resultMap: map[string]string{ + "id": "ID", + "ne_type": "NeType", + "ne_name": "NeName", + "rm_uid": "RmUID", + "timestamp": "Timestamp", + "cdr_json": "CDRJSONStr", + "created_at": "CreatedAt", + }, +} + +// CDREventImpl CDR会话事件 数据层处理 +type CDREventImpl struct { + // 查询视图对象SQL + selectSql string + // 结果字段与实体映射 + resultMap map[string]string +} + +// convertResultRows 将结果记录转实体结果组 +func (r *CDREventImpl) convertResultRows(rows []map[string]any) []model.CDREvent { + arr := make([]model.CDREvent, 0) + for _, row := range rows { + item := model.CDREvent{} + for key, value := range row { + if keyMapper, ok := r.resultMap[key]; ok { + repo.SetFieldValue(&item, keyMapper, value) + } + } + arr = append(arr, item) + } + return arr +} + +// SelectPage 根据条件分页查询 +func (r *CDREventImpl) SelectPage(querys model.CDREventQuery) map[string]any { + // 查询条件拼接 + var conditions []string + var params []any + if querys.NeType != "" { + conditions = append(conditions, "ne_type = ?") + params = append(params, querys.NeType) + } + if querys.RmUID != "" { + conditions = append(conditions, "rm_uid = ?") + params = append(params, querys.RmUID) + } + if querys.StartTime != "" { + conditions = append(conditions, "timestamp >= ?") + beginDate := date.ParseStrToDate(querys.StartTime, date.YYYY_MM_DD_HH_MM_SS) + params = append(params, beginDate.Unix()) + } + if querys.EndTime != "" { + conditions = append(conditions, "timestamp <= ?") + endDate := date.ParseStrToDate(querys.EndTime, date.YYYY_MM_DD_HH_MM_SS) + params = append(params, endDate.Unix()) + } + + // 构建查询条件语句 + whereSql := "" + if len(conditions) > 0 { + whereSql += " where " + strings.Join(conditions, " and ") + } + + result := map[string]any{ + "total": 0, + "rows": []model.CDREvent{}, + } + + // 查询数量 长度为0直接返回 + totalSql := "select count(1) as 'total' from cdr_event" + totalRows, err := datasource.RawDB("", totalSql+whereSql, params) + if err != nil { + logger.Errorf("total err => %v", err) + return result + } + total := parse.Number(totalRows[0]["total"]) + if total == 0 { + return result + } else { + result["total"] = total + } + + // 分页 + pageNum, pageSize := repo.PageNumSize(querys.PageNum, querys.PageSize) + pageSql := " limit ?,? " + params = append(params, pageNum*pageSize) + params = append(params, pageSize) + + // 排序 + orderSql := "" + if querys.SortField != "" { + sortSql := querys.SortField + if querys.SortOrder != "" { + if querys.SortOrder == "desc" { + sortSql += " desc " + } else { + sortSql += " asc " + } + } + orderSql = fmt.Sprintf(" order by %s ", sortSql) + } + + // 查询数据 + + querySql := r.selectSql + whereSql + orderSql + pageSql + results, err := datasource.RawDB("", querySql, params) + if err != nil { + logger.Errorf("query err => %v", err) + } + + // 转换实体 + result["rows"] = r.convertResultRows(results) + return result +} diff --git a/src/modules/network_data/repository/perf_kpi.go b/src/modules/network_data/repository/perf_kpi.go new file mode 100644 index 00000000..2c21acdf --- /dev/null +++ b/src/modules/network_data/repository/perf_kpi.go @@ -0,0 +1,12 @@ +package repository + +import "ems.agt/src/modules/network_element/model" + +// 性能统计 数据层接口 +type IPerfKPI interface { + // SelectGoldKPI 通过网元指标数据信息 + SelectGoldKPI(query model.GoldKPIQuery, kpiIds []string) []map[string]any + + // SelectGoldKPITitle + SelectGoldKPITitle(neType string) []model.GoldKPITitle +} diff --git a/src/modules/network_data/repository/perf_kpi.impl.go b/src/modules/network_data/repository/perf_kpi.impl.go new file mode 100644 index 00000000..0b13abab --- /dev/null +++ b/src/modules/network_data/repository/perf_kpi.impl.go @@ -0,0 +1,90 @@ +package repository + +import ( + "fmt" + "strings" + + "ems.agt/src/framework/datasource" + "ems.agt/src/framework/logger" + "ems.agt/src/modules/network_element/model" +) + +// 实例化数据层 PerfKPIImpl 结构体 +var NewPerfKPIImpl = &PerfKPIImpl{} + +// PerfKPIImpl 性能统计 数据层处理 +type PerfKPIImpl struct{} + +// SelectGoldKPI 通过网元指标数据信息 +func (r *PerfKPIImpl) SelectGoldKPI(query model.GoldKPIQuery, kpiIds []string) []map[string]any { + // 查询条件拼接 + var conditions []string + var params []any + if query.RmUID != "" { + conditions = append(conditions, "gk.rm_uid = ?") + params = append(params, query.RmUID) + } + if query.NeType != "" { + conditions = append(conditions, "gk.ne_type = ?") + params = append(params, query.NeType) + } + if query.StartTime != "" { + conditions = append(conditions, "gk.start_time >= ?") + params = append(params, query.StartTime) + } + if query.EndTime != "" { + conditions = append(conditions, "gk.start_time <= ?") + params = append(params, query.EndTime) + } + // 构建查询条件语句 + whereSql := "" + if len(conditions) > 0 { + whereSql += " where " + strings.Join(conditions, " and ") + } + + // 查询字段列 + timeFormat := "DATE_FORMAT(gk.start_time, '%Y-%m-%d %H:')" + minuteGroup := fmt.Sprintf("LPAD(FLOOR(MINUTE(gk.start_time) / %d) * %d, 2, '0')", query.Interval, query.Interval) + groupByField := fmt.Sprintf("CONCAT( %s, %s ) AS timeGroup", timeFormat, minuteGroup) + var fields = []string{ + groupByField, + "min(CASE WHEN gk.index != '' THEN gk.index ELSE 0 END) AS startIndex", + "min(CASE WHEN gk.ne_name != '' THEN gk.ne_name ELSE 0 END) AS neName", + } + for _, kid := range kpiIds { + // 特殊字段,只取最后一次收到的非0值 + if kid == "AMF.01" || kid == "UDM.01" || kid == "UDM.02" || kid == "UDM.03" { + str := fmt.Sprintf("IFNULL(SUBSTRING_INDEX(GROUP_CONCAT( CASE WHEN gk.kpi_id = '%s' and gk.VALUE != 0 THEN gk.VALUE END ), ',', 1), 0) AS '%s'", kid, kid) + fields = append(fields, str) + } else { + str := fmt.Sprintf("sum(CASE WHEN gk.kpi_id = '%s' THEN gk.value ELSE 0 END) AS '%s'", kid, kid) + fields = append(fields, str) + } + } + fieldsSql := strings.Join(fields, ",") + + // 查询数据 + if query.SortField == "" { + query.SortField = "timeGroup" + } + if query.SortOrder == "" { + query.SortOrder = "desc" + } + orderSql := fmt.Sprintf(" order by %s %s", query.SortField, query.SortOrder) + querySql := fmt.Sprintf("SELECT %s FROM gold_kpi gk %s GROUP BY timeGroup %s", fieldsSql, whereSql, orderSql) + results, err := datasource.RawDB("", querySql, params) + if err != nil { + logger.Errorf("query err => %v", err) + } + return results +} + +// SelectGoldKPITitle 通过网元指标数据信息 +func (r *PerfKPIImpl) SelectGoldKPITitle(neType string) []model.GoldKPITitle { + result := []model.GoldKPITitle{} + tx := datasource.DefaultDB().Table("kpi_title").Where("ne_type = ?", neType).Find(&result) + if err := tx.Error; err != nil { + logger.Errorf("Delete err => %v", err) + } + return result +} diff --git a/src/modules/network_data/service/cdr_event.go b/src/modules/network_data/service/cdr_event.go new file mode 100644 index 00000000..8efc2232 --- /dev/null +++ b/src/modules/network_data/service/cdr_event.go @@ -0,0 +1,9 @@ +package service + +import "ems.agt/src/modules/network_data/model" + +// CDR会话事件 服务层接口 +type ICDREvent interface { + // SelectPage 根据条件分页查询 + SelectPage(querys model.CDREventQuery) map[string]any +} diff --git a/src/modules/network_data/service/cdr_event.impl.go b/src/modules/network_data/service/cdr_event.impl.go new file mode 100644 index 00000000..e6a950c7 --- /dev/null +++ b/src/modules/network_data/service/cdr_event.impl.go @@ -0,0 +1,22 @@ +package service + +import ( + "ems.agt/src/modules/network_data/model" + "ems.agt/src/modules/network_data/repository" +) + +// 实例化数据层 CDREventImpl 结构体 +var NewCDREventImpl = &CDREventImpl{ + cdrEventRepository: repository.NewCDREventImpl, +} + +// CDREventImpl CDR会话事件 服务层处理 +type CDREventImpl struct { + // CDR会话事件数据信息 + cdrEventRepository repository.ICDREvent +} + +// SelectPage 根据条件分页查询 +func (r *CDREventImpl) SelectPage(querys model.CDREventQuery) map[string]any { + return r.cdrEventRepository.SelectPage(querys) +} diff --git a/src/modules/network_data/service/perf_kpi.go b/src/modules/network_data/service/perf_kpi.go new file mode 100644 index 00000000..3deba4f5 --- /dev/null +++ b/src/modules/network_data/service/perf_kpi.go @@ -0,0 +1,12 @@ +package service + +import "ems.agt/src/modules/network_element/model" + +// 性能统计 服务层接口 +type IPerfKPI interface { + // SelectGoldKPI 通过网元指标数据信息 + SelectGoldKPI(query model.GoldKPIQuery) []map[string]any + + // SelectGoldKPITitle + SelectGoldKPITitle(neType string) []model.GoldKPITitle +} diff --git a/src/modules/network_data/service/perf_kpi.impl.go b/src/modules/network_data/service/perf_kpi.impl.go new file mode 100644 index 00000000..91cf51d7 --- /dev/null +++ b/src/modules/network_data/service/perf_kpi.impl.go @@ -0,0 +1,38 @@ +package service + +import ( + "ems.agt/src/modules/network_element/model" + "ems.agt/src/modules/network_element/repository" +) + +// 实例化数据层 PerfKPIImpl 结构体 +var NewPerfKPIImpl = &PerfKPIImpl{ + perfKPIRepository: repository.NewPerfKPIImpl, +} + +// PerfKPIImpl 性能统计 服务层处理 +type PerfKPIImpl struct { + // 性能统计数据信息 + perfKPIRepository repository.IPerfKPI +} + +// SelectGoldKPI 通过网元指标数据信息 +func (r *PerfKPIImpl) SelectGoldKPI(query model.GoldKPIQuery) []map[string]any { + // 获取数据指标id + var kpiIds []string + kpiTitles := r.perfKPIRepository.SelectGoldKPITitle(query.NeType) + for _, kpiId := range kpiTitles { + kpiIds = append(kpiIds, kpiId.KPIID) + } + + data := r.perfKPIRepository.SelectGoldKPI(query, kpiIds) + if data == nil { + return []map[string]any{} + } + return data +} + +// SelectGoldKPITitle 通过网元指标数据信息 +func (r *PerfKPIImpl) SelectGoldKPITitle(neType string) []model.GoldKPITitle { + return r.perfKPIRepository.SelectGoldKPITitle(neType) +}