|
|
@@ -5,122 +5,198 @@ import humps from 'humps';
|
|
|
import { Auth } from '@/utils/auth';
|
|
|
import { AccountTransferSchema } from '@/schemas/account';
|
|
|
|
|
|
+const API_BASE_URL = process.env.TARO_APP_API_BASE_URL;
|
|
|
|
|
|
-const API_BASE_URL = process.env.TARO_APP_API_BASE_URL
|
|
|
+// ---- Token 刷新 ----
|
|
|
|
|
|
-const handleExpiredLogin = () => {
|
|
|
- Auth.clearToken();
|
|
|
- Taro.redirectTo({ url: '/pages/login/index' });
|
|
|
- throw new Error('登录已过期,请重新登录');
|
|
|
-}
|
|
|
+/** 防止并发刷新:Promise 锁 */
|
|
|
+let refreshPromise: Promise<boolean> | null = null;
|
|
|
|
|
|
+/** 静默刷新 token,成功返回 true */
|
|
|
+const tryRefreshToken = async (): Promise<boolean> => {
|
|
|
+ const refreshToken = Auth.getRefreshToken();
|
|
|
+ if (!refreshToken) return false;
|
|
|
|
|
|
-export const requestApi = async (options: Taro.request.Option, defaultErrorMsg: string = '') => {
|
|
|
+ // 已有进行中的刷新则复用
|
|
|
+ if (refreshPromise) return refreshPromise;
|
|
|
|
|
|
- const accessToken = Auth.getAccessToken();
|
|
|
- if (accessToken) {
|
|
|
- options.header = options.header || {};
|
|
|
- options.header['Authorization'] = `Bearer ${accessToken}`;
|
|
|
+ refreshPromise = (async () => {
|
|
|
+ try {
|
|
|
+ const response = await Taro.request({
|
|
|
+ url: `${API_BASE_URL}/system/auth/refresh`,
|
|
|
+ method: 'POST',
|
|
|
+ header: { 'Content-Type': 'application/json' },
|
|
|
+ data: humps.decamelizeKeys({ refreshToken }),
|
|
|
+ timeout: 10000,
|
|
|
+ });
|
|
|
+ if (response.statusCode === 200 && response.data && response.data.code === 0) {
|
|
|
+ const newToken = humps.camelizeKeys(response.data.data);
|
|
|
+ Auth.setToken(newToken);
|
|
|
+ console.log('[Token刷新] 成功');
|
|
|
+ return true;
|
|
|
+ }
|
|
|
+ console.log('[Token刷新] 失败:', response.data && response.data.msg);
|
|
|
+ return false;
|
|
|
+ } catch (e) {
|
|
|
+ console.error('[Token刷新] 异常:', e);
|
|
|
+ return false;
|
|
|
+ } finally {
|
|
|
+ refreshPromise = null;
|
|
|
}
|
|
|
+ })();
|
|
|
|
|
|
- options.data = humps.decamelizeKeys(options.data);
|
|
|
- console.log('[API请求]', options.url, JSON.stringify(options.data));
|
|
|
+ return refreshPromise;
|
|
|
+};
|
|
|
|
|
|
- try {
|
|
|
- const response = await Taro.request({
|
|
|
- ...options,
|
|
|
- url: `${API_BASE_URL}${options.url}`,
|
|
|
- timeout: 10000,
|
|
|
- });
|
|
|
+/** 登录已过期:清除凭据,跳转登录页 */
|
|
|
+const handleExpiredLogin = () => {
|
|
|
+ Auth.clearToken();
|
|
|
+ Taro.reLaunch({ url: '/pages/login/index' });
|
|
|
+ throw new Error('登录已过期,请重新登录');
|
|
|
+};
|
|
|
|
|
|
- if (response.statusCode === 200) {
|
|
|
- if (response.data.code === 0) {
|
|
|
- return humps.camelizeKeys(response.data.data);
|
|
|
- } else {
|
|
|
- throw new Error(response.data.msg || defaultErrorMsg);
|
|
|
- }
|
|
|
- } else {
|
|
|
- throw new Error(`${defaultErrorMsg} (${response.statusCode})`);
|
|
|
+export const requestApi = async (options: Taro.request.Option, defaultErrorMsg: string = '') => {
|
|
|
+ const accessToken = Auth.getAccessToken();
|
|
|
+ if (accessToken) {
|
|
|
+ options.header = options.header || {};
|
|
|
+ options.header['Authorization'] = `Bearer ${accessToken}`;
|
|
|
+ }
|
|
|
+
|
|
|
+ options.data = humps.decamelizeKeys(options.data);
|
|
|
+ console.log('[API请求]', options.url, JSON.stringify(options.data));
|
|
|
+
|
|
|
+ try {
|
|
|
+ const response = await Taro.request({
|
|
|
+ ...options,
|
|
|
+ url: `${API_BASE_URL}${options.url}`,
|
|
|
+ timeout: 10000,
|
|
|
+ });
|
|
|
+
|
|
|
+ // 401:尝试静默刷新
|
|
|
+ if (response.statusCode === 401) {
|
|
|
+ const refreshed = await tryRefreshToken();
|
|
|
+ if (refreshed) {
|
|
|
+ // 刷新成功,用新 token 重试一次
|
|
|
+ const newToken = Auth.getAccessToken();
|
|
|
+ options.header = options.header || {};
|
|
|
+ options.header['Authorization'] = `Bearer ${newToken}`;
|
|
|
+ const retryResponse = await Taro.request({
|
|
|
+ ...options,
|
|
|
+ url: `${API_BASE_URL}${options.url}`,
|
|
|
+ timeout: 10000,
|
|
|
+ });
|
|
|
+ if (retryResponse.statusCode === 200) {
|
|
|
+ if (retryResponse.data.code === 0) {
|
|
|
+ return humps.camelizeKeys(retryResponse.data.data);
|
|
|
+ }
|
|
|
+ throw new Error(retryResponse.data.msg || defaultErrorMsg);
|
|
|
}
|
|
|
-
|
|
|
- } catch (error: any) {
|
|
|
- if (error && error.status === 401) {
|
|
|
- handleExpiredLogin();
|
|
|
+ if (retryResponse.statusCode === 401) {
|
|
|
+ handleExpiredLogin();
|
|
|
}
|
|
|
- throw new Error((error && error.data && error.data.msg) || defaultErrorMsg);
|
|
|
+ throw new Error(`${defaultErrorMsg} (${retryResponse.statusCode})`);
|
|
|
+ }
|
|
|
+ // 刷新失败,跳转登录
|
|
|
+ handleExpiredLogin();
|
|
|
+ }
|
|
|
+
|
|
|
+ if (response.statusCode === 200) {
|
|
|
+ if (response.data.code === 0) {
|
|
|
+ return humps.camelizeKeys(response.data.data);
|
|
|
+ } else {
|
|
|
+ throw new Error(response.data.msg || defaultErrorMsg);
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ throw new Error(`${defaultErrorMsg} (${response.statusCode})`);
|
|
|
}
|
|
|
+ } catch (error: any) {
|
|
|
+ // 网络错误、超时等
|
|
|
+ throw new Error((error && error.message) || defaultErrorMsg);
|
|
|
+ }
|
|
|
};
|
|
|
|
|
|
// 发送短信验证码
|
|
|
export const sendSmsCodeApi = async (mobile: string) => {
|
|
|
- return requestApi(
|
|
|
- {
|
|
|
- url: '/system/auth/sms-code',
|
|
|
- method: 'POST',
|
|
|
- header: {
|
|
|
- 'Content-Type': 'application/json',
|
|
|
- },
|
|
|
- data: {
|
|
|
- mobile,
|
|
|
- template_name: 'verify',
|
|
|
- },
|
|
|
+ return requestApi(
|
|
|
+ {
|
|
|
+ url: '/system/auth/sms-code',
|
|
|
+ method: 'POST',
|
|
|
+ header: {
|
|
|
+ 'Content-Type': 'application/json',
|
|
|
},
|
|
|
- '发送验证码失败',
|
|
|
- );
|
|
|
+ data: {
|
|
|
+ mobile,
|
|
|
+ template_name: 'verify',
|
|
|
+ },
|
|
|
+ },
|
|
|
+ '发送验证码失败',
|
|
|
+ );
|
|
|
};
|
|
|
|
|
|
// 短信验证码登录
|
|
|
export const loginApi = async (data: LoginFormData) => {
|
|
|
- return requestApi({
|
|
|
- url: '/system/auth/login/sms',
|
|
|
- method: 'POST',
|
|
|
- header: {
|
|
|
- "Content-Type": "application/json",
|
|
|
- },
|
|
|
- data: {
|
|
|
- mobile: data.mobile,
|
|
|
- code: data.code,
|
|
|
- template_name: 'verify',
|
|
|
- },
|
|
|
- }, '登录失败');
|
|
|
+ return requestApi(
|
|
|
+ {
|
|
|
+ url: '/system/auth/login/sms',
|
|
|
+ method: 'POST',
|
|
|
+ header: {
|
|
|
+ 'Content-Type': 'application/json',
|
|
|
+ },
|
|
|
+ data: {
|
|
|
+ mobile: data.mobile,
|
|
|
+ code: data.code,
|
|
|
+ template_name: 'verify',
|
|
|
+ },
|
|
|
+ },
|
|
|
+ '登录失败',
|
|
|
+ );
|
|
|
};
|
|
|
|
|
|
-
|
|
|
export const getUserInfoApi = async () => {
|
|
|
- return requestApi({
|
|
|
- url: '/payment/employee/info',
|
|
|
- method: 'GET',
|
|
|
- dataType: 'json',
|
|
|
- header: {
|
|
|
- "Content-Type": "application/json",
|
|
|
- },
|
|
|
- }, '获取用户信息失败');
|
|
|
+ return requestApi(
|
|
|
+ {
|
|
|
+ url: '/payment/employee/info',
|
|
|
+ method: 'GET',
|
|
|
+ dataType: 'json',
|
|
|
+ header: {
|
|
|
+ 'Content-Type': 'application/json',
|
|
|
+ },
|
|
|
+ },
|
|
|
+ '获取用户信息失败',
|
|
|
+ );
|
|
|
};
|
|
|
|
|
|
-
|
|
|
export const transferApi = async (data: AccountTransferSchema) => {
|
|
|
- return requestApi({
|
|
|
- url: '/payment/account/transfer',
|
|
|
- method: 'POST',
|
|
|
- dataType: 'json',
|
|
|
- header: {
|
|
|
- "Content-Type": "application/json",
|
|
|
- },
|
|
|
- data,
|
|
|
- }, '转账失败');
|
|
|
-}
|
|
|
-
|
|
|
-
|
|
|
-export const listTransferApi = async (
|
|
|
- data: { pageNo: number, pageSize: number, status?: string, orderNo?: string }
|
|
|
-) => {
|
|
|
- return requestApi({
|
|
|
- url: '/payment/account/transfer',
|
|
|
- method: 'GET',
|
|
|
- dataType: 'json',
|
|
|
- header: {
|
|
|
- "Content-Type": "application/json",
|
|
|
- },
|
|
|
- data,
|
|
|
- }, '获取转账记录失败');
|
|
|
-}
|
|
|
+ return requestApi(
|
|
|
+ {
|
|
|
+ url: '/payment/account/transfer',
|
|
|
+ method: 'POST',
|
|
|
+ dataType: 'json',
|
|
|
+ header: {
|
|
|
+ 'Content-Type': 'application/json',
|
|
|
+ },
|
|
|
+ data,
|
|
|
+ },
|
|
|
+ '转账失败',
|
|
|
+ );
|
|
|
+};
|
|
|
+
|
|
|
+export const listTransferApi = async (data: {
|
|
|
+ pageNo: number;
|
|
|
+ pageSize: number;
|
|
|
+ status?: string;
|
|
|
+ orderNo?: string;
|
|
|
+}) => {
|
|
|
+ return requestApi(
|
|
|
+ {
|
|
|
+ url: '/payment/account/transfer',
|
|
|
+ method: 'GET',
|
|
|
+ dataType: 'json',
|
|
|
+ header: {
|
|
|
+ 'Content-Type': 'application/json',
|
|
|
+ },
|
|
|
+ data,
|
|
|
+ },
|
|
|
+ '获取转账记录失败',
|
|
|
+ );
|
|
|
+};
|