最近主导了一个电商系统的设计开发过程,包括前期分析设计,框架搭建,功能模块的具体开发(主要负责在线支付部分),成功上线后的部署维护,运维策略等等全过程。
虽然这个系统不是什么超大型的电商系统 数亿计的并发,但团队里的主要成员都有多年的开发经验以及电商的经验,系统设计方面还是麻雀虽小,但五脏俱全。
系统客户端有 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; }