BossAgents SCSAI 回写优化 — 实现方案文档

# BossAgents SCSAI 回写优化 — 实现方案文档

版本:v1.0 | 日期:2026-06-25 | 基于 spec-SCSAI回写优化.md 需求规格 v2.0


1. 实现模型

1.1 上下文视图

1.1.1 系统上下文

SCSAI 回写优化在 BossAgents 整体架构中的定位——修复前端创建闭环、后端降级路径、关系格式转换、后端重试回滚、create_post 可靠性、前后端同步六大关键路径缺陷:

``

┌─────────────────────────────────────────────────────────────────────────┐

│ 外部系统 │

│ ┌───────────────┐ ┌───────────────┐ ┌────────────────────────────┐ │

│ │ 业务用户 │ │ SCSAI agent│ │ SmartLLMRouter │ │

│ │ (Web/小程序) │ │ (AML操作) │ │ (5级降级链路) │ │

│ └───────┬───────┘ └───────┬───────┘ └─────────────┬──────────────┘ │

└──────────┼──────────────────┼─────────────────────────┼─────────────────┘

│ │ │

▼ ▼ ▼

┌─────────────────────────────────────────────────────────────────────────┐

│ BossAgents 核心层 │

│ │

│ ┌─────────────────────────────────────────────────────────────────┐ │

│ │ 前端创建路径(权威实现) │ │

│ │ useCapabilityCreate.js → AmlBuilder.js → /SCSAI-api/ApplyItem │ │

│ │ ★ 5.1: generate输出对接前端创建流程 │ │

│ │ ★ 5.5: create_post规则执行结果可感知 │ │

│ └─────────────────────────────────────────────────────────────────┘ │

│ │

│ ┌─────────────────────────────────────────────────────────────────┐ │

│ │ 后端创建路径(镜像实现) │ │

│ │ CapabilityRuntime.create() → rule-engine.js createItem() │ │

│ │ → AMLGenerator / aml-builder.js → SCSAIClient.sendAML() │ │

│ │ ★ 5.2: AMLGenerator item_properties补齐 │ │

│ │ ★ 5.3: 关系发现格式转换 │ │

│ │ ★ 5.4: 后端创建路径重试与回滚 │ │

│ └─────────────────────────────────────────────────────────────────┘ │

│ │

│ ┌─────────────────────────────────────────────────────────────────┐ │

│ │ 同步维护层 │ │

│ │ ★ 5.6: 前后端创建逻辑同步维护 │ │

│ │ aml-builder.js @mirror-of AmlBuilder.js │ │

│ │ unified-create.js @mirror-of useCapabilityCreate.js │ │

│ └─────────────────────────────────────────────────────────────────┘ │

└─────────────────────────────────────────────────────────────────────────┘

`

1.1.2 需求与模块映射

需求ID优先级需求名称主要修改模块修改类型
5.1P0generate能力输出与前端创建流程对接capability-runtime.js, rule-engine.js, useCapabilityCreate.js修改
5.2P0AMLGenerator降级路径item_properties补齐aml-generator.js修改
5.3P0关系发现格式转换capability-runtime.js, relationship-resolver.js修改+新增
5.4P1后端创建路径重试与回滚SCSAI-client.js, rule-engine.js, unified-create.js修改
5.5P1前端create_post规则执行可靠性rule-engine.js, server.js, useCapabilityCreate.js修改+新增
5.6P2前后端创建逻辑同步维护aml-builder.js, unified-create.js, 测试文件修改+新增

1.2 服务/组件总体架构

1.2.1 优化后的回写数据流

`

┌─────────────────────────┐

│ 用户触发 generate 能力 │

└───────────┬─────────────┘

┌───────────▼─────────────┐

│ CapabilityRuntime.generate() │

│ → executeGenerate() │

│ 返回 generated_content │

│ ★ 标准化输出格式: │

│ { item_type, properties, │

│ item_properties, │

│ relationships } │

└───────────┬─────────────┘

┌────────────────────┼────────────────────┐

│ │ │

┌───────────▼──────────┐ ┌─────▼──────────┐ ┌──────▼──────────┐

│ 前端权威创建路径 │ │ 后端镜像创建路径 │ │ 后端降级路径 │

│ useCapabilityCreate │ │ rule-engine.js │ │ AMLGenerator │

│ → AmlBuilder.js │ │ → aml-builder.js│ │ ★ item_props │

│ → /SCSAI-api/ApplyItem │ │ → SCSAIClient │ │ 补齐 │

│ ★ 自动消费 generate │ │ ★ 重试与回滚 │ │ │

│ 输出 │ │ ★ 关系格式转换 │ │ │

└───────────┬──────────┘ └─────┬──────────┘ └──────┬──────────┘

│ │ │

│ ┌────────▼────────┐ │

│ │ SCSAIClient │ │

│ │ ★ SOAP Fault解析 │ │

│ │ ★ 超时结构化错误 │ │

│ │ ★ 重试策略 │ │

│ └────────┬────────┘ │

│ │ │

┌───────────▼────────────────────▼─────────────────────▼──────────┐

│ SCSAI agent │

└───────────────────────────────┬──────────────────────────────────┘

┌───────────────────▼───────────────────────┐

│ create_post 规则执行 │

│ ★ 前端可感知执行结果(非 fire-and-forget) │

│ ★ 重试机制(最多2次,指数退避) │

│ ★ 超时处理(30秒) │

│ ★ 结果持久化(前端断线可补发) │

└───────────────────────────────────────────┘

`

1.2.2 关系发现格式转换流

`

RelationshipResolver.discoverRelations()

▼ 返回 source_index/target_index 格式

[{ parent_index: 0, child_index: 1, relation_type: 'Part BOM', properties: {...} }]

▼ ★ 格式转换器(新增 convertDiscoveryRelations)

[{ relationship_type: 'Part BOM', related_item_type: 'Part',

related_items: [{ properties: {...} }] }]

▼ 传入 AmlBuilder / AMLGenerator

buildAML({ relationships: 转换后数据 })

