cf63b75590
Co-authored-by: Copilot <copilot@github.com>
622 lines
20 KiB
Go
622 lines
20 KiB
Go
package services
|
|
|
|
import (
|
|
"encoding/json"
|
|
"log/slog"
|
|
"strings"
|
|
"time"
|
|
|
|
"cs-agent/internal/models"
|
|
"cs-agent/internal/pkg/enums"
|
|
"cs-agent/internal/pkg/errorsx"
|
|
"cs-agent/internal/pkg/openidentity"
|
|
"cs-agent/internal/wxwork"
|
|
|
|
"github.com/mlogclub/simple/common/strs"
|
|
"github.com/silenceper/wechat/v2/work/kf"
|
|
"github.com/silenceper/wechat/v2/work/kf/syncmsg"
|
|
)
|
|
|
|
const (
|
|
wxWorkKFSystemOperatorName = "wxwork_kf"
|
|
wxWorkKFSyncMsgLimit = 1000
|
|
)
|
|
|
|
var WxWorkKFInboundService = newWxWorkKFInboundService()
|
|
|
|
func newWxWorkKFInboundService() *wxWorkKFInboundService {
|
|
return &wxWorkKFInboundService{}
|
|
}
|
|
|
|
type wxWorkKFInboundService struct {
|
|
}
|
|
|
|
func (s *wxWorkKFInboundService) SyncCallbackMessages(message kf.CallbackMessage) error {
|
|
cli, err := wxwork.GetWorkCli().GetKF()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
cursor := ""
|
|
if state := WxWorkKFSyncStateService.GetByOpenKfID(message.OpenKfID); state != nil {
|
|
cursor = strings.TrimSpace(state.NextCursor)
|
|
}
|
|
|
|
for {
|
|
result, syncErr := cli.SyncMsg(kf.SyncMsgOptions{
|
|
Cursor: cursor,
|
|
Token: message.Token,
|
|
Limit: wxWorkKFSyncMsgLimit,
|
|
OpenKfID: message.OpenKfID,
|
|
})
|
|
if syncErr != nil {
|
|
return syncErr
|
|
}
|
|
|
|
for _, item := range result.MsgList {
|
|
if err := s.consumeSyncMessage(item); err != nil {
|
|
slog.Error("consume wxwork kf sync message failed",
|
|
"open_kfid", item.OpenKFID,
|
|
"external_userid", item.ExternalUserID,
|
|
"msg_id", item.MsgID,
|
|
"msg_type", item.MsgType,
|
|
"event", item.EventType,
|
|
"error", err,
|
|
)
|
|
}
|
|
}
|
|
|
|
if err := s.saveNextCursor(message.OpenKfID, result.NextCursor); err != nil {
|
|
return err
|
|
}
|
|
|
|
if result.HasMore != 1 || strings.TrimSpace(result.NextCursor) == "" {
|
|
return nil
|
|
}
|
|
cursor = result.NextCursor
|
|
}
|
|
}
|
|
|
|
func (s *wxWorkKFInboundService) consumeSyncMessage(item syncmsg.Message) error {
|
|
msgID := strings.TrimSpace(item.MsgID)
|
|
if msgID == "" {
|
|
return errorsx.InvalidParam("企业微信消息ID不能为空")
|
|
}
|
|
if WxWorkKFMessageRefService.Take("wx_msg_id = ?", msgID) != nil {
|
|
return nil
|
|
}
|
|
|
|
switch strings.TrimSpace(item.MsgType) {
|
|
case "text":
|
|
return s.handleTextMessage(item)
|
|
case "image":
|
|
return s.handleImageMessage(item)
|
|
case "file":
|
|
return s.handleFileMessage(item)
|
|
case "event":
|
|
return s.handleEventMessage(item)
|
|
default:
|
|
return s.handleUnsupportedMessage(item)
|
|
}
|
|
}
|
|
|
|
func (s *wxWorkKFInboundService) handleTextMessage(item syncmsg.Message) error {
|
|
payload := syncmsg.Text{}
|
|
if err := json.Unmarshal(item.OriginData, &payload); err != nil {
|
|
return err
|
|
}
|
|
|
|
conversation, err := s.ensureConversation(payload.BaseMessage, map[string]any{
|
|
"msgType": payload.MsgType,
|
|
"menuId": payload.Text.MenuID,
|
|
})
|
|
if err != nil {
|
|
return err
|
|
}
|
|
message, err := MessageService.SendCustomerMessage(
|
|
conversation.ID,
|
|
s.buildInboundClientMsgID(item.MsgID),
|
|
enums.IMMessageTypeText,
|
|
strings.TrimSpace(payload.Text.Content),
|
|
"",
|
|
s.buildExternalUser(payload.ExternalUserID),
|
|
)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return s.createMessageRef(conversation.ID, message.ID, item, enums.WxWorkKFMessageDirectionIn, enums.WxWorkKFMessageSendStatusReceived)
|
|
}
|
|
|
|
func (s *wxWorkKFInboundService) handleImageMessage(item syncmsg.Message) error {
|
|
payload := syncmsg.Image{}
|
|
if err := json.Unmarshal(item.OriginData, &payload); err != nil {
|
|
return err
|
|
}
|
|
conversation, err := s.ensureConversation(payload.BaseMessage, map[string]any{
|
|
"msgType": payload.MsgType,
|
|
"mediaId": payload.Image.MediaID,
|
|
})
|
|
if err != nil {
|
|
return err
|
|
}
|
|
canonicalPayload, content, err := s.buildInboundAssetPayload(conversation.ID, strings.TrimSpace(payload.Image.MediaID))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
message, err := MessageService.SendCustomerMessage(
|
|
conversation.ID,
|
|
s.buildInboundClientMsgID(item.MsgID),
|
|
enums.IMMessageTypeImage,
|
|
content,
|
|
canonicalPayload,
|
|
s.buildExternalUser(payload.ExternalUserID),
|
|
)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return s.createMessageRef(conversation.ID, message.ID, item, enums.WxWorkKFMessageDirectionIn, enums.WxWorkKFMessageSendStatusReceived)
|
|
}
|
|
|
|
func (s *wxWorkKFInboundService) handleFileMessage(item syncmsg.Message) error {
|
|
payload := syncmsg.File{}
|
|
if err := json.Unmarshal(item.OriginData, &payload); err != nil {
|
|
return err
|
|
}
|
|
conversation, err := s.ensureConversation(payload.BaseMessage, map[string]any{
|
|
"msgType": payload.MsgType,
|
|
"mediaId": payload.File.MediaID,
|
|
})
|
|
if err != nil {
|
|
return err
|
|
}
|
|
canonicalPayload, content, err := s.buildInboundAssetPayload(conversation.ID, strings.TrimSpace(payload.File.MediaID))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
message, err := MessageService.SendCustomerMessage(
|
|
conversation.ID,
|
|
s.buildInboundClientMsgID(item.MsgID),
|
|
enums.IMMessageTypeAttachment,
|
|
content,
|
|
canonicalPayload,
|
|
s.buildExternalUser(payload.ExternalUserID),
|
|
)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return s.createMessageRef(conversation.ID, message.ID, item, enums.WxWorkKFMessageDirectionIn, enums.WxWorkKFMessageSendStatusReceived)
|
|
}
|
|
|
|
func (s *wxWorkKFInboundService) handleUnsupportedMessage(item syncmsg.Message) error {
|
|
base, err := s.parseBaseMessage(item.OriginData)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
conversation, convErr := s.ensureConversation(base, map[string]any{"msgType": item.MsgType})
|
|
if convErr != nil {
|
|
return convErr
|
|
}
|
|
content := s.buildUnsupportedContent(item.MsgType)
|
|
message, err := MessageService.SendCustomerMessage(
|
|
conversation.ID,
|
|
s.buildInboundClientMsgID(item.MsgID),
|
|
enums.IMMessageTypeText,
|
|
content,
|
|
string(item.OriginData),
|
|
s.buildExternalUser(base.ExternalUserID),
|
|
)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return s.createMessageRef(conversation.ID, message.ID, item, enums.WxWorkKFMessageDirectionIn, enums.WxWorkKFMessageSendStatusReceived)
|
|
}
|
|
|
|
func (s *wxWorkKFInboundService) handleEventMessage(item syncmsg.Message) error {
|
|
switch strings.TrimSpace(item.EventType) {
|
|
case enums.WxWorkKFEventTypeEnterSession:
|
|
return s.handleEnterSessionEvent(item)
|
|
case enums.WxWorkKFEventTypeSessionStatusChange:
|
|
return s.handleSessionStatusChangeEvent(item)
|
|
case enums.WxWorkKFEventTypeMsgSendFail:
|
|
return s.handleMsgSendFailEvent(item)
|
|
default:
|
|
return s.recordOrphanEvent(item, "收到未处理的企业微信事件")
|
|
}
|
|
}
|
|
|
|
func (s *wxWorkKFInboundService) handleEnterSessionEvent(item syncmsg.Message) error {
|
|
payload := syncmsg.EnterSessionEvent{}
|
|
if err := json.Unmarshal(item.OriginData, &payload); err != nil {
|
|
return err
|
|
}
|
|
base := s.normalizeEventBaseMessage(payload.BaseMessage, payload.Event.OpenKFID, payload.Event.ExternalUserID)
|
|
conversation, err := s.ensureConversation(base, map[string]any{
|
|
"scene": payload.Event.Scene,
|
|
"sceneParam": payload.Event.SceneParam,
|
|
"welcomeCode": payload.Event.WelcomeCode,
|
|
})
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if err := s.createMessageRef(conversation.ID, 0, item, enums.WxWorkKFMessageDirectionIn, enums.WxWorkKFMessageSendStatusReceived); err != nil {
|
|
return err
|
|
}
|
|
return s.appendConversationEvent(conversation.ID, "微信客户进入会话", string(item.OriginData))
|
|
}
|
|
|
|
func (s *wxWorkKFInboundService) handleSessionStatusChangeEvent(item syncmsg.Message) error {
|
|
payload := syncmsg.SessionStatusChangeEvent{}
|
|
if err := json.Unmarshal(item.OriginData, &payload); err != nil {
|
|
return err
|
|
}
|
|
base := s.normalizeEventBaseMessage(payload.BaseMessage, payload.Event.OpenKFID, payload.Event.ExternalUserID)
|
|
base.ReceptionistUserID = payload.Event.NewReceptionistUserID
|
|
conversation, err := s.ensureConversation(base, map[string]any{
|
|
"changeType": payload.Event.ChangeType,
|
|
"msgCode": payload.Event.MsgCode,
|
|
})
|
|
if err != nil {
|
|
return err
|
|
}
|
|
sessionStatus := enums.WxWorkKFSessionStatusActive
|
|
switch payload.Event.ChangeType {
|
|
case 2:
|
|
sessionStatus = enums.WxWorkKFSessionStatusTransfer
|
|
case 3:
|
|
sessionStatus = enums.WxWorkKFSessionStatusClosed
|
|
}
|
|
channel, channelErr := s.getChannelByOpenKfID(payload.Event.OpenKFID)
|
|
if channelErr != nil {
|
|
return channelErr
|
|
}
|
|
if err := s.upsertConversationMapping(conversation.ID, channel.ID, payload.Event.OpenKFID, payload.Event.ExternalUserID, payload.Event.NewReceptionistUserID, sessionStatus, payload.SendTime, payload.MsgID, string(item.OriginData)); err != nil {
|
|
return err
|
|
}
|
|
if err := s.createMessageRef(conversation.ID, 0, item, enums.WxWorkKFMessageDirectionIn, enums.WxWorkKFMessageSendStatusReceived); err != nil {
|
|
return err
|
|
}
|
|
return s.appendConversationEvent(conversation.ID, "微信会话状态变更", string(item.OriginData))
|
|
}
|
|
|
|
func (s *wxWorkKFInboundService) handleMsgSendFailEvent(item syncmsg.Message) error {
|
|
payload := syncmsg.MsgSendFailEvent{}
|
|
if err := json.Unmarshal(item.OriginData, &payload); err != nil {
|
|
return err
|
|
}
|
|
slog.Warn("received wxwork msg_send_fail event",
|
|
slog.String("msg", string(item.OriginData)),
|
|
)
|
|
base := s.normalizeEventBaseMessage(payload.BaseMessage, payload.Event.OpenKFID, payload.Event.ExternalUserID)
|
|
conversation, err := s.ensureConversation(base, map[string]any{
|
|
"failMsgId": payload.Event.FailMsgID,
|
|
"failType": payload.Event.FailType,
|
|
})
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if ref := WxWorkKFMessageRefService.GetByWxMsgID(payload.Event.FailMsgID); ref != nil {
|
|
targetMessageID := ref.MessageID
|
|
slog.Warn("mark wxwork message ref failed",
|
|
"conversation_id", ref.ConversationID,
|
|
"message_id", ref.MessageID,
|
|
"wx_msg_id", ref.WxMsgID,
|
|
"fail_type", payload.Event.FailType,
|
|
"open_kfid", payload.Event.OpenKFID,
|
|
"external_userid", payload.Event.ExternalUserID,
|
|
)
|
|
_ = WxWorkKFMessageRefService.Updates(ref.ID, map[string]any{
|
|
"send_status": enums.WxWorkKFMessageSendStatusFailed,
|
|
"fail_reason": string(item.OriginData),
|
|
"updated_at": time.Now(),
|
|
})
|
|
|
|
if outbox := ChannelMessageOutboxService.GetByMessageID(enums.ChannelTypeWxWorkKF, targetMessageID); outbox != nil {
|
|
if err := WxWorkKFOutboundService.markOutboxFailed(outbox, string(item.OriginData)); err != nil {
|
|
slog.Warn("mark wxwork outbox failed from callback event failed",
|
|
"outbox_id", outbox.ID,
|
|
"conversation_id", outbox.ConversationID,
|
|
"message_id", outbox.MessageID,
|
|
"fail_msg_id", payload.Event.FailMsgID,
|
|
"fail_type", payload.Event.FailType,
|
|
"error", err,
|
|
)
|
|
} else {
|
|
slog.Warn("mark wxwork outbox failed from callback event",
|
|
"outbox_id", outbox.ID,
|
|
"conversation_id", outbox.ConversationID,
|
|
"message_id", outbox.MessageID,
|
|
"fail_msg_id", payload.Event.FailMsgID,
|
|
"fail_type", payload.Event.FailType,
|
|
)
|
|
}
|
|
} else {
|
|
slog.Warn("wxwork msg_send_fail event missing outbox",
|
|
"message_id", targetMessageID,
|
|
"fail_msg_id", payload.Event.FailMsgID,
|
|
"fail_type", payload.Event.FailType,
|
|
)
|
|
}
|
|
} else {
|
|
slog.Warn("wxwork msg_send_fail event missing message ref",
|
|
"fail_msg_id", payload.Event.FailMsgID,
|
|
"fail_type", payload.Event.FailType,
|
|
"open_kfid", payload.Event.OpenKFID,
|
|
"external_userid", payload.Event.ExternalUserID,
|
|
)
|
|
}
|
|
|
|
if err := s.createMessageRef(conversation.ID, 0, item, enums.WxWorkKFMessageDirectionIn, enums.WxWorkKFMessageSendStatusReceived); err != nil {
|
|
return err
|
|
}
|
|
return s.appendConversationEvent(conversation.ID, "微信消息发送失败事件", string(item.OriginData))
|
|
}
|
|
|
|
func (s *wxWorkKFInboundService) recordOrphanEvent(item syncmsg.Message, content string) error {
|
|
base, err := s.parseBaseMessage(item.OriginData)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
conversation, convErr := s.ensureConversation(base, map[string]any{"eventType": item.EventType})
|
|
if convErr != nil {
|
|
return convErr
|
|
}
|
|
if err := s.createMessageRef(conversation.ID, 0, item, enums.WxWorkKFMessageDirectionIn, enums.WxWorkKFMessageSendStatusReceived); err != nil {
|
|
return err
|
|
}
|
|
return s.appendConversationEvent(conversation.ID, content, string(item.OriginData))
|
|
}
|
|
|
|
func (s *wxWorkKFInboundService) ensureConversation(base syncmsg.BaseMessage, profile map[string]any) (*models.Conversation, error) {
|
|
externalID := strings.TrimSpace(base.ExternalUserID)
|
|
if externalID == "" {
|
|
return nil, errorsx.InvalidParam("企业微信客户ID不能为空")
|
|
}
|
|
channel, err := s.getChannelByOpenKfID(base.OpenKFID)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
external := s.buildExternalUser(externalID)
|
|
conversation, err := ConversationService.Create(external, channel.ID, channel.AIAgentID)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if err := s.upsertConversationMapping(
|
|
conversation.ID,
|
|
channel.ID,
|
|
base.OpenKFID,
|
|
base.ExternalUserID,
|
|
base.ReceptionistUserID,
|
|
enums.WxWorkKFSessionStatusActive,
|
|
base.SendTime,
|
|
base.MsgID,
|
|
s.mustMarshal(profile),
|
|
); err != nil {
|
|
return nil, err
|
|
}
|
|
return conversation, nil
|
|
}
|
|
|
|
func (s *wxWorkKFInboundService) upsertConversationMapping(conversationID, channelID int64, openKfID, externalUserID, servicerUserID string, sessionStatus enums.WxWorkKFSessionStatus, sendTime uint64, lastMsgID, rawProfile string) error {
|
|
now := time.Now()
|
|
lastMsgTime := s.parseSendTime(sendTime)
|
|
existing := WxWorkKFConversationService.Take("conversation_id = ?", conversationID)
|
|
if existing != nil {
|
|
updates := map[string]any{
|
|
"channel_id": channelID,
|
|
"open_kf_id": strings.TrimSpace(openKfID),
|
|
"external_user_id": strings.TrimSpace(externalUserID),
|
|
"servicer_user_id": strings.TrimSpace(servicerUserID),
|
|
"session_status": string(sessionStatus),
|
|
"last_wx_msg_id": strings.TrimSpace(lastMsgID),
|
|
"updated_at": now,
|
|
"status": enums.StatusOk,
|
|
}
|
|
if lastMsgTime != nil {
|
|
updates["last_wx_msg_time"] = *lastMsgTime
|
|
}
|
|
if strings.TrimSpace(rawProfile) != "" {
|
|
updates["raw_profile"] = rawProfile
|
|
}
|
|
return WxWorkKFConversationService.Updates(existing.ID, updates)
|
|
}
|
|
|
|
return WxWorkKFConversationService.Create(&models.WxWorkKFConversation{
|
|
ConversationID: conversationID,
|
|
ChannelID: channelID,
|
|
OpenKfID: strings.TrimSpace(openKfID),
|
|
ExternalUserID: strings.TrimSpace(externalUserID),
|
|
ServicerUserID: strings.TrimSpace(servicerUserID),
|
|
SessionStatus: string(sessionStatus),
|
|
LastWxMsgID: strings.TrimSpace(lastMsgID),
|
|
LastWxMsgTime: lastMsgTime,
|
|
RawProfile: strings.TrimSpace(rawProfile),
|
|
Status: enums.StatusOk,
|
|
AuditFields: models.AuditFields{
|
|
CreatedAt: now,
|
|
CreateUserID: 0,
|
|
CreateUserName: wxWorkKFSystemOperatorName,
|
|
UpdatedAt: now,
|
|
UpdateUserID: 0,
|
|
UpdateUserName: wxWorkKFSystemOperatorName,
|
|
},
|
|
})
|
|
}
|
|
|
|
func (s *wxWorkKFInboundService) createMessageRef(conversationID, messageID int64, item syncmsg.Message, direction enums.WxWorkKFMessageDirection, sendStatus enums.WxWorkKFMessageSendStatus) error {
|
|
if WxWorkKFMessageRefService.Take("wx_msg_id = ?", item.MsgID) != nil {
|
|
return nil
|
|
}
|
|
now := time.Now()
|
|
return WxWorkKFMessageRefService.Create(&models.WxWorkKFMessageRef{
|
|
ConversationID: conversationID,
|
|
MessageID: messageID,
|
|
WxMsgID: strings.TrimSpace(item.MsgID),
|
|
Direction: string(direction),
|
|
Origin: int(item.Origin),
|
|
OpenKfID: strings.TrimSpace(item.OpenKFID),
|
|
ExternalUserID: strings.TrimSpace(item.ExternalUserID),
|
|
SendStatus: string(sendStatus),
|
|
RawPayload: string(item.OriginData),
|
|
Status: enums.StatusOk,
|
|
AuditFields: models.AuditFields{
|
|
CreatedAt: now,
|
|
CreateUserID: 0,
|
|
CreateUserName: wxWorkKFSystemOperatorName,
|
|
UpdatedAt: now,
|
|
UpdateUserID: 0,
|
|
UpdateUserName: wxWorkKFSystemOperatorName,
|
|
},
|
|
})
|
|
}
|
|
|
|
func (s *wxWorkKFInboundService) saveNextCursor(openKfID, nextCursor string) error {
|
|
openKfID = strings.TrimSpace(openKfID)
|
|
if openKfID == "" {
|
|
return errorsx.InvalidParam("openKfID不能为空")
|
|
}
|
|
now := time.Now()
|
|
state := WxWorkKFSyncStateService.Take("open_kf_id = ?", openKfID)
|
|
if state != nil {
|
|
return WxWorkKFSyncStateService.Updates(state.ID, map[string]any{
|
|
"next_cursor": strings.TrimSpace(nextCursor),
|
|
"last_sync_at": now,
|
|
"updated_at": now,
|
|
"status": enums.StatusOk,
|
|
})
|
|
}
|
|
return WxWorkKFSyncStateService.Create(&models.WxWorkKFSyncState{
|
|
OpenKfID: openKfID,
|
|
NextCursor: strings.TrimSpace(nextCursor),
|
|
LastSyncAt: &now,
|
|
Status: enums.StatusOk,
|
|
AuditFields: models.AuditFields{
|
|
CreatedAt: now,
|
|
CreateUserID: 0,
|
|
CreateUserName: wxWorkKFSystemOperatorName,
|
|
UpdatedAt: now,
|
|
UpdateUserID: 0,
|
|
UpdateUserName: wxWorkKFSystemOperatorName,
|
|
},
|
|
})
|
|
}
|
|
|
|
func (s *wxWorkKFInboundService) appendConversationEvent(conversationID int64, content, payload string) error {
|
|
return ConversationEventLogService.Create(&models.ConversationEventLog{
|
|
ConversationID: conversationID,
|
|
EventType: enums.IMEventTypeWxWorkKFEvent,
|
|
OperatorType: enums.IMSenderTypeSystem,
|
|
OperatorID: 0,
|
|
Content: strings.TrimSpace(content),
|
|
Payload: strings.TrimSpace(payload),
|
|
CreatedAt: time.Now(),
|
|
})
|
|
}
|
|
|
|
func (s *wxWorkKFInboundService) buildInboundAssetPayload(conversationID int64, mediaID string) (string, string, error) {
|
|
mediaID = strings.TrimSpace(mediaID)
|
|
if mediaID == "" {
|
|
return "", "", errorsx.InvalidParam("企业微信媒体ID不能为空")
|
|
}
|
|
materialCli := wxwork.GetWorkCli().GetMaterial()
|
|
data, err := materialCli.GetTempFile(mediaID)
|
|
if err != nil {
|
|
return "", "", err
|
|
}
|
|
asset, err := AssetService.UploadBytes(data, "", "", nil)
|
|
if err != nil {
|
|
return "", "", err
|
|
}
|
|
canonicalPayload, err := buildIMMessageAssetPayload(asset)
|
|
if err != nil {
|
|
return "", "", err
|
|
}
|
|
content := strings.TrimSpace(asset.Filename)
|
|
if strs.IsBlank(content) {
|
|
content = "[文件]"
|
|
}
|
|
return canonicalPayload, content, nil
|
|
}
|
|
|
|
func (s *wxWorkKFInboundService) getChannelByOpenKfID(openKfID string) (*models.Channel, error) {
|
|
openKfID = strings.TrimSpace(openKfID)
|
|
if openKfID == "" {
|
|
return nil, errorsx.InvalidParam("企业微信 openKfID 不能为空")
|
|
}
|
|
channel := ChannelService.GetEnabledWxWorkKFChannelByOpenKfID(openKfID)
|
|
if channel == nil {
|
|
return nil, errorsx.InvalidParam("未找到匹配的企业微信接入渠道")
|
|
}
|
|
if channel.AIAgentID <= 0 {
|
|
return nil, errorsx.InvalidParam("企业微信接入渠道未绑定AI Agent")
|
|
}
|
|
agent := AIAgentService.Get(channel.AIAgentID)
|
|
if agent == nil || agent.Status != enums.StatusOk {
|
|
return nil, errorsx.InvalidParam("企业微信接入渠道绑定的AI Agent不存在或已禁用")
|
|
}
|
|
return channel, nil
|
|
}
|
|
|
|
func (s *wxWorkKFInboundService) buildExternalUser(externalUserID string) openidentity.ExternalUser {
|
|
return openidentity.ExternalUser{
|
|
ExternalSource: enums.ExternalSourceWxWorkKF,
|
|
ExternalID: strings.TrimSpace(externalUserID),
|
|
ExternalName: strings.TrimSpace(externalUserID),
|
|
}
|
|
}
|
|
|
|
func (s *wxWorkKFInboundService) buildInboundClientMsgID(wxMsgID string) string {
|
|
return "wxwork_kf:" + strings.TrimSpace(wxMsgID)
|
|
}
|
|
|
|
func (s *wxWorkKFInboundService) buildUnsupportedContent(msgType string) string {
|
|
switch strings.TrimSpace(msgType) {
|
|
case "voice":
|
|
return "[语音]"
|
|
case "video":
|
|
return "[视频]"
|
|
case "location":
|
|
return "[位置]"
|
|
case "link":
|
|
return "[链接]"
|
|
case "business_card":
|
|
return "[名片]"
|
|
case "miniprogram":
|
|
return "[小程序]"
|
|
default:
|
|
return "[" + strings.TrimSpace(msgType) + "]"
|
|
}
|
|
}
|
|
|
|
func (s *wxWorkKFInboundService) parseSendTime(sendTime uint64) *time.Time {
|
|
if sendTime == 0 {
|
|
return nil
|
|
}
|
|
t := time.Unix(int64(sendTime), 0)
|
|
return &t
|
|
}
|
|
|
|
func (s *wxWorkKFInboundService) mustMarshal(value any) string {
|
|
if value == nil {
|
|
return ""
|
|
}
|
|
raw, err := json.Marshal(value)
|
|
if err != nil {
|
|
return ""
|
|
}
|
|
return string(raw)
|
|
}
|
|
|
|
func (s *wxWorkKFInboundService) parseBaseMessage(raw []byte) (syncmsg.BaseMessage, error) {
|
|
base := syncmsg.BaseMessage{}
|
|
err := json.Unmarshal(raw, &base)
|
|
return base, err
|
|
}
|
|
|
|
func (s *wxWorkKFInboundService) normalizeEventBaseMessage(base syncmsg.BaseMessage, openKfID, externalUserID string) syncmsg.BaseMessage {
|
|
base.OpenKFID = strings.TrimSpace(openKfID)
|
|
base.ExternalUserID = strings.TrimSpace(externalUserID)
|
|
return base
|
|
}
|