要进一步增强MES日志分析工具,支持动态规则管理的扩展功能,包括提供API接口以允许外部系统推送规则,以及支持规则优先级(临时规则可覆盖配置文件规则)

要进一步增强MES日志分析工具,支持动态规则管理的扩展功能,包括提供API接口以允许外部系统推送规则,以及支持规则优先级(临时规则可覆盖配置文件规则),我们需要扩展之前的`AdvancedMESLogAnalyzer.cs`代码。以下是实现思路和更新后的C#代码,基于之前的实现,并提供详细的中文解释。

 实现思路
1. 动态规则管理:
   - API接口:
     - 实现一个简单的HTTP API(如使用ASP.NET Core),提供端点(如`/api/rules`)以接受POST请求,推送新的规则。
     - API支持添加、更新或删除规则,规则以JSON格式传输,与`config.json`格式一致。
     - 使用线程安全的存储(如`ConcurrentDictionary`)管理规则,允许动态更新。
   - 规则优先级:
     - 为每条规则添加`Priority`字段,值越高优先级越高。
     - 临时规则(命令行或API推送)可设置高优先级,覆盖配置文件规则。
     - 分析时按优先级排序,优先匹配高优先级规则。

2. 代码结构调整:
   - 将规则管理逻辑提取到单独的`RuleManager`类,处理规则的加载、存储和优先级排序。
   - 集成ASP.NET Core,提供轻量级API服务,运行在独立线程或进程中。
   - 修改`MESLogAnalyzer`以支持动态规则查询和优先级匹配。

3. 配置文件更新:
   - 扩展`config.json`,添加`Priority`字段(默认值较低,如0)。
   - API推送的规则可指定更高优先级(如10)。

4. 报告增强:
   - 在报告中显示规则的优先级和来源(`ConfigFile`、`CommandLine`或`API`)。
   - 如果多条规则匹配,注明使用的最高优先级规则。

5. 其他保持不变:
   - 保留之前的复杂条件(`KeyFields`条件匹配、调用顺序)、性能优化(索引、并行处理)和运行时`config.json`监控。
   - 仅扩展动态规则管理功能。

 更新后的C#代码
以下是支持API接口和规则优先级的代码,使用ASP.NET Core实现API,并扩展规则管理逻辑。由于代码较长,重点展示修改部分,完整项目需包含依赖(如`Microsoft.AspNetCore.App`)。

