feat: enhance task management with execution ID support and add callback service
This commit is contained in:
parent
660c3bcd6f
commit
a0c89cac52
@ -1,4 +1,4 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
|
||||
<PropertyGroup>
|
||||
@ -80,6 +80,7 @@
|
||||
<Private>False</Private>
|
||||
</Reference>
|
||||
<Reference Include="System" />
|
||||
<Reference Include="System.Configuration" />
|
||||
<Reference Include="System.Buffers, Version=4.0.3.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51, processorArchitecture=MSIL">
|
||||
<HintPath>packages\System.Buffers.4.5.1\lib\netstandard1.1\System.Buffers.dll</HintPath>
|
||||
</Reference>
|
||||
@ -143,6 +144,7 @@
|
||||
<Compile Include="Services\RevitService.cs" />
|
||||
<Compile Include="Services\DocumentService.cs" />
|
||||
<Compile Include="Services\TaskManager.cs" />
|
||||
<Compile Include="Services\TaskCallbackService.cs" />
|
||||
<Compile Include="Services\ShellAnalyzer.cs" />
|
||||
<Compile Include="Services\ShellOptimizer.cs" />
|
||||
<!-- Common -->
|
||||
@ -154,4 +156,4 @@
|
||||
<None Include="packages.config" />
|
||||
</ItemGroup>
|
||||
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
|
||||
</Project>
|
||||
</Project>
|
||||
|
||||
@ -196,7 +196,7 @@ namespace RevitHttpControl.Controllers
|
||||
}
|
||||
|
||||
// 创建异步任务
|
||||
var taskId = TaskManager.Instance.CreateTask();
|
||||
var taskId = TaskManager.Instance.CreateTask(request.ExecutionId);
|
||||
TaskManager.Instance.SetTaskRunning(taskId);
|
||||
|
||||
// 异步执行导出操作
|
||||
|
||||
@ -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);
|
||||
|
||||
// 异步执行打开文件操作
|
||||
|
||||
@ -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);
|
||||
|
||||
// 异步执行薄壳优化
|
||||
|
||||
@ -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);
|
||||
|
||||
// 异步执行统计操作
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace RevitHttpControl.Models
|
||||
{
|
||||
@ -8,6 +9,12 @@ namespace RevitHttpControl.Models
|
||||
/// </summary>
|
||||
public class ExportIfcRequest
|
||||
{
|
||||
/// <summary>
|
||||
/// 外部执行ID(可选,用于批处理回调)
|
||||
/// </summary>
|
||||
[JsonProperty("execution_id")]
|
||||
public string ExecutionId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 输出文件路径(可选,如果不指定则自动生成)
|
||||
/// </summary>
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
using System;
|
||||
using System;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace RevitHttpControl.Models
|
||||
{
|
||||
@ -7,6 +8,12 @@ namespace RevitHttpControl.Models
|
||||
/// </summary>
|
||||
public class OpenFileRequest
|
||||
{
|
||||
/// <summary>
|
||||
/// 外部执行ID(可选,用于批处理回调)
|
||||
/// </summary>
|
||||
[JsonProperty("execution_id")]
|
||||
public string ExecutionId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 文件路径
|
||||
/// </summary>
|
||||
|
||||
@ -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
|
||||
/// <summary>
|
||||
/// 文件大小(字节)
|
||||
/// </summary>
|
||||
[JsonProperty("fileSize")]
|
||||
public long FileSizeBytes { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 文件大小(格式化显示)
|
||||
/// </summary>
|
||||
public string FileSizeDisplay { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 面片数
|
||||
/// </summary>
|
||||
[JsonProperty("polygonCount")]
|
||||
public long PolygonCount { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 模型特征数
|
||||
/// </summary>
|
||||
[JsonProperty("featureCount")]
|
||||
public int FeatureCount { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@ -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
|
||||
/// </summary>
|
||||
public class ShellExecuteRequest
|
||||
{
|
||||
/// <summary>
|
||||
/// 外部执行ID(可选,用于批处理回调)
|
||||
/// </summary>
|
||||
[JsonProperty("execution_id")]
|
||||
public string ExecutionId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 优化模式
|
||||
/// </summary>
|
||||
@ -54,6 +61,12 @@ namespace RevitHttpControl.Models
|
||||
/// </summary>
|
||||
public class ShellCustomExecuteRequest
|
||||
{
|
||||
/// <summary>
|
||||
/// 外部执行ID(可选,用于批处理回调)
|
||||
/// </summary>
|
||||
[JsonProperty("execution_id")]
|
||||
public string ExecutionId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 要删除的类别ID列表(BuiltInCategory 的 int 值)
|
||||
/// </summary>
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
using System;
|
||||
using System;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace RevitHttpControl.Models
|
||||
{
|
||||
@ -7,6 +8,12 @@ namespace RevitHttpControl.Models
|
||||
/// </summary>
|
||||
public class StatsRequest
|
||||
{
|
||||
/// <summary>
|
||||
/// 外部执行ID(可选,用于批处理回调)
|
||||
/// </summary>
|
||||
[JsonProperty("execution_id")]
|
||||
public string ExecutionId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 统计类型
|
||||
/// </summary>
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
using System;
|
||||
using System;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace RevitHttpControl.Models
|
||||
{
|
||||
@ -7,6 +8,11 @@ namespace RevitHttpControl.Models
|
||||
/// </summary>
|
||||
public class TaskStatusResponse
|
||||
{
|
||||
/// <summary>
|
||||
/// 外部执行ID(用于批处理回调)
|
||||
/// </summary>
|
||||
public string ExecutionId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 任务ID
|
||||
/// </summary>
|
||||
@ -84,4 +90,31 @@ namespace RevitHttpControl.Models
|
||||
/// </summary>
|
||||
public string StatusUrl { get; set; }
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 批处理任务结果回调请求体
|
||||
/// </summary>
|
||||
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; }
|
||||
}
|
||||
}
|
||||
@ -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
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 计算模型面片总数(基于可三角化几何)
|
||||
/// </summary>
|
||||
/// <param name="doc">Revit文档</param>
|
||||
/// <returns>面片总数</returns>
|
||||
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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 统计模型特征数(按模型类别去重)
|
||||
/// </summary>
|
||||
/// <param name="doc">Revit文档</param>
|
||||
/// <returns>模型特征数</returns>
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 递归统计几何中的三角面片数量
|
||||
/// </summary>
|
||||
/// <param name="geometry">几何对象集合</param>
|
||||
/// <returns>三角面片数量</returns>
|
||||
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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取文档文件大小(字节)
|
||||
/// </summary>
|
||||
|
||||
100
Services/TaskCallbackService.cs
Normal file
100
Services/TaskCallbackService.cs
Normal file
@ -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
|
||||
{
|
||||
/// <summary>
|
||||
/// 批处理任务结果回调服务
|
||||
/// </summary>
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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
|
||||
/// 创建新任务
|
||||
/// </summary>
|
||||
/// <returns>任务ID</returns>
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 后台触发任务结果回调,避免阻塞调用线程
|
||||
/// </summary>
|
||||
/// <param name="task">任务状态</param>
|
||||
private static void NotifyTaskResultInBackground(TaskStatusResponse task)
|
||||
{
|
||||
System.Threading.Tasks.Task.Run(() =>
|
||||
{
|
||||
try
|
||||
{
|
||||
TaskCallbackService.NotifyTaskResult(task);
|
||||
}
|
||||
catch
|
||||
{
|
||||
// 回调失败不影响主流程
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 取消任务
|
||||
/// </summary>
|
||||
|
||||
@ -1,5 +1,10 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<configuration>
|
||||
<appSettings>
|
||||
<add key="PluginCallbackBaseUrl" value="" />
|
||||
<add key="PluginCallbackToken" value="" />
|
||||
<add key="PluginSoftwareId" value="revit" />
|
||||
</appSettings>
|
||||
<runtime>
|
||||
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
|
||||
<dependentAssembly>
|
||||
@ -12,4 +17,4 @@
|
||||
</dependentAssembly>
|
||||
</assemblyBinding>
|
||||
</runtime>
|
||||
</configuration>
|
||||
</configuration>
|
||||
|
||||
Loading…
Reference in New Issue
Block a user