`

1.3 实现设计文档

1.3.1 P0-5.1: generate能力输出与前端创建流程对接

#### 需求概述

executeGenerate() 返回的 generated_content 需要输出为前端 useCapabilityCreate 可直接消费的结构化 JSON,且前端能自动启动创建流程。

#### 当前问题分析

  1. executeGenerate() 输出格式不标准:当前 generated_content 来自规则 action_script 的自由格式输出,不保证包含 item_type/properties/item_properties/relationships 四层结构
  2. 前端无法自动消费:useCapabilityCreate.js 期望接收 { item_type, properties, item_properties, relationships } 格式,但 generate 返回的是自由文本或非标准 JSON
  3. LLM 降级路径输出不标准:LLM Router 返回的内容可能是 {"content": "..."} 而非创建所需的结构化数据

#### 修改文件

| 文件路径 | 修改类型 | 说明 |

|---------|---------|------|

| server/core/rule-engine.js | 修改 | executeGenerate() 返回标准化 generated_content |

| server/core/capability-runtime.js | 修改 | generate() 方法确保输出格式标准化 |

| src/composables/useCapabilityCreate.js | 修改 | 新增 createFromGenerate() 方法消费 generate 输出 |

#### 核心类/函数设计

1. rule-engine.js — executeGenerate() 输出标准化

executeGenerate() 方法(第1683行)中,对 generated_content 进行格式标准化处理:

`javascript

// executeGenerate() 返回前,标准化 generated_content

_standardizeGeneratedContent(rawContent, item_type) {

if (!rawContent) return null;

// 如果已是标准格式(含 item_type + properties),直接返回

if (typeof rawContent === 'object' && rawContent.item_type && rawContent.properties) {

return rawContent;

}

// 如果是字符串,尝试解析 JSON

let parsed = rawContent;

if (typeof rawContent === 'string') {

try {

const jsonMatch = rawContent.match(/\{[\s\S]*\}/);

if (jsonMatch) parsed = JSON.parse(jsonMatch[0]);

} catch (e) {

// 无法解析,返回原始文本包装

return { item_type, properties: { description: rawContent }, _unparseable: true };

}

}

// 如果解析后的对象有 properties 字段,提取标准格式

if (parsed && typeof parsed === 'object') {

// 情况1:已经是标准格式

if (parsed.item_type && parsed.properties) return parsed;

// 情况2:LLM 返回 { properties: {...}, relationships: [...] } 但无 item_type

if (parsed.properties && typeof parsed.properties === 'object') {

return {

item_type: item_type || parsed.item_type || 'Part',

properties: parsed.properties,

item_properties: parsed.item_properties || {},

relationships: parsed.relationships || []

};

}

// 情况3:LLM 返回扁平属性对象(最常见)

// 将非标准字段(relationships, item_properties)提取出来

const { relationships, item_properties, ...properties } = parsed;

return {

item_type: item_type || parsed.item_type || 'Part',

properties,

item_properties: item_properties || {},

relationships: relationships || []

};

}

// 无法标准化

return { item_type, properties: { description: String(rawContent) }, _unparseable: true };

}

`

2. capability-runtime.js — generate() 方法增强

generate() 方法(第774行)中,确保返回的 generated_content 是标准格式:

`javascript

async generate(context, options = {}) {

const { item_type, data, template_id, autoCreate } = context;

const engine = await this._initRuleEngine();

const startTime = Date.now();

let result = null;

if (engine) {

const engineResult = await engine.executeGenerate(

{ item_type, data, template_id },

this._buildEngineOptions()

);

if (engineResult && engineResult.executed) {

this._recordRuleEngineHit();

// ★ 标准化 generated_content

engineResult.generated_content = this._standardizeGeneratedContent(

engineResult.generated_content, item_type

);

result = this._formatResult('generate', engineResult, {

source: engineResult.source || 'rule_engine',

durationMs: Date.now() - startTime

});

}

}

if (!result) {

// LLM 降级:使用创建专用 prompt

const systemPrompt = this._buildCreatePrompt(item_type, null, data);

const llmResult = await this._callLLMViaRouter({

prompt: JSON.stringify(data),

systemPrompt,

taskType: 'generate',

capability: 'generate',

context: { item_type, data },

});

if (llmResult) {

// ★ 标准化 LLM 输出

const standardized = this._standardizeGeneratedContent(

llmResult.data || llmResult.raw, item_type

);

result = this._formatResult('generate', {

executed: true,

generated_content: standardized,

scope: 'generate',

item_type

}, { source: 'llm_router', durationMs: Date.now() - startTime });

}

}

if (!result) {

return { capability: 'generate', success: false, error: '规则引擎和 LLM 均不可用' };

}

// ★ autoCreate 标记:指示前端是否自动启动创建流程

result.autoCreate = autoCreate !== false;

return result;

}

/**

  • ★ 标准化 generate 输出为前端可消费格式

*/

_standardizeGeneratedContent(rawContent, item_type) {

if (!rawContent) return null;

if (typeof rawContent === 'object' && rawContent.item_type && rawContent.properties) {

return rawContent;

}

let parsed = rawContent;

if (typeof rawContent === 'string') {

try {

const jsonMatch = rawContent.match(/\{[\s\S]*\}/);

if (jsonMatch) parsed = JSON.parse(jsonMatch[0]);

} catch (e) {

return { item_type, properties: { description: rawContent }, _unparseable: true };

}

}

if (parsed && typeof parsed === 'object') {

if (parsed.item_type && parsed.properties) return parsed;

if (parsed.properties && typeof parsed.properties === 'object') {

return {

item_type: item_type || parsed.item_type || 'Part',

properties: parsed.properties,

item_properties: parsed.item_properties || {},

relationships: parsed.relationships || []

};

}

const { relationships, item_properties, ...properties } = parsed;

return {

item_type: item_type || parsed.item_type || 'Part',

properties,

item_properties: item_properties || {},

relationships: relationships || []

};

}

return { item_type, properties: { description: String(rawContent) }, _unparseable: true };

}

`

3. useCapabilityCreate.js — 新增 createFromGenerate() 方法

在前端 useCapabilityCreate.js 中新增方法,消费 generate 输出并自动启动创建流程:

`javascript

/**

  • ★ 从 generate 输出创建对象
  • @param {Object} generatedContent - generate 返回的结构化数据
  • { item_type, properties, item_properties, relationships }
  • @param {Object} options - { autoCreate: true/false }
  • @returns {Object} 创建结果

*/