```x-csharp
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.DependencyInjection;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;

namespace DynamicMESLogAnalyzer
{
    public class LogEntry
    {
        public DateTime Timestamp { get; set; }
        public string LogLevel { get; set; }
        public string Uri { get; set; }
        public JObject Request { get; set; }
        public JObject Response { get; set; }
        public int Time { get; set; }
        public string Status { get; set; }
        public string RawMessage { get; set; }
    }

    public class KeyFieldCondition
    {
        public string Field { get; set; }
        public string MatchType { get; set; } // "Exact", "Regex", "Contains"
        public string Pattern { get; set; }
    }

    public class DependencyRule
    {
        public string TargetApi { get; set; }
        public List<string> PrerequisiteApis { get; set; }
        public List<KeyFieldCondition> KeyFields { get; set; }
        public string ErrorCode { get; set; }
        public int? TimeWindowSeconds { get; set; }
        public int MinCallCount { get; set; } = 1;
        public List<string> PrerequisiteOrder { get; set; }
        public string Source { get; set; } = "ConfigFile";
        public int Priority { get; set; } = 0; // 优先级,越大越高
    }

    public class PrerequisiteStatus
    {
        public string PrerequisiteApi { get; set; }
        public string Status { get; set; }
        public List<LogEntry> FailedCalls { get; set; }
        public int SuccessfulCallCount { get; set; }
        public string OrderIssue { get; set; }
    }

    public class AnalysisResult
    {
        public LogEntry ErrorEntry { get; set; }
        public List<PrerequisiteStatus> PrerequisiteStatuses { get; set; }
        public Dictionary<string, string> KeyValues { get; set; }
        public string Details { get; set; }
    }

    public class RuleManager
    {
        private readonly ConcurrentDictionary<string, DependencyRule> rules = new ConcurrentDictionary<string, DependencyRule>();
        private readonly string configPath;

        public RuleManager(string configPath)
        {
            this.configPath = configPath;
            LoadConfig(configPath, false);
            SetupConfigWatcher();
        }

        private void SetupConfigWatcher()
        {
            if (!string.IsNullOrEmpty(configPath) && File.Exists(configPath))
            {
                var watcher = new FileSystemWatcher
                {
                    Path = Path.GetDirectoryName(configPath),
                    Filter = Path.GetFileName(configPath),
                    NotifyFilter = NotifyFilters.LastWrite
                };
                watcher.Changed += (s, e) => LoadConfig(configPath, false);
                watcher.EnableRaisingEvents = true;
            }
        }

        public void LoadConfig(string configPath, bool throwOnError = true)
        {
            try
            {
                if (File.Exists(configPath))
                {
                    string configJson = File.ReadAllText(configPath);
                    var configRules = JsonConvert.DeserializeObject<List<DependencyRule>>(configJson);
                    configRules.ForEach(r => r.Source = "ConfigFile");
                    foreach (var rule in configRules)
                    {
                        rules[rule.TargetApi + ":" + rule.ErrorCode] = rule;
                    }
                    Console.WriteLine($"Loaded {configRules.Count} rules from {configPath}");
                }
            }
            catch (Exception ex)
            {
                if (throwOnError)
                    throw new Exception($"Failed to load config file: {ex.Message}");
                else
                    Console.WriteLine($"Failed to reload config: {ex.Message}");
            }
        }

        public void AddOrUpdateRules(List<DependencyRule> newRules, string source)
        {
            foreach (var rule in newRules)
            {
                rule.Source = source;
                rules[rule.TargetApi + ":" + rule.ErrorCode] = rule;
            }
            Console.WriteLine($"Added/Updated {newRules.Count} rules from {source}");
        }

        public void RemoveRule(string targetApi, string errorCode)
        {
            rules.TryRemove(targetApi + ":" + errorCode, out _);
            Console.WriteLine($"Removed rule for {targetApi}:{errorCode}");
        }

        public List<DependencyRule> GetRules()
        {
            return rules.Values.OrderByDescending(r => r.Priority).ToList();
        }
    }

    public class MESLogAnalyzer
    {
        private List<LogEntry> logEntries = new List<LogEntry>();
        private readonly RuleManager ruleManager;
        private Dictionary<string, Dictionary<string, List<LogEntry>>> keyFieldIndex;

        public MESLogAnalyzer(string configPath)
        {
            ruleManager = new RuleManager(configPath);
        }

        public void LoadTempRules(string tempRulesJson)
        {
            try
            {
                var tempRules = JsonConvert.DeserializeObject<List<DependencyRule>>(tempRulesJson);
                ruleManager.AddOrUpdateRules(tempRules, "CommandLine");
            }
            catch (Exception ex)
            {
                throw new Exception($"Failed to load temporary rules: {ex.Message}");
            }
        }

        public void LoadLogFile(string filePath)
        {
            string[] lines = File.ReadAllLines(filePath);
            Regex logPattern = new Regex(@"\[(.*?)\]\s*(\w+)\s*-\s*PostSync,uri:(.*?),request:(.*?),response:(.*?),time:(\d+),(\w+\s+\w+\s+\w+)");

            logEntries.Clear();
            keyFieldIndex = new Dictionary<string, Dictionary<string, List<LogEntry>>>();

            foreach (string line in lines)
            {
                Match match = logPattern.Match(line);
                if (match.Success)
                {
                    try
                    {
                        var entry = new LogEntry
                        {
                            Timestamp = DateTime.Parse(match.Groups[1].Value),
                            LogLevel = match.Groups[2].Value,
                            Uri = match.Groups[3].Value,
                            Request = JsonConvert.DeserializeObject<JObject>(match.Groups[4].Value),
                            Response = JsonConvert.DeserializeObject<JObject>(match.Groups[5].Value),
                            Time = int.Parse(match.Groups[6].Value),
                            Status = match.Groups[7].Value,
                            RawMessage = line
                        };
                        logEntries.Add(entry);

                        foreach (var rule in ruleManager.GetRules())
                        {
                            foreach (var keyField in rule.KeyFields)
                            {
                                string value = entry.Request[keyField.Field]?.Value<string>();
                                if (!string.IsNullOrEmpty(value))
                                {
                                    if (!keyFieldIndex.ContainsKey(keyField.Field))
                                        keyFieldIndex[keyField.Field] = new Dictionary<string, List<LogEntry>>();
                                    if (!keyFieldIndex[keyField.Field].ContainsKey(value))
                                        keyFieldIndex[keyField.Field][value] = new List<LogEntry>();
                                    keyFieldIndex[keyField.Field][value].Add(entry);
                                }
                            }
                        }
                    }
                    catch (Exception ex)
                    {
                        Console.WriteLine($"Failed to parse line: {line}\nError: {ex.Message}");
                    }
                }
            }
        }

        private bool MatchKeyField(KeyFieldCondition condition, string value)
        {
            if (string.IsNullOrEmpty(value))
                return false;

            switch (condition.MatchType)
            {
                case "Exact":
                    return value == condition.Pattern;
                case "Regex":
                    return Regex.IsMatch(value, condition.Pattern);
                case "Contains":
                    return value.Contains(condition.Pattern);
                default:
                    return false;
            }
        }

        public List<AnalysisResult> Analyze()
        {
            var results = new List<AnalysisResult>();

            Parallel.ForEach(logEntries.Where(e => e.Response["isError"]?.Value<bool>() == true),
                entry =>
                {
                    lock (results)
                    {
                        foreach (var rule in ruleManager.GetRules())
                        {
                            if (entry.Uri.EndsWith(rule.TargetApi) && entry.Response["code"]?.Value<string>() == rule.ErrorCode)
                            {
                                var keyValues = new Dictionary<string, string>();
                                bool allKeyFieldsValid = true;

                                foreach (var keyField in rule.KeyFields)
                                {
                                    string keyValue = entry.Request[keyField.Field]?.Value<string>();
                                    if (!MatchKeyField(keyField, keyValue))
                                    {
                                        allKeyFieldsValid = false;
                                        break;
                                    }
                                    keyValues[keyField.Field] = keyValue;
                                }

                                if (!allKeyFieldsValid)
                                    continue;

                                var prereqStatuses = new List<PrerequisiteStatus>();
                                bool hasIssue = false;

                                string orderIssue = null;
                                if (rule.PrerequisiteOrder != null && rule.PrerequisiteOrder.Count > 1)
                                {
                                    for (int i = 1; i < rule.PrerequisiteOrder.Count; i++)
                                    {
                                        var prevApi = rule.PrerequisiteOrder[i - 1];
                                        var currApi = rule.PrerequisiteOrder[i];

                                        var prevCalls = logEntries
                                            .Where(e => e.Uri.EndsWith(prevApi) &&
                                                        rule.KeyFields.All(kf => MatchKeyField(kf, e.Request[kf.Field]?.Value<string>())))
                                            .ToList();
                                        var currCalls = logEntries
                                            .Where(e => e.Uri.EndsWith(currApi) &&
                                                        rule.KeyFields.All(kf => MatchKeyField(kf, e.Request[kf.Field]?.Value<string>())))
                                            .ToList();

                                        if (prevCalls.Any() && currCalls.Any() &&
                                            prevCalls.Max(c => c.Timestamp) < currCalls.Min(c => c.Timestamp))
                                        {
                                            orderIssue = $"Incorrect order: {prevApi} called after {currApi}";
                                            hasIssue = true;
                                            break;
                                        }
                                    }
                                }

                                foreach (var prereqApi in rule.PrerequisiteApis)
                                {
                                    var prereqCalls = new List<LogEntry>();
                                    bool firstKeyField = true;
                                    foreach (var keyField in rule.KeyFields)
                                    {
                                        string keyValue = keyValues[keyField.Field];
                                        if (keyFieldIndex.ContainsKey(keyField.Field) &&
                                            keyFieldIndex[keyField.Field].ContainsKey(keyValue))
                                        {
                                            var candidateCalls = keyFieldIndex[keyField.Field][keyValue]
                                                .Where(e => e.Uri.EndsWith(prereqApi) &&
                                                            e.Timestamp < entry.Timestamp &&
                                                            rule.KeyFields.All(kf => MatchKeyField(kf, e.Request[kf.Field]?.Value<string>())))
                                                .ToList();
                                            prereqCalls = firstKeyField ? candidateCalls : prereqCalls.Intersect(candidateCalls).ToList();
                                            firstKeyField = false;
                                        }
                                        else
                                        {
                                            prereqCalls = new List<LogEntry>();
                                            break;
                                        }
                                    }

                                    var status = new PrerequisiteStatus
                                    {
                                        PrerequisiteApi = prereqApi,
                                        FailedCalls = new List<LogEntry>(),
                                        SuccessfulCallCount = 0,
                                        OrderIssue = orderIssue
                                    };

                                    if (!prereqCalls.Any())
                                    {
                                        status.Status = "NotCalled";
                                        hasIssue = true;
                                    }
                                    else
                                    {
                                        var validCalls = prereqCalls;
                                        if (rule.TimeWindowSeconds.HasValue)
                                        {
                                            var timeWindow = TimeSpan.FromSeconds(rule.TimeWindowSeconds.Value);
                                            validCalls = prereqCalls
                                                .Where(c => entry.Timestamp - c.Timestamp <= timeWindow)
                                                .ToList();
                                        }

                                        var successfulCalls = validCalls
                                            .Where(c => c.Response["isError"]?.Value<bool>() == false)
                                            .ToList();
                                        var failedCalls = validCalls
                                            .Where(c => c.Response["isError"]?.Value<bool>() == true)
                                            .ToList();

                                        status.SuccessfulCallCount = successfulCalls.Count;
                                        status.FailedCalls = failedCalls;

                                        if (successfulCalls.Count >= rule.MinCallCount)
                                        {
                                            status.Status = "Success";
                                        }
                                        else if (validCalls.Any())
                                        {
                                            status.Status = failedCalls.Any() ? "Failed" : "InsufficientCalls";
                                            hasIssue = true;
                                        }
                                        else
                                        {
                                            status.Status = "OutOfTimeWindow";
                                            hasIssue = true;
                                        }
                                    }

                                    prereqStatuses.Add(status);
                                }

                                if (hasIssue)
                                {
                                    var details = new List<string>
                                    {
                                        $"Error in {rule.TargetApi} for {string.Join(", ", keyValues.Select(kv => $"{kv.Key}: '{kv.Value}'"))} likely caused by issues with prerequisite APIs (Rule Source: {rule.Source}, Priority: {rule.Priority}):"
                                    };
                                    foreach (var status in prereqStatuses)
                                    {
                                        details.Add($"- {status.PrerequisiteApi}: {status.Status} (Successful calls: {status.SuccessfulCallCount}, Required: {rule.MinCallCount})");
                                        if (status.Status == "Failed" && status.FailedCalls.Any())
                                        {
                                            details.Add("  Failed calls:");
                                            foreach (var failedCall in status.FailedCalls)
                                            {
                                                details.Add($"    Timestamp: {failedCall.Timestamp}, Error: {failedCall.Response["message"]?.Value<string>()}");
                                            }
                                        }
                                        if (status.OrderIssue != null)
                                        {
                                            details.Add($"  Order Issue: {status.OrderIssue}");
                                        }
                                        if (rule.TimeWindowSeconds.HasValue)
                                        {
                                            details.Add($"  Time Window: Within {rule.TimeWindowSeconds.Value} seconds");
                                        }
                                    }

                                    lock (results)
                                    {
                                        results.Add(new AnalysisResult
                                        {
                                            ErrorEntry = entry,
                                            PrerequisiteStatuses = prereqStatuses,
                                            KeyValues = keyValues,
                                            Details = string.Join("\n", details)
                                        });
                                    }
                                }
                            }
                        }
                    }
                });

            return results.OrderBy(r => r.ErrorEntry.Timestamp).ToList();
        }

        public void GenerateReport(List<AnalysisResult> results, string outputPath)
        {
            using (StreamWriter writer = new StreamWriter(outputPath))
            {
                writer.WriteLine("MES Log Analysis Report");
                writer.WriteLine("=====================");
                writer.WriteLine($"Generated on: 2025-05-17 23:00:00 KST");
                writer.WriteLine($"Total Errors Analyzed: {results.Count}");
                writer.WriteLine();

                foreach (var result in results)
                {
                    writer.WriteLine($"Error Timestamp: {result.ErrorEntry.Timestamp}");
                    writer.WriteLine($"Error API: {result.ErrorEntry.Uri}");
                    writer.WriteLine($"Error Message: {result.ErrorEntry.Response["message"]?.Value<string>()}");
                    writer.WriteLine($"Key Field Values: {string.Join(", ", result.KeyValues.Select(kv => $"{kv.Key}: {kv.Value}"))}");
                    writer.WriteLine($"Details:\n{result.Details}");
                    writer.WriteLine($"Raw Log: {result.ErrorEntry.RawMessage}");
                    writer.WriteLine();
                }

                if (results.Count == 0)
                    writer.WriteLine("No errors matched the defined dependency rules.");
            }
        }
    }

    public class Startup
    {
        public void ConfigureServices(IServiceCollection services)
        {
            services.AddSingleton<RuleManager>();
        }

        public void Configure(IApplicationBuilder app, IWebHostEnvironment env, RuleManager ruleManager)
        {
            app.UseRouting();
            app.UseEndpoints(endpoints =>
            {
                endpoints.MapPost("/api/rules", async context =>
                {
                    try
                    {
                        using (var reader = new StreamReader(context.Request.Body))
                        {
                            var json = await reader.ReadToEndAsync();
                            var rules = JsonConvert.DeserializeObject<List<DependencyRule>>(json);
                            ruleManager.AddOrUpdateRules(rules, "API");
                            context.Response.StatusCode = 200;
                            await context.Response.WriteAsync("Rules updated successfully");
                        }
                    }
                    catch (Exception ex)
                    {
                        context.Response.StatusCode = 400;
                        await context.Response.WriteAsync($"Error: {ex.Message}");
                    }
                });

                endpoints.MapDelete("/api/rules/{targetApi}/{errorCode}", async context =>
                {
                    var targetApi = context.Request.RouteValues["targetApi"].ToString();
                    var errorCode = context.Request.RouteValues["errorCode"].ToString();
                    ruleManager.RemoveRule(targetApi, errorCode);
                    context.Response.StatusCode = 200;
                    await context.Response.WriteAsync("Rule removed successfully");
                });

                endpoints.MapGet("/api/rules", async context =>
                {
                    var rules = ruleManager.GetRules();
                    context.Response.ContentType = "application/json";
                    await context.Response.WriteAsync(JsonConvert.SerializeObject(rules));
                });
            });
        }
    }

    public class Program
    {
        public static async Task Main(string[] args)
        {
            if (args.Length < 3)
            {
                Console.WriteLine("Usage: DynamicMESLogAnalyzer <logFilePath> <configFilePath> <outputReportPath> [--temp-rules <json>]");
                return;
            }

            string logFilePath = args[0];
            string configFilePath = args[1];
            string outputPath = args[2];
            string tempRulesJson = null;

            if (args.Length > 3 && args[3] == "--temp-rules" && args.Length > 4)
            {
                tempRulesJson = args[4];
            }

            try
            {
                // Start API server
                var host = new WebHostBuilder()
                    .UseKestrel()
                    .UseStartup<Startup>()
                    .ConfigureServices(services =>
                    {
                        services.AddSingleton(new RuleManager(configFilePath));
                    })
                    .UseUrls("http://localhost:5000")
                    .Build();

                var apiTask = host.RunAsync();

                // Run analyzer
                var analyzer = new MESLogAnalyzer(configFilePath);
                if (!string.IsNullOrEmpty(tempRulesJson))
                {
                    analyzer.LoadTempRules(tempRulesJson);
                }
                analyzer.LoadLogFile(logFilePath);
                var results = analyzer.Analyze();
                analyzer.GenerateReport(results, outputPath);

                Console.WriteLine($"Analysis complete. Report generated at {outputPath}");
                Console.WriteLine("API server running at http://localhost:5000");

                await apiTask;
            }
            catch (Exception ex)
            {
                Console.WriteLine($"Error: {ex.Message}");
            }
        }
    }
}
```

 配置文件示例
