- A+
所属分类:.NET技术
前言
上一篇,我只实现了一键检测代码变化,本篇才是真正的实现了一键打包发布
效果图
客户端打包待发布文件
/// <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 服务, 这个也很简单, 我就不发了, 大家自行实现吧, 完结~