From 02c162ee8f468515082917817135a864552621bd Mon Sep 17 00:00:00 2001
From: zerlei <1445089819@qq.com>
Date: Wed, 31 Jul 2024 17:14:07 +0800
Subject: [PATCH] =?UTF-8?q?feat:=20local=20server=20=E6=B7=BB=E5=8A=A0?=
=?UTF-8?q?=E8=BF=9E=E6=8E=A5?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
Server/Common/AES.cs | 128 ++++++++++++
Server/Common/Message.cs | 7 +
.../Controllers/LocalServerController.cs | 14 ++
Server/LocalServer/LocalServer.csproj | 4 +
Server/LocalServer/LocalSyncServer.cs | 95 ++++++++-
Server/LocalServer/LocalSyncServerFactory.cs | 16 +-
Server/LocalServer/Models/Config.cs | 32 +++
Server/LocalServer/StateHelper.cs | 189 ++++++++++++++++++
8 files changed, 479 insertions(+), 6 deletions(-)
create mode 100644 Server/Common/AES.cs
create mode 100644 Server/Common/Message.cs
create mode 100644 Server/LocalServer/Models/Config.cs
create mode 100644 Server/LocalServer/StateHelper.cs
diff --git a/Server/Common/AES.cs b/Server/Common/AES.cs
new file mode 100644
index 0000000..2ba39cb
--- /dev/null
+++ b/Server/Common/AES.cs
@@ -0,0 +1,128 @@
+using System;
+using System.IO;
+using System.Security.Cryptography;
+using System.Text;
+
+namespace Common;
+
+
+///
+/// 与目标服务器通信将会加密
+///
+public class AESHelper
+{
+ static readonly byte[] Key =
+ [
+ 0x11,
+ 0xF2,
+ 0xAF,
+ 0xCF,
+ 0xFF,
+ 0x8B,
+ 0x4C,
+ 0x7D,
+ 0x23,
+ 0x96,
+ 0x1B,
+ 0x32,
+ 0x43,
+ 0xA4,
+ 0x55,
+ 0xF6,
+ 0x29,
+ 0x1C,
+ 0x1B,
+ 0x92,
+ 0x23,
+ 0x44,
+ 0xB5,
+ 0xF6,
+ ];
+ static readonly byte[] IV =
+ [
+ 0xD1,
+ 0xF7,
+ 0xAB,
+ 0xCA,
+ 0xBC,
+ 0x7B,
+ 0x2C,
+ 0x3D,
+ 0xFA,
+ 0xAA,
+ 0xFC,
+ 0xA8,
+ 0x28,
+ 0x19,
+ 0x9C,
+ 0xB6,
+ ];
+
+ public static byte[] EncryptStringToBytes_Aes(string plainText)
+ {
+ // Check arguments.
+ if (plainText == null || plainText.Length <= 0)
+ throw new ArgumentNullException(nameof(plainText), "can't be null");
+ byte[] encrypted;
+
+ // Create an Aes object
+ // with the specified key and IV.
+ using (Aes aesAlg = Aes.Create())
+ {
+ aesAlg.Key = Key;
+ aesAlg.IV = IV;
+
+ // Create an encryptor to perform the stream transform.
+ ICryptoTransform encryptor = aesAlg.CreateEncryptor(aesAlg.Key, aesAlg.IV);
+
+ // Create the streams used for encryption.
+ using MemoryStream msEncrypt = new();
+ using CryptoStream csEncrypt = new(
+ msEncrypt,
+ encryptor,
+ CryptoStreamMode.Write
+ );
+ using (StreamWriter swEncrypt = new(csEncrypt))
+ {
+ //Write all data to the stream.
+ swEncrypt.Write(plainText);
+ }
+ encrypted = msEncrypt.ToArray();
+ }
+
+ // Return the encrypted bytes from the memory stream.
+ return encrypted;
+ }
+
+ public static string DecryptStringFromBytes_Aes(byte[] cipherText)
+ {
+ // Check arguments.
+ if (cipherText == null || cipherText.Length <= 0)
+ throw new ArgumentNullException(nameof(cipherText), "can't be null");
+
+ // Declare the string used to hold
+ // the decrypted text.
+ string plaintext = string.Empty;
+
+ // Create an Aes object
+ // with the specified key and IV.
+ using (Aes aesAlg = Aes.Create())
+ {
+ aesAlg.Key = Key;
+ aesAlg.IV = IV;
+
+ // Create a decryptor to perform the stream transform.
+ ICryptoTransform decryptor = aesAlg.CreateDecryptor(aesAlg.Key, aesAlg.IV);
+
+ // Create the streams used for decryption.
+ using MemoryStream msDecrypt = new(cipherText);
+ using CryptoStream csDecrypt = new(msDecrypt, decryptor, CryptoStreamMode.Read);
+ using StreamReader srDecrypt = new(csDecrypt);
+ // Read the decrypted bytes from the decrypting stream
+ // and place them in a string.
+ plaintext = srDecrypt.ReadToEnd();
+ }
+
+ return plaintext;
+ }
+}
diff --git a/Server/Common/Message.cs b/Server/Common/Message.cs
new file mode 100644
index 0000000..ca076ef
--- /dev/null
+++ b/Server/Common/Message.cs
@@ -0,0 +1,7 @@
+namespace Common;
+
+public class SyncMsg(bool isSuccess, string body)
+{
+ public bool IsSuccess { get; set; } = isSuccess;
+ public string Body { get; set; } = body;
+}
diff --git a/Server/LocalServer/Controllers/LocalServerController.cs b/Server/LocalServer/Controllers/LocalServerController.cs
index 2f2f7a9..fe5a360 100644
--- a/Server/LocalServer/Controllers/LocalServerController.cs
+++ b/Server/LocalServer/Controllers/LocalServerController.cs
@@ -1,3 +1,4 @@
+using System.Net.NetworkInformation;
using System.Text;
using Microsoft.AspNetCore.Mvc;
@@ -28,6 +29,19 @@ namespace LocalServer.Controllers
HttpContext.Response.StatusCode = StatusCodes.Status400BadRequest;
}
}
+
+ [Route("/macaddr")]
+ public string GetMacAddress()
+ {
+ NetworkInterface[] nics = NetworkInterface.GetAllNetworkInterfaces();
+ string macaddrs = "";
+ foreach (NetworkInterface nic in nics)
+ {
+ PhysicalAddress physicalAddress = nic.GetPhysicalAddress();
+ macaddrs += physicalAddress.ToString() + ";";
+ }
+ return macaddrs;
+ }
//TODO 是否在本地记载同步日志?
}
}
diff --git a/Server/LocalServer/LocalServer.csproj b/Server/LocalServer/LocalServer.csproj
index 9daa180..2f1bad2 100644
--- a/Server/LocalServer/LocalServer.csproj
+++ b/Server/LocalServer/LocalServer.csproj
@@ -10,4 +10,8 @@
+
+
+
+
diff --git a/Server/LocalServer/LocalSyncServer.cs b/Server/LocalServer/LocalSyncServer.cs
index a55b662..c03b213 100644
--- a/Server/LocalServer/LocalSyncServer.cs
+++ b/Server/LocalServer/LocalSyncServer.cs
@@ -1,19 +1,108 @@
using System.Net.WebSockets;
+using System.Text;
+using System.Text.Json;
+using Common;
+using LocalServer.Models;
namespace LocalServer;
public class LocalSyncServer
{
- public readonly WebSocket Socket;
+ public StateHelpBase StateHelper;
+
+ public Config? SyncConfig;
+
+ ///
+ /// 发布源连接
+ ///
+ public readonly WebSocket LocalSocket;
+
+ ///
+ /// 发布源-缓冲区,存储数据 最大1MB
+ ///
+ public byte[] Buffer = new byte[1024 * 1024];
+
+ ///
+ /// 发布目标-连接
+ ///
+ public readonly ClientWebSocket RemoteSocket = new();
+
+ ///
+ /// 发布开始时间
+ ///
+ private readonly DateTime StartTime = DateTime.Now;
+
+ ///
+ /// 发布名称
+ ///
public readonly string Name;
+ ///
+ /// 父工程,用于释放资源
+ ///
public readonly LocalSyncServerFactory Factory;
public LocalSyncServer(WebSocket socket, string name, LocalSyncServerFactory factory)
{
- Socket = socket;
+ LocalSocket = socket;
Name = name;
Factory = factory;
-
+ StateHelper = new LocalAuthorityState(this);
}
+
+ public async Task Start()
+ {
+ //最大1MB
+ var buffer = new byte[1024 * 1024];
+ var receiveResult = await LocalSocket.ReceiveAsync(
+ new ArraySegment(buffer),
+ CancellationToken.None
+ );
+
+ while (!receiveResult.CloseStatus.HasValue)
+ {
+ await LocalSocket.SendAsync(
+ new ArraySegment(buffer, 0, receiveResult.Count),
+ receiveResult.MessageType,
+ receiveResult.EndOfMessage,
+ CancellationToken.None
+ );
+
+ receiveResult = await LocalSocket.ReceiveAsync(
+ new ArraySegment(buffer),
+ CancellationToken.None
+ );
+ }
+ Factory.RemoveLocalSyncServer(this);
+ await LocalSocket.CloseAsync(
+ receiveResult.CloseStatus.Value,
+ receiveResult.CloseStatusDescription,
+ CancellationToken.None
+ );
+ }
+
+ public async void LocalSocketSendMsg(object msgOb)
+ {
+ string msg = JsonSerializer.Serialize(msgOb);
+ await LocalSocket.SendAsync(
+ new ArraySegment(Encoding.UTF8.GetBytes(msg)),
+ WebSocketMessageType.Text,
+ true,
+ CancellationToken.None
+ );
+ }
+
+ public async void RemoteSocketSendMsg(object msgOb)
+ {
+ string msg = JsonSerializer.Serialize(msgOb);
+ var buffer = AESHelper.EncryptStringToBytes_Aes(msg);
+ await RemoteSocket.SendAsync(
+ buffer,
+ WebSocketMessageType.Text,
+ true,
+ CancellationToken.None
+ );
+ }
+
+ public void Close() { }
}
diff --git a/Server/LocalServer/LocalSyncServerFactory.cs b/Server/LocalServer/LocalSyncServerFactory.cs
index b06c64f..a273c83 100644
--- a/Server/LocalServer/LocalSyncServerFactory.cs
+++ b/Server/LocalServer/LocalSyncServerFactory.cs
@@ -4,19 +4,29 @@ namespace LocalServer;
public class LocalSyncServerFactory
{
- public void CreateLocalSyncServer(WebSocket socket, string name)
+ private readonly object Lock = new();
+
+ public async void CreateLocalSyncServer(WebSocket socket, string name)
{
if (Servers.Select(x => x.Name == name).Any())
{
throw new Exception("there already is a server with that name is Runing!");
}
- Servers.Add(new LocalSyncServer(socket, name, this));
+ var server = new LocalSyncServer(socket, name, this);
+ lock (Lock)
+ {
+ Servers.Add(server);
+ }
+ await server.Start();
}
private readonly List Servers = [];
public void RemoveLocalSyncServer(LocalSyncServer server)
{
- Servers.Remove(server);
+ lock (Lock)
+ {
+ Servers.Remove(server);
+ }
}
}
diff --git a/Server/LocalServer/Models/Config.cs b/Server/LocalServer/Models/Config.cs
new file mode 100644
index 0000000..222fa79
--- /dev/null
+++ b/Server/LocalServer/Models/Config.cs
@@ -0,0 +1,32 @@
+namespace LocalServer.Models;
+
+public class DirFileConfig
+{
+ ///
+ /// 本地-源根目录
+ ///
+ public required string LocalRootPath { get; set; }
+
+ ///
+ /// 远程-目标根目录
+ ///
+ public required string RemoteRootPath { get; set; }
+
+ ///
+ /// 排除的文件,它是根目录的相对路径
+ ///
+ public List? ExcludeFiles { get; set; }
+
+ ///
+ /// 除此外全部忽略,最高优先级,若有值,ExcludeFiles 将被忽略,它是根目录的相对路径
+ ///
+ public List? CherryPickFiles { get; set; }
+}
+
+public class Config
+{
+ public required string Name { get; set; }
+ public required string RemoteUrl { get; set; }
+
+ public List? DirFileConfigs { get; set; }
+}
diff --git a/Server/LocalServer/StateHelper.cs b/Server/LocalServer/StateHelper.cs
new file mode 100644
index 0000000..707f2f9
--- /dev/null
+++ b/Server/LocalServer/StateHelper.cs
@@ -0,0 +1,189 @@
+using System.Net.NetworkInformation;
+using System.Net.WebSockets;
+using System.Runtime.Intrinsics.Arm;
+using System.Security.AccessControl;
+using System.Text;
+using System.Text.Json;
+using Common;
+using LocalServer.Models;
+
+namespace LocalServer;
+
+// enum StateWhenMsg
+// {
+// Authority = 0,
+// ConfigInfo = 1,
+// LocalPackAndUpload = 2,
+// RemoteUnPackAndRelease = 3,
+// }
+
+public abstract class StateHelpBase(LocalSyncServer context)
+{
+ protected readonly LocalSyncServer Context = context;
+
+ public abstract void HandleRemoteMsg(SyncMsg? msg);
+
+ public abstract void HandleLocalMsg(SyncMsg? msg);
+}
+
+///
+/// 0. 发布源验证密码
+///
+///
+public class LocalAuthorityState(LocalSyncServer context) : StateHelpBase(context)
+{
+ public override void HandleRemoteMsg(SyncMsg? msg)
+ {
+ throw new NotImplementedException("error usage!");
+ }
+
+ public override void HandleLocalMsg(SyncMsg? msg)
+ {
+ if (msg == null)
+ {
+ return;
+ }
+ else
+ {
+ string Pwd = msg.Body;
+ if (Pwd == "Xfs1%$@_fdYU.>>")
+ {
+ Context.LocalSocketSendMsg(new SyncMsg(true, "源服务密码校验成功!"));
+ Context.StateHelper = new WaitingConfigInfoState(Context);
+ }
+ else
+ {
+ throw new UnauthorizedAccessException("pwd error!");
+ }
+ }
+ }
+}
+
+///
+/// 1. 获取配置信息,它包含目标的服务器的配置信息
+///
+///
+public class WaitingConfigInfoState(LocalSyncServer context) : StateHelpBase(context)
+{
+ public override void HandleRemoteMsg(SyncMsg? msg) { }
+
+ public override void HandleLocalMsg(SyncMsg? msg)
+ {
+ if (msg == null)
+ {
+ return;
+ }
+ else
+ {
+ string ConfigInfo = msg.Body;
+ Context.SyncConfig =
+ JsonSerializer.Deserialize(ConfigInfo)
+ ?? throw new NullReferenceException("ConfigInfo is null");
+ var task = Context.RemoteSocket.ConnectAsync(
+ new Uri(Context.SyncConfig.RemoteUrl),
+ CancellationToken.None
+ );
+ if (task.Wait(10000))
+ {
+ if (Context.RemoteSocket.State == WebSocketState.Open)
+ {
+ var state = new RemoteAuthorityState(Context);
+ state.SendPwdToRemoteServer();
+ Context.StateHelper = state;
+ }
+ else
+ {
+ throw new Exception("connect remote server failed!");
+ }
+ }
+ else
+ {
+ throw new TimeoutException("connect remote server timeout");
+ }
+ }
+ }
+}
+
+///
+/// 2. 目标服务器权限校验
+///
+///
+public class RemoteAuthorityState(LocalSyncServer context) : StateHelpBase(context)
+{
+ public override void HandleRemoteMsg(SyncMsg? msg)
+ {
+ if (msg == null)
+ {
+ return;
+ }
+ else { }
+ }
+
+ public override void HandleLocalMsg(SyncMsg? msg) { }
+
+ public void SendPwdToRemoteServer()
+ {
+ var authorityInfo = new
+ {
+ Pwd = "xfs@#123hd??1>>|12#4",
+ MacAdr = new LocalServer.Controllers.LocalServerController(
+ Context.Factory
+ ).GetMacAddress()
+ };
+ Context.RemoteSocketSendMsg(authorityInfo);
+ }
+}
+
+///
+/// 3. 文件比较
+///
+///
+public class DirFilesDiffState(LocalSyncServer context) : StateHelpBase(context)
+{
+ public override void HandleRemoteMsg(SyncMsg? msg)
+ {
+ if (msg == null)
+ {
+ return;
+ }
+ else { }
+ }
+
+ public override void HandleLocalMsg(SyncMsg? msg) { }
+}
+
+///
+/// 4. 本地打包并上传
+///
+///
+public class LocalPackAndUploadState(LocalSyncServer context) : StateHelpBase(context)
+{
+ public override void HandleRemoteMsg(SyncMsg? msg)
+ {
+ if (msg == null)
+ {
+ return;
+ }
+ else { }
+ }
+
+ public override void HandleLocalMsg(SyncMsg? msg) { }
+}
+
+///
+/// 5. 目标服务器解包并发布
+///
+///
+public class RemoteUnPackAndReleaseState(LocalSyncServer context) : StateHelpBase(context)
+{
+ public override void HandleRemoteMsg(SyncMsg? msg)
+ {
+ if (msg == null)
+ {
+ return;
+ }
+ else { }
+ }
+
+ public override void HandleLocalMsg(SyncMsg? msg) { }
+}