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);
}
}
}