using System; using System.IO; using System.Net; using System.Text; using System.Threading; using JmSetupSheetReceiver.Models; namespace JmSetupSheetReceiver { /// /// 机明程序单对外提交 — 接收端演示源码(学习 / 联调用)。 /// /// 接收 POST JSON 后用 Newtonsoft.Json 还原为 强类型对象, /// 控制台可 summary / tools / images 等命令查看与操作。 /// /// /// 默认:http://127.0.0.1:1234/ /// 参数:JmSetupSheetReceiver.exe [监听前缀] [--fail] [--pretty] /// /// internal static class Program { private static bool _forceFail; private static bool _prettyJson; private static volatile bool _running = true; private static int Main(string[] args) { string prefix = "http://127.0.0.1:1234/"; ParseArgs(args, ref prefix); string receivedDir = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Received"); Directory.CreateDirectory(receivedDir); if (!HttpListener.IsSupported) { Console.WriteLine("当前系统不支持 HttpListener。"); return 1; } var listener = new HttpListener(); listener.Prefixes.Add(prefix); try { listener.Start(); } catch (HttpListenerException ex) { Console.WriteLine("无法监听 {0}", prefix); Console.WriteLine("原因: {0}", ex.Message); Console.WriteLine(); Console.WriteLine("提示: 非管理员账户监听 localhost 以外地址时,可能需要先执行:"); Console.WriteLine(" netsh http add urlacl url={0} user=%USERNAME%", prefix); return 2; } PrintBanner(prefix, receivedDir); SetupSheetOps.PrintHelp(); Console.CancelKeyPress += (s, e) => { e.Cancel = true; _running = false; try { listener.Stop(); } catch { } }; // 监听循环放后台,主线程跑命令行,便于操作已还原对象 var acceptThread = new Thread(() => AcceptLoop(listener, receivedDir)) { IsBackground = true, Name = "HttpAccept" }; acceptThread.Start(); RunCommandLoop(receivedDir); _running = false; try { listener.Stop(); } catch { } try { listener.Close(); } catch { } return 0; } private static void AcceptLoop(HttpListener listener, string receivedDir) { while (_running && listener.IsListening) { HttpListenerContext context; try { context = listener.GetContext(); } catch (HttpListenerException) { break; } catch (ObjectDisposedException) { break; } ThreadPool.QueueUserWorkItem(_ => HandleRequest(context, receivedDir)); } } private static void ParseArgs(string[] args, ref string prefix) { if (args == null) return; foreach (string raw in args) { if (string.IsNullOrWhiteSpace(raw)) continue; string a = raw.Trim(); if (string.Equals(a, "--fail", StringComparison.OrdinalIgnoreCase) || string.Equals(a, "-fail", StringComparison.OrdinalIgnoreCase)) { _forceFail = true; continue; } if (string.Equals(a, "--pretty", StringComparison.OrdinalIgnoreCase) || string.Equals(a, "-pretty", StringComparison.OrdinalIgnoreCase)) { _prettyJson = true; continue; } if (a.StartsWith("-")) { Console.WriteLine("未知参数: {0}(已忽略)", a); continue; } prefix = a; if (!prefix.EndsWith("/")) prefix += "/"; } } private static void PrintBanner(string prefix, string receivedDir) { Console.WriteLine("================================================"); Console.WriteLine(" 机明程序单接收端演示 JmSetupSheetReceiver"); Console.WriteLine("================================================"); Console.WriteLine("监听地址 : {0}", prefix); Console.WriteLine("落盘目录 : {0}", receivedDir); Console.WriteLine("GET 探活 : 浏览器打开上述地址"); Console.WriteLine("POST : JSON → 对象 + 原文/分行JSON/明文落盘 → {0}", _forceFail ? "500 (--fail)" : "200"); Console.WriteLine("落盘文件 : *.json(原文)+ *.pretty.json(分行)+ *.txt(中文明文)"); if (_prettyJson) Console.WriteLine("提示 : --pretty 已默认等价(还原成功总会写 .pretty.json)"); Console.WriteLine("明文 : 控制台打印中英对照(预览图仅字节摘要)"); Console.WriteLine("对象访问 : SetupSheetStore.Latest / 下方控制台命令"); Console.WriteLine("按 Ctrl+C 或输入 quit 退出"); Console.WriteLine("================================================"); Console.WriteLine(); } private static void RunCommandLoop(string receivedDir) { while (_running) { Console.Write("> "); string line; try { line = Console.ReadLine(); } catch { break; } if (line == null) break; line = line.Trim(); if (line.Length == 0) continue; string cmd = line; string arg = null; int sp = line.IndexOf(' '); if (sp > 0) { cmd = line.Substring(0, sp).Trim(); arg = line.Substring(sp + 1).Trim(); } try { DispatchCommand(cmd, arg, receivedDir); } catch (Exception ex) { Console.WriteLine("命令错误: {0}", ex.Message); } } } private static void DispatchCommand(string cmd, string arg, string receivedDir) { if (string.Equals(cmd, "quit", StringComparison.OrdinalIgnoreCase) || string.Equals(cmd, "exit", StringComparison.OrdinalIgnoreCase)) { _running = false; return; } if (string.Equals(cmd, "help", StringComparison.OrdinalIgnoreCase) || string.Equals(cmd, "?", StringComparison.OrdinalIgnoreCase)) { SetupSheetOps.PrintHelp(); return; } var data = SetupSheetStore.Latest; if (string.Equals(cmd, "summary", StringComparison.OrdinalIgnoreCase) || string.Equals(cmd, "s", StringComparison.OrdinalIgnoreCase)) { SetupSheetOps.PrintSummary(data); return; } if (string.Equals(cmd, "text", StringComparison.OrdinalIgnoreCase) || string.Equals(cmd, "plain", StringComparison.OrdinalIgnoreCase)) { if (data == null) { Console.WriteLine("尚无已还原对象。请先 POST 或 load。"); return; } SetupSheetOps.PrintPlainText(data); string txtPath = Path.Combine(receivedDir, "latest-plain.txt"); SetupSheetOps.SavePlainText(data, txtPath); Console.WriteLine("明文已另存: {0}", txtPath); return; } if (string.Equals(cmd, "tools", StringComparison.OrdinalIgnoreCase) || string.Equals(cmd, "t", StringComparison.OrdinalIgnoreCase)) { SetupSheetOps.PrintToolPaths(data); return; } if (string.Equals(cmd, "tool", StringComparison.OrdinalIgnoreCase)) { if (string.IsNullOrEmpty(arg)) { Console.WriteLine("用法: tool <刀路名|NC名|刀具名>"); return; } SetupSheetOps.PrintToolPathDetail(SetupSheetOps.FindToolPath(data, arg)); return; } if (string.Equals(cmd, "positions", StringComparison.OrdinalIgnoreCase) || string.Equals(cmd, "pos", StringComparison.OrdinalIgnoreCase)) { SetupSheetOps.PrintSummary(data); return; } if (string.Equals(cmd, "images", StringComparison.OrdinalIgnoreCase) || string.Equals(cmd, "img", StringComparison.OrdinalIgnoreCase)) { if (data == null) { Console.WriteLine("尚无已还原对象。请先 POST 一份程序单 JSON。"); return; } string dir = Path.Combine(receivedDir, "images_" + DateTime.Now.ToString("yyyyMMdd-HHmmss")); int n = SetupSheetOps.SavePreviewImages(data, dir); Console.WriteLine("共导出 {0} 张预览图 → {1}", n, dir); return; } if (string.Equals(cmd, "dump", StringComparison.OrdinalIgnoreCase)) { if (data == null) { Console.WriteLine("尚无已还原对象。"); return; } string path = Path.Combine(receivedDir, "latest-object.json"); File.WriteAllText(path, SetupSheetOps.Serialize(data), new UTF8Encoding(false)); Console.WriteLine("已将对象重新序列化到: {0}", path); return; } if (string.Equals(cmd, "path", StringComparison.OrdinalIgnoreCase)) { Console.WriteLine("原始 JSON: {0}", SetupSheetStore.LatestJsonPath ?? "(尚无)"); Console.WriteLine("明文 TXT : {0}", SetupSheetStore.LatestTxtPath ?? "(尚无)"); return; } if (string.Equals(cmd, "load", StringComparison.OrdinalIgnoreCase)) { // 从已落盘文件再次还原,方便离线调试 string file = arg; if (string.IsNullOrEmpty(file)) file = SetupSheetStore.LatestJsonPath; if (string.IsNullOrEmpty(file) || !File.Exists(file)) { Console.WriteLine("用法: load [json文件路径]"); return; } string json = File.ReadAllText(file, Encoding.UTF8); var sheet = SetupSheetOps.Deserialize(json); string txtPath = Path.ChangeExtension(file, ".txt"); if (string.Equals(Path.GetExtension(file), ".txt", StringComparison.OrdinalIgnoreCase)) txtPath = Path.Combine(receivedDir, "loaded-plain.txt"); SetupSheetOps.SavePlainText(sheet, txtPath); SetupSheetStore.SetLatest(sheet, file, txtPath); Console.WriteLine("已从文件还原对象: {0}", file); Console.WriteLine("明文已写入: {0}", txtPath); SetupSheetOps.PrintPlainText(sheet); return; } Console.WriteLine("未知命令: {0} (输入 help 查看)", cmd); } private static void HandleRequest(HttpListenerContext context, string receivedDir) { HttpListenerRequest request = context.Request; HttpListenerResponse response = context.Response; string method = request.HttpMethod ?? ""; string path = request.Url != null ? request.Url.AbsolutePath : "/"; try { if (string.Equals(method, "GET", StringComparison.OrdinalIgnoreCase)) { string help = "JmSetupSheetReceiver — 机明程序单接收端演示\r\n\r\n" + "POST application/json → 还原为 SetupsheetData,落盘 JSON 与明文 TXT。\r\n" + "成功返回 HTTP 200: {\"success\":true,\"message\":\"received\"}\r\n" + "控制台命令: summary / text / tools / tool <名> / images / dump / load\r\n"; WriteText(response, 200, "text/plain; charset=utf-8", help); Console.WriteLine("[{0:HH:mm:ss}] GET {1} -> 200 (probe)", DateTime.Now, path); return; } if (!string.Equals(method, "POST", StringComparison.OrdinalIgnoreCase)) { WriteText(response, 405, "text/plain; charset=utf-8", "Method Not Allowed. Use POST."); Console.WriteLine("[{0:HH:mm:ss}] {1} {2} -> 405", DateTime.Now, method, path); return; } // 机明推送为 UTF-8 JSON。Content-Type 若未带 charset,HttpListener.ContentEncoding // 在中文 Windows 上常为系统默认(GBK),误解码会弄坏中文并导致反序列化失败。 string body = ReadRequestBodyAsUtf8(request); SetupsheetData sheet = null; string deserializeError = null; try { sheet = SetupSheetOps.Deserialize(body); } catch (Exception ex) { deserializeError = ex.Message; } // 始终保留原始报文;另存缩进分行的 .pretty.json 便于阅读 string stamp = DateTime.Now.ToString("yyyyMMdd-HHmmss-fff"); string fileName = stamp + ".json"; string filePath = Path.Combine(receivedDir, fileName); File.WriteAllText(filePath, body ?? string.Empty, new UTF8Encoding(false)); string prettyPath = null; if (sheet != null) { try { prettyPath = Path.Combine(receivedDir, stamp + ".pretty.json"); File.WriteAllText(prettyPath, SetupSheetOps.Serialize(sheet), new UTF8Encoding(false)); } catch { prettyPath = null; } } else if (_prettyJson) { // 还原失败时 --pretty 无法生成对象美化稿,仅保留原文 } int len = body == null ? 0 : Encoding.UTF8.GetByteCount(body); if (sheet != null) { string txtPath = Path.ChangeExtension(filePath, ".txt"); SetupSheetOps.SavePlainText(sheet, txtPath); SetupSheetStore.SetLatest(sheet, filePath, txtPath); Console.WriteLine(); Console.WriteLine("[{0:HH:mm:ss}] POST {1} -> 对象已还原 (SetupsheetData)", DateTime.Now, path); Console.WriteLine(" 原文 JSON : {0} ({1} bytes)", fileName, len); if (prettyPath != null) Console.WriteLine(" 分行 JSON : {0}", Path.GetFileName(prettyPath)); Console.WriteLine(" 明文 TXT : {0}", Path.GetFileName(txtPath)); Console.WriteLine("---------- 明文开始 ----------"); SetupSheetOps.PrintPlainText(sheet); Console.WriteLine("---------- 明文结束 ----------"); Console.Write("> "); } else { Console.WriteLine(); Console.WriteLine("[{0:HH:mm:ss}] POST {1} -> JSON 已落盘,但对象还原失败", DateTime.Now, path); Console.WriteLine(" 保存: {0} ({1} bytes)", fileName, len); Console.WriteLine(" 原因: {0}", deserializeError); Console.Write("> "); } if (_forceFail) { WriteText(response, 500, "application/json; charset=utf-8", "{\"success\":false,\"message\":\"demo forced failure (--fail)\"}"); return; } const string okJson = "{\"success\":true,\"message\":\"received\"}"; WriteText(response, 200, "application/json; charset=utf-8", okJson); } catch (Exception ex) { try { WriteText(response, 500, "text/plain; charset=utf-8", "Error: " + ex.Message); } catch { } Console.WriteLine("[{0:HH:mm:ss}] ERROR: {1}", DateTime.Now, ex.Message); } finally { try { response.OutputStream.Close(); } catch { } } } /// /// 按 UTF-8 读取请求体(忽略可能错误的 ContentEncoding)。 /// private static string ReadRequestBodyAsUtf8(HttpListenerRequest request) { using (var ms = new MemoryStream()) { request.InputStream.CopyTo(ms); byte[] raw = ms.ToArray(); if (raw.Length >= 3 && raw[0] == 0xEF && raw[1] == 0xBB && raw[2] == 0xBF) { return Encoding.UTF8.GetString(raw, 3, raw.Length - 3); } return Encoding.UTF8.GetString(raw); } } private static void WriteText(HttpListenerResponse response, int statusCode, string contentType, string text) { byte[] buffer = Encoding.UTF8.GetBytes(text ?? string.Empty); response.StatusCode = statusCode; response.ContentType = contentType; response.ContentEncoding = Encoding.UTF8; response.ContentLength64 = buffer.Length; response.OutputStream.Write(buffer, 0, buffer.Length); } } }