扩展`config.json`,添加`Priority`字段:

```json
[
    {
        "TargetApi": "/api/eapmanagement/ProductOut/CheckProductOut",
        "PrerequisiteApis": [
            "/api/eapmanagement/TraySN/TraySNBind",
            "/api/eapmanagement/FixtureWithDut/FixtureBindDut"
        ],
        "KeyFields": [
            { "Field": "TraySn", "MatchType": "Regex", "Pattern": "^G-1.*" },
            { "Field": "LotSn", "MatchType": "Contains", "Pattern": "P2519" }
        ],
        "ErrorCode": "EAPManagement:04448",
        "TimeWindowSeconds": 3600,
        "MinCallCount": 1,
        "PrerequisiteOrder": [
            "/api/eapmanagement/TraySN/TraySNBind",
            "/api/eapmanagement/FixtureWithDut/FixtureBindDut"
        ],
        "Priority": 0
    },
    {
        "TargetApi": "/api/eapmanagement/ProductIn/CheckProductIn",
        "PrerequisiteApis": [
            "/api/eapmanagement/FixtureWithDut/FixtureUnBindEquip"
        ],
        "KeyFields": [
            { "Field": "TraySn", "MatchType": "Exact", "Pattern": "G-1-V400L-W2-H-2024-2-008" }
        ],
        "ErrorCode": "EAPManagement:04448",
        "TimeWindowSeconds": 7200,
        "MinCallCount": 2,
        "Priority": 0
    }
]
```

 API使用示例
