前段时间小忙了一阵,微信公众号的开发,从零开始看文档,踩了不少坑,也算是熬过来了,最近考虑做一些总结,方便以后再开发的时候回顾,也给正在做相关项目的同学做个参考。

思路

微信接入:用户消息和开发者需要的事件推送都会通过微信方服务器发起一个请求,转发到你在公众平台配置的服务器url地址,微信方将带上signature,timestamp,nonce,echostr四个参数,我们自己服务器通过拼接公众平台配置的token,以及传上来的timestamp,nonce进行SHA1加密后匹配signature,返回ture说明接入成功。

消息回复:当用户给公众号发送消息时,微信服务器会将用户消息以xml格式通过POST请求到我们配置好的服务器对应的接口,而我们要做的事情就是根据消息类型等做相应的逻辑处理,并将最后的返回结果也通过xml格式return给微信服务器,微信方再传达给用户的这样一个过程。 

公众平台配置

Controller

@Controller
@RequestMapping("/wechat")
publicclass WechatController {
@Value("${DNBX_TOKEN}")
private String DNBX_TOKEN; private static final Logger LOGGER = LoggerFactory.getLogger(WechatController.class); @Resource
WechatService wechatService; /**
* 微信接入
* @param wc
* @return
* @throws IOException
*/
@RequestMapping(value="/connect",method = {RequestMethod.GET, RequestMethod.POST})
@ResponseBody
publicvoid 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 (isGet) {
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!");
} }
} catch (Exception e) {
LOGGER.error("Connect the weixin server is error.");
}finally{
out.close();
}
}
}

>签名验证 checkSignature

从上面的controller我们可以看到,我封装了一个工具类SignUtil,调用了里面的一个叫checkSignature,传入了四个值,DNBX_TOKEN, signature, timestamp, nonce。这个过程非常重要,其实我们可以理解为将微信传过来的值进行一个加解密的过程,很多大型的项目所有的接口为保证安全性都会有这样一个验证的过程。DNBX_TOKEN我们在微信公众平台配置的一个token字符串,主意保密哦!其他三个都是微信服务器发送get请求传过来的参数,我们进行一层sha1加密:
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;
} }

>SHA1:

/**
* 微信公众平台(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);
}
}
}

当你在公众平台提交保存,并且看到绿色的提示“接入成功”之后,恭喜你已经完成微信接入。这个过程需要细心一点,注意加密算法里的大小写,如果接入不成功,大多数情况都是加密算法的问题,多检查检查。

实现消息自动回复service

/**
* 处理微信发来的请求
*
* @param request
* @return
*/
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)) {
//这里根据关键字执行相应的逻辑,只有你想不到的,没有做不到的
if(content.equals("xxx")){ } //自动回复
TextMessage text = new TextMessage();
text.setContent("the text is" + content);
text.setToUserName(fromUserName);
text.setFromUserName(toUserName);
text.setCreateTime(new Date().getTime() + "");
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(new Date().getTime() + "");
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("customer_telephone")) {
TextMessage text = new TextMessage();
text.setContent("0755-86671980");
text.setToUserName(fromUserName);
text.setFromUserName(toUserName);
text.setCreateTime(new Date().getTime() + "");
text.setMsgType(MessageUtil.RESP_MESSAGE_TYPE_TEXT); respMessage = MessageUtil.textMessageToXml(text);
}
}
}
}
catch (Exception e) {
Logger.error("error......")
}
return respMessage;
}

先贴代码如上,大多都有注释,读一遍基本语义也懂了不需要多解释。

有一个地方格外需要注意:

上面标红的fromUserName和toUserName刚好相反,这也是坑之一,还记得我当时调了很久,明明都没有问题就是不通,最后把这两个一换消息就收到了!其实回过头想也对,返回给微信服务器这时本身角色就变了,所以发送和接收方也肯定是相反的。

MessageUtil    

public class MessageUtil {

