微信公众平台java接入
环境准备
java开发环境不必多少,由于微信公众平台是线上平台所以你提供的url肯定是要外网可以访问的,所以我们需要第三方开发平台把我们的内网映射成外网,我使用的是Sunny-Ngrok,免费且方便,进入网页下载客户端,怎么使用后面再说,我们先需要配置隧道如下:
注意:必须以http://或https://开头,分别支持80端口和443端口。本地端口即为本地服务需配置的
开通隧道后如下:
本地发布后就可以使用客户端进行映射,如下:
按照提示填写Sunny-Ngrok的隧道id就行。
填写服务配置
登录微信公众平台,找到开发的基础配置,如下:
url:必须以http://或https://开头,分别支持80端口和443端口。用来接收微信消息和事件接口的url。
token:用户随意填写必须为英文或数字,长度为3-32字符。用来生成签名,微信服务器和自己服务器进行对比验证。
EncodingAESKey:消息加***由43位字符组成,可随机修改,字符范围为A-Z,a-z,0-9。开发者自己填写或者自动生成,用于消息加***。
点击提交后微信服务器会和我们的服务器进行验证服务地址验证性,下面将介绍。
验证服务地址有效性
开发者提交信息后,微信服务器将发送GET请求到填写的服务器地址URL上,GET请求携带参数如下表所示:
下面我们通过程序来看看怎么利用这些参数对服务地址进行有效性验证。
package weixinjava;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
public class WXServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
String signature=req.getParameter("signature");
String timestamp=req.getParameter("timestamp");
String nonce=req.getParameter("nonce");
String echostr=req.getParameter("echostr");
System.out.println("__________");
PrintWriter out = resp.getWriter();
if (CheckUtil.checkSignature(signature, timestamp, nonce)) {
out.println(echostr);
}
}
}
从req中获得参数后进行验证,如下代码:
package weixinjava;
import java.security.MessageDigest;
import java.util.Arrays;
public class CheckUtil {
//和微信公众平台配置的token须保持一致
private static final String token = "jby646277";
public static boolean checkSignature(String signature,String timestamp,String nonce) {
System.out.println(signature);
String[] arr = new String[]{token,timestamp,nonce};
//字典排序
Arrays.sort(arr);
//生成字符串
StringBuffer content = new StringBuffer();
for (int i = 0; i < arr.length; i++) {
content.append(arr[i]);
}
//sha1加密
String temp = getSha1(content.toString());
return temp.equals(signature);
}
private static String getSha1(String str) {
if (str == null || str.length() == 0) {
return null;
}
char hexDigits[] = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' };
MessageDigest mdTemp;
try {
mdTemp = MessageDigest.getInstance("SHA1");
mdTemp.update(str.getBytes("UTF-8"));
byte[] md = mdTemp.digest();
int j = md.length;
char buf[] = new char[j * 2];
int k = 0;
for (int i = 0; i < j; i++) {
byte b0 = md[i];
buf[k++] = hexDigits[b0 >>> 4 & 0xf];
buf[k++] = hexDigits[b0 & 0xf];
}
return new String(buf);
} catch (Exception e) {
return null;
}
}
}
可以看出验证步骤如下:
- 将token,timestamp,nonce进行字典排序,拼接成一个字符串。
- 将字符串进行sha1加密。
- 将加密后的字符创和signature比较。true则提交成功,请原样返回echostr参数内容;false则为失败。
看到这里很容易明白关键在于你程序中的token是否和公众平台上配置的一样,那么为啥不直接配置的和程序的token进行对比呢?显然不能这样,必须使用转换(加密)后的字符串,那直接对token字符串进行转换不就好了?注意到有个时间戳和随机数,他们是保证提交请求的实时性和随机性,这样能保证每次转换的字符串都不一样,相对而言较为安全,虽然知道算法根据结果完全可以反算得出token。
补充:
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://xmlns.jcp.org/xml/ns/javaee" xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd" id="WebApp_ID" version="3.1">
<display-name>WeiXin</display-name>
<servlet>
<servlet-name>weixin</servlet-name>
<servlet-class>weixinjava.WXServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>weixin</servlet-name>
<url-pattern>/wx</url-pattern>
</servlet-mapping>
<welcome-file-list>
<welcome-file>index.jsp</welcome-file>
</welcome-file-list>
</web-app>
需要的jar自己去网上下载导入,或者配置pom.xml
消息接收与响应
当用户向你的公众号发送消息时,微信服务器将post消息的xml数据包到你的url上。微信服务器在五秒内未得到响应会断掉连接,并发起重试,重试三次,可以回复空串,微信服务器不会做出任何响应。
文本消息
xml数据包结构:
<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>
我们会把微信服务器post过来的xml转换为map方便对应取出,然后我们需要返回给微信服务器的消息格式肯定也是xml,我们可以利用第三方工具直接把对象转xml,具体见下面代码。
返回文本消息封装对象:
package weixinjava;
public class TextMessage {
private String ToUserName;
private String FromUserName;
private long CreateTime;
private String MsgType;
private String Content;
private String 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 String getContent() {
return Content;
}
public void setContent(String content) {
Content = content;
}
public String getMsgId() {
return MsgId;
}
public void setMsgId(String msgId) {
MsgId = msgId;
}
}
dopost:
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
req.setCharacterEncoding("UTF-8");
resp.setCharacterEncoding("UTF-8");
Map<String,String> map=MessageUtil.xmlToMap(req);
String ToUserName=map.get("ToUserName");
String FromUserName=map.get("FromUserName");
String MsgType=map.get("MsgType");
String Content=map.get("Content");
String message=null;
PrintWriter out=resp.getWriter();
if(MessageUtil.MESSAGE_TEXT.equals(MsgType)){
if(Content.equals("1")){
message=MessageUtil.returnText(ToUserName,FromUserName,"峰哥很帅");
}else if(Content.equals("2")){
message=MessageUtil.returnText(ToUserName,FromUserName,"峰哥很帅");
}else if(Content.equals("3")){
message=MessageUtil.returnText(ToUserName,FromUserName,"别说了");
}else{
message=MessageUtil.returnText(ToUserName,FromUserName,"呵呵");
}
}else if(MessageUtil.MESSAGE_EVENT.equals(MsgType)){
String eventType=map.get("Event");
if(MessageUtil.MESSAGE_SUBSCRIBE.equals(eventType)){
message=MessageUtil.returnText(ToUserName,FromUserName,MessageUtil.menuText());
}
}
out.print(message);
out.close();
}
工具代码:
package weixinjava;
import com.thoughtworks.xstream.XStream;
import org.dom4j.Document;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
import java.io.InputStream;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class MessageUtil{
public static final String MESSAGE_TEXT="text";
public static final String MESSAGE_EVENT="event";
public static final String MESSAGE_SUBSCRIBE="subscribe";
public static final String MESSAGE_UNSUBSCRIBE="unsubscribe";
public static Map<String,String> xmlToMap(HttpServletRequest req) throws IOException {
Map<String,String> map=new HashMap<String, String>();
InputStream in=req.getInputStream();
try {
SAXReader reader=new SAXReader();
Document doc=reader.read(in);
Element root=doc.getRootElement();
List<Element> list=root.elements();
for (Element element:list) {
map.put(element.getName(),element.getText());
}
} catch (Exception e) {
e.printStackTrace();
}finally {
in.close();
}
return map;
}
public static String textMessageToXml(TextMessage textMessage){
XStream xStream=new XStream();
xStream.alias("xml",textMessage.getClass());
return xStream.toXML(textMessage);
}
public static String menuText(){
StringBuffer sb=new StringBuffer();
sb.append("哈哈,王者之峰回來了,有事沒事來看下哦。\n");
sb.append("沒事可以看我的博客:http://blog.csdn.net/qq_23473123\n");
sb.append("欢迎回复文字。\n");
sb.append("1:峰哥很帅。\n");
sb.append("2:峰哥很帅。\n");
sb.append("3:别说了。\n");
return sb.toString();
}
public static String returnText(String ToUserName,String FromUserName,String content){
String message=null;
TextMessage text=new TextMessage();
text.setFromUserName(ToUserName);
text.setToUserName(FromUserName);
text.setCreateTime(new Date().getTime());
text.setMsgType(MessageUtil.MESSAGE_TEXT);
text.setContent(content);
message=MessageUtil.textMessageToXml(text);
return message;
}
}
其实蛮简单的从req里获取消息,resp返回消息,按照格式来就行。
结果如下:
图文消息
xml数据包结构:
<xml>
<ToUserName><![CDATA[toUser]]></ToUserName>
<FromUserName><![CDATA[fromUser]]></FromUserName>
<CreateTime>12345678</CreateTime>
<MsgType><![CDATA[news]]></MsgType>
<ArticleCount>2</ArticleCount>
<Articles>
<item>
<Title><![CDATA[title1]]></Title>
<Description><![CDATA[description1]]></Description>
<PicUrl><![CDATA[picurl]]></PicUrl>
<Url><![CDATA[url]]></Url>
</item>
<item>
<Title><![CDATA[title]]></Title>
<Description><![CDATA[description]]></Description>
<PicUrl><![CDATA[picurl]]></PicUrl>
<Url><![CDATA[url]]></Url>
</item>
</Articles>
</xml>
返回图文消息封装对象:
package Message;
import java.util.List;
public class NewsMassage extends BaseMassage{
private int ArticleCount;
private List<News> Articles;
public int getArticleCount() {
return ArticleCount;
}
public void setArticleCount(int articleCount) {
ArticleCount = articleCount;
}
public List<News> getArticles() {
return Articles;
}
public void setArticles(List<News> articles) {
Articles = articles;
}
}
package Message;
public class BaseMassage{
private String ToUserName;
private String FromUserName;
private long CreateTime;
private String MsgType;
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;
}
}
package Message;
public class News{
private String Title;
private String Description;
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;
}
}
doPost
List<News> newsList=new ArrayList<News>();
News news=new News();
news.setDescription("互相学习,互相进步哦。");
news.setPicUrl("http://wanghf.ngrok.cc/images/blog.png");
news.setTitle("王者之峰博客");
news.setUrl("http://blog.csdn.net/qq_23473123");
newsList.add(news);
message=MessageUtil.returnNews(ToUserName,FromUserName,newsList.size(),newsList);
public static String returnNews(String ToUserName,String FromUserName,int ArticleCount,List<News> newsList){
String message=null;
NewsMassage news=new NewsMassage();
news.setToUserName(FromUserName);
news.setFromUserName(ToUserName);
news.setCreateTime(new Date().getTime());
news.setMsgType(MessageUtil.MESSAGE_NEWS);
news.setArticleCount(ArticleCount);
news.setArticles(newsList);
message=MessageUtil.newsMessageToXml(news);
return message;
}
public static String newsMessageToXml(NewsMassage newsMessage){
XStream xStream=new XStream();
xStream.alias("xml",newsMessage.getClass());
xStream.alias("item",new News().getClass());
return xStream.toXML(newsMessage);
}
结果展示:
图片消息
xml数据包结构:
<xml>
<ToUserName><![CDATA[toUser]]></ToUserName>
<FromUserName><![CDATA[fromUser]]></FromUserName>
<CreateTime>12345678</CreateTime>
<MsgType><![CDATA[image]]></MsgType>
<Image>
<MediaId><![CDATA[media_id]]></MediaId>
</Image>
</xml>
可以看到图片消息的xml比较简单,但是注意到有个MediaId,这个是上传图片时微信服务器产生的,所以我们想实现回复图文消息必须得先上传图片,然后就简单了可以参考上面回复文字的,我们这里重点放在图片上传,看到微信公众平台开发者文档
可以看到需要access_token,那么这个是什么呢?怎么获得?
公众号只要调用接口就需要使用access_token,作为调用凭证,并且具有时效性过两个小时就会刷新,并且获取次数是有限的所以我们自己需要保存
获取access_token
接下来我们通过java代码来获取access_token。
package weixinjava;
import Message.AccessToken;
import net.sf.json.JSONObject;
import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.util.EntityUtils;
import java.io.IOException;
public class WeiXinUtil {
public static AccessToken getAccessToken(String appid, String secret) throws IOException {
AccessToken token = new AccessToken();
String url = "https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=APPID&secret=APPSECRET".replace("APPID", appid).replace("APPSECRET", secret);
JSONObject jsonObject=doGetStr(url);
if(jsonObject!=null){
token.setAccessToken(jsonObject.getString("access_token"));
token.setExpiresIn(jsonObject.getInt("expires_in"));
}
return token;
}
/*
*get请求
*/
public static JSONObject doGetStr(String url) throws IOException {
JSONObject jsonObject=null;
DefaultHttpClient client=new DefaultHttpClient();
HttpGet httpGet=new HttpGet(url);
HttpResponse httpResponse=client.execute(httpGet);
HttpEntity httpEntity=httpResponse.getEntity();
if(httpEntity!=null){
String result= EntityUtils.toString(httpEntity,"UTF-8");
jsonObject=JSONObject.fromObject(result);
}
return jsonObject;
}
public static void main(String[] args) throws IOException {
System.out.println(getAccessToken("wx1c7356240130df4a","067707aa0936ce56160ee1a470f811b6").getAccessToken());
}
}
上传图片
接下来我们通过java代码给微信服务器上传图片
package weixinjava;
import Message.AccessToken;
import net.sf.json.JSONObject;
import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.util.EntityUtils;
import java.io.*;
import java.net.HttpURLConnection;
import java.net.URL;
public class WXUtil{
public static AccessToken getAccessToken(String appid, String secret) throws IOException {
AccessToken token = new AccessToken();
String url = "https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=APPID&secret=APPSECRET".replace("APPID", appid).replace("APPSECRET", secret);
JSONObject jsonObject = doGetStr(url);
if (jsonObject != null) {
token.setAccessToken(jsonObject.getString("access_token"));
token.setExpiresIn(jsonObject.getInt("expires_in"));
}
return token;
}
public static JSONObject doGetStr(String url) throws IOException {
JSONObject jsonObject = null;
DefaultHttpClient client = new DefaultHttpClient();
HttpGet httpGet = new HttpGet(url);
HttpResponse httpResponse = client.execute(httpGet);
HttpEntity httpEntity = httpResponse.getEntity();
if (httpEntity != null) {
String result = EntityUtils.toString(httpEntity, "UTF-8");
jsonObject = JSONObject.fromObject(result);
}
return jsonObject;
}
public static String uploadFile(String filePath, String accessToken, String type) throws IOException {
File file = new File(filePath);
if (!file.exists() || !file.isFile()) {
throw new IOException("文件不存在");
}
String url = "https://api.weixin.qq.com/cgi-bin/media/upload?access_token=ACCESS_TOKEN&type=TYPE".replace("ACCESS_TOKEN", accessToken).replace("TYPE", type);
URL urlObj = new URL(url);
//连接
HttpURLConnection con = (HttpURLConnection) urlObj.openConnection();
con.setRequestMethod("POST");
con.setDoInput(true);
con.setDoOutput(true);
con.setUseCaches(false);
//设置请求头信息
con.setRequestProperty("Connection", "Keep-Alive");
con.setRequestProperty("Charset", "UTF-8");
//设置边界
String BOUNDARY = "----------" + System.currentTimeMillis();
con.setRequestProperty("Content-Type", "multipart/form-data; boundary=" + BOUNDARY);
StringBuilder sb = new StringBuilder();
sb.append("--");
sb.append(BOUNDARY);
sb.append("\r\n");
sb.append("Content-Disposition: form-data;name=\"file\";filename=\"" + file.getName() + "\"\r\n");
sb.append("Content-Type:application/octet-stream\r\n\r\n");
byte[] head = sb.toString().getBytes("utf-8");
//获得输出流
OutputStream out = new DataOutputStream(con.getOutputStream());
//输出表头
out.write(head);
//文件正文部分
//把文件已流文件的方式 推入到url中
DataInputStream in = new DataInputStream(new FileInputStream(file));
int bytes = 0;
byte[] bufferOut = new byte[1024];
while ((bytes = in.read(bufferOut)) != -1) {
out.write(bufferOut, 0, bytes);
}
in.close();
//结尾部分
byte[] foot = ("\r\n--" + BOUNDARY + "--\r\n").getBytes("utf-8");//定义最后数据分隔线
out.write(foot);
out.flush();
out.close();
StringBuffer buffer = new StringBuffer();
BufferedReader reader = null;
String result = null;
try {
//定义BufferedReader输入流来读取URL的响应
reader = new BufferedReader(new InputStreamReader(con.getInputStream()));
String line = null;
while ((line = reader.readLine()) != null) {
buffer.append(line);
}
if (result == null) {
result = buffer.toString();
}
} catch (IOException e) {
e.printStackTrace();
} finally {
if (reader != null) {
reader.close();
}
}
JSONObject jsonObj = JSONObject.fromObject(result);
System.out.println(jsonObj);
String typeName = "media_id";
if (!"image".equals(type)) {
typeName = type + "_media_id";
}
String mediaId = jsonObj.getString(typeName);
return mediaId;
}
public static void main(String[] args) throws IOException {
// System.out.println("accessToken:"+getAccessToken("wx1c7356240130df4a","067707aa0936ce56160ee1a470f811b6").getAccessToken());
System.out.println("mediaId:"+uploadFile("C:/Users/Administrator/Pictures/huangtugaoyuan.jpg","ejn9c4ptsC0JfYY9s8duoDbUWnkLTWJdbTJuhYNgG-EOhP3YNUb6qyugdX2bNzjad0I1GuOD8_I531SwWGgWbYw1f_wF73gAlazn5qIBwInz8pCSuP13uo-OlVhkz5qqTMBdAAAKDS","image"));
}
}
现在有了mediaId,可以进行图片回复了。结果如下: