因为认证问题,所以就先用测试账号,点击开发者工具,进入公众平台测试帐号
域名验证:
1.服务器验证,修改接口配置信息:
注意,接口配置信息中,url为你服务器下验证token合法所在的地址的绝对路径,此处为hello/start在服务器下的绝对路径,token为一个约定的钥匙,服务器验证时,微信会用token加密一段字符串,它会把加密字符串用到的其它参数发送到服务器,用服务器定义的token进行加密并拿到结果,随后它会对比两次结果,如果相同则通过验证,所以微信后台的token和服务器上token必须一致,用相同加密方法加密后才能得到相同结果。
验证方法如下:方法为GET
@RequestMapping(value="Start",method=RequestMethod.GET)
public void start(HttpServletRequest request, HttpServletResponse response) throws IOException {
System.out.println("开始签名校验");
//得到服务器传过来的4个参数
String signature = request.getParameter("signature");
String timestamp = request.getParameter("timestamp");
String nonce = request.getParameter("nonce");
String echostr = request.getParameter("echostr");
ArrayList<String> array = new ArrayList<String>();
array.add(signature);
array.add(timestamp);
array.add(nonce);
// 1.将token、timestamp、nonce三个参数进行字典序排序
String sortString = sort(token, timestamp, nonce);
// 2. 将三个参数字符串拼接成一个字符串进行sha1加密
String mytoken = Decript.SHA1(sortString);
// 3.将sha1加密后的字符串可与signature对比,标识该请求来源于微信
if (mytoken != null && mytoken != "" && mytoken.equals(signature)) {
System.out.println("签名校验通过。");
response.getWriter().println(echostr); //如果检验成功输出echostr,微信服务器接收到此输出,才会确认检验完成。
} else {
System.out.println("签名校验失败。");
}
}
sort方法:
/**
* 排序方法
* @param token
* @param timestamp
* @param nonce
* @return
*/
public static String sort(String token, String timestamp, String nonce) {
String[] strArray = { token, timestamp, nonce };
Arrays.sort(strArray);
StringBuilder sbuilder = new StringBuilder();
for (String str : strArray) {
sbuilder.append(str);
}
return sbuilder.toString();
}
SHA1加密
/**
*
* @param decript待加密字段
* @return
*/
public static String SHA1(String decript) {
try {
MessageDigest digest = MessageDigest
.getInstance("SHA-1");
digest.update(decript.getBytes());
byte messageDigest[] = digest.digest();
// Create Hex String
StringBuffer hexString = new StringBuffer();
// 字节数组转换为 十六进制 数
for (int i = 0; i < messageDigest.length; i++) {
String shaHex = Integer.toHexString(messageDigest[i] & 0xFF);
if (shaHex.length() < 2) {
hexString.append(0);
}
hexString.append(shaHex);
}
return hexString.toString();
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
}
return "";
}
与微信服务器上利用token生成的字符串相同后,则通过验证。
自定义菜单:
利用在线接口调试工具可以直接生成:
首先用公众号账号密码拿到一个基础token,拿到基础接口的一个调用凭证,用于创建菜单,然后自定义菜单
body示例如下:
{
"button":[
{
"type":"view",
"name":"一键申请",
"url":"http://Scanhandle?Page=1"
},
{
"type":"view",
"name":"进度查询",
"url":"http://Scanhandle?Page=2"
},
{
"type":"view",
"name":"个人中心",
"url":"http://Scanhandle?Page=3"
}]
}
还可以生成子菜单,自行查阅官方文档。
消息推送:
在微信用户和公众号产生交互的过程中,用户的某些操作会使得微信服务器通过事件推送的形式通知到开发者在开发者中心处设置的服务器地址,此处为验证签名的地址,hello/start在服务器下的绝对路径,方法为POST:,因为消息要符合微信推送XML数据包结构比如:
<xml>
<ToUserName>ToUserName>
<FromUserName>FromUserName>
<CreateTime>1348831860CreateTime>
<MsgType>MsgType>
<Content>Content>
<MsgId>1234567890123456MsgId>
xml>
所以消息发送要经过一定的格式处理,首先按着格式规范解析订阅用户发送的信息,然后进行相应的回复。
@RequestMapping(value="Start",method=RequestMethod.POST)
public void messageRecieve(HttpServletRequest request, HttpServletResponse response) throws IOException {
//消息的接受、处理、响应
request.setCharacterEncoding("utf-8");
response.setCharacterEncoding("utf-8");
String respMessage = null;
//调用核心业务类型接受消息、处理消息,消息需要进行相应的格式封装
respMessage = EastnetService.processRequest(request, response);
//响应消息
PrintWriter out = response.getWriter();
out.print(respMessage);
out.close();
}
import com.alibaba.fastjson.support.odps.udf.CodecCheck.A;
import com.sf.signing.weixin.util.ConstantUtil;
import com.sf.signing.wx_access.message.Article;
import com.sf.signing.wx_access.message.TextMessage;
import com.sf.signing.wx_access.util.MessageUtil;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.Date;
import java.util.Map;
public class EastnetService {
public static String processRequest(HttpServletRequest request, HttpServletResponse response) {
String respMessage = null;
//默认返回的文本消息类容
String respContent = "";
String fromUserName="";
String toUserName ="";
String msgType ="";
String lantitude="";
String longtitude="";
try {
//xml请求解析
Map<String,String> requestMap = MessageUtil.pareXml(request);
//发送方账号(open_id)
fromUserName = requestMap.get("FromUserName");
//公众账号
toUserName = requestMap.get("ToUserName");
//消息类型
msgType = requestMap.get("MsgType");
// String eventType = requestMap.get("Event");
String fromContent=requestMap.get("Content");
String userName="";
//订阅
String eventTypeSub = requestMap.get("Event");
Article article=new Article();
if((MessageUtil.EVENT_TYPE_SUBSCRIBE).equals(eventTypeSub)){
article.setTitle("电子签约平台欢迎您");
article.setDescription("点击查看详细信息");
// String classpath=request.getSession().getServletContext().getRealPath("/");
// article.setPicUrl(ConstantUtil.BASE_PATH+"/file/get?path=welcom.jpg");
article.setPicUrl(ConstantUtil.BASE_PATH+"/assets/pic/welcom.jpg");
article.setUrl(ConstantUtil.BASE_PATH+"/welcome.html");
respMessage="<xml><ToUserName>ToUserName>"+
"<FromUserName>FromUserName><CreateTime>"+
System.currentTimeMillis()+"CreateTime><MsgType>MsgType>\r\n" +
"<ArticleCount>1ArticleCount>\r\n" +
"<Articles>\r\n" +
"<item>\r\n" +
"<Title>Title> \r\n" +
"<Description>Description>\r\n" +
"<PicUrl>PicUrl>\r\n" +
"<Url>Url>\r\n" +
"item>\r\n" +
"Articles>\r\n" +
"xml>";
}
//上报位置
if (MessageUtil.REQ_MESSSAGE_TYPE_LOCATION.equals(eventTypeSub)) {
// lantitude=requestMap.get("Latitude");
// longtitude=requestMap.get("Longitude");
// respMessage=("<xml><ToUserName>ToUserName>"+"<FromUserName>FromUserName><CreateTime>"+System.currentTimeMillis()+"CreateTime><MsgType>MsgType><Content>Content>xml>");
//
//
}
//自动回复
if("帮助".equals(fromContent)){
respContent=fromUserName+" "+toUserName+"绑定账号:请回复 用户名绑定+用户名,例:用户名绑定fangw\n行程查看:请回复 行程查看\n行程添加:请回复 行程添加\n行程修改:请回复 行程修改\n";
article.setTitle("电子签约平台欢迎您");
article.setDescription("点击查看详细信息");
//String classpath = EastnetService.class.getClass().getResource("/").getPath().replaceFirst("/", "");
//String webappRoot = classpath.replaceAll("/WEB-INF/classes/", "");
article.setPicUrl(ConstantUtil.BASE_PATH+"/assets/pic/welcom.jpg");
article.setUrl(ConstantUtil.BASE_PATH+"/welcome.html");
respMessage="<xml><ToUserName>ToUserName>"+
"<FromUserName>FromUserName><CreateTime>"+
System.currentTimeMillis()+"CreateTime><MsgType>MsgType>\r\n" +
"<ArticleCount>1ArticleCount>\r\n" +
"<Articles>\r\n" +
"<item>\r\n" +
"<Title>Title> \r\n" +
"<Description>Description>\r\n" +
"<PicUrl>PicUrl>\r\n" +
"<Url>Url>\r\n" +
"item>\r\n" +
"Articles>\r\n" +
"xml>";
}
//回复文本消息
// TextMessage textMessage = new TextMessage();
// textMessage.setToUserName(toUserName);
// textMessage.setFromUserName(fromUserName);
// textMessage.setCreateTime(new Date().getTime());
// textMessage.setMsgType(MessageUtil.RESP_MESSSAGE_TYPE_TEXT);
// textMessage.setFuncFlag(0);
// StringBuffer sb=new StringBuffer();
} catch (Exception e) {
respMessage=("<xml><ToUserName>ToUserName>"+"<FromUserName>FromUserName><CreateTime>"+System.currentTimeMillis()+"CreateTime><MsgType>MsgType><Content>Content>xml>");
}
return respMessage;
}
}
方法相关类如下::
import com.sf.signing.wx_access.message.Article;
import com.sf.signing.wx_access.message.MusicMessage;
import com.sf.signing.wx_access.message.NewsMessage;
import com.sf.signing.wx_access.message.TextMessage;
import com.thoughtworks.xstream.XStream;
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;
import org.dom4j.Document;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;
import javax.servlet.http.HttpServletRequest;
import java.io.InputStream;
import java.io.Writer;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class MessageUtil {
/**
* 返回信息类型:文本
*/
public static final String RESP_MESSSAGE_TYPE_TEXT = "text";
/**
* 返回信息类型:音乐
*/
public static final String RESP_MESSSAGE_TYPE_MUSIC = "music";
/**
* 返回信息类型:图文
*/
public static final String RESP_MESSSAGE_TYPE_NEWS = "news";
/**
* 请求信息类型:文本
*/
public static final String REQ_MESSSAGE_TYPE_TEXT = "text";
/**
* 请求信息类型:图片
*/
public static final String REQ_MESSSAGE_TYPE_IMAGE = "image";
/**
* 请求信息类型:链接
*/
public static final String REQ_MESSSAGE_TYPE_LINK = "link";
/**
* 请求信息类型:地理位置
*/
public static final String REQ_MESSSAGE_TYPE_LOCATION = "LOCATION";
/**
* 请求信息类型:音频
*/
public static final String REQ_MESSSAGE_TYPE_VOICE = "voice";
/**
* 请求信息类型:推送
*/
public static final String REQ_MESSSAGE_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";
/**
* 事件类型:view(自定义菜单点击事件,返回url)
*/
public static final String EVENT_TYPE_VIEW= "VIEW";
/**
* 解析微信发来的请求 XML
*/
@SuppressWarnings("unchecked")
public static Map pareXml(HttpServletRequest request) throws Exception {
//将解析的结果存储在HashMap中
Map reqMap = new HashMap();
//从request中取得输入流
InputStream inputStream = request.getInputStream();
//读取输入流
SAXReader reader = new SAXReader();
Document document = reader.read(inputStream);
//得到xml根元素
Element root = document.getRootElement();
//得到根元素的所有子节点
List elementList = root.elements();
//遍历所有的子节点取得信息类容
for(Element elem:elementList){
reqMap.put(elem.getName(),elem.getText());
}
//释放资源
inputStream.close();
inputStream = null;
return reqMap;
}
/**
* 响应消息转换成xml返回
* 文本消息对象转换成xml
*/
public static String textMessageToXml(TextMessage textMessage) {
xstream.alias("xml", textMessage.getClass());
return xstream.toXML(textMessage);
}
/**
* 音乐消息的对象的转换成xml
*
*/
public static String musicMessageToXml(MusicMessage musicMessage) {
xstream.alias("xml", musicMessage.getClass());
return xstream.toXML(musicMessage);
}
/**
* 图文消息的对象转换成xml
*
*/
public static String newsMessageToXml(NewsMessage newsMessage) {
xstream.alias("xml", newsMessage.getClass());
xstream.alias("item", new Article().getClass());
return xstream.toXML(newsMessage);
}
/**
* 拓展xstream,使得支持CDATA块
*
*/
private static XStream xstream = new XStream(new XppDriver(){
public HierarchicalStreamWriter createWriter(Writer out){
return new PrettyPrintWriter(out){
//对所有的xml节点的转换都增加CDATA标记
boolean cdata = true;
@SuppressWarnings("unchecked")
public void startNode(String name,Class clazz){
super.startNode(name,clazz);
}
protected void writeText(QuickWriter writer,String text){
if(cdata){
writer.write(");
writer.write(text);
writer.write("]]>");
}else{
writer.write(text);
}
}
};
}
});
}
消息实体类:
public class Article {
//图文消息名称
private String Title;
//图文消息描述
private String Description;
//图文链接,支持JPG,PNG,格式,较好的效果为大图640*320,小图
//80*80,限制图片链接的域名需要与开发者填写的基本资料中的url一致
private String PicUrl;
//点击图文消息跳转的链接
private String Url;
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 getPicUrl() {
return PicUrl;
}
public void setPicUrl(String picUrl) {
PicUrl = picUrl;
}
public String getUrl() {
return Url;
}
public void setUrl(String url) {
Url = url;
}
}
至此基本消息交互和自定义菜单完成,随后为网页授权,订阅用户点击菜单后如果要携带信息进入第三方网站,开发者必须经过用户同意拿到授权token才能进行,用户同意后,开发者就可以拿到用户独一无二的openid,利用网页授权的token和用户id,则可以获取到用户的详细信息。
首先是设置授权回调页面域名,允许网页授权获取用户基本信息。
注意,此处为服务器域名,如果你代码放到baidu服务器下则此处为baidu.com,不要添加其他内容,否则会出错。
之前自定义菜单点击后就会跳转到scandle这个controller,controller代码如下:
import java.io.IOException;
import java.util.ArrayList;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import com.sf.signing.weixin.service.SignClientService;
import com.sf.signing.weixin.util.ConstantUtil;
import com.sf.signing.wx_access.model.Token;
import com.sf.signing.wx_access.util.GetTokenUtil;
import com.sf.signing.wx_access.util.Oauth;
@Controller
public class LoginController {
public static Boolean check=false;
@Autowired
private SignClientService clinetService;
@RequestMapping(value="Scanhandle",method=RequestMethod.GET)
public void login(HttpServletRequest request,HttpServletResponse response) throws ServletException, IOException{
/*
* @param code 这是微信提供的东西code
* @param jsonstring 用户信息json字符串
*/
String code = request.getParameter("code");
String page=request.getParameter("Page");
String open_id = null;
//如果没有code,就向微信提供的网址请求
//state里面还可以带上你需要拿到用户信息之后进一步处理的参数
if (code == null) {
/*
* @param appid 微信提供给你的appid
* @param redirect_uri 你自己的服务器地址/工程名/本页的Servelt名字
* @param state 你还需要带上的参数
* @param page 跳转页面的参数,根据参数到不同页面
*/
response.sendRedirect(ConstantUtil.URL_BEG+page+ConstantUtil.URL_PARAM);
} else {
check=true;
//在后续需要进一步处理的参数,取出
String param = request.getParameter("state");
//向oauth.java拿到了用户信息之后,存到Session
ArrayList list= new Oauth().getUserinfo(code);
open_id = list.get(0);
// String token=GetTokenUtil.getToken().getAccessToken();
// String token= TokenThread.accessToken.getAccessToken();
String token="";
Token token1=GetTokenUtil.getToken();
if(token1!=null) {
token=token1.getAccessToken();
}
String netToken= list.get(1);
request.getSession().setAttribute("open_id", open_id);
request.getSession().setAttribute("access_token",netToken);
String clientId = clinetService.getClient(open_id);
request.getSession().setAttribute("client_id", clientId);
//这里可以再写,你需要进一步处理的代码
// 跳转到index.jsp
if(page.equals("1")){
//推送
// TemplateUtil.sentMsg(open_id,token);
request.getRequestDispatcher("index.html").forward(request, response);
}
else if(page.equals("2")){
request.getRequestDispatcher("progress-query.html").forward(request, response);
}else if(page.equals("3")){
request.getRequestDispatcher("user-center.html").forward(request, response);
}
}
}
}
auth类:
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.URL;
import java.util.ArrayList;
public class Oauth {
public static String id=null;
/**
*
* @param code如果用户授权网页访问,则会有一个授权码code
* @return
* @throws IOException
*/
public ArrayList getUserinfo(String code) throws IOException {
ArrayList strings=new ArrayList();
StringBuilder json = new StringBuilder();
String url = null;
BufferedReader in = null;
String inputLine = null;
String jsonstring = null;
JSONObject jobject = null;
// 这里的appid与secret换成你自己的appid与secret,code为用户同意访问后的一个标志
url = "https://api.weixin.qq.com/sns/oauth2/access_token?appid=ae&secret=e677ff&code="
+ code + "&grant_type=authorization_code";
//logger.info("url1:"+url);
in = new BufferedReader(new InputStreamReader(new URL(url)
.openConnection().getInputStream(), "utf-8"));
while ((inputLine = in.readLine()) != null) {
json.append(inputLine);
}
//logger.info("String:");
in.close();
String openid = JSON.parseObject(json.toString()).getString("openid");
String token = JSON.parseObject(json.toString()).getString("access_token");
strings.add(openid);
strings.add(token);
// return strings;
//下面为用户详细信息,用id和token一起获得
jsonstring = json.toString();
jobject = JSON.parseObject(jsonstring);
json = new StringBuilder();
url = "https://api.weixin.qq.com/sns/userinfo?access_token="
+ jobject.getString("access_token") + "&openid="
+ jobject.getString("openid");
//logger.info("url2:"+url);
in = new BufferedReader(new InputStreamReader(new URL(url)
.openConnection().getInputStream(), "utf-8"));
inputLine = null;
while ((inputLine = in.readLine()) != null) {
json.append(inputLine);
}
in.close();
jsonstring = json.toString();
//logger.info("DetailString"+jsonstring);
strings.add(jsonstring);
return strings;
}
}
网页只需要进行相应的接收即可得到用户信息并返回相关页面。微信对接基本功能全部实现。
资源路径如下:http://download.csdn.net/download/qq_31443653/9970347