Skip to content

【feat】扩展企业微信包,RequestMsgType.Event 下新的回调事件 KF_MSG_OR_EVENT #3175

@laboratory9173

Description

@laboratory9173

此版块专为反馈 bug 及提交需求服务,不负责解答开发问题,请勿发表开发问题,
如果您需要这方面的帮助,请移步问答社区https://weixin.senparc.com/QA

问题描述

微信客服 新接入了回调,进入到 ChatMessageContext 自定义上下文的GetRequestEntityMappingResult的函数里,解析XML 回调参数时候会报错,只能麻烦作者加一个 RequestMsgType.Event 下新的回调事件 KF_MSG_OR_EVENT

[[[WeixinException]]]
[2025/08/13 17:11:14.9403]
[线程:7]
	WeixinException
	AccessTokenOrAppId:
	Message:RequestMessage转换出错!可能是MsgType不存在!,XML:<xml>
  <ToUserName><![CDATA[XXXX]]></ToUserName>
  <CreateTime>XXX</CreateTime>
  <MsgType><![CDATA[event]]></MsgType>
  <Event><![CDATA[kf_msg_or_event]]></Event>
  <Token><![CDATA[XXXX]]></Token>
  <OpenKfId><![CDATA[XXXX]]></OpenKfId>
</xml>
	StackTrace:
	InnerException:Value cannot be null. (Parameter 'entity')
	InnerException.StackTrace:   at Senparc.NeuChar.Helpers.EntityHelper.FillEntityWithXml[T](T entity, XDocument doc)
   at Senparc.Weixin.Work.RequestMessageFactory.GetRequestEntity[TMC](TMC messageContext, XDocument doc)
重现问题步骤(如果可以)
  1. 新建消息回调上下文、消息处理器
  2. 息回调上下文、消息处理器代码如下
/// <summary>
/// 处理企业微信微信客服聊天请求消息
/// </summary>
[HttpPost]
[ServiceUrlEntry("微信客服聊天接收消息服务器", WxAppCodes.CustomerCare, ServiceUrlEntryType.Message, "/WxCorp/Message/Chat")]
public async Task<IActionResult> Chat(PostModel postModel)
{
    var agent = AppConfig.Settings.WeixinCorp.GetAgentByCode(WxAppCodes.Chat);
    postModel.CorpId = AppConfig.Settings.WeixinCorp.WeixinSettings.CorpId;
    postModel.Token          = agent.Token;
    postModel.EncodingAESKey = agent.EncodingAESKey;
    var messageHandler = new ChatMessageHandler(await Request.GetRequestMemoryStreamAsync(), 
                                                postModel, MAX_RECORD_COUNT);

    return await Post("微信客服", postModel, messageHandler);
}

private async Task<IActionResult> Post(string agentName, PostModel postModel, ChatMessageHandler messageHandler)
{
    try {
        SenparcTrace.SendApiLog($"微信客服{agentName}收到消息", messageHandler.RequestDocument?.ToString()); 
// 这个地方他是空的,应该是解码有问题
/*
[[[接口调用]]]
[2025/08/13 15:35:41.4462]
[线程:24]
	URL:微信客服微信客服收到消息
	Result:
*/
        
        messageHandler.SaveRequestMessageLog();

        // 处理微信消息
        await messageHandler.ExecuteAsync(new CancellationToken());

        messageHandler.SaveResponseMessageLog();

        return new FixWeixinBugWeixinResult(messageHandler);
    }
    catch (Exception ex) {
        ex.Log();
        return BadRequest();
    }
}
// 消息处理器
public class ChatMessageHandler : WorkMessageHandler<ChatMessageContext>
{
    public ChatMessageHandler(Stream inputStream, PostModel postModel, int maxRecordCount = 0, IServiceProvider serviceProvider = null)
        : base(inputStream, postModel, maxRecordCount, serviceProvider)
    {
    }

