-
Notifications
You must be signed in to change notification settings - Fork 0
Open
Description
import { Model, ModelManager, createModel } from 'model-reaction';
// 字段依赖关系类型
export interface FieldDependency {
field: string; // 字段名
dependsOn: string[]; // 依赖的字段列表
dependedBy: string[]; // 被哪些字段依赖
isComputed: boolean; // 是否是计算字段
}
// 字段依赖关系图
export interface DependencyGraph {
[field: string]: FieldDependency;
}
/**
* 解析模型中字段之间的依赖关系
* @param schema 模型架构
* @returns 依赖关系图
*/
export function analyzeFieldDependencies(schema: Model): DependencyGraph {
const graph: DependencyGraph = {};
// 初始化所有字段的依赖关系
Object.keys(schema).forEach(field => {
graph[field] = {
field,
dependsOn: [],
dependedBy: [],
isComputed: false
};
});
// 分析字段之间的依赖关系
Object.entries(schema).forEach(([field, fieldSchema]) => {
if (fieldSchema.reaction) {
const reactions = Array.isArray(fieldSchema.reaction)
? fieldSchema.reaction
: [fieldSchema.reaction];
// 标记为计算字段
graph[field].isComputed = true;
// 处理每个反应
reactions.forEach(reaction => {
// 添加依赖关系
reaction.fields.forEach(dependencyField => {
// 当前字段依赖于 dependencyField
if (!graph[field].dependsOn.includes(dependencyField)) {
graph[field].dependsOn.push(dependencyField);
}
// dependencyField 被当前字段依赖
if (graph[dependencyField] && !graph[dependencyField].dependedBy.includes(field)) {
graph[dependencyField].dependedBy.push(field);
}
});
});
}
});
return graph;
}
/**
* 检测循环依赖
* @param graph 依赖关系图
* @returns 循环依赖的路径数组,如果没有循环依赖则返回空数组
*/
export function detectCircularDependencies(graph: DependencyGraph): string[][] {
const circularPaths: string[][] = [];
const visited = new Set<string>();
const path: string[] = [];
function dfs(field: string) {
// 如果在当前路径中已经访问过该字段,说明存在循环依赖
if (path.includes(field)) {
const cycle = [...path.slice(path.indexOf(field)), field];
circularPaths.push(cycle);
return;
}
// 如果已经在其他路径中访问过该字段,不需要重复访问
if (visited.has(field)) return;
visited.add(field);
path.push(field);
// 递归访问依赖的字段
const dependency = graph[field];
if (dependency) {
dependency.dependsOn.forEach(dep => dfs(dep));
}
path.pop();
}
// 对每个字段进行深度优先搜索
Object.keys(graph).forEach(field => {
if (!visited.has(field)) {
dfs(field);
}
});
return circularPaths;
}
/**
* 获取字段的所有依赖链
* @param field 字段名
* @param graph 依赖关系图
* @returns 依赖链数组
*/
export function getFieldDependencyChains(field: string, graph: DependencyGraph): string[][] {
const chains: string[][] = [];
const visited = new Set<string>();
function dfs(currentField: string, currentPath: string[] = []) {
// 避免循环依赖导致的无限递归
if (currentPath.includes(currentField)) return;
const newPath = [...currentPath, currentField];
// 如果当前字段没有依赖,则找到了一条完整的依赖链
const dependency = graph[currentField];
if (!dependency || dependency.dependsOn.length === 0) {
chains.push(newPath);
return;
}
// 递归查找依赖链
dependency.dependsOn.forEach(dep => {
dfs(dep, newPath);
});
}
dfs(field);
return chains;
}
/**
* 可视化依赖关系图(使用线条表示反转的层级依赖关系,合并相同字段)
* @param graph 依赖关系图
* @returns 使用线条表示的反转层级依赖关系图
*/
export function visualizeDependencyGraph(graph: DependencyGraph): string {
let result = '字段依赖关系图 (反转层级,合并相同字段):\n';
// 获取所有没有依赖其他字段的底层字段(源节点)
const bottomLevelFields = Object.values(graph)
.filter(dep => dep.dependsOn.length === 0)
.map(dep => dep.field);
// 如果没有底层字段,则使用所有字段
const startFields = bottomLevelFields.length > 0 ?
bottomLevelFields :
Object.keys(graph);
// 为每个起始字段生成反转依赖树,使用全局visited集合来跟踪已访问的字段
const globalVisited = new Set<string>();
startFields.forEach(field => {
// 如果该字段已经在其他树中被访问过,则跳过
if (!globalVisited.has(field)) {
result += renderReverseDependencyTree(field, graph, '', true, globalVisited);
}
});
return result;
}
/**
* 递归渲染反转依赖树(从底层字段向上),合并相同字段
* @param field 当前字段
* @param graph 依赖关系图
* @param prefix 前缀(用于缩进和连接线)
* @param isLast 是否是父节点的最后一个子节点
* @param visited 全局已访问的字段集合(用于合并相同字段)
* @param fieldPositions 字段名位置映射,用于垂直对齐相同字段
* @returns 渲染后的反转依赖树文本
*/
function renderReverseDependencyTree(
field: string,
graph: DependencyGraph,
prefix: string = '',
isLast: boolean = true,
visited: Set<string> = new Set(),
fieldPositions: Record<string, number> = {}
): string {
// 检查是否已经访问过该字段
const alreadyVisited = visited.has(field);
visited.add(field);
const dependency = graph[field];
if (!dependency) return prefix + (isLast ? '└── ' : '├── ') + field + '\n';
// 当前节点的表示
let result = '';
// 计算当前前缀长度
const currentPrefixLength = prefix.length + (isLast ? 4 : 4); // '└── ' 或 '├── ' 的长度为4
// 如果这个字段名之前出现过,使用记录的位置进行对齐
// 否则,使用当前位置并记录下来
let extraPadding = 0; // 记录额外的填充长度
if (field in fieldPositions) {
const targetPosition = fieldPositions[field];
const paddingDashes = '─'.repeat(Math.max(0, targetPosition - currentPrefixLength));
extraPadding = paddingDashes.length; // 保存额外填充的长度
// 如果是循环依赖或已经访问过,标记为引用
if (alreadyVisited) {
result = prefix + (isLast ? '└──' : '├──') + paddingDashes + ' ' + field + ' (已在图中)\n';
} else {
result = prefix + (isLast ? '└──' : '├──') + paddingDashes + ' ' +
field + (dependency.isComputed ? ' (计算字段)' : '') + '\n';
}
} else {
// 第一次出现的字段,使用标准格式并记录位置
fieldPositions[field] = currentPrefixLength;
// 如果是循环依赖或已经访问过,标记为引用
if (alreadyVisited) {
result = prefix + (isLast ? '└── ' : '├── ') + field + ' (已在图中)\n';
} else {
result = prefix + (isLast ? '└── ' : '├── ') +
field + (dependency.isComputed ? ' (计算字段)' : '') + '\n';
}
}
// 递归渲染被依赖的字段(向上)
const dependedBy = dependency.dependedBy;
// 过滤掉已经在当前路径中访问过的字段,避免循环依赖
const pathVisited = new Set<string>();
pathVisited.add(field);
// 子节点的前缀
let childPrefix;
if (extraPadding > 0) {
// 当有额外填充时,调整子节点的连接线
const extraSpace = ' '.repeat(extraPadding);
childPrefix = prefix + (isLast ? ' ' + extraSpace : '│ ' + extraSpace);
} else {
childPrefix = prefix + (isLast ? ' ' : '│ ');
}
dependedBy.forEach((dep, index) => {
// 跳过当前路径中已访问的字段(循环依赖)
if (pathVisited.has(dep)) return;
const isLastChild = index === dependedBy.length - 1;
result += renderReverseDependencyTree(dep, graph, childPrefix, isLastChild, visited, fieldPositions);
});
return result;
}
// 创建模型
// 创建精简版电子商务订单模型(所有字段在同一层级)
const simplifiedOrderModel: any = {
// 客户信息
customerId: {},
customerName: {},
customerVipLevel: {
default: 0,
reaction: {
fields: ['totalHistoricalSpent']
}
},
// 地址信息
shippingCountry: {},
shippingCity: {},
billingAddressSameAsShipping: {
default: true
},
billingCountry: {
default: "",
reaction: {
fields: ['shippingCountry', 'billingAddressSameAsShipping']
}
},
billingCity: {
default: "",
reaction: {
fields: ['shippingCity', 'billingAddressSameAsShipping']
}
},
// 商品信息
products: {},
productCount: {
reaction: {
fields: ['products']
}
},
totalWeight: {
reaction: {
fields: ['products']
}
},
// 价格计算
subtotal: {
reaction: {
fields: ['products']
}
},
taxRate: {
default: 0.1,
reaction: {
fields: ['shippingCountry']
}
},
taxAmount: {
reaction: {
fields: ['subtotal', 'taxRate']
}
},
shippingCost: {
reaction: {
fields: ['totalWeight', 'shippingCountry', 'customerVipLevel']
}
},
discountAmount: {
reaction: {
fields: ['customerVipLevel', 'subtotal']
}
},
totalAmount: {
reaction: {
fields: ['subtotal', 'taxAmount', 'shippingCost', 'discountAmount']
}
},
// 支付信息
paymentMethod: {
default: "credit_card"
},
paymentStatus: {
default: "pending"
},
// 订单状态
orderStatus: {
default: "draft",
reaction: {
fields: ['paymentStatus', 'fulfillmentStatus']
}
},
// 履行信息
fulfillmentStatus: {
default: "pending",
reaction: {
fields: ['paymentStatus']
}
},
estimatedDelivery: {
reaction: {
fields: ['fulfillmentStatus', 'shippingCountry', 'totalWeight']
}
},
// 历史数据
totalHistoricalSpent: {
default: 0
},
// 系统信息
createdAt: {
default: Date.now
},
updatedAt: {
reaction: {
fields: ['orderStatus', 'paymentStatus', 'fulfillmentStatus']
}
}
};
// 分析字段依赖关系
const simplifiedDependencyGraph = analyzeFieldDependencies(simplifiedOrderModel);
// 检测循环依赖
const simplifiedCircularPaths = detectCircularDependencies(simplifiedDependencyGraph);
// 可视化依赖关系
console.log(visualizeDependencyGraph(simplifiedDependencyGraph));Metadata
Metadata
Assignees
Labels
No labels