diff --git a/Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Application.Contracts/Dtos/AiUserRoleMenuDto.cs b/Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Application.Contracts/Dtos/AiUserRoleMenuDto.cs
index d3297f59..fa054cca 100644
--- a/Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Application.Contracts/Dtos/AiUserRoleMenuDto.cs
+++ b/Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Application.Contracts/Dtos/AiUserRoleMenuDto.cs
@@ -8,4 +8,29 @@ public class AiUserRoleMenuDto:UserRoleMenuDto
/// 是否绑定服务号
///
public bool IsBindFuwuhao { get; set; }
+
+ ///
+ /// 是否为VIP用户
+ ///
+ public bool IsVip { get; set; }
+
+ ///
+ /// VIP到期时间
+ ///
+ public DateTime? VipExpireTime { get; set; }
+
+ ///
+ /// 尊享包总Token数
+ ///
+ public long PremiumTotalTokens { get; set; }
+
+ ///
+ /// 尊享包已使用Token数
+ ///
+ public long PremiumUsedTokens { get; set; }
+
+ ///
+ /// 尊享包剩余Token数
+ ///
+ public long PremiumRemainingTokens { get; set; }
}
\ No newline at end of file
diff --git a/Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Application.Contracts/Dtos/Pay/GoodsListOutput.cs b/Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Application.Contracts/Dtos/Pay/GoodsListOutput.cs
new file mode 100644
index 00000000..f6696356
--- /dev/null
+++ b/Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Application.Contracts/Dtos/Pay/GoodsListOutput.cs
@@ -0,0 +1,44 @@
+using Yi.Framework.AiHub.Domain.Shared.Enums;
+
+namespace Yi.Framework.AiHub.Application.Contracts.Dtos.Pay;
+
+///
+/// 商品列表输出DTO
+///
+public class GoodsListOutput
+{
+ ///
+ /// 商品名称
+ ///
+ public string GoodsName { get; set; }
+
+ ///
+ /// 商品原价
+ ///
+ public decimal OriginalPrice { get; set; }
+
+ ///
+ /// 商品实际价格(折扣后的价格)
+ ///
+ public decimal GoodsPrice { get; set; }
+
+ ///
+ /// 商品类型
+ ///
+ public GoodsTypeEnum GoodsType { get; set; }
+
+ ///
+ /// 商品备注
+ ///
+ public string Remark { get; set; }
+
+ ///
+ /// 折扣金额(仅尊享包)
+ ///
+ public decimal? DiscountAmount { get; set; }
+
+ ///
+ /// 折扣说明(仅尊享包)
+ ///
+ public string? DiscountDescription { get; set; }
+}
diff --git a/Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Application.Contracts/IServices/IPayService.cs b/Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Application.Contracts/IServices/IPayService.cs
index 1f303501..55edcacf 100644
--- a/Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Application.Contracts/IServices/IPayService.cs
+++ b/Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Application.Contracts/IServices/IPayService.cs
@@ -30,4 +30,10 @@ public interface IPayService : IApplicationService
/// 查询订单状态输入
/// 订单状态信息
Task QueryOrderStatusAsync([FromQuery] QueryOrderStatusInput input);
+
+ ///
+ /// 获取商品列表
+ ///
+ /// 商品列表
+ Task> GetGoodsListAsync();
}
\ No newline at end of file
diff --git a/Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Application/Services/AiAccountService.cs b/Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Application/Services/AiAccountService.cs
index e5290f0d..c1a15efc 100644
--- a/Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Application/Services/AiAccountService.cs
+++ b/Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Application/Services/AiAccountService.cs
@@ -8,6 +8,8 @@ using Yi.Framework.AiHub.Domain.Entities;
using Yi.Framework.Rbac.Application.Contracts.IServices;
using Yi.Framework.Rbac.Domain.Shared.Dtos;
using Yi.Framework.SqlSugarCore.Abstractions;
+using Yi.Framework.AiHub.Domain.Extensions;
+using Yi.Framework.AiHub.Domain.Shared.Consts;
namespace Yi.Framework.AiHub.Application.Services;
@@ -15,11 +17,19 @@ public class AiAccountService : ApplicationService
{
private IAccountService _accountService;
private ISqlSugarRepository _userRepository;
+ private ISqlSugarRepository _rechargeRepository;
+ private ISqlSugarRepository _premiumPackageRepository;
- public AiAccountService(IAccountService accountService, ISqlSugarRepository userRepository)
+ public AiAccountService(
+ IAccountService accountService,
+ ISqlSugarRepository userRepository,
+ ISqlSugarRepository rechargeRepository,
+ ISqlSugarRepository premiumPackageRepository)
{
_accountService = accountService;
_userRepository = userRepository;
+ _rechargeRepository = rechargeRepository;
+ _premiumPackageRepository = premiumPackageRepository;
}
///
@@ -33,7 +43,54 @@ public class AiAccountService : ApplicationService
var userId = CurrentUser.GetId();
var userAccount = await _accountService.GetAsync(null, null, userId: CurrentUser.GetId());
var output = userAccount.Adapt();
+
+ // 是否绑定服务号
output.IsBindFuwuhao = await _userRepository.IsAnyAsync(x => userId == x.UserId);
+
+ // 是否为VIP用户
+ output.IsVip = CurrentUser.IsAiVip();
+
+ // 获取VIP到期时间
+ if (output.IsVip)
+ {
+ var recharges = await _rechargeRepository._DbQueryable
+ .Where(x => x.UserId == userId)
+ .ToListAsync();
+
+ if (recharges.Any())
+ {
+ // 如果有任何一个充值记录的过期时间为null,说明是永久VIP
+ if (recharges.Any(x => !x.ExpireDateTime.HasValue))
+ {
+ output.VipExpireTime = null; // 永久VIP
+ }
+ else
+ {
+ // 取最大的过期时间
+ output.VipExpireTime = recharges
+ .Where(x => x.ExpireDateTime.HasValue)
+ .Max(x => x.ExpireDateTime);
+ }
+ }
+ }
+
+ // 获取尊享包Token信息
+ var premiumPackages = await _premiumPackageRepository._DbQueryable
+ .Where(x => x.UserId == userId && x.IsActive)
+ .ToListAsync();
+
+ if (premiumPackages.Any())
+ {
+ // 过滤掉已过期的包
+ var validPackages = premiumPackages
+ .Where(p => p.IsAvailable())
+ .ToList();
+
+ output.PremiumTotalTokens = validPackages.Sum(x => x.TotalTokens);
+ output.PremiumUsedTokens = validPackages.Sum(x => x.UsedTokens);
+ output.PremiumRemainingTokens = validPackages.Sum(x => x.RemainingTokens);
+ }
+
return output;
}
}
\ No newline at end of file
diff --git a/Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Application/Services/OpenApiService.cs b/Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Application/Services/OpenApiService.cs
index 7c004ac6..8fba666b 100644
--- a/Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Application/Services/OpenApiService.cs
+++ b/Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Application/Services/OpenApiService.cs
@@ -2,14 +2,18 @@
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Logging;
using Volo.Abp.Application.Services;
+using Volo.Abp.Users;
+using Yi.Framework.AiHub.Domain.Entities;
using Yi.Framework.AiHub.Domain.Entities.Model;
using Yi.Framework.AiHub.Domain.Extensions;
using Yi.Framework.AiHub.Domain.Managers;
+using Yi.Framework.AiHub.Domain.Shared.Consts;
using Yi.Framework.AiHub.Domain.Shared.Dtos.Anthropic;
using Yi.Framework.AiHub.Domain.Shared.Dtos.OpenAi;
using Yi.Framework.AiHub.Domain.Shared.Dtos.OpenAi.Embeddings;
using Yi.Framework.AiHub.Domain.Shared.Dtos.OpenAi.Images;
using Yi.Framework.AiHub.Domain.Shared.Enums;
+using Yi.Framework.Rbac.Application.Contracts.IServices;
using Yi.Framework.SqlSugarCore.Abstractions;
namespace Yi.Framework.AiHub.Application.Services;
@@ -22,10 +26,13 @@ public class OpenApiService : ApplicationService
private readonly AiGateWayManager _aiGateWayManager;
private readonly ISqlSugarRepository _aiModelRepository;
private readonly AiBlacklistManager _aiBlacklistManager;
+ private readonly IAccountService _accountService;
+ private readonly PremiumPackageManager _premiumPackageManager;
public OpenApiService(IHttpContextAccessor httpContextAccessor, ILogger logger,
TokenManager tokenManager, AiGateWayManager aiGateWayManager,
- ISqlSugarRepository aiModelRepository, AiBlacklistManager aiBlacklistManager)
+ ISqlSugarRepository aiModelRepository, AiBlacklistManager aiBlacklistManager,
+ IAccountService accountService, PremiumPackageManager premiumPackageManager)
{
_httpContextAccessor = httpContextAccessor;
_logger = logger;
@@ -33,6 +40,8 @@ public class OpenApiService : ApplicationService
_aiGateWayManager = aiGateWayManager;
_aiModelRepository = aiModelRepository;
_aiBlacklistManager = aiBlacklistManager;
+ _accountService = accountService;
+ _premiumPackageManager = premiumPackageManager;
}
///
@@ -120,7 +129,7 @@ public class OpenApiService : ApplicationService
///
- /// Anthropic对话
+ /// Anthropic对话(尊享服务专用)
///
///
///
@@ -132,6 +141,27 @@ public class OpenApiService : ApplicationService
var httpContext = this._httpContextAccessor.HttpContext;
var userId = await _tokenManager.GetUserIdAsync(GetTokenByHttpContext(httpContext));
await _aiBlacklistManager.VerifiyAiBlacklist(userId);
+
+ // 验证用户是否为VIP
+ var userInfo = await _accountService.GetAsync(null, null, userId);
+ if (userInfo == null)
+ {
+ throw new UserFriendlyException("用户信息不存在");
+ }
+
+ // 检查是否为VIP(使用RoleCodes判断)
+ if (!userInfo.RoleCodes.Contains(AiHubConst.VipRole) && userInfo.User.UserName != "cc")
+ {
+ throw new UserFriendlyException("该接口为尊享服务专用,需要VIP权限才能使用");
+ }
+
+ // 检查尊享token包用量
+ var availableTokens = await _premiumPackageManager.GetAvailableTokensAsync(userId);
+ if (availableTokens <= 0)
+ {
+ throw new UserFriendlyException("尊享token包用量不足,请先购买尊享token包");
+ }
+
//ai网关代理httpcontext
if (input.Stream)
{
diff --git a/Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Application/Services/PayService.cs b/Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Application/Services/PayService.cs
index ec4b3d4b..872a1959 100644
--- a/Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Application/Services/PayService.cs
+++ b/Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Application/Services/PayService.cs
@@ -14,6 +14,7 @@ using Yi.Framework.AiHub.Domain.Entities.Pay;
using Yi.Framework.SqlSugarCore.Abstractions;
using System.ComponentModel;
using System.Reflection;
+using Volo.Abp.Users;
using Yi.Framework.AiHub.Application.Contracts.Dtos.Recharge;
namespace Yi.Framework.AiHub.Application.Services;
@@ -28,18 +29,22 @@ public class PayService : ApplicationService, IPayService
private readonly ILogger _logger;
private readonly ISqlSugarRepository _payOrderRepository;
private readonly IRechargeService _rechargeService;
+ private readonly PremiumPackageManager _premiumPackageManager;
public PayService(
AlipayManager alipayManager,
PayManager payManager,
- ILogger logger, ISqlSugarRepository payOrderRepository,
- IRechargeService rechargeService)
+ ILogger logger,
+ ISqlSugarRepository payOrderRepository,
+ IRechargeService rechargeService,
+ PremiumPackageManager premiumPackageManager)
{
_alipayManager = alipayManager;
_payManager = payManager;
_logger = logger;
_payOrderRepository = payOrderRepository;
_rechargeService = rechargeService;
+ _premiumPackageManager = premiumPackageManager;
}
///
@@ -51,7 +56,7 @@ public class PayService : ApplicationService, IPayService
[HttpPost("pay/Order")]
public async Task CreateOrderAsync(CreateOrderInput input)
{
- // 1. 通过PayManager创建订单
+ // 1. 通过PayManager创建订单(内部会验证VIP资格)
var order = await _payManager.CreateOrderAsync(input.GoodsType);
// 2. 通过AlipayManager发起页面支付
@@ -92,7 +97,6 @@ public class PayService : ApplicationService, IPayService
// 2. 验证签名
await _alipayManager.VerifyNotifyAsync(notifyData);
-
// 3. 记录支付通知
await _payManager.RecordPayNoticeAsync(notifyData, signStr);
@@ -108,16 +112,40 @@ public class PayService : ApplicationService, IPayService
_logger.LogInformation("订单状态更新成功,订单号:{OutTradeNo},状态:{TradeStatus}", outTradeNo, tradeStatus);
- //5.充值Vip
- await _rechargeService.RechargeVipAsync(new RechargeCreateInput
+ // 5. 根据商品类型进行不同的处理
+ if (order.GoodsType.IsPremiumPackage())
{
- UserId = order.UserId,
- RechargeAmount = order.TotalAmount,
- Content = order.GoodsName,
- Months = order.GoodsType.GetValidMonths(),
- Remark = "自助充值",
- ContactInfo = null
- });
+ // 处理尊享包商品:创建尊享包记录
+ await _premiumPackageManager.CreatePremiumPackageAsync(
+ order.UserId,
+ order.GoodsType,
+ order.TotalAmount,
+ 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
{
@@ -158,6 +186,85 @@ public class PayService : ApplicationService, IPayService
};
}
+ ///
+ /// 获取商品列表
+ ///
+ /// 商品列表
+ [HttpGet("pay/GoodsList")]
+ public async Task> GetGoodsListAsync()
+ {
+ var goodsList = new List();
+
+ // 获取当前用户的累加充值金额(仅已登录用户)
+ decimal totalRechargeAmount = 0m;
+ if (CurrentUser.IsAuthenticated)
+ {
+ totalRechargeAmount = await _payManager.GetUserTotalRechargeAmountAsync(CurrentUser.GetId());
+ }
+
+ // 遍历所有商品枚举
+ foreach (GoodsTypeEnum goodsType in Enum.GetValues(typeof(GoodsTypeEnum)))
+ {
+ var originalPrice = goodsType.GetTotalAmount();
+ decimal actualPrice = originalPrice;
+ decimal? discountAmount = null;
+ string? discountDescription = null;
+
+ // 如果是尊享包商品,计算折扣
+ if (goodsType.IsPremiumPackage() && CurrentUser.IsAuthenticated)
+ {
+ discountAmount = goodsType.CalculateDiscount(totalRechargeAmount);
+ actualPrice = goodsType.GetDiscountedPrice(totalRechargeAmount);
+
+ if (discountAmount > 0)
+ {
+ discountDescription = $"已优惠 ¥{discountAmount:F2}(累计充值每10元减1元,最多减20元)";
+ }
+ else
+ {
+ discountDescription = "累计充值每10元可减1元,最多减20元";
+ }
+ }
+
+ var goodsItem = new GoodsListOutput
+ {
+ GoodsName = goodsType.GetDisplayName(),
+ OriginalPrice = originalPrice,
+ GoodsPrice = actualPrice,
+ GoodsType = goodsType,
+ Remark = GetGoodsRemark(goodsType),
+ DiscountAmount = discountAmount,
+ DiscountDescription = discountDescription
+ };
+
+ goodsList.Add(goodsItem);
+ }
+
+ return goodsList;
+ }
+
+ ///
+ /// 获取商品备注信息
+ ///
+ /// 商品类型
+ /// 商品备注
+ private string GetGoodsRemark(GoodsTypeEnum goodsType)
+ {
+ if (goodsType.IsPremiumPackage())
+ {
+ var tokenAmount = goodsType.GetTokenAmount();
+ return $"尊享包服务,提供 {tokenAmount:N0} Tokens(需要VIP资格)";
+ }
+ else if (goodsType.IsVipService())
+ {
+ var validMonths = goodsType.GetValidMonths();
+ var monthlyPrice = goodsType.GetMonthlyPrice();
+ return $"VIP服务,有效期 {validMonths} 个月,月均价 ¥{monthlyPrice:F2}";
+ }
+
+ return "未知商品类型";
+ }
+
///
/// 获取交易状态描述
///
@@ -183,4 +290,4 @@ public class PayService : ApplicationService, IPayService
}
return TradeStatusEnum.WAIT_TRADE;
}
-}
\ No newline at end of file
+}
diff --git a/Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Domain.Shared/Enums/GoodsTypeEnum.cs b/Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Domain.Shared/Enums/GoodsTypeEnum.cs
index 711b3d07..0f1016ff 100644
--- a/Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Domain.Shared/Enums/GoodsTypeEnum.cs
+++ b/Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Domain.Shared/Enums/GoodsTypeEnum.cs
@@ -11,7 +11,7 @@ public class PriceAttribute : Attribute
{
public decimal Price { get; }
public int ValidMonths { get; }
-
+
public PriceAttribute(double price, int validMonths)
{
Price = (decimal)price;
@@ -26,56 +26,98 @@ public class PriceAttribute : Attribute
public class DisplayNameAttribute : Attribute
{
public string DisplayName { get; }
-
+
public DisplayNameAttribute(string displayName)
{
DisplayName = displayName;
}
}
+///
+/// 商品类型特性
+/// 用于标识商品是VIP服务还是尊享包服务
+///
+[AttributeUsage(AttributeTargets.Field)]
+public class GoodsCategoryAttribute : Attribute
+{
+ public GoodsCategoryType Category { get; }
+
+ public GoodsCategoryAttribute(GoodsCategoryType category)
+ {
+ Category = category;
+ }
+}
+
+///
+/// 商品类别类型
+///
+public enum GoodsCategoryType
+{
+ ///
+ /// VIP服务
+ ///
+ VipService = 1,
+
+ ///
+ /// 尊享包服务
+ ///
+ PremiumPackage = 2
+}
+
+///
+/// Token数量特性
+/// 用于标识尊享包的token数量
+///
+[AttributeUsage(AttributeTargets.Field)]
+public class TokenAmountAttribute : Attribute
+{
+ public long TokenAmount { get; }
+
+ public TokenAmountAttribute(long tokenAmount)
+ {
+ TokenAmount = tokenAmount;
+ }
+}
+
///
/// 商品枚举
///
public enum GoodsTypeEnum
{
+ // VIP服务
[Price(29.9, 1)]
[DisplayName("YiXinVip 1 month")]
+ [GoodsCategory(GoodsCategoryType.VipService)]
YiXinVip1 = 1,
-
+
[Price(83.7, 3)]
[DisplayName("YiXinVip 3 month")]
+ [GoodsCategory(GoodsCategoryType.VipService)]
YiXinVip3 = 3,
-
+
[Price(155.4, 6)]
[DisplayName("YiXinVip 6 month")]
+ [GoodsCategory(GoodsCategoryType.VipService)]
YiXinVip6 = 6,
-
+
[Price(183.2, 8)]
[DisplayName("YiXinVip 8 month")]
- YiXinVip8 = 8
- // [Price(197.1, 9)]
- // [DisplayName("YiXinVip 9 month")]
- // YiXinVip9 = 9
+ [GoodsCategory(GoodsCategoryType.VipService)]
+ YiXinVip8 = 8,
+
+ // 尊享包服务 - 需要VIP资格才能购买
+ [Price(188.9, 0)]
+ [DisplayName("Premium Package 5000W Tokens")]
+ [GoodsCategory(GoodsCategoryType.PremiumPackage)]
+ [TokenAmount(5000)]
+ PremiumPackage5000W = 101,
+
+ [Price(248.9, 0)]
+ [DisplayName("Premium Package 10000W Tokens")]
+ [GoodsCategory(GoodsCategoryType.PremiumPackage)]
+ [TokenAmount(10000)]
+ PremiumPackage10000W = 102,
- // [Price(0.01, 1)]
- // [DisplayName("YiXinVip Test")]
- // YiXinVipTest = 0,
- //
- // [Price(0.01, 1)]
- // [DisplayName("YiXinVip 1 month")]
- // YiXinVip1 = 1,
- //
- // [Price(0.01, 3)]
- // [DisplayName("YiXinVip 3 month")]
- // YiXinVip3 = 3,
- //
- // [Price(0.01, 6)]
- // [DisplayName("YiXinVip 6 month")]
- // YiXinVip6 = 6,
- //
- // [Price(0.01, 10)]
- // [DisplayName("YiXinVip 10 month")]
- // YiXinVip10 = 10
}
public static class GoodsTypeEnumExtensions
@@ -91,7 +133,7 @@ public static class GoodsTypeEnumExtensions
var priceAttribute = fieldInfo?.GetCustomAttribute();
return priceAttribute?.Price ?? 0m;
}
-
+
///
/// 获取商品价格描述
///
@@ -102,7 +144,7 @@ public static class GoodsTypeEnumExtensions
var price = goodsType.GetTotalAmount();
return $"¥{price:F1}";
}
-
+
///
/// 获取商品名称
///
@@ -114,7 +156,7 @@ public static class GoodsTypeEnumExtensions
var displayNameAttribute = fieldInfo?.GetCustomAttribute();
return displayNameAttribute?.DisplayName ?? goodsType.ToString();
}
-
+
///
/// 获取商品有效月份
///
@@ -126,7 +168,7 @@ public static class GoodsTypeEnumExtensions
var priceAttribute = fieldInfo?.GetCustomAttribute();
return priceAttribute?.ValidMonths ?? 1;
}
-
+
///
/// 获取商品月均价格
///
@@ -138,4 +180,86 @@ public static class GoodsTypeEnumExtensions
var validMonths = goodsType.GetValidMonths();
return validMonths > 0 ? totalPrice / validMonths : 0m;
}
+
+ ///
+ /// 获取商品类别
+ ///
+ /// 商品类型
+ /// 商品类别
+ public static GoodsCategoryType GetGoodsCategory(this GoodsTypeEnum goodsType)
+ {
+ var fieldInfo = goodsType.GetType().GetField(goodsType.ToString());
+ var categoryAttribute = fieldInfo?.GetCustomAttribute();
+ return categoryAttribute?.Category ?? GoodsCategoryType.VipService;
+ }
+
+ ///
+ /// 是否为尊享包商品
+ ///
+ /// 商品类型
+ /// 是否为尊享包
+ public static bool IsPremiumPackage(this GoodsTypeEnum goodsType)
+ {
+ return goodsType.GetGoodsCategory() == GoodsCategoryType.PremiumPackage;
+ }
+
+ ///
+ /// 是否为VIP服务商品
+ ///
+ /// 商品类型
+ /// 是否为VIP服务
+ public static bool IsVipService(this GoodsTypeEnum goodsType)
+ {
+ return goodsType.GetGoodsCategory() == GoodsCategoryType.VipService;
+ }
+
+ ///
+ /// 获取尊享包Token数量
+ ///
+ /// 商品类型
+ /// Token数量
+ public static long GetTokenAmount(this GoodsTypeEnum goodsType)
+ {
+ var fieldInfo = goodsType.GetType().GetField(goodsType.ToString());
+ var tokenAttribute = fieldInfo?.GetCustomAttribute();
+ return tokenAttribute?.TokenAmount ?? 0;
+ }
+
+ ///
+ /// 计算折扣金额(仅用于尊享包)
+ /// 规则:每累加充值10元,减少1元,最多减少20元
+ ///
+ /// 商品类型
+ /// 用户累加充值金额
+ /// 折扣金额
+ public static decimal CalculateDiscount(this GoodsTypeEnum goodsType, decimal totalRechargeAmount)
+ {
+ // 只有尊享包才有折扣
+ if (!goodsType.IsPremiumPackage())
+ {
+ return 0m;
+ }
+
+ // 每10元减1元
+ var discountAmount = Math.Floor(totalRechargeAmount / 10m);
+
+ // 最多减少20元
+ return Math.Min(discountAmount, 20m);
+ }
+
+ ///
+ /// 获取折扣后的价格(仅用于尊享包)
+ ///
+ /// 商品类型
+ /// 用户累加充值金额
+ /// 折扣后的价格
+ public static decimal GetDiscountedPrice(this GoodsTypeEnum goodsType, decimal totalRechargeAmount)
+ {
+ var originalPrice = goodsType.GetTotalAmount();
+ var discount = goodsType.CalculateDiscount(totalRechargeAmount);
+ var discountedPrice = originalPrice - discount;
+
+ // 确保价格不为负数,至少为0.01元
+ return Math.Max(discountedPrice, 0.01m);
+ }
}
diff --git a/Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Domain/AiGateWay/Impl/ThorClaude/Chats/AnthropicChatCompletionsService.cs b/Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Domain/AiGateWay/Impl/ThorClaude/Chats/AnthropicChatCompletionsService.cs
index 7573bfec..e08b95e2 100644
--- a/Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Domain/AiGateWay/Impl/ThorClaude/Chats/AnthropicChatCompletionsService.cs
+++ b/Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Domain/AiGateWay/Impl/ThorClaude/Chats/AnthropicChatCompletionsService.cs
@@ -22,7 +22,6 @@ public class AnthropicChatCompletionsService(IHttpClientFactory httpClientFactor
{
options.Endpoint = "https://api.anthropic.com/";
}
-
var client = httpClientFactory.CreateClient();
var headers = new Dictionary
@@ -77,7 +76,7 @@ public class AnthropicChatCompletionsService(IHttpClientFactory httpClientFactor
throw new Exception("OpenAI对话异常" + response.StatusCode.ToString());
}
-
+
var value =
await response.Content.ReadFromJsonAsync(ThorJsonSerializer.DefaultOptions,
cancellationToken: cancellationToken);
@@ -95,7 +94,7 @@ public class AnthropicChatCompletionsService(IHttpClientFactory httpClientFactor
{
options.Endpoint = "https://api.anthropic.com/";
}
-
+
var client = httpClientFactory.CreateClient();
var headers = new Dictionary
diff --git a/Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Domain/Entities/PremiumPackageAggregateRoot.cs b/Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Domain/Entities/PremiumPackageAggregateRoot.cs
new file mode 100644
index 00000000..2bda12f6
--- /dev/null
+++ b/Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Domain/Entities/PremiumPackageAggregateRoot.cs
@@ -0,0 +1,147 @@
+using SqlSugar;
+using Volo.Abp.Domain.Entities.Auditing;
+
+namespace Yi.Framework.AiHub.Domain.Entities;
+
+///
+/// 尊享包聚合根
+/// 用于给VIP扩展额外购买尊享token包
+///
+[SugarTable("Ai_PremiumPackage")]
+[SugarIndex($"index_{nameof(UserId)}", nameof(UserId), OrderByType.Asc)]
+public class PremiumPackageAggregateRoot : FullAuditedAggregateRoot
+{
+ public PremiumPackageAggregateRoot()
+ {
+ }
+
+ public PremiumPackageAggregateRoot(Guid userId, long totalTokens, string packageName)
+ {
+ UserId = userId;
+ TotalTokens = totalTokens;
+ RemainingTokens = totalTokens;
+ PackageName = packageName;
+ IsActive = true;
+ }
+
+ ///
+ /// 用户ID
+ ///
+ public Guid UserId { get; set; }
+
+ ///
+ /// 包名称
+ ///
+ public string PackageName { get; set; }
+
+ ///
+ /// 总用量(总token数)
+ ///
+ public long TotalTokens { get; set; }
+
+ ///
+ /// 剩余用量(剩余token数)
+ ///
+ public long RemainingTokens { get; set; }
+
+ ///
+ /// 已使用token数
+ ///
+ public long UsedTokens { get; set; }
+
+ ///
+ /// 到期时间
+ ///
+ public DateTime? ExpireDateTime { get; set; }
+
+ ///
+ /// 是否激活
+ ///
+ public bool IsActive { get; set; }
+
+ ///
+ /// 购买金额
+ ///
+ public decimal PurchaseAmount { get; set; }
+
+ ///
+ /// 备注
+ ///
+ public string? Remark { get; set; }
+
+ ///
+ /// 消耗token
+ ///
+ /// 消耗的token数量
+ /// 是否消耗成功
+ 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;
+ }
+
+ ///
+ /// 检查是否可用
+ ///
+ /// 是否可用
+ public bool IsAvailable()
+ {
+ if (!IsActive)
+ {
+ return false;
+ }
+
+ if (RemainingTokens <= 0)
+ {
+ return false;
+ }
+
+ if (ExpireDateTime.HasValue && ExpireDateTime.Value < DateTime.Now)
+ {
+ return false;
+ }
+
+ return true;
+ }
+
+ ///
+ /// 停用尊享包
+ ///
+ public void Deactivate()
+ {
+ IsActive = false;
+ }
+
+ ///
+ /// 激活尊享包
+ ///
+ public void Activate()
+ {
+ IsActive = true;
+ }
+
+ ///
+ /// 设置到期时间
+ ///
+ /// 到期时间
+ public void SetExpireDateTime(DateTime expireDateTime)
+ {
+ ExpireDateTime = expireDateTime;
+ }
+}
diff --git a/Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Domain/Managers/AiGateWayManager.cs b/Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Domain/Managers/AiGateWayManager.cs
index 8905f2c0..22c31057 100644
--- a/Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Domain/Managers/AiGateWayManager.cs
+++ b/Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Domain/Managers/AiGateWayManager.cs
@@ -31,6 +31,7 @@ public class AiGateWayManager : DomainService
private readonly AiMessageManager _aiMessageManager;
private readonly UsageStatisticsManager _usageStatisticsManager;
private readonly ISpecialCompatible _specialCompatible;
+ private PremiumPackageManager? _premiumPackageManager;
public AiGateWayManager(ISqlSugarRepository aiAppRepository, ILogger logger,
AiMessageManager aiMessageManager, UsageStatisticsManager usageStatisticsManager,
@@ -43,6 +44,9 @@ public class AiGateWayManager : DomainService
_specialCompatible = specialCompatible;
}
+ private PremiumPackageManager PremiumPackageManager =>
+ _premiumPackageManager ??= LazyServiceProvider.LazyGetRequiredService();
+
///
/// 获取模型
///
@@ -510,6 +514,17 @@ public class AiGateWayManager : DomainService
});
await _usageStatisticsManager.SetUsageAsync(userId.Value, request.Model, data.TokenUsage);
+
+ // 扣减尊享token包用量
+ var totalTokens = (data.TokenUsage?.InputTokens ?? 0) + (data.TokenUsage?.OutputTokens ?? 0);
+ if (totalTokens > 0)
+ {
+ var consumeSuccess = await PremiumPackageManager.ConsumeTokensAsync(userId.Value, totalTokens);
+ if (!consumeSuccess)
+ {
+ _logger.LogWarning($"用户 {userId.Value} 尊享token包扣减失败,消耗token数: {totalTokens}");
+ }
+ }
}
await response.WriteAsJsonAsync(data, cancellationToken);
@@ -560,29 +575,30 @@ public class AiGateWayManager : DomainService
{
_logger.LogError(e, $"Ai对话异常");
var errorContent = $"对话Ai异常,异常信息:\n当前Ai模型:{request.Model}\n异常信息:{e.Message}\n异常堆栈:{e}";
- var model = new AnthropicStreamDto
- {
- Message = new AnthropicChatCompletionDto
- {
- content =
- [
- new AnthropicChatCompletionDtoContent
- {
- text = errorContent,
- }
- ],
- },
- Error = new AnthropicStreamErrorDto
- {
- Type = null,
- Message = errorContent
- }
- };
- var message = JsonConvert.SerializeObject(model, new JsonSerializerSettings
- {
- ContractResolver = new CamelCasePropertyNamesContractResolver()
- });
- await response.WriteAsJsonAsync(message, ThorJsonSerializer.DefaultOptions);
+ throw new UserFriendlyException(errorContent);
+ // var model = new AnthropicStreamDto
+ // {
+ // Message = new AnthropicChatCompletionDto
+ // {
+ // content =
+ // [
+ // new AnthropicChatCompletionDtoContent
+ // {
+ // text = errorContent,
+ // }
+ // ],
+ // },
+ // Error = new AnthropicStreamErrorDto
+ // {
+ // Type = null,
+ // Message = errorContent
+ // }
+ // };
+ // var message = JsonConvert.SerializeObject(model, new JsonSerializerSettings
+ // {
+ // ContractResolver = new CamelCasePropertyNamesContractResolver()
+ // });
+ // await response.WriteAsJsonAsync(message, ThorJsonSerializer.DefaultOptions);
}
await _aiMessageManager.CreateUserMessageAsync(userId, sessionId,
@@ -602,6 +618,20 @@ public class AiGateWayManager : DomainService
});
await _usageStatisticsManager.SetUsageAsync(userId, request.Model, tokenUsage);
+
+ // 扣减尊享token包用量
+ if (userId.HasValue && tokenUsage is not null)
+ {
+ var totalTokens = tokenUsage.TotalTokens??0;
+ if (totalTokens > 0)
+ {
+ var consumeSuccess = await PremiumPackageManager.ConsumeTokensAsync(userId.Value, totalTokens);
+ if (!consumeSuccess)
+ {
+ _logger.LogWarning($"用户 {userId.Value} 尊享token包扣减失败,消耗token数: {totalTokens}");
+ }
+ }
+ }
}
#region Anthropic格式Http响应
diff --git a/Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Domain/Managers/PayManager.cs b/Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Domain/Managers/PayManager.cs
index 4939005b..220c218a 100644
--- a/Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Domain/Managers/PayManager.cs
+++ b/Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Domain/Managers/PayManager.cs
@@ -1,9 +1,11 @@
using System.Text.Json;
using Volo.Abp.Domain.Services;
using Volo.Abp.Users;
+using Yi.Framework.AiHub.Domain.Entities;
using Yi.Framework.AiHub.Domain.Entities.Pay;
using Yi.Framework.AiHub.Domain.Shared.Enums;
using Yi.Framework.SqlSugarCore.Abstractions;
+using Yi.Framework.AiHub.Domain.Extensions;
namespace Yi.Framework.AiHub.Domain.Managers;
@@ -15,14 +17,18 @@ public class PayManager : DomainService
private readonly ISqlSugarRepository _payNoticeRepository;
private readonly ICurrentUser _currentUser;
private readonly ISqlSugarRepository _payOrderRepository;
+ private readonly ISqlSugarRepository _rechargeRepository;
public PayManager(
ISqlSugarRepository payNoticeRepository,
- ICurrentUser currentUser, ISqlSugarRepository payOrderRepository)
+ ICurrentUser currentUser,
+ ISqlSugarRepository payOrderRepository,
+ ISqlSugarRepository rechargeRepository)
{
_payNoticeRepository = payNoticeRepository;
_currentUser = currentUser;
_payOrderRepository = payOrderRepository;
+ _rechargeRepository = rechargeRepository;
}
///
@@ -38,18 +44,43 @@ public class PayManager : DomainService
throw new UserFriendlyException("用户未登录");
}
+ var userId = _currentUser.GetId();
+
+ // 如果是尊享包商品,需要验证用户是否为VIP
+ if (goodsType.IsPremiumPackage())
+ {
+ if (!_currentUser.IsAiVip())
+ {
+ throw new UserFriendlyException("购买尊享包需要VIP资格,请先开通VIP");
+ }
+ }
+
// 生成订单号
var outTradeNo = GenerateOutTradeNo();
// 获取商品信息
var goodsName = goodsType.GetDisplayName();
- var totalAmount = goodsType.GetTotalAmount();
+
+ // 计算订单金额(尊享包使用折扣价格,VIP服务使用原价)
+ decimal totalAmount;
+ if (goodsType.IsPremiumPackage())
+ {
+ // 获取用户累加充值金额
+ var totalRechargeAmount = await GetUserTotalRechargeAmountAsync(userId);
+ // 使用折扣后的价格
+ totalAmount = goodsType.GetDiscountedPrice(totalRechargeAmount);
+ }
+ else
+ {
+ // VIP服务使用原价
+ totalAmount = goodsType.GetTotalAmount();
+ }
// 创建订单实体
var payOrder = new PayOrderAggregateRoot
{
OutTradeNo = outTradeNo,
- UserId = _currentUser.GetId(),
+ UserId = userId,
UserName = _currentUser.UserName ?? string.Empty,
TotalAmount = totalAmount,
GoodsName = goodsName,
@@ -135,4 +166,19 @@ public class PayManager : DomainService
}
return TradeStatusEnum.WAIT_TRADE;
}
-}
\ No newline at end of file
+
+ ///
+ /// 获取用户累加充值金额
+ ///
+ /// 用户ID
+ /// 累加充值金额
+ public async Task GetUserTotalRechargeAmountAsync(Guid userId)
+ {
+ var totalAmount = await _rechargeRepository
+ ._DbQueryable
+ .Where(x => x.UserId == userId )
+ .SumAsync(x => x.RechargeAmount);
+
+ return totalAmount;
+ }
+}
diff --git a/Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Domain/Managers/PremiumPackageManager.cs b/Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Domain/Managers/PremiumPackageManager.cs
new file mode 100644
index 00000000..f666bd96
--- /dev/null
+++ b/Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Domain/Managers/PremiumPackageManager.cs
@@ -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;
+
+///
+/// 尊享包管理器
+///
+public class PremiumPackageManager : DomainService
+{
+ private readonly ISqlSugarRepository _premiumPackageRepository;
+ private readonly ILogger _logger;
+
+ public PremiumPackageManager(
+ ISqlSugarRepository premiumPackageRepository,
+ ILogger logger)
+ {
+ _premiumPackageRepository = premiumPackageRepository;
+ _logger = logger;
+ }
+
+ ///
+ /// 为用户创建尊享包
+ ///
+ /// 用户ID
+ /// 商品类型
+ /// 支付金额
+ /// 过期月数,0或null表示永久
+ ///
+ public async Task 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;
+ }
+
+ ///
+ /// 消耗用户尊享包的Token
+ ///
+ /// 用户ID
+ /// 需要消耗的Token数量
+ /// 是否消耗成功
+ public async Task 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;
+ }
+
+ ///
+ /// 获取用户可用的尊享包总Token数
+ ///
+ /// 用户ID
+ /// 可用Token总数
+ public async Task 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);
+ }
+
+ ///
+ /// 获取用户的所有尊享包
+ ///
+ /// 用户ID
+ /// 尊享包列表
+ public async Task> GetUserPremiumPackagesAsync(Guid userId)
+ {
+ return await _premiumPackageRepository._DbQueryable
+ .Where(x => x.UserId == userId)
+ .OrderByDescending(x => x.CreationTime)
+ .ToListAsync();
+ }
+
+ ///
+ /// 停用过期的尊享包
+ ///
+ /// 停用的包数量
+ public async Task 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;
+ }
+}