    public override IWorkResponseMessageBase DefaultResponseMessage(IWorkRequestMessageBase requestMessage)
    {
        var responseMessage = this.CreateResponseMessage<ResponseMessageText>();
        responseMessage.Content = "这是一条没有找到合适回复信息的默认消息。";
        return responseMessage;
    }
    
}

// 消息处理器上下文

public class ChatMessageContext : MessageContext<IWorkRequestMessageBase, IWorkResponseMessageBase>, IMessageContext<IWorkRequestMessageBase, IWorkResponseMessageBase>
{
    /// <summary>
    /// 获取请求消息和实体之间的映射结果
    /// </summary>
    /// <param name="requestMsgType"></param>
    /// <returns></returns>
    public override IWorkRequestMessageBase GetRequestEntityMappingResult(RequestMsgType requestMsgType, XDocument doc = null)
    {
        IWorkRequestMessageBase requestMessage;
        switch (requestMsgType)
        {
            case RequestMsgType.Text:
                requestMessage = new RequestMessageText();
                break;
            case RequestMsgType.Location:
                requestMessage = new RequestMessageLocation();
                break;
            case RequestMsgType.Image:
                requestMessage = new RequestMessageImage();
                break;
            case RequestMsgType.Voice:
                requestMessage = new RequestMessageVoice();
                break;
            case RequestMsgType.Video:
                requestMessage = new RequestMessageVideo();
                break;
            case RequestMsgType.ShortVideo:
                requestMessage = new RequestMessageShortVideo();
                break;
            case RequestMsgType.File:
                requestMessage = new RequestMessageFile();
                break;
            case RequestMsgType.Event:
                //判断Event类型
                switch (doc.Root.Element("Event").Value.ToUpper())
                {
                    case "CLICK":
                        requestMessage = new RequestMessageEvent_Click();
                        break;
                    case "VIEW":
                        requestMessage = new RequestMessageEvent_View();
                        break;
                    case "SUBSCRIBE":
                        requestMessage = new RequestMessageEvent_Subscribe();
                        break;
                    case "UNSUBSCRIBE":
                        requestMessage = new RequestMessageEvent_UnSubscribe();
                        break;
                    case "SCANCODE_PUSH":
                        requestMessage = new RequestMessageEvent_Scancode_Push();
                        break;
                    case "SCANCODE_WAITMSG":
                        requestMessage = new RequestMessageEvent_Scancode_Waitmsg();
                        break;
                    case "PIC_SYSPHOTO":
                        requestMessage = new RequestMessageEvent_Pic_Sysphoto();
                        break;
                    case "PIC_PHOTO_OR_ALBUM":
                        requestMessage = new RequestMessageEvent_Pic_Photo_Or_Album();
                        break;
                    case "PIC_WEIXIN":
                        requestMessage = new RequestMessageEvent_Pic_Weixin();
                        break;
                    case "LOCATION_SELECT":
                        requestMessage = new RequestMessageEvent_Location_Select();
                        break;
                    case "LOCATION":
                        requestMessage = new RequestMessageEvent_Location();
                        break;
                    case "ENTER_AGENT":
                        requestMessage = new RequestMessageEvent_Enter_Agent();
                        break;
                    case "BATCH_JOB_RESULT":
                        requestMessage = new RequestMessageEvent_Batch_Job_Result();
                        break;
                    case "CHANGE_CONTACT":
                        switch (doc.Root.Element("ChangeType").Value.ToUpper())
                        {
                            case "CREATE_USER":
                                requestMessage = new RequestMessageEvent_Change_Contact_User_Create();
                                break;
                            case "UPDATE_USER":
                                requestMessage = new RequestMessageEvent_Change_Contact_User_Update();
                                break;
                            case "DELETE_USER":
                                requestMessage = new RequestMessageEvent_Change_Contact_User_Base();
                                break;
                            case "CREATE_PARTY":
                                requestMessage = new RequestMessageEvent_Change_Contact_Party_Create();
                                break;
                            case "UPDATE_PARTY":
                                requestMessage = new RequestMessageEvent_Change_Contact_Party_Update();
                                break;
                            case "DELETE_PARTY":
                                requestMessage = new RequestMessageEvent_Change_Contact_Party_Base();
                                break;
                            case "UPDATE_TAG":
                                requestMessage = new RequestMessageEvent_Change_Contact_Tag_Update();
                                break;
                            default://其他意外类型(也可以选择抛出异常)
                                requestMessage = new RequestMessageEventBase();
                                break;
                        }
                        break;
                    case "CHANGE_EXTERNAL_CONTACT":
                        switch (doc.Root.Element("ChangeType").Value.ToUpper())
                        {
                            case "ADD_EXTERNAL_CONTACT":
                                requestMessage = new RequestMessageEvent_Change_ExternalContact_Add();
                                break;
                            case "ADD_HALF_EXTERNAL_CONTACT":
                                requestMessage = new RequestMessageEvent_Change_ExternalContact_Add_Half();
                                break;
                            case "EDIT_EXTERNAL_CONTACT":
                                requestMessage = new RequestMessageEvent_Change_ExternalContact_Modified();
                                break;
                            case "DEL_EXTERNAL_CONTACT":
                                requestMessage = new RequestMessageEvent_Change_ExternalContact_Del();
                                break;
                            case "DEL_FOLLOW_USER":
                                requestMessage = new RequestMessageEvent_Change_ExternalContact_Del_FollowUser();
                                break;
                            case "MSG_AUDIT_APPROVED":
                                requestMessage = new RequestMessageEvent_Change_ExternalContact_MsgAudit();
                                break;
                            default:
                                requestMessage = new RequestMessageEventBase();
                                break;
                        }
                        break;
                    case "CHANGE_EXTERNAL_CHAT":
                        switch (doc.Root.Element("ChangeType").Value.ToUpper())
                        {
                            case "CREATE":
                                requestMessage = new RequestMessageEvent_Change_External_Chat_Create();
                                break;
                            case "UPDATE":
                                requestMessage = new RequestMessageEvent_Change_External_Chat_Update(doc.Root.Element("MemChangeList"));
                                break;
                            case "DISMISS":
                                requestMessage = new RequestMessageEvent_Change_External_Chat_Dismiss();
                                break;
                            default://其他意外类型(也可以选择抛出异常)
                                requestMessage = new RequestMessageEventBase();
                                break;
                        }
                        break;
                    case "CHANGE_EXTERNAL_TAG":
                        var tagType = doc.Root.Element("TagType")?.Value;
                        switch (doc.Root.Element("ChangeType").Value.ToUpper())
                        {
                            case "CREATE":
                                requestMessage = new RequestMessageEvent_Change_External_Tag_Create(tagType);
                                break;
                            case "UPDATE":
                                requestMessage = new RequestMessageEvent_Change_External_Tag_Update(tagType);
                                break;
                            case "DELETE":
                                requestMessage = new RequestMessageEvent_Change_External_Tag_Delete(tagType);
                                break;
                            case "SHUFFLE":
                                requestMessage = new RequestMessageEvent_Change_External_Tag_Shuffle();
                                break;
                            default:
                                requestMessage = new RequestMessageEventBase();
                                break;
                        }
                        break;
                    case "LIVING_STATUS_CHANGE":
                        requestMessage = new RequestMessageEvent_Living_Status_Change_Base();
                        break;
                    case "SYS_APPROVAL_CHANGE":
                        requestMessage = new RequestMessageEvent_SysApprovalChange();
                        break;
                    case "OPEN_APPROVAL_CHANGE":
                        requestMessage = new RequestMessageEvent_OpenApprovalChange();
                        break;
                    case "MSGAUDIT_NOTIFY":
                        requestMessage = new RequestMessageEvent_MsgAuditNotify();
                        break;
                    case "TEMPLATE_CARD_EVENT": 
                        requestMessage = new RequestMessageEvent_TemplateCardEvent();
                        break;  
                    case "TEMPLATE_CARD_MENU_EVENT": 
                        requestMessage = new RequestMessageEvent_TemplateCardMenuEvent();
                        break;
                    case "KF_MSG_OR_EVENT":
                        /*
                         * 新增一个客服事件回调
                         * HELP HELP HELP HELP HELP!!!!!!!!!!!!!!!!!!!!!!!!!!!!
                         * 这里面还有个 Event 类型新加一个枚举做不到啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊!!!!!!!!!
                         */
                        requestMessage = new RequestMessageEventBase();
                        break;
                    default:
                        requestMessage = new RequestMessageEventBase();
                        break;
                }
                break;
            default:
                throw new UnknownRequestMsgTypeException(string.Format("MsgType:{0} 在RequestMessageFactory中没有对应的处理程序!", requestMsgType), new ArgumentOutOfRangeException());
        }
        return requestMessage;
    }

