思考一种好的架构(十四)

 

CI/CD

说下类库的发布流程,而不是产品发布的流程

修改代码->修改csproj中的版本->执行打包命令->执行上传命令->上传修改的文件到git仓库

(我使用的git仓库是码云,以下代码示例都是与码云做的对接,使用其他仓库也是一样的操作流程,不同的方式)

其中修改版本----->上传nuget包 都可以做到自动化,也就是持续交付(持续部署)

组内开发者只需要关注修改代码->上传代码,等几十秒后就会有一个新版本在nuget仓库中出现,不再需要做重复性的工作,解放双手

下面进入正题:

 

 

配置码云的webHook

 

它应该叫Warehouse Event 而不是WebHook😂

 

这就是我们的核心类

 

IDelivery  任务处理

IDeliveryTaskQueue 任务处理队列

IGitProcess Git处理命令封装

INugetProcess Nuget处理命令封装

 

 

先看基础的

 public interface IGitProcess
    {
        /// <summary>
        /// 获取Git仓库
        /// </summary>
        /// <param name="gitAddress"></param>
        /// <returns></returns>
        string Obtain(string gitAddress);

        /// <summary>
        /// 拉取 git 仓库
        /// </summary>
        /// <param name="gitAddress"></param>
        /// <returns></returns>
        string Pull(string gitAddress);

        /// <summary>
        /// 克隆Git仓库
        /// </summary>
        /// <param name="gitAddress"></param>
        string Clone(string gitAddress);

        /// <summary>
        /// 获取仓库名称
        /// </summary>
        /// <param name="gitAddress"></param>
        string GetGitFolderMame(string gitAddress);

        /// <summary>
        /// 提交到原创仓库
        /// </summary>
        /// <param name="gitAddress"></param>
        /// <returns></returns>
        string Push(string gitAddress);
    }

 

 public interface INugetProcess
    {

        /// <summary>
        /// 打包上传
        /// </summary>
        /// <param name="folderPath">文件路径</param>
        /// <param name="apiKey">APIKey</param>
        /// <param name="source">目标仓库</param>
        /// <param name="author">作者</param>
        /// <param name="describe">描述</param>
        string GenerationUpload(string folderPath, string apiKey, string source, string author, string describe);

        /// <summary>
        /// 修改CsProj文件
        /// </summary>
        /// <param name="folderPath"></param>
        /// <param name="author"></param>
        /// <param name="describe"></param>
        /// <returns>修改的版本</returns>

        string EditCsProjXml(string folderPath, string author, string describe);

    }

 

这两个是对Git 和Nuget命令的封装,只封装我们需要的,不要考虑扩展性什么的,需要什么就写什么。

 

 class RedirectRun
    {
        /// <summary>
        /// 功能:重定向执行
        /// </summary>
        /// <param name="p"></param>
        /// <param name="exe"></param>
        /// <param name="arg"></param>
        /// <param name="output"></param>
        public static void RedirectExcuteProcess(Process p,string exe, string arg, DataReceivedEventHandler output, StringBuilder writeContent=null)
        {
                p.StartInfo.FileName = exe;
                p.StartInfo.Arguments = arg;
                writeContent.AppendLine(exe +" "+ arg);

                p.StartInfo.UseShellExecute = false;    //输出信息重定向
                p.StartInfo.CreateNoWindow = true;
                p.StartInfo.RedirectStandardError = true;
                p.StartInfo.RedirectStandardOutput = true;

                 p.OutputDataReceived += output;
                p.ErrorDataReceived += output;

                p.Start();                    //启动线程

                p.BeginOutputReadLine();
                p.BeginErrorReadLine();
                p.WaitForExit();            //等待进程结束
        }
     }

 