    /**
* 返回消息类型:文本
*/
public static final String RESP_MESSAGE_TYPE_TEXT = "text"; /**
* 返回消息类型:音乐
*/
public static final String RESP_MESSAGE_TYPE_MUSIC = "music"; /**
* 返回消息类型:图文
*/
public static final String RESP_MESSAGE_TYPE_NEWS = "news"; /**
* 请求消息类型:文本
*/
public static final String REQ_MESSAGE_TYPE_TEXT = "text"; /**
* 请求消息类型:图片
*/
public static final String REQ_MESSAGE_TYPE_IMAGE = "image"; /**
* 请求消息类型:链接
*/
public static final String REQ_MESSAGE_TYPE_LINK = "link"; /**
* 请求消息类型:地理位置
*/
public static final String REQ_MESSAGE_TYPE_LOCATION = "location"; /**
* 请求消息类型:音频
*/
public static final String REQ_MESSAGE_TYPE_VOICE = "voice"; /**
* 请求消息类型:推送
*/
public static final String REQ_MESSAGE_TYPE_EVENT = "event"; /**
* 事件类型:subscribe(订阅)
*/
public static final String EVENT_TYPE_SUBSCRIBE = "subscribe"; /**
* 事件类型:unsubscribe(取消订阅)
*/
public static final String EVENT_TYPE_UNSUBSCRIBE = "unsubscribe"; /**
* 事件类型:CLICK(自定义菜单点击事件)
*/
public static final String EVENT_TYPE_CLICK = "CLICK";
}

这里为了程序可读性、扩展性更好一点,我做了一些封装,定义了几个常量,以及将微信传过来的一些参数封装成java bean持久化对象,核心代码如上。重点讲下xml和map之间的转换

其实这个问题要归咎于微信是用xml通讯,而我们平时一般是用json,所以可能短时间内会有点不适应

>引入jar包

<!-- 解析xml -->
<dependency>
<groupId>dom4j</groupId>
<artifactId>dom4j</artifactId>
<version>1.6.1</version>
</dependency> <dependency>
<groupId>com.thoughtworks.xstream</groupId>
<artifactId>xstream</artifactId>
<version>1.4.9</version>
</dependency>

>xml转map集合对象

/**
* xml转换为map
* @param request
* @return
* @throws IOException
*/
@SuppressWarnings("unchecked")
public static Map<String, String> xmlToMap(HttpServletRequest request) throws IOException{
Map<String, String> map = new HashMap<String, String>();
SAXReader reader = new SAXReader(); InputStream ins = null;
try {
ins = request.getInputStream();
} catch (IOException e1) {
e1.printStackTrace();
}
Document doc = null;
try {
doc = reader.read(ins);
Element root = doc.getRootElement(); List<Element> list = root.elements(); for (Element e : list) {
map.put(e.getName(), e.getText());
} return map;
} catch (DocumentException e1) {
e1.printStackTrace();
}finally{
ins.close();
} return null;
}

>文本消息对象转换成xml

/**
* 文本消息对象转换成xml
*
* @param textMessage 文本消息对象
* @return xml
*/
public static String textMessageToXml(TextMessage textMessage){
XStream xstream = new XStream();
xstream.alias("xml", textMessage.getClass());
return xstream.toXML(textMessage);
}

到此为止已经大功告成了,这个时候可以在公众号里尝试发送“测试”,你会收到微信回复的“the text is 测试”,这也是上面代码里做的回复处理,当然你也可以发挥你的想象用他做所有你想做的事了,比如回复1查天气,2查违章等等....

