帮助手册

NC 接收端源码

JmNcReceiver 接收 POST JSON 数组并还原为 NcSubmitItem 列表。完整工程见 nc-receiver/。

文件说明
Program.csHttpListener + 控制台命令
NcOps.cs反序列化、明文、extract
Models/NcSubmitModels.cs强类型模型
lib/Newtonsoft.Json.dllJSON 库

Program.cs

using System;
using System.Collections.Generic;
using System.IO;
using System.Net;
using System.Text;
using System.Threading;
using JmNcReceiver.Models;

namespace JmNcReceiver
{
	/// <summary>
	/// 机明 NC 对外提交 — 接收端演示源码(学习 / 联调用)。
	/// 默认监听 http://127.0.0.1:1235/
	/// 用法:JmNcReceiver.exe [监听前缀] [--fail] [--pretty]
	/// </summary>
	internal static class Program
	{
		private static bool _forceFail;
		private static bool _prettyJson = true;
		private static volatile bool _running = true;

		private static int Main(string[] args)
		{
			string prefix = "http://127.0.0.1:1235/";
			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);
			NcOps.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("http://", StringComparison.OrdinalIgnoreCase)
					|| a.StartsWith("https://", StringComparison.OrdinalIgnoreCase))
				{
					prefix = a.EndsWith("/") ? a : (a + "/");
				}
			}
		}

		private static void PrintBanner(string prefix, string receivedDir)
		{
			Console.WriteLine("========================================");
			Console.WriteLine("  机明 NC 接收端演示 (JmNcReceiver)");
			Console.WriteLine("  监听: {0}", prefix);
			Console.WriteLine("  落盘: {0}", receivedDir);
			Console.WriteLine("  强制失败: {0}", _forceFail ? "是 (--fail)" : "否");
			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[] parts = line.Split(new[] { ' ' }, 2, StringSplitOptions.RemoveEmptyEntries);
				string cmd = parts[0].ToLowerInvariant();
				string arg = parts.Length > 1 ? parts[1].Trim() : null;

				try
				{
					if (cmd == "quit" || cmd == "exit")
					{
						_running = false;
						break;
					}
					if (cmd == "help" || cmd == "?")
					{
						NcOps.PrintHelp();
						continue;
					}
					if (cmd == "summary")
					{
						NcOps.PrintSummary(NcSubmitStore.Latest);
						continue;
					}
					if (cmd == "text")
					{
						var items = NcSubmitStore.Latest;
						string text = NcOps.ToPlainText(items);
						Console.WriteLine(text);
						string outPath = Path.Combine(receivedDir, "latest-plain.txt");
						File.WriteAllText(outPath, text, new UTF8Encoding(false));
						Console.WriteLine("已写入 {0}", outPath);
						continue;
					}
					if (cmd == "dump")
					{
						var items = NcSubmitStore.Latest;
						if (items == null)
						{
							Console.WriteLine("(尚无批次)");
							continue;
						}
						string outPath = Path.Combine(receivedDir, "latest-object.json");
						File.WriteAllText(outPath, NcOps.ToPrettyJson(items), new UTF8Encoding(false));
						Console.WriteLine("已写入 {0}", outPath);
						continue;
					}
					if (cmd == "extract")
					{
						string outDir = Path.Combine(receivedDir, "extracted");
						int n = NcOps.ExtractStreams(NcSubmitStore.Latest, outDir);
						Console.WriteLine(n == 0 ? "(本批无 fileStream 可解码)" : ("共写出 " + n + " 个文件"));
						continue;
					}
					if (cmd == "load")
					{
						string path = string.IsNullOrEmpty(arg)
							? Path.Combine(receivedDir, "latest-object.json")
							: arg;
						if (!File.Exists(path))
						{
							Console.WriteLine("文件不存在: {0}", path);
							continue;
						}
						string json = File.ReadAllText(path, Encoding.UTF8);
						var items = NcOps.Deserialize(json);
						NcSubmitStore.SetLatest(items);
						Console.WriteLine(NcOps.ToPlainText(items));
						continue;
					}
					Console.WriteLine("未知命令。输入 help 查看。");
				}
				catch (Exception ex)
				{
					Console.WriteLine("命令失败: {0}", ex.Message);
				}
			}
		}

		private static void HandleRequest(HttpListenerContext context, string receivedDir)
		{
			HttpListenerRequest request = context.Request;
			HttpListenerResponse response = context.Response;
			try
			{
				if (string.Equals(request.HttpMethod, "GET", StringComparison.OrdinalIgnoreCase))
				{
					WriteText(response, 200, "JmNcReceiver OK. POST JSON array (NcSubmitItem[]) to this URL.");
					return;
				}

				if (!string.Equals(request.HttpMethod, "POST", StringComparison.OrdinalIgnoreCase))
				{
					WriteText(response, 405, "Method Not Allowed");
					return;
				}

				string json = ReadRequestBodyAsUtf8(request);
				string stamp = DateTime.Now.ToString("yyyyMMdd_HHmmss_fff");
				string baseName = "nc_" + stamp;
				string rawPath = Path.Combine(receivedDir, baseName + ".json");
				File.WriteAllText(rawPath, json ?? string.Empty, new UTF8Encoding(false));

				IList<NcSubmitItem> items = null;
				string err = null;
				try
				{
					items = NcOps.Deserialize(json);
					NcSubmitStore.SetLatest(items);
				}
				catch (Exception ex)
				{
					err = ex.Message;
				}

				if (items != null)
				{
					string plain = NcOps.ToPlainText(items);
					Console.WriteLine();
					Console.WriteLine(plain);
					File.WriteAllText(Path.Combine(receivedDir, baseName + ".txt"), plain, new UTF8Encoding(false));
					if (_prettyJson)
					{
						File.WriteAllText(Path.Combine(receivedDir, baseName + ".pretty.json"),
							NcOps.ToPrettyJson(items), new UTF8Encoding(false));
					}
				}
				else
				{
					Console.WriteLine();
					Console.WriteLine("[{0}] 收到 POST,但反序列化失败: {1}", stamp, err);
					Console.WriteLine("原文已保存: {0}", rawPath);
				}

				if (_forceFail)
				{
					WriteText(response, 500, "Forced failure (--fail). Payload saved.");
					return;
				}

				if (items == null)
				{
					WriteText(response, 400, "Invalid JSON: " + (err ?? "unknown"));
					return;
				}

				WriteText(response, 200, "OK " + items.Count + " item(s)");
			}
			catch (Exception ex)
			{
				try { WriteText(response, 500, ex.Message); } catch { }
			}
			finally
			{
				try { response.OutputStream.Close(); } catch { }
			}
		}

		/// <summary>强制按 UTF-8 读 Body,避免 HttpListener 在无 charset 时按系统默认编码误读。</summary>
		private static string ReadRequestBodyAsUtf8(HttpListenerRequest request)
		{
			using (var ms = new MemoryStream())
			{
				request.InputStream.CopyTo(ms);
				byte[] bytes = ms.ToArray();
				if (bytes.Length >= 3 && bytes[0] == 0xEF && bytes[1] == 0xBB && bytes[2] == 0xBF)
				{
					return Encoding.UTF8.GetString(bytes, 3, bytes.Length - 3);
				}
				return Encoding.UTF8.GetString(bytes);
			}
		}

		private static void WriteText(HttpListenerResponse response, int status, string text)
		{
			byte[] buf = Encoding.UTF8.GetBytes(text ?? string.Empty);
			response.StatusCode = status;
			response.ContentType = "text/plain; charset=utf-8";
			response.ContentEncoding = Encoding.UTF8;
			response.ContentLength64 = buf.Length;
			response.OutputStream.Write(buf, 0, buf.Length);
		}
	}
}

相关页面:联调演示 · NC 数据对接 · NcOps.cs