說在前面
根據(jù)產(chǎn)品需求执赡,需要在已有平臺上接入微信第三方平臺,這也是我第一次開發(fā)微信相關內(nèi)容函筋,在這期間走了不少彎路,今天有點時間寫下來奠伪,希望能對新的開發(fā)者有點幫助跌帐,少踩點坑。
解密方式
開放平臺和公眾平臺中都有相關的解密實例代碼绊率,但想直接使用的話谨敛,還需要進行加工處理,這里貼出我自己用的解密類:
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的長度要符合微信規(guī)定
private static String encodingAesKey = "XXXXXXXXXX";
//公眾號第三方平臺的appid藐俺,不用糾結該appid炊甲,在創(chuàng)建完第三方平臺后微信就會給到你
private static String appId = "XXXXXXX";
//微信加密簽名
private static String msg_signature;
//時間戳
private static String timestamp;
//隨機數(shù)
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");
//隨機數(shù)
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創(chuàng)建完第三方平臺微信就會給到你梭姓,token和加解密key都可以隨便填零抬,但是要符合微信的規(guī)范
獲取ticket
微信服務器回向授權事件接收URL沒隔10分鐘定時推送ticket除师,在收到ticket后需要進行解密獲取,接收到后必須直接返回success
/**
* @param postdata 微信發(fā)送過來的加密的xml格式數(shù)據(jù)凝化,通過在創(chuàng)建第三方平臺是填寫的授權事件URL關聯(lián)
* 除了接受授權事件(成功授權、取消授權以及授權更新)外官辽,在接受ticket及授權后回調(diào)URI也會用到該方法
* @return 根據(jù)微信開放平臺規(guī)定蛹磺,接收到授權事件后只需要直接返回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("調(diào)用接受授權事件通知的方法 <getAuthorizedEvent> 的入?yún)椋?----------------------" + 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("接受到微信發(fā)送的ticket,ticket = " + authorizedMap.get("ComponentVerifyTicket"));
this.setPublicAuthorizedCode(authorizedMap.get("ComponentVerifyTicket")); // 根據(jù)ticket去刷新公共授權碼
} else if (infoType.equals("unauthorized")) { // 接受的是取消授權事件同仆,將微信授權狀態(tài)設為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";
}
根據(jù)ticket鳖轰、appid和appsecret來獲得token
由于該token的有效時間為2個小時,在我的設計中扶镀,數(shù)據(jù)庫表中有一個token_update_time字段蕴侣,每次接收到ticket就取當前時間與updatetime做對比,如果超過1小時50分臭觉,就調(diào)用接口重新獲取token
昆雀,當然取updatetime操作肯定做了緩存0.0
/**
* 刷新公共授權碼,由于component_access_token需要2個小時刷新一次蝠筑,所以需要判斷本地表中存在的第三方接口調(diào)用憑據(jù)updateTime和當前時間的差值
* 如果超過1小時50分就調(diào)用微信接口更新狞膘,否則不做任何操作
*
* @param componentVerifyTicket 根據(jù)最近可用的component_verify_ticket來獲得componentAccessToken和preAuthCode
*/
private void setPublicAuthorizedCode(String componentVerifyTicket) {
// 根據(jù)tenantId查出 當前公共授權碼表中的 ComponentVerifyTicket
System.out.println("執(zhí)行controller層 刷新公共授權碼的方法 <setPublicAuthorizedCode> 的入?yún)椋?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)) {
// 拼裝參數(shù),添加到本地數(shù)據(jù)庫
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("更新第三方接口調(diào)用憑據(jù)component_access_token 【0:失敗什乙,1:成功】 update = " + update);
} else {
System.out.println("Controller層執(zhí)行 《setPublicAuthorizedCode》方法時返回值有錯---------");
}
} // 如果小于則不需要更新
} else { //首次接收ticket挽封,需要走一遍整個流程,獲取component_access_token和pre_auth_code臣镣,添加進本地數(shù)據(jù)庫
// 首先獲取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");
// 封裝參數(shù)辅愿,添加進本地數(shù)據(jù)庫
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("首次添加公共授權碼進本地數(shù)據(jù)庫 【0:失敗,1:成功】 insert = " + insert);
}else {
System.out.println("首次請求componentAccessToken或者preAuthCode時失敗----------");
}
}
}
根據(jù)token來獲得pre_auth_code
預授權碼的有效時間為10分鐘忆某,且該預授權碼只能使用一次
点待,就是說若在10分鐘之內(nèi)要進行第二次掃碼,就需要調(diào)用接口重新獲得該預授權碼弃舒,這個太坑了癞埠,我之前還準備10分鐘之內(nèi)復用同一個
/**
* 新增授權,預授權碼pre_auth_code 10分鐘更新一次
* 每次請求新增授權都去獲取新的預授權碼 保存進本地數(shù)據(jù)庫
*/
@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"; // 授權成功回調(diào)url
AccessTokenInfo accessTokenInfo = iAccessTokenInfoSV.selectActInfo(); // 獲取公共授權碼對象
Long currentTime = System.currentTimeMillis();
String preAuthCode = "";
String componentAccessToken = accessTokenInfo.getComponentAccessToken();
// 接下來根據(jù)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); // 更新本地數(shù)據(jù)庫
System.out.println("更新預授權碼 【0:失敗聋呢,1:成功】 update = " + update);
} else {
System.out.println("Controller層 請求新增授權方法《authorize》時 component_access_token 的值過期了C缱佟!O髅獭M教健!N箍摺测暗!");
}
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();
}
}
引導進入授權頁面
參數(shù)為https://mp.weixin.qq.com/cgi-bin/componentloginpage?component_appid=xxxx&pre_auth_code=xxxxx&redirect_uri=xxxx央串,授權成功后會回調(diào)uri
會將用戶的授權碼authorization_code返回 該授權碼的有效期為10分鐘,這個回調(diào)uri很重要碗啄,微信第三方平臺也有一個推送授權相關通知的接口
质和,但是由于業(yè)務原因沒有采用該接口(如果有需要了解的可以私聊我或者留言)
當公眾號對第三方平臺進行授權、取消授權稚字、更新授權后饲宿,微信服務器會向第三方平臺方的授權事件接收URL(創(chuàng)建第三方平臺時填寫)推送相關通知。
進行授權:是指進行第一次授權胆描,如果已經(jīng)授權過再繼續(xù)掃碼授權不會觸發(fā)
取消授權:是指在微信公眾平臺官網(wǎng)手動取消已經(jīng)授權的第三方平臺
更新授權:是指在已經(jīng)進行過第一次授權瘫想,再次授權的時候更改已經(jīng)授權過的權限集
現(xiàn)在我的實現(xiàn)方式是直接拿到回調(diào)uri中的authorization_code來進行下一步操作因為這個code不管你用不用它都會在授權成功后出現(xiàn)在地址欄中
根據(jù)authorization_code獲取公眾號授權信息
這個沒啥說的,官方文檔已經(jīng)寫得很清楚了昌讲,這一步獲得了我們最需要的authorizer_appid和authorizer_access_token
通過authorizer_appid可以來獲取公眾號的基本信息国夜,authorizer_access_token是用來調(diào)用微信公眾平臺的相關接口
注意:該authorizer_access_token就等同于微信公眾平臺的access_token,不用糾結這個短绸!
微信公眾平臺接口參考官方文檔
詳細代碼在我的github上车吹,如果剛好能幫到你,記得給個贊鴨醋闭!