【G】开源的分布式部署解决方案(一) - 开篇

2018-06-22 07:44:39来源:未知 阅读 ()

新老客户大回馈,云服务器低至5折

G.系列导航

【G】开源的分布式部署解决方案 - 导航

做这个开源项目的意义是什么?(口水自问自答,不喜可略过)

从功能上来说,请参考 预告篇,因自知当时预告篇没有任何含金量,所以并没有主动推送到首页,而是私下的给一些人发的。

从个人角度上来说,我希望.net的环境会越来越好,就我自己的成长曲线是从mxdn开始自学、cxdn尝试解答问题、博客园读别人博客再到自己写博客、最后到工作中经常使用到的stackxxxxflow、gxxgle。这其中我当然是走了很多弯路,踩过无数的坑,也埋了无数的坑。现在自己有了一点小小的能力,想尽自己所能,通过一个项目整理出来给大家参考、学习的同时也能给自己一些意见。对新人来说也算是一种回馈,对老人来说也算是互相切磋互相提升。

 

什么是分布式部署解决方案?

部署也有很多人叫发布。那正常的流程是什么样的呢?

 

相信这张图大家都不会太陌生。一般少数几台服务器的时候直接这样发布是最省时省力的。

但是,如果有一天,你的服务器变成几十台,然后又面临着经常发布的情况该怎么办呢?

@echo off
echo Start to Publish ApplicationName All Site
set local="C:\inetpub\ApplicationName"
Set RemotePath=C$\inetpub\ApplicationName

For %%I in (192.168.0.2 192.168.0.3 ... 192.168.0.n) do (
echo "Publish ApplicationName %%I"
net use \\%%I\ipc$ Password /User:UserName
For %%P in (default) do (
echo "Publish \\%%I\%RemotePath%\"dd
xcopy /e /y %local% "\\%%I\%RemotePath%\"
)
net use \\%%I\ipc$ /del
)

:End
pause;

上面这段代码,相信也有一部分人用过吧。使用流程也比较简单,先用vs发布到本地,然后再使用批处理文件,通过发布命令来进行多台网站的部署。

 

突然有一天,你是上司跟你说,你的这段批处理有很多问题,比如不利于维护、没有部署记录、发布后首次访问很慢、没有版本管理、部署过程一旦发生异常也没有容错机制等等。

是的,这些问题都还是基础的。如果你的服务器数量达到一定级别以后,你的部署会变得异常艰难。某台服务器上部署了多少个服务、某几台服务器是一组,这些会让你抓狂,甚至想说脏话,就如同下图。

由于后面还会有一系列文章要出,此处就不再过多的解释,当你看到完整的项目之后,相信你会对它有一个新的认识。

 

第一个正式版包含哪些功能?

请允许我偷个懒,从预告篇把尚不成熟的功能列表copy一份过来:

 

1 基础的权限控制(可能只支持一个角色,甚至是写死)

2 部署项目管理(以Jenkins为例,做自动化构建方案)

3 部署服务器管理(以Aliyun服务器为例)

4 部署组管理(包括部署组、部署组项)

5 创建部署任务(包括触发自动化构建、部署文件关联等)

6 部署服务器常驻服务(以Windows Service的形式驻留,主要完成中控的指令协助部署)

7 一键部署(支持单台或部署组)

  7.1 支持手动绑定历史部署文件版本(理论上支持所有的,如Jenkins这种构建方案还会涉及到保存构建副本数量和天数等,另本功能只支持单台部署,部署组的修改版本通过创建部署任务时统一指定)

  7.2 自动检测部署文件包状态(以Jenkins为例,会自动等待构建完毕,并会自行检测构建状态判断是否继续向下执行)

  7.3 下载部署文件包并解压

  7.4 控制负载均衡降权

  7.5 部署(包括等待服务器正在执行的请求完成的检测)

  7.6 触发缓存(也可以理解为触发首次访问,提升用户体验)

  7.7 控制负载均衡升权

 

PS:其中为了支持单台服务器部署,所以一些扩展性功能会采用插件(暂定)的形式允许自行实现,同时也会包含一套以阿里云、jenkisn为基础的示例实现。

后续也会考虑把一些功能提取出来做成API,允许把功能嵌入到自己的系统里去。当然,这个要看有多少人需要了。

 

