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必须添加到白名单中。
    这个要求公众号是认证的,个人的没有授权。

  • 相关阅读:
    HDU 1501 Zipper(DFS)
    HDU 2181 哈密顿绕行世界问题(DFS)
    HDU 1254 推箱子(BFS)
    HDU 1045 Fire Net (DFS)
    HDU 2212 DFS
    HDU 1241Oil Deposits (DFS)
    HDU 1312 Red and Black (DFS)
    HDU 1010 Tempter of the Bone(DFS+奇偶剪枝)
    HDU 1022 Train Problem I(栈)
    HDU 1008 u Calculate e
  • 原文地址:https://www.cnblogs.com/shy-huang/p/8675446.html
Copyright © 2011-2022 走看看