HaniBlindBox/server/HoneyBox/src/HoneyBox.Core/Services/QuanYiService.cs
2026-02-05 11:32:15 +08:00

465 lines
16 KiB
C#

using HoneyBox.Core.Interfaces;
using HoneyBox.Model.Data;
using HoneyBox.Model.Entities;
using HoneyBox.Model.Models.Vip;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging;
namespace HoneyBox.Core.Services;
/// <summary>
/// 权益服务实现
/// </summary>
public class QuanYiService : IQuanYiService
{
private readonly HoneyBoxDbContext _dbContext;
private readonly ILogger<QuanYiService> _logger;
private readonly string _imageBaseUrl;
public QuanYiService(HoneyBoxDbContext dbContext, ILogger<QuanYiService> logger)
{
_dbContext = dbContext;
_logger = logger;
_imageBaseUrl = "https://example.com"; // TODO: Configure from appsettings
}
/// <inheritdoc/>
public async Task<QuanYiResponse> GetQuanYiAsync(int userId)
{
var user = await _dbContext.Users.FirstOrDefaultAsync(u => u.Id == userId);
if (user == null)
{
throw new InvalidOperationException("用户不存在");
}
// 从数据库获取等级配置
var vipLevelConfigs = await _dbContext.VipLevels
.Where(v => v.DeletedAt == null)
.OrderBy(v => v.Level)
.ToListAsync();
var response = new QuanYiResponse
{
UserImg = GetImageUrl(user.HeadImg),
QuanYiLevel = GetQuanYiLevelInfo(user.OuQiLevel ?? 0, user.OuQi ?? 0, vipLevelConfigs)
};
// Get all VIP levels with level > 0
var vipLevels = await _dbContext.VipLevels
.Where(v => v.Level > 0 && v.DeletedAt == null)
.OrderBy(v => v.Level)
.ToListAsync();
var levelList = new List<QuanYiLevelDto>();
foreach (var vipLevel in vipLevels)
{
var levelDto = new QuanYiLevelDto
{
Id = vipLevel.Id,
Level = vipLevel.Level
};
// Get coupon rewards (type = 1)
var puJiang = await _dbContext.VipLevelRewards
.Where(r => r.VipLevelId == vipLevel.Id && r.Type == 1 && r.DeletedAt == null)
.OrderBy(r => r.Id)
.Select(r => new QuanYiRewardDto
{
Id = r.Id,
Title = r.Title,
ZNum = r.ZNum,
ImgUrl = r.ImgUrl
})
.ToListAsync();
levelDto.PuJiang = puJiang;
// Get prize rewards (type = 2)
var gaoJiangRaw = await _dbContext.VipLevelRewards
.Where(r => r.VipLevelId == vipLevel.Id && r.Type == 2 && r.DeletedAt == null)
.OrderBy(r => r.Id)
.Select(r => new QuanYiRewardDto
{
Id = r.Id,
Title = r.Title,
ZNum = r.ZNum,
ImgUrl = r.ImgUrl
})
.ToListAsync();
// Process image URLs after query
foreach (var item in gaoJiangRaw)
{
item.ImgUrl = GetImageUrl(item.ImgUrl);
}
levelDto.GaoJiang = gaoJiangRaw;
// Check if user can claim this level's rewards
if ((user.OuQiLevel ?? 0) >= vipLevel.Level)
{
levelDto.IsLing = 1; // Can claim
}
else
{
levelDto.IsLing = 0; // Cannot claim
}
// Check if user has already claimed this level's rewards
var hasClaimed = await _dbContext.UserVipRewards
.AnyAsync(r => r.VipLevelId == vipLevel.Id
&& r.VipLevel == vipLevel.Level
&& r.UserId == userId
&& r.DeletedAt == null);
if (hasClaimed)
{
levelDto.IsLing = 2; // Already claimed
}
levelList.Add(levelDto);
}
response.LevelList = levelList;
// Get danye content (ids 1 and 2)
var danyeList = await _dbContext.Danyes
.Where(d => d.Id == 1 || d.Id == 2)
.Select(d => new DanyeDto
{
Id = d.Id,
Title = d.Title,
Content = d.Content
})
.ToListAsync();
response.DanyeList = danyeList;
return response;
}
/// <inheritdoc/>
public async Task<QuanYiLingResponse> ClaimQuanYiAsync(int userId, int levelId)
{
var user = await _dbContext.Users.FirstOrDefaultAsync(u => u.Id == userId);
if (user == null)
{
throw new InvalidOperationException("用户不存在");
}
var vipLevel = await _dbContext.VipLevels
.FirstOrDefaultAsync(v => v.Id == levelId && v.DeletedAt == null);
if (vipLevel == null)
{
throw new InvalidOperationException("数据错误");
}
if (vipLevel.Level > (user.OuQiLevel ?? 0))
{
throw new InvalidOperationException("暂未达到该等级");
}
// Check if rewards exist for this level
var hasRewards = await _dbContext.VipLevelRewards
.AnyAsync(r => r.VipLevelId == vipLevel.Id && r.DeletedAt == null);
if (!hasRewards)
{
throw new InvalidOperationException("奖品不存在");
}
// Note: PHP code allows re-claiming, so we don't check for existing claims
var rewards = new List<QuanYiLingRewardDto>();
using var transaction = await _dbContext.Database.BeginTransactionAsync();
try
{
// Process coupon rewards (type = 1)
var couponRewards = await _dbContext.VipLevelRewards
.Where(r => r.VipLevelId == vipLevel.Id && r.Type == 1 && r.DeletedAt == null)
.ToListAsync();
foreach (var reward in couponRewards)
{
var endTime = DateTime.UtcNow.AddDays(reward.EffectiveDay ?? 7);
// Add to response
rewards.Add(new QuanYiLingRewardDto
{
Id = reward.Id,
Title = reward.Title,
Type = 1,
JianPrice = reward.JianPrice,
ManPrice = reward.ManPrice,
EffectiveDay = reward.EffectiveDay,
EndTime = endTime.ToString("yyyy-MM-dd HH:mm:ss"),
ZNum = reward.ZNum
});
// Get coupon ttype
byte? ttype = null;
if (reward.CouponId.HasValue)
{
ttype = await _dbContext.Coupons
.Where(c => c.Id == reward.CouponId.Value)
.Select(c => c.Ttype)
.FirstOrDefaultAsync();
}
// Create coupon receives for the user
var zNum = reward.ZNum ?? 1;
for (int i = 0; i < zNum; i++)
{
var couponReceive = new CouponReceife
{
UserId = userId,
Title = reward.Title,
Price = reward.JianPrice ?? 0,
ManPrice = reward.ManPrice ?? 0,
EndTime = endTime,
CreatedAt = DateTime.UtcNow,
CouponId = reward.Id,
State = ttype ?? 0,
Status = 1,
Ttype = ttype ?? 0
};
_dbContext.CouponReceives.Add(couponReceive);
}
// Record user vip reward
var userVipReward = new UserVipReward
{
UserId = userId,
VipLevelId = vipLevel.Id,
VipLevel = vipLevel.Level,
CouponId = reward.CouponId,
Type = 1,
Title = reward.Title,
JianPrice = reward.JianPrice,
ManPrice = reward.ManPrice,
EffectiveDay = reward.EffectiveDay,
EndTime = endTime,
ZNum = reward.ZNum,
CreatedAt = DateTime.UtcNow,
UpdatedAt = DateTime.UtcNow
};
_dbContext.UserVipRewards.Add(userVipReward);
}
// Process prize rewards (type = 2) with probability
var prizeRewards = await _dbContext.VipLevelRewards
.Where(r => r.VipLevelId == vipLevel.Id && r.Type == 2 && r.Probability > 0 && r.DeletedAt == null)
.ToListAsync();
if (prizeRewards.Any())
{
// Build probability pool
var probabilityPool = new List<VipLevelReward>();
foreach (var prize in prizeRewards)
{
var count = (int)((prize.Probability ?? 0) * 100);
for (int i = 0; i < count; i++)
{
probabilityPool.Add(prize);
}
}
if (probabilityPool.Any())
{
// Shuffle and pick one
var random = new Random();
var shuffled = probabilityPool.OrderBy(x => random.Next()).ToList();
var selectedPrize = shuffled[random.Next(shuffled.Count)];
// Add to response
rewards.Add(new QuanYiLingRewardDto
{
Id = selectedPrize.Id,
Title = selectedPrize.Title,
Type = 2,
ImgUrl = GetImageUrl(selectedPrize.ImgUrl),
ShangId = selectedPrize.PrizeLevelId
});
// Create order for the prize
var orderNum = GenerateOrderNo("MH_");
var order = new Order
{
UserId = userId,
OrderNum = orderNum,
OrderTotal = 0,
OrderZheTotal = 0,
Price = 0,
UseMoney = 0,
UseIntegral = 0,
UseScore = 0,
Zhe = 0,
GoodsId = 0,
Num = 0,
GoodsPrice = 0,
GoodsTitle = string.Empty,
GoodsImgurl = string.Empty,
PrizeNum = 0,
Status = 0,
PayType = 0,
OrderType = 10,
CreatedAt = DateTime.UtcNow
};
_dbContext.Orders.Add(order);
await _dbContext.SaveChangesAsync();
// Create order item
var orderItem = new OrderItem
{
OrderId = 0,
UserId = userId,
Status = 0,
GoodsId = 0,
Num = 0,
ShangId = selectedPrize.PrizeLevelId ?? 0,
GoodslistId = 0,
GoodslistTitle = selectedPrize.Title,
GoodslistImgurl = selectedPrize.ImgUrl ?? string.Empty,
GoodslistPrice = selectedPrize.JiangPrice ?? 0,
GoodslistMoney = selectedPrize.Money ?? 0,
GoodslistType = 1,
CreatedAt = DateTime.UtcNow,
PrizeCode = selectedPrize.PrizeCode ?? string.Empty,
OrderType = 10,
LuckNo = 0
};
_dbContext.OrderItems.Add(orderItem);
// Record user vip reward for prize
var userVipReward = new UserVipReward
{
UserId = userId,
VipLevelId = vipLevel.Id,
VipLevel = vipLevel.Level,
Type = 2,
Title = selectedPrize.Title,
ImgUrl = selectedPrize.ImgUrl,
PrizeLevelId = selectedPrize.PrizeLevelId,
JiangPrice = selectedPrize.JiangPrice,
Money = selectedPrize.Money,
ScMoney = selectedPrize.ScMoney,
PrizeCode = selectedPrize.PrizeCode,
Probability = selectedPrize.Probability,
CreatedAt = DateTime.UtcNow,
UpdatedAt = DateTime.UtcNow
};
_dbContext.UserVipRewards.Add(userVipReward);
}
}
await _dbContext.SaveChangesAsync();
await transaction.CommitAsync();
_logger.LogInformation("User {UserId} claimed VIP level {LevelId} rewards", userId, levelId);
return new QuanYiLingResponse(rewards);
}
catch (Exception ex)
{
await transaction.RollbackAsync();
_logger.LogError(ex, "Failed to claim VIP rewards for user {UserId}, level {LevelId}", userId, levelId);
throw new InvalidOperationException("网络故障,请稍后再试");
}
}
/// <summary>
/// Get image URL with base URL
/// </summary>
private string GetImageUrl(string? path)
{
if (string.IsNullOrWhiteSpace(path))
return string.Empty;
if (path.StartsWith("http://") || path.StartsWith("https://"))
return path;
return $"{_imageBaseUrl}/{path.TrimStart('/')}";
}
/// <summary>
/// Get QuanYi level info based on ou_qi_level and ou_qi
/// </summary>
private QuanYiLevelInfo GetQuanYiLevelInfo(int ouQiLevel, int ouQi, List<VipLevel> vipLevelConfigs)
{
// 从数据库配置构建等级阈值
var levelThresholds = vipLevelConfigs
.ToDictionary(v => v.Level, v => v.Number);
// 构建等级标题
var levelTitles = vipLevelConfigs
.ToDictionary(v => v.Level, v => v.Title ?? $"等级{v.Level}");
// 如果没有配置,使用默认值
if (!levelThresholds.Any())
{
levelThresholds = new Dictionary<int, int>
{
{ 0, 0 },
{ 1, 100 },
{ 2, 500 },
{ 3, 1000 },
{ 4, 3000 },
{ 5, 6000 },
{ 6, 10000 },
{ 7, 20000 }
};
levelTitles = new Dictionary<int, string>
{
{ 0, "普通" },
{ 1, "青铜" },
{ 2, "白银" },
{ 3, "黄金" },
{ 4, "铂金" },
{ 5, "钻石" },
{ 6, "星耀" },
{ 7, "王者" }
};
}
var title = levelTitles.GetValueOrDefault(ouQiLevel, "普通");
var nextLevel = ouQiLevel + 1;
var nextOuQi = levelThresholds.GetValueOrDefault(nextLevel, 0);
var currentLevelOuQi = levelThresholds.GetValueOrDefault(ouQiLevel, 0);
var maxLevel = levelThresholds.Keys.DefaultIfEmpty(0).Max();
// 计算差值和进度
int cha;
int jindu;
if (nextLevel > maxLevel || nextOuQi == 0)
{
// 已满级
cha = -1;
jindu = 100;
}
else
{
cha = Math.Max(0, nextOuQi - ouQi);
// 计算当前等级内的进度
var levelRange = nextOuQi - currentLevelOuQi;
var progress = ouQi - currentLevelOuQi;
jindu = levelRange > 0 ? Math.Min(100, Math.Max(0, progress * 100 / levelRange)) : 0;
}
return new QuanYiLevelInfo
{
Level = ouQiLevel,
Title = title,
OuQi = ouQi,
NextOuQi = nextOuQi,
Cha = cha,
Jindu = jindu
};
}
/// <summary>
/// Generate order number
/// </summary>
private string GenerateOrderNo(string prefix)
{
var timestamp = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds();
var random = new Random().Next(1000, 9999);
return $"{prefix}{timestamp}{random}";
}
}