feat: update

This commit is contained in:
2025-11-06 16:37:01 +08:00
parent c0d54b8513
commit 855f289579
59 changed files with 3398 additions and 572 deletions

0
utils/common.ts Normal file
View File

132
utils/config.ts Normal file
View File

@@ -0,0 +1,132 @@
/**
* 应用配置工具
* 统一管理环境变量和配置
*/
import Constants from 'expo-constants';
import { Platform } from 'react-native';
/**
* 环境类型
*/
export type Environment = 'development' | 'staging' | 'production';
/**
* 获取当前环境
*/
export const getEnvironment = (): Environment => {
if (__DEV__) {
return 'development';
}
// 可以通过环境变量或其他方式判断 staging 环境
const env = process.env.EXPO_PUBLIC_ENV;
if (env === 'staging') {
return 'staging';
}
return 'production';
};
/**
* 获取 API 基础 URL
*/
export const getApiBaseUrl = (): string => {
// 1. 优先使用环境变量
const envApiUrl = process.env.EXPO_PUBLIC_API_URL;
if (envApiUrl) {
return envApiUrl;
}
// 2. 根据环境返回不同的 URL
const env = getEnvironment();
switch (env) {
case 'development':
// 开发环境
if (Platform.OS === 'web') {
// Web 平台使用代理服务器
// 代理服务器运行在 http://localhost:8086
// 会将 /api/* 请求转发到目标服务器
return 'http://localhost:8086/api';
} else {
// iOS/Android 使用本机 IP
// ⚠️ 重要:需要替换为你的本机 IP 地址
// 查看本机 IP
// - Windows: ipconfig
// - Mac/Linux: ifconfig
// - 或者使用 Metro Bundler 显示的 IP
return 'http://192.168.1.100:8086/api';
}
case 'staging':
// 预发布环境
return 'https://staging-api.yourdomain.com/api';
case 'production':
// 生产环境
return 'https://api.yourdomain.com/api';
default:
return '/api';
}
};
/**
* 获取 API 超时时间
*/
export const getApiTimeout = (): number => {
const timeout = process.env.EXPO_PUBLIC_API_TIMEOUT;
return timeout ? Number(timeout) : 10000;
};
/**
* 应用配置
*/
export const config = {
// 环境
env: getEnvironment(),
isDev: __DEV__,
// API 配置
api: {
baseURL: getApiBaseUrl(),
timeout: getApiTimeout(),
},
// 应用信息
app: {
name: process.env.EXPO_PUBLIC_APP_NAME || 'RN Demo',
version: process.env.EXPO_PUBLIC_APP_VERSION || '1.0.0',
bundleId: Constants.expoConfig?.ios?.bundleIdentifier || '',
packageName: Constants.expoConfig?.android?.package || '',
vk: 'fT6phq0wkOPRlAoyToidAnkogUV7ttGo',
nc: 1,
aseqId: '7',
},
// 平台信息
platform: {
os: Platform.OS,
version: Platform.Version,
isWeb: Platform.OS === 'web',
isIOS: Platform.OS === 'ios',
isAndroid: Platform.OS === 'android',
},
};
/**
* 打印配置信息(仅开发环境)
*/
export const printConfig = () => {
if (__DEV__) {
console.log('📋 App Configuration:', {
environment: config.env,
apiBaseURL: config.api.baseURL,
platform: config.platform.os,
version: config.app.version,
});
}
};
export default config;

218
utils/date.ts Normal file
View File

@@ -0,0 +1,218 @@
/**
* Day.js 日期工具函数
* 统一管理日期格式化和处理
*/
import dayjs from 'dayjs';
import relativeTime from 'dayjs/plugin/relativeTime';
import calendar from 'dayjs/plugin/calendar';
import duration from 'dayjs/plugin/duration';
import isSameOrBefore from 'dayjs/plugin/isSameOrBefore';
import isSameOrAfter from 'dayjs/plugin/isSameOrAfter';
import 'dayjs/locale/zh-cn';
// 扩展插件
dayjs.extend(relativeTime);
dayjs.extend(calendar);
dayjs.extend(duration);
dayjs.extend(isSameOrBefore);
dayjs.extend(isSameOrAfter);
// 设置默认语言为中文
dayjs.locale('zh-cn');
/**
* 日期格式常量
*/
export const DATE_FORMATS = {
FULL: 'YYYY-MM-DD HH:mm:ss',
DATE: 'YYYY-MM-DD',
TIME: 'HH:mm:ss',
DATE_TIME: 'YYYY-MM-DD HH:mm',
MONTH_DAY: 'MM-DD',
HOUR_MINUTE: 'HH:mm',
YEAR_MONTH: 'YYYY-MM',
CHINESE_DATE: 'YYYY年MM月DD日',
CHINESE_FULL: 'YYYY年MM月DD日 HH:mm:ss',
} as const;
/**
* 格式化日期
* @param date 日期Date、时间戳或字符串
* @param format 格式默认YYYY-MM-DD HH:mm:ss
*/
export const formatDate = (
date: Date | string | number,
format: string = DATE_FORMATS.FULL
): string => {
return dayjs(date).format(format);
};
/**
* 格式化为相对时间3分钟前、2小时前
*/
export const formatRelativeTime = (date: Date | string | number): string => {
return dayjs(date).fromNow();
};
/**
* 格式化为日历时间(如:今天、昨天、上周)
*/
export const formatCalendarTime = (date: Date | string | number): string => {
return dayjs(date).calendar(null, {
sameDay: '[今天] HH:mm',
lastDay: '[昨天] HH:mm',
lastWeek: 'MM-DD HH:mm',
sameElse: 'YYYY-MM-DD HH:mm',
});
};
/**
* 格式化聊天时间
* 今天:显示时间
* 昨天:显示"昨天 + 时间"
* 本年:显示"月-日 时间"
* 往年:显示"年-月-日"
*/
export const formatChatTime = (timestamp: number | string | Date): string => {
const date = dayjs(timestamp);
const now = dayjs();
if (date.isSame(now, 'day')) {
// 今天
return date.format('HH:mm');
} else if (date.isSame(now.subtract(1, 'day'), 'day')) {
// 昨天
return '昨天 ' + date.format('HH:mm');
} else if (date.isSame(now, 'year')) {
// 本年
return date.format('MM-DD HH:mm');
} else {
// 往年
return date.format('YYYY-MM-DD');
}
};
/**
* 获取时间差(返回对象)
*/
export const getTimeDiff = (
startDate: Date | string | number,
endDate: Date | string | number = new Date()
) => {
const start = dayjs(startDate);
const end = dayjs(endDate);
const diff = end.diff(start);
const duration = dayjs.duration(diff);
return {
years: duration.years(),
months: duration.months(),
days: duration.days(),
hours: duration.hours(),
minutes: duration.minutes(),
seconds: duration.seconds(),
milliseconds: duration.milliseconds(),
totalDays: Math.floor(duration.asDays()),
totalHours: Math.floor(duration.asHours()),
totalMinutes: Math.floor(duration.asMinutes()),
totalSeconds: Math.floor(duration.asSeconds()),
};
};
/**
* 判断是否是今天
*/
export const isToday = (date: Date | string | number): boolean => {
return dayjs(date).isSame(dayjs(), 'day');
};
/**
* 判断是否是昨天
*/
export const isYesterday = (date: Date | string | number): boolean => {
return dayjs(date).isSame(dayjs().subtract(1, 'day'), 'day');
};
/**
* 判断是否是本周
*/
export const isThisWeek = (date: Date | string | number): boolean => {
return dayjs(date).isSame(dayjs(), 'week');
};
/**
* 判断是否是本月
*/
export const isThisMonth = (date: Date | string | number): boolean => {
return dayjs(date).isSame(dayjs(), 'month');
};
/**
* 判断是否是本年
*/
export const isThisYear = (date: Date | string | number): boolean => {
return dayjs(date).isSame(dayjs(), 'year');
};
/**
* 获取日期范围的开始和结束
*/
export const getDateRange = (type: 'day' | 'week' | 'month' | 'year') => {
const now = dayjs();
return {
start: now.startOf(type).toDate(),
end: now.endOf(type).toDate(),
};
};
/**
* 添加时间
*/
export const addTime = (
date: Date | string | number,
amount: number,
unit: dayjs.ManipulateType
): Date => {
return dayjs(date).add(amount, unit).toDate();
};
/**
* 减去时间
*/
export const subtractTime = (
date: Date | string | number,
amount: number,
unit: dayjs.ManipulateType
): Date => {
return dayjs(date).subtract(amount, unit).toDate();
};
/**
* 判断日期是否在范围内
*/
export const isBetween = (
date: Date | string | number,
startDate: Date | string | number,
endDate: Date | string | number
): boolean => {
const target = dayjs(date);
return target.isAfter(startDate) && target.isBefore(endDate);
};
/**
* 获取当前时间戳(毫秒)
*/
export const now = (): number => {
return dayjs().valueOf();
};
/**
* 获取当前时间戳(秒)
*/
export const nowInSeconds = (): number => {
return Math.floor(dayjs().valueOf() / 1000);
};
export default dayjs;

38
utils/index.ts Normal file
View File

@@ -0,0 +1,38 @@
/**
* Utils 模块统一导出
*/
// Network API
export {
default as api,
request,
cancelAllRequests,
cancelRequest,
createRetryRequest,
} from './network/api';
export type { ApiResponse, ApiError, RequestConfig } from './network/api';
// Storage
export { default as Storage, STORAGE_KEYS } from './storage';
export { default as SessionStorage, SESSION_KEYS } from './sessionStorage';
export { default as StorageManager } from './storageManager';
export type { StorageType, StorageOptions } from './storageManager';
// Config
export { default as config, printConfig } from './config';
// Date utilities
export {
formatDate,
formatRelativeTime,
formatChatTime,
parseDate,
isToday,
isYesterday,
isSameDay,
addDays,
subtractDays,
startOfDay,
endOfDay,
} from './date';

589
utils/network/api.ts Normal file
View File