核心技术与开发工具

后端:.net 4.6.1,EF6,mvc5(不上.net core的原因是1.1.0目前并不稳定,还是有不少bug,我真的亲身踩到了,实在是不想无限踩过去,请原谅我没有勇于踩坑的心,确确实实是精力有限)

前端:AdminLTE 2 支持PC、Mobile双端

数据库:SQL Server LocalDB

IDE:Visual Studio 2017 RC

源码托管:oschina git

 

现在做到哪一步了?

先上一张解决方案资源管理器的截图,简单的看下项目结构。

可以看到,目前只有一个项目,而且比较乱,Business、Data、Models 各种乱入。

这么低级的一个项目怎么好意思开源的?

是的,没错,目前项目状态就是这样。

因为我要做的不是发布出来一个成品,写个介绍就完了。

我会从0代码开始在git上保留整个过程。且根据进度不定期更新博文来讲解编码过程中的各种问题以及解决思路。

而下一篇的名字我也已经想好了。

【G】开源的分布式部署解决方案(二) -  好项目是从烂项目基础上重构出来的

在我的这个开源系列博客中,你可以看到一个开源项目的完整成长历程。我不知道这个项目会走多远,但我坚信这个项目一定会帮到一部分人,这就足够了。

下面放几张截图,也是下一篇内容的主角。

(简单的登录页)

(后台UI的基础框架)

(已完成的一个功能,部署服务器管理)

(支持手机端的table)

 

登录的实现

为了避免太水的嫌疑,上一点点干货。

新建项目时,点击Change Authentication会看到4个选项。至于如何选择我就不多说了,我先说说我选了 Identity(Individual User Accounts)踩过的坑。

因为我之前主要是做架构方向的,可以理解为这些都是所谓的“小弟”在做的功能,而我一个不小心就掉进了陷阱里去。说是陷阱吧,其实主要是因为自己不熟悉,毕竟mvc还是比较庞大的,即便你顺着它的思路去做,也未必能做得对。

默认情况下生成好的项目是已经使用推荐的EF6实现了整个登录流程。而我又希望自己可以掌控关键的验证逻辑,所以一路摸下去把 ApplicationUser、AppplicationUserManager、UserManager、UserStore都看了一遍,经过各种尝试,发现验证逻辑封装在Microsoft.AspNet.Identity.EntityFramework(这个是盲打的,已经回不去了,如果错了欢迎指正)。

找到了目标当然第一想法是功课他,随着不断的踩坑发现工作量异常的大,遂放弃。想从另外一个角度入手,因为UserManager才是核心,那我顺着这条线是否可以把他已经实现的EF那套开给剔除掉。

于是就有了下面的代码:

首先要改造Model,于是自己把 ApplicationUser、IdentityUser、UserStore都重写了。前面两个比较简单,重点是UserStore,要实现IUserStore<TUser>这个接口,这里是自行编写验证逻辑的转折点。

其次,Controller也进行的小改造,去掉了SignInManager等,只保留了UserManager,同时Startup.Auth里面的DbContext也换成了自己的。

最后把Name也一同存入了Cookie,方便layout里面要显示用户名。

 

核心代码如下:

1.IdentityUser 其实这个类可以跟 ApplicationUser合并的,我也是跟官方生成项目时的写法保留下来的。可能故意分开在ApplicationUser里面写的是逻辑,而IdentityUser是对属性的定义,这样会显得更清晰一点吧,虽然我也会这么做,也很热衷于这么做,但区别是我不会让ApplicationUser继承IdentityUser,而是写一个Business来实现方法做一个彻底的拆分。

    public class IdentityUser : IUser<string>
    {
        public string Id { get; set; }

        public string Name { get; set; }

        public string Password { get; set; }

        public string Role { get; set; }

        public string UserName { get; set; }
    }