async function createFromGenerate(generatedContent, options = {}) {

const { autoCreate = true } = options;

// 1. 格式校验

if (!generatedContent || !generatedContent.item_type) {

error.value = '生成内容无法自动创建,请手动填写'

return { success: false, reason: 'unparseable' }

}

// 2. 保留生成内容(失败不丢失)

result.value = { generated_content: generatedContent }

if (!autoCreate) {

// 仅展示,不自动创建

return { success: true, autoCreate: false, generatedContent }

}

// 3. 启动创建流程(复用 useCapabilityCreate 的 10 步流程)

try {

const createResult = await createObject({

item_type: generatedContent.item_type,

properties: generatedContent.properties || {},

item_properties: generatedContent.item_properties || {},

relationships: generatedContent.relationships || []

})

return createResult

} catch (e) {

// 创建失败,保留 generated_content 供用户手动修正

error.value = 创建失败: ${e.message},生成内容已保留

return {

success: false,

reason: 'create_failed',

generatedContent,

error: e.message

}

}

}

`

#### 数据模型

generate 输出标准化格式

`typescript

interface GeneratedContent {

item_type: string // 必填,SCSAI ItemType 名称

properties: Record // 必填,普通属性键值对

item_properties?: Record

action: 'add' | 'get'

item_type: string

properties?: Record

id?: string

keyed_name?: string

}>

relationships?: Array<{ // 可选,关系定义

relationship_type: string

related_item_type: string

related_items: Array<{

properties?: Record

related_id?: string | object

relItemProps?: Record

}>

}>

_unparseable?: boolean // 内部标记:原始内容无法解析

}

`

#### 依赖关系

  • 依赖 useCapabilityCreate.jscreateObject() 方法(已存在)
  • 依赖 AmlBuilder.jsbuildAML() 方法(已存在)
  • 依赖 rule-engine.jsexecuteGenerate() 方法(已存在)

1.3.2 P0-5.2: AMLGenerator降级路径item_properties补齐

#### 需求概述

AMLGenerator._buildAML() 当前不支持 item_properties 字段的嵌套输出,item/foreign 类型字段的对象值被直接输出为字符串(裸 GUID),需与前端 AmlBuilder.buildItemElement() 输出一致。

#### 当前问题分析

  1. _buildAML() 缺少 item_properties 处理:当前代码(第347-383行)仅处理 properties 和 relationships,无 item_properties 嵌套输出
  2. item/foreign 类型字段对象值被裸输出:第359行 return \ <${key}>${String(value)}\; 将对象值直接 String() 化,输出 [object Object]
  3. 与 AmlBuilder 输出不一致:AmlBuilder.buildItemElement() 支持 item_properties 嵌套创建和 action='get' 引用

#### 修改文件

| 文件路径 | 修改类型 | 说明 |

|---------|---------|------|

| server/core/aml-generator.js | 修改 | _buildAML() 方法添加 item_properties 嵌套输出 |

#### 核心类/函数设计

1. aml-generator.js — 修改 _buildAML() 方法

_buildAML() 方法中,将 item/foreign 类型字段的对象值按 item_properties 格式输出,并在 properties XML 后、relationships XML 前插入 item_properties 嵌套输出:

`javascript

_buildAML(data, schema, options) {

const { itemType } = schema;

const action = options.action || 'add';

const { properties } = schema;

const skipKeys = ['relationships', '_relationships', 'id'];

// ★ 收集 item_properties(从 data 中提取或从 schema 推断)

const itemProps = data.item_properties || data._item_properties || {};

// 构建属性 XML(★ 修改:item/foreign 类型对象值不直接输出,转入 itemProps)

const propertiesXml = Object.entries(data)

.filter(([key, value]) => value !== undefined && value !== null

&& !skipKeys.includes(key)

&& key !== 'item_properties' && key !== '_item_properties')

.map(([key, value]) => {

const prop = properties[key];

const dataType = prop?.type || 'string';

// ★ item/foreign 类型且值为对象 → 转入 item_properties 处理

if ((dataType === 'item' || dataType === 'foreign') && typeof value === 'object' && value !== null) {

itemProps[key] = value;

return null; // 不在 properties XML 中输出

}

// item/foreign 类型且值为字符串(GUID)→ 直接输出

if (dataType === 'item' || dataType === 'foreign') {

return <${key}>${String(value)};

}

if (['list', 'color list', 'date', 'integer', 'float', 'decimal', 'number', 'boolean'].includes(dataType)) {

return <${key}>${this._escapeXml(String(value))};

}

const escaped = this._escapeXml(String(value));

return <${key}>;

})

.filter(Boolean) // ★ 过滤掉 null(被转入 item_properties 的字段)

.join('\n');

// ★ 构建 item_properties 嵌套 XML(与 AmlBuilder.buildItemElement 一致)

const itemPropsXml = this._buildItemPropertiesXml(itemProps);

// 构建 Relationships XML

const relationships = data.relationships || data._relationships || [];

const relationshipsXml = this._buildRelationshipsXml(relationships);

const idAttr = (action !== 'add' && data.id) ? id="${data.id}" : '';

const aml =

${propertiesXml}${itemPropsXml}${relationshipsXml}

;

return aml;

}

/**

  • ★ 新增:构建 item_properties 嵌套 XML
  • 与 AmlBuilder.buildItemElement() 的 item_properties 处理逻辑一致

*

  • 支持两种格式:
  • 1. 嵌套创建:{ action: 'add', item_type: 'WBS Element', properties: { name: 'xxx' } }
  • 2. 引用已有:{ action: 'get', item_type: 'Method', id: 'xxx', keyed_name: 'yyy' }

*/

_buildItemPropertiesXml(itemProperties) {

if (!itemProperties || typeof itemProperties !== 'object') return '';

let xml = '';

for (const [propName, propDef] of Object.entries(itemProperties)) {

if (!propDef || typeof propDef !== 'object') continue;

if (propDef.action === 'add' && propDef.item_type) {

// 嵌套创建:递归构建子 Item

const subProps = propDef.properties || {};

const subPropsXml = Object.entries(subProps)

.filter(([, v]) => v !== undefined && v !== null)

.map(([k, v]) => {

if (typeof v === 'object') return null; // 不支持更深层嵌套

return <${k}>${this._escapeXml(String(v))};

})

.filter(Boolean)

.join('\n');

xml += \n <${propName}>;

xml += \n ;

xml += subPropsXml ? '\n' + subPropsXml : '';

xml += \n ;

xml += \n ;

} else if (propDef.action === 'get' && propDef.id && propDef.item_type) {

// 引用已有对象

const keyedName = propDef.keyed_name || '';

xml += \n <${propName}>;

xml += ;

xml += ;

}

}

return xml;

}