@@ -0,0 +1,589 @@
/**
* Axios API 配置
* 统一管理 HTTP 请求
*
* 功能特性:
* - 自动添加 Token
* - Token 自动刷新
* - 请求重试机制
* - 请求取消功能
* - 统一错误处理
* - 请求/响应日志
* - Loading 状态管理
*/
import axios, {
AxiosError,
AxiosRequestConfig,
AxiosResponse,
InternalAxiosRequestConfig,
CancelTokenSource,
AxiosRequestHeaders,
} from 'axios';
import AsyncStorage from '@react-native-async-storage/async-storage';
import { router } from 'expo-router';
import { config } from '../config';
import { transformRequest, parseResponse } from './helper';
import { cloneDeep, pick } from 'lodash-es';
import md5 from 'md5';
/**
* API 响应数据结构
*/
export interface ApiResponse<T = any> {
code: number;
message: string;
data: T;
success?: boolean;
}
/**
* API 错误响应
*/
export interface ApiError {
code: number;
message: string;
errors?: Record<string, string[]>;
}
/**
* 请求配置扩展
*/
export interface RequestConfig extends AxiosRequestConfig {
/** 是否显示 loading */
showLoading?: boolean;
/** 是否显示错误提示 */
showError?: boolean;
/** 是否重试 */
retry?: boolean;
/** 重试次数 */
retryCount?: number;
/** 是否需要 token */
requiresAuth?: boolean;
/** 自定义错误处理 */
customErrorHandler?: (error: AxiosError<ApiError>) => void;
}
// API 基础配置
const API_CONFIG = {
baseURL: config.api.baseURL,
timeout: config.api.timeout,
headers: {
'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8',
Accept: 'application/json, application/xml, text/play, text/html, *.*',
},
};
// 创建 axios 实例
const api = axios.create(API_CONFIG);
// 请求队列(用于取消请求)
const pendingRequests = new Map<string, CancelTokenSource>();
// 是否正在刷新 token
let isRefreshing = false;
// 刷新 token 时的请求队列
let refreshSubscribers: Array<(token: string) => void> = [];
/**
* 生成请求唯一标识
*/
function generateRequestKey(config: InternalAxiosRequestConfig): string {
const cmdId = config.headers.cmdId || config.url;
const data = cloneDeep(config.method === 'post' ? config.data : config.params);
return `${cmdId}&${data ? md5(JSON.stringify(data)) : ''}`;
}
/**
* 添加请求到队列
*/
function addPendingRequest(config: InternalAxiosRequestConfig): void {
const requestKey = generateRequestKey(config);
// 如果已存在相同请求,取消之前的请求
if (pendingRequests.has(requestKey)) {
const source = pendingRequests.get(requestKey);
source?.cancel('重复请求已取消');
}
// 创建新的取消令牌
const source = axios.CancelToken.source();
config.cancelToken = source.token;
pendingRequests.set(requestKey, source);
}
/**
* 从队列中移除请求
*/
function removePendingRequest(config: InternalAxiosRequestConfig | AxiosRequestConfig): void {
const requestKey = generateRequestKey(config as InternalAxiosRequestConfig);
pendingRequests.delete(requestKey);
}
/**
* 订阅 token 刷新
*/
function subscribeTokenRefresh(callback: (token: string) => void): void {
refreshSubscribers.push(callback);
}
/**
* 通知所有订阅者 token 已刷新
*/
function onTokenRefreshed(token: string): void {
refreshSubscribers.forEach((callback) => callback(token));
refreshSubscribers = [];
}
/**
* 刷新 token
*/
async function refreshAccessToken(): Promise<string | null> {
try {
const refreshToken = await AsyncStorage.getItem('refresh_token');
if (!refreshToken) {
throw new Error('No refresh token');
}
// 调用刷新 token 接口
const response = await axios.post<ApiResponse<{ token: string; refreshToken: string }>>(
`${config.api.baseURL}/auth/refresh-token`,
{ refreshToken }
);
const { token, refreshToken: newRefreshToken } = response.data.data;
// 保存新的 token
await AsyncStorage.setItem('auth_token', token);
await AsyncStorage.setItem('refresh_token', newRefreshToken);
return token;
} catch (error) {
// 刷新失败,清除所有 token
await AsyncStorage.multiRemove(['auth_token', 'refresh_token']);
return null;
}
}
/**
* 请求拦截器
* 在请求发送前添加 token 等信息
*/
api.interceptors.request.use(
async (config: InternalAxiosRequestConfig) => {
try {
// 添加到请求队列(防止重复请求)
addPendingRequest(config);
const { apiName } = config.headers;
const { headers, data } = transformRequest(pick(config, ['headers', 'data']));
config.headers = {
...headers,
...(__DEV__ ? { apiName } : {}),
} as AxiosRequestHeaders;
config.data = data;
if (Number(config.headers.cmdId) !== 381120) {
config.url = '/v2/';
}
// if (__DEV__ && apiName) {
// config.url = `${config.url}?${apiName}`;
// }
// // 从本地存储获取 token
// const token = await AsyncStorage.getItem('auth_token');
//
// // 添加 token 到请求头
// if (token && config.headers) {
// config.headers.Authorization = `Bearer ${token}`;
// }
// 添加请求时间戳(用于计算请求耗时)
(config as any).metadata = { startTime: Date.now() };
// 打印请求信息(开发环境)
if (__DEV__) {
console.log('📤 API Request:', {
method: config.method?.toUpperCase(),
url: config.url,
baseURL: config.baseURL,
fullURL: `${config.baseURL}${config.url}`,
params: config.params,
headers: config.headers,
});
}
return config;
} catch (error) {
console.error('❌ Request interceptor error:', error);
return Promise.reject(error);
}
},
(error) => {
console.error('❌ Request error:', error);
return Promise.reject(error);
}
);
/**
* 响应拦截器
* 统一处理响应和错误
*/
api.interceptors.response.use(
async (response: AxiosResponse) => {
// 从请求队列中移除
removePendingRequest(response.config);
// 计算请求耗时
const duration = Date.now() - (response.config as any).metadata?.startTime;
const resData: any = await parseResponse(response);
// 打印响应信息(开发环境)
// if (__DEV__) {
// console.log('📥 API Response:', {
// url: response.config.url,
// status: response.status,
// duration: `${duration}ms`,
// data: response.data,
// });
// }
// 统一处理响应数据格式
// const apiResponse = response.data as ApiResponse;
// 如果后端返回的数据结构包含 code 和 data
// if (apiResponse && typeof apiResponse === 'object' && 'code' in apiResponse) {
// // 检查业务状态码
// if (apiResponse.code !== 0 && apiResponse.code !== 200) {
// // 业务错误
// const error = new Error(apiResponse.message || '请求失败') as any;
// error.code = apiResponse.code;
// error.response = response;
// return Promise.reject(error);
// }
//
// // 返回 data 字段
// return apiResponse.data;
// }
// 直接返回响应数据
// return response.data;
return Promise.resolve(resData);
},
async (error: AxiosError<ApiError>) => {
// 从请求队列中移除
if (error.config) {
removePendingRequest(error.config);
}
// 如果是取消的请求,直接返回
if (axios.isCancel(error)) {
if (__DEV__) {
console.log('🚫 Request cancelled:', error.message);
}
return Promise.reject(error);
}
const originalRequest = error.config as RequestConfig & { _retry?: boolean };
// 打印错误信息
if (__DEV__) {
console.error('❌ API Error:', {
method: error.config?.method,
cmdId: error.config?.headers?.cmdId,
status: error.response?.status,
message: error.message,
data: error.response?.data,
});
}
// 处理不同的错误状态码
if (error.response) {
const { status, data } = error.response;
switch (status) {
case 401: {
// Token 过期,尝试刷新
if (!originalRequest._retry) {
if (isRefreshing) {
// 如果正在刷新,将请求加入队列
return new Promise((resolve) => {
subscribeTokenRefresh((token: string) => {
if (originalRequest.headers) {
originalRequest.headers.Authorization = `Bearer ${token}`;
}
resolve(api(originalRequest));
});
});
}
originalRequest._retry = true;
isRefreshing = true;
try {
const newToken = await refreshAccessToken();
if (newToken) {
// Token 刷新成功
isRefreshing = false;
onTokenRefreshed(newToken);
// 重试原请求
if (originalRequest.headers) {
originalRequest.headers.Authorization = `Bearer ${newToken}`;
}
return api(originalRequest);
} else {
// Token 刷新失败,跳转到登录页
isRefreshing = false;
await AsyncStorage.multiRemove(['auth_token', 'refresh_token']);
// 跳转到登录页
if (router.canGoBack()) {
router.replace('/(auth)/login' as any);
}
}
} catch (refreshError) {
isRefreshing = false;
await AsyncStorage.multiRemove(['auth_token', 'refresh_token']);
// 跳转到登录页
if (router.canGoBack()) {
router.replace('/(auth)/login' as any);
}
return Promise.reject(refreshError);
}
}
break;
}
case 403:
// 禁止访问
console.error('❌ 403: 没有权限访问该资源');
break;
case 404:
// 资源不存在
console.error('❌ 404: 请求的资源不存在');
break;
case 422:
// 表单验证错误
console.error('❌ 422: 表单验证失败', data);
break;
case 429:
// 请求过于频繁
console.error('❌ 429: 请求过于频繁,请稍后再试');
break;
case 500:
// 服务器错误
console.error('❌ 500: 服务器内部错误');
break;
case 502:
// 网关错误
console.error('❌ 502: 网关错误');
break;
case 503:
// 服务不可用
console.error('❌ 503: 服务暂时不可用');
break;
default:
console.error(`${status}: 未知错误`);
}
} else if (error.request) {
// 请求已发送但没有收到响应
console.error('❌ 网络错误: 请检查网络连接');
} else {
// 请求配置出错
console.error('❌ 请求配置错误:', error.message);
}
// 自定义错误处理
if (originalRequest?.customErrorHandler) {
originalRequest.customErrorHandler(error);
}
return Promise.reject(error);
}
);
/**
* 取消所有待处理的请求
*/
export function cancelAllRequests(message = '请求已取消'): void {
pendingRequests.forEach((source) => {
source.cancel(message);
});
pendingRequests.clear();
}
/**
* 取消指定 URL 的请求
*/
export function cancelRequest(url: string): void {
pendingRequests.forEach((source, key) => {
if (key.includes(url)) {
source.cancel('请求已取消');
pendingRequests.delete(key);
}
});
}
/**
* 通用请求方法(增强版)
*/
export const request = {
/**
* GET 请求
*/
get: <T = any>(url: string, config?: RequestConfig) => api.get<T, T>(url, config),
/**
* POST 请求
*/
post: <T = any>(url: string, data?: any, config?: RequestConfig) =>
api.post<T, T>(url, data, config),
/**
* PUT 请求
*/
put: <T = any>(url: string, data?: any, config?: RequestConfig) =>
api.put<T, T>(url, data, config),
/**
* DELETE 请求
*/
delete: <T = any>(url: string, config?: RequestConfig) => api.delete<T, T>(url, config),
/**
* PATCH 请求
*/
patch: <T = any>(url: string, data?: any, config?: RequestConfig) =>
api.patch<T, T>(url, data, config),
/**
* 上传文件
*/
upload: <T = any>(
url: string,
file: File | Blob,
onProgress?: (progress: number) => void,
config?: RequestConfig
) => {
const formData = new FormData();
formData.append('file', file);
return api.post<T, T>(url, formData, {
...config,
headers: {
'Content-Type': 'multipart/form-data',
...config?.headers,
},
onUploadProgress: (progressEvent) => {
if (onProgress && progressEvent.total) {
const progress = Math.round((progressEvent.loaded * 100) / progressEvent.total);
onProgress(progress);
}
},
});
},
/**
* 下载文件
*/
download: async (
url: string,
filename?: string,
onProgress?: (progress: number) => void,
config?: RequestConfig
) => {
const response: any = await api.get(url, {
...config,
responseType: 'blob',
onDownloadProgress: (progressEvent) => {
if (onProgress && progressEvent.total) {
const progress = Math.round((progressEvent.loaded * 100) / progressEvent.total);
onProgress(progress);
}
},
});
// 创建下载链接
const blob = new Blob([response]);
const downloadUrl = window.URL.createObjectURL(blob);
const link = document.createElement('a');
link.href = downloadUrl;
link.download = filename || 'download';
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
window.URL.revokeObjectURL(downloadUrl);
return response;
},
/**
* 并发请求
*/
all: <T = any>(requests: Promise<T>[]) => Promise.all(requests),
/**
* 串行请求
*/
series: async <T = any>(requests: (() => Promise<T>)[]): Promise<T[]> => {
const results: T[] = [];
for (const request of requests) {
const result = await request();
results.push(result);
}
return results;
},
};
/**
* 创建带重试的请求
*/
export function createRetryRequest<T = any>(
requestFn: () => Promise<T>,
maxRetries = 3,
retryDelay = 1000
): Promise<T> {
return new Promise((resolve, reject) => {
let retries = 0;
const attempt = async () => {
try {
const result = await requestFn();
resolve(result);
} catch (error) {
retries++;
if (retries < maxRetries) {
if (__DEV__) {
console.log(`🔄 Retrying request (${retries}/${maxRetries})...`);
}
setTimeout(attempt, retryDelay * retries);
} else {
reject(error);
}
}
};
attempt();
});
}
export default api;

