说在前面
根据产品需求,需要在已有平台上接入微信第三方平台,这也是我第一次开发微信相关内容,在这期间走了不少弯路,今天有点时间写下来,希望能对新的开发者有点帮助,少踩点坑。
解密方式
开放平台和公众平台中都有相关的解密实例代码,但想直接使用的话,还需要进行加工处理,这里贴出我自己用的解密类:
package com.cn.controller.weChat.util;
import javax.servlet.http.HttpServletRequest;
/**
* Created by YancyPeng on 2018/10/16.
* 微信消息加解密工具
*/
public class SignUtil {
private static WXBizMsgCrypt pc;
//在第三方平台填写的token,该token可以自己随意填写
private static String token = "";
//在第三方平台填写的加解密key,这个也是自己随意填写,但是key的长度要符合微信规定
private static String encodingAesKey = "XXXXXXXXXX";
//公众号第三方平台的appid,不用纠结该appid,在创建完第三方平台后微信就会给到你
private static String appId = "XXXXXXX";
//微信加密签名
private static String msg_signature;
//时间戳
private static String timestamp;
//随机数
private static String nonce;
static {
try {
pc = new WXBizMsgCrypt(token, encodingAesKey, appId);
} catch (AesException e) {
e.printStackTrace();
}
}
/**
* @param request
* @param encryptMsg 加密的消息
* @return 返回解密后xml格式字符串消息
*/
public static String decryptMsg(HttpServletRequest request, String encryptMsg) {
String result = "";
//获取微信加密签名
msg_signature = request.getParameter("msg_signature");
//时间戳
timestamp = request.getParameter("timestamp");
//随机数
nonce = request.getParameter("nonce");
System.out.println("微信加密签名为:-----------------" +msg_signature);
try {
result = pc.decryptMsg(msg_signature, timestamp, nonce, encryptMsg);
} catch (AesException e) {
e.printStackTrace();
}
return result;
}
/**
* @param replyMsg 需要加密的xml格式字符串
* @return 加密过后的xml格式字符串
*/
public static String encryptMsg(String replyMsg) {
try {
replyMsg = pc.encryptMsg(replyMsg, timestamp, nonce);
} catch (AesException e) {
e.printStackTrace();
}
return replyMsg;
}
private SignUtil() {
}
}
这其中用到的相关类就是微信官方提供的示例代码,下载即可,接下来进入正文
不用纠结appid、token和加解密key,appid创建完第三方平台微信就会给到你,token和加解密key都可以随便填,但是要符合微信的规范
获取ticket
微信服务器回向授权事件接收URL没隔10分钟定时推送ticket,在收到ticket后需要进行解密获取,接收到后必须直接返回success
/**
* @param postdata 微信发送过来的加密的xml格式数据,通过在创建第三方平台是填写的授权事件URL关联
* 除了接受授权事件(成功授权、取消授权以及授权更新)外,在接受ticket及授权后回调URI也会用到该方法
* @return 根据微信开放平台规定,接收到授权事件后只需要直接返回success
*/
@RequestMapping(value = "/event", method = RequestMethod.POST)
// @ApiOperation(value = "接受授权事件通知和ticket", notes = "返回sucess",
// consumes = "application/json", produces = "application/json", httpMethod = "POST")
// @ApiImplicitParams({})
// @ApiResponses({
// @ApiResponse(code = 200, message = "成功", response = JSONObject.class),
// @ApiResponse(code = 500, message = "失败", response = JSONObject.class)
// })
public String receiveAuthorizedEvent(@RequestBody(required = false) String postdata, HttpServletRequest request) {
System.out.println("调用接受授权事件通知的方法 的入参为:-----------------------" + postdata);
String decryptXml = SignUtil.decryptMsg(request, postdata); // 获得解密后的xml文件
String infoType; // 事件类型
try {
authorizedMap = XmlUtil.xmlToMap(decryptXml); // 获得xml文件对应的map
System.out.println("解密后的xml文件为:------" + authorizedMap);
} catch (Exception e) {
e.printStackTrace();
}
if ((infoType = authorizedMap.get("InfoType")).equals("component_verify_ticket")) { //如果是接受ticket
System.out.println("接受到微信发送的ticket,ticket = " + authorizedMap.get("ComponentVerifyTicket"));
this.setPublicAuthorizedCode(authorizedMap.get("ComponentVerifyTicket")); // 根据ticket去刷新公共授权码
} else if (infoType.equals("unauthorized")) { // 接受的是取消授权事件,将微信授权状态设为3
String authorizerAppid = authorizedMap.get("AuthorizerAppid");
JSONObject params = new JSONObject();
params.put("authorizerAppid", authorizerAppid);
params.put("authorizerState", "3");
int update = iWeChatInfoSV.updateByAuthAppid(params);
System.out.println("微信端取消授权 【0:失败,1:成功】 update = " + update);
} // 如果是授权成功和更新授权事件,则什么都不做,在authorizedSuccess中进行处理
return "success";
}
根据ticket、appid和appsecret来获得token
由于该token的有效时间为2个小时,在我的设计中,数据库表中有一个token_update_time字段,每次接收到ticket就取当前时间与updatetime做对比,如果超过1小时50分,就调用接口重新获取token
,当然取updatetime操作肯定做了缓存0.0
/**
* 刷新公共授权码,由于component_access_token需要2个小时刷新一次,所以需要判断本地表中存在的第三方接口调用凭据updateTime和当前时间的差值
* 如果超过1小时50分就调用微信接口更新,否则不做任何操作
*
* @param componentVerifyTicket 根据最近可用的component_verify_ticket来获得componentAccessToken和preAuthCode
*/
private void setPublicAuthorizedCode(String componentVerifyTicket) {
// 根据tenantId查出 当前公共授权码表中的 ComponentVerifyTicket
System.out.println("执行controller层 刷新公共授权码的方法 的入参为: componentVerifyTicket = " + componentVerifyTicket);
AccessTokenInfo accessTokenInfo = iAccessTokenInfoSV.selectActInfo();
if (null != accessTokenInfo) { // 如果不是首次接受ticket
Long tokenUpdateTime = (new SimpleDateFormat("yyyy-MM-dd HH:mm:ss")).parse(accessTokenInfo.getTokenUpdateTime(),
new ParsePosition(0)).getTime();
Long currentTime = System.currentTimeMillis();
if ((currentTime - tokenUpdateTime) / 1000 >= 6600) { // 如果大于等于1小时50分
// 获取 component_access_token
JSONObject params = new JSONObject();
params.put("component_verify_ticket", componentVerifyTicket);
params.put("component_appsecret", ComponentAppSecret);
params.put("component_appid", ComponentAppId);
String result = HttpClientUtil.httpPost("https://api.weixin.qq.com/cgi-bin/component/api_component_token", params.toJSONString());
System.out.println("获取component_access_token的结果为:---------------------" + result);
String componentAccessToken = JSONObject.parseObject(result).getString("component_access_token");
if (!StringUtils.isEmpty(componentAccessToken)) {
// 拼装参数,添加到本地数据库
JSONObject tokenParams = new JSONObject();
tokenParams.put("componentVerifyTicket", componentVerifyTicket);
tokenParams.put("componentAccessToken", componentAccessToken);
tokenParams.put("tokenUpdateTime", new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(currentTime));
int update = iAccessTokenInfoSV.updateAccessToken(tokenParams);
System.out.println("更新第三方接口调用凭据component_access_token 【0:失败,1:成功】 update = " + update);
} else {
System.out.println("Controller层执行 《setPublicAuthorizedCode》方法时返回值有错---------");
}
} // 如果小于则不需要更新
} else { //首次接收ticket,需要走一遍整个流程,获取component_access_token和pre_auth_code,添加进本地数据库
// 首先获取component_access_token
JSONObject params = new JSONObject();
params.put("component_verify_ticket", componentVerifyTicket);
params.put("component_appsecret", ComponentAppSecret);
params.put("component_appid", ComponentAppId);
String result = HttpClientUtil.httpPost("https://api.weixin.qq.com/cgi-bin/component/api_component_token", params.toJSONString());
System.out.println("首次获取component_access_token的结果为:---------------------" + result);
String componentAccessToken = JSONObject.parseObject(result).getString("component_access_token");
// 获取pre_auth_code
JSONObject preParams = new JSONObject();
preParams.put("component_appid", ComponentAppId);
result = HttpClientUtil.httpPost("https://api.weixin.qq.com/cgi-bin/component/api_create_preauthcode?component_access_token=" + componentAccessToken, preParams.toJSONString());
System.out.println("首次获取的pre_auth_code为:------------------------" + result);
String preAuthCode = JSONObject.parseObject(result).getString("pre_auth_code");
// 封装参数,添加进本地数据库
if (!StringUtils.isEmpty(componentAccessToken) && !StringUtils.isEmpty(preAuthCode)){
JSONObject tokenParams = new JSONObject();
tokenParams.put("componentVerifyTicket", componentVerifyTicket);
tokenParams.put("componentAccessToken", componentAccessToken);
tokenParams.put("preAuthCode", preAuthCode);
int insert = iAccessTokenInfoSV.insertSelective(tokenParams);
System.out.println("首次添加公共授权码进本地数据库 【0:失败,1:成功】 insert = " + insert);
}else {
System.out.println("首次请求componentAccessToken或者preAuthCode时失败----------");
}
}
}
根据token来获得pre_auth_code
预授权码的有效时间为10分钟,且该预授权码只能使用一次
,就是说若在10分钟之内要进行第二次扫码,就需要调用接口重新获得该预授权码,这个太坑了,我之前还准备10分钟之内复用同一个
/**
* 新增授权,预授权码pre_auth_code 10分钟更新一次
* 每次请求新增授权都去获取新的预授权码 保存进本地数据库
*/
@RequestMapping(method = RequestMethod.GET)
// @ApiOperation(value = "新增授权", notes = "重定向到微信授权二维码页面",
// consumes = "application/json", produces = "application/json", httpMethod = "GET")
// @ApiImplicitParams({})
// @ApiResponses({
// @ApiResponse(code = 200, message = "成功", response = JSONObject.class),
// @ApiResponse(code = 500, message = "失败", response = JSONObject.class)
// })
public void authorize() {
// HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
HttpServletResponse response = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getResponse();
String redirectUrl = "http://XXXXXXX"; // 授权成功回调url
AccessTokenInfo accessTokenInfo = iAccessTokenInfoSV.selectActInfo(); // 获取公共授权码对象
Long currentTime = System.currentTimeMillis();
String preAuthCode = "";
String componentAccessToken = accessTokenInfo.getComponentAccessToken();
// 接下来根据component_access_token来获取预授权码 pre_auth_code
JSONObject params = new JSONObject();
params.put("component_appid", ComponentAppId);
String result = HttpClientUtil.httpPost("https://api.weixin.qq.com/cgi-bin/component/api_create_preauthcode?component_access_token=" + componentAccessToken, params.toJSONString());
preAuthCode = JSONObject.parseObject(result).getString("pre_auth_code");
System.out.println("获取的pre_auth_code为:------------------------" + preAuthCode);
if (!(StringUtils.isEmpty(preAuthCode))) { // 如果获取到预授权码才更新
JSONObject preParams = new JSONObject();
preParams.put("preAuthCode", preAuthCode);
int update = iAccessTokenInfoSV.updateAccessToken(preParams); // 更新本地数据库
System.out.println("更新预授权码 【0:失败,1:成功】 update = " + update);
} else {
System.out.println("Controller层 请求新增授权方法《authorize》时 component_access_token 的值过期了!!!!!!!");
}
try {
response.sendRedirect("https://mp.weixin.qq.com/cgi-bin/componentloginpage?component_appid=" + ComponentAppId
+ "&pre_auth_code=" + preAuthCode + "&redirect_uri=" + redirectUrl);
} catch (IOException e) {
e.printStackTrace();
}
}
引导进入授权页面
参数为https://mp.weixin.qq.com/cgi-bin/componentloginpage?component_appid=xxxx&pre_auth_code=xxxxx&redirect_uri=xxxx,授权成功后会回调uri
会将用户的授权码authorization_code返回 该授权码的有效期为10分钟,这个回调uri很重要,微信第三方平台也有一个推送授权相关通知的接口
,但是由于业务原因没有采用该接口(如果有需要了解的可以私聊我或者留言)
当公众号对第三方平台进行授权、取消授权、更新授权后,微信服务器会向第三方平台方的授权事件接收URL(创建第三方平台时填写)推送相关通知。
进行授权:是指进行第一次授权,如果已经授权过再继续扫码授权不会触发
取消授权:是指在微信公众平台官网手动取消已经授权的第三方平台
更新授权:是指在已经进行过第一次授权,再次授权的时候更改已经授权过的权限集
现在我的实现方式是直接拿到回调uri中的authorization_code来进行下一步操作因为这个code不管你用不用它都会在授权成功后出现在地址栏中
根据authorization_code获取公众号授权信息
这个没啥说的,官方文档已经写得很清楚了,这一步获得了我们最需要的authorizer_appid和authorizer_access_token
通过authorizer_appid可以来获取公众号的基本信息,authorizer_access_token是用来调用微信公众平台的相关接口
注意:该authorizer_access_token就等同于微信公众平台的access_token,不用纠结这个!
微信公众平台接口参考官方文档
详细代码在我的github上,如果刚好能帮到你,记得给个赞鸭!