公众号消息转发到客服

洛塔服务号回复012获取代码。

功能说明

给公众号发送消息,实现将消息转发给客服(可指定客服)。

  1. 普通微信用户发送消息到公众号
  2. 公众号将消息post到开发者填写的url上(设置与开发–>基本配置,右侧服务器配置)
  3. 接入成功后,后续会直接发给客服,不会继续走开发者填写的url

准备工作

  • 启动服务器配置
    位置:设置与开发–>基本配置,右侧服务器配置
    开启服务器配置需要将对应的url代码部署上,Java可以使用
	/**
	 * 完整项目源码可关注公众号"lootaayun"(洛塔),回复012获取
	 */
	@GetMapping("wx12")
	public void wxGet(HttpServletRequest request, PrintWriter pw) {
		// 微信加密签名,需要使用本地计算出来的和这个对比,确认是微信发送的消息
		String signature = request.getParameter("signature");
		String timestamp = request.getParameter("timestamp"); // 时间戳
		String nonce = request.getParameter("nonce"); // 随机数
		String echostr = request.getParameter("echostr"); // 随机字符串
		// 将token、timestamp、nonce三个参数进行字典序排序
		List<String> list = new ArrayList<String>();
		list.add("lootaa"); // 公众号后台设置的token
		list.add(timestamp);
		list.add(nonce);
		Collections.sort(list);
		// 将三个参数字符串拼接成一个字符串进行sha1加密
		String tokenStr = "";
		for (int i = 0; i < list.size(); i++) {
			tokenStr += list.get(i);
		}
		String signatureStr = DigestUtils.sha1Hex(tokenStr);
		// 开发者获得加密后的字符串可与signature对比,标识该请求来源于微信
		if (signature.equals(signatureStr)) {
			pw.write(echostr); // 原样返回echostr参数内容
		} else {
			pw.write("");
		}
	}

转发到客服

并非所有消息都可以转发到客服,除了点击事件、地理位置外,微信用户自己的消息类型也有限制。

  • 文本:可以正常转发到客服
  • 图片:可以正常转发到客服
  • 语音:可以转发,但是客服只能看到消息,不能听语音
  • 视频:可以正常转发到客服
  • 地理位置:转发无效,客服收不到
  • 链接:转发无效,客服收不到

如果需要微信来分配客服,可以使用下面的参数返回(对调接收到的ToUserName和FromUserName,见代码)

 <xml> 
  <ToUserName><![CDATA[touser]]></ToUserName>  
  <FromUserName><![CDATA[fromuser]]></FromUserName>  
  <CreateTime>1399197672</CreateTime>  
  <MsgType><![CDATA[transfer_customer_service]]></MsgType> 
</xml>

如果指定给某个客服,添加个TransInfo来指定客服账号即可。客服账号的格式必须是客服号码@公众号的微信号。比如客服账号是lootaa,公众号设置的微信号是lootaayun,那么参数就需要使用lootaa@lootaayun

 <xml> 
  <ToUserName><![CDATA[touser]]></ToUserName>  
  <FromUserName><![CDATA[fromuser]]></FromUserName>  
  <CreateTime>1399197672</CreateTime>  
  <MsgType><![CDATA[transfer_customer_service]]></MsgType>  
  <TransInfo> 
    <KfAccount><![CDATA[lootaa@lootaayun]]></KfAccount> 
  </TransInfo> 
</xml>

几个参数的小问题

  1. ToUserName:就是url收到的FromUserName
  2. FromUserName:就是url收到的ToUserName
  3. CreateTime:不要使用当前时间,而是直接使用url收到的CreateTime
  4. MsgType:固定值 transfer_customer_service
  5. KfAccount:格式必须是客服账号@公众号微信号

