feat(member): change MemberId to Guid strongly typed id
- Convert MemberId from long to Guid strongly typed ID - Update all Member commands to use record pattern with MemberId - Update all Member endpoints to use record pattern with MemberId - Update entity configurations to use GuidVersion7ValueGenerator - Add implicit conversion operators for MemberId Migration: ChangeMemberIdToGuid
This commit is contained in:
parent
3b8b86a258
commit
96503a593d
@ -5,64 +5,34 @@ using Fengling.Member.Infrastructure.Repositories;
|
||||
|
||||
namespace Fengling.Member.Application.Commands.Member;
|
||||
|
||||
public class AddMemberTagCommand : IRequest<AddMemberTagResponse>
|
||||
{
|
||||
public long MemberId { get; set; }
|
||||
public string TagId { get; set; } = string.Empty;
|
||||
public string? TagName { get; set; }
|
||||
}
|
||||
public record AddMemberTagCommand(MemberId MemberId, string TagId, string? TagName)
|
||||
: IRequest<AddMemberTagResponse>;
|
||||
|
||||
public class AddMemberTagResponse
|
||||
{
|
||||
public long MemberId { get; set; }
|
||||
public string TagId { get; set; } = string.Empty;
|
||||
public string? TagName { get; set; }
|
||||
public DateTime AddedAt { get; set; }
|
||||
}
|
||||
public record AddMemberTagResponse(MemberId MemberId, string TagId, string? TagName, DateTime AddedAt);
|
||||
|
||||
public class AddMemberTagCommandValidator : AbstractValidator<AddMemberTagCommand>
|
||||
{
|
||||
public AddMemberTagCommandValidator()
|
||||
{
|
||||
RuleFor(x => x.MemberId).GreaterThan(0);
|
||||
RuleFor(x => x.TagId).NotEmpty().MaximumLength(50);
|
||||
RuleFor(x => x.TagName).MaximumLength(100);
|
||||
}
|
||||
}
|
||||
|
||||
public class AddMemberTagCommandHandler : IRequestHandler<AddMemberTagCommand, AddMemberTagResponse>
|
||||
public class AddMemberTagCommandHandler(IMemberRepository memberRepository, ILogger<AddMemberTagCommandHandler> logger)
|
||||
: IRequestHandler<AddMemberTagCommand, AddMemberTagResponse>
|
||||
{
|
||||
private readonly IMemberRepository _memberRepository;
|
||||
private readonly ILogger<AddMemberTagCommandHandler> _logger;
|
||||
|
||||
public AddMemberTagCommandHandler(
|
||||
IMemberRepository memberRepository,
|
||||
ILogger<AddMemberTagCommandHandler> logger)
|
||||
{
|
||||
_memberRepository = memberRepository;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public async Task<AddMemberTagResponse> Handle(AddMemberTagCommand request, CancellationToken cancellationToken)
|
||||
{
|
||||
_logger.LogInformation("Adding tag {TagId} to member {MemberId}", request.TagId, request.MemberId);
|
||||
logger.LogInformation("Adding tag {TagId} to member {MemberId}", request.TagId, request.MemberId);
|
||||
|
||||
var member = await _memberRepository.GetAsync(request.MemberId, cancellationToken);
|
||||
if (member == null)
|
||||
{
|
||||
throw new KeyNotFoundException($"会员不存在: {request.MemberId}");
|
||||
}
|
||||
var member = await memberRepository.GetAsync(request.MemberId, cancellationToken)
|
||||
?? throw new KeyNotFoundException($"会员不存在: {request.MemberId}");
|
||||
|
||||
member.AddTag(request.TagId, request.TagName);
|
||||
|
||||
_logger.LogInformation("Tag {TagId} added to member {MemberId}", request.TagId, request.MemberId);
|
||||
logger.LogInformation("Tag {TagId} added to member {MemberId}", request.TagId, request.MemberId);
|
||||
|
||||
return new AddMemberTagResponse
|
||||
{
|
||||
MemberId = member.Id,
|
||||
TagId = request.TagId,
|
||||
TagName = request.TagName,
|
||||
AddedAt = DateTime.UtcNow
|
||||
};
|
||||
return new AddMemberTagResponse(member.Id, request.TagId, request.TagName, DateTime.UtcNow);
|
||||
}
|
||||
}
|
||||
|
||||
@ -5,64 +5,34 @@ using Fengling.Member.Infrastructure.Repositories;
|
||||
|
||||
namespace Fengling.Member.Application.Commands.Member;
|
||||
|
||||
public class BindAlipayCommand : IRequest<BindAlipayResponse>
|
||||
{
|
||||
public long MemberId { get; set; }
|
||||
public string AlipayOpenId { get; set; } = string.Empty;
|
||||
public string? AlipayUserId { get; set; }
|
||||
}
|
||||
public record BindAlipayCommand(MemberId MemberId, string AlipayOpenId, string? AlipayUserId)
|
||||
: IRequest<BindAlipayResponse>;
|
||||
|
||||
public class BindAlipayResponse
|
||||
{
|
||||
public long MemberId { get; set; }
|
||||
public string AlipayOpenId { get; set; } = string.Empty;
|
||||
public string? AlipayUserId { get; set; }
|
||||
public DateTime BoundAt { get; set; }
|
||||
}
|
||||
public record BindAlipayResponse(MemberId MemberId, string AlipayOpenId, string? AlipayUserId, DateTime BoundAt);
|
||||
|
||||
public class BindAlipayCommandValidator : AbstractValidator<BindAlipayCommand>
|
||||
{
|
||||
public BindAlipayCommandValidator()
|
||||
{
|
||||
RuleFor(x => x.MemberId).GreaterThan(0);
|
||||
RuleFor(x => x.AlipayOpenId).NotEmpty().MaximumLength(128);
|
||||
RuleFor(x => x.AlipayUserId).MaximumLength(128);
|
||||
}
|
||||
}
|
||||
|
||||
public class BindAlipayCommandHandler : IRequestHandler<BindAlipayCommand, BindAlipayResponse>
|
||||
public class BindAlipayCommandHandler(IMemberRepository memberRepository, ILogger<BindAlipayCommandHandler> logger)
|
||||
: IRequestHandler<BindAlipayCommand, BindAlipayResponse>
|
||||
{
|
||||
private readonly IMemberRepository _memberRepository;
|
||||
private readonly ILogger<BindAlipayCommandHandler> _logger;
|
||||
|
||||
public BindAlipayCommandHandler(
|
||||
IMemberRepository memberRepository,
|
||||
ILogger<BindAlipayCommandHandler> logger)
|
||||
{
|
||||
_memberRepository = memberRepository;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public async Task<BindAlipayResponse> Handle(BindAlipayCommand request, CancellationToken cancellationToken)
|
||||
{
|
||||
_logger.LogInformation("Binding Alipay for member {MemberId}", request.MemberId);
|
||||
logger.LogInformation("Binding Alipay for member {MemberId}", request.MemberId);
|
||||
|
||||
var member = await _memberRepository.GetAsync(request.MemberId, cancellationToken);
|
||||
if (member == null)
|
||||
{
|
||||
throw new KeyNotFoundException($"会员不存在: {request.MemberId}");
|
||||
}
|
||||
var member = await memberRepository.GetAsync(request.MemberId, cancellationToken)
|
||||
?? throw new KeyNotFoundException($"会员不存在: {request.MemberId}");
|
||||
|
||||
member.BindOAuth(OAuthProvider.Alipay, request.AlipayOpenId, request.AlipayUserId);
|
||||
|
||||
_logger.LogInformation("Alipay bound successfully for member {MemberId}", request.MemberId);
|
||||
logger.LogInformation("Alipay bound successfully for member {MemberId}", request.MemberId);
|
||||
|
||||
return new BindAlipayResponse
|
||||
{
|
||||
MemberId = member.Id,
|
||||
AlipayOpenId = request.AlipayOpenId,
|
||||
AlipayUserId = request.AlipayUserId,
|
||||
BoundAt = DateTime.UtcNow
|
||||
};
|
||||
return new BindAlipayResponse(member.Id, request.AlipayOpenId, request.AlipayUserId, DateTime.UtcNow);
|
||||
}
|
||||
}
|
||||
|
||||
@ -5,70 +5,37 @@ using Fengling.Member.Infrastructure.Repositories;
|
||||
|
||||
namespace Fengling.Member.Application.Commands.Member;
|
||||
|
||||
public class BindOAuthCommand : IRequest<BindOAuthResponse>
|
||||
{
|
||||
public OAuthProvider Provider { get; set; }
|
||||
public long MemberId { get; set; }
|
||||
public string OpenId { get; set; } = string.Empty;
|
||||
public string? UnionId { get; set; }
|
||||
}
|
||||
public record BindOAuthCommand(OAuthProvider Provider, MemberId MemberId, string OpenId, string? UnionId)
|
||||
: IRequest<BindOAuthResponse>;
|
||||
|
||||
public class BindOAuthResponse
|
||||
{
|
||||
public long MemberId { get; set; }
|
||||
public OAuthProvider Provider { get; set; }
|
||||
public string OpenId { get; set; } = string.Empty;
|
||||
public string? UnionId { get; set; }
|
||||
public DateTime BoundAt { get; set; }
|
||||
}
|
||||
public record BindOAuthResponse(MemberId MemberId, OAuthProvider Provider, string OpenId, string? UnionId, DateTime BoundAt);
|
||||
|
||||
public class BindOAuthCommandValidator : AbstractValidator<BindOAuthCommand>
|
||||
{
|
||||
public BindOAuthCommandValidator()
|
||||
{
|
||||
RuleFor(x => x.Provider).IsInEnum();
|
||||
RuleFor(x => x.MemberId).GreaterThan(0);
|
||||
RuleFor(x => x.OpenId).NotEmpty().MaximumLength(128);
|
||||
RuleFor(x => x.UnionId).MaximumLength(128);
|
||||
}
|
||||
}
|
||||
|
||||
public class BindOAuthCommandHandler : IRequestHandler<BindOAuthCommand, BindOAuthResponse>
|
||||
public class BindOAuthCommandHandler(IMemberRepository memberRepository, ILogger<BindOAuthCommandHandler> logger)
|
||||
: IRequestHandler<BindOAuthCommand, BindOAuthResponse>
|
||||
{
|
||||
private readonly IMemberRepository _memberRepository;
|
||||
private readonly ILogger<BindOAuthCommandHandler> _logger;
|
||||
|
||||
public BindOAuthCommandHandler(
|
||||
IMemberRepository memberRepository,
|
||||
ILogger<BindOAuthCommandHandler> logger)
|
||||
{
|
||||
_memberRepository = memberRepository;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public async Task<BindOAuthResponse> Handle(BindOAuthCommand request, CancellationToken cancellationToken)
|
||||
{
|
||||
_logger.LogInformation("Binding {Provider} for member {MemberId}",
|
||||
logger.LogInformation("Binding {Provider} for member {MemberId}",
|
||||
request.Provider.GetProviderName(), request.MemberId);
|
||||
|
||||
var member = await _memberRepository.GetAsync(request.MemberId, cancellationToken);
|
||||
if (member == null)
|
||||
{
|
||||
throw new KeyNotFoundException($"会员不存在: {request.MemberId}");
|
||||
}
|
||||
var member = await memberRepository.GetAsync(request.MemberId, cancellationToken)
|
||||
?? throw new KeyNotFoundException($"会员不存在: {request.MemberId}");
|
||||
|
||||
member.BindOAuth(request.Provider, request.OpenId, request.UnionId);
|
||||
|
||||
_logger.LogInformation("{Provider} bound successfully for member {MemberId}",
|
||||
logger.LogInformation("{Provider} bound successfully for member {MemberId}",
|
||||
request.Provider.GetProviderName(), request.MemberId);
|
||||
|
||||
return new BindOAuthResponse
|
||||
{
|
||||
MemberId = member.Id,
|
||||
Provider = request.Provider,
|
||||
OpenId = request.OpenId,
|
||||
UnionId = request.UnionId,
|
||||
BoundAt = DateTime.UtcNow
|
||||
};
|
||||
return new BindOAuthResponse(member.Id, request.Provider, request.OpenId, request.UnionId, DateTime.UtcNow);
|
||||
}
|
||||
}
|
||||
|
||||
@ -5,64 +5,34 @@ using Fengling.Member.Infrastructure.Repositories;
|
||||
|
||||
namespace Fengling.Member.Application.Commands.Member;
|
||||
|
||||
public class BindWechatCommand : IRequest<BindWechatResponse>
|
||||
{
|
||||
public long MemberId { get; set; }
|
||||
public string OpenId { get; set; } = string.Empty;
|
||||
public string? UnionId { get; set; }
|
||||
}
|
||||
public record BindWechatCommand(MemberId MemberId, string OpenId, string? UnionId)
|
||||
: IRequest<BindWechatResponse>;
|
||||
|
||||
public class BindWechatResponse
|
||||
{
|
||||
public long MemberId { get; set; }
|
||||
public string OpenId { get; set; } = string.Empty;
|
||||
public string? UnionId { get; set; }
|
||||
public DateTime BoundAt { get; set; }
|
||||
}
|
||||
public record BindWechatResponse(MemberId MemberId, string OpenId, string? UnionId, DateTime BoundAt);
|
||||
|
||||
public class BindWechatCommandValidator : AbstractValidator<BindWechatCommand>
|
||||
{
|
||||
public BindWechatCommandValidator()
|
||||
{
|
||||
RuleFor(x => x.MemberId).GreaterThan(0);
|
||||
RuleFor(x => x.OpenId).NotEmpty().MaximumLength(64);
|
||||
RuleFor(x => x.UnionId).MaximumLength(64);
|
||||
}
|
||||
}
|
||||
|
||||
public class BindWechatCommandHandler : IRequestHandler<BindWechatCommand, BindWechatResponse>
|
||||
public class BindWechatCommandHandler(IMemberRepository memberRepository, ILogger<BindWechatCommandHandler> logger)
|
||||
: IRequestHandler<BindWechatCommand, BindWechatResponse>
|
||||
{
|
||||
private readonly IMemberRepository _memberRepository;
|
||||
private readonly ILogger<BindWechatCommandHandler> _logger;
|
||||
|
||||
public BindWechatCommandHandler(
|
||||
IMemberRepository memberRepository,
|
||||
ILogger<BindWechatCommandHandler> logger)
|
||||
{
|
||||
_memberRepository = memberRepository;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public async Task<BindWechatResponse> Handle(BindWechatCommand request, CancellationToken cancellationToken)
|
||||
{
|
||||
_logger.LogInformation("Binding wechat for member {MemberId}", request.MemberId);
|
||||
logger.LogInformation("Binding wechat for member {MemberId}", request.MemberId);
|
||||
|
||||
var member = await _memberRepository.GetAsync(request.MemberId, cancellationToken);
|
||||
if (member == null)
|
||||
{
|
||||
throw new KeyNotFoundException($"会员不存在: {request.MemberId}");
|
||||
}
|
||||
var member = await memberRepository.GetAsync(request.MemberId, cancellationToken)
|
||||
?? throw new KeyNotFoundException($"会员不存在: {request.MemberId}");
|
||||
|
||||
member.BindWechat(request.OpenId, request.UnionId);
|
||||
|
||||
_logger.LogInformation("Wechat bound successfully for member {MemberId}", request.MemberId);
|
||||
logger.LogInformation("Wechat bound successfully for member {MemberId}", request.MemberId);
|
||||
|
||||
return new BindWechatResponse
|
||||
{
|
||||
MemberId = member.Id,
|
||||
OpenId = request.OpenId,
|
||||
UnionId = request.UnionId,
|
||||
BoundAt = DateTime.UtcNow
|
||||
};
|
||||
return new BindWechatResponse(member.Id, request.OpenId, request.UnionId, DateTime.UtcNow);
|
||||
}
|
||||
}
|
||||
|
||||
@ -5,48 +5,33 @@ using Fengling.Member.Infrastructure.Repositories;
|
||||
|
||||
namespace Fengling.Member.Application.Commands.Member;
|
||||
|
||||
public class RegisterMemberCommand : IRequest<RegisterMemberResponse>
|
||||
public record RegisterMemberCommand(long TenantId, string? PhoneNumber, string? OpenId, string? UnionId, string? Source)
|
||||
: IRequest<RegisterMemberResponse>;
|
||||
|
||||
public record RegisterMemberResponse(MemberId MemberId, long TenantId, string? PhoneNumber, string? OpenId, MemberStatus Status, DateTime RegisteredAt);
|
||||
|
||||
public class RegisterMemberCommandValidator : AbstractValidator<RegisterMemberCommand>
|
||||
{
|
||||
public long TenantId { get; set; }
|
||||
public string? PhoneNumber { get; set; }
|
||||
public string? OpenId { get; set; }
|
||||
public string? UnionId { get; set; }
|
||||
public string? Source { get; set; }
|
||||
public RegisterMemberCommandValidator()
|
||||
{
|
||||
RuleFor(x => x.TenantId).GreaterThan(0);
|
||||
RuleFor(x => x.PhoneNumber).Matches(@"^1[3-9]\d{9}$").When(x => x.PhoneNumber != null);
|
||||
}
|
||||
}
|
||||
|
||||
public class RegisterMemberResponse
|
||||
public class RegisterMemberCommandHandler(IMemberRepository memberRepository, ILogger<RegisterMemberCommandHandler> logger)
|
||||
: IRequestHandler<RegisterMemberCommand, RegisterMemberResponse>
|
||||
{
|
||||
public long MemberId { get; set; }
|
||||
public long TenantId { get; set; }
|
||||
public string? PhoneNumber { get; set; }
|
||||
public string? OpenId { get; set; }
|
||||
public MemberStatus Status { get; set; } = MemberStatus.Active;
|
||||
public DateTime RegisteredAt { get; set; }
|
||||
}
|
||||
|
||||
public class RegisterMemberCommandHandler : IRequestHandler<RegisterMemberCommand, RegisterMemberResponse>
|
||||
{
|
||||
private readonly IMemberRepository _memberRepository;
|
||||
private readonly ILogger<RegisterMemberCommandHandler> _logger;
|
||||
|
||||
public RegisterMemberCommandHandler(
|
||||
IMemberRepository memberRepository,
|
||||
ILogger<RegisterMemberCommandHandler> logger)
|
||||
{
|
||||
_memberRepository = memberRepository;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public async Task<RegisterMemberResponse> Handle(RegisterMemberCommand request, CancellationToken cancellationToken)
|
||||
{
|
||||
_logger.LogInformation("Registering new member for tenant {TenantId}", request.TenantId);
|
||||
logger.LogInformation("Registering new member for tenant {TenantId}", request.TenantId);
|
||||
|
||||
if (!string.IsNullOrEmpty(request.PhoneNumber))
|
||||
{
|
||||
var existingMember = await _memberRepository.GetByPhoneNumberAsync(request.TenantId, request.PhoneNumber, cancellationToken);
|
||||
var existingMember = await memberRepository.GetByPhoneNumberAsync(request.TenantId, request.PhoneNumber, cancellationToken);
|
||||
if (existingMember != null)
|
||||
{
|
||||
_logger.LogWarning("Member with phone {PhoneNumber} already exists in tenant {TenantId}",
|
||||
logger.LogWarning("Member with phone {PhoneNumber} already exists in tenant {TenantId}",
|
||||
request.PhoneNumber, request.TenantId);
|
||||
|
||||
if (!string.IsNullOrEmpty(request.OpenId) && string.IsNullOrEmpty(existingMember.OpenId))
|
||||
@ -54,34 +39,18 @@ public class RegisterMemberCommandHandler : IRequestHandler<RegisterMemberComman
|
||||
existingMember.BindWechat(request.OpenId, request.UnionId);
|
||||
}
|
||||
|
||||
return new RegisterMemberResponse
|
||||
{
|
||||
MemberId = existingMember.Id,
|
||||
TenantId = existingMember.TenantId,
|
||||
PhoneNumber = existingMember.PhoneNumber,
|
||||
OpenId = existingMember.OpenId,
|
||||
Status = existingMember.Status,
|
||||
RegisteredAt = existingMember.CreatedAt
|
||||
};
|
||||
return new RegisterMemberResponse(existingMember.Id, existingMember.TenantId, existingMember.PhoneNumber, existingMember.OpenId, existingMember.Status, existingMember.CreatedAt);
|
||||
}
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(request.OpenId))
|
||||
{
|
||||
var existingByOpenId = await _memberRepository.GetByOpenIdAsync(request.OpenId, cancellationToken);
|
||||
var existingByOpenId = await memberRepository.GetByOpenIdAsync(request.OpenId, cancellationToken);
|
||||
if (existingByOpenId != null)
|
||||
{
|
||||
_logger.LogWarning("Member with OpenId {OpenId} already exists", request.OpenId);
|
||||
logger.LogWarning("Member with OpenId {OpenId} already exists", request.OpenId);
|
||||
|
||||
return new RegisterMemberResponse
|
||||
{
|
||||
MemberId = existingByOpenId.Id,
|
||||
TenantId = existingByOpenId.TenantId,
|
||||
PhoneNumber = existingByOpenId.PhoneNumber,
|
||||
OpenId = existingByOpenId.OpenId,
|
||||
Status = existingByOpenId.Status,
|
||||
RegisteredAt = existingByOpenId.CreatedAt
|
||||
};
|
||||
return new RegisterMemberResponse(existingByOpenId.Id, existingByOpenId.TenantId, existingByOpenId.PhoneNumber, existingByOpenId.OpenId, existingByOpenId.Status, existingByOpenId.CreatedAt);
|
||||
}
|
||||
}
|
||||
|
||||
@ -92,18 +61,10 @@ public class RegisterMemberCommandHandler : IRequestHandler<RegisterMemberComman
|
||||
member.BindWechat(request.OpenId, request.UnionId);
|
||||
}
|
||||
|
||||
await _memberRepository.AddAsync(member, cancellationToken);
|
||||
await memberRepository.AddAsync(member, cancellationToken);
|
||||
|
||||
_logger.LogInformation("Member registered successfully with ID {MemberId}", member.Id);
|
||||
logger.LogInformation("Member registered successfully with ID {MemberId}", member.Id);
|
||||
|
||||
return new RegisterMemberResponse
|
||||
{
|
||||
MemberId = member.Id,
|
||||
TenantId = member.TenantId,
|
||||
PhoneNumber = member.PhoneNumber,
|
||||
OpenId = member.OpenId,
|
||||
Status = member.Status,
|
||||
RegisteredAt = member.CreatedAt
|
||||
};
|
||||
return new RegisterMemberResponse(member.Id, member.TenantId, member.PhoneNumber, member.OpenId, member.Status, member.CreatedAt);
|
||||
}
|
||||
}
|
||||
|
||||
@ -3,7 +3,17 @@ using Fengling.Member.Domain.Events.Member;
|
||||
|
||||
namespace Fengling.Member.Domain.Aggregates.Users;
|
||||
|
||||
public class MemberEntity : Entity<long>, IAggregateRoot
|
||||
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; }
|
||||
|
||||
@ -2,7 +2,7 @@ namespace Fengling.Member.Domain.Aggregates.Users;
|
||||
|
||||
public class MemberTag : Entity<long>
|
||||
{
|
||||
public long MemberId { get; private set; }
|
||||
public MemberId MemberId { get; private set; } = default!;
|
||||
public string TagId { get; private set; } = string.Empty;
|
||||
public string? TagName { get; private set; }
|
||||
public DateTime CreatedAt { get; private set; } = DateTime.UtcNow;
|
||||
@ -11,7 +11,7 @@ public class MemberTag : Entity<long>
|
||||
{
|
||||
}
|
||||
|
||||
public static MemberTag Create(long memberId, string tagId, string? tagName = null)
|
||||
public static MemberTag Create(MemberId memberId, string tagId, string? tagName = null)
|
||||
{
|
||||
return new MemberTag
|
||||
{
|
||||
|
||||
@ -2,7 +2,7 @@ namespace Fengling.Member.Domain.Aggregates.Users;
|
||||
|
||||
public class OAuthAuthorization : Entity<long>
|
||||
{
|
||||
public long MemberId { get; private set; }
|
||||
public MemberId MemberId { get; private set; } = default!;
|
||||
public OAuthProvider Provider { get; private set; }
|
||||
public string OpenId { get; private set; } = string.Empty;
|
||||
public string? UnionId { get; private set; }
|
||||
@ -16,7 +16,7 @@ public class OAuthAuthorization : Entity<long>
|
||||
{
|
||||
}
|
||||
|
||||
public static OAuthAuthorization Create(long memberId, OAuthProvider provider, string openId, string? unionId = null)
|
||||
public static OAuthAuthorization Create(MemberId memberId, OAuthProvider provider, string openId, string? unionId = null)
|
||||
{
|
||||
return new OAuthAuthorization
|
||||
{
|
||||
|
||||
@ -1,7 +1,9 @@
|
||||
using Fengling.Member.Domain.Aggregates.Users;
|
||||
|
||||
namespace Fengling.Member.Domain.Events.Member;
|
||||
|
||||
public record MemberRegisteredEvent(
|
||||
long MemberId,
|
||||
MemberId MemberId,
|
||||
long TenantId,
|
||||
string? PhoneNumber,
|
||||
DateTime RegisteredAt
|
||||
|
||||
@ -19,7 +19,8 @@ public class MemberEntityTypeConfiguration : IEntityTypeConfiguration<MemberEnti
|
||||
|
||||
builder.Property(m => m.Id)
|
||||
.HasColumnName("id")
|
||||
.UseIdentityColumn();
|
||||
.UseGuidVersion7ValueGenerator()
|
||||
.HasComment("会员标识");
|
||||
|
||||
builder.Property(m => m.TenantId)
|
||||
.HasColumnName("tenant_id")
|
||||
|
||||
596
src/Fengling.Member.Infrastructure/Migrations/20260209163416_ChangeMemberIdToGuid.Designer.cs
generated
Normal file
596
src/Fengling.Member.Infrastructure/Migrations/20260209163416_ChangeMemberIdToGuid.Designer.cs
generated
Normal file
@ -0,0 +1,596 @@
|
||||
// <auto-generated />
|
||||
using System;
|
||||
using Fengling.Member.Infrastructure;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace Fengling.Member.Infrastructure.Migrations
|
||||
{
|
||||
[DbContext(typeof(ApplicationDbContext))]
|
||||
[Migration("20260209163416_ChangeMemberIdToGuid")]
|
||||
partial class ChangeMemberIdToGuid
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void BuildTargetModel(ModelBuilder modelBuilder)
|
||||
{
|
||||
#pragma warning disable 612, 618
|
||||
modelBuilder
|
||||
.HasAnnotation("ProductVersion", "9.0.0")
|
||||
.HasAnnotation("Relational:MaxIdentifierLength", 63);
|
||||
|
||||
NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder);
|
||||
|
||||
modelBuilder.Entity("Fengling.Member.Domain.Aggregates.PointsModel.PointsAccount", b =>
|
||||
{
|
||||
b.Property<long>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("bigint")
|
||||
.HasColumnName("id");
|
||||
|
||||
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<long>("Id"));
|
||||
|
||||
b.Property<DateTime>("CreatedAt")
|
||||
.HasColumnType("timestamp with time zone")
|
||||
.HasColumnName("created_at");
|
||||
|
||||
b.Property<int>("FrozenPoints")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("integer")
|
||||
.HasDefaultValue(0)
|
||||
.HasColumnName("frozen_points");
|
||||
|
||||
b.Property<long>("MemberId")
|
||||
.HasColumnType("bigint")
|
||||
.HasColumnName("user_id");
|
||||
|
||||
b.Property<long>("TenantId")
|
||||
.HasColumnType("bigint")
|
||||
.HasColumnName("tenant_id");
|
||||
|
||||
b.Property<int>("TotalPoints")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("integer")
|
||||
.HasDefaultValue(0)
|
||||
.HasColumnName("points");
|
||||
|
||||
b.Property<DateTime?>("UpdatedAt")
|
||||
.HasColumnType("timestamp with time zone")
|
||||
.HasColumnName("updated_at");
|
||||
|
||||
b.Property<int>("Version")
|
||||
.IsConcurrencyToken()
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("integer")
|
||||
.HasDefaultValue(1)
|
||||
.HasColumnName("version");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("MemberId")
|
||||
.IsUnique()
|
||||
.HasDatabaseName("idx_points_account_memberid");
|
||||
|
||||
b.HasIndex("MemberId", "TenantId")
|
||||
.HasDatabaseName("idx_points_account_member_tenant");
|
||||
|
||||
b.ToTable("mka_integraldetails", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Fengling.Member.Domain.Aggregates.PointsModel.PointsTransaction", b =>
|
||||
{
|
||||
b.Property<long>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("bigint");
|
||||
|
||||
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<long>("Id"));
|
||||
|
||||
b.Property<DateTime>("CreatedAt")
|
||||
.HasColumnType("timestamp with time zone");
|
||||
|
||||
b.Property<DateTime>("ExpireAt")
|
||||
.HasColumnType("timestamp with time zone");
|
||||
|
||||
b.Property<long>("MemberId")
|
||||
.HasColumnType("bigint");
|
||||
|
||||
b.Property<int>("Points")
|
||||
.HasColumnType("integer");
|
||||
|
||||
b.Property<long>("PointsAccountId")
|
||||
.HasColumnType("bigint");
|
||||
|
||||
b.Property<string>("Remark")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<string>("SourceId")
|
||||
.IsRequired()
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<string>("TransactionType")
|
||||
.IsRequired()
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<int>("TransactionTypeCategory")
|
||||
.HasColumnType("integer");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("PointsAccountId");
|
||||
|
||||
b.ToTable("PointsTransactions");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Fengling.Member.Domain.Aggregates.PointsRuleModel.PointsRule", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.HasColumnType("uuid")
|
||||
.HasComment("规则标识");
|
||||
|
||||
b.Property<int>("BasePoints")
|
||||
.HasColumnType("integer")
|
||||
.HasComment("基础积分");
|
||||
|
||||
b.Property<int>("CalculationMode")
|
||||
.HasColumnType("integer")
|
||||
.HasComment("计算模式");
|
||||
|
||||
b.Property<string>("Code")
|
||||
.IsRequired()
|
||||
.HasMaxLength(50)
|
||||
.HasColumnType("character varying(50)")
|
||||
.HasComment("规则编码");
|
||||
|
||||
b.Property<DateTime>("CreatedAt")
|
||||
.HasColumnType("timestamp with time zone")
|
||||
.HasComment("创建时间");
|
||||
|
||||
b.Property<DateTime>("EffectiveFrom")
|
||||
.HasColumnType("timestamp with time zone")
|
||||
.HasComment("生效开始时间");
|
||||
|
||||
b.Property<DateTime?>("EffectiveTo")
|
||||
.HasColumnType("timestamp with time zone")
|
||||
.HasComment("生效结束时间");
|
||||
|
||||
b.Property<bool>("IsActive")
|
||||
.HasColumnType("boolean")
|
||||
.HasComment("是否启用");
|
||||
|
||||
b.Property<string>("Name")
|
||||
.IsRequired()
|
||||
.HasMaxLength(200)
|
||||
.HasColumnType("character varying(200)")
|
||||
.HasComment("规则名称");
|
||||
|
||||
b.Property<int>("Priority")
|
||||
.HasColumnType("integer")
|
||||
.HasComment("优先级");
|
||||
|
||||
b.Property<int>("RuleType")
|
||||
.HasColumnType("integer")
|
||||
.HasComment("规则类型");
|
||||
|
||||
b.Property<DateTime>("UpdatedAt")
|
||||
.HasColumnType("timestamp with time zone")
|
||||
.HasComment("更新时间");
|
||||
|
||||
b.Property<int>("ValidityDays")
|
||||
.HasColumnType("integer")
|
||||
.HasComment("有效期天数");
|
||||
|
||||
b.Property<decimal?>("WeightFactor")
|
||||
.HasPrecision(18, 4)
|
||||
.HasColumnType("numeric(18,4)")
|
||||
.HasComment("权重因子");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("Code")
|
||||
.IsUnique();
|
||||
|
||||
b.HasIndex("IsActive");
|
||||
|
||||
b.ToTable("PointsRules", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Fengling.Member.Domain.Aggregates.PointsRuleModel.PointsRuleCondition", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.HasColumnType("uuid")
|
||||
.HasComment("条件标识");
|
||||
|
||||
b.Property<DateTime>("CreatedAt")
|
||||
.HasColumnType("timestamp with time zone")
|
||||
.HasComment("创建时间");
|
||||
|
||||
b.Property<int>("DimensionType")
|
||||
.HasColumnType("integer")
|
||||
.HasComment("维度类型");
|
||||
|
||||
b.Property<string>("DimensionValue")
|
||||
.IsRequired()
|
||||
.HasMaxLength(200)
|
||||
.HasColumnType("character varying(200)")
|
||||
.HasComment("维度值");
|
||||
|
||||
b.Property<string>("Operator")
|
||||
.HasMaxLength(20)
|
||||
.HasColumnType("character varying(20)")
|
||||
.HasComment("操作符");
|
||||
|
||||
b.Property<Guid>("RuleId")
|
||||
.HasColumnType("uuid")
|
||||
.HasComment("关联规则标识");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("RuleId", "DimensionType");
|
||||
|
||||
b.ToTable("PointsRuleConditions", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Fengling.Member.Domain.Aggregates.Users.MemberEntity", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.HasColumnType("uuid")
|
||||
.HasColumnName("id")
|
||||
.HasComment("会员标识");
|
||||
|
||||
b.Property<DateTime>("CreatedAt")
|
||||
.HasColumnType("timestamp with time zone")
|
||||
.HasColumnName("created_at");
|
||||
|
||||
b.Property<string>("OpenId")
|
||||
.HasMaxLength(64)
|
||||
.HasColumnType("character varying(64)")
|
||||
.HasColumnName("open_id");
|
||||
|
||||
b.Property<string>("PhoneNumber")
|
||||
.HasMaxLength(20)
|
||||
.HasColumnType("character varying(20)")
|
||||
.HasColumnName("phone_number");
|
||||
|
||||
b.Property<string>("Status")
|
||||
.IsRequired()
|
||||
.HasMaxLength(20)
|
||||
.HasColumnType("character varying(20)")
|
||||
.HasColumnName("status");
|
||||
|
||||
b.Property<long>("TenantId")
|
||||
.HasColumnType("bigint")
|
||||
.HasColumnName("tenant_id");
|
||||
|
||||
b.Property<string>("UnionId")
|
||||
.HasMaxLength(64)
|
||||
.HasColumnType("character varying(64)")
|
||||
.HasColumnName("union_id");
|
||||
|
||||
b.Property<DateTime?>("UpdatedAt")
|
||||
.HasColumnType("timestamp with time zone")
|
||||
.HasColumnName("updated_at");
|
||||
|
||||
b.Property<int>("Version")
|
||||
.IsConcurrencyToken()
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("integer")
|
||||
.HasDefaultValue(1)
|
||||
.HasColumnName("version");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("OpenId")
|
||||
.HasDatabaseName("idx_member_openid");
|
||||
|
||||
b.HasIndex("TenantId")
|
||||
.HasDatabaseName("idx_member_tenantid");
|
||||
|
||||
b.HasIndex("UnionId")
|
||||
.HasDatabaseName("idx_member_unionid");
|
||||
|
||||
b.HasIndex("TenantId", "PhoneNumber")
|
||||
.HasDatabaseName("idx_member_tenant_phone");
|
||||
|
||||
b.ToTable("fls_member", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Fengling.Member.Domain.Aggregates.Users.MemberTag", b =>
|
||||
{
|
||||
b.Property<long>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("bigint")
|
||||
.HasColumnName("id");
|
||||
|
||||
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<long>("Id"));
|
||||
|
||||
b.Property<DateTime>("CreatedAt")
|
||||
.HasColumnType("timestamp with time zone")
|
||||
.HasColumnName("created_at");
|
||||
|
||||
b.Property<Guid>("MemberId")
|
||||
.HasColumnType("uuid")
|
||||
.HasColumnName("member_id");
|
||||
|
||||
b.Property<string>("TagId")
|
||||
.IsRequired()
|
||||
.HasMaxLength(50)
|
||||
.HasColumnType("character varying(50)")
|
||||
.HasColumnName("tag_id");
|
||||
|
||||
b.Property<string>("TagName")
|
||||
.HasMaxLength(100)
|
||||
.HasColumnType("character varying(100)")
|
||||
.HasColumnName("tag_name");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("TagId")
|
||||
.HasDatabaseName("idx_membertag_tagid");
|
||||
|
||||
b.HasIndex("MemberId", "TagId")
|
||||
.IsUnique()
|
||||
.HasDatabaseName("idx_membertag_member_tag");
|
||||
|
||||
b.ToTable("fls_member_tag", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Fengling.Member.Domain.Aggregates.Users.OAuthAuthorization", b =>
|
||||
{
|
||||
b.Property<long>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("bigint");
|
||||
|
||||
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<long>("Id"));
|
||||
|
||||
b.Property<string>("AccessToken")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<DateTime>("AuthorizedAt")
|
||||
.HasColumnType("timestamp with time zone");
|
||||
|
||||
b.Property<DateTime?>("LastLoginAt")
|
||||
.HasColumnType("timestamp with time zone");
|
||||
|
||||
b.Property<Guid>("MemberId")
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b.Property<string>("OpenId")
|
||||
.IsRequired()
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<int>("Provider")
|
||||
.HasColumnType("integer");
|
||||
|
||||
b.Property<string>("RefreshToken")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<DateTime?>("TokenExpiredAt")
|
||||
.HasColumnType("timestamp with time zone");
|
||||
|
||||
b.Property<string>("UnionId")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("MemberId");
|
||||
|
||||
b.ToTable("OAuthAuthorization");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Fengling.Member.Domain.Aggregates.Users.WechatAuthorization", b =>
|
||||
{
|
||||
b.Property<long>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("bigint")
|
||||
.HasColumnName("id");
|
||||
|
||||
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<long>("Id"));
|
||||
|
||||
b.Property<DateTime>("AuthorizedAt")
|
||||
.HasColumnType("timestamp with time zone")
|
||||
.HasColumnName("authorized_at");
|
||||
|
||||
b.Property<DateTime?>("LastLoginAt")
|
||||
.HasColumnType("timestamp with time zone")
|
||||
.HasColumnName("last_login_at");
|
||||
|
||||
b.Property<long>("MemberId")
|
||||
.HasColumnType("bigint")
|
||||
.HasColumnName("member_id");
|
||||
|
||||
b.Property<string>("OpenId")
|
||||
.IsRequired()
|
||||
.HasMaxLength(64)
|
||||
.HasColumnType("character varying(64)")
|
||||
.HasColumnName("open_id");
|
||||
|
||||
b.Property<string>("UnionId")
|
||||
.HasMaxLength(64)
|
||||
.HasColumnType("character varying(64)")
|
||||
.HasColumnName("union_id");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("MemberId")
|
||||
.HasDatabaseName("idx_wechat_auth_memberid");
|
||||
|
||||
b.HasIndex("OpenId")
|
||||
.IsUnique()
|
||||
.HasDatabaseName("idx_wechat_auth_openid");
|
||||
|
||||
b.HasIndex("UnionId")
|
||||
.HasDatabaseName("idx_wechat_auth_unionid");
|
||||
|
||||
b.ToTable("fls_wechat_authorization", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("NetCorePal.Extensions.DistributedTransactions.CAP.Persistence.CapLock", b =>
|
||||
{
|
||||
b.Property<string>("Key")
|
||||
.HasMaxLength(128)
|
||||
.HasColumnType("character varying(128)");
|
||||
|
||||
b.Property<string>("Instance")
|
||||
.HasMaxLength(256)
|
||||
.HasColumnType("character varying(256)");
|
||||
|
||||
b.Property<DateTime?>("LastLockTime")
|
||||
.HasColumnType("TIMESTAMP");
|
||||
|
||||
b.HasKey("Key");
|
||||
|
||||
b.ToTable("CAPLock", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("NetCorePal.Extensions.DistributedTransactions.CAP.Persistence.PublishedMessage", b =>
|
||||
{
|
||||
b.Property<long>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("bigint");
|
||||
|
||||
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<long>("Id"));
|
||||
|
||||
b.Property<DateTime>("Added")
|
||||
.HasColumnType("TIMESTAMP");
|
||||
|
||||
b.Property<string>("Content")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<DateTime?>("ExpiresAt")
|
||||
.HasColumnType("TIMESTAMP");
|
||||
|
||||
b.Property<string>("Name")
|
||||
.IsRequired()
|
||||
.HasMaxLength(200)
|
||||
.HasColumnType("character varying(200)");
|
||||
|
||||
b.Property<int?>("Retries")
|
||||
.HasColumnType("integer");
|
||||
|
||||
b.Property<string>("StatusName")
|
||||
.IsRequired()
|
||||
.HasMaxLength(40)
|
||||
.HasColumnType("character varying(40)");
|
||||
|
||||
b.Property<string>("Version")
|
||||
.HasMaxLength(20)
|
||||
.HasColumnType("character varying(20)");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex(new[] { "ExpiresAt", "StatusName" }, "IX_ExpiresAt_StatusName");
|
||||
|
||||
b.HasIndex(new[] { "Version", "ExpiresAt", "StatusName" }, "IX_Version_ExpiresAt_StatusName");
|
||||
|
||||
b.ToTable("CAPPublishedMessage", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("NetCorePal.Extensions.DistributedTransactions.CAP.Persistence.ReceivedMessage", b =>
|
||||
{
|
||||
b.Property<long>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("bigint");
|
||||
|
||||
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<long>("Id"));
|
||||
|
||||
b.Property<DateTime>("Added")
|
||||
.HasColumnType("TIMESTAMP");
|
||||
|
||||
b.Property<string>("Content")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<DateTime?>("ExpiresAt")
|
||||
.HasColumnType("TIMESTAMP");
|
||||
|
||||
b.Property<string>("Group")
|
||||
.HasMaxLength(200)
|
||||
.HasColumnType("character varying(200)");
|
||||
|
||||
b.Property<string>("Name")
|
||||
.IsRequired()
|
||||
.HasMaxLength(400)
|
||||
.HasColumnType("character varying(400)");
|
||||
|
||||
b.Property<int?>("Retries")
|
||||
.HasColumnType("integer");
|
||||
|
||||
b.Property<string>("StatusName")
|
||||
.IsRequired()
|
||||
.HasMaxLength(50)
|
||||
.HasColumnType("character varying(50)");
|
||||
|
||||
b.Property<string>("Version")
|
||||
.HasMaxLength(20)
|
||||
.HasColumnType("character varying(20)");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex(new[] { "ExpiresAt", "StatusName" }, "IX_ExpiresAt_StatusName")
|
||||
.HasDatabaseName("IX_ExpiresAt_StatusName1");
|
||||
|
||||
b.HasIndex(new[] { "Version", "ExpiresAt", "StatusName" }, "IX_Version_ExpiresAt_StatusName")
|
||||
.HasDatabaseName("IX_Version_ExpiresAt_StatusName1");
|
||||
|
||||
b.ToTable("CAPReceivedMessage", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Fengling.Member.Domain.Aggregates.PointsModel.PointsTransaction", b =>
|
||||
{
|
||||
b.HasOne("Fengling.Member.Domain.Aggregates.PointsModel.PointsAccount", null)
|
||||
.WithMany("Transactions")
|
||||
.HasForeignKey("PointsAccountId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Fengling.Member.Domain.Aggregates.PointsRuleModel.PointsRuleCondition", b =>
|
||||
{
|
||||
b.HasOne("Fengling.Member.Domain.Aggregates.PointsRuleModel.PointsRule", null)
|
||||
.WithMany("Conditions")
|
||||
.HasForeignKey("RuleId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Fengling.Member.Domain.Aggregates.Users.MemberTag", b =>
|
||||
{
|
||||
b.HasOne("Fengling.Member.Domain.Aggregates.Users.MemberEntity", null)
|
||||
.WithMany("Tags")
|
||||
.HasForeignKey("MemberId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Fengling.Member.Domain.Aggregates.Users.OAuthAuthorization", b =>
|
||||
{
|
||||
b.HasOne("Fengling.Member.Domain.Aggregates.Users.MemberEntity", null)
|
||||
.WithMany("OAuthAuthorizations")
|
||||
.HasForeignKey("MemberId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Fengling.Member.Domain.Aggregates.PointsModel.PointsAccount", b =>
|
||||
{
|
||||
b.Navigation("Transactions");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Fengling.Member.Domain.Aggregates.PointsRuleModel.PointsRule", b =>
|
||||
{
|
||||
b.Navigation("Conditions");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Fengling.Member.Domain.Aggregates.Users.MemberEntity", b =>
|
||||
{
|
||||
b.Navigation("OAuthAuthorizations");
|
||||
|
||||
b.Navigation("Tags");
|
||||
});
|
||||
#pragma warning restore 612, 618
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,72 @@
|
||||
using System;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace Fengling.Member.Infrastructure.Migrations
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class ChangeMemberIdToGuid : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.AlterColumn<Guid>(
|
||||
name: "MemberId",
|
||||
table: "OAuthAuthorization",
|
||||
type: "uuid",
|
||||
nullable: false,
|
||||
oldClrType: typeof(long),
|
||||
oldType: "bigint");
|
||||
|
||||
migrationBuilder.AlterColumn<Guid>(
|
||||
name: "member_id",
|
||||
table: "fls_member_tag",
|
||||
type: "uuid",
|
||||
nullable: false,
|
||||
oldClrType: typeof(long),
|
||||
oldType: "bigint");
|
||||
|
||||
migrationBuilder.AlterColumn<Guid>(
|
||||
name: "id",
|
||||
table: "fls_member",
|
||||
type: "uuid",
|
||||
nullable: false,
|
||||
comment: "会员标识",
|
||||
oldClrType: typeof(long),
|
||||
oldType: "bigint")
|
||||
.OldAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.AlterColumn<long>(
|
||||
name: "MemberId",
|
||||
table: "OAuthAuthorization",
|
||||
type: "bigint",
|
||||
nullable: false,
|
||||
oldClrType: typeof(Guid),
|
||||
oldType: "uuid");
|
||||
|
||||
migrationBuilder.AlterColumn<long>(
|
||||
name: "member_id",
|
||||
table: "fls_member_tag",
|
||||
type: "bigint",
|
||||
nullable: false,
|
||||
oldClrType: typeof(Guid),
|
||||
oldType: "uuid");
|
||||
|
||||
migrationBuilder.AlterColumn<long>(
|
||||
name: "id",
|
||||
table: "fls_member",
|
||||
type: "bigint",
|
||||
nullable: false,
|
||||
oldClrType: typeof(Guid),
|
||||
oldType: "uuid",
|
||||
oldComment: "会员标识")
|
||||
.Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -233,12 +233,10 @@ namespace Fengling.Member.Infrastructure.Migrations
|
||||
|
||||
modelBuilder.Entity("Fengling.Member.Domain.Aggregates.Users.MemberEntity", b =>
|
||||
{
|
||||
b.Property<long>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("bigint")
|
||||
.HasColumnName("id");
|
||||
|
||||
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<long>("Id"));
|
||||
b.Property<Guid>("Id")
|
||||
.HasColumnType("uuid")
|
||||
.HasColumnName("id")
|
||||
.HasComment("会员标识");
|
||||
|
||||
b.Property<DateTime>("CreatedAt")
|
||||
.HasColumnType("timestamp with time zone")
|
||||
@ -310,8 +308,8 @@ namespace Fengling.Member.Infrastructure.Migrations
|
||||
.HasColumnType("timestamp with time zone")
|
||||
.HasColumnName("created_at");
|
||||
|
||||
b.Property<long>("MemberId")
|
||||
.HasColumnType("bigint")
|
||||
b.Property<Guid>("MemberId")
|
||||
.HasColumnType("uuid")
|
||||
.HasColumnName("member_id");
|
||||
|
||||
b.Property<string>("TagId")
|
||||
@ -354,8 +352,8 @@ namespace Fengling.Member.Infrastructure.Migrations
|
||||
b.Property<DateTime?>("LastLoginAt")
|
||||
.HasColumnType("timestamp with time zone");
|
||||
|
||||
b.Property<long>("MemberId")
|
||||
.HasColumnType("bigint");
|
||||
b.Property<Guid>("MemberId")
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b.Property<string>("OpenId")
|
||||
.IsRequired()
|
||||
|
||||
@ -2,35 +2,51 @@ using Fengling.Member.Domain.Aggregates.Users;
|
||||
|
||||
namespace Fengling.Member.Infrastructure.Repositories;
|
||||
|
||||
public interface IMemberRepository : IRepository<Fengling.Member.Domain.Aggregates.Users.MemberEntity, long>
|
||||
public interface IMemberRepository : IRepository<Fengling.Member.Domain.Aggregates.Users.MemberEntity, MemberId>
|
||||
{
|
||||
Task<Fengling.Member.Domain.Aggregates.Users.MemberEntity?> GetByPhoneNumberAsync(long tenantId, string phoneNumber, CancellationToken cancellationToken = default);
|
||||
Task<Fengling.Member.Domain.Aggregates.Users.MemberEntity?> GetByOpenIdAsync(string openId, CancellationToken cancellationToken = default);
|
||||
Task<Fengling.Member.Domain.Aggregates.Users.MemberEntity?> GetByUnionIdAsync(string unionId, CancellationToken cancellationToken = default);
|
||||
Task<IEnumerable<Fengling.Member.Domain.Aggregates.Users.MemberEntity>> GetByTenantIdAsync(long tenantId, int page = 1, int pageSize = 20, CancellationToken cancellationToken = default);
|
||||
Task<bool> ExistsByPhoneNumberAsync(long tenantId, string phoneNumber, CancellationToken cancellationToken = default);
|
||||
Task<Fengling.Member.Domain.Aggregates.Users.MemberEntity?> GetByPhoneNumberAsync(long tenantId, string phoneNumber,
|
||||
CancellationToken cancellationToken = default);
|
||||
|
||||
Task<Fengling.Member.Domain.Aggregates.Users.MemberEntity?> GetByOpenIdAsync(string openId,
|
||||
CancellationToken cancellationToken = default);
|
||||
|
||||
Task<Fengling.Member.Domain.Aggregates.Users.MemberEntity?> GetByUnionIdAsync(string unionId,
|
||||
CancellationToken cancellationToken = default);
|
||||
|
||||
Task<IEnumerable<Fengling.Member.Domain.Aggregates.Users.MemberEntity>> GetByTenantIdAsync(long tenantId,
|
||||
int page = 1, int pageSize = 20, CancellationToken cancellationToken = default);
|
||||
|
||||
Task<bool> ExistsByPhoneNumberAsync(long tenantId, string phoneNumber,
|
||||
CancellationToken cancellationToken = default);
|
||||
|
||||
Task<bool> ExistsByOpenIdAsync(string openId, CancellationToken cancellationToken = default);
|
||||
}
|
||||
|
||||
public class MemberRepository(ApplicationDbContext context) :
|
||||
RepositoryBase<Fengling.Member.Domain.Aggregates.Users.MemberEntity, long, ApplicationDbContext>(context), IMemberRepository
|
||||
public class MemberRepository(ApplicationDbContext context)
|
||||
: RepositoryBase<Fengling.Member.Domain.Aggregates.Users.MemberEntity, MemberId, ApplicationDbContext>(context),
|
||||
IMemberRepository
|
||||
{
|
||||
public async Task<Fengling.Member.Domain.Aggregates.Users.MemberEntity?> GetByPhoneNumberAsync(long tenantId, string phoneNumber, CancellationToken cancellationToken = default)
|
||||
public async Task<Fengling.Member.Domain.Aggregates.Users.MemberEntity?> GetByPhoneNumberAsync(long tenantId,
|
||||
string phoneNumber, CancellationToken cancellationToken = default)
|
||||
{
|
||||
return await DbContext.Members.FirstOrDefaultAsync(m => m.TenantId == tenantId && m.PhoneNumber == phoneNumber, cancellationToken);
|
||||
return await DbContext.Members.FirstOrDefaultAsync(m => m.TenantId == tenantId && m.PhoneNumber == phoneNumber,
|
||||
cancellationToken);
|
||||
}
|
||||
|
||||
public async Task<Fengling.Member.Domain.Aggregates.Users.MemberEntity?> GetByOpenIdAsync(string openId, CancellationToken cancellationToken = default)
|
||||
public async Task<Fengling.Member.Domain.Aggregates.Users.MemberEntity?> GetByOpenIdAsync(string openId,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
return await DbContext.Members.FirstOrDefaultAsync(m => m.OpenId == openId, cancellationToken);
|
||||
}
|
||||
|
||||
public async Task<Fengling.Member.Domain.Aggregates.Users.MemberEntity?> GetByUnionIdAsync(string unionId, CancellationToken cancellationToken = default)
|
||||
public async Task<Fengling.Member.Domain.Aggregates.Users.MemberEntity?> GetByUnionIdAsync(string unionId,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
return await DbContext.Members.FirstOrDefaultAsync(m => m.UnionId == unionId, cancellationToken);
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<Fengling.Member.Domain.Aggregates.Users.MemberEntity>> GetByTenantIdAsync(long tenantId, int page = 1, int pageSize = 20, CancellationToken cancellationToken = default)
|
||||
public async Task<IEnumerable<Fengling.Member.Domain.Aggregates.Users.MemberEntity>> GetByTenantIdAsync(
|
||||
long tenantId, int page = 1, int pageSize = 20, CancellationToken cancellationToken = default)
|
||||
{
|
||||
return await DbContext.Members
|
||||
.Where(m => m.TenantId == tenantId)
|
||||
@ -40,9 +56,11 @@ public class MemberRepository(ApplicationDbContext context) :
|
||||
.ToListAsync(cancellationToken);
|
||||
}
|
||||
|
||||
public async Task<bool> ExistsByPhoneNumberAsync(long tenantId, string phoneNumber, CancellationToken cancellationToken = default)
|
||||
public async Task<bool> ExistsByPhoneNumberAsync(long tenantId, string phoneNumber,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
return await DbContext.Members.AnyAsync(m => m.TenantId == tenantId && m.PhoneNumber == phoneNumber, cancellationToken);
|
||||
return await DbContext.Members.AnyAsync(m => m.TenantId == tenantId && m.PhoneNumber == phoneNumber,
|
||||
cancellationToken);
|
||||
}
|
||||
|
||||
public async Task<bool> ExistsByOpenIdAsync(string openId, CancellationToken cancellationToken = default)
|
||||
|
||||
@ -2,18 +2,13 @@ using FastEndpoints;
|
||||
using MediatR;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Fengling.Member.Application.Commands.Member;
|
||||
using Fengling.Member.Domain.Aggregates.Users;
|
||||
|
||||
namespace Fengling.Member.Web.Endpoints.v1;
|
||||
|
||||
public class BindAlipayEndpoint : Endpoint<BindAlipayRequest, BindAlipayResponse>
|
||||
public class BindAlipayEndpoint(IMediator mediator)
|
||||
: Endpoint<BindAlipayRequest, BindAlipayResponse>
|
||||
{
|
||||
private readonly IMediator _mediator;
|
||||
|
||||
public BindAlipayEndpoint(IMediator mediator)
|
||||
{
|
||||
_mediator = mediator;
|
||||
}
|
||||
|
||||
public override void Configure()
|
||||
{
|
||||
Post("/api/v1/members/{MemberId}/alipay");
|
||||
@ -26,37 +21,13 @@ public class BindAlipayEndpoint : Endpoint<BindAlipayRequest, BindAlipayResponse
|
||||
|
||||
public override async Task HandleAsync(BindAlipayRequest req, CancellationToken ct)
|
||||
{
|
||||
var command = new BindAlipayCommand
|
||||
{
|
||||
MemberId = req.MemberId,
|
||||
AlipayOpenId = req.AlipayOpenId,
|
||||
AlipayUserId = req.AlipayUserId
|
||||
};
|
||||
var command = new BindAlipayCommand(req.MemberId, req.AlipayOpenId, req.AlipayUserId);
|
||||
|
||||
var result = await _mediator.Send(command, ct);
|
||||
var result = await mediator.Send(command, ct);
|
||||
|
||||
Response = new BindAlipayResponse
|
||||
{
|
||||
MemberId = result.MemberId,
|
||||
AlipayOpenId = result.AlipayOpenId,
|
||||
AlipayUserId = result.AlipayUserId,
|
||||
BoundAt = result.BoundAt
|
||||
};
|
||||
Response = new BindAlipayResponse(result.MemberId, result.AlipayOpenId, result.AlipayUserId, result.BoundAt);
|
||||
}
|
||||
}
|
||||
|
||||
public class BindAlipayRequest
|
||||
{
|
||||
[FromRoute]
|
||||
public long MemberId { get; set; }
|
||||
public string AlipayOpenId { get; set; } = string.Empty;
|
||||
public string? AlipayUserId { get; set; }
|
||||
}
|
||||
|
||||
public class BindAlipayResponse
|
||||
{
|
||||
public long MemberId { get; set; }
|
||||
public string AlipayOpenId { get; set; } = string.Empty;
|
||||
public string? AlipayUserId { get; set; }
|
||||
public DateTime BoundAt { get; set; }
|
||||
}
|
||||
public record BindAlipayRequest([FromRoute] MemberId MemberId, string AlipayOpenId, string? AlipayUserId);
|
||||
public record BindAlipayResponse(MemberId MemberId, string AlipayOpenId, string? AlipayUserId, DateTime BoundAt);
|
||||
|
||||
@ -1,18 +1,13 @@
|
||||
using FastEndpoints;
|
||||
using MediatR;
|
||||
using Fengling.Member.Application.Commands.Member;
|
||||
using Fengling.Member.Domain.Aggregates.Users;
|
||||
|
||||
namespace Fengling.Member.Web.Endpoints.v1;
|
||||
|
||||
public class RegisterMemberEndpoint : Endpoint<RegisterMemberRequest, RegisterMemberResponse>
|
||||
public class RegisterMemberEndpoint(IMediator mediator)
|
||||
: Endpoint<RegisterMemberRequest, RegisterMemberResponse>
|
||||
{
|
||||
private readonly IMediator _mediator;
|
||||
|
||||
public RegisterMemberEndpoint(IMediator mediator)
|
||||
{
|
||||
_mediator = mediator;
|
||||
}
|
||||
|
||||
public override void Configure()
|
||||
{
|
||||
Post("/api/v1/members");
|
||||
@ -26,44 +21,13 @@ public class RegisterMemberEndpoint : Endpoint<RegisterMemberRequest, RegisterMe
|
||||
|
||||
public override async Task HandleAsync(RegisterMemberRequest req, CancellationToken ct)
|
||||
{
|
||||
var command = new RegisterMemberCommand
|
||||
{
|
||||
TenantId = req.TenantId,
|
||||
PhoneNumber = req.PhoneNumber,
|
||||
OpenId = req.OpenId,
|
||||
UnionId = req.UnionId,
|
||||
Source = req.Source
|
||||
};
|
||||
var command = new RegisterMemberCommand(req.TenantId, req.PhoneNumber, req.OpenId, req.UnionId, req.Source);
|
||||
|
||||
var result = await _mediator.Send(command, ct);
|
||||
var result = await mediator.Send(command, ct);
|
||||
|
||||
Response = new RegisterMemberResponse
|
||||
{
|
||||
MemberId = result.MemberId,
|
||||
TenantId = result.TenantId,
|
||||
PhoneNumber = result.PhoneNumber,
|
||||
OpenId = result.OpenId,
|
||||
Status = result.Status.ToString(),
|
||||
RegisteredAt = result.RegisteredAt
|
||||
};
|
||||
Response = new RegisterMemberResponse(result.MemberId, result.TenantId, result.PhoneNumber, result.OpenId, result.Status.ToString(), result.RegisteredAt);
|
||||
}
|
||||
}
|
||||
|
||||
public class RegisterMemberRequest
|
||||
{
|
||||
public long TenantId { get; set; }
|
||||
public string? PhoneNumber { get; set; }
|
||||
public string? OpenId { get; set; }
|
||||
public string? UnionId { get; set; }
|
||||
public string? Source { get; set; }
|
||||
}
|
||||
|
||||
public class RegisterMemberResponse
|
||||
{
|
||||
public long MemberId { get; set; }
|
||||
public long TenantId { get; set; }
|
||||
public string? PhoneNumber { get; set; }
|
||||
public string? OpenId { get; set; }
|
||||
public string Status { get; set; } = string.Empty;
|
||||
public DateTime RegisteredAt { get; set; }
|
||||
}
|
||||
public record RegisterMemberRequest(long TenantId, string? PhoneNumber, string? OpenId, string? UnionId, string? Source);
|
||||
public record RegisterMemberResponse(MemberId MemberId, long TenantId, string? PhoneNumber, string? OpenId, string Status, DateTime RegisteredAt);
|
||||
|
||||
@ -2,18 +2,13 @@ using FastEndpoints;
|
||||
using MediatR;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Fengling.Member.Application.Commands.Member;
|
||||
using Fengling.Member.Domain.Aggregates.Users;
|
||||
|
||||
namespace Fengling.Member.Web.Endpoints.v1;
|
||||
|
||||
public class BindOAuthEndpoint : Endpoint<BindOAuthRequest, BindOAuthResponse>
|
||||
public class BindOAuthEndpoint(IMediator mediator)
|
||||
: Endpoint<BindOAuthRequest, BindOAuthResponse>
|
||||
{
|
||||
private readonly IMediator _mediator;
|
||||
|
||||
public BindOAuthEndpoint(IMediator mediator)
|
||||
{
|
||||
_mediator = mediator;
|
||||
}
|
||||
|
||||
public override void Configure()
|
||||
{
|
||||
Post("/api/v1/members/{MemberId}/oauth");
|
||||
@ -26,42 +21,13 @@ public class BindOAuthEndpoint : Endpoint<BindOAuthRequest, BindOAuthResponse>
|
||||
|
||||
public override async Task HandleAsync(BindOAuthRequest req, CancellationToken ct)
|
||||
{
|
||||
var command = new BindOAuthCommand
|
||||
{
|
||||
MemberId = req.MemberId,
|
||||
Provider = req.Provider,
|
||||
OpenId = req.OpenId,
|
||||
UnionId = req.UnionId
|
||||
};
|
||||
var command = new BindOAuthCommand(req.Provider, req.MemberId, req.OpenId, req.UnionId);
|
||||
|
||||
var result = await _mediator.Send(command, ct);
|
||||
var result = await mediator.Send(command, ct);
|
||||
|
||||
Response = new BindOAuthResponse
|
||||
{
|
||||
MemberId = result.MemberId,
|
||||
Provider = result.Provider,
|
||||
OpenId = result.OpenId,
|
||||
UnionId = result.UnionId,
|
||||
BoundAt = result.BoundAt
|
||||
};
|
||||
Response = new BindOAuthResponse(result.MemberId, result.Provider, result.OpenId, result.UnionId, result.BoundAt);
|
||||
}
|
||||
}
|
||||
|
||||
public class BindOAuthRequest
|
||||
{
|
||||
[FromRoute]
|
||||
public long MemberId { get; set; }
|
||||
[FromRoute]
|
||||
public Domain.Aggregates.Users.OAuthProvider Provider { get; set; }
|
||||
public string OpenId { get; set; } = string.Empty;
|
||||
public string? UnionId { get; set; }
|
||||
}
|
||||
|
||||
public class BindOAuthResponse
|
||||
{
|
||||
public long MemberId { get; set; }
|
||||
public Domain.Aggregates.Users.OAuthProvider Provider { get; set; }
|
||||
public string OpenId { get; set; } = string.Empty;
|
||||
public string? UnionId { get; set; }
|
||||
public DateTime BoundAt { get; set; }
|
||||
}
|
||||
public record BindOAuthRequest([FromRoute] MemberId MemberId, [FromRoute] OAuthProvider Provider, string OpenId, string? UnionId);
|
||||
public record BindOAuthResponse(MemberId MemberId, OAuthProvider Provider, string OpenId, string? UnionId, DateTime BoundAt);
|
||||
|
||||
@ -2,18 +2,13 @@ using FastEndpoints;
|
||||
using MediatR;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Fengling.Member.Application.Commands.Member;
|
||||
using Fengling.Member.Domain.Aggregates.Users;
|
||||
|
||||
namespace Fengling.Member.Web.Endpoints.v1;
|
||||
|
||||
public class BindWechatEndpoint : Endpoint<BindWechatRequest, BindWechatResponse>
|
||||
public class BindWechatEndpoint(IMediator mediator)
|
||||
: Endpoint<BindWechatRequest, BindWechatResponse>
|
||||
{
|
||||
private readonly IMediator _mediator;
|
||||
|
||||
public BindWechatEndpoint(IMediator mediator)
|
||||
{
|
||||
_mediator = mediator;
|
||||
}
|
||||
|
||||
public override void Configure()
|
||||
{
|
||||
Post("/api/v1/members/{MemberId}/wechat");
|
||||
@ -26,37 +21,13 @@ public class BindWechatEndpoint : Endpoint<BindWechatRequest, BindWechatResponse
|
||||
|
||||
public override async Task HandleAsync(BindWechatRequest req, CancellationToken ct)
|
||||
{
|
||||
var command = new BindWechatCommand
|
||||
{
|
||||
MemberId = req.MemberId,
|
||||
OpenId = req.OpenId,
|
||||
UnionId = req.UnionId
|
||||
};
|
||||
var command = new BindWechatCommand(req.MemberId, req.OpenId, req.UnionId);
|
||||
|
||||
var result = await _mediator.Send(command, ct);
|
||||
var result = await mediator.Send(command, ct);
|
||||
|
||||
Response = new BindWechatResponse
|
||||
{
|
||||
MemberId = result.MemberId,
|
||||
OpenId = result.OpenId,
|
||||
UnionId = result.UnionId,
|
||||
BoundAt = result.BoundAt
|
||||
};
|
||||
Response = new BindWechatResponse(result.MemberId, result.OpenId, result.UnionId, result.BoundAt);
|
||||
}
|
||||
}
|
||||
|
||||
public class BindWechatRequest
|
||||
{
|
||||
[FromRoute]
|
||||
public long MemberId { get; set; }
|
||||
public string OpenId { get; set; } = string.Empty;
|
||||
public string? UnionId { get; set; }
|
||||
}
|
||||
|
||||
public class BindWechatResponse
|
||||
{
|
||||
public long MemberId { get; set; }
|
||||
public string OpenId { get; set; } = string.Empty;
|
||||
public string? UnionId { get; set; }
|
||||
public DateTime BoundAt { get; set; }
|
||||
}
|
||||
public record BindWechatRequest([FromRoute] MemberId MemberId, string OpenId, string? UnionId);
|
||||
public record BindWechatResponse(MemberId MemberId, string OpenId, string? UnionId, DateTime BoundAt);
|
||||
|
||||
Loading…
Reference in New Issue
Block a user