`

#### 依赖关系

  • 依赖 server/utils/aml-builder.jsbuildItemElement() 作为格式参考(唯一真相源)
  • 不引入新依赖

1.3.3 P0-5.3: 关系发现格式转换

#### 需求概述

RelationshipResolver.discoverRelations() 返回 source_index/target_index 格式的关系数据,需转换为 AmlBuilder 标准的 relationship_type/related_item_type/related_items 格式后传入 AML 构建。

#### 当前问题分析

  1. capability-runtime.js create() 直接推送 source_index 格式:第880-888行,discoveredRelations 被直接 push 到 relationships 数组,保留了 source_index/target_index 字段
  2. AmlBuilder 无法处理 source_index 格式:AmlBuilder.buildRelationshipElement() 期望 relationship_type/related_item_type/related_items 格式
  3. 缺少格式转换器:系统中无 source_index → AmlBuilder 标准格式的转换逻辑

#### 修改文件

| 文件路径 | 修改类型 | 说明 |

|---------|---------|------|

| server/core/capability-runtime.js | 修改 | create() 方法中关系发现结果格式转换 |

| server/core/relationship-resolver.js | 新增 | convertDiscoveryRelations() 静态方法 |

#### 核心类/函数设计

1. relationship-resolver.js — 新增格式转换方法

在 RelationshipResolver 类中新增静态方法:

`javascript

/**

  • ★ 将 discoverRelations() 返回的 source_index/target_index 格式
  • 转换为 AmlBuilder 标准的 relationship_type/related_item_type/related_items 格式

*

  • @param {Array} discoveredRelations - discoverRelations() 返回的关系数组
  • [{ parent_index, child_index, relation_type, properties, field_hint }]
  • @param {Array} batchItems - 原始对象数组
  • @param {string} mainItemType - 主对象类型
  • @returns {Array} AmlBuilder 标准格式的关系数组

*/

static convertDiscoveryRelations(discoveredRelations, batchItems, mainItemType) {

if (!Array.isArray(discoveredRelations) || discoveredRelations.length === 0) return [];

if (!Array.isArray(batchItems) || batchItems.length === 0) return [];

// 按 relation_type 分组

const grouped = {};

for (const rel of discoveredRelations) {

const relType = rel.relation_type || rel.relationship_type;

if (!relType) {

console.warn('[RelationshipResolver] 关系类型为空,跳过');

continue;

}

const parentIdx = rel.parent_index ?? rel.source_index;

const childIdx = rel.child_index ?? rel.target_index;

// 索引越界检查

if (parentIdx === undefined || childIdx === undefined) continue;

if (parentIdx < 0 || parentIdx >= batchItems.length || childIdx < 0 || childIdx >= batchItems.length) {

console.warn([RelationshipResolver] 关系索引越界: parent=${parentIdx}, child=${childIdx}, batch_size=${batchItems.length});

continue;

}

if (!grouped[relType]) {

grouped[relType] = { relationship_type: relType, related_items: [] };

}

const childItem = batchItems[childIdx];

const relatedItemType = childItem.item_type || mainItemType;

// 设置 related_item_type(同一关系类型下取第一个子对象的类型)

if (!grouped[relType].related_item_type) {

grouped[relType].related_item_type = relatedItemType;

}

// 构建相关项

const relatedItem = {

properties: { ...childItem }

};

// 关系级别属性(如 quantity, sort_order)

if (rel.properties && Object.keys(rel.properties).length > 0) {

const relItemProps = {};

for (const [k, v] of Object.entries(rel.properties)) {

if (v !== null && v !== undefined && k !== 'SCSAI_relation_id' && k !== 'direction') {

relatedItem[k] = v; // 放在 related_item 层级

}

}

}

grouped[relType].related_items.push(relatedItem);

}

// 校验转换结果

const result = [];

for (const group of Object.values(grouped)) {

if (!group.relationship_type || !group.related_item_type || !group.related_items || group.related_items.length === 0) {

console.warn('[RelationshipResolver] 转换结果校验失败,跳过:', JSON.stringify(group).substring(0, 100));

continue;

}

result.push(group);

}

return result;

}

`

2. capability-runtime.js — 修改 create() 方法

create() 方法(第871-893行)中,将关系发现结果转换为 AmlBuilder 标准格式:

`javascript

// ===== 关系感知:批量关系发现 =====

let discoveredRelations = [];

if (resolver && Array.isArray(context.batch_items) && context.batch_items.length > 1) {

try {

const discovery = await resolver.discoverRelations(item_type, context.batch_items);

discoveredRelations = discovery.relations || [];

if (discoveredRelations.length > 0) {

this._log('create', item_type, '自动发现关系', ${discoveredRelations.length} 条);

// ★ 格式转换:source_index/target_index → relationship_type/related_item_type/related_items

const { RelationshipResolver } = require('./relationship-resolver');

const convertedRelations = RelationshipResolver.convertDiscoveryRelations(

discoveredRelations, context.batch_items, item_type

);

// 合并到 relationships(转换后的标准格式)

for (const rel of convertedRelations) {

relationships.push(rel);

}

}

} catch (e) {

this._log('create', item_type, '关系发现失败(非阻塞)', e.message);

}

}

`

#### 依赖关系

  • 依赖 relationship-resolver.jsdiscoverRelations() 方法(已存在)
  • 依赖 AmlBuilder.buildRelationshipElement() 的输入格式要求(已存在)

1.3.4 P1-5.4: 后端创建路径重试与回滚

#### 需求概述

后端 createItem()(unified-create.js / rule-engine.js)中 applyAML 失败后需实施重试,SCSAIClient.sendAML() 需解析 SOAP Fault 返回结构化错误,多关系对象部分失败需回滚。

#### 当前问题分析

  1. SCSAI-client.js sendAML() 超时抛异常:第121-123行,超时时 reject(new Error(...)),调用方需 try/catch
  2. rule-engine.js createItem() 无重试:第2538-2550行,applyAML 失败后直接返回 { success: false }
  3. unified-create.js 无回滚:创建步骤失败后不回滚已创建的对象
  4. SOAP Fault 解析已有但不完善_parseFault() 方法已存在(第137-153行),但 sendAML() 的错误路径未统一使用

#### 修改文件

| 文件路径 | 修改类型 | 说明 |

|---------|---------|------|

| server/utils/SCSAI-client.js | 修改 | sendAML() 超时返回结构化错误而非抛异常 |

| server/core/rule-engine.js | 修改 | createItem() 添加重试与回滚逻辑 |

| server/routes/unified-create.js | 修改 | 7步创建流程添加回滚逻辑 |

#### 核心类/函数设计

1. SCSAI-client.js — sendAML() 超时返回结构化错误

修改 _sendRequest() 方法,超时时 resolve 而非 reject:

`javascript

