- Convert CampaignId to partial record implementing IGuidStronglyTypedId - Add PointsAccountId as IInt64StronglyTypedId with Snowflake ID generation - Add Deleted and RowVersion to MemberEntity and PointsAccount - Update PointsAccountEntityTypeConfiguration to use SnowFlakeValueGenerator BREAKING CHANGE: PointsAccount now uses PointsAccountId (long) instead of plain long
159 lines
4.9 KiB
C#
159 lines
4.9 KiB
C#
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<MemberId>, 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<MemberTag> _tags = new();
|
|
public IReadOnlyCollection<MemberTag> Tags => _tags.AsReadOnly();
|
|
|
|
private readonly List<OAuthAuthorization> _oauthAuthorizations = new();
|
|
public IReadOnlyCollection<OAuthAuthorization> 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;
|
|
}
|
|
}
|