一、微信公众平台开发接入指南
接入微信公众平台开发,需要按照如下步骤完成:
1、填写服务器配置
登录微信公众平台官网后,在公众平台后台管理页面 - 开发者中心页,点击“修改配置”按钮,填写服务器地址(URL)、Token和EncodingAESKey,其中URL是开发者用来接收微信消息和事件的接口URL。Token可由开发者可以任意填写,用作生成签名(该Token会和接口URL中包含的Token进行比对,从而验证安全性)。EncodingAESKey由开发者手动填写或随机生成,将用作消息体加解密密钥。
2、验证服务器地址的有效性
开发者提交信息后,微信服务器将发送GET请求到填写的服务器地址URL上,GET请求携带四个参数:
参数 |
描述 |
signature |
微信加密签名,signature结合了开发者填写的token参数和请求中的timestamp参数、nonce参数。 |
timestamp |
时间戳 |
nonce |
随机数 |
echostr |
随机字符串 |
3、依据接口文档实现业务逻辑
验证URL有效性成功后即接入生效,成为开发者。如果公众号类型为服务号(订阅号只能使用普通消息接口),可以在公众平台网站中申请认证,认证成功的服务号将获得众多接口权限,以满足开发者需求。此后用户每次向公众号发送消息、或者产生自定义菜单点击事件时,开发者填写的服务器配置URL将得到微信服务器推送过来的消息和事件,然后开发者可以依据自身业务逻辑进行响应,例如回复消息等。公众号调用各接口时,一般会获得正确的结果,具体结果可见对应接口的说明。返回错误时,可根据返回码来查询错误原因。全局返回码说明用户向公众号发送消息时,公众号方收到的消息发送者是一个OpenID,是使用用户微信号加密后的结果,每个用户对每个公众号有一个唯一的OpenID。
三、微信服务器向公众号服务器推送消息的类型
当用户发送消息给公众号时(或某些特定的用户操作引发的事件推送时),会产生一个POST请求,开发者可以在响应包(Get)中返回特定XML结构,来对该消息进行响应(现支持回复文本、图片、图文、语音、视频、音乐)。严格来说,发送被动响应消息其实并不是一种接口,而是对微信服务器发过来消息的一次回复。
微信服务器在将用户的消息发给公众号的开发者服务器地址(开发者中心处配置)后,微信服务器在五秒内收不到响应会断掉连接,并且重新发起请求,总共重试三次,如果在调试中,发现用户无法收到响应的消息,可以检查是否消息处理超时。关于重试的消息排重,有msgid的消息推荐使用msgid排重。事件类型消息推荐使用FromUserName + CreateTime 排重。
微信服务器推送消息类型的XML数据包结构
参数 |
描述 |
ToUserName |
开发者微信号 |
FromUserName |
发送方帐号(一个OpenID) |
CreateTime |
消息创建时间 (整型) |
MsgType |
text |
Content |
文本消息内容 |
MsgId |
消息id,64位整型 |
参数 |
描述 |
ToUserName |
开发者微信号 |
FromUserName |
发送方帐号(一个OpenID) |
CreateTime |
消息创建时间 (整型) |
MsgType |
image |
PicUrl |
图片链接 |
MediaId |
图片消息媒体id,可以调用多媒体文件下载接口拉取数据。 |
MsgId |
消息id,64位整型 |
参数 |
描述 |
ToUserName |
开发者微信号 |
FromUserName |
发送方帐号(一个OpenID) |
CreateTime |
消息创建时间 (整型) |
MsgType |
语音为voice |
MediaId |
语音消息媒体id,可以调用多媒体文件下载接口拉取数据。 |
Format |
语音格式,如amr,speex等 |
MsgID |
消息id,64位整型 |
请注意,开通语音识别后,用户每次发送语音给公众号时,微信会在推送的语音消息XML数据包中,增加一个Recongnition字段(注:由于客户端缓存,开发者开启或者关闭语音识别功能,对新关注者立刻生效,对已关注用户需要24小时生效。开发者可以重新关注此帐号进行测试)。开启语音识别后的语音XML数据包如下:
多出的字段中,Format为语音格式,一般为amr,Recognition为语音识别结果,使用UTF8编码。
参数 |
描述 |
ToUserName |
开发者微信号 |
FromUserName |
发送方帐号(一个OpenID) |
CreateTime |
消息创建时间 (整型) |
MsgType |
视频为video |
MediaId |
视频消息媒体id,可以调用多媒体文件下载接口拉取数据。 |
ThumbMediaId |
视频消息缩略图的媒体id,可以调用多媒体文件下载接口拉取数据。 |
MsgId |
消息id,64位整型 |
参数 |
描述 |
ToUserName |
开发者微信号 |
FromUserName |
发送方帐号(一个OpenID) |
CreateTime |
消息创建时间 (整型) |
MsgType |
小视频为shortvideo |
MediaId |
视频消息媒体id,可以调用多媒体文件下载接口拉取数据。 |
ThumbMediaId |
视频消息缩略图的媒体id,可以调用多媒体文件下载接口拉取数据。 |
MsgId |
消息id,64位整型 |
参数 |
描述 |
ToUserName |
开发者微信号 |
FromUserName |
发送方帐号(一个OpenID) |
CreateTime |
消息创建时间 (整型) |
MsgType |
location |
Location_X |
地理位置维度 |
Location_Y |
地理位置经度 |
Scale |
地图缩放大小 |
Label |
地理位置信息 |
MsgId |
消息id,64位整型 |
参数 |
描述 |
ToUserName |
接收方微信号 |
FromUserName |
发送方微信号,若为普通用户,则是一个OpenID |
CreateTime |
消息创建时间 |
MsgType |
消息类型,link |
Title |
消息标题 |
Description |
消息描述 |
Url |
消息链接 |
MsgId |
消息id,64位整型 |
示例代码(Java版)
首先接收微信服务器POST请求数据, 代码如下:接收微信服务器POST请求数据, 代码如下:
1. /**
2. * 微信公众平台 所有接口入口
3. *
4. * @param request
5. * the request send by the client to the server
6. * @param response
7. * the response send by the server to the client
8. * @throws ServletException
9. * if an error occurred
10. * @throws IOException
11. * if an error occurred
12. */
13. public void doPost(HttpServletRequest request, HttpServletResponse response)
14. throws ServletException, IOException {
15. response.setCharacterEncoding("UTF-8");
16. request.setCharacterEncoding("UTF-8");
17. PrintWriter out = response.getWriter();
18. try {
19. InputStream is = request.getInputStream();
20. PushManage push = new PushManage();
21. String getXml = push.PushManageXml(is);
22. out.print(getXml);
23. } catch (JDOMException e) {
24. e.printStackTrace();
25. out.print("");
26. } catch (Exception e) {
27. out.print("");
28. } finally {
29. if(out!=null) {
30. out.flush();
31. out.close();
32. }
33. }
34. }
四、被动回复用户消息格式
假如服务器无法保证在五秒内处理并回复,必须做出下述回复,这样微信服务器才不会对此作任何处理,并且不会发起重试(这种情况下,可以使用客服消息接口进行异步回复),否则,将出现严重的错误提示。详见下面说明:
1、(推荐方式)直接回复success
2、直接回复空串(指字节长度为0的空字符串,而不是XML结构体中content字段的内容为空)
参数 |
是否必须 |
描述 |
ToUserName |
是 |
接收方帐号(收到的OpenID) |
FromUserName |
是 |
开发者微信号 |
CreateTime |
是 |
消息创建时间 (整型) |
MsgType |
是 |
text |
Content |
是 |
回复的消息内容(换行:在content中能够换行,微信客户端就支持换行显示) |
参数 |
是否必须 |
说明 |
ToUserName |
是 |
接收方帐号(收到的OpenID) |
FromUserName |
是 |
开发者微信号 |
CreateTime |
是 |
消息创建时间 (整型) |
MsgType |
是 |
image |
MediaId |
是 |
通过素材管理接口上传多媒体文件,得到的id。 |
参数 |
是否必须 |
说明 |
ToUserName |
是 |
接收方帐号(收到的OpenID) |
FromUserName |
是 |
开发者微信号 |
CreateTime |
是 |
消息创建时间戳 (整型) |
MsgType |
是 |
语音,voice |
MediaId |
是 |
通过素材管理接口上传多媒体文件,得到的id |
参数 |
是否必须 |
说明 |
ToUserName |
是 |
接收方帐号(收到的OpenID) |
FromUserName |
是 |
开发者微信号 |
CreateTime |
是 |
消息创建时间 (整型) |
MsgType |
是 |
video |
MediaId |
是 |
通过素材管理接口上传多媒体文件,得到的id |
Title |
否 |
视频消息的标题 |
Description |
否 |
视频消息的描述 |
参数 |
是否必须 |
说明 |
ToUserName |
是 |
接收方帐号(收到的OpenID) |
FromUserName |
是 |
开发者微信号 |
CreateTime |
是 |
消息创建时间 (整型) |
MsgType |
是 |
music |
Title |
否 |
音乐标题 |
Description |
否 |
音乐描述 |
MusicURL |
否 |
音乐链接 |
HQMusicUrl |
否 |
高质量音乐链接,WIFI环境优先使用该链接播放音乐 |
ThumbMediaId |
否 |
缩略图的媒体id,通过素材管理接口上传多媒体文件,得到的id |
参数 |
是否必须 |
说明 |
ToUserName |
是 |
接收方帐号(收到的OpenID) |
FromUserName |
是 |
开发者微信号 |
CreateTime |
是 |
消息创建时间 (整型) |
MsgType |
是 |
news |
ArticleCount |
是 |
图文消息个数,限制为10条以内 |
Articles |
是 |
多条图文消息信息,默认第一个item为大图,注意,如果图文数超过10,则将会无响应 |
Title |
否 |
图文消息标题 |
Description |
否 |
图文消息描述 |
PicUrl |
否 |
图片链接,支持JPG、PNG格式,较好的效果为大图360*200,小图200*200 |
Url |
否 |
点击图文消息跳转链接 |
|
|
|
根据POST请求的XML数据解析并响应相关信息,代码如下
1. package cn.wuzhuti.weixin.utils;
2.
3. import java.io.IOException;
4. import java.io.InputStream;
5. import java.text.SimpleDateFormat;
6. import java.util.Date;
7. import java.util.List;
8. import org.jdom2.Document;
9. import org.jdom2.Element;
10. import org.jdom2.JDOMException;
11. import org.jdom2.input.SAXBuilder;
12. import org.jdom2.output.XMLOutputter;
13.
14. public class PushManage {
15.
16. public String PushManageXml(InputStream is) throws JDOMException {
17.
18. String returnStr = ""; // 反回Servlet字符串
19. String toName = ""; // 开发者微信号
20. String fromName = ""; // 发送方帐号(一个OpenID)
21. String type = ""; // 请求类型
22. String con = ""; // 消息内容(接收)
23. String event = ""; // 自定义按钮事件请求
24. String eKey = ""; // 事件请求key值
25.
26. try {
27.
28. SAXBuilder sax = new SAXBuilder();
29. Document doc = sax.build(is);
30. // 获得文件的根元素
31. Element root = doc.getRootElement();
32.
33. // 获得根元素的第一级子节点
34. List list = root.getChildren();
35. for (int j = 0; j < list.size(); j++) {
36. // 获得结点
37. Element first = (Element) list.get(j);
38.
39. if (first.getName().equals("ToUserName")) {
40. toName = first.getValue().trim();
41. } else if (first.getName().equals("FromUserName")) {
42. fromName = first.getValue().trim();
43. } else if (first.getName().equals("MsgType")) {
44. type = first.getValue().trim();
45. } else if (first.getName().equals("Content")) {
46. con = first.getValue().trim();
47. } else if (first.getName().equals("Event")) {
48. event = first.getValue().trim();
49. } else if (first.getName().equals("EventKey")) {
50. eKey = first.getValue().trim();
51. }
52. }
53. } catch (IOException e) {
54. //异常
55. }
56.
57. if (type.equals("event")) { //此为事件
58. if (event.equals("subscribe")) {// 此为 关注事件
59.
60. } else if (event.equals("unsubscribe")) { //此为取消关注事件
61.
62. } else if (event.equals("CLICK")) { //此为 自定义菜单按钮点击事件
63. // 以下为自定义按钮事件
64. if (eKey.equals("V1")) { //菜单1
65. returnStr = getBackXMLTypeText(toName,fromName,"点击了菜单1");
66. } else if (eKey.equals("V2")) { //菜单2
67. returnStr = getBackXMLTypeText(toName,fromName,"点击了菜单2");
68. }
69. }
70. } else if (type.equals("text")) { // 此为 文本信息
71. returnStr = getBackXMLTypeText(toName,fromName,"输入了:"+con);
72. }
73.
74. return returnStr;
75. }
76.
77.
78. /**
79. * 编译文本信息
80. *
81. * @author xiaowu
82. * @since 2013-9-27
83. * @param toName
84. * @param FromName
85. * @param content
86. * @return
87. */
88. private String getBackXMLTypeText(String toName, String fromName,
89. String content) {
90.
91. String returnStr = "";
92.
93. SimpleDateFormat format = new SimpleDateFormat("yyyyMMddHHmmss");
94. String times = format.format(new Date());
95.
96. Element rootXML = new Element("xml");
97.
98. rootXML.addContent(new Element("ToUserName").setText(fromName));
99. rootXML.addContent(new Element("FromUserName").setText(toName));
100. rootXML.addContent(new Element("CreateTime").setText(times));
101. rootXML.addContent(new Element("MsgType").setText("text"));
102. rootXML.addContent(new Element("Content").setText(content));
103.
104. Document doc = new Document(rootXML);
105.
106. XMLOutputter XMLOut = new XMLOutputter();
107. returnStr = XMLOut.outputString(doc);
108.
109. return returnStr;
110. }
111.
112. /**
113. * 编译图片信息(单图模式)
114. *
115. * @author xiaowu
116. * @since 2013-9-27
117. * @param toName
118. * @param FromName
119. * @param content
120. * @return
121. */
122. private String getBackXMLTypeImg(String toName, String fromName,
123. String title, String content, String url, String pUrl) {
124.
125. String returnStr = "";
126.
127. SimpleDateFormat format = new SimpleDateFormat("yyyyMMddHHmmss");
128. String times = format.format(new Date());
129.
130. Element rootXML = new Element("xml");
131.
132. rootXML.addContent(new Element("ToUserName").setText(fromName));
133. rootXML.addContent(new Element("FromUserName").setText(toName));
134. rootXML.addContent(new Element("CreateTime").setText(times));
135. rootXML.addContent(new Element("MsgType").setText("news"));
136. rootXML.addContent(new Element("ArticleCount").setText("1"));
137.
138. Element fXML = new Element("Articles");
139. Element mXML = null;
140.
141. mXML = new Element("item");
142. mXML.addContent(new Element("Title").setText(title));
143. mXML.addContent(new Element("Description").setText(content));
144. mXML.addContent(new Element("PicUrl").setText(pUrl));
145. mXML.addContent(new Element("Url").setText(url));
146. fXML.addContent(mXML);
147. rootXML.addContent(fXML);
148.
149. Document doc = new Document(rootXML);
150.
151. XMLOutputter XMLOut = new XMLOutputter();
152. returnStr = XMLOut.outputString(doc);
153.
154. return returnStr;
155. }
156. /**
157. * 编译图片信息(无图模式)
158. *
159. * @author xiaowu
160. * @since 2013-9-27
161. * @param toName
162. * @param FromName
163. * @param content
164. * @return
165. */
166. private String getBackXMLTypeImg(String toName, String fromName,
167. String title, String content, String url) {
168.
169. String returnStr = "";
170.
171. SimpleDateFormat format = new SimpleDateFormat("yyyyMMddHHmmss");
172. String times = format.format(new Date());
173.
174. Element rootXML = new Element("xml");
175.
176. rootXML.addContent(new Element("ToUserName").setText(fromName));
177. rootXML.addContent(new Element("FromUserName").setText(toName));
178. rootXML.addContent(new Element("CreateTime").setText(times));
179. rootXML.addContent(new Element("MsgType").setText("news"));
180. rootXML.addContent(new Element("ArticleCount").setText("1"));
181.
182. Element fXML = new Element("Articles");
183. Element mXML = null;
184.
185. //String url = "";
186. String ss = "";
187. mXML = new Element("item");
188. mXML.addContent(new Element("Title").setText(title));
189. mXML.addContent(new Element("Description").setText(content));
190. mXML.addContent(new Element("PicUrl").setText(ss));
191. mXML.addContent(new Element("Url").setText(url));
192. fXML.addContent(mXML);
193. rootXML.addContent(fXML);
194.
195. Document doc = new Document(rootXML);
196.
197. XMLOutputter XMLOut = new XMLOutputter();
198. returnStr = XMLOut.outputString(doc);
199.
200. return returnStr;
201. }
202.
203. /**
204. * 编译音乐信息
205. *
206. * @author xiaowu
207. * @since 2013-9-27
208. * @param toName
209. * @param FromName
210. * @param content
211. * @return
212. */
213. @SuppressWarnings("unused")
214. private String getBackXMLTypeMusic(String toName, String fromName,
215. String content) {
216.
217. String returnStr = "";
218.
219. SimpleDateFormat format = new SimpleDateFormat("yyyyMMddHHmmss");
220. String times = format.format(new Date());
221.
222. Element rootXML = new Element("xml");
223.
224. rootXML.addContent(new Element("ToUserName").setText(fromName));
225. rootXML.addContent(new Element("FromUserName").setText(toName));
226. rootXML.addContent(new Element("CreateTime").setText(times));
227. rootXML.addContent(new Element("MsgType").setText("music"));
228.
229. Element mXML = new Element("Music");
230.
231. mXML.addContent(new Element("Title").setText("音乐"));
232. mXML.addContent(new Element("Description").setText("音乐让人心情舒畅!"));
233. mXML.addContent(new Element("MusicUrl").setText(content));
234. mXML.addContent(new Element("HQMusicUrl").setText(content));
235.
236. rootXML.addContent(mXML);
237.
238. Document doc = new Document(rootXML);
239.
240. XMLOutputter XMLOut = new XMLOutputter();
241. returnStr = XMLOut.outputString(doc);
242.
243. return returnStr;
244. }
245. }