zoukankan      html  css  js  c++  java
  • 微信公众号 智能客服

    前言

    微信公众号的开发,园子里有很多资料,这里简述。
    虽说是智能,现在是仿佛智障,很多是hard code逻辑,日后将逐步加入LUIS,现在一些常用的打招呼(你好,您好,hi,hey,hello,how are you等识别比较好)。

    业务性的处理,更多是逻辑上,比如常用的回复1,2,3,4然后返回对应的消息。这里涉及多轮会话,要求对上文的记忆,不然容易回答串题了。

    还有就是一些分词技术,这个目前对空格敏感,还有就是南京市长江大桥,是南京市长,还是长江大桥?这个是日后话题了。

    .NET Core APIi的方式,创建一个WeChat API Controller

    验证微信服务器地址的方法:

        [HttpGet]
        [Route("api/wechat")]
        public IActionResult Get(string signature, string timestamp, string nonce, string echostr)
        {
            var token = ConfigReader.TokenStr;//微信公众平台后台设置的Token
            if (string.IsNullOrEmpty(token))
            {
                return NotFound("请先设置Token!");
                //return new HttpResponseMessage() { Content = new StringContent("请先设置Token!", Encoding.GetEncoding("UTF-8"), "application/x-www-form-urlencoded") };
            }
            var ent = "";
            if (!BasicAPI.CheckSignature(signature, timestamp, nonce, token, out ent))
            {
                return NotFound("验证微信签名失败!");
                //return new HttpResponseMessage() { Content = new StringContent("参数错误!", Encoding.GetEncoding("UTF-8"), "application/x-www-form-urlencoded") };
            }
            //返回随机字符串则表示验证通过
            return Ok(echostr);
            //return new HttpResponseMessage() { Content = new StringContent(echostr, Encoding.GetEncoding("UTF-8"), "application/x-www-form-urlencoded") };
        }
    

    验证成功之后,所有消息,微信会转到这个方法:

        用户发送消息后,微信平台自动Post一个请求到这里,并等待响应XML。
        [HttpPost]
        [Route("api/wechat")]
        public async Task<IActionResult> Post()
        {
            WeChatMessage message = null;
            string signature = HttpContext.Request.Query["signature"].ToString();
            string timestamp = HttpContext.Request.Query["timestamp"].ToString();
            string nonce = HttpContext.Request.Query["nonce"].ToString();
            string echostr = HttpContext.Request.Query["echostr"].ToString();
    
            var safeMode = HttpContext.Request.Query["encrypt_type"].ToString() == "aes";
            using (var streamReader = new StreamReader(HttpContext.Request.Body, Encoding.UTF8))
            {
                var decryptMsg = string.Empty;
                var msg = streamReader.ReadToEnd();
    
                #region 解密
                if (safeMode)
                {
                    var msg_signature = HttpContext.Request.Query["msg_signature"];
                    var wechatBizMsgCrypt = new WeChatMsgCrypt(ConfigReader.TokenUrl, ConfigReader.EncodingAESKey, ConfigReader.AppId);
                    var ret = wechatBizMsgCrypt.DecryptMsg(msg_signature, timestamp, nonce, msg, ref decryptMsg);
                    if (ret != 0)//解密失败
                    {
                        //TODO:失败的业务处理逻辑
                    }
                }
                else
                {
                    decryptMsg = msg;
                }
                #endregion
    
                message = ParseMessageType.Parse(decryptMsg);
            }
            var response = await new WeChatExecutor(repo).Execute(message);
            var encryptMsg = string.Empty;
    
            #region 加密
            if (safeMode)
            {
                var msg_signature = HttpContext.Request.Query["msg_signature"];
                var wxBizMsgCrypt = new WeChatMsgCrypt(ConfigReader.TokenUrl, ConfigReader.EncodingAESKey, ConfigReader.AppId);
                var ret = wxBizMsgCrypt.EncryptMsg(response, timestamp, nonce, ref encryptMsg);
                if (ret != 0)//加密失败
                {
                    //TODO:开发者加密失败的业务处理逻辑
                }
            }
            else
            {
                encryptMsg = response;
            }
            #endregion
    
            return Ok(encryptMsg);
            //return new HttpResponseMessage()
            //{
            //    Content = new StringContent(encryptMsg, Encoding.GetEncoding("UTF-8"), "application/x-www-form-urlencoded")
            //    //ContentType = "text/xml",              
            //};
        }
    

    将接收到的消息,转发到Bot(Microsoft BotFramework)服务,将Bot服务的SecretKey配置上:

        public async static Task<string> PostMessage(string message, string openId)
        {
            HttpClient client;
            HttpResponseMessage response;
    
            bool IsReplyReceived = false;
    
            string ReceivedString = null;
    
            client = new HttpClient();
            client.BaseAddress = new Uri("https://directline.botframework.com/api/conversations/");
            client.DefaultRequestHeaders.Accept.Clear();
            client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
            //bot-int WeChat channel
            client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("BotConnector", ConfigReader.BotSecretKey);
    
            response = await client.GetAsync("/api/tokens/");
            if (response.IsSuccessStatusCode)
            {
                var conversation = new Conversation();
                response = await client.PostAsJsonAsync("/api/conversations/", conversation);
                if (response.IsSuccessStatusCode)
                {
                    Conversation ConversationInfo = response.Content.ReadAsAsync(typeof(Conversation)).Result as Conversation;
    
                    var contentString = response.Content.ReadAsStringAsync();
                    string conversationUrl = ConversationInfo.conversationId + "/messages/";
                    Message msg = new Message() { text = message, from = openId, channelData = "WeChat" };
                    response = await client.PostAsJsonAsync(conversationUrl, msg);
                    if (response.IsSuccessStatusCode)
                    {
                        response = await client.GetAsync(conversationUrl);
                        if (response.IsSuccessStatusCode)
                        {
                            MessageSet BotMessage = response.Content.ReadAsAsync(typeof(MessageSet)).Result as MessageSet;
                            ReceivedString = BotMessage.messages[1].text;
                            IsReplyReceived = true;
                        }
                    }
                }
            }
            return ReceivedString;
        }
    

    这里要定义几个model,用于序列化Bot返回的数据类型:

    public class Conversation
    {
        public string conversationId { get; set; }
        public string token { get; set; }
        public int expires_in { get; set; }
    }
    
    public class MessageSet
    {
        public Message[] messages { get; set; }
        public string watermark { get; set; }
        public string eTag { get; set; }
    }
    
    public class Message
    {
        public string id { get; set; }
        public string conversationId { get; set; }
        public DateTime created { get; set; }
        public string from { get; set; }
        public string text { get; set; }
        public string channelData { get; set; }
        public string[] images { get; set; }
        public Attachment[] attachments { get; set; }
        public string eTag { get; set; }
    }
    
    public class Attachment
    {
        public string url { get; set; }
        public string contentType { get; set; }
    }
    

    Bot server中的消息路由处理方法

    public virtual async Task<HttpResponseMessage> Post([FromBody] Activity activity)
        {
            if (activity != null && activity.GetActivityType() == ActivityTypes.Message)
            {
                logger.Information(new ConversationEvent
                {
                    EventType = LogEventType.ChatBotActivity,
                    Message = $"ChatBot message controller receives a message, content: {activity.Text}",
                    Activity = activity
                });
                var isAgent = await ServiceManager.AgentManager.IsAgent(activity.From.Id);
                var context = await ServiceManager.ContextManager.GetUserContext(activity, isAgent);
    
                var activityLogger = new Logger.CosmosDbActivityLogger();
                if (await ServiceManager.CommandMessageHandler.HandleCommandAsync(activity, isAgent) == false)
                {
                    if (!string.IsNullOrEmpty(activity.Text)
                        && activity.Text.ToLower().Contains(CommandRequestConnection))
                    {
                        var contextManager = ServiceManager.ContextManager;
                        var messageRouterResultHandler = ServiceManager.MessageRouterResultHandler;
                        var result = await contextManager.RequestConnection(context, activity);
                        await activityLogger.LogAsync(activity);
                        // Handle the result, if required
                        await messageRouterResultHandler.HandleResultAsync(result);
                    }
                    else
                    {
                        if (isAgent)
                        {
                            IAgentActor agentActor = SFHelper.GetActor<IAgentActor>(activity);
                            await activityLogger.LogAsync(activity);
                            await agentActor.MessagePost(context, new ActivityDataContract(activity));
                        }
                        else
                        {
                            ICustomerActor customerActor = SFHelper.GetActor<ICustomerActor>(activity);
                            await customerActor.MessagePost(context, new ActivityDataContract(activity));
                            if (activity.Text.Equals("没有解决") || activity.Text.Equals("解决了"))
                            {
                                await ServiceManager.InquiryManager.CloseInquiryAsync(context);
                            }
                        }
                    }
                }
            }
            else
            {
                HandleSystemMessage(activity);
            }
            return new HttpResponseMessage(HttpStatusCode.Accepted);
        }
    

    定义回复消息内容,markdown格式,微信支持不友好。

    public static Activity BuildProactiveGreeting(Activity context)
        {
            var responseMessage = context.CreateReply();
    
            // TODO: Move to MenuManager via DataReposotry to persist to CosmosDB
            var topLevelMenuItems = new List<CardActionItem>
            {
                new CardActionItem() { Title = "技术支持", ActionType = ActionTypes.ImBack },
                new CardActionItem() { Title = "账单/订阅/配额支持", ActionType = ActionTypes.ImBack },
                new CardActionItem() { Title = "产品咨询", ActionType = ActionTypes.ImBack },
                new CardActionItem() { Title = "注册问题", ActionType = ActionTypes.ImBack }
            };
    
            // TODO: make sure we get correct name from wechat channel
            string cardTitleText = "Hi,我是您的智能客服,等您很久啦~猜您可能对以下内容感兴趣:";
            var heroCard = BuildHeroCardWithActionButtons(cardTitleText, topLevelMenuItems);
    
            responseMessage.AttachmentLayout = AttachmentLayoutTypes.List;
            responseMessage.Attachments = new List<Attachment>() { heroCard.ToAttachment() };
            return responseMessage;
        }
    

    Bot回复消息:

    var client = new ConnectorClient(new Uri(message.ServiceUrl), new MicrosoftAppCredentials());
    var reply = GreetingHelper.BuildProactiveGreeting(message);
    client.Conversations.ReplyToActivityAsync(reply);
    

    效果图

    就是打招呼的时候,会回复一个简单得产品介绍,暂时没有多轮会话,因为上下文理解出现一些问题,就不献丑了(滑稽)。

    后记

    日后还有个切换到人工客服,当输入人工客服时,调用微信公众号的客服消息接口,每两小时(过期时间7200s)获取一次token,
    然后根据用户的OPENID,将微信支持的消息格式,发送到粉丝端,获取token的IP必须添加到白名单中。
    这个要求公众号是认证的,个人的没有授权。

  • 相关阅读:
    格式化你的git message
    git merge
    Git远程操作详解
    Limit
    EmailService
    RequestContextHolder getHttpServletRequest
    spring boot GlobalExceptionHandler @RestControllerAdvice @ExceptionHandler
    redis 的雪崩和穿透?
    FileUtil
    getWeekDay TimeUtil
  • 原文地址:https://www.cnblogs.com/shy-huang/p/8675446.html
Copyright © 2011-2022 走看看