Files
be.ems/src/modules/network_data/service/cdr_event.go
2025-09-28 19:28:52 +08:00

797 lines
22 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package service
import (
"encoding/json"
"fmt"
"strconv"
"strings"
"time"
"be.ems/src/framework/i18n"
"be.ems/src/framework/logger"
"be.ems/src/framework/utils/date"
"be.ems/src/framework/utils/file"
"be.ems/src/framework/utils/parse"
"be.ems/src/modules/network_data/model"
"be.ems/src/modules/network_data/repository"
sysService "be.ems/src/modules/system/service"
)
// 实例化数据层 CDREvent 结构体
var NewCDREvent = &CDREvent{
cdrEventRepository: repository.NewCDREvent,
}
// CDREvent CDR会话事件 服务层处理
type CDREvent struct {
cdrEventRepository *repository.CDREvent // CDR会话事件数据信息
}
// FindByPage 根据条件分页查询
func (r CDREvent) FindByPage(neType string, query map[string]string) ([]model.CDREvent, int64) {
return r.cdrEventRepository.SelectByPage(neType, query)
}
// DeleteByIds 批量删除信息
func (r CDREvent) DeleteByIds(neType string, ids []int64) (int64, error) {
// 检查是否存在
rows := r.cdrEventRepository.SelectByIds(neType, ids)
if len(rows) <= 0 {
return 0, fmt.Errorf("not data")
}
if len(rows) == len(ids) {
rows := r.cdrEventRepository.DeleteByIds(neType, ids)
return rows, nil
}
// 删除信息失败!
return 0, fmt.Errorf("delete fail")
}
// Insert 新增信息
func (s CDREvent) Insert(param model.CDREvent) int64 {
return s.cdrEventRepository.Insert(param)
}
// ExportSMSC 导出数据到 xlsx 文件
func (r CDREvent) ExportSMSC(rows []model.CDREvent, fileName, language string) (string, error) {
// 第一行表头标题
headerCells := map[string]string{
"A1": "ID",
"B1": "NE Name",
"C1": "Record Behavior",
"D1": "Service Type",
"E1": "Caller",
"F1": "Called",
"G1": "Result",
"H1": "Time",
}
// 读取字典数据 CDR 原因码
dictCDRCauseCode := sysService.NewSysDictData.FindByType("cdr_cause_code")
// 从第二行开始的数据
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.CdrJson), &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"
}
}
// 结果原因
if v, ok := cdrJSON["cause"]; ok && v != nil && callResult == "Fail" {
cause := fmt.Sprint(v)
for _, v := range dictCDRCauseCode {
if cause == v.DataValue {
callResult = fmt.Sprintf("%s, %s", callResult, i18n.TKey(language, v.DataLabel))
break
}
}
}
// 取时间
timeStr := ""
if v, ok := cdrJSON["updateTime"]; ok && v != nil {
if releaseTime := parse.Number(v); releaseTime > 0 {
timeStr = date.ParseDateToStr(releaseTime, date.YYYY_MM_DDTHH_MM_SSZ)
} else {
timeStr = v.(string)
}
}
dataCells = append(dataCells, map[string]any{
"A" + idx: row.ID,
"B" + idx: row.NeName,
"C" + idx: recordType,
"D" + idx: serviceType,
"E" + idx: caller,
"F" + idx: called,
"G" + idx: callResult,
"H" + idx: timeStr,
})
}
// 导出数据表格
return file.WriteSheet(headerCells, dataCells, fileName, "")
}
// ExportSMF 导出数据到 xlsx 文件
func (r CDREvent) ExportSMF(rows []model.CDREvent, fileName string) (string, error) {
// 第一行表头标题
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.CdrJson), &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)
}
}
// 数据量上行链路
var dataVolumeUplink int64 = 0
// 数据量下行链路
var dataVolumeDownlink int64 = 0
// 数据总量
var dataTotalVolume int64 = 0
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 += parse.Number(dup)
}
if ddown, ddownOk := udata["dataVolumeDownlink"]; ddownOk {
dataVolumeDownlink += parse.Number(ddown)
}
if dt, dtOk := udata["dataTotalVolume"]; dtOk {
dataTotalVolume += 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)
}
}
}
// 记录网络参数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: dataVolumeUplink,
"H" + idx: dataVolumeDownlink,
"I" + idx: 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,
})
}
// 导出数据表格
return file.WriteSheet(headerCells, dataCells, fileName, "")
}
// ExportSGWC 导出数据到 xlsx 文件
func (r CDREvent) ExportSGWC(rows []model.CDREvent, fileName string) (string, error) {
// 第一行表头标题
headerCells := map[string]string{
"A1": "ID",
"B1": "NE Name",
"C1": "Resource Unique ID",
"D1": "Charging ID",
"E1": "IMSI",
"F1": "MSISDN",
"G1": "GPRS Uplink",
"H1": "GPRS Downlink",
"I1": "Duration",
"J1": "Invocation Time",
"K1": "PGW Address",
"L1": "SGW Address",
"M1": "RAT Type",
"N1": "PDPPDN Type",
"O1": "PDPPDN Address",
"P1": "Node Address",
"Q1": "Node Type",
"R1": "Record Access Point Name NI",
"S1": "Record Cause For Rec Closing",
"T1": "Record Sequence Number",
"U1": "Local Record Sequence Number",
"V1": "Record Type",
"W1": "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.CdrJson), &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))
}
// IMSI
servedIMSI := ""
if v, ok := cdrJSON["servedIMSI"]; ok && v != nil {
servedIMSI = fmt.Sprint(v)
}
// MSISDN
servedMSISDN := ""
if v, ok := cdrJSON["servedMSISDN"]; ok && v != nil {
servedMSISDN = fmt.Sprint(v)
}
// pGWAddressUsed
pGWAddressUsed := ""
if v, ok := cdrJSON["pGWAddressUsed"]; ok && v != nil {
pGWAddressUsed = fmt.Sprint(v)
headerCells["K1"] = "PGW Address"
}
if v, ok := cdrJSON["GGSNAddress"]; ok && v != nil {
pGWAddressUsed = fmt.Sprint(v)
headerCells["K1"] = "GGSN Address"
}
// sGWAddress
sGWAddress := ""
if v, ok := cdrJSON["sGWAddress"]; ok && v != nil {
sGWAddress = fmt.Sprint(v)
headerCells["L1"] = "SGW Address"
}
if v, ok := cdrJSON["SGSNAddress"]; ok && v != nil {
sGWAddress = fmt.Sprint(v)
headerCells["L1"] = "SGSN Address"
}
// recordType
recordType := ""
if v, ok := cdrJSON["recordType"]; ok && v != nil {
recordType = fmt.Sprint(v)
}
// rATType
rATType := ""
if v, ok := cdrJSON["rATType"]; ok && v != nil {
rATType = fmt.Sprint(v)
}
// pdpPDNType
pdpPDNType := ""
if v, ok := cdrJSON["pdpPDNType"]; ok && v != nil {
pdpPDNType = fmt.Sprint(v)
}
// servedPDPPDNAddress
servedPDPPDNAddress := ""
if v, ok := cdrJSON["servedPDPPDNAddress"]; ok && v != nil {
servedPDPPDNAddress = fmt.Sprint(v)
}
// servedPDPPDNAddress
servingNodeAddress := []string{}
if v, ok := cdrJSON["servingNodeAddress"]; ok && v != nil {
for _, v := range v.([]any) {
servingNodeAddress = append(servingNodeAddress, fmt.Sprint(v))
}
}
// servingNodeType
servingNodeType := []string{}
if v, ok := cdrJSON["servingNodeType"]; ok && v != nil {
for _, v := range v.([]any) {
if v, ok := v.(map[string]any)["servingNodeType"]; ok && v != nil {
servingNodeType = append(servingNodeType, fmt.Sprint(v))
}
}
}
// accessPointNameNI
accessPointNameNI := ""
if v, ok := cdrJSON["accessPointNameNI"]; ok && v != nil {
accessPointNameNI = fmt.Sprint(v)
}
// causeForRecClosing
causeForRecClosing := ""
if v, ok := cdrJSON["causeForRecClosing"]; ok && v != nil {
causeForRecClosing = fmt.Sprint(v)
}
// recordSequenceNumber
recordSequenceNumber := ""
if v, ok := cdrJSON["recordSequenceNumber"]; ok && v != nil {
recordSequenceNumber = fmt.Sprint(v)
}
// localRecordSequenceNumber
localRecordSequenceNumber := ""
if v, ok := cdrJSON["localRecordSequenceNumber"]; ok && v != nil {
localRecordSequenceNumber = fmt.Sprint(v)
}
// 数据量上行链路
var dataVolumeGPRSUplink int64 = 0
// 数据量下行链路
var dataVolumeGPRSDownlink int64 = 0
if v, ok := cdrJSON["listOfTrafficVolumes"]; ok && v != nil {
usageList := v.([]any)
if len(usageList) > 0 {
for _, used := range usageList {
usedUnit := used.(map[string]any)
if dup, dupOk := usedUnit["dataVolumeGPRSUplink"]; dupOk {
dataVolumeGPRSUplink = parse.Number(dup)
}
if ddown, ddownOk := usedUnit["dataVolumeGPRSDownlink"]; ddownOk {
dataVolumeGPRSDownlink = parse.Number(ddown)
}
}
}
}
// 时长
duration := "-"
if v, ok := cdrJSON["duration"]; ok && v != nil {
duration = fmt.Sprint(parse.Number(v))
}
// 调用时间
invocationTimestamp := ""
if v, ok := cdrJSON["recordOpeningTime"]; ok && v != nil {
invocationTimestamp = v.(string)
}
dataCells = append(dataCells, map[string]any{
"A" + idx: row.ID,
"B" + idx: row.NeName,
"C" + idx: row.RmUid,
"D" + idx: chargingID,
"E" + idx: servedIMSI,
"F" + idx: servedMSISDN,
"G" + idx: dataVolumeGPRSUplink,
"H" + idx: dataVolumeGPRSDownlink,
"I" + idx: duration,
"J" + idx: invocationTimestamp,
"K" + idx: pGWAddressUsed,
"L" + idx: sGWAddress,
"M" + idx: rATType,
"N" + idx: pdpPDNType,
"O" + idx: servedPDPPDNAddress,
"P" + idx: strings.Join(servingNodeAddress, ","),
"Q" + idx: strings.Join(servingNodeType, ","),
"R" + idx: accessPointNameNI,
"S" + idx: causeForRecClosing,
"T" + idx: recordSequenceNumber,
"U" + idx: localRecordSequenceNumber,
"V" + idx: recordType,
"W" + idx: invocationTimestamp,
})
}
// 导出数据表格
return file.WriteSheet(headerCells, dataCells, fileName, "")
}
// ExportIMS 导出数据到 xlsx 文件
func (r CDREvent) ExportIMS(rows []model.CDREvent, fileName, language string) (string, error) {
// 第一行表头标题
headerCells := map[string]string{
"A1": "ID",
"B1": "NE Name",
"C1": "Record Behavior",
"D1": "Type",
"E1": "Caller",
"F1": "Called",
"G1": "Duration",
"H1": "Result Code",
"I1": "Result Cause",
"J1": "Call Start Time",
"K1": "Hangup Time",
}
// 读取字典数据 CDR SIP响应代码类别类型
dictCDRSipCode := sysService.NewSysDictData.FindByType("cdr_sip_code")
// 读取字典数据 CDR SIP响应代码类别类型原因
dictCDRSipCodeCause := sysService.NewSysDictData.FindByType("cdr_sip_code_cause")
// 读取字典数据 CDR 呼叫类型
dictCDRCallType := sysService.NewSysDictData.FindByType("cdr_call_type")
// 从第二行开始的数据
dataCells := make([]map[string]any, 0)
for i, row := range rows {
idx := strconv.Itoa(i + 2)
// 解析 JSON 字符串为 map
var cdrJSON map[string]any
err := json.Unmarshal([]byte(row.CdrJson), &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)
}
// 呼叫类型
callType := "sms"
callTypeLable := "SMS"
if v, ok := cdrJSON["callType"]; ok && v != nil {
callType = v.(string)
for _, v := range dictCDRCallType {
if callType == v.DataValue {
callTypeLable = i18n.TKey(language, v.DataLabel)
break
}
}
}
// 被叫
called := ""
if v, ok := cdrJSON["calledParty"]; ok && v != nil {
called = v.(string)
}
// 主叫
caller := ""
if v, ok := cdrJSON["callerParty"]; ok && v != nil {
caller = v.(string)
}
// 时长
duration := "-"
if v, ok := cdrJSON["callDuration"]; ok && v != nil && callType != "sms" {
duration = fmt.Sprintf("%ds", parse.Number(v))
}
// 呼叫结果 非短信都有code作为结果 sms短信都ok
callResult := "Other"
callCause := "Call failure for other reason"
if callType == "sms" {
callResult = "Success"
callCause = "Normal Send"
} else {
if v, ok := cdrJSON["cause"]; ok && v != nil {
cause := fmt.Sprint(v)
for _, v := range dictCDRSipCode {
if cause == v.DataValue {
callResult = i18n.TKey(language, v.DataLabel)
break
}
}
for _, v := range dictCDRSipCodeCause {
if cause == v.DataValue {
callCause = i18n.TKey(language, v.DataLabel)
break
}
}
}
}
// 呼叫时间
seizureTimeStr := ""
if v, ok := cdrJSON["seizureTime"]; ok && v != nil {
if seizureTime := parse.Number(v); seizureTime > 0 {
seizureTimeStr = date.ParseDateToStr(seizureTime, date.YYYY_MM_DDTHH_MM_SSZ)
} else {
seizureTimeStr = v.(string)
}
}
// 挂断时间
releaseTimeStr := ""
if v, ok := cdrJSON["releaseTime"]; ok && v != nil {
if releaseTime := parse.Number(v); releaseTime > 0 {
releaseTimeStr = date.ParseDateToStr(releaseTime, date.YYYY_MM_DDTHH_MM_SSZ)
} else {
releaseTimeStr = v.(string)
}
}
dataCells = append(dataCells, map[string]any{
"A" + idx: row.ID,
"B" + idx: row.NeName,
"C" + idx: recordType,
"D" + idx: callTypeLable,
"E" + idx: caller,
"F" + idx: called,
"G" + idx: duration,
"H" + idx: callResult,
"I" + idx: callCause,
"J" + idx: seizureTimeStr,
"K" + idx: releaseTimeStr,
})
}
// 导出数据表格
return file.WriteSheet(headerCells, dataCells, fileName, "")
}
// IMSCDRMOSHour CDR MOS 统计
func (r CDREvent) IMSCDRMOSHour(rmUID string, timestamp int64) []map[string]any {
t := time.UnixMilli(timestamp)
beginTime := t
endTime := t
// 检查时分秒是否都为零
if t.Hour() == 0 && t.Minute() == 0 && t.Second() == 0 {
// 获取当天起始时间00:00:00
beginTime = t.Truncate(time.Hour)
// 计算当天结束时间23:59:59
endTime = beginTime.Add(23*time.Hour + 59*time.Minute + 59*time.Second)
} else {
// 起始时间:当前小时的 00 分 00 秒
beginTime = t.Truncate(time.Hour)
// 结束时间:当前小时的 59 分 59 秒 999 毫秒
endTime = beginTime.Add(time.Hour - time.Millisecond)
}
query := map[string]string{
"rmUID": rmUID,
"recordType": "MOC",
"beginTime": fmt.Sprintf("%d", beginTime.Unix()),
"endTime": fmt.Sprintf("%d", endTime.Unix()),
}
rows, total := r.cdrEventRepository.SelectByPage("IMS", query)
if total == 0 {
return []map[string]any{}
}
// 创建一个map来存储按时间段合并后的数据
timeGroup := make(map[int64]map[string]float64)
// 遍历每个数据项
for _, row := range rows {
// 将毫秒时间戳转换为小时级时间戳(保留到小时的起始毫秒)
timeHour := row.Timestamp / 3600 * 3600 // 1小时 = 3600000毫秒
// 解析 JSON 字符串为 map
var cdrJSON map[string]interface{}
err := json.Unmarshal([]byte(row.CdrJson), &cdrJSON)
if err != nil {
logger.Warnf("Unmarshal JSON: %s", err.Error())
continue
}
// 记录类型
var mosAverage float64 = 0
if v, ok := cdrJSON["mosAverage"]; ok && v != nil {
mosAverage = v.(float64)
} else {
continue
}
// 合并到对应的小时段
if _, exists := timeGroup[timeHour]; !exists {
timeGroup[timeHour] = map[string]float64{
"total": 0,
"mosSum": 0,
}
}
timeGroup[timeHour]["total"] += 1
timeGroup[timeHour]["mosSum"] += mosAverage
}
// 时间组合输出
data := make([]map[string]any, 0, len(timeGroup))
for hour, sums := range timeGroup {
data = append(data, map[string]any{
"timeGroup": fmt.Sprintf("%d", hour),
"total": sums["total"],
"mosSum": float64(int(sums["mosSum"]*100)) / 100,
"mosAvg": float64(int(sums["mosSum"]/sums["total"]*100)) / 100,
})
}
return data
}
// IMSCDRCCTHour CDR CCT 统计
func (r CDREvent) IMSCDRCCTHour(rmUID string, timestamp int64) []map[string]any {
t := time.UnixMilli(timestamp)
beginTime := t
endTime := t
// 检查时分秒是否都为零
if t.Hour() == 0 && t.Minute() == 0 && t.Second() == 0 {
// 获取当天起始时间00:00:00
beginTime = t.Truncate(time.Hour)
// 计算当天结束时间23:59:59
endTime = beginTime.Add(23*time.Hour + 59*time.Minute + 59*time.Second)
} else {
// 起始时间:当前小时的 00 分 00 秒
beginTime = t.Truncate(time.Hour)
// 结束时间:当前小时的 59 分 59 秒 999 毫秒
endTime = beginTime.Add(time.Hour - time.Millisecond)
}
query := map[string]string{
"rmUID": rmUID,
"recordType": "MOC",
"beginTime": fmt.Sprintf("%d", beginTime.Unix()),
"endTime": fmt.Sprintf("%d", endTime.Unix()),
}
rows, total := r.cdrEventRepository.SelectByPage("IMS", query)
if total == 0 {
return []map[string]any{}
}
// 创建一个map来存储按时间段合并后的数据
timeGroup := make(map[int64]map[string]float64)
// 遍历每个数据项
for _, row := range rows {
// 将毫秒时间戳转换为小时级时间戳(保留到小时的起始毫秒)
timeHour := row.Timestamp / 3600 * 3600 // 1小时 = 3600000毫秒
// 解析 JSON 字符串为 map
var cdrJSON map[string]interface{}
err := json.Unmarshal([]byte(row.CdrJson), &cdrJSON)
if err != nil {
logger.Warnf("Unmarshal JSON: %s", err.Error())
continue
}
// 记录类型
var callConnectionTime float64 = 0
if v, ok := cdrJSON["callConnectionTime"]; ok && v != nil {
callConnectionTime = v.(float64)
} else {
continue
}
// 合并到对应的小时段
if _, exists := timeGroup[timeHour]; !exists {
timeGroup[timeHour] = map[string]float64{
"total": 0,
"cctSum": 0,
}
}
timeGroup[timeHour]["total"] += 1
timeGroup[timeHour]["cctSum"] += callConnectionTime
}
// 时间组合输出
data := make([]map[string]any, 0, len(timeGroup))
for hour, sums := range timeGroup {
data = append(data, map[string]any{
"timeGroup": fmt.Sprintf("%d", hour),
"total": sums["total"],
"cctSum": float64(int(sums["cctSum"]*100)) / 100,
"cctAvg": float64(int(sums["cctSum"]/sums["total"]*100)) / 100,
})
}
return data
}