在 微信开发平台对接流程(Java版本)1
我们已经准备好了第三方开发平台的相关准备。接下来,就需要对接代码了。
主要是第三方授权技术流程
思路:
微信第三方会每隔一段时间(10分钟)会验证第三方票据。所以需要先接收第三方票据。接着。需要获取第三方的compent_access_token。然后根据第三方的compent_access_token来定时获取授权公众号的access_token.
代码:
验证票据
对应的表
CREATE TABLE `wx_open_account` (
`id` varchar(36) NOT NULL COMMENT '主键',
`appid` varchar(200) DEFAULT NULL COMMENT '第三方公众号的appId',
`appsecret` varchar(500) DEFAULT NULL COMMENT '第三方公众号的appSecret',
`ticket` varchar(2000) DEFAULT NULL COMMENT '第三方平台推送 :票据 ticket',
`get_ticket_time` datetime DEFAULT NULL COMMENT '票据接收时间',
`component_access_token` varchar(2000) DEFAULT NULL COMMENT '平台方access_token',
`get_access_token_time` datetime DEFAULT NULL COMMENT '平台方获取access_token时间',
PRIMARY KEY (`id`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8 ROW_FORMAT=DYNAMIC COMMENT='微信第三方平台账号表';
我们需要把在表里创建一条第三方的数据。只需要appid(下图1)和appSecret(下图2)就行。
图1
ThirdController
/**
* 接收微信服务器发送的component_verify_ticket
*/
@PostMapping("/getComponentVerifyTicket")
@CrossOrigin
public String getComponentVerifyTicket(HttpServletRequest request, HttpServletResponse response) throws Exception {
return thirdService.getComponentVerifyTicket(request,response);
}
ThirdService
@Override
public String getComponentVerifyTicket(HttpServletRequest request, HttpServletResponse response) {
logger.info("接收微信服务器发送的component_verify_ticket begin");
try {
request.setCharacterEncoding("UTF-8");
response.setCharacterEncoding("UTF-8");
// 微信加密签名
String msg_signature = request.getParameter("msg_signature");
// 时间戳
String timestamp = request.getParameter("timestamp");
// 随机数
String nonce = request.getParameter("nonce");
// 从请求中读取整个post数据
InputStream inputStream;
String postData = null;
inputStream = request.getInputStream();
//获取接收到消息里的XML密文,存放在postData中
postData = IOUtils.toString(inputStream, "UTF-8");
logger.info("postData:" + postData);
WxBizMsgCrypt wxcpt = null;
logger.info("postData:" + postData);
logger.info("msg_signature:" + msg_signature);
logger.info("timestamp:" + timestamp);
logger.info("nonce:" + nonce);
logger.info("getServetoken:" + environment.getProperty("wx.component_token"));
logger.info("getServeencodingaeskey:" + environment.getProperty("wx.component_encodingaeskey"));
logger.info("getServeappid:" + environment.getProperty("wx.component_appid"));
//从XML中获取<Encrypt></Encrypt>标签内的密文文本
//加密处理
Map map2 = XmlUtil.xmlToMap(postData);
String encrypt = map2.get("Encrypt").toString();
logger.info("Encrypt:" + encrypt);
//格式化密文文本,否则没有<ToUserName>标签,会解密失败,参考官方的加解密代码JAVA版本
String format = "<xml><ToUserName><![CDATA[toUser]]></ToUserName><Encrypt><![CDATA[%1$s]]></Encrypt></xml>";
String fromXML = String.format(format, encrypt);
//解密后的明文
String msg = "";
if (StringUtils.isEmpty(encrypt)) {
msg = fromXML;
} else {
wxcpt = new WxBizMsgCrypt(environment.getProperty("wx.component_token"), environment.getProperty("wx.component_encodingaeskey"), environment.getProperty("wx.component_appid"));
// 解密消息
msg = wxcpt.decryptMsg(msg_signature, timestamp, nonce, fromXML);
}
logger.info("msg:" + msg);
//将XML格式字符串转为Map类型
Map<String, Object> msgMap = XmlUtil.xmlToMap(msg);
//获取infotype,注:微信开放平台文档中标明固定为:"component_verify_ticket",但参考其他代码,还包含authorized???
String infotype = msgMap.get("InfoType").toString();
logger.info(infotype);
switch (infotype) {
//验证票据
case "component_verify_ticket":
//查询库中的第三方信息,并且准备存储ticket
WxOpenAccount weixinOpenAccount = openAccountMapperExt.queryOneByAppid(environment.getProperty("wx.component_appid"));
String ComponentVerifyTicket = msgMap.get("ComponentVerifyTicket").toString();
logger.info("component_verify_ticket:" + ComponentVerifyTicket);
weixinOpenAccount.setTicket(ComponentVerifyTicket);
weixinOpenAccount.setGetTicketTime(new Date());
openAccountMapperExt.updateByPrimaryKeySelective(weixinOpenAccount);
//将票据值写入Redis缓存中
// webChatCatchUtil.setWeixinOpenComponentVerifyTicket(ComponentVerifyTicket);
break;
case "unauthorized"://用户取消授权
break;
}
} catch (Exception e) {
logger.error("Exception", e);
}
return "success";
}
wx.component_token:消息校验Token(在1中)
wx.component_encodingaeskey:消息加密解密key
图2
wx.component_appid:图1 的appId(也就是1)
一些工具类
WxBizMsgCrypt
package com.iflytek.util.wx.aes;
/**
* 提供接收和推送给公众平台消息的加解密接口(UTF8编码的字符串).
* <ol>
* <li>第三方回复加密消息给公众平台</li>
* <li>第三方收到公众平台发送的消息,验证消息的安全性,并对消息进行解密。</li>
* </ol>
* 说明:异常java.security.InvalidKeyException:illegal Key Size的解决方案
* <ol>
* <li>在官方网站下载JCE无限制权限策略文件(JDK7的下载地址:
* http://www.oracle.com/technetwork/java/javase/downloads/jce-7-download-432124.html</li>
* <li>下载后解压,可以看到local_policy.jar和US_export_policy.jar以及readme.txt</li>
* <li>如果安装了JRE,将两个jar文件放到%JRE_HOME%\lib\security目录下覆盖原来的文件</li>
* <li>如果安装了JDK,将两个jar文件放到%JDK_HOME%\jre\lib\security目录下覆盖原来文件</li>
* </ol>
*/
import com.iflytek.exception.AesException;
import org.apache.commons.codec.binary.Base64;
import javax.crypto.Cipher;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.Charset;
import java.security.SecureRandom;
import java.util.Arrays;
/**
* @desc 微信消息体加密解密工具类
*/
public class WxBizMsgCrypt {
static Charset CHARSET = Charset.forName("utf-8");
Base64 base64 = new Base64();
byte[] aesKey;
String token;
String appId;
/**
* 构造函数
* @param token 公众平台上,开发者设置的token
* @param encodingAesKey 公众平台上,开发者设置的EncodingAESKey
* @param appId 公众平台appid
*
* @throws AesException 执行失败,请查看该异常的错误码和具体的错误信息
*/
public WxBizMsgCrypt(String token, String encodingAesKey, String appId) throws AesException {
if (encodingAesKey.length() != 43) {
throw new AesException(AesException.IllegalAesKey);
}
this.token = token;
this.appId = appId;
aesKey = Base64.decodeBase64(encodingAesKey + "=");
}
/**
* 生成4个字节的网络字节序
* @param sourceNumber 源数字
* @return 返回字节数组
*/
byte[] getNetworkBytesOrder(int sourceNumber) {
byte[] orderBytes = new byte[4];
orderBytes[3] = (byte) (sourceNumber & 0xFF);
orderBytes[2] = (byte) (sourceNumber >> 8 & 0xFF);
orderBytes[1] = (byte) (sourceNumber >> 16 & 0xFF);
orderBytes[0] = (byte) (sourceNumber >> 24 & 0xFF);
return orderBytes;
}
/**
* 还原4个字节的网络字节序
* @param orderBytes 需要还原的字节数组
* @return 返回数字
*/
int recoverNetworkBytesOrder(byte[] orderBytes) {
int sourceNumber = 0;
for (int i = 0; i < 4; i++) {
sourceNumber <<= 8;
sourceNumber |= orderBytes[i] & 0xff;
}
return sourceNumber;
}
/**
* 随机生成16位字符串
* @return 返回随机字符串
*/
String getRandomStr() {
String base = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
SecureRandom random = new SecureRandom();
StringBuffer sb = new StringBuffer();
for (int i = 0; i < 16; i++) {
int number = random.nextInt(base.length());
sb.append(base.charAt(number));
}
return sb.toString();
}
/**
* 对明文进行加密.
*
* @param text 需要加密的明文
* @return 加密后base64编码的字符串
* @throws AesException aes加密失败
*/
String encrypt(String randomStr, String text) throws AesException {
ByteGroup byteCollector = new ByteGroup();
byte[] randomStrBytes = randomStr.getBytes(CHARSET);
byte[] textBytes = text.getBytes(CHARSET);
byte[] networkBytesOrder = getNetworkBytesOrder(textBytes.length);
byte[] appidBytes = appId.getBytes(CHARSET);
// randomStr + networkBytesOrder + text + appid
byteCollector.addBytes(randomStrBytes);
byteCollector.addBytes(networkBytesOrder);
byteCollector.addBytes(textBytes);
byteCollector.addBytes(appidBytes);
// ... + pad: 使用自定义的填充方式对明文进行补位填充
byte[] padBytes = PkCs7Encoder.encode(byteCollector.size());
byteCollector.addBytes(padBytes);
// 获得最终的字节流, 未加密
byte[] unencrypted = byteCollector.toBytes();
try {
// 设置加密模式为AES的CBC模式
Cipher cipher = Cipher.getInstance("AES/CBC/NoPadding");
SecretKeySpec keySpec = new SecretKeySpec(aesKey, "AES");
IvParameterSpec iv = new IvParameterSpec(aesKey, 0, 16);
cipher.init(Cipher.ENCRYPT_MODE, keySpec, iv);
// 加密
byte[] encrypted = cipher.doFinal(unencrypted);
// 使用BASE64对加密后的字符串进行编码
String base64Encrypted = base64.encodeToString(encrypted);
return base64Encrypted;
} catch (Exception e) {
e.printStackTrace();
throw new AesException(AesException.EncryptAESError);
}
}
/**
* 对密文进行解密.
*
* @param text 需要解密的密文
* @return 解密得到的明文
* @throws AesException aes解密失败
*/
String decrypt(String text) throws AesException {
byte[] original;
try {
// 设置解密模式为AES的CBC模式
Cipher cipher = Cipher.getInstance("AES/CBC/NoPadding");
SecretKeySpec key_spec = new SecretKeySpec(aesKey, "AES");
IvParameterSpec iv = new IvParameterSpec(Arrays.copyOfRange(aesKey, 0, 16));
cipher.init(Cipher.DECRYPT_MODE, key_spec, iv);
// 使用BASE64对密文进行解码
byte[] encrypted = Base64.decodeBase64(text);
// 解密
original = cipher.doFinal(encrypted);
} catch (Exception e) {
e.printStackTrace();
throw new AesException(AesException.DecryptAESError);
}
String xmlContent, from_appid;
try {
// 去除补位字符
byte[] bytes = PkCs7Encoder.decode(original);
// 分离16位随机字符串,网络字节序和AppId
byte[] networkOrder = Arrays.copyOfRange(bytes, 16, 20);
int xmlLength = recoverNetworkBytesOrder(networkOrder);
xmlContent = new String(Arrays.copyOfRange(bytes, 20, 20 + xmlLength), CHARSET);
from_appid = new String(Arrays.copyOfRange(bytes, 20 + xmlLength, bytes.length),
CHARSET);
} catch (Exception e) {
e.printStackTrace();
throw new AesException(AesException.IllegalBuffer);
}
// appid不相同的情况
if (!from_appid.equals(appId)) {
throw new AesException(AesException.ValidateAppidError);
}
return xmlContent;
}
/**
* 将公众平台回复用户的消息加密打包.
* <ol>
* <li>对要发送的消息进行AES-CBC加密</li>
* <li>生成安全签名</li>
* <li>将消息密文和安全签名打包成xml格式</li>
* </ol>
*
* @param replyMsg 公众平台待回复用户的消息,xml格式的字符串
* @param timeStamp 时间戳,可以自己生成,也可以用URL参数的timestamp
* @param nonce 随机串,可以自己生成,也可以用URL参数的nonce
*
* @return 加密后的可以直接回复用户的密文,包括msg_signature, timestamp, nonce, encrypt的xml格式的字符串
* @throws AesException 执行失败,请查看该异常的错误码和具体的错误信息
*/
public String encryptMsg(String replyMsg, String timeStamp, String nonce) throws AesException {
// 加密
String encrypt = encrypt(getRandomStr(), replyMsg);
// 生成安全签名
if (timeStamp == "") {
timeStamp = Long.toString(System.currentTimeMillis());
}
String signature = Sha1Util.getSha1Token(token, timeStamp, nonce, encrypt);
// 生成发送的xml
String result = XmlParse.generate(encrypt, signature, timeStamp, nonce);
return result;
}
/**
* 检验消息的真实性,并且获取解密后的明文.
* <ol>
* <li>利用收到的密文生成安全签名,进行签名验证</li>
* <li>若验证通过,则提取xml中的加密消息</li>
* <li>对消息进行解密</li>
* </ol>
*
* @param msgSignature 签名串,对应URL参数的msg_signature
* @param timeStamp 时间戳,对应URL参数的timestamp
* @param nonce 随机串,对应URL参数的nonce
* @param postData 密文,对应POST请求的数据
*
* @return 解密后的原文
* @throws AesException 执行失败,请查看该异常的错误码和具体的错误信息
*/
public String decryptMsg(String msgSignature, String timeStamp, String nonce, String postData)
throws AesException {
// 密钥,公众账号的app secret
// 提取密文
Object[] encrypt = XmlParse.extract(postData);
// 验证安全签名
String signature = Sha1Util.getSha1Token(token, timeStamp, nonce, encrypt[1].toString());
// 和URL中的签名比较是否相等
// System.out.println("第三方收到URL中的签名:" + msg_sign);
// System.out.println("第三方校验签名:" + signature);
if (!signature.equals(msgSignature)) {
throw new AesException(AesException.ValidateSignatureError);
}
// 解密
String result = decrypt(encrypt[1].toString());
return result;
}
/**
* 验证URL
* @param msgSignature 签名串,对应URL参数的msg_signature
* @param timeStamp 时间戳,对应URL参数的timestamp
* @param nonce 随机串,对应URL参数的nonce
* @param echoStr 随机串,对应URL参数的echostr
*
* @return 解密之后的echostr
* @throws AesException 执行失败,请查看该异常的错误码和具体的错误信息
*/
public String verifyUrl(String msgSignature, String timeStamp, String nonce, String echoStr)
throws AesException {
String signature = Sha1Util.getSha1Token(token, timeStamp, nonce, echoStr);
if (!signature.equals(msgSignature)) {
throw new AesException(AesException.ValidateSignatureError);
}
String result = decrypt(echoStr);
return result;
}
}
XmlUtil是下面包的工具类
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>5.5.0</version>
</dependency>
获取compent_access_token
这个是定时任务获取。因此,需要我们存储到redis中。因为它的有效期是7200s.。接着,我们在更新下该第三方下的公众号token.
需要的表。
CREATE TABLE `wx_token` (
`id` int(11) unsigned NOT NULL AUTO_INCREMENT,
`jwid` varchar(64) DEFAULT NULL COMMENT '公众号',
`name` varchar(64) NOT NULL COMMENT '名称',
`application_type` varchar(10) DEFAULT NULL COMMENT '应用类型',
`qrcodeimg` varchar(255) DEFAULT NULL COMMENT '微信二维码图片',
`weixin_number` varchar(64) DEFAULT NULL COMMENT '微信号',
`weixin_appid` varchar(50) DEFAULT NULL COMMENT '微信AppId',
`weixin_appsecret` varchar(255) DEFAULT NULL COMMENT '微信AppSecret',
`account_type` varchar(255) DEFAULT NULL COMMENT '公众号类型',
`auth_status` varchar(255) DEFAULT NULL COMMENT '是否认证',
`access_token` varchar(2000) DEFAULT NULL COMMENT 'Access_Token',
`token_gettime` datetime DEFAULT NULL COMMENT 'token获取的时间',
`apiticket` varchar(255) DEFAULT NULL COMMENT 'api凭证',
`apiticket_gettime` datetime DEFAULT NULL COMMENT 'apiticket获取时间',
`jsapiticket` varchar(255) DEFAULT NULL COMMENT 'jsapiticket',
`jsapiticket_gettime` datetime DEFAULT NULL COMMENT 'jsapiticket获取时间',
`auth_type` varchar(10) DEFAULT NULL COMMENT '类型:1手动授权,2扫码授权',
`business_info` varchar(5000) DEFAULT NULL COMMENT '功能的开通状况:1代表已开通',
`func_info` varchar(5000) DEFAULT NULL COMMENT '公众号授权给开发者的权限集',
`nick_name` varchar(200) DEFAULT NULL COMMENT '授权方昵称',
`headimgurl` varchar(1000) DEFAULT NULL COMMENT '授权方头像',
`authorization_info` varchar(5000) DEFAULT NULL COMMENT '授权信息',
`authorizer_refresh_token` varchar(500) DEFAULT NULL COMMENT '刷新token',
`token` varchar(32) DEFAULT NULL COMMENT '令牌',
`authorization_status` varchar(10) DEFAULT NULL COMMENT '授权状态(1正常,2已取消)',
`create_time` datetime DEFAULT NULL COMMENT '创建时间',
`create_user` varchar(255) DEFAULT NULL COMMENT '创建人',
`update_time` datetime DEFAULT NULL COMMENT '修改时间',
`update_user` varchar(255) DEFAULT NULL COMMENT '修改人',
`delete_flag` int(2) DEFAULT '0' COMMENT '逻辑删除 0未删除 1已删除',
PRIMARY KEY (`id`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8 ROW_FORMAT=DYNAMIC COMMENT='系统公众号表';
上代码。
/**
* 定时刷新TOKEN
*/
@Scheduled(cron = "${wx.open.task}")
public void run() {
logger.info("===================重置公众号AccseeToken定时任务开启==========================");
long start = System.currentTimeMillis();
try {
//1.重置第三方平台AccessTOKEN
resetComponentAccessToken();
//2.重置公众号的Token
WxTokenExample example = new WxTokenExample();
WxTokenExample.Criteria crt = example.createCriteria();
//授权状态(1正常,2已取消)
crt.andAuthorizationStatusEqualTo("1");
crt.andDeleteFlagEqualTo(0);
Date date = new Date();
crt.andTokenGettimeLessThan(new Date(date.getTime() - 60 * 60 * 1000));
List<WxToken> tokenList = tokenMapperExt.selectByExample(example);
for (WxToken myJwWebJwid : tokenList) {
try {
if ("2".equals(myJwWebJwid.getAuthType())) {
//第三方平台
resetAccessTokenByType2(myJwWebJwid);
} else {
resetAccessTokenByType1(myJwWebJwid);
}
} catch (Exception e) {
logger.info("重置AccseeToken定时任务异常e={}", new Object[]{e});
}
}
} catch (Exception e) {
logger.info("重置AccseeToken定时任务异常e={}", new Object[]{e});
}
logger.info("===================重置AccseeToken定时任务结束,用时={}ms.==========================", new Object[]{System.currentTimeMillis() - start});
}
/**
* 重置第三方平台AccessTOKEN
*
* @param
* @return
*/
private void resetComponentAccessToken() {
try {
Date date = new Date();
long time = date.getTime() - 1000 * 60 * 90;
Date refDate = new Date(time);
//根据APPID从数据库中获取基本信息(包括秘钥)
if (StringUtils.isEmpty(environment.getProperty("wx.component_appid"))) {
return;
}
WxOpenAccount weixinOpenAccount = openAccountMapperExt.queryOneByAppid(environment.getProperty("wx.component_appid"));
logger.info("微信第三方账号信息:"+ JSON.toJSONString(weixinOpenAccount));
//第三方平台账号上次获取token时间超过1个半小时,则进行重置
if (weixinOpenAccount != null && weixinOpenAccount.getGetAccessTokenTime().before(refDate)) {
logger.info("时间匹配:"+weixinOpenAccount.getGetAccessTokenTime().before(refDate));
Map<String, Object> param = new HashMap<String, Object>(8);
param.put("component_appid", environment.getProperty("wx.component_appid"));
//从数据库中获取秘钥
param.put("component_appsecret", weixinOpenAccount.getAppsecret());
param.put("component_verify_ticket", weixinOpenAccount.getTicket());
JSONObject jsonObject = new JSONObject(param);
JSONObject jsonObj = WxHttpUtil.postForJson(environment.getProperty("wx.baseUrl") +CommonWeixinProperties.GET_COMPONENT_ACCESS_ACTOKEN_URL, jsonObject.toString());
logger.info("重置第三方平台ACCESSTOKEN时返回的报文" + jsonObj);
if (jsonObj != null && jsonObj.containsKey("component_access_token")) {
weixinOpenAccount.setComponentAccessToken(jsonObj.getString("component_access_token"));
weixinOpenAccount.setGetAccessTokenTime(new Date());
openAccountMapperExt.updateByPrimaryKeySelective(weixinOpenAccount);
//---------------------第三方平台账号重置token后写入redis缓存-----------------------------
try {
redisUtil.setKey(Constant.WX_THIRD_INFO, weixinOpenAccount, 5400);
} catch (Exception e) {
logger.error("----------第三方平台账号重置TOKEN错误-------------" + e.toString());
e.printStackTrace();
}
//---------------------第三方平台账号重置token后写入redis缓存-----------------------------
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 根据APPID和秘钥 获取ACCESSTOKEN
*
* @param wxToken
* @return
*/
private void resetAccessTokenByType1(WxToken wxToken) {
//根据APPID和秘钥 获取ACCESSTOKEN,并获取apiticket和jsapiticket
Map<String, Object> data = AccessTokenUtil.getAccseeToken(wxToken.getWeixinAppid(), wxToken.getWeixinAppsecret());
logger.info("resetAccessTokenByType1重置第三方平台下公众号信息时返回的报文" + JSONObject.toJSONString(data));
if (data != null && "success".equals(data.get("status").toString())) {
wxToken.setAccessToken(data.get("accessToken").toString());
wxToken.setTokenGettime(new Date());
wxToken.setApiticket(data.get("apiTicket").toString());
wxToken.setApiticketGettime(new Date());
wxToken.setJsapiticket(data.get("jsApiTicket").toString());
wxToken.setJsapiticketGettime(new Date());
tokenMapperExt.updateByPrimaryKeySelective(wxToken);
//-------H5平台独立公众号,重置redis缓存-------------------------------------------
try {
redisUtil.setKey(Constant.WX_ACCOUNT+wxToken.getId(), wxToken, 5400);
} catch (Exception e) {
logger.error("定时任务:独立公众号,重置redis缓存token失败,{}", e);
}
//--------独立公众号,重置redis缓存---------------------------------------
} else if ("responseErr".equals(data.get("status").toString())) {
logger.error(data.get("msg").toString());
} else {
logger.error("根据APPID和秘钥 获取ACCESSTOKEN,并获取apiticket和jsapiticket 失败");
}
}
/**
* 获取ACCESSTOKEN
*获取/刷新接口调用令牌
* @param wxToken
* @return
*/
private String resetAccessTokenByType2(WxToken wxToken) {
try {
WxOpenAccount weixinOpenAccount = openAccountMapperExt.queryOneByAppid(environment.getProperty("wx.component_appid"));
if (weixinOpenAccount == null) {
logger.error("重置accessToken时获取WEIXINOPENACCOUNT为空");
return "重置accessToken时获取WEIXINOPENACCOUNT为空";
}
//重置授权公众号或小程序的接口调用凭据
String getAuthorizerTokenUrl = environment.getProperty("wx.baseUrl") +CommonWeixinProperties.GET_AUTHORIZER_ACCESS_TOKEN_URL;
getAuthorizerTokenUrl = getAuthorizerTokenUrl.replace("COMPONENT_ACCESS_TOKEN", weixinOpenAccount.getComponentAccessToken());
// 拼装参数
JSONObject js = new JSONObject();
// 第三方平台appid
js.put("component_appid", environment.getProperty("wx.component_appid"));
// 授权用户的appid
js.put("authorizer_appid", wxToken.getWeixinAppid());
// 刷新令牌
js.put("authorizer_refresh_token", wxToken.getAuthorizerRefreshToken());
JSONObject jsonObj = WxHttpUtil.postForJson(getAuthorizerTokenUrl, js.toString());
logger.info("resetAccessTokenByType2重置第三方平台下公众号信息时返回的报文" + jsonObj);
if (jsonObj != null && !jsonObj.containsKey("errcode")) {
String authorizerAccessToken = jsonObj.getString("authorizer_access_token");
String authorizerRefreshToken = jsonObj.getString("authorizer_refresh_token");
wxToken.setAccessToken(authorizerAccessToken);
wxToken.setTokenGettime(new Date());
wxToken.setAuthorizerRefreshToken(authorizerRefreshToken);
//update jsapiticket
Map<String, String> apiTicket = AccessTokenUtil.getApiTicket(wxToken.getAccessToken());
if(apiTicket!=null){
if ("true".equals(apiTicket.get("status"))) {
wxToken.setApiticket(apiTicket.get("apiTicket"));
wxToken.setApiticketGettime(new Date());
wxToken.setJsapiticket(apiTicket.get("jsApiTicket"));
wxToken.setJsapiticketGettime(new Date());
}
}
tokenMapperExt.updateByPrimaryKeySelective(wxToken);
//-------H5平台独立公众号,重置redis缓存-------------------------------------------
try {
redisUtil.setKey(wxToken.getWeixinAppid(), wxToken.getAccessToken(), 5400);
} catch (Exception e) {
logger.error("----------定时任务:独立公众号,重置redis缓存token失败-------------" + e.toString());
}
//--------H5平台独立公众号,重置redis缓存---------------------------------------
} else {
logger.error("重置Token失败");
}
return "success";
} catch (Exception e) {
e.printStackTrace();
return "重置accessToken时发生异常:" + e.getMessage();
}
}
授权
是第三方生成一个二维码,公众号管理员,扫描授权后。我们就可以存储到表中。这样就可以管理公众号了。
扫码授权
controller
/**
* 授权二维码页面
*/
@RequestMapping(value = "/getAuthhorization", method = RequestMethod.GET)
@CrossOrigin
public Result getAuthhorization(HttpServletRequest request) {
return thirdService.getAuthhorization(request);
}
service
public Result getAuthhorization(HttpServletRequest request) {
try {
WxOpenAccount weixinOpenAccount = openAccountMapperExt.queryOneByAppid(environment.getProperty("wx.component_appid"));
if (weixinOpenAccount == null) {
return Result.sendFailure("通过APPID获取WEIXINOPENACCOUNT为空!");
}
//获取ACCESSTOKEN
if (StringUtils.isEmpty(weixinOpenAccount.getComponentAccessToken())) {
return Result.sendFailure("未获取到第三方平台的ACCESSTOKEN!");
}
String redirect_uri = URLEncoder.encode(environment.getProperty("wx.authhorizationCallBackUrl") + "?userId=" + loginCache.getUserId(), "UTF-8");
String authhorizationUrl = null;
//获取预授权码
String preAuthCode = getPreAuthCode(environment.getProperty("wx.component_appid"), weixinOpenAccount.getComponentAccessToken());
if (preAuthCode == null) {
return Result.sendFailure("获取预授权码失败");
} else {
authhorizationUrl = environment.getProperty("wx.authhorizationUrl").replace("PRE_AUTH_CODE", preAuthCode);
}
authhorizationUrl = authhorizationUrl.replace("REDIRECT_URI", redirect_uri).replace("COMPONENT_APPID", environment.getProperty("wx.component_appid"));
logger.info("扫描的二维码地址是" + authhorizationUrl);
return Result.sendSuccess("获取授权二维码成功", authhorizationUrl);
} catch (Exception e) {
logger.error("扫描授权失败,{}", e);
return Result.sendFailure("扫描授权失败!");
}
}
public String getPreAuthCode(String component_appid, String component_access_token) {
String pre_auth_code = null;
String requestUrl = environment.getProperty("wx.preUrl").replace("COMPONENT_ACCESS_TOKEN", component_access_token);
GetPreAuthCodeParam getPreAuthCodeParam = new GetPreAuthCodeParam();
getPreAuthCodeParam.setComponent_appid(component_appid);
String obj = JSONObject.toJSONString(getPreAuthCodeParam);
HttpResponse response = WxHttpUtil.post(requestUrl, obj);
logger.info("getPreAuthCode Body:" + response.body());
JSONObject jsonObject = JSONObject.parseObject(response.body());
if (jsonObject.containsKey("errcode")) {
logger.error("获取权限令牌信息!errcode=" + jsonObject.getString("errcode") + ",errmsg = " + jsonObject.getString("errmsg"));
return pre_auth_code;
} else {
pre_auth_code = jsonObject.getString("pre_auth_code");
return pre_auth_code;
}
}
wx.authhorizationUrl:微信授权二维的地址需要(https://mp.weixin.qq.com/cgi-bin/componentloginpage?component_appid=COMPONENT_APPID&pre_auth_code=PRE_AUTH_CODE&auth_type=1&redirect_uri=REDIRECT_URI)
wx.preUrl:获取预授权的地址(https://api.weixin.qq.com/cgi-bin/component/api_create_preauthcode?component_access_token=COMPONENT_ACCESS_TOKEN)
wx.authhorizationCallBackUrl:回调地址(用户授权后,微信回调的地址。xxx/callback)
授权回调
微信公众号授权后回调的地址
Cotroller
/**
* 授权回调地址
*
* @param request
* @return
*/
@RequestMapping(value = "/callback", method = RequestMethod.GET)
@CrossOrigin
public void callback(HttpServletRequest request, HttpServletResponse response) throws Exception {
thirdService.callBack(request, response);
}
Service
@Override
public void callBack(HttpServletRequest request, HttpServletResponse response) {
try {
String authCode = request.getParameter("auth_code");
//回调的时候添加的userId
String userId = request.getParameter("userId");
WxOpenAccount weixinOpenAccount = openAccountMapperExt.queryOneByAppid(environment.getProperty("wx.component_appid"));
//调取接口获取平台ACCESSTOKEN
String componentAccessToken = weixinOpenAccount.getComponentAccessToken();
if (StringUtils.isEmpty(componentAccessToken)) {
logger.error("授权公共号回调时获取ACCESSTOKEN为空!");
}
//调取接口 使用授权码获取授权信息
String urlFormat = environment.getProperty("wx.apiQueryAuth").replace("COMPONENT_ACCESS_TOKEN", componentAccessToken);
JSONObject json = new JSONObject();
json.put("component_appid", environment.getProperty("wx.component_appid"));
json.put("authorization_code", authCode);
logger.info("授权公共号回调后调取接口请求参数为:{}", new Object[]{json.toString()});
JSONObject jsonObject = WxHttpUtil.postForJson(urlFormat, JSONObject.toJSONString(json));
logger.info("授权公共号回调后调取接口返回参数为:{}", new Object[]{jsonObject});
if (jsonObject != null && !jsonObject.containsKey("errcode")) {
WxToken wxToken = new WxToken();
// 保存授权公众号的部分信息
wxToken.setCreateUser(userId);
save(jsonObject, wxToken);
// 通过第三方token获取公众号信息
String getAuthorizerInfoUrl = environment.getProperty("wx.baseUrl") + CommonWeixinProperties.getAuthorizerInfo.replace("COMPONENT_ACCESS_TOKEN", componentAccessToken);
JSONObject j = new JSONObject();
// 第三方平台appid
j.put("component_appid", environment.getProperty("wx.component_appid"));
// 授权用户的appid
j.put("authorizer_appid", wxToken.getWeixinAppid());
JSONObject jsonObj = WxHttpUtil.postForJson(getAuthorizerInfoUrl, j.toString());
logger.info("===========授权回调方法===获取授权公众号详细Info===" + jsonObj.toString() + "===========");
if (jsonObj != null && !jsonObj.containsKey("errcode")) {
update(jsonObj, wxToken);
}
//给用户绑定相应的微信公众号
if (userId != null) {
}
}
//存储到redis中。
redisUtil.setKey(wxToken.getWeixinAppid(), wxToken.getAccessToken(), 5400);
}
logger.info("授权成功!");
String redirectUrl=environment.getProperty("wx.oAuthDomain");
response.sendRedirect(redirectUrl);
} catch (Exception e) {
e.printStackTrace();
logger.error("授权信息回调方法中,发生错误,错误信息={}", e);
logger.error("授权失败");
}
}
wx.apiQueryAuth:获取授权方的帐号基本信息(https://api.weixin.qq.com/cgi-bin/component/api_get_authorizer_info?component_access_token=COMPONENT_ACCESS_TOKEN)
这样你以为就结束了。但是不是,还有一个接收信息的方法。
接收公众号信息
Controller
/**
* 接收微信服务器
*/
@RequestMapping("/{APPID}/message")
public void message(HttpServletRequest request, HttpServletResponse response,@PathVariable("APPID") String APPID) throws Exception {
thirdService.getMessage(request,response,APPID);
}
Service
public void getMessage(HttpServletRequest req, HttpServletResponse resp, String appid) {
// 微信加密签名,signature结合了开发者填写的token参数和请求中的timestamp参数、nonce参数。
String signature = req.getParameter("msg_signature");
logger.info("第三方平台全网发布-------------" + appid + "-----------验证开始。。。。msg_signature=" + signature);
setHeaderParam(req, resp);
if (!StringUtils.isNotBlank(signature)) {
return;// 微信推送给第三方开放平台的消息一定是加过密的,无消息加密无法解密消息
}
//时间戳
String timestamp = req.getParameter("timestamp");
// 随机数
String nonce = req.getParameter("nonce");
PrintWriter out = null;
Map<String, Object> receiveMap = null;
try {
out = resp.getWriter();
//兼容加密与不加密
String resultMsg = null;
if (StringUtils.isEmpty(signature)) {
//不加密处理
receiveMap = MessageUtil.parseXml(req);
logger.info("接收的微信推送消息为:{}", receiveMap.toString());
} else {
//加密处理
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
dbf.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);
DocumentBuilder db = dbf.newDocumentBuilder();
Document document = db.parse(req.getInputStream());
Element root = document.getDocumentElement();
String encrypt = root.getElementsByTagName("Encrypt").item(0).getTextContent();
String format = "<xml><ToUserName><![CDATA[toUser]]></ToUserName><Encrypt><![CDATA[%1$s]]></Encrypt></xml>";
String fromXml = String.format(format, encrypt);
//解密密文
WxBizMsgCrypt pc = new WxBizMsgCrypt(environment.getProperty("wx.component_token"), environment.getProperty("wx.component_encodingaeskey"), environment.getProperty("wx.component_appid"));
resultMsg = pc.decryptMsg(signature, timestamp, nonce, fromXml);
logger.info("接收的微信推送消息为:{}", resultMsg);
}
//处理接收到的信息。
wxMessageService.processMessage(resultMsg, receiveMap, appid);
out.println("");
} catch (Exception e) {
logger.error(e.getLocalizedMessage(), e);
if (out != null) {
out.println("");
}
} finally {
if (out != null) {
out.close();
}
out = null;
}
logger.info("全网发布接入检测消息反馈开始---------------APPID=" + appid);
}
这样就完成了。