Init
This commit is contained in:
@@ -0,0 +1,633 @@
|
||||
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/mlogclub/simple/sqls"
|
||||
"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.buildExternalInfo(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.buildExternalInfo(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.buildExternalInfo(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.buildExternalInfo(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.buildExternalInfo(externalID)
|
||||
conversation := ConversationService.FindOne(sqls.NewCnd().
|
||||
Eq("external_source", external.ExternalSource).
|
||||
Eq("external_id", external.ExternalID).
|
||||
In("status", []enums.IMConversationStatus{
|
||||
enums.IMConversationStatusPending,
|
||||
enums.IMConversationStatusActive,
|
||||
}).
|
||||
Desc("id"))
|
||||
|
||||
if conversation == nil {
|
||||
conversation, err = ConversationService.Create(external, 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) buildExternalInfo(externalUserID string) openidentity.ExternalInfo {
|
||||
return openidentity.ExternalInfo{
|
||||
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
|
||||
}
|
||||
Reference in New Issue
Block a user