Files
be.ems/src/modules/ne_data/service/kpi_report.go
2025-09-19 15:10:55 +08:00

502 lines
14 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"
"sort"
"time"
"be.ems/src/framework/constants"
"be.ems/src/framework/database/redis"
"be.ems/src/framework/utils/parse"
neModel "be.ems/src/modules/ne/model"
"be.ems/src/modules/ne_data/model"
"be.ems/src/modules/ne_data/repository"
)
// 实例化数据层 KpiReport 结构体
var NewKpiReport = &KpiReport{
kpiReportRepository: repository.NewKpiReport,
}
// KpiReport 指标统计 服务层处理
type KpiReport struct {
kpiReportRepository *repository.KpiReport // 指标数据信息
}
// FindData 通过网元指标数据信息
func (s KpiReport) FindData(query model.KPIQuery) []map[string]any {
// 原始数据
rows := s.kpiReportRepository.SelectKPI(query)
if len(rows) <= 0 {
return []map[string]any{}
}
kpiIdsHas := false
kpiIds := []string{}
// 处理数据
arr := []map[string]any{}
for _, row := range rows {
// 解析 JSON 字符串为 map
var kpiValues []map[string]any
err := json.Unmarshal([]byte(row.KpiValues), &kpiValues)
if err != nil {
continue
}
item := map[string]any{
"neType": row.NeType,
"neName": row.NeName,
"rmUID": row.RmUid,
"startIndex": row.Index,
"timeGroup": row.CreatedAt,
}
// 遍历 kpiValues 数组
for _, v := range kpiValues {
kpiId := "-"
if k, ok := v["kpiId"]; ok {
kpiId = fmt.Sprint(k)
}
item[kpiId] = v["value"]
}
arr = append(arr, item)
// 添加指标ID
if !kpiIdsHas {
for _, v := range kpiValues {
kpiId := "-"
if k, ok := v["kpiId"]; ok {
kpiId = fmt.Sprint(k)
}
kpiIds = append(kpiIds, kpiId)
}
kpiIdsHas = true
}
}
// 时间密度分钟 数值单位秒 5分钟的传入300秒
timeInterval := query.Interval
// 创建一个map来存储按时间段合并后的数据
timeGroup := make(map[int64][]map[string]any)
// 遍历每个数据项
for _, v := range arr {
itemTime := parse.Number(v["timeGroup"])
// 计算时间戳的x分钟时间段使用秒并除以x分钟
timeMinute := itemTime / 1000 / timeInterval * timeInterval
// 合并到对应的时间段
timeGroup[timeMinute] = append(timeGroup[timeMinute], v)
}
// 时间组合输出
data := []map[string]any{}
for _, records := range timeGroup {
if len(records) <= 0 {
continue
}
// 转换为具体时间显示(根据需要可以格式化显示)
// timeStr := time.Unix(k, 0).Format("2006-01-02 15:04:05")
// fmt.Printf("Time Group: %s records: %d\n", timeStr, len(records))
startItem := records[len(records)-1] // 取最后一条数据也是最开始startIndex
if len(records) >= 2 { // 最后一条数据不参与计算
for _, record := range records[:len(records)-1] {
// fmt.Printf(" - startIndex: %v, Value: %v\n", record["startIndex"], record["timeGroup"])
// 遍历kpiIds数组对lastRecord赋值
for _, kpiId := range kpiIds {
if v, ok := record[kpiId]; ok {
// 特殊字段只取一次收到的非0值
if kpiId == "AMF.01" || kpiId == "UDM.01" || kpiId == "UDM.02" || kpiId == "UDM.03" || kpiId == "SMF.01" {
// startItem[kpiId] = parse.Number(v)
continue // startIndex的值不累加不取最后
} else {
value := parse.Number(startItem[kpiId])
startItem[kpiId] = value + parse.Number(v)
}
}
}
}
}
data = append(data, startItem)
}
// 按时间排序
sort.SliceStable(data, func(i, j int) bool {
vi := parse.Number(data[i]["timeGroup"])
vj := parse.Number(data[j]["timeGroup"])
if query.SortOrder == "asc" {
return vi < vj // asc
}
return vi > vj // desc
})
return data
}
// Insert 新增信息
func (s KpiReport) Insert(param model.KpiReport) int64 {
return s.kpiReportRepository.Insert(param)
}
// FindTitle 网元对应的指标名称
func (r KpiReport) FindTitle(neType string) []model.KpiTitle {
return r.kpiReportRepository.SelectKPITitle(neType)
}
// FindByPage 根据条件分页查询
func (r KpiReport) TitleFindByPage(query map[string]string) ([]model.KpiTitle, int64) {
return r.kpiReportRepository.TitleSelectByPage(query)
}
// TitleFind 查询信息
func (r KpiReport) TitleFind(param model.KpiTitle) []model.KpiTitle {
return r.kpiReportRepository.TitleSelect(param)
}
// TitleUpdate 更新信息
func (r KpiReport) TitleUpdate(param model.KpiTitle) int64 {
return r.kpiReportRepository.TitleUpdate(param)
}
// TitleDeleteByIds 批量删除信息
func (r KpiReport) TitleDeleteByIds(ids []int64) (int64, error) {
rows := r.kpiReportRepository.TitleDeleteByIds(ids)
if rows > 0 {
return rows, nil
}
// 删除信息失败!
return 0, fmt.Errorf("delete fail")
}
// TitleInsert 新增信息
func (r KpiReport) TitleInsert(param model.KpiTitle) int64 {
return r.kpiReportRepository.TitleInsert(param)
}
// UPFTodayFlowFind 查询UPF总流量 N3上行 N6下行
// day 统计天数
// down * 8 / 1000 / 1000 单位M
func (r KpiReport) UPFTodayFlowFind(rmUID string, day int) (int64, int64) {
// 获取当前日期
now := time.Now()
var upTotal, downTotal int64
// 查询最近day天的数据
for i := 0; i <= day; i++ {
dateKey := now.AddDate(0, 0, -i).Format("2006-01-02")
key := fmt.Sprintf("%s:UPF_FLOW:%s:%s", constants.CACHE_NE_DATA, rmUID, dateKey)
// 读取缓存数据
up, err := redis.GetHash("", key, "up")
if err != nil || up == "" {
up = "0"
}
down, err := redis.GetHash("", key, "down")
if err != nil || down == "" {
down = "0"
}
upTotal += parse.Number(up)
downTotal += parse.Number(down)
}
return upTotal, downTotal
}
// UPFTodayFlow UPF流量今日统计
func (r KpiReport) UPFTodayFlowUpdate(rmUID string, upValue, downValue int64) error {
// 按日期存储统计数据
dateKey := time.Now().Format("2006-01-02")
key := fmt.Sprintf("%s:UPF_FLOW:%s:%s", constants.CACHE_NE_DATA, rmUID, dateKey)
// 使用HIncrBy实时累加统计值
if err := redis.IncrBy("", key, "up", upValue); err != nil {
return err
}
if err := redis.IncrBy("", key, "down", downValue); err != nil {
return err
}
return nil
}
// UPFTodayFlowLoad UPF上下行数据到redis
// day 统计天数
func (r KpiReport) UPFTodayFlowLoad(day int) {
cacheKeys, _ := redis.GetKeys("", constants.CACHE_NE_INFO+":UPF:*")
if len(cacheKeys) == 0 {
return
}
now := time.Now()
for _, key := range cacheKeys {
var v neModel.NeInfo
jsonStr, _ := redis.Get("", key)
if len(jsonStr) > 7 {
json.Unmarshal([]byte(jsonStr), &v)
}
if v.NeType == "UPF" && v.RmUID != "" {
// 查询最近day天的数据
for i := 0; i <= day; i++ {
dateKey := now.AddDate(0, 0, -i).Format("2006-01-02")
key := fmt.Sprintf("%s:UPF_FLOW:%s:%s", constants.CACHE_NE_DATA, v.RmUID, dateKey)
// 根据传入天数计算时间范围
beginTime := now.AddDate(0, 0, -i).Truncate(24 * time.Hour).UnixMilli()
endTime := beginTime + 24*60*60*1000 - 1
// 查询历史数据
rows := r.kpiReportRepository.SelectUPF(v.RmUID, beginTime, endTime)
var upTotal, downTotal int64
// 处理历史数据
for _, row := range rows {
var kpiValues []map[string]any
if err := json.Unmarshal([]byte(row.KpiValues), &kpiValues); err != nil {
continue
}
for _, v := range kpiValues {
if k, ok := v["kpiId"]; ok {
if k == "UPF.03" {
upTotal += parse.Number(v["value"])
}
if k == "UPF.06" {
downTotal += parse.Number(v["value"])
}
}
}
}
err := redis.SetHash("", key, map[string]any{
"up": upTotal,
"down": downTotal,
})
if err != nil {
continue
}
// 设置key的过期时间为30天自动清理旧数据
daySub := (30 - i) * 24
err = redis.Expire("", key, time.Duration(daySub)*time.Hour)
if err != nil {
continue
}
}
}
}
}
// IMSBusyHour IMS忙时流量统计
// SCSCF.06呼叫尝试次数 SCSCF.09呼叫成功次数
func (r KpiReport) IMSBusyHour(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)
}
// 转换为毫秒级时间戳
rows := r.kpiReportRepository.SelectIMS(rmUID, beginTime.UnixMilli(), endTime.UnixMilli())
// 创建一个map来存储按时间段合并后的数据
timeGroup := make(map[int64]map[string]int64)
// 遍历每个数据项
for _, row := range rows {
// 将毫秒时间戳转换为小时级时间戳(保留到小时的起始毫秒)
timeHour := row.CreatedAt / 3600000 * 3600000 // 1小时 = 3600000毫秒
// 解析 JSON 字符串为 map
var kpiValues []map[string]any
err := json.Unmarshal([]byte(row.KpiValues), &kpiValues)
if err != nil {
continue
}
var callAttempts, callCompletions int64
for _, v := range kpiValues {
if k, ok := v["kpiId"]; ok {
if k == "SCSCF.06" {
callAttempts = parse.Number(v["value"])
}
if k == "SCSCF.09" {
callCompletions = parse.Number(v["value"])
}
}
}
// 合并到对应的小时段
if _, exists := timeGroup[timeHour]; !exists {
timeGroup[timeHour] = map[string]int64{
"callAttempts": 0,
"callCompletions": 0,
}
}
timeGroup[timeHour]["callAttempts"] += callAttempts
timeGroup[timeHour]["callCompletions"] += callCompletions
}
// 时间组合输出
data := make([]map[string]any, 0, len(timeGroup))
for hour, sums := range timeGroup {
data = append(data, map[string]any{
"timeGroup": fmt.Sprintf("%d", hour),
"callAttempts": sums["callAttempts"],
"callCompletions": sums["callCompletions"],
})
}
return data
}
// 定义结构体用于存储话务量值和对应的时间
type TrafficData struct {
Time int64 `json:"time"` // 时间戳(毫秒)
Value float64 `json:"value"` // 话务量值
}
// IMSBusyWeek IMS忙时流量统计 周
func (r KpiReport) IMSBusyWeek(rmUID string, weekStart, weekEnd int64) map[string]any {
weekStartTime := time.UnixMilli(weekStart)
weekEndTime := time.UnixMilli(weekEnd)
// 1. 获取一周内每小时的呼叫数据
// 转换为毫秒级时间戳
rows := r.kpiReportRepository.SelectIMS(rmUID, weekStartTime.UnixMilli(), weekEndTime.UnixMilli())
// 创建一个map来存储按时间段合并后的数据
timeGroup := make(map[int64]map[string]int64)
// 遍历每个数据项
for _, row := range rows {
// 将毫秒时间戳转换为小时级时间戳(保留到小时的起始毫秒)
timeHour := row.CreatedAt / 3600000 * 3600000 // 1小时 = 3600000毫秒
// 解析 JSON 字符串为 map
var kpiValues []map[string]any
err := json.Unmarshal([]byte(row.KpiValues), &kpiValues)
if err != nil {
continue
}
var callAttempts, callCompletions int64
for _, v := range kpiValues {
if k, ok := v["kpiId"]; ok {
if k == "SCSCF.06" {
callAttempts = parse.Number(v["value"])
}
if k == "SCSCF.09" {
callCompletions = parse.Number(v["value"])
}
}
}
// 合并到对应的小时段
if _, exists := timeGroup[timeHour]; !exists {
timeGroup[timeHour] = map[string]int64{
"callAttempts": 0,
"callCompletions": 0,
}
}
timeGroup[timeHour]["callAttempts"] += callAttempts
timeGroup[timeHour]["callCompletions"] += callCompletions
}
// 时间组合输出
data := make([]map[string]any, 0, len(timeGroup))
for hour, sums := range timeGroup {
data = append(data, map[string]any{
"timeGroup": fmt.Sprintf("%d", hour),
"callAttempts": sums["callAttempts"],
"callCompletions": sums["callCompletions"],
})
}
if len(data) == 0 {
return map[string]any{
"busyHourAverageBHCA": 0,
"busyHourAverageBHCC": 0,
"topFourHoursBHCA": []float64{},
"topFourHoursBHCC": []float64{},
"totalHours": 0,
}
}
// 2. 分离BHCA和BHCC数据并按降序排序
var bhcaData []TrafficData
var bhccData []TrafficData
for _, row := range data {
// 获取时间戳
timeValue := int64(0)
if t, ok := row["timeGroup"]; ok {
timeValue = parse.Number(t)
}
// 处理BHCA数据
if value, ok := row["callAttempts"]; ok {
bhcaVal := parse.Number(value)
bhcaData = append(bhcaData, TrafficData{
Time: timeValue,
Value: float64(bhcaVal),
})
}
// 处理BHCC数据
if value, ok := row["callCompletions"]; ok {
bhccVal := parse.Number(value)
bhccData = append(bhccData, TrafficData{
Time: timeValue,
Value: float64(bhccVal),
})
}
}
// 按降序排序(值大的在前)
sort.Slice(bhcaData, func(i, j int) bool { return bhcaData[i].Value > bhcaData[j].Value })
sort.Slice(bhccData, func(i, j int) bool { return bhccData[i].Value > bhccData[j].Value })
// 3. 取前四个最高值并计算平均值
topFourBHCA := getTopFourTrafficData(bhcaData)
topFourBHCC := getTopFourTrafficData(bhccData)
avgBHCA := calculateTrafficDataAverage(topFourBHCA)
avgBHCC := calculateTrafficDataAverage(topFourBHCC)
// 4. 返回结果
return map[string]any{
"busyHourAverageBHCA": avgBHCA,
"busyHourAverageBHCC": avgBHCC,
"topFourHoursBHCA": topFourBHCA,
"topFourHoursBHCC": topFourBHCC,
"totalHours": len(data),
}
}
// 辅助函数获取前四个最高值的TrafficData
func getTopFourTrafficData(data []TrafficData) []TrafficData {
if len(data) == 0 {
return []TrafficData{}
}
// 最多取前四个值
maxCount := 4
if len(data) < maxCount {
maxCount = len(data)
}
return data[:maxCount]
}
// 辅助函数计算TrafficData的平均值
func calculateTrafficDataAverage(data []TrafficData) float64 {
if len(data) == 0 {
return 0
}
var sum float64 = 0
for _, v := range data {
sum += v.Value
}
return sum / float64(len(data))
}