492
utils/network/des.ts Normal file
View File

@@ -0,0 +1,492 @@
// @ts-nocheck
//des
//this takes the key, the message, and whether to encrypt or decrypt
export function des(key, message, encrypt, mode, iv, padding) {
if (encrypt)
//如果是加密的话,首先转换编码
message = unescape(encodeURIComponent(message));
//declaring this locally speeds things up a bit
const spfunction1 = [
0x1010400, 0, 0x10000, 0x1010404, 0x1010004, 0x10404, 0x4, 0x10000, 0x400, 0x1010400, 0x1010404,
0x400, 0x1000404, 0x1010004, 0x1000000, 0x4, 0x404, 0x1000400, 0x1000400, 0x10400, 0x10400,
0x1010000, 0x1010000, 0x1000404, 0x10004, 0x1000004, 0x1000004, 0x10004, 0, 0x404, 0x10404,
0x1000000, 0x10000, 0x1010404, 0x4, 0x1010000, 0x1010400, 0x1000000, 0x1000000, 0x400,
0x1010004, 0x10000, 0x10400, 0x1000004, 0x400, 0x4, 0x1000404, 0x10404, 0x1010404, 0x10004,
0x1010000, 0x1000404, 0x1000004, 0x404, 0x10404, 0x1010400, 0x404, 0x1000400, 0x1000400, 0,
0x10004, 0x10400, 0, 0x1010004,
];
const spfunction2 = [
-0x7fef7fe0, -0x7fff8000, 0x8000, 0x108020, 0x100000, 0x20, -0x7fefffe0, -0x7fff7fe0,
-0x7fffffe0, -0x7fef7fe0, -0x7fef8000, -0x80000000, -0x7fff8000, 0x100000, 0x20, -0x7fefffe0,
0x108000, 0x100020, -0x7fff7fe0, 0, -0x80000000, 0x8000, 0x108020, -0x7ff00000, 0x100020,
-0x7fffffe0, 0, 0x108000, 0x8020, -0x7fef8000, -0x7ff00000, 0x8020, 0, 0x108020, -0x7fefffe0,
0x100000, -0x7fff7fe0, -0x7ff00000, -0x7fef8000, 0x8000, -0x7ff00000, -0x7fff8000, 0x20,
-0x7fef7fe0, 0x108020, 0x20, 0x8000, -0x80000000, 0x8020, -0x7fef8000, 0x100000, -0x7fffffe0,
0x100020, -0x7fff7fe0, -0x7fffffe0, 0x100020, 0x108000, 0, -0x7fff8000, 0x8020, -0x80000000,
-0x7fefffe0, -0x7fef7fe0, 0x108000,
];
const spfunction3 = [
0x208, 0x8020200, 0, 0x8020008, 0x8000200, 0, 0x20208, 0x8000200, 0x20008, 0x8000008, 0x8000008,
0x20000, 0x8020208, 0x20008, 0x8020000, 0x208, 0x8000000, 0x8, 0x8020200, 0x200, 0x20200,
0x8020000, 0x8020008, 0x20208, 0x8000208, 0x20200, 0x20000, 0x8000208, 0x8, 0x8020208, 0x200,
0x8000000, 0x8020200, 0x8000000, 0x20008, 0x208, 0x20000, 0x8020200, 0x8000200, 0, 0x200,
0x20008, 0x8020208, 0x8000200, 0x8000008, 0x200, 0, 0x8020008, 0x8000208, 0x20000, 0x8000000,
0x8020208, 0x8, 0x20208, 0x20200, 0x8000008, 0x8020000, 0x8000208, 0x208, 0x8020000, 0x20208,
0x8, 0x8020008, 0x20200,
];
const spfunction4 = [
0x802001, 0x2081, 0x2081, 0x80, 0x802080, 0x800081, 0x800001, 0x2001, 0, 0x802000, 0x802000,
0x802081, 0x81, 0, 0x800080, 0x800001, 0x1, 0x2000, 0x800000, 0x802001, 0x80, 0x800000, 0x2001,
0x2080, 0x800081, 0x1, 0x2080, 0x800080, 0x2000, 0x802080, 0x802081, 0x81, 0x800080, 0x800001,
0x802000, 0x802081, 0x81, 0, 0, 0x802000, 0x2080, 0x800080, 0x800081, 0x1, 0x802001, 0x2081,
0x2081, 0x80, 0x802081, 0x81, 0x1, 0x2000, 0x800001, 0x2001, 0x802080, 0x800081, 0x2001, 0x2080,
0x800000, 0x802001, 0x80, 0x800000, 0x2000, 0x802080,
];
const spfunction5 = [
0x100, 0x2080100, 0x2080000, 0x42000100, 0x80000, 0x100, 0x40000000, 0x2080000, 0x40080100,
0x80000, 0x2000100, 0x40080100, 0x42000100, 0x42080000, 0x80100, 0x40000000, 0x2000000,
0x40080000, 0x40080000, 0, 0x40000100, 0x42080100, 0x42080100, 0x2000100, 0x42080000,
0x40000100, 0, 0x42000000, 0x2080100, 0x2000000, 0x42000000, 0x80100, 0x80000, 0x42000100,
0x100, 0x2000000, 0x40000000, 0x2080000, 0x42000100, 0x40080100, 0x2000100, 0x40000000,
0x42080000, 0x2080100, 0x40080100, 0x100, 0x2000000, 0x42080000, 0x42080100, 0x80100,
0x42000000, 0x42080100, 0x2080000, 0, 0x40080000, 0x42000000, 0x80100, 0x2000100, 0x40000100,
0x80000, 0, 0x40080000, 0x2080100, 0x40000100,
];
const spfunction6 = [
0x20000010, 0x20400000, 0x4000, 0x20404010, 0x20400000, 0x10, 0x20404010, 0x400000, 0x20004000,
0x404010, 0x400000, 0x20000010, 0x400010, 0x20004000, 0x20000000, 0x4010, 0, 0x400010,
0x20004010, 0x4000, 0x404000, 0x20004010, 0x10, 0x20400010, 0x20400010, 0, 0x404010, 0x20404000,
0x4010, 0x404000, 0x20404000, 0x20000000, 0x20004000, 0x10, 0x20400010, 0x404000, 0x20404010,
0x400000, 0x4010, 0x20000010, 0x400000, 0x20004000, 0x20000000, 0x4010, 0x20000010, 0x20404010,
0x404000, 0x20400000, 0x404010, 0x20404000, 0, 0x20400010, 0x10, 0x4000, 0x20400000, 0x404010,
0x4000, 0x400010, 0x20004010, 0, 0x20404000, 0x20000000, 0x400010, 0x20004010,
];
const spfunction7 = [
0x200000, 0x4200002, 0x4000802, 0, 0x800, 0x4000802, 0x200802, 0x4200800, 0x4200802, 0x200000,
0, 0x4000002, 0x2, 0x4000000, 0x4200002, 0x802, 0x4000800, 0x200802, 0x200002, 0x4000800,
0x4000002, 0x4200000, 0x4200800, 0x200002, 0x4200000, 0x800, 0x802, 0x4200802, 0x200800, 0x2,
0x4000000, 0x200800, 0x4000000, 0x200800, 0x200000, 0x4000802, 0x4000802, 0x4200002, 0x4200002,
0x2, 0x200002, 0x4000000, 0x4000800, 0x200000, 0x4200800, 0x802, 0x200802, 0x4200800, 0x802,
0x4000002, 0x4200802, 0x4200000, 0x200800, 0, 0x2, 0x4200802, 0, 0x200802, 0x4200000, 0x800,
0x4000002, 0x4000800, 0x800, 0x200002,
];
const spfunction8 = [
0x10001040, 0x1000, 0x40000, 0x10041040, 0x10000000, 0x10001040, 0x40, 0x10000000, 0x40040,
0x10040000, 0x10041040, 0x41000, 0x10041000, 0x41040, 0x1000, 0x40, 0x10040000, 0x10000040,
0x10001000, 0x1040, 0x41000, 0x40040, 0x10040040, 0x10041000, 0x1040, 0, 0, 0x10040040,
0x10000040, 0x10001000, 0x41040, 0x40000, 0x41040, 0x40000, 0x10041000, 0x1000, 0x40,
0x10040040, 0x1000, 0x41040, 0x10001000, 0x40, 0x10000040, 0x10040000, 0x10040040, 0x10000000,
0x40000, 0x10001040, 0, 0x10041040, 0x40040, 0x10000040, 0x10040000, 0x10001000, 0x10001040, 0,
0x10041040, 0x41000, 0x41000, 0x1040, 0x1040, 0x40040, 0x10000000, 0x10041000,
];
//create the 16 or 48 subkeys we will need
const keys = des_createKeys(key);
let m = 0,
i,
j,
temp,
temp2,
right1,
right2,
left,
right,
looping;
let cbcleft, cbcleft2, cbcright, cbcright2;
let endloop, loopinc;
var len = message.length;
let chunk = 0;
//set up the loops for single and triple des
const iterations = keys.length == 32 ? 3 : 9; //single or triple des
if (iterations == 3) {
looping = encrypt ? [0, 32, 2] : [30, -2, -2];
} else {
looping = encrypt ? [0, 32, 2, 62, 30, -2, 64, 96, 2] : [94, 62, -2, 32, 64, 2, 30, -2, -2];
}
//pad the message depending on the padding parameter
if (padding == 2)
message += ' '; //pad the message with spaces
else if (padding == 1) {
if (encrypt) {
temp = 8 - (len % 8);
message += String.fromCharCode(temp, temp, temp, temp, temp, temp, temp, temp);
if (temp === 8) len += 8;
}
} //PKCS7 padding
else if (!padding) message += '\0\0\0\0\0\0\0\0'; //pad the message out with null bytes
//store the result here
let result = '';
let tempresult = '';
if (mode == 1) {
//CBC mode
cbcleft =
(iv.charCodeAt(m++) << 24) |
(iv.charCodeAt(m++) << 16) |
(iv.charCodeAt(m++) << 8) |
iv.charCodeAt(m++);
cbcright =
(iv.charCodeAt(m++) << 24) |
(iv.charCodeAt(m++) << 16) |
(iv.charCodeAt(m++) << 8) |
iv.charCodeAt(m++);
m = 0;
}
//loop through each 64 bit chunk of the message
while (m < len) {
left =
(message.charCodeAt(m++) << 24) |
(message.charCodeAt(m++) << 16) |
(message.charCodeAt(m++) << 8) |
message.charCodeAt(m++);
right =
(message.charCodeAt(m++) << 24) |
(message.charCodeAt(m++) << 16) |
(message.charCodeAt(m++) << 8) |
message.charCodeAt(m++);
//for Cipher Block Chaining mode, xor the message with the previous result
if (mode == 1) {
if (encrypt) {
left ^= cbcleft;
right ^= cbcright;
} else {
cbcleft2 = cbcleft;
cbcright2 = cbcright;
cbcleft = left;
cbcright = right;
}
}
//first each 64 but chunk of the message must be permuted according to IP
temp = ((left >>> 4) ^ right) & 0x0f0f0f0f;
right ^= temp;
left ^= temp << 4;
temp = ((left >>> 16) ^ right) & 0x0000ffff;
right ^= temp;
left ^= temp << 16;
temp = ((right >>> 2) ^ left) & 0x33333333;
left ^= temp;
right ^= temp << 2;
temp = ((right >>> 8) ^ left) & 0x00ff00ff;
left ^= temp;
right ^= temp << 8;
temp = ((left >>> 1) ^ right) & 0x55555555;
right ^= temp;
left ^= temp << 1;
left = (left << 1) | (left >>> 31);
right = (right << 1) | (right >>> 31);
//do this either 1 or 3 times for each chunk of the message
for (j = 0; j < iterations; j += 3) {
endloop = looping[j + 1];
loopinc = looping[j + 2];
//now go through and perform the encryption or decryption
for (i = looping[j]; i != endloop; i += loopinc) {
//for efficiency
right1 = right ^ keys[i];
right2 = ((right >>> 4) | (right << 28)) ^ keys[i + 1];
//the result is attained by passing these bytes through the S selection functions
temp = left;
left = right;
right =
temp ^
(spfunction2[(right1 >>> 24) & 0x3f] |
spfunction4[(right1 >>> 16) & 0x3f] |
spfunction6[(right1 >>> 8) & 0x3f] |
spfunction8[right1 & 0x3f] |
spfunction1[(right2 >>> 24) & 0x3f] |
spfunction3[(right2 >>> 16) & 0x3f] |
spfunction5[(right2 >>> 8) & 0x3f] |
spfunction7[right2 & 0x3f]);
}
temp = left;
left = right;
right = temp; //unreverse left and right
} //for either 1 or 3 iterations
//move then each one bit to the right
left = (left >>> 1) | (left << 31);
right = (right >>> 1) | (right << 31);
//now perform IP-1, which is IP in the opposite direction
temp = ((left >>> 1) ^ right) & 0x55555555;
right ^= temp;
left ^= temp << 1;
temp = ((right >>> 8) ^ left) & 0x00ff00ff;
left ^= temp;
right ^= temp << 8;
temp = ((right >>> 2) ^ left) & 0x33333333;
left ^= temp;
right ^= temp << 2;
temp = ((left >>> 16) ^ right) & 0x0000ffff;
right ^= temp;
left ^= temp << 16;
temp = ((left >>> 4) ^ right) & 0x0f0f0f0f;
right ^= temp;
left ^= temp << 4;
//for Cipher Block Chaining mode, xor the message with the previous result
if (mode == 1) {
if (encrypt) {
cbcleft = left;
cbcright = right;
} else {
left ^= cbcleft2;
right ^= cbcright2;
}
}
tempresult += String.fromCharCode(
left >>> 24,
(left >>> 16) & 0xff,
(left >>> 8) & 0xff,
left & 0xff,
right >>> 24,
(right >>> 16) & 0xff,
(right >>> 8) & 0xff,
right & 0xff
);
chunk += 8;
if (chunk == 512) {
result += tempresult;
tempresult = '';
chunk = 0;
}
} //for every 8 characters, or 64 bits in the message
//return the result as an array
result += tempresult;
if (!encrypt) result = result.replace(/\0*$/g, '');
if (!encrypt) {
//如果是解密的话解密结束后对PKCS7 padding进行解码并转换成utf-8编码
if (padding === 1) {
//PKCS7 padding解码
var len = result.length,
paddingChars = 0;
len && (paddingChars = result.charCodeAt(len - 1));
paddingChars <= 8 && (result = result.substring(0, len - paddingChars));
}
//转换成UTF-8编码
result = decodeURIComponent(escape(result));
}
return result;
} //end of des
//des_createKeys
//this takes as input a 64 bit key (even though only 56 bits are used)
//as an array of 2 integers, and returns 16 48 bit keys
function des_createKeys(key) {
//declaring this locally speeds things up a bit
const pc2bytes0 = [
0, 0x4, 0x20000000, 0x20000004, 0x10000, 0x10004, 0x20010000, 0x20010004, 0x200, 0x204,
0x20000200, 0x20000204, 0x10200, 0x10204, 0x20010200, 0x20010204,
];
const pc2bytes1 = [
0, 0x1, 0x100000, 0x100001, 0x4000000, 0x4000001, 0x4100000, 0x4100001, 0x100, 0x101, 0x100100,
0x100101, 0x4000100, 0x4000101, 0x4100100, 0x4100101,
];
const pc2bytes2 = [
0, 0x8, 0x800, 0x808, 0x1000000, 0x1000008, 0x1000800, 0x1000808, 0, 0x8, 0x800, 0x808,
0x1000000, 0x1000008, 0x1000800, 0x1000808,
];
const pc2bytes3 = [
0, 0x200000, 0x8000000, 0x8200000, 0x2000, 0x202000, 0x8002000, 0x8202000, 0x20000, 0x220000,
0x8020000, 0x8220000, 0x22000, 0x222000, 0x8022000, 0x8222000,
];
const pc2bytes4 = [
0, 0x40000, 0x10, 0x40010, 0, 0x40000, 0x10, 0x40010, 0x1000, 0x41000, 0x1010, 0x41010, 0x1000,
0x41000, 0x1010, 0x41010,
];
const pc2bytes5 = [
0, 0x400, 0x20, 0x420, 0, 0x400, 0x20, 0x420, 0x2000000, 0x2000400, 0x2000020, 0x2000420,
0x2000000, 0x2000400, 0x2000020, 0x2000420,
];
const pc2bytes6 = [
0, 0x10000000, 0x80000, 0x10080000, 0x2, 0x10000002, 0x80002, 0x10080002, 0, 0x10000000,
0x80000, 0x10080000, 0x2, 0x10000002, 0x80002, 0x10080002,
];
const pc2bytes7 = [
0, 0x10000, 0x800, 0x10800, 0x20000000, 0x20010000, 0x20000800, 0x20010800, 0x20000, 0x30000,
0x20800, 0x30800, 0x20020000, 0x20030000, 0x20020800, 0x20030800,
];
const pc2bytes8 = [
0, 0x40000, 0, 0x40000, 0x2, 0x40002, 0x2, 0x40002, 0x2000000, 0x2040000, 0x2000000, 0x2040000,
0x2000002, 0x2040002, 0x2000002, 0x2040002,
];
const pc2bytes9 = [
0, 0x10000000, 0x8, 0x10000008, 0, 0x10000000, 0x8, 0x10000008, 0x400, 0x10000400, 0x408,
0x10000408, 0x400, 0x10000400, 0x408, 0x10000408,
];
const pc2bytes10 = [
0, 0x20, 0, 0x20, 0x100000, 0x100020, 0x100000, 0x100020, 0x2000, 0x2020, 0x2000, 0x2020,
0x102000, 0x102020, 0x102000, 0x102020,
];
const pc2bytes11 = [
0, 0x1000000, 0x200, 0x1000200, 0x200000, 0x1200000, 0x200200, 0x1200200, 0x4000000, 0x5000000,
0x4000200, 0x5000200, 0x4200000, 0x5200000, 0x4200200, 0x5200200,
];
const pc2bytes12 = [
0, 0x1000, 0x8000000, 0x8001000, 0x80000, 0x81000, 0x8080000, 0x8081000, 0x10, 0x1010,
0x8000010, 0x8001010, 0x80010, 0x81010, 0x8080010, 0x8081010,
];
const pc2bytes13 = [
0, 0x4, 0x100, 0x104, 0, 0x4, 0x100, 0x104, 0x1, 0x5, 0x101, 0x105, 0x1, 0x5, 0x101, 0x105,
];
//how many iterations (1 for des, 3 for triple des)
const iterations = key.length > 8 ? 3 : 1; //changed by Paul 16/6/2007 to use Triple DES for 9+ byte keys
//stores the return keys
const keys = new Array(32 * iterations);
//now define the left shifts which need to be done
const shifts = [0, 0, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 0];
//other variables
let lefttemp,
righttemp,
m = 0,
n = 0,
temp;
for (let j = 0; j < iterations; j++) {
//either 1 or 3 iterations
let left =
(key.charCodeAt(m++) << 24) |
(key.charCodeAt(m++) << 16) |
(key.charCodeAt(m++) << 8) |
key.charCodeAt(m++);
let right =
(key.charCodeAt(m++) << 24) |
(key.charCodeAt(m++) << 16) |
(key.charCodeAt(m++) << 8) |
key.charCodeAt(m++);
temp = ((left >>> 4) ^ right) & 0x0f0f0f0f;
right ^= temp;
left ^= temp << 4;
temp = ((right >>> -16) ^ left) & 0x0000ffff;
left ^= temp;
right ^= temp << -16;
temp = ((left >>> 2) ^ right) & 0x33333333;
right ^= temp;
left ^= temp << 2;
temp = ((right >>> -16) ^ left) & 0x0000ffff;
left ^= temp;
right ^= temp << -16;
temp = ((left >>> 1) ^ right) & 0x55555555;
right ^= temp;
left ^= temp << 1;
temp = ((right >>> 8) ^ left) & 0x00ff00ff;
left ^= temp;
right ^= temp << 8;
temp = ((left >>> 1) ^ right) & 0x55555555;
right ^= temp;
left ^= temp << 1;
//the right side needs to be shifted and to get the last four bits of the left side
temp = (left << 8) | ((right >>> 20) & 0x000000f0);
//left needs to be put upside down
left =
(right << 24) |
((right << 8) & 0xff0000) |
((right >>> 8) & 0xff00) |
((right >>> 24) & 0xf0);
right = temp;
//now go through and perform these shifts on the left and right keys
for (let i = 0; i < shifts.length; i++) {
//shift the keys either one or two bits to the left
if (shifts[i]) {
left = (left << 2) | (left >>> 26);
right = (right << 2) | (right >>> 26);
} else {
left = (left << 1) | (left >>> 27);
right = (right << 1) | (right >>> 27);
}
left &= -0xf;
right &= -0xf;
//now apply PC-2, in such a way that E is easier when encrypting or decrypting
//this conversion will look like PC-2 except only the last 6 bits of each byte are used
//rather than 48 consecutive bits and the order of lines will be according to
//how the S selection functions will be applied: S2, S4, S6, S8, S1, S3, S5, S7
lefttemp =
pc2bytes0[left >>> 28] |
pc2bytes1[(left >>> 24) & 0xf] |
pc2bytes2[(left >>> 20) & 0xf] |
pc2bytes3[(left >>> 16) & 0xf] |
pc2bytes4[(left >>> 12) & 0xf] |
pc2bytes5[(left >>> 8) & 0xf] |
pc2bytes6[(left >>> 4) & 0xf];
righttemp =
pc2bytes7[right >>> 28] |
pc2bytes8[(right >>> 24) & 0xf] |
pc2bytes9[(right >>> 20) & 0xf] |
pc2bytes10[(right >>> 16) & 0xf] |
pc2bytes11[(right >>> 12) & 0xf] |
pc2bytes12[(right >>> 8) & 0xf] |
pc2bytes13[(right >>> 4) & 0xf];
temp = ((righttemp >>> 16) ^ lefttemp) & 0x0000ffff;
keys[n++] = lefttemp ^ temp;
keys[n++] = righttemp ^ (temp << 16);
}
} //for each iterations
//return the keys we've created
return keys;
} //end of des_createKeys
function genkey(key, start, end) {
//8 byte / 64 bit Key (DES) or 192 bit Key
return { key: pad(key.slice(start, end)), vector: 1 };
}
function pad(key) {
for (let i = key.length; i < 24; i++) {
key += '0';
}
return key;
}
//3DES加密使用PKCS7 padding
function encrypt_3des(key, input) {
const genKey = genkey(key, 0, 24);
return btoa(des(genKey.key, input, 1, 0, 0, 1));
}
//3DES解密使用PKCS7 padding
function decrypt_3des(key, input) {
const genKey = genkey(key, 0, 24);
return des(genKey.key, atob(input), 0, 0, 0, 1);
}
////////////////////////////// TEST //////////////////////////////
export function stringToHex(s) {
let r = '0x';
const hexes = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'];
for (let i = 0; i < s.length; i++) {
r += hexes[s.charCodeAt(i) >> 4] + hexes[s.charCodeAt(i) & 0xf];
}
return r;
}
export function stringToHexArray(s) {
const arr = [];
const hexes = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'];
for (let i = 0; i < s.length; i++) {
arr.push(hexes[s.charCodeAt(i) >> 4] + hexes[s.charCodeAt(i) & 0xf]);
}
return arr;
}
export function hexToString(h) {
let r = '';
for (let i = h.substr(0, 2) == '0x' ? 2 : 0; i < h.length; i += 2) {
r += String.fromCharCode(parseInt(h.substr(i, 2), 16));
}
return r;
}
/* eslint-enable */

