✨ feat(server): 添加静态状态页与调试入口
- 将状态页、调试页改为 `wwwroot` 静态资源 - 补充调试配置接口与前端脚本 - 为兼容层、规划层和运行时补充日志 - 更新集成测试覆盖新入口
This commit is contained in:
@@ -2,6 +2,7 @@ using System.Text.Json;
|
||||
using System.Text.Json.Nodes;
|
||||
using Flyshot.Core.Config;
|
||||
using Flyshot.Core.Domain;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace Flyshot.ControllerClientCompat;
|
||||
|
||||
@@ -41,16 +42,19 @@ public sealed class JsonFlyshotTrajectoryStore : IFlyshotTrajectoryStore
|
||||
{
|
||||
private readonly ControllerClientCompatOptions _options;
|
||||
private readonly RobotConfigLoader _configLoader;
|
||||
private readonly ILogger<JsonFlyshotTrajectoryStore>? _logger;
|
||||
|
||||
/// <summary>
|
||||
/// 初始化基于 JSON 文件的轨迹存储。
|
||||
/// </summary>
|
||||
/// <param name="options">兼容层基础配置,用于定位工作区根目录。</param>
|
||||
/// <param name="configLoader">旧版 RobotConfig.json 加载器,用于反序列化已保存的轨迹。</param>
|
||||
public JsonFlyshotTrajectoryStore(ControllerClientCompatOptions options, RobotConfigLoader configLoader)
|
||||
/// <param name="logger">日志记录器;允许 null。</param>
|
||||
public JsonFlyshotTrajectoryStore(ControllerClientCompatOptions options, RobotConfigLoader configLoader, ILogger<JsonFlyshotTrajectoryStore>? logger = null)
|
||||
{
|
||||
_options = options ?? throw new ArgumentNullException(nameof(options));
|
||||
_configLoader = configLoader ?? throw new ArgumentNullException(nameof(configLoader));
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
@@ -59,6 +63,12 @@ public sealed class JsonFlyshotTrajectoryStore : IFlyshotTrajectoryStore
|
||||
ArgumentNullException.ThrowIfNull(settings);
|
||||
ArgumentNullException.ThrowIfNull(trajectory);
|
||||
|
||||
_logger?.LogInformation(
|
||||
"TrajectoryStore 保存轨迹: robot={RobotName}, name={TrajectoryName}, waypoints={WaypointCount}",
|
||||
robotName,
|
||||
trajectory.Name,
|
||||
trajectory.Waypoints.Count);
|
||||
|
||||
var path = ResolveStorePath(robotName);
|
||||
var directory = Path.GetDirectoryName(path)!;
|
||||
Directory.CreateDirectory(directory);
|
||||
@@ -92,6 +102,8 @@ public sealed class JsonFlyshotTrajectoryStore : IFlyshotTrajectoryStore
|
||||
PropertyNamingPolicy = JsonNamingPolicy.SnakeCaseLower
|
||||
};
|
||||
File.WriteAllText(path, root.ToJsonString(writeOptions));
|
||||
|
||||
_logger?.LogInformation("TrajectoryStore 轨迹已保存到 {Path}", path);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
@@ -102,9 +114,12 @@ public sealed class JsonFlyshotTrajectoryStore : IFlyshotTrajectoryStore
|
||||
throw new ArgumentException("轨迹名称不能为空。", nameof(trajectoryName));
|
||||
}
|
||||
|
||||
_logger?.LogInformation("TrajectoryStore 删除轨迹: robot={RobotName}, name={TrajectoryName}", robotName, trajectoryName);
|
||||
|
||||
var path = ResolveStorePath(robotName);
|
||||
if (!File.Exists(path))
|
||||
{
|
||||
_logger?.LogWarning("TrajectoryStore 删除失败: 文件不存在 {Path}", path);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -112,19 +127,27 @@ public sealed class JsonFlyshotTrajectoryStore : IFlyshotTrajectoryStore
|
||||
var root = JsonNode.Parse(existingJson)?.AsObject();
|
||||
if (root is null)
|
||||
{
|
||||
_logger?.LogWarning("TrajectoryStore 删除失败: 无法解析 JSON {Path}", path);
|
||||
return;
|
||||
}
|
||||
|
||||
if (root.TryGetPropertyValue("flying_shots", out var flyingShotsNode) && flyingShotsNode is JsonObject flyingShotsObj)
|
||||
{
|
||||
flyingShotsObj.Remove(trajectoryName);
|
||||
|
||||
var writeOptions = new JsonSerializerOptions
|
||||
var removed = flyingShotsObj.Remove(trajectoryName);
|
||||
if (removed)
|
||||
{
|
||||
WriteIndented = true,
|
||||
PropertyNamingPolicy = JsonNamingPolicy.SnakeCaseLower
|
||||
};
|
||||
File.WriteAllText(path, root.ToJsonString(writeOptions));
|
||||
var writeOptions = new JsonSerializerOptions
|
||||
{
|
||||
WriteIndented = true,
|
||||
PropertyNamingPolicy = JsonNamingPolicy.SnakeCaseLower
|
||||
};
|
||||
File.WriteAllText(path, root.ToJsonString(writeOptions));
|
||||
_logger?.LogInformation("TrajectoryStore 轨迹已删除: {TrajectoryName}", trajectoryName);
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger?.LogWarning("TrajectoryStore 删除失败: 轨迹不存在 {TrajectoryName}", trajectoryName);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -134,12 +157,15 @@ public sealed class JsonFlyshotTrajectoryStore : IFlyshotTrajectoryStore
|
||||
var path = ResolveStorePath(robotName);
|
||||
if (!File.Exists(path))
|
||||
{
|
||||
_logger?.LogInformation("TrajectoryStore 无持久化数据: {Path}", path);
|
||||
settings = null;
|
||||
return new Dictionary<string, ControllerClientCompatUploadedTrajectory>(StringComparer.Ordinal);
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
_logger?.LogInformation("TrajectoryStore 正在加载: {Path}", path);
|
||||
|
||||
var workspaceRoot = ResolveWorkspaceRoot();
|
||||
var loaded = _configLoader.Load(path, workspaceRoot);
|
||||
settings = loaded.Robot;
|
||||
@@ -156,10 +182,18 @@ public sealed class JsonFlyshotTrajectoryStore : IFlyshotTrajectoryStore
|
||||
dict[program.Key] = traj;
|
||||
}
|
||||
|
||||
_logger?.LogInformation(
|
||||
"TrajectoryStore 加载完成: robot={RobotName}, 轨迹数={Count}, useDo={UseDo}, ioKeepCycles={IoKeepCycles}",
|
||||
robotName,
|
||||
dict.Count,
|
||||
settings?.UseDo,
|
||||
settings?.IoKeepCycles);
|
||||
|
||||
return dict;
|
||||
}
|
||||
catch
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger?.LogError(ex, "TrajectoryStore 加载失败: {Path}", path);
|
||||
settings = null;
|
||||
return new Dictionary<string, ControllerClientCompatUploadedTrajectory>(StringComparer.Ordinal);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user