惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

WordPress大学
WordPress大学
博客园 - 司徒正美
Last Week in AI
Last Week in AI
博客园 - 聂微东
Jina AI
Jina AI
月光博客
月光博客
爱范儿
爱范儿
美团技术团队
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
Hugging Face - Blog
Hugging Face - Blog
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
博客园 - 叶小钗
T
Tailwind CSS Blog
博客园 - 【当耐特】
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
Apple Machine Learning Research
Apple Machine Learning Research
有赞技术团队
有赞技术团队
罗磊的独立博客
小众软件
小众软件
雷峰网
雷峰网
IT之家
IT之家
大猫的无限游戏
大猫的无限游戏
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
V
Visual Studio Blog

博客园 - 华安

C#中用Task.Run+await Task.Delay 与 Timer 的性能比较 C#中Microsoft.Extensions.Caching.Memory 与 System.Runtime.Caching.MemoryCache区别 自适应网格系统:CSS Grid中repeat()、auto-fill与auto-fit的深度解析 CSS3中响应式布局两大神器display:flex和display:grid springBoot中的 pom.xml文件 bulid学习 CSS中元素的display显示方式有多种,隐藏、块级、内联、内联-块级 SQl Server 中的 go 是什么作用 CSS中 display:flex的align-items: stretch; 移动端浏览器(尤其是 iOS Safari)的橡皮筋回弹效果(Overscroll / Bounce Effect) 手机端浏览器上ES6中的Fetch回调执行 window.open没效果 用Flex实现兼容性好的全屏布局 在 VS Code 中使用 C# Dev Kit 和 Unity Tools 调试 Unity 2022 Unity 可编程物件(ScriptableObject) wx.requestSubscribeMessage(Object object) 和 wx.requestSubscribeDeviceMessage(Object object) 这两个有什么区别 微信小程序中 wx.hideLoading() 后调用 wx.showToast()的问题 Windows 中启动 Nginx的常用命令 CSS进阶技巧:字体渐变、描边、倒影与渐变色描边全解析 netCore 中各DLL引用了 SkiaSharp.dll的问题 unity中预制体解包 在 Unity 中,Time.timeScale实现游戏暂停加速等 微信小程序中关联微信支付 unity中的 Navigation AI使用 C#中TaskCompletionSource(简称 TCS)学习 Unity 编辑器 中,快捷键 Ctrl + Shift + F 的功能 unity中按下 F键,让物体聚焦 微信中进入定页面的,判断时通过扫二维码进入的,还是点小程序名称进入的 MYSQL中从JSON字符串中提取指定的值 Unity2022中创建动画 Animation(旧方法) Unity 中区别,public 和 [SerializeField] Unity中 onCollisionEnter2D与OnTriggerEnter2D 区别
微信小程序中的 联系客服 最基本的使用方法
华安 · 2026-09-04 · via 博客园 - 华安

微信官方文档:

 https://developers.weixin.qq.com/miniprogram/dev/server/API/kf-mgnt/kf-message/api_sendcustommessage.html

 https://developers.weixin.qq.com/miniprogram/dev/component/button.html

1.在小程序页面中添加一个连续客服的按钮:

<button open-type="contact" 
        bindcontact="handleContact"
        session-from="来源标识">
  联系客服
</button>
Page({
  handleContact(e) {
    console.log(e.detail.path)   // 用户点击的消息路径
    console.log(e.detail.query)  // 携带的参数
  }
})

用户点击后进入微信内置的客服聊天界面。

或者使用:

wx.openCustomerServiceConversation({
  sessionFrom: '你的会话来源标识',
  showMessageCard: true,
  sendMessageTitle: '您好,有什么可以帮您?',
  sendMessagePath: '/pages/index/index',
  sendMessageImg: 'https://example.com/image.png',
  success(res) {
    console.log('打开客服会话成功', res);
  },
  fail(err) {
    console.error('打开客服会话失败', err);
  }
});

官方文档:https://developers.weixin.qq.com/minigame/dev/api/open-api/customer-message/wx.openCustomerServiceConversation.html

2、进入小程序的管理平台进行设置开启消息推送,

  管理  -> 开发管理 -> 开发设置 -> 消息推送 -> 开启,注意消息的格式要设置成 JSON格式,默认的是 XML 格式,我的代码中全都使用的是json格式解析的。

 Token和EncodingAESKey主要是用于设置 消息推送URL(服务器地址)  时候校验地址是否正确的,下面的代码中 WeChatCustomerService方法的get方法就是校验使用的。

企业微信截图_17885128906914

3、我用的后台处理是用netCore,