1. 添加/更新规则:
   ```bash
   curl -X POST http://localhost:5000/api/rules \
   -H "Content-Type: application/json" \
   -d '[{"TargetApi":"/api/eapmanagement/Parameter/UploadParameterDatas","PrerequisiteApis":["/api/eapmanagement/TraySN/TraySNBind"],"KeyFields":[{"Field":"LotSn","MatchType":"Exact","Pattern":"P25190002"}],"ErrorCode":"EAPManagement:04448","MinCallCount":1,"Priority":10}]'
   ```
   - 响应:`Rules updated successfully`

2. 删除规则:
   ```bash
   curl -X DELETE http://localhost:5000/api/rules/%2Fapi%2Feapmanagement%2FParameter%2FUploadParameterDatas/EAPManagement:04448
   ```
   - 响应:`Rule removed successfully`

3. 获取当前规则:
   ```bash
   curl http://localhost:5000/api/rules
   ```
   - 响应:当前所有规则的JSON列表

 使用方法
1. 环境准备:
   - 安装.NET SDK。
   - 添加NuGet包:`Newtonsoft.Json`、`Microsoft.AspNetCore.App`。

2. 准备配置文件:
   - 创建`config.json`,定义规则,指定`Priority`。

3. 编译和运行:
   - 保存代码为`DynamicMESLogAnalyzer.cs`。
   - 编译并运行:
     ```bash
     dotnet run MES.log config.json output_report.txt
     ```
     - 可选:添加临时规则:
       ```bash
       dotnet run MES.log config.json output_report.txt --temp-rules '<json>'
       ```

