最近公司在开发微信项目,所以自己也试着申请了个人的订阅服务号,实现了通过微信接收信息转发至java后台解析并回复的消息的简单功能,在还没忘记的时候记录一下,以便日后查阅,并且贡献出代码希望能给大家一个参考。
好首先你要看下面的示例,要事先申请微信公众平台的订阅服务号(个人只能申请这个),地址https://mp.weixin.qq.com ,申请的范例我这里就不讲了,一般根据提示可以自行完成,如果这都完成不了,那只能去度娘翻翻了。
要想让用户发送给公众帐号的消息转发给java后台服务器,首先要 在开发者中心 进行 服务器配置 ,
下图为认证启动后小效果:
你要先进入到 修改配置里面,如下图:
你要填写这几个文本框内的内容,
1.URL 不用解释了,就是微信将用户发来的消息转发到你服务器的请求的地址,我让微信把请求发送到本地服务这样方便调试。
推荐一款反向代理的工具 pagekite.net ,感兴趣的朋友可以去搜索一下。使用相当方便,就是需要python2.7.x环境支持,然后运行下载的一个脚本,输入你的邮箱,然后在输入你要设置的域名前缀,就搞定,下次运行就不用在输入,它影射的是本地80端口,所以你启动服务的时候记得改成80端口就对了,还有这个貌似对于一个邮箱只有31天和5个连接的限制,PS:邮箱这东西都是免费的,你懂的。
2.Token:这个长度符合就行 自己随意
3.EncodingAESKey:点击随机生成 就OK
下面介绍我的代码:
我的开发环境是eclipse+springMvc
"/chat" 是我最终项目的请求Controller URL路径
下面是homecontroller
@Controller @RequestMapping("/*") public class HomeController { private String Token = "123456789abcdef"; @RequestMapping(value = "chat", method = { RequestMethod.GET, RequestMethod.POST }) @ResponseBody public void liaotian(Model model, HttpServletRequest request, HttpServletResponse response) { System.out.println("进入chat"); boolean isGet = request.getMethod().toLowerCase().equals("get"); if (isGet) { String signature = request.getParameter("signature"); String timestamp = request.getParameter("timestamp"); String nonce = request.getParameter("nonce"); String echostr = request.getParameter("echostr"); System.out.println(signature); System.out.println(timestamp); System.out.println(nonce); System.out.println(echostr); access(request, response); } else { // 进入POST聊天处理 System.out.println("enter post"); try { // 接收消息并返回消息 acceptMessage(request, response); } catch (IOException e) { e.printStackTrace(); } } } /** * 验证URL真实性 * * @author morning * @date 2015年2月17日 上午10:53:07 * @param request * @param response * @return String */ private String access(HttpServletRequest request, HttpServletResponse response) { // 验证URL真实性 System.out.println("进入验证access"); 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); System.out.println("成功返回 echostr:" + echostr); return echostr; } catch (IOException e) { e.printStackTrace(); } } System.out.println("失败 认证"); 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(MsgType.Text.toString())) { // 文本消息 System.out.println("开发者微信号:" + inputMsg.getToUserName()); System.out.println("发送方帐号:" + inputMsg.getFromUserName()); System.out.println("消息创建时间:" + inputMsg.getCreateTime() + new Date(createTime * 1000l)); System.out.println("消息内容:" + inputMsg.getContent()); System.out.println("消息Id:" + inputMsg.getMsgId()); 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>"); System.out.println(str.toString()); response.getWriter().write(str.toString()); } // 获取并返回多图片消息 if (msgType.equals(MsgType.Image.toString())) { System.out.println("获取多媒体信息"); System.out.println("多媒体文件id:" + inputMsg.getMediaId()); System.out.println("图片链接:" + inputMsg.getPicUrl()); System.out.println("消息id,64位整型:" + inputMsg.getMsgId()); OutputMessage outputMsg = new OutputMessage(); outputMsg.setFromUserName(servername); outputMsg.setToUserName(custermname); outputMsg.setCreateTime(returnTime); outputMsg.setMsgType(msgType); ImageMessage images = new ImageMessage(); images.setMediaId(inputMsg.getMediaId()); outputMsg.setImage(images); System.out.println("xml转换:/n" + xs.toXML(outputMsg)); response.getWriter().write(xs.toXML(outputMsg)); } } }
加密SHA1,此代码是来自网上
/* * 微信公众平台(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. */ package com.mor.util; 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); } } }
输入信息实体类 InputMessage
<pre name="code" class="java">/* * 微信公众平台(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. */ package com.mor.maven.demo.mavenweb.model; 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; } }
为了加入 CDATA 验证创建的@interface类
package com.mor.maven.demo.mavenweb.model; 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工具类
package com.mor.util; import java.io.Writer; import java.lang.reflect.Field; import com.mor.maven.demo.mavenweb.model.XStreamCDATA; 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 转换工具类 * * @author morning * @date 2015年2月16日 下午2:42:50 */ 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; } }
输出实体类 OutputMessage
package com.mor.maven.demo.mavenweb.model; import com.thoughtworks.xstream.annotations.XStreamAlias; /** * * @author morning * @date 2015年2月16日 下午2:29:32 */ @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; } }
图片信息实体类
package com.mor.maven.demo.mavenweb.model; import com.thoughtworks.xstream.annotations.XStreamAlias; @XStreamAlias("Image") public class ImageMessage extends MediaIdMessage { }
多媒体id 实体类
package com.mor.maven.demo.mavenweb.model; 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; } }
基本就这些类,也不知道拷贝全没有。
不过在输出xml的时候由于要添加CDATA标签所以没有实现完美,目前自己在SerializeXmlUtil 内添加了一下判断
如果是子标签下的值目前只能用这种方法加CDATA,不知道各位同学有没有好的方法。
目前只是实现了服务器认证,接收文本信息并回复原文本信息加上些附加信息,接收图片信息并返回原图片信息。
后期会有扩展,先记录到此。