要进一步增强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系统集成,实时适配新的错误分析需求。