init
This commit is contained in:
commit
2b8870a40e
51 changed files with 5845 additions and 0 deletions
298
utils/cloud.js
Normal file
298
utils/cloud.js
Normal file
|
@ -0,0 +1,298 @@
|
|||
/**
|
||||
* 云服务相关工具函数
|
||||
*/
|
||||
|
||||
// 导入统一配置
|
||||
const config = require('./config');
|
||||
|
||||
/**
|
||||
* 检查JWT token是否需要刷新
|
||||
* @returns {boolean}
|
||||
*/
|
||||
const shouldRefreshToken = () => {
|
||||
try {
|
||||
const token = wx.getStorageSync(config.JWT_CONFIG.storage.access);
|
||||
if (!token) return true;
|
||||
|
||||
// 解析JWT token(不验证签名)
|
||||
const [, payload] = token.split('.');
|
||||
const { exp } = JSON.parse(atob(payload));
|
||||
const expirationTime = exp * 1000; // 转换为毫秒
|
||||
|
||||
// 如果token将在5分钟内过期,则刷新
|
||||
return Date.now() + config.JWT_CONFIG.refreshThreshold > expirationTime;
|
||||
} catch (error) {
|
||||
console.error('Token解析失败:', error);
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 刷新JWT token
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
const refreshToken = async () => {
|
||||
try {
|
||||
const refreshToken = wx.getStorageSync(config.JWT_CONFIG.storage.refresh);
|
||||
if (!refreshToken) {
|
||||
throw new Error('No refresh token available');
|
||||
}
|
||||
|
||||
const response = await wx.request({
|
||||
url: `${config.API_BASE_URL}${config.API_ENDPOINTS.AUTH.REFRESH}`,
|
||||
method: 'POST',
|
||||
header: {
|
||||
'Authorization': `Bearer ${refreshToken}`
|
||||
}
|
||||
});
|
||||
|
||||
if (response.statusCode === 200 && response.data.access_token) {
|
||||
wx.setStorageSync(config.JWT_CONFIG.storage.access, response.data.access_token);
|
||||
if (response.data.refresh_token) {
|
||||
wx.setStorageSync(config.JWT_CONFIG.storage.refresh, response.data.refresh_token);
|
||||
}
|
||||
} else {
|
||||
throw new Error('Token refresh failed');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Token刷新失败:', error);
|
||||
// 清除所有token,强制用户重新登录
|
||||
wx.removeStorageSync(config.JWT_CONFIG.storage.access);
|
||||
wx.removeStorageSync(config.JWT_CONFIG.storage.refresh);
|
||||
throw new Error('认证已过期,请重新登录');
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 发送HTTP请求的通用函数
|
||||
* @param {string} url - API URL
|
||||
* @param {Object} options - 请求选项
|
||||
* @returns {Promise<any>} 响应数据
|
||||
*/
|
||||
const request = async (url, options = {}) => {
|
||||
// 检查并刷新token
|
||||
if (shouldRefreshToken()) {
|
||||
await refreshToken();
|
||||
}
|
||||
|
||||
const accessToken = wx.getStorageSync(config.JWT_CONFIG.storage.access);
|
||||
const defaultOptions = {
|
||||
method: 'GET',
|
||||
header: {
|
||||
'Content-Type': 'application/json',
|
||||
'Authorization': `Bearer ${accessToken}`
|
||||
}
|
||||
};
|
||||
|
||||
const requestOptions = {
|
||||
...defaultOptions,
|
||||
...options,
|
||||
url: `${config.API_BASE_URL}${url}`
|
||||
};
|
||||
|
||||
// 合并headers
|
||||
requestOptions.header = {
|
||||
...defaultOptions.header,
|
||||
...options.header
|
||||
};
|
||||
|
||||
try {
|
||||
const response = await new Promise((resolve, reject) => {
|
||||
wx.request({
|
||||
...requestOptions,
|
||||
success: resolve,
|
||||
fail: reject
|
||||
});
|
||||
});
|
||||
|
||||
// 处理401错误(token无效)
|
||||
if (response.statusCode === 401) {
|
||||
// 尝试刷新token并重试请求
|
||||
await refreshToken();
|
||||
return request(url, options); // 递归调用,使用新token重试
|
||||
}
|
||||
|
||||
if (response.statusCode >= 200 && response.statusCode < 300) {
|
||||
return response.data;
|
||||
}
|
||||
|
||||
throw new Error(response.data?.message || '请求失败');
|
||||
} catch (error) {
|
||||
console.error('API请求失败:', {
|
||||
endpoint: url,
|
||||
method: requestOptions.method,
|
||||
status: error.statusCode || 'N/A',
|
||||
error: error.message
|
||||
});
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 上传令牌数据到云端
|
||||
* @param {Array} tokens - 令牌数据数组
|
||||
* @returns {Promise<string>} 云端数据ID
|
||||
*/
|
||||
const uploadTokens = async (tokens) => {
|
||||
if (!tokens || tokens.length === 0) {
|
||||
throw new Error('没有可上传的数据');
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await request(config.API_ENDPOINTS.OTP.SAVE, {
|
||||
method: 'POST',
|
||||
data: {
|
||||
tokens,
|
||||
timestamp: Date.now()
|
||||
}
|
||||
});
|
||||
|
||||
if (response.success) {
|
||||
return response.data.id;
|
||||
}
|
||||
|
||||
throw new Error(response.message || '上传失败');
|
||||
} catch (error) {
|
||||
console.error('上传令牌数据失败:', error);
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 从云端获取最新的令牌数据
|
||||
* @returns {Promise<Object>} 云端数据对象
|
||||
*/
|
||||
const fetchLatestTokens = async () => {
|
||||
try {
|
||||
const response = await request(config.API_ENDPOINTS.OTP.RECOVER, {
|
||||
method: 'POST',
|
||||
data: {
|
||||
timestamp: Date.now()
|
||||
}
|
||||
});
|
||||
|
||||
if (response.success && response.data?.tokens) {
|
||||
return {
|
||||
tokens: response.data.tokens,
|
||||
timestamp: response.data.timestamp,
|
||||
num: response.data.tokens.length
|
||||
};
|
||||
}
|
||||
|
||||
throw new Error(response.message || '未找到云端数据');
|
||||
} catch (error) {
|
||||
console.error('获取云端数据失败:', error);
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 初始化云服务
|
||||
* 检查认证状态并尝试恢复会话
|
||||
* @returns {Promise<boolean>} 初始化是否成功
|
||||
*/
|
||||
const initCloud = async () => {
|
||||
try {
|
||||
// 检查是否有有效的访问令牌
|
||||
const accessToken = wx.getStorageSync(config.JWT_CONFIG.storage.access);
|
||||
if (!accessToken) {
|
||||
console.log('未找到访问令牌,需要登录');
|
||||
return false;
|
||||
}
|
||||
|
||||
// 验证令牌有效性
|
||||
if (shouldRefreshToken()) {
|
||||
await refreshToken();
|
||||
}
|
||||
return true;
|
||||
} catch (error) {
|
||||
console.warn('云服务初始化失败:', error);
|
||||
// 清除所有认证信息
|
||||
wx.removeStorageSync(config.JWT_CONFIG.storage.access);
|
||||
wx.removeStorageSync(config.JWT_CONFIG.storage.refresh);
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 比较本地和云端数据
|
||||
* @param {Array} localTokens - 本地令牌数组
|
||||
* @param {Array} cloudTokens - 云端令牌数组
|
||||
* @returns {Object} 比较结果
|
||||
*/
|
||||
const compareTokens = (localTokens, cloudTokens) => {
|
||||
const result = {
|
||||
added: [],
|
||||
removed: [],
|
||||
modified: [],
|
||||
unchanged: []
|
||||
};
|
||||
|
||||
// 创建查找映射
|
||||
const localMap = new Map(localTokens.map(t => [t.id, t]));
|
||||
const cloudMap = new Map(cloudTokens.map(t => [t.id, t]));
|
||||
|
||||
// 查找添加和修改的令牌
|
||||
cloudTokens.forEach(cloudToken => {
|
||||
const localToken = localMap.get(cloudToken.id);
|
||||
if (!localToken) {
|
||||
result.added.push(cloudToken);
|
||||
} else if (JSON.stringify(localToken) !== JSON.stringify(cloudToken)) {
|
||||
result.modified.push(cloudToken);
|
||||
} else {
|
||||
result.unchanged.push(cloudToken);
|
||||
}
|
||||
});
|
||||
|
||||
// 查找删除的令牌
|
||||
localTokens.forEach(localToken => {
|
||||
if (!cloudMap.has(localToken.id)) {
|
||||
result.removed.push(localToken);
|
||||
}
|
||||
});
|
||||
|
||||
return result;
|
||||
};
|
||||
|
||||
/**
|
||||
* 合并本地和云端数据
|
||||
* @param {Array} localTokens - 本地令牌数组
|
||||
* @param {Array} cloudTokens - 云端令牌数组
|
||||
* @param {Object} options - 合并选项
|
||||
* @param {boolean} [options.preferCloud=true] - 冲突时是否优先使用云端数据
|
||||
* @returns {Array} 合并后的令牌数组
|
||||
*/
|
||||
const mergeTokens = (localTokens, cloudTokens, options = { preferCloud: true }) => {
|
||||
const comparison = compareTokens(localTokens, cloudTokens);
|
||||
const result = [...comparison.unchanged];
|
||||
|
||||
// 添加新令牌
|
||||
result.push(...comparison.added);
|
||||
|
||||
// 处理修改的令牌
|
||||
comparison.modified.forEach(cloudToken => {
|
||||
if (options.preferCloud) {
|
||||
result.push(cloudToken);
|
||||
} else {
|
||||
const localToken = localTokens.find(t => t.id === cloudToken.id);
|
||||
result.push(localToken);
|
||||
}
|
||||
});
|
||||
|
||||
// 如果不优先使用云端数据,保留本地删除的令牌
|
||||
if (!options.preferCloud) {
|
||||
result.push(...comparison.removed);
|
||||
}
|
||||
|
||||
return result;
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
uploadTokens,
|
||||
fetchLatestTokens,
|
||||
compareTokens,
|
||||
mergeTokens,
|
||||
initCloud,
|
||||
shouldRefreshToken,
|
||||
refreshToken
|
||||
};
|
Loading…
Add table
Add a link
Reference in a new issue