4. 使用API:
   - 程序启动后,API服务器运行在`http://localhost:5000`。
   - 使用`curl`或其他HTTP客户端推送规则。

5. 输出:
   - 报告列出错误详情,包括规则来源和优先级。

 示例输出
基于提供的MES日志和配置,报告可能如下:

```
MES Log Analysis Report
=====================
Generated on: 2025-05-17 23:00:00 KST
Total Errors Analyzed: 2

Error Timestamp: 2025-05-16 21:55:05
Error API: http://172.16.6.101:44388/api/eapmanagement/ProductOut/CheckProductOut
Error Message: 该托盘G-1-V400L-W2-H-2024-2-008没有需要出站的器件!
Key Field Values: TraySn: G-1-V400L-W2-H-2024-2-008, LotSn: P25190002
Details:
Error in /api/eapmanagement/ProductOut/CheckProductOut for TraySn: 'G-1-V400L-W2-H-2024-2-008', LotSn: 'P25190002' likely caused by issues with prerequisite APIs (Rule Source: ConfigFile, Priority: 0):
- /api/eapmanagement/TraySN/TraySNBind: NotCalled (Successful calls: 0, Required: 1)
  Time Window: Within 3600 seconds
- /api/eapmanagement/FixtureWithDut/FixtureBindDut: NotCalled (Successful calls: 0, Required: 1)
  Time Window: Within 3600 seconds
  Order Issue: Incorrect order: /api/eapmanagement/TraySN/TraySNBind called after /api/eapmanagement/FixtureWithDut/FixtureBindDut
Raw Log: [2025-05-16 21:55:05,908] INFO - PostSync,uri:http://172.16.6.101:44388/api/eapmanagement/ProductOut/CheckProductOut,...

Error Timestamp: 2025-05-16 10:37:09
Error API: http://172.16.6.101:44388/api/eapmanagement/Parameter/UploadParameterDatas
Error Message: 设备8H-ECHJZZ-01未在产品工艺工序中绑定!
Key Field Values: LotSn: P25190002
Details:
Error in /api/eapmanagement/Parameter/UploadParameterDatas for LotSn: 'P25190002' likely caused by issues with prerequisite APIs (Rule Source: API, Priority: 10):
- /api/eapmanagement/TraySN/TraySNBind: Failed (Successful calls: 0, Required: 1)
  Failed calls:
    Timestamp: 2025-05-16 08:42:29, Error: 托盘G-1-1-H-2024-11-013未绑定器件!
Raw Log: [2025-05-16 10:37:09,009] INFO - PostSync,uri:http://172.16.6.101:44388/api/eapmanagement/Parameter/UploadParameterDatas,...
```

 代码说明