这个也是非常基础的类,执行编写的命令

 

 /// <summary>
    /// Git服务
    /// </summary>
   public class GitProcess: IGitProcess
    {
        public string Clone(string gitAddress)
        {

            StringBuilder message = new StringBuilder();
            var process = new System.Diagnostics.Process();
            process.StartInfo.FileName = System.Environment.CurrentDirectory + "\\" + GetGitFolderMame(gitAddress); ;
            RedirectRun.RedirectExcuteProcess(process, "git.exe", $"clone {gitAddress}", (x, c) =>
            {
                message.AppendLine(c.Data);
            }, message);
            return message.ToString();
        }
        public string GetGitFolderMame(string gitAddress) {
            var gitFolderName = gitAddress.Split("/");
            return gitFolderName[gitFolderName.Length-1].Replace(".git", "");
        }

        public string Obtain(string gitAddress)
        {
            
            string result = string.Empty;
            var gitFolderName = GetGitFolderMame(gitAddress);
            if (System.IO.Directory.Exists(System.Environment.CurrentDirectory + "\\" + gitFolderName))
            {
                result = Pull(gitAddress);
            }
            else
            {
                result = Clone(gitAddress);
            }
            return result;
        }

        public string Pull(string gitAddress)
        {
            var gitFolderName = System.Environment.CurrentDirectory + "\\" + GetGitFolderMame(gitAddress);
            StringBuilder message = new StringBuilder();
            var process = new System.Diagnostics.Process();
            process.StartInfo.WorkingDirectory = gitFolderName;
            RedirectRun.RedirectExcuteProcess(process, "git.exe", @"pull --allow-unrelated-histories", (x, c) =>
            {
                //if (csFiles!=null&&c.Data!=null&&c.Data.Contains(".cs"))
                //{
                //    csFiles.Add(gitFolderName+"\\"+c.Data.Split(" | ")[0].Replace(" ",""));
                //}
                message.AppendLine(c.Data);
            },message);
            return message.ToString();
        }

        public string Push(string gitAddress)
        {
            var gitFolderName = System.Environment.CurrentDirectory + "\\" + GetGitFolderMame(gitAddress);
            StringBuilder message = new StringBuilder();
            var process = new System.Diagnostics.Process();
            process.StartInfo.WorkingDirectory = gitFolderName;
            RedirectRun.RedirectExcuteProcess(process, "git.exe", @"add .", (x, c) =>
            {
                message.AppendLine(c.Data);
            }, message);

            process = new System.Diagnostics.Process();
            process.StartInfo.WorkingDirectory = gitFolderName;
            RedirectRun.RedirectExcuteProcess(process, "git.exe", $@"commit -m '___Nuget打包任务___'", (x, c) =>
            {
                message.AppendLine(c.Data);
            }, message);

            process = new System.Diagnostics.Process();
            process.StartInfo.WorkingDirectory = gitFolderName;
            RedirectRun.RedirectExcuteProcess(process, "git.exe", @"push", (x, c) =>
            {
                message.AppendLine(c.Data);
            }, message);
            return message.ToString();

            return message.ToString();


        }
    }

 

Obtain是一个综合函数,由它来觉得是clone还是pull
Push 的commit -m 参数可以自定义,因为web入口需要区分是开发者提交还是自动交付的提交

非常简单

 

 public class NugetProcess : INugetProcess
    {
        private FileInfo[] GetCsprojFiles(DirectoryInfo folder) {
            List<FileInfo> files = new List<FileInfo>();
            foreach (var item in folder.GetDirectories())
            {
                files.AddRange(GetCsprojFiles(item));
            }
            files.AddRange(folder.GetFiles("*.csproj"));
            return files.ToArray();
        }

        public string GenerationUpload(string folderPath, string apiKey, string source, string author, string describe)
        {
            StringBuilder writeContent = new StringBuilder();
            FileInfo fileInfo = new FileInfo(folderPath);
            if (fileInfo.Extension.ToLower()!= ".csproj")
            {
                return "所选文件不为Csproj项目文件";
            }
            var version =  EditCsProjXml(folderPath, author, describe);
            RedirectRun.RedirectExcuteProcess(new System.Diagnostics.Process(), "dotnet", $"build {fileInfo.FullName}", (x, c) =>
            {
                writeContent.AppendLine(c.Data);
            }, writeContent);
            RedirectRun.RedirectExcuteProcess(new System.Diagnostics.Process(), "dotnet.exe", $"pack {fileInfo.FullName} --output {fileInfo.Directory.FullName}", (x, c) =>
            {
                writeContent.AppendLine(c.Data);
            }, writeContent);
            RedirectRun.RedirectExcuteProcess(new System.Diagnostics.Process(), "dotnet.exe", $"nuget push {fileInfo.FullName.Replace(".csproj", $".{version}.nupkg")} --api-key {apiKey} --source {source}", (x, c) =>
            {
                writeContent.AppendLine(c.Data);
            }, writeContent);
            return writeContent.ToString();
        }

        public string EditCsProjXml(string folderPath,string author, string describe) {
            XmlDocument xml = new XmlDocument();
            xml.Load(folderPath);

            XmlNode projectNode = xml.SelectSingleNode("Project");
            XmlNode propertyGroupNode = projectNode.SelectSingleNode("PropertyGroup");
            var result = EditVersionNumber(propertyGroupNode);
            EditAuthor(propertyGroupNode, author);
            EditDescribe(propertyGroupNode, describe);
            xml.Save(folderPath);
            return result;
        }
        private string EditVersionNumber(XmlNode node)
        {
            var versionNode = node.SelectSingleNode("Version");
            if (versionNode == null)
            {
                versionNode = node.AppendChild(node.OwnerDocument.CreateElement("Version"));
                versionNode.InnerText = "1.0.0";
            }

            var wholeVersion = versionNode.InnerText.Split(".");
            var version_int = int.Parse(wholeVersion[wholeVersion.Length - 1]) + 1;
            var resultVersion = new StringBuilder();
            for (int i = 0; i < wholeVersion.Length - 1; i++)
            {
                resultVersion.Append(wholeVersion[i] + ".");
            }
            resultVersion.Append(version_int);
            versionNode.InnerText = resultVersion.ToString();
            return resultVersion.ToString();
        }

        /// <summary>
        /// 修改作者
        /// </summary>
        /// <param name="content"></param>
        /// <param name="author"></param>
        /// <returns></returns>
        private bool EditAuthor(XmlNode node, string author)
        {
            var versionNode = node.SelectSingleNode("Authors");
            if (versionNode == null)
            {
                versionNode = node.AppendChild(node.OwnerDocument.CreateElement("Authors"));
            }
            versionNode.InnerText = author;
            return true;
        }

        /// <summary>
        /// 修改描述
        /// </summary>
        /// <param name="content"></param>
        /// <param name="describe"></param>
        /// <returns></returns>
        private bool EditDescribe(XmlNode node, string describe)
        {
            var versionNode = node.SelectSingleNode("Description");
            if (versionNode == null)
            {
                versionNode = node.AppendChild(node.OwnerDocument.CreateElement("Description"));
            }
            var index= versionNode.InnerText.IndexOf("-");
            if (index != -1)
            {
                versionNode.InnerText = versionNode.InnerText.Replace(versionNode.InnerText.Substring(index, versionNode.InnerText.Length- index), "-"+describe);
            }
            else
            {
                versionNode.InnerText = versionNode.InnerText + "-" + describe;

            }
            return true;
        }
    }

 

 

GenerationUpload 生成并上传 ,也是一个综合操作的函数
使用XmlDocument去修改csproj中的信息,版本号、作者、描述


任务执行者:
 public interface IDelivery
    {
        /// <summary>
        /// 服务库(码云专用)
        /// </summary>
        /// <param name="gitAddress"></param>
        /// <param name="apiKey"></param>
        /// <param name="source"></param>
        /// <param name="changeCsFolders">更改文件列表</param>
        /// <param name="author">作者</param>
        /// <param name="describe">描述</param>
        /// <returns></returns>
        string MaYunServiceLibrary(string gitAddress, string apiKey, string source,List<string> changeCsFiles, string author,string describe);
    }
 public class Delivery : IDelivery
    {
        IGitProcess gitProcess; INugetProcess nugetProcess;
        public Delivery(IGitProcess gitProcess,INugetProcess nugetProcess) {
            this.gitProcess = gitProcess;
            this.nugetProcess = nugetProcess;
        }

        public string MaYunServiceLibrary(string gitAddress, string apiKey, string source, List<string> changeCsFiles, string author, string describe)
        {
            List<string> csProjects = new List<string>();
            StringBuilder message = new StringBuilder();
            message.AppendLine(gitProcess.Obtain(gitAddress));
            foreach (var item in changeCsFiles)
            {
                var csprojectItem = GetUpwardCsProject(new FileInfo(item).Directory);
                if (!csProjects.Contains(csprojectItem))
                    csProjects.Add(csprojectItem);
            }
            foreach (var item in csProjects)
            {
                message.AppendLine(nugetProcess.GenerationUpload(item, apiKey, source, author, describe));
            }
            message.AppendLine(gitProcess.Push(gitAddress));
            return message.ToString();
        }


        private string GetUpwardCsProject(DirectoryInfo directoryInfo)
        {
           var file = directoryInfo.GetFiles("*.csproj");
            if (file.Length > 0)
            {
                return file[0].FullName;
            }
            else {
                return GetUpwardCsProject(directoryInfo.Parent);
            }
        }

 

在这查找并过滤了重复的csproj文件

 

  public interface IDeliveryTaskQueue
    {
        Queue<DeliveryTaskDTO> queue { get; }
        void AddQueue(DeliveryTaskDTO modle);
    }
    public class DeliveryTaskQueue : IDeliveryTaskQueue
    {
        public Queue<DeliveryTaskDTO> queue { get; private set; }
        Thread TaskProcessing { get; }
        IDelivery delivery { get; }
        EventWaitHandle _waitHandle { get; }
        public DeliveryTaskQueue(IDelivery delivery) {
            queue = new Queue<DeliveryTaskDTO>();
            this.delivery = delivery;
            _waitHandle  = new AutoResetEvent(false);
            TaskProcessing = new Thread(Processing);
            TaskProcessing.Start();
        }

        public void AddQueue(DeliveryTaskDTO modle)
        {
            if (modle.ChangeCsFiles.Count==0)
            {
                Console.WriteLine("当前请求没有更改文件");
                return;
            }
            queue.Enqueue(modle);
            _waitHandle.Set();
            
        }
        private void Processing(object o) {
            while (true)
            {
                if (queue.Count==0)
                {
                    Console.WriteLine("等待任务");
                    _waitHandle.WaitOne();
                }
                else {
                    var taskInfo = queue.Dequeue();
                    try
                    {
                        Console.WriteLine(delivery.MaYunServiceLibrary(taskInfo.GitAddress, taskInfo.ApiKey, taskInfo.Source, taskInfo.ChangeCsFiles, taskInfo.Author, taskInfo.Describe));
                        Console.WriteLine("任务处理完毕..");
                    }
                    catch (Exception e)
                    {
                        Console.WriteLine("处理任务出现异常:"+e);
                    }
                  
                }
            }
        }
    }

 

由于可能会遇到并发提交的问题,所以需要一个队列。

 

 public static void ConfigureServices(IServiceCollection services)
        {
            services.AddTransient<IDeliveryTaskQueue, Dragon.Delivery.ServiceLibrary.Server.DeliveryTaskQueue>();
            services.AddTransient<IDelivery, Dragon.Delivery.ServiceLibrary.Server.Delivery>();
            services.AddTransient<IGitProcess, Dragon.Delivery.ServiceLibrary.Server.GitProcess>();
            services.AddTransient<INugetProcess, Dragon.Delivery.ServiceLibrary.Server.NugetProcess>();
        }

        public static void Configure(IApplicationBuilder app, IHostingEnvironment env)
        {
            
        }

 

 

再来就是WEB了

 

 public class MaYunController : ControllerBase
    {
        IDeliveryTaskQueue deliveryTaskQueue;
        IGitProcess gitProcess;
        public MaYunController(IDeliveryTaskQueue  deliveryTaskQueue, IGitProcess gitProcess) {
            this.deliveryTaskQueue = deliveryTaskQueue;
            this.gitProcess = gitProcess;
        }
        // GET api/values
        [HttpPost]
        public IActionResult MaYunHook(MaYunHookQo maYunHookQo)
        {
            foreach (var item in maYunHookQo.commits)
            {
                if (item.message.Contains("___Nuget打包任务___"))
                {
                    //表明这次推送是打包推送的
                    continue;
                }
                if (item.message.Contains("Merge")&&item.message.Contains("branch"))
                {
                    //表明这次推送是合并分支的请求
                    continue;
                }
                Console.WriteLine("收到打包任务:" + item.message + " 作者:" + item.author.name);
                List<string> changeCsFiles = new List<string>();
                changeCsFiles.AddRange(item.removed.Where(x => x.Contains(".cs")).Select(x => $"{System.Environment.CurrentDirectory}\\{gitProcess.GetGitFolderMame(maYunHookQo.repository.clone_url)}\\{x}").ToList());
                changeCsFiles.AddRange(item.added.Where(x => x.Contains(".cs")).Select(x => $"{System.Environment.CurrentDirectory}\\{gitProcess.GetGitFolderMame(maYunHookQo.repository.clone_url)}\\{x}").ToList());
                changeCsFiles.AddRange(item.modified.Where(x => x.Contains(".cs")).Select(x => $"{System.Environment.CurrentDirectory}\\{gitProcess.GetGitFolderMame(maYunHookQo.repository.clone_url)}\\{x}").ToList());
                deliveryTaskQueue.AddQueue(new ServiceLibrary.PublicServer.modle.DTO.DeliveryTaskDTO()
                {
                    ApiKey = "71a6ab5d-308d-32c6-a7e8-25ff096f020a",
                    Source = "http://x.x.x.x:8081/repository/nuget-hosted/",
                    GitAddress = maYunHookQo.repository.clone_url,
                    Author = item.author.name,
                    Describe = item.message,
                    ChangeCsFiles = changeCsFiles
                });
            }
            return Ok();
        }
    }

 

 

从码云的请求中获取信息然后提交到任务队列

 

那个请求模型就不贴了,代码太多了,可以自行获取

 

 

然后到json转C#实体的网站上

https://www.sojson.com/json2entity.html做个转换就行啦。

以上:使用持续交付减少开发流程

 

posted @ 2020-04-03 13:59  AnAng  阅读(205)  评论(0编辑  收藏  举报