优化
This commit is contained in:
106
client/shared/api/buildStorage.ts
Normal file
106
client/shared/api/buildStorage.ts
Normal file
@@ -0,0 +1,106 @@
|
||||
import 'regenerator-runtime/runtime'; // react-native-storage 需要, 确保其存在
|
||||
import Storage, { NotFoundError } from 'react-native-storage';
|
||||
import _isNil from 'lodash/isNil';
|
||||
import _isUndefined from 'lodash/isUndefined';
|
||||
|
||||
/**
|
||||
* 构建一个存储对象
|
||||
*/
|
||||
export function buildStorage(backend: any) {
|
||||
const storage = new Storage({
|
||||
// 最大容量,默认值1000条数据循环存储
|
||||
size: 1000,
|
||||
|
||||
// 存储引擎:对于RN使用AsyncStorage,对于web使用window.localStorage
|
||||
// 如果不指定则数据只会保存在内存中,重启后即丢失
|
||||
// storageBackend:
|
||||
// config.platform === 'app'
|
||||
// ? require('react-native').AsyncStorage
|
||||
// : window.localStorage,
|
||||
storageBackend: backend,
|
||||
|
||||
// 数据过期时间,默认一整天(1000 * 3600 * 24 毫秒),设为null则永不过期
|
||||
defaultExpires: 1000 * 3600 * 24,
|
||||
|
||||
// 读写时在内存中缓存数据。默认启用。
|
||||
enableCache: true,
|
||||
|
||||
// 如果storage中没有相应数据,或数据已过期,
|
||||
// 则会调用相应的sync方法,无缝返回最新数据。
|
||||
// sync方法的具体说明会在后文提到
|
||||
// 你可以在构造函数这里就写好sync的方法
|
||||
// 或是在任何时候,直接对storage.sync进行赋值修改
|
||||
// 或是写到另一个文件里,这里require引入
|
||||
// sync: require('你可以另外写一个文件专门处理sync')
|
||||
});
|
||||
|
||||
const rnStorage = {
|
||||
set: async (key: string, data: any) => {
|
||||
try {
|
||||
if (!!key && typeof key === 'string' && !_isUndefined(data)) {
|
||||
await storage.save({ key, data });
|
||||
}
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
}
|
||||
|
||||
return data;
|
||||
},
|
||||
/**
|
||||
* 自定义过期时间的存储
|
||||
* set默认为1天,该方法自定义过期时间
|
||||
*/
|
||||
setWithExpires: async (key: string, data: any, expires: number) => {
|
||||
try {
|
||||
if (!!key && typeof key === 'string' && !_isUndefined(data)) {
|
||||
await storage.save({ key, data, expires });
|
||||
}
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
}
|
||||
|
||||
return data;
|
||||
},
|
||||
get: async (key: string, defaultVal?: any) => {
|
||||
let res: any;
|
||||
try {
|
||||
res = await storage.load({
|
||||
key,
|
||||
autoSync: true,
|
||||
syncInBackground: false,
|
||||
});
|
||||
} catch (e: any) {
|
||||
if (!(e instanceof NotFoundError)) {
|
||||
// 过滤NotFoundError
|
||||
console.log(`get key ${key} error:`, String(e));
|
||||
}
|
||||
|
||||
res = _isNil(defaultVal) ? null : defaultVal;
|
||||
}
|
||||
return res;
|
||||
},
|
||||
remove: async (key: string) => {
|
||||
await storage.remove({ key });
|
||||
},
|
||||
/**
|
||||
* 持久化存储, 永不过期
|
||||
*/
|
||||
save: async (key: string, data: any) => {
|
||||
try {
|
||||
if (!!key && typeof key === 'string' && !_isUndefined(data)) {
|
||||
await storage.save({
|
||||
key,
|
||||
data,
|
||||
expires: null,
|
||||
});
|
||||
}
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
}
|
||||
|
||||
return data!;
|
||||
},
|
||||
};
|
||||
|
||||
return rnStorage;
|
||||
}
|
||||
90
client/shared/api/request.ts
Normal file
90
client/shared/api/request.ts
Normal file
@@ -0,0 +1,90 @@
|
||||
import axios, { AxiosRequestConfig } from 'axios';
|
||||
import _get from 'lodash/get';
|
||||
import _isFunction from 'lodash/isFunction';
|
||||
import { t } from '../i18n';
|
||||
import { getErrorHook, tokenGetter } from '../manager/request';
|
||||
import { getServiceUrl, onServiceUrlChange } from '../manager/service';
|
||||
|
||||
export type CommonRequestResult<T> =
|
||||
| ({
|
||||
result: false;
|
||||
msg: string;
|
||||
} & Partial<T>) // 并上一个T是为了方便取值, 但需要判定
|
||||
| ({
|
||||
result: true;
|
||||
} & T);
|
||||
|
||||
class RequestError extends Error {}
|
||||
|
||||
export type RequestConfig = AxiosRequestConfig;
|
||||
|
||||
/**
|
||||
* 创建请求实例
|
||||
*/
|
||||
function createRequest() {
|
||||
const ins = axios.create({
|
||||
baseURL: getServiceUrl(),
|
||||
});
|
||||
onServiceUrlChange((getUrl) => {
|
||||
// 重置请求地址
|
||||
ins.defaults.baseURL = getUrl();
|
||||
});
|
||||
|
||||
ins.interceptors.request.use(async (val) => {
|
||||
if (
|
||||
['post', 'get'].includes(String(val.method).toLowerCase()) &&
|
||||
!val.headers['X-Token']
|
||||
) {
|
||||
// 任何请求都尝试增加token
|
||||
val.headers['X-Token'] = await tokenGetter();
|
||||
}
|
||||
|
||||
return val;
|
||||
});
|
||||
|
||||
ins.interceptors.response.use(
|
||||
(val) => {
|
||||
/**
|
||||
* 预处理返回的数据
|
||||
*/
|
||||
val.data = _get(val.data, 'data', val.data);
|
||||
|
||||
return val;
|
||||
},
|
||||
(err) => {
|
||||
// 尝试获取错误信息
|
||||
const responseData = _get(err, 'response.data') ?? {};
|
||||
let errorMsg: string = responseData.message;
|
||||
const code: number = responseData.code;
|
||||
|
||||
if (responseData.type === 'VALIDATION_ERROR') {
|
||||
// 校验失败
|
||||
errorMsg = t('请求参数校验失败');
|
||||
|
||||
if (Array.isArray(responseData.data)) {
|
||||
console.error(JSON.stringify(responseData.data));
|
||||
|
||||
try {
|
||||
errorMsg += `: ${responseData.data
|
||||
.map((item: any) => item.field)
|
||||
.join(', ')}`;
|
||||
} catch (e) {}
|
||||
}
|
||||
}
|
||||
|
||||
// comment it logic because if not throw error, react query will cache `{ data: { result: false, msg: errorMsg, code } }` and has some problem
|
||||
if (_isFunction(getErrorHook)) {
|
||||
const isContinue = getErrorHook(err);
|
||||
// if (isContinue === false) {
|
||||
// return { data: { result: false, msg: errorMsg, code } };
|
||||
// }
|
||||
}
|
||||
|
||||
throw new RequestError(errorMsg ?? err.message);
|
||||
}
|
||||
);
|
||||
|
||||
return ins;
|
||||
}
|
||||
|
||||
export const request = createRequest();
|
||||
236
client/shared/api/socket.ts
Normal file
236
client/shared/api/socket.ts
Normal file
@@ -0,0 +1,236 @@
|
||||
import { io, Socket } from 'socket.io-client';
|
||||
import _isNil from 'lodash/isNil';
|
||||
import { getServiceUrl } from '../manager/service';
|
||||
import { isDevelopment } from '../utils/environment';
|
||||
import { showErrorToasts, showGlobalLoading, showToasts } from '../manager/ui';
|
||||
import { t } from '../i18n';
|
||||
import { sharedEvent } from '../event';
|
||||
import msgpackParser from 'socket.io-msgpack-parser';
|
||||
import { getGlobalConfig } from '../model/config';
|
||||
|
||||
class SocketEventError extends Error {
|
||||
name = 'SocketEventError';
|
||||
}
|
||||
|
||||
type SocketEventRespones<T = unknown> =
|
||||
| {
|
||||
result: true;
|
||||
data: T;
|
||||
}
|
||||
| {
|
||||
result: false;
|
||||
message: string;
|
||||
};
|
||||
|
||||
/**
|
||||
* 封装后的 Socket
|
||||
*/
|
||||
export class AppSocket {
|
||||
private listener: [string, (data: unknown) => void][] = [];
|
||||
|
||||
constructor(private socket: Socket) {
|
||||
socket.onAny((eventName: string, data: unknown) => {
|
||||
const matched = this.listener.filter(([ev]) => ev === eventName); // 匹配到的监听器列表
|
||||
if (matched.length === 0) {
|
||||
// 没有匹配到任何处理函数
|
||||
console.warn(`[Socket IO] Unhandler event: ${eventName}`, data);
|
||||
return;
|
||||
}
|
||||
|
||||
matched.forEach(([, cb]) => {
|
||||
cb(data);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
get connected(): boolean {
|
||||
return this.socket.connected;
|
||||
}
|
||||
|
||||
async request<T = unknown>(
|
||||
eventName: string,
|
||||
eventData: unknown = {}
|
||||
): Promise<T> {
|
||||
return new Promise((resolve, reject) => {
|
||||
this.socket.emit(eventName, eventData, (resp: SocketEventRespones<T>) => {
|
||||
if (resp.result === true) {
|
||||
resolve(resp.data);
|
||||
} else if (resp.result === false) {
|
||||
reject(
|
||||
new SocketEventError(
|
||||
`[${eventName}]: ${resp.message}: \ndata: ${JSON.stringify(
|
||||
eventData
|
||||
)}`
|
||||
)
|
||||
);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 监听远程通知
|
||||
*/
|
||||
listen<T>(eventName: string, callback: (data: T) => void) {
|
||||
this.listener.push([`notify:${eventName}`, callback as any]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 移除监听函数
|
||||
*/
|
||||
removeListener(eventName: string, callback: (data: any) => void) {
|
||||
const index = this.listener.findIndex(
|
||||
(item) => item[0] === `notify:${eventName}` && item[1] === callback
|
||||
);
|
||||
if (index >= 0) {
|
||||
this.listener.splice(index, 1);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 模拟重连
|
||||
* NOTICE: 仅用于开发环境
|
||||
*/
|
||||
mockReconnect() {
|
||||
this.socket.disconnect();
|
||||
showToasts('reconnect after 5s');
|
||||
setTimeout(() => {
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
this.socket.io.skipReconnect = false;
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
this.socket.io.reconnect();
|
||||
}, 5 * 1000);
|
||||
}
|
||||
|
||||
/**
|
||||
* 断线重连后触发
|
||||
*/
|
||||
onReconnect(cb: () => void) {
|
||||
this.socket.io.on('reconnect', cb);
|
||||
}
|
||||
|
||||
/**
|
||||
* 断开连接
|
||||
*/
|
||||
disconnect() {
|
||||
this.socket.disconnect();
|
||||
}
|
||||
|
||||
/**
|
||||
* 初始Socket状态管理提示
|
||||
*/
|
||||
private closeFn: unknown = null; // 全局loading关闭函数
|
||||
setupSocketStatusTip() {
|
||||
const socket = this.socket;
|
||||
|
||||
const showConnecting = () => {
|
||||
if (this.closeFn) {
|
||||
return;
|
||||
}
|
||||
this.closeFn = showGlobalLoading(t('正在重新链接'));
|
||||
};
|
||||
|
||||
const closeConnecting = () => {
|
||||
if (this.closeFn && typeof this.closeFn === 'function') {
|
||||
this.closeFn();
|
||||
this.closeFn = null;
|
||||
}
|
||||
};
|
||||
|
||||
// 网络状态管理
|
||||
socket.on('connect', () => {
|
||||
console.log('连接成功');
|
||||
closeConnecting();
|
||||
|
||||
sharedEvent.emit('updateNetworkStatus', 'connected');
|
||||
});
|
||||
socket.on('connecting', (data) => {
|
||||
console.log('正在连接');
|
||||
|
||||
showConnecting();
|
||||
|
||||
sharedEvent.emit('updateNetworkStatus', 'reconnecting');
|
||||
});
|
||||
socket.on('disconnect', (data) => {
|
||||
console.log('与服务器的链接已断开');
|
||||
showErrorToasts(t('与服务器的链接已断开'));
|
||||
closeConnecting();
|
||||
sharedEvent.emit('updateNetworkStatus', 'disconnected');
|
||||
});
|
||||
socket.on('connect_error', (data) => {
|
||||
console.log('连接失败');
|
||||
showErrorToasts(t('连接失败'));
|
||||
closeConnecting();
|
||||
sharedEvent.emit('updateNetworkStatus', 'disconnected');
|
||||
});
|
||||
|
||||
socket.io.on('reconnect', (data) => {
|
||||
console.log('重连成功');
|
||||
|
||||
closeConnecting();
|
||||
sharedEvent.emit('updateNetworkStatus', 'connected');
|
||||
});
|
||||
socket.io.on('reconnect_attempt', (data) => {
|
||||
console.log('重连中...');
|
||||
showConnecting();
|
||||
sharedEvent.emit('updateNetworkStatus', 'reconnecting');
|
||||
});
|
||||
socket.io.on('reconnect_error', (error) => {
|
||||
console.error('重连尝试失败...', error);
|
||||
showConnecting();
|
||||
sharedEvent.emit('updateNetworkStatus', 'reconnecting');
|
||||
});
|
||||
socket.io.on('reconnect_failed', () => {
|
||||
console.error('重连失败...');
|
||||
showConnecting();
|
||||
sharedEvent.emit('updateNetworkStatus', 'disconnected');
|
||||
});
|
||||
socket.io.on('error', (error) => {
|
||||
console.error('网络出现异常', error);
|
||||
showErrorToasts(t('网络出现异常'));
|
||||
closeConnecting();
|
||||
sharedEvent.emit('updateNetworkStatus', 'disconnected');
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
let _socket: Socket;
|
||||
/**
|
||||
* 创建Socket连接
|
||||
* 如果已经有Socket连接则关闭上一个
|
||||
* @param token Token
|
||||
*/
|
||||
export function createSocket(token: string): Promise<AppSocket> {
|
||||
if (!_isNil(_socket)) {
|
||||
_socket.close();
|
||||
}
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
const disableMsgpack = getGlobalConfig().disableMsgpack;
|
||||
_socket = io(getServiceUrl(), {
|
||||
transports: ['websocket'],
|
||||
auth: {
|
||||
token,
|
||||
},
|
||||
forceNew: true,
|
||||
parser: disableMsgpack ? undefined : msgpackParser,
|
||||
});
|
||||
_socket.once('connect', () => {
|
||||
// 连接成功
|
||||
const appSocket = new AppSocket(_socket);
|
||||
appSocket.setupSocketStatusTip();
|
||||
resolve(appSocket);
|
||||
});
|
||||
_socket.once('error', () => {
|
||||
reject();
|
||||
});
|
||||
|
||||
if (isDevelopment) {
|
||||
_socket.onAny((...args) => {
|
||||
console.debug('Receive Notify:', args);
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user