feat: 完成尊享服务
This commit is contained in:
@@ -28,18 +28,22 @@ public class PayService : ApplicationService, IPayService
|
|||||||
private readonly ILogger<PayService> _logger;
|
private readonly ILogger<PayService> _logger;
|
||||||
private readonly ISqlSugarRepository<PayOrderAggregateRoot, Guid> _payOrderRepository;
|
private readonly ISqlSugarRepository<PayOrderAggregateRoot, Guid> _payOrderRepository;
|
||||||
private readonly IRechargeService _rechargeService;
|
private readonly IRechargeService _rechargeService;
|
||||||
|
private readonly PremiumPackageManager _premiumPackageManager;
|
||||||
|
|
||||||
public PayService(
|
public PayService(
|
||||||
AlipayManager alipayManager,
|
AlipayManager alipayManager,
|
||||||
PayManager payManager,
|
PayManager payManager,
|
||||||
ILogger<PayService> logger, ISqlSugarRepository<PayOrderAggregateRoot, Guid> payOrderRepository,
|
ILogger<PayService> logger,
|
||||||
IRechargeService rechargeService)
|
ISqlSugarRepository<PayOrderAggregateRoot, Guid> payOrderRepository,
|
||||||
|
IRechargeService rechargeService,
|
||||||
|
PremiumPackageManager premiumPackageManager)
|
||||||
{
|
{
|
||||||
_alipayManager = alipayManager;
|
_alipayManager = alipayManager;
|
||||||
_payManager = payManager;
|
_payManager = payManager;
|
||||||
_logger = logger;
|
_logger = logger;
|
||||||
_payOrderRepository = payOrderRepository;
|
_payOrderRepository = payOrderRepository;
|
||||||
_rechargeService = rechargeService;
|
_rechargeService = rechargeService;
|
||||||
|
_premiumPackageManager = premiumPackageManager;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -51,7 +55,7 @@ public class PayService : ApplicationService, IPayService
|
|||||||
[HttpPost("pay/Order")]
|
[HttpPost("pay/Order")]
|
||||||
public async Task<CreateOrderOutput> CreateOrderAsync(CreateOrderInput input)
|
public async Task<CreateOrderOutput> CreateOrderAsync(CreateOrderInput input)
|
||||||
{
|
{
|
||||||
// 1. 通过PayManager创建订单
|
// 1. 通过PayManager创建订单(内部会验证VIP资格)
|
||||||
var order = await _payManager.CreateOrderAsync(input.GoodsType);
|
var order = await _payManager.CreateOrderAsync(input.GoodsType);
|
||||||
|
|
||||||
// 2. 通过AlipayManager发起页面支付
|
// 2. 通过AlipayManager发起页面支付
|
||||||
@@ -92,7 +96,6 @@ public class PayService : ApplicationService, IPayService
|
|||||||
// 2. 验证签名
|
// 2. 验证签名
|
||||||
await _alipayManager.VerifyNotifyAsync(notifyData);
|
await _alipayManager.VerifyNotifyAsync(notifyData);
|
||||||
|
|
||||||
|
|
||||||
// 3. 记录支付通知
|
// 3. 记录支付通知
|
||||||
await _payManager.RecordPayNoticeAsync(notifyData, signStr);
|
await _payManager.RecordPayNoticeAsync(notifyData, signStr);
|
||||||
|
|
||||||
@@ -108,16 +111,40 @@ public class PayService : ApplicationService, IPayService
|
|||||||
|
|
||||||
_logger.LogInformation("订单状态更新成功,订单号:{OutTradeNo},状态:{TradeStatus}", outTradeNo, tradeStatus);
|
_logger.LogInformation("订单状态更新成功,订单号:{OutTradeNo},状态:{TradeStatus}", outTradeNo, tradeStatus);
|
||||||
|
|
||||||
//5.充值Vip
|
// 5. 根据商品类型进行不同的处理
|
||||||
await _rechargeService.RechargeVipAsync(new RechargeCreateInput
|
if (order.GoodsType.IsPremiumPackage())
|
||||||
{
|
{
|
||||||
UserId = order.UserId,
|
// 处理尊享包商品:创建尊享包记录
|
||||||
RechargeAmount = order.TotalAmount,
|
await _premiumPackageManager.CreatePremiumPackageAsync(
|
||||||
Content = order.GoodsName,
|
order.UserId,
|
||||||
Months = order.GoodsType.GetValidMonths(),
|
order.GoodsType,
|
||||||
Remark = "自助充值",
|
order.TotalAmount,
|
||||||
ContactInfo = null
|
expireMonths: null // 尊享包不设置过期时间,或者可以根据需求设置
|
||||||
});
|
);
|
||||||
|
|
||||||
|
_logger.LogInformation(
|
||||||
|
$"用户 {order.UserId} 购买尊享包成功,订单号:{outTradeNo},商品:{order.GoodsName}");
|
||||||
|
}
|
||||||
|
else if (order.GoodsType.IsVipService())
|
||||||
|
{
|
||||||
|
// 处理VIP服务商品:充值VIP
|
||||||
|
await _rechargeService.RechargeVipAsync(new RechargeCreateInput
|
||||||
|
{
|
||||||
|
UserId = order.UserId,
|
||||||
|
RechargeAmount = order.TotalAmount,
|
||||||
|
Content = order.GoodsName,
|
||||||
|
Months = order.GoodsType.GetValidMonths(),
|
||||||
|
Remark = "自助充值",
|
||||||
|
ContactInfo = null
|
||||||
|
});
|
||||||
|
|
||||||
|
_logger.LogInformation(
|
||||||
|
$"用户 {order.UserId} 充值VIP成功,订单号:{outTradeNo},月数:{order.GoodsType.GetValidMonths()}");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_logger.LogWarning($"未知的商品类型:{order.GoodsType},订单号:{outTradeNo}");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -183,4 +210,4 @@ public class PayService : ApplicationService, IPayService
|
|||||||
}
|
}
|
||||||
return TradeStatusEnum.WAIT_TRADE;
|
return TradeStatusEnum.WAIT_TRADE;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ public class PriceAttribute : Attribute
|
|||||||
{
|
{
|
||||||
public decimal Price { get; }
|
public decimal Price { get; }
|
||||||
public int ValidMonths { get; }
|
public int ValidMonths { get; }
|
||||||
|
|
||||||
public PriceAttribute(double price, int validMonths)
|
public PriceAttribute(double price, int validMonths)
|
||||||
{
|
{
|
||||||
Price = (decimal)price;
|
Price = (decimal)price;
|
||||||
@@ -26,37 +26,114 @@ public class PriceAttribute : Attribute
|
|||||||
public class DisplayNameAttribute : Attribute
|
public class DisplayNameAttribute : Attribute
|
||||||
{
|
{
|
||||||
public string DisplayName { get; }
|
public string DisplayName { get; }
|
||||||
|
|
||||||
public DisplayNameAttribute(string displayName)
|
public DisplayNameAttribute(string displayName)
|
||||||
{
|
{
|
||||||
DisplayName = displayName;
|
DisplayName = displayName;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 商品类型特性
|
||||||
|
/// 用于标识商品是VIP服务还是尊享包服务
|
||||||
|
/// </summary>
|
||||||
|
[AttributeUsage(AttributeTargets.Field)]
|
||||||
|
public class GoodsCategoryAttribute : Attribute
|
||||||
|
{
|
||||||
|
public GoodsCategoryType Category { get; }
|
||||||
|
|
||||||
|
public GoodsCategoryAttribute(GoodsCategoryType category)
|
||||||
|
{
|
||||||
|
Category = category;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 商品类别类型
|
||||||
|
/// </summary>
|
||||||
|
public enum GoodsCategoryType
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// VIP服务
|
||||||
|
/// </summary>
|
||||||
|
VipService = 1,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 尊享包服务
|
||||||
|
/// </summary>
|
||||||
|
PremiumPackage = 2
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Token数量特性
|
||||||
|
/// 用于标识尊享包的token数量
|
||||||
|
/// </summary>
|
||||||
|
[AttributeUsage(AttributeTargets.Field)]
|
||||||
|
public class TokenAmountAttribute : Attribute
|
||||||
|
{
|
||||||
|
public long TokenAmount { get; }
|
||||||
|
|
||||||
|
public TokenAmountAttribute(long tokenAmount)
|
||||||
|
{
|
||||||
|
TokenAmount = tokenAmount;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 商品枚举
|
/// 商品枚举
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public enum GoodsTypeEnum
|
public enum GoodsTypeEnum
|
||||||
{
|
{
|
||||||
|
// VIP服务
|
||||||
[Price(29.9, 1)]
|
[Price(29.9, 1)]
|
||||||
[DisplayName("YiXinVip 1 month")]
|
[DisplayName("YiXinVip 1 month")]
|
||||||
|
[GoodsCategory(GoodsCategoryType.VipService)]
|
||||||
YiXinVip1 = 1,
|
YiXinVip1 = 1,
|
||||||
|
|
||||||
[Price(83.7, 3)]
|
[Price(83.7, 3)]
|
||||||
[DisplayName("YiXinVip 3 month")]
|
[DisplayName("YiXinVip 3 month")]
|
||||||
|
[GoodsCategory(GoodsCategoryType.VipService)]
|
||||||
YiXinVip3 = 3,
|
YiXinVip3 = 3,
|
||||||
|
|
||||||
[Price(155.4, 6)]
|
[Price(155.4, 6)]
|
||||||
[DisplayName("YiXinVip 6 month")]
|
[DisplayName("YiXinVip 6 month")]
|
||||||
|
[GoodsCategory(GoodsCategoryType.VipService)]
|
||||||
YiXinVip6 = 6,
|
YiXinVip6 = 6,
|
||||||
|
|
||||||
[Price(183.2, 8)]
|
[Price(183.2, 8)]
|
||||||
[DisplayName("YiXinVip 8 month")]
|
[DisplayName("YiXinVip 8 month")]
|
||||||
YiXinVip8 = 8
|
[GoodsCategory(GoodsCategoryType.VipService)]
|
||||||
|
YiXinVip8 = 8,
|
||||||
|
|
||||||
|
// 尊享包服务 - 需要VIP资格才能购买
|
||||||
|
[Price(49.9, 0)]
|
||||||
|
[DisplayName("Premium Package 5M Tokens")]
|
||||||
|
[GoodsCategory(GoodsCategoryType.PremiumPackage)]
|
||||||
|
[TokenAmount(5_000_000)]
|
||||||
|
PremiumPackage5M = 101,
|
||||||
|
|
||||||
|
[Price(99.9, 0)]
|
||||||
|
[DisplayName("Premium Package 10M Tokens")]
|
||||||
|
[GoodsCategory(GoodsCategoryType.PremiumPackage)]
|
||||||
|
[TokenAmount(10_000_000)]
|
||||||
|
PremiumPackage10M = 102,
|
||||||
|
|
||||||
|
[Price(199.9, 0)]
|
||||||
|
[DisplayName("Premium Package 25M Tokens")]
|
||||||
|
[GoodsCategory(GoodsCategoryType.PremiumPackage)]
|
||||||
|
[TokenAmount(25_000_000)]
|
||||||
|
PremiumPackage25M = 103,
|
||||||
|
|
||||||
|
[Price(399.9, 0)]
|
||||||
|
[DisplayName("Premium Package 50M Tokens")]
|
||||||
|
[GoodsCategory(GoodsCategoryType.PremiumPackage)]
|
||||||
|
[TokenAmount(50_000_000)]
|
||||||
|
PremiumPackage50M = 104
|
||||||
|
|
||||||
// [Price(197.1, 9)]
|
// [Price(197.1, 9)]
|
||||||
// [DisplayName("YiXinVip 9 month")]
|
// [DisplayName("YiXinVip 9 month")]
|
||||||
// YiXinVip9 = 9
|
// YiXinVip9 = 9
|
||||||
|
|
||||||
// [Price(0.01, 1)]
|
// [Price(0.01, 1)]
|
||||||
// [DisplayName("YiXinVip Test")]
|
// [DisplayName("YiXinVip Test")]
|
||||||
// YiXinVipTest = 0,
|
// YiXinVipTest = 0,
|
||||||
@@ -91,7 +168,7 @@ public static class GoodsTypeEnumExtensions
|
|||||||
var priceAttribute = fieldInfo?.GetCustomAttribute<PriceAttribute>();
|
var priceAttribute = fieldInfo?.GetCustomAttribute<PriceAttribute>();
|
||||||
return priceAttribute?.Price ?? 0m;
|
return priceAttribute?.Price ?? 0m;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 获取商品价格描述
|
/// 获取商品价格描述
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -102,7 +179,7 @@ public static class GoodsTypeEnumExtensions
|
|||||||
var price = goodsType.GetTotalAmount();
|
var price = goodsType.GetTotalAmount();
|
||||||
return $"¥{price:F1}";
|
return $"¥{price:F1}";
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 获取商品名称
|
/// 获取商品名称
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -114,7 +191,7 @@ public static class GoodsTypeEnumExtensions
|
|||||||
var displayNameAttribute = fieldInfo?.GetCustomAttribute<DisplayNameAttribute>();
|
var displayNameAttribute = fieldInfo?.GetCustomAttribute<DisplayNameAttribute>();
|
||||||
return displayNameAttribute?.DisplayName ?? goodsType.ToString();
|
return displayNameAttribute?.DisplayName ?? goodsType.ToString();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 获取商品有效月份
|
/// 获取商品有效月份
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -126,7 +203,7 @@ public static class GoodsTypeEnumExtensions
|
|||||||
var priceAttribute = fieldInfo?.GetCustomAttribute<PriceAttribute>();
|
var priceAttribute = fieldInfo?.GetCustomAttribute<PriceAttribute>();
|
||||||
return priceAttribute?.ValidMonths ?? 1;
|
return priceAttribute?.ValidMonths ?? 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 获取商品月均价格
|
/// 获取商品月均价格
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -138,4 +215,48 @@ public static class GoodsTypeEnumExtensions
|
|||||||
var validMonths = goodsType.GetValidMonths();
|
var validMonths = goodsType.GetValidMonths();
|
||||||
return validMonths > 0 ? totalPrice / validMonths : 0m;
|
return validMonths > 0 ? totalPrice / validMonths : 0m;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 获取商品类别
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="goodsType">商品类型</param>
|
||||||
|
/// <returns>商品类别</returns>
|
||||||
|
public static GoodsCategoryType GetGoodsCategory(this GoodsTypeEnum goodsType)
|
||||||
|
{
|
||||||
|
var fieldInfo = goodsType.GetType().GetField(goodsType.ToString());
|
||||||
|
var categoryAttribute = fieldInfo?.GetCustomAttribute<GoodsCategoryAttribute>();
|
||||||
|
return categoryAttribute?.Category ?? GoodsCategoryType.VipService;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 是否为尊享包商品
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="goodsType">商品类型</param>
|
||||||
|
/// <returns>是否为尊享包</returns>
|
||||||
|
public static bool IsPremiumPackage(this GoodsTypeEnum goodsType)
|
||||||
|
{
|
||||||
|
return goodsType.GetGoodsCategory() == GoodsCategoryType.PremiumPackage;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 是否为VIP服务商品
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="goodsType">商品类型</param>
|
||||||
|
/// <returns>是否为VIP服务</returns>
|
||||||
|
public static bool IsVipService(this GoodsTypeEnum goodsType)
|
||||||
|
{
|
||||||
|
return goodsType.GetGoodsCategory() == GoodsCategoryType.VipService;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 获取尊享包Token数量
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="goodsType">商品类型</param>
|
||||||
|
/// <returns>Token数量</returns>
|
||||||
|
public static long GetTokenAmount(this GoodsTypeEnum goodsType)
|
||||||
|
{
|
||||||
|
var fieldInfo = goodsType.GetType().GetField(goodsType.ToString());
|
||||||
|
var tokenAttribute = fieldInfo?.GetCustomAttribute<TokenAmountAttribute>();
|
||||||
|
return tokenAttribute?.TokenAmount ?? 0;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,7 +22,6 @@ public class AnthropicChatCompletionsService(IHttpClientFactory httpClientFactor
|
|||||||
{
|
{
|
||||||
options.Endpoint = "https://api.anthropic.com/";
|
options.Endpoint = "https://api.anthropic.com/";
|
||||||
}
|
}
|
||||||
|
|
||||||
var client = httpClientFactory.CreateClient();
|
var client = httpClientFactory.CreateClient();
|
||||||
|
|
||||||
var headers = new Dictionary<string, string>
|
var headers = new Dictionary<string, string>
|
||||||
@@ -77,7 +76,7 @@ public class AnthropicChatCompletionsService(IHttpClientFactory httpClientFactor
|
|||||||
|
|
||||||
throw new Exception("OpenAI对话异常" + response.StatusCode.ToString());
|
throw new Exception("OpenAI对话异常" + response.StatusCode.ToString());
|
||||||
}
|
}
|
||||||
|
|
||||||
var value =
|
var value =
|
||||||
await response.Content.ReadFromJsonAsync<AnthropicChatCompletionDto>(ThorJsonSerializer.DefaultOptions,
|
await response.Content.ReadFromJsonAsync<AnthropicChatCompletionDto>(ThorJsonSerializer.DefaultOptions,
|
||||||
cancellationToken: cancellationToken);
|
cancellationToken: cancellationToken);
|
||||||
@@ -95,7 +94,7 @@ public class AnthropicChatCompletionsService(IHttpClientFactory httpClientFactor
|
|||||||
{
|
{
|
||||||
options.Endpoint = "https://api.anthropic.com/";
|
options.Endpoint = "https://api.anthropic.com/";
|
||||||
}
|
}
|
||||||
|
|
||||||
var client = httpClientFactory.CreateClient();
|
var client = httpClientFactory.CreateClient();
|
||||||
|
|
||||||
var headers = new Dictionary<string, string>
|
var headers = new Dictionary<string, string>
|
||||||
|
|||||||
@@ -0,0 +1,147 @@
|
|||||||
|
using SqlSugar;
|
||||||
|
using Volo.Abp.Domain.Entities.Auditing;
|
||||||
|
|
||||||
|
namespace Yi.Framework.AiHub.Domain.Entities;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 尊享包聚合根
|
||||||
|
/// 用于给VIP扩展额外购买尊享token包
|
||||||
|
/// </summary>
|
||||||
|
[SugarTable("Ai_PremiumPackage")]
|
||||||
|
[SugarIndex($"index_{nameof(UserId)}", nameof(UserId), OrderByType.Asc)]
|
||||||
|
public class PremiumPackageAggregateRoot : FullAuditedAggregateRoot<Guid>
|
||||||
|
{
|
||||||
|
public PremiumPackageAggregateRoot()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public PremiumPackageAggregateRoot(Guid userId, long totalTokens, string packageName)
|
||||||
|
{
|
||||||
|
UserId = userId;
|
||||||
|
TotalTokens = totalTokens;
|
||||||
|
RemainingTokens = totalTokens;
|
||||||
|
PackageName = packageName;
|
||||||
|
IsActive = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 用户ID
|
||||||
|
/// </summary>
|
||||||
|
public Guid UserId { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 包名称
|
||||||
|
/// </summary>
|
||||||
|
public string PackageName { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 总用量(总token数)
|
||||||
|
/// </summary>
|
||||||
|
public long TotalTokens { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 剩余用量(剩余token数)
|
||||||
|
/// </summary>
|
||||||
|
public long RemainingTokens { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 已使用token数
|
||||||
|
/// </summary>
|
||||||
|
public long UsedTokens { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 到期时间
|
||||||
|
/// </summary>
|
||||||
|
public DateTime? ExpireDateTime { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 是否激活
|
||||||
|
/// </summary>
|
||||||
|
public bool IsActive { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 购买金额
|
||||||
|
/// </summary>
|
||||||
|
public decimal PurchaseAmount { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 备注
|
||||||
|
/// </summary>
|
||||||
|
public string? Remark { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 消耗token
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="tokenCount">消耗的token数量</param>
|
||||||
|
/// <returns>是否消耗成功</returns>
|
||||||
|
public bool ConsumeTokens(long tokenCount)
|
||||||
|
{
|
||||||
|
if (RemainingTokens < tokenCount)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!IsActive)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ExpireDateTime.HasValue && ExpireDateTime.Value < DateTime.Now)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
RemainingTokens -= tokenCount;
|
||||||
|
UsedTokens += tokenCount;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 检查是否可用
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>是否可用</returns>
|
||||||
|
public bool IsAvailable()
|
||||||
|
{
|
||||||
|
if (!IsActive)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (RemainingTokens <= 0)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ExpireDateTime.HasValue && ExpireDateTime.Value < DateTime.Now)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 停用尊享包
|
||||||
|
/// </summary>
|
||||||
|
public void Deactivate()
|
||||||
|
{
|
||||||
|
IsActive = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 激活尊享包
|
||||||
|
/// </summary>
|
||||||
|
public void Activate()
|
||||||
|
{
|
||||||
|
IsActive = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 设置到期时间
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="expireDateTime">到期时间</param>
|
||||||
|
public void SetExpireDateTime(DateTime expireDateTime)
|
||||||
|
{
|
||||||
|
ExpireDateTime = expireDateTime;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -560,29 +560,30 @@ public class AiGateWayManager : DomainService
|
|||||||
{
|
{
|
||||||
_logger.LogError(e, $"Ai对话异常");
|
_logger.LogError(e, $"Ai对话异常");
|
||||||
var errorContent = $"对话Ai异常,异常信息:\n当前Ai模型:{request.Model}\n异常信息:{e.Message}\n异常堆栈:{e}";
|
var errorContent = $"对话Ai异常,异常信息:\n当前Ai模型:{request.Model}\n异常信息:{e.Message}\n异常堆栈:{e}";
|
||||||
var model = new AnthropicStreamDto
|
throw new UserFriendlyException(errorContent);
|
||||||
{
|
// var model = new AnthropicStreamDto
|
||||||
Message = new AnthropicChatCompletionDto
|
// {
|
||||||
{
|
// Message = new AnthropicChatCompletionDto
|
||||||
content =
|
// {
|
||||||
[
|
// content =
|
||||||
new AnthropicChatCompletionDtoContent
|
// [
|
||||||
{
|
// new AnthropicChatCompletionDtoContent
|
||||||
text = errorContent,
|
// {
|
||||||
}
|
// text = errorContent,
|
||||||
],
|
// }
|
||||||
},
|
// ],
|
||||||
Error = new AnthropicStreamErrorDto
|
// },
|
||||||
{
|
// Error = new AnthropicStreamErrorDto
|
||||||
Type = null,
|
// {
|
||||||
Message = errorContent
|
// Type = null,
|
||||||
}
|
// Message = errorContent
|
||||||
};
|
// }
|
||||||
var message = JsonConvert.SerializeObject(model, new JsonSerializerSettings
|
// };
|
||||||
{
|
// var message = JsonConvert.SerializeObject(model, new JsonSerializerSettings
|
||||||
ContractResolver = new CamelCasePropertyNamesContractResolver()
|
// {
|
||||||
});
|
// ContractResolver = new CamelCasePropertyNamesContractResolver()
|
||||||
await response.WriteAsJsonAsync(message, ThorJsonSerializer.DefaultOptions);
|
// });
|
||||||
|
// await response.WriteAsJsonAsync(message, ThorJsonSerializer.DefaultOptions);
|
||||||
}
|
}
|
||||||
|
|
||||||
await _aiMessageManager.CreateUserMessageAsync(userId, sessionId,
|
await _aiMessageManager.CreateUserMessageAsync(userId, sessionId,
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ using Volo.Abp.Users;
|
|||||||
using Yi.Framework.AiHub.Domain.Entities.Pay;
|
using Yi.Framework.AiHub.Domain.Entities.Pay;
|
||||||
using Yi.Framework.AiHub.Domain.Shared.Enums;
|
using Yi.Framework.AiHub.Domain.Shared.Enums;
|
||||||
using Yi.Framework.SqlSugarCore.Abstractions;
|
using Yi.Framework.SqlSugarCore.Abstractions;
|
||||||
|
using Yi.Framework.AiHub.Domain.Extensions;
|
||||||
|
|
||||||
namespace Yi.Framework.AiHub.Domain.Managers;
|
namespace Yi.Framework.AiHub.Domain.Managers;
|
||||||
|
|
||||||
@@ -38,6 +39,15 @@ public class PayManager : DomainService
|
|||||||
throw new UserFriendlyException("用户未登录");
|
throw new UserFriendlyException("用户未登录");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 如果是尊享包商品,需要验证用户是否为VIP
|
||||||
|
if (goodsType.IsPremiumPackage())
|
||||||
|
{
|
||||||
|
if (!_currentUser.IsAiVip())
|
||||||
|
{
|
||||||
|
throw new UserFriendlyException("购买尊享包需要VIP资格,请先开通VIP");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// 生成订单号
|
// 生成订单号
|
||||||
var outTradeNo = GenerateOutTradeNo();
|
var outTradeNo = GenerateOutTradeNo();
|
||||||
|
|
||||||
@@ -135,4 +145,4 @@ public class PayManager : DomainService
|
|||||||
}
|
}
|
||||||
return TradeStatusEnum.WAIT_TRADE;
|
return TradeStatusEnum.WAIT_TRADE;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,184 @@
|
|||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
using Volo.Abp.Domain.Services;
|
||||||
|
using Yi.Framework.AiHub.Domain.Entities;
|
||||||
|
using Yi.Framework.AiHub.Domain.Shared.Enums;
|
||||||
|
using Yi.Framework.SqlSugarCore.Abstractions;
|
||||||
|
|
||||||
|
namespace Yi.Framework.AiHub.Domain.Managers;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 尊享包管理器
|
||||||
|
/// </summary>
|
||||||
|
public class PremiumPackageManager : DomainService
|
||||||
|
{
|
||||||
|
private readonly ISqlSugarRepository<PremiumPackageAggregateRoot, Guid> _premiumPackageRepository;
|
||||||
|
private readonly ILogger<PremiumPackageManager> _logger;
|
||||||
|
|
||||||
|
public PremiumPackageManager(
|
||||||
|
ISqlSugarRepository<PremiumPackageAggregateRoot, Guid> premiumPackageRepository,
|
||||||
|
ILogger<PremiumPackageManager> logger)
|
||||||
|
{
|
||||||
|
_premiumPackageRepository = premiumPackageRepository;
|
||||||
|
_logger = logger;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 为用户创建尊享包
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="userId">用户ID</param>
|
||||||
|
/// <param name="goodsType">商品类型</param>
|
||||||
|
/// <param name="totalAmount">支付金额</param>
|
||||||
|
/// <param name="expireMonths">过期月数,0或null表示永久</param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public async Task<PremiumPackageAggregateRoot> CreatePremiumPackageAsync(
|
||||||
|
Guid userId,
|
||||||
|
GoodsTypeEnum goodsType,
|
||||||
|
decimal totalAmount,
|
||||||
|
int? expireMonths = null)
|
||||||
|
{
|
||||||
|
if (!goodsType.IsPremiumPackage())
|
||||||
|
{
|
||||||
|
throw new UserFriendlyException($"商品类型 {goodsType} 不是尊享包商品");
|
||||||
|
}
|
||||||
|
|
||||||
|
var tokenAmount = goodsType.GetTokenAmount();
|
||||||
|
var packageName = goodsType.GetDisplayName();
|
||||||
|
|
||||||
|
var premiumPackage = new PremiumPackageAggregateRoot(userId, tokenAmount, packageName)
|
||||||
|
{
|
||||||
|
PurchaseAmount = totalAmount
|
||||||
|
};
|
||||||
|
|
||||||
|
// 设置到期时间
|
||||||
|
if (expireMonths.HasValue && expireMonths.Value > 0)
|
||||||
|
{
|
||||||
|
premiumPackage.SetExpireDateTime(DateTime.Now.AddMonths(expireMonths.Value));
|
||||||
|
}
|
||||||
|
|
||||||
|
await _premiumPackageRepository.InsertAsync(premiumPackage);
|
||||||
|
|
||||||
|
_logger.LogInformation(
|
||||||
|
$"用户 {userId} 购买尊享包成功: {packageName}, Token数量: {tokenAmount}, 金额: {totalAmount}");
|
||||||
|
|
||||||
|
return premiumPackage;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 消耗用户尊享包的Token
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="userId">用户ID</param>
|
||||||
|
/// <param name="tokenCount">需要消耗的Token数量</param>
|
||||||
|
/// <returns>是否消耗成功</returns>
|
||||||
|
public async Task<bool> ConsumeTokensAsync(Guid userId, long tokenCount)
|
||||||
|
{
|
||||||
|
// 获取用户所有可用的尊享包,按剩余token升序排列(优先消耗快用完的)
|
||||||
|
var availablePackages = await _premiumPackageRepository._DbQueryable
|
||||||
|
.Where(x => x.UserId == userId && x.IsActive && x.RemainingTokens > 0)
|
||||||
|
.OrderBy(x => x.RemainingTokens)
|
||||||
|
.ToListAsync();
|
||||||
|
|
||||||
|
if (!availablePackages.Any())
|
||||||
|
{
|
||||||
|
_logger.LogWarning($"用户 {userId} 没有可用的尊享包");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 过滤掉已过期的包
|
||||||
|
var validPackages = availablePackages
|
||||||
|
.Where(p => p.IsAvailable())
|
||||||
|
.ToList();
|
||||||
|
|
||||||
|
if (!validPackages.Any())
|
||||||
|
{
|
||||||
|
_logger.LogWarning($"用户 {userId} 的尊享包已全部过期");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 计算总可用Token
|
||||||
|
var totalAvailableTokens = validPackages.Sum(p => p.RemainingTokens);
|
||||||
|
if (totalAvailableTokens < tokenCount)
|
||||||
|
{
|
||||||
|
_logger.LogWarning(
|
||||||
|
$"用户 {userId} 尊享包Token不足,需要: {tokenCount}, 可用: {totalAvailableTokens}");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 从可用的包中逐个扣除Token
|
||||||
|
var remainingToConsume = tokenCount;
|
||||||
|
foreach (var package in validPackages)
|
||||||
|
{
|
||||||
|
if (remainingToConsume <= 0)
|
||||||
|
break;
|
||||||
|
|
||||||
|
var toConsume = Math.Min(remainingToConsume, package.RemainingTokens);
|
||||||
|
if (package.ConsumeTokens(toConsume))
|
||||||
|
{
|
||||||
|
await _premiumPackageRepository.UpdateAsync(package);
|
||||||
|
remainingToConsume -= toConsume;
|
||||||
|
|
||||||
|
_logger.LogInformation(
|
||||||
|
$"用户 {userId} 从尊享包 {package.Id} 消耗 {toConsume} tokens, 剩余: {package.RemainingTokens}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return remainingToConsume == 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 获取用户可用的尊享包总Token数
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="userId">用户ID</param>
|
||||||
|
/// <returns>可用Token总数</returns>
|
||||||
|
public async Task<long> GetAvailableTokensAsync(Guid userId)
|
||||||
|
{
|
||||||
|
var packages = await _premiumPackageRepository._DbQueryable
|
||||||
|
.Where(x => x.UserId == userId && x.IsActive && x.RemainingTokens > 0)
|
||||||
|
.ToListAsync();
|
||||||
|
|
||||||
|
return packages
|
||||||
|
.Where(p => p.IsAvailable())
|
||||||
|
.Sum(p => p.RemainingTokens);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 获取用户的所有尊享包
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="userId">用户ID</param>
|
||||||
|
/// <returns>尊享包列表</returns>
|
||||||
|
public async Task<List<PremiumPackageAggregateRoot>> GetUserPremiumPackagesAsync(Guid userId)
|
||||||
|
{
|
||||||
|
return await _premiumPackageRepository._DbQueryable
|
||||||
|
.Where(x => x.UserId == userId)
|
||||||
|
.OrderByDescending(x => x.CreationTime)
|
||||||
|
.ToListAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 停用过期的尊享包
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>停用的包数量</returns>
|
||||||
|
public async Task<int> DeactivateExpiredPackagesAsync()
|
||||||
|
{
|
||||||
|
_logger.LogInformation("开始执行尊享包过期自动停用任务");
|
||||||
|
|
||||||
|
var now = DateTime.Now;
|
||||||
|
var expiredPackages = await _premiumPackageRepository._DbQueryable
|
||||||
|
.Where(x => x.IsActive && x.ExpireDateTime.HasValue && x.ExpireDateTime.Value < now)
|
||||||
|
.ToListAsync();
|
||||||
|
|
||||||
|
if (!expiredPackages.Any())
|
||||||
|
{
|
||||||
|
_logger.LogInformation("没有找到过期的尊享包");
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var package in expiredPackages)
|
||||||
|
{
|
||||||
|
package.Deactivate();
|
||||||
|
await _premiumPackageRepository.UpdateAsync(package);
|
||||||
|
}
|
||||||
|
|
||||||
|
_logger.LogInformation($"成功停用 {expiredPackages.Count} 个过期的尊享包");
|
||||||
|
return expiredPackages.Count;
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user