Skip to content

Commit

Permalink
feat: use Dictionary for scoreboard item cache
Browse files Browse the repository at this point in the history
  • Loading branch information
GZTimeWalker committed Mar 31, 2024
1 parent 43d2154 commit 3323562
Show file tree
Hide file tree
Showing 15 changed files with 93 additions and 60 deletions.
2 changes: 1 addition & 1 deletion src/GZCTF/Controllers/AdminController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -204,7 +204,7 @@ public async Task<IActionResult> SearchUsers([FromQuery] string hint, Cancellati
{
var loweredHint = hint.ToLower();
return Ok((await userManager.Users.Where(item =>
item.UserName!.ToLower().Contains(loweredHint) ||
item.UserName!.ToLower().Contains(loweredHint) ||
item.StdNumber.ToLower().Contains(loweredHint) ||
item.Email!.ToLower().Contains(loweredHint) ||
item.PhoneNumber!.ToLower().Contains(loweredHint) ||
Expand Down
31 changes: 17 additions & 14 deletions src/GZCTF/Controllers/GameController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -533,7 +533,7 @@ public async Task<IActionResult> ChallengesWithTeamInfo([FromRoute] int id, Canc

ScoreboardModel scoreboard = await gameRepository.GetScoreboard(context.Game!, token);

ScoreboardItem? boardItem = scoreboard.Items.FirstOrDefault(i => i.Id == context.Participation!.TeamId);
ScoreboardItem? boardItem = scoreboard.Items[context.Participation!.TeamId];

// make sure team info is not null
boardItem ??= new ScoreboardItem
Expand Down Expand Up @@ -921,7 +921,8 @@ public async Task<IActionResult> SubmitWriteup([FromRoute] int id, IFormFile fil

if (DateTimeOffset.UtcNow - instance.LastContainerOperation < TimeSpan.FromSeconds(10))
return new JsonResult(new RequestResponse(localizer[nameof(Resources.Program.Game_OperationTooFrequent)],
StatusCodes.Status429TooManyRequests)) { StatusCode = StatusCodes.Status429TooManyRequests };
StatusCodes.Status429TooManyRequests))
{ StatusCode = StatusCodes.Status429TooManyRequests };

if (instance.Container is not null)
{
Expand All @@ -934,15 +935,15 @@ public async Task<IActionResult> SubmitWriteup([FromRoute] int id, IFormFile fil

return await gameInstanceRepository.CreateContainer(instance, context.Participation!.Team, context.User!,
context.Game!.ContainerCountLimit, token) switch
{
null or (TaskStatus.Failed, null) => BadRequest(
new RequestResponse(localizer[nameof(Resources.Program.Game_ContainerCreationFailed)])),
(TaskStatus.Denied, null) => BadRequest(
new RequestResponse(localizer[nameof(Resources.Program.Game_ContainerNumberLimitExceeded),
context.Game.ContainerCountLimit])),
(TaskStatus.Success, var x) => Ok(ContainerInfoModel.FromContainer(x!)),
_ => throw new UnreachableException()
};
{
null or (TaskStatus.Failed, null) => BadRequest(
new RequestResponse(localizer[nameof(Resources.Program.Game_ContainerCreationFailed)])),
(TaskStatus.Denied, null) => BadRequest(
new RequestResponse(localizer[nameof(Resources.Program.Game_ContainerNumberLimitExceeded),
context.Game.ContainerCountLimit])),
(TaskStatus.Success, var x) => Ok(ContainerInfoModel.FromContainer(x!)),
_ => throw new UnreachableException()
};
}

/// <summary>
Expand Down Expand Up @@ -1035,7 +1036,8 @@ public async Task<IActionResult> SubmitWriteup([FromRoute] int id, IFormFile fil

if (DateTimeOffset.UtcNow - instance.LastContainerOperation < TimeSpan.FromSeconds(10))
return new JsonResult(new RequestResponse(localizer[nameof(Resources.Program.Game_OperationTooFrequent)],
StatusCodes.Status429TooManyRequests)) { StatusCode = StatusCodes.Status429TooManyRequests };
StatusCodes.Status429TooManyRequests))
{ StatusCode = StatusCodes.Status429TooManyRequests };

var destroyId = instance.Container.ContainerId;

Expand Down Expand Up @@ -1068,7 +1070,8 @@ public async Task<IActionResult> SubmitWriteup([FromRoute] int id, IFormFile fil
{
ContextInfo res = new()
{
User = await userManager.GetUserAsync(User), Game = await gameRepository.GetGameById(id, token)
User = await userManager.GetUserAsync(User),
Game = await gameRepository.GetGameById(id, token)
};

if (res.Game is null)
Expand Down Expand Up @@ -1139,4 +1142,4 @@ public ContextInfo WithResult(IActionResult res)
return this;
}
}
}
}
3 changes: 2 additions & 1 deletion src/GZCTF/Controllers/ProxyController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -173,7 +173,8 @@ public async Task<IActionResult> ProxyForNoInstance(Guid id, CancellationToken t
TaskStatus.Failed, LogLevel.Debug);
return new JsonResult(new RequestResponse(
localizer[nameof(Resources.Program.Proxy_ContainerConnectionFailed), e.SocketErrorCode],
StatusCodes.Status418ImATeapot)) { StatusCode = StatusCodes.Status418ImATeapot };
StatusCodes.Status418ImATeapot))
{ StatusCode = StatusCodes.Status418ImATeapot };
}

