diff --git a/src/modules/network_data/controller/ims.go b/src/modules/network_data/controller/ims.go index feef3813..3c7f9d5c 100644 --- a/src/modules/network_data/controller/ims.go +++ b/src/modules/network_data/controller/ims.go @@ -118,8 +118,8 @@ func (s *IMSController) CDRExport(c *gin.Context) { "A1": "ID", "B1": "Record Behavior", "C1": "Type", - "D1": "Called", - "E1": "Caller", + "D1": "Caller", + "E1": "Called", "F1": "Duration", "G1": "Result", "H1": "Time", @@ -193,8 +193,8 @@ func (s *IMSController) CDRExport(c *gin.Context) { "A" + idx: row.ID, "B" + idx: recordType, "C" + idx: callTypeLable, - "D" + idx: called, - "E" + idx: caller, + "D" + idx: caller, + "E" + idx: called, "F" + idx: duration, "G" + idx: callResult, "H" + idx: timeStr, diff --git a/src/modules/network_data/controller/smsc.go b/src/modules/network_data/controller/smsc.go new file mode 100644 index 00000000..a2b3b6c3 --- /dev/null +++ b/src/modules/network_data/controller/smsc.go @@ -0,0 +1,190 @@ +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" + "github.com/gin-gonic/gin" + "github.com/gin-gonic/gin/binding" +) + +// 实例化控制层 SMSCController 结构体 +var NewSMSCController = &SMSCController{ + neInfoService: neService.NewNeInfoImpl, + cdrEventService: neDataService.NewCDREventSMSCImpl, +} + +// 网元SMSC +// +// PATH /smsc +type SMSCController struct { + // 网元信息服务 + neInfoService neService.INeInfo + // CDR会话事件服务 + cdrEventService neDataService.ICDREventSMSC +} + +// CDR会话列表 +// +// GET /cdr/list +func (s *SMSCController) CDRList(c *gin.Context) { + language := ctx.AcceptLanguage(c) + var querys model.CDREventSMSCQuery + 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)) +} + +// CDR会话删除 +// +// DELETE /cdr/:cdrIds +func (s *SMSCController) 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 *SMSCController) CDRExport(c *gin.Context) { + language := ctx.AcceptLanguage(c) + // 查询结果,根据查询条件结果,单页最大值限制 + var querys model.CDREventSMSCQuery + 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.cdrEventService.SelectPage(querys) + if parse.Number(data["total"]) == 0 { + // 导出数据记录为空 + c.JSON(200, result.ErrMsg(i18n.TKey(language, "app.common.exportEmpty"))) + return + } + rows := data["rows"].([]model.CDREventSMSC) + + // 导出文件名称 + fileName := fmt.Sprintf("smsc_cdr_event_export_%d_%d.xlsx", len(rows), time.Now().UnixMilli()) + // 第一行表头标题 + headerCells := map[string]string{ + "A1": "ID", + "B1": "Record Behavior", + "C1": "Service Type", + "D1": "Caller", + "E1": "Called", + "F1": "Result", + "G1": "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 + } + // 记录类型 + recordType := "" + if v, ok := cdrJSON["recordType"]; ok && v != nil { + recordType = v.(string) + } + // 服务类型 + serviceType := "" + if v, ok := cdrJSON["serviceType"]; ok && v != nil { + serviceType = v.(string) + } + // 被叫 + called := "" + if v, ok := cdrJSON["calledParty"]; ok && v != nil { + called = v.(string) + } + // 主叫 + caller := "" + if v, ok := cdrJSON["callerParty"]; ok && v != nil { + caller = v.(string) + } + // 呼叫结果 0失败,1成功 + callResult := "Fail" + if v, ok := cdrJSON["result"]; ok && v != nil { + resultVal := parse.Number(v) + if resultVal == 1 { + callResult = "Success" + } + } + // 取时间 + timeStr := "" + if v, ok := cdrJSON["updateTime"]; ok && v != nil { + releaseTime := parse.Number(v) + timeStr = date.ParseDateToStr(releaseTime, date.YYYY_MM_DDTHH_MM_SSZ) + } + + dataCells = append(dataCells, map[string]any{ + "A" + idx: row.ID, + "B" + idx: recordType, + "C" + idx: serviceType, + "D" + idx: caller, + "E" + idx: called, + "F" + idx: callResult, + "G" + 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/cdr_event_smsc.go b/src/modules/network_data/model/cdr_event_smsc.go new file mode 100644 index 00000000..6a50e597 --- /dev/null +++ b/src/modules/network_data/model/cdr_event_smsc.go @@ -0,0 +1,30 @@ +package model + +import "time" + +// CDREventSMSC CDR会话对象SMSC cdr_event_smsc +type CDREventSMSC 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"` +} + +// CDREventSMSCQuery CDR会话对象SMSC查询参数结构体 +type CDREventSMSCQuery struct { + NeType string `json:"neType" form:"neType" binding:"required"` // 网元类型SMSC + NeID string `json:"neId" form:"neId" binding:"required"` + RmUID string `json:"rmUID" form:"rmUID"` + RecordType string `json:"recordType" form:"recordType"` // 记录行为 MOSM MTSM + CallerParty string `json:"callerParty" form:"callerParty"` // 主叫号码 + CalledParty string `json:"calledParty" form:"calledParty"` // 被叫号码 + 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 1f484641..69b513d8 100644 --- a/src/modules/network_data/network_data.go +++ b/src/modules/network_data/network_data.go @@ -61,6 +61,25 @@ func Setup(router *gin.Engine) { ) } + // 网元SMSC + smscGroup := neDataGroup.Group("/smsc") + { + smscGroup.GET("/cdr/list", + middleware.PreAuthorize(nil), + controller.NewSMSCController.CDRList, + ) + smscGroup.DELETE("/cdr/:cdrIds", + middleware.PreAuthorize(nil), + collectlogs.OperateLog(collectlogs.OptionNew("log.operate.title.smscCDR", collectlogs.BUSINESS_TYPE_DELETE)), + controller.NewSMSCController.CDRRemove, + ) + smscGroup.POST("/cdr/export", + middleware.PreAuthorize(nil), + collectlogs.OperateLog(collectlogs.OptionNew("log.operate.title.smscCDR", collectlogs.BUSINESS_TYPE_EXPORT)), + controller.NewSMSCController.CDRExport, + ) + } + // 网元SMF smfGroup := neDataGroup.Group("/smf") { diff --git a/src/modules/network_data/repository/cdr_event_smsc.go b/src/modules/network_data/repository/cdr_event_smsc.go new file mode 100644 index 00000000..403e0e40 --- /dev/null +++ b/src/modules/network_data/repository/cdr_event_smsc.go @@ -0,0 +1,15 @@ +package repository + +import "be.ems/src/modules/network_data/model" + +// CDR会话事件SMSC 数据层接口 +type ICDREventSMSC interface { + // SelectPage 根据条件分页查询 + SelectPage(querys model.CDREventSMSCQuery) map[string]any + + // SelectByIds 通过ID查询 + SelectByIds(cdrIds []string) []model.CDREventSMSC + + // DeleteByIds 批量删除信息 + DeleteByIds(cdrIds []string) int64 +} diff --git a/src/modules/network_data/repository/cdr_event_smsc.impl.go b/src/modules/network_data/repository/cdr_event_smsc.impl.go new file mode 100644 index 00000000..82182313 --- /dev/null +++ b/src/modules/network_data/repository/cdr_event_smsc.impl.go @@ -0,0 +1,181 @@ +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" +) + +// 实例化数据层 CDREventSMSCImpl 结构体 +var NewCDREventSMSCImpl = &CDREventSMSCImpl{ + selectSql: `select id, ne_type, ne_name, rm_uid, timestamp, cdr_json, created_at from cdr_event_smsc`, + + resultMap: map[string]string{ + "id": "ID", + "ne_type": "NeType", + "ne_name": "NeName", + "rm_uid": "RmUID", + "timestamp": "Timestamp", + "cdr_json": "CDRJSONStr", + "created_at": "CreatedAt", + }, +} + +// CDREventSMSCImpl CDR会话事件 数据层处理 +type CDREventSMSCImpl struct { + // 查询视图对象SQL + selectSql string + // 结果字段与实体映射 + resultMap map[string]string +} + +// convertResultRows 将结果记录转实体结果组 +func (r *CDREventSMSCImpl) convertResultRows(rows []map[string]any) []model.CDREventSMSC { + arr := make([]model.CDREventSMSC, 0) + for _, row := range rows { + item := model.CDREventSMSC{} + 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 *CDREventSMSCImpl) SelectPage(querys model.CDREventSMSCQuery) 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) + } + // MySQL8支持的 + // if querys.RecordType != "" { + // recordTypes := strings.Split(querys.RecordType, ",") + // placeholder := repo.KeyPlaceholderByQuery(len(recordTypes)) + // conditions = append(conditions, fmt.Sprintf("JSON_EXTRACT(cdr_json, '$.recordType') in (%s)", placeholder)) + // for _, recordType := range recordTypes { + // params = append(params, recordType) + // } + // } + // Mariadb不支持json in查询改or + if querys.RecordType != "" { + recordTypes := strings.Split(querys.RecordType, ",") + var queryStrArr []string + for _, recordType := range recordTypes { + queryStrArr = append(queryStrArr, "JSON_EXTRACT(cdr_json, '$.recordType') = ?") + params = append(params, recordType) + } + conditions = append(conditions, fmt.Sprintf("( %s )", strings.Join(queryStrArr, " OR "))) + } + + // 构建查询条件语句 + whereSql := "" + if len(conditions) > 0 { + whereSql += " where " + strings.Join(conditions, " and ") + } + + result := map[string]any{ + "total": 0, + "rows": []model.CDREventSMSC{}, + } + + // 查询数量 长度为0直接返回 + totalSql := "select count(1) as 'total' from cdr_event_smsc" + 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 *CDREventSMSCImpl) SelectByIds(cdrIds []string) []model.CDREventSMSC { + placeholder := repo.KeyPlaceholderByQuery(len(cdrIds)) + querySql := r.selectSql + " where id in (" + placeholder + ")" + parameters := repo.ConvertIdsSlice(cdrIds) + results, err := datasource.RawDB("", querySql, parameters) + if err != nil { + logger.Errorf("query err => %v", err) + return []model.CDREventSMSC{} + } + // 转换实体 + return r.convertResultRows(results) +} + +// DeleteByIds 批量删除信息 +func (r *CDREventSMSCImpl) DeleteByIds(cdrIds []string) int64 { + placeholder := repo.KeyPlaceholderByQuery(len(cdrIds)) + sql := "delete from cdr_event_smsc where id in (" + placeholder + ")" + parameters := repo.ConvertIdsSlice(cdrIds) + 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/cdr_event_smsc.go b/src/modules/network_data/service/cdr_event_smsc.go new file mode 100644 index 00000000..472ffea7 --- /dev/null +++ b/src/modules/network_data/service/cdr_event_smsc.go @@ -0,0 +1,12 @@ +package service + +import "be.ems/src/modules/network_data/model" + +// CDR会话事件SMSC 服务层接口 +type ICDREventSMSC interface { + // SelectPage 根据条件分页查询 + SelectPage(querys model.CDREventSMSCQuery) map[string]any + + // DeleteByIds 批量删除信息 + DeleteByIds(cdrIds []string) (int64, error) +} diff --git a/src/modules/network_data/service/cdr_event_smsc.impl.go b/src/modules/network_data/service/cdr_event_smsc.impl.go new file mode 100644 index 00000000..b2d27f3d --- /dev/null +++ b/src/modules/network_data/service/cdr_event_smsc.impl.go @@ -0,0 +1,37 @@ +package service + +import ( + "fmt" + + "be.ems/src/modules/network_data/model" + "be.ems/src/modules/network_data/repository" +) + +var NewCDREventSMSCImpl = &CDREventSMSCImpl{ + cdrEventRepository: repository.NewCDREventSMSCImpl, +} + +type CDREventSMSCImpl struct { + // CDR会话事件数据信息 + cdrEventRepository repository.ICDREventSMSC +} + +func (r *CDREventSMSCImpl) SelectPage(querys model.CDREventSMSCQuery) map[string]any { + return r.cdrEventRepository.SelectPage(querys) +} + +// DeleteByIds 批量删除信息 +func (r *CDREventSMSCImpl) 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") +}