領域驅動設計實戰案例(三):實現經銷商上下文領域層之POCO模型、 倉儲與領域邏輯、經銷商登陸倉儲與邏輯

DDD實戰進階第一波:開發通常業務的大健康行業直銷系統數據庫

1、實現經銷商上下文領域層之POCO模型後端

從這篇文章開始,咱們開始介紹大健康行業直銷系統領域層的實現。服務器

先簡單講下業務方面的需求:直銷系統會有一個頂級的經銷商,經銷商的基本信息中包括經銷商的名字、聯繫人(由於在平臺購買產品後,會寄送給聯繫人)、總的電子幣(電子幣是由經銷商支付產生,購買產品後會扣減電子幣)、總的獎金幣(系統週期性根據經銷商購買的東西來肯定獎金幣,獎金幣能夠購買東西,也能夠提現)、總PV(經銷商購買時,會根據購買產品的PV進行累加)、卡的類型(根據經銷商初次的電子幣肯定卡的類型)、子經銷商個數(子經銷商的註冊由父經銷商進行,父經銷商的直接子經銷商不超過2個)、級別(根據週期消費總額肯定經銷商級別);另外經銷商有個層級結構,最後系統固然還要對應經銷商的登陸信息,默認系統會有個登陸密碼;經銷商在註冊子經銷商時,會從本身扣除一部分電子幣附加到子經銷商上。微信

從整個需求的理解並經過對DDD理解來看,咱們會有兩個聚合,分別是經銷商聚合(包括經銷商、聯繫人、層級)和登陸聚合。前後端分離

clipboard.png

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

相關文章
相關標籤/搜索