测试代码

	@PostMapping("wx12")
	public void wxPost(HttpServletRequest request, HttpServletResponse response, PrintWriter pw) throws Exception {
		String token = "lootaa";
		String encodingAesKey = "FpKEYJDuwK92k2juU2z0sUvTmc3hB4W5wGLJEKay8oK";
		String appid = "wx276049d6a7551dca";
    	WXBizMsgCrypt pc = new WXBizMsgCrypt(token, encodingAesKey, appid);
        String timestamp = request.getParameter("timestamp");    
        String nonce = request.getParameter("nonce");  
        String msgSignature = request.getParameter("msg_signature");  
		Document doc = getDocument(request);
		String result2 = pc.decryptMsg(msgSignature, timestamp, nonce, doc.asXML());
		System.out.println("解密后明文: " + result2);
		JSONObject resultJson = documentToJSONObject(result2);
		String messageType = resultJson.getString("MsgType");
		// 完全支持的类型: text image video shortvideo
		// 支持一半的类型(能收到消息但是不能播放): voice
		// 不支持的类型:location link
		if("text;image;voice;video;shortvideo;location;link".indexOf(messageType) > -1) {
			String result = "<xml>" + "<ToUserName><![CDATA[" + resultJson.getString("FromUserName") + "]]></ToUserName>"
					+ "<FromUserName><![CDATA[" + resultJson.getString("ToUserName") + "]]></FromUserName>" + "<CreateTime>"
					+ resultJson.getString("CreateTime") + "</CreateTime>" + "<MsgType><![CDATA[transfer_customer_service]]></MsgType>"
					+ "<TransInfo><KfAccount><![CDATA[lootaa@lootaayun]]></KfAccount></TransInfo>" //如果转发到指定客服就添加TransInfo
					+ "</xml>";
			result = pc.encryptMsg(result, timestamp, nonce);
			pw.write(result);
		}

	}

用到的几个辅助类

	public static Document getDocument(HttpServletRequest request) {
		SAXReader reader = new SAXReader();
		try {
			InputStream ins = request.getInputStream();
			Document doc = reader.read(ins);
			return doc;
		} catch (IOException e) {
			e.printStackTrace();
		} catch (DocumentException e) {
			e.printStackTrace();
		}
		return null;
	}
	
	public static JSONObject documentToJSONObject(String xml) {
        JSONObject jsonObject = null;
        try {
            jsonObject = elementToJSONObject(DocumentHelper.parseText(xml).getRootElement());
        } catch (DocumentException e) {
            e.printStackTrace();
        }
        return jsonObject;
    }
	
	@SuppressWarnings("unchecked")
	public static JSONObject elementToJSONObject(Element node) {
        JSONObject result = new JSONObject();
        // 当前节点的名称、文本内容和属性
		List<Attribute> listAttr = node.attributes();// 当前节点的所有属性的list
        for (Attribute attr : listAttr) {// 遍历当前节点的所有属性
            result.put(attr.getName(), attr.getValue());
        }
        // 递归遍历当前节点所有的子节点
        List<Element> listElement = node.elements();// 所有一级子节点的list
        if (!listElement.isEmpty()) {
            for (Element e : listElement) {// 遍历所有一级子节点
                if (e.attributes().isEmpty() && e.elements().isEmpty()) // 判断一级节点是否有属性和子节点
                    result.put(e.getName(), e.getTextTrim());// 沒有则将当前节点作为上级节点的属性对待
                else {
                    if (!result.containsKey(e.getName())) // 判断父节点是否存在该一级节点名称的属性
                        result.put(e.getName(), new JSONArray());// 没有则创建
                    ((JSONArray) result.get(e.getName())).add(elementToJSONObject(e));// 将该一级节点放入该节点名称的属性对应的值中
                }
            }
        }
        return result;
    }

完整代码

package com.lootaa.wechat;

import java.io.IOException;
import java.io.InputStream;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.commons.codec.digest.DigestUtils;
import org.dom4j.Attribute;
import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.DocumentHelper;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RestController;

import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.lootaa.wechat.util.WXBizMsgCrypt;

/**
 * 订阅通知
 * 前置条件:启用了服务器配置
 */
@RestController
public class Test012 {

