using System.Text.RegularExpressions; using Fengling.Member.Domain.Events.Member; using NetCorePal.Extensions.Domain; namespace Fengling.Member.Domain.Aggregates.Users; public partial record MemberId : IGuidStronglyTypedId { public static MemberId New() => new MemberId(Guid.NewGuid()); public Guid Value => this; public static implicit operator Guid(MemberId id) => id.Value; public static implicit operator MemberId(Guid value) => new MemberId(value); } public class MemberEntity : Entity, IAggregateRoot { public long TenantId { get; private set; } public string? PhoneNumber { get; private set; } public string? OpenId { get; private set; } public string? UnionId { get; private set; } public MemberStatus Status { get; private set; } = MemberStatus.Active; public DateTime CreatedAt { get; private set; } = DateTime.UtcNow; public DateTime? UpdatedAt { get; private set; } public int Version { get; private set; } = 1; public Deleted Deleted { get; private set; } = new(); public RowVersion RowVersion { get; private set; } = new(0); private readonly List _tags = new(); public IReadOnlyCollection Tags => _tags.AsReadOnly(); private readonly List _oauthAuthorizations = new(); public IReadOnlyCollection OAuthAuthorizations => _oauthAuthorizations.AsReadOnly(); private MemberEntity() { } public static MemberEntity Create(long tenantId, string? phoneNumber = null) { if (tenantId <= 0) throw new ArgumentException("租户ID必须大于0", nameof(tenantId)); var member = new MemberEntity { TenantId = tenantId, PhoneNumber = phoneNumber, Status = MemberStatus.Active, CreatedAt = DateTime.UtcNow }; member.AddDomainEvent(new MemberRegisteredEvent(member.Id, member.TenantId, phoneNumber, member.CreatedAt)); return member; } public void BindPhoneNumber(string phoneNumber) { if (string.IsNullOrWhiteSpace(phoneNumber)) throw new ArgumentException("手机号不能为空", nameof(phoneNumber)); if (!Regex.IsMatch(phoneNumber, @"^1[3-9]\d{9}$")) throw new ArgumentException("手机号格式不正确", nameof(phoneNumber)); PhoneNumber = phoneNumber; UpdatedAt = DateTime.UtcNow; } public void BindOAuth(OAuthProvider provider, string openId, string? unionId = null) { if (string.IsNullOrWhiteSpace(openId)) throw new ArgumentException("OpenId不能为空", nameof(openId)); UpdatePrimaryOAuthId(provider, openId, unionId); UpdatedAt = DateTime.UtcNow; var authorization = _oauthAuthorizations.FirstOrDefault(x => x.Provider == provider && x.OpenId == openId); if (authorization == null) { authorization = OAuthAuthorization.Create(Id, provider, openId, unionId); _oauthAuthorizations.Add(authorization); } else { authorization.UpdateUnionId(unionId); } } public void BindWechat(string openId, string? unionId = null) { BindOAuth(OAuthProvider.Wechat, openId, unionId); } private void UpdatePrimaryOAuthId(OAuthProvider provider, string openId, string? unionId) { if (provider == OAuthProvider.Wechat) { OpenId = openId; UnionId = unionId ?? UnionId; } } public OAuthAuthorization? GetOAuthAuthorization(OAuthProvider provider) { return _oauthAuthorizations.FirstOrDefault(x => x.Provider == provider); } public bool HasOAuthBound(OAuthProvider provider, string openId) { return _oauthAuthorizations.Any(x => x.Provider == provider && x.OpenId == openId); } public void RecordOAuthLogin(OAuthProvider provider, string openId) { var authorization = _oauthAuthorizations.FirstOrDefault(x => x.Provider == provider && x.OpenId == openId); authorization?.RecordLogin(); } public void AddTag(string tagId, string? tagName = null) { var existingTag = _tags.FirstOrDefault(t => t.TagId == tagId); if (existingTag != null) return; _tags.Add(MemberTag.Create(Id, tagId, tagName)); } public void RemoveTag(string tagId) { var tag = _tags.FirstOrDefault(t => t.TagId == tagId); if (tag != null) _tags.Remove(tag); } public void Freeze() { if (Status == MemberStatus.Frozen) return; Status = MemberStatus.Frozen; UpdatedAt = DateTime.UtcNow; } public void Unfreeze() { Status = MemberStatus.Active; UpdatedAt = DateTime.UtcNow; } public void Deactivate() { if (Status == MemberStatus.Inactive) return; Status = MemberStatus.Inactive; UpdatedAt = DateTime.UtcNow; } }