using WebSocket ws = await HttpContext.WebSockets.AcceptWebSocketAsync();
Expand Down
21 changes: 14 additions & 7 deletions src/GZCTF/Controllers/TeamController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -144,7 +144,8 @@ public async Task<IActionResult> CreateTeam([FromBody] TeamUpdateModel model, Ca

if (team.CaptainId != user!.Id)
return new JsonResult(new RequestResponse(localizer[nameof(Resources.Program.Auth_AccessForbidden)],
StatusCodes.Status403Forbidden)) { StatusCode = StatusCodes.Status403Forbidden };
StatusCodes.Status403Forbidden))
{ StatusCode = StatusCodes.Status403Forbidden };

team.UpdateInfo(model);

Expand Down Expand Up @@ -183,7 +184,8 @@ public async Task<IActionResult> CreateTeam([FromBody] TeamUpdateModel model, Ca

if (team.CaptainId != user!.Id)
return new JsonResult(new RequestResponse(localizer[nameof(Resources.Program.Auth_AccessForbidden)],
StatusCodes.Status403Forbidden)) { StatusCode = StatusCodes.Status403Forbidden };
StatusCodes.Status403Forbidden))
{ StatusCode = StatusCodes.Status403Forbidden };

if (team.Locked && await teamRepository.AnyActiveGame(team, token))
return BadRequest(new RequestResponse(localizer[nameof(Resources.Program.Team_Locked)]));
Expand Down Expand Up @@ -231,7 +233,8 @@ public async Task<IActionResult> InviteCode([FromRoute] int id, CancellationToke

if (team.CaptainId != user!.Id)
return new JsonResult(new RequestResponse(localizer[nameof(Resources.Program.Auth_AccessForbidden)],
StatusCodes.Status403Forbidden)) { StatusCode = StatusCodes.Status403Forbidden };
StatusCodes.Status403Forbidden))
{ StatusCode = StatusCodes.Status403Forbidden };

return Ok(team.InviteCode);
}
Expand Down Expand Up @@ -264,7 +267,8 @@ public async Task<IActionResult> UpdateInviteToken([FromRoute] int id, Cancellat

if (team.CaptainId != user!.Id)
return new JsonResult(new RequestResponse(localizer[nameof(Resources.Program.Auth_AccessForbidden)],
StatusCodes.Status403Forbidden)) { StatusCode = StatusCodes.Status403Forbidden };
StatusCodes.Status403Forbidden))
{ StatusCode = StatusCodes.Status403Forbidden };

team.UpdateInviteToken();