1. 动态规则管理:
   - API接口:
     - 使用ASP.NET Core实现轻量级API,端点包括:
       - `POST /api/rules`:添加或更新规则。
       - `DELETE /api/rules/{targetApi}/{errorCode}`:删除规则。
       - `GET /api/rules`:获取当前规则。
     - API运行在`http://localhost:5000`,与主程序并行。
   - 规则优先级:
     - `DependencyRule`添加`Priority`字段,默认0(配置文件)。
     - 临时规则(命令行或API)可设高优先级(如10)。
     - `RuleManager.GetRules`按优先级降序排序,确保高优先级规则优先匹配。

2. RuleManager:
   - 使用`ConcurrentDictionary`存储规则,键为`TargetApi:ErrorCode`,确保线程安全。
   - `AddOrUpdateRules`:添加或更新规则,设置`Source`(`API`或`CommandLine`)。
   - `RemoveRule`:删除指定规则。
   - `LoadConfig`:加载配置文件规则,覆盖已有`ConfigFile`来源规则。

3. 集成与分析:
   - `MESLogAnalyzer`使用`RuleManager`获取规则,保持之前的功能(复杂条件、性能优化)。
   - 分析时遍历按优先级排序的规则,优先匹配高优先级规则。

4. 报告增强:
   - 显示规则的`Source`和`Priority`。
   - 注明使用的规则来源(如`API`推送的规则)。

 中文解释
 功能改进