119
utils/network/error.ts Normal file
View File

@@ -0,0 +1,119 @@
// import { useTenantStore, useUserStore, useModalStore } from '@star/stores';
// import { mb_t } from '@star/languages';
// import { router } from '@routers';
import { endsWith, includes, startsWith } from 'lodash-es';
// import { ModalTypeEnum } from '@star/constants';
// 错误提示
export default class NetworkError {
tag: string;
key: string;
info: string;
level: string;
origin: string;
cmd: string;
code: number;
logout: boolean;
update: boolean;
time: string;
constructor(options: any) {
this.tag = '';
this.key = '';
this.info = '';
this.level = '';
this.origin = '';
this.cmd = '';
// this.code = 0
this.code = -1;
this.logout = false;
this.update = false;
this.time = new Date().toLocaleString().split('GMT')[0];
if (options) {
if (options.key) {
this.key = options.key;
}
if (options.tag) {
this.tag = options.tag;
}
if (options.info) {
if (startsWith(options.info, '{') && endsWith(options.info, '}')) {
const result = JSON.parse(options.info);
this.info = result?.msg || result?.exMessage || '';
} else {
this.info = options.info;
}
}
if (options.level) {
this.level = options.level;
}
if (options.origin) {
this.origin = options.origin;
}
if (options.cmd) {
this.cmd = options.cmd;
}
if (options.code) {
this.code = options.code;
}
if (options.logout) {
this.logout = options.logout;
}
if (options.update) {
this.update = options.update;
}
}
// const tenantStore = useTenantStore();
// if ([371130, 381119].includes(Number(this.cmd))) {
// tenantStore.setRepairStatus(true);
// }
if (this.info.indexOf('invalid_token') !== -1) {
this.info = '亲,麻烦重新登录哦';
} else if (this.info.indexOf('Request failed with status code 500') !== -1) {
this.info = '网络不给力,请检查网络' + '(500)';
} else if (this.info.indexOf('Request failed with status code 502') !== -1) {
this.info = '网络不给力,请检查网络' + '(502)';
} else if (this.info.indexOf('Error 503 Service Unavailable') !== -1) {
this.info = '网络不给力,请检查网络' + '(503)';
} else if (this.info.indexOf('Request failed with status code 504') !== -1) {
this.info = '网络不给力,请检查网络' + '(504)';
} else if (this.info.indexOf('timeout of 20000ms') !== -1) {
this.info = '请求超时,请检查网络';
} else if (this.info.indexOf('Error 400 Bad Request') !== -1) {
this.info = '网络不给力,请重新尝试';
} else if (this.info.indexOf('Network Error') !== -1) {
this.info = '网络错误,请检查网络';
} else if (options?.code === '1115') {
// tenantStore.setRepairData(options.origin);
// tenantStore.setRepairStatus(true);
} else {
console.error('err.info:', this.cmd, this.info, options);
// this.info = this.info
// if (this.info) {
// showFailToast(mb_t(this.info));
// }
// if (this.info && this.info.includes('重新登录')) {
// const userStore = useUserStore();
// userStore?.clearUserInfo?.();
// if (
// !startsWith(router.currentRoute.value?.path, '/activity') &&
// !includes(['home', 'mine', 'activity'], router.currentRoute.value?.name)
// ) {
// const modalStore = useModalStore();
// const path = router.currentRoute.value?.fullPath;
// modalStore.showRegisterModal({ redirect: path });
// router.replace?.('/home');
// }
// }
// if (this.info.includes('访问限制')) {
// tenantStore.setIPLimitStatus(true);
// }
// [371130, 370433]返回了请求没有任何数据 才跳维护 其他接口忽略
// if ([371130, 370433].includes(Number(this.cmd))) {
// // console.log(371130, 370433, this.info, '????????????????????????????');
// tenantStore.setRepairStatus(true);
// }
}
// this.info = mb_t(this.info);
}
}