	public static final String APPID = "wx276049d6a7551dca";
	public static final String SECRET = "cbe109fdf6f399bd72ed3a4afafa21b1";
	
	/**
	 * 完整项目源码可关注公众号"lootaayun"(洛塔),回复012获取
	 */
	@GetMapping("wx12")
	public void wxGet(HttpServletRequest request, PrintWriter pw) {
		// 微信加密签名,需要使用本地计算出来的和这个对比,确认是微信发送的消息
		String signature = request.getParameter("signature");
		String timestamp = request.getParameter("timestamp"); // 时间戳
		String nonce = request.getParameter("nonce"); // 随机数
		String echostr = request.getParameter("echostr"); // 随机字符串
		// 将token、timestamp、nonce三个参数进行字典序排序
		List<String> list = new ArrayList<String>();
		list.add("lootaa"); // 公众号后台设置的token
		list.add(timestamp);
		list.add(nonce);
		Collections.sort(list);
		// 将三个参数字符串拼接成一个字符串进行sha1加密
		String tokenStr = "";
		for (int i = 0; i < list.size(); i++) {
			tokenStr += list.get(i);
		}
		String signatureStr = DigestUtils.sha1Hex(tokenStr);
		// 开发者获得加密后的字符串可与signature对比,标识该请求来源于微信
		if (signature.equals(signatureStr)) {
			pw.write(echostr); // 原样返回echostr参数内容
		} else {
			pw.write("");
		}
	}
	
	public static Document getDocument(HttpServletRequest request) {
		SAXReader reader = new SAXReader();
		try {
			InputStream ins = request.getInputStream();
			Document doc = reader.read(ins);
			return doc;
		} catch (IOException e) {
			e.printStackTrace();
		} catch (DocumentException e) {
			e.printStackTrace();
		}
		return null;
	}
	
	public static JSONObject documentToJSONObject(String xml) {
        JSONObject jsonObject = null;
        try {
            jsonObject = elementToJSONObject(DocumentHelper.parseText(xml).getRootElement());
        } catch (DocumentException e) {
            e.printStackTrace();
        }
        return jsonObject;
    }
	
	@SuppressWarnings("unchecked")
	public static JSONObject elementToJSONObject(Element node) {
        JSONObject result = new JSONObject();
        // 当前节点的名称、文本内容和属性
		List<Attribute> listAttr = node.attributes();// 当前节点的所有属性的list
        for (Attribute attr : listAttr) {// 遍历当前节点的所有属性
            result.put(attr.getName(), attr.getValue());
        }
        // 递归遍历当前节点所有的子节点
        List<Element> listElement = node.elements();// 所有一级子节点的list
        if (!listElement.isEmpty()) {
            for (Element e : listElement) {// 遍历所有一级子节点
                if (e.attributes().isEmpty() && e.elements().isEmpty()) // 判断一级节点是否有属性和子节点
                    result.put(e.getName(), e.getTextTrim());// 沒有则将当前节点作为上级节点的属性对待
                else {
                    if (!result.containsKey(e.getName())) // 判断父节点是否存在该一级节点名称的属性
                        result.put(e.getName(), new JSONArray());// 没有则创建
                    ((JSONArray) result.get(e.getName())).add(elementToJSONObject(e));// 将该一级节点放入该节点名称的属性对应的值中
                }
            }
        }
        return result;
    }
	
