Java微信公众平台开发_03_消息管理之被动回复消息
GitHub源码:https://github.com/shirayner/weixin_gz
一、本节要点
1.回调url
上一节,我们启用服务器配置的时候,填写了一个服务器地址(url),如下图,这个url就是回调url,是开发者用来接收微信消息和事件的接口URL 。也就是说,用户在微信公众号中发送的消息会被推送到这个回调url,而我们可以接收用户的消息,并进行回复。
2.被动回复消息的流程
官方文档:
我们在上一节中设置的消息加解密方式是安全模式。因此在用户发给公众号的消息(接收消息)以及公众号被动回复用户消息(回复消息)都会加密,
流程:
用户发送消息之后,微信服务器将消息传递给 第三方服务器,第三方服务器接收到消息后,再对消息做出相应的回复消息。
接收消息:需先从request请求对象的输入流中获取请求参数和已加密的请求消息,再对已加密的请求消息进行解密操作,即可获得明文。
然后就行对明文消息的业务处理了。
回复消息:封装好回复消息后,需先对回复消息进行加密,获得已已加密消息,然后再通过http请求调用被动回复消息的接口,来发送消息。
3.被动回复消息加解密
3.1接收消息的 解密
(1)从请求的输入流中获取加密的请求消息
//1.获取加密的请求消息:使用输入流获得加密请求消息postData
ServletInputStream in = request.getInputStream();
BufferedReader reader =new BufferedReader(new InputStreamReader(in)); String tempStr=""; //作为输出字符串的临时串,用于判断是否读取完毕
while(null!=(tempStr=reader.readLine())){
postData+=tempStr;
} logger.info("postData:"+postData);
(2)对加密的请求消息进行解密获得明文
WXBizMsgCrypt wxcpt = new WXBizMsgCrypt(Env.TOKEN,Env.ENCODING_AES_KEY,Env.APP_ID);
result=wxcpt.DecryptMsg(msgSignature, timeStamp, nonce, postData);
(3)解密算法
直接调用微信官方的 WXBizMsgCrypt 类的 DecryptMsg(String, String, String, String) 方法即可。
3.2 回复消息的加密
直接用官方加解密工具类。
wxcpt = new WXBizMsgCrypt(Env.TOKEN,Env.ENCODING_AES_KEY,Env.APP_ID);
replyMsg=wxcpt.EncryptMsg(replyMsg, timeStamp, nonce);
4.消息对象的封装
根据官方文档消息的xml传输格式,我们可以将消息封装成对象。请参见后面的代码实现部分
5.数据传输—对象 转成 xml字符串
根据官方文档,数据是以XML数据包的形式进行传输的。因此,我们需要
(1)解析微信发来的请求(xmlStr),从xml字符串中获取需要的信息
(2)回复消息时,将消息对象转成xml字符串。
我们是使用dom4j,xstream来进行这个转换的,因此需要导入jar包,maven依赖如下:
<!-- 7.XML解析 -->
<dependency>
<groupId>dom4j</groupId>
<artifactId>dom4j</artifactId>
<version>1.6.1</version>
</dependency> <dependency>
<groupId>com.thoughtworks.xstream</groupId>
<artifactId>xstream</artifactId>
<version>1.4.10</version>
</dependency>
具体请参见代码实现的 MessageUtil 部分。
5.1 解析微信发来的请求(XML),获取请求参数
/**
* @desc :1.解析微信发来的请求(XML),获取请求参数
*
* @param request
* @return
* @throws Exception Map<String,String>
*/
public static Map<String, String> parseXml(HttpServletRequest request) throws Exception {
// 将解析结果存储在HashMap中
Map<String, String> map = new HashMap<String, String>(); // 从request中取得输入流
InputStream inputStream = request.getInputStream();
// 读取输入流
SAXReader reader = new SAXReader();
Document document = reader.read(inputStream);
// 得到xml根元素
Element root = document.getRootElement();
// 得到根元素的所有子节点
List<Element> elementList = root.elements(); // 遍历所有子节点
for (Element e : elementList)
map.put(e.getName(), e.getText()); // 释放资源
inputStream.close();
inputStream = null; return map;
}
5.2 将文本消息转成xml字符串
/**
* 2.文本消息对象转换成xml
*
* @param textMessage 文本消息对象
* @return xml
*/
public static String textMessageToXml(TextMessage textMessage) {
xstream.alias("xml", textMessage.getClass());
return xstream.toXML(textMessage);
}
二、代码实现
1.微信配置类—Env.java
微信公众号接入配置类
package com.ray.weixin.gz.config; /**@desc : 微信公众号接入配置
*
* @author: shirayner
* @date : 2017年9月27日 下午4:57:36
*/ public class Env { /**
* 1. 企业应用接入秘钥相关
*/
// public static final String APP_ID = "wx4ddse2334debebef2cc";
//public static final String APP_SECRET = "068e2599abf88ba72frrgbfs6f3c56e"; //测试号
public static final String APP_ID = "wxa00642deff56g062";
public static final String APP_SECRET = "fcc96fefdgdhtj1a46af7993784917"; /**
* 2.服务器配置:
* 启用并设置服务器配置后,用户发给公众号的消息以及开发者需要的事件推送,将被微信转发到该URL中
*/
public static final String TOKEN = "weixin";
public static final String ENCODING_AES_KEY = "JvJ1Dww6tjUU2psC3pdewegreHfovfWP3LfX1xrriz1"; }
2.HTTP请求工具类—HttpHelper.java
package com.ray.weixin.gz.util; import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream; import org.apache.http.Consts;
import org.apache.http.Header;
import org.apache.http.HeaderElement;
import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.HttpStatus;
import org.apache.http.NameValuePair;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.ContentType;
import org.apache.http.entity.FileEntity;
import org.apache.http.entity.StringEntity;
import org.apache.http.entity.mime.MultipartEntityBuilder;
import org.apache.http.entity.mime.content.FileBody;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.protocol.BasicHttpContext;
import org.apache.http.util.EntityUtils; import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject; /**
* HTTP请求封装,建议直接使用sdk的API
*/
public class HttpHelper { /**
* @desc :1.发起GET请求
*
* @param url
* @return JSONObject
* @throws Exception
*/
public static JSONObject doGet(String url) throws Exception { //1.生成一个请求
HttpGet httpGet = new HttpGet(url);
//2.配置请求的属性
RequestConfig requestConfig = RequestConfig.custom().setSocketTimeout(5000).setConnectTimeout(5000).build();//
httpGet.setConfig(requestConfig); //3.发起请求,获取响应信息
//3.1 创建httpClient
CloseableHttpClient httpClient = HttpClients.createDefault();
CloseableHttpResponse response = null;
try {
//3.2 发起请求,获取响应信息
response = httpClient.execute(httpGet, new BasicHttpContext()); //如果返回结果的code不等于200,说明出错了
if (response.getStatusLine().getStatusCode() != 200) { System.out.println("request url failed, http code=" + response.getStatusLine().getStatusCode()
+ ", url=" + url);
return null;
}
//4.解析请求结果
HttpEntity entity = response.getEntity(); //reponse返回的数据在entity中
if (entity != null) {
String resultStr = EntityUtils.toString(entity, "utf-8"); //将数据转化为string格式
System.out.println("GET请求结果:"+resultStr);
JSONObject result = JSON.parseObject(resultStr); //将String转换为 JSONObject if(result.getInteger("errcode")==null) {
return result;
}else if (0 == result.getInteger("errcode")) {
return result;
}else {
System.out.println("request url=" + url + ",return value=");
System.out.println(resultStr);
int errCode = result.getInteger("errcode");
String errMsg = result.getString("errmsg");
throw new Exception("error code:"+errCode+", error message:"+errMsg);
}
}
} catch (IOException e) {
System.out.println("request url=" + url + ", exception, msg=" + e.getMessage());
e.printStackTrace();
} finally {
if (response != null) try {
response.close(); //释放资源 } catch (IOException e) {
e.printStackTrace();
}
} return null;
} /** 2.发起POST请求
* @desc :
*
* @param url 请求url
* @param data 请求参数(json)
* @return
* @throws Exception JSONObject
*/
public static JSONObject doPost(String url, Object data) throws Exception {
//1.生成一个请求
HttpPost httpPost = new HttpPost(url); //2.配置请求属性
//2.1 设置请求超时时间
RequestConfig requestConfig = RequestConfig.custom().setSocketTimeout(100000).setConnectTimeout(100000).build();
httpPost.setConfig(requestConfig);
//2.2 设置数据传输格式-json
httpPost.addHeader("Content-Type", "application/json");
//2.3 设置请求实体,封装了请求参数
StringEntity requestEntity = new StringEntity(JSON.toJSONString(data), "utf-8");
httpPost.setEntity(requestEntity); //3.发起请求,获取响应信息
//3.1 创建httpClient
CloseableHttpClient httpClient = HttpClients.createDefault();
CloseableHttpResponse response = null; try { //3.3 发起请求,获取响应
response = httpClient.execute(httpPost, new BasicHttpContext()); if (response.getStatusLine().getStatusCode() != 200) { System.out.println("request url failed, http code=" + response.getStatusLine().getStatusCode()
+ ", url=" + url);
return null;
} //获取响应内容
HttpEntity entity = response.getEntity();
if (entity != null) {
String resultStr = EntityUtils.toString(entity, "utf-8");
System.out.println("POST请求结果:"+resultStr); //解析响应内容
JSONObject result = JSON.parseObject(resultStr); if(result.getInteger("errcode")==null) {
return result;
}else if (0 == result.getInteger("errcode")) {
return result;
}else {
System.out.println("request url=" + url + ",return value=");
System.out.println(resultStr);
int errCode = result.getInteger("errcode");
String errMsg = result.getString("errmsg");
throw new Exception("error code:"+errCode+", error message:"+errMsg);
}
}
} catch (IOException e) {
System.out.println("request url=" + url + ", exception, msg=" + e.getMessage());
e.printStackTrace();
} finally {
if (response != null) try {
response.close(); //释放资源 } catch (IOException e) {
e.printStackTrace();
}
} return null;
} /**
* @desc : 3.上传文件
*
* @param url 请求url
* @param file 上传的文件
* @return
* @throws Exception JSONObject
*/
public static JSONObject uploadMedia(String url, File file) throws Exception {
HttpPost httpPost = new HttpPost(url);
CloseableHttpResponse response = null;
CloseableHttpClient httpClient = HttpClients.createDefault();
RequestConfig requestConfig = RequestConfig.custom().setSocketTimeout(5000).setConnectTimeout(5000).build();
httpPost.setConfig(requestConfig); //2.3 设置请求实体,封装了请求参数
HttpEntity requestEntity = MultipartEntityBuilder.create().addPart("media",
new FileBody(file, ContentType.create("multipart/form-data", Consts.UTF_8), file.getName())).build(); //FileEntity requestEntity = new FileEntity(file,ContentType.MULTIPART_FORM_DATA); httpPost.setEntity(requestEntity); try {
response = httpClient.execute(httpPost, new BasicHttpContext()); if (response.getStatusLine().getStatusCode() != 200) { System.out.println("request url failed, http code=" + response.getStatusLine().getStatusCode()
+ ", url=" + url);
return null;
}
HttpEntity entity = response.getEntity();
if (entity != null) {
String resultStr = EntityUtils.toString(entity, "utf-8"); JSONObject result = JSON.parseObject(resultStr);
//上传临时素材成功
if (result.getString("errcode")== null) {
// 成功
//result.remove("errcode");
//result.remove("errmsg");
return result;
} else {
System.out.println("request url=" + url + ",return value=");
System.out.println(resultStr);
int errCode = result.getInteger("errcode");
String errMsg = result.getString("errmsg");
throw new Exception("error code:"+errCode+", error message:"+errMsg);
}
}
} catch (IOException e) {
System.out.println("request url=" + url + ", exception, msg=" + e.getMessage());
e.printStackTrace();
} finally {
if (response != null) try {
response.close(); //释放资源 } catch (IOException e) {
e.printStackTrace();
}
} return null;
} /**
* @desc : 上传PDF
* 见微信电子发票章节
* 9. 向用户提供发票或其它消费凭证PDF
*
* @param url
* @param file
* @return
* @throws Exception
* JSONObject
*/
public static JSONObject uploadPDF(String url, File file) throws Exception {
HttpPost httpPost = new HttpPost(url);
CloseableHttpResponse response = null;
CloseableHttpClient httpClient = HttpClients.createDefault();
RequestConfig requestConfig = RequestConfig.custom().setSocketTimeout(5000).setConnectTimeout(5000).build();
httpPost.setConfig(requestConfig); //2.3 设置请求实体,封装了请求参数
HttpEntity requestEntity = MultipartEntityBuilder.create().addPart("media",
new FileBody(file, ContentType.create("multipart/form-data", Consts.UTF_8), file.getName())).build(); httpPost.setEntity(requestEntity); try {
response = httpClient.execute(httpPost, new BasicHttpContext()); if (response.getStatusLine().getStatusCode() != 200) { System.out.println("request url failed, http code=" + response.getStatusLine().getStatusCode()
+ ", url=" + url);
return null;
}
HttpEntity entity = response.getEntity();
if (entity != null) {
String resultStr = EntityUtils.toString(entity, "utf-8"); JSONObject result = JSON.parseObject(resultStr);
//上传临时素材成功
if (result.getString("errcode")== null) {
// 成功
//result.remove("errcode");
//result.remove("errmsg");
return result;
} else {
System.out.println("request url=" + url + ",return value=");
System.out.println(resultStr);
int errCode = result.getInteger("errcode");
String errMsg = result.getString("errmsg");
throw new Exception("error code:"+errCode+", error message:"+errMsg);
}
}
} catch (IOException e) {
System.out.println("request url=" + url + ", exception, msg=" + e.getMessage());
e.printStackTrace();
} finally {
if (response != null) try {
response.close(); //释放资源 } catch (IOException e) {
e.printStackTrace();
}
} return null;
}
/**
* @desc : 4.下载文件 -get
*
* @param url 请求url
* @param fileDir 下载路径
* @return
* @throws Exception File
*/
public static File downloadMedia(String url, String fileDir) throws Exception {
//1.生成一个请求
HttpGet httpGet = new HttpGet(url);
//2.配置请求属性
RequestConfig requestConfig = RequestConfig.custom().setSocketTimeout(100000).setConnectTimeout(100000).build();
httpGet.setConfig(requestConfig); //3.发起请求,获取响应信息
//3.1 创建httpClient
CloseableHttpClient httpClient = HttpClients.createDefault();
CloseableHttpResponse response = null; //4.设置本地保存的文件
//File file = new File(fileDir);
File file = null;
try {
//5. 发起请求,获取响应信息
response = httpClient.execute(httpGet, new BasicHttpContext());
System.out.println("HttpStatus.SC_OK:"+HttpStatus.SC_OK);
System.out.println("response.getStatusLine().getStatusCode():"+response.getStatusLine().getStatusCode());
System.out.println("http-header:"+JSON.toJSONString( response.getAllHeaders() ));
System.out.println("http-filename:"+getFileName(response) ); //请求成功
if(HttpStatus.SC_OK==response.getStatusLine().getStatusCode()){ //6.取得请求内容
HttpEntity entity = response.getEntity(); if (entity != null) {
//这里可以得到文件的类型 如image/jpg /zip /tiff 等等 但是发现并不是十分有效,有时明明后缀是.rar但是取到的是null,这点特别说明
System.out.println(entity.getContentType());
//可以判断是否是文件数据流
System.out.println(entity.isStreaming()); //6.1 输出流
//6.1.1获取文件名,拼接文件路径
String fileName=getFileName(response);
fileDir=fileDir+fileName;
file = new File(fileDir);
//6.1.2根据文件路径获取输出流
FileOutputStream output = new FileOutputStream(file); //6.2 输入流:从钉钉服务器返回的文件流,得到网络资源并写入文件
InputStream input = entity.getContent(); //6.3 将数据写入文件:将输入流中的数据写入到输出流
byte b[] = new byte[1024];
int j = 0;
while( (j = input.read(b))!=-1){
output.write(b,0,j);
}
output.flush();
output.close();
}
if (entity != null) {
entity.consumeContent();
}
}
} catch (IOException e) {
System.out.println("request url=" + url + ", exception, msg=" + e.getMessage());
e.printStackTrace();
} finally {
if (response != null) try {
response.close(); //释放资源 } catch (IOException e) {
e.printStackTrace();
}
} return file;
} /**
* @desc : 5.下载文件 - post
*
* @param url 请求url
* @param data post请求参数
* @param fileDir 文件下载路径
* @return
* @throws Exception File
*/
public static File downloadMedia(String url, Object data, String fileDir) throws Exception {
//1.生成一个请求
HttpPost httpPost = new HttpPost(url); //2.配置请求属性
//2.1 设置请求超时时间
RequestConfig requestConfig = RequestConfig.custom().setSocketTimeout(100000).setConnectTimeout(100000).build();
httpPost.setConfig(requestConfig);
//2.2 设置数据传输格式-json
httpPost.addHeader("Content-Type", "application/json");
//2.3 设置请求参数
StringEntity requestEntity = new StringEntity(JSON.toJSONString(data), "utf-8");
httpPost.setEntity(requestEntity); //3.发起请求,获取响应信息
//3.1 创建httpClient
CloseableHttpClient httpClient = HttpClients.createDefault();
CloseableHttpResponse response = null; //4.设置本地保存的文件
//File file = new File(fileDir);
File file = null;
try {
//5. 发起请求,获取响应信息
response = httpClient.execute(httpPost, new BasicHttpContext());
System.out.println("HttpStatus.SC_OK:"+HttpStatus.SC_OK);
System.out.println("response.getStatusLine().getStatusCode():"+response.getStatusLine().getStatusCode());
System.out.println("http-header:"+JSON.toJSONString( response.getAllHeaders() ));
System.out.println("http-filename:"+getFileName(response) ); //请求成功
if(HttpStatus.SC_OK==response.getStatusLine().getStatusCode()){ //6.取得请求内容
HttpEntity entity = response.getEntity(); if (entity != null) {
//这里可以得到文件的类型 如image/jpg /zip /tiff 等等 但是发现并不是十分有效,有时明明后缀是.rar但是取到的是null,这点特别说明
System.out.println(entity.getContentType());
//可以判断是否是文件数据流
System.out.println(entity.isStreaming()); //6.1 输出流
//6.1.1获取文件名,拼接文件路径
String fileName=getFileName(response);
fileDir=fileDir+fileName;
file = new File(fileDir);
//6.1.2根据文件路径获取输出流
FileOutputStream output = new FileOutputStream(file); //6.2 输入流:从钉钉服务器返回的文件流,得到网络资源并写入文件
InputStream input = entity.getContent(); //6.3 将数据写入文件:将输入流中的数据写入到输出流
byte b[] = new byte[1024];
int j = 0;
while( (j = input.read(b))!=-1){
output.write(b,0,j);
}
output.flush();
output.close();
}
if (entity != null) {
entity.consumeContent();
}
}
} catch (IOException e) {
System.out.println("request url=" + url + ", exception, msg=" + e.getMessage());
e.printStackTrace();
} finally {
if (response != null) try {
response.close(); //释放资源 } catch (IOException e) {
e.printStackTrace();
}
} return file;
} /** 5. 获取response header中Content-Disposition中的filename值
* @desc :
*
* @param response 响应
* @return String
*/
public static String getFileName(HttpResponse response) {
Header contentHeader = response.getFirstHeader("Content-Disposition");
String filename = null;
if (contentHeader != null) {
HeaderElement[] values = contentHeader.getElements();
if (values.length == 1) {
NameValuePair param = values[0].getParameterByName("filename");
if (param != null) {
try {
//filename = new String(param.getValue().toString().getBytes(), "utf-8");
//filename=URLDecoder.decode(param.getValue(),"utf-8");
filename = param.getValue();
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
return filename;
} }
3、接收消息的封装
3.1 消息基类—BaseMessage
package com.ray.weixin.gz.model.message.request; /**
* @desc : 消息基类(普通用户 -> 公众帐号)
*
* @author: shirayner
* @date : 2017年11月13日 上午10:58:08
*/
public class BaseMessage {
// 开发者微信号
private String ToUserName;
// 发送方帐号(一个OpenID)
private String FromUserName;
// 消息创建时间 (整型)
private long CreateTime;
// 消息类型(text/image/location/link)
private String MsgType;
// 消息id,64位整型
private long MsgId; 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;
}
}
3.2 文本消息—TextMessage
package com.ray.weixin.gz.model.message.request; /**
* @desc : 文本消息
*
* @author: shirayner
* @date : 2017年11月13日 上午11:04:09
*/
public class TextMessage extends BaseMessage {
// 消息内容
private String Content; public String getContent() {
return Content;
} public void setContent(String content) {
Content = content;
}
}
3.3 图片消息—ImageMessage
package com.ray.weixin.gz.model.message.request; /**
* @desc : 图片消息
*
* @author: shirayner
* @date : 2017年11月13日 上午11:04:33
*/
public class ImageMessage extends BaseMessage {
// 图片链接
private String PicUrl; public String getPicUrl() {
return PicUrl;
} public void setPicUrl(String picUrl) {
PicUrl = picUrl;
}
}
3.4 链接消息—LinkMessage
package com.ray.weixin.gz.model.message.request; /**
* @desc :链接消息
*
* @author: shirayner
* @date : 2017年11月13日 上午11:05:46
*/
public class LinkMessage extends BaseMessage {
// 消息标题
private String Title;
// 消息描述
private String Description;
// 消息链接
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 getUrl() {
return Url;
} public void setUrl(String url) {
Url = url;
}
}
3.5 地理位置消息—LocationMessage
package com.ray.weixin.gz.model.message.request; /**
* @desc : 地理位置消息
*
* @author: shirayner
* @date : 2017年11月13日 上午11:07:39
*/
public class LocationMessage extends BaseMessage {
// 地理位置维度
private String Location_X;
// 地理位置经度
private String Location_Y;
// 地图缩放大小
private String Scale;
// 地理位置信息
private String Label; public String getLocation_X() {
return Location_X;
} public void setLocation_X(String location_X) {
Location_X = location_X;
} public String getLocation_Y() {
return Location_Y;
} public void setLocation_Y(String location_Y) {
Location_Y = location_Y;
} public String getScale() {
return Scale;
} public void setScale(String scale) {
Scale = scale;
} public String getLabel() {
return Label;
} public void setLabel(String label) {
Label = label;
}
}
3.6 音频消息—VoiceMessage
package com.ray.weixin.gz.model.message.request; /**
* @desc :音频消息
*
* @author: shirayner
* @date : 2017年11月13日 上午11:08:25
*/
public class VoiceMessage extends BaseMessage {
// 媒体ID
private String MediaId;
// 语音格式
private String Format; public String getMediaId() {
return MediaId;
} public void setMediaId(String mediaId) {
MediaId = mediaId;
} public String getFormat() {
return Format;
} public void setFormat(String format) {
Format = format;
}
}
4. 回复消息的封装
4.1 消息基类—BaseMessage
package com.ray.weixin.gz.model.message.response; /**
* @desc : 消息基类(公众帐号 -> 普通用户)
*
* @author: shirayner
* @date : 2017年11月13日 上午11:10:32
*/
public class BaseMessage {
// 接收方帐号(收到的OpenID)
private String ToUserName;
// 开发者微信号
private String FromUserName;
// 消息创建时间 (整型)
private long CreateTime;
// 消息类型(text/music/news)
private String MsgType;
// 位0x0001被标志时,星标刚收到的消息
private int FuncFlag; 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 int getFuncFlag() {
return FuncFlag;
} public void setFuncFlag(int funcFlag) {
FuncFlag = funcFlag;
}
}
4.2 文本消息—TextMessage
package com.ray.weixin.gz.model.message.response; /**
* @desc :
*
* @author: shirayner
* @date : 2017年11月13日 上午11:10:58
*/
public class TextMessage extends BaseMessage {
// 回复的消息内容
private String Content; public String getContent() {
return Content;
} public void setContent(String content) {
Content = content;
}
}
4.3 音乐消息—MusicMessage
Music
package com.ray.weixin.gz.model.message.response; /**
* @desc : 音乐model
*
* @author: shirayner
* @date : 2017年11月13日 上午11:12:47
*/
public class Music {
// 音乐名称
private String Title;
// 音乐描述
private String Description;
// 音乐链接
private String MusicUrl;
// 高质量音乐链接,WIFI环境优先使用该链接播放音乐
private String HQMusicUrl; 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 getMusicUrl() {
return MusicUrl;
} public void setMusicUrl(String musicUrl) {
MusicUrl = musicUrl;
} public String getHQMusicUrl() {
return HQMusicUrl;
} public void setHQMusicUrl(String musicUrl) {
HQMusicUrl = musicUrl;
} }
MusicMessage
package com.ray.weixin.gz.model.message.response; /**
* @desc : 音乐消息
*
* @author: shirayner
* @date : 2017年11月13日 上午11:12:06
*/
public class MusicMessage extends BaseMessage {
// 音乐
private Music Music; public Music getMusic() {
return Music;
} public void setMusic(Music music) {
Music = music;
}
}
4.4 图文消息—NewsMessage
Article
package com.ray.weixin.gz.model.message.response; /**
* @desc : 图文model
*
* @author: shirayner
* @date : 2017年11月13日 上午11:15:30
*/
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 null == Description ? "" : Description;
} public void setDescription(String description) {
Description = description;
} public String getPicUrl() {
return null == PicUrl ? "" : PicUrl;
} public void setPicUrl(String picUrl) {
PicUrl = picUrl;
} public String getUrl() {
return null == Url ? "" : Url;
} public void setUrl(String url) {
Url = url;
} }
NewsMessage
package com.ray.weixin.gz.model.message.response; import java.util.List; /**
* @desc : 图文消息
*
* @author: shirayner
* @date : 2017年11月13日 上午11:13:36
*/
public class NewsMessage extends BaseMessage {
// 图文消息个数,限制为10条以内
private int ArticleCount;
// 多条图文消息信息,默认第一个item为大图
private List<Article> Articles; public int getArticleCount() {
return ArticleCount;
} public void setArticleCount(int articleCount) {
ArticleCount = articleCount;
} public List<Article> getArticles() {
return Articles;
} public void setArticles(List<Article> articles) {
Articles = articles;
}
}
5.接收微信消息和事件—WeiXinServlet.java
package com.ray.weixin.gz.controller;
import java.io.IOException;
import java.io.PrintWriter; import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse; import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger; import com.qq.weixin.mp.aes.AesException;
import com.qq.weixin.mp.aes.WXBizMsgCrypt;
import com.ray.weixin.gz.config.Env;
import com.ray.weixin.gz.service.message.ReplyMessageService; /**
* Servlet implementation class WeiXinServlet
*/
public class WeiXinServlet extends HttpServlet {
private static final Logger logger = LogManager.getLogger(WeiXinServlet.class); private static final long serialVersionUID = 1L; /**
* Default constructor.
*/
public WeiXinServlet() {
// TODO Auto-generated constructor stub
} //1.接收 回调模式 的请求
protected void doGet(HttpServletRequest request, HttpServletResponse response) {
logger.info("get--------------");
//一、校验URL
//1.准备校验参数
// 微信加密签名
String msgSignature = request.getParameter("signature");
// 时间戳
String timeStamp = request.getParameter("timestamp");
// 随机数
String nonce = request.getParameter("nonce");
// 随机字符串
String echoStr = request.getParameter("echostr"); PrintWriter out=null;
try {
//2.校验url
//2.1 创建加解密类
WXBizMsgCrypt wxcpt = new WXBizMsgCrypt(Env.TOKEN,Env.ENCODING_AES_KEY,Env.APP_ID); //2.2进行url校验
//不抛异常就说明校验成功
String sEchoStr= wxcpt.verifyUrl_WXGZ(msgSignature, Env.TOKEN, timeStamp, nonce,echoStr); //2.3若校验成功,则原样返回 echoStr out = response.getWriter();
out.print(sEchoStr); } catch (AesException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} finally {
if (out != null) {
out.close();
out = null; //释放资源
}
}
} //2.接收 微信消息和事件 的请求
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
logger.info("post--------------");
//1.将请求、响应的编码均设置为UTF-8(防止中文乱码)
request.setCharacterEncoding("UTF-8");
response.setCharacterEncoding("UTF-8"); //2.调用消息业务类接收消息、处理消息
String respMessage = ReplyMessageService.reply(request); //3.响应消息
PrintWriter out = response.getWriter();
out.print(respMessage);
out.close(); } }
6.被动回复消息业务类—ReplyMessageService.java
package com.ray.weixin.gz.service.message; import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.Date;
import java.util.Map; import javax.servlet.ServletInputStream;
import javax.servlet.http.HttpServletRequest;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger; import com.alibaba.fastjson.JSON;
import com.qq.weixin.mp.aes.AesException;
import com.qq.weixin.mp.aes.WXBizMsgCrypt;
import com.ray.weixin.gz.config.Env;
import com.ray.weixin.gz.model.message.response.TextMessage;
import com.ray.weixin.gz.util.MessageUtil; /**@desc : 发送消息-被动回复消息业务类
* Passive reply message
* @author: shirayner
* @date : 2017年10月31日 下午12:24:41
*/
public class ReplyMessageService {
private static final Logger logger = LogManager.getLogger(ReplyMessageService.class); /**
* @desc :1.回复消息
*
* @param request
* @return
* String 回复消息的加密xml字符串
*/
public static String reply( HttpServletRequest request ) {
String parms=JSON.toJSONString(request.getParameterMap());
logger.info("parms:"+parms);
//1.解密:从request中获取消息明文
String xmlMsg=decryptMsg(request);
logger.info(xmlMsg); //2.获取回复消息(明文)
String replyMsg = getReplyMsg( xmlMsg); //3.根据消息加密方式判断是否加密
String timeStamp = request.getParameter("timestamp"); // 时间戳
String nonce = request.getParameter("nonce"); // 随机数
String encryptType=request.getParameter("encrypt_type"); //3.1 安全模式-加密:将回复消息加密
if(null!=encryptType) {
WXBizMsgCrypt wxcpt=null;
try {
wxcpt = new WXBizMsgCrypt(Env.TOKEN,Env.ENCODING_AES_KEY,Env.APP_ID);
replyMsg=wxcpt.EncryptMsg(replyMsg, timeStamp, nonce); } catch (AesException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
} return replyMsg;
} /**
* @desc :2.从request中获取消息明文
* 从request中获取加密消息,将其解密并返回
* @param request
* @return String 消息明文
*/
public static String decryptMsg(HttpServletRequest request) { String postData=""; // 密文,对应POST请求的数据
String result=""; // 明文,解密之后的结果 String msgSignature = request.getParameter("msg_signature"); // 微信加密签名
String timeStamp = request.getParameter("timestamp"); // 时间戳
String nonce = request.getParameter("nonce"); // 随机数
String encryptType=request.getParameter("encrypt_type"); try {
//1.获取加密的请求消息:使用输入流获得加密请求消息postData
ServletInputStream in = request.getInputStream();
BufferedReader reader =new BufferedReader(new InputStreamReader(in)); String tempStr=""; //作为输出字符串的临时串,用于判断是否读取完毕
while(null!=(tempStr=reader.readLine())){
postData+=tempStr;
} logger.info("postData:"+postData); //2.获取消息明文:对加密的请求消息进行解密获得明文
if(null!=encryptType) {
logger.info("安全模式:消息被加密");
WXBizMsgCrypt wxcpt = new WXBizMsgCrypt(Env.TOKEN,Env.ENCODING_AES_KEY,Env.APP_ID);
result=wxcpt.DecryptMsg(msgSignature, timeStamp, nonce, postData);
}else {
logger.info("明文模式");
result=postData;
} } catch (IOException e) {
e.printStackTrace();
} catch (AesException e) {
e.printStackTrace();
} return result;
} /**
* @desc :获取回复消息
*
* @param request
* @return String 返回加密后的回复消息
*/
public static String getReplyMsg(String xmlMsg){
String replyMsg = null; try {
//2.解析微信发来的请求,解析xml字符串
Map<String, String> requestMap= MessageUtil.parseXml(xmlMsg); //3.获取请求参数
//3.1 企业微信CorpID
String fromUserName = requestMap.get("FromUserName");
//3.2 成员UserID
String toUserName = requestMap.get("ToUserName");
//3.3 消息类型与事件
String msgType = requestMap.get("MsgType");
String eventType = requestMap.get("Event");
String eventKey = requestMap.get("EventKey");
logger.info("fromUserName:"+fromUserName);
logger.info("toUserName:"+toUserName);
logger.info("msgType:"+msgType);
logger.info("Event:"+eventType+" eventKey:"+eventKey); //4.组装 回复文本消息
TextMessage textMessage = new TextMessage();
textMessage.setToUserName(fromUserName);
textMessage.setFromUserName(toUserName);
textMessage.setCreateTime(new Date().getTime());
textMessage.setMsgType(MessageUtil.RESP_MESSAGE_TYPE_TEXT);
//4.1.获取回复消息的内容 :消息的分类处理
String replyContent=getReplyContentByMsgType(msgType, eventType, eventKey);
textMessage.setContent(replyContent);
System.out.println("replyContent:"+replyContent); //5.获取xml字符串: 将(被动回复消息型的)文本消息对象 转成 xml字符串
replyMsg = MessageUtil.textMessageToXml(textMessage); } catch (Exception e) {
e.printStackTrace();
} return replyMsg;
} /**
* @desc :3.处理消息:根据消息类型获取回复内容
*
* @param msgType 消息类型
* @return String 回复内容
*/
public static String getReplyContentByMsgType(String msgType,String eventType,String eventKey){
String replyContent="";
//1.文本消息
if (msgType.equals(MessageUtil.REQ_MESSAGE_TYPE_TEXT)) {
replyContent = "您发送的是文本消息!"; }
//2.图片消息
else if (msgType.equals(MessageUtil.REQ_MESSAGE_TYPE_IMAGE)) {
replyContent = "您发送的是图片消息!";
}
//3.地理位置消息
else if (msgType.equals(MessageUtil.REQ_MESSAGE_TYPE_LOCATION)) { replyContent = "您发送的是地理位置消息 !";
}
//4.链接消息
else if (msgType.equals(MessageUtil.REQ_MESSAGE_TYPE_LINK)) {
replyContent = "您发送的是链接消息!";
}
//5.音频消息
else if (msgType.equals(MessageUtil.REQ_MESSAGE_TYPE_VOICE)) {
replyContent = "您发送的是音频消息!";
}
//6.事件推送
else if (msgType.equals(MessageUtil.REQ_MESSAGE_TYPE_EVENT)) {
replyContent=getReplyContentByEventType(eventType, eventKey);
}
//7.请求异常
else {
replyContent="请求处理异常,请稍候尝试!";
} return replyContent;
} /**
* @desc :5.处理消息:根据事件类型获取回复内容
*
* @param eventType 事件类型
* @param eventKey 事件key值
* @return
* String
*/
public static String getReplyContentByEventType(String eventType,String eventKey){ String respContent="";
// 订阅
if (eventType.equals(MessageUtil.EVENT_TYPE_SUBSCRIBE)) {
respContent = "欢迎关注!";
}
// 取消订阅
else if (eventType.equals(MessageUtil.EVENT_TYPE_UNSUBSCRIBE)) {
// TODO 取消订阅后用户再收不到公众号发送的消息,因此不需要回复消息
}
//上报地理位置事件
else if(eventType.equals("LOCATION")){ }
// 自定义菜单点击事件
else if (eventType.equals(MessageUtil.EVENT_TYPE_CLICK)) { if (eventKey.equals("12")) { } else if (eventKey.equals("13")) {
respContent = "周边搜索菜单项被点击!";
} else if (eventKey.equals("14")) {
respContent = "历史上的今天菜单项被点击!";
} else if (eventKey.equals("21")) {
respContent = "歌曲点播菜单项被点击!";
} else if (eventKey.equals("22")) { respContent = "经典游戏菜单项被点击!";
} else if (eventKey.equals("23")) {
respContent = "美女电台菜单项被点击!";
} else if (eventKey.equals("24")) {
respContent = "人脸识别菜单项被点击!";
} else if (eventKey.equals("25")) {
respContent = "聊天唠嗑菜单项被点击!";
} else if (eventKey.equals("31")) {
respContent = "Q友圈菜单项被点击!";
} else if (eventKey.equals("32")) {
respContent = "电影排行榜菜单项被点击!";
} else if (eventKey.equals("33")) {
respContent = "幽默笑话菜单项被点击!";
}
}
return respContent;
} }
三、参考资料
Java微信公众平台开发_03_消息管理之被动回复消息的更多相关文章
- Java微信公众平台开发_06_素材管理
一.本节要点 1.官方文档的media 这个media可以理解为文件,即我们需要以POST方式提交一个文件 2.媒体文件有效期 媒体文件在微信后台保存时间为3天,即3天后media_id失效. 二.代 ...
- Java微信公众平台开发_07_JSSDK图片上传
一.本节要点 1.获取jsapi_ticket //2.获取getJsapiTicket的接口地址,有效期为7200秒 private static final String GET_JSAPITIC ...
- Senparc.Weixin.MP SDK 微信公众平台开发教程(十五):消息加密
前不久,微信的企业号使用了强制的消息加密方式,随后公众号也加入了可选的消息加密选项.目前企业号和公众号的加密方式是一致的(格式会有少许差别). 加密设置 进入公众号后台的“开发者中心”,我们可以看到U ...
- Java微信公众平台开发_02_启用服务器配置
源码将在晚上上传到 github 一.准备阶段 需要准备事项: 1.一个能在公网上访问的项目: 见:[ Java微信公众平台开发_01_本地服务器映射外网 ] 2.一个微信公众平台账号: 去注册: ...
- Java微信公众平台开发【番外篇】(七)--公众平台测试帐号的申请
转自:http://www.cuiyongzhi.com/post/45.html 前面几篇一直都在写一些比较基础接口的使用,在这个过程中一直使用的都是我个人微博认证的一个个人账号,原本准备这篇是写[ ...
- Java微信公众平台开发--番外篇,对GlobalConstants文件的补充
转自:http://www.cuiyongzhi.com/post/63.html 之前发过一个[微信开发]系列性的文章,也引来了不少朋友观看和点评交流,可能我在写文章时有所疏忽,对部分文件给出的不是 ...
- C#微信公众号开发系列教程六(被动回复与上传下载多媒体文件)
微信公众号开发系列教程一(调试环境部署) 微信公众号开发系列教程一(调试环境部署续:vs远程调试) C#微信公众号开发系列教程二(新手接入指南) C#微信公众号开发系列教程三(消息体签名及加解密) C ...
- Java微信公众平台开发(十二)--微信用户信息的获取
转自:http://www.cuiyongzhi.com/post/56.html 前面的文章有讲到微信的一系列开发文章,包括token获取.菜单创建等,在这一篇将讲述在微信公众平台开发中如何获取微信 ...
- Java微信公众平台开发(一)--接入微信公众平台
转自:http://www.cuiyongzhi.com/post/38.html (一)接入流程解析 在我们的开发过程中无论如何最好的参考工具当然是我们的官方文档了:http://mp.weixin ...
随机推荐
- 重读金典------高质量C编程指南(林锐)-------第二章 程序的板式
2.1 空行 规则1:在每个类声明之后,每个函数定义结束之后加空行. 规则2:在某个函数体内,相关的不加空行,不相关的加空行. // 空行 void Function1(-) { - } // 空行 ...
- 百度Echart3
由于项目需要在首页搞一个订单数量的走势图,经过多方查找,体验,感觉ECharts不错,封装的很细,我们只需要看自己需要那种类型的图表,搞定好自己的json数据就OK.至于说如何体现出来,官网的教程很详 ...
- Linux下MySQL定时按日期备份数据
一.使用mysql内置命令 mysqldump Usage: mysqldump [OPTIONS] database [tables] mysqldump [OPTIONS] --databases ...
- 深度 | Facebook的图像识别很强大,一次开源了三款机器视觉工具(附论文)
http://mp.weixin.qq.com/s?__biz=MzA3MzI4MjgzMw==&mid=2650718597&idx=1&sn=56aa4e5deff9962 ...
- 检查Nginx的配置,重载配置和重启的方法
Nginx 安装后只有一个程序文件,本身并不提供各种管理程序,它是使用参数和系统信号机制对 Nginx 进程本身进行控制的. Nginx 的参数包括有如下几个: 可以这样使用 /usr/local/n ...
- Linux 命令汇总总结相关
玩了linux快一年,简单总结下网络相关的命令,具体每个命令的参数可以用到再细看. 1.ifconfig:查询.设置网卡和IP网段等相关参数,包括MTU.2.ifup.ifdown:这两个命令就是一个 ...
- 一些重要的地址:md5在线解密破解
md5在线解密破解:https://www.cmd5.com/
- MySql 三大知识点——索引、锁、事务(转)
1. 索引 索引,类似书籍的目录,可以根据目录的某个页码立即找到对应的内容. 索引的优点:1. 天生排序.2. 快速查找.索引的缺点:1. 占用空间.2. 降低更新表的速度. 注意点:小表使用全表扫描 ...
- 【题解】Counting D-sets(容斥+欧拉定理)
[题解]Counting D-sets(容斥+欧拉定理) 没时间写先咕咕咕. vjCodeChef - CNTDSETS 就是容斥,只是难了一二三四五\(\dots \inf\)点 题目大意: 给定你 ...
- BZOJ2328: [HNOI2011]赛车游戏
BZOJ2328: [HNOI2011]赛车游戏 Description 题解Here! 一开始被题面那一长串的描述吓到了,一直没敢做... 然后尝试着硬着头皮读懂题面. 然后...这不是贪心么??? ...