一是因为经常逛知乎,发现好些知乎大神的签名上都放的自己的微信公众号,但是关注之后就回一句简单的谢谢关注之类的话,缺乏互动性。二来是之前网盘共享了一堆本科生的毕设,为了防止别有用心的人拿去卖,加了密码,想要获取密码的话得加我微信验证。最近好多人都来加微信求密码,实在忙不过来,于是就想到了搞一个微信公众号的聊天机器人。于是乎借着周末的时间搞了一个能自动回复密码的机器人儿,但是后来发现这个机器人还可以做得更好,比如有粉丝不问密码的时候还可以聊点别的。网上搜了一下,看到一款机器人叫“图灵机器人”,首先图灵这个名字就很喜欢啊,其次它还提供API,正是我想要的。
2.怎么搞?这里分两种情况,因为世界上有两种人啊,一种程序猿,一种普通人(简称麻瓜)。
2.1 for麻瓜
打开http://www.tuling123.com/ 注册->登录->个人中心->我的机器人-创建机器人,然后下拉看到微信接入,点击“+微信公众号”,然后根据提示一步步操作,最后就好了。是不是很简单?效果如下图
2.2 for程序猿
对于傲娇的程序猿来说,我们才不屑于这种傻瓜式的接入方式,还好图灵机器人提供了API接口可以拯救一下程序猿们。看下图是不是很亲切?
下面就来详细说一下如何接入。
(1)你得有一台主机,一个域名。没有的话蹭一下你身边的基友的吧!
(2)微信开发者配置。
微信会首先验证你的服务器和域名,所以这里需要填上你的接口地址(注意:不是域名,是接口地址)
这个接口应该由一个web服务提供,下面给出我用java实现的版本:
java 73行
@Controller @RequestMapping("/msg") public class WxMessageController extends WxBaseController{ private static String TOKEN = "token123"; @Autowired private WxPaperPwdService wxPaperPwdService; @Autowired private WxTuringService wxTuringService; @RequestMapping(value="home",method={RequestMethod.GET,RequestMethod.POST}) @ResponseBody public void home(HttpServletRequest req,HttpServletResponse res){ //get为请求验证,post为消息 boolean isGet = req.getMethod().toLowerCase().equals("get"); if(isGet){ log.info("info:check url"); checkUrl(req,res); }else { log.info("info:received message"); try { chat(req,res); } catch (IOException e) { log.error("error:"+e.getMessage()); } } } private void checkUrl(HttpServletRequest req,HttpServletResponse res){ String signature = req.getParameter("signature"); String timestamp = req.getParameter("timestamp");// 时间戳 String nonce = req.getParameter("nonce");// 随机数 String echostr = req.getParameter("echostr");// 随机字符串 List params = new ArrayList(); params.add(TOKEN); params.add(timestamp); params.add(nonce); Collections.sort(params, new Comparator() { public int compare(String o1, String o2) { return o1.compareTo(o2); } }); String temp = SecurityUtils.SHA1Encode(params.get(0) + params.get(1) + params.get(2)); if (temp.equals(signature)) { try { res.getWriter().write(echostr); log.info("info:pass,hashcode is "+temp); } catch (Exception e) { log.error("error:check error" + e.getMessage()); } } } private void chat(HttpServletRequest req,HttpServletResponse res) throws IOException{ WxMessage message = new WxMessage(); Map msgMap =message.xmlToMap(req); if("text".equals(msgMap.get("MsgType"))){ String content = msgMap.get("Content"); //获取密码服务 if(WxConstant.SERVNAME_PAPERPWD.equals(WxRules.getRule(content))){ log.info("paper password service"); wxPaperPwdService.getPwd(msgMap, res); }else{ log.info("turing machine service"); wxTuringService.turingChat(msgMap, res); } } } }
微信做接口验证的时候走的是get方式,接收微信消息的时候走的是post方式,所以会根据不同的请求方式判断是验证还是聊天。同时附上一个加密用的工具类。
java 66行
public class SecurityUtils { private static final char[] HEX_DIGITS = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' }; /** * sha1加密 * @param str * @return */ public static String SHA1Encode(String str){ if (str == null) { return null; } try { MessageDigest messageDigest = MessageDigest.getInstance("SHA1"); messageDigest.update(str.getBytes()); return getFormattedText(messageDigest.digest()); } catch (Exception e) { throw new RuntimeException(e); } } private static String getFormattedText(byte[] bytes) { int len = bytes.length; StringBuilder buf = new StringBuilder(len * 2); // 把密文转换成十六进制的字符串形式 for (int j = 0; j < len; j++) { buf.append(HEX_DIGITS[(bytes[j] >> 4) & 0x0f]); buf.append(HEX_DIGITS[bytes[j] & 0x0f]); } return buf.toString(); } /** * Md5加密 * @param s * @return */ public static String MD5(String s) { char hexDigits[] = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' }; try { byte[] btInput = s.getBytes("utf-8"); // 获得MD5摘要算法的 MessageDigest 对象 MessageDigest mdInst = MessageDigest.getInstance("MD5"); // 使用指定的字节更新摘要 mdInst.update(btInput); // 获得密文 byte[] md = mdInst.digest(); // 把密文转换成十六进制的字符串形式 int j = md.length; char str[] = new char[j * 2]; int k = 0; for (int i = 0; i < j; i++) { byte byte0 = md[i]; str[k++] = hexDigits[byte0 >>> 4 & 0xf]; str[k++] = hexDigits[byte0 & 0xf]; } return new String(str); } catch (Exception e) { e.printStackTrace(); return null; } } }
有了上面两端咒文,接口验证应该是没有问题了。当然这个代码你直接粘贴过去肯定是跑不起来,别担心我的整个工程一直在完善中,文末放我的联系方式,你可以通过我的联系方式获取完整的工程代码。
(3)接入图灵机器人API
加入你上面的配置没问题了,可以看下面如何接入图灵机器人的API。有点开发经验的都知道如何调用API吧,下面我就简单贴一下代码。还是那句话这些代码直接粘贴过去跑不起来,完整代码请看文末联系方式,找我。代码免费赠送。
java 31行
public class TuringApiUtils { private static final String API_URL = "http://www.tuling123.com/openapi/api"; private static final String API_KEY = ""; private static final String SECRET = ""; public static JSONObject post(Map msgMap) throws ClientProtocolException, IOException{ String content = msgMap.get("Content"); String wxid = msgMap.get("FromUserName"); JSONObject data = new JSONObject(); data.put("key", API_KEY); data.put("info", content); data.put("userid", wxid); String timestamp = String.valueOf(System.currentTimeMillis()); String keyParam = SECRET+timestamp+API_KEY; String key = SecurityUtils.MD5(keyParam); Aes mc = new Aes(key); String datas = mc.encrypt(data.toJSONString()); JSONObject json = new JSONObject(); json.put("key", API_KEY); json.put("timestamp", timestamp); json.put("data", datas); JSONObject res = (JSONObject) JSONObject.parse(PostServer.SendPost(json.toJSONString(), API_URL)); return res; } }
java 59行
public class PostServer { /** * 向后台发送post请求 * @param param * @param url * @return */ public static String SendPost(String param, String url) { OutputStreamWriter out = null; BufferedReader in = null; String result = ""; try { URL realUrl = new URL(url); HttpURLConnection conn = (HttpURLConnection) realUrl .openConnection(); conn.setDoOutput(true); conn.setDoInput(true); conn.setUseCaches(false); conn.setRequestMethod("POST"); conn.setConnectTimeout(50000); conn.setReadTimeout(50000); conn.setRequestProperty("Content-Type", "application/json"); conn.setRequestProperty("Accept", "application/json"); conn.setRequestProperty("Authorization", "token"); conn.setRequestProperty("tag", "htc_new"); conn.connect(); out = new OutputStreamWriter(conn.getOutputStream(), "UTF-8"); out.write(param); out.flush(); out.close(); // in = new BufferedReader(new InputStreamReader( conn.getInputStream(), "UTF-8")); String line = ""; while ((line = in.readLine()) != null) { result += line; } } catch (Exception e) { e.printStackTrace(); } finally { try { if (out != null) { out.close(); } if (in != null) { in.close(); } } catch (IOException ex) { ex.printStackTrace(); } } return result; } }
java 66行
public class Aes { private Key key; /** * AES CBC模式使用的Initialization Vector */ private IvParameterSpec iv; /** * Cipher 物件 */ private Cipher cipher; /** * 构造方法 * @param strKet * 密钥 */ public Aes(String strKey) { try { this.key = new SecretKeySpec(getHash("MD5", strKey), "AES"); this.iv = new IvParameterSpec(new byte[] { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }); this.cipher = Cipher.getInstance("AES/CBC/PKCS5Padding"); } catch (final Exception ex) { throw new RuntimeException(ex.getMessage()); } } /** * 加密方法 * * 说明:采用128位 * * @return 加密结果 */ public String encrypt(String strContent) { try { byte[] data = strContent.getBytes("UTF-8"); cipher.init(Cipher.ENCRYPT_MODE, key, iv); byte[] encryptData = cipher.doFinal(data); String encryptResult = new String(Base64.encodeBase64( encryptData), "UTF-8"); return encryptResult; } catch (Exception ex) { throw new RuntimeException(ex.getMessage()); } } /** * * @param algorithm * @param text * @return */ private static byte[] getHash(String algorithm, String text) { try { byte[] bytes = text.getBytes("UTF-8"); final MessageDigest digest = MessageDigest.getInstance(algorithm); digest.update(bytes); return digest.digest(); } catch (final Exception ex) { throw new RuntimeException(ex.getMessage()); } } }
上面的代码做的事情很简单,就是请求接口获取返回值。目前我的代码只是完成了获取返回的文本类和链接类数据,而且代码写的比较乱,没脸贴完整的[捂脸],下面是部分效果截图:
如果你感兴趣,请把玩我的公众号,或者像我索取完整代码。附公众号二维码一枚: