diff --git a/README.md b/README.md
index 2238e3c..c828db1 100644
--- a/README.md
+++ b/README.md
@@ -1,5 +1,15 @@
> 这是一个基于 asp.net c# 的发布工具。
+```bash
+# 此行命令差一个运行环境复制。bin/roslyn
+msbuild .\HMES-H7-HNFYMF.WEB\HMES_H7_HNFYMF.WEB.csproj /t:ResolveReferences /t:Compile /t:_CopyWebApplication /p:Configuration=Release /p:WebProjectOutputDir=C:\publish /p:OutputPath=C:\publish\bin
+
+
+# 此命令是一个完整的发布命令
+
+msdeploy.exe -verb:sync -source:contentPath=D:\git\HMES-H7-HNFY\HMES-H7-HNFYMF\HMES-H7-HNFYMF.WEB -dest:contentPath=D:\git\HMES-H7-HNFY\HMES-H7-HNFYMF\release -disablerule:BackupRule
+```
+
```plantuml
package 服务器 {
diff --git a/Server/Common/AES.cs b/Server/Common/AES.cs
index 2ba39cb..cac98c2 100644
--- a/Server/Common/AES.cs
+++ b/Server/Common/AES.cs
@@ -1,7 +1,4 @@
-using System;
-using System.IO;
using System.Security.Cryptography;
-using System.Text;
namespace Common;
@@ -94,7 +91,7 @@ public class AESHelper
return encrypted;
}
- public static string DecryptStringFromBytes_Aes(byte[] cipherText)
+ public static string DecryptStringFromBytes_Aes(byte[] cipherText)
{
// Check arguments.
if (cipherText == null || cipherText.Length <= 0)
diff --git a/Server/Common/Config.cs b/Server/Common/Config.cs
new file mode 100644
index 0000000..7fddd56
--- /dev/null
+++ b/Server/Common/Config.cs
@@ -0,0 +1,80 @@
+namespace Common;
+
+public class DirFileConfig
+{
+ ///
+ /// 相对路径
+ ///
+ public required string DirPath { get; set; }
+
+ ///
+ /// 排除的文件,它是根目录的相对路径
+ ///
+ public List? Excludes { get; set; }
+
+ ///
+ /// 除此外全部忽略,最高优先级,若有值,ExcludeFiles 将被忽略,它是根目录的相对路径
+ ///
+ public List? CherryPicks { get; set; }
+ public Dir? LocalDirInfo { get; set; }
+}
+
+public class Config
+{
+ ///
+ /// 发布的项目名称
+ ///
+ public required string Name { get; set; }
+
+ ///
+ /// 远程Url
+ ///
+ public required string RemoteUrl { get; set; }
+ ///
+ /// 链接到远程的密码
+ ///
+ public required string RemotePwd {get;set;}
+
+ ///
+ /// 是否发布数据库
+ ///
+ public required bool IsDeployDb {get;set;}
+
+ ///
+ /// 源数据库连接字符串(ip地址相对LocalServer)
+ ///
+ public required string SrcDbConnection { get; set; }
+ ///
+ /// 目标数据库连接字符串(ip地址相对RemoteServer)
+ ///
+ public required string DstDbConnection { get; set; }
+
+ ///
+ /// 同步的表
+ ///
+ public required List? SyncDataTables {get;set;}
+
+ ///
+ /// 是否发布项目
+ ///
+ public required bool IsDeployProject {get;set;}
+ ///
+ /// 项目的绝对路径 空字符串表示不发布,不为空LocalRootPath将是发布路径。
+ ///
+ public required string LocalProjectAbsolutePath { get; set; }
+
+ ///
+ /// 本地父文件路径
+ ///
+ public required string LocalRootPath { get; set; }
+
+ ///
+ /// 远程父路径
+ ///
+ public required string RemoteRootPath { get; set; }
+
+ ///
+ /// 同步的文件夹配置
+ ///
+ public required List DirFileConfigs { get; set; }
+}
diff --git a/Server/Common/Dir.cs b/Server/Common/Dir.cs
index 6c47f34..7c577d1 100644
--- a/Server/Common/Dir.cs
+++ b/Server/Common/Dir.cs
@@ -309,11 +309,27 @@ public class Dir(string path, List? children = null, NextOpType? nex
}
///
- /// 从文件目录结构提起文件信息,注意,此目录文件树不包含文件内容,仅有修改时间mtime
+ /// 从文件夹中提取信息
///
- ///
- public void ExtractInfo()
+ /// 绝对路径,只包含的文件或者目录
+ /// 绝对路径,排除的文件或目录
+ ///
+ public void ExtractInfo(List? cherryPicks = null, List? exculdes = null)
{
+ bool filter(string path)
+ {
+ if (cherryPicks != null)
+ {
+ return cherryPicks.Contains(path);
+ }
+
+ if (exculdes != null)
+ {
+ return !exculdes.Contains(path);
+ }
+ return true;
+ }
+
if (this.Children.Count != 0)
{
throw new NotSupportedException("this dir is not empty.");
@@ -322,13 +338,19 @@ public class Dir(string path, List? children = null, NextOpType? nex
string[] dirs = Directory.GetDirectories(this.FormatedPath);
foreach (var file in files)
{
- this.Children.Add(new File(file, System.IO.File.GetLastWriteTime($"{file}")));
+ if (filter(file))
+ {
+ this.Children.Add(new File(file, System.IO.File.GetLastWriteTime($"{file}")));
+ }
}
foreach (var dir in dirs)
{
- var ndir = new Dir(dir);
- ndir.ExtractInfo();
- this.Children.Add(ndir);
+ if (filter(dir))
+ {
+ var ndir = new Dir(dir);
+ ndir.ExtractInfo();
+ this.Children.Add(ndir);
+ }
}
}
diff --git a/Server/Common/Message.cs b/Server/Common/Message.cs
index ca076ef..e2c14aa 100644
--- a/Server/Common/Message.cs
+++ b/Server/Common/Message.cs
@@ -1,7 +1,30 @@
namespace Common;
-public class SyncMsg(bool isSuccess, string body)
+public enum SyncMsgType
{
- public bool IsSuccess { get; set; } = isSuccess;
+ Error = 0,
+ General = 1,
+ Process = 2,
+ DirFilePack = 3
+}
+public enum SyncProcessStep
+{
+ Connect = 1,
+ DeployProject = 2,
+ DiffFileAndPack = 3,
+ PackSqlServer = 4,
+ Upload = 5,
+ Publish = 6
+}
+public class SyncMsg(SyncMsgType msgType, SyncProcessStep step, string body)
+{
+ public SyncMsgType? Type { get; set; } = msgType;
+
+ public SyncProcessStep Step {get;set;} = step;
+
+ public bool IsSuccess
+ {
+ get { return Type != SyncMsgType.Error; }
+ }
public string Body { get; set; } = body;
}
diff --git a/Server/LocalServer/Controllers/LocalServerController.cs b/Server/LocalServer/Controllers/LocalServerController.cs
index fe5a360..297dff3 100644
--- a/Server/LocalServer/Controllers/LocalServerController.cs
+++ b/Server/LocalServer/Controllers/LocalServerController.cs
@@ -8,7 +8,7 @@ namespace LocalServer.Controllers
{
private readonly LocalSyncServerFactory Factory = factory;
- [Route("/")]
+ [Route("/websoc")]
public async Task WebsocketConnection(string Name)
{
if (HttpContext.WebSockets.IsWebSocketRequest)
diff --git a/Server/LocalServer/LocalSyncServer.cs b/Server/LocalServer/LocalSyncServer.cs
index c03b213..428a674 100644
--- a/Server/LocalServer/LocalSyncServer.cs
+++ b/Server/LocalServer/LocalSyncServer.cs
@@ -2,16 +2,26 @@ using System.Net.WebSockets;
using System.Text;
using System.Text.Json;
using Common;
-using LocalServer.Models;
namespace LocalServer;
public class LocalSyncServer
{
+#pragma warning disable CA2211 // Non-constant fields should not be visible
+ public static string TempRootFile = "C:/TempPack";
+#pragma warning restore CA2211 // Non-constant fields should not be visible
public StateHelpBase StateHelper;
public Config? SyncConfig;
+ public Config NotNullSyncConfig {get {
+ if (SyncConfig == null)
+ {
+ throw new ArgumentNullException("SyncConfig");
+ }
+ return SyncConfig;
+ }}
+
///
/// 发布源连接
///
@@ -20,7 +30,7 @@ public class LocalSyncServer
///
/// 发布源-缓冲区,存储数据 最大1MB
///
- public byte[] Buffer = new byte[1024 * 1024];
+ // public byte[] Buffer = new byte[1024 * 1024];
///
/// 发布目标-连接
@@ -47,41 +57,96 @@ public class LocalSyncServer
LocalSocket = socket;
Name = name;
Factory = factory;
- StateHelper = new LocalAuthorityState(this);
+ StateHelper = new ConnectAuthorityHelper(this);
}
- public async Task Start()
+ public async Task RemoteSocketConnect()
{
- //最大1MB
- var buffer = new byte[1024 * 1024];
- var receiveResult = await LocalSocket.ReceiveAsync(
- new ArraySegment(buffer),
- CancellationToken.None
- );
-
- while (!receiveResult.CloseStatus.HasValue)
+ if (SyncConfig != null)
{
- await LocalSocket.SendAsync(
- new ArraySegment(buffer, 0, receiveResult.Count),
- receiveResult.MessageType,
- receiveResult.EndOfMessage,
- CancellationToken.None
- );
-
- receiveResult = await LocalSocket.ReceiveAsync(
- new ArraySegment(buffer),
+ await RemoteSocket.ConnectAsync(
+ new Uri(SyncConfig.RemoteUrl + "/websoc"),
CancellationToken.None
);
}
- Factory.RemoveLocalSyncServer(this);
- await LocalSocket.CloseAsync(
- receiveResult.CloseStatus.Value,
- receiveResult.CloseStatusDescription,
- CancellationToken.None
- );
+ else
+ {
+ throw new ArgumentException("SyncConfig is null!");
+ }
}
- public async void LocalSocketSendMsg(object msgOb)
+ public async Task RemoteSocketLiten()
+ {
+ string CloseMsg = "任务结束关闭";
+ try
+ {
+ while (RemoteSocket.State == WebSocketState.Open)
+ {
+ var buffer = new byte[1024 * 1024];
+ var receiveResult = await RemoteSocket.ReceiveAsync(
+ new ArraySegment(buffer),
+ CancellationToken.None
+ );
+ if (receiveResult.MessageType == WebSocketMessageType.Close)
+ {
+ Close(receiveResult.CloseStatusDescription);
+ }
+ else
+ {
+ var nbuffer = new byte[receiveResult.Count];
+ System.Buffer.BlockCopy(buffer, 0, nbuffer, 0, receiveResult.Count);
+ StateHelper.ReceiveRemoteMsg(nbuffer);
+ }
+ }
+ }
+ catch (Exception e)
+ {
+ CloseMsg = e.Message;
+ }
+ finally
+ {
+ Close(CloseMsg);
+ }
+ }
+
+ public async Task LocalSocketListen()
+ {
+ string CloseMsg = "任务结束关闭";
+ try
+ {
+ //最大1MB!=
+ var buffer = new byte[1024 * 1024];
+
+ while (LocalSocket.State == WebSocketState.Open)
+ {
+ var receiveResult = await LocalSocket.ReceiveAsync(
+ new ArraySegment(buffer),
+ CancellationToken.None
+ );
+
+ if (receiveResult.MessageType == WebSocketMessageType.Close)
+ {
+ Close(receiveResult.CloseStatusDescription);
+ }
+ else
+ {
+ StateHelper.ReceiveLocalMsg(
+ Encoding.UTF8.GetString(buffer, 0, receiveResult.Count)
+ );
+ }
+ }
+ }
+ catch (Exception e)
+ {
+ CloseMsg = e.Message;
+ }
+ finally
+ {
+ Close(CloseMsg);
+ }
+ }
+
+ public async Task LocalSocketSendMsg(object msgOb)
{
string msg = JsonSerializer.Serialize(msgOb);
await LocalSocket.SendAsync(
@@ -92,7 +157,7 @@ public class LocalSyncServer
);
}
- public async void RemoteSocketSendMsg(object msgOb)
+ public async Task RemoteSocketSendMsg(object msgOb)
{
string msg = JsonSerializer.Serialize(msgOb);
var buffer = AESHelper.EncryptStringToBytes_Aes(msg);
@@ -104,5 +169,40 @@ public class LocalSyncServer
);
}
- public void Close() { }
+ public void Close(string? CloseReason)
+ {
+ try
+ {
+ if (LocalSocket.State == WebSocketState.Open)
+ {
+ LocalSocket
+ .CloseAsync(
+ WebSocketCloseStatus.NormalClosure,
+ CloseReason,
+ CancellationToken.None
+ )
+ .Wait(60 * 1000);
+ }
+
+ if (RemoteSocket.State == WebSocketState.Open)
+ {
+ RemoteSocket
+ .CloseAsync(
+ WebSocketCloseStatus.NormalClosure,
+ CloseReason,
+ CancellationToken.None
+ )
+ .Wait(60 * 1000);
+ }
+ }
+ catch (Exception e)
+ {
+ //TODO 日志
+ Console.WriteLine(e.Message);
+ }
+ finally
+ {
+ Factory.RemoveLocalSyncServer(this);
+ }
+ }
}
diff --git a/Server/LocalServer/LocalSyncServerFactory.cs b/Server/LocalServer/LocalSyncServerFactory.cs
index a273c83..19aada5 100644
--- a/Server/LocalServer/LocalSyncServerFactory.cs
+++ b/Server/LocalServer/LocalSyncServerFactory.cs
@@ -6,18 +6,21 @@ public class LocalSyncServerFactory
{
private readonly object Lock = new();
- public async void CreateLocalSyncServer(WebSocket socket, string name)
+ public 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!");
+ throw new Exception("LocalServer:存在同名发布源!");
}
var server = new LocalSyncServer(socket, name, this);
lock (Lock)
{
Servers.Add(server);
}
- await server.Start();
+ //脱离当前函数栈
+ Task.Run(async ()=>{
+ await server.LocalSocketListen();
+ });
}
private readonly List Servers = [];
diff --git a/Server/LocalServer/Models/Config.cs b/Server/LocalServer/Models/Config.cs
deleted file mode 100644
index 222fa79..0000000
--- a/Server/LocalServer/Models/Config.cs
+++ /dev/null
@@ -1,32 +0,0 @@
-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/Program.cs b/Server/LocalServer/Program.cs
index 652ebf8..94106d7 100644
--- a/Server/LocalServer/Program.cs
+++ b/Server/LocalServer/Program.cs
@@ -1,6 +1,14 @@
using LocalServer;
-var builder = WebApplication.CreateBuilder(args);
+var builder = WebApplication.CreateBuilder(args);
+ConfigurationBuilder configurationBuilder = new ();
+
+//添加配置文件路径
+configurationBuilder.SetBasePath(Directory.GetCurrentDirectory()).AddJsonFile("appsettings.json");
+
+//加载文件
+IConfiguration _configuration = configurationBuilder.Build();
+LocalSyncServer.TempRootFile = _configuration["TempDir"]??"C:/TempPack";;
// Add services to the container.
builder.Services.AddControllers();
@@ -11,7 +19,6 @@ builder.Services.AddSwaggerGen();
builder.Services.AddSingleton();
var app = builder.Build();
-
// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
@@ -20,7 +27,8 @@ if (app.Environment.IsDevelopment())
}
app.UseWebSockets();
app.UseAuthorization();
-
+app.Urls.Clear();
+app.Urls.Add("http://0.0.0.0:6818");
app.MapControllers();
app.Run();
diff --git a/Server/LocalServer/StateHelper.cs b/Server/LocalServer/StateHelper.cs
index 707f2f9..4dfc78f 100644
--- a/Server/LocalServer/StateHelper.cs
+++ b/Server/LocalServer/StateHelper.cs
@@ -1,11 +1,7 @@
-using System.Net.NetworkInformation;
-using System.Net.WebSockets;
-using System.Runtime.Intrinsics.Arm;
-using System.Security.AccessControl;
-using System.Text;
+using System.Diagnostics;
+using System.Runtime.InteropServices;
using System.Text.Json;
using Common;
-using LocalServer.Models;
namespace LocalServer;
@@ -17,173 +13,403 @@ namespace LocalServer;
// RemoteUnPackAndRelease = 3,
// }
-public abstract class StateHelpBase(LocalSyncServer context)
+public abstract class StateHelpBase(
+ LocalSyncServer context,
+ SyncProcessStep step = SyncProcessStep.Connect
+)
{
protected readonly LocalSyncServer Context = context;
- public abstract void HandleRemoteMsg(SyncMsg? msg);
+ protected readonly SyncProcessStep Step = step;
- public abstract void HandleLocalMsg(SyncMsg? msg);
+ public SyncMsg CreateErrMsg(string Body)
+ {
+ return new SyncMsg(SyncMsgType.Error, Step, Body);
+ }
+
+ public SyncMsg CreateMsg(string body, SyncMsgType type = SyncMsgType.General)
+ {
+ return new SyncMsg(type, Step, body);
+ }
+
+ public void ReceiveLocalMsg(string msg)
+ {
+ var syncMsg =
+ JsonSerializer.Deserialize(msg)
+ ?? throw new NullReferenceException("msg is null");
+ if (syncMsg.Step != Step)
+ {
+ throw new Exception("Sync step error!");
+ }
+ HandleLocalMsg(syncMsg);
+ }
+
+ public void ReceiveRemoteMsg(byte[] bytes)
+ {
+ var msg = AESHelper.DecryptStringFromBytes_Aes(bytes);
+
+ var syncMsg =
+ JsonSerializer.Deserialize(msg)
+ ?? throw new NullReferenceException("msg is null");
+ if (syncMsg.Step != Step)
+ {
+ throw new Exception("Sync step error!");
+ }
+ HandleLocalMsg(syncMsg);
+ }
+
+ protected abstract void HandleRemoteMsg(SyncMsg msg);
+
+ protected abstract void HandleLocalMsg(SyncMsg msg);
}
///
-/// 0. 发布源验证密码
+/// 0. 链接验证
///
///
-public class LocalAuthorityState(LocalSyncServer context) : StateHelpBase(context)
+public class ConnectAuthorityHelper(LocalSyncServer context)
+ : StateHelpBase(context, SyncProcessStep.Connect)
{
- public override void HandleRemoteMsg(SyncMsg? msg)
+ // 如果密码错误,那么就直接关闭连接,不会进入这个方法
+ protected override void HandleRemoteMsg(SyncMsg msg)
{
- throw new NotImplementedException("error usage!");
+ //将remote的消息传递到前端界面
+ Context.LocalSocketSendMsg(msg).Wait();
+ //下一步
+ var deployHelper = new DeployHelper(Context);
+ Context.StateHelper = deployHelper;
+ deployHelper.DeployProcess();
}
- public override void HandleLocalMsg(SyncMsg? msg)
+ protected override void HandleLocalMsg(SyncMsg msg)
{
- if (msg == null)
+ //收到配置文件
+ var config = JsonSerializer.Deserialize(msg.Body);
+ Context.SyncConfig = config;
+ Context.RemoteSocketConnect().Wait(60 * 1000);
+ Task.Run(async () =>
{
- return;
- }
- else
- {
- string Pwd = msg.Body;
- if (Pwd == "Xfs1%$@_fdYU.>>")
+ if (Context.SyncConfig != null)
{
- Context.LocalSocketSendMsg(new SyncMsg(true, "源服务密码校验成功!"));
- Context.StateHelper = new WaitingConfigInfoState(Context);
+ await Context.RemoteSocketSendMsg(CreateMsg(Context.SyncConfig.RemotePwd));
}
else
{
- throw new UnauthorizedAccessException("pwd error!");
+ throw new NullReferenceException("Config is null!");
}
- }
+ await Context.RemoteSocketLiten();
+ });
}
}
///
-/// 1. 获取配置信息,它包含目标的服务器的配置信息
+/// 1. 执行发布步骤
///
///
-public class WaitingConfigInfoState(LocalSyncServer context) : StateHelpBase(context)
+public class DeployHelper(LocalSyncServer context)
+ : StateHelpBase(context, SyncProcessStep.DeployProject)
{
- public override void HandleRemoteMsg(SyncMsg? msg) { }
-
- public override void HandleLocalMsg(SyncMsg? msg)
+ public void DeployProcess()
{
- if (msg == null)
+ if (Context.NotNullSyncConfig.IsDeployProject == false)
{
- return;
+ Context.LocalSocketSendMsg(CreateMsg("配置为不发布跳过此步骤")).Wait();
+ var h = new DiffFileAndPackHelper(Context);
+ Context.StateHelper = h;
+ h.DiffProcess();
}
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 (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
{
- if (Context.RemoteSocket.State == WebSocketState.Open)
+ ProcessStartInfo startInfo =
+ new()
+ {
+ FileName = "cmd.exe", // The command to execute (can be any command line tool)
+ Arguments =
+ $"msdeploy.exe -verb:sync -source:contentPath={Context.NotNullSyncConfig.LocalProjectAbsolutePath} -dest:contentPath={Context.NotNullSyncConfig.LocalRootPath} -disablerule:BackupRule",
+ // The arguments to pass to the command (e.g., list directory contents)
+ RedirectStandardOutput = true, // Redirect the standard output to a string
+ UseShellExecute = false, // Do not use the shell to execute the command
+ CreateNoWindow = true // Do not create a new window for the command
+ };
+ using Process process = new() { StartInfo = startInfo };
+ // Start the process
+ process.Start();
+
+ // Read the output from the process
+ string output = process.StandardOutput.ReadToEnd();
+
+ // Wait for the process to exit
+ process.WaitForExit();
+
+ if (process.ExitCode == 0)
{
- var state = new RemoteAuthorityState(Context);
- state.SendPwdToRemoteServer();
- Context.StateHelper = state;
+ Context.LocalSocketSendMsg(CreateMsg("发布成功!")).Wait();
+ var h = new DiffFileAndPackHelper(Context);
+ Context.StateHelper = h;
+ h.DiffProcess();
}
else
{
- throw new Exception("connect remote server failed!");
+ Context.LocalSocketSendMsg(CreateErrMsg(output)).Wait();
+ throw new Exception("执行发布错误,错误信息参考上一条消息!");
}
}
else
{
- throw new TimeoutException("connect remote server timeout");
+ throw new NotSupportedException("只支持windows!");
}
}
}
+
+ protected override void HandleRemoteMsg(SyncMsg msg) { }
+
+ protected override void HandleLocalMsg(SyncMsg msg) { }
}
-///
-/// 2. 目标服务器权限校验
-///
-///
-public class RemoteAuthorityState(LocalSyncServer context) : StateHelpBase(context)
+public class DiffFileAndPackHelper(LocalSyncServer context)
+ : StateHelpBase(context, SyncProcessStep.DiffFileAndPack)
{
- public override void HandleRemoteMsg(SyncMsg? msg)
+ public void DiffProcess()
{
- if (msg == null)
+ //提取本地文件的信息
+ Context.NotNullSyncConfig.DirFileConfigs.ForEach(e =>
{
- return;
- }
- else { }
+ e.LocalDirInfo = new Dir(Context.NotNullSyncConfig.LocalRootPath + e.DirPath);
+ e.LocalDirInfo.ExtractInfo(e.CherryPicks, e.Excludes);
+ });
+ //将配置信息发送到remoteServer
+ Context
+ .RemoteSocketSendMsg(CreateMsg(JsonSerializer.Serialize(Context.NotNullSyncConfig)))
+ .Wait();
}
- public override void HandleLocalMsg(SyncMsg? msg) { }
+ protected override void HandleLocalMsg(SyncMsg msg) { }
+
+ protected override void HandleRemoteMsg(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 { }
- }
+// ///
+// /// 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) { }
-}
+// public override void HandleLocalMsg(SyncMsg? msg)
+// {
+// if (msg == null)
+// {
+// return;
+// }
+// else
+// {
+// string Pwd = msg.Body;
+// if (Pwd == "Xfs1%$@_fdYU.>>")
+// {
+// Context.LocalSocketSendMsg(new SyncMsg(SyncMsgType.Success, "源服务密码校验成功!"));
+// Context.StateHelper = new WaitingConfigInfoState(Context);
+// }
+// else
+// {
+// throw new UnauthorizedAccessException("pwd error!");
+// }
+// }
+// }
+// }
-///
-/// 4. 本地打包并上传
-///
-///
-public class LocalPackAndUploadState(LocalSyncServer context) : StateHelpBase(context)
-{
- public override void HandleRemoteMsg(SyncMsg? msg)
- {
- if (msg == null)
- {
- return;
- }
- else { }
- }
+// ///
+// /// 1. 获取配置信息,它包含目标的服务器的配置信息
+// ///
+// ///
+// public class WaitingConfigInfoState(LocalSyncServer context) : StateHelpBase(context)
+// {
+// public override void HandleRemoteMsg(SyncMsg? msg) { }
- public override void HandleLocalMsg(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");
+// }
+// }
+// }
+// }
-///
-/// 5. 目标服务器解包并发布
-///
-///
-public class RemoteUnPackAndReleaseState(LocalSyncServer context) : StateHelpBase(context)
-{
- public override void HandleRemoteMsg(SyncMsg? msg)
- {
- if (msg == null)
- {
- return;
- }
- else { }
- }
+// ///
+// /// 2. 目标服务器权限校验
+// ///
+// ///
+// public class RemoteAuthorityState(LocalSyncServer context) : StateHelpBase(context)
+// {
+// public override void HandleRemoteMsg(SyncMsg? msg)
+// {
+// if (msg == null)
+// {
+// return;
+// }
+// else
+// {
+// if (msg.Type == SyncMsgType.Success)
+// {
+// Context.LocalSocketSendMsg(new SyncMsg(SyncMsgType.Success, "远程服务器校验成功!"));
+// var diffState = new DirFilesDiffState(Context);
+// diffState.SendSyncConfigToRemote();
+// Context.StateHelper = diffState;
+// }
+// else
+// {
+// throw new Exception("远程服务器权限校验失败,请检查Local Server 的Mac地址是否在 Remote Server 的允许列表内!");
+// }
+// }
+// }
- public override void HandleLocalMsg(SyncMsg? msg) { }
-}
+// 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
+// {
+// if (msg.IsSuccess)
+// {
+// var state = new LocalPackAndUploadState(Context);
+// state.PackDiffDir(msg);
+// Context.StateHelper = state;
+// }
+// else
+// {
+// throw new Exception(msg.Body);
+// }
+// }
+// }
+
+// public override void HandleLocalMsg(SyncMsg? msg) { }
+
+// public void SendSyncConfigToRemote()
+// {
+// Context.RemoteSocketSendMsg(
+// Context.SyncConfig
+// ?? throw new NullReferenceException("SyncConfig should't be null here!")
+// );
+// }
+// }
+
+// ///
+// /// 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) { }
+
+// ///
+// /// 打包文件
+// ///
+// ///
+// ///
+// public void PackDiffDir(SyncMsg msg)
+// {
+// if (msg.IsSuccess)
+// {
+// var diff = JsonSerializer.Deserialize(msg.Body);
+
+// Context.LocalSocketSendMsg(new SyncMsg(SyncMsgType.Success, "文件打包完成!"));
+// Context.LocalSocketSendMsg(new SyncMsg(SyncMsgType.Success, "文件上传完成!"));
+// }
+// else
+// {
+// throw new Exception(msg.Body);
+// }
+// }
+
+// private void UploadPackedFiles(string absolutePath)
+// {
+// //TODO 传递上传进度到前端。
+// }
+// }
+
+// ///
+// /// 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) { }
+// }
diff --git a/Server/LocalServer/appsettings.json b/Server/LocalServer/appsettings.json
index 10f68b8..234a3a5 100644
--- a/Server/LocalServer/appsettings.json
+++ b/Server/LocalServer/appsettings.json
@@ -5,5 +5,6 @@
"Microsoft.AspNetCore": "Warning"
}
},
- "AllowedHosts": "*"
+ "AllowedHosts": "*",
+ "TempDir":"D:/TempPack"
}
diff --git a/Server/RemoteServer/Models/SqliteDbContext.cs b/Server/RemoteServer/Models/SqliteDbContext.cs
index c1c0150..0faec09 100644
--- a/Server/RemoteServer/Models/SqliteDbContext.cs
+++ b/Server/RemoteServer/Models/SqliteDbContext.cs
@@ -2,14 +2,9 @@
namespace RemoteServer.Models;
-public class SqliteDbContext : DbContext
+public class SqliteDbContext(IConfiguration configuration) : DbContext
{
- protected readonly IConfiguration Configuration;
-
- public SqliteDbContext(IConfiguration configuration)
- {
- Configuration = configuration;
- }
+ protected readonly IConfiguration Configuration = configuration;
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
diff --git a/Server/RemoteServer/Program.cs b/Server/RemoteServer/Program.cs
index e74c114..6e0b33b 100644
--- a/Server/RemoteServer/Program.cs
+++ b/Server/RemoteServer/Program.cs
@@ -1,7 +1,6 @@
using Microsoft.EntityFrameworkCore;
using RemoteServer.Models;
-using RemoteServer;
var builder = WebApplication.CreateBuilder(args);
// Add services to the container.
@@ -22,8 +21,9 @@ if (app.Environment.IsDevelopment())
app.UseSwagger();
app.UseSwaggerUI();
}
+app.UseWebSockets();
app.Urls.Clear();
-app.Urls.Add("http://0.0.0.0:6888");
+app.Urls.Add("http://0.0.0.0:6828");
app.MapControllers();
app.Run();
diff --git a/Server/RemoteServer/RemoteSyncServer.cs b/Server/RemoteServer/RemoteSyncServer.cs
new file mode 100644
index 0000000..40d33a3
--- /dev/null
+++ b/Server/RemoteServer/RemoteSyncServer.cs
@@ -0,0 +1,157 @@
+
+using System.Net.WebSockets;
+using System.Text;
+using System.Text.Json;
+using Common;
+
+namespace RemoteServer;
+
+public class RemoteSyncServer
+{
+#pragma warning disable CA2211 // Non-constant fields should not be visible
+ public static string TempRootFile = "C:/TempPack";
+#pragma warning restore CA2211 // Non-constant fields should not be visible
+ // public StateHelpBase StateHelper;
+
+ public Config? SyncConfig;
+
+ public Config NotNullSyncConfig {get {
+ if (SyncConfig == null)
+ {
+ throw new ArgumentNullException("SyncConfig");
+ }
+ return SyncConfig;
+ }}
+
+ ///
+ /// remote server
+ ///
+ public readonly WebSocket RemoteSocket;
+
+ ///
+ /// 发布源-缓冲区,存储数据 最大1MB
+ ///
+ public byte[] Buffer = new byte[1024 * 1024];
+
+
+ ///
+ /// 发布开始时间
+ ///
+ private readonly DateTime StartTime = DateTime.Now;
+
+ ///
+ /// 发布名称
+ ///
+ public readonly string Name;
+
+ ///
+ /// 父工程,用于释放资源
+ ///
+ public readonly RemoteSyncServerFactory Factory;
+
+ public RemoteSyncServer(WebSocket socket, string name, RemoteSyncServerFactory factory)
+ {
+ RemoteSocket = socket;
+ Name = name;
+ Factory = factory;
+ // StateHelper = new ConnectAuthorityHelper(this);
+ }
+
+
+ public async Task RemoteSocketListen()
+ {
+ string CloseMsg = "任务结束关闭";
+ try
+ {
+ //最大1MB!=
+ var buffer = new byte[1024 * 1024];
+
+ while (RemoteSocket.State == WebSocketState.Open)
+ {
+ var receiveResult = await RemoteSocket.ReceiveAsync(
+ new ArraySegment(buffer),
+ CancellationToken.None
+ );
+
+ if (receiveResult.MessageType == WebSocketMessageType.Close)
+ {
+ Close(receiveResult.CloseStatusDescription);
+ }
+ else
+ {
+ // StateHelper.ReceiveLocalMsg(
+ // Encoding.UTF8.GetString(buffer, 0, receiveResult.Count)
+ // );
+ }
+ }
+ }
+ catch (Exception e)
+ {
+ CloseMsg = e.Message;
+ }
+ finally
+ {
+ Close(CloseMsg);
+ }
+ }
+
+ // public async Task LocalSocketSendMsg(object msgOb)
+ // {
+ // string msg = JsonSerializer.Serialize(msgOb);
+ // await RemoteSocket.SendAsync(
+ // new ArraySegment(Encoding.UTF8.GetBytes(msg)),
+ // WebSocketMessageType.Text,
+ // true,
+ // CancellationToken.None
+ // );
+ // }
+
+ public async Task 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(string? CloseReason)
+ {
+ try
+ {
+ if (RemoteSocket.State == WebSocketState.Open)
+ {
+ RemoteSocket
+ .CloseAsync(
+ WebSocketCloseStatus.NormalClosure,
+ CloseReason,
+ CancellationToken.None
+ )
+ .Wait(60 * 1000);
+ }
+
+ if (RemoteSocket.State == WebSocketState.Open)
+ {
+ RemoteSocket
+ .CloseAsync(
+ WebSocketCloseStatus.NormalClosure,
+ CloseReason,
+ CancellationToken.None
+ )
+ .Wait(60 * 1000);
+ }
+ }
+ catch (Exception e)
+ {
+ //TODO 日志
+ Console.WriteLine(e.Message);
+ }
+ finally
+ {
+ Factory.RemoveLocalSyncServer(this);
+ }
+ }
+}
diff --git a/Server/RemoteServer/RemoteSyncServerFactory.cs b/Server/RemoteServer/RemoteSyncServerFactory.cs
new file mode 100644
index 0000000..371f5fb
--- /dev/null
+++ b/Server/RemoteServer/RemoteSyncServerFactory.cs
@@ -0,0 +1,35 @@
+using System.Net.WebSockets;
+
+namespace RemoteServer;
+
+public class RemoteSyncServerFactory
+{
+ private readonly object Lock = new();
+
+ public void CreateLocalSyncServer(WebSocket socket, string name)
+ {
+ if (Servers.Select(x => x.Name == name).Any())
+ {
+ throw new Exception("RemoteServer:存在同名发布源!");
+ }
+ var server = new RemoteSyncServer(socket, name, this);
+ lock (Lock)
+ {
+ Servers.Add(server);
+ }
+ //脱离当前函数栈
+ Task.Run(async ()=>{
+ await server.RemoteSocketListen();
+ });
+ }
+
+ private readonly List Servers = [];
+
+ public void RemoveLocalSyncServer(RemoteSyncServer server)
+ {
+ lock (Lock)
+ {
+ Servers.Remove(server);
+ }
+ }
+}
diff --git a/Server/RemoteServer/StateHelper.cs b/Server/RemoteServer/StateHelper.cs
new file mode 100644
index 0000000..48c0364
--- /dev/null
+++ b/Server/RemoteServer/StateHelper.cs
@@ -0,0 +1,415 @@
+using System.Diagnostics;
+using System.Runtime.InteropServices;
+using System.Text.Json;
+using Common;
+
+namespace RemoteServer;
+
+// enum StateWhenMsg
+// {
+// Authority = 0,
+// ConfigInfo = 1,
+// LocalPackAndUpload = 2,
+// RemoteUnPackAndRelease = 3,
+// }
+
+public abstract class StateHelpBase(
+ LocalSyncServer context,
+ SyncProcessStep step = SyncProcessStep.Connect
+)
+{
+ protected readonly LocalSyncServer Context = context;
+
+ protected readonly SyncProcessStep Step = step;
+
+ public SyncMsg CreateErrMsg(string Body)
+ {
+ return new SyncMsg(SyncMsgType.Error, Step, Body);
+ }
+
+ public SyncMsg CreateMsg(string body, SyncMsgType type = SyncMsgType.General)
+ {
+ return new SyncMsg(type, Step, body);
+ }
+
+ public void ReceiveLocalMsg(string msg)
+ {
+ var syncMsg =
+ JsonSerializer.Deserialize(msg)
+ ?? throw new NullReferenceException("msg is null");
+ if (syncMsg.Step != Step)
+ {
+ throw new Exception("Sync step error!");
+ }
+ HandleLocalMsg(syncMsg);
+ }
+
+ public void ReceiveRemoteMsg(byte[] bytes)
+ {
+ var msg = AESHelper.DecryptStringFromBytes_Aes(bytes);
+
+ var syncMsg =
+ JsonSerializer.Deserialize(msg)
+ ?? throw new NullReferenceException("msg is null");
+ if (syncMsg.Step != Step)
+ {
+ throw new Exception("Sync step error!");
+ }
+ HandleLocalMsg(syncMsg);
+ }
+
+ protected abstract void HandleRemoteMsg(SyncMsg msg);
+
+ protected abstract void HandleLocalMsg(SyncMsg msg);
+}
+
+///
+/// 0. 链接验证
+///
+///
+public class ConnectAuthorityHelper(LocalSyncServer context)
+ : StateHelpBase(context, SyncProcessStep.Connect)
+{
+ // 如果密码错误,那么就直接关闭连接,不会进入这个方法
+ protected override void HandleRemoteMsg(SyncMsg msg)
+ {
+ //将remote的消息传递到前端界面
+ Context.LocalSocketSendMsg(msg).Wait();
+ //下一步
+ var deployHelper = new DeployHelper(Context);
+ Context.StateHelper = deployHelper;
+ deployHelper.DeployProcess();
+ }
+
+ protected override void HandleLocalMsg(SyncMsg msg)
+ {
+ //收到配置文件
+ var config = JsonSerializer.Deserialize(msg.Body);
+ Context.SyncConfig = config;
+ Context.RemoteSocketConnect().Wait(60 * 1000);
+ Task.Run(async () =>
+ {
+ if (Context.SyncConfig != null)
+ {
+ await Context.RemoteSocketSendMsg(CreateMsg(Context.SyncConfig.RemotePwd));
+ }
+ else
+ {
+ throw new NullReferenceException("Config is null!");
+ }
+ await Context.RemoteSocketLiten();
+ });
+ }
+}
+
+///
+/// 1. 执行发布步骤
+///
+///
+public class DeployHelper(LocalSyncServer context)
+ : StateHelpBase(context, SyncProcessStep.DeployProject)
+{
+ public void DeployProcess()
+ {
+ if (Context.NotNullSyncConfig.IsDeployProject == false)
+ {
+ Context.LocalSocketSendMsg(CreateMsg("配置为不发布跳过此步骤")).Wait();
+ var h = new DiffFileAndPackHelper(Context);
+ Context.StateHelper = h;
+ h.DiffProcess();
+ }
+ else
+ {
+ if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
+ {
+ ProcessStartInfo startInfo =
+ new()
+ {
+ FileName = "cmd.exe", // The command to execute (can be any command line tool)
+ Arguments =
+ $"msdeploy.exe -verb:sync -source:contentPath={Context.NotNullSyncConfig.LocalProjectAbsolutePath} -dest:contentPath={Context.NotNullSyncConfig.LocalRootPath} -disablerule:BackupRule",
+ // The arguments to pass to the command (e.g., list directory contents)
+ RedirectStandardOutput = true, // Redirect the standard output to a string
+ UseShellExecute = false, // Do not use the shell to execute the command
+ CreateNoWindow = true // Do not create a new window for the command
+ };
+ using Process process = new() { StartInfo = startInfo };
+ // Start the process
+ process.Start();
+
+ // Read the output from the process
+ string output = process.StandardOutput.ReadToEnd();
+
+ // Wait for the process to exit
+ process.WaitForExit();
+
+ if (process.ExitCode == 0)
+ {
+ Context.LocalSocketSendMsg(CreateMsg("发布成功!")).Wait();
+ var h = new DiffFileAndPackHelper(Context);
+ Context.StateHelper = h;
+ h.DiffProcess();
+ }
+ else
+ {
+ Context.LocalSocketSendMsg(CreateErrMsg(output)).Wait();
+ throw new Exception("执行发布错误,错误信息参考上一条消息!");
+ }
+ }
+ else
+ {
+ throw new NotSupportedException("只支持windows!");
+ }
+ }
+ }
+
+ protected override void HandleRemoteMsg(SyncMsg msg) { }
+
+ protected override void HandleLocalMsg(SyncMsg msg) { }
+}
+
+public class DiffFileAndPackHelper(LocalSyncServer context)
+ : StateHelpBase(context, SyncProcessStep.DiffFileAndPack)
+{
+ public void DiffProcess()
+ {
+ //提取本地文件的信息
+ Context.NotNullSyncConfig.DirFileConfigs.ForEach(e =>
+ {
+ e.LocalDirInfo = new Dir(Context.NotNullSyncConfig.LocalRootPath + e.DirPath);
+ e.LocalDirInfo.ExtractInfo(e.CherryPicks, e.Excludes);
+ });
+ //将配置信息发送到remoteServer
+ Context
+ .RemoteSocketSendMsg(CreateMsg(JsonSerializer.Serialize(Context.NotNullSyncConfig)))
+ .Wait();
+ }
+
+ protected override void HandleLocalMsg(SyncMsg msg) { }
+
+ protected override void HandleRemoteMsg(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(SyncMsgType.Success, "源服务密码校验成功!"));
+// 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
+// {
+// if (msg.Type == SyncMsgType.Success)
+// {
+// Context.LocalSocketSendMsg(new SyncMsg(SyncMsgType.Success, "远程服务器校验成功!"));
+// var diffState = new DirFilesDiffState(Context);
+// diffState.SendSyncConfigToRemote();
+// Context.StateHelper = diffState;
+// }
+// else
+// {
+// throw new Exception("远程服务器权限校验失败,请检查Local Server 的Mac地址是否在 Remote Server 的允许列表内!");
+// }
+// }
+// }
+
+// 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
+// {
+// if (msg.IsSuccess)
+// {
+// var state = new LocalPackAndUploadState(Context);
+// state.PackDiffDir(msg);
+// Context.StateHelper = state;
+// }
+// else
+// {
+// throw new Exception(msg.Body);
+// }
+// }
+// }
+
+// public override void HandleLocalMsg(SyncMsg? msg) { }
+
+// public void SendSyncConfigToRemote()
+// {
+// Context.RemoteSocketSendMsg(
+// Context.SyncConfig
+// ?? throw new NullReferenceException("SyncConfig should't be null here!")
+// );
+// }
+// }
+
+// ///
+// /// 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) { }
+
+// ///
+// /// 打包文件
+// ///
+// ///
+// ///
+// public void PackDiffDir(SyncMsg msg)
+// {
+// if (msg.IsSuccess)
+// {
+// var diff = JsonSerializer.Deserialize(msg.Body);
+
+// Context.LocalSocketSendMsg(new SyncMsg(SyncMsgType.Success, "文件打包完成!"));
+// Context.LocalSocketSendMsg(new SyncMsg(SyncMsgType.Success, "文件上传完成!"));
+// }
+// else
+// {
+// throw new Exception(msg.Body);
+// }
+// }
+
+// private void UploadPackedFiles(string absolutePath)
+// {
+// //TODO 传递上传进度到前端。
+// }
+// }
+
+// ///
+// /// 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) { }
+// }
diff --git a/Server/ServerTest/FilesSeed.cs b/Server/ServerTest/FilesSeed.cs
index c5b37e9..24ee286 100644
--- a/Server/ServerTest/FilesSeed.cs
+++ b/Server/ServerTest/FilesSeed.cs
@@ -1,5 +1,4 @@
-using System.Text;
-using Common;
+using Common;
namespace ServerTest;
diff --git a/Tool/JsScript/release.js b/Tool/JsScript/release.js
new file mode 100644
index 0000000..4e99fc2
--- /dev/null
+++ b/Tool/JsScript/release.js
@@ -0,0 +1,115 @@
+import chalk from "chalk";
+import WebSocket from "ws";
+
+
+//#region ############################## 配置文件 ###################################
+
+//这是个例子,请在`config`中写你的配置
+const example_config = {
+ //发布的项目名称,它的目的是为了防止有两个人同时发布一个项目,和便于排查发布历史记录
+ Name: "",
+ //发布服务器的地址
+ RemoteUrl: "http://192.168.1.100:8067",
+ //源SqlServer数据库的链接字符串,一般是开发或者测试数据库,**此数据库的ip是相对于运行此脚本的机器**
+ SrcDbConnection: "",
+ //目的SqlServer数据库的链接字符串,一般是正式环境数据库,**此数据库的ip是相对于运行RemoteServer的机器**
+ DstDbConnection: "",
+
+ //发布数据库时,只同步结构。此数组中的表,将会连数据也一起同步
+ SyncDataTables:[],
+ //源文件目录地址,是要发布的文件根目录,它是绝对路径,!执行发布时将发布到这个目录!
+ LocalRootPath: "",
+ //目标文件目录地址,也就是部署服务的机器上的项目文件根目录,它是绝对路径
+ RemoteRootPath: "",
+
+ //根目录下的子目录,分子目录是为了针对不同的目录有不同的发布策略,它是相对路径,即相对于LocalRootPath和RemoteRootPath,文件数据依此进行
+ DirFileConfigs: [
+ {
+ //子目录的相对路径
+ DirPath: "",
+ //排除的文件或目录,它是相对路径,相对于!!!LocalRootPath和RemoteRootPath!!!
+ Excludes: [],
+ //只追踪文件或目录,它是相对路径,相对于!!!LocalRootPath和RemoteRootPath!!!,它的优先级最高,如果你指定了它的值,Excludes将会失效
+ CherryPicks: [],
+ },
+ ],
+};
+const config = {};
+//#endregion
+
+//#region ############################## 打印函数 ###################################
+
+/**
+ * 在新行打印错误信息
+ */
+function PrintErrInNewLine(str) {
+ process.stdout.write("\n");
+ var chunk = chalk["red"]("[错误]: ");
+ process.stdout.write(chunk);
+ process.stdout.write(str);
+}
+
+/**
+ * 在新行打印成功信息
+ */
+function PrintSuccessInNewLine(str) {
+ process.stdout.write("\n");
+ var chunk = chalk["green"]("[成功]: ");
+ process.stdout.write(chunk);
+ process.stdout.write(str);
+}
+
+/**
+ * 在新行打印一般信息
+ */
+function PrintGeneralInNewLine(str) {
+ process.stdout.write("\n");
+ process.stdout.write(str);
+}
+/**
+ * 在当前行打印一般信息,打印此行信息之前会清除当前行
+ */
+function PrintGeneralInCurrentLine(str) {
+ process.stdout.write(`\r${str}`);
+}
+
+//#endregion
+
+//#region ############################### work #############################################
+
+/**
+ * 1-n. localServer 工作,此处只展示信息
+ */
+
+//这个是固定死的
+const wsUrl = `ws://127.0.0.1:4538/websoc?Name=${config.Name}`;
+const ws = new WebSocket(wsUrl);
+
+ws.on('open', () => {
+ //上传配置
+ ws.send(JSON.stringify(config),(err)=>{
+ console.log(err)
+ ws.close()
+ })
+
+});
+
+ws.on('message', (message) => {
+ var msg = message.toString('utf8')
+ DealMsg(msg)
+});
+
+ws.on('close', () => {
+});
+
+function DealMsg(str) {
+ var msg = JSON.parse(str)
+ if(msg.IsSuccess) {
+
+ } else {
+ PrintErrInNewLine(msg.Body)
+ ws.close()
+ }
+
+}
+//#endregion
diff --git a/Tool/JsScript/test.js b/Tool/JsScript/test.js
new file mode 100644
index 0000000..182ed42
--- /dev/null
+++ b/Tool/JsScript/test.js
@@ -0,0 +1,37 @@
+// import WebSocket from 'ws';
+
+// const wsUrl = 'wss://toolin.cn/echo';
+// const ws = new WebSocket(wsUrl);
+
+// ws.on('open', () => {
+// console.log('Connected to WebSocket server');
+// ws.send("赵磊f",(err)=>{
+// console.log(err)
+// })
+
+// });
+
+// ws.on('message', (message) => {
+// var str = message.toString('utf8')
+// if(str.includes("赵磊f")) {
+// ws.close()
+// }
+// console.log('Received message:',str);
+// });
+
+// ws.on('close', () => {
+// console.log('Disconnected from WebSocket server');
+// });
+
+import chalk from 'chalk';
+function logProgress(current, total) {
+ const progressPercentage = (current / total) * 100;
+ var str = `Progress: ${progressPercentage.toFixed(2)}%\r`
+ var x = chalk['red'](str);
+ process.stdout.write(x);
+}
+
+// Example usage:
+setInterval(() => {
+ logProgress(Math.floor(Math.random() * 100), 100);
+}, 100); // Update progress every 100 milliseconds with a random value