Expand Down Expand Up @@ -302,7 +306,8 @@ public async Task<IActionResult> KickUser([FromRoute] int id, [FromRoute] Guid u

if (team.CaptainId != user!.Id)
return new JsonResult(new RequestResponse(localizer[nameof(Resources.Program.Auth_AccessForbidden)],
StatusCodes.Status403Forbidden)) { StatusCode = StatusCodes.Status403Forbidden };
StatusCodes.Status403Forbidden))
{ StatusCode = StatusCodes.Status403Forbidden };

IDbContextTransaction trans = await teamRepository.BeginTransactionAsync(token);

Expand Down Expand Up @@ -489,7 +494,8 @@ public async Task<IActionResult> Avatar([FromRoute] int id, IFormFile file, Canc

if (team.CaptainId != user!.Id)
return new JsonResult(new RequestResponse(localizer[nameof(Resources.Program.Auth_AccessForbidden)],
StatusCodes.Status403Forbidden)) { StatusCode = StatusCodes.Status403Forbidden };
StatusCodes.Status403Forbidden))
{ StatusCode = StatusCodes.Status403Forbidden };

if (file.Length == 0)
return BadRequest(new RequestResponse(localizer[nameof(Resources.Program.File_SizeZero)]));
Expand Down Expand Up @@ -541,7 +547,8 @@ public async Task<IActionResult> DeleteTeam(int id, CancellationToken token)

if (team.CaptainId != user!.Id)
return new JsonResult(new RequestResponse(localizer[nameof(Resources.Program.Auth_AccessForbidden)],
StatusCodes.Status403Forbidden)) { StatusCode = StatusCodes.Status403Forbidden };
StatusCodes.Status403Forbidden))
{ StatusCode = StatusCodes.Status403Forbidden };

if (team.Locked && await teamRepository.AnyActiveGame(team, token))
return BadRequest(new RequestResponse(localizer[nameof(Resources.Program.Team_Locked)]));
Expand Down
4 changes: 3 additions & 1 deletion src/GZCTF/Extensions/CaptchaExtension.cs
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,9 @@ public sealed class CloudflareTurnstile(IOptions<CaptchaConfig>? options) : Capt

TurnstileRequestModel req = new()
{
Secret = Config.SecretKey, Response = model.Challenge, RemoteIp = ip.ToString()
Secret = Config.SecretKey,
Response = model.Challenge,
RemoteIp = ip.ToString()
};

const string api = "https://challenges.cloudflare.com/turnstile/v0/siteverify";
Expand Down
5 changes: 4 additions & 1 deletion src/GZCTF/Models/Data/GameEvent.cs
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,10 @@ public class GameEvent : FormattableData<EventType>
Type = EventType.FlagSubmit,
Values =
[
ans.ToString(), submission.Answer, submission.GameChallenge.Title, submission.ChallengeId.ToString()
ans.ToString(),
submission.Answer,
submission.GameChallenge.Title,
submission.ChallengeId.ToString()
]
};
}
12 changes: 10 additions & 2 deletions src/GZCTF/Models/Request/Game/ScoreboardModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,15 @@ public partial class ScoreboardModel
/// <summary>
/// 队伍信息
/// </summary>
public IEnumerable<ScoreboardItem> Items { get; set; } = default!;
[JsonIgnore]
public Dictionary<int, ScoreboardItem> Items { get; set; } = default!;

/// <summary>
/// 队伍信息列表
/// </summary>
[MemoryPackIgnore]
[JsonPropertyName("items")]
public IEnumerable<ScoreboardItem> ItemList => Items.Values;

/// <summary>
/// 题目信息
Expand Down Expand Up @@ -226,4 +234,4 @@ public partial class Blood
/// 获得此血的时间
/// </summary>
public DateTimeOffset? SubmitTimeUtc { get; set; }
}
}
10 changes: 6 additions & 4 deletions src/GZCTF/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -403,15 +403,17 @@ public static IActionResult InvalidModelStateHandler(ActionContext context)
return new JsonResult(
new RequestResponse(errors is [_, ..]
? errors
: localizer[nameof(Resources.Program.Model_ValidationFailed)])) { StatusCode = 400 };
: localizer[nameof(Resources.Program.Model_ValidationFailed)]))
{ StatusCode = 400 };