_sendRequest(url, options, postData) {

return new Promise((resolve, reject) => {

// ... 现有代码 ...

// 超时处理(★ 修改:resolve 而非 reject,返回结构化错误)

req.setTimeout(this.timeout, () => {

req.destroy();

// ★ 不再 reject,改为 resolve 结构化错误

resolve({

statusCode: 0,

headers: {},

data: '',

timeoutError: true,

timeoutMs: this.timeout

});

});

// ... 现有代码 ...

});

}

`

修改 sendAML() 方法,处理超时响应:

`javascript

async sendAML(aml, opts = {}) {

// ... 现有代码 ...

const response = await this._sendRequest(url, options, soapEnvelope);

// ★ 超时处理:返回结构化错误

if (response.timeoutError) {

return {

success: false,

items: [],

fault: {

code: 'TIMEOUT',

string: SCSAI请求超时 (${response.timeoutMs}ms),

actor: '',

detail: timeout after ${response.timeoutMs}ms

},

rawXml: '',

count: 0

};

}

// 检查 HTTP 状态码

if (response.statusCode >= 400) {

return {

success: false,

items: [],

fault: {

code: 'HTTP_ERROR',

string: HTTP ${response.statusCode},

actor: '',

detail: response.data

},

rawXml: response.data,

count: 0

};

}

return this._parseResponse(response.data);

}

`

2. rule-engine.js — createItem() 添加重试与回滚

createItem() 方法中,对 applyAML 调用添加重试逻辑:

`javascript

/**

  • ★ 带重试的 AML 提交
  • @param {Function} applyAML - AML 提交函数
  • @param {string} aml - AML 字符串
  • @param {Object} context - 上下文(用于重试时修改 AML)
  • @returns {Object} { result, retryCount, retryReasons }

*/

async _applyAMLWithRetry(applyAML, aml, context) {

const maxRetries = 3;

let currentAml = aml;

const retryReasons = [];

for (let attempt = 0; attempt <= maxRetries; attempt++) {

const result = await applyAML(currentAml);

// 成功

if (result && (result.items?.length > 0 || result.Item)) {

return { result, retryCount: attempt, retryReasons };

}

// 解析错误

const fault = result?.fault || {};

const faultCode = fault.code || '';

const faultString = fault.string || '';

// 权限错误 → 重试1次(重新获取 token 后重试)

if (/no default permission|permission_id|access denied/i.test(faultString) && attempt === 0) {

retryReasons.push({ attempt, reason: 'permission_error', faultCode, faultString });

// 权限重试:注入 World 权限

const worldAml = World;

try {

const worldRes = await applyAML(worldAml);

const worldId = worldRes?.items?.[0]?.id;

if (worldId) {

currentAml = currentAml.replace(

//,

);

}

} catch (e) { / 忽略 / }

continue;

}

// 唯一性冲突 → 追加后缀重试(最多3次)

if (/not unique|PropertiesAreNotUnique/i.test(faultString)) {

retryReasons.push({ attempt, reason: 'uniqueness_conflict', faultCode, faultString });

const suffix = -${attempt + 1};

// 在 item_number 或 name 后追加后缀

currentAml = currentAml.replace(

/([^<]+)<\/item_number>/,

(match, val) => ${val}${suffix}

);

if (attempt < 2) continue; // 最多追加3次后缀

}

// 超时 → 重试1次

if (faultCode === 'TIMEOUT' && attempt === 0) {

retryReasons.push({ attempt, reason: 'timeout', faultCode, faultString });

continue;

}

// 其他错误 → 指数退避重试

if (attempt < maxRetries) {

retryReasons.push({ attempt, reason: 'other_error', faultCode, faultString });

await new Promise(resolve => setTimeout(resolve, Math.pow(2, attempt) * 1000));

continue;

}

// 全部重试失败

return {

result,

retryCount: attempt,

retryReasons,

finalFault: fault

};

}

}

/**

  • ★ 回滚已创建的对象
  • @param {Function} applyAML - AML 提交函数
  • @param {Array} createdIds - 已创建对象的 ID 列表 [{ item_type, item_id }]
  • @returns {Object} { rolled_back, rolled_back_ids, orphan_ids }

*/

async _rollbackCreatedItems(applyAML, createdIds) {

const rolledBackIds = [];

const orphanIds = [];

for (const { item_type, item_id } of createdIds) {

try {

const deleteAml = ;

const result = await applyAML(deleteAml);

if (result?.items?.length > 0 || result?.Item) {

rolledBackIds.push(item_id);

// ★ 审计日志

console.log('[AUDIT] rollback_delete', JSON.stringify({

action: 'rollback_delete',

item_type,

item_id,

reason: '后续步骤失败,回滚已创建对象',

timestamp: new Date().toISOString()

}));

} else {

orphanIds.push(item_id);

console.error('[CRITICAL] 回滚失败,孤儿对象:', item_id);

}

} catch (e) {

orphanIds.push(item_id);

console.error('[CRITICAL] 回滚异常,孤儿对象:', item_id, e.message);

}

}

return {

rolled_back: orphanIds.length === 0,

rolled_back_ids: rolledBackIds,

orphan_ids: orphanIds

};

}

`

3. rule-engine.js — createItem() 集成重试与回滚

修改 createItem() 方法中 applyAML 调用部分(第2538行附近):

`javascript

// 3. 构建AML并提交(★ 使用带重试的提交)

let itemId = null, amlError = null;

