Skip to content

简单查看字段依赖关系 #1

@EdwardZZZ

Description

@EdwardZZZ
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

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions