Files
sundynix-plant-mp/utils/request.js
T
2026-02-14 08:32:47 +08:00

321 lines
11 KiB
JavaScript

class WxRequest {
constructor(options = {}) {
this.baseUrl = options.baseUrl || '';
this.header = options.header || {};
this.interceptors = {
request: (config) => config,
response: (response) => response
};
}
/**
* Set request interceptor
* @param {Function} callback
*/
setRequestInterceptor(callback) {
this.interceptors.request = callback;
}
/**
* Set response interceptor
* @param {Function} callback
*/
setResponseInterceptor(callback) {
this.interceptors.response = callback;
}
/**
* Core request method
* @param {Object} options
*/
request(options = {}) {
// Merge headers
const header = { ...this.header, ...options.header };
// Prepare config
let config = {
url: (options.url.startsWith('http') ? options.url : this.baseUrl + options.url),
method: options.method || 'GET',
data: options.data || {},
header: header,
timeout: options.timeout || 60000,
skipToken: options.skipToken || false
};
return new Promise(async (resolve, reject) => {
// Apply request interceptor (Async)
try {
config = await Promise.resolve(this.interceptors.request(config));
} catch (ignore) {
// If interceptor fails/rejects, likely means we shouldn't proceed
// But for now let's just log or reject
console.warn('Interceptor warning', ignore);
}
wx.request({
...config,
success: (res) => {
// Apply response interceptor (pass the whole wx response)
const processedResponse = this.interceptors.response(res);
const { statusCode, data } = processedResponse;
// Auto-refresh token if 401 (One-time retry)
if ((statusCode === 401 || data.code === 401) && !options.skipToken && !options._retry) {
const app = getApp();
if (app && app.forceRefreshLogin) {
console.log('401 detected, refreshing token...');
app.forceRefreshLogin().then(() => {
// Retry Original Request
this.request({ ...options, _retry: true })
.then(resolve)
.catch(reject);
}).catch(err => {
console.error('Token refresh failed', err);
reject(data);
});
return;
}
}
// 1. Check HTTP Status Code
if (statusCode >= 200 && statusCode < 300) {
// 2. Check Business Logic Code (Assuming 200 is success based on common Go patterns,
// matching the user's struct: Code, Data, Msg)
// If data.code exists, we check it. If not (maybe not JSON), we pass through or handle.
const businessCode = data.code;
// Strict check: if code is 200, success.
if (businessCode === 200) {
resolve(data.data); // Return the inner Data object
} else {
// Business logic error
this.handleError({
errMsg: data.msg || 'Error',
code: businessCode
});
reject(data);
}
} else {
// Handle non-200 HTTP errors
this.handleError({ ...res, errMsg: `HTTP Error: ${statusCode}` });
reject(res);
}
},
fail: (err) => {
this.handleError({ ...err, errMsg: 'Network Error' });
reject(err);
}
});
});
}
/**
* Handle global errors (Toast)
* @param {Object} error
*/
handleError(error) {
const message = error.errMsg || error.msg || '请求失败,请稍后重试';
wx.showToast({
title: message,
icon: 'none',
duration: 3000
});
console.error('API Error:', error);
}
/**
* Upload file
* @param {string} filePath Local file path
* @param {string} name Form field name (default: file)
* @param {Object} formData Additional form data
*/
upload(filePath, name = 'file', formData = {}) {
// Prepare config
let config = {
url: this.baseUrl + '/oss/upload',
header: { ...this.header }, // Copy default headers
filePath: filePath,
name: name,
formData: formData
};
// Apply request interceptor (Async)
return new Promise(async (resolve, reject) => {
try {
config = await Promise.resolve(this.interceptors.request(config));
} catch (ignore) {
console.warn('Interceptor warning', ignore);
}
wx.uploadFile({
url: config.url,
filePath: config.filePath,
name: config.name,
formData: config.formData,
header: config.header,
success: (res) => {
// wx.uploadFile returns data as String, need to parse
let data;
try {
data = JSON.parse(res.data);
} catch (e) {
data = { code: 500, msg: 'Response parse error', data: res.data };
}
// Reconstruct a response object for interceptor
const responseObj = { ...res, data: data };
// Apply response interceptor
const processedResponse = this.interceptors.response(responseObj);
const { statusCode, data: finalData } = processedResponse;
// Status Code check (uploadFile also returns statusCode)
if (statusCode >= 200 && statusCode < 300) {
const businessCode = finalData.code;
if (businessCode === 200) {
resolve(finalData.data);
} else {
this.handleError({
errMsg: finalData.msg || 'Upload Error',
code: businessCode
});
reject(finalData);
}
} else {
this.handleError({ ...res, errMsg: `HTTP Error: ${statusCode}` });
reject(res);
}
},
fail: (err) => {
this.handleError({ ...err, errMsg: 'Upload Network Error' });
reject(err);
}
});
});
}
/**
* Upload file to a specific URL path
* @param {string} urlPath API path (e.g. '/classify/plant')
* @param {string} filePath Local file path
* @param {string} name Form field name (default: file)
* @param {Object} formData Additional form data
*/
uploadToUrl(urlPath, filePath, name = 'file', formData = {}) {
let config = {
url: this.baseUrl + urlPath,
header: { ...this.header },
filePath: filePath,
name: name,
formData: formData
};
return new Promise(async (resolve, reject) => {
try {
config = await Promise.resolve(this.interceptors.request(config));
} catch (ignore) {
console.warn('Interceptor warning', ignore);
}
wx.uploadFile({
url: config.url,
filePath: config.filePath,
name: config.name,
formData: config.formData,
header: config.header,
success: (res) => {
let data;
try {
data = JSON.parse(res.data);
} catch (e) {
data = { code: 500, msg: 'Response parse error', data: res.data };
}
const responseObj = { ...res, data: data };
const processedResponse = this.interceptors.response(responseObj);
const { statusCode, data: finalData } = processedResponse;
if (statusCode >= 200 && statusCode < 300) {
const businessCode = finalData.code;
if (businessCode === 200) {
resolve(finalData.data);
} else {
this.handleError({
errMsg: finalData.msg || 'Upload Error',
code: businessCode
});
reject(finalData);
}
} else {
this.handleError({ ...res, errMsg: `HTTP Error: ${statusCode}` });
reject(res);
}
},
fail: (err) => {
this.handleError({ ...err, errMsg: 'Upload Network Error' });
reject(err);
}
});
});
}
get(url, data = {}, header = {}) {
return this.request({ url, method: 'GET', data, header });
}
post(url, data = {}, header = {}) {
return this.request({ url, method: 'POST', data, header });
}
}
// Initialize with default instance
const request = new WxRequest({
//baseUrl: 'http://192.168.0.184:8889',
baseUrl: 'https://go.sundynix.cn/api',
header: {
'Content-Type': 'application/json'
}
});
// Example: Setup default interceptors
request.setRequestInterceptor(async (config) => {
// Skip checking token for login API itself
if (config.url.includes('/auth/miniLogin') || config.skipToken) {
return config;
}
let token = wx.getStorageSync('token');
// If no token, attempt to wait for login
if (!token) {
const app = getApp();
if (app && app.ensureLogin) {
try {
token = await app.ensureLogin();
} catch (e) {
// Login failed
console.warn('Auto-login failed in interceptor', e);
}
}
}
// Inject token if available
if (token) {
config.header['Authorization'] = `Bearer ${token}`;
}
return config;
});
request.setResponseInterceptor((response) => {
// Handle global token expiration (e.g., HTTP 401 or Business Code 401)
if (response.statusCode === 401 || response.data?.code === 401) {
wx.removeStorageSync('token');
// Redirect to login if needed, or just warn
// wx.reLaunch({ url: '/pages/login/index' });
}
return response;
});
export default request;
export { WxRequest };