2.UserStore其实并没有多少借鉴的意义,因为这是很小学生的写法了,主要是为了突出UserStore的重要性。

    public class UserStore : IUserStore<ApplicationUser>
    {
        public async Task CreateAsync(ApplicationUser user)
        {
            using (var dbContext = GDbContext.Create())
            {
                dbContext.User.Add(new User()
                {
                    UserName = user.UserName,
                    Password = user.Password,
                    Name = user.Name,
                    Role = user.Role,
                });

                await dbContext.SaveChangesAsync();
            }
        }

        public Task DeleteAsync(ApplicationUser user)
        {
            throw new NotImplementedException();
        }

        public void Dispose()
        {
            //nothing to do
        }

        public Task<ApplicationUser> FindByIdAsync(string userId)
        {
            int id = 0;
            if (!Int32.TryParse(userId, out id))
            {
                throw new ArgumentException(nameof(userId));
            }

            using (var dbContext = GDbContext.Create())
            {
                var user = dbContext.User.SingleOrDefault(u => u.ID == id && !u.IsDelete);

                return Task.FromResult(new ApplicationUser()
                {
                    Id = user.ID.ToString(),
                    Name = user.Name,
                    Password = user.Password,
                    Role = user.Role,
                    UserName = user.UserName
                });
            }
        }

        public Task<ApplicationUser> FindByNameAsync(string userName)
        {
            using (var dbContext = GDbContext.Create())
            {
                var user = dbContext.User.SingleOrDefault(u => u.UserName == userName && !u.IsDelete);

                return Task.FromResult(new ApplicationUser()
                {
                    Id = user.ID.ToString(),
                    Name = user.Name,
                    Password = user.Password,
                    Role = user.Role,
                    UserName = user.UserName
                });
            }
        }

        public Task UpdateAsync(ApplicationUser user)
        {
            throw new NotImplementedException();
        }
    }

 

 3.Startup.Auth ConfigureAuth方法进行了如下的改动,主要是修改 DbContext 的获取,以及SecurityStampValidator.OnvalidateIdentity这个方法的参数调整。

           // 配置数据库上下文、用户管理器和登录管理器,以便为每个请求使用单个实例
            app.CreatePerOwinContext(GDbContext.Create);
            app.CreatePerOwinContext(() => new UserManager<ApplicationUser>(new UserStore()));

            // 使应用程序可以使用 Cookie 来存储已登录用户的信息
            // 并使用 Cookie 来临时存储有关使用第三方登录提供程序登录的用户的信息
            // 配置登录 Cookie
            app.UseCookieAuthentication(new CookieAuthenticationOptions
            {
                AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie,
                LoginPath = new PathString("/Account/Login"),
                Provider = new CookieAuthenticationProvider
                {
                    // 当用户登录时使应用程序可以验证安全戳。
                    // 这是一项安全功能,当你更改密码或者向帐户添加外部登录名时,将使用此功能。
                    OnValidateIdentity = SecurityStampValidator.OnValidateIdentity<UserManager<ApplicationUser>, ApplicationUser>(
                        validateInterval: TimeSpan.FromMinutes(30),
                        regenerateIdentity: (manager, user) => user.GenerateUserIdentityAsync(manager))
                }
            });            

 

说起来简单,但真的做起来还是踩过了大大小小的坑,即便是你觉得很不起眼的一个功能,都有可能折腾的你要死要活。

我会告诉你我新建项目、删除项目、再新建、再删除,这个过程已经重复好多遍了吗?

当你看到一段好的代码在你面前时,又有几个人能体会写的人死了多少脑细胞?

我不会拒绝伸手党,但我希望伸手的同时能再点个赞,你的支持是无数同行坚持在这条路上的动力。

 

声明

1.在出第一个正式版之前不会使用任何开源协议

2.永远免费,不会出什么专业版、旗舰版之类的进行收费

3.第一个版本完结时间目前还不知道,我这个人懒散惯了,但我会尽量多抽出一些时间尽快写出来。

 

G.开源分布式部署 QQ群:601476986 (本群会实时更新进度,相比来说肯定比博客频繁得多)

Git地址:http://git.oschina.net/doddgu/G   (希望大家可以顺手点个star,如果有喜欢的朋友捐赠下就更好了,感谢各位的支持)

标签:

版权申明:本站文章部分自网络,如有侵权,请联系:west999com@outlook.com
特别注意:本站所有转载文章言论不代表本站观点,本站所提供的摄影照片,插画,设计作品,如需使用,请与原作者联系,版权归原作者所有

上一篇:ASP.NET下使用xml反序列化、缓存实现个性化配置文件的实时生效

下一篇:我是如何开发企业网站的