try {

const cleanProps = {};

for (const [k, v] of Object.entries(context.properties || {})) {

if (v === null || v === undefined || v === '') continue;

if (typeof v === 'object') continue;

cleanProps[k] = v;

}

const { buildAML } = require('../utils/aml-builder');

const aml = buildAML({ item_type, action: 'add', properties: cleanProps, item_properties: context.item_properties, relationships: context.relationships });

if (applyAML) {

// ★ 带重试的 AML 提交

const { result: applyResult, retryCount, finalFault } = await this._applyAMLWithRetry(applyAML, aml, context);

// 提取 itemId

if (applyResult?.Item?.id) itemId = applyResult.Item.id;

if (!itemId && applyResult?.items?.[0]?.id) itemId = applyResult.items[0].id;

if (!itemId && applyResult?.id) itemId = applyResult.id;

if (!itemId) {

// ★ 嵌套 AML 失败 → 分步降级

const hasNested = Object.keys(context.item_properties || {}).length > 0 ||

(context.relationships || []).length > 0;

if (hasNested && retryCount > 0) {

// 分步降级:先创建主对象(仅 properties),再逐个创建子对象和关系

const stepDownResult = await this._stepDownCreate(applyAML, item_type, cleanProps, context.item_properties, context.relationships);

if (stepDownResult.success) {

itemId = stepDownResult.item_id;

} else {

return { success: false, errors: ['SCSAI提交失败(含分步降级)'], warnings, rule_results: allResults, aml, retry_count: retryCount, final_fault: finalFault };

}

} else {

return { success: false, errors: ['SCSAI提交失败'], warnings, rule_results: allResults, aml, retry_count: retryCount, final_fault: finalFault };

}

}

} else {

return { success: true, aml, rule_results: allResults, warnings, item_id: null };

}

} catch (e) { amlError = e.message; errors.push('AML创建失败: ' + e.message); }

`

#### 依赖关系

  • 依赖 SCSAI-client.jssendAML() 方法(已存在,需修改超时处理)
  • 依赖 aml-builder.jsbuildAML() 方法(已存在)

1.3.5 P1-5.5: 前端create_post规则执行可靠性

#### 需求概述

前端创建成功后触发的 create_post 规则执行结果需能被前端感知,不允许 fire-and-forget 导致规则执行丢失。

#### 当前问题分析

  1. create_post 是 fire-and-forget:rule-engine.js createItem() 第2566-2570行,create_post 执行结果仅记录在 allResults.create_post 中,但前端通过 /SCSAI-api/ApplyItem 直接提交时,不经过 createItem()
  2. 前端直接提交不触发 create_post:前端通过 /SCSAI-api/ApplyItem 代理提交 AML,绕过了后端规则引擎的 create_post 执行
  3. 无 create_post 结果通知机制:即使后端执行了 create_post,前端也无法获取结果

#### 修改文件

| 文件路径 | 修改类型 | 说明 |

|---------|---------|------|

| server.js | 修改 | 新增 /api/rule-engine/execute-create-post 端点 |

| server/core/rule-engine.js | 修改 | 新增 executeCreatePost() 方法(带重试和超时) |

| src/composables/useCapabilityCreate.js | 修改 | 创建成功后调用 create_post 端点 |

#### 核心类/函数设计

1. rule-engine.js — 新增 executeCreatePost() 方法

`javascript

/**

  • ★ 执行 create_post 规则(带重试和超时)
  • 供前端创建成功后调用,替代 fire-and-forget 模式

*

  • @param {Object} context - { item_type, item_id, properties, relationships }
  • @param {Object} options - { maxRetries: 2, timeout: 30000 }
  • @returns {Object} { success, rule_results, retry_count, error }

*/

async executeCreatePost(context, options = {}) {

const { item_type, item_id } = context;

const maxRetries = options.maxRetries ?? 2;

const timeoutMs = options.timeout ?? 30000;

if (!item_type || !item_id) {

return { success: false, error: '缺少 item_type 或 item_id', rule_results: [] };

}

const postContext = { ...context, item_id };

let lastError = null;

for (let attempt = 0; attempt <= maxRetries; attempt++) {

try {

// 超时控制

const result = await Promise.race([

this.execute('create_post', postContext, { conflict_strategy: 'merge' }),

new Promise((_, reject) =>

setTimeout(() => reject(new Error('create_post 执行超时')), timeoutMs)

)

]);

// 审计日志

console.log('[AUDIT] create_post', JSON.stringify({

action: 'create_post_execute',

item_type,

item_id,

attempt,

success: true,

rule_count: result.results?.length || 0,

timestamp: new Date().toISOString()

}));

return {

success: true,

rule_results: result.results || [],

retry_count: attempt,

elapsed: result.elapsed || 0

};

} catch (e) {

lastError = e.message;

// 审计日志(失败)

console.log('[AUDIT] create_post_failed', JSON.stringify({

action: 'create_post_execute',

item_type,

item_id,

attempt,

success: false,

error: lastError,

timestamp: new Date().toISOString()

}));

// 指数退避重试

if (attempt < maxRetries) {

await new Promise(resolve => setTimeout(resolve, Math.pow(2, attempt) * 1000));

}

}

}

return {

success: false,

error: lastError,

rule_results: [],

retry_count: maxRetries

};

}

`

2. server.js — 新增 create_post 端点

在 server.js 的路由处理中新增端点:

`javascript

// POST /api/rule-engine/execute-create-post

// 前端创建成功后调用,执行 create_post 规则

if (pathname === '/api/rule-engine/execute-create-post') {

const { item_type, item_id, properties, relationships } = body || {};

if (!item_type || !item_id) {

return { success: false, message: '请提供 item_type 和 item_id' };

}

try {

const result = await engine.executeCreatePost(

{ item_type, item_id, properties: properties || {}, relationships: relationships || [] },

{ maxRetries: 2, timeout: 30000 }

);

return { success: result.success, data: result };

} catch (e) {

return { success: false, message: e.message };

}

}

`

3. useCapabilityCreate.js — 创建成功后调用 create_post

在前端创建成功后,调用 create_post 端点获取规则执行结果:

`javascript

// ★ 创建成功后执行 create_post 规则(替代 fire-and-forget)

async function executeCreatePost(itemType, itemId, properties, relationships) {

try {

const response = await fetch('/api/rule-engine/execute-create-post', {

method: 'POST',

headers: { 'Content-Type': 'application/json' },

body: JSON.stringify({

item_type: itemType,

item_id: itemId,

properties,

relationships

})

})

if (!response.ok) {

console.warn('[useCapabilityCreate] create_post 请求失败:', response.status)

return { success: false, error: HTTP ${response.status} }

}

const result = await response.json()

if (result.success) {

console.log('[useCapabilityCreate] create_post 执行成功:', result.data?.rule_results?.length || 0, '条规则')

} else {

console.warn('[useCapabilityCreate] create_post 执行失败:', result.data?.error)

// ★ 不回滚主对象,仅通知用户

error.value = 对象创建成功,但后续处理失败:${result.data?.error || '未知原因'}

}

return result

} catch (e) {

console.warn('[useCapabilityCreate] create_post 请求异常:', e.message)

return { success: false, error: e.message }

}

}

