feat(server/logs): 内核 DLog 日志管理 API 与地图内容读取接口
LogsController 暴露 SimpleLite 落盘 DLog(概览/文件浏览/条目/合订/分析/原文/下载), 含路径穿越防护、单文件与跨文件聚合的条数/字节上限,鉴权收紧为 PlatformScope(仅平台后台用户); MapsContentController 经投影 API 定位地图目录后读取地图 JSON,含路径穿越校验。 Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -0,0 +1,108 @@
|
||||
using System.Text.Json;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.Extensions.Options;
|
||||
using MiGu.Server.Auth;
|
||||
using MiGu.Server.Launcher;
|
||||
|
||||
namespace MiGu.Server.Controllers;
|
||||
|
||||
/// <summary>
|
||||
/// 读取 SimpleLite 地图固定目录下的 JSON 原文,供平台「地图管理」右侧预览。
|
||||
/// 先向 SimpleLite 拉取 maps 列表拿到 directory,再读本机同路径文件(与 SimpleLite 同机部署)。
|
||||
/// </summary>
|
||||
[ApiController]
|
||||
[Authorize]
|
||||
[Route("api/maps")]
|
||||
public class MapsContentController : ControllerBase
|
||||
{
|
||||
private readonly IHttpClientFactory _httpFactory;
|
||||
private readonly InternalTokenStore _internalToken;
|
||||
private readonly SimpleLiteOptions _sl;
|
||||
private readonly ILogger<MapsContentController> _log;
|
||||
|
||||
public MapsContentController(
|
||||
IHttpClientFactory httpFactory,
|
||||
InternalTokenStore internalToken,
|
||||
IOptions<SimpleLiteOptions> sl,
|
||||
ILogger<MapsContentController> log)
|
||||
{
|
||||
_httpFactory = httpFactory;
|
||||
_internalToken = internalToken;
|
||||
_sl = sl.Value;
|
||||
_log = log;
|
||||
}
|
||||
|
||||
[HttpGet("{name}/content")]
|
||||
public async Task<IActionResult> GetContent(string name, CancellationToken ct)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(name)
|
||||
|| name.Contains("..", StringComparison.Ordinal)
|
||||
|| name.IndexOfAny(['/', '\\', ':', '*', '?', '"', '<', '>', '|']) >= 0)
|
||||
{
|
||||
return BadRequest(new { message = "地图名称非法" });
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var directory = await FetchMapsDirectoryAsync(ct);
|
||||
if (string.IsNullOrWhiteSpace(directory))
|
||||
return StatusCode(503, new { message = "无法从 SimpleLite 获取地图目录" });
|
||||
|
||||
var fileName = name.EndsWith(".json", StringComparison.OrdinalIgnoreCase) ? name : $"{name}.json";
|
||||
var fullPath = Path.GetFullPath(Path.Combine(directory, fileName));
|
||||
var root = Path.GetFullPath(directory);
|
||||
if (!fullPath.StartsWith(root, StringComparison.OrdinalIgnoreCase))
|
||||
return BadRequest(new { message = "路径校验失败" });
|
||||
|
||||
if (!System.IO.File.Exists(fullPath))
|
||||
return NotFound(new { message = $"地图文件不存在:{fileName}" });
|
||||
|
||||
var content = await System.IO.File.ReadAllTextAsync(fullPath, ct);
|
||||
return Ok(new
|
||||
{
|
||||
name,
|
||||
fileName,
|
||||
path = fullPath,
|
||||
content
|
||||
});
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_log.LogWarning(ex, "读取地图 JSON 失败 name={Name}", name);
|
||||
return StatusCode(500, new { message = $"读取地图 JSON 失败:{ex.Message}" });
|
||||
}
|
||||
}
|
||||
|
||||
private async Task<string?> FetchMapsDirectoryAsync(CancellationToken ct)
|
||||
{
|
||||
var client = _httpFactory.CreateClient();
|
||||
client.Timeout = TimeSpan.FromSeconds(8);
|
||||
var url = $"http://127.0.0.1:{_sl.ProjectionPort}/projection/map-edit/maps";
|
||||
using var msg = new HttpRequestMessage(HttpMethod.Get, url);
|
||||
if (!string.IsNullOrEmpty(_internalToken.Token))
|
||||
msg.Headers.TryAddWithoutValidation("X-Platform-Internal-Token", _internalToken.Token);
|
||||
|
||||
using var resp = await client.SendAsync(msg, ct);
|
||||
var body = await resp.Content.ReadAsStringAsync(ct);
|
||||
if (!resp.IsSuccessStatusCode)
|
||||
throw new InvalidOperationException($"SimpleLite maps 列表返回 {(int)resp.StatusCode}");
|
||||
|
||||
using var doc = JsonDocument.Parse(body);
|
||||
var root = doc.RootElement;
|
||||
if (root.TryGetProperty("success", out var ok) && ok.ValueKind == JsonValueKind.False)
|
||||
{
|
||||
var message = root.TryGetProperty("message", out var m) ? m.GetString() : "maps 列表失败";
|
||||
throw new InvalidOperationException(message ?? "maps 列表失败");
|
||||
}
|
||||
|
||||
JsonElement data = root;
|
||||
if (root.TryGetProperty("data", out var d) && d.ValueKind == JsonValueKind.Object)
|
||||
data = d;
|
||||
|
||||
if (data.TryGetProperty("directory", out var dir) && dir.ValueKind == JsonValueKind.String)
|
||||
return dir.GetString();
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user