diff --git a/src/modules/network_data/controller/sgwc.go b/src/modules/network_data/controller/sgwc.go new file mode 100644 index 00000000..48c2b9ab --- /dev/null +++ b/src/modules/network_data/controller/sgwc.go @@ -0,0 +1,319 @@ +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/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" + "github.com/gin-gonic/gin" + "github.com/gin-gonic/gin/binding" +) + +// 实例化控制层 SGWCController 结构体 +var NewSGWC = &SGWCController{ + neInfoService: neService.NewNeInfo, + cdrEventService: neDataService.NewCDREventSGWC, + udmUserInfoService: neDataService.NewUDMUserInfo, +} + +// 网元SGWC +// +// PATH /SGWC +type SGWCController struct { + neInfoService *neService.NeInfo // 网元信息服务 + cdrEventService *neDataService.CDREventSGWC // CDR会话事件服务 + udmUserInfoService *neDataService.UDMUserInfo // UDM用户信息服务 +} + +// CDR会话列表 +// +// GET /cdr/list +func (s *SGWCController) CDRList(c *gin.Context) { + language := ctx.AcceptLanguage(c) + var querys model.CDREventSGWCQuery + if err := c.ShouldBindQuery(&querys); err != nil { + c.JSON(400, result.CodeMsg(400, i18n.TKey(language, "app.common.err400"))) + return + } + + // 查询网元信息 rmUID + 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 + + // 查询数据 + rows, total := s.cdrEventService.SelectPage(querys) + c.JSON(200, result.Ok(map[string]any{ + "total": total, + "rows": rows, + })) +} + +// CDR会话删除 +// +// DELETE /cdr/:cdrIds +func (s *SGWCController) CDRRemove(c *gin.Context) { + language := ctx.AcceptLanguage(c) + cdrIds := c.Param("cdrIds") + if cdrIds == "" { + c.JSON(400, result.CodeMsg(400, i18n.TKey(language, "app.common.err400"))) + return + } + // 处理字符转id数组后去重 + ids := strings.Split(cdrIds, ",") + uniqueIDs := parse.RemoveDuplicates(ids) + if len(uniqueIDs) <= 0 { + c.JSON(200, result.Err(nil)) + return + } + rows, err := s.cdrEventService.DeleteByIds(uniqueIDs) + if err != nil { + c.JSON(200, result.ErrMsg(i18n.TKey(language, err.Error()))) + return + } + msg := i18n.TTemplate(language, "app.common.deleteSuccess", map[string]any{"num": rows}) + c.JSON(200, result.OkMsg(msg)) +} + +// CDR会话列表导出 +// +// POST /cdr/export +func (s *SGWCController) CDRExport(c *gin.Context) { + language := ctx.AcceptLanguage(c) + // 查询结果,根据查询条件结果,单页最大值限制 + var querys model.CDREventSGWCQuery + 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 + } + // 查询网元信息 rmUID + 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 + rows, total := s.cdrEventService.SelectPage(querys) + if total == 0 { + // 导出数据记录为空 + c.JSON(200, result.ErrMsg(i18n.TKey(language, "app.common.exportEmpty"))) + return + } + + // 导出文件名称 + fileName := fmt.Sprintf("SGWC_cdr_event_export_%d_%d.xlsx", len(rows), time.Now().UnixMilli()) + // 第一行表头标题 + headerCells := map[string]string{ + "A1": "ID", + "B1": "Charging ID", + "C1": "NE Name", + "D1": "Resource Unique ID", + "E1": "Subscriber ID Data", + "F1": "Subscriber ID Type", + "G1": "Data Volume Uplink", + "H1": "Data Volume Downlink", + "I1": "Data Total Volume", + "J1": "Duration", + "K1": "Invocation Time", + "L1": "User Identifier", + "M1": "SSC Mode", + "N1": "DNN ID", + "O1": "PDU Type", + "P1": "RAT Type", + "Q1": "PDU IPv4 Address", + "R1": "Network Function IPv4", + "S1": "PDU IPv6 Address Swith Prefix", + "T1": "Record Network Function ID", + "U1": "Record Type", + "V1": "Record Opening Time", + } + // 从第二行开始的数据 + dataCells := make([]map[string]any, 0) + + for i, row := range rows { + idx := strconv.Itoa(i + 2) + // 解析 JSON 字符串为 map + var cdrJSON map[string]interface{} + err := json.Unmarshal([]byte(row.CDRJSONStr), &cdrJSON) + if err != nil { + logger.Warnf("CDRExport Error parsing JSON: %s", err.Error()) + continue + } + // 计费ID + chargingID := "" + if v, ok := cdrJSON["chargingID"]; ok && v != nil { + chargingID = fmt.Sprint(parse.Number(v)) + } + // 订阅 ID 类型 + subscriptionIDType := "-" + // 订阅 ID 数据 + subscriptionIDData := "-" + if v, ok := cdrJSON["subscriberIdentifier"]; ok && v != nil { + if sub, subOk := v.(map[string]any); subOk && sub != nil { + subscriptionIDType = sub["subscriptionIDType"].(string) + subscriptionIDData = sub["subscriptionIDData"].(string) + } + } + + // 网络功能 IPv4 地址 + networkFunctionIPv4Address := "" + if v, ok := cdrJSON["nFunctionConsumerInformation"]; ok && v != nil { + if conInfo, conInfoOk := v.(map[string]any); conInfoOk && conInfo != nil { + networkFunctionIPv4Address = conInfo["networkFunctionIPv4Address"].(string) + } + } + + // 数据量上行链路 + dataVolumeUplink := []string{} + // 数据量下行链路 + dataVolumeDownlink := []string{} + // 数据总量 + dataTotalVolume := []string{} + + if v, ok := cdrJSON["listOfMultipleUnitUsage"]; ok && v != nil { + usageList := v.([]any) + if len(usageList) > 0 { + for _, used := range usageList { + usedUnit := used.(map[string]any) + usedUnitList := usedUnit["usedUnitContainer"].([]any) + if len(usedUnitList) > 0 { + for _, data := range usedUnitList { + udata := data.(map[string]any) + if dup, dupOk := udata["dataVolumeUplink"]; dupOk { + dataVolumeUplink = append(dataVolumeUplink, fmt.Sprint(parse.Number(dup))) + } + if ddown, ddownOk := udata["dataVolumeDownlink"]; ddownOk { + dataVolumeDownlink = append(dataVolumeDownlink, fmt.Sprint(parse.Number(ddown))) + } + if dt, dtOk := udata["dataTotalVolume"]; dtOk { + dataTotalVolume = append(dataTotalVolume, fmt.Sprint(parse.Number(dt))) + } + } + } + } + } + } + // 时长 + duration := "-" + if v, ok := cdrJSON["duration"]; ok && v != nil { + duration = fmt.Sprint(parse.Number(v)) + } + // 调用时间 + invocationTimestamp := "" + if v, ok := cdrJSON["invocationTimestamp"]; ok && v != nil { + invocationTimestamp = v.(string) + } + // 记录打开时间 + User_Identifier := "" + SSC_Mode := "" + RAT_Type := "" + DNN_ID := "" + PDU_Type := "" + PDU_IPv4 := "" + PDU_IPv6 := "" + if v, ok := cdrJSON["pDUSessionChargingInformation"]; ok && v != nil { + pduInfo := v.(map[string]any) + + if v, ok := pduInfo["userIdentifier"]; ok && v != nil { + User_Identifier = v.(string) + } + if v, ok := pduInfo["sSCMode"]; ok && v != nil { + SSC_Mode = v.(string) + } + if v, ok := pduInfo["rATType"]; ok && v != nil { + RAT_Type = v.(string) + } + if v, ok := pduInfo["dNNID"]; ok && v != nil { + DNN_ID = v.(string) + } + if v, ok := pduInfo["pDUType"]; ok && v != nil { + PDU_Type = v.(string) + } + if v, ok := pduInfo["pDUAddress"]; ok && v != nil { + pDUAddress := v.(map[string]any) + if addr, ok := pDUAddress["pDUIPv4Address"]; ok && addr != nil { + PDU_IPv4 = addr.(string) + } + if addr, ok := pDUAddress["pDUIPv6AddresswithPrefix"]; ok && addr != nil { + PDU_IPv6 = addr.(string) + } + } + + // pduSessionChargingInformation = fmt.Sprintf(`User Identifier: %s + // SSC Mode: %s RAT Type: %s DNN ID: %s + // PDU Type: %s + // PDU IPv4 Address: %s + // PDU IPv6 Addres Swith Prefix: %s`, User_Identifier, SSC_Mode, RAT_Type, DNN_ID, PDU_Type, PDU_IPv4, PDU_IPv6) + } + + // 记录网络参数ID + recordNFID := "" + if v, ok := cdrJSON["recordingNetworkFunctionID"]; ok && v != nil { + recordNFID = v.(string) + } + + //记录开始时间 + recordOpeningTime := "" + if v, ok := cdrJSON["recordOpeningTime"]; ok && v != nil { + recordOpeningTime = v.(string) + } + + //记录类型 + recordType := "" + if v, ok := cdrJSON["recordType"]; ok && v != nil { + recordType = v.(string) + } + + dataCells = append(dataCells, map[string]any{ + "A" + idx: row.ID, + "B" + idx: chargingID, + "C" + idx: row.NeName, + "D" + idx: row.RmUID, + "E" + idx: subscriptionIDData, + "F" + idx: subscriptionIDType, + "G" + idx: strings.Join(dataVolumeUplink, ","), + "H" + idx: strings.Join(dataVolumeDownlink, ","), + "I" + idx: strings.Join(dataTotalVolume, ","), + "J" + idx: duration, + "K" + idx: invocationTimestamp, + "L" + idx: User_Identifier, + "M" + idx: SSC_Mode, + "N" + idx: DNN_ID, + "O" + idx: PDU_Type, + "P" + idx: RAT_Type, + "Q" + idx: PDU_IPv4, + "R" + idx: networkFunctionIPv4Address, + "S" + idx: PDU_IPv6, + "T" + idx: recordNFID, + "U" + idx: recordType, + "V" + idx: recordOpeningTime, + }) + } + + // 导出数据表格 + 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/cdr_event_sgwc.go b/src/modules/network_data/model/cdr_event_sgwc.go new file mode 100644 index 00000000..17a0e819 --- /dev/null +++ b/src/modules/network_data/model/cdr_event_sgwc.go @@ -0,0 +1,36 @@ +package model + +import "time" + +// CDREventSGWC CDR会话对象SGWC cdr_event_sgwc +type CDREventSGWC 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"` + + // ====== 非数据库字段属性 ====== +} + +// TableName 表名称 +func (*CDREventSGWC) TableName() string { + return "cdr_event_sgwc" +} + +// CDREventSGWCQuery CDR会话对象SGWC查询参数结构体 +type CDREventSGWCQuery struct { + NeType string `json:"neType" form:"neType" binding:"required,oneof=SGWC"` // SGWC + NeID string `json:"neId" form:"neId" binding:"required"` + RmUID string `json:"rmUID" form:"rmUID"` + RecordType string `json:"recordType" form:"recordType"` // 暂时没用到 + SubscriberID string `json:"subscriberID" form:"subscriberID"` + StartTime string `json:"startTime" form:"startTime"` + EndTime string `json:"endTime" form:"endTime"` + SortField string `json:"sortField" form:"sortField" binding:"omitempty,oneof=timestamp"` // 排序字段,填写结果字段 + SortOrder string `json:"sortOrder" form:"sortOrder" binding:"omitempty,oneof=asc desc"` // 排序升降序,asc desc + PageNum int64 `json:"pageNum" form:"pageNum" binding:"required"` + PageSize int64 `json:"pageSize" form:"pageSize" binding:"required"` +} diff --git a/src/modules/network_data/network_data.go b/src/modules/network_data/network_data.go index c37539d8..faa613e5 100644 --- a/src/modules/network_data/network_data.go +++ b/src/modules/network_data/network_data.go @@ -277,4 +277,23 @@ func Setup(router *gin.Engine) { controller.NewMME.NbInfoList, ) } + + // 网元SGWC + sgwcGroup := neDataGroup.Group("/sgwc") + { + sgwcGroup.GET("/cdr/list", + middleware.PreAuthorize(nil), + controller.NewSGWC.CDRList, + ) + sgwcGroup.DELETE("/cdr/:cdrIds", + middleware.PreAuthorize(nil), + collectlogs.OperateLog(collectlogs.OptionNew("log.operate.title.sgwcCDR", collectlogs.BUSINESS_TYPE_DELETE)), + controller.NewSGWC.CDRRemove, + ) + sgwcGroup.POST("/cdr/export", + middleware.PreAuthorize(nil), + collectlogs.OperateLog(collectlogs.OptionNew("log.operate.title.sgwcCDR", collectlogs.BUSINESS_TYPE_EXPORT)), + controller.NewSGWC.CDRExport, + ) + } } diff --git a/src/modules/network_data/repository/cdr_event_sgwc.go b/src/modules/network_data/repository/cdr_event_sgwc.go new file mode 100644 index 00000000..49829631 --- /dev/null +++ b/src/modules/network_data/repository/cdr_event_sgwc.go @@ -0,0 +1,94 @@ +package repository + +import ( + "be.ems/src/framework/datasource" + "be.ems/src/framework/logger" + "be.ems/src/modules/network_data/model" +) + +// 实例化数据层 CDREventSGWC 结构体 +var NewCDREventSGWC = &CDREventSGWC{} + +// CDREventSGWC CDR会话事件 数据层处理 +type CDREventSGWC struct{} + +// SelectByPage 分页查询集合 +func (r CDREventSGWC) SelectByPage(querys model.CDREventSGWCQuery) ([]model.CDREventSGWC, int64) { + tx := datasource.DB("").Model(&model.CDREventSGWC{}) + // 查询条件拼接 + if querys.NeType != "" { + tx = tx.Where("ne_type = ?", querys.NeType) + } + if querys.RmUID != "" { + tx = tx.Where("rm_uid = ?", querys.RmUID) + } + if querys.StartTime != "" { + startTime := querys.StartTime + if len(startTime) == 13 { + startTime = startTime[:10] + } + tx = tx.Where("timestamp >= ?", startTime) + } + if querys.EndTime != "" { + endTime := querys.EndTime + if len(endTime) == 13 { + endTime = endTime[:10] + } + tx = tx.Where("timestamp <= ?", endTime) + } + if querys.RecordType != "" { + tx = tx.Where("JSON_EXTRACT(cdr_json, '$.recordType') = ?", querys.RecordType) + } + if querys.SubscriberID != "" { + tx = tx.Where("JSON_EXTRACT(cdr_json, '$.subscriberIdentifier.subscriptionIDData') = ?", querys.SubscriberID) + } + + // 查询结果 + var total int64 = 0 + rows := []model.CDREventSGWC{} + + // 查询数量为0直接返回 + if err := tx.Count(&total).Error; err != nil || total <= 0 { + return rows, total + } + + // 查询数据分页 + pageNum, pageSize := datasource.PageNumSize(querys.PageNum, querys.PageSize) + tx = tx.Limit(pageSize).Offset(pageSize * pageNum) + err := tx.Find(&rows).Error + if err != nil { + logger.Errorf("query find err => %v", err.Error()) + return rows, total + } + return rows, total +} + +// SelectByIds 通过ID查询 +func (r *CDREventSGWC) SelectByIds(ids []string) []model.CDREventSGWC { + rows := []model.CDREventSGWC{} + if len(ids) <= 0 { + return rows + } + tx := datasource.DB("").Model(&model.CDREventSGWC{}) + // 构建查询条件 + tx = tx.Where("id in ?", ids) + // 查询数据 + if err := tx.Find(&rows).Error; err != nil { + logger.Errorf("query find err => %v", err.Error()) + return rows + } + return rows +} + +// DeleteByIds 批量删除信息 +func (r *CDREventSGWC) DeleteByIds(ids []string) int64 { + if len(ids) <= 0 { + return 0 + } + tx := datasource.DB("").Where("id in ?", ids) + if err := tx.Delete(&model.CDREventSGWC{}).Error; err != nil { + logger.Errorf("delete err => %v", err.Error()) + return 0 + } + return tx.RowsAffected +} diff --git a/src/modules/network_data/service/cdr_event_sgwc.go b/src/modules/network_data/service/cdr_event_sgwc.go new file mode 100644 index 00000000..2e29f4d1 --- /dev/null +++ b/src/modules/network_data/service/cdr_event_sgwc.go @@ -0,0 +1,39 @@ +package service + +import ( + "fmt" + + "be.ems/src/modules/network_data/model" + "be.ems/src/modules/network_data/repository" +) + +// 实例化数据层 CDREventSGWC 结构体 +var NewCDREventSGWC = &CDREventSGWC{ + cdrEventRepository: repository.NewCDREventSGWC, +} + +// CDREventSGWC CDR会话事件SGWC 服务层处理 +type CDREventSGWC struct { + cdrEventRepository *repository.CDREventSGWC // CDR会话事件数据信息 +} + +// SelectPage 根据条件分页查询 +func (r *CDREventSGWC) SelectPage(querys model.CDREventSGWCQuery) ([]model.CDREventSGWC, int64) { + return r.cdrEventRepository.SelectByPage(querys) +} + +// DeleteByIds 批量删除信息 +func (r *CDREventSGWC) DeleteByIds(cdrIds []string) (int64, error) { + // 检查是否存在 + ids := r.cdrEventRepository.SelectByIds(cdrIds) + if len(ids) <= 0 { + return 0, fmt.Errorf("not data") + } + + if len(ids) == len(cdrIds) { + rows := r.cdrEventRepository.DeleteByIds(cdrIds) + return rows, nil + } + // 删除信息失败! + return 0, fmt.Errorf("delete fail") +}