一:申请公众号
无聊的去申请一个个人公众号,试了下微信实现自定义菜单与自定义对话的一些基本功能,如果多花点时间,金钱(= =!)还是有不少功能可以使用,还是蛮有意思的。
废话不多说,先看一下申请的流程。首先进入微信公众平台申请一个个人的订阅号,其实它自带的功能里就有自动回复的设置,不过还是试试开发接口去实现,更有意思。
二:接口实现
在开发-基本配置里,服务器配置就是当前公众号和我们自己服务器建立连接的配置。
controller:该方法的路径就是基本配置里的服务器地址(URL)
/** * 微信接入 * @param * @return * @throws IOException */ @RequestMapping(value="/wechat/connect",method = {RequestMethod.GET, RequestMethod.POST}) @ResponseBody public void connectWeixin(HttpServletRequest request, HttpServletResponse response) throws IOException { // 将请求、响应的编码均设置为UTF-8(防止中文乱码) request.setCharacterEncoding("UTF-8"); //微信服务器POST消息时用的是UTF-8编码,在接收时也要用同样的编码,否则中文会乱码; response.setCharacterEncoding("UTF-8"); //在响应消息(回复消息给用户)时,也将编码方式设置为UTF-8,原理同上;boolean isGet = request.getMethod().toLowerCase().equals("get"); PrintWriter out = response.getWriter(); try { if (RequestMethod.GET.name().equals(request.getMethod())) { String signature = request.getParameter("signature");// 微信加密签名 String timestamp = request.getParameter("timestamp");// 时间戳 String nonce = request.getParameter("nonce");// 随机数 String echostr = request.getParameter("echostr");//随机字符串 // 通过检验signature对请求进行校验,若校验成功则原样返回echostr,表示接入成功,否则接入失败 if (SignUtil.checkSignature(DNBX_TOKEN, signature, timestamp, nonce)) { LOGGER.info("Connect the weixin server is successful."); response.getWriter().write(echostr); } else { LOGGER.error("Failed to verify the signature!"); } }else{ String respMessage = ""; try { respMessage = wechatService.weixinPost(request); out.write(respMessage); LOGGER.info("The request completed successfully"); LOGGER.info("to weixin server "+respMessage); } catch (Exception e) { LOGGER.error("Failed to convert the message from weixin!", e); } } } catch (Exception e) { LOGGER.error("Connect the weixin server is error.", e); }finally{ out.close(); } }
校验的代码如下:
public class SignUtil { /** * 验证签名 * * @param token 微信服务器token,在env.properties文件中配置的和在开发者中心配置的必须一致 * @param signature 微信服务器传过来sha1加密的证书签名 * @param timestamp 时间戳 * @param nonce 随机数 * @return */ public static boolean checkSignature(String token,String signature, String timestamp, String nonce) { String[] arr = new String[] { token, timestamp, nonce }; // 将token、timestamp、nonce三个参数进行字典序排序 Arrays.sort(arr); // 将三个参数字符串拼接成一个字符串进行sha1加密 String tmpStr = SHA1.encode(arr[0] + arr[1] + arr[2]); // 将sha1加密后的字符串可与signature对比,标识该请求来源于微信 return tmpStr != null ? tmpStr.equals(signature.toUpperCase()) : false; } }
import java.security.MessageDigest; /** * 微信公众平台(JAVA) SDK * * SHA1算法 * * @author helijun 2016/06/15 19:49 */ public final class SHA1 { private static final char[] HEX_DIGITS = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' }; /** * Takes the raw bytes from the digest and formats them correct. * * @param bytes the raw bytes from the digest. * @return the formatted bytes. */ 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(); } public static String encode(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); } } }
其中加密字段分别为:signature:微信加密签名 timestamp:时间戳 nonce:随机数 echostr:随机字符串,
而代码中的DNBX_TOKEN 就是公众号里配置的
再来看看如何处理具体的post请求:
import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.JSONObject; import com.yxy.nova.bean.wechat.TextMessage; import com.yxy.nova.util.MessageUtil; import com.yxy.nova.util.SimpleHttpClient; import com.yxy.nova.util.wechat.weather.TianQiCityID; import com.yxy.nova.util.wechat.weather.TianQiWeatherHelper; import com.yxy.nova.web.util.WebUtil; import org.apache.commons.lang3.StringUtils; import org.apache.http.NameValuePair; import org.apache.http.message.BasicNameValuePair; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Service; import javax.servlet.http.HttpServletRequest; import java.io.IOException; import java.util.Map; /** * @date 2021/1/22 上午11:38 * @Description */ @Service public class WechatServiceImpl implements WechatService { private final Logger LOGGER = LoggerFactory.getLogger(getClass()); private static JSONObject accessToken = new JSONObject(); @Value("${wechat-AppId}") private String appleId; @Value("${wechat-AppSecret}") private String appSecret; @Autowired private SimpleHttpClient simpleHttpClient; /** * 处理微信发来的请求 * * @param request * @return */ @Override public String weixinPost(HttpServletRequest request) { String respMessage = null; try { // xml请求解析 Map<String, String> requestMap = MessageUtil.xmlToMap(request); // 发送方帐号(open_id) String fromUserName = requestMap.get("FromUserName"); // 公众帐号 String toUserName = requestMap.get("ToUserName"); // 消息类型 String msgType = requestMap.get("MsgType"); // 消息内容 String content = requestMap.get("Content"); LOGGER.info("FromUserName is:" + fromUserName + ", ToUserName is:" + toUserName + ", MsgType is:" + msgType); // 文本消息 if (msgType.equals(MessageUtil.REQ_MESSAGE_TYPE_TEXT)) { //这里根据关键字执行相应的逻辑,只有你想不到的,没有做不到的 TianQiCityID ci = new TianQiCityID(); if(ci.getCityIDMap().get(content) !=null){ TextMessage text = new TextMessage(); TianQiWeatherHelper tianQiWeatherHelper = new TianQiWeatherHelper(); tianQiWeatherHelper.setSimpleHttpClient(simpleHttpClient); text.setContent(tianQiWeatherHelper.getWeatherReportByCityName(content)); text.setToUserName(fromUserName); text.setFromUserName(toUserName); text.setCreateTime(System.currentTimeMillis() + ""); text.setMsgType(msgType); respMessage = MessageUtil.textMessageToXml(text); } if ("天气".equals(content)) { TextMessage text = new TextMessage(); TianQiWeatherHelper tianQiWeatherHelper = new TianQiWeatherHelper(); tianQiWeatherHelper.setSimpleHttpClient(simpleHttpClient); text.setContent(tianQiWeatherHelper.getWeatherReportByIP(WebUtil.getRemoteAddr(request))); text.setToUserName(fromUserName); text.setFromUserName(toUserName); text.setCreateTime(System.currentTimeMillis() + ""); text.setMsgType(msgType); respMessage = MessageUtil.textMessageToXml(text); } //自动回复 // TextMessage text = new TextMessage(); // text.setContent("the text is" + content); // text.setToUserName(fromUserName); // text.setFromUserName(toUserName); // text.setCreateTime(System.currentTimeMillis() + ""); // text.setMsgType(msgType); // respMessage = MessageUtil.textMessageToXml(text); } // else if (msgType.equals(MessageUtil.REQ_MESSAGE_TYPE_EVENT)) {// 事件推送 // String eventType = requestMap.get("Event");// 事件类型 // // if (eventType.equals(MessageUtil.EVENT_TYPE_SUBSCRIBE)) {// 订阅 // respContent = "欢迎关注xxx公众号!"; // return MessageResponse.getTextMessage(fromUserName , toUserName , respContent); // } else if (eventType.equals(MessageUtil.EVENT_TYPE_CLICK)) {// 自定义菜单点击事件 // String eventKey = requestMap.get("EventKey");// 事件KEY值,与创建自定义菜单时指定的KEY值对应 // logger.info("eventKey is:" +eventKey); // return xxx; // } // } //开启微信声音识别测试 2015-3-30 // else if(msgType.equals("voice")) // { // String recvMessage = requestMap.get("Recognition"); // //respContent = "收到的语音解析结果:"+recvMessage; // if(recvMessage!=null){ // respContent = TulingApiProcess.getTulingResult(recvMessage); // }else{ // respContent = "您说的太模糊了,能不能重新说下呢?"; // } // return MessageResponse.getTextMessage(fromUserName , toUserName , respContent); // } //拍照功能 // else if(msgType.equals("pic_sysphoto")) // { // // } // else // { // return MessageResponse.getTextMessage(fromUserName , toUserName , "返回为空"); // } // 事件推送 else if (msgType.equals(MessageUtil.REQ_MESSAGE_TYPE_EVENT)) { String eventType = requestMap.get("Event");// 事件类型 // 订阅 if (eventType.equals(MessageUtil.EVENT_TYPE_SUBSCRIBE)) { TextMessage text = new TextMessage(); text.setContent("欢迎关注,xxx"); text.setToUserName(fromUserName); text.setFromUserName(toUserName); text.setCreateTime(System.currentTimeMillis() + ""); text.setMsgType(MessageUtil.RESP_MESSAGE_TYPE_TEXT); respMessage = MessageUtil.textMessageToXml(text); } // TODO 取消订阅后用户再收不到公众号发送的消息,因此不需要回复消息 else if (eventType.equals(MessageUtil.EVENT_TYPE_UNSUBSCRIBE)) {// 取消订阅 } // 自定义菜单点击事件 else if (eventType.equals(MessageUtil.EVENT_TYPE_CLICK)) { String eventKey = requestMap.get("EventKey");// 事件KEY值,与创建自定义菜单时指定的KEY值对应 if (eventKey.equals("weather")) { TextMessage text = new TextMessage(); text.setContent("请输入城市名称"); text.setToUserName(fromUserName); text.setFromUserName(toUserName); text.setCreateTime(System.currentTimeMillis() + ""); text.setMsgType(MessageUtil.RESP_MESSAGE_TYPE_TEXT); respMessage = MessageUtil.textMessageToXml(text); } } } } catch (Exception e) { LOGGER.error("error......"); } return respMessage; } }
如上面的代码,我在文本消息中,简单的实现了根据城市名称查询天气情况。接口的实现就不贴出来了,就是一个简单的接口对接。
三:第三方接口
网上有关于 www.weather.com.cn网站获取天气情况的接口,但是本人测试使用发现返回字段比较少,而且刷新的不是很及时,
现在推荐一个免费的第三方接口:https://www.tianqiapi.com/,在api接口中有三个免费的接口,可以实现一些简单的功能了。
我在demo中使用的就是:免费实况天气接口:https://www.tianqiapi.com/index/doc?version=v6,用完感受还是不错的。
至此,已经实现了公众号的部分功能,其实微信的接口文档还有很多,还有很多更好玩的功能,比如微信对话开放平台,有兴趣的可以申请开发权限,去探索一下吧。