    /// <summary>
    /// 获取响应消息和实体之间的映射结果 (什么也没处理默认的)
    /// </summary>
    public override IWorkResponseMessageBase GetResponseEntityMappingResult(ResponseMsgType responseMsgType, XDocument doc = null)
    {
        IWorkResponseMessageBase responseMessage;
        switch (responseMsgType)
        {
            case ResponseMsgType.Text:
                responseMessage = new ResponseMessageText();
                break;
            case ResponseMsgType.News:
                responseMessage = new ResponseMessageNews();
                break;
            case ResponseMsgType.Image:
                responseMessage = new ResponseMessageImage();
                break;
            case ResponseMsgType.Voice:
                responseMessage = new ResponseMessageVoice();
                break;
            case ResponseMsgType.Video:
                responseMessage = new ResponseMessageVideo();
                break;
            case ResponseMsgType.NoResponse:
                responseMessage = new WorkResponseMessageNoResponse();
                break;
            case ResponseMsgType.SuccessResponse:
                responseMessage = new WorkSuccessResponseMessage();
                break;

            #region 不支持
            case ResponseMsgType.Transfer_Customer_Service:
            case ResponseMsgType.Music:
            #endregion

            #region 扩展类型
            case ResponseMsgType.MultipleNews:
            case ResponseMsgType.LocationMessage:
            case ResponseMsgType.UseApi:
            #endregion

            case ResponseMsgType.Other:
            case ResponseMsgType.Unknown:
            default:
                responseMessage = new WorkResponseMessageUnknownType()
                {
                    ResponseDocument = doc
                };
                break;
        }
        return responseMessage;

    }
}
  1. 接入回调 微信客服回调接入
  2. 发现消息处理器 ExecuteAsyc 之后并不会把加密后的 Encrypt 解析出,并且原始XML文档也读不出来,断点在执行
 await messageHandler.ExecuteAsync(new CancellationToken()); // 断点进去不知道他在干啥了
  1. 请求作者加一个 "KF_MSG_OR_EVENT" 的事件吧,求求了
微信官方文档 URL

https://kf.weixin.qq.com/api/doc/path/94745
https://developer.work.weixin.qq.com/document/path/94670

微信官方文档快照(直接复制关键内容到下方)
<xml>
   <ToUserName><![CDATA[ww12345678910]]></ToUserName>
   <CreateTime>1348831860</CreateTime>
   <MsgType><![CDATA[event]]></MsgType>
   <Event><![CDATA[kf_msg_or_event]]></Event>
   <Token><![CDATA[ENCApHxnGDNAVNY4AaSJKj4Tb5mwsEMzxhFmHVGcra996NR]]></Token>
   <OpenKfId><![CDATA[wkxxxxxxx]]></OpenKfId>
</xml>
发现问题的模块
  • Senparc.Weixin.Work 版本:
模块对应的 .net 版本
  • .net 8
开发环境
  • Visual Studio 2022
缓存环境
  • 服务器内存缓存
系统环境
  • Windows,版本
联系方式

Email:[email protected]

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions