init
项目初始化
This commit is contained in:
76
utils/api.js
Normal file
76
utils/api.js
Normal file
@@ -0,0 +1,76 @@
|
||||
//const BASE_URL = 'https://muguaclub.cn';
|
||||
//const BASE_URL = "http://127.0.0.1:8000";
|
||||
const BASE_URL = 'https://m.muguaclub.cn/clock';
|
||||
const STATIC_BASE_URL = 'https://m.muguaclub.cn';
|
||||
|
||||
let redirectingProfile = false;
|
||||
|
||||
function goProfile() {
|
||||
if (redirectingProfile) {
|
||||
return;
|
||||
}
|
||||
const pages = getCurrentPages();
|
||||
const current = pages.length ? pages[pages.length - 1] : null;
|
||||
const route = current && current.route;
|
||||
if (route === 'pages/profile/profile') {
|
||||
return;
|
||||
}
|
||||
redirectingProfile = true;
|
||||
wx.switchTab({
|
||||
url: '/pages/profile/profile',
|
||||
complete() {
|
||||
setTimeout(() => {
|
||||
redirectingProfile = false;
|
||||
}, 1200);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function request(options) {
|
||||
const { url, method = 'GET', data = {}, auth = true } = options;
|
||||
const isIdempotent = method === 'GET' || method === 'DELETE';
|
||||
let retried = false;
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
const doRequest = () => {
|
||||
const header = { 'Content-Type': 'application/json' };
|
||||
const token = wx.getStorageSync('token');
|
||||
if (auth && token) {
|
||||
header['Authorization'] = 'Bearer ' + token;
|
||||
}
|
||||
wx.request({
|
||||
url: BASE_URL + url,
|
||||
method,
|
||||
data,
|
||||
header,
|
||||
success(res) {
|
||||
if (res.statusCode >= 200 && res.statusCode < 300) {
|
||||
resolve(res.data);
|
||||
} else if (res.statusCode === 401) {
|
||||
wx.removeStorageSync('token');
|
||||
if (auth) {
|
||||
goProfile();
|
||||
}
|
||||
reject(res.data || { error: '未登录' });
|
||||
} else {
|
||||
reject(res.data || { error: '请求失败' });
|
||||
}
|
||||
},
|
||||
fail(err) {
|
||||
// 冷启动时首个请求可能因网络栈未就绪而失败,空闲时对幂等请求自动重试一次
|
||||
const msg = (err && err.errMsg) || '';
|
||||
const domainError = /url not in domain list|domain not/i.test(msg);
|
||||
if (!retried && isIdempotent && !domainError) {
|
||||
retried = true;
|
||||
setTimeout(doRequest, 800);
|
||||
return;
|
||||
}
|
||||
reject(err);
|
||||
}
|
||||
});
|
||||
};
|
||||
doRequest();
|
||||
});
|
||||
}
|
||||
|
||||
module.exports = { request, BASE_URL, STATIC_BASE_URL };
|
||||
109
utils/auth.js
Normal file
109
utils/auth.js
Normal file
@@ -0,0 +1,109 @@
|
||||
const api = require('./api');
|
||||
|
||||
let loginPromise = null;
|
||||
|
||||
function getToken() {
|
||||
return wx.getStorageSync('token') || '';
|
||||
}
|
||||
|
||||
function setToken(token) {
|
||||
wx.setStorageSync('token', token);
|
||||
}
|
||||
|
||||
function clearToken() {
|
||||
wx.removeStorageSync('token');
|
||||
}
|
||||
|
||||
function wxLogin(retries) {
|
||||
const remain = typeof retries === 'number' ? retries : 3;
|
||||
return new Promise((resolve, reject) => {
|
||||
wx.login({
|
||||
success(res) {
|
||||
if (res && res.code) {
|
||||
resolve(res.code);
|
||||
} else {
|
||||
reject(new Error((res && res.errMsg) || '微信登录失败'));
|
||||
}
|
||||
},
|
||||
fail: reject
|
||||
});
|
||||
}).catch((err) => {
|
||||
// 冷启动时微信登录服务可能尚未就绪,指数退避重试
|
||||
if (remain > 0) {
|
||||
const delay = 400 + (4 - remain) * 200; // 首次 600ms,依次 800ms、1000ms
|
||||
return new Promise((resolve) => setTimeout(resolve, delay))
|
||||
.then(() => wxLogin(remain - 1));
|
||||
}
|
||||
throw err;
|
||||
});
|
||||
}
|
||||
|
||||
// 整个微信登录流程失败时重新取 code 再试一次,覆盖冷启动首个网络请求偶发失败
|
||||
function wechatLogin(retries) {
|
||||
const remain = typeof retries === 'number' ? retries : 1;
|
||||
return wxLogin()
|
||||
.then((code) => api.request({
|
||||
url: '/api/v1/auth/wechat-login',
|
||||
method: 'POST',
|
||||
data: { code },
|
||||
auth: false
|
||||
}))
|
||||
.catch((err) => {
|
||||
if (remain > 0) {
|
||||
return new Promise((resolve) => setTimeout(resolve, 500)).then(() => wechatLogin(remain - 1));
|
||||
}
|
||||
throw err;
|
||||
});
|
||||
}
|
||||
|
||||
// 单例登录:App.onLaunch 与首屏页面 onShow 可能几乎同时调用,
|
||||
// 共享同一个进行中的登录 Promise,避免重复 wx.login / wechat-login 造成竞态。
|
||||
// 关键:登录失败时必须清空单例,让下一次调用能重新发起,
|
||||
// 否则冷启动 wx.login 偶发失败后,后续页面永远拿不到 token。
|
||||
function ensureLogin(refresh) {
|
||||
const existing = getToken();
|
||||
if (existing && !refresh) {
|
||||
return Promise.resolve(existing);
|
||||
}
|
||||
if (loginPromise) {
|
||||
return loginPromise;
|
||||
}
|
||||
const p = wechatLogin()
|
||||
.then((res) => {
|
||||
if (res && res.success) {
|
||||
setToken(res.token);
|
||||
return res.token;
|
||||
}
|
||||
// need_phone:未绑定手机号时 resolve(null),由页面引导去「我的」绑定
|
||||
return null;
|
||||
})
|
||||
.finally(() => {
|
||||
// 无论成功失败,完成后都清掉单例,让下一次调用可以重新发起
|
||||
if (loginPromise === p) {
|
||||
loginPromise = null;
|
||||
}
|
||||
});
|
||||
loginPromise = p;
|
||||
return p;
|
||||
}
|
||||
|
||||
function loginWithPhone(phoneCode) {
|
||||
return wxLogin()
|
||||
.then((code) => api.request({
|
||||
url: '/api/v1/auth/bind-phone',
|
||||
method: 'POST',
|
||||
data: { code, phone_code: phoneCode },
|
||||
auth: false
|
||||
}))
|
||||
.then((res) => {
|
||||
if (res && res.success) {
|
||||
setToken(res.token);
|
||||
return res;
|
||||
}
|
||||
const error = new Error((res && res.error) || '手机号登录失败');
|
||||
error.error = (res && res.error) || '手机号登录失败';
|
||||
throw error;
|
||||
});
|
||||
}
|
||||
|
||||
module.exports = { getToken, setToken, clearToken, wxLogin, ensureLogin, loginWithPhone };
|
||||
Reference in New Issue
Block a user