465 lines
16 KiB
C#
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}";
|
|
}
|
|
}
|