G.系列導航
抱歉
首先我先說聲抱歉,因為上一篇結尾預告第三篇本該是“部署項目管理”,那為什么變成本篇呢?
請容我解釋一下,在預告篇到現在為止,經常會有人問我這個項目到底是干什么的。或許之前寫的比較粗糙。那我相信目前定稿后的功能概覽圖應該會給大家一個比較清晰的認識。
另外也有不少人問我,更新進度這么慢會不會太監?
這個請放心,不會的,因為我們公司也要用到本項目,只是沒列為緊急項目內,我也是抽空寫寫,畢竟我們已經有個精簡中的精簡版在運行着,也就是本項目的雛形。
PS:以下絕大多數內容沒有明顯說明的話僅針對一期有效。
一期功能概覽圖
(點擊圖片看大圖)
先簡單解釋下圖標的意思
相信已經有人知道了,是的,就是進度。
雖然純黃色的比較多,但因為本身就定位PC+Mobile雙端的情況下,確實對我這個不寫前端的人有一點點挑戰的。
好在目前進展還算順利,發現的坑都填上了。
PS:這是終結嗎?並不是,后面還會繼續維護出新功能,比如項目組內各項目依賴部署、多人部署同一項目時的實時進度跟進、項目健康檢查、灰度發布支持、更人性化的UI、更嚴謹的邏輯,部署本身就是個大工程,先走好當下的第一步。
適用場景
單機項目:
服務器數量:1
項目類型:網站
宿主:IIS
負載均衡:無
持續集成:無
可使用單機部署,或創建環境后只加入一台服務器,后續操作同多機項目。
多機項目
服務器數量:>1
項目類型:網站 (目前僅支持網站,后期會支持服務、Job程序,允許擴展)
宿主:IIS(目前僅支持IIS,后期會支持Windows服務,允許擴展)
負載均衡:有(目前僅支持阿里雲,允許擴展)
持續集成:有(目前僅支持Jenkins,允許擴展)
項目部署、版本回滾、部署后預熱、按單機部署或項目級部署、環境隔離、控制負載均衡阻斷請求、防止正在處理的請求中斷等。
至於詳細的功能剖析,會在后面的博客中
名詞解釋
項目組
一個虛擬分組,用於將一些業務耦合度較高的項目捆綁在一起,后續或許會根據需要擴展支持按項目組部署,可根據依賴關系等條件部署。
項目
部署基准元素,可針對項目設置一些屬性來決定部署流程的走向。
環境
一個虛擬的分組,與項目組不同的是,這個分組作用在服務器上。這里有一個比較特殊的概念叫環境標簽,主要是用來區分環境分類的,比如開發、測試、仿真、生產等,作為一個一級分類來使用。
而環境這個概念本身,會在創建部署任務的時候,用於關聯部署應該發生在哪些服務器上的。因為環境本身又包括了服務器。這樣部署概念就完成了一個閉環。
當然這個流程就發生在剛才,我寫環境介紹的時候突然想到的,我漏掉了關鍵的一個環節就是如何讓部署、項目、服務器之間聯系在一起,這的確是不該發生的事情。
服務器
部署單元,作為部署真正作用到的實體,這里有個小坑,就是目前暫時不支持服務器環境的初始化。比如安裝個.net之類的就需要你手動來操作了。當然,服務器多的情況下,像阿里雲本身就有快照之類的服務提供。
一鍵部署
一個自動化操作的動作,會根據之前項目配置階段設置好的屬性、流程等自動完成部署。因為部署本身是一個很復雜的過程,從項目構建、測試、打包、上傳、覆蓋、重啟服務等等一系列操作。這當中人為的失誤以及繁瑣過程實在是讓人抓狂至極。通過之前一步步的操作走到這里,以及后續博客篇幅以幾個示例展開講解會慢慢揭開自動化部署的神秘面紗。
UI
拿出幾個有代表性的界面給大家先看看吧
項目列表界面-PC版
項目列表界面-手機版
項目組列表-PC版
項目組列表-手機版
抽取公共類的小波折
寫這個類的時候其實是因為發現單表操作的功能還是不少的,如果提煉出來一個公共操作類的話就節約了我不少的時間。
在這個單表操作公共類里我目前希望有4個方法,因為這4個的操作概率比較高。Create、Update、GetList、GetByID。
方法不難找,只是過程有點反復,有幾個比較關鍵的信息,比如底層操作數據庫用的EF,又有Entity和Model的概念,自然就少不了AutoMapper。
using Framework.Mapping.Base; using G.Client.Data.Entities.Base; using G.Client.Data.Wrapper; using G.Client.Model.DeployManage; using System; using System.Collections.Generic; using System.Data.Entity; using System.Linq; using System.Linq.Expressions; using System.Text; using System.Threading.Tasks; namespace G.Client.Infrastructure.Data.Pipeline.Database { public class SingleTableEngineer<TEntity, TKey, TListModel, TCreateModel, TEditModel> where TEntity : EntityBase<TKey> where TListModel : class where TCreateModel : class where TEditModel : class, IEditViewModel<TKey> where TKey: IEquatable<TKey> { public virtual async Task<List<TListModel>> GetList() { using (var dbContext = GDbContext.Create()) { var query = from entity in GDbContext.GetDbSet<TEntity, TKey>(dbContext).Where(entity => !entity.IsDelete) select entity; var mapper = new MapperBase<TListModel, TEntity>(); return mapper.GetModelList(await query.ToListAsync()); } } public virtual async Task<TKey> Create(TCreateModel model) { using (var dbContext = GDbContext.Create()) { var mapper = new MapperBase<TCreateModel, TEntity>(); var entity = mapper.GetEntity(model); GDbContext.GetDbSet<TEntity, TKey>(dbContext).Add(entity); await dbContext.SaveChangesAsync(); return entity.ID; } } public async Task<TEditModel> GetByID(TKey id) { using (var dbContext = GDbContext.Create()) { var entity = await GDbContext.GetDbSet<TEntity, TKey>(dbContext).SingleAsync(e => e.ID.Equals(id)); var mapper = new MapperBase<TEditModel, TEntity>(); return mapper.GetModel(entity); } } public virtual async Task<TEditModel> Update(TEditModel model) { using (var dbContext = GDbContext.Create()) { var entity = await GDbContext.GetDbSet<TEntity, TKey>(dbContext).SingleAsync(e => e.ID.Equals(model.ID)); entity = new MapperBase<TEditModel, TEntity>().GetEntity(model, entity); entity.SetUpdateInfo(); GDbContext.GetDbSet<TEntity, TKey>(dbContext).Attach(entity); dbContext.Entry(entity).State = EntityState.Modified; await dbContext.SaveChangesAsync(); return model; } } } }
Not Support Exception
整個類提煉過程並沒有想象中那么復雜,就是遇到了個小坑。
剛開始的時候TKey是沒有做約束的,這樣再調用 SingleAsync的時候就報錯了。
首先不做約束 == 就不能使用,只能用Equals,那對於SingleAsync這個方法來說就變成了 object.Equals(object) ,而EF也大大方方的拋出了一個 Not Support Exception。
后來加了一行代碼 where TKey: IEquatable<TKey> ,輕松搞定。
丑陋的GetDbSet
因為要根據泛型獲取到DbSet,所以DbContext暴露了一個GetDbSet的方法。
然后就是操作公共類中出現的 DbContext.GetDbSet<TEntity, TKey>(dbContext) ,其實只不知道原由的話第一反應是上面的using已經Create了一個DbContext為什么下面還這么復雜的GetDbSet又把這個DbContext作為參數給扔了回去。
這我真的只能說,已經拖了很多進度就沒多少心情糾結這個事情了。
小技巧
作為一個做C/S轉B/S的人,深深的感覺到要踩的坑還有太多太多。比如一個創建頁面,text不賦值的時候是null,而我的數據庫設計是不允許為null的,但業務又允許為空,那我希望這個值是空字符串。
所以之前傻傻的先 a??"" 這樣寫了一下。后來越來越覺得不對勁,本着自己的經驗,猜測DataAnnotation應該會處理這樣的數據,畢竟從很多人的設計角度上來說是應該有這樣的功能的。
於是出現了下面這段代碼
namespace G.Client.Model.DeployManage.DeployProjectModels { public class CreateDeployProjectViewModel { [Required, StringLength(50, MinimumLength = 2)] public string Name { get; set; } public int DeployProjectGroupID { get; set; } public DeployProjectType Type { get; set; } public DeployHostType Host { get; set; } [Required(AllowEmptyStrings = true), StringLength(50, MinimumLength = 0)] [DisplayFormat(ConvertEmptyStringToNull = false, NullDisplayText = "")] public string LoadBalanceIdentity { get; set; } } }
[Required(AllowEmptyStrings = true), StringLength(50, MinimumLength = 0)] [DisplayFormat(ConvertEmptyStringToNull = false, NullDisplayText = "")]
這兩行是關鍵,首先 Required 是必填字段,這里按理說為空字符串也是應該報錯的,但 AllowEmptyStrings=True 就保證了空字符串可以通過校驗。
其次 ConvertEmptyStringToNull = false,這里就阻止了把空字符串轉換為null,同時 NullDisplayText = "" 也保證了如果真的是null也會被糾正為空字符串。
從此我就告別了 ??
國際慣例
源碼Git地址:http://git.oschina.net/doddgu/G
G.開源分布式部署 QQ群:601476986 (本群會實時更新進度,相比來說肯定比博客頻繁得多)
下一篇預告:經過這次的事情,防止打臉,不敢預告了。