diff --git a/src/modules/network_data/controller/mme.go b/src/modules/network_data/controller/mme.go new file mode 100644 index 00000000..52a7f18c --- /dev/null +++ b/src/modules/network_data/controller/mme.go @@ -0,0 +1,188 @@ +package controller + +import ( + "encoding/json" + "fmt" + "strconv" + "strings" + "time" + + "be.ems/src/framework/i18n" + "be.ems/src/framework/logger" + "be.ems/src/framework/utils/ctx" + "be.ems/src/framework/utils/date" + "be.ems/src/framework/utils/file" + "be.ems/src/framework/utils/parse" + "be.ems/src/framework/vo/result" + "be.ems/src/modules/network_data/model" + neDataService "be.ems/src/modules/network_data/service" + neService "be.ems/src/modules/network_element/service" + sysService "be.ems/src/modules/system/service" + "github.com/gin-gonic/gin" + "github.com/gin-gonic/gin/binding" +) + +// 实例化控制层 MMEController 结构体 +var NewMMEController = &MMEController{ + neInfoService: neService.NewNeInfoImpl, + ueEventService: neDataService.NewUEEventMMEImpl, +} + +// 网元MME +// +// PATH /mme +type MMEController struct { + // 网元信息服务 + neInfoService neService.INeInfo + // UE会话事件服务 + ueEventService neDataService.IUEEventMME +} + +// UE会话列表 +// +// GET /ue/list +func (s *MMEController) UEList(c *gin.Context) { + language := ctx.AcceptLanguage(c) + var querys model.UEEventMMEQuery + 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.ueEventService.SelectPage(querys) + c.JSON(200, result.Ok(data)) +} + +// UE会话删除 +// +// DELETE /ue/:ueIds +func (s *MMEController) UERemove(c *gin.Context) { + language := ctx.AcceptLanguage(c) + ueIds := c.Param("ueIds") + if ueIds == "" { + c.JSON(400, result.CodeMsg(400, i18n.TKey(language, "app.common.err400"))) + return + } + // 处理字符转id数组后去重 + ids := strings.Split(ueIds, ",") + uniqueIDs := parse.RemoveDuplicates(ids) + if len(uniqueIDs) <= 0 { + c.JSON(200, result.Err(nil)) + return + } + rows, err := s.ueEventService.DeleteByIds(uniqueIDs) + if err != nil { + c.JSON(200, result.ErrMsg(i18n.TKey(language, err.Error()))) + return + } + msg := i18n.TTemplate(language, "app.common.deleteSuccess", map[string]any{"num": rows}) + c.JSON(200, result.OkMsg(msg)) +} + +// UE会话列表导出 +// +// POST /ue/export +func (s *MMEController) UEExport(c *gin.Context) { + language := ctx.AcceptLanguage(c) + // 查询结果,根据查询条件结果,单页最大值限制 + var querys model.UEEventMMEQuery + if err := c.ShouldBindBodyWith(&querys, binding.JSON); err != nil { + c.JSON(400, result.CodeMsg(400, i18n.TKey(language, "app.common.err400"))) + return + } + // 限制导出数据集 + if querys.PageSize > 10000 { + querys.PageSize = 10000 + } + data := s.ueEventService.SelectPage(querys) + if parse.Number(data["total"]) == 0 { + // 导出数据记录为空 + c.JSON(200, result.ErrMsg(i18n.TKey(language, "app.common.exportEmpty"))) + return + } + rows := data["rows"].([]model.UEEventMME) + + // 导出文件名称 + fileName := fmt.Sprintf("mme_ue_event_export_%d_%d.xlsx", len(rows), time.Now().UnixMilli()) + // 第一行表头标题 + headerCells := map[string]string{ + "A1": "ID", + "B1": "IMSI", + "C1": "Event Type", + "D1": "Result", + "E1": "Time", + } + // 读取字典数据 UE 事件类型MME + dictUEEventMMEType := sysService.NewSysDictDataImpl.SelectDictDataByType("ue_event_mme_type") + // 读取字典数据 UE 事件结果MME + dictUEEventMMEResult := sysService.NewSysDictDataImpl.SelectDictDataByType("ue_event_mme_result") + // 从第二行开始的数据 + dataCells := make([]map[string]any, 0) + for i, row := range rows { + idx := strconv.Itoa(i + 2) + // 解析 JSON 字符串为 map + var eventJSON map[string]interface{} + err := json.Unmarshal([]byte(row.EventJSONStr), &eventJSON) + if err != nil { + logger.Warnf("UEExport Error parsing JSON: %s", err.Error()) + continue + } + + // 取IMSI + imsi := "" + if v, ok := eventJSON["imsi"]; ok && v != nil { + imsi = v.(string) + } + // 取类型 + eventType := row.EventType + for _, v := range dictUEEventMMEType { + if row.EventType == v.DictValue { + eventType = i18n.TKey(language, v.DictLabel) + break + } + } + // 取结果 + eventResult := "" + if v, ok := eventJSON["result"]; ok && v != nil { + eventResult = v.(string) + for _, v := range dictUEEventMMEResult { + if eventResult == v.DictValue { + eventResult = i18n.TKey(language, v.DictLabel) + break + } + } + } + // 取时间 + timeStr := "" + if v, ok := eventJSON["timestamp"]; ok && v != nil { + rowTime := parse.Number(v) + timeStr = date.ParseDateToStr(rowTime, date.YYYY_MM_DDTHH_MM_SSZ) + } + + dataCells = append(dataCells, map[string]any{ + "A" + idx: row.ID, + "B" + idx: imsi, + "C" + idx: eventType, + "D" + idx: eventResult, + "E" + idx: timeStr, + }) + } + + // 导出数据表格 + saveFilePath, err := file.WriteSheet(headerCells, dataCells, fileName, "") + if err != nil { + c.JSON(200, result.ErrMsg(err.Error())) + return + } + + c.FileAttachment(saveFilePath, fileName) +} diff --git a/src/modules/network_data/model/ue_event_amf.go b/src/modules/network_data/model/ue_event_amf.go index 905f458c..6fa549bd 100644 --- a/src/modules/network_data/model/ue_event_amf.go +++ b/src/modules/network_data/model/ue_event_amf.go @@ -2,7 +2,7 @@ package model import "time" -// UEEventAMF UE会话对象 ue_event_amf +// UEEventAMF UE会话对象AMF ue_event_amf type UEEventAMF struct { ID string `json:"id" gorm:"column:id;primaryKey;autoIncrement"` NeType string `json:"neType" gorm:"column:ne_type"` @@ -14,7 +14,7 @@ type UEEventAMF struct { CreatedAt time.Time `json:"createdAt" gorm:"column:created_at;default:CURRENT_TIMESTAMP"` } -// UEEventAMFQuery UE会话对象查询参数结构体 +// UEEventAMFQuery UE会话对象AMF查询参数结构体 type UEEventAMFQuery struct { NeType string `json:"neType" form:"neType" binding:"required"` // 网元类型, 暂时支持AMF NeID string `json:"neId" form:"neId" binding:"required"` diff --git a/src/modules/network_data/model/ue_event_mme.go b/src/modules/network_data/model/ue_event_mme.go new file mode 100644 index 00000000..65d21375 --- /dev/null +++ b/src/modules/network_data/model/ue_event_mme.go @@ -0,0 +1,30 @@ +package model + +import "time" + +// UEEventMME UE会话对象MME ue_event_mme +type UEEventMME 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"` + EventType string `json:"eventType" gorm:"column:event_type"` // 事件类型 ECM STATUS, EMM STATUS + EventJSONStr string `json:"eventJSON" gorm:"column:event_json"` + CreatedAt time.Time `json:"createdAt" gorm:"column:created_at;default:CURRENT_TIMESTAMP"` +} + +// UEEventMMEQuery UE会话对象MME查询参数结构体 +type UEEventMMEQuery struct { + NeType string `json:"neType" form:"neType" binding:"required"` // 网元类型, 暂时支持MME + NeID string `json:"neId" form:"neId" binding:"required"` + RmUID string `json:"rmUID" form:"rmUID"` + EventType string `json:"eventType" form:"eventType"` // 事件类型 ECM STATUS, EMM STATUS + IMSI string `json:"imsi" form:"imsi"` // imsi + StartTime string `json:"startTime" form:"startTime"` + EndTime string `json:"endTime" form:"endTime"` + SortField string `json:"sortField" form:"sortField" binding:"omitempty,oneof=timestamp"` // 排序字段,填写结果字段 + SortOrder string `json:"sortOrder" form:"sortOrder" binding:"omitempty,oneof=asc desc"` // 排序升降序,asc desc + PageNum int64 `json:"pageNum" form:"pageNum" binding:"required"` + PageSize int64 `json:"pageSize" form:"pageSize" binding:"required"` +} diff --git a/src/modules/network_data/network_data.go b/src/modules/network_data/network_data.go index 2cd2bfc2..1f484641 100644 --- a/src/modules/network_data/network_data.go +++ b/src/modules/network_data/network_data.go @@ -215,4 +215,23 @@ func Setup(router *gin.Engine) { controller.NewUDMSub.Import, ) } + + // 网元MME + mmeGroup := neDataGroup.Group("/mme") + { + mmeGroup.GET("/ue/list", + middleware.PreAuthorize(nil), + controller.NewMMEController.UEList, + ) + mmeGroup.DELETE("/ue/:ueIds", + middleware.PreAuthorize(nil), + collectlogs.OperateLog(collectlogs.OptionNew("log.operate.title.mmeUE", collectlogs.BUSINESS_TYPE_DELETE)), + controller.NewMMEController.UERemove, + ) + mmeGroup.POST("/ue/export", + middleware.PreAuthorize(nil), + collectlogs.OperateLog(collectlogs.OptionNew("log.operate.title.mmeUE", collectlogs.BUSINESS_TYPE_EXPORT)), + controller.NewMMEController.UEExport, + ) + } } diff --git a/src/modules/network_data/repository/ue_event_mme.go b/src/modules/network_data/repository/ue_event_mme.go new file mode 100644 index 00000000..7a77fb18 --- /dev/null +++ b/src/modules/network_data/repository/ue_event_mme.go @@ -0,0 +1,15 @@ +package repository + +import "be.ems/src/modules/network_data/model" + +// UE会话事件MME 数据层接口 +type IUEEventMME interface { + // SelectPage 根据条件分页查询 + SelectPage(querys model.UEEventMMEQuery) map[string]any + + // SelectByIds 通过ID查询 + SelectByIds(ueIds []string) []model.UEEventMME + + // DeleteByIds 批量删除信息 + DeleteByIds(ueIds []string) int64 +} diff --git a/src/modules/network_data/repository/ue_event_mme.impl.go b/src/modules/network_data/repository/ue_event_mme.impl.go new file mode 100644 index 00000000..180bc9df --- /dev/null +++ b/src/modules/network_data/repository/ue_event_mme.impl.go @@ -0,0 +1,175 @@ +package repository + +import ( + "fmt" + "strings" + + "be.ems/src/framework/datasource" + "be.ems/src/framework/logger" + "be.ems/src/framework/utils/parse" + "be.ems/src/framework/utils/repo" + "be.ems/src/modules/network_data/model" +) + +// 实例化数据层 UEEventMMEImpl 结构体 +var NewUEEventMMEImpl = &UEEventMMEImpl{ + selectSql: `select id, ne_type, ne_name, rm_uid, timestamp, event_type, event_json, created_at from ue_event_mme`, + + resultMap: map[string]string{ + "id": "ID", + "ne_type": "NeType", + "ne_name": "NeName", + "rm_uid": "RmUID", + "timestamp": "Timestamp", + "event_type": "EventType", + "event_json": "EventJSONStr", + "created_at": "CreatedAt", + }, +} + +// UEEventMMEImpl UE会话事件 数据层处理 +type UEEventMMEImpl struct { + // 查询视图对象SQL + selectSql string + // 结果字段与实体映射 + resultMap map[string]string +} + +// convertResultRows 将结果记录转实体结果组 +func (r *UEEventMMEImpl) convertResultRows(rows []map[string]any) []model.UEEventMME { + arr := make([]model.UEEventMME, 0) + for _, row := range rows { + item := model.UEEventMME{} + 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 *UEEventMMEImpl) SelectPage(querys model.UEEventMMEQuery) 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 >= ?") + if len(querys.StartTime) == 13 { + querys.StartTime = querys.StartTime[:10] + } + params = append(params, querys.StartTime) + } + if querys.EndTime != "" { + conditions = append(conditions, "timestamp <= ?") + if len(querys.EndTime) == 13 { + querys.EndTime = querys.EndTime[:10] + } + params = append(params, querys.EndTime) + } + if querys.EventType != "" { + eventTypes := strings.Split(querys.EventType, ",") + placeholder := repo.KeyPlaceholderByQuery(len(eventTypes)) + conditions = append(conditions, fmt.Sprintf("event_type in (%s)", placeholder)) + for _, eventType := range eventTypes { + params = append(params, eventType) + } + } + if querys.IMSI != "" { + conditions = append(conditions, "JSON_EXTRACT(event_json, '$.imsi') = ?") + params = append(params, querys.IMSI) + } + + // 构建查询条件语句 + whereSql := "" + if len(conditions) > 0 { + whereSql += " where " + strings.Join(conditions, " and ") + } + + result := map[string]any{ + "total": 0, + "rows": []model.UEEventMME{}, + } + + // 查询数量 长度为0直接返回 + totalSql := "select count(1) as 'total' from ue_event_mme" + totalRows, err := datasource.RawDB("", totalSql+whereSql, params) + if err != nil { + logger.Errorf("total err => %v", err) + return result + } + total := parse.Number(totalRows[0]["total"]) + if total == 0 { + return result + } else { + result["total"] = total + } + + // 分页 + pageNum, pageSize := repo.PageNumSize(querys.PageNum, querys.PageSize) + pageSql := " limit ?,? " + params = append(params, pageNum*pageSize) + params = append(params, pageSize) + + // 排序 + orderSql := "" + if querys.SortField != "" { + sortSql := querys.SortField + if querys.SortOrder != "" { + if querys.SortOrder == "desc" { + sortSql += " desc " + } else { + sortSql += " asc " + } + } + orderSql = fmt.Sprintf(" order by id desc, %s ", sortSql) + } + + // 查询数据 + querySql := r.selectSql + whereSql + orderSql + pageSql + results, err := datasource.RawDB("", querySql, params) + if err != nil { + logger.Errorf("query err => %v", err) + } + + // 转换实体 + result["rows"] = r.convertResultRows(results) + return result +} + +// SelectByIds 通过ID查询 +func (r *UEEventMMEImpl) SelectByIds(ueIds []string) []model.UEEventMME { + placeholder := repo.KeyPlaceholderByQuery(len(ueIds)) + querySql := r.selectSql + " where id in (" + placeholder + ")" + parameters := repo.ConvertIdsSlice(ueIds) + results, err := datasource.RawDB("", querySql, parameters) + if err != nil { + logger.Errorf("query err => %v", err) + return []model.UEEventMME{} + } + // 转换实体 + return r.convertResultRows(results) +} + +// DeleteByIds 批量删除信息 +func (r *UEEventMMEImpl) DeleteByIds(ueIds []string) int64 { + placeholder := repo.KeyPlaceholderByQuery(len(ueIds)) + sql := "delete from ue_event_mme where id in (" + placeholder + ")" + parameters := repo.ConvertIdsSlice(ueIds) + results, err := datasource.ExecDB("", sql, parameters) + if err != nil { + logger.Errorf("delete err => %v", err) + return 0 + } + return results +} diff --git a/src/modules/network_data/service/ue_event_amf.go b/src/modules/network_data/service/ue_event_amf.go index 148d4013..e5f77a63 100644 --- a/src/modules/network_data/service/ue_event_amf.go +++ b/src/modules/network_data/service/ue_event_amf.go @@ -2,7 +2,7 @@ package service import "be.ems/src/modules/network_data/model" -// UE会话事件 服务层接口 +// UE会话事件AMF 服务层接口 type IUEEventAMF interface { // SelectPage 根据条件分页查询 SelectPage(querys model.UEEventAMFQuery) map[string]any diff --git a/src/modules/network_data/service/ue_event_amf.impl.go b/src/modules/network_data/service/ue_event_amf.impl.go index 29706cd7..8d5f61c9 100644 --- a/src/modules/network_data/service/ue_event_amf.impl.go +++ b/src/modules/network_data/service/ue_event_amf.impl.go @@ -7,24 +7,24 @@ import ( "be.ems/src/modules/network_data/repository" ) -// 实例化数据层 UEEventAMFImpl 结构体 -var NewUEEventAMFImpl = &UEEventAMFImpl{ - ueEventRepository: repository.NewUEEventAMFImpl, +// 实例化数据层 UEEventMMEImpl 结构体 +var NewUEEventMMEImpl = &UEEventMMEImpl{ + ueEventRepository: repository.NewUEEventMMEImpl, } -// UEEventAMFImpl UE会话事件 服务层处理 -type UEEventAMFImpl struct { +// UEEventMMEImpl UE会话事件MME 服务层处理 +type UEEventMMEImpl struct { // UE会话事件数据信息 - ueEventRepository repository.IUEEventAMF + ueEventRepository repository.IUEEventMME } // SelectPage 根据条件分页查询 -func (r *UEEventAMFImpl) SelectPage(querys model.UEEventAMFQuery) map[string]any { +func (r *UEEventMMEImpl) SelectPage(querys model.UEEventMMEQuery) map[string]any { return r.ueEventRepository.SelectPage(querys) } // DeleteByIds 批量删除信息 -func (r *UEEventAMFImpl) DeleteByIds(ueIds []string) (int64, error) { +func (r *UEEventMMEImpl) DeleteByIds(ueIds []string) (int64, error) { // 检查是否存在 ids := r.ueEventRepository.SelectByIds(ueIds) if len(ids) <= 0 { diff --git a/src/modules/network_data/service/ue_event_mme.go b/src/modules/network_data/service/ue_event_mme.go new file mode 100644 index 00000000..d4b5dd86 --- /dev/null +++ b/src/modules/network_data/service/ue_event_mme.go @@ -0,0 +1,12 @@ +package service + +import "be.ems/src/modules/network_data/model" + +// UE会话事件MME 服务层接口 +type IUEEventMME interface { + // SelectPage 根据条件分页查询 + SelectPage(querys model.UEEventMMEQuery) map[string]any + + // DeleteByIds 批量删除信息 + DeleteByIds(ueIds []string) (int64, error) +} diff --git a/src/modules/network_data/service/ue_event_mme.impl.go b/src/modules/network_data/service/ue_event_mme.impl.go new file mode 100644 index 00000000..9c7e0710 --- /dev/null +++ b/src/modules/network_data/service/ue_event_mme.impl.go @@ -0,0 +1,40 @@ +package service + +import ( + "fmt" + + "be.ems/src/modules/network_data/model" + "be.ems/src/modules/network_data/repository" +) + +// 实例化数据层 UEEventAMFImpl 结构体 +var NewUEEventAMFImpl = &UEEventAMFImpl{ + ueEventRepository: repository.NewUEEventAMFImpl, +} + +// UEEventAMFImpl UE会话事件AMF 服务层处理 +type UEEventAMFImpl struct { + // UE会话事件数据信息 + ueEventRepository repository.IUEEventAMF +} + +// SelectPage 根据条件分页查询 +func (r *UEEventAMFImpl) SelectPage(querys model.UEEventAMFQuery) map[string]any { + return r.ueEventRepository.SelectPage(querys) +} + +// DeleteByIds 批量删除信息 +func (r *UEEventAMFImpl) DeleteByIds(ueIds []string) (int64, error) { + // 检查是否存在 + ids := r.ueEventRepository.SelectByIds(ueIds) + if len(ids) <= 0 { + return 0, fmt.Errorf("no data") + } + + if len(ids) == len(ueIds) { + rows := r.ueEventRepository.DeleteByIds(ueIds) + return rows, nil + } + // 删除信息失败! + return 0, fmt.Errorf("delete fail") +}