JAVA实现 springMVC方式的微信接入、实现消息自动回复实例

网友投稿 389 2023-06-25


JAVA实现 springMVC方式的微信接入、实现消息自动回复实例

1.思路

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

1.公众平台配置

2.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();

}

}

}

3.签名验证 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);

}

}

}

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

4. 实现消息自动回复service

/**

* 处理微信发来的请求

*

* @param request

* @return

*/

public String weixinPost(HttpServletRequest request) {

String respMessage = null;

try {

// xml请求解析

Map 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)) {// 订阅

return MessageResponse.getTextMessage(fromUserName , toUserName , respContent);

} else if (eventType.equals(MessageUtil.EVENT_TYPE_CLICK)) {// 自定义菜单点击事件

String eventKey = requestMap.get("EventKey");// 事件KEY值,与创建自定义菜单时指定的KEY值对应

http:// 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);

}

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

5.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,所以可能短时间内会有点不适应

1.引入jar包

dom4j

dom4j

1.6.1

com.thoughtworks.xstream

xstream

1.4.9

2.xml转map集合对象

/**

* xml转换为map

* @param request

* @return

* @throws IOException

*/

@SuppressWarnings("unchecked")

public static Map xmlToMap(HttpServletRequest request) throws IOException{

Map map = new HashMap();

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 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;

}

3.文本消息对象转换成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);

}


版权声明:本文内容由网络用户投稿,版权归原作者所有,本站不拥有其著作权,亦不承担相应法律责任。如果您发现本站中有涉嫌抄袭或描述失实的内容,请联系我们jiasou666@gmail.com 处理,核实后本网站将在24小时内删除侵权内容。

上一篇:java设计模式之简单工厂模式
下一篇:详解微信小程序 页面跳转 传递参数
相关文章

 发表评论

暂时没有评论,来抢沙发吧~