JAVA实现 springMVC方式的微信接入、实现消息自动回复的更多相关文章

  1. Java实现JsApi方式的微信支付

    要使用JsApi进行微信支付,首先要从微信获得一个prepay_id,然后通过调用微信的jsapi完成支付,JS API的返回结果get_brand_wcpay_request:ok仅在用户成功完成支 ...

  2. 微信公众平台消息接口API指南

    简介 微信公众平台消息接口为开发者提供了一种新的消息处理方式.微信公众平台消息接口为开发者提供与用户进行消息交互的能力.对于成功接入消息接口的微信公众账号,当用户发消息给公众号,微信公众平台服务器会使 ...

  3. JAVA配置&注解方式搭建简单的SpringMVC前后台交互系统

    前面两篇文章介绍了 基于XML方式搭建SpringMVC前后台交互系统的方法,博文链接如下: http://www.cnblogs.com/hunterCecil/p/8252060.html htt ...

  4. 微信企业号接收消息(使用SpringMVC)

    微信企业号接收消息(使用SpringMVC) 微信企业号接收消息(使用SpringMVC) 将应用设置在回调模式时,企业可以通过回调URL接收员工回复的消息,以及员工关注.点击菜单.上报地理位置等事件 ...

  5. 第七篇 :微信公众平台开发实战Java版之如何获取微信用户基本信息

    在关注者与公众号产生消息交互后,公众号可获得关注者的OpenID(加密后的微信号,每个用户对每个公众号的OpenID是唯一的.对于不同公众号,同一用户的openid不同). 公众号可通过本接口来根据O ...

  6. 第六篇 :微信公众平台开发实战Java版之如何自定义微信公众号菜单

    我们来了解一下 自定义菜单创建接口: http请求方式:POST(请使用https协议) https://api.weixin.qq.com/cgi-bin/menu/create?access_to ...

  7. Java开发微信公众号(四)---微信服务器post消息体的接收及消息的处理

    在前几节文章中我们讲述了微信公众号环境的搭建.如何接入微信公众平台.以及微信服务器请求消息,响应消息,事件消息以及工具处理类的封装:接下来我们重点说一下-微信服务器post消息体的接收及消息的处理,这 ...

  8. Java企业微信开发_05_消息推送之发送消息(主动)

    一.本节要点 1.发送消息与被动回复消息 (1)流程不同:发送消息是第三方服务器主动通知微信服务器向用户发消息.而被动回复消息是 用户发送消息之后,微信服务器将消息传递给 第三方服务器,第三方服务器接 ...

  9. 微信公众平台开发教程(一)_微信接入校验以及token获取

    微信公众平台是运营者通过公众号为微信用户提供资讯和服务的平台,而公众平台开发接口则是提供服务的基础. 接入微信公众平台开发,开发者需要按照如下步骤完成: 1.填写服务器配置 2.验证服务器地址的有效性 ...

随机推荐

  1. 报错: Unable to start activity ComponentInfo:You need to use a Theme.AppCompat theme

    转自 http://www.2cto.com/kf/201605/506596.html

  2. 红米3 SudaMod(android_6.01_r72)高配指纹/农历/归属地/SM天气/流畅运行/红外线正常/更新于20161025

    一.写在前面 我只是个人爱好,本ROM未集成任何第三方推广软件,我只是喜欢把好的资源分享出来,若可以,我们一起学习,一起进步. 请不要问我怎么刷机! 请不要问我玩游戏卡不卡(有钱你就换好点的手机)! ...

  3. 江太公:javascript count(a)(b)(c)(d)运行过程思考

    昨天,我弟抛给我一个js的题,使用类似标题那样的调用方法计算a*b*c*d以致无穷的实现方法.思考了半天,终于理清了它的运行过程,记录于下: 函数体: <!DOCTYPE html> &l ...

  4. bzoj 2648 KD-tree

    稍微看了一下KD-tree的讲义,大概明白了它的原理,但是实现不出来... 所以无耻的抄了一下黄学长的... #include<iostream> #include<cstdio&g ...

  5. 我的SqlHelper类!

    using System;using System.Collections.Generic;using System.Linq;using System.Text;using System.Threa ...

  6. 树莓派系统介绍:DIetPi

    项目主页:http://fuzon.co.uk/phpbb/viewtopic.php?f=8&t=6 当前版本:V34(15年4月16日发布) DietPi是国外一个基于Raspbian的精 ...

  7. C#基础知识简单梳理

    本文是转发博友的总结,方便自己以后随时温习: 1.值类型和引用类型 1.1堆和栈 简单的说值类型存放在堆栈上面,引用类型的数据存放在托管堆上面(它的引用地址却存放在堆栈上面)! 栈:它是一个内存数组, ...

  8. linux REDHAT6.4下安装ArcGIS Server 10.1

    1 安装环境 因为Linux的发行版本比较多,我们在使用的时候请严格按照官网给的给出的版本,在官网上给出的是经过严格测试的,如果采用其他的,即便安装上了,在后续的运作中出现问题,这个可就麻烦了,官网对 ...

  9. C++ GC

    http://www.codeproject.com/Articles/912/A-garbage-collection-framework-for-C http://www.codeproject. ...

  10. R语言-Kindle特价书爬榜示例 & 输出HTML小技巧

    博客总目录:http://www.cnblogs.com/weibaar/p/4507801.html ---- 自从买了kindle以后,总是想要定期刷有没有便宜的书,amazon经常有些1元/2元 ...