洛塔服务号回复012获取代码。
功能说明给公众号发送消息,实现将消息转发给客服(可指定客服)。
- 普通微信用户发送消息到公众号
- 公众号将消息post到开发者填写的url上(设置与开发–>基本配置,右侧服务器配置)
- 接入成功后,后续会直接发给客服,不会继续走开发者填写的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 list = new ArrayList();
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,见代码)
1399197672
如果指定给某个客服,添加个TransInfo来指定客服账号即可。客服账号的格式必须是客服号码@公众号的微信号。比如客服账号是lootaa,公众号设置的微信号是lootaayun,那么参数就需要使用lootaa@lootaayun
1399197672
几个参数的小问题
- ToUserName:就是url收到的FromUserName
- FromUserName:就是url收到的ToUserName
- CreateTime:不要使用当前时间,而是直接使用url收到的CreateTime
- MsgType:固定值 transfer_customer_service
- 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 = "" + ""
+ "" + ""
+ resultJson.getString("CreateTime") + "" + ""
+ "" //如果转发到指定客服就添加TransInfo
+ "";
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 listAttr = node.attributes();// 当前节点的所有属性的list
for (Attribute attr : listAttr) {// 遍历当前节点的所有属性
result.put(attr.getName(), attr.getValue());
}
// 递归遍历当前节点所有的子节点
List 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 list = new ArrayList();
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 listAttr = node.attributes();// 当前节点的所有属性的list
for (Attribute attr : listAttr) {// 遍历当前节点的所有属性
result.put(attr.getName(), attr.getValue());
}
// 递归遍历当前节点所有的子节点
List 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 = "" + ""
+ "" + ""
+ resultJson.getString("CreateTime") + "" + ""
+ "" //如果转发到指定客服就添加TransInfo
+ "";
result = pc.encryptMsg(result, timestamp, nonce);
pw.write(result);
}
}
}