基于DotNetty实现自动发布 – 实现一键打包发布

  • 基于DotNetty实现自动发布 – 实现一键打包发布已关闭评论
  • 48 次浏览
  • A+
所属分类:.NET技术
摘要

上一篇,我只实现了一键检测代码变化,本篇才是真正的实现了一键打包发布


前言

上一篇,我只实现了一键检测代码变化,本篇才是真正的实现了一键打包发布

效果图

基于DotNetty实现自动发布 - 实现一键打包发布
基于DotNetty实现自动发布 - 实现一键打包发布
基于DotNetty实现自动发布 - 实现一键打包发布

客户端打包待发布文件

    /// <summary>     /// 把多个文件添加到压缩包 (保留文件夹层级关系)     /// </summary>     public static async Task<ZipFileResult> CreateZipAsync(IEnumerable<ZipFileInfo> zipFileInfo)     {         return await Task.Run(() =>         {             var zipDir = EnsureZipDirCreated();             var zipFileName = $"{DateTime.Now:yyyyMMdd_HHmmss_}{Guid.NewGuid()}.zip";             var zipPath = Path.Combine(zipDir, zipFileName);             using var archive = ZipFile.Open(zipPath, ZipArchiveMode.Update);             foreach (var item in zipFileInfo)             {                 archive.CreateEntryFromFile(item.FileAbsolutePath, item.FileRelativePath, CompressionLevel.SmallestSize);             }             return new ZipFileResult() { FullFileName = zipPath, FileName = zipFileName };         });     } 

客户端封装 NettyMessage

            //读取zip字节数组,填充到 NettyMessage 的 Body             var body = await File.ReadAllBytesAsync(zipResult.FullFileName);              //NettyHeader             var header = new DeployRequestHeader()             {                 Files = PublishFiles,                 SolutionName = SolutionName,                 ProjectName = webProject!.ProjectName,                 ZipFileName = zipResult.FileName,             };              var nettyMessage = new NettyMessage { Header = header, Body = body };              //创建 NettyClient             Logger.Info("开始发送");             using var nettyClient = new NettyClient(webProject.ServerIp, webProject.ServerPort);             await nettyClient.SendAsync(nettyMessage);             Logger.Info("完成发送");              Growl.SuccessGlobal($"发布成功");              //保存发布记录             await solutionRepo.SaveFirstPublishAsync(SolutionId, SolutionName, lastGitCommit!.Sha);             Growl.SuccessGlobal($"操作成功");              quickDeployDialog?.Close(); 

NettyHeader 设计

具体实现是 DeployRequestHeader, 继承自 NettyHeader, 保存待发布文件集合,项目名称,解决方案名称, zip 文件名称等

/// <summary> /// 发布请求头部 /// </summary> public class DeployRequestHeader : NettyHeader {     public DeployRequestHeader() : base("Deploy/Run") { }     public List<DeployFileInfo> Files { get; set; } = [];     public string ProjectName { get; set; } = string.Empty;     public string SolutionName { get; set; } = string.Empty;     public string ZipFileName { get; set; } = string.Empty; } 

服务端处理

  • 解压 zip
  • 备份目标文件(存在才备份)
  • 替换目标文件(不存在则新建)
/// <summary> /// 执行服务端发布 /// </summary> /// <param name="model"></param> public void Run(DeployRequestHeader model) {     Logger.Warn($"收到客户端的消息: {model.ToJsonString(true)}");      var configs = NettyServer.AppHost.Services.GetRequiredService<IOptions<List<ProjectConfig>>>();     var projectConfig = configs.Value.FirstOrDefault(a => a.ProjectName == model.ProjectName);     if (projectConfig == null)     {         Logger.Error("请现在服务器项目的appsettings.json中配置项目信息");         return;     }      var zipBytes = Request.Body;     if (zipBytes == null || zipBytes.Length == 0)     {         Logger.Error("ZipBytes为空");         return;     }      var zipFileName = model.ZipFileName;     if (string.IsNullOrEmpty(zipFileName))     {         Logger.Error("ZipFileName为空");         return;     }      //解压     var zipDir = ZipHelper.UnZip(zipBytes, zipFileName);      Logger.Info($"解压成功: {zipDir}");      //备份并覆盖旧文件     DoPublish(model.Files, zipDir, zipFileName, projectConfig);      Logger.Info($"发布成功: {zipDir}"); } 
/// <summary> /// 备份并覆盖旧文件 /// </summary> private static void DoPublish(List<DeployFileInfo> files, string zipDir, string zipFileName, ProjectConfig projectConfig) {     try     {         //先创建备份文件夹         var backupDir = EnsureBackupDirCreated(zipFileName);          //遍历每个待发布的文件,依次先备份再替换         foreach (DeployFileInfo file in files)         {             //文件相对路径(相对于待发布的项目根目录,也是相对于解压后的根目录)             var relativeFilePath = file.PublishFileRelativePath;              //源文件路径(解压后的文件路径)             var sourceFileName = Path.Combine(zipDir, relativeFilePath);              //待发布的文件路径 (服务器真实文件路径)             var destFileName = Path.Combine(projectConfig.ProjectDir, relativeFilePath);              //服务器已存在此文件,先执行备份             if (File.Exists(destFileName))             {                 //备份文件路径                 var backupFileName = Path.Combine(backupDir, relativeFilePath);                 //确保创建备份文件夹                 var backupFileDir = Path.GetDirectoryName(backupFileName);                 if (!Directory.Exists(backupFileDir))                 {                     Directory.CreateDirectory(backupFileDir!);                 }                 File.Copy(destFileName, backupFileName);             }             else             {                 //服务器不存在此文件,先创建文件夹层级(比如你新加了一个页面demo.aspx,需要发布到服务器对应的位置)                 var destFileDir = Path.GetDirectoryName(destFileName);                 if (!Directory.Exists(destFileDir))                 {                     Directory.CreateDirectory(destFileDir!);                 }             }              //替换服务器文件             File.Copy(sourceFileName, destFileName, true);         }     }     catch (Exception ex)     {         Logger.Error(ex.ToString());     } } 

总结

至此,我已经完成了自动发布项目的主体功能,实现自动检测代码变化,自动一键打包发布, 不足的地方有: 第一次发布需要手动处理, 项目也需要手动编译,并配置输出目录,但是我相信,这些都不是问题,只要有思路,都是可以解决的,我主要分享下我的实现步骤

注意

1. 本项目目前只支持 .net framework 的单体 Web 项目
2. 客户端和服务端均是 Windows 服务器
3. 线上项目是 IIS 部署的
4. 代码可能存在 BUG,大家发现可以自行解决,或者联系我,我后面不准备继续维护这个项目,毕竟主要是学习分享用的~~~

代码仓库

项目暂且就叫 OpenDeploy

欢迎大家拍砖,Star

下一步

服务端目前是控制台实现, 可以部署为 Windows 服务, 这个也很简单, 我就不发了, 大家自行实现吧, 完结~