Files
squad-rain-ops-mini/Services/CdkService.cs

218 lines
9.1 KiB
C#
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using RainOpsMini.Helpers;
using RainOpsMini.Models;
using RainOpsMini.Models;
using SqlSugar;
namespace RainOpsMini.Services
{
public class CdkService
{
public static async Task<List<CdkInfo>> GenerateCdksAsync(string type, int duration, int count, string reason, string creator)
{
var list = new List<CdkInfo>();
using var db = DbHelper.GetInstance();
string batchId = Guid.NewGuid().ToString("N");
for (int i = 0; i < count; i++)
{
var cdk = new CdkInfo
{
Code = "CDK" + GenerateRandomCode(13), // Total 16 chars
Type = type,
DurationDays = duration,
Reason = reason,
Creator = creator,
Status = "Unused",
CreatedAt = DateTime.UtcNow,
BatchId = batchId
};
list.Add(cdk);
}
await db.Insertable(list).ExecuteCommandAsync();
return list;
}
public static async Task<(bool success, string message)> RedeemCdkAsync(string code, string steamId)
{
using var db = DbHelper.GetInstance();
// 1. Find CDK
var cdk = await db.Queryable<CdkInfo>().FirstAsync(c => c.Code == code);
if (cdk == null) return (false, "无效的兑换码");
if (cdk.Status != "Unused") return (false, "该兑换码已被使用");
// 2. Validate SteamID (Simple check)
if (string.IsNullOrWhiteSpace(steamId) || steamId.Length < 17) return (false, "无效的 SteamID");
// 3. Check Batch Limit (One redemption per batch per user)
if (!string.IsNullOrEmpty(cdk.BatchId))
{
var alreadyRedeemed = await db.Queryable<CdkInfo>()
.Where(c => c.BatchId == cdk.BatchId && c.RedeemedBy == steamId)
.AnyAsync();
if (alreadyRedeemed)
{
return (false, "您已领取过该批次的兑换码,无法重复领取");
}
}
// 4. Update CDK status
cdk.Status = "Used";
cdk.RedeemedBy = steamId;
cdk.RedeemedAt = DateTime.UtcNow;
await db.Updateable(cdk).ExecuteCommandAsync();
// 5. Grant VIP/SVIP
// Map type to internal group name
bool isRedeemingSvip = cdk.Type == "SVIP";
var existingVip = VipStorage.GetBySteamId(steamId);
// Initializing/Migrating Expiries
DateTime? currentVipExpiry = null;
DateTime? currentSvipExpiry = null;
string finalGroup = "";
DateTime finalExpiry = DateTime.MinValue;
if (existingVip != null)
{
// Migrate legacy data if needed
if (existingVip.VipExpiryDate == null && existingVip.SvipExpiryDate == null)
{
if (existingVip.Group == "SVIP_RainOpsMini")
{
existingVip.SvipExpiryDate = existingVip.ExpiryDate;
// SVIP usually implies VIP privileges, but let's keep them somewhat distinct or
// initialize VIP expiry to same as SVIP if we consider SVIP > VIP.
// For independent tracking requested by user, if they were SVIP, we only set SVIP expiry.
// But if they downgrade, they might have lost VIP.
// Let's assume SVIP covers VIP, so we might set VIP expiry too?
// User said: "我是SVIP身份但是我兑换VIP的CDK也会延长SVIP的时长要分开独立计算"
// This implies they want to accumulate VIP time even while SVIP.
// So if we are migrating an SVIP user, we probably should set VIP expiry to same date?
// Or maybe leave VIP as null (expired/inactive)?
// If we leave VIP null, and they redeem VIP, they get VIP time.
// If their SVIP expires, they fall back to VIP.
// Let's set SvipExpiryDate = ExpiryDate. VipExpiryDate = null (or maybe ExpiryDate if we want to be generous).
// Let's be strict: SVIP implies SVIP expiry.
}
else if (existingVip.Group == "VIP_RainOpsMini")
{
existingVip.VipExpiryDate = existingVip.ExpiryDate;
}
}
currentVipExpiry = existingVip.VipExpiryDate;
currentSvipExpiry = existingVip.SvipExpiryDate;
}
// Calculate new expiry
if (isRedeemingSvip)
{
// Update SVIP Expiry
DateTime baseTime = (currentSvipExpiry.HasValue && currentSvipExpiry.Value > DateTime.Now) ? currentSvipExpiry.Value : DateTime.Now;
currentSvipExpiry = baseTime.AddDays(cdk.DurationDays);
}
else
{
// Update VIP Expiry
DateTime baseTime = (currentVipExpiry.HasValue && currentVipExpiry.Value > DateTime.Now) ? currentVipExpiry.Value : DateTime.Now;
currentVipExpiry = baseTime.AddDays(cdk.DurationDays);
}
// Determine Effective Group
// Rule: SVIP > VIP > None
bool hasActiveSvip = currentSvipExpiry.HasValue && currentSvipExpiry.Value > DateTime.Now;
bool hasActiveVip = currentVipExpiry.HasValue && currentVipExpiry.Value > DateTime.Now;
if (hasActiveSvip)
{
finalGroup = "SVIP_RainOpsMini";
finalExpiry = currentSvipExpiry.Value;
}
else if (hasActiveVip)
{
finalGroup = "VIP_RainOpsMini";
finalExpiry = currentVipExpiry.Value;
}
else
{
// Both expired? Or just newly added but expired (unlikely with duration > 0)
// If redeeming valid CDK, at least one should be active.
// Fallback
finalGroup = isRedeemingSvip ? "SVIP_RainOpsMini" : "VIP_RainOpsMini";
finalExpiry = DateTime.Now.AddDays(cdk.DurationDays); // Should not happen
}
if (existingVip != null)
{
existingVip.Group = finalGroup;
existingVip.ExpiryDate = finalExpiry;
existingVip.VipExpiryDate = currentVipExpiry;
existingVip.SvipExpiryDate = currentSvipExpiry;
existingVip.Remark = $"CDK: {cdk.Reason}";
VipStorage.Save();
}
else
{
// New User
var newVip = new VipMember
{
SteamId = steamId,
Name = "Redeemed User",
Group = finalGroup,
ExpiryDate = finalExpiry,
VipExpiryDate = currentVipExpiry,
SvipExpiryDate = currentSvipExpiry,
Remark = $"CDK: {cdk.Reason}",
CreatedAt = DateTime.Now
};
try
{
var players = Program.RconCache?.Players?.ToList();
if (players != null)
{
var player = players.FirstOrDefault(p => p.SteamId == steamId);
if (player != null) newVip.Name = player.Name;
}
}
catch {}
VipStorage.Add(newVip);
}
// Sync to config
try
{
string cfgPath = System.IO.Path.Combine(Environment.CurrentDirectory, "SquadGame", "ServerConfig", "Admins.cfg");
VipStorage.SyncToAdminConfig(cfgPath);
}
catch (Exception ex)
{
RainOpsLog.Log($"[CDK] Sync Admin Config Failed: {ex.Message}");
}
string svipMsg = hasActiveSvip ? $"{currentSvipExpiry:yyyy-MM-dd HH:mm}" : (currentSvipExpiry.HasValue ? "已过期" : "未开通");
string vipMsg = hasActiveVip ? $"{currentVipExpiry:yyyy-MM-dd HH:mm}" : (currentVipExpiry.HasValue ? "已过期" : "未开通");
string groupDisplay = finalGroup == "SVIP_RainOpsMini" ? "SVIP" : "VIP";
return (true, $"兑换成功!\n当前生效{groupDisplay}\nSVIP到期{svipMsg}\nVIP 到期:{vipMsg}");
}
private static string GenerateRandomCode(int length)
{
const string chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
var random = new Random();
return new string(Enumerable.Repeat(chars, length)
.Select(s => s[random.Next(s.Length)]).ToArray());
}
}
}