|
|
@@ -3,33 +3,30 @@
|
|
|
<head>
|
|
|
<meta charset="UTF-8" />
|
|
|
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no" />
|
|
|
- <title>微信授权</title>
|
|
|
+ <title>正在加载中</title>
|
|
|
<style>
|
|
|
- body { margin: 0; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif; background: #f5f5f5; color: #333; }
|
|
|
- .wrap { max-width: 480px; margin: 80px auto; padding: 24px; text-align: center; }
|
|
|
- .tip { font-size: 15px; line-height: 1.6; color: #666; }
|
|
|
- .err { color: #333; margin-top: 12px; word-break: break-all; }
|
|
|
- .fail { color: #c00; }
|
|
|
+ html, body { margin: 0; padding: 0; background: #fff; }
|
|
|
</style>
|
|
|
</head>
|
|
|
<body>
|
|
|
- <div class="wrap">
|
|
|
- <p class="tip" id="tip">正在授权,请稍候…</p>
|
|
|
- <p class="err" id="err"></p>
|
|
|
- </div>
|
|
|
<script>
|
|
|
/**
|
|
|
- * 部署说明:
|
|
|
- * 1) 同源部署:API_BASE 留空
|
|
|
- * 2) Nginx 静态 + 独立 API:API_BASE 填后端地址
|
|
|
- * 3) 公众号后台「网页授权域名」= 本页域名
|
|
|
- * 4) 已有 openId 时也会调 /mp/silent/userInfo 刷新拉黑/加销售信息
|
|
|
+ * 空白页:抬头「正在加载中」,拿到跳转链接后 location.replace。
|
|
|
+ * 调用约定(每种进入场景只打 1 次对应接口):
|
|
|
+ * - 首次进入无 openId → GET /mp/silent/authUrl,再跳转微信
|
|
|
+ * - 微信回跳带 code → 只调 POST /mp/silent/resolve
|
|
|
+ * - 再次进入已有 openId → 只调 POST /mp/silent/userInfo
|
|
|
*/
|
|
|
var API_BASE = 'https://wx.jy.cc/apis';
|
|
|
var APP_ID = 'wxe704b34112659eb1';
|
|
|
var OPENID_KEY = 'mp_silent_openid';
|
|
|
+ var BIZ_CACHE_KEY = 'mp_silent_biz_cache';
|
|
|
+ var REQ_LOCK_KEY = 'mp_silent_req_lock';
|
|
|
+ /** 同一次授权回跳若页面被加载两次,短时间内复用结果,避免 resolve + userInfo */
|
|
|
+ var BIZ_CACHE_TTL_MS = 8000;
|
|
|
+
|
|
|
+ var pageBooted = false;
|
|
|
|
|
|
- function $(id) { return document.getElementById(id); }
|
|
|
function qs(name) {
|
|
|
var m = location.search.match(new RegExp('[?&]' + name + '=([^&]*)'));
|
|
|
return m ? decodeURIComponent(m[1].replace(/\+/g, ' ')) : '';
|
|
|
@@ -63,43 +60,68 @@
|
|
|
history.replaceState(null, '', location.pathname + (location.hash || ''));
|
|
|
}
|
|
|
}
|
|
|
- function showError(msg) {
|
|
|
- $('tip').textContent = '授权失败';
|
|
|
- $('err').className = 'err fail';
|
|
|
- $('err').textContent = msg || '未知错误';
|
|
|
- }
|
|
|
- function finishOk(openId, jumpUrl, tip, blacklisted) {
|
|
|
+ function finishOk(openId, jumpUrl) {
|
|
|
clearCodeFromUrl();
|
|
|
if (openId) {
|
|
|
try { sessionStorage.setItem(OPENID_KEY, openId); } catch (e) {}
|
|
|
}
|
|
|
var url = (jumpUrl || '').replace(/^\s+|\s+$/g, '');
|
|
|
if (url && !isSameAuthPage(url)) {
|
|
|
- $('tip').textContent = '授权成功,正在跳转…';
|
|
|
- $('err').textContent = '';
|
|
|
location.replace(url);
|
|
|
- return;
|
|
|
}
|
|
|
- $('tip').textContent = tip || '授权成功';
|
|
|
- $('err').className = blacklisted ? 'err fail' : 'err';
|
|
|
- $('err').textContent = '';
|
|
|
+ // 无跳转链接:保持空白页,抬头仍为「正在加载中」
|
|
|
+ }
|
|
|
+ function saveBizCache(res) {
|
|
|
+ try {
|
|
|
+ sessionStorage.setItem(BIZ_CACHE_KEY, JSON.stringify({
|
|
|
+ t: Date.now(),
|
|
|
+ openId: res.openId || '',
|
|
|
+ url: res.url || ''
|
|
|
+ }));
|
|
|
+ } catch (e) {}
|
|
|
+ }
|
|
|
+ function readBizCache(openId) {
|
|
|
+ try {
|
|
|
+ var raw = sessionStorage.getItem(BIZ_CACHE_KEY);
|
|
|
+ if (!raw) return null;
|
|
|
+ var o = JSON.parse(raw);
|
|
|
+ if (!o || o.openId !== openId) return null;
|
|
|
+ if (Date.now() - (o.t || 0) > BIZ_CACHE_TTL_MS) return null;
|
|
|
+ return o;
|
|
|
+ } catch (e) {
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ /** 页面级 + 短时存储锁,防止并发/双加载重复请求 */
|
|
|
+ function tryLockRequest(kind) {
|
|
|
+ if (window.__mpSilentReqing) {
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+ try {
|
|
|
+ var now = Date.now();
|
|
|
+ var raw = sessionStorage.getItem(REQ_LOCK_KEY);
|
|
|
+ if (raw) {
|
|
|
+ var o = JSON.parse(raw);
|
|
|
+ if (o && o.kind === kind && now - (o.t || 0) < 3000) {
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ sessionStorage.setItem(REQ_LOCK_KEY, JSON.stringify({ kind: kind, t: now }));
|
|
|
+ } catch (e) {}
|
|
|
+ window.__mpSilentReqing = true;
|
|
|
+ return true;
|
|
|
+ }
|
|
|
+ function unlockRequest() {
|
|
|
+ window.__mpSilentReqing = false;
|
|
|
}
|
|
|
function applyBizRes(res) {
|
|
|
if (!res || res.code !== 200 || !res.openId) {
|
|
|
- showError((res && res.msg) || '获取用户信息失败');
|
|
|
+ unlockRequest();
|
|
|
return;
|
|
|
}
|
|
|
- finishOk(res.openId, res.url || '', res.tip || '', !!res.blacklisted);
|
|
|
- }
|
|
|
- /** 已有 openId:每次进入都调后端刷新业务信息 */
|
|
|
- function loadUserInfo(openId, state) {
|
|
|
- $('tip').textContent = '正在获取信息…';
|
|
|
- postJson(apiUrl('/mp/silent/userInfo'), {
|
|
|
- openId: openId,
|
|
|
- state: state || undefined
|
|
|
- }).then(applyBizRes).catch(function (e) {
|
|
|
- showError('网络异常:' + (e && e.message ? e.message : e));
|
|
|
- });
|
|
|
+ saveBizCache(res);
|
|
|
+ unlockRequest();
|
|
|
+ finishOk(res.openId, res.url || '');
|
|
|
}
|
|
|
function getJson(url) {
|
|
|
return fetch(url, { method: 'GET', credentials: 'omit' }).then(function (res) {
|
|
|
@@ -116,53 +138,52 @@
|
|
|
return res.json();
|
|
|
});
|
|
|
}
|
|
|
-
|
|
|
- function start() {
|
|
|
- if (!isWeChat()) {
|
|
|
- showError('请在微信中打开本页面');
|
|
|
+ /** 调 authUrl 获取授权链接后跳转微信 */
|
|
|
+ function goWechatOAuth(appId, state) {
|
|
|
+ if (!tryLockRequest('authUrl')) {
|
|
|
return;
|
|
|
}
|
|
|
-
|
|
|
- var appId = qs('appId') || APP_ID;
|
|
|
- var state = qs('state') || '';
|
|
|
- var code = qs('code');
|
|
|
-
|
|
|
- if (!code) {
|
|
|
- var cachedOpenId = '';
|
|
|
- try { cachedOpenId = sessionStorage.getItem(OPENID_KEY) || ''; } catch (e) {}
|
|
|
- // 已有 openId 且非强制重新授权:仍调接口拿拉黑/加销售信息
|
|
|
- if (cachedOpenId && qs('force') !== '1') {
|
|
|
- loadUserInfo(cachedOpenId, state);
|
|
|
+ var redirectUri = currentPageUrlWithoutQuery();
|
|
|
+ var authQuery = 'redirectUri=' + encodeURIComponent(redirectUri);
|
|
|
+ if (appId) authQuery += '&appId=' + encodeURIComponent(appId);
|
|
|
+ if (state) authQuery += '&state=' + encodeURIComponent(state);
|
|
|
+ getJson(apiUrl('/mp/silent/authUrl?' + authQuery)).then(function (res) {
|
|
|
+ unlockRequest();
|
|
|
+ if (!res || res.code !== 200 || !res.oauthUrl) {
|
|
|
return;
|
|
|
}
|
|
|
-
|
|
|
- var redirectUri = currentPageUrlWithoutQuery();
|
|
|
- var authQuery = 'redirectUri=' + encodeURIComponent(redirectUri);
|
|
|
- if (appId) authQuery += '&appId=' + encodeURIComponent(appId);
|
|
|
- if (state) authQuery += '&state=' + encodeURIComponent(state);
|
|
|
-
|
|
|
- getJson(apiUrl('/mp/silent/authUrl?' + authQuery)).then(function (res) {
|
|
|
- if (!res || res.code !== 200 || !res.oauthUrl) {
|
|
|
- showError((res && res.msg) || '获取授权链接失败');
|
|
|
- return;
|
|
|
- }
|
|
|
- location.replace(res.oauthUrl);
|
|
|
- }).catch(function (e) {
|
|
|
- showError('网络异常:' + (e && e.message ? e.message : e));
|
|
|
- });
|
|
|
+ location.replace(res.oauthUrl);
|
|
|
+ }).catch(function () {
|
|
|
+ unlockRequest();
|
|
|
+ });
|
|
|
+ }
|
|
|
+ /** 只调 userInfo 一次 */
|
|
|
+ function loadUserInfoOnce(openId, state) {
|
|
|
+ var cached = readBizCache(openId);
|
|
|
+ if (cached) {
|
|
|
+ finishOk(cached.openId, cached.url);
|
|
|
return;
|
|
|
}
|
|
|
-
|
|
|
- // 同一 code 只换一次票;已换过则用 openId 再拉业务信息
|
|
|
+ if (!tryLockRequest('userInfo')) {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ postJson(apiUrl('/mp/silent/userInfo'), {
|
|
|
+ openId: openId,
|
|
|
+ state: state || undefined
|
|
|
+ }).then(applyBizRes).catch(function () {
|
|
|
+ unlockRequest();
|
|
|
+ });
|
|
|
+ }
|
|
|
+ /** 只调 resolve 一次 */
|
|
|
+ function resolveOnce(code, appId, state) {
|
|
|
var codeLockKey = 'mp_silent_code_' + code;
|
|
|
try {
|
|
|
if (sessionStorage.getItem(codeLockKey) === 'done') {
|
|
|
- var doneOpenId = '';
|
|
|
- try { doneOpenId = sessionStorage.getItem(OPENID_KEY) || ''; } catch (e) {}
|
|
|
- if (doneOpenId) {
|
|
|
- loadUserInfo(doneOpenId, qs('state') || state);
|
|
|
- } else {
|
|
|
- showError('授权状态异常,请刷新重试');
|
|
|
+ clearCodeFromUrl();
|
|
|
+ var openId = '';
|
|
|
+ try { openId = sessionStorage.getItem(OPENID_KEY) || ''; } catch (e) {}
|
|
|
+ if (openId) {
|
|
|
+ loadUserInfoOnce(openId, state);
|
|
|
}
|
|
|
return;
|
|
|
}
|
|
|
@@ -172,25 +193,57 @@
|
|
|
sessionStorage.setItem(codeLockKey, 'pending');
|
|
|
} catch (e) {}
|
|
|
|
|
|
- var resolveState = qs('state') || state;
|
|
|
+ if (!tryLockRequest('resolve')) {
|
|
|
+ return;
|
|
|
+ }
|
|
|
postJson(apiUrl('/mp/silent/resolve'), {
|
|
|
code: code,
|
|
|
appId: appId || undefined,
|
|
|
- state: resolveState || undefined
|
|
|
+ state: state || undefined
|
|
|
}).then(function (res) {
|
|
|
if (!res || res.code !== 200 || !res.openId) {
|
|
|
try { sessionStorage.removeItem(codeLockKey); } catch (e) {}
|
|
|
- showError((res && res.msg) || '获取 openId 失败');
|
|
|
+ unlockRequest();
|
|
|
return;
|
|
|
}
|
|
|
try { sessionStorage.setItem(codeLockKey, 'done'); } catch (e) {}
|
|
|
applyBizRes(res);
|
|
|
- }).catch(function (e) {
|
|
|
+ }).catch(function () {
|
|
|
try { sessionStorage.removeItem(codeLockKey); } catch (e2) {}
|
|
|
- showError('网络异常:' + (e && e.message ? e.message : e));
|
|
|
+ unlockRequest();
|
|
|
});
|
|
|
}
|
|
|
|
|
|
+ function start() {
|
|
|
+ if (pageBooted) {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ pageBooted = true;
|
|
|
+ document.title = '正在加载中';
|
|
|
+
|
|
|
+ if (!isWeChat()) {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ var appId = qs('appId') || APP_ID;
|
|
|
+ var state = qs('state') || '';
|
|
|
+ var code = qs('code');
|
|
|
+
|
|
|
+ if (code) {
|
|
|
+ resolveOnce(code, appId, state);
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ var cachedOpenId = '';
|
|
|
+ try { cachedOpenId = sessionStorage.getItem(OPENID_KEY) || ''; } catch (e) {}
|
|
|
+ if (cachedOpenId && qs('force') !== '1') {
|
|
|
+ loadUserInfoOnce(cachedOpenId, state);
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ goWechatOAuth(appId, state);
|
|
|
+ }
|
|
|
+
|
|
|
start();
|
|
|
</script>
|
|
|
</body>
|