errors = (from val in context.ModelState.Values
where val.Errors.Count > 0
select val.Errors.FirstOrDefault()?.ErrorMessage).FirstOrDefault();
where val.Errors.Count > 0
select val.Errors.FirstOrDefault()?.ErrorMessage).FirstOrDefault();

return new JsonResult(new RequestResponse(errors is [_, ..]
? errors
: localizer[nameof(Resources.Program.Model_ValidationFailed)])) { StatusCode = 400 };
: localizer[nameof(Resources.Program.Model_ValidationFailed)]))
{ StatusCode = 400 };
}
}
}
6 changes: 3 additions & 3 deletions src/GZCTF/Providers/EntityConfigurationProvider.cs
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ async Task WatchDatabase(CancellationToken token)
try
{
await Task.Delay(source.PollingInterval, token);
IDictionary<string, string?> actualData = await GetDataAsync(token);
Dictionary<string, string?> actualData = await GetDataAsync(token);

var computedHash = ConfigHash(actualData);
if (!computedHash.SequenceEqual(_lastHash))
Expand All @@ -69,7 +69,7 @@ AppDbContext CreateAppDbContext()
return new AppDbContext(builder.Options);
}

async Task<IDictionary<string, string?>> GetDataAsync(CancellationToken token = default)
async Task<Dictionary<string, string?>> GetDataAsync(CancellationToken token = default)
{
AppDbContext context = CreateAppDbContext();
return await context.Configs.ToDictionaryAsync(c => c.ConfigKey, c => c.Value,
Expand Down Expand Up @@ -118,4 +118,4 @@ public override void Load()
CancellationToken cancellationToken = _cancellationTokenSource.Token;
_databaseWatcher = Task.Run(() => WatchDatabase(cancellationToken), cancellationToken);
}
}
}
21 changes: 11 additions & 10 deletions src/GZCTF/Repositories/GameRepository.cs
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ public async Task<ScoreboardModel> GetScoreboardWithMembers(Game game, Cancellat
// In most cases, we can get the scoreboard from the cache
ScoreboardModel scoreboard = await GetScoreboard(game, token);

foreach (ScoreboardItem item in scoreboard.Items)
foreach (ScoreboardItem item in scoreboard.Items.Values)
item.TeamInfo = await teamRepository.GetTeamById(item.Id, token);

return scoreboard;
Expand Down Expand Up @@ -148,13 +148,13 @@ record Data(GameInstance GameInstance, Submission? Submission);
public async Task<ScoreboardModel> GenScoreboard(Game game, CancellationToken token = default)
{
Data[] data = await FetchData(game, token);
IDictionary<int, Blood?[]> bloods = GenBloods(data);
ScoreboardItem[] items = GenScoreboardItems(data, game, bloods);
Dictionary<int, Blood?[]> bloods = GenBloods(data);
Dictionary<int, ScoreboardItem> items = GenScoreboardItems(data, game, bloods);
return new()
{
Challenges = GenChallenges(data, bloods),
Items = items,
TimeLines = GenTopTimeLines(items, game),
TimeLines = GenTopTimeLines(items.Values, game),
BloodBonusValue = game.BloodBonus.Val
};
}
Expand All @@ -176,7 +176,7 @@ public async Task<ScoreboardModel> GenScoreboard(Game game, CancellationToken to
(j, s) => new Data(j.Instance, s)
).AsSplitQuery().ToArrayAsync(token);

static IDictionary<int, Blood?[]> GenBloods(Data[] data) =>
static Dictionary<int, Blood?[]> GenBloods(Data[] data) =>
data.GroupBy(j => j.GameInstance.Challenge).Select(g => new
{
g.Key,
Expand All @@ -203,7 +203,7 @@ public async Task<ScoreboardModel> GenScoreboard(Game game, CancellationToken to
}).ToDictionary(a => a.Key.Id, a => a.Value);

static Dictionary<ChallengeTag, IEnumerable<ChallengeInfo>> GenChallenges(Data[] data,
IDictionary<int, Blood?[]> bloods) =>
Dictionary<int, Blood?[]> bloods) =>
data.GroupBy(g => g.GameInstance.Challenge)
.Select(c => new ChallengeInfo
{
Expand All @@ -217,7 +217,7 @@ public async Task<ScoreboardModel> GenScoreboard(Game game, CancellationToken to
.OrderBy(i => i.Key)
.ToDictionary(c => c.Key, c => c.AsEnumerable());

static ScoreboardItem[] GenScoreboardItems(Data[] data, Game game, IDictionary<int, Blood?[]> bloods)
static Dictionary<int, ScoreboardItem> GenScoreboardItems(Data[] data, Game game, Dictionary<int, Blood?[]> bloods)
{
Dictionary<string, int> ranks = [];
return data.GroupBy(j => j.GameInstance.Participation)
Expand Down Expand Up @@ -310,7 +310,7 @@ static ScoreboardItem[] GenScoreboardItems(Data[] data, Game game, IDictionary<i
}
return j;
}).ToArray();
}).ToDictionary(d => d.Id);
}

