前言
最近接到个需求,将小程序的客服由人工回复改成由 AI 回复。本文略过 AI 那段不谈,来说说微信相关 API 的接入
操作
编写接收消息推送的接口
想要自动回复,那就需要知道用户问了什么问题。我们提供一个接口接收用户发给小程序的消息以及开发者需要的事件推送。参考文档
@Controller
@RequestMapping("/wx/xcx/empevent/*")
public class DemoEventController {
private static final String APITOKEN = "必须为英文或数字,长度为3-32字符"; // 定义Token 务必与服务器保持一致
private static final String EncodingAESKey = "消息加密密钥由43位字符组成,字符范围为A-Z,a-z,0-9";
private String appid = "小程序的appid";
private String appSecret = "小程序的appSecret";
protected static final String TRANSFER_FORMAT = "<xml> \n" +
"<ToUserName><![CDATA[%1$s]]></ToUserName>\n" +
"<FromUserName><![CDATA[%2$s]]></FromUserName>\n" +
"<CreateTime>%3$s</CreateTime>\n" +
"<MsgType><![CDATA[transfer_customer_service]]></MsgType>\n" +
"</xml>";
@RequestMapping("receiveMsg")
public void receiveMsg(HttpServletRequest request, HttpServletResponse response) throws Exception {
ServletOutputStream outputStream = response.getOutputStream();
String signature = request.getParameter("signature");
String timestamp = request.getParameter("timestamp");
String nonce = request.getParameter("nonce");
String echostr = request.getParameter("echostr");
String msgSignature = request.getParameter("msg_signature");
//验证请求签名
if (!checkSignature(signature, timestamp, nonce, APITOKEN)) {
System.out.println("The request signature is invalid");
return;
}
//首次请求申请验证,返回echostr
if (echostr != null) {
outputStreamWrite(outputStream, echostr);
return;
}
//loger.info("1.收到微信服务器消息");
Map<String, String> wxdata = parseXml(request);
String encrypt = wxdata.get("Encrypt");
WXBizMsgCrypt pc = new WXBizMsgCrypt(APITOKEN, EncodingAESKey, appid);
String result2 = pc.decryptMsg(msgSignature, timestamp, nonce, encrypt);
System.out.println("小程序客服解密后明文: " + result2); // 拿到明文,剩下就是业务逻辑了
// 转发消息,让人工客服处理
// String transferMsg = String.format(TRANSFER_FORMAT, FromUserName, ToUserName, CreateTime);
// outputStreamWrite(outputStream, pc.encryptMsg(transferMsg, timestamp, nonce));
}
/**
* 验证签名
*
* @param signature
* @param timestamp
* @param nonce
* @return
*/
protected boolean checkSignature(String signature, String timestamp, String nonce, String apitoken) {
// 将token、timestamp、nonce三个参数进行字典排序
String[] arr = new String[] { apitoken, timestamp, nonce };
Arrays.sort(arr);
// 将三个参数字符串拼接成一个字符串
StringBuilder content = new StringBuilder();
for (int i = 0; i < arr.length; i++) {
content.append(arr[i]);
}
try {
//获取加密工具
MessageDigest md = MessageDigest.getInstance("SHA-1");
// 对拼接好的字符串进行sha1加密
byte[] digest = md.digest(content.toString().getBytes());
String tmpStr = byteToStr(digest);
//获得加密后的字符串与signature对比
return tmpStr != null ? tmpStr.equals(signature.toUpperCase()): false;
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
}
return false;
}
protected String byteToStr(byte[] byteArray) {
String strDigest = "";
for (int i = 0; i < byteArray.length; i++) {
strDigest += byteToHexStr(byteArray[i]);
}
return strDigest;
}
protected String byteToHexStr(byte mByte) {
char[] Digit = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A',
'B', 'C', 'D', 'E', 'F' };
char[] tempArr = new char[2];
tempArr[0] = Digit[(mByte >>> 4) & 0X0F];
tempArr[1] = Digit[mByte & 0X0F];
String s = new String(tempArr);
return s;
}
/**
* 数据流输出
* @param outputStream
* @param text
* @return
*/
protected boolean outputStreamWrite(OutputStream outputStream, String text){
try {
outputStream.write(text.getBytes("utf-8"));
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
return false;
} catch (IOException e) {
e.printStackTrace();
return false;
}
return true;
}
/**
* dom4j 解析 xml 转换为 map
* @param request
* @return
* @throws Exception
*/
protected Map<String, String> parseXml(HttpServletRequest request) throws Exception {
// 将解析结果存储在HashMap中
Map<String, String> map = new HashMap<String, String>();
// 从request中取得输入流
InputStream inputStream = request.getInputStream();
// 读取输入流
SAXReader reader = new SAXReader();
Document document = reader.read(inputStream);
// 得到xml根元素
Element root = document.getRootElement();
// 得到根元素的所有子节点
List<Element> elementList = root.elements();
// 遍历所有子节点
for (Element e : elementList)
map.put(e.getName(), e.getText());
// 释放资源
inputStream.close();
inputStream = null;
return map;
}
}
请自行更改代码中的APITOKEN、EncodingAESKey、appid、appSecret
转发消息所需要的参数 FromUserName, ToUserName, CreateTime,在解密后明文中均可得到
注意事项:
- 此接口会接收到多种类型的消息,请参考文档
- 如果需要转发消息,让人工客服处理,请只针对微信用户发来的消息才进行转发,而对于其他任何事件都不应该转接,否则客服在客服系统上就会看到一些无意义的消息了。
坑:
转发消息的 FromUserName, ToUserName 这两个参数的位置我没有写错,坑爹的微信,请看文档
配置
登录小程序管理后台,在开发与服务 → 开发管理 → 开发设置 → 消息推送中配置,如下图
注意:
- Token 和 EncodingAESKey 需要和代码中保持一致
- 需要编写好接收消息推送的接口并发布上线,否则此处的配置会保存失败
总结
没啥好总结的,引用的相关文档都在文中用超链接的方式给出了