目录
1接收消息:更多参考开发文档文本消息 | 微信开放 文档 (qq.com)
2.回复消息的文档:更多参考开发文档回复文本消息 | 微信开放文档 (qq.com)
1.1获取token 更多参考微信开放文档 (qq.com)
2.1接收消息更多参考关注/取消关注事件 | 微信开放文档 (qq.com)
一.首先了解接口文档
二.配置你的服务器
1.申请测试号(如果条件允许可以使用正式账号)
微信公众平台 (qq.com)在开发文档里申请
2.认证你的服务端口是否合法
url写的为你服务的路径接口(请求为get请求,端口为80,或者443)接入概述 | 微信开放文档 (qq.com)
这里没有认证的会有一个提交的按钮,提交就是去验证url接口的合法性
开发者提交信息后,微信服务器将发送GET请求到填写的服务器地址URL上,GET请求携带参数如下表所示:
参数 | 描述 |
signature | 微信加密签名,signature结合了开发者填写的token参数和请求中的timestamp参数、nonce参数。 |
timestamp | 时间戳 |
nonce | 随机数 |
echostr | 随机字符串 |
认证服务接口的搭建
代码的实现:
@GetMapping("/wx")
public String wxget(HttpServletRequest request){
//微信加密签名,signature结合了开发者填写的token参数和请求中的timestamp参数、nonce参数。
String signature = request.getParameter("signature");
//时间戳
String timestamp = request.getParameter("timestamp");
//随机数
String nonce = request.getParameter("nonce");
//随机字符串 用于返回 如果返回这个串就微信会认证成功
String echostr = request.getParameter("echostr");
//你在接口配置信息设置token
String token="liutong";
//调用方法进行字典排序并拼接
String sort = sort(token, timestamp, nonce);
//进行了sha1的加密
String gen = SHA1.gen(sort);
//与signature进行比对
if (gen.equals(signature)){
return echostr;
}
return "失败";
}
//对传入的字符串先进行字典排序在进行拼接 进入字典排序的原因是微信给的signature
//是对里面这几个参数进行字典排序然后拼接加密得到的,如果你不进行与signature进行比对
//那么这可有可无,但是不符合安全的逻辑。
public static String sort(String token, String timestamp, String nonce) {
String[] strArray = {token, timestamp, nonce};
//字典排序
Arrays.sort(strArray);
StringBuilder sb = new StringBuilder();
for (String str : strArray) {
sb.append(str);
}
return sb.toString();
}
三.接收消息和消息回复(消息尽量文明否则可能被微信屏蔽)
接收消息的接口还是我们配置的url路径接口,但是请求方式却为post,所以我们要一个相同的接口但是请求方式为post的接口
这里就会用到dom4j,微信发送的消息全部都是xml文档格式,所以我们需要将xml解析成为我们需要的格式。
1接收消息:更多参考开发文档文本消息 | 微信开放 文档 (qq.com)
这里是接收文本消息文档
<xml>
<ToUserName><![CDATA[toUser]]></ToUserName>
<FromUserName><![CDATA[fromUser]]></FromUserName>
<CreateTime>1348831860</CreateTime>
<MsgType><![CDATA[text]]></MsgType>
<Content><![CDATA[this is a test]]></Content>
<MsgId>1234567890123456</MsgId>
</xml>
参数 | 描述 |
ToUserName | 开发者微信号 |
FromUserName | 发送方帐号(一个OpenID) |
CreateTime | 消息创建时间 (整型) |
MsgType | 消息类型,文本为text |
Content | 文本消息内容 |
MsgId | 消息id,64位整型 |
2.回复消息的文档:更多参考开发文档回复文本消息 | 微信开放文档 (qq.com)
这里回复文本消息文档
<xml>
<ToUserName><![CDATA[toUser]]></ToUserName>
<FromUserName><![CDATA[fromUser]]></FromUserName>
<CreateTime>12345678</CreateTime>
<MsgType><![CDATA[text]]></MsgType>
<Content><![CDATA[你好]]></Content>
</xml>
参数 | 是否必须 | 描述 |
ToUserName | 是 | 接收方帐号(收到的OpenID) |
FromUserName | 是 | 开发者微信号 |
CreateTime | 是 | 消息创建时间 (整型) |
MsgType | 是 | 消息类型,文本为text |
Content | 是 | 回复的消息内容(换行:在content中能够换行,微信客户端就支持换行显示) |
3.简单代码的实现(实现的为回复文本信息):
@PostMapping("/wx")
public String wxpost(HttpServletRequest request, HttpServletResponse response) throws Exception {
//解析xml格式
Map<String, String> map = pareXml(request);
//回复的消息 拼接后用于返回
String message="";
//接收方帐号(收到的OpenID)
String ToUserName="";
//发送方
String FromUserName="";
//消息创建时间 (整型)
String CreateTime="";
//消息类型,文本为text
String MsgType="";
//回复的消息内容(换行:在content中能够换行,微信客户端就支持换行显示)
String Content="";
//获取发送信息的用户 因为他就是我们的接收方
ToUserName=map.get("FromUserName").toString();
//获取用户发送信息的目标 因为他要回复信息 所以他就是发送方
FromUserName=map.get("ToUserName").toString();
//获取用户发送信息的类型
MsgType=map.get("MsgType").toString();
//获取时间戳
CreateTime=map.get("CreateTime").toString();
try {
if (MsgType.equals("text")){
String content = map.get("Content").toString();
if (content.equals("刘桐")){
Content="支持作者:请作者喝一杯咖啡";
}else {
Content="我是刘桐的小助手:超你妈的,滚";
}
//将回复的信息设置为text
MsgType="text";
}
//返回的信息拼接为微信接受的xml格式字符串
message="<xml>\n" +
" <ToUserName><![CDATA["+ToUserName+"]]></ToUserName>\n" +
" <FromUserName><![CDATA["+FromUserName+"]]></FromUserName>\n" +
" <CreateTime>"+CreateTime+"</CreateTime>\n" +
" <MsgType><![CDATA["+MsgType+"]]></MsgType>\n" +
" <Content><![CDATA["+Content+"]]></Content>\n" +
"</xml>";
System.out.println(message);
return message;
}catch (Exception e){
e.printStackTrace();
Content="不好意思,服务器宕机了,请联系拥有者:<刘桐>,手机号:888888888888";
return message;
}
}
//解析xml格式
public static Map<String,String> pareXml(HttpServletRequest request) throws Exception {
//将解析的结果放在map里
Map<String,String> map=new HashMap<String,String>();
//request中获得输入流 接收消息为什么要用数据流获取当普通微信用户向公众账号发消息时,微信服务器将POST消息的XML数据包到开发者填写的URL上。
ServletInputStream inputStream = request.getInputStream();
//读取输入流
SAXReader saxReader = new SAXReader();
Document document = saxReader.read(inputStream);
//获得xml的根元素
Element rootElement = document.getRootElement();
//得到根元素的所有子节点
List<Element> elements = rootElement.elements();
//将信息存入map
for (Element element : elements) {
map.put(element.getName(),element.getText());
}
inputStream.close();
return map;
}
四.微信公众号菜单开发
1.准备工作
1.1获取token 更多参考微信开放文档 (qq.com)
获取token的接口:
https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=APPID&secret=APPSECRET
参数说明
参数 | 是否必须 | 说明 |
grant_type | 是 | 获取access_token填写client_credential |
appid | 是 | 第三方用户唯一凭证 |
secret | 是 | 第三方用户唯一凭证密钥,即appsecret |
返回说明
正常情况下,微信会返回下述JSON数据包给公众号:
{"access_token":"ACCESS_TOKEN","expires_in":7200}
参数说明
参数 | 说明 |
access_token | 获取到的凭证 |
expires_in | 凭证有效时间,单位:秒 |
代码的示例
//这是一个实体类方便存取我们的获取的token信息
private static AccessToken accessToken;
//获取token的接口
private static String GET_TOKEN_URL = "https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=APPID&secret=APPSECRET";
//你的appID
private static String APPID = "wx5aa0f93f4a2674d4";
//这里是你的appsecret
private static String APPSECRET = "e36822e1a6e8de6feaee75c1b775d736";
//获取令牌
@GetMapping("/getToken")
public void getToken() throws IOException {
if (accessToken==null || accessToken.getExpiresTime()<new Date().getTime()){
//拼接GET_TOKEN_URL将里面对应得到信息替换为自己的
String url = GET_TOKEN_URL.replace("APPID", APPID).replace("APPSECRET", APPSECRET);
//调用自己封装的工具类
String s = HttpUtils.senGet(url);
accessToken = JSON.parseObject(s, AccessToken.class);
accessToken.setExpiresTime(accessToken.getExpiresTime()+new Date().getTime());
System.out.println(accessToken);
}
}
封装的工具类HttpUtils
//这里其实就是封装了一个httpcliet工具类 目的就是去请求传过来的路径接口 也是获取获取token的路径接口
public static String senGet(String url) throws IOException {
String result = "";
CloseableHttpClient httpClient = HttpClients.createDefault();
HttpGet httpGet = new HttpGet(url);
CloseableHttpResponse response=null;
try {
response = httpClient.execute(httpGet);
HttpEntity entity = response.getEntity();
result = EntityUtils.toString(entity);
return result;
}catch (Exception e){
return "失败";
} finally {
httpClient.close();
response.close();
}
}
封装的令牌实体类
@Data
@AllArgsConstructor
@NoArgsConstructor
public class AccessToken {
private String accessToken;//该属性为存放的令牌
private long expiresTime;//存放的是过期时间 为当前时间加上令牌的有效时间
}
1.2 创建菜单
首先创建菜单之前我们可以在微信开放文档 (qq.com)看到各种类型按钮。
可以看到每一种按钮的属性都是不同的,而且从我们从文档中创建菜单的示例中可以看到这一个类似联动(二级联动,三级联动之类)的json串格式。所以我们就排除了,我们创建多个不同对象,因为一个list集合中只能存放一种类型。所以我们只有通过子父类的方式,将共同属性name建立一个父类,其他的去继承这个父类,list就为list。
示例代码
更多实体类的创建参考微信开放文档 (qq.com)我这里只是创建了几个必须类和一个click类
共同的父类
@Data
@AllArgsConstructor
@NoArgsConstructor
public class AbstractButton {
private String name;//按钮标题
}
一级菜单实体类
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Button {
//这里list放的是父类 这样就可以实现只要这个父类的子类都可以放进去
//完全实现了我们多个不同的按钮都放进一级菜单
//并且我们的二级菜单也是通过继承父类实现的 所里我们二级菜单可以放进去
private List<AbstractButton> button;
}
二级菜单实体类
@Data
@AllArgsConstructor
@NoArgsConstructor //这里继承公共父类AbstractButton为了方便存入一级菜单, 看一级菜单的list为List<AbstractButton>
public class SubButton extends AbstractButton {
//这里list放的是父类 这样就可以实现只要这个父类的子类都可以放进去
//完全实现了我们多个不同的按钮都放进二级菜单
private List<AbstractButton> sub_button;
}
click的实体
@Data
@AllArgsConstructor
@NoArgsConstructor
public class ClickButton extends AbstractButton{
//事件类型
private String type;
//
private String key;
}
private static AccessToken accessToken;
private static String GET_TOKEN_URL = "https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=APPID&secret=APPSECRET";
private static String CREATE_MENU_URL = "https://api.weixin.qq.com/cgi-bin/menu/create?access_token=ACCESS_TOKEN";
private static String APPID = "wx5aa0f93f4a2674d4";
private static String APPSECRET = "e36822e1a6e8de6feaee75c1b775d736";
//获取令牌
@GetMapping("/getToken")
public void getToken() throws IOException {
if (accessToken==null || accessToken.getExpiresTime()<new Date().getTime()){
String url = GET_TOKEN_URL.replace("APPID", APPID).replace("APPSECRET", APPSECRET);
String s = HttpUtils.senGet(url);
accessToken = JSON.parseObject(s, AccessToken.class);
accessToken.setExpiresTime(accessToken.getExpiresTime()+new Date().getTime());
System.out.println(accessToken);
}
}
//创建菜单
@GetMapping("/setButton")
public void setButton() throws IOException {
//创建一级菜单
Button button = new Button();
//在第三个菜单中创建二级菜单
SubButton subButton = new SubButton();
subButton.setName("有菜单");
List<AbstractButton> list2 = new ArrayList();
ClickButton clickButton1 = new ClickButton();
clickButton1.setName("你的");
clickButton1.setKey("1");
clickButton1.setType("click");
ClickButton clickButton2 = new ClickButton();
clickButton2.setName("w我的");
clickButton2.setKey("2");
clickButton2.setType("click");
list2.add(clickButton1);
list2.add(clickButton2);
subButton.setSub_button(list2);
List<AbstractButton> list = new ArrayList();
ClickButton clickButton = new ClickButton();
clickButton.setName("点击");
clickButton.setKey("11");
clickButton.setType("click");
list.add(clickButton);
list.add(subButton);
button.setButton(list);
String s = JSON.toJSONString(button);
System.out.println(s);
//获取令牌
this.getToken();
String url = CREATE_MENU_URL.replace("ACCESS_TOKEN", accessToken.getAccessToken());
//调用工具类去请求创建菜单
String result = HttpUtils.sendPost(url, s);
System.out.println(result);
}
HttpUtils工具类这里是去创建菜单的方法
//这里其实就是封装了一个httpcliet工具类 目的就是去请求传过来的路径接口 也就是创建菜单的接口
public static String sendPost(String url, String jsonStr) throws IOException {
String result = "";
CloseableHttpClient httpClient = HttpClients.createDefault();
HttpPost httpPost = new HttpPost(url);
StringEntity entity = new StringEntity(jsonStr, "UTF-8");
httpPost.setEntity(entity);
httpPost.setHeader("Content-type", "application/json");
CloseableHttpResponse response=null;
try {
response=httpClient.execute(httpPost);
HttpEntity httpEntity = response.getEntity();
result=EntityUtils.toString(httpEntity);
return result;
}catch (Exception e){
return "失败";
}finally {
httpClient.close();
response.close();
}
}
2菜单对应事件进行自动消息回复
2.1接收消息更多参考关注/取消关注事件 | 微信开放文档 (qq.com)
这里是click事件的示例
当这些事件触发后就会访问我们配置服务进行处理(依然是post请求)
自定义菜单事件
用户点击自定义菜单后,微信会把点击事件推送给开发者,请注意,点击菜单弹出子菜单,不会产生上报。
点击菜单拉取消息时的事件推送
推送XML数据包示例:
<xml>
<ToUserName><![CDATA[toUser]]></ToUserName>
<FromUserName><![CDATA[FromUser]]></FromUserName>
<CreateTime>123456789</CreateTime>
<MsgType><![CDATA[event]]></MsgType>
<Event><![CDATA[CLICK]]></Event>
<EventKey><![CDATA[EVENTKEY]]></EventKey>
</xml>
参数说明:
参数 | 描述 |
ToUserName | 开发者微信号 |
FromUserName | 发送方帐号(一个OpenID) |
CreateTime | 消息创建时间 (整型) |
MsgType | 消息类型,event |
Event | 事件类型,CLICK |
EventKey | 事件KEY值,与自定义菜单接口中KEY值对应 |
2.2回复消息
这里依旧回复的是text
@PostMapping("/wx")
public String wxpost(HttpServletRequest request, HttpServletResponse response) throws Exception {
//解析xml格式
Map<String, String> map = pareXml(request);
//回复的消息
String message="";
//接收方帐号(收到的OpenID)
String ToUserName="";
//发送方
String FromUserName="";
//消息创建时间 (整型)
String CreateTime="";
//消息类型,文本为text
String MsgType="";
//回复的消息内容(换行:在content中能够换行,微信客户端就支持换行显示)
String Content="";
ToUserName=map.get("FromUserName").toString();
FromUserName=map.get("ToUserName").toString();
MsgType=map.get("MsgType").toString();
CreateTime=map.get("CreateTime").toString();
try {
if (MsgType.equals("event")){
//Event事件类型
String Event=map.get("Event").toString();
if (Event.equals("CLICK")){
//事件KEY值,与自定义菜单接口中KEY值对应
String EventKey=map.get("EventKey").toString();
if (EventKey.equals("11")){
//设置返回的消息
Content="别怕是是是";
//设置返回消息的类型
MsgType="text";
System.out.println("进来了");
}
}
}
message="<xml>\n" +
" <ToUserName><![CDATA["+ToUserName+"]]></ToUserName>\n" +
" <FromUserName><![CDATA["+FromUserName+"]]></FromUserName>\n" +
" <CreateTime>"+CreateTime+"</CreateTime>\n" +
" <MsgType><![CDATA["+MsgType+"]]></MsgType>\n" +
" <Content><![CDATA["+Content+"]]></Content>\n" +
"</xml>";
System.out.println(message);
return message;
}catch (Exception e){
e.printStackTrace();
Content="不好意思,服务器宕机了,请联系拥有者:<刘桐>,手机号:888888888888";
return message;
}
}
//解析xml格式
public static Map<String,String> pareXml(HttpServletRequest request) throws Exception {
//将解析的结果放在map里
Map<String,String> map=new HashMap<String,String>();
//request中获得输入流
ServletInputStream inputStream = request.getInputStream();
//读取输入流
SAXReader saxReader = new SAXReader();
Document document = saxReader.read(inputStream);
//获得xml的根元素
Element rootElement = document.getRootElement();
//得到根元素的所有子节点
List<Element> elements = rootElement.elements();
//将信息存入map
for (Element element : elements) {
map.put(element.getName(),element.getText());
}
inputStream.close();
return map;
}
public static String sort(String token, String timestamp, String nonce) {
String[] strArray = {token, timestamp, nonce};
//可以算是字典排序
Arrays.sort(strArray);
StringBuilder sb = new StringBuilder();
for (String str : strArray) {
sb.append(str);
}
return sb.toString();
}