- API接口:
  - 提供HTTP API,允许外部系统(如MES管理平台)动态推送规则。
  - 支持添加、删除和查询规则,规则格式与`config.json`一致。
  - 例如,推送高优先级规则以覆盖配置文件规则,用于临时调试。
- 规则优先级:
  - 每条规则有`Priority`字段,高优先级规则优先匹配。
  - 配置文件规则默认`Priority: 0`,API或命令行规则可设更高优先级(如`10`)。
  - 确保临时规则(如API推送)覆盖静态配置。

 代码变化
- RuleManager:
  - 新增类管理规则,使用`ConcurrentDictionary`存储。
  - 支持动态加载、API更新和优先级排序。
- API服务:
  - 使用ASP.NET Core Kestrel服务器,提供`/api/rules`端点。
  - 异步处理请求,确保不阻塞主分析流程。
- DependencyRule:
  - 添加`Priority`字段,影响规则匹配顺序。
- Analyze:
  - 使用`ruleManager.GetRules()`获取按优先级排序的规则。
- GenerateReport:
  - 显示规则的`Source`和`Priority`。

 使用场景
- API推送规则:
  - MES系统检测到新错误模式,通过API推送规则,指定高优先级(如`Priority: 10`)。
  - 例如,针对特定`LotSn`的临时规则,无需修改`config.json`。
