最近主導了一個電商系統的設計開發過程,包括前期分析設計,框架搭建,功能模組的具體開發(主要負責線上支付部分),成功上線後的部署維護,運維策略等等全過程。
雖然這個系統不是什麼超大型的電商系統 數億計的併發,但團隊裡的主要成員都有多年的開發經驗以及電商的經驗,系統設計方面還是麻雀雖小,但五臟俱全。
系統客戶端有 ios , android,H5,微信小程式,後臺方面用.net web api + sql server,圖片資源的讀寫使用阿里雲,快取則自己搭建redis,支付方面既有使用一些第三方支付平臺,也有和支付寶微信等。。。。 技術點很多,說實話,基本上一般應用系統開發用到的技術基本都用到了,也遇到了很多的坑。所以覺得把裡面的程式碼整理歸納出來很有必要,對於以後的系統開發,特別同類專案的開發,很有借鑑意義。由於我沒有參與前端的開發,所以這裡主要以.nt 後臺架構的講述為主。
由於是.nt mvc web api,所以選擇了 EF作為orm元件。程式分層架構分為: 資料層 DAL和 IDAL,業務層BLL和IBLL,Entity(實體定義),WebAPI,另外還建了一個Utility專案,用於容納一些與業務無關並且可以複用的功能性程式碼以及第三方開原始碼,另外還有兩個單獨的解決方案,一個名為thirdparty的web api專案, 用於和第三方平臺的介面互動,包括向簡訊平臺傳送簡訊驗證碼,向阿里雲上傳圖片,查詢 快遞單物流狀態,向推送平臺(極光,微信公眾號)推送訊息。另一個為windows服務程式,用於執行自動化工作任務。
下面分別講述。
一、DAL。主要負責封裝對EF的底層資料操作。其中有一段對EF 的DBContext 實現單例模式的程式碼,是我當時從網上摘抄下來的,程式碼如下
public class DbContextFactory { public static WinLinkContext Create() { WinLinkContext dbContext = CallContext.GetData("DbContext") as WinLinkContext; if (dbContext == null) { dbContext = new WinLinkContext(); CallContext.SetData("DbContext", dbContext); } return dbContext; } }
其實我最初使用EF時,我記得官方文件都是提倡 dbcontext 是即用即銷的,形如 (using WinLinkContext = new WinLinkContext() ){...} 那樣,有必要把dbcontext只例項化一個並快取下來,這樣能提高效能和節省資源嗎? 開始由於專案工期緊張,沒有考慮太多,想著這是別人使用過的成熟程式碼,還是某大咖的博文例子,應該錯不了。結果後來還是出現了問題。某同事a寫日誌處理模組的時候,也呼叫了這個 DbContextFactory.Create() ,本意是想把異常發生時的資訊記錄到資料庫,但由於和業務程式碼公用一個dbcontext,問題就來了。比如某同事b寫了幾行 add/update EF的程式碼,接著又寫了幾行其他的程式碼, 還沒執行 savechanges,但這幾行其他程式碼出現bug,引發異常,於是日誌捕捉到了,日誌模組就進行使用ef進行插入日誌資訊,執行savechanges,這時重點來了 , 這個savechanges就會把整個dbcontext的更新內容提交到資料庫了!於是連日誌模組都報錯了,而且錯誤提示是同事b的程式碼內容。當然也有讀者會說日誌模組應該獨立開不應該和業務混在一起,確實後來我們也把它獨立開了。但這個例子表明 dbcontext使用單例模式有很大問題,我認為還是應該遵循微軟官方示例,即用即銷,就算要快取,至少每個模組獨立開,不能整個應用使用唯一的一個。比如把上面的程式碼稍加變化,根據名稱讀寫不同的dbcontext。
public class DbContextFactory { public static WinLinkContext Create(string dbName) { WinLinkContext dbContext = CallContext.GetData(dbName) as WinLinkContext; if (dbContext == null) { dbContext = new WinLinkContext(); CallContext.SetData(dbName, dbContext); } return dbContext; } }
另外還有一個BaseDAL的類,用於封裝常規的增刪改查以及分頁等方法,程式碼大致如下(裡面的db物件就是上文提到dbcontext):
public T Add(T t) { return db.Set<T>().Add(t); } public void Update(T t) { db.Set<T>().AddOrUpdate(t); } public void Delete(T t) { db.Set<T>().Remove(t); } public bool Delete(string id) { var entity = db.Set<T>().Find(id); if (entity == null) { return false; } db.Set<T>().Remove(entity); return true; } public int Delete(string[] ids) { foreach (var item in ids) { var entity = db.Set<T>().Find(item);//如果實體已經在記憶體中,那麼就直接從記憶體拿,如果記憶體中跟蹤實體沒有,那麼才查詢資料庫。 db.Set<T>().Remove(entity); } return ids.Count(); } public T Find(Expression<Func<T, bool>> findLambda) { return db.Set<T>().FirstOrDefault(findLambda); } public T Find(string id) { return db.Set<T>().Find(id); } public IQueryable<T> Where(Expression<Func<T, bool>> whereLambda) { return db.Set<T>().Where(whereLambda); } public IQueryable<T> GetByPage<Type>(int pageSize, int pageIndex, out int total, Expression<Func<T, bool>> whereLambda, Expression<Func<T, Type>> orderByLambda, bool isAsc) { var queryData = db.Set<T>().Where(whereLambda); //是否升序 if (isAsc) { queryData = queryData.OrderBy(orderByLambda); } else { queryData = queryData.OrderByDescending(orderByLambda); } total = queryData.Count(); if (total > 0) { if (pageIndex <= 1) { queryData = queryData.Take(pageSize); } else { queryData = queryData.Skip((pageIndex - 1) * pageSize).Take(pageSize); } } return queryData; }