434
utils/network/helper.ts Normal file
View File

@@ -0,0 +1,434 @@
import { HmacMD5 } from 'crypto-js';
import Base64 from 'crypto-js/enc-base64';
import Latin1 from 'crypto-js/enc-latin1';
import md5 from 'md5';
import { AxiosResponse } from 'axios';
import * as des from './des';
import NetworkError from './error';
import { toNumber, toString, startsWith, isString, isNumber } from 'lodash-es';
import { NetworkTypeEnum } from '@/constants/network';
import appConfig from '../config';
// import NetworkError from './error'
// import { storeToRefs, useTenantStore, useUserStore, useAppStore, start } from '../index';
// import { isMobile, getBetPlatform } from '@star/utils';
// import { langToNum } from '@star/languages';
// 请求到的数据返回
export type NetworkResponse<T> = {
type: NetworkTypeEnum;
data: T;
};
export const getBetPlatform = (isReturnIndex = false) => {
// 5=PC; 7=HOMESCREEN_IOS; 6=HOMESCREEN_ANDROID; 4=H5_IOS 3=IOS 2=H5_ANDROID; 1=ANDROID 8=马甲包
return 'H5_IOS';
// const platform = new URLSearchParams(window.location.search).get('platform');
// if (platform) {
// return platform?.includes('IOS') ? 'IOS' : platform?.includes('ANDROID') ? 'ANDROID' : '';
// }
// if (isAppMJB()) {
// return 'APPS_ANDROID';
// }
// if (isPWA()) {
// if (isIOS()) {
// return isReturnIndex ? 4 : 'HS_IOS';
// } else {
// return isReturnIndex ? 2 : 'HS_ANDROID';
// }
// }
// if (isAndroid()) {
// if (BASE_CONFIG.appVersion > 0) {
// return isReturnIndex ? 1 : 'ANDROID';
// } else {
// return isReturnIndex ? 2 : 'H5_ANDROID';
// }
// }
// if (isIOS()) {
// if (BASE_CONFIG.appVersion > 0) {
// return isReturnIndex ? 3 : 'IOS';
// } else {
// return isReturnIndex ? 4 : 'H5_IOS';
// }
// }
// return isReturnIndex ? 5 : 'PC';
};
const uuid = (len: number, radix: number) => {
const chars = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz'.split('');
const uuid: any[] = [];
radix = radix || chars.length;
if (len) {
// Compact form
for (let i = 0; i < len; i++) uuid[i] = chars[0 | (Math.random() * radix)];
} else {
// rfc4122, version 4 form
/* eslint-disable */
let r;
// rfc4122 requires these characters
uuid[8] = uuid[13] = uuid[18] = uuid[23] = '-';
uuid[14] = '4';
// Fill in random data. At i==19 set the high bits of clock sequence as
// per rfc4122, sec. 4.1.5
for (let i = 0; i < 36; i++) {
if (!uuid[i]) {
r = 0 | (Math.random() * 16);
uuid[i] = chars[i == 19 ? (r & 0x3) | 0x8 : r];
}
}
/* eslint-enable */
}
return uuid.join('');
};
// 格式化要发送的数据
export const formatSendData = (data: any, type: number = 0) => {
// url code
if (type === 0) {
const arr: any[] = [];
for (const k in data) {
const v = data[k];
if (v instanceof Array) {
for (const subK in v) {
arr.push(`${k}[]=${v[subK]}`);
}
} else {
arr.push(`${k}=${data[k]}`);
}
}
return arr.join('&');
} else if (type === 2) {
return data.join('/');
}
// json
return JSON.stringify(data);
};
export const getP = (p: any) => {
return HmacMD5(p, '7NEkojNzfkk=').toString();
};
export const enD = (rk: string, str: string) => {
const enc = des.des(rk, str, 1, 0, null, 1);
return Base64.stringify(Latin1.parse(enc));
};
export const dnD = (rk: string, str: string) => {
const s = Latin1.stringify(Base64.parse(str));
const d = des.des(rk, s, 0, 0, null, 1);
return d;
};
export const enP = (rk: string, vk: string, t: number) => {
const enc = des.des(vk, rk + t, 1, 0, null, 1);
return Base64.stringify(Latin1.parse(enc));
};
export const dnP = (vk: string, str: string) => {
const s = Latin1.stringify(Base64.parse(str));
const p = des.des(vk, s, 0, 0, null, 1);
return p;
};
export const enC = (rk: string, vk: string, m: string) => {
const enc = HmacMD5(m + rk, vk);
return Base64.stringify(enc);
};
export const getRequestKey = (cmdId: number, data: any) => {
return `${cmdId}&${data ? md5(JSON.stringify(data)) : ''}`;
};
// 加工请求数据
export const transformRequest = (config: any) => {
const { headerType = 2, paramType = 0, cmdId, tid, ...reset } = config.headers;
const headers: Record<string, any> = {};
// const { tenantInfo } = storeToRefs(useTenantStore());
// const { userInfo } = storeToRefs(useUserStore());
// const { language } = storeToRefs(useAppStore());
const t = new Date().getTime();
const rk = md5(toString(Math.random() + t)).substring(0, 8);
const vk = appConfig.app.vk as string;
const pwds = enP(rk, vk, t);
const tenantInfo = {
tid: 3,
};
let userInfo = {
cust_id: '',
cust_name: '',
access_token: ''
};
// if (['17', '3310052', '310111', '310122', '310400', '4', '402', '401', '310635'].includes(cmdId)) {
// const publicParams = {
// tid: tid ?? tenantInfo.value?.tid ?? '',
// cust_id: (config.data?.cust_id === 0 ? '0' : '') || userInfo?.value?.cust_id || '',
// cust_name: (config.data?.cust_name === 0 ? '0' : config.data?.cust_name) || userInfo?.value?.cust_name || '',
// oper_code: (config.data?.cust_id === 0 ? '0' : '') || userInfo?.value?.cust_id || '',
// oper_name: (config.data?.cust_name === 0 ? '0' : config.data?.cust_name) || userInfo?.value?.cust_name || '',
// };
// const localData = localStorage.getItem('publicData') || '';
// const publicData = localData ? JSON.parse(localData) : {};
// if (['17', '3310052', '310111', '310122'].includes(cmdId)) {
// if (['4', '402', '401'].includes(cmdId)) {
// publicParams.tid = publicParams?.tid.toString();
// publicData.client_type = publicData?.client_type.toString();
// publicData.oper_class_type = publicData?.oper_class_type.toString();
// publicData.oper_source_type = publicData?.oper_source_type.toString();
// publicData.mark = publicData?.mark.toString();
// // publicData.ori_value = "";
// // publicData.new_value = "";
// // publicData.trans_type = "101";
// // publicData.busi_type = "206";
// // publicData.oper_method = "11";
// }
//
// Object.assign(config.data, publicParams, publicData);
// } else if (['310400'].includes(cmdId)) {
// console.log('publicParams', publicParams, publicData);
// config.data.memberOperInfoVo = {
// ...publicParams,
// ...publicData,
// ori_value: '',
// new_value: '',
// oper_method: 1,
// trans_type: 105,
// busi_type: 222,
// };
// } else if (['310635', '4', '402', '401'].includes(cmdId)) {
// config.data.trans_type = '101';
// config.data.client_type = publicData.client_type;
// config.data.browser_brand = publicData.browser_brand;
// config.data.os_name = publicData.os_name;
// config.data.os_version = publicData.os_version;
// config.data.device_platform = publicData.device_platform;
// config.data.device_model = publicData.device_model;
// config.data.device_number = publicData.device_number;
// config.data.device_fingerprint = publicData.device_fingerprint;
// }
//
// console.log('config.data', config.data);
// }
const sendStr = enD(rk, formatSendData(config.data, toNumber(paramType)));
const sendDateStr = enD(rk, t.toString());
const checkOr = enC(rk, vk, sendDateStr);
headers.cmdId = cmdId;
headers.aseqId = appConfig.app.aseqId;
headers.nc = appConfig.app.nc;
headers.tid = tid ?? tenantInfo.tid ?? '';
// 试玩游戏cust_id=0 header需要保持一致
headers.custId = (config.data?.cust_id === 0 ? '0' : '') || userInfo?.cust_id || '';
headers.reqId = uuid(32, 16);
// headers.isMobileOpen = isMobile() ? '1' : '0';
headers.isMobileOpen = '1';
// headers.languageNum = langToNum(language.value);
headers.languageNum = 0;
headers.project = 'tiyu-app';
headers.platform = getBetPlatform(); // 哪端
headers.checkOr = checkOr;
headers.pwds = pwds;
headers.datetime = t;
headers.tbc = md5(t + 'fT6phq0wkOPRlAoyToidAnkogUV7tBBD');
headers.reqKey = getRequestKey(cmdId, config.data); // 每一个接口请求的唯一key前端用
if (toNumber(headerType) === 1) {
const grantType = 'password';
const scope = 'read write';
const signature = `react_clientgrant_type=${grantType}scope=${scope}cmd_id=${cmdId}react`;
headers.signature = md5(signature);
} else if (toNumber(headerType) === 2) {
const authorization = `Bearer ${userInfo?.access_token || ''}`;
const signature = `react_clientauthorization=${authorization}cmd_id=${cmdId}react`;
headers.authorization = authorization;
headers.signature = md5(signature);
} else if (toNumber(headerType) === 3) {
const grantType = 'refresh_token';
const scope = '';
const signature = `react_clientgrant_type=${grantType}scope=${scope}cmd_id=${cmdId}react`;
headers.signature = md5(signature);
}
// console.log(headers, cmdId, '<------ request headers');
// localStorage.getItem('SHOW_DATA') &&
// console.error(cmdId, '最终请求参数', formatSendData(config.data, toNumber(paramType))); // 查看最终请求参数
console.log(cmdId, 'request data --->', config.data);
return {
headers: { ...reset, ...headers },
data: sendStr,
};
};
// 解释响应数据
export const parseResponse = (response: AxiosResponse): Promise<NetworkResponse<any>> => {
try {
const { headers, data } = response;
const reqHeaders = response.config?.headers || {};
// if (reqHeaders.cmdId == 310122) {
// console.log(data, reqHeaders.cmdId, '<<<<<<<<<<<<<<<<<<<<<<<<<< parseResponse data');
// }
if (isString(data) && data.length > 0 && !startsWith(data, '<html>')) {
// 检查 headers 是否存在必要的属性
if (!headers || !headers.pwds || !headers.datetime) {
console.error('Response headers missing required fields:', headers);
throw new NetworkError({
key: '',
tag: 'Network error 0',
info: '响应头缺少必要字段',
origin: '',
cmd: reqHeaders.cmdId,
});
}
const drk = dnP(appConfig.app.vk, headers.pwds).substring(0, 8);
const dm = dnD(drk, data);
const dc = enC(drk, appConfig.app.vk, enD(drk, toString(headers.datetime)));
// if (reqHeaders.cmdId == 310122) {
// console.log(dm, JSON.parse(dm), reqHeaders.cmdId);
// }
// localStorage.getItem('SHOW_DATA') &&
// console.error(reqHeaders.cmdId, dm ? JSON.parse(dm) : dm); // 查看请求返回的数据
if (!dm) {
throw new NetworkError({
key: '',
tag: 'Network error 1',
info: '数据为空',
origin: '',
cmd: reqHeaders.cmdId,
});
}
if (dc !== headers.checkor) {
throw new NetworkError({
key: '',
tag: 'Network error 2',
info: '返回数据异常',
origin: '',
cmd: reqHeaders.cmdId,
});
}
const resData: any = JSON.parse(dm);
console.log(
JSON.parse(JSON.stringify(resData)),
reqHeaders.cmdId,
'<<<<<<<<<<<<<<<<<<<<<<<<<< parseResponse resData'
);
if (resData) {
if (resData.exLevel) {
// 接口请求的错误处理
let err: any;
// 预存奖励领取的时候 返回'0'提示
if ([724209].includes(+reqHeaders.cmdId)) {
throw {
key: '',
tag: 'Network error 3',
info: resData.exMessage,
origin: resData,
cmd: reqHeaders.cmdId,
code: resData.exCode,
};
} else {
err = new NetworkError({
key: '',
tag: 'Network error 3',
info: resData.exMessage,
origin: resData,
cmd: reqHeaders.cmdId,
code: resData.exCode,
});
}
// 版本号不对,强制更新
if (err.code === 1108) {
err.update = true;
} else if ([1200, 1201, 1109, 1112, 1202, 1007].includes(+err.code)) {
// 1200 refresh_token错误
// 1201 access_token错误
// 1112 IP限制
err.logout = true; // 退出登录
err.tag = 'Network error 4';
err.logoutMessage = '返回异常';
}
throw err;
} else if (resData.error) {
throw new NetworkError({
key: '',
tag: 'Network error 5',
info: resData.error_description,
origin: resData,
cmd: reqHeaders.cmdId,
code: resData.exCode,
});
} else if (resData.response_code === '0') {
// 回收第三方金额 没有的的时候 也是返回'0'
// 预存奖励领取的时候 返回'0'提示
if ([3911381, 724209].includes(+reqHeaders.cmdId)) {
resData.response_code = '1';
} else {
throw new NetworkError({
key: '',
tag: 'Network error 8',
info: resData.msg || '操作失败',
origin: resData,
cmd: reqHeaders.cmdId,
code: resData.exCode,
});
}
}
} else {
if (!isNumber(toNumber(resData))) {
throw new NetworkError({
key: '',
tag: 'Network error 1',
info: '数据为空',
origin: '',
cmd: reqHeaders.cmdId,
});
}
}
return Promise.resolve({ type: NetworkTypeEnum.SUCCESS, data: resData });
} else {
localStorage.getItem('SHOW_DATA') && console.error(reqHeaders.cmdId, data); // 查看请求返回的原始数据
throw new NetworkError({
key: '',
tag: 'Network error 7',
info: '请求没有返回任何数据',
// origin: context || response.toString(),
cmd: reqHeaders.cmdId,
code: -1,
});
}
} catch (e) {
// // 404等错误处理
// if (typeof context === 'object') {
// errStr = JSON.stringify(context);
// } else {
// const arr = context.match(/title[\s\S]*?title/g);
// if (arr && arr.length !== 0) {
// errStr = arr[0].replace(/title>/g, '').replace(/<\/title/g, '');
// }
// }
// let logout = false;
// if (errStr.indexOf('invalid_token') !== -1) {
// logout = true;
// }
// // 类似这种错误的时候 跳到登录页 "error":"access_denied" "error":"unauthorized"
// if (errStr.indexOf('access_denied') !== -1 || errStr.indexOf('unauthorized') !== -1) {
// logout = true;
// }
// err = new NetworkError({
// key: key,
// tag: 'Network error 6',
// info: errStr,
// origin: context,
// cmd: reqHeaders.cmdId,
// code: -1,
// logout: logout,
// });
return Promise.reject({ type: NetworkTypeEnum.ERROR, data: e });
}
};

