230 lines
8.1 KiB
Go
230 lines
8.1 KiB
Go
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: "",
|
|
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)
|
|
}
|