Files
StoreWeb/Modules/Store.Users/Commands/ShopRenewCommand.cs
T

147 lines
6.3 KiB
C#
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
using System.ComponentModel.DataAnnotations;
using YourNamespace;
using MAX.Framework.Mediation;
using MAX.Core.Mediation;
using Store.Users.Behaviors;
using MAX.Core.Helpers;
using MAX.Framework.Commons;
using MediatR;
using Store.Users.Contracts.Dtos;
using Store.Users.Entities;
namespace Store.Users.Commands;
/// <summary>商铺续费,续费时长单位为月。</summary>
[MediatRBehavior(typeof(ValidationBehavior<,>))]
[MediatRBehavior(typeof(TransactionBehavior<,>))]
[MediatRBehavior(typeof(AccountConsumptionBehavior<,>))]
[MediatRBehavior(typeof(ShopOwnershipBehavior<,>))]
public sealed record ShopRenewCommand(string UserName, string ShopId, int RenewDuration, string? Remark = null)
: IRequest<ShopRenewResponse>, IValidatableRequest, IAccountConsumptionRequest, IShopOwnershipRequest
{
// 由消费管道建立,仅在当前事务执行期间有效。
AccountConsumptionContext? IAccountConsumptionRequest.ConsumptionContext { get; set; }
ShopEntity? IShopOwnershipRequest.OwnedShop { get; set; }
void IValidatableRequest.Validate()
{
if (string.IsNullOrWhiteSpace(UserName) || UserName.Trim().Length > 20)
throw new ValidationException("用户名不能为空且长度不能超过20个字符");
if (string.IsNullOrWhiteSpace(ShopId) || ShopId.Trim().Length > 50)
throw new ValidationException("店铺ID不能为空且长度不能超过50个字符");
if (RenewDuration is < 1 or > 120)
throw new ValidationException("续费时长必须为1-120个月");
if (Remark?.Trim().Length > 255)
throw new ValidationException("备注长度不能超过255个字符");
}
ConsumptionQuote IAccountConsumptionRequest.CalculateConsumption(UserEntity user)
{
if (RenewDuration is < 1 or > 120)
throw new AppException("续费时长必须为1-120个月");
int price = Math.Max(30, user.ShopPointsPerMonth);
return new ConsumptionQuote((decimal)price * RenewDuration, price);
}
}
internal sealed class ShopRenewCommandHandler(UnitOfWorkExecutionContext executionContext)
: IRequestHandler<ShopRenewCommand, ShopRenewResponse>
{
public async Task<ShopRenewResponse> Handle(ShopRenewCommand request, CancellationToken cancellationToken)
{
ArgumentNullException.ThrowIfNull(request);
cancellationToken.ThrowIfCancellationRequested();
var userName = request.UserName.Trim();
var shopId = request.ShopId.Trim();
var remark = string.IsNullOrWhiteSpace(request.Remark) ? null : request.Remark.Trim();
var context = ((IAccountConsumptionRequest)request).ConsumptionContext
?? throw new InvalidOperationException("续费命令必须通过账户消费管道执行");
var orm = executionContext.Orm;
var user = context.User;
var shop = ((IShopOwnershipRequest)request).OwnedShop
?? throw new InvalidOperationException("续费命令必须通过商铺归属管道执行");
decimal pointsPerMonth = context.Quote.UnitPrice;
decimal cost = context.Quote.Amount;
decimal newPoints = user.Points - cost;
var now = DateTime.Now;
var currentExpire = await orm.Select<ShopExpireEntity>().Where(a => a.ShopId == shopId)
.FirstAsync(cancellationToken);
var expireBase = now;
if (currentExpire is { ExpireTime: > 0 })
{
var currentTime = DateHelper.ConvertFromUnixTimestampInSeconds(currentExpire.ExpireTime);
if (currentTime > expireBase)
expireBase = currentTime;
}
int newExpireTime = CalculateNewExpireTime(expireBase, request.RenewDuration);
int affected = await orm.Update<UserEntity>().Set(a => a.Points, newPoints)
.Where(a => a.Id == user.Id && a.Points == user.Points)
.ExecuteAffrowsAsync(cancellationToken);
if (affected != 1)
throw new AppException("账户已发生改变,请刷新账户后重新操作");
for (int i = 0; i < request.RenewDuration; i++)
{
var record = new UserPointsRecordEntity
{
OptionType = "续费",
OperatorType = "用户",
OperatorName = userName,
UserName = userName,
UserNick = user.NickName,
DeltaPoints = pointsPerMonth,
NewPoints = user.Points - (decimal)pointsPerMonth * (i + 1),
ShopId = shop.ShopId,
ShopAccount = shop.ShopAccount,
Remark = remark,
AddTime = now.AddSeconds(i)
};
if (await orm.Insert(record).ExecuteAffrowsAsync(cancellationToken) != 1)
throw new AppException("新增操作记录失败,请稍候重试");
}
if (currentExpire == null)
{
affected = await orm.Insert(new ShopExpireEntity
{
ShopId = shopId,
ExpireTime = newExpireTime,
AddTime = now,
MdyTime = now
}).ExecuteAffrowsAsync(cancellationToken);
}
else
{
affected = await orm.Update<ShopExpireEntity>()
.Set(a => a.ExpireTime, newExpireTime).Set(a => a.MdyTime, now)
.Where(a => a.Id == currentExpire.Id && a.ExpireTime == currentExpire.ExpireTime)
.ExecuteAffrowsAsync(cancellationToken);
}
if (affected != 1)
throw new AppException("更新商铺过期时间失败,请刷新店铺后重新操作");
cancellationToken.ThrowIfCancellationRequested();
return new ShopRenewResponse { ShopId = shopId, UserPoints = newPoints, ExpireTime‌ = newExpireTime };
}
private static int CalculateNewExpireTime(DateTime expireBase, int duration)
{
DateTime newExpireDate;
try
{
newExpireDate = expireBase.AddMonths(duration);
}
catch (ArgumentOutOfRangeException)
{
throw new AppException("续费时长超出允许范围");
}
long timestamp = DateHelper.GetUnixTimestampInSeconds(newExpireDate);
if (timestamp > int.MaxValue || timestamp < 0)
throw new AppException("续费时间超出系统支持范围");
return (int)timestamp;
}
}