feat: 增加在各个步骤前后调用扩展命令

c# 异步任务里面的异常,不会在异步之外被捕获。
```
try {

Task.Run(()=>{

throw new Exception()
//此异常不会被捕获

)
};
catch(Exception ex) {
//将不会捕获异常
}
This commit is contained in:
ZhaoLei 2024-10-29 12:46:13 +08:00
parent a0889310d7
commit 4724e96efe
14 changed files with 357 additions and 76 deletions

View file

@ -1,5 +1,33 @@
namespace Common;
public class ExecProcess
{
/// <summary>
/// 步骤
/// </summary>
public SyncProcessStep Step { get; set; }
/// <summary>
/// A after B before
/// </summary>
public required string StepBeforeOrAfter { get; set; }
/// <summary>
/// L Local S Server
/// </summary>
public required string ExecInLocalOrServer { get; set; }
/// <summary>
/// 执行的应用程序名称
/// </summary>
public required string FileName { get; set; }
/// <summary>
/// 执行的应用程序参数
/// </summary>
public required string Argumnets { get; set; }
}
public class DirFileConfig
{
/// <summary>
@ -127,4 +155,9 @@ public class Config
/// 同步的文件夹配置
/// </summary>
public required List<DirFileConfig> DirFileConfigs { get; set; }
/// <summary>
/// 按照步骤执行应用程序扩展列表
/// </summary>
public required List<ExecProcess> ExecProcesses { get; set; }
}

View file

@ -6,7 +6,6 @@ namespace Common;
public abstract class AbsPipeLine(bool isAES)
{
/// <summary>
/// pipeLine工作函数生效期间永久阻塞
/// </summary>
@ -18,6 +17,7 @@ public abstract class AbsPipeLine(bool isAES)
{
return true;
};
/// <summary>
/// 监听pipeline 消息由Work 函数调用
/// </summary>
@ -48,7 +48,6 @@ public abstract class AbsPipeLine(bool isAES)
/// <returns>上传完成时返回</returns>/
public abstract Task UploadFile(string url, string filePath, Func<double, bool> progressCb);
/// <summary>
/// 管道消息是否使用AES加密
/// </summary>
@ -147,7 +146,7 @@ public class WebSocPipeLine<TSocket>(TSocket socket, bool isAES) : AbsPipeLine(i
new SyncMsg
{
Type = SyncMsgType.Error,
Step = SyncProcessStep.CloseError,
Step = SyncProcessStep.Close,
Body = CloseReason ?? ""
}
);

View file

@ -16,7 +16,7 @@ public enum SyncProcessStep
PackSqlServer = 4,
UploadAndUnpack = 5,
Publish = 6,
CloseError = 7
Close = 7
}
public class SyncMsg

View file

@ -1,3 +1,5 @@
using System.Diagnostics;
using System.IO.Pipelines;
using System.Xml.Linq;
using Common;
@ -8,6 +10,7 @@ public class LocalSyncServer
#pragma warning disable CA2211 // Non-constant fields should not be visible
public static string TempRootFile = "C:/TempPack";
public static string SqlPackageAbPath = "sqlpackage";
// 使用msdeploy 将会打包当前可运行的内容,它很有可能不包含最新的构建
//public static string MsdeployAbPath = "msdeploy";
@ -15,16 +18,88 @@ public class LocalSyncServer
//使用msbuild 会缺少.net frame的运行环境 bin\roslyn 里面的内容,第一次需要人为复制一下,后面就就好了。
public static string MSBuildAbPath = "MSBuild";
#pragma warning restore CA2211 // Non-constant fields should not be visible
/// <summary>
/// 连接状态流程管理LocalPipe 和 remotePipe 也在此处交换信息
/// </summary>
private StateHelpBase StateHelper;
public void SetStateHelper(StateHelpBase helper)
{
StateHelper = helper;
try
{
StateHelper = helper;
if (SyncConfig != null)
{
var LastExec = NotNullSyncConfig.ExecProcesses.Find(x =>
{
return x.StepBeforeOrAfter == "A"
&& x.Step == (helper.Step - 1)
&& x.ExecInLocalOrServer == "L";
});
ExecProcess(LastExec);
var CurrentExec = NotNullSyncConfig.ExecProcesses.Find(x =>
{
return x.StepBeforeOrAfter == "B"
&& x.Step == helper.Step
&& x.ExecInLocalOrServer == "L";
});
ExecProcess(CurrentExec);
}
}
catch (Exception ex)
{
Close(ex.Message);
}
}
public void ExecProcess(ExecProcess? ep)
{
if (ep != null)
{
ProcessStartInfo startInfo =
new()
{
StandardOutputEncoding = System.Text.Encoding.UTF8,
Arguments = ep.Argumnets,
FileName = ep.FileName, // The command to execute (can be any command line tool)
// 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)
{
LocalPipe
.SendMsg(
StateHelper.CreateMsg(
$"{ep.Step}-{ep.StepBeforeOrAfter}-{ep.ExecInLocalOrServer}-{ep.FileName} {ep.Argumnets} 执行成功!"
)
)
.Wait();
}
else
{
LocalPipe
.SendMsg(
StateHelper.CreateMsg(
$"{ep.Step}-{ep.StepBeforeOrAfter}-{ep.ExecInLocalOrServer}-{ep.FileName} {ep.Argumnets} 失败 {output}"
)
)
.Wait();
throw new Exception("错误,信息参考上一条消息!");
}
}
}
/// <summary>
@ -103,7 +178,7 @@ public class LocalSyncServer
public readonly AbsPipeLine LocalPipe;
/// <summary>
/// local server 和 remote server 的连接,它有加密
/// local server 和 remote server 的连接,它有加密
/// </summary>
public readonly AbsPipeLine RemotePipe;

View file

@ -14,7 +14,7 @@
"dotnetRunMessages": true,
"launchBrowser": true,
"launchUrl": "swagger",
"applicationUrl": "http://localhost:5014",
"applicationUrl": "http://localhost:6818",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}

View file

@ -40,7 +40,7 @@ public abstract class StateHelpBase(
var syncMsg =
JsonSerializer.Deserialize<SyncMsg>(msg)
?? throw new NullReferenceException("msg is null");
if (syncMsg.Step != Step)
if (syncMsg.Step != Step && syncMsg.Step != SyncProcessStep.Close)
{
throw new Exception("Sync step error!");
}
@ -55,7 +55,7 @@ public abstract class StateHelpBase(
var syncMsg =
JsonSerializer.Deserialize<SyncMsg>(msg)
?? throw new NullReferenceException("msg is null");
if (syncMsg.Step != Step)
if (syncMsg.Step != Step && syncMsg.Step != SyncProcessStep.Close)
{
throw new Exception("Sync step error!");
}
@ -416,7 +416,7 @@ public class FinallyPublishHelper(LocalSyncServer context)
{
protected override void HandleLocalMsg(SyncMsg msg)
{
throw new NotImplementedException();
//throw new NotImplementedException();
}
/// <summary>
@ -425,6 +425,18 @@ public class FinallyPublishHelper(LocalSyncServer context)
/// <param name="msg"></param>
protected override void HandleRemoteMsg(SyncMsg msg)
{
if (msg.Body == "发布完成!")
{
Context.SetStateHelper(new NormalCloseHelper(Context));
}
Context.LocalPipe.SendMsg(msg).Wait();
}
}
public class NormalCloseHelper(LocalSyncServer context)
: StateHelpBase(context, SyncProcessStep.Close)
{
protected override void HandleRemoteMsg(SyncMsg msg) { }
protected override void HandleLocalMsg(SyncMsg msg) { }
}

View file

@ -0,0 +1,17 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
https://go.microsoft.com/fwlink/?LinkID=208121.
-->
<Project>
<PropertyGroup>
<DeleteExistingFiles>false</DeleteExistingFiles>
<ExcludeApp_Data>false</ExcludeApp_Data>
<LaunchSiteAfterPublish>true</LaunchSiteAfterPublish>
<LastUsedBuildConfiguration>Release</LastUsedBuildConfiguration>
<LastUsedPlatform>Any CPU</LastUsedPlatform>
<PublishProvider>FileSystem</PublishProvider>
<PublishUrl>bin\Release\net8.0\publish\</PublishUrl>
<WebPublishMethod>FileSystem</WebPublishMethod>
<_TargetId>Folder</_TargetId>
</PropertyGroup>
</Project>

View file

@ -0,0 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
https://go.microsoft.com/fwlink/?LinkID=208121.
-->
<Project>
<PropertyGroup>
<History>True|2024-10-28T10:24:22.2791029Z||;True|2024-10-28T17:30:28.6485638+08:00||;False|2024-10-28T17:29:49.7948159+08:00||;</History>
<LastFailureDetails />
<_PublishTargetUrl>D:\git\FileSqlServerSync\Server\RemoteServer\bin\Release\net8.0\publish\</_PublishTargetUrl>
</PropertyGroup>
</Project>

View file

@ -5,5 +5,6 @@
</PropertyGroup>
<PropertyGroup>
<ActiveDebugProfile>http</ActiveDebugProfile>
<NameOfLastUsedPublishProfile>D:\git\FileSqlServerSync\Server\RemoteServer\Properties\PublishProfiles\FolderProfile.pubxml</NameOfLastUsedPublishProfile>
</PropertyGroup>
</Project>

View file

@ -1,3 +1,4 @@
using System.Diagnostics;
using System.Net.WebSockets;
using Common;
@ -13,7 +14,78 @@ public class RemoteSyncServer
public void SetStateHelpBase(StateHelpBase stateHelper)
{
StateHelper = stateHelper;
try
{
StateHelper = stateHelper;
if (SyncConfig != null)
{
var LastExec = NotNullSyncConfig.ExecProcesses.Find(x =>
{
return x.StepBeforeOrAfter == "A"
&& x.Step == (stateHelper.Step - 1)
&& x.ExecInLocalOrServer == "S";
});
ExecProcess(LastExec);
var CurrentExec = NotNullSyncConfig.ExecProcesses.Find(x =>
{
return x.StepBeforeOrAfter == "B"
&& x.Step == stateHelper.Step
&& x.ExecInLocalOrServer == "S";
});
ExecProcess(CurrentExec);
}
}
catch (Exception ex)
{
Close(ex.Message);
}
}
public void ExecProcess(ExecProcess? ep)
{
if (ep != null)
{
ProcessStartInfo startInfo =
new()
{
StandardOutputEncoding = System.Text.Encoding.UTF8,
Arguments = ep.Argumnets,
FileName = ep.FileName, // The command to execute (can be any command line tool)
// 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)
{
Pipe.SendMsg(
StateHelper.CreateMsg(
$"{ep.Step}-{ep.StepBeforeOrAfter}-{ep.ExecInLocalOrServer}-{ep.FileName} {ep.Argumnets} 执行成功!"
)
)
.Wait();
}
else
{
Pipe.SendMsg(
StateHelper.CreateMsg(
$"{ep.Step}-{ep.StepBeforeOrAfter}-{ep.ExecInLocalOrServer}-{ep.FileName} {ep.Argumnets} 失败 {output}"
)
)
.Wait();
throw new Exception("错误,信息参考上一条消息!");
}
}
}
public StateHelpBase GetStateHelpBase()

View file

@ -13,7 +13,7 @@ public abstract class StateHelpBase(
{
protected readonly RemoteSyncServer Context = context;
protected readonly SyncProcessStep Step = step;
public readonly SyncProcessStep Step = step;
public SyncMsg CreateErrMsg(string Body)
{
@ -42,7 +42,7 @@ public abstract class StateHelpBase(
var syncMsg =
JsonSerializer.Deserialize<SyncMsg>(msg)
?? throw new NullReferenceException("msg is null");
if (syncMsg.Step != Step)
if (syncMsg.Step != Step && syncMsg.Step != SyncProcessStep.Close)
{
throw new Exception("Sync step error!");
}
@ -134,7 +134,14 @@ public class UnPackAndReleaseHelper(RemoteSyncServer context)
Context.Pipe.SendMsg(h.CreateMsg("将要发布数据库,可能时间会较长!")).Wait();
Task.Run(() =>
{
h.FinallyPublish();
try
{
h.FinallyPublish();
}
catch (Exception e)
{
Context.Close(e.Message);
}
});
}
@ -154,6 +161,9 @@ public class FinallyPublishHelper(RemoteSyncServer context)
+ $" /TargetServerName:{Context.NotNullSyncConfig.DstDb.ServerName} /TargetDatabaseName:{Context.NotNullSyncConfig.DstDb.DatabaseName}"
+ $" /TargetUser:{Context.NotNullSyncConfig.DstDb.User} /TargetPassword:{Context.NotNullSyncConfig.DstDb.Password} /TargetTrustServerCertificate:True";
//Context
// .Pipe.SendMsg(CreateMsg($"发布脚本为: {RemoteSyncServer.SqlPackageAbPath} {arguments}"))
// .Wait();
ProcessStartInfo startInfo =
new()
{
@ -201,8 +211,15 @@ public class FinallyPublishHelper(RemoteSyncServer context)
}
});
Context.SetStateHelpBase(new NormalCloseHelper(Context));
Context.Pipe.SendMsg(CreateMsg("发布完成!")).Wait();
}
protected override void HandleMsg(SyncMsg msg) { }
}
public class NormalCloseHelper(RemoteSyncServer context)
: StateHelpBase(context, SyncProcessStep.Close)
{
protected override void HandleMsg(SyncMsg msg) { }
}

View file

@ -45,6 +45,36 @@ public class PipeSeed : IDisposable
DirFileConfigs = new List<DirFileConfig>
{
new DirFileConfig { DirPath = "/bin", Excludes = ["/roslyn", "/Views"] }
},
// C:/Windows/System32/inetsrv/appcmd.exe stop sites "publicserver"
// C:/Windows/System32/inetsrv/appcmd.exe start sites "publicserver"
ExecProcesses = new List<ExecProcess>
{
new ExecProcess
{
Argumnets = "ls",
FileName = "powershell",
StepBeforeOrAfter = "A",
ExecInLocalOrServer = "L",
Step = SyncProcessStep.DeployProject,
},
new ExecProcess
{
Argumnets = "ls",
FileName = "powershell",
StepBeforeOrAfter = "B",
ExecInLocalOrServer = "S",
Step = SyncProcessStep.Publish,
},
new ExecProcess
{
Argumnets = "ls",
FileName = "powershell",
StepBeforeOrAfter = "A",
ExecInLocalOrServer = "S",
Step = SyncProcessStep.Publish,
},
}
};
}

View file

@ -14,7 +14,12 @@ public class PipeTest
[Fact]
public async void TestCase()
{
//msbuild 只能在windows上跑
//if (System.IO.File.Exists("Pipe.txt"))
//{
// System.IO.File.Delete("Pipe.txt");
//}
//System.IO.File.Create("Pipe.txt");
//msbuild ÖťÄÜÔÚwindowsÉĎĹÜ
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
{
var p1 = new TestPipe(false, "1");
@ -27,9 +32,18 @@ public class PipeTest
#pragma warning disable CS8602 // Dereference of a possibly null reference.
if (msg.Body == "·¢²¼Íê³É£¡")
{
_ = p1.Close("正常退出!");
Task.Run(() =>
{
Task.Delay(1000).Wait();
_ = p1.Close("ŐýłŁÍËłöŁĄ");
});
}
#pragma warning restore CS8602 // Dereference of a possibly null reference.
System.IO.File.AppendAllText(
"Pipe.txt",
$"{msg.Step}-{msg.Type}:{msg.Body}\n"
);
Console.WriteLine(b);
return true;
}

View file

@ -7,21 +7,21 @@ const LocalHost = "127.0.0.1";
//这是个例子,请在`config`中写你的配置
const example_config = {
//发布的名称,每个项目具有唯一的一个名称
Name: "Test",
RemotePwd: "t123",
Name: "FYMF",
RemotePwd: "FYMF",
//远程服务器地址,也就是发布的目的地,它是正式环境
RemoteUrl: "127.0.0.1:6819",
RemoteUrl: "127.0.0.1:8007",
//是否发布数据库 sqlserver
IsDeployDb: false,
//是否发布前重新构建项目
IsDeployProject: false,
IsDeployProject: true,
//项目地址
LocalProjectAbsolutePath:
"D:/git/HMES-H7-HNFY/HMES-H7-HNFYMF/HMES-H7-HNFYMF.WEB",
//源文件目录地址,是要发布的文件根目录,它是绝对路径,!执行发布时将发布到这个目录!
LocalRootPath: "D:/FileSyncTest/src",
//目标文件目录地址,也就是部署服务的机器上的项目文件根目录,它是绝对路径
RemoteRootPath: "D:/FileSyncTest/dst",
RemoteRootPath: "D:/FYMF",
//源数据库配置 SqlServer,将会同步数据库的结构
SrcDb: {
//Host
@ -45,7 +45,7 @@ const example_config = {
ServerName: "127.0.0.1",
DatabaseName: "HMES_H7_HNFYMF",
User: "sa",
Password: "0",
Password: "Yuanmo520...",
TrustServerCertificate: "True",
},
//子目录配置每个子目录都有自己不同的发布策略它是相对路径即相对于LocalRootPath和RemoteRootPath(注意 '/',这将拼成一个完整的路径),文件数据依此进行,
@ -60,58 +60,58 @@ const example_config = {
],
};
const config = {
//发布的名称,每个项目具有唯一的一个名称
Name: "Test",
RemotePwd: "t123",
//远程服务器地址,也就是发布的目的地,它是正式环境
RemoteUrl: "127.0.0.1:6819",
//是否发布数据库 sqlserver
IsDeployDb: true,
//是否发布前重新构建项目
IsDeployProject: true,
//项目地址
LocalProjectAbsolutePath:
"D:/git/HMES-H7-HNFY/HMES-H7-HNFYMF/HMES-H7-HNFYMF.WEB",
//源文件目录地址,是要发布的文件根目录,它是绝对路径,!执行发布时将发布到这个目录!
LocalRootPath: "D:/FileSyncTest/src",
//目标文件目录地址,也就是部署服务的机器上的项目文件根目录,它是绝对路径
RemoteRootPath: "D:/FileSyncTest/dst",
//源数据库配置 SqlServer,将会同步数据库的结构
SrcDb: {
//Host
ServerName: "172.16.12.2",
//数据库名
DatabaseName: "HMES_H7_HNFYMF",
User: "hmes-h7",
Password: "Hmes-h7666",
//是否信任服务器证书
TrustServerCertificate: "True",
//同步的数据,这些数据将会同步
SyncTablesData: [
"dbo.sys_Button",
"dbo.sys_Menu",
"dbo.sys_Module",
"dbo.sys_Page",
],
},
//目标数据库配置 sqlserver
DstDb: {
ServerName: "127.0.0.1",
DatabaseName: "HMES_H7_HNFYMF",
User: "sa",
Password: "0",
TrustServerCertificate: "True",
},
//子目录配置每个子目录都有自己不同的发布策略它是相对路径即相对于LocalRootPath和RemoteRootPath(注意 '/',这将拼成一个完整的路径),文件数据依此进行,
DirFileConfigs: [
{
DirPath: "/bin",
//排除的文件或目录它是相对路径相对于LocalRootPath和RemoteRootPath
Excludes: ["/roslyn", "/Views"],
//只追踪文件或目录它是相对路径相对于LocalRootPath和RemoteRootPath它的优先级最高如果你指定了它的值Excludes将会失效
// CherryPicks:[]
},
],
//发布的名称,每个项目具有唯一的一个名称
Name: "FYMF",
RemotePwd: "FYMF",
//远程服务器地址,也就是发布的目的地,它是正式环境
RemoteUrl: "127.0.0.1:8007",
//是否发布数据库 sqlserver
IsDeployDb: true,
//是否发布前重新构建项目
IsDeployProject: false,
//项目地址
LocalProjectAbsolutePath:
"D:/git/HMES-H7-HNFY/HMES-H7-HNFYMF/HMES-H7-HNFYMF.WEB",
//源文件目录地址,是要发布的文件根目录,它是绝对路径,!执行发布时将发布到这个目录!
LocalRootPath: "D:/FileSyncTest/src",
//目标文件目录地址,也就是部署服务的机器上的项目文件根目录,它是绝对路径
RemoteRootPath: "D:/FYMF",
//源数据库配置 SqlServer,将会同步数据库的结构
SrcDb: {
//Host
ServerName: "172.16.12.2",
//数据库名
DatabaseName: "HMES_H7_HNFYMF",
User: "hmes-h7",
Password: "Hmes-h7666",
//是否信任服务器证书
TrustServerCertificate: "True",
//同步的数据,这些数据将会同步
SyncTablesData: [
"dbo.sys_Button",
"dbo.sys_Menu",
"dbo.sys_Module",
"dbo.sys_Page",
],
},
//目标数据库配置 sqlserver
DstDb: {
ServerName: "127.0.0.1",
DatabaseName: "HMES_H7_HNFYMF",
User: "sa",
Password: "Yuanmo520...",
TrustServerCertificate: "True",
},
//子目录配置每个子目录都有自己不同的发布策略它是相对路径即相对于LocalRootPath和RemoteRootPath(注意 '/',这将拼成一个完整的路径),文件数据依此进行,
DirFileConfigs: [
{
DirPath: "/bin",
//排除的文件或目录它是相对路径相对于LocalRootPath和RemoteRootPath
Excludes: ["/roslyn", "/Views"],
//只追踪文件或目录它是相对路径相对于LocalRootPath和RemoteRootPath它的优先级最高如果你指定了它的值Excludes将会失效
// CherryPicks:[]
},
],
};
//#endregion