基于之前的文章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请求错误,错误信息:\n" + 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+")");
}
}
完成!