From a0c89cac5261221b709d446fc1479c5b85cc7622 Mon Sep 17 00:00:00 2001 From: sladro Date: Mon, 2 Mar 2026 14:18:58 +0800 Subject: [PATCH] feat: enhance task management with execution ID support and add callback service --- (.NET Framework).csproj | 6 +- Controllers/ExportController.cs | 2 +- Controllers/FileController.cs | 4 +- Controllers/ShellController.cs | 4 +- Controllers/StatsController.cs | 4 +- Models/ExportModels.cs | 7 ++ Models/FileModels.cs | 9 ++- Models/OverviewModels.cs | 14 ++++ Models/ShellModels.cs | 15 +++- Models/StatsModels.cs | 9 ++- Models/TaskModels.cs | 37 ++++++++- Services/RevitService.cs | 135 +++++++++++++++++++++++++++++++- Services/TaskCallbackService.cs | 100 +++++++++++++++++++++++ Services/TaskManager.cs | 31 +++++++- app.config | 9 ++- 15 files changed, 366 insertions(+), 20 deletions(-) create mode 100644 Services/TaskCallbackService.cs diff --git a/(.NET Framework).csproj b/(.NET Framework).csproj index b4e7e6a..8b26c66 100644 --- a/(.NET Framework).csproj +++ b/(.NET Framework).csproj @@ -1,4 +1,4 @@ - + @@ -80,6 +80,7 @@ False + packages\System.Buffers.4.5.1\lib\netstandard1.1\System.Buffers.dll @@ -143,6 +144,7 @@ + @@ -154,4 +156,4 @@ - \ No newline at end of file + diff --git a/Controllers/ExportController.cs b/Controllers/ExportController.cs index a59e0bc..ae345f5 100644 --- a/Controllers/ExportController.cs +++ b/Controllers/ExportController.cs @@ -196,7 +196,7 @@ namespace RevitHttpControl.Controllers } // 创建异步任务 - var taskId = TaskManager.Instance.CreateTask(); + var taskId = TaskManager.Instance.CreateTask(request.ExecutionId); TaskManager.Instance.SetTaskRunning(taskId); // 异步执行导出操作 diff --git a/Controllers/FileController.cs b/Controllers/FileController.cs index 0fbb0dd..84ac9de 100644 --- a/Controllers/FileController.cs +++ b/Controllers/FileController.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Net; using System.Net.Http; using System.Web.Http; @@ -150,7 +150,7 @@ namespace RevitHttpControl.Controllers } // 创建异步任务 - var taskId = TaskManager.Instance.CreateTask(); + var taskId = TaskManager.Instance.CreateTask(request.ExecutionId); TaskManager.Instance.SetTaskRunning(taskId); // 异步执行打开文件操作 diff --git a/Controllers/ShellController.cs b/Controllers/ShellController.cs index 5517102..dfb267d 100644 --- a/Controllers/ShellController.cs +++ b/Controllers/ShellController.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Net; using System.Net.Http; using System.Web.Http; @@ -126,7 +126,7 @@ namespace RevitHttpControl.Controllers } // 创建异步任务 - var taskId = TaskManager.Instance.CreateTask(); + var taskId = TaskManager.Instance.CreateTask(request.ExecutionId); TaskManager.Instance.SetTaskRunning(taskId); // 异步执行薄壳优化 diff --git a/Controllers/StatsController.cs b/Controllers/StatsController.cs index 28b5c42..146df87 100644 --- a/Controllers/StatsController.cs +++ b/Controllers/StatsController.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Net; using System.Net.Http; using System.Web.Http; @@ -114,7 +114,7 @@ namespace RevitHttpControl.Controllers } // 创建异步任务 - var taskId = TaskManager.Instance.CreateTask(); + var taskId = TaskManager.Instance.CreateTask(request.ExecutionId); TaskManager.Instance.SetTaskRunning(taskId); // 异步执行统计操作 diff --git a/Models/ExportModels.cs b/Models/ExportModels.cs index 083e9df..0f21ee0 100644 --- a/Models/ExportModels.cs +++ b/Models/ExportModels.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using Newtonsoft.Json; namespace RevitHttpControl.Models { @@ -8,6 +9,12 @@ namespace RevitHttpControl.Models /// public class ExportIfcRequest { + /// + /// 外部执行ID(可选,用于批处理回调) + /// + [JsonProperty("execution_id")] + public string ExecutionId { get; set; } + /// /// 输出文件路径(可选,如果不指定则自动生成) /// diff --git a/Models/FileModels.cs b/Models/FileModels.cs index 504b715..1d5ae79 100644 --- a/Models/FileModels.cs +++ b/Models/FileModels.cs @@ -1,4 +1,5 @@ -using System; +using System; +using Newtonsoft.Json; namespace RevitHttpControl.Models { @@ -7,6 +8,12 @@ namespace RevitHttpControl.Models /// public class OpenFileRequest { + /// + /// 外部执行ID(可选,用于批处理回调) + /// + [JsonProperty("execution_id")] + public string ExecutionId { get; set; } + /// /// 文件路径 /// diff --git a/Models/OverviewModels.cs b/Models/OverviewModels.cs index 838064a..10011d3 100644 --- a/Models/OverviewModels.cs +++ b/Models/OverviewModels.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; +using Newtonsoft.Json; namespace RevitHttpControl.Models { @@ -75,12 +76,25 @@ namespace RevitHttpControl.Models /// /// 文件大小(字节) /// + [JsonProperty("fileSize")] public long FileSizeBytes { get; set; } /// /// 文件大小(格式化显示) /// public string FileSizeDisplay { get; set; } + + /// + /// 面片数 + /// + [JsonProperty("polygonCount")] + public long PolygonCount { get; set; } + + /// + /// 模型特征数 + /// + [JsonProperty("featureCount")] + public int FeatureCount { get; set; } } /// diff --git a/Models/ShellModels.cs b/Models/ShellModels.cs index 4082ee5..90dac1c 100644 --- a/Models/ShellModels.cs +++ b/Models/ShellModels.cs @@ -1,8 +1,9 @@ -using System; +using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; +using Newtonsoft.Json; namespace RevitHttpControl.Models { @@ -38,6 +39,12 @@ namespace RevitHttpControl.Models /// public class ShellExecuteRequest { + /// + /// 外部执行ID(可选,用于批处理回调) + /// + [JsonProperty("execution_id")] + public string ExecutionId { get; set; } + /// /// 优化模式 /// @@ -54,6 +61,12 @@ namespace RevitHttpControl.Models /// public class ShellCustomExecuteRequest { + /// + /// 外部执行ID(可选,用于批处理回调) + /// + [JsonProperty("execution_id")] + public string ExecutionId { get; set; } + /// /// 要删除的类别ID列表(BuiltInCategory 的 int 值) /// diff --git a/Models/StatsModels.cs b/Models/StatsModels.cs index ee54c6d..01c9304 100644 --- a/Models/StatsModels.cs +++ b/Models/StatsModels.cs @@ -1,4 +1,5 @@ -using System; +using System; +using Newtonsoft.Json; namespace RevitHttpControl.Models { @@ -7,6 +8,12 @@ namespace RevitHttpControl.Models /// public class StatsRequest { + /// + /// 外部执行ID(可选,用于批处理回调) + /// + [JsonProperty("execution_id")] + public string ExecutionId { get; set; } + /// /// 统计类型 /// diff --git a/Models/TaskModels.cs b/Models/TaskModels.cs index ec66e91..35ae592 100644 --- a/Models/TaskModels.cs +++ b/Models/TaskModels.cs @@ -1,4 +1,5 @@ -using System; +using System; +using Newtonsoft.Json; namespace RevitHttpControl.Models { @@ -7,6 +8,11 @@ namespace RevitHttpControl.Models /// public class TaskStatusResponse { + /// + /// 外部执行ID(用于批处理回调) + /// + public string ExecutionId { get; set; } + /// /// 任务ID /// @@ -84,4 +90,31 @@ namespace RevitHttpControl.Models /// public string StatusUrl { get; set; } } -} + + /// + /// 批处理任务结果回调请求体 + /// + public class TaskResultCallbackRequest + { + [JsonProperty("execution_id")] + public string ExecutionId { get; set; } + + [JsonProperty("software_id")] + public string SoftwareId { get; set; } + + [JsonProperty("status")] + public string Status { get; set; } + + [JsonProperty("error_message")] + public string ErrorMessage { get; set; } + + [JsonProperty("result")] + public object Result { get; set; } + + [JsonProperty("finished_at")] + public string FinishedAt { get; set; } + + [JsonProperty("token")] + public string Token { get; set; } + } +} \ No newline at end of file diff --git a/Services/RevitService.cs b/Services/RevitService.cs index e395894..d9eb7d5 100644 --- a/Services/RevitService.cs +++ b/Services/RevitService.cs @@ -428,7 +428,9 @@ namespace RevitHttpControl.Services IsCentralFile = doc.IsWorkshared, Status = doc.IsModified ? "已修改" : "已保存", FileSizeBytes = fileSizeBytes, - FileSizeDisplay = fileSizeDisplay + FileSizeDisplay = fileSizeDisplay, + PolygonCount = CalculatePolygonCountFast(doc, maxElements: 200, maxMilliseconds: 1200), + FeatureCount = CalculateFeatureCount(doc) }; // 2. 快速统计主要构件(批量获取避免多次调用) @@ -609,6 +611,137 @@ namespace RevitHttpControl.Services } } + /// + /// 计算模型面片总数(基于可三角化几何) + /// + /// Revit文档 + /// 面片总数 + private static long CalculatePolygonCountFast(Document doc, int maxElements, int maxMilliseconds) + { + long totalTriangles = 0; + var processedElements = 0; + var stopwatch = System.Diagnostics.Stopwatch.StartNew(); + + try + { + var options = new Options + { + ComputeReferences = false, + IncludeNonVisibleObjects = false, + DetailLevel = ViewDetailLevel.Medium + }; + + var elements = new FilteredElementCollector(doc) + .WhereElementIsNotElementType() + .Where(e => e.Category != null && e.Category.CategoryType == CategoryType.Model); + + foreach (var element in elements) + { + if (processedElements >= maxElements || stopwatch.ElapsedMilliseconds >= maxMilliseconds) + { + break; + } + + GeometryElement geometry = null; + try + { + geometry = element.get_Geometry(options); + } + catch + { + continue; + } + + if (geometry == null) + continue; + + totalTriangles += CountTrianglesFromGeometry(geometry); + processedElements++; + } + } + catch + { + // 忽略计算异常并返回已累计值 + } + + return totalTriangles; + } + + /// + /// 统计模型特征数(按模型类别去重) + /// + /// Revit文档 + /// 模型特征数 + private static int CalculateFeatureCount(Document doc) + { + try + { + return new FilteredElementCollector(doc) + .WhereElementIsNotElementType() + .Where(e => e.Category != null && e.Category.CategoryType == CategoryType.Model) + .Select(e => e.Category.Id.IntegerValue) + .Distinct() + .Count(); + } + catch + { + return 0; + } + } + + /// + /// 递归统计几何中的三角面片数量 + /// + /// 几何对象集合 + /// 三角面片数量 + private static long CountTrianglesFromGeometry(GeometryElement geometry) + { + long triangles = 0; + + foreach (var obj in geometry) + { + if (obj is Solid solid && solid.Faces != null && solid.Faces.Size > 0) + { + foreach (Face face in solid.Faces) + { + try + { + var mesh = face.Triangulate(); + if (mesh != null) + triangles += mesh.NumTriangles; + } + catch + { + // 忽略单个面三角化失败 + } + } + continue; + } + + if (obj is Mesh meshObj) + { + triangles += meshObj.NumTriangles; + continue; + } + + if (obj is GeometryInstance instance) + { + try + { + var instanceGeometry = instance.GetInstanceGeometry(); + if (instanceGeometry != null) + triangles += CountTrianglesFromGeometry(instanceGeometry); + } + catch + { + // 忽略单个实例解析失败 + } + } + } + + return triangles; + } + /// /// 获取文档文件大小(字节) /// diff --git a/Services/TaskCallbackService.cs b/Services/TaskCallbackService.cs new file mode 100644 index 0000000..19fa0b6 --- /dev/null +++ b/Services/TaskCallbackService.cs @@ -0,0 +1,100 @@ +using System; +using System.Configuration; +using System.Net.Http; +using System.Text; +using System.Threading; +using Newtonsoft.Json; +using RevitHttpControl.Models; + +namespace RevitHttpControl.Services +{ + /// + /// 批处理任务结果回调服务 + /// + public static class TaskCallbackService + { + private const string DefaultEndpointPath = "/api/v1/plugin-callbacks/task-result"; + private const string DefaultSoftwareId = "revit"; + private static readonly TimeSpan[] RetryDelays = + { + TimeSpan.FromSeconds(1), + TimeSpan.FromSeconds(3), + TimeSpan.FromSeconds(5) + }; + + public static void NotifyTaskResult(TaskStatusResponse task) + { + if (task == null || (task.Status != TaskStatus.Completed && task.Status != TaskStatus.Failed)) + { + return; + } + + var baseAddress = ConfigurationManager.AppSettings["PluginCallbackBaseUrl"]; + var token = ConfigurationManager.AppSettings["PluginCallbackToken"]; + var softwareId = ConfigurationManager.AppSettings["PluginSoftwareId"]; + + if (string.IsNullOrWhiteSpace(baseAddress) || string.IsNullOrWhiteSpace(token)) + { + return; + } + + if (string.IsNullOrWhiteSpace(softwareId)) + { + softwareId = DefaultSoftwareId; + } + + var callbackUrl = $"{baseAddress.TrimEnd('/')}{DefaultEndpointPath}"; + var payload = new TaskResultCallbackRequest + { + ExecutionId = task.ExecutionId, + SoftwareId = softwareId, + Status = task.Status == TaskStatus.Completed ? "success" : "failed", + ErrorMessage = task.Status == TaskStatus.Failed + ? (string.IsNullOrWhiteSpace(task.ErrorMessage) ? "UNKNOWN_ERROR" : task.ErrorMessage) + : null, + Result = task.Status == TaskStatus.Completed ? (task.Result ?? new { }) : new { }, + FinishedAt = (task.CompletedAt ?? DateTime.UtcNow).ToString("o"), + Token = token + }; + + SendWithRetry(callbackUrl, payload); + } + + private static void SendWithRetry(string callbackUrl, TaskResultCallbackRequest payload) + { + var json = JsonConvert.SerializeObject(payload); + using (var httpClient = new HttpClient()) + { + if (Post(httpClient, callbackUrl, json)) + { + return; + } + + foreach (var delay in RetryDelays) + { + Thread.Sleep(delay); + if (Post(httpClient, callbackUrl, json)) + { + return; + } + } + } + } + + private static bool Post(HttpClient httpClient, string callbackUrl, string json) + { + try + { + using (var content = new StringContent(json, Encoding.UTF8, "application/json")) + using (var response = httpClient.PostAsync(callbackUrl, content).GetAwaiter().GetResult()) + { + return response.IsSuccessStatusCode; + } + } + catch + { + return false; + } + } + } +} diff --git a/Services/TaskManager.cs b/Services/TaskManager.cs index d71bb3a..73b0c34 100644 --- a/Services/TaskManager.cs +++ b/Services/TaskManager.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Linq; @@ -46,17 +46,21 @@ namespace RevitHttpControl.Services /// 创建新任务 /// /// 任务ID - public Guid CreateTask() + public Guid CreateTask(string executionId = null) { // 检查是否需要清理过期任务 CheckAndCleanupIfNeeded(); var taskId = Guid.NewGuid(); + var effectiveExecutionId = string.IsNullOrWhiteSpace(executionId) + ? taskId.ToString() + : executionId; var task = new TaskStatusResponse { TaskId = taskId, Status = TaskStatus.Pending, - CreatedAt = DateTime.UtcNow + CreatedAt = DateTime.UtcNow, + ExecutionId = effectiveExecutionId }; _tasks.TryAdd(taskId, task); @@ -100,6 +104,7 @@ namespace RevitHttpControl.Services task.Status = TaskStatus.Completed; task.Result = result; task.CompletedAt = DateTime.UtcNow; + NotifyTaskResultInBackground(task); } } @@ -115,9 +120,29 @@ namespace RevitHttpControl.Services task.Status = TaskStatus.Failed; task.ErrorMessage = errorMessage; task.CompletedAt = DateTime.UtcNow; + NotifyTaskResultInBackground(task); } } + /// + /// 后台触发任务结果回调,避免阻塞调用线程 + /// + /// 任务状态 + private static void NotifyTaskResultInBackground(TaskStatusResponse task) + { + System.Threading.Tasks.Task.Run(() => + { + try + { + TaskCallbackService.NotifyTaskResult(task); + } + catch + { + // 回调失败不影响主流程 + } + }); + } + /// /// 取消任务 /// diff --git a/app.config b/app.config index c860d87..1729791 100644 --- a/app.config +++ b/app.config @@ -1,5 +1,10 @@ - + + + + + + @@ -12,4 +17,4 @@ - \ No newline at end of file +