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; /// /// 读取 Simple3 地图固定目录下的 JSON 原文,供平台「地图管理」右侧预览。 /// 先向 Simple3 拉取 maps 列表拿到 directory,再读本机同路径文件(与 Simple3 同机部署)。 /// [ApiController] [Authorize] [Route("api/maps")] public class MapsContentController : ControllerBase { private readonly IHttpClientFactory _httpFactory; private readonly InternalTokenStore _internalToken; private readonly Simple3Options _sl; private readonly ILogger _log; public MapsContentController( IHttpClientFactory httpFactory, InternalTokenStore internalToken, IOptions sl, ILogger log) { _httpFactory = httpFactory; _internalToken = internalToken; _sl = sl.Value; _log = log; } [HttpGet("{name}/content")] public async Task 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 = "无法从 Simple3 获取地图目录" }); 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); // 不返回 fullPath:避免向前端泄露服务器目录结构。 return Ok(new { name, fileName, content }); } catch (Exception ex) { _log.LogWarning(ex, "读取地图 JSON 失败 name={Name}", name); return StatusCode(500, new { message = $"读取地图 JSON 失败:{ex.Message}" }); } } private async Task 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($"Simple3 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; } }