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