`

#### 数据模型

create_post 结果持久化(用于前端断线后补发):

`sql

CREATE TABLE IF NOT EXISTS create_post_results (

id INTEGER PRIMARY KEY AUTOINCREMENT,

item_type TEXT NOT NULL,

item_id TEXT NOT NULL,

success INTEGER NOT NULL DEFAULT 0,

rule_results TEXT, -- JSON

error TEXT,

retry_count INTEGER DEFAULT 0,

created_at TEXT DEFAULT (datetime('now','localtime')),

delivered INTEGER DEFAULT 0, -- 是否已推送到前端

delivered_at TEXT

);

CREATE INDEX IF NOT EXISTS idx_post_results_item ON create_post_results(item_type, item_id);

CREATE INDEX IF NOT EXISTS idx_post_results_undelivered ON create_post_results(delivered) WHERE delivered = 0;

`


1.3.6 P2-5.6: 前后端创建逻辑同步维护

#### 需求概述

后端 aml-builder.js 与前端 AmlBuilder.js、后端 unified-create.js 与前端 useCapabilityCreate.js 的逻辑差异需通过自动化测试检测,后端镜像代码需标注前端对应文件和同步状态。

#### 当前问题分析

  1. aml-builder.js 与 AmlBuilder.js 已有差异:后端 aml-builder.js 无 _formatValue() 方法(前端有 list 值匹配、日期格式化等),esc() 方法实现略有不同
  2. unified-create.js 与 useCapabilityCreate.js 差异更大:unified-create.js 是简化版7步流程,useCapabilityCreate.js 是完整10步流程(含 AI 纠错闭环)
  3. 无自动化一致性测试:CI 中无前后端输出对比测试

#### 修改文件

| 文件路径 | 修改类型 | 说明 |

|---------|---------|------|

| server/utils/aml-builder.js | 修改 | 添加文件头 @mirror-of 标注 |

| server/routes/unified-create.js | 修改 | 添加文件头 @mirror-of 标注 |

| tests/aml-builder-consistency.test.js | 新增 | 前后端 AmlBuilder 输出一致性测试 |

| tests/create-flow-consistency.test.js | 新增 | 前后端创建流程逻辑一致性测试 |

#### 核心类/函数设计

1. 文件头 @mirror-of 标注

server/utils/aml-builder.js 文件头部添加:

`javascript

/**

  • 服务端 AML 构建器 - 将标准 JSON 格式转换为 SCSAI AML

*

  • @mirror-of src/utils/AmlBuilder.js
  • @last-sync 2026-06-25
  • @sync-status drifted

*

  • ⚠️ 此文件逻辑必须与前端 src/utils/AmlBuilder.js 保持完全一致
  • 前端逻辑经过反复测试(十几种复杂业务对象),后端不得自创逻辑

*

  • 已知差异(drifted 项):
  • 1. 前端有 _formatValue() 方法(list值匹配、日期格式化),后端无
  • 2. 前端 esc() 不转义单引号,后端转义
  • 3. 前端支持 default_permission 注入,后端支持
  • 4. 前端 buildRelationshipElement 支持 isSameType 分支,后端无

*/

`

server/routes/unified-create.js 文件头部添加:

`javascript

/**

  • unified-create.js — 服务端统一对象创建 API

*

  • @mirror-of src/composables/useCapabilityCreate.js
  • @last-sync 2026-06-25
  • @sync-status drifted

*

  • 镜像 src/composables/useCapabilityCreate.js 的创建流程

*

  • 已知差异(drifted 项):
  • 1. 前端10步完整流程(含AI纠错3次重试),后端7步简化流程
  • 2. 前端有 _dualDbDedupCheck 双库查重,后端无
  • 3. 前端有 precreateNestedItem 预创建,后端有简化版
  • 4. 前端有权限重试(World权限),后端有
  • 5. 前端有去重重试(追加后缀),后端有

*/

`

2. 自动化一致性测试

`javascript

// tests/aml-builder-consistency.test.js

/**

  • 前后端 AmlBuilder 输出一致性测试

*

  • 目的:检测后端 aml-builder.js 与前端 AmlBuilder.js 的输出差异
  • 在 CI 中运行,差异时构建失败

*/

const { buildAML: serverBuildAML } = require('../server/utils/aml-builder')

// 测试用例:标准三层数据结构

const testCases = [

{

name: '普通属性',

data: { item_type: 'Part', action: 'add', properties: { name: '测试零件', item_number: 'P-001' } }

},

{

name: '含 item_properties(嵌套创建)',

data: {

item_type: 'Project', action: 'add',

properties: { name: '测试项目' },

item_properties: {

wbs_id: { action: 'add', item_type: 'WBS Element', properties: { name: '测试 WBS' } }

}

}

},

{

name: '含 item_properties(引用已有)',

data: {

item_type: 'Project', action: 'add',

properties: { name: '测试项目' },

item_properties: {

scheduling_method: { action: 'get', item_type: 'Method', id: 'ABC123', keyed_name: 'Standard' }

}

}

},

{

name: '含 relationships',

data: {

item_type: 'Part', action: 'add',

properties: { name: '父零件' },

relationships: [{

relationship_type: 'Part BOM',

related_item_type: 'Part',

related_items: [{ properties: { name: '子零件', item_number: 'P-002' } }]

}]

}

},

{

name: '完整三层结构',

data: {

item_type: 'Project', action: 'add',

properties: { name: '完整项目' },

item_properties: {

wbs_id: { action: 'add', item_type: 'WBS Element', properties: { name: '项目 WBS' } }

},

relationships: [{

relationship_type: 'Project Tree',

related_item_type: 'Project',

related_items: [{ properties: { name: '子项目' } }]

}]

}

}

]