/// <summary>
/// 客服消息处理
/// </summary>
/// <returns></returns>
//[HttpPost]
public async Task<IActionResult> WeChatCustomerService()
{
    // GET 请求 = 微信服务器配置 URL 时的签名验证
    if (Request.Method.ToUpper() == "GET")
    {
        var signature = Request.Query["signature"].ToString();
        var timestamp = Request.Query["timestamp"].ToString();
        var nonce = Request.Query["nonce"].ToString();
        var echostr = Request.Query["echostr"].ToString();
        return Ok(echostr); //ValidSignature();
    }
    // POST 请求 = 接收用户消息
    string _postBody = await GDCUtility.GetRequestBody();
    string resultInfoModel=await GDCWeChatCustomerServiceHelper.HandleMessageAsync(_postBody);
    return Ok(resultInfoModel);
}
/// <summary>
/// 微信客服的帮助类
/// </summary>
public class GDCWeChatCustomerServiceHelper
{
    private readonly static IMemoryCache _cache=new MemoryCache(new MemoryCacheOptions());

    #region 一个简单的接受客户的消息并处理的过程,以后都可以参考这个来扩展
    public static async Task<String> HandleMessageAsync(string postBody)
    {
        try
        {
            // using var reader = new StreamReader(Request.Body, Encoding.UTF8);
            var body = postBody; // await reader.ReadToEndAsync();
            //_logger.LogDebug("收到微信消息: {Body}", body);
            GDCLogHelper.AddLog($"收到微信消息: {body}", "HandleMessageAsync",Modules.AccessData);

            if (string.IsNullOrWhiteSpace(body))
            {
                return "success";
            }

            var msg = JsonSerializer.Deserialize<WxMessageModel>(body);
            if (msg == null)
            {
                return "success";
            }

            // 排重:防止微信重试导致重复处理
            var msgKey = $"wx_msg_{msg.FromUserName}_{msg.CreateTime}_{ (msg.MsgId==null ?  msg.EventKey : msg.MsgId.ToString()) }";
            if (_cache.TryGetValue(msgKey, out _))
            {
                //_logger.LogInformation("消息已处理,跳过重复推送: {Key}", msgKey);
                return "success";
            }
            _cache.Set(msgKey, true, TimeSpan.FromMinutes(5));

           // GDCLogHelper.AddLog($"收到微信消息MsgType: {msg.MsgType?.ToLower()}", "HandleMessageAsync", Modules.AccessData);
            // 根据消息类型分发处理
            switch (msg.MsgType?.ToLower())
            {
                case "text":
                    await HandleTextMessageAsync(msg);
                    break;

                case "image":
                    await HandleImageMessageAsync(msg);
                    break;

                case "event":
                    await HandleEventMessageAsync(msg);
                    break;

                default:
                    //_logger.LogInformation("收到未处理的消息类型: {MsgType}", msg.MsgType);
                    break;
            }
        }
        catch (Exception ex)
        {
            //_logger.LogError(ex, "处理微信消息异常");
        }

        // 必须返回 success,否则微信会重试3次
        return "success";
    }

    /// <summary>
    /// 处理文字消息
    /// </summary>
    private static async Task HandleTextMessageAsync(WxMessageModel msg)
    {
        // _logger.LogInformation("用户[{Openid}]发送文字: {Content}", msg.FromUserName, msg.Content);

        //GDCLogHelper.AddLog($"用户[{msg.FromUserName}]发送文字: {msg.Content}", "HandleMessageAsync", Modules.AccessData);

        // 简单关键词回复,实际可接入 NLP/AI
        var reply = msg.Content switch
        {
            "1" => "【订单查询】请输入您的订单号",
            "2" => "【售后服务】请描述您遇到的问题",
            "帮助" or "help" => "回复数字选择服务:\n1. 订单查询\n2. 售后服务\n3. 联系人工客服",
            _ => $"收到您的消息:{msg.Content}\n客服正在处理中..."
        };

        await SendCustomerMessageAsync(msg.FromUserName, reply);
    }

    /// <summary>
    /// 处理图片消息
    /// </summary>
    private static async Task HandleImageMessageAsync(WxMessageModel msg)
    {
        // _logger.LogInformation("用户[{Openid}]发送图片: {PicUrl}", msg.FromUserName, msg.PicUrl);
        await SendCustomerMessageAsync(msg.FromUserName, "收到图片,客服正在查看,请稍候...");
    }

    /// <summary>
    /// 处理事件消息(用户进入客服会话等),第一次点 联系客服 之类 的进入
    /// </summary>
    private static async Task HandleEventMessageAsync(WxMessageModel msg)
    {
        if (msg.Event == "user_enter_tempsession")
        {
           // _logger.LogInformation("用户[{Openid}]进入客服会话", msg.FromUserName);

            // 发送欢迎语 + 菜单
            var welcome = "👋 您好!欢迎来到客服中心\n\n" +
                          "回复数字选择服务:\n" +
                          "1️⃣ 订单查询\n" +
                          "2️⃣ 售后服务\n" +
                          "3️⃣ 联系人工客服\n\n" +
                          "或输入您的问题,智能客服为您解答";

            await SendCustomerMessageAsync(msg.FromUserName, welcome);
        }
    }


    #endregion

    #region 基本的给用户发送消息方法

