mirror of
https://gitee.com/xiarenalofs/squad-rain-ops-mini.git
synced 2026-08-05 13:05:52 +08:00
218 lines
9.1 KiB
C#
218 lines
9.1 KiB
C#
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());
|
||
}
|
||
}
|
||
}
|