describe('AmlBuilder 前后端一致性', () => {

testCases.forEach(({ name, data }) => {

test(name, () => {

const serverAml = serverBuildAML(data)

// 基本校验:输出非空

expect(serverAml).toBeTruthy()

// 结构校验:包含必要的 XML 元素

expect(serverAml).toContain('')

expect(serverAml).toContain('')

expect(serverAml).toContain(type="${data.item_type}")

expect(serverAml).toContain(action="${data.action || 'add'}")

// item_properties 校验

if (data.item_properties) {

for (const [propName, propDef] of Object.entries(data.item_properties)) {

expect(serverAml).toContain(<${propName}>)

expect(serverAml).toContain()

if (propDef.action === 'add') {

expect(serverAml).toContain(type="${propDef.item_type}")

} else if (propDef.action === 'get') {

expect(serverAml).toContain(id="${propDef.id}")

}

}

}

// relationships 校验

if (data.relationships?.length > 0) {

expect(serverAml).toContain('')

expect(serverAml).toContain('')

for (const rel of data.relationships) {

expect(serverAml).toContain(type="${rel.relationship_type}")

}

}

})

})

})

`

#### 依赖关系

  • 依赖 CI/CD 流水线(需配置测试步骤)
  • 依赖 aml-builder.jsAmlBuilder.js 的输出格式(已存在)

2. 接口设计

2.1 总体设计

SCSAI 回写优化涉及的接口变更遵循以下原则:

  1. 前端直接提交模式不变/SCSAI-api/ApplyItem 代理保持不变
  2. 新增 create_post 端点:前端创建成功后主动调用
  3. 后端内部接口增强:重试、回滚、格式转换为内部逻辑,不新增外部 API

2.2 接口清单

2.2.1 新增接口

| 接口 | 方法 | 路径 | 说明 |

|------|------|------|------|

| create_post 执行 | POST | /api/rule-engine/execute-create-post | 前端创建成功后调用,执行 create_post 规则并返回结果 |

POST /api/rule-engine/execute-create-post

请求体:

`json

{

"item_type": "Project",

"item_id": "ABC123DEF456",

"properties": { "name": "测试项目" },

"relationships": []

}

`

响应体(成功):

`json

{

"success": true,

"data": {

"success": true,

"rule_results": [

{ "rule_id": "builtin-project-create-post-001", "rule_name": "Project: 子任务编号补全", "result": { "modified": true } }

],

"retry_count": 0,

"elapsed": 150

}

}

`

响应体(失败):

`json

{

"success": false,

"data": {

"success": false,

"error": "create_post 执行超时",

"rule_results": [],

"retry_count": 2

}

}

`

2.2.2 变更接口

接口变更类型说明
CapabilityRuntime.generate()返回值变更generated_content 标准化为 { item_type, properties, item_properties, relationships } 格式
SCSAIClient.sendAML()行为变更超时不再抛异常,返回 { success: false, fault: { code: 'TIMEOUT' } }
rule-engine.js createItem()行为变更applyAML 失败后实施重试,支持分步降级和回滚

4. 数据模型

4.1 设计目标

  1. generate 输出标准化:定义 GeneratedContent 接口,确保 generate 输出可被前端消费
  2. 关系格式统一:定义 AmlBuilderRelation 接口,统一关系数据格式
  3. create_post 结果持久化:新增 create_post_results 表,支持前端断线后补发
  4. 重试策略配置化:重试策略参数可配置,不硬编码

4.2 模型实现

4.2.1 GeneratedContent 接口

`typescript

// generate 能力输出标准化格式

interface GeneratedContent {

// 必填

item_type: string; // SCSAI ItemType 名称

properties: Record; // 普通属性键值对

// 可选

item_properties?: Record

action: 'add' | 'get';

item_type: string;

properties?: Record;

id?: string; // action='get' 时必填

keyed_name?: string; // action='get' 时推荐

}>;

relationships?: AmlBuilderRelation[]; // 关系定义

// 内部标记

_unparseable?: boolean; // 原始内容无法解析

_source?: 'rule_engine' | 'llm_router'; // 生成来源

}

`

4.2.2 AmlBuilderRelation 接口

`typescript

// AmlBuilder 标准关系格式(转换后)

interface AmlBuilderRelation {

relationship_type: string; // SCSAI RelationshipType 名称

related_item_type: string; // 关联对象 ItemType

related_items: AmlBuilderRelatedItem[]; // 关联对象列表

}

interface AmlBuilderRelatedItem {

properties?: Record; // 子对象属性

item_properties?: Record; // 子对象 Item 类型属性

related_id?: string | object; // 引用 ID

relItemProps?: Record; // 关系级别附加属性

id?: string; // 已有对象 ID

item_number?: string; // 已有对象编号

}

`

4.2.3 DiscoveryRelation 接口(转换前)

`typescript

// discoverRelations() 返回格式(转换前)

interface DiscoveryRelation {

parent_index: number; // 源对象在 batch_items 中的索引

child_index: number; // 目标对象在 batch_items 中的索引

relation_type: string; // SCSAI RelationshipType 名称

properties?: Record; // 关系级别属性

field_hint?: string; // 关系发现来源提示

parent_ref?: string; // 父对象引用

}

`

4.2.4 create_post_results 表

`sql

CREATE TABLE IF NOT EXISTS create_post_results (

id INTEGER PRIMARY KEY AUTOINCREMENT,

item_type TEXT NOT NULL,

item_id TEXT NOT NULL,

success INTEGER NOT NULL DEFAULT 0,

rule_results TEXT, -- JSON 格式

error TEXT,

retry_count INTEGER DEFAULT 0,

created_at TEXT DEFAULT (datetime('now','localtime')),

delivered INTEGER DEFAULT 0, -- 是否已推送到前端

delivered_at TEXT

);

CREATE INDEX IF NOT EXISTS idx_post_results_item ON create_post_results(item_type, item_id);

CREATE INDEX IF NOT EXISTS idx_post_results_undelivered ON create_post_results(delivered) WHERE delivered = 0;

`

4.2.5 重试策略配置

`yaml

config.yaml 新增配置段

SCSAI_retry:

max_retries: 3

permission_retry: 1

uniqueness_retry: 3

timeout_retry: 1

backoff_base_ms: 1000

create_post:

max_retries: 2

timeout_ms: 30000

backoff_base_ms: 1000

`

4.2.6 回滚审计日志格式

`typescript

// 审计日志记录格式

interface RollbackAuditLog {

action: 'rollback_delete';

item_type: string;

item_id: string;

reason: string;

timestamp: string; // ISO 8601

operator?: string; // 操作人(可选)

}

interface CreatePostAuditLog {

action: 'create_post_execute' | 'create_post_failed';

item_type: string;

item_id: string;

attempt: number;

success: boolean;

error?: string;

rule_count?: number;

timestamp: string;

}

``

← 返回案例列表
分享:
🤖 Try Now →
🤖
🎁