- 规则优先级:
  - 配置文件定义通用规则(`Priority: 0`),API推送特定规则(`Priority: 10`)覆盖。
  - 确保临时调试规则优先于静态配置。
- 动态管理:
  - 外部系统定期更新规则,工具自动应用新规则,无需重启。

 扩展功能
1. API增强:
   - 添加身份验证(如API密钥)限制规则推送。
   - 支持批量操作(如一次性删除多条规则)。
2. 规则验证:
   - 在API推送时验证规则格式(如`KeyFields`的`Pattern`合法性)。
   - 提供规则冲突检测(如相同`TargetApi`和`ErrorCode`的规则)。
3. 优先级策略:
   - 支持动态优先级调整(如基于错误频率自动提升规则优先级)。
   - 添加规则有效期,过期规则自动移除。
4. 性能优化:
   - 使用缓存存储高频规则,减少排序开销。
   - 实现规则分片,分布式处理超大规则集。

 注意事项
- API安全:
  - 当前API无身份验证,生产环境需添加认证机制。
  - 限制API访问来源(如IP白名单)。
- 规则冲突:
  - 相同`TargetApi:ErrorCode`的规则会被覆盖,确保API推送的规则优先级明确。
- 性能:
  - 高并发API请求可能影响规则更新,需优化`ConcurrentDictionary`操作。
  - 大量规则可能增加`Analyze`时间,建议限制规则数量。
- 配置文件:
  - 确保`Priority`值合理,API规则优先级需高于配置文件。

这个工具通过API接口和规则优先级支持动态规则管理,极大提升了灵活性和可扩展性,适合与MES系统集成,实时适配新的错误分析需求。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

zhxup606

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值
OSZAR »