基于之前的文章SSM配置的项目:http://www.cnblogs.com/mangyang/p/5168291.html
来进行微信第三方开发,
微信二次开发,官方还是网上有很多介绍了,这里就不在进行讲述了 直接上干货。
首先 与微信对接,服务器配置,需要80端口和443端口开放的服务器,这里推荐 使用 python 的pagekite,一款反向代理的工具,具体安装百度搜,提供下配置放方法:http://jingyan.baidu.com/article/0eb457e52ca0af03f0a90568.html
配置好了之后,使用maven 或者 tomcat 等中间件 启动咱们的项目,访问之前注册的 pagekite地址,试一下,正常访问就进行下一步,
一、服务器配置认证
首先根据官方文档得知,
1)将token、timestamp、nonce三个参数进行字典序排序
2)将三个参数字符串拼接成一个字符串进行sha1加密
3)开发者获得加密后的字符串可与signature对比,标识该请求来源于微信
SHA1.java
package com.wx.util; /* * 微信公众平台(JAVA) SDK * * Copyright (c) 2014, Ansitech Network Technology Co.,Ltd All rights reserved. * http://www.ansitech.com/weixin/sdk/ * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ import java.security.MessageDigest; /** * <p> * Title: SHA1算法 * </p> * */ 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); } } }
然后编写controller 来接收 微信的 认证请求
WxManagerController.java
package com.controller; import java.io.IOException; import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; import java.util.List; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.springframework.stereotype.Controller; import org.springframework.ui.Model; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.ResponseBody; import com.wx.util.SHA1; @Controller @RequestMapping( "/wx" ) public class WxManagerController { private String Token = "testfortoken"; @RequestMapping(value = "", method = { RequestMethod.GET, RequestMethod.POST }) @ResponseBody public void load(Model model, HttpServletRequest request, HttpServletResponse response) { //判断访问方式 boolean isGet = request.getMethod().toLowerCase().equals("get"); if (isGet) { //进行认证 access(request, response); } else { //处理微信post请求 } } /** * 验证URL真实性 * * @param request * @param response * @return String */ private String access(HttpServletRequest request, HttpServletResponse response) { // 验证URL真实性 String signature = request.getParameter("signature");// 微信加密签名 String timestamp = request.getParameter("timestamp");// 时间戳 String nonce = request.getParameter("nonce");// 随机数 String echostr = request.getParameter("echostr");// 随机字符串 List<String> params = new ArrayList<String>(); params.add(Token); params.add(timestamp); params.add(nonce); // 1. 将token、timestamp、nonce三个参数进行字典序排序 Collections.sort(params, new Comparator<String>() { @Override public int compare(String o1, String o2) { return o1.compareTo(o2); } }); // 2. 将三个参数字符串拼接成一个字符串进行sha1加密 String temp = SHA1.encode(params.get(0) + params.get(1) + params.get(2)); if (temp.equals(signature)) { try { response.getWriter().write(echostr); return echostr; } catch (IOException e) { e.printStackTrace(); } } return null; } }
OK,到微信公众号后台,开发-基本配置-修改配置
token是项目里自己填写的参数,一致即可,
EncodingAESKey 随机生成即可,
点击提交。、
成功!
下面做一些功能,用户消息处理以及事件处理,
二、消息,事件处理
根据官方文档得知,处理微信的post请求,微信是以xml格式发送的,所以要解析xml并做处理,这里使用XStream
maven 使用的 oschina库 搜不到 xstream-1.4.8 版本 手动下载了一个 下载链接:http://pan.baidu.com/s/1dEAdDVb
其他的maven 可直接pom里配置。
pom.xml
<dependency> <groupId>xstream</groupId> <artifactId>xstream</artifactId> <version>1.4.8</version> <scope>system</scope> <systemPath>${project.basedir}/WebContent/WEB-INF/lib/xstream-1.4.8.jar</systemPath> </dependency> <dependency> <groupId>xmlpull</groupId> <artifactId>xmlpull</artifactId> <version>1.1.3.1</version> </dependency> <dependency> <groupId>xpp3</groupId> <artifactId>xpp3</artifactId> <version>1.1.4c</version> </dependency>
xstream 下的 systemPath 填写项目里对应的jar位置。
开始编写代码
加入 CDATA 验证创建的@interface类
XStreamCDATA.java
package com.penuel.mythopoet.wx.manager; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; @Retention(RetentionPolicy.RUNTIME) @Target({ ElementType.FIELD }) public @interface XStreamCDATA { }
XStream工具类
SerializeXmlUtil.java
package com.wx.util; import java.io.Writer; import java.lang.reflect.Field; import com.thoughtworks.xstream.XStream; import com.thoughtworks.xstream.annotations.XStreamAlias; import com.thoughtworks.xstream.core.util.QuickWriter; import com.thoughtworks.xstream.io.HierarchicalStreamWriter; import com.thoughtworks.xstream.io.xml.PrettyPrintWriter; import com.thoughtworks.xstream.io.xml.XppDriver; /** * xml 转换工具类 */ public class SerializeXmlUtil { public static XStream createXstream() { return 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); // 业务处理,对于用XStreamCDATA标记的Field,需要加上CDATA标签 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; // first, scan self cdata = existsCDATA(targetClass, fieldAlias); if (cdata) return cdata; // if cdata is false, scan supperClass until java.lang.Object 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) { if ("MediaId".equals(fieldAlias)) { return true; // 特例添加 morning99 } // scan fields Field[] fields = clazz.getDeclaredFields(); for (Field field : fields) { // 1. exists XStreamCDATA if (field.getAnnotation(XStreamCDATA.class) != null) { XStreamAlias xStreamAlias = field.getAnnotation(XStreamAlias.class); // 2. exists XStreamAlias if (null != xStreamAlias) { if (fieldAlias.equals(xStreamAlias.value()))// matched return true; } else {// not exists XStreamAlias if (fieldAlias.equals(field.getName())) return true; } } } return false; } }
微信误参数字典
WxErrorCode.java
package com.wx.util; import java.util.HashMap; import java.util.Map; @SuppressWarnings("unchecked") public class WxErrorCode { @SuppressWarnings("rawtypes") public static final Map ERRORCODE=new HashMap<Integer,String>(); static{ ERRORCODE.put(-1,"系统繁忙"); ERRORCODE.put(0,"请求成功"); ERRORCODE.put(40001,"获取access_token时AppSecret错误,或者access_token无效"); ERRORCODE.put(40002,"不合法的凭证类型"); ERRORCODE.put(40003,"不合法的OpenID"); ERRORCODE.put(40004,"不合法的媒体文件类型"); ERRORCODE.put(40005,"不合法的文件类型"); ERRORCODE.put(40006,"不合法的文件大小"); ERRORCODE.put(40007,"不合法的媒体文件id"); ERRORCODE.put(40008,"不合法的消息类型"); ERRORCODE.put(40009,"不合法的图片文件大小"); ERRORCODE.put(40010,"不合法的语音文件大小"); ERRORCODE.put(40011,"不合法的视频文件大小"); ERRORCODE.put(40012,"不合法的缩略图文件大小"); ERRORCODE.put(40013,"不合法的APPID"); ERRORCODE.put(40014,"不合法的access_token"); ERRORCODE.put(40015,"不合法的菜单类型"); ERRORCODE.put(40016,"不合法的按钮个数"); ERRORCODE.put(40017,"不合法的按钮个数"); ERRORCODE.put(40018,"不合法的按钮名字长度"); ERRORCODE.put(40019,"不合法的按钮KEY长度"); ERRORCODE.put(40020,"不合法的按钮URL长度"); ERRORCODE.put(40021,"不合法的菜单版本号"); ERRORCODE.put(40022,"不合法的子菜单级数"); ERRORCODE.put(40023,"不合法的子菜单按钮个数"); ERRORCODE.put(40024,"不合法的子菜单按钮类型"); ERRORCODE.put(40025,"不合法的子菜单按钮名字长度"); ERRORCODE.put(40026,"不合法的子菜单按钮KEY长度"); ERRORCODE.put(40027,"不合法的子菜单按钮URL长度"); ERRORCODE.put(40028,"不合法的自定义菜单使用用户"); ERRORCODE.put(40029,"不合法的oauth_code"); ERRORCODE.put(40030,"不合法的refresh_token"); ERRORCODE.put(40031,"不合法的openid列表"); ERRORCODE.put(40032,"不合法的openid列表长度"); ERRORCODE.put(40033,"不合法的请求字符,不能包含\uxxxx格式的字符"); ERRORCODE.put(40035,"不合法的参数"); ERRORCODE.put(40038,"不合法的请求格式"); ERRORCODE.put(40039,"不合法的URL长度"); ERRORCODE.put(40050,"不合法的分组id"); ERRORCODE.put(40051,"分组名字不合法"); ERRORCODE.put(41001,"缺少access_token参数"); ERRORCODE.put(41002,"缺少appid参数"); ERRORCODE.put(41003,"缺少refresh_token参数"); ERRORCODE.put(41004,"缺少secret参数"); ERRORCODE.put(41005,"缺少多媒体文件数据"); ERRORCODE.put(41006,"缺少media_id参数"); ERRORCODE.put(41007,"缺少子菜单数据"); ERRORCODE.put(41008,"缺少oauth code"); ERRORCODE.put(41009,"缺少openid"); ERRORCODE.put(42001,"access_token超时"); ERRORCODE.put(42002,"refresh_token超时"); ERRORCODE.put(42003,"oauth_code超时"); ERRORCODE.put(43001,"需要GET请求"); ERRORCODE.put(43002,"需要POST请求"); ERRORCODE.put(43003,"需要HTTPS请求"); ERRORCODE.put(43004,"需要接收者关注"); ERRORCODE.put(43005,"需要好友关系"); ERRORCODE.put(44001,"多媒体文件为空"); ERRORCODE.put(44002,"POST的数据包为空"); ERRORCODE.put(44003,"图文消息内容为空"); ERRORCODE.put(44004,"文本消息内容为空"); ERRORCODE.put(45001,"多媒体文件大小超过限制"); ERRORCODE.put(45002,"消息内容超过限制"); ERRORCODE.put(45003,"标题字段超过限制"); ERRORCODE.put(45004,"描述字段超过限制"); ERRORCODE.put(45005,"链接字段超过限制"); ERRORCODE.put(45006,"图片链接字段超过限制"); ERRORCODE.put(45007,"语音播放时间超过限制"); ERRORCODE.put(45008,"图文消息超过限制"); ERRORCODE.put(45009,"接口调用超过限制"); ERRORCODE.put(45010,"创建菜单个数超过限制"); ERRORCODE.put(45015,"回复时间超过限制"); ERRORCODE.put(45016,"系统分组,不允许修改"); ERRORCODE.put(45017,"分组名字过长"); ERRORCODE.put(45018,"分组数量超过上限"); ERRORCODE.put(46001,"不存在媒体数据"); ERRORCODE.put(46002,"不存在的菜单版本"); ERRORCODE.put(46003,"不存在的菜单数据"); ERRORCODE.put(46004,"不存在的用户"); ERRORCODE.put(47001,"解析JSON/XML内容错误"); ERRORCODE.put(48001,"api功能未授权"); ERRORCODE.put(50001,"用户未授权该api"); } }
消息工具类
WxMessageUtil.java
package com.wx.util; /** * 消息工具类 * */ public class WxMessageUtil { /** * 返回消息类型:文本 */ 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"; }
微信POST的XML数据包转换为消息接受对象
InputMessage.java
package com.penuel.mythopoet.wx.manager; /* * 微信公众平台(JAVA) SDK * * Copyright (c) 2014, Ansitech Network Technology Co.,Ltd All rights reserved. * http://www.ansitech.com/weixin/sdk/ * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ import java.io.Serializable; import com.thoughtworks.xstream.annotations.XStreamAlias; /** * POST的XML数据包转换为消息接受对象 * * <p> * 由于POST的是XML数据包,所以不确定为哪种接受消息,<br/> * 所以直接将所有字段都进行转换,最后根据<tt>MsgType</tt>字段来判断取何种数据 * </p> * */ @XStreamAlias("xml") public class InputMessage implements Serializable { /** * */ private static final long serialVersionUID = 1L; @XStreamAlias("ToUserName") private String ToUserName; @XStreamAlias("FromUserName") private String FromUserName; @XStreamAlias("CreateTime") private Long CreateTime; @XStreamAlias("MsgType") private String MsgType = "text"; @XStreamAlias("MsgId") private Long MsgId; // 文本消息 @XStreamAlias("Content") private String Content; // 图片消息 @XStreamAlias("PicUrl") private String PicUrl; // 位置消息 @XStreamAlias("LocationX") private String LocationX; @XStreamAlias("LocationY") private String LocationY; @XStreamAlias("Scale") private Long Scale; @XStreamAlias("Label") private String Label; // 链接消息 @XStreamAlias("Title") private String Title; @XStreamAlias("Description") private String Description; @XStreamAlias("Url") private String URL; // 语音信息 @XStreamAlias("MediaId") private String MediaId; @XStreamAlias("Format") private String Format; @XStreamAlias("Recognition") private String Recognition; // 事件 @XStreamAlias("Event") private String Event; @XStreamAlias("EventKey") private String EventKey; @XStreamAlias("Ticket") private String Ticket; 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; } public Long getMsgId() { return MsgId; } public void setMsgId(Long msgId) { MsgId = msgId; } public String getContent() { return Content; } public void setContent(String content) { Content = content; } public String getPicUrl() { return PicUrl; } public void setPicUrl(String picUrl) { PicUrl = picUrl; } public String getLocationX() { return LocationX; } public void setLocationX(String locationX) { LocationX = locationX; } public String getLocationY() { return LocationY; } public void setLocationY(String locationY) { LocationY = locationY; } public Long getScale() { return Scale; } public void setScale(Long scale) { Scale = scale; } public String getLabel() { return Label; } public void setLabel(String label) { Label = label; } public String getTitle() { return Title; } public void setTitle(String title) { Title = title; } public String getDescription() { return Description; } public void setDescription(String description) { Description = description; } public String getURL() { return URL; } public void setURL(String uRL) { URL = uRL; } public String getEvent() { return Event; } public void setEvent(String event) { Event = event; } public String getEventKey() { return EventKey; } public void setEventKey(String eventKey) { EventKey = eventKey; } public String getMediaId() { return MediaId; } public void setMediaId(String mediaId) { MediaId = mediaId; } public String getFormat() { return Format; } public void setFormat(String format) { Format = format; } public String getRecognition() { return Recognition; } public void setRecognition(String recognition) { Recognition = recognition; } public String getTicket() { return Ticket; } public void setTicket(String ticket) { Ticket = ticket; } }
xml输出配置
OutputMessage.java
package com.wx.util; import com.thoughtworks.xstream.annotations.XStreamAlias; @XStreamAlias("xml") public class OutputMessage { @XStreamAlias("ToUserName") @XStreamCDATA private String ToUserName; @XStreamAlias("FromUserName") @XStreamCDATA private String FromUserName; @XStreamAlias("CreateTime") private Long CreateTime; @XStreamAlias("MsgType") @XStreamCDATA private String MsgType = "text"; private ImageMessage Image; 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; } public ImageMessage getImage() { return Image; } public void setImage(ImageMessage image) { Image = image; } }
图片消息支持
MediaIdMessage.java
package com.wx.util; import com.thoughtworks.xstream.annotations.XStreamAlias; public class MediaIdMessage { @XStreamAlias("MediaId") @XStreamCDATA private String MediaId; public String getMediaId() { return MediaId; } public void setMediaId(String mediaId) { MediaId = mediaId; } }
ImageMessage.java
package com.wx.util; import com.thoughtworks.xstream.annotations.XStreamAlias; @XStreamAlias("Image") public class ImageMessage extends MediaIdMessage { }
ok 以上配置完成后即可 编写post处理controller
WxManagerController.java
修改后最终
package com.controller; import java.io.IOException; import java.util.ArrayList; import java.util.Calendar; import java.util.Collections; import java.util.Comparator; import java.util.List; import javax.servlet.ServletInputStream; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.springframework.stereotype.Controller; import org.springframework.ui.Model; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.ResponseBody; import com.thoughtworks.xstream.XStream; import com.wx.util.ImageMessage; import com.wx.util.InputMessage; import com.wx.util.OutputMessage; import com.wx.util.SHA1; import com.wx.util.SerializeXmlUtil; import com.wx.util.WxMessageUtil; @Controller @RequestMapping( "/wx" ) public class WxManagerController { private String Token = "testfortoken"; @RequestMapping(value = "", method = { RequestMethod.GET, RequestMethod.POST }) @ResponseBody public void load(Model model, HttpServletRequest request, HttpServletResponse response) { //判断访问方式 boolean isGet = request.getMethod().toLowerCase().equals("get"); if (isGet) { //进行认证 access(request, response); } else { try { //转码UTF-8,防止乱码 request.setCharacterEncoding("utf-8"); response.setCharacterEncoding( "utf-8" ); //处理微信post请求 acceptMessage(request,response); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } } } /** * 验证URL真实性 * * @param request * @param response * @return String */ private String access(HttpServletRequest request, HttpServletResponse response) { // 验证URL真实性 String signature = request.getParameter("signature");// 微信加密签名 String timestamp = request.getParameter("timestamp");// 时间戳 String nonce = request.getParameter("nonce");// 随机数 String echostr = request.getParameter("echostr");// 随机字符串 List<String> params = new ArrayList<String>(); params.add(Token); params.add(timestamp); params.add(nonce); // 1. 将token、timestamp、nonce三个参数进行字典序排序 Collections.sort(params, new Comparator<String>() { @Override public int compare(String o1, String o2) { return o1.compareTo(o2); } }); // 2. 将三个参数字符串拼接成一个字符串进行sha1加密 String temp = SHA1.encode(params.get(0) + params.get(1) + params.get(2)); if (temp.equals(signature)) { try { response.getWriter().write(echostr); return echostr; } catch (IOException e) { e.printStackTrace(); } } return null; } private void acceptMessage(HttpServletRequest request, HttpServletResponse response) throws IOException { // 处理接收消息 ServletInputStream in = request.getInputStream(); // 将POST流转换为XStream对象 XStream xs = SerializeXmlUtil.createXstream(); xs.processAnnotations(InputMessage.class); xs.processAnnotations(OutputMessage.class); // 将指定节点下的xml节点数据映射为对象 xs.alias("xml", InputMessage.class); // 将流转换为字符串 StringBuilder xmlMsg = new StringBuilder(); byte[] b = new byte[4096]; for (int n; (n = in.read(b)) != -1;) { xmlMsg.append(new String(b, 0, n, "UTF-8")); } // 将xml内容转换为InputMessage对象 InputMessage inputMsg = (InputMessage) xs.fromXML(xmlMsg.toString()); String servername = inputMsg.getToUserName();// 服务端 String custermname = inputMsg.getFromUserName();// 客户端 long createTime = inputMsg.getCreateTime();// 接收时间 Long returnTime = Calendar.getInstance().getTimeInMillis() / 1000;// 返回时间 // 取得消息类型 String msgType = inputMsg.getMsgType(); // 根据消息类型获取对应的消息内容 if (msgType.equals(WxMessageUtil.REQ_MESSAGE_TYPE_TEXT)) { // 文本消息 StringBuffer str = new StringBuffer(); str.append("<xml>"); str.append("<ToUserName><![CDATA[" + custermname + "]]></ToUserName>"); str.append("<FromUserName><![CDATA[" + servername + "]]></FromUserName>"); str.append("<CreateTime>" + returnTime + "</CreateTime>"); str.append("<MsgType><![CDATA[" + msgType + "]]></MsgType>"); str.append("<Content><![CDATA[您发送的是:" + inputMsg.getContent() + "?]]></Content>"); str.append("</xml>"); response.getWriter().write(str.toString()); } // 获取并返回多图片消息 else if(msgType.equals(WxMessageUtil.REQ_MESSAGE_TYPE_IMAGE)) { System.out.println("获取多媒体信息"); String mediaId = inputMsg.getMediaId();//多媒体文件id String picUrl = inputMsg.getPicUrl();//图片链接 long msgId = inputMsg.getMsgId();//消息id,64位整型 OutputMessage outputMsg = new OutputMessage(); outputMsg.setFromUserName(servername); outputMsg.setToUserName(custermname); outputMsg.setCreateTime(returnTime); outputMsg.setMsgType(msgType); ImageMessage images = new ImageMessage(); images.setMediaId(mediaId); outputMsg.setImage(images); response.getWriter().write(xs.toXML(outputMsg)); } //事件 else if (msgType.equals(WxMessageUtil.REQ_MESSAGE_TYPE_EVENT)) { // 事件类型 String eventType = inputMsg.getEvent(); if (eventType.equals(WxMessageUtil.EVENT_TYPE_SUBSCRIBE)) { // 关注 }else if (eventType.equals(WxMessageUtil.EVENT_TYPE_UNSUBSCRIBE)) { //取消关注 }else if(eventType.equals(WxMessageUtil.EVENT_TYPE_CLICK)){ //点击 } } } }
启动测试,重新提交一下服务器认证。
效果:
完成,进行下一个功能 素材库管理
三、素材库
操作素材库需要 access_token 接口调用凭据,
下面编写 access_token 方法。
创建 AccessToken model
AccessToken.java
package com.wx.model; public class AccessToken { // 获取到的凭证 private String token; // 凭证有效时间,单位:秒 private int expiresIn; public String getToken() { return token; } public void setToken(String token) { this.token = token; } public int getExpiresIn() { return expiresIn; } public void setExpiresIn(int expiresIn) { this.expiresIn = expiresIn; } }
想微信发起post请求必须使用https方式,所以需要编写配置https请求方式,证书配置如下,
MyX509TrustManager.java
package com.wx.util; import java.security.cert.X509Certificate; import javax.net.ssl.X509TrustManager; public class MyX509TrustManager implements X509TrustManager { public void checkClientTrusted(X509Certificate[] chain, String authType) { } public void checkServerTrusted(X509Certificate[] chain, String authType) { } public X509Certificate[] getAcceptedIssuers() { return null; } }
配置后编写处理https发起以及处理,和获取accesstoken方法
WxManagerUtil.java
package com.wx.util; import java.io.BufferedReader; import java.io.InputStream; import java.io.InputStreamReader; import java.io.OutputStream; import java.net.ConnectException; import java.net.URL; import javax.net.ssl.HttpsURLConnection; import javax.net.ssl.SSLContext; import javax.net.ssl.SSLSocketFactory; import javax.net.ssl.TrustManager; import com.alibaba.fastjson.JSONException; import com.alibaba.fastjson.JSONObject;
import com.wx.model.AccessToken; public class WxManagerUtil { // 获取access_token的接口地址(GET) 限200(次/天) public final static String access_token_url = "https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=APPID&secret=APPSECRET"; /** * 发起https请求并获取结果 * * @param requestUrl 请求地址 * @param requestMethod 请求方式(GET、POST) * @param outputStr 提交的数据 * @return JSONObject(通过JSONObject.get(key)的方式获取json对象的属性值) */ public static JSONObject httpRequest(String requestUrl, String requestMethod, String outputStr) { JSONObject jsonObject = null; StringBuffer buffer = new StringBuffer(); try { // 创建SSLContext对象,并使用我们指定的信任管理器初始化 TrustManager[] tm = { new MyX509TrustManager() }; SSLContext sslContext = SSLContext.getInstance("SSL", "SunJSSE"); sslContext.init(null, tm, new java.security.SecureRandom()); // 从上述SSLContext对象中得到SSLSocketFactory对象 SSLSocketFactory ssf = sslContext.getSocketFactory(); URL url = new URL(requestUrl); HttpsURLConnection httpUrlConn = (HttpsURLConnection) url.openConnection(); httpUrlConn.setSSLSocketFactory(ssf); httpUrlConn.setDoOutput(true); httpUrlConn.setDoInput(true); httpUrlConn.setUseCaches(false); // 设置请求方式(GET/POST) httpUrlConn.setRequestMethod(requestMethod); if ("GET".equalsIgnoreCase(requestMethod)) httpUrlConn.connect(); // 当有数据需要提交时 if (null != outputStr) { OutputStream outputStream = httpUrlConn.getOutputStream(); // 注意编码格式,防止中文乱码 outputStream.write(outputStr.getBytes("UTF-8")); outputStream.close(); } // 将返回的输入流转换成字符串 InputStream inputStream = httpUrlConn.getInputStream(); InputStreamReader inputStreamReader = new InputStreamReader(inputStream, "utf-8"); BufferedReader bufferedReader = new BufferedReader(inputStreamReader); String str = null; while ((str = bufferedReader.readLine()) != null) { buffer.append(str); } bufferedReader.close(); inputStreamReader.close(); // 释放资源 inputStream.close(); inputStream = null; httpUrlConn.disconnect(); jsonObject = JSONObject.parseObject(buffer.toString()); } catch (ConnectException ce) { System.out.println("微信服务器连接超时!"); } catch (Exception e) { System.out.println("HTTPS请求错误,错误信息: " + e.getMessage()); } return jsonObject; } /** * 生成AccessToken * @param appid * @param appsecret * @return */ public static AccessToken getAccessToken(String appid, String appsecret) { AccessToken accessToken = null; String requestUrl = access_token_url.replace("APPID", appid).replace( "APPSECRET", appsecret); JSONObject jsonObject = httpRequest(requestUrl, "GET", null); // 如果请求成功 if (null != jsonObject) { try { accessToken = new AccessToken(); accessToken.setToken(jsonObject.getString("access_token")); accessToken.setExpiresIn(jsonObject.getIntValue("expires_in")); } catch (JSONException e) { accessToken = null; // 获取token失败 System.out.println("获取TOKEN失败("+jsonObject.getString("errcode")+")"); } } return accessToken; } }
创建素材库model
WxArticles.java
package com.wx.model; public class WxArticles { private String title; //标题 private String thumb_media_id;//图文消息的封面图片素材id private String author;//作者 private String digest;//图文消息的摘要,仅有单图文消息才有摘要,多图文此处为空 private String show_cover_pic;//是否显示封面,0为false,即不显示,1为true,即显示 private String content;// 图文消息的具体内容,支持HTML标签,必须少于2万字符,小于1M,且此处会去除JS private String content_source_url;//图文消息的原文地址,即点击“阅读原文”后的URL public String getTitle() { return title; } public void setTitle(String title) { this.title = title; } public String getThumb_media_id() { return thumb_media_id; } public void setThumb_media_id(String thumb_media_id) { this.thumb_media_id = thumb_media_id; } public String getAuthor() { return author; } public void setAuthor(String author) { this.author = author; } public String getDigest() { return digest; } public void setDigest(String digest) { this.digest = digest; } public String getShow_cover_pic() { return show_cover_pic; } public void setShow_cover_pic(String show_cover_pic) { this.show_cover_pic = show_cover_pic; } public String getContent() { return content; } public void setContent(String content) { this.content = content; } public String getContent_source_url() { return content_source_url; } public void setContent_source_url(String content_source_url) { this.content_source_url = content_source_url; } }
完成以上基本OK。
先写一个测试方法,提交图文素材 需要thumb_media_id 这个参数,即:图文消息的封面图片素材id(必须是永久mediaID)
我们现在公众号里上传一个图片素材,然后去获取他的thumb_media_id后再 提交新的图文素材,这里使用用户发送消息来触发测试
最终代码:
WxManagerController.java
package com.controller; import java.io.IOException; import java.util.ArrayList; import java.util.Calendar; import java.util.Collections; import java.util.Comparator; import java.util.HashMap; import java.util.List; import java.util.Map; import javax.servlet.ServletInputStream; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.eclipse.jdt.internal.compiler.batch.Main; import org.springframework.stereotype.Controller; import org.springframework.ui.Model; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.ResponseBody; import com.alibaba.fastjson.JSONObject; import com.thoughtworks.xstream.XStream; import com.wx.model.AccessToken; import com.wx.model.WxArticles; import com.wx.util.ImageMessage; import com.wx.util.InputMessage; import com.wx.util.OutputMessage; import com.wx.util.SHA1; import com.wx.util.SerializeXmlUtil; import com.wx.util.WxManagerUtil; import com.wx.util.WxMessageUtil; @Controller @RequestMapping( "/wx" ) public class WxManagerController { private String Token = "CL0WQY79GJ12XV643BEZKMF5PHTAN"; @RequestMapping(value = "", method = { RequestMethod.GET, RequestMethod.POST }) @ResponseBody public void load(Model model, HttpServletRequest request, HttpServletResponse response) { //判断访问方式 boolean isGet = request.getMethod().toLowerCase().equals("get"); if (isGet) { //进行认证 access(request, response); } else { try { //转码UTF-8,防止乱码 request.setCharacterEncoding("utf-8"); response.setCharacterEncoding( "utf-8" ); //处理微信post请求 acceptMessage(request,response); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } } } /** * 验证URL真实性 * * @param request * @param response * @return String */ private String access(HttpServletRequest request, HttpServletResponse response) { // 验证URL真实性 String signature = request.getParameter("signature");// 微信加密签名 String timestamp = request.getParameter("timestamp");// 时间戳 String nonce = request.getParameter("nonce");// 随机数 String echostr = request.getParameter("echostr");// 随机字符串 List<String> params = new ArrayList<String>(); params.add(Token); params.add(timestamp); params.add(nonce); // 1. 将token、timestamp、nonce三个参数进行字典序排序 Collections.sort(params, new Comparator<String>() { @Override public int compare(String o1, String o2) { return o1.compareTo(o2); } }); // 2. 将三个参数字符串拼接成一个字符串进行sha1加密 String temp = SHA1.encode(params.get(0) + params.get(1) + params.get(2)); if (temp.equals(signature)) { try { response.getWriter().write(echostr); return echostr; } catch (IOException e) { e.printStackTrace(); } } return null; } private void acceptMessage(HttpServletRequest request, HttpServletResponse response) throws IOException { // 处理接收消息 ServletInputStream in = request.getInputStream(); // 将POST流转换为XStream对象 XStream xs = SerializeXmlUtil.createXstream(); xs.processAnnotations(InputMessage.class); xs.processAnnotations(OutputMessage.class); // 将指定节点下的xml节点数据映射为对象 xs.alias("xml", InputMessage.class); // 将流转换为字符串 StringBuilder xmlMsg = new StringBuilder(); byte[] b = new byte[4096]; for (int n; (n = in.read(b)) != -1;) { xmlMsg.append(new String(b, 0, n, "UTF-8")); } // 将xml内容转换为InputMessage对象 InputMessage inputMsg = (InputMessage) xs.fromXML(xmlMsg.toString()); String servername = inputMsg.getToUserName();// 服务端 String custermname = inputMsg.getFromUserName();// 客户端 long createTime = inputMsg.getCreateTime();// 接收时间 Long returnTime = Calendar.getInstance().getTimeInMillis() / 1000;// 返回时间 // 取得消息类型 String msgType = inputMsg.getMsgType(); // 根据消息类型获取对应的消息内容 if (msgType.equals(WxMessageUtil.REQ_MESSAGE_TYPE_TEXT)) { // 文本消息 // StringBuffer str = new StringBuffer(); // str.append("<xml>"); // str.append("<ToUserName><![CDATA[" + custermname + "]]></ToUserName>"); // str.append("<FromUserName><![CDATA[" + servername + "]]></FromUserName>"); // str.append("<CreateTime>" + returnTime + "</CreateTime>"); // str.append("<MsgType><![CDATA[" + msgType + "]]></MsgType>"); // str.append("<Content><![CDATA[您发送的是:" + inputMsg.getContent() + "?]]></Content>"); // str.append("</xml>"); // response.getWriter().write(str.toString()); AccessToken at = WxManagerUtil.getAccessToken("你的AppID", "你的AppSecret"); String mediaId = getImgsFor(at.getToken()); addImgTest(at.getToken(),mediaId); } // 获取并返回多图片消息 else if(msgType.equals(WxMessageUtil.REQ_MESSAGE_TYPE_IMAGE)) { System.out.println("获取多媒体信息"); String mediaId = inputMsg.getMediaId();//多媒体文件id String picUrl = inputMsg.getPicUrl();//图片链接 long msgId = inputMsg.getMsgId();//消息id,64位整型 OutputMessage outputMsg = new OutputMessage(); outputMsg.setFromUserName(servername); outputMsg.setToUserName(custermname); outputMsg.setCreateTime(returnTime); outputMsg.setMsgType(msgType); ImageMessage images = new ImageMessage(); images.setMediaId(mediaId); outputMsg.setImage(images); response.getWriter().write(xs.toXML(outputMsg)); } //事件 else if (msgType.equals(WxMessageUtil.REQ_MESSAGE_TYPE_EVENT)) { // 事件类型 String eventType = inputMsg.getEvent(); if (eventType.equals(WxMessageUtil.EVENT_TYPE_SUBSCRIBE)) { // 关注 }else if (eventType.equals(WxMessageUtil.EVENT_TYPE_UNSUBSCRIBE)) { //取消关注 }else if(eventType.equals(WxMessageUtil.EVENT_TYPE_CLICK)){ //点击 } } } public String getImgsFor(String token){ //取第一个图片素材 String geturls = "https://api.weixin.qq.com/cgi-bin/material/batchget_material?access_token="+token; String jsonval="{"type":"image","offset":"0","count":"1"}"; JSONObject jsonObject = WxManagerUtil.httpRequest(geturls, "POST", jsonval); String result=null; if (null != jsonObject) { JSONObject josns =(JSONObject) jsonObject.getJSONArray("item").get(0); result = josns.getString("media_id"); } return result; } public void addImgTest(String token,String mediaId){ List<WxArticles> list = new ArrayList<WxArticles>(); WxArticles wxArticles = new WxArticles(); wxArticles.setTitle("a title"); wxArticles.setAuthor("a author"); wxArticles.setContent("a content"); wxArticles.setContent_source_url("a content_source_url"); wxArticles.setDigest("a digest"); wxArticles.setShow_cover_pic("a show_cover_pic"); wxArticles.setThumb_media_id(mediaId); list.add(wxArticles); Map<String,List<WxArticles>> maplist = new HashMap<String,List<WxArticles>>(); maplist.put("articles", list); String urls= "https://api.weixin.qq.com/cgi-bin/material/add_news?access_token="+token; String jsons = JSONObject.toJSONString(maplist); JSONObject jsonObject = WxManagerUtil.httpRequest(urls, "POST", jsons); String result=null; if (null != jsonObject) { result = jsonObject.getString("media_id"); System.out.println("返回("+result+")"); } } }
上传永久素材成功!
别忘了 填写自己的 appID 和appSecret;
AccessToken at = WxManagerUtil.getAccessToken("你的AppID", "你的AppSecret");
补充: 自定义菜单 功能;
WxManagerController.java 下添加此方法
private void accessMenu(String token){ String menu = "{"button":[{"type":"click","name":"项目管理","key":"20_PROMANAGE"},{"type":"click","name":"机构运作","key":"30_ORGANIZATION"},{"name":"日常工作","sub_button":[{"type":"click","name":"待办工单","key":"01_WAITING"},{"type":"click","name":"已办工单","key":"02_FINISH"},{"type":"click","name":"我的工单","key":"03_MYJOB"},{"type":"click","name":"公告消息箱","key":"04_MESSAGEBOX"},{"type":"click","name":"签到","key":"05_SIGN"}]}]}"; String requestUrl = "https://api.weixin.qq.com/cgi-bin/menu/create?access_token="+token; int result = 0 ; JSONObject jsonObject = WxManagerUtil.httpRequest(requestUrl, "POST", menu); if (null != jsonObject) { if (0 != jsonObject.getIntValue("errcode")) { result = jsonObject.getIntValue("errcode"); System.out.println("创建菜单失败("+result+")"); } System.out.println("创建成功("+result+")"); } }
完成!