220
utils/sessionStorage.ts Normal file
View File

@@ -0,0 +1,220 @@
/**
* Session Storage 实现
*
* React Native 没有原生的 sessionStorage这里提供一个内存实现
* 数据只在应用运行期间保存,应用关闭后会丢失
*
* 特点:
* - 数据存储在内存中
* - 应用重启后数据丢失
* - 适用于临时数据、会话数据
* - API 与 localStorage 类似
*/
/**
* Session Storage 键名常量
*/
export enum SESSION_KEYS {
TEMP_DATA = 'temp_data',
FORM_DRAFT = 'form_draft',
SEARCH_HISTORY = 'search_history',
CURRENT_TAB = 'current_tab',
SCROLL_POSITION = 'scroll_position',
FILTER_STATE = 'filter_state',
}
/**
* Session Storage 类
*
* 使用 Map 实现内存存储
*/
class SessionStorage {
private static storage: Map<string, string> = new Map();
/**
* 存储字符串
*/
static setString(key: string, value: string): void {
try {
this.storage.set(key, value);
if (__DEV__) {
console.log(`💾 SessionStorage set: ${key}`);
}
} catch (error) {
console.error(`SessionStorage setString error for key "${key}":`, error);
throw error;
}
}
/**
* 获取字符串
*/
static getString(key: string): string | null {
try {
const value = this.storage.get(key) ?? null;
if (__DEV__) {
console.log(`📖 SessionStorage get: ${key}`, value ? '✓' : '✗');
}
return value;
} catch (error) {
console.error(`SessionStorage getString error for key "${key}":`, error);
return null;
}
}
/**
* 存储对象(自动序列化为 JSON
*/
static setObject<T>(key: string, value: T): void {
try {
const jsonValue = JSON.stringify(value);
this.storage.set(key, jsonValue);
if (__DEV__) {
console.log(`💾 SessionStorage set object: ${key}`);
}
} catch (error) {
console.error(`SessionStorage setObject error for key "${key}":`, error);
throw error;
}
}
/**
* 获取对象(自动反序列化 JSON
*/
static getObject<T>(key: string): T | null {
try {
const jsonValue = this.storage.get(key);
if (jsonValue === undefined) {
return null;
}
const value = JSON.parse(jsonValue) as T;
if (__DEV__) {
console.log(`📖 SessionStorage get object: ${key}`);
}
return value;
} catch (error) {
console.error(`SessionStorage getObject error for key "${key}":`, error);
return null;
}
}
/**
* 删除指定键
*/
static remove(key: string): void {
try {
this.storage.delete(key);
if (__DEV__) {
console.log(`🗑️ SessionStorage remove: ${key}`);
}
} catch (error) {
console.error(`SessionStorage remove error for key "${key}":`, error);
throw error;
}
}
/**
* 清空所有存储
*/
static clear(): void {
try {
this.storage.clear();
if (__DEV__) {
console.log('🗑️ SessionStorage cleared all');
}
} catch (error) {
console.error('SessionStorage clear error:', error);
throw error;
}
}
/**
* 获取所有键名
*/
static getAllKeys(): string[] {
try {
const keys = Array.from(this.storage.keys());
if (__DEV__) {
console.log('🔑 SessionStorage all keys:', keys);
}
return keys;
} catch (error) {
console.error('SessionStorage getAllKeys error:', error);
return [];
}
}
/**
* 获取存储项数量
*/
static get length(): number {
return this.storage.size;
}
/**
* 检查键是否存在
*/
static has(key: string): boolean {
return this.storage.has(key);
}
/**
* 批量获取
*/
static multiGet(keys: string[]): [string, string | null][] {
try {
return keys.map((key) => [key, this.storage.get(key) ?? null]);
} catch (error) {
console.error('SessionStorage multiGet error:', error);
return [];
}
}
/**
* 批量设置
*/
static multiSet(keyValuePairs: [string, string][]): void {
try {
keyValuePairs.forEach(([key, value]) => {
this.storage.set(key, value);
});
if (__DEV__) {
console.log(`💾 SessionStorage multiSet: ${keyValuePairs.length} items`);
}
} catch (error) {
console.error('SessionStorage multiSet error:', error);
throw error;
}
}
/**
* 批量删除
*/
static multiRemove(keys: string[]): void {
try {
keys.forEach((key) => {
this.storage.delete(key);
});
if (__DEV__) {
console.log(`🗑️ SessionStorage multiRemove: ${keys.length} items`);
}
} catch (error) {
console.error('SessionStorage multiRemove error:', error);
throw error;
}
}
/**
* 获取所有数据(调试用)
*/
static getAll(): Record<string, string> {
const result: Record<string, string> = {};
this.storage.forEach((value, key) => {
result[key] = value;
});
return result;
}
}
export default SessionStorage;

