template/packages-user/data-common/src/replay/func.ts
2026-07-16 20:34:28 +08:00

184 lines
5.6 KiB
TypeScript

import { logger } from '@motajs/common';
import { IReplaySystem } from './types';
interface IReplaySafetyCollection {
/** 这个安全对象(由 `@shouldReplay()` 触发)的名称,即传入给 `@shouldReplay` 的参数 */
readonly name: string;
/** 这个安全对象所包含的所有字对象 */
readonly messages: IReplaySafetyCollection[];
}
interface IReplaySafetyDetailQueue {
/** 详细信息代码 */
readonly code: number;
/** 此详细信息对应的安全对象 */
readonly collection: IReplaySafetyCollection;
}
type ReplayDecorator = <
Return,
This,
Func extends (this: This, ...args: any[]) => Return
>(
method: Func,
context: ClassMethodDecoratorContext
) => (this: This, ...args: any[]) => Return;
/** 本次收集使用的录像系统 */
let replaySystem: IReplaySystem | null = null;
/** 当前是否正在进行录像收集 */
let collecting: boolean = false;
/** 收集之前录像系统的录像长度 */
let beforeLength: number = 0;
/** 收集时是否有 `@ignoreReplay` 调用 */
let shouldIgnore: boolean = false;
/** 录像安全收集根对象 */
const collection: IReplaySafetyCollection = {
name: 'root',
messages: []
};
/** 当前录像安全收集对象 */
let currentCollection: IReplaySafetyCollection | null = null;
/** 详细信息 code */
let detailCode = 0;
/** 录像收集详细信息队列,保留 50 个以确保可以在控制台重复输出 */
const detailQueue: IReplaySafetyDetailQueue[] = [];
/**
* 开始录像安全性检查收集,一般情况下不需要手动调用此接口
* @param system 录像系统对象
*/
export function beginReplaySafetyCollection(system: IReplaySystem): void {
if (collecting || replaySystem) {
logger.warn(159);
return;
}
replaySystem = system;
collecting = true;
beforeLength = system.route.length;
collection.messages.length = 0;
currentCollection = collection;
}
/**
* 结束录像安全性检查收集,并输出收集结果
*/
export function endReplaySafetyCollection(): void {
if (!collecting || !replaySystem) {
logger.warn(160);
return;
}
if (shouldIgnore) return;
if (replaySystem.route.length > beforeLength) return;
if (collection.messages.length === 0) return;
// 需要把收集内容输出,这里只输出一层,完整输出需要在控制台手动调用
const code = detailCode++;
const col: IReplaySafetyCollection = {
name: `detail#${code}`,
messages: collection.messages.slice()
};
const command = `Mota.require('@user/data-common').logReplaySafetyDetail(${code});`;
const simplified = [...collection.messages]
.sort((a, b) => b.messages.length - a.messages.length)
.map(v => v.name)
.join('\n');
logger.warn(161, command, simplified);
while (detailQueue.length > 50) {
detailQueue.shift();
}
detailQueue.push({
code,
collection: col
});
}
/**
* 输出指定的录像安全对象的所有详细信息
* @param collection 录像安全对象
*/
function logCollection(collection: IReplaySafetyCollection): void {
console.group(collection.name);
const sorted = collection.messages.toSorted(
(a, b) => b.messages.length - a.messages.length
);
for (const msg of sorted) {
if (msg.messages.length === 0) {
console.log(msg.name);
} else {
logCollection(msg);
}
}
console.groupEnd();
}
/**
* 输出某次录像安全检查的详细信息
* @param code 录像安全检查对象的代码
*/
export function logReplaySafetyDetail(code: number): void {
const root = detailQueue.find(v => v.code === code);
if (!root) {
logger.warn(162, code.toString());
return;
}
logCollection(root.collection);
}
/**
* 将一个类的方法标记为需要计入录像
* @param message 如果录像安全检查出现问题,那么会使用此作为信息输出
*/
export function shouldReplay(message: string): ReplayDecorator {
return function <
Return,
This,
Args extends any[],
Func extends (this: This, ...args: Args) => Return
>(
this: This,
method: Func,
context: ClassMethodDecoratorContext
): (this: This, ...args: Args) => Return {
return function (this: This, ...args: Args): Return {
const before = currentCollection;
if (!before) return method.apply(this, args);
const exist = before.messages.find(v => v.name === message);
const newCollection: IReplaySafetyCollection = exist ?? {
name: `${String(context.name)}: ${message}`,
messages: []
};
if (!exist) {
before.messages.push(newCollection);
}
currentCollection = newCollection;
const result = method.apply(this, args);
currentCollection = before;
return result;
};
};
}
/**
* 将一个类的方法标记为忽略录像安全检查,一般用于异步内容等不是立刻会触发状态修改的方法,
* 不要使用此方法来规避控制台的录像错误警告,否则很可能导致录像出错。
*/
export function ignoreReplay(): ReplayDecorator {
return function <
Return,
This,
Args extends any[],
Func extends (this: This, ...args: Args) => Return
>(this: This, method: Func): (this: This, ...args: Args) => Return {
return function (this: This, ...args: Args): Return {
shouldIgnore = true;
return method.apply(this, args);
};
};
}