package cdr import ( "encoding/json" "fmt" "net/http" "strings" "time" ueCallBackTicket "be.ems/features/ue/mf_callback_ticket" "be.ems/lib/config" "be.ems/lib/core/ctx" "be.ems/lib/dborm" "be.ems/lib/email" "be.ems/lib/log" "be.ems/lib/services" neService "be.ems/src/modules/network_element/service" wsService "be.ems/src/modules/ws/service" ) var ( UriCDREvent = config.DefaultUriPrefix + "/cdrManagement/v1/elementType/{elementTypeValue}/objectType/cdrEvent" UriCDRFile = config.DefaultUriPrefix + "/cdrManagement/v1/elementType/{elementTypeValue}/objectType/cdrFile" CustomUriCDREvent = config.UriPrefix + "/cdrManagement/v1/elementType/{elementTypeValue}/objectType/cdrEvent" CustomUriCDRFile = config.UriPrefix + "/cdrManagement/v1/elementType/{elementTypeValue}/objectType/cdrFile" ) // CDREvent CDR数据表格结构体 type CDREvent struct { NeType string `json:"neType" xorm:"ne_type"` NeName string `json:"neName" xorm:"ne_name"` RmUID string `json:"rmUID" xorm:"rm_uid"` Timestamp int `json:"timestamp" xorm:"timestamp"` CDR map[string]any `json:"CDR" xorm:"cdr_json"` } // PostCDREventFrom 接收CDR数据请求 func PostCDREventFrom(w http.ResponseWriter, r *http.Request) { log.Info("PostCDREventFrom processing... ") neType := ctx.GetParam(r, "elementTypeValue") var cdrEvent CDREvent if err := ctx.ShouldBindJSON(r, &cdrEvent); err != nil { services.ResponseInternalServerError500ProcessError(w, err) return } neTypeLower := strings.ToLower(cdrEvent.NeType) if neType == "" || neType != neTypeLower { services.ResponseInternalServerError500ProcessError(w, fmt.Errorf("inconsistent network element types")) return } tableName := fmt.Sprintf("cdr_event_%s", neTypeLower) affected, err := dborm.XormInsertTableOne(tableName, cdrEvent) if err != nil && affected <= 0 { log.Error("Failed to insert "+tableName, err) services.ResponseInternalServerError500ProcessError(w, err) return } // 发送到匹配的网元 neInfo := neService.NewNeInfo.SelectNeInfoByRmuid(cdrEvent.RmUID) if neInfo.RmUID == cdrEvent.RmUID { // 推送到ws订阅组 switch neInfo.NeType { case "IMS": if v, ok := cdrEvent.CDR["recordType"]; ok && (v == "MOC" || v == "MTSM") { wsService.NewWSSend.ByGroupID(wsService.GROUP_IMS_CDR+neInfo.NeId, cdrEvent) } case "SMF": wsService.NewWSSend.ByGroupID(wsService.GROUP_SMF_CDR+neInfo.NeId, cdrEvent) case "SMSC": wsService.NewWSSend.ByGroupID(wsService.GROUP_SMSC_CDR+neInfo.NeId, cdrEvent) case "SGWC": wsService.NewWSSend.ByGroupID(wsService.GROUP_SGWC_CDR+neInfo.NeId, cdrEvent) } } // MF网元类型特殊处理, 未接电话的回拨工单流转处理 if neTypeLower == "mf" && cdrEvent.CDR["recordType"] == "MOC" && cdrEvent.CDR["agentName"] == "" { // 发送到MF网元 // 构造网元MF的API地址 url := fmt.Sprintf("http://%s:%d/api/rest/systemManagement/v1/elementType/%s/objectType/config/agents", neInfo.IP, neInfo.Port, strings.ToLower(neInfo.NeType)) // 发送HTTP请求获取座席列表 resp, err := http.Get(url) if err != nil { log.Error("Failed to get MF agents", err) services.ResponseInternalServerError500ProcessError(w, err) return } defer resp.Body.Close() // 解析座席列表响应 var agentResp struct { Code int `json:"code"` Data []ueCallBackTicket.AgentInfo `json:"data"` Msg string `json:"msg"` } if err := json.NewDecoder(resp.Body).Decode(&agentResp); err != nil { log.Error("Failed to decode MF agents response", err) services.ResponseInternalServerError500ProcessError(w, err) return } // 调用服务获取最新一个被分配工单的座席和下一个要分配的座席 mfService := ueCallBackTicket.NewCallbackTicketService() lastAgentName, err := mfService.GetLastAssignedAgent() if err != nil { log.Error("Failed to get last assigned agent", err) // 可以继续执行,不返回错误 } // 选择下一个要分配的座席 selectedAgent := mfService.SelectNextAgent(agentResp.Data, lastAgentName) if selectedAgent != nil { // 创建回调工单 var updatedAt *int64 = nil ticket := ueCallBackTicket.CallbackTicket{ CallerNumber: cdrEvent.CDR["callerParty"].(string), CalleeNumber: cdrEvent.CDR["calledParty"].(string), Status: ueCallBackTicket.TicketStatusNew.Enum(), AgentName: selectedAgent.Name, AgentEmail: selectedAgent.Email, AgentMobile: selectedAgent.Mobile, Comment: "", MsdData: cdrEvent.CDR["msdData"].(string), RmUid: cdrEvent.RmUID, CreatedAt: time.Now().UnixMicro(), UpdatedAt: updatedAt, } if err := mfService.InsertCallbackTicket(&ticket); err != nil { log.Error("Failed to insert MF callback ticket", err) // services.ResponseInternalServerError500ProcessError(w, err) // return } // 新工单分配后发送邮件通知 if selectedAgent.Email != "" { // 发送邮件通知 emailConfig := config.GetSMTPConfig() if emailConfig != nil && emailConfig.Enabled { // 创建配置副本,避免修改全局配置 emailCopy := *emailConfig // 浅拷贝结构体 // 合并配置中的To地址和当前工单的座席邮箱 var recipients []string // 添加配置中的原始收件人(如管理员、监控人员等) if len(emailConfig.To) > 0 { recipients = append(recipients, strings.Split(emailConfig.To, ",")...) } // 添加当前工单的座席邮箱 recipients = append(recipients, ticket.AgentEmail) // 去重处理(避免重复邮箱) emailCopy.To = strings.Join(email.RemoveDuplicateEmails(recipients), ",") // 设置邮件主题和内容 emailCopy.Subject = "新工单分配通知" emailCopy.Body = fmt.Sprintf("您被分配了一个新的回拨工单(编号:%d, 主叫号码:%s), 请及时处理.", ticket.TicketId, ticket.CallerNumber) go email.SendEmailWithGomail(emailCopy) // 异步发送 } } } log.Warn("No available agents found for callback ticket") } // MF网元类型特殊处理, 处理座席回拨的工单流转 if neTypeLower == "mf" && cdrEvent.CDR["recordType"] == "MTC" { // 获取座席号码(主叫)和被叫号码 agentNumber, ok1 := cdrEvent.CDR["callerParty"].(string) callerNumber, ok2 := cdrEvent.CDR["calledParty"].(string) if !ok1 || !ok2 { log.Error("Invalid CDR format: missing callerParty or calledParty") services.ResponseInternalServerError500ProcessError(w, fmt.Errorf("invalid CDR format")) return } // 获取通话时长 callDuration, ok := cdrEvent.CDR["callDuration"].(float64) if !ok { // 尝试其他可能的类型 if durationInt, ok := cdrEvent.CDR["callDuration"].(int); ok { callDuration = float64(durationInt) } else { log.Error("Invalid CDR format: callDuration is not a number") services.ResponseInternalServerError500ProcessError(w, fmt.Errorf("invalid CDR format")) return } } // 通过座席号码和主叫号码查找符合条件的工单 mfService := ueCallBackTicket.NewCallbackTicketService() ticket, err := mfService.FindCallbackTicketByAgentAndCaller(agentNumber, callerNumber) if err != nil { log.Error("Failed to find callback ticket", err) services.ResponseInternalServerError500ProcessError(w, err) return } if ticket == nil { // 没有找到对应的工单,可能是手动呼叫,不处理 log.Warn(fmt.Sprintf("No callback ticket found for agent %s and caller %s", agentNumber, callerNumber)) services.ResponseStatusOK204NoContent(w) return } // 获取通话信息 seizureTime, _ := cdrEvent.CDR["seizureTime"].(string) releaseTime, _ := cdrEvent.CDR["releaseTime"].(string) cause, _ := cdrEvent.CDR["cause"].(string) // 处理回拨结果并更新工单 if err := mfService.ProcessCallbackResult(ticket, callDuration, seizureTime, releaseTime, cause); err != nil { log.Error("Failed to process callback result", err) services.ResponseInternalServerError500ProcessError(w, err) return } log.Info(fmt.Sprintf("Successfully processed callback for ticket %d", ticket.TicketId)) } services.ResponseStatusOK204NoContent(w) }