DDD實戰進階第一波:開發通常業務的大健康行業直銷系統數據庫
1、實現經銷商上下文領域層之POCO模型後端
從這篇文章開始,咱們開始介紹大健康行業直銷系統領域層的實現。服務器
先簡單講下業務方面的需求:直銷系統會有一個頂級的經銷商,經銷商的基本信息中包括經銷商的名字、聯繫人(由於在平臺購買產品後,會寄送給聯繫人)、總的電子幣(電子幣是由經銷商支付產生,購買產品後會扣減電子幣)、總的獎金幣(系統週期性根據經銷商購買的東西來肯定獎金幣,獎金幣能夠購買東西,也能夠提現)、總PV(經銷商購買時,會根據購買產品的PV進行累加)、卡的類型(根據經銷商初次的電子幣肯定卡的類型)、子經銷商個數(子經銷商的註冊由父經銷商進行,父經銷商的直接子經銷商不超過2個)、級別(根據週期消費總額肯定經銷商級別);另外經銷商有個層級結構,最後系統固然還要對應經銷商的登陸信息,默認系統會有個登陸密碼;經銷商在註冊子經銷商時,會從本身扣除一部分電子幣附加到子經銷商上。微信
從整個需求的理解並經過對DDD理解來看,咱們會有兩個聚合,分別是經銷商聚合(包括經銷商、聯繫人、層級)和登陸聚合。前後端分離
1.經銷商聚合根:ssh
public partial class Dealers:IAggregationRootide
{ public Dealers() { } public string Code { get; set; } [Key] public Guid Id { get; set; } public string Name { get; set; } public string Tel { get; set; } public decimal TotalEleMoney { get; set; } public decimal JiangJInMoney { get; set; } public decimal TotalPV { get; set; } public CardType CardType { get; set; } public Level Level { get; set; } public int SubCount { get; set; } public List<Contact> Contacts { get; set; } public DealerTree DealerTree { get; set; } } public enum CardType : int { 普通會員=1, 銀卡會員=2, 金卡會員=3 } public enum Level : int { 片區經理=1, 省區經理=2, 大區經理=3, 董事=4 }
2.聯繫人值對象:ui
public partial class Contact : IValueObjectthis
{ public Contact() { } public Guid Id { get; set; } public string ContactName { get; set; } public string ContactTel { get; set; } public string Province { get; set; } public string City { get; set; } public string Zero { get; set; } public string Street { get; set; } public IsDefaultContact IsDefault { get; set; } } public enum IsDefaultContact : int { 默認=1, 非默認=2 }
3.層次結構值對象:idea
public partial class DealerTree : IValueObject
{ public DealerTree() { } public Guid Id { get; set; } public Guid DealerId { get; set; } public Guid? ParentDealerId { get; set; } public int Layer { get; set; } }
從經銷商聚合你們能夠看到,在建立一個經銷商時,除了有經銷商的基本信息外,還必須同時建立聯繫人與層次結構,這樣一個經銷商纔是完整的,並且經銷商也引用到了聯繫人與層次結構。
4.登陸聚合根:
public partial class Login : IAggregationRoot
{ public Login() { } //表明登陸的電話號碼 public string Code { get; set; } public string Password { get; set; } public Guid DealerId { get; set; } [Key] public Guid Id { get ; set ; } }
5.處理經銷商界限上下文與數據訪問上下文的映射
關於如何講經銷商界限上下文映射到數據訪問上下文,請參考產品上下文的相關實現,這裏就再也不累述了。
下一篇文章開始講經銷商上下文倉儲的實現,由於在註冊子經銷商的領域邏輯中,會經過倉儲去判斷當前經銷商是否子經銷商個數超過2個。
2、實現經銷商上下文倉儲與領域邏輯
上篇文章主要講述了經銷商上下文的需求與POCO對象,這篇文章主要講述該界限上下文的倉儲與領域邏輯的實現。
關於界限上下文與EF Core數據訪問上下文參考產品上下文相應的實現,這裏再也不累述。由於在經銷商上下文中有兩個聚合,一個是經銷商聚合,一個是登陸聚合,因此咱們須要實現兩個倉儲接口:
1.經銷商倉儲接口定義:
public interface IDealerRepository
{ void CreateDealer<T>(T dealer) where T : class, IAggregationRoot; //獲取上級經銷商(當前代註冊經銷商)的層次結構 int GetParentDealerLayer(Guid dealerid); //將上級經銷商(代註冊經銷商)的子個數加一 void AddParentSubCount(Guid? parentdealerid); //減去父進銷商的電子幣(用於註冊和下單時,扣減經銷商的電子幣) void SubParentEleMoney(Guid parentdealerid, decimal subelemoney); //下訂單時,增長經銷商的PV void AddDealerPV(Guid dealerid, decimal orderpv); }
2.登陸倉儲接口定義:
public interface ILoginRepository
{ void CreateLogin<T>(T login) where T : class, IAggregationRoot; Guid UserLogin(string tel, string password); }
3.具體對應的倉儲實如今倉儲實現的項目中本身實現,主要經過EF Core完成數據庫的訪問與操做。
4.經銷商聚合中聯繫人對象的領域邏輯實現:
public partial class Contact
{ public Contact CreateContact(Guid dealerid,string name,string tel,string province,string city, string zero,string street,int isdefault) { this.Id = Guid.NewGuid(); this.DealerId = dealerid; this.ContactName = name; this.ContactTel = tel; this.Province = province; this.City = city; this.Zero = zero; this.Street = street; switch (isdefault) { case 1:this.IsDefault = IsDefaultContact.默認; break; case 2:this.IsDefault = IsDefaultContact.非默認; break; } return this; } }
5.經銷商聚合中經銷商層次結構對象的領域邏輯實現:
public partial class DealerTree
{ private readonly IDealerRepository idealerrepository; public DealerTree(IDealerRepository idealerrepository) { this.idealerrepository = idealerrepository; } public DealerTree CreateDealerTree(Guid? parentdealerid,Guid dealerid) { this.Id = Guid.NewGuid(); this.DealerId = dealerid; this.ParentDealerId = parentdealerid; this.Layer = parentdealerid == null ? 1 : idealerrepository.GetParentDealerLayer(Guid.Parse(parentdealerid.ToString())) + 1; return this; } }
6.經銷商聚合中經銷商對象的領域邏輯實現:
public partial class Dealers
{ private readonly IDealerRepository idealerrepository; public Dealers(IDealerRepository idealerrepository) { this.idealerrepository = idealerrepository; } public Dealers RegisterDealer(Guid id,string name,string tel,decimal telmoney,List<Contact> contacts,Guid? parentid) { this.Id = id; this.Code = "Code " + name; this.Name = name; this.Tel = tel; this.TotalEleMoney = telmoney; if (telmoney < 2000) { this.CardType = CardType.普通會員; } else if (telmoney >= 2000 && telmoney < 4000) { this.CardType = CardType.銀卡會員; } else { this.CardType = CardType.金卡會員; } this.SubCount = 0; this.TotalPV = 0; this.JiangJInMoney = 0; this.Contacts = contacts; this.DealerTree = new DealerTree(idealerrepository).CreateDealerTree(parentid, id); return this; } }
7.登陸聚合中登陸對象的領域邏輯實現:
public partial class Login
{ public Login CreateLogin(string code,Guid dealerid) { this.Id = Guid.NewGuid(); //手機號 this.Code = code; //默認初始密碼 this.Password=MD5Encrption.GetMd5Str("111111"); this.DealerId = dealerid; return this; } }
這樣,咱們就完成了基本數據庫的訪問、操做和相關領域邏輯的實現。
3、實現經銷商登陸倉儲與邏輯
上一篇文章主要講了經銷商註冊的倉儲和領域邏輯的實現,咱們先把應用服務協調完成經銷商註冊這部分暫停一下,後面文章統一講。這篇文章主要講講經銷商登陸的倉儲和相關邏輯的實現。
在現代應用程序先後端分離的實現中,一般不是將用戶登陸的信息存儲在服務器端Session,由於會存在服務器Session沒法傳遞的狀況,也存在WebApi調用時沒法經過Authorize Attribute判斷用戶是否已經登陸並獲取用戶身份信息的問題。因此現代應用程序都是由服務器後端返回Token給客戶端,客戶端將Token存儲在客戶端Session中,客戶端在請求後端接口時,帶上Token,服務器端就可以識別客戶端是否通過身份驗證,並且能夠直接拿到客戶端的身份。
要實現經銷商的登陸,主要由如下幾個步驟組成
1.實現經銷商登陸時信息查詢的倉儲。
2.在應用服務中,單獨創建一個查詢文件夾放置經銷商登陸的查詢邏輯。
3.在登陸WebApi中,調用應用服務的查詢邏輯並分發Token。
1.實現經銷商登陸時信息查詢的倉儲:
public interface ILoginRepository
{ Guid UserLogin(string tel, string password); }
public class LoginEFCoreRepository : ILoginRepository
{ private readonly DbContext context; public LoginEFCoreRepository(DbContext context) { this.context = context; } public Guid UserLogin(string tel, string password) { var dealercontext = this.context as DealerEFCoreContext; var enpassword = MD5Encrption.GetMd5Str(password); var logindealer= dealercontext.Login.Where(p => p.Code == tel && p.Password == enpassword).FirstOrDefault(); if (logindealer != null) { return logindealer.DealerId; } return Guid.Empty; } }
2.應用服務中調用倉儲完成用戶登陸的查詢
public class UserLoginQuery:BaseAppSrv
{ private readonly IRepository irepository; private readonly ILoginRepository iloginrepository; public UserLoginQuery(IRepository irepository, ILoginRepository iloginrepository) { this.iloginrepository = iloginrepository; this.irepository = irepository; } public Guid Login(UserLoginDTO userlogindto) { try { using (irepository) { return iloginrepository.UserLogin(userlogindto.Telphone, userlogindto.Password); } } catch(Exception error) { throw error; } } }
3.在登陸WebApi中調用應用服務,並分發令牌
[AllowAnonymous]
[HttpPost] [Route("UserLogin")] public ResultEntity<UserLoginResultDTO> UserLogin([FromBody] UserLoginDTO userlogindto) { var result = new ResultEntity<UserLoginResultDTO>(); var idealercontext = servicelocator.GetService<IDealerContext>(); var irepository = servicelocator.GetService<IRepository>(new ParameterOverrides { { "context", idealercontext } }); var iloginrepository = servicelocator.GetService<ILoginRepository>(new ParameterOverrides { { "context", idealercontext } }); UserLoginQuery userloginquery = new UserLoginQuery(irepository, iloginrepository); try { var dealerid = userloginquery.Login(userlogindto); if (dealerid != Guid.Empty) { var token = new JwtTokenBuilder() .AddSecurityKey(JwtSecurityKey.Create("msshcjsecretmsshcjsecret")) .AddSubject(userlogindto.Telphone) .AddIssuer("DDD1ZXSystem") .AddAudience("DDD1ZXSystem") .AddClaim("role", "NormalUser") .AddExpiry(600) .Build(); var userloginresultdto = new UserLoginResultDTO(); userloginresultdto.Tel = userlogindto.Telphone; userloginresultdto.DealerId = dealerid; userloginresultdto.Token = token.Value; result.IsSuccess = true; result.Data = userloginresultdto; result.Msg = "登陸成功!"; } else { result.ErrorCode = 300; result.Msg = "登陸失敗!"; } } catch (Exception error) { result.ErrorCode = 200; result.Msg = error.Message; } return result; }
這裏的UserLoginDTO定義以下:
public class UserLoginDTO
{ public string Telphone { get; set; } public string Password { get; set; } }
這裏的UserLoginResultDTO定義以下:
public class UserLoginResultDTO
{ public string Tel { get; set; } public Guid DealerId { get; set; } public string Token { get; set; } }
這裏的JwtTokenBuilder定義以下:
public class JwtTokenBuilder
{ private SecurityKey securityKey = null; private string subject = ""; private string issuer = ""; private string audience = ""; private Dictionary<string, string> claims = new Dictionary<string, string>(); private int expiryInMinutes = 5; public JwtTokenBuilder AddSecurityKey(SecurityKey securityKey) { this.securityKey = securityKey; return this; } public JwtTokenBuilder AddSubject(string subject) { this.subject = subject; return this; } public JwtTokenBuilder AddIssuer(string issuer) { this.issuer = issuer; return this; } public JwtTokenBuilder AddAudience(string audience) { this.audience = audience; return this; } public JwtTokenBuilder AddClaim(string type,string value) { this.claims.Add(type, value); return this; } public JwtTokenBuilder AddExpiry(int expiryInMinutes) { this.expiryInMinutes = expiryInMinutes; return this; } public JwtToken Build() { var claims = new List<Claim> { new Claim(JwtRegisteredClaimNames.Sub,this.subject), new Claim(JwtRegisteredClaimNames.Jti,Guid.NewGuid().ToString()) }.Union(this.claims.Select(item => new Claim(item.Key, item.Value))); var token = new JwtSecurityToken(issuer: this.issuer, audience: this.audience, claims: claims, expires: DateTime.UtcNow.AddMinutes(this.expiryInMinutes), signingCredentials: new SigningCredentials(this.securityKey, SecurityAlgorithms.HmacSha256)); return new JwtToken(token); } }
這裏的BearerUserInfo定義以下:
public class BearerUserInfo:Controller
{ public string GetUserName() { var principal = HttpContext.User as ClaimsPrincipal; if (principal != null) { foreach(var claim in principal.Claims) { if (claim.Subject != null) { var subjectclaims = claim.Subject.Claims as List<Claim>; return subjectclaims[0].Value; } } } return null; } }
這裏的JwtSecurityKey定義以下:
public static class JwtSecurityKey
{ public static SymmetricSecurityKey Create(string secret) { return new SymmetricSecurityKey(Encoding.UTF8.GetBytes(secret)); } }
這裏的JwtToken定義以下:
public class JwtToken
{ private JwtSecurityToken token; public JwtToken(JwtSecurityToken token) { this.token = token; } public DateTime ValidTo => token.ValidTo; public string Value => new JwtSecurityTokenHandler().WriteToken(this.token); }
以上採用了.net core中關於OWIN的使用,具體不清楚的屬性和方法,能夠參考OWIN中.net core的實現標準,這裏就不累述了,具體能夠參考微信公衆號中的視頻講解。
QQ討論羣:309287205DDD實戰進階視頻請關注微信公衆號:msshcj