static Dictionary<string, IEnumerable<TopTimeLine>> GenTopTimeLines(IEnumerable<ScoreboardItem> items, Game game)
Expand Down Expand Up @@ -342,7 +342,8 @@ static IEnumerable<TimeLine> GenTimeLine(IEnumerable<ChallengeItem> items)
score += i.Score;
return new TimeLine
{
Score = score, Time = i.SubmitTimeUtc!.Value // 此处不为 null
Score = score,
Time = i.SubmitTimeUtc!.Value // 此处不为 null
};
});
}
Expand Down Expand Up @@ -377,4 +378,4 @@ public async Task<byte[]> Handler(AsyncServiceScope scope, CacheRequest request,
public static CacheRequest MakeCacheRequest(int id) =>
new(Services.Cache.CacheKey.ScoreBoardBase,
new() { AbsoluteExpirationRelativeToNow = TimeSpan.FromDays(14) }, id.ToString());
}
}
3 changes: 2 additions & 1 deletion src/GZCTF/Services/Container/Manager/KubernetesManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,8 @@ public class KubernetesManager : IContainerManager
},
Requests = new Dictionary<string, ResourceQuantity>
{
["cpu"] = new("10m"), ["memory"] = new("32Mi")
["cpu"] = new("10m"),
["memory"] = new("32Mi")
}
}
}
Expand Down
8 changes: 5 additions & 3 deletions src/GZCTF/Services/Container/Manager/SwarmManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ public async Task DestroyContainerAsync(Models.Data.Container container, Cancell
ServiceCreateParameters parameters = GetServiceCreateParameters(config);
var retry = 0;
ServiceCreateResponse? serviceRes;
CreateContainer:
CreateContainer:
try
{
serviceRes = await _client.Swarm.CreateServiceAsync(parameters, token);
Expand Down Expand Up @@ -164,7 +164,8 @@ public async Task DestroyContainerAsync(Models.Data.Container container, Cancell
Labels =
new Dictionary<string, string>
{
["TeamId"] = config.TeamId, ["UserId"] = config.UserId.ToString()
["TeamId"] = config.TeamId,
["UserId"] = config.UserId.ToString()
},
Mode = new() { Replicated = new() { Replicas = 1 } },
TaskTemplate = new()
Expand Down Expand Up @@ -194,7 +195,8 @@ public async Task DestroyContainerAsync(Models.Data.Container container, Cancell
[
new()
{
PublishMode = _meta.ExposePort ? "global" : "vip", TargetPort = (uint)config.ExposedPort
PublishMode = _meta.ExposePort ? "global" : "vip",
TargetPort = (uint)config.ExposedPort
}
]
}
Expand Down
3 changes: 2 additions & 1 deletion src/GZCTF/Services/Container/Provider/KubernetesProvider.cs
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,8 @@ void InitKubernetes(bool withAuth, RegistryConfig? registry)
{
IpBlock = new()
{
Cidr = "0.0.0.0/0", Except = _kubernetesMetadata.Config.AllowCidr ?? ["10.0.0.0/8"]
Cidr = "0.0.0.0/0",
Except = _kubernetesMetadata.Config.AllowCidr ?? ["10.0.0.0/8"]
}
}
]
Expand Down

0 comments on commit 3323562

Please sign in to comment.