184
utils/storage.ts Normal file
View File

@@ -0,0 +1,184 @@
/**
* AsyncStorage 封装工具
* 提供类型安全的本地存储操作
*/
import AsyncStorage from '@react-native-async-storage/async-storage';
/**
* 存储键名常量
*/
export enum STORAGE_KEYS {
AUTH_TOKEN = 'auth_token',
USER_INFO = 'user_info',
SETTINGS = 'settings',
THEME = 'theme',
LANGUAGE = 'language',
USER_PREFERENCES = 'user_preferences',
TENANT_STORE = 'tenant_storage',
USER_STORE = 'user_storage',
SETTINGS_STORE = 'settings_storage',
}
/**
* Storage 工具类
*/
class Storage {
/**
* 存储字符串
*/
static async setString(key: string, value: string): Promise<void> {
try {
await AsyncStorage.setItem(key, value);
if (__DEV__) {
console.log(`💾 Storage set: ${key}`);
}
} catch (error) {
console.error(`Storage setString error for key "${key}":`, error);
throw error;
}
}
/**
* 获取字符串
*/
static async getString(key: string): Promise<string | null> {
try {
const value = await AsyncStorage.getItem(key);
if (__DEV__) {
console.log(`📖 Storage get: ${key}`, value ? '✓' : '✗');
}
return value;
} catch (error) {
console.error(`Storage getString error for key "${key}":`, error);
return null;
}
}
/**
* 存储对象(自动序列化为 JSON
*/
static async setObject<T>(key: string, value: T): Promise<void> {
try {
const jsonValue = JSON.stringify(value);
await AsyncStorage.setItem(key, jsonValue);
if (__DEV__) {
console.log(`💾 Storage set object: ${key}`);
}
} catch (error) {
console.error(`Storage setObject error for key "${key}":`, error);
throw error;
}
}
/**
* 获取对象(自动反序列化 JSON
*/
static async getObject<T>(key: string): Promise<T | null> {
try {
const jsonValue = await AsyncStorage.getItem(key);
if (jsonValue === null) {
return null;
}
const value = JSON.parse(jsonValue) as T;
if (__DEV__) {
console.log(`📖 Storage get object: ${key}`);
}
return value;
} catch (error) {
console.error(`Storage getObject error for key "${key}":`, error);
return null;
}
}
/**
* 删除指定键
*/
static async remove(key: string): Promise<void> {
try {
await AsyncStorage.removeItem(key);
if (__DEV__) {
console.log(`🗑️ Storage remove: ${key}`);
}
} catch (error) {
console.error(`Storage remove error for key "${key}":`, error);
throw error;
}
}
/**
* 清空所有存储
*/
static async clear(): Promise<void> {
try {
await AsyncStorage.clear();
if (__DEV__) {
console.log('🗑️ Storage cleared all');
}
} catch (error) {
console.error('Storage clear error:', error);
throw error;
}
}
/**
* 获取所有键名
*/
static async getAllKeys(): Promise<string[]> {
try {
const keys = await AsyncStorage.getAllKeys();
if (__DEV__) {
console.log('🔑 Storage all keys:', keys);
}
return keys;
} catch (error) {
console.error('Storage getAllKeys error:', error);
return [];
}
}
/**
* 批量获取
*/
static async multiGet(keys: string[]): Promise<[string, string | null][]> {
try {
const values = await AsyncStorage.multiGet(keys);
return values;
} catch (error) {
console.error('Storage multiGet error:', error);
return [];
}
}
/**
* 批量设置
*/
static async multiSet(keyValuePairs: [string, string][]): Promise<void> {
try {
await AsyncStorage.multiSet(keyValuePairs);
if (__DEV__) {
console.log(`💾 Storage multiSet: ${keyValuePairs.length} items`);
}
} catch (error) {
console.error('Storage multiSet error:', error);
throw error;
}
}
/**
* 批量删除
*/
static async multiRemove(keys: string[]): Promise<void> {
try {
await AsyncStorage.multiRemove(keys);
if (__DEV__) {
console.log(`🗑️ Storage multiRemove: ${keys.length} items`);
}
} catch (error) {
console.error('Storage multiRemove error:', error);
throw error;
}
}
}
export default Storage;

