using Store.Users.Services; using MAX.Framework.Validation; using Store.Users.Dtos; 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; /// 商铺续费,续费时长单位为月。 [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, string? OperatorName = null) : IRequest, IValidatableRequest, IAccountConsumptionRequest, IShopOwnershipRequest { // 由消费管道建立,仅在当前事务执行期间有效。 AccountConsumptionContext? IAccountConsumptionRequest.ConsumptionContext { get; set; } ShopEntity? IShopOwnershipRequest.OwnedShop { get; set; } void IValidatableRequest.Validate() { RequestValidator.Validate(new UserNameRequest { UserName = UserName?.Trim()! }); UserInputChecks.Length(UserName.Trim(), 20, "用户名"); UserInputChecks.Length(OperatorName, 20, "操作者"); var request = new ShopRenewRequest { ShopId = ShopId, RenewDuration = RenewDuration, Remark = Remark }; RequestValidator.Validate(request); UserInputChecks.Renew(request); } 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 { public async Task 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 operatorName = request.OperatorName ?? userName; var actor = await orm.Select().Where(u => u.UserName == operatorName).FirstAsync(cancellationToken) ?? throw new AppException("操作者账户不存在"); if (!UserAccessPolicy.CanManage(actor, user) && actor.Id != user.Id) throw new AppException("无权为该账户续费", 403); 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().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().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 = UserAccessPolicy.IsAdmin(actor) ? "管理员" : "用户", OperatorName = actor.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() .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; } }