• 服务器配置好后,就可以进行消息的自动回复了。本文记述主线·消息接受和发送简单介绍·xml格式解析·其他代码简介PS:以上仅为本人学习流程,延伸阅读请参考官方文档或其他大牛作品(其实本人也是摸着石头过河的-.-...
    在之前的博客中,讲到了服务器配置一项。服务器配置好后,就可以进行消息的自动回复了。


    本文记述主线
    ·消息接受和发送简单介绍
    ·xml格式解析
    ·其他代码简介

    PS:以上仅为本人学习流程,延伸阅读请参考官方文档或其他大牛作品(其实本人也是摸着石头过河的-.-)。当然,有疑问或不当的地方,欢迎大家留言讨论。

    · 消息接受和发送简单介绍


    消息的自定回复,就是用户在订阅号中进行了某些操作(订阅、点击菜单、输入内容等),然后通过微信服务器发送到个人服务器上,个人服务器处理操作后,再通过微信服务器转发给用户。
    我们需要考虑的过程是,个人服务器和微信服务器的交互过程。

    官网介绍如下,感觉比较易于理解:
    当用户发送消息给公众号时(或某些特定的用户操作引发的事件推送时),会产生一个POST请求,开发者可以在响应包(Get)中返回特定XML结构,来对该消息进行响应(现支持回复文本、图片、图文、语音、视频、音乐)。严格来说,发送被动响应消息其实并不是一种接口,而是对微信服务器发过来消息的一次回复。

    从微信服务器接收到的消息例子(文本消息):

    <xml>
    <ToUserName><![CDATA[toUser]]></ToUserName>
    <FromUserName><![CDATA[fromUser]]></FromUserName> 
    <CreateTime>1348831860</CreateTime>
    <MsgType><![CDATA[text]]></MsgType>
    <Content><![CDATA[this is a test]]></Content>
    <MsgId>1234567890123456</MsgId>
    </xml>


    其中前四个项目是固定了,每个消息都有这四个,ToUserName、FromUserName、CreateTime和MsgType,后面试根据不同的消息类型(MsgType)而不同的;
    MsgType包括文本、图片、语音、视频、小视频、地理位置、连接等消息,和关注/取消关注、扫描二维码、自定义菜单等时间推送,详细可关注官网,本博客仅以文本作为例子;
    MsgType是消息的时候,有MsgId,是事件推送的时候,没有MsgId;
    如果想避免重复,存在MsgId的时候,使用MsgId作为判断,不存在MsgId的话,可以使用 FromUserName+CreateTime作为判断;
    <![CDATA ]>符号的含义是,在xml解析的时候,在这个符号内的所有字符均解析为字符串,尤其是一些特殊符号(</>d等),一些数值类型的一般不加这个符号。


    发送给微信服务器的消息例子(文本消息):

    <xml>
    <ToUserName><![CDATA[toUser]]></ToUserName>
    <FromUserName><![CDATA[fromUser]]></FromUserName>
    <CreateTime>12345678</CreateTime>
    <MsgType><![CDATA[text]]></MsgType>
    <Content><![CDATA[你好]]></Content>
    </xml>

    格式和要求基本上和接受的消息一样,不一样的地方在于MsgType没有事件推送类型,只有消息类型,包括文本、图片、语音、视频、音乐和图文等。


    · xml格式解析

    xml格式的解析是xml和java之间的转换。这里我用到了dom4j包和xstream包。
    这两个包的maven导入如下:

    <dependency>
      <groupId>dom4j</groupId>
      <artifactId>dom4j</artifactId>
      <version>1.6</version>
    </dependency>
    <dependency>
      <groupId>com.thoughtworks.xstream</groupId>
      <artifactId>xstream</artifactId>
      <version>1.4.8</version>
    </dependency>

    先说由xml转化为java
    类:MessageUtil

    public static Map<String, String> parseXml(HttpServletRequest request) throws Exception {
    	Map<String, String> map = new HashMap<String, String>();
    
    	InputStream inputStream = request.getInputStream();
    
    	SAXReader reader = new SAXReader();
    	Document document = reader.read(inputStream);
    
    	Element root = document.getRootElement();
    
    	@SuppressWarnings("unchecked")
    	List<Element> elements = root.elements();
    
    	for (Element el : elements) {
    		map.put(el.getName(), el.getText());
    	}
    
    	inputStream.close();
    	inputStream = null;
    
    	return map;
    }

    分析:
    这里是从request的InputStream流中读取数据,通过dom4j包转化为Map类型。
    其中需要注意的是收到的xml格式中,标签的首字母是大写的,例<ToUserName></ToUserName>,Map中的key是'ToUserName'。


    由java转化为xml,以text类型的xml格式为例
    类:MessageUtil

    public static String textMessageToXml(TextRespMessage textMessage) {
    	xstream.autodetectAnnotations(true);
    	return xstream.toXML(textMessage);
    }
    
    private static XStream xstream = new XStream(new XppDriver() {
    	@Override
    	public HierarchicalStreamWriter createWriter(Writer out) {
    		return new PrettyPrintWriter(out) {
    
    			boolean cdata = false;
    			Class<?> targetClass = null;
    
    			@Override
    			public void startNode(String name, @SuppressWarnings("rawtypes") Class clazz) {
    				super.startNode(name, clazz);
    				if (!name.equals("xml")) {
    					cdata = needCDATA(targetClass, name);
    				} else {
    					targetClass = clazz;
    				}
    			}
    
    			@Override
    			protected void writeText(QuickWriter writer, String text) {
    				if (cdata) {
    					writer.write("<![CDATA[");
    					writer.write(text);
    					writer.write("]]>");
    				} else {
    					writer.write(text);
    				}
    			}
    		};
    	}
    });
    
    private static boolean needCDATA(Class<?> targetClass, String fieldAlias) {
    	boolean cdata = false;
    	cdata = existsCDATA(targetClass, fieldAlias);
    	if (cdata) {
    		return cdata;
    	}
    	Class<?> superClass = targetClass.getSuperclass();
    	while (!superClass.equals(Object.class)) {
    		cdata = existsCDATA(superClass, fieldAlias);
    		if (cdata) {
    			return cdata;
    		}
    		superClass = superClass.getClass().getSuperclass();
    	}
    	return false;
    }
    
    private static boolean existsCDATA(Class<?> clazz, String fieldAlias) {
    	Field[] fields = clazz.getDeclaredFields();
    	for (Field field : fields) {
    		if (field.getAnnotation(XStreamCDATA.class) != null) {
    			XStreamAlias xStreamAlias = field.getAnnotation(XStreamAlias.class);
    			if (null != xStreamAlias) {
    				if (fieldAlias.equals(xStreamAlias.value()))
    					return true;
    			} else {
    				if (fieldAlias.equals(field.getName()))
    					return true;
    			}
    		}
    	}
    	return false;
    }

    用到的实体类:

    public class BaseRespMessage {
    
    	// 发送方帐号(一个OpenID)
    	@XStreamCDATA
    	@XStreamAlias("ToUserName")
    	private String toUserName;
    
    	// 开发者微信号
    	@XStreamCDATA
    	@XStreamAlias("FromUserName")
    	private String fromUserName;
    
    	// 消息创建时间 (整型)
    	@XStreamAlias("CreateTime")
    	private long createTime;
    
    	// test
    	@XStreamCDATA
    	@XStreamAlias("MsgType")
    	private String msgType;
    
    	// getter/setter方法省略
    }
    
    @XStreamAlias("xml")
    public class TextRespMessage extends BaseRespMessage {
    
    	// 文本消息内容
    	@XStreamCDATA
    	@XStreamAlias("Content")
    	private String content;
    
    	// getter/setter方法省略
    }
    
    @Retention(RetentionPolicy.RUNTIME)  
    @Target({ElementType.FIELD})
    public @interface XStreamCDATA {
    
    }

    分析:
    由java到xml主要用到了xstream包,通过toXml方法,直接把对象类转化为xml文档。
    但是由于微信要求的xml格式如下,包括头字母大小和<![CDATA[]]>的问题。这里我采用注解的方式来解决这两个问题。
    <ToUserName><![CDATA[toUser]]></ToUserName>


    在实体类的定义中,toUserName的定义如下,通过@XStreamAlias来设定别名来保证生产的xml中首字母大写。
    通过XStreamCDTA的自定义注解,来说明哪些项目需要追加<![CDATA[]]>,具体直接参考代码,还是比较容易理解的。这个方法是在网上看到的方法。
    @XStreamCDATA
    @XStreamAlias("ToUserName")
    private String toUserName;


    对于<![CDATA[]]>的问题,除了上述方法外,我通过规律还尝试了另外一种方法。
    通过观察xml格式,里面数值类型没有追加<![CDATA[]]>,其他都追加了,所以可以在重写startNode方法的时候,直接判断Class的类型如果为String类型的话,就追加<![CDATA[]]>。这样就能节省很多代码。
    注:因为本人没有尝试过所有的微信xml格式,所以不一定全部适用,目前我所遇到的格式都适用。

    @Override
    public void startNode(String name, @SuppressWarnings("rawtypes") Class clazz) {
    	super.startNode(name, clazz);
    	if(clazz.equals(String.class)) {
    		cdata = true;
    	} else {
    		cdata = false;
    	}
    }

    ·其他代码简介


    解析xml的代码完成后,这边是调用解析的代码,和简单业务逻辑的代码。

    调用的代码:
    WechatController

    @RequestMapping(value = "/verifyWechat", method = RequestMethod.POST)
    public void post(HttpServletRequest request, HttpServletResponse response) {
    	logger.info("post start.");
    
    	String xmlMessge = autoReturnService.processRequest(request);
    
    	PrintWriter writer = null;
    
    	try {
    		writer = response.getWriter();
    		writer.print(xmlMessge);
    	} catch (IOException e) {
    		e.printStackTrace();
    		logger.info("post exception.");
    	} finally {
    		writer.close();
    		writer = null;
    	}
    
    	logger.info("post end.");
    }

    自动回复的业务逻辑的代码
    AutoReturnServiceImpl

    @Service
    public class AutoReturnServiceImpl implements AutoReturnService {
    
    	private static final Logger logger = Logger.getLogger(AutoReturnServiceImpl.class);
    
    	@Override
    	public String processRequest(HttpServletRequest request) {
    
    		logger.info("processRequest start.");
    
    		String respXmlMessage = null;
    		try {
    			Map<String, String> map = MessageUtil.parseXml(request);
    
    			String toUserName = map.get("ToUserName");
    			String fromUserName = map.get("FromUserName");
    			String msgType = map.get("MsgType");
    
    			TextRespMessage textRespMessage = new TextRespMessage();
    			textRespMessage.setToUserName(fromUserName);
    			textRespMessage.setFromUserName(toUserName);
    			textRespMessage.setCreateTime(new Date().getTime());
    			textRespMessage.setMsgType(Constants.RESP_MESSAGE_TYPE);
    
    			// text type
    			if (Constants.REQ_MESSAGE_TYPE.equals(msgType)) {
    
    				String content = map.get("Content");
    				if (content.contains("1")) {
    					textRespMessage.setContent("收到的是1");
    					respXmlMessage = MessageUtil.textMessageToXml(textRespMessage);
    				} else if (content.contains("2")) {
    					textRespMessage.setContent("收到的是2");
    					respXmlMessage = MessageUtil.textMessageToXml(textRespMessage);
    				} else {
    					textRespMessage.setContent("你说的是什么,我不懂!");
    					respXmlMessage = MessageUtil.textMessageToXml(textRespMessage);
    				}
    			} else {
    				textRespMessage.setContent("收到的是我不能理解的类型!");
    				respXmlMessage = MessageUtil.textMessageToXml(textRespMessage);
    			}
    
    		} catch (Exception e) {
    			e.printStackTrace();
    			respXmlMessage = "success"; // 官网推荐设置
    		}
    
    		logger.info("processRequest end.");
    		return respXmlMessage;
    	}
    }

    使用到的实体类:

    public class BaseReqMessage {
    
    	// 开发者微信号
    	private String toUserName;
    
    	// 发送方帐号(一个OpenID)
    	private String fromUserName;
    
    	// 消息创建时间 (整型)
    	private long createTime;
    
    	// test
    	private String msgType;
    
    	// 消息id,64位整型
    	private long msgId;
    
    	// getter/setter方法省略
    }
    
    public class TextReqMessage extends BaseReqMessage {
    
    	// 文本消息内容
    	private String content;
    
    	// getter/setter方法省略
    }
    

    总结:
    以上就是就是自动回复消息的简单示例,简单来说,就是处理一个request(post)请求,接收到微信服务器的xml格式的请求,解析xml-进行业务处理-进行封装生xml格式,通过response返回。
    中间用到的解析xml格式和封装xml格式。


    源码会在之后的博客中提供。


    转载请注明出处,谢谢

    展开全文
  • 基于 laravel框架的微信公众号的自动回复功能。主要是图文消息和文本类型

    最近开始做公司的微信项目,深切的感受到了微信的不同,做微信是需要从头开始好好学的,在此记录一下学习微信的过程,也希望以后能根据这些文章迅速掌握微信开发的知识。少踩坑,,(laravel框架)

    1、首先需要进入微信公众号--》基本配置--》填上你的服务器信息--》验证TOKEN,获取appId和appkey

    2、下载官方的SDK文件,参考文档:https://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1421140543

    demo下载地址:http://www.cnblogs.com/txw1958/p/tutorials-of-wechat-public-platform.html       方倍工作室 的。

    3、首先需要明确(1)验证token通过,证明你的服务器可以和微信进行交互

    (2)进入微信页面,发送消息,此时,后台应接受消息,并判断消息类型

    (3)根据消息类型,拼接不同的xml数据,返回给模板

    (4)然后就实现了微信上自动回复的功能了。

    4、


    以上为验证token之后,在控制器设置的微信入口文件:这个文件也就是demo中的reponseMsg()方法:

    主要用于获得微信发送过来的数据,然后再把tousername,fromusername等信息放到一起,提取用户发送消息的类型$event,并且请求下一个方法:

    5、

    这个方法主要是为了判断请求的类型,根据不同的类型会有不同的处理方法。在此只处理text类型的数据,并且调用text方法,获取拼接的数据。最后返回给响应方法handle_response()方法。

    6、

    在这里,我们把回复的内容都给写死了,大家到时候也完全可以从数据库读出这些数据。里面就是简单的拼接了一些图文信息,然后把这些信息返回。数组$arr是方便在模板上输出图文消息。

    7、

    该控制器主要是根据传过来的event不同,判断该调用哪个模板,该回复什么样的消息等。如果$event==text,则调用默认的模板:$xml = view(wechat.tpl_reply_text) ;如果$event为news,则需要返回图文消息,我们把数据都放到数组里面,在模板上输出即可。

    8、

    <xml>
      <ToUserName><![CDATA[{{$ToUserName}}]]></ToUserName>
      <FromUserName><![CDATA[{{$FromUserName}}]]></FromUserName>
      <CreateTime>{{$CreateTime}}</CreateTime>
      <MsgType><![CDATA[news]]></MsgType>
      <ArticleCount>{{$ArticleCount}}</ArticleCount>
      <Articles>
    @foreach ($news  as $k => $v)
      <item>
      <Title><![CDATA[{{$v['Title']}}]]></Title>
      <Description><![CDATA[{{$v['Description']}}]]></Description>
      <PicUrl><![CDATA[{{$v['PicUrl']}}]]></PicUrl>
      <Url><![CDATA[{{$v['Url']}}]]></Url>
      </item>
    @endforeach
      </Articles>
    </xml>

    这就是模板部分:直接输出即可。

    9、在此已经是完成了微信的自动回复功能。用户输入指定的内容的时候,,我们也回复指定的内容回去。

    微信官方的给的demo被拆分了,没有按照官方上那种返回xml数据,而是采用了laravel的模板来输出。


    10、总结:虽然整体来说不够简洁,但也算是实现了功能,在此做个记录。以后水平高了再完善完善。

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

    问题描述:

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

    查阅官方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接口了,例子后面的文章将会讲到。

    展开全文
  • 一、前言 hello小伙伴们,大家好,本篇的主题是使用Java开发微信公众号之...微信开发文档: 微信官方文档-被动回复用户消息 二、版本说明 spring boot.version: v2.1.7.RELEASE java.version: 1.8 weixin-java-mp

    一、前言

    hello小伙伴们,大家好,本篇的主题是使用Java开发微信公众号之被动回复用户消息-回复图文消息,那么对于不太了解微信公众号被动回复用户消息(文本消息、图片消息)的小伙伴们,可以先看一下前面我写过的文章: Java开发微信公众号之被动回复用户消息-回复文本消息 , 废话不多说,下面开始进入主题。

    微信开发文档: 微信官方文档-被动回复用户消息

    二、版本说明

    • spring boot.version: v2.1.7.RELEASE
    • java.version: 1.8
    • weixin-java-mp.version: 3.5.0

    三、被动回复消息

    当用户发送消息给公众号时(或某些特定的用户操作引发的事件推送时),会产生一个POST请求,开发者可以在响应包(Get)中返回特定XML结构,来对该消息进行响应(现支持回复文本图片图文语音视频音乐)。严格来说,发送被动响应消息其实并不是一种接口,而是对微信服务器发过来消息的一次回复。

    四、消息排重

    微信服务器在将用户的消息发给公众号的开发者服务器URL地址(开发者中心处配置)后,微信服务器在五秒内收不到响应会断掉连接,并且重新发起请求,总共重试三次,如果在Debug调试中,发现用户无法收到响应的消息,可以检查是否消息处理超时,一般debug调试的情况下,超过5秒就会收不到响应。关于重试的消息排重,每一次消息都应该保持其幂等性,在推送的xml数据结构中,有msgid的消息推荐使用msgid排重。如果是事件类型消息推荐使用FromUserName + CreateTime进行排重。

    五、如何保证五秒内处理并回复

    假如服务器无法保证在五秒内处理并回复,必须做出下述回复,这样微信服务器才不会对此作任何处理,并且不会发起重试(这种情况下,可以使用客服消息接口进行异步回复),否则,将出现严重的错误提示。详见下面说明:

    • 1、直接回复success(推荐方式)
    • 2、直接回复空串(指字节长度为0的空字符串,而不是XML结构体中content字段的内容为空)

    一旦遇到服务器五秒内没有做处理并且响应回复或者开发者回复了异常数据,比如JSON数据等;微信都会在公众号会话中,向用户下发系统提示“该公众号暂时无法提供服务,请稍后再试”

    六、如何回复图文消息

    回复图片(不支持gif动图)多媒体消息时需要预先通过素材管理接口上传临时素材到微信服务器;也可以通过上传素材管理接口中的永久素材到微信微服务器,只不过临时素材在微信服务器上存储的有效期只有3天,而永久素材永远有效,由于微信永久素材的数量有上限,如果你的项目环境对对媒体素材文件需求量很大,谨慎使用永久素材上传,使用临时素材即可;不过也没关系,微信开发者文档也提供了删除永久素材和删除临时素材的接口,根据场景合理使用即可。
    通过素材管理接口上传完临时素材或者永久素材后,接口会返回一个关键字段叫做mediaId,这个mediaId返回给我们之后需要我们自己存储记录起来,等到需要使用多媒体文件素材的时候,都是通过传递mediaId到接口参数中,微信服务器会根据该mediaId去查找对应的素材文件,然后做对应的回复处理,比如回复图片信息、回复图文信息等等。

    七、预先上传图片素材

    请注意: 前面已经强调过,回复图片(不支持gif动图)等多媒体消息时需要预先通过素材管理接口上传临时素材到微信服务器,可以使用素材管理中的临时素材,也可以使用永久素材。
    所以我们在实现被动回复消息-回复图文消息的功能之前,需要先上传图片临时素材或者图片永久素材到微信平台,接下来我们先观察下图文消息所需要的xml格式和参数说明。

    1. 回复图文消息xml格式

    <xml>
      <ToUserName><![CDATA[toUser]]></ToUserName>
      <FromUserName><![CDATA[fromUser]]></FromUserName>
      <CreateTime>12345678</CreateTime>
      <MsgType><![CDATA[news]]></MsgType>
      <ArticleCount>1</ArticleCount>
      <Articles>
        <item>
          <Title><![CDATA[title1]]></Title>
          <Description><![CDATA[description1]]></Description>
          <PicUrl><![CDATA[picurl]]></PicUrl>
          <Url><![CDATA[url]]></Url>
        </item>
      </Articles>
    </xml>
    

    2. 回复图文消息参数说明

    参数 是否必须 说明
    ToUserName 接收方帐号(收到的OpenID)
    FromUserName 开发者微信号
    CreateTime 消息创建时间 (整型)
    MsgType 消息类型,图文为news
    ArticleCount 图文消息个数;当用户发送文本、图片、语音、视频、图文、地理位置这六种消息时,开发者只能回复1条图文消息;其余场景最多可回复8条图文消息
    Articles 图文消息信息,注意,如果图文数超过限制,则将只发限制内的条数
    Title 图文消息标题
    Description 图文消息描述
    PicUrl 图片链接,支持JPG、PNG格式,较好的效果为大图360200,小图200200
    Url 点击图文消息跳转链接

    3. 上传永久图片素材

    /**
     * @desc:
     * @author: cao_wencao
     * @date: 2020-05-21 13:47
     */
    @Slf4j
    @RestController
    @RequestMapping("/wx/material")
    public class WxMaterialController {
    
       private static final SimpleDateFormat sdf =new SimpleDateFormat("yyyy-MM-dd HH:mm:ss SSS" );
    
        @Autowired
        private WeChatMaterialUtils materialUtils;
    
        /**
         * 上传单个图片文件
         */
        @PostMapping("/uploadImg")
        public ApiResult uploadImg(@RequestParam("imgFile") MultipartFile imgFile) throws IOException {
            log.info("multipartFile = " + imgFile);
            log.info("ContentType = " + imgFile.getContentType());
            log.info("OriginalFilename = " + imgFile.getOriginalFilename());
            log.info("Name = " + imgFile.getName());
            log.info("Size = " + imgFile.getSize());
            String fileExt = imgFile.getOriginalFilename().substring(imgFile.getOriginalFilename().lastIndexOf(".") + 1);
            log.info("fileExt = " + fileExt);
    
            WxMpMaterialUploadResult wxMpMaterialUploadResult
                     = materialUtils.uploadFilesToWeChat("image", fileExt ,imgFile.getName(), imgFile.getInputStream());
            if(null == wxMpMaterialUploadResult){
                return ApiResult.error("上传图片失败");
            }
            log.info("wxMpMaterialUploadResult = : " + JSON.toJSONString(wxMpMaterialUploadResult));
            return ApiResult.succee(wxMpMaterialUploadResult,"上传图片成功");
        }
       }
    

    4. PostMan测试图片上传

    4.1 传参如下

    • headers

    “key”:“Content-Type”, “value”:“multipart/form-data”

    • body
      在这里插入图片描述
    • 响应结果
    {
        "code": 200,
        "message": "上传图片成功",
        "data": {
            "mediaId": "1C72rnlYrj7ZqBiRGdKCoUUudPCjA5qMkJZeODvTN9U",
            "url": "http://mmbiz.qpic.cn/mmbiz_jpg/DwMlrmia5oVQrbNsb6GJ64xlUtfTXspTcyuV1m6ykgiaGJQYR374WWfGGrgibJOibYc1df7Wyicw4u9f5CV3u7oAzow/0?wx_fmt=jpeg"
        }
    }
    
    • 访问url
      在这里插入图片描述

    八、封装图文消息Handler

    • TextMsgHandler.java
    package com.thinkingcao.weixin.handler;
    
    import me.chanjar.weixin.common.api.WxConsts;
    import me.chanjar.weixin.common.error.WxErrorException;
    import me.chanjar.weixin.common.session.WxSessionManager;
    import me.chanjar.weixin.mp.api.WxMpService;
    import me.chanjar.weixin.mp.bean.message.WxMpXmlMessage;
    import me.chanjar.weixin.mp.bean.message.WxMpXmlOutMessage;
    import me.chanjar.weixin.mp.bean.message.WxMpXmlOutNewsMessage;
    import me.chanjar.weixin.mp.bean.result.WxMpUser;
    import org.springframework.stereotype.Component;
    
    import java.util.ArrayList;
    import java.util.List;
    import java.util.Map;
    
    /**
     * @desc: 文本类型消息处理-TEXT
     * @link: XmlMsgType.TEXT
     * @author: cao_wencao
     * @date: 2020-05-20 15:15
     */
    @Component
    public class TextMsgHandler extends AbstractHandler {
    
        @Override
        public WxMpXmlOutMessage handle(WxMpXmlMessage wxMessage, Map<String, Object> map, WxMpService wxMpService, WxSessionManager wxSessionManager) throws WxErrorException {
            //判断传递过来的消息,类型是否为TEXT
            if (wxMessage.getMsgType().equals(WxConsts.XmlMsgType.TEXT)) {
                //TODO: 如果需要做微信消息日志存储,可以在这里进行日志存储到数据库,这里省略不写。
            }
            // 获取微信用户基本信息
            WxMpUser userWxInfo = wxMpService.getUserService().userInfo(wxMessage.getFromUser(), "zh_CN");
            if (null == userWxInfo){
                return null;
            }
            String content = wxMessage.getContent();
            if ("文本".equals(content)){
                //下面两种响应方式都可以
                //return new TextBuilder().build("您的一互动,泛起了我内心的涟漪。",wxMessage,wxMpService);
                return WxMpXmlOutMessage
                        .TEXT()
                        .content("您的一互动,就激起了我内心的无限可能")
                        .fromUser(wxMessage.getToUser())
                        .toUser(wxMessage.getFromUser())
                        .build();
            }
            if ("图片".equals(content)){
                return WxMpXmlOutMessage
                        .IMAGE()
                        .mediaId("1C72rnlYrj7ZqBiRGdKCoS54AXQwSo4iULd9qRhOC-U")
                        .fromUser(wxMessage.getToUser())
                        .toUser(wxMessage.getFromUser())
                        .build();
            }
            if ("图文".equals(content)){
                List<WxMpXmlOutNewsMessage.Item> articles = new ArrayList<>();
                WxMpXmlOutNewsMessage.Item item = new WxMpXmlOutNewsMessage.Item();
                item.setDescription("使用Java语言进行开发微信公众号发送图文消息");
                item.setPicUrl("http://mmbiz.qpic.cn/mmbiz_jpg/DwMlrmia5oVQrbNsb6GJ64xlUtfTXspTcgAU6Jvbt3uL72LqN3ToB4ibiaWkTZT7SD0IUD56uGiaFRXpI8vBYtYrAQ/0?wx_fmt=jpeg");
                item.setTitle("SpringBoot开发微信公众号");
                item.setUrl("https://blog.csdn.net/thinkingcao/category_9277860.html");
    
                articles.add(item);
                return WxMpXmlOutMessage
                        .NEWS()
                        .addArticle(item)
                        .articles(articles)
                        .fromUser(wxMessage.getToUser())
                        .toUser(wxMessage.getFromUser())
                        .build();
            }
            return null;
        }
    }
    

    九、测试被动回复图文消息

    在公众号对话框内输入: 图文 关键字后,后台会通过路由匹配该消息类型MsgType = text ,然后找到具体的文本消息处理器,也就是TextMsgHandler,然后进一步匹配关键字‘图文’,然后组装回复图文消息字段,将图文消息回复给用户互动者,那么这里简单插一句,在实际项目开发中,构建被动消息回复肯定是要实现动态的内容,那么所有字段产生的数据肯定都是要通过数据库表记录的,在构建被动消息回复时,从数据库取即可,这样一来就实现了动态被动回复用户消息。

    1. 后台请求响应
    2020-06-10 23:51:23.334 DEBUG 4812 --- [nio-8080-exec-3] m.c.w.mp.api.impl.BaseWxMpServiceImpl    : 
    【请求地址】: https://api.weixin.qq.com/cgi-bin/user/info?access_token=34_Gsg49pv1E8sqzyc7DeHUXSVtUQZSxxTXkXp3h_S_MCOXYwIuqqH41B4a6eF3HpokFucpJVjJIPpPvKwQYCXFMWdAJc45Yd8BmEk-PPHWDg_iJ5INbTuWn9m3gQdHQV-zcbtFN2V8-DGDZo6WSHXfAAAWLB
    【请求参数】:openid=oGjQdw2EyT7CBNfN84Te6IpmflCM&lang=zh_CN
    【响应数据】:{"subscribe":1,"openid":"oGjQdw2EyT7CBNfN84Te6IpmflCM","nickname":"曹","sex":1,"language":"zh_CN","city":"墨尔本","province":"维多利亚","country":"澳大利亚","headimgurl":"http:\/\/thirdwx.qlogo.cn\/mmopen\/1ZMUBCDTp8ZAsxH99cX3icFXXDSstNaIR1FDpibnmfNPEn1J7Hf9yLXicSHJiciaEgtwgTXRicib9X2mua4bpeEg2sWNics6rXnIKKq7\/132","subscribe_time":1589956861,"remark":"","groupid":0,"tagid_list":[],"subscribe_scene":"ADD_SCENE_QR_CODE","qr_scene":0,"qr_scene_str":""}
    2020-06-10 23:51:23.335 DEBUG 4812 --- [nio-8080-exec-3] m.c.weixin.mp.api.WxMpMessageRouter      : End session access: async=false, sessionId=oGjQdw2EyT7CBNfN84Te6IpmflCM
    2020-06-10 23:51:23.335 DEBUG 4812 --- [pool-1-thread-4] m.c.weixin.mp.api.WxMpMessageRouter      : End session access: async=true, sessionId=oGjQdw2EyT7CBNfN84Te6IpmflCM
    2020-06-10 23:51:23.336 DEBUG 4812 --- [nio-8080-exec-3] c.t.w.controller.WxPortalController      : 
    组装回复信息:<xml>
      <ToUserName><![CDATA[oGjQdw2EyT7CBNfN84Te6IpmflCM]]></ToUserName>
      <FromUserName><![CDATA[gh_833ac613acf7]]></FromUserName>
      <CreateTime>1591804283</CreateTime>
      <MsgType><![CDATA[news]]></MsgType>
      <Articles>
        <item>
          <Title><![CDATA[SpringBoot开发微信公众号]]></Title>
          <Description><![CDATA[使用Java语言进行开发微信公众号发送图文消息]]></Description>
          <PicUrl><![CDATA[http://mmbiz.qpic.cn/mmbiz_jpg/DwMlrmia5oVQrbNsb6GJ64xlUtfTXspTcyuV1m6ykgiaGJQYR374WWfGGrgibJOibYc1df7Wyicw4u9f5CV3u7oAzow/0?wx_fmt=jpeg]]></PicUrl>
          <Url><![CDATA[https://blog.csdn.net/thinkingcao/category_9277860.html]]></Url>
        </item>
      </Articles>
      <ArticleCount>1</ArticleCount>
    </xml>
    
    1. 对话框互动
      在这里插入图片描述

    十、源码

    源码: https://github.com/Thinkingcao/SpringBootLearning/tree/master/springboot-wechat

    展开全文
  • 上一篇《微信开发学习总结(一)——微信开发环境搭建》我们已经完成了微信开发的准备工作,准备工作完成之后,就要开始步入正题了。 一、微信公众平台的基本原理  在开始做之前,先简单介绍了微信公众平台的基本...
  • 微信开发回复消息收不到的最大罪魁祸首,微信公众账号为什么回复消息不成功的原因解决办法 上一篇文章讲到如何配置微信开发,以及.NET平台使用MVC做一个小DEMO的流程,还有常见收不到消息的问题,本篇文章讲的是一...
  • 自动回复消息,就是把自动回复的信息,组成xml文件,每一个回复都会对应着一个xml文件,并不是所有回复都使用的一个xml。下面分别是回复文本、图片、图文消息。在回复图片和图文中都需要上传图片,分别需要点用接口...
  • 因为我自己之前开发过一个自动回复看电影的微信公众号,还是有一些经验的,如果你能够做的更好的话,还可以添加其他的功能,赚点零花钱是完全没有问题的,有人可能会问这怎么可能会赚钱的呢? 微信用户是巨大的,...
  • 关于微信接入第三方域名进行业务开发的相关说明已在另外一篇做了说明,这里不做赘述,接入步骤请移步 https://blog.csdn.net/qq_43638176/article/details/88915436 本篇示范微信交互过程中的几个基础事件,主要...
  • 本课程是基于ThinkPHP5.0.1的微信开发教程,在整个课程中全部采用原理讲解和实践开发配套的方式。在该教程中还包含了源码供大家下载。代码下载地址:https://code.csdn.net/hayixia606/wechat/tree/master
  • 微信公众号开发者模式下,自动回复功能不能使用。 基于此,需开发该功能。 我这里使用的是客服功能,并没有选择自动回复的原因是因为需求中有一条消息请求可能匹配多个答案的情况。 这里自动回复功能中的 虽然...
  • 文章详情:【微信公众号开发】三、解析微信事件XML数据消息及响应 链接:https://www.microanswer.cn/blog/13
  • 既然我们选用了开发者中心,那微信公众平台的自动回复就不得行了,那就自己开发吧。 这就要在服务器配置的URL里面处理了。 一、自动回复处理代码 @RequestMapping(value="/api",method=RequestMethod.POST) ...
  • 微信开发与代码的编写(一) 微信开发环境的搭建 目前移动开发处于比较火的的趋势,很多的开发者都跃跃欲试,目前移动App开发领域主要分为以下几种类型    我在平时的工作中接触得比较多的就是基于Android的Native...
  • 一、微信开发模式 二、自己的服务器和域名 1.可以购买阿里云的服务器和域名。 2.使用内网渗透软件NATAPP把自己的计算机打造成自定义服务器,会自动拥有一个域名。 缺点就是关掉后域名会变化,因为是免费的,...
  • 首先上传一张图片到定义的文件夹里 然后再上传到微信公众号里,在添加一个reply_image数据表,把信息添加到这个数据表中,具体代码如下 public function upload(){ $upload = new \Think\Upload();// 实例化上传类...
  • 需求在公众号内的输入任意文字,文字相关的第三方网站链接实现简单后台设置我们在微信公众平台上,是可以进行简单的自定义消息回复的。这里的显然不能满足我们的需求。配置服务器配置域名略配置测试服务器略。和一般...
  • 微信公众号后台有编辑模式和代码模式两种,设置成代码模式后,微信服务器会把公众号收到的所有消息以post方式发送到验证接入填写的url上,携带的请求参数为xml格式,由自己写的controller负责处理消息并按照微信要求...
  • 我们先打开微信开发文档,选择“消息管理”模块中的”接收普通消息“ 文档中已经告诉我们,当普通微信用户向公众账号发送消息时,微信服务器会把该消息封装成XML数据包通过POST的方式发送到开发者填写的URL上...
1 2 3 4 5 ... 20
收藏数 9,220
精华内容 3,688