242
utils/storageManager.ts Normal file
View File

@@ -0,0 +1,242 @@
/**
* 统一存储管理器
*
* 提供统一的接口来使用 localStorage (AsyncStorage) 或 sessionStorage
*
* 使用场景:
* - localStorage: 持久化数据,应用重启后仍然存在
* - sessionStorage: 临时数据,应用重启后丢失
*
* 示例:
* ```typescript
* // 使用 localStorage默认
* await StorageManager.set('user', userData);
*
* // 使用 sessionStorage
* await StorageManager.set('temp', tempData, { type: 'session' });
*
* // 获取数据(自动从正确的存储中读取)
* const user = await StorageManager.get('user');
* const temp = await StorageManager.get('temp', { type: 'session' });
* ```
*/
import Storage from './storage';
import SessionStorage from './sessionStorage';
/**
* 存储类型
*/
export type StorageType = 'local' | 'session';
/**
* 存储选项
*/
export interface StorageOptions {
/**
* 存储类型
* - 'local': 持久化存储AsyncStorage
* - 'session': 会话存储(内存)
*/
type?: StorageType;
}
/**
* 统一存储管理器
*/
class StorageManager {
/**
* 存储字符串
*/
static async setString(
key: string,
value: string,
options: StorageOptions = {}
): Promise<void> {
const { type = 'local' } = options;
if (type === 'session') {
SessionStorage.setString(key, value);
} else {
await Storage.setString(key, value);
}
}
/**
* 获取字符串
*/
static async getString(
key: string,
options: StorageOptions = {}
): Promise<string | null> {
const { type = 'local' } = options;
if (type === 'session') {
return SessionStorage.getString(key);
} else {
return await Storage.getString(key);
}
}
/**
* 存储对象
*/
static async setObject<T>(
key: string,
value: T,
options: StorageOptions = {}
): Promise<void> {
const { type = 'local' } = options;
if (type === 'session') {
SessionStorage.setObject(key, value);
} else {
await Storage.setObject(key, value);
}
}
/**
* 获取对象
*/
static async getObject<T>(
key: string,
options: StorageOptions = {}
): Promise<T | null> {
const { type = 'local' } = options;
if (type === 'session') {
return SessionStorage.getObject<T>(key);
} else {
return await Storage.getObject<T>(key);
}
}
/**
* 删除指定键
*/
static async remove(key: string, options: StorageOptions = {}): Promise<void> {
const { type = 'local' } = options;
if (type === 'session') {
SessionStorage.remove(key);
} else {
await Storage.remove(key);
}
}
/**
* 清空指定类型的所有存储
*/
static async clear(options: StorageOptions = {}): Promise<void> {
const { type = 'local' } = options;
if (type === 'session') {
SessionStorage.clear();
} else {
await Storage.clear();
}
}
/**
* 获取所有键名
*/
static async getAllKeys(options: StorageOptions = {}): Promise<string[]> {
const { type = 'local' } = options;
if (type === 'session') {
return SessionStorage.getAllKeys();
} else {
return await Storage.getAllKeys();
}
}
/**
* 检查键是否存在
*/
static async has(key: string, options: StorageOptions = {}): Promise<boolean> {
const { type = 'local' } = options;
if (type === 'session') {
return SessionStorage.has(key);
} else {
const value = await Storage.getString(key);
return value !== null;
}
}
/**
* 批量获取
*/
static async multiGet(
keys: string[],
options: StorageOptions = {}
): Promise<[string, string | null][]> {
const { type = 'local' } = options;
if (type === 'session') {
return SessionStorage.multiGet(keys);
} else {
return await Storage.multiGet(keys);
}
}
/**
* 批量设置
*/
static async multiSet(
keyValuePairs: [string, string][],
options: StorageOptions = {}
): Promise<void> {
const { type = 'local' } = options;
if (type === 'session') {
SessionStorage.multiSet(keyValuePairs);
} else {
await Storage.multiSet(keyValuePairs);
}
}
/**
* 批量删除
*/
static async multiRemove(
keys: string[],
options: StorageOptions = {}
): Promise<void> {
const { type = 'local' } = options;
if (type === 'session') {
SessionStorage.multiRemove(keys);
} else {
await Storage.multiRemove(keys);
}
}
/**
* 获取存储大小(仅 session storage
*/
static getSize(options: StorageOptions = {}): number {
const { type = 'local' } = options;
if (type === 'session') {
return SessionStorage.length;
} else {
// AsyncStorage 不支持直接获取大小
return -1;
}
}
/**
* 清空所有存储local + session
*/
static async clearAll(): Promise<void> {
await Storage.clear();
SessionStorage.clear();
if (__DEV__) {
console.log('🗑️ All storage cleared (local + session)');
}
}
}
export default StorageManager;