	@PostMapping("wx12")
	public void wxPost(HttpServletRequest request, HttpServletResponse response, PrintWriter pw) throws Exception {
		String token = "lootaa";
		String encodingAesKey = "FpKEYJDuwK92k2juU2z0sUvTmc3hB4W5wGLJEKay8oK";
		String appid = "wx276049d6a7551dca";
    	WXBizMsgCrypt pc = new WXBizMsgCrypt(token, encodingAesKey, appid);
        String timestamp = request.getParameter("timestamp");    
        String nonce = request.getParameter("nonce");  
        String msgSignature = request.getParameter("msg_signature");  
		Document doc = getDocument(request);
		String result2 = pc.decryptMsg(msgSignature, timestamp, nonce, doc.asXML());
		System.out.println("解密后明文: " + result2);
		JSONObject resultJson = documentToJSONObject(result2);
		String messageType = resultJson.getString("MsgType");
		// 完全支持的类型: text image video shortvideo
		// 支持一半的类型(能收到消息但是不能播放): voice
		// 不支持的类型:location link
		if("text;image;voice;video;shortvideo;location;link".indexOf(messageType) > -1) {
			String result = "<xml>" + "<ToUserName><![CDATA[" + resultJson.getString("FromUserName") + "]]></ToUserName>"
					+ "<FromUserName><![CDATA[" + resultJson.getString("ToUserName") + "]]></FromUserName>" + "<CreateTime>"
					+ resultJson.getString("CreateTime") + "</CreateTime>" + "<MsgType><![CDATA[transfer_customer_service]]></MsgType>"
					+ "<TransInfo><KfAccount><![CDATA[lootaa@lootaayun]]></KfAccount></TransInfo>" //如果转发到指定客服就添加TransInfo
					+ "</xml>";
			result = pc.encryptMsg(result, timestamp, nonce);
			pw.write(result);
		}

	}
	
}

### 实现方案 为了在微信公众号中集成DeepSeek实现智能客服功能,可以采用如下方法: #### 架构设计 架构上主要分为前端和后端两部分。前端负责收集用户的输入并通过API发送给后端处理;后端则利用DeepSeek的技术栈来解析请求、生成回复,并通过相同的API通道返回至前端展示给用户。 #### 前端开发 对于微信公众号而言,前端主要是指配置自动回复规则以及自定义菜单等界面元素。这些可以通过微信公众平台提供的开发者工具完成设置。当接收到消息时,会触发预先设定好的事件处理器,进而调用外部的服务接口[^1]。 ```json { "msgtype": "text", "text": { "content": "正在为您查询..." } } ``` 此JSON片段展示了向用户反馈初步响应的消息体格式。 #### 后端搭建 后端需构建RESTful API服务器用于接收来自微信服务器转发过来的信息包。该服务器应能理解XML格式的数据流——这是微信官方规定的通信协议之一。之后,提取出有效载荷中的文本内容交给DeepSeek引擎分析处理。这里推荐使用Python Flask框架配合Pydantic库来进行快速原型制作[^2]。 ```python from flask import Flask, request, jsonify import requests app = Flask(__name__) @app.route('/wechat', methods=['POST']) def wechat(): xml_data = request.data.decode('utf-8') # 解析 XML 并获取用户消息 (此处省略具体实现) response_from_deepseek = requests.post( 'https://api.deepseek.com/v1/chat', json={"message": user_message}, headers={'Authorization': 'Bearer YOUR_API_KEY'} ).json() reply_content = response_from_deepseek.get('response') return f""" <xml> <ToUserName><![CDATA[{to_user}]]></ToUserName> <FromUserName><![CDATA[{from_user}]]></FromUserName> <CreateTime>{create_time}</CreateTime> <MsgType><![CDATA[text]]></MsgType> <Content><![CDATA[{reply_content}]]></Content> </xml> """ if __name__ == '__main__': app.run() ``` 上述代码段提供了一个简单的Flask应用程序实例,它监听`/wechat`路径下的HTTP POST请求,代表来自微信的推送通知。接着将从中抽取出来的纯文本形式的问题提交给DeepSeek聊天机器人API获得解答建议,最后按照规定封装成XML字符串回传给微信客户端显示给最终使用者查看。 #### 测试与优化 完成基本的功能对接后,还需要经过一系列严格的测试环节确保整个系统的稳定性和可靠性。特别是要关注高并发情况下的性能表现和服务可用性等问题。此外,在实际部署前最好先在一个较小范围内试运行一段时间以便及时发现潜在缺陷并加以修正。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

lootaa

你的鼓励是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值