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; /// /// 权益服务实现 /// public class QuanYiService : IQuanYiService { private readonly HoneyBoxDbContext _dbContext; private readonly ILogger _logger; private readonly string _imageBaseUrl; public QuanYiService(HoneyBoxDbContext dbContext, ILogger logger) { _dbContext = dbContext; _logger = logger; _imageBaseUrl = "https://example.com"; // TODO: Configure from appsettings } /// public async Task 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(); 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; } /// public async Task 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(); 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(); 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("网络故障,请稍后再试"); } } /// /// Get image URL with base URL /// 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('/')}"; } /// /// Get QuanYi level info based on ou_qi_level and ou_qi /// private QuanYiLevelInfo GetQuanYiLevelInfo(int ouQiLevel, int ouQi, List 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 { { 0, 0 }, { 1, 100 }, { 2, 500 }, { 3, 1000 }, { 4, 3000 }, { 5, 6000 }, { 6, 10000 }, { 7, 20000 } }; levelTitles = new Dictionary { { 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 }; } /// /// Generate order number /// private string GenerateOrderNo(string prefix) { var timestamp = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds(); var random = new Random().Next(1000, 9999); return $"{prefix}{timestamp}{random}"; } }