開(kāi)發(fā)接入2.1微信公眾平臺接口測試賬號的申請流程及步驟
優(yōu)采云 發(fā)布時(shí)間: 2021-08-01 00:31開(kāi)發(fā)接入2.1微信公眾平臺接口測試賬號的申請流程及步驟
一、前言
隨著(zhù)微信的普及,年輕一代逐漸從QQ轉向微信。界面簡(jiǎn)潔,功能強大,男女老少皆宜,是微信的特色。正是這一特性,使微信成為了中國社交軟件的巨頭。因此,很多產(chǎn)品需要在微信中開(kāi)發(fā)以滿(mǎn)足需求。
本文主要講服務(wù)號的開(kāi)發(fā),與微信服務(wù)器的交互,以及使用微信公眾號的Oauth2授權,在微信中展示本地開(kāi)發(fā)的內容并進(jìn)行交互。由于公眾號申請需要時(shí)間和經(jīng)驗,需要公司相關(guān)資質(zhì),作為個(gè)人開(kāi)發(fā)者,可以先在微信官方平臺申請測試號,使用測試號授權與微信服務(wù)器交互并調試這一頁(yè)。在開(kāi)始之前,朋友們需要了解他們的需求。微信分為企業(yè)號、服務(wù)號、訂閱號。不同的賬戶(hù)有不同的功能。具體可以到微信官網(wǎng)查看需要開(kāi)發(fā)什么類(lèi)型的賬號。
二、development 接入2.1 微信公眾平臺接口測試賬號申請
如前言所述,在開(kāi)發(fā)中,我們一半人會(huì )使用官方微信公眾號作為我們的生產(chǎn)線(xiàn)環(huán)境,然后aut環(huán)境可以使用測試號進(jìn)行開(kāi)發(fā),從而實(shí)現與微信的交互。測試賬號申請鏈接如下
測試帳戶(hù)申請
進(jìn)入后,您將看到以下屏幕。登錄
登錄后,您會(huì )看到以下屏幕:
因為我已經(jīng)配置好了,所以顯示配置后的相關(guān)參數。如果是第一次進(jìn)入,需要自己配置相關(guān)參數。首先要注意的是,系統會(huì )為你生成一個(gè)appId,appsercet,后面會(huì )講到這個(gè)的作用?,F在你需要配置 URL 和 Token。
具體來(lái)說(shuō),這個(gè)所謂的URL就是你需要在你的代碼中與微信進(jìn)行Token驗證交互的一種方法。配置完成后,微信會(huì )使用配置的url發(fā)起http請求。請注意,發(fā)起方法是獲取請求。因此,代碼中提供的接口需要將請求頭設置為get請求:
method=(RequestMethod.GET) 方法中需要驗證微信發(fā)送的消息。下面的token需要和你項目中配置的token一致。微信發(fā)送請求后,會(huì )帶上簽名和時(shí)間戳。 , 隨機數被檢查。如果匹配成功,則可以進(jìn)行下一步。如果不一致,則匹配失敗。需要注意的是,這個(gè)url需要有自己的域名。我個(gè)人在Sunny-Ngrok上申請了內網(wǎng)穿透,并贈送了一個(gè)域名。
2.2 sunn-Ngrok 內網(wǎng)滲透賬號申請
點(diǎn)擊申請賬號
賬戶(hù)創(chuàng )建后,會(huì )有如上圖所示。其實(shí),開(kāi)通域名的方式有很多種。這里我只是給我一個(gè)參考方法。然后點(diǎn)擊打開(kāi)隧道,購買(mǎi)隧道
一個(gè)月10元不算貴。購買(mǎi)后點(diǎn)擊隧道管理
購買(mǎi)后會(huì )有記錄,可以同時(shí)查看贈送域名和查看狀態(tài)。此時(shí)的狀態(tài)為離線(xiàn)。如果是離線(xiàn),微信公眾平臺微信發(fā)起的請求無(wú)法配置從本機接受,需要到官網(wǎng)下載客戶(hù)端啟動(dòng)隧道。
cmd命令行進(jìn)入sunny.exe所在目錄并執行
sunny.exe clientid 隧道 ID
如上圖所示,將域名指向你本地的ip和端口號后,就可以在公眾號提交配置了。在我自己機器的代碼中,我做了如下配置:
@RequestMapping(value = "/wx/wxmsgreceive", method = {RequestMethod.GET})
public void verifywxtoken(HttpServletRequest request, HttpServletResponse response) throws Exception {
request.setCharacterEncoding("UTF-8");
response.setCharacterEncoding("UTF-8");
logger.info("開(kāi)始校驗信息是否是從微信服務(wù)器發(fā)出");
// 簽名
String signature = request.getParameter("signature");
// 時(shí)間戳
String timestamp = request.getParameter("timestamp");
// 隨機數
String nonce = request.getParameter("nonce");
// 通過(guò)檢驗signature對請求進(jìn)行校驗,若校驗成功則原樣返回echostr,表示接入成功,否則接入失敗
String result = Sha1.gen(SERVER_TOKEN, timestamp, nonce);
if (signature.equals(result)) {
// 隨機字符串
String echostr = request.getParameter("echostr");
logger.debug("接入成功,echostr {}", echostr);
response.getWriter().write(echostr);
}
}
微信驗證碼
public static String gen(String token, String timestamp, String nonce) throws NoSuchAlgorithmException {
String[] arr = new String[]{token, timestamp, nonce};
Arrays.sort(arr);
StringBuffer content = new StringBuffer();
for (String a : arr) {
content.append(a);
}
return DigestUtils.sha1Hex(content.toString());
}
因為我配置的請求頭是/wx/wxmsgreceive,所以公共平臺上的配置頁(yè)面是域名+/wx/wxmsgreceive。
此時(shí),當您在網(wǎng)頁(yè)上點(diǎn)擊提交時(shí),微信會(huì )向本機發(fā)送認證請求,以確保本機的服務(wù)必須開(kāi)啟。
這是官方的說(shuō)明,所以我們開(kāi)發(fā)的項目中需要提供一個(gè)暴露的接口來(lái)驗證微信服務(wù)器,主要是驗證微信公眾號網(wǎng)頁(yè)上填寫(xiě)的token驗證。官方文檔說(shuō)明如下:
所以在項目中,放置在配置文件或常量池中的token必須是微信公眾號訪(fǎng)問(wèn)中填寫(xiě)的token的字符串。如果輸入不同或者方法中的解密比較有問(wèn)題,在網(wǎng)頁(yè)上點(diǎn)擊確定,就會(huì )出現“失敗”字樣。
2.3 微信開(kāi)發(fā)者工具準備
下載微信開(kāi)發(fā)者工具。
點(diǎn)擊下載微信開(kāi)發(fā)者工具
下載完成后掃碼登錄
選擇微信公眾號網(wǎng)頁(yè)開(kāi)發(fā)
此工具是后續開(kāi)發(fā)中訪(fǎng)問(wèn)URL和前端調試必不可少的工具。
2.3 Oauth2 授權
此時(shí)測試號已經(jīng)配置好了,那么接下來(lái)我們如何開(kāi)發(fā)呢?在微信公眾號頁(yè)面,部分頁(yè)面需要根據用戶(hù)的身份回顯不同的信息,比如用戶(hù)的還款清單。這些實(shí)現首先需要獲取個(gè)人信息然后與數據庫進(jìn)行交互,需要獲取用戶(hù)的唯一標識。在微信中,每個(gè)人都有并且只有一個(gè)唯一標識openId。這個(gè)openId需要通過(guò)微信的授權和回調獲取。同時(shí),微信公開(kāi)文檔上也有詳細的授權方式介紹。如何授權可以根據自己的業(yè)務(wù)需要使用。在實(shí)際業(yè)務(wù)中,根據項目的特點(diǎn)固化不同的用戶(hù)。我們所做的是將用戶(hù)固化到數據庫中,生成一個(gè) UUID,并將 UUID 放入 cookie。授權非常重要。通過(guò)它的控件,我們可以通過(guò)獲取當前用戶(hù)的openid來(lái)比較用戶(hù)是會(huì )員還是普通會(huì )員,是非管理員還是管理員等。這在頁(yè)面跳轉中起著(zhù)至關(guān)重要的作用。同時(shí),在代碼中,一些靜態(tài)頁(yè)面可能不一定需要授權,任何人都可以查看。所以在代碼中,我使用了一個(gè)過(guò)濾器,直接在前臺發(fā)布了一系列.css、.js、.html等。特殊要求,我拉過(guò)來(lái),要求微信授權。例如,我在項目中。 .go請求中需要驗證,通過(guò)微信回調獲取當前用戶(hù)信息,固化用戶(hù)。代碼如下:
@WebFilter(urlPatterns = "/*", filterName = "authFilter")
public class AuthFilter implements Filter {
private final Logger logger = LoggerFactory.getLogger(AuthFilter.class);
@Autowired
private GeneralConfigService gcService;
@Autowired
private WxUserVerify wxUserVerify;
@Override
public void init(FilterConfig filterConfig) throws ServletException {
}
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain chain)
throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) servletRequest;
HttpServletResponse response = (HttpServletResponse) servletResponse;
try {
String url = request.getRequestURI();
logger.info("URL:" + url);
// 1.請求內容為WEB靜態(tài)內容時(shí)直接放行
if (StringUtil.separatorEndWith(gcService.getStaticResourceConfig(), url)) {
chain.doFilter(servletRequest, servletResponse);
return;
}
// 2.不是靜態(tài)資源進(jìn)行驗證
if(!checkUrl(request, response, url)){
return;
}
chain.doFilter(servletRequest, servletResponse);
return;
} catch (FilterException e1) {
//自定義異常,這里可以直接把錯誤信息拋出去,不需要記錄日志,因為這里是業(yè)務(wù)異常。
ResponseUtil.error(response, e1.getMessage());
} catch (Exception e2) {
//無(wú)法捕捉的異常,不能直接把錯誤信息拋出去,需要包裝下錯誤信息
logger.error(e2.getMessage(), e2);
ResponseUtil.error(response, "系統異常,請稍后再試...");
}
}
/**
* 與數據庫配置url進(jìn)行匹配
* @param request
* @param response
* @param url
* @return
*/
private boolean checkUrl(HttpServletRequest request, HttpServletResponse response, String url) {
boolean passFlag=true;
//與數據庫進(jìn)行匹配
Map urlConfig = getUrlConfig();
//比較匹配項
String substring = url.substring(url.lastIndexOf("/") + 1);
if (!urlConfig.containsKey(substring)) {
return passFlag;
}
CallBackMsg msg = wxUserVerify.doVerify(request, response);
if (msg.getResultCode().equals(CallBackMsg.WX_VALID_CONTINUE)) {
passFlag=false;
} else if (msg.getResultCode().equals(Const.ERROR_CODE)) {
throw new FilterException(FilterExceptionEnum.ERROR_WX_VALID_FAILE);
}
return passFlag;
}
/**
* 獲取地址配置信息
* @return
*/
private Map getUrlConfig() {
// 3.獲取需要驗證用戶(hù)的請求配置
Map configAddressList = gcService.getConfigAddressList();
logger.info(configAddressList.toString());
if (configAddressList.isEmpty()) {
logger.error("urlValidConfig為空,請檢查!");
throw new FilterException(FilterExceptionEnum.ERROR_VALIDCONF_NULL);
}
return configAddressList;
}
@Override
public void destroy() {
}
}
通過(guò)創(chuàng )建一個(gè)類(lèi)來(lái)實(shí)現Filter過(guò)濾器進(jìn)行驗證,@WebFilter(urlPatterns = "/*", filterName = "authFilter")對所有請求進(jìn)行過(guò)濾攔截,其中封裝的方法當是特定請求時(shí)會(huì )被拉取申請授權
private CallBackMsg impower(HttpServletRequest request, HttpServletResponse response) {
CallBackMsg msg = new CallBackMsg();
String code = request.getParameter("code");
try {
// 從配置獲取access_token
String appId = getWxParams("wxAppid");
String secret = getWxParams("wxSecret");
String accessTokenUrl = gcService.getUrlWxFwGetToken() + "?appid=" + appId + "&secret=" + secret + "&code="
+ code + "&grant_type=authorization_code";
logger.info("accessTokenUrl:" + accessTokenUrl);
String accessTokenResult = "";
if (StringUtil.isEmpty(proxyFlag)) {
proxyFlag = gcService.getProxyFlag();
}
if (Const.PROXY_FLAG_USED.equals(proxyFlag)) {
accessTokenResult = HttpUtils.sendPostHttpsViaProxy(accessTokenUrl, "", gcService.getProxyIp(),
gcService.getProxyPort());
} else {
accessTokenResult = HttpUtils.sendPostHttps(accessTokenUrl, "");
}
logger.info("accessTokenResult:" + accessTokenResult);
JSONObject accessTokenJson = JSONObject.parseObject(accessTokenResult);
String accessToken = accessTokenJson.getString("access_token");
logger.info("accessToken:" + accessToken);
String openid = accessTokenJson.getString("openid");
logger.info("openid:" + openid);
// 查詢(xún)數據庫中的用戶(hù)數據
String reqUrl = gcService.getSkylarkServiceUrl() + "wechat/searchwxuserinfo";
ResponseDto responseQuery = this.restInvoke(reqUrl, getQueryParam(openid, null, 1));
String userInfoDb = responseQuery.getRspMesg();
JSONObject userInfoDbJson = JSONObject.parseObject(userInfoDb);
String uuid = "";
// 數據庫不存在該用戶(hù)則保存數據庫,存在則返回已存在的UUID
if (StringUtils.isEmpty(userInfoDbJson)) {
// 查詢(xún)微信中的用戶(hù)數據
uuid = UUIDGenerator.genUUID();
// 獲取用戶(hù)信息
String userInfoUrl = gcService.getUrlWxFwGetUser() + "?access_token=" + accessToken + "&openid="
+ openid + "&lang=zh_CN";
logger.info("userInfoUrl:" + userInfoUrl);
String userInfoResult = "";
if (Const.PROXY_FLAG_USED.equals(proxyFlag)) {
userInfoResult = HttpUtils.sendPostHttpsViaProxy(userInfoUrl, "", gcService.getProxyIp(),
gcService.getProxyPort());
} else {
userInfoResult = HttpUtils.sendPostHttps(userInfoUrl, "");
}
logger.info("userInfoResult:" + userInfoResult);
JSONObject userInfoJson = JSONObject.parseObject(userInfoResult);
// 固化用戶(hù)
String addReq = gcService.getSkylarkServiceUrl() + "wechat/addwxuserinfo";
ResponseDto addResp = this.restInvoke(addReq,
getEntityParam(uuid, openid, userInfoJson));
// 失敗另處理
if (!(Const.SUCCESS_CODE).equals(addResp.getRspCode())) {
// 保存數據失敗,認證失敗
msg.setResultCode(Const.ERROR_CODE);
return msg;
}
} else {
uuid = userInfoDbJson.getString("id");
}
// 權限:本項目中的類(lèi)都可以訪(fǎng)問(wèn)該cookie,存儲到客戶(hù)端
Cookie cookie = new Cookie(Const.TOKEN, uuid);
cookie.setPath("/");
response.addCookie(cookie);
//將token存入session
HttpSession session = request.getSession();
session.setAttribute("authToken",uuid);
} catch (IOException e) {
logger.error(e.getMessage());
}
msg.setResultCode(Const.SUCCESS_CODE);
return msg;
}
因為項目中使用了代理服務(wù)器,不需要本地開(kāi)發(fā),所以方法有點(diǎn)亂,但是大體思路是客戶(hù)端發(fā)起帶有特殊后綴的請求時(shí),被過(guò)濾器攔截接下來(lái),與微信服務(wù)器進(jìn)行交互授權的過(guò)程。授權成功后,向微信提供回調地址,并將微信帶回的用戶(hù)信息保存在表中。同時(shí),當前用戶(hù)在庫表中生成的唯一標識標識存儲在cookie域中。
這樣就可以將獲取到的openid放入cookie中了。那么這個(gè)openId就可以固化了,也就是存儲在數據庫中。在會(huì )話(huà)中,如果點(diǎn)擊某個(gè)頁(yè)面需要使用微信授權,可以先從cookie域中獲取。如果cookie不可用,則向微信發(fā)起授權請求,獲取openid后,存儲cookie和curing。有一些步驟可以實(shí)現這一點(diǎn)。微信開(kāi)放平臺上有相關(guān)的demo。如果您需要我提供它們,請留言。后續授權碼我會(huì )貼出來(lái)供大家參考。