微信开发关注没有响应_java响应微信微信小程序请求 - CSDN
  • 当我们关注某些微信公众号的时候,有的公众号会给我们回复一条文本信息。本节内容,我们就来实现如何在用户关注公众号后,由公众号给用户回复一条文本信息。同样在用户取消关注时,也可以给用户发送一条信息。

    一、实际需求      

           当我们关注某些微信公众号的时候,有的公众号会给我们回复一条文本信息。本节内容,我们就来实现如何在用户关注公众号后,由公众号给用户回复一条文本信息。同样在用户取消关注时,也可以给用户发送一条信息。

    二、开发文档

          我们是基于微信的第三方平台来做二次开发,开发的依据必须是官方的API也就是开发文档。所以,我们要先查询开发文档来找到关注和取关事件说明。访问url为:https://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1421140454。如图:


          由此可知,我们的公众号服务接收到微信服务器回传的xml信息,从中获取MsgType和Event的值,可以区分出用户的关注和取消关注的行为,对不同的行为程序可以做出不同的响应。

    三、编码实现

    1、引入相关jar包:dom4j-1.6.1.jar、servlet-api-2.5.jar、xstream-1.3.1.jar用来处理xml文件。

    2、根据开发文档中的xml内容,创建消息对象基础类BaseMessage:

    /*
     * 消息体基础类
     */
    public class BaseMessage {
    	private String ToUserName;
    	private String FromUserName;
    	private long CreateTime;
    	private String MsgType;
    	public String getToUserName() {
    		return ToUserName;
    	}
    	public void setToUserName(String toUserName) {
    		ToUserName = toUserName;
    	}
    	public String getFromUserName() {
    		return FromUserName;
    	}
    	public void setFromUserName(String fromUserName) {
    		FromUserName = fromUserName;
    	}
    	public long getCreateTime() {
    		return CreateTime;
    	}
    	public void setCreateTime(long createTime) {
    		CreateTime = createTime;
    	}
    	public String getMsgType() {
    		return MsgType;
    	}
    	public void setMsgType(String msgType) {
    		MsgType = msgType;
    	}
    }
    3、创建文本消息类TextMessage继承消息基础类:

    /*
     * 文本消息类
     */
    public class TextMessage extends BaseMessage{
    	private String Content;
    	private String MsgId;	
    	public String getContent() {
    		return Content;
    	}
    	public void setContent(String content) {
    		Content = content;
    	}
    	public String getMsgId() {
    		return MsgId;
    	}
    	public void setMsgId(String msgId) {
    		MsgId = msgId;
    	}
    	
    }
    4、创建处理xml的工具类XmlUtil:

    /*
     * xml处理工具类
     */
    public class XmlUtil {
    	/*
    	 * xml转map
    	 */
    	public static Map<String, String> xmlToMap(HttpServletRequest request) throws IOException, DocumentException{
    		HashMap<String, String> map = new HashMap<String,String>();
    		SAXReader reader = new SAXReader();
    
    		InputStream ins = request.getInputStream();
    		Document doc = reader.read(ins);
    
    		Element root = doc.getRootElement();
    		@SuppressWarnings("unchecked")
    		List<Element> list = (List<Element>)root.elements();
    
    		for(Element e:list){
    			map.put(e.getName(), e.getText());
    		}
    		ins.close();
    		return map;
    	}
    	/*
    	 * 文本消息对象转xml
    	 */
    	public static String textMsgToxml(TextMessage textMessage){
    		XStream xstream = new XStream();
    		xstream.alias("xml", textMessage.getClass());
    		return xstream.toXML(textMessage);
    	}
    }
    5、创建消息处理类MessageUtil:

    /*
     * 消息处理工具类
     */
    public class MessageUtil {
    	public static final String MSGTYPE_EVENT = "event";//消息类型--事件
    	public static final String MESSAGE_SUBSCIBE = "subscribe";//消息事件类型--订阅事件
    	public static final String MESSAGE_UNSUBSCIBE = "unsubscribe";//消息事件类型--取消订阅事件
    	public static final String MESSAGE_TEXT = "text";//消息类型--文本消息
    	
    	/*
    	 * 组装文本消息
    	 */
    	public static String textMsg(String toUserName,String fromUserName,String content){
    		TextMessage text = new TextMessage();
    		text.setFromUserName(toUserName);
    		text.setToUserName(fromUserName);
    		text.setMsgType(MESSAGE_TEXT);
    		text.setCreateTime(new Date().getTime());
    		text.setContent(content);
    		return XmlUtil.textMsgToxml(text);
    	}
    	
    	/*
    	 * 响应订阅事件--回复文本消息
    	 */
    	public static String subscribeForText(String toUserName,String fromUserName){
    		return textMsg(toUserName, fromUserName, "欢迎关注,精彩内容不容错过!!!");
    	}
    	
    	/*
    	 * 响应取消订阅事件
    	 */
    	public static String unsubscribe(String toUserName,String fromUserName){
    		//TODO 可以进行取关后的其他后续业务处理
    		System.out.println("用户:"+ fromUserName +"取消关注~");
    		return "";
    	}
    }
    6、在WeixinServlet类中新增doPost方法,用来响应微信用户发来的操作请求:

            /*
    	 * 响应post请求,微信中消息和菜单交互都是采用post请求
    	 */
    	@Override
    	protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws IOException{
    		req.setCharacterEncoding("UTF-8");
    		resp.setCharacterEncoding("UTF-8");
    		PrintWriter out = resp.getWriter();
    		String message = "success";
    		try {
    			//把微信返回的xml信息转义成map
    			Map<String, String> map = XmlUtil.xmlToMap(req);
    			String fromUserName = map.get("FromUserName");//消息来源用户标识
    			String toUserName = map.get("ToUserName");//消息目的用户标识
    			String msgType = map.get("MsgType");//消息类型
    			String content = map.get("Content");//消息内容
    			
    			String eventType = map.get("Event");
    			if(MessageUtil.MSGTYPE_EVENT.equals(msgType)){//如果为事件类型
    				if(MessageUtil.MESSAGE_SUBSCIBE.equals(eventType)){//处理订阅事件
    					message = MessageUtil.subscribeForText(toUserName, fromUserName);
    				}else if(MessageUtil.MESSAGE_UNSUBSCIBE.equals(eventType)){//处理取消订阅事件
    					message = MessageUtil.unsubscribe(toUserName, fromUserName);
    				}
    			}
    		} catch (DocumentException e) {
    			e.printStackTrace();
    		}finally{
    			out.println(message);
    			if(out!=null){
    				out.close();
    			}
    		}
    	}

    7、代码结构图:


    四、本节小结

           通过以上操作,我们在新增加一个功能的时候,首先要查看官方开发文档,依据其xml交互格式来封装自己的类和方法,在此基础上增加业务逻辑来实现符合自己的需求实现。

    五、相关链接

    源码地址:https://github.com/bifengmiaozhuan/weixinService

    更多学习讨论, 请加入

    官方爬虫、nlp技术qq群:320349384 

    天亮教育官方群:318971238,

    hadoop & spark & hive技术群:297585251, 

    官网:myhope365.com

    官方天亮论坛:http://bbs.myhope365.com/ 

    天亮教育视频链接http://pan.baidu.com/s/1jIxI4IU 密码:zqa7

    欢迎关注天亮教育公众号:




    展开全文
  •  在项目开发中 我们有些功能是需要查询远程数据库或者是获取设备端上传数据的,这时候如果远程服务器响应不及时,或者设备端响应延迟高,微信公众号就会出现该“该微信公众号出现故障”的提示,很不友好,并且在接...

    问题描述:

      在项目开发中 我们有些功能是需要查询远程数据库或者是获取设备端上传数据的,这时候如果远程服务器响应不及时,或者设备端响应延迟高,微信公众号就会出现该“该微信公众号出现故障”的提示,很不友好,并且在接下来的一段时间又会连续收到三条相同的响应。

    查阅官方API得知:

      微信服务器在五秒内收不到响应会断掉链接,并且重新发起请求,总共重试三次。微信没有收到响应,但是却触发了我们的服务,因此导致了重复推送消息的问题

    解决方式1:使用客服API

      我们可以先给微信服务器反馈一个空白或者不空白的数据(XML或者非XML都行),然后调用微信的高级接口(客服API),给用户发送数据即可(理由:我们反馈了一个任意数据,微信服务器认为他的工作完成了,就不会再重复发送数据了,此时我们再调用高级接口发送数据也就不会有干扰了-即使是用户短时间内多次发送请求)

    解决方式2:消息排重问题(我使用的是这种)

      当服务器收到微信请求的时候,我发现若是在没有及时响应的情况下,重复发送的三次请求ID都是一样的,并且消息创建时间也一样。于是,当请求过来的时候,我试着将此次消息ID或创建时间(可以唯一标识这条消息的均可)放进request作用域,当下一次请求到来的时候,从作用域中取出消息ID进行对比,若发现是同一消息,则不做处理。这样就不会出现相同的三次请求让服务器处理,以此来解决重复响应的问题。并且由于没有对接下来的请求做处理,微信前端也不会提示“公众号出现故障”的字眼,完美解决!

    转载于:https://www.cnblogs.com/lanmao123/p/10530213.html

    展开全文
  • 微信开发之入门教程

    2016-01-12 10:55:26
    其实微信开发跟web开发没有多大的区别,只是经过了微信,然后再由浏览器打开。因为经过微信,因此你的web会附加一些微信平台所提供的一些功能,如获取用户地理位置、获取微信用户头像、拍照上传、

    时间葱葱,小宝鸽入职也有半年了,刚入的时候刚好有负责开发一个微信企业号的新项目。从项目的一无所有到第一版上线,再一步步完善升级。期间学到了许多东西。对微信开发也是有了一定的认识。在此,小宝鸽再次无私地分享给大家啦。

    其实微信开发跟web开发没有多大的区别,只是经过了微信,然后再由浏览器打开。因为经过微信,因此你的web会附加一些微信平台所提供的一些功能,如获取用户地理位置、获取微信用户头像、拍照上传、发送微信消息等等,通过微信接口即可调用。要将web项目挂靠在微信公众平台上是需要一个帐号的。微信公众平号分为服务号、订阅号、企业号。这三种帐号有一些小区别,但是开发流程都是差不多的,只是开放的功能上有些区别,知道其中一种开发,其他的也差不多。关于具体区别先不作过多介绍,后面的文章会讲到。接下来我们以企业号为例带大家进入微信开发之旅。

    接下来将从下面几个角度带大家了解微信开发:

    (1)申请企业号体验号
    (2)企业号的一些配置
    (3)微信JS接口调用

    好了现在马上开始:

    一、申请企业号体验号

    1.1、首先来到微信企业号的网址 https://qy.weixin.qq.com/

    这里写图片描述

    1.2、可以看到“开发者中心”字眼,点击进入相应页面

    这里写图片描述

    1.3、进入后可以看到“欢迎你,开发者”的公告,右侧有个“申请体验号”,点击进入相应页面。

    这里写图片描述

    1.4、进入“申请体验号”后,可以看到注册流程,按照注册流程填写相应资料并申请,验证邮箱绑定微信号后体验号就申请成功啦。

    这里写图片描述

    1.5、申请成功之后,回到https://qy.weixin.qq.com/,用微信扫一扫扫描登录下方的二维码,输入对应密码即可登录成功,来到你的微信企业号首页啦。

    这里写图片描述

    二、企业号的一些配置

    2.1、添加子部门,如下图,将鼠标放到“企业号体验43560625”就会出现小下标,然后点击添加子部门,填写好信息保存,然后刷新页面即可。

    这里写图片描述
    这里写图片描述

    2.2、添加成员,点击通讯录,然后如下图进行操作,即可添加成员。部门选择刚刚添加的部门

    这里写图片描述

    2.3、然成员关注该企业号,如果添加成员的时候有输入邮箱,可以在通讯录的成员管理那里给成员发送关注邀请,邀请会将企业号二维码发送到对应成员邮箱。另外一种方法直接点击“设置”即可看到体验号二维码。让成员扫这个二维码关注也是可以的(需要注意的是,体验号只能最多关注10个成员哦)。另外下图中的CorpID (wx7099477f2de8aded)非常重要的,先记录起来,下面接口微信JS调用的时候会用到。

    这里写图片描述

    2.4、添加管理组,点击“设置”–>”权限管理”,就会跳转到下图页面,然后“新建管理组”,选择管理员的时候,如果提示该成员已在其他管理组,那么估计需要添加成员了。小宝鸽添加了一个管理组“测试”,添加成功后如下图。其中Secret也是非常重要的东西,之后JS接口调用获取签名需要用到。

    这里写图片描述

    2.5、应用管理。猿友们可以看到左侧菜单中有个”应用中心”。点击应用中心将来到下图页面。”企业小助手”就是本企业号默认存在的一个应用。猿友们可以自行创建更多的应用。

    这里写图片描述

    点击”企业小助手”将会来到下面的界面,默认是回调模式,我们需要设置成普通模式。

    这里写图片描述

    点击”普通模式”,启用模式,然后启用”自定义菜单”。

    这里写图片描述

    自定义菜单启用完成之后,点击自定义菜单中的设置,将会跳转到如下页面:

    这里写图片描述

    添加菜单”测试”,然后设置”微信信息”,内容为”测试啦啦啦”,保存–>发布,然后右边有个预览,点击菜单”测试”,就会自动回复消息,如下图:

    这里写图片描述

    菜单响应除了发送微信消息也可以是跳转到某个链接,因为跳转链接是需要配置可信域名的,因此先介绍如何配置可信域名
    应用管理还有一个地方需要设置的,那就可信域名,如下图,回到”企业小助手”应用的详情页面,添加可信域名,可信域名是有一些要求的(1. 设置的应用域名须通过ICP备案的验证,2. 请使用二级或二级以上域名),这里小宝鸽网上找了一个”yo.bbdfun.com”,猿友们也可以使用这个

    这里写图片描述

    配置了可信域名之后呢,猿友们可以配置跳转到链接的菜单啦,注意配置的url必须是已可信域名作为域名哈,例如:

    这里写图片描述

    三、微信JS接口调用

    3.1、微信提供了一系列的JS接口,使得公众号企开发十分快捷高效,微信JS-SDK接口:
    http://qydev.weixin.qq.com/wiki/index.php?title=%E5%BE%AE%E4%BF%A1JS-SDK%E6%8E%A5%E5%8F%A3
    各位猿友们可粗略看一下上面文档,便可知道大概提供的一些功能。

    3.2、各位猿友们粗略看完”微信JS-SDK接口”,应该有看到下图的说明吧,接口的使用是需要注入权限验证配置的,现在我们上面的体验号等等的一系列操作就派上用场啦。

    这里写图片描述

    3.3、接下来将会一点点向大家介绍怎么调用微信接口啦

    微信接口文档之后的猿友们应该都知道微信接口的调用步骤如下:

    这里写图片描述

    最重要的还是步骤二:权限验证配置。里面有几个参数,小宝鸽将会为猿友们一一介绍:
    debug: true, // 开启调试模式,调用的所有api的返回值会在客户端alert出来
    appId: ”, // 必填,企业号的唯一标识,此处填写企业号corpid
    timestamp: , // 必填,生成签名的时间戳
    nonceStr: ”, // 必填,生成签名的随机串
    signature: ”,// 必填,签名,见附录1
    jsApiList: [] // 必填,需要使用的JS接口列表,所有JS接口列表见附录2

    debug、appId和jsApiList相信各位猿友们应该都知道大概是什么东西。那么现在给各位猿友们重点介绍:timestamp、nonceStr、signature。
    其实timestamp、nonceStr是用来生成signature的。
    js生成时间戳方法:timestamp = Date.parse(new Date()); //1414587457
    另外,nonceStr也是一串随机串,我们也用时间戳就好了nonceStr=Date.parse(new Date()); //1414587457

    剩下的就是最关键的signature生成方法,这里需要引入access_token概念
    生成signature签名第一步获得access_token:
    浏览器输入:https://qyapi.weixin.qq.com/cgi-bin/gettoken?corpid=wx5f24fa0db1819ea2&corpsecret=uQtWzF0bQtl2KRHX0amekjpq8L0aO96LSpSNfctOBLRbuYPO4DUBhMn0_v2jHS-9
    即可得到access_token:YoxXjnJS57r8gk5Nf-Ki_mSvn98fILxv56EE7NFWE3qQNOH3OaW4iDWwLc05g1mdbuNhipK8fgy-q-pA93DqFw(其有效期为7200秒,即两个小时)

    这里写图片描述

    生成signature签名第二步通过access_token获得ticket:
    浏览器输入:https://qyapi.weixin.qq.com/cgi-bin/get_jsapi_ticket?access_token=YoxXjnJS57r8gk5Nf-Ki_mSvn98fILxv56EE7NFWE3qQNOH3OaW4iDWwLc05g1mdbuNhipK8fgy-q-pA93DqFw
    即可得到ticket:”sM4AOVdWfPE4DxkXGEs8VLMMSNOZxIv5IhnWCyv5sA4UgJuWuMQdfMCeyC5kSL_c7OIMGeETC2y9PXfLbFIFNw(其有效期也是7200秒,即两个小时)

    这里写图片描述

    生成signature签名第三步通过ticket以及下面参数拼成字符串:
    noncestr=1414587457
    jsapi_ticket(即上面的ticket)=sM4AOVdWfPE4DxkXGEs8VLMMSNOZxIv5IhnWCyv5sA5kumyWTQ2VcKEcphBAW62J_HUgmaiKEQ3qhwj5Vlqq7g
    timestamp=1414587457
    url=http://mp.weixin.qq.com

    通过上面的参数拼成(注意参数顺序必须一样):jsapi_ticket=sM4AOVdWfPE4DxkXGEs8VLMMSNOZxIv5IhnWCyv5sA5kumyW
    TQ2VcKEcphBAW62J_HUgmaiKEQ3qhwj5Vlqq7g
    &noncestr=1414587457&timestamp=1414587457&url=http://mp.weixin.qq.com

    最后利用上面的字符串进行sha1加密,有在线的校验工具http://mp.weixin.qq.com/debug/cgi-bin/sandbox?t=jsapisign。但是真正开发的时候肯定是需要写代码的,下面附上sha1加密的java算法:`

    jdk也有提供这个java.security这个包,里面封装好了sha1加密算法。使用方法可参考博主的另外一篇博客AES加密解密 SHA1、SHA加密 MD5加密

    注意真正获取access_token、ticket的时候是需要通过代码实现的,上面在浏览器输入对应地址获取只是为了理顺流程。下面是通过java代码获取。

    3.4、java代码获取签名

    关于使用java代码获取签名的详细过程请参考博主的另外一篇文章 微信开发之使用java获取签名signature(贴源码,附工程)

    该文章有详细的代码,而且附工程下载。

    获取到了签名之后就可以调用微信js接口了,例子后面的文章将会讲到。

    展开全文
  • 上一篇《微信开发学习总结(一)——微信开发环境搭建》我们已经完成了微信开发的准备工作,准备工作完成之后,就要开始步入正题了。 一、微信公众平台的基本原理  在开始做之前,先简单介绍了微信公众平台的基本...

    上一篇《微信开发学习总结(一)——微信开发环境搭建》我们已经完成了微信开发的准备工作,准备工作完成之后,就要开始步入正题了。

    一、微信公众平台的基本原理

      在开始做之前,先简单介绍了微信公众平台的基本原理。

      微信服务器就相当于一个转发服务器,终端(手机、Pad等)发起请求至微信服务器,微信服务器然后将请求转发给我们的应用服务器。应用服务器处理完毕后,将响应数据回发给微信服务器,微信服务器再将具体响应信息回复到微信App终端。

      通信协议为:HTTP

      数据传输格式为:XML

      具体的流程如下图所示:

      

      来一张更加直观的图吧:

      

      我们需要做的事情,就是对微信服务器转发的HTTP请求做出响应。具体的请求内容,我们按照特定的XML格式去解析,处理完毕后,也要按照特定的XML格式返回。

    二、微信公众号接入

      在微信公众平台开发者文档上,关于公众号接入这一节内容在接入指南上写的比较详细的,文档中说接入公众号需要3个步骤,分别是:

      1、填写服务器配置
      2、验证服务器地址的有效性
      3、依据接口文档实现业务逻辑

      其实,第3步已经不能算做公众号接入的步骤,而是接入之后,开发人员可以根据微信公众号提供的接口所能做的一些开发。

      第1步中服务器配置包含服务器地址(URL)、Token和EncodingAESKey。

      服务器地址即公众号后台提供业务逻辑的入口地址,目前只支持80端口,之后包括接入验证以及任何其它的操作的请求(例如消息的发送、菜单管理、素材管理等)都要从这个地址进入。接入验证和其它请求的区别就是,接入验证时是get请求,其它时候是post请求;

      Token可由开发者可以任意填写,用作生成签名(该Token会和接口URL中包含的Token进行比对,从而验证安全性);

      EncodingAESKey由开发者手动填写或随机生成,将用作消息体加解密密钥。本例中全部以未加密的明文消息方式,不涉及此配置项。

      第2步,验证服务器地址的有效性,当点击“提交”按钮后,微信服务器将发送一个http的get请求到刚刚填写的服务器地址,并且携带四个参数:

      

      接到请求后,我们需要做如下三步,若确认此次GET请求来自微信服务器,原样返回echostr参数内容,则接入生效,否则接入失败。

      1. 将token、timestamp、nonce三个参数进行字典序排序
      2. 将三个参数字符串拼接成一个字符串进行sha1加密
      3. 开发者获得加密后的字符串可与signature对比,标识该请求来源于微信

      下面我们用Java代码来演示一下这个验证过程

      使用IDE(Eclipse或者IntelliJ IDEA)创建一个JavaWeb项目,这里我使用的是IntelliJ IDEA,项目目录结构如下图所示:

      

      编写一个servlevt,在其中的doGet方法中定义校验方法,具体代码如下:

    复制代码
      1 package me.gacl.wx.web.servlet;
      2 
      3 import javax.servlet.ServletException;
      4 import javax.servlet.annotation.WebServlet;
      5 import javax.servlet.http.HttpServlet;
      6 import javax.servlet.http.HttpServletRequest;
      7 import javax.servlet.http.HttpServletResponse;
      8 import java.io.IOException;
      9 import java.security.MessageDigest;
     10 import java.security.NoSuchAlgorithmException;
     11 import java.util.Arrays;
     12 
     13 /**
     14  * Created by xdp on 2016/1/25.
     15  * 使用@WebServlet注解配置WxServlet,urlPatterns属性指明了WxServlet的访问路径
     16  */
     17 @WebServlet(urlPatterns="/WxServlet")
     18 public class WxServlet extends HttpServlet {
     19 
     20     /**
     21      * Token可由开发者可以任意填写,用作生成签名(该Token会和接口URL中包含的Token进行比对,从而验证安全性)
     22      * 比如这里我将Token设置为gacl
     23      */
     24     private final String TOKEN = "gacl";
     25 
     26     protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
     27 
     28     }
     29 
     30     protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
     31         System.out.println("开始校验签名");
     32         /**
     33          * 接收微信服务器发送请求时传递过来的4个参数
     34          */
     35         String signature = request.getParameter("signature");//微信加密签名signature结合了开发者填写的token参数和请求中的timestamp参数、nonce参数。
     36         String timestamp = request.getParameter("timestamp");//时间戳
     37         String nonce = request.getParameter("nonce");//随机数
     38         String echostr = request.getParameter("echostr");//随机字符串
     39         //排序
     40         String sortString = sort(TOKEN, timestamp, nonce);
     41         //加密
     42         String mySignature = sha1(sortString);
     43         //校验签名
     44         if (mySignature != null && mySignature != "" && mySignature.equals(signature)) {
     45             System.out.println("签名校验通过。");
     46             //如果检验成功输出echostr,微信服务器接收到此输出,才会确认检验完成。
     47             //response.getWriter().println(echostr);
     48             response.getWriter().write(echostr);
     49         } else {
     50             System.out.println("签名校验失败.");
     51         }
     52 
     53     }
     54 
     55     /**
     56      * 排序方法
     57      *
     58      * @param token
     59      * @param timestamp
     60      * @param nonce
     61      * @return
     62      */
     63     public String sort(String token, String timestamp, String nonce) {
     64         String[] strArray = {token, timestamp, nonce};
     65         Arrays.sort(strArray);
     66         StringBuilder sb = new StringBuilder();
     67         for (String str : strArray) {
     68             sb.append(str);
     69         }
     70 
     71         return sb.toString();
     72     }
     73 
     74     /**
     75      * 将字符串进行sha1加密
     76      *
     77      * @param str 需要加密的字符串
     78      * @return 加密后的内容
     79      */
     80     public String sha1(String str) {
     81         try {
     82             MessageDigest digest = MessageDigest.getInstance("SHA-1");
     83             digest.update(str.getBytes());
     84             byte messageDigest[] = digest.digest();
     85             // Create Hex String
     86             StringBuffer hexString = new StringBuffer();
     87             // 字节数组转换为 十六进制 数
     88             for (int i = 0; i < messageDigest.length; i++) {
     89                 String shaHex = Integer.toHexString(messageDigest[i] & 0xFF);
     90                 if (shaHex.length() < 2) {
     91                     hexString.append(0);
     92                 }
     93                 hexString.append(shaHex);
     94             }
     95             return hexString.toString();
     96 
     97         } catch (NoSuchAlgorithmException e) {
     98             e.printStackTrace();
     99         }
    100         return "";
    101     }
    102 }
    复制代码

      我这里用的Servlet3.0,使用Servlet3.0的好处就是可以直接使用@WebServlet注解映射Servlet的访问路径,不再需要在web.xml文件中进行配置.

      将WxStudy项目部署到Tomcat服务器中运行,直接启动项目,然后用ngrok将本地8080端口映射到外网(如何使用ngrok请参考博客《微信开发学习总结(一)——微信开发环境搭建》)。如下图所示:

      

      测试是否可以通过http://xdp.ngrok.natapp.cn地址正常访问,测试结果如下:

      

      可以看到,我们的项目已经可以被外网正常访问到了。

      进入微信测试公众号管理界面,在接口配置信息中填入映射的外网地址和token,如下图所示:

     

      点击提交按钮,页面会提示配置成功,

      

      IDE的控制台中输出了校验通过的信息,如下图所示:

      

      到此,我们的公众号应用已经能够和微信服务器正常通信了,也就是说我们的公众号已经接入到微信公众平台了。

    三、access_token管理

    3.1、access_token介绍

      我们的公众号和微信服务器对接成功之后,接下来要做的就是根据我们的业务需求调用微信公众号提供的接口来实现相应的逻辑了。在使用微信公众号接口中都需要一个access_token。

      关于access_token,在微信公众平台开发者文档上的获取接口调用凭据有比较详细的介绍:access_token是公众号的全局唯一票据,公众号调用各接口时都需使用access_token,开发者需要妥善保存access_token的存储至少要保留512个字符空间。access_token的有效期目前为2个小时,需定时刷新,重复获取将导致上次获取的access_token失效。并且每天调用获取access_token接口的上限是2000次。

      总结以上说明,access_token需要做到以下两点:

      1.因为access_token有2个小时的时效性,要有一个机制保证最长2个小时重新获取一次。

      2.因为接口调用上限每天2000次,所以不能调用太频繁。

    3.2、微信公众平台提供的获取access_token的接口

      关于access_token的获取方式,在微信公众平台开发者文档上有说明,公众号可以调用一个叫"获取access token"的接口来获取access_token。

      获取access token接口调用请求说明

        http请求方式: GET

        请求的URL地址:https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=APPID&secret=APPSECRET
        
      

      我们可以看到,调用过程中需要传递appID和AppSecret,appID和AppSecret是在申请公众号的时候自动分配给公众号的,相当于公众号的身份标示,使用微信公众号的注册帐号登录到腾讯提供的微信公众号管理后台就可以看到自己申请的公众号的AppID和AppSecret,如下图所示:

      

      这是我申请公众号测试帐号时分配到的AppID和AppSecret。

    3.3、获取access_token方案以及具体实现

      这里采用的方案是这样的,定义一个默认启动的servlet,在init方法中启动一个Thread,这个进程中定义一个无限循环的方法,用来获取access_token,当获取成功后,此进程休眠7000秒(7000秒=1.944444444444444小时),否则休眠3秒钟继续获取。流程图如下:

      

      下面正式开始在工程中实现以上思路,因为返回的数据都是json格式,这里会用到阿里的fastjson库,为构造请求和处理请求后的数据序列化和反序列化提供支持。

      1.定义一个AccessToken实体类

    复制代码
     1 package me.gacl.wx.entry;
     2 
     3 /**
     4  * AccessToken的数据模型
     5  * Created by xdp on 2016/1/25.
     6  */
     7 public class AccessToken {
     8 
     9     //获取到的凭证
    10     private String accessToken;
    11     //凭证有效时间,单位:秒
    12     private int expiresin;
    13 
    14     public String getAccessToken() {
    15         return accessToken;
    16     }
    17 
    18     public void setAccessToken(String accessToken) {
    19         this.accessToken = accessToken;
    20     }
    21 
    22     public int getExpiresin() {
    23         return expiresin;
    24     }
    25 
    26     public void setExpiresin(int expiresin) {
    27         this.expiresin = expiresin;
    28     }
    29 }
    复制代码

     2.定义一个AccessTokenInfo类,用于存放获取到的AccessToken,代码如下:

    复制代码
     1 package me.gacl.wx.Common;
     2 
     3 import me.gacl.wx.entry.AccessToken;
     4 
     5 /**
     6  * Created by xdp on 2016/1/25.
     7  */
     8 public class AccessTokenInfo {
     9 
    10     //注意是静态的
    11     public static AccessToken accessToken = null;
    12 }
    复制代码

      3.编写一个用于发起https请求的工具类NetWorkHelper,代码如下:

    复制代码
     1 package me.gacl.wx.util;
     2 
     3 import javax.net.ssl.*;
     4 import java.io.BufferedReader;
     5 import java.io.InputStream;
     6 import java.io.InputStreamReader;
     7 import java.net.URL;
     8 import java.security.cert.CertificateException;
     9 import java.security.cert.X509Certificate;
    10 
    11 /**
    12  * 访问网络用到的工具类
    13  */
    14 public class NetWorkHelper {
    15 
    16     /**
    17      * 发起Https请求
    18      * @param reqUrl 请求的URL地址
    19      * @param requestMethod
    20      * @return 响应后的字符串
    21      */
    22     public String getHttpsResponse(String reqUrl, String requestMethod) {
    23         URL url;
    24         InputStream is;
    25         String resultData = "";
    26         try {
    27             url = new URL(reqUrl);
    28             HttpsURLConnection con = (HttpsURLConnection) url.openConnection();
    29             TrustManager[] tm = {xtm};
    30 
    31             SSLContext ctx = SSLContext.getInstance("TLS");
    32             ctx.init(null, tm, null);
    33 
    34             con.setSSLSocketFactory(ctx.getSocketFactory());
    35             con.setHostnameVerifier(new HostnameVerifier() {
    36                 @Override
    37                 public boolean verify(String arg0, SSLSession arg1) {
    38                     return true;
    39                 }
    40             });
    41 
    42 
    43             con.setDoInput(true); //允许输入流,即允许下载
    44 
    45             //在android中必须将此项设置为false
    46             con.setDoOutput(false); //允许输出流,即允许上传
    47             con.setUseCaches(false); //不使用缓冲
    48             if (null != requestMethod && !requestMethod.equals("")) {
    49                 con.setRequestMethod(requestMethod); //使用指定的方式
    50             } else {
    51                 con.setRequestMethod("GET"); //使用get请求
    52             }
    53             is = con.getInputStream();   //获取输入流,此时才真正建立链接
    54             InputStreamReader isr = new InputStreamReader(is);
    55             BufferedReader bufferReader = new BufferedReader(isr);
    56             String inputLine;
    57             while ((inputLine = bufferReader.readLine()) != null) {
    58                 resultData += inputLine + "\n";
    59             }
    60             System.out.println(resultData);
    61 
    62         } catch (Exception e) {
    63             e.printStackTrace();
    64         }
    65         return resultData;
    66     }
    67 
    68     X509TrustManager xtm = new X509TrustManager() {
    69         @Override
    70         public X509Certificate[] getAcceptedIssuers() {
    71             return null;
    72         }
    73 
    74         @Override
    75         public void checkServerTrusted(X509Certificate[] arg0, String arg1)
    76                 throws CertificateException {
    77 
    78         }
    79 
    80         @Override
    81         public void checkClientTrusted(X509Certificate[] arg0, String arg1)
    82                 throws CertificateException {
    83 
    84         }
    85     };
    86 }
    复制代码

      getHttpsResponse方法是请求一个https地址,参数requestMethod为字符串“GET”或者“POST”,传null或者“”默认为get方式。

      4.定义一个默认启动的servlet,在init方法中启动一个新的线程去获取accessToken

    复制代码
     1 package me.gacl.wx.web.servlet;
     2 
     3 import com.alibaba.fastjson.JSON;
     4 import com.alibaba.fastjson.JSONObject;
     5 import me.gacl.wx.Common.AccessTokenInfo;
     6 import me.gacl.wx.entry.AccessToken;
     7 import me.gacl.wx.util.NetWorkHelper;
     8 
     9 import javax.servlet.ServletException;
    10 import javax.servlet.annotation.WebInitParam;
    11 import javax.servlet.annotation.WebServlet;
    12 import javax.servlet.http.HttpServlet;
    13 
    14 /**
    15  * 用于获取accessToken的Servlet
    16  * Created by xdp on 2016/1/25.
    17  */
    18 @WebServlet(
    19         name = "AccessTokenServlet",
    20         urlPatterns = {"/AccessTokenServlet"},
    21         loadOnStartup = 1,
    22         initParams = {
    23                 @WebInitParam(name = "appId", value = "wxbe4d433e857e8bb1"),
    24                 @WebInitParam(name = "appSecret", value = "ccbc82d560876711027b3d43a6f2ebda")
    25         })
    26 public class AccessTokenServlet extends HttpServlet {
    27 
    28     @Override
    29     public void init() throws ServletException {
    30         System.out.println("启动WebServlet");
    31         super.init();
    32 
    33         final String appId = getInitParameter("appId");
    34         final String appSecret = getInitParameter("appSecret");
    35 
    36         //开启一个新的线程
    37         new Thread(new Runnable() {
    38             @Override
    39             public void run() {
    40                 while (true) {
    41                     try {
    42                         //获取accessToken
    43                         AccessTokenInfo.accessToken = getAccessToken(appId, appSecret);
    44                         //获取成功
    45                         if (AccessTokenInfo.accessToken != null) {
    46                             //获取到access_token 休眠7000秒,大约2个小时左右
    47                             Thread.sleep(7000 * 1000);
    48                             //Thread.sleep(10 * 1000);//10秒钟获取一次
    49                         } else {
    50                             //获取失败
    51                             Thread.sleep(1000 * 3); //获取的access_token为空 休眠3秒
    52                         }
    53                     } catch (Exception e) {
    54                         System.out.println("发生异常:" + e.getMessage());
    55                         e.printStackTrace();
    56                         try {
    57                             Thread.sleep(1000 * 10); //发生异常休眠1秒
    58                         } catch (Exception e1) {
    59 
    60                         }
    61                     }
    62                 }
    63 
    64             }
    65         }).start();
    66     }
    67 
    68     /**
    69      * 获取access_token
    70      *
    71      * @return AccessToken
    72      */
    73     private AccessToken getAccessToken(String appId, String appSecret) {
    74         NetWorkHelper netHelper = new NetWorkHelper();
    75         /**
    76          * 接口地址为https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=APPID&secret=APPSECRET,其中grant_type固定写为client_credential即可。
    77          */
    78         String Url = String.format("https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=%s&secret=%s", appId, appSecret);
    79         //此请求为https的get请求,返回的数据格式为{"access_token":"ACCESS_TOKEN","expires_in":7200}
    80         String result = netHelper.getHttpsResponse(Url, "");
    81         System.out.println("获取到的access_token="+result);
    82         //使用FastJson将Json字符串解析成Json对象
    83         JSONObject json = JSON.parseObject(result);
    84         AccessToken token = new AccessToken();
    85         token.setAccessToken(json.getString("access_token"));
    86         token.setExpiresin(json.getInteger("expires_in"));
    87         return token;
    88     }
    89 }
    复制代码

      AccessTokenServlet采用注解的方式进行配置
      至此代码实现完毕,将项目部署,看到控制台输出如下:

      

      为了方便看效果,可以把休眠时间设置短一点,比如10秒获取一次,然后将access_token输出。

      下面做一个测试jsp页面,并把休眠时间设置为10秒,这样过10秒刷新页面,就可以看到变化

    复制代码
     1 <%-- Created by IntelliJ IDEA. --%>
     2 <%@ page contentType="text/html;charset=UTF-8" language="java" %>
     3 <%@ page import="me.gacl.wx.Common.AccessTokenInfo"%>
     4 <html>
     5   <head>
     6     <title></title>
     7   </head>
     8   <body>
     9     微信学习
    10     <hr/>
    11     access_token为:<%=AccessTokenInfo.accessToken.getAccessToken()%>
    12   </body>
    13 </html>
    复制代码

      

      10秒钟后刷新页面,access_token变了,如下图所示:

      

    四、接收微信服务器发送的消息并做出响应

      经过上述的三步,我们开发前的准备工作已经完成了,接下来要做的就是接收微信服务器发送的消息并做出响应

      从微信公众平台接口消息指南中可以了解到,当用户向公众帐号发消息时,微信服务器会将消息通过POST方式提交给我们在接口配置信息中填写的URL,而我们就需要在URL所指向的请求处理类WxServlet的doPost方法中接收消息、处理消息和响应消息。

    4.1.编写一个用于处理消息的工具类

      编写处理消息的工具栏,工具类代码如下:

    复制代码
      1 package me.gacl.wx.util;
      2 
      3 import org.dom4j.Document;
      4 import org.dom4j.Element;
      5 import org.dom4j.io.SAXReader;
      6 
      7 import javax.servlet.http.HttpServletRequest;
      8 import java.io.InputStream;
      9 import java.text.DateFormat;
     10 import java.text.SimpleDateFormat;
     11 import java.util.Date;
     12 import java.util.HashMap;
     13 import java.util.List;
     14 import java.util.Map;
     15 
     16 /**
     17  * 消息处理工具类
     18  * Created by xdp on 2016/1/26.
     19  */
     20 public class MessageHandlerUtil {
     21 
     22     /**
     23      * 解析微信发来的请求(XML)
     24      * @param request
     25      * @return map
     26      * @throws Exception
     27      */
     28     public static Map<String,String> parseXml(HttpServletRequest request) throws Exception {
     29         // 将解析结果存储在HashMap中
     30         Map<String,String> map = new HashMap();
     31         // 从request中取得输入流
     32         InputStream inputStream = request.getInputStream();
     33         System.out.println("获取输入流");
     34         // 读取输入流
     35         SAXReader reader = new SAXReader();
     36         Document document = reader.read(inputStream);
     37         // 得到xml根元素
     38         Element root = document.getRootElement();
     39         // 得到根元素的所有子节点
     40         List<Element> elementList = root.elements();
     41 
     42         // 遍历所有子节点
     43         for (Element e : elementList) {
     44             System.out.println(e.getName() + "|" + e.getText());
     45             map.put(e.getName(), e.getText());
     46         }
     47 
     48         // 释放资源
     49         inputStream.close();
     50         inputStream = null;
     51         return map;
     52     }
     53 
     54     // 根据消息类型 构造返回消息
     55     public static String buildXml(Map<String,String> map) {
     56         String result;
     57         String msgType = map.get("MsgType").toString();
     58         System.out.println("MsgType:" + msgType);
     59         if(msgType.toUpperCase().equals("TEXT")){
     60             result = buildTextMessage(map, "孤傲苍狼在学习和总结微信开发了,构建一条文本消息:Hello World!");
     61         }else{
     62             String fromUserName = map.get("FromUserName");
     63             // 开发者微信号
     64             String toUserName = map.get("ToUserName");
     65             result = String
     66                     .format(
     67                             "<xml>" +
     68                                     "<ToUserName><![CDATA[%s]]></ToUserName>" +
     69                                     "<FromUserName><![CDATA[%s]]></FromUserName>" +
     70                                     "<CreateTime>%s</CreateTime>" +
     71                                     "<MsgType><![CDATA[text]]></MsgType>" +
     72                                     "<Content><![CDATA[%s]]></Content>" +
     73                                     "</xml>",
     74                             fromUserName, toUserName, getUtcTime(),
     75                             "请回复如下关键词:\n文本\n图片\n语音\n视频\n音乐\n图文");
     76         }
     77 
     78         return result;
     79     }
     80 
     81     /**
     82      * 构造文本消息
     83      *
     84      * @param map
     85      * @param content
     86      * @return
     87      */
     88     private static String buildTextMessage(Map<String,String> map, String content) {
     89         //发送方帐号
     90         String fromUserName = map.get("FromUserName");
     91         // 开发者微信号
     92         String toUserName = map.get("ToUserName");
     93         /**
     94          * 文本消息XML数据格式
     95          * <xml>
     96              <ToUserName><![CDATA[toUser]]></ToUserName>
     97              <FromUserName><![CDATA[fromUser]]></FromUserName>
     98              <CreateTime>1348831860</CreateTime>
     99              <MsgType><![CDATA[text]]></MsgType>
    100              <Content><![CDATA[this is a test]]></Content>
    101              <MsgId>1234567890123456</MsgId>
    102          </xml>
    103          */
    104         return String.format(
    105                 "<xml>" +
    106                         "<ToUserName><![CDATA[%s]]></ToUserName>" +
    107                         "<FromUserName><![CDATA[%s]]></FromUserName>" +
    108                         "<CreateTime>%s</CreateTime>" +
    109                         "<MsgType><![CDATA[text]]></MsgType>" +
    110                         "<Content><![CDATA[%s]]></Content>" + "</xml>",
    111                 fromUserName, toUserName, getUtcTime(), content);
    112     }
    113 
    114     private static String getUtcTime() {
    115         Date dt = new Date();// 如果不需要格式,可直接用dt,dt就是当前系统时间
    116         DateFormat df = new SimpleDateFormat("yyyyMMddhhmm");// 设置显示格式
    117         String nowTime = df.format(dt);
    118         long dd = (long) 0;
    119         try {
    120             dd = df.parse(nowTime).getTime();
    121         } catch (Exception e) {
    122 
    123         }
    124         return String.valueOf(dd);
    125     }
    126 }
    复制代码

      为了方便解析微信服务器发送给我们的xml格式的数据,这里我们借助于开源框架dom4j去解析xml(这里使用的是dom4j-2.0.0-RC1.jar)

      

    4.2.在WxServlet的doPost方法中处理请求

      WxServlet的doPost方法的代码如下:

    复制代码
     1  /**
     2      * 处理微信服务器发来的消息
     3      */
     4     protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
     5         // TODO 接收、处理、响应由微信服务器转发的用户发送给公众帐号的消息
     6         // 将请求、响应的编码均设置为UTF-8(防止中文乱码)
     7         request.setCharacterEncoding("UTF-8");
     8         response.setCharacterEncoding("UTF-8");
     9         System.out.println("请求进入");
    10         String result = "";
    11         try {
    12             Map<String,String> map = MessageHandlerUtil.parseXml(request);
    13             System.out.println("开始构造消息");
    14             result = MessageHandlerUtil.buildXml(map);
    15             System.out.println(result);
    16             if(result.equals("")){
    17                 result = "未正确响应";
    18             }
    19         } catch (Exception e) {
    20             e.printStackTrace();
    21             System.out.println("发生异常:"+ e.getMessage());
    22         }
    23         response.getWriter().println(result);
    24     }
    复制代码

      到此,我们的WxServlet已经可以正常处理用户的请求并做出响应了.接下来我们测试一下我们开发好的公众号应用是否可以正常和微信用户交互

      将WxStudy部署到Tomcat服务器,启动服务器,记得使用ngrok将本地Tomcat服务器的8080端口映射到外网,保证接口配置信息的URL地址:http://xdp.ngrok.natapp.cn/WxServlet可以正常与微信服务器通信

      登录到我们的测试公众号的管理后台,然后用微信扫描一下测试号的二维码,如下图所示:

      

     

      

      

      关注成功后,我们开发好的公众号应用会先给用户发一条提示用户操作的文本消息,微信用户根据提示操作输入"文本",我们的公众号应用接收到用户请求后就给用户回复了一条我们自己构建好的文本消息,如下图所示:

      

      我们的公众号应用响应给微信用户的文本消息的XML数据如下:

    复制代码
    1 <xml>
    2   <ToUserName><![CDATA[ojADgs0eDaqh7XkTM9GvDmdYPoDw]]></ToUserName>
    3   <FromUserName><![CDATA[gh_43df3882c452]]></FromUserName>
    4   <CreateTime>1453755900000</CreateTime>
    5   <MsgType><![CDATA[text]]></MsgType>
    6   <Content><![CDATA[孤傲苍狼在学习和总结微信开发了,构建一条文本消息:Hello World!]]></Content>
    7 </xml>
    复制代码

      测试公众号的管理后台也可以看到关注测试号的用户列表,如下图所示:

      

      通过这个简单的入门程序,我们揭开了微信开发的神秘面纱了.

      本篇的内容就这么多了。本文的内容和代码参考了用java开发微信公众号:公众号接入和access_token管理(二)这篇博客,在此对作者"风的姿态"表示感谢。

      下载测试项目部署运行时,由于项目中使用的是Servlet3.0,所以要求部署的Tomcat必须是7.x,以前也写过几篇关于Servlet3.0的博客,大家有兴趣的话可以去看看,本篇博文对应的测试项目代码下载http://pan.baidu.com/s/1hrfcGks

    展开全文
  • “`
  • 微信开发与代码的编写(一) 微信开发环境的搭建 目前移动开发处于比较火的的趋势,很多的开发者都跃跃欲试,目前移动App开发领域主要分为以下几种类型    我在平时的工作中接触得比较多的就是基于Android的Native...
  • 用户在关注与取消关注公众号时,微信会把这个事件推送到开发者填写的URL。方便开发者给用户下发欢迎消息或者做帐号的解绑。 微信服务器在五秒内收不到响应会断掉连接,并且重新发起请求,总共重试三次 关于重试的...
  • 微信开发之架构设计

    2014-10-30 11:45:32
     本文将讲解微信开发的前期准备,包括微信开发上的一些坑、架构上的设计、接口上需要注意的地方,全部来自自己的开发经验,如有不对,请指正。   微信开发的坑   1、微信授权  微信...
  • 微信api 关注事件案例

    2016-02-18 09:14:22
    header('Content-type: text/html; charset=utf-8');#设置头信息 require_once('zhphpWeixinApi....#加载微信接口类文件 $zhwx=new zhphpWeixinApi();//实例化 $configArr=array( 'token'=>'wxtest1', 'appid'
  • 手把手教你springboot企业微信开发(二)1、企业微信开发第一步2、weixin-java-cp-demo-master1)、引入thymeleaf改造项目2)增加Configuration3)回到企业号4)穿透内外网5)完善 从这一篇开始,开始从实际项目探索...
  • 个人微信开发api文档

    2019-05-24 04:13:32
    个人微信开发api文档 个人微信开发api文档,个人微信开发sdk,个人微信开发协议接口 微信开发sdk服务端调用接口 1、基础消息类型 1、客户端发送的心跳包 HeartBeatReq = 1001; 2、消息接收确认回复...
  • 10万+IT人都在关注,史上最全面的微信公众号开发系列教程:基于Springboot开发公众号关注/取消关注事件 前言: 关于监听公众号用户关注和取消关注的消息事件,微信官方文档给出的参考内容寥寥无几,具体如何配置...
  • 关于手机端的浏览器的兼容性问题相信一直是开发者们的一块心病,对于微信开发前端组件这一块总是找不到合适的移动端组件,什么都要自己去实现,这个开发成本就大了去了。于是乎博主打算另辟蹊径,找找基于微信开发的...
  • 1、在进行微信开发之前你 首先你得需要一个微信公众号(具体的申请步骤省略)2、进行微信开发的开发配置(我这里是配置一个测试账号后面的微信支付还是要走微信公众号 配置是一样的)URL为微信开发正确响应发送...
  • 当我们利用服务器和代码成为高级开发之后,微信公众平台编辑好的当用户关注时发送给用户的语句就会失效,此时应该怎么办,接下来,用代码来来教你实现。首先先看一下官方的开发文档: 关注/取消关注事件 用户在...
  • CleverCode前一段时间想去接触一下微信开发,申请了一个人订阅号,发现暂不能申请个人认证,而且没有微信接口的很多权限,也没有自定义菜单的权限(开发模式下)。在开发模式下,只能到手公众号里面的回复信息,然后...
1 2 3 4 5 ... 20
收藏数 24,851
精华内容 9,940
关键字:

微信开发关注没有响应