diff --git a/src/modules/network_data/controller/amf.go b/src/modules/network_data/controller/amf.go index 886f1f70..605217ec 100644 --- a/src/modules/network_data/controller/amf.go +++ b/src/modules/network_data/controller/amf.go @@ -3,12 +3,14 @@ package controller import ( "encoding/json" "fmt" + "sort" "strconv" "strings" "time" "be.ems/src/framework/i18n" "be.ems/src/framework/logger" + "be.ems/src/framework/resp" "be.ems/src/framework/utils/ctx" "be.ems/src/framework/utils/date" "be.ems/src/framework/utils/file" @@ -370,3 +372,129 @@ func (s *AMFController) NbStateList(c *gin.Context) { c.JSON(200, result.OkData(data)) } + +// 获取审计日志 +// +// GET /log/audit +// +// @Tags network_data/amf +// @Accept json +// @Produce json +// @Param neId query string true "NE ID" default(001) +// @Success 200 {object} object "Response Results" +// @Security TokenAuth +// @Summary Access to the audit log +// @Description Access to the audit log +// @Router /neData/amf/log/audit [get] +func (s *AMFController) AuditLog(c *gin.Context) { + var query struct { + NeId string `form:"neId" binding:"required"` + BeginTime int64 `json:"beginTime" form:"beginTime"` + EndTime int64 `json:"endTime" form:"endTime"` + } + if err := c.ShouldBindQuery(&query); err != nil { + errMsgs := fmt.Sprintf("bind err: %s", resp.FormatBindError(err)) + c.JSON(422, resp.CodeMsg(resp.CODE_PARAM_PARSER, errMsgs)) + return + } + + // 处理时间范围 + if query.BeginTime != 0 && query.BeginTime < 1e12 { + c.JSON(422, resp.CodeMsg(resp.CODE_PARAM_CHEACK, "begin time must ms")) + return + } + if query.EndTime != 0 && query.EndTime < 1e12 { + c.JSON(422, resp.CodeMsg(resp.CODE_PARAM_CHEACK, "end time must ms")) + return + } + + auditLog, err := neDataService.NewAMF.GetAuditLog(query.NeId) + if err != nil { + c.JSON(200, result.ErrMsg(err.Error())) + return + } + if len(auditLog) == 0 { + c.JSON(200, result.ErrMsg("audit log is empty")) + return + } + + // 对auditLog按time字段排序 + sort.SliceStable(auditLog, func(i, j int) bool { + // 获取i的time值 + timeI, okI := auditLog[i]["time"].(string) + // 获取j的time值 + timeJ, okJ := auditLog[j]["time"].(string) + // 如果任何一个不包含有效time字段,将它们放在后面 + if !okI || !okJ { + return okI && !okJ + } + // 解析时间字符串 + tI, errI := time.Parse(time.DateTime, timeI) + tJ, errJ := time.Parse(time.DateTime, timeJ) + + // 如果任何一个解析失败,将它们放在后面 + if errI != nil || errJ != nil { + return errJ != nil && errI == nil + } + // 按时间升序排序(如果需要降序,改为tI.After(tJ)) + return tI.After(tJ) + }) + + // 响应格式 + timeStr := "" + data := make([]map[string]any, 0) + if query.BeginTime > 1e12 && query.EndTime > 1e12 { + // 时间处理 + beginT := time.UnixMilli(query.BeginTime) + endT := time.UnixMilli(query.EndTime) + timeStr = fmt.Sprintf("%s - %s", beginT.Format(time.DateTime), endT.Format(time.DateTime)) + for _, v := range auditLog { + timeV, ok := v["time"].(string) + if !ok { + continue + } + // 解析时间 + t, err := time.Parse(time.DateTime, timeV) + if err != nil { + continue + } + + // 检查时间是否在范围内 + if t.After(beginT) && t.After(endT) { + data = append(data, map[string]any{ + "supi": fmt.Sprint(v["supi"]), + "amf_ue_ngap_id": parse.Number(v["amf_ue_ngap_id"]), + "ran_ue_ngap_id": parse.Number(v["ran_ue_ngap_id"]), + "gnb_id": parse.Number(v["gnb_id"]), + "time": timeV, + }) + } + } + } else { + lastItem := auditLog[0] + lastTime := "" + if timeV, ok := lastItem["time"].(string); ok { + lastTime = timeV[:19] + } + timeStr = lastTime + for _, v := range auditLog { + timeV, ok := v["time"].(string) + if !ok { + continue + } + if !strings.HasPrefix(timeV, lastTime) { + continue + } + data = append(data, map[string]any{ + "supi": fmt.Sprint(v["supi"]), + "amf_ue_ngap_id": parse.Number(v["amf_ue_ngap_id"]), + "ran_ue_ngap_id": parse.Number(v["ran_ue_ngap_id"]), + "gnb_id": parse.Number(v["gnb_id"]), + }) + } + } + c.JSON(200, map[string]any{ + "time": timeStr, + "ngap_context": data, + }) +} diff --git a/src/modules/network_data/service/amf.go b/src/modules/network_data/service/amf.go new file mode 100644 index 00000000..8956c32c --- /dev/null +++ b/src/modules/network_data/service/amf.go @@ -0,0 +1,98 @@ +package service + +import ( + "fmt" + "os" + "path/filepath" + "runtime" + "strings" + "time" + + "be.ems/src/framework/utils/file" + neService "be.ems/src/modules/network_element/service" +) + +// 实例化数据层 AMF 结构体 +var NewAMF = &AMF{ + neInfoService: neService.NewNeInfo, +} + +// AMF 备份相关 服务层处理 +type AMF struct { + neInfoService *neService.NeInfo // 网元信息服务 +} + +// FTPConfigInfo 获取FTP配置信息 +func (r AMF) GetAuditLog(neId string) ([]map[string]any, error) { + // 网管本地路径 + omcPath := "/usr/local/omc/backup/log" + if runtime.GOOS == "windows" { + omcPath = fmt.Sprintf("C:%s", omcPath) + } + localDirPath := fmt.Sprintf("%s/%s/%s", omcPath, "amf", neId) + + // 网元主机的SSH客户端 + sshClient, err := r.neInfoService.NeRunSSHClient("AMF", neId) + if err != nil { + return nil, err + } + defer sshClient.Close() + // 网元主机的SSH客户端进行文件传输 + sftpClient, err := sshClient.NewClientSFTP() + if err != nil { + return nil, fmt.Errorf("ne info sftp client err") + } + defer sftpClient.Close() + // 网元端文件路径 + neFilePath := "/var/log/amf-audit.log" + localFilePath := filepath.Join(localDirPath, filepath.Base(neFilePath)) + // 网元端复制到本地 + if err = sftpClient.CopyFileRemoteToLocal(neFilePath, localFilePath); err != nil { + return nil, fmt.Errorf("copy amf audit log err") + } + + // 读取本地文件 + arr := file.ReadFileTXTLine(",", localFilePath) + if len(arr) == 0 { + return nil, fmt.Errorf("read file err") + } + // 解析日志 + data := make([]map[string]any, 0) + for _, item := range arr { + if len(item) < 10 { + continue + } + timeStr := item[0] + timeStr = timeStr[0:10] + " " + timeStr[11:] // 将破折号替换成空格,方便解析 + t, err := time.Parse(time.DateTime, timeStr) + if err != nil { + continue + } + + supi := strings.TrimPrefix(item[1], "SUPI:imsi-") + amfUeNgapId := strings.TrimPrefix(item[2], "AMF-UE-NGAP-ID:") + ranUeNgapId := strings.TrimPrefix(item[3], "RAN-UE-NGAP-ID:") + gnbId := strings.TrimPrefix(item[4], "GNB-ID:") + gnbAddr := strings.TrimPrefix(item[5], "GNB-ADDR:") + tai := strings.TrimPrefix(item[6], "TAI:") + opType := strings.TrimPrefix(item[7], "OP-TYPE:") + regType := strings.TrimPrefix(item[8], "REG-TYPE:") + result := strings.TrimPrefix(item[9], "RESULT:") + + data = append(data, map[string]any{ + "time": t.Format(time.DateTime), + "supi": supi, + "amf_ue_ngap_id": amfUeNgapId, + "ran_ue_ngap_id": ranUeNgapId, + "gnb_id": gnbId, + "gnb_addr": gnbAddr, + "tai": tai, + "op_type": opType, + "reg_type": regType, + "result": result, + }) + } + + os.Remove(localFilePath) + return data, nil +}