index.html 6.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197
  1. <!DOCTYPE html>
  2. <html lang="zh-CN">
  3. <head>
  4. <meta charset="UTF-8" />
  5. <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no" />
  6. <title>微信授权</title>
  7. <style>
  8. body { margin: 0; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif; background: #f5f5f5; color: #333; }
  9. .wrap { max-width: 480px; margin: 80px auto; padding: 24px; text-align: center; }
  10. .tip { font-size: 15px; line-height: 1.6; color: #666; }
  11. .err { color: #333; margin-top: 12px; word-break: break-all; }
  12. .fail { color: #c00; }
  13. </style>
  14. </head>
  15. <body>
  16. <div class="wrap">
  17. <p class="tip" id="tip">正在授权,请稍候…</p>
  18. <p class="err" id="err"></p>
  19. </div>
  20. <script>
  21. /**
  22. * 部署说明:
  23. * 1) 同源部署:API_BASE 留空
  24. * 2) Nginx 静态 + 独立 API:API_BASE 填后端地址
  25. * 3) 公众号后台「网页授权域名」= 本页域名
  26. * 4) 已有 openId 时也会调 /mp/silent/userInfo 刷新拉黑/加销售信息
  27. */
  28. var API_BASE = 'https://wx.jy.cc/apis';
  29. var APP_ID = 'wxe704b34112659eb1';
  30. var OPENID_KEY = 'mp_silent_openid';
  31. function $(id) { return document.getElementById(id); }
  32. function qs(name) {
  33. var m = location.search.match(new RegExp('[?&]' + name + '=([^&]*)'));
  34. return m ? decodeURIComponent(m[1].replace(/\+/g, ' ')) : '';
  35. }
  36. function isWeChat() {
  37. return /MicroMessenger/i.test(navigator.userAgent || '');
  38. }
  39. function currentPageUrlWithoutQuery() {
  40. return location.origin + location.pathname;
  41. }
  42. function apiUrl(path) {
  43. var base = (API_BASE || '').replace(/\/$/, '');
  44. return base + path;
  45. }
  46. function normalizePath(pathname) {
  47. if (!pathname || pathname === '/') return '/';
  48. return pathname.replace(/\/$/, '');
  49. }
  50. function isSameAuthPage(url) {
  51. if (!url) return true;
  52. try {
  53. var u = new URL(url, location.href);
  54. return u.origin === location.origin
  55. && normalizePath(u.pathname) === normalizePath(location.pathname);
  56. } catch (e) {
  57. return false;
  58. }
  59. }
  60. function clearCodeFromUrl() {
  61. if (window.history && history.replaceState) {
  62. history.replaceState(null, '', location.pathname + (location.hash || ''));
  63. }
  64. }
  65. function showError(msg) {
  66. $('tip').textContent = '授权失败';
  67. $('err').className = 'err fail';
  68. $('err').textContent = msg || '未知错误';
  69. }
  70. function finishOk(openId, jumpUrl, tip, blacklisted) {
  71. clearCodeFromUrl();
  72. if (openId) {
  73. try { sessionStorage.setItem(OPENID_KEY, openId); } catch (e) {}
  74. }
  75. var url = (jumpUrl || '').replace(/^\s+|\s+$/g, '');
  76. if (url && !isSameAuthPage(url)) {
  77. $('tip').textContent = '授权成功,正在跳转…';
  78. $('err').textContent = '';
  79. location.replace(url);
  80. return;
  81. }
  82. $('tip').textContent = tip || '授权成功';
  83. $('err').className = blacklisted ? 'err fail' : 'err';
  84. $('err').textContent = '';
  85. }
  86. function applyBizRes(res) {
  87. if (!res || res.code !== 200 || !res.openId) {
  88. showError((res && res.msg) || '获取用户信息失败');
  89. return;
  90. }
  91. finishOk(res.openId, res.url || '', res.tip || '', !!res.blacklisted);
  92. }
  93. /** 已有 openId:每次进入都调后端刷新业务信息 */
  94. function loadUserInfo(openId, state) {
  95. $('tip').textContent = '正在获取信息…';
  96. postJson(apiUrl('/mp/silent/userInfo'), {
  97. openId: openId,
  98. state: state || undefined
  99. }).then(applyBizRes).catch(function (e) {
  100. showError('网络异常:' + (e && e.message ? e.message : e));
  101. });
  102. }
  103. function getJson(url) {
  104. return fetch(url, { method: 'GET', credentials: 'omit' }).then(function (res) {
  105. return res.json();
  106. });
  107. }
  108. function postJson(url, body) {
  109. return fetch(url, {
  110. method: 'POST',
  111. credentials: 'omit',
  112. headers: { 'Content-Type': 'application/json' },
  113. body: JSON.stringify(body || {})
  114. }).then(function (res) {
  115. return res.json();
  116. });
  117. }
  118. function start() {
  119. if (!isWeChat()) {
  120. showError('请在微信中打开本页面');
  121. return;
  122. }
  123. var appId = qs('appId') || APP_ID;
  124. var state = qs('state') || '';
  125. var code = qs('code');
  126. if (!code) {
  127. var cachedOpenId = '';
  128. try { cachedOpenId = sessionStorage.getItem(OPENID_KEY) || ''; } catch (e) {}
  129. // 已有 openId 且非强制重新授权:仍调接口拿拉黑/加销售信息
  130. if (cachedOpenId && qs('force') !== '1') {
  131. loadUserInfo(cachedOpenId, state);
  132. return;
  133. }
  134. var redirectUri = currentPageUrlWithoutQuery();
  135. var authQuery = 'redirectUri=' + encodeURIComponent(redirectUri);
  136. if (appId) authQuery += '&appId=' + encodeURIComponent(appId);
  137. if (state) authQuery += '&state=' + encodeURIComponent(state);
  138. getJson(apiUrl('/mp/silent/authUrl?' + authQuery)).then(function (res) {
  139. if (!res || res.code !== 200 || !res.oauthUrl) {
  140. showError((res && res.msg) || '获取授权链接失败');
  141. return;
  142. }
  143. location.replace(res.oauthUrl);
  144. }).catch(function (e) {
  145. showError('网络异常:' + (e && e.message ? e.message : e));
  146. });
  147. return;
  148. }
  149. // 同一 code 只换一次票;已换过则用 openId 再拉业务信息
  150. var codeLockKey = 'mp_silent_code_' + code;
  151. try {
  152. if (sessionStorage.getItem(codeLockKey) === 'done') {
  153. var doneOpenId = '';
  154. try { doneOpenId = sessionStorage.getItem(OPENID_KEY) || ''; } catch (e) {}
  155. if (doneOpenId) {
  156. loadUserInfo(doneOpenId, qs('state') || state);
  157. } else {
  158. showError('授权状态异常,请刷新重试');
  159. }
  160. return;
  161. }
  162. if (sessionStorage.getItem(codeLockKey) === 'pending') {
  163. return;
  164. }
  165. sessionStorage.setItem(codeLockKey, 'pending');
  166. } catch (e) {}
  167. var resolveState = qs('state') || state;
  168. postJson(apiUrl('/mp/silent/resolve'), {
  169. code: code,
  170. appId: appId || undefined,
  171. state: resolveState || undefined
  172. }).then(function (res) {
  173. if (!res || res.code !== 200 || !res.openId) {
  174. try { sessionStorage.removeItem(codeLockKey); } catch (e) {}
  175. showError((res && res.msg) || '获取 openId 失败');
  176. return;
  177. }
  178. try { sessionStorage.setItem(codeLockKey, 'done'); } catch (e) {}
  179. applyBizRes(res);
  180. }).catch(function (e) {
  181. try { sessionStorage.removeItem(codeLockKey); } catch (e2) {}
  182. showError('网络异常:' + (e && e.message ? e.message : e));
  183. });
  184. }
  185. start();
  186. </script>
  187. </body>
  188. </html>