做这个之前搞懂微信公众平台和微信开发平台的区别
微信公众平台是我们常见的公众号,包括订阅号、服务号和企业号,主要用于不具备太强技术开放能力,拥有一定运营能力的品牌、商户、媒体以及个人,作为一个自媒体平台或者服务窗口来用,是面向更广大的人群使用的。
微信开放平台是一个开发者平台,针对的是有较强技术开发能力、能够研发同微信对接的应用的开发者来使用的,面向的是技术公司和开发者,不是面向所有人都可以使用的。
公众平台只有一个网页授权域名,一个公众号基本只能做一件事情,局限性太大了。假如一家大型公司。业务范围不只是一种,有衣食住行等等服务时,将其外包给其他公司做,那是不是要创建几个IP,几个域名?现在使用微信开放平台,只需要授权公众号列表,以及白名单设置即可,这样不同的公司,只需要专注于自己的模块功能,在同一个平台完成不同的功能,这样一个公众号可以实现多个功能。
微信开放平台,总结就一个字 坑 深渊巨坑 ,各种文档对于开发者来说十分不友好,很多描述都是模棱两可。掉进了一个坑爬起来还没走两步,又是一个坑。下面还是跟着文档来。
先来准备工作,配置好该有的信息,下载官方的代码,加密解密需要(坑)
白名单,就把IP地址填写好就行了,然后去这里下载官方提供的代码
流程:
小程序或者公众号授权给第三方平台的技术实现流程比较简单,以公众号为例,如下图所示:
第一步、推送component_verify_ticket协议
这个玩意巨鸡儿恶心,没有接口,官方说明不是很详细,用起来很难受,对推送来的信息进行解密,格式转换,获取
将获取到的信息写入到文件中token.properties便于存储读取数据,之后获取到的信息都写入到该文件中
package com.wlw.servlet;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.PrintWriter;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;
import com.wlw.preAuth.WXBiz.AesException;
import com.wlw.preAuth.WXBiz.WXBizMsgCrypt;
import com.wlw.util.PropUtil;
@WebServlet("/wx9325b66ca913be2e/callback") //平台的AppID,详细参考 授权事件接收URL http://w.ngrok.xiaomiqiu.cn/we3/$APPID$/callback
public class callback extends HttpServlet{
/**
*
*/
private static final long serialVersionUID = 1L;
private static final String appid = "wx9325b66ca913be2e";
private static final String token = "opentoken";
private static final String decryptStr = "9VQuyIhjcJLH4Q84Zk6XkHhAdXU4Msg3N4uYJkqHHl2";
private static final String secreet = "64e647c56e02e9a75fece3535b014acf";
private static final String component_access_token_url = "https://api.weixin.qq.com/cgi-bin/component/api_component_token";
private static final String pre_auth_code_url = "https://api.weixin.qq.com/cgi-bin/component/api_create_preauthcode?component_access_token=";
private static final String auth_page_url = "https://mp.weixin.qq.com/cgi-bin/componentloginpage?";
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
doPost(req, resp);
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
try { //后台获取微信推送消息的时间,便于记录
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
System.out.println("本次微信推送请求时间:"+sdf.format(new Date()));
Map map = xmlToMap(req); //将推送过来的XML的信息转换为map格式
//进行请求参数解密 调用官方的代码中的方法进行解密
WXBizMsgCrypt crypt = new WXBizMsgCrypt(token, decryptStr, appid);
String decryptStr = crypt.decrypt(map.get("Encrypt"));
/*上述解密之后为xml形式字符串,样例数据格式如下
1531378645
*/
Map decryptStrMap = xmlStrToMap(decryptStr);
//由于微信每10分钟会自动刷新ComponentVerifyTicket并通知,此处将解密后的ComponentVerifyTicket进行保存
PropUtil.setProp("token.properties", "component_verify_ticket",decryptStrMap.get("ComponentVerifyTicket"));
//返回suceess至微信请求端
PrintWriter pWriter = resp.getWriter();
pWriter.println("success");
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
//将微信推送的信息转换为map类型的方法
public static Map xmlToMap(HttpServletRequest request) throws IOException, DocumentException {
Map map = new HashMap<>();
SAXReader reader = new SAXReader();
InputStream in = request.getInputStream();
Document document = reader.read(in);
Element root = document.getRootElement();
List list = root.elements();
for (Element e : list) { //遍历测试是否获取到了
map.put(e.getName(), e.getText());
System.out.println(e.getName());
System.out.println(e.getText());
}
in.close();
return map;
}
//将解析处理后的XML格式的数据转换为Map格式
public static Map xmlStrToMap(String xmlStr){
SAXReader saxReader = new SAXReader();
Document document;
Map map = new HashMap<>();
try {
document = saxReader.read(new ByteArrayInputStream(xmlStr.getBytes("UTF-8")));
Element root = document.getRootElement();
List list = root.elements();
for (Element e : list) {
map.put(e.getName(), e.getText());
System.out.println(e.getName());
System.out.println(e.getText());
}
} catch (Exception e) {
e.printStackTrace();
}
return map;
}
}
AppID,详细参考 授权事件接收URL http://w.ngrok.xiaomiqiu.cn/we3/$APPID$/callback
public class callback extends HttpServlet{
/**
*
*/
private static final long serialVersionUID = 1L;
private static final String appid = "wx9325b66ca913be2e";
private static final String token = "opentoken";
private static final String decryptStr = "9VQuyIhjcJLH4Q84Zk6XkHhAdXU4Msg3N4uYJkqHHl2";
private static final String secreet = "64e647c56e02e9a75fece3535b014acf";
private static final String component_access_token_url = "https://api.weixin.qq.com/cgi-bin/component/api_component_token";
private static final String pre_auth_code_url = "https://api.weixin.qq.com/cgi-bin/component/api_create_preauthcode?component_access_token=";
private static final String auth_page_url = "https://mp.weixin.qq.com/cgi-bin/componentloginpage?";
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
doPost(req, resp);
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
try { //后台获取微信推送消息的时间,便于记录
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
System.out.println("本次微信推送请求时间:"+sdf.format(new Date()));
Map map = xmlToMap(req); //将推送过来的XML的信息转换为map格式
//进行请求参数解密 调用官方的代码中的方法进行解密
WXBizMsgCrypt crypt = new WXBizMsgCrypt(token, decryptStr, appid);
String decryptStr = crypt.decrypt(map.get("Encrypt"));
/*上述解密之后为xml形式字符串,样例数据格式如下
1531378645
*/
Map decryptStrMap = xmlStrToMap(decryptStr);
//由于微信每10分钟会自动刷新ComponentVerifyTicket并通知,此处将解密后的ComponentVerifyTicket进行保存
PropUtil.setProp("token.properties", "component_verify_ticket",decryptStrMap.get("ComponentVerifyTicket"));
//返回suceess至微信请求端
PrintWriter pWriter = resp.getWriter();
pWriter.println("success");
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
//将微信推送的信息转换为map类型的方法
public static Map xmlToMap(HttpServletRequest request) throws IOException, DocumentException {
Map map = new HashMap<>();
SAXReader reader = new SAXReader();
InputStream in = request.getInputStream();
Document document = reader.read(in);
Element root = document.getRootElement();
List list = root.elements();
for (Element e : list) { //遍历测试是否获取到了
map.put(e.getName(), e.getText());
System.out.println(e.getName());
System.out.println(e.getText());
}
in.close();
return map;
}
//将解析处理后的XML格式的数据转换为Map格式
public static Map xmlStrToMap(String xmlStr){
SAXReader saxReader = new SAXReader();
Document document;
Map map = new HashMap<>();
try {
document = saxReader.read(new ByteArrayInputStream(xmlStr.getBytes("UTF-8")));
Element root = document.getRootElement();
List list = root.elements();
for (Element e : list) {
map.put(e.getName(), e.getText());
System.out.println(e.getName());
System.out.println(e.getText());
}
} catch (Exception e) {
e.printStackTrace();
}
return map;
}
}
授权页面,点击连接进入回调地址,处理对应的逻辑
<%@ page language="java" contentType="text/html; charset=utf-8"
pageEncoding="utf-8"%>
Insert title here
点击授权
回调地址处理后续的业务
获取到了component_verify_ticket之后其他的不难了,就只是消息参数的组装和路径的填写,下面消息的组装就不使用实体类了,直接使用JSONObject
package com.wlw.servlet;
import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import com.wlw.util.PropUtil;
import com.wlw.util.wenxinUtil;
import net.sf.json.JSONObject;
@WebServlet("/auth")
public class auth extends HttpServlet{
/**
*
*/
private static final long serialVersionUID = 1L;
private static final String appid = "wx9325b66ca913be2e"; //AppID
private static final String token = "opentoken"; //消息校验Token
private static final String decryptStr = "9VQuyIhjcJLH4Q84Zk6XkHhAdXU4Msg3N4uYJkqHHl2"; //消息加解密Key
private static final String secreet = "****************"; //AppSecret
private static final String component_access_token_url = "https://api.weixin.qq.com/cgi-bin/component/api_component_token";
private static final String pre_auth_code_url = "https://api.weixin.qq.com/cgi-bin/component/api_create_preauthcode?component_access_token=";
private static final String auth_page_url = "https://mp.weixin.qq.com/cgi-bin/componentloginpage?";
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
doPost(req, resp);
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
try {
//第一步:获取第三方平台调用凭据 component_access_token
JSONObject json = new JSONObject();
json.put("component_appid", appid);
json.put("component_appsecret", secreet);
json.put("component_verify_ticket", PropUtil.getProp("token.properties", "component_verify_ticket"));
System.out.println("开始请求component_access_token_url,请求参数:"+json.toString());
JSONObject jsonResult = wenxinUtil.HttpRequest(component_access_token_url, "POST", json.toString());
PropUtil.setProp("token.properties", "component_access_token",jsonResult.get("component_access_token").toString());
System.out.println("获取第三方开放平台access_token,返回结果:"+jsonResult.toString());
//第二步:获取预授权码 pre_auth_code
JSONObject preJson = new JSONObject();
preJson.put("component_appid", appid);
System.out.println("开始请求pre_auth_code_url,url地址为:"+pre_auth_code_url+jsonResult.get("component_access_token")+",请求参数:"+preJson.toString());
JSONObject preResult = wenxinUtil.HttpRequest(pre_auth_code_url+jsonResult.get("component_access_token"), "POST", preJson.toString());
System.out.println("获取预授权码接口返回结果:"+preResult.toString());
String redirectUrl = auth_page_url+"component_appid="+appid+"&pre_auth_code="+preResult.get("pre_auth_code")+"&redirect_uri=http://w.ngrok.xiaomiqiu.cn/we3/nofiy&auth_type=3";
System.out.println("重定向微信开放平台授权页面url:"+redirectUrl);
resp.sendRedirect(redirectUrl);
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
package com.wlw.servlet;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.Date;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import com.wlw.util.PropUtil;
import com.wlw.util.wenxinUtil;
import net.sf.json.JSONObject;
@WebServlet("/nofiy")
public class nofiy extends HttpServlet{
/**
*
*/
private static final long serialVersionUID = 1L;
private static final String appid = "wx9325b66ca913be2e";
private static final String token = "opentoken";
private static final String decryptStr = "9VQuyIhjcJLH4Q84Zk6XkHhAdXU4Msg3N4uYJkqHHl2";
private static final String secreet = "64e647c56e02e9a75fece3535b014acf";
private static final String component_access_token_url = "https://api.weixin.qq.com/cgi-bin/component/api_component_token";
private static final String pre_auth_code_url = "https://api.weixin.qq.com/cgi-bin/component/api_create_preauthcode?component_access_token=";
private static final String auth_page_url = "https://mp.weixin.qq.com/cgi-bin/componentloginpage?";
private static final String authorizer_access_token_url = "https://api.weixin.qq.com/cgi-bin/component/api_query_auth?component_access_token=";
private static final String api_authorizer_token_url = "https:// api.weixin.qq.com /cgi-bin/component/api_authorizer_token?component_access_token=";
private static final String api_get_authorizer_info_url="https://api.weixin.qq.com/cgi-bin/component/api_get_authorizer_info?component_access_token=";
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
doPost(req, resp);
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
System.out.println("本次微信授权回调请求时间:"+sdf.format(new Date()));
//获取微信回调返回的授权码集过期时间,并进行存储
String auth_code = req.getParameter("auth_code");
String expires_in = req.getParameter("expires_in");
PropUtil.setProp("token.properties", "auth_code",auth_code);
PropUtil.setProp("token.properties", "expires_in",expires_in);
//4.使用授权码换取公众号或小程序的接口调用凭据和授权信息
JSONObject json = new JSONObject();
json.put("component_appid", appid);
json.put("authorization_code", auth_code);
JSONObject jsonResult = wenxinUtil.HttpRequest(authorizer_access_token_url, "POST", json.toString());
//授权方的AppID authorizer_appid
String authorizer_appid=jsonResult.get("authorizer_appid").toString();
//授权方接口调用凭据 authorizer_access_token
String authorizer_access_token=jsonResult.get("authorizer_access_token").toString();
//接口调用凭据刷新令牌 authorizer_refresh_token
String authorizer_refresh_token=jsonResult.get("authorizer_refresh_token").toString();
// (加上 授权方的AppID 区分)
PropUtil.setProp("token.properties", authorizer_appid+"authorizer_appid",authorizer_appid);
PropUtil.setProp("token.properties", authorizer_appid+"authorizer_access_token",authorizer_access_token);
PropUtil.setProp("token.properties", authorizer_appid+"authorizer_refresh_token",authorizer_refresh_token);
//5.获取(刷新)授权公众号或小程序的接口调用凭据(令牌)
/* JSONObject jsonObject = new JSONObject();
jsonObject.put("component_appid", appid);
jsonObject.put("authorizer_appid", authorizer_appid);
jsonObject.put("authorizer_refresh_token", authorizer_refresh_token);
JSONObject preResult=wenxinUtil.HttpRequest(api_authorizer_token_url, "POST", jsonObject.toString());
//授权方令牌 authorizer_access_token
String authorizer_access_token=preResult.get("authorizer_access_token").toString();
//刷新令牌 authorizer_refresh_token
String authorizer_refresh_token=preResult.get("authorizer_refresh_token").toString();
PropUtil.setProp("token.properties", authorizer_appid+"authorizer_access_token",authorizer_access_token);
PropUtil.setProp("token.properties", authorizer_appid+"authorizer_refresh_token",authorizer_refresh_token);*/
//6.获取授权方的帐号基本信息
JSONObject object=new JSONObject();
object.put("component_appid", appid);
object.put("authorizer_appid", authorizer_appid);
JSONObject result=wenxinUtil.HttpRequest(api_get_authorizer_info_url, "POST", object.toString());
System.out.println("result=\t"+result);
}
}
另外的两个工具类
工具类一 : 将获取到的数据写入文件中
package com.wlw.util;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.Properties;
public class PropUtil {
public static void main(String[] args) {
System.out.println(PropUtil.getProp("token.properties", "name"));
PropUtil.setProp("token.properties", "name", "11111111133333111");
}
/**
* * 获取文件中指定key的值
* @param filePath文件路径(根目录下)
* @param keyֵ
*/
public static String getProp(String filename, String key) {
try {
Properties props = new Properties();
String filepath = Thread.currentThread().getContextClassLoader().getResource("").getPath() + filename;
File file = new File(filepath);
InputStream in = new FileInputStream(file);
props.load(in);
in.close();
String value = props.getProperty(key);
return value == null ? "":value;
} catch (Exception e)
{
e.printStackTrace();
return null;
}
}
/**
* *设置指定key值,并保存至文件
*
* @param filePath文件路径(根目录下)
*
*/
public static void setProp(String filename, String key, String value) {
try {
Properties prop = new Properties();
String filepath = Thread.currentThread().getContextClassLoader().getResource("").getPath() + filename;
File file = new File(filepath);
InputStream in = new FileInputStream(file);
prop.load(in);
in.close();
System.out.println("filepath---------->" + filepath);
OutputStream fos = new FileOutputStream(file);
prop.setProperty(key, value);
prop.store(fos, "Update '" + key + "' value");
fos.flush();
fos.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}
工具类二 : 处理htpps请求
package com.wlw.util;
/**
* 请求数据通用类*/
import java.io.BufferedReader;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import com.wlw.menu.Menu;
import net.sf.json.JSONObject;
import wlw.wechat.msg.model.media.AccessToken;
public class wenxinUtil {
/**
* 发起https请求并获取结果
*
* @param requestUrl 请求地址
* @param requestMethod 请求方式(GET、POST)
* @param outputStr 提交的数据
* @return JSONObject 返回JSONObject对象(通过JSONObject.get(key)的方式获取json对象的属性值)
* @author Engineer.Jsp
*/
public static JSONObject HttpRequest(String request , String RequestMethod , String output){
@SuppressWarnings("unused")
// 定义 JSONObject 对象
JSONObject jsonObject = null;
// buffer 缓冲流
StringBuffer buffer = new StringBuffer();
try {
//建立连接
URL url = new URL(request);
// 获取 HttpURLConnection对象
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
// 设置输出流
connection.setDoOutput(true);
// 设置输入流
connection.setDoInput(true);
// 是否使用缓存
connection.setUseCaches(false);
// 请求方式
connection.setRequestMethod(RequestMethod);
// 流不为空
if(output!=null){
// 获取流
OutputStream out = connection.getOutputStream();
// 写数据流 UTF-8 编码
out.write(output.getBytes("UTF-8"));
// 关闭
out.close();
}
//流处理
InputStream input = connection.getInputStream();
// 读取流 UTF-8 编码
InputStreamReader inputReader = new InputStreamReader(input,"UTF-8");
// 缓冲流 buffer
BufferedReader reader = new BufferedReader(inputReader);
// 定义 String 来读取每一行
String line;
// 循环读取每一行,知道数据没有了,意思是读取完了
while((line=reader.readLine())!=null){
// 添加到StringBuffer字符流里面
buffer.append(line);
}
//关闭连接、释放资源
reader.close();
// 关闭
inputReader.close();
// 关闭
input.close();
// 强制释放对象,缓解JVM内存
input = null;
// 关闭连接
connection.disconnect();
// 把写到 StringBuffer 字符流的数据用 JSONObject.fromObject 函数转换成 JSONObject 对象
jsonObject = JSONObject.fromObject(buffer.toString());
System.out.println(buffer.toString());
} catch (Exception e) {
}
// 如果为空,返回一个空对象,否则返回jsonObject实例对象
return jsonObject;
}
}
然后开启Tomcat,打开JSP页面等微信端发送消息来。
发送信息来了后点击登录,会跳转
用微信扫码即可授权成功!!!
后台返回的消息