|
@@ -0,0 +1,180 @@
|
|
|
+type FieldType = 'string' | 'number' | 'boolean' | 'object' | 'array' | 'any';
|
|
|
+
|
|
|
+interface FieldSchema {
|
|
|
+ name: string;
|
|
|
+ type: FieldType;
|
|
|
+ description?: string;
|
|
|
+ required?: boolean;
|
|
|
+}
|
|
|
+
|
|
|
+interface FlowTaskOptions {
|
|
|
+ title: string;
|
|
|
+ input?: FieldSchema[];
|
|
|
+ output?: FieldSchema[];
|
|
|
+ initialData?: Record<string, any>;
|
|
|
+}
|
|
|
+
|
|
|
+export class FlowTask {
|
|
|
+ // 核心属性
|
|
|
+ readonly title: string;
|
|
|
+ protected data: Record<string, any> = {};
|
|
|
+ private _status: 'idle' | 'running' | 'success' | 'failed' = 'idle';
|
|
|
+ private _progress: number = 0;
|
|
|
+
|
|
|
+ // 校验规则
|
|
|
+ private readonly inputSchema: FieldSchema[];
|
|
|
+ private readonly outputSchema: FieldSchema[];
|
|
|
+
|
|
|
+ // 添加执行时间记录
|
|
|
+ private _startTime?: Date;
|
|
|
+ private _endTime?: Date;
|
|
|
+ // 添加执行时间信息
|
|
|
+ get executionTime(): number {
|
|
|
+ if (!this._startTime) return 0;
|
|
|
+ const end = this._endTime || new Date();
|
|
|
+ return (end.getTime() - this._startTime.getTime()) / 1000;
|
|
|
+ }
|
|
|
+
|
|
|
+ constructor(options: FlowTaskOptions) {
|
|
|
+ this.title = options.title;
|
|
|
+ this.data = options.initialData || {};
|
|
|
+ this.inputSchema = options.input || [];
|
|
|
+ this.outputSchema = options.output || [];
|
|
|
+ }
|
|
|
+
|
|
|
+ /************************************
|
|
|
+ * 核心执行流程 *
|
|
|
+ ************************************/
|
|
|
+
|
|
|
+ async execute(): Promise<void> {
|
|
|
+
|
|
|
+ try {
|
|
|
+ if (this._status !== 'idle') return;
|
|
|
+ this._startTime = new Date();
|
|
|
+
|
|
|
+ this._status = 'running';
|
|
|
+ this.validateInput(); // 输入校验
|
|
|
+
|
|
|
+ this.beforeExecute();
|
|
|
+ await this.handle(); // 执行用户自定义逻辑
|
|
|
+ this.afterExecute();
|
|
|
+
|
|
|
+ this.validateOutput(); // 输出校验
|
|
|
+ this._status = 'success';
|
|
|
+ this.onSuccess();
|
|
|
+ } catch (error) {
|
|
|
+ this._status = 'failed';
|
|
|
+ this.onFailure(error as Error);
|
|
|
+ throw error; // 重新抛出错误确保执行器能捕获
|
|
|
+ }
|
|
|
+ this._endTime = new Date();
|
|
|
+ }
|
|
|
+
|
|
|
+ /************************************
|
|
|
+ * 用户可覆盖方法 *
|
|
|
+ ************************************/
|
|
|
+
|
|
|
+ // 主处理函数(用户需要覆盖的核心方法)
|
|
|
+ protected async handle(): Promise<void> {
|
|
|
+ // 默认空实现,抛出错误提示需要实现
|
|
|
+ throw new Error('必须实现 handle() 方法');
|
|
|
+ }
|
|
|
+
|
|
|
+ // 生命周期钩子(可选覆盖)
|
|
|
+ protected beforeExecute(): void {}
|
|
|
+ protected afterExecute(): void {}
|
|
|
+ protected onProgress(progress: number): void {}
|
|
|
+ protected onSuccess(): void {}
|
|
|
+ protected onFailure(error: Error): void {}
|
|
|
+
|
|
|
+ /************************************
|
|
|
+ * 数据校验系统 *
|
|
|
+ ************************************/
|
|
|
+
|
|
|
+ private validateInput(): void {
|
|
|
+ const errors: string[] = [];
|
|
|
+
|
|
|
+ this.inputSchema.forEach(field => {
|
|
|
+ const value = this.data[field.name];
|
|
|
+
|
|
|
+ // 检查必填字段
|
|
|
+ if (field.required && value === undefined) {
|
|
|
+ errors.push(`缺少必要字段:${field.name}`);
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 类型校验
|
|
|
+ if (value !== undefined && !this.checkType(value, field.type)) {
|
|
|
+ errors.push(`${field.name} 类型错误,期望 ${field.type},实际 ${this.getType(value)}`);
|
|
|
+ }
|
|
|
+ });
|
|
|
+
|
|
|
+ if (errors.length > 0) {
|
|
|
+ throw new Error(`输入校验失败:\n${errors.join('\n')}`);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ private validateOutput(): void {
|
|
|
+ const missingFields = this.outputSchema
|
|
|
+ .filter(f => f.required)
|
|
|
+ .filter(f => !(f.name in this.data))
|
|
|
+ .map(f => f.name);
|
|
|
+
|
|
|
+ if (missingFields.length > 0) {
|
|
|
+ throw new Error(`输出校验失败,缺少字段:${missingFields.join(', ')}`);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /************************************
|
|
|
+ * 工具方法 *
|
|
|
+ ************************************/
|
|
|
+
|
|
|
+ // 类型检查
|
|
|
+ private checkType(value: any, expected: FieldType): boolean {
|
|
|
+ const actualType = this.getType(value);
|
|
|
+ return expected === 'any' || actualType === expected;
|
|
|
+ }
|
|
|
+
|
|
|
+ private getType(value: any): FieldType {
|
|
|
+ if (Array.isArray(value)) return 'array';
|
|
|
+ if (value === null) return 'object';
|
|
|
+ return typeof value as FieldType;
|
|
|
+ }
|
|
|
+
|
|
|
+ /************************************
|
|
|
+ * 公共接口 *
|
|
|
+ ************************************/
|
|
|
+
|
|
|
+ // 更新任务数据
|
|
|
+ updateData(key: string, value: any): this {
|
|
|
+ this.data[key] = value;
|
|
|
+ return this;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 获取任务数据
|
|
|
+ getData<T = any>(key: string): T {
|
|
|
+ return this.data[key];
|
|
|
+ }
|
|
|
+
|
|
|
+ // 进度更新
|
|
|
+ setProgress(value: number): void {
|
|
|
+ if (value < 0 || value > 1) return;
|
|
|
+ this._progress = value;
|
|
|
+ this.onProgress(value);
|
|
|
+ }
|
|
|
+
|
|
|
+ // 状态访问器
|
|
|
+ get progress(): number {
|
|
|
+ return this._progress;
|
|
|
+ }
|
|
|
+
|
|
|
+ get status() {
|
|
|
+ return this._status;
|
|
|
+ }
|
|
|
+
|
|
|
+ get output() {
|
|
|
+ return Object.fromEntries(
|
|
|
+ this.outputSchema.map(f => [f.name, this.data[f.name]])
|
|
|
+ );
|
|
|
+ }
|
|
|
+}
|