四端上传

This commit is contained in:
tianmo committed 2026-09-29 15:25:40 +08:00
1 parent 25bbb5f9c8
commit c6e2cb4d5b
276 files changed
+17812 -3

No files matched your search

+198
View File
@@ -0,0 +1,198 @@
/**
* 统一请求层与接口封装。
*
* 替代旧版本「每个接口手写一遍 wx.request + Promise」的写法。
* 职责:
* - 自动附带 Authorization
* - 统一解包 { code, message, data, requestId }
* - token 失效自动刷新,刷新失败自动重新登录
*/
const config = require('../config/index');
const store = require('./store');
/** 进行中的刷新,避免并发 401 触发多次刷新 */
let refreshing = null;
function staticUrl(path) {
if (!path) return '';
if (/^https?:\/\//i.test(path)) return path;
return config.STATIC_BASE_URL.replace(/\/+$/, '') + '/' + String(path).replace(/^\/+/, '');
}
function normalizeError(body, statusCode) {
if (body && typeof body.code === 'number') {
return { code: body.code, message: body.message || '请求失败', requestId: body.requestId };
}
return { code: -1, message: '服务异常(' + statusCode + ')' };
}
/** 底层请求。不做鉴权重试,返回完整响应体。 */
function rawRequest(options) {
const header = { 'content-type': 'application/json' };
if (options.auth !== false) {
const token = store.getToken();
if (token) header.Authorization = 'Bearer ' + token;
}
return new Promise((resolve, reject) => {
wx.request({
url: config.API_BASE_URL.replace(/\/+$/, '') + options.url,
method: options.method || 'GET',
data: options.data,
header,
timeout: config.TIMEOUT,
success(res) {
resolve({ statusCode: res.statusCode, body: res.data || {} });
},
fail() {
reject({ code: -1, message: '网络异常,请检查网络后重试' });
},
});
});
}
/** 带鉴权的请求。token 失效时刷新一次并重试。 */
function request(options) {
return rawRequest(options).then(({ statusCode, body }) => {
if (body.code === 0) return body.data;
const needLogin = statusCode === 401 || body.code === 2000 || body.code === 2001;
if (needLogin && options.auth !== false && options.retried !== true) {
return refreshAccessToken()
.then(() => request(Object.assign({}, options, { retried: true })))
.catch(() => {
store.reset();
throw { code: 2000, message: '登录已过期,请重新进入小程序' };
});
}
throw normalizeError(body, statusCode);
});
}
function refreshAccessToken() {
if (refreshing) return refreshing;
const refreshToken = store.getRefreshToken();
if (!refreshToken) return Promise.reject({ code: 2000, message: '未登录' });
refreshing = rawRequest({
url: '/v1/auth/refresh',
method: 'POST',
data: { refreshToken },
auth: false,
})
.then(({ body }) => {
if (body.code !== 0) throw { code: 2000, message: '登录已过期' };
store.setTokens(body.data.accessToken);
return body.data.accessToken;
})
.finally(() => {
refreshing = null;
});
return refreshing;
}
// ── 登录 ──────────────────────────────────────────────────────────────────
/** wx.login 换 code,再用 code 换服务端 token。 */
function login() {
return new Promise((resolve, reject) => {
wx.login({
success(res) {
if (!res.code) {
reject({ code: 3002, message: '微信登录失败,请重试' });
return;
}
request({
url: '/v1/auth/wx-login',
method: 'POST',
data: { code: res.code },
auth: false,
})
.then((data) => {
store.setTokens(data.accessToken, data.refreshToken);
store.setUser(data.user);
resolve(data);
})
.catch(reject);
},
fail() {
reject({ code: 3002, message: '微信登录失败,请重试' });
},
});
});
}
function logout() {
return request({ url: '/v1/auth/logout', method: 'POST' }).catch(() => null).then(() => {
store.reset();
});
}
// ── 首页内容 ──────────────────────────────────────────────────────────────
const api = {
staticUrl,
login,
logout,
getConfig: () => request({ url: '/v1/config', auth: false }),
getBanners: () => request({ url: '/v1/banners', auth: false }),
getHomeCourses: () => request({ url: '/v1/home/courses', auth: false }),
getHomeFreeCourses: () => request({ url: '/v1/home/free-courses', auth: false }),
getArticles: (page, pageSize) =>
request({ url: '/v1/articles?page=' + (page || 1) + '&pageSize=' + (pageSize || 10), auth: false }),
getArticle: (id) => request({ url: '/v1/articles/' + id, auth: false }),
getTips: () => request({ url: '/v1/tips', auth: false }),
// ── 课程 ────────────────────────────────────────────────────────────
getCourses: () => request({ url: '/v1/courses' }),
getCourse: (code) => request({ url: '/v1/courses/' + code }),
getLessons: (code) => request({ url: '/v1/courses/' + code + '/lessons' }),
getPlayUrl: (code, chapterId, playId) =>
request({
url:
'/v1/courses/' +
code +
'/play?chapterId=' +
encodeURIComponent(chapterId) +
'&playId=' +
encodeURIComponent(playId),
}),
getFreeLessons: (seriesNo) =>
request({ url: '/v1/free-lessons' + (seriesNo === undefined ? '' : '?seriesNo=' + seriesNo), auth: false }),
// ── 用户 ────────────────────────────────────────────────────────────
getMe: () => request({ url: '/v1/me' }),
updateMe: (payload) => request({ url: '/v1/me', method: 'PATCH', data: payload }),
getEntitlements: () => request({ url: '/v1/me/entitlements' }),
// ── 订单与支付 ──────────────────────────────────────────────────────
createOrder: (courseCode) =>
request({ url: '/v1/orders', method: 'POST', data: { courseCode: courseCode } }),
getOrder: (outTradeNo) => request({ url: '/v1/orders/' + outTradeNo }),
};
/**
* 拉起虚拟支付。
*
* 支付成功只代表「钱付了」,权益由服务端收到发货推送后发放,
* 因此这里不直接开通课程,而是让调用方轮询订单状态。
*/
function requestVirtualPayment(payData) {
return new Promise((resolve, reject) => {
wx.requestVirtualPayment({
signData: payData.signData,
mode: payData.mode,
paySig: payData.paySig,
signature: payData.signature,
success: resolve,
fail: reject,
});
});
}
module.exports = Object.assign(api, { requestVirtualPayment });