    /// <summary>
    /// 发送文本客服消息
    /// </summary>
    public static async Task<bool> SendCustomerMessageAsync(string openid, string content)
    {
        var accessToken = GDCWeChatAppHelper.GetAccessToken();
        var url = $"https://api.weixin.qq.com/cgi-bin/message/custom/send?access_token={accessToken}";
        var payload = new
        {
            touser = openid,
            msgtype = "text",
            text = new { content }
        };
        //这个会对 导致 👋 被转成了 \uD83D\uDC4B
        //var json = JsonSerializer.Serialize(payload, new JsonSerializerOptions
        //{
        //    PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
        //    Encoder = System.Text.Encodings.Web.JavaScriptEncoder.UnsafeRelaxedJsonEscaping //防止转义
        //});

        var json = Newtonsoft.Json.JsonConvert.SerializeObject(payload);

        Console.WriteLine(json);

        var response =GDCHttpRequestHelper.HttpPostString(url,json,"", "application/json", "application/json").Result;
        //var result = await response.Content.ReadAsStringAsync();

        //_logger.LogDebug("发送客服消息结果: {Result}", result);
        //GDCLogHelper.AddLog($"发送客服消息结果: {response}", "HandleMessageAsync", Modules.AccessData);
        var wxRes = JsonSerializer.Deserialize<WxResponseModel>(response);
        if (wxRes?.ErrCode != 0)
        {
            // _logger.LogError("发送客服消息失败: {ErrCode} {ErrMsg}", wxRes?.ErrCode, wxRes?.ErrMsg);
            //GDCLogHelper.AddLog($"发送客服消息失败: {wxRes?.ErrCode} {wxRes?.ErrMsg}", "HandleMessageAsync", Modules.AccessData);
            return false;
        }
        return true;
    }


    /// <summary>
    /// 发送小程序卡片(引导用户跳转页面)
    /// </summary>
    public static async Task<bool> SendMiniProgramCardAsync(string openid, string title, string pagePath, string thumbMediaId)
    {
        var accessToken = GDCWeChatAppHelper.GetAccessToken();
        var url = $"https://api.weixin.qq.com/cgi-bin/message/custom/send?access_token={accessToken}";

        var payload = new
        {
            touser = openid,
            msgtype = "miniprogrampage",
            miniprogrampage = new
            {
                title,
                pagepath = pagePath,
                thumb_media_id = thumbMediaId
            }
        };
        //这个会对 导致 👋 被转成了 \uD83D\uDC4B
        //var json = JsonSerializer.Serialize(payload, new JsonSerializerOptions
        //{
        //    PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
        //    Encoder = System.Text.Encodings.Web.JavaScriptEncoder.UnsafeRelaxedJsonEscaping //防止转义
        //});

        var json = Newtonsoft.Json.JsonConvert.SerializeObject(payload);

        var response = GDCHttpRequestHelper.HttpPostString(url,json,"", "application/json", "application/json").Result;
        //var result = await response.Content.ReadAsStringAsync();
        var wxRes = JsonSerializer.Deserialize<WxResponseModel>(response);
        return wxRes?.ErrCode == 0;
    }

    #endregion
}

/// <summary>
/// 微信推送的消息结构
/// </summary>
public class WxMessageModel
{
    [JsonPropertyName("ToUserName")]
    public string ToUserName { get; set; }

    [JsonPropertyName("FromUserName")]
    public string FromUserName { get; set; }

    [JsonPropertyName("CreateTime")]
    public long CreateTime { get; set; }

    [JsonPropertyName("MsgType")]
    public string MsgType { get; set; }

    [JsonPropertyName("MsgId")]
    public long? MsgId { get; set; }

    [JsonPropertyName("Content")]
    public string Content { get; set; }

    [JsonPropertyName("PicUrl")]
    public string PicUrl { get; set; }

    [JsonPropertyName("MediaId")]
    public string MediaId { get; set; }

    [JsonPropertyName("Event")]
    public string Event { get; set; }

    [JsonPropertyName("EventKey")]
    public string EventKey { get; set; }

    [JsonPropertyName("SessionFrom")]
    public string SessionFrom { get; set; }
}
/// <summary>
/// 微信接口通用响应
/// </summary>
public class WxResponseModel
{
    [JsonPropertyName("errcode")]
    public int ErrCode { get; set; }

    [JsonPropertyName("errmsg")]
    public string ErrMsg { get; set; }
}

4、运行的效果

企业微信截图_1788513297858

企业微信截图_17885132706759

ffb676e1b5e287ee9c62c8d825b4176

重要说明:

如果小程序后台配置了多名客服,消息会由系统根据在线情况分配给相应客服,

  • 消息会由微信的客服系统自动根据在线状态和接待能力等规则分配给具体某个在线客服。
  • 开发者和小程序端无法控制或指定具体将用户消息分配给哪位客服。
  • 系统会优先分配给在线且空闲的客服,保证用户能尽快获得响应。

这个自动分配机制是微信官方托管的,目的是均衡客服工作量和提升用户体验。

如果还想更多的要求,就自己动手仔细研究。。。