feat: 增加尊享包商品及折扣逻辑,完善VIP与尊享包相关接口和数据返回
- 新增尊享包商品类型,支持 5000W 和 10000W Tokens - 增加尊享包折扣计算与折扣后价格获取方法 - PayService 新增获取商品列表接口,支持尊享包折扣展示 - PayManager 支持尊享包订单金额按折扣计算,并新增获取用户累计充值金额方法 - OpenApiService Anthropic接口增加VIP与尊享包用量校验 - AiGateWayManager 增加尊享包Token扣减逻辑 - AiAccountService 返回用户VIP状态、到期时间及尊享包Token统计信息
This commit is contained in:
@@ -8,4 +8,29 @@ public class AiUserRoleMenuDto:UserRoleMenuDto
|
|||||||
/// 是否绑定服务号
|
/// 是否绑定服务号
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public bool IsBindFuwuhao { get; set; }
|
public bool IsBindFuwuhao { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 是否为VIP用户
|
||||||
|
/// </summary>
|
||||||
|
public bool IsVip { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// VIP到期时间
|
||||||
|
/// </summary>
|
||||||
|
public DateTime? VipExpireTime { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 尊享包总Token数
|
||||||
|
/// </summary>
|
||||||
|
public long PremiumTotalTokens { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 尊享包已使用Token数
|
||||||
|
/// </summary>
|
||||||
|
public long PremiumUsedTokens { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 尊享包剩余Token数
|
||||||
|
/// </summary>
|
||||||
|
public long PremiumRemainingTokens { get; set; }
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,44 @@
|
|||||||
|
using Yi.Framework.AiHub.Domain.Shared.Enums;
|
||||||
|
|
||||||
|
namespace Yi.Framework.AiHub.Application.Contracts.Dtos.Pay;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 商品列表输出DTO
|
||||||
|
/// </summary>
|
||||||
|
public class GoodsListOutput
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 商品名称
|
||||||
|
/// </summary>
|
||||||
|
public string GoodsName { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 商品原价
|
||||||
|
/// </summary>
|
||||||
|
public decimal OriginalPrice { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 商品实际价格(折扣后的价格)
|
||||||
|
/// </summary>
|
||||||
|
public decimal GoodsPrice { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 商品类型
|
||||||
|
/// </summary>
|
||||||
|
public GoodsTypeEnum GoodsType { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 商品备注
|
||||||
|
/// </summary>
|
||||||
|
public string Remark { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 折扣金额(仅尊享包)
|
||||||
|
/// </summary>
|
||||||
|
public decimal? DiscountAmount { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 折扣说明(仅尊享包)
|
||||||
|
/// </summary>
|
||||||
|
public string? DiscountDescription { get; set; }
|
||||||
|
}
|
||||||
@@ -30,4 +30,10 @@ public interface IPayService : IApplicationService
|
|||||||
/// <param name="input">查询订单状态输入</param>
|
/// <param name="input">查询订单状态输入</param>
|
||||||
/// <returns>订单状态信息</returns>
|
/// <returns>订单状态信息</returns>
|
||||||
Task<QueryOrderStatusOutput> QueryOrderStatusAsync([FromQuery] QueryOrderStatusInput input);
|
Task<QueryOrderStatusOutput> QueryOrderStatusAsync([FromQuery] QueryOrderStatusInput input);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 获取商品列表
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>商品列表</returns>
|
||||||
|
Task<List<GoodsListOutput>> GetGoodsListAsync();
|
||||||
}
|
}
|
||||||
@@ -8,6 +8,8 @@ using Yi.Framework.AiHub.Domain.Entities;
|
|||||||
using Yi.Framework.Rbac.Application.Contracts.IServices;
|
using Yi.Framework.Rbac.Application.Contracts.IServices;
|
||||||
using Yi.Framework.Rbac.Domain.Shared.Dtos;
|
using Yi.Framework.Rbac.Domain.Shared.Dtos;
|
||||||
using Yi.Framework.SqlSugarCore.Abstractions;
|
using Yi.Framework.SqlSugarCore.Abstractions;
|
||||||
|
using Yi.Framework.AiHub.Domain.Extensions;
|
||||||
|
using Yi.Framework.AiHub.Domain.Shared.Consts;
|
||||||
|
|
||||||
namespace Yi.Framework.AiHub.Application.Services;
|
namespace Yi.Framework.AiHub.Application.Services;
|
||||||
|
|
||||||
@@ -15,11 +17,19 @@ public class AiAccountService : ApplicationService
|
|||||||
{
|
{
|
||||||
private IAccountService _accountService;
|
private IAccountService _accountService;
|
||||||
private ISqlSugarRepository<AiUserExtraInfoEntity> _userRepository;
|
private ISqlSugarRepository<AiUserExtraInfoEntity> _userRepository;
|
||||||
|
private ISqlSugarRepository<AiRechargeAggregateRoot> _rechargeRepository;
|
||||||
|
private ISqlSugarRepository<PremiumPackageAggregateRoot> _premiumPackageRepository;
|
||||||
|
|
||||||
public AiAccountService(IAccountService accountService, ISqlSugarRepository<AiUserExtraInfoEntity> userRepository)
|
public AiAccountService(
|
||||||
|
IAccountService accountService,
|
||||||
|
ISqlSugarRepository<AiUserExtraInfoEntity> userRepository,
|
||||||
|
ISqlSugarRepository<AiRechargeAggregateRoot> rechargeRepository,
|
||||||
|
ISqlSugarRepository<PremiumPackageAggregateRoot> premiumPackageRepository)
|
||||||
{
|
{
|
||||||
_accountService = accountService;
|
_accountService = accountService;
|
||||||
_userRepository = userRepository;
|
_userRepository = userRepository;
|
||||||
|
_rechargeRepository = rechargeRepository;
|
||||||
|
_premiumPackageRepository = premiumPackageRepository;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -33,7 +43,54 @@ public class AiAccountService : ApplicationService
|
|||||||
var userId = CurrentUser.GetId();
|
var userId = CurrentUser.GetId();
|
||||||
var userAccount = await _accountService.GetAsync(null, null, userId: CurrentUser.GetId());
|
var userAccount = await _accountService.GetAsync(null, null, userId: CurrentUser.GetId());
|
||||||
var output = userAccount.Adapt<AiUserRoleMenuDto>();
|
var output = userAccount.Adapt<AiUserRoleMenuDto>();
|
||||||
|
|
||||||
|
// 是否绑定服务号
|
||||||
output.IsBindFuwuhao = await _userRepository.IsAnyAsync(x => userId == x.UserId);
|
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;
|
return output;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -2,14 +2,18 @@
|
|||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
using Volo.Abp.Application.Services;
|
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.Entities.Model;
|
||||||
using Yi.Framework.AiHub.Domain.Extensions;
|
using Yi.Framework.AiHub.Domain.Extensions;
|
||||||
using Yi.Framework.AiHub.Domain.Managers;
|
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.Anthropic;
|
||||||
using Yi.Framework.AiHub.Domain.Shared.Dtos.OpenAi;
|
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.Embeddings;
|
||||||
using Yi.Framework.AiHub.Domain.Shared.Dtos.OpenAi.Images;
|
using Yi.Framework.AiHub.Domain.Shared.Dtos.OpenAi.Images;
|
||||||
using Yi.Framework.AiHub.Domain.Shared.Enums;
|
using Yi.Framework.AiHub.Domain.Shared.Enums;
|
||||||
|
using Yi.Framework.Rbac.Application.Contracts.IServices;
|
||||||
using Yi.Framework.SqlSugarCore.Abstractions;
|
using Yi.Framework.SqlSugarCore.Abstractions;
|
||||||
|
|
||||||
namespace Yi.Framework.AiHub.Application.Services;
|
namespace Yi.Framework.AiHub.Application.Services;
|
||||||
@@ -22,10 +26,13 @@ public class OpenApiService : ApplicationService
|
|||||||
private readonly AiGateWayManager _aiGateWayManager;
|
private readonly AiGateWayManager _aiGateWayManager;
|
||||||
private readonly ISqlSugarRepository<AiModelEntity> _aiModelRepository;
|
private readonly ISqlSugarRepository<AiModelEntity> _aiModelRepository;
|
||||||
private readonly AiBlacklistManager _aiBlacklistManager;
|
private readonly AiBlacklistManager _aiBlacklistManager;
|
||||||
|
private readonly IAccountService _accountService;
|
||||||
|
private readonly PremiumPackageManager _premiumPackageManager;
|
||||||
|
|
||||||
public OpenApiService(IHttpContextAccessor httpContextAccessor, ILogger<OpenApiService> logger,
|
public OpenApiService(IHttpContextAccessor httpContextAccessor, ILogger<OpenApiService> logger,
|
||||||
TokenManager tokenManager, AiGateWayManager aiGateWayManager,
|
TokenManager tokenManager, AiGateWayManager aiGateWayManager,
|
||||||
ISqlSugarRepository<AiModelEntity> aiModelRepository, AiBlacklistManager aiBlacklistManager)
|
ISqlSugarRepository<AiModelEntity> aiModelRepository, AiBlacklistManager aiBlacklistManager,
|
||||||
|
IAccountService accountService, PremiumPackageManager premiumPackageManager)
|
||||||
{
|
{
|
||||||
_httpContextAccessor = httpContextAccessor;
|
_httpContextAccessor = httpContextAccessor;
|
||||||
_logger = logger;
|
_logger = logger;
|
||||||
@@ -33,6 +40,8 @@ public class OpenApiService : ApplicationService
|
|||||||
_aiGateWayManager = aiGateWayManager;
|
_aiGateWayManager = aiGateWayManager;
|
||||||
_aiModelRepository = aiModelRepository;
|
_aiModelRepository = aiModelRepository;
|
||||||
_aiBlacklistManager = aiBlacklistManager;
|
_aiBlacklistManager = aiBlacklistManager;
|
||||||
|
_accountService = accountService;
|
||||||
|
_premiumPackageManager = premiumPackageManager;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -120,7 +129,7 @@ public class OpenApiService : ApplicationService
|
|||||||
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Anthropic对话
|
/// Anthropic对话(尊享服务专用)
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="input"></param>
|
/// <param name="input"></param>
|
||||||
/// <param name="cancellationToken"></param>
|
/// <param name="cancellationToken"></param>
|
||||||
@@ -132,6 +141,27 @@ public class OpenApiService : ApplicationService
|
|||||||
var httpContext = this._httpContextAccessor.HttpContext;
|
var httpContext = this._httpContextAccessor.HttpContext;
|
||||||
var userId = await _tokenManager.GetUserIdAsync(GetTokenByHttpContext(httpContext));
|
var userId = await _tokenManager.GetUserIdAsync(GetTokenByHttpContext(httpContext));
|
||||||
await _aiBlacklistManager.VerifiyAiBlacklist(userId);
|
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
|
//ai网关代理httpcontext
|
||||||
if (input.Stream)
|
if (input.Stream)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ using Yi.Framework.AiHub.Domain.Entities.Pay;
|
|||||||
using Yi.Framework.SqlSugarCore.Abstractions;
|
using Yi.Framework.SqlSugarCore.Abstractions;
|
||||||
using System.ComponentModel;
|
using System.ComponentModel;
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
|
using Volo.Abp.Users;
|
||||||
using Yi.Framework.AiHub.Application.Contracts.Dtos.Recharge;
|
using Yi.Framework.AiHub.Application.Contracts.Dtos.Recharge;
|
||||||
|
|
||||||
namespace Yi.Framework.AiHub.Application.Services;
|
namespace Yi.Framework.AiHub.Application.Services;
|
||||||
@@ -185,6 +186,85 @@ public class PayService : ApplicationService, IPayService
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 获取商品列表
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>商品列表</returns>
|
||||||
|
[HttpGet("pay/GoodsList")]
|
||||||
|
public async Task<List<GoodsListOutput>> GetGoodsListAsync()
|
||||||
|
{
|
||||||
|
var goodsList = new List<GoodsListOutput>();
|
||||||
|
|
||||||
|
// 获取当前用户的累加充值金额(仅已登录用户)
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 获取商品备注信息
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="goodsType">商品类型</param>
|
||||||
|
/// <returns>商品备注</returns>
|
||||||
|
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 "未知商品类型";
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 获取交易状态描述
|
/// 获取交易状态描述
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|||||||
@@ -106,53 +106,18 @@ public enum GoodsTypeEnum
|
|||||||
YiXinVip8 = 8,
|
YiXinVip8 = 8,
|
||||||
|
|
||||||
// 尊享包服务 - 需要VIP资格才能购买
|
// 尊享包服务 - 需要VIP资格才能购买
|
||||||
[Price(49.9, 0)]
|
[Price(188.9, 0)]
|
||||||
[DisplayName("Premium Package 5M Tokens")]
|
[DisplayName("Premium Package 5000W Tokens")]
|
||||||
[GoodsCategory(GoodsCategoryType.PremiumPackage)]
|
[GoodsCategory(GoodsCategoryType.PremiumPackage)]
|
||||||
[TokenAmount(5_000_000)]
|
[TokenAmount(5000)]
|
||||||
PremiumPackage5M = 101,
|
PremiumPackage5000W = 101,
|
||||||
|
|
||||||
[Price(99.9, 0)]
|
[Price(248.9, 0)]
|
||||||
[DisplayName("Premium Package 10M Tokens")]
|
[DisplayName("Premium Package 10000W Tokens")]
|
||||||
[GoodsCategory(GoodsCategoryType.PremiumPackage)]
|
[GoodsCategory(GoodsCategoryType.PremiumPackage)]
|
||||||
[TokenAmount(10_000_000)]
|
[TokenAmount(10000)]
|
||||||
PremiumPackage10M = 102,
|
PremiumPackage10000W = 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)]
|
|
||||||
// [DisplayName("YiXinVip 9 month")]
|
|
||||||
// YiXinVip9 = 9
|
|
||||||
|
|
||||||
// [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
|
public static class GoodsTypeEnumExtensions
|
||||||
@@ -259,4 +224,42 @@ public static class GoodsTypeEnumExtensions
|
|||||||
var tokenAttribute = fieldInfo?.GetCustomAttribute<TokenAmountAttribute>();
|
var tokenAttribute = fieldInfo?.GetCustomAttribute<TokenAmountAttribute>();
|
||||||
return tokenAttribute?.TokenAmount ?? 0;
|
return tokenAttribute?.TokenAmount ?? 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 计算折扣金额(仅用于尊享包)
|
||||||
|
/// 规则:每累加充值10元,减少1元,最多减少20元
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="goodsType">商品类型</param>
|
||||||
|
/// <param name="totalRechargeAmount">用户累加充值金额</param>
|
||||||
|
/// <returns>折扣金额</returns>
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 获取折扣后的价格(仅用于尊享包)
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="goodsType">商品类型</param>
|
||||||
|
/// <param name="totalRechargeAmount">用户累加充值金额</param>
|
||||||
|
/// <returns>折扣后的价格</returns>
|
||||||
|
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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -31,6 +31,7 @@ public class AiGateWayManager : DomainService
|
|||||||
private readonly AiMessageManager _aiMessageManager;
|
private readonly AiMessageManager _aiMessageManager;
|
||||||
private readonly UsageStatisticsManager _usageStatisticsManager;
|
private readonly UsageStatisticsManager _usageStatisticsManager;
|
||||||
private readonly ISpecialCompatible _specialCompatible;
|
private readonly ISpecialCompatible _specialCompatible;
|
||||||
|
private PremiumPackageManager? _premiumPackageManager;
|
||||||
|
|
||||||
public AiGateWayManager(ISqlSugarRepository<AiAppAggregateRoot> aiAppRepository, ILogger<AiGateWayManager> logger,
|
public AiGateWayManager(ISqlSugarRepository<AiAppAggregateRoot> aiAppRepository, ILogger<AiGateWayManager> logger,
|
||||||
AiMessageManager aiMessageManager, UsageStatisticsManager usageStatisticsManager,
|
AiMessageManager aiMessageManager, UsageStatisticsManager usageStatisticsManager,
|
||||||
@@ -43,6 +44,9 @@ public class AiGateWayManager : DomainService
|
|||||||
_specialCompatible = specialCompatible;
|
_specialCompatible = specialCompatible;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private PremiumPackageManager PremiumPackageManager =>
|
||||||
|
_premiumPackageManager ??= LazyServiceProvider.LazyGetRequiredService<PremiumPackageManager>();
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 获取模型
|
/// 获取模型
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -510,6 +514,17 @@ public class AiGateWayManager : DomainService
|
|||||||
});
|
});
|
||||||
|
|
||||||
await _usageStatisticsManager.SetUsageAsync(userId.Value, request.Model, data.TokenUsage);
|
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);
|
await response.WriteAsJsonAsync(data, cancellationToken);
|
||||||
@@ -603,6 +618,20 @@ public class AiGateWayManager : DomainService
|
|||||||
});
|
});
|
||||||
|
|
||||||
await _usageStatisticsManager.SetUsageAsync(userId, request.Model, tokenUsage);
|
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响应
|
#region Anthropic格式Http响应
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
using System.Text.Json;
|
using System.Text.Json;
|
||||||
using Volo.Abp.Domain.Services;
|
using Volo.Abp.Domain.Services;
|
||||||
using Volo.Abp.Users;
|
using Volo.Abp.Users;
|
||||||
|
using Yi.Framework.AiHub.Domain.Entities;
|
||||||
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;
|
||||||
@@ -16,14 +17,18 @@ public class PayManager : DomainService
|
|||||||
private readonly ISqlSugarRepository<PayNoticeRecordAggregateRoot, Guid> _payNoticeRepository;
|
private readonly ISqlSugarRepository<PayNoticeRecordAggregateRoot, Guid> _payNoticeRepository;
|
||||||
private readonly ICurrentUser _currentUser;
|
private readonly ICurrentUser _currentUser;
|
||||||
private readonly ISqlSugarRepository<PayOrderAggregateRoot, Guid> _payOrderRepository;
|
private readonly ISqlSugarRepository<PayOrderAggregateRoot, Guid> _payOrderRepository;
|
||||||
|
private readonly ISqlSugarRepository<AiRechargeAggregateRoot, Guid> _rechargeRepository;
|
||||||
|
|
||||||
public PayManager(
|
public PayManager(
|
||||||
ISqlSugarRepository<PayNoticeRecordAggregateRoot, Guid> payNoticeRepository,
|
ISqlSugarRepository<PayNoticeRecordAggregateRoot, Guid> payNoticeRepository,
|
||||||
ICurrentUser currentUser, ISqlSugarRepository<PayOrderAggregateRoot, Guid> payOrderRepository)
|
ICurrentUser currentUser,
|
||||||
|
ISqlSugarRepository<PayOrderAggregateRoot, Guid> payOrderRepository,
|
||||||
|
ISqlSugarRepository<AiRechargeAggregateRoot, Guid> rechargeRepository)
|
||||||
{
|
{
|
||||||
_payNoticeRepository = payNoticeRepository;
|
_payNoticeRepository = payNoticeRepository;
|
||||||
_currentUser = currentUser;
|
_currentUser = currentUser;
|
||||||
_payOrderRepository = payOrderRepository;
|
_payOrderRepository = payOrderRepository;
|
||||||
|
_rechargeRepository = rechargeRepository;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -39,6 +44,8 @@ public class PayManager : DomainService
|
|||||||
throw new UserFriendlyException("用户未登录");
|
throw new UserFriendlyException("用户未登录");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var userId = _currentUser.GetId();
|
||||||
|
|
||||||
// 如果是尊享包商品,需要验证用户是否为VIP
|
// 如果是尊享包商品,需要验证用户是否为VIP
|
||||||
if (goodsType.IsPremiumPackage())
|
if (goodsType.IsPremiumPackage())
|
||||||
{
|
{
|
||||||
@@ -53,13 +60,27 @@ public class PayManager : DomainService
|
|||||||
|
|
||||||
// 获取商品信息
|
// 获取商品信息
|
||||||
var goodsName = goodsType.GetDisplayName();
|
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
|
var payOrder = new PayOrderAggregateRoot
|
||||||
{
|
{
|
||||||
OutTradeNo = outTradeNo,
|
OutTradeNo = outTradeNo,
|
||||||
UserId = _currentUser.GetId(),
|
UserId = userId,
|
||||||
UserName = _currentUser.UserName ?? string.Empty,
|
UserName = _currentUser.UserName ?? string.Empty,
|
||||||
TotalAmount = totalAmount,
|
TotalAmount = totalAmount,
|
||||||
GoodsName = goodsName,
|
GoodsName = goodsName,
|
||||||
@@ -145,4 +166,19 @@ public class PayManager : DomainService
|
|||||||
}
|
}
|
||||||
return TradeStatusEnum.WAIT_TRADE;
|
return TradeStatusEnum.WAIT_TRADE;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 获取用户累加充值金额
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="userId">用户ID</param>
|
||||||
|
/// <returns>累加充值金额</returns>
|
||||||
|
public async Task<decimal> GetUserTotalRechargeAmountAsync(Guid userId)
|
||||||
|
{
|
||||||
|
var totalAmount = await _rechargeRepository
|
||||||
|
._DbQueryable
|
||||||
|
.Where(x => x.UserId == userId )
|
||||||
|
.SumAsync(x => x.RechargeAmount);
|
||||||
|
|
||||||
|
return totalAmount;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user