Skip to content

Commit

Permalink
fix: independent cache helper from repository
Browse files Browse the repository at this point in the history
  • Loading branch information
GZTimeWalker committed Aug 19, 2023
1 parent 47b84a7 commit 785c017
Show file tree
Hide file tree
Showing 13 changed files with 206 additions and 124 deletions.
43 changes: 38 additions & 5 deletions src/GZCTF/Controllers/EditController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
using GZCTF.Models.Request.Game;
using GZCTF.Models.Request.Info;
using GZCTF.Repositories.Interface;
using GZCTF.Services;
using GZCTF.Services.Interface;
using GZCTF.Utils;
using Microsoft.AspNetCore.Identity;
Expand All @@ -24,6 +25,7 @@ namespace GZCTF.Controllers;
public class EditController : Controller
{
private readonly ILogger<EditController> _logger;
private readonly CacheHelper _cacheHelper;
private readonly UserManager<UserInfo> _userManager;
private readonly IPostRepository _postRepository;
private readonly IGameNoticeRepository _gameNoticeRepository;
Expand All @@ -33,7 +35,9 @@ public class EditController : Controller
private readonly IContainerManager _containerService;
private readonly IContainerRepository _containerRepository;

public EditController(UserManager<UserInfo> userManager,
public EditController(
CacheHelper cacheHelper,
UserManager<UserInfo> userManager,
ILogger<EditController> logger,
IPostRepository postRepository,
IContainerRepository containerRepository,
Expand All @@ -44,6 +48,7 @@ public EditController(UserManager<UserInfo> userManager,
IFileRepository fileService)
{
_logger = logger;
_cacheHelper = cacheHelper;
_fileService = fileService;
_userManager = userManager;
_gameRepository = gameRepository;
Expand Down Expand Up @@ -235,7 +240,7 @@ public async Task<IActionResult> UpdateGame([FromRoute] int id, [FromBody] GameI
game.Update(model);
await _gameRepository.SaveAsync(token);
_gameRepository.FlushGameInfoCache();
await _gameRepository.FlushScoreboardCache(game.Id, token);
await _cacheHelper.FlushScoreboardCache(game.Id, token);

return Ok(GameInfoModel.FromGame(game));
}
Expand All @@ -251,6 +256,7 @@ public async Task<IActionResult> UpdateGame([FromRoute] int id, [FromBody] GameI
/// <response code="200">成功删除比赛</response>
[HttpDelete("Games/{id}")]
[ProducesResponseType(typeof(GameInfoModel), StatusCodes.Status200OK)]
[ProducesResponseType(typeof(RequestResponse), StatusCodes.Status400BadRequest)]
[ProducesResponseType(typeof(RequestResponse), StatusCodes.Status404NotFound)]
public async Task<IActionResult> DeleteGame([FromRoute] int id, CancellationToken token)
{
Expand All @@ -259,7 +265,34 @@ public async Task<IActionResult> DeleteGame([FromRoute] int id, CancellationToke
if (game is null)
return NotFound(new RequestResponse("比赛未找到", 404));

await _gameRepository.DeleteGame(game, token);
return await _gameRepository.DeleteGame(game, token) switch
{
TaskStatus.Success => Ok(),
TaskStatus.Failed => BadRequest(new RequestResponse("比赛删除失败,文件可能已受损,请重试")),
_ => throw new NotImplementedException()
};
}

/// <summary>
/// 删除比赛的全部 WriteUp
/// </summary>
/// <remarks>
/// 删除比赛的全部 WriteUp,需要管理员权限
/// </remarks>
/// <param name="id"></param>
/// <param name="token"></param>
/// <response code="200">成功删除比赛 WriteUps</response>
[HttpDelete("Games/{id}/WriteUps")]
[ProducesResponseType(typeof(GameInfoModel), StatusCodes.Status200OK)]
[ProducesResponseType(typeof(RequestResponse), StatusCodes.Status404NotFound)]
public async Task<IActionResult> DeleteGameWriteUps([FromRoute] int id, CancellationToken token)
{
var game = await _gameRepository.GetGameById(id, token);

if (game is null)
return NotFound(new RequestResponse("比赛未找到", 404));

await _gameRepository.DeleteAllWriteUps(game, token);

return Ok();
}
Expand Down Expand Up @@ -553,7 +586,7 @@ await _gameNoticeRepository.AddNotice(new()
}

// always flush scoreboard
await _gameRepository.FlushScoreboardCache(game.Id, token);
await _cacheHelper.FlushScoreboardCache(game.Id, token);

return Ok(ChallengeEditDetailModel.FromChallenge(res));
}
Expand Down Expand Up @@ -676,7 +709,7 @@ public async Task<IActionResult> RemoveGameChallenge([FromRoute] int id, [FromRo
await _challengeRepository.RemoveChallenge(res, token);

// always flush scoreboard
await _gameRepository.FlushScoreboardCache(game.Id, token);
await _cacheHelper.FlushScoreboardCache(game.Id, token);

return Ok();
}
Expand Down
44 changes: 44 additions & 0 deletions src/GZCTF/Extensions/CacheExtensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
using GZCTF.Utils;
using MemoryPack;
using Microsoft.Extensions.Caching.Distributed;

namespace GZCTF.Extensions;

public static class CacheExtensions
{
/// <summary>
/// 获取缓存或重新构建,如果缓存不存在会阻塞
/// 使用 CacheMaker 和 CacheRequest 代替处理耗时更久的缓存
/// </summary>
public static async Task<T> GetOrCreateAsync<T, L>(this IDistributedCache cache,
ILogger<L> logger,
string key,
Func<DistributedCacheEntryOptions, Task<T>> func,
CancellationToken token = default)
where T : class
{
var value = await cache.GetAsync(key, token);
T? result = default;

if (value is not null)
{
try
{
result = MemoryPackSerializer.Deserialize<T>(value);
}
catch
{ }
if (result is not null)
return result;
}

var cacheOptions = new DistributedCacheEntryOptions();
result = await func(cacheOptions);
var bytes = MemoryPackSerializer.Serialize(result);

await cache.SetAsync(key, bytes, cacheOptions, token);
logger.SystemLog($"重建缓存:{key} @ {bytes.Length} bytes", TaskStatus.Success, LogLevel.Debug);

return result;
}
}
2 changes: 2 additions & 0 deletions src/GZCTF/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -214,6 +214,8 @@

builder.Services.AddChannel<Submission>();
builder.Services.AddChannel<CacheRequest>();
builder.Services.AddSingleton<CacheHelper>();

builder.Services.AddHostedService<CacheMaker>();
builder.Services.AddHostedService<FlagChecker>();
builder.Services.AddHostedService<CronJobService>();
Expand Down
3 changes: 2 additions & 1 deletion src/GZCTF/Repositories/GameNoticeRepository.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using GZCTF.Hubs;
using GZCTF.Extensions;
using GZCTF.Hubs;
using GZCTF.Hubs.Clients;
using GZCTF.Repositories.Interface;
using GZCTF.Utils;
Expand Down
26 changes: 17 additions & 9 deletions src/GZCTF/Repositories/GameRepository.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
using System.Text;
using System.Threading.Channels;
using GZCTF.Extensions;
using GZCTF.Models.Request.Game;
using GZCTF.Repositories.Interface;
using GZCTF.Services;
Expand All @@ -18,23 +18,20 @@ public class GameRepository : RepositoryBase, IGameRepository
private readonly IParticipationRepository _participationRepository;
private readonly byte[]? _xorkey;
private readonly ILogger<GameRepository> _logger;
private readonly ChannelWriter<CacheRequest> _cacheRequestChannelWriter;

public GameRepository(IDistributedCache cache,
ITeamRepository teamRepository,
IChallengeRepository challengeRepository,
IParticipationRepository participationRepository,
IConfiguration configuration,
ILogger<GameRepository> logger,
ChannelWriter<CacheRequest> cacheRequestChannelWriter,
AppDbContext context) : base(context)
{
_cache = cache;
_logger = logger;
_teamRepository = teamRepository;
_challengeRepository = challengeRepository;
_participationRepository = participationRepository;
_cacheRequestChannelWriter = cacheRequestChannelWriter;

var xorkeyStr = configuration["XorKey"];
_xorkey = string.IsNullOrEmpty(xorkeyStr) ? null : Encoding.UTF8.GetBytes(xorkeyStr);
Expand Down Expand Up @@ -96,7 +93,7 @@ public async Task<ScoreboardModel> GetScoreboardWithMembers(Game game, Cancellat
}


public async Task DeleteGame(Game game, CancellationToken token = default)
public async Task<TaskStatus> DeleteGame(Game game, CancellationToken token = default)
{
var trans = await BeginTransactionAsync(token);

Expand All @@ -123,23 +120,34 @@ public async Task DeleteGame(Game game, CancellationToken token = default)

_cache.Remove(CacheKey.BasicGameInfo);
_cache.Remove(CacheKey.ScoreBoard(game.Id));

return TaskStatus.Success;
}
catch
{
_logger.SystemLog($"删除比赛失败,相关文件可能已受损,请重新删除", TaskStatus.Pending, LogLevel.Debug);
await trans.RollbackAsync(token);
throw;

return TaskStatus.Failed;
}
}

public async Task DeleteAllWriteUps(Game game, CancellationToken token = default)
{
await _context.Entry(game).Collection(g => g.Participations).LoadAsync(token);

_logger.SystemLog($"正在清理比赛 {game.Title}{game.Participations.Count} 个队伍相关文件……", TaskStatus.Pending, LogLevel.Debug);

foreach (var part in game.Participations)
await _participationRepository.DeleteParticipationWriteUp(part, token);
}

public Task<Game[]> GetGames(int count, int skip, CancellationToken token)
=> _context.Games.OrderByDescending(g => g.Id).Skip(skip).Take(count).ToArrayAsync(token);

public void FlushGameInfoCache()
=> _cache.Remove(CacheKey.BasicGameInfo);

public async Task FlushScoreboardCache(int gameId, CancellationToken token)
=> await _cacheRequestChannelWriter.WriteAsync(ScoreboardCacheHandler.MakeCacheRequest(gameId), token);

#region Generate Scoreboard

private record Data(Instance Instance, Submission? Submission);
Expand Down
9 changes: 5 additions & 4 deletions src/GZCTF/Repositories/Interface/IGameRepository.cs
Original file line number Diff line number Diff line change
Expand Up @@ -75,14 +75,15 @@ public interface IGameRepository : IRepository
/// <param name="game">比赛对象</param>
/// <param name="token"></param>
/// <returns></returns>
public Task DeleteGame(Game game, CancellationToken token = default);
public Task<TaskStatus> DeleteGame(Game game, CancellationToken token = default);

/// <summary>
/// 刷新排行榜
/// 删除比赛的全部 WriteUp
/// </summary>
/// <param name="gameId">比赛Id</param>
/// <param name="game">比赛对象</param>
/// <param name="token"></param>
public Task FlushScoreboardCache(int gameId, CancellationToken token);
/// <returns></returns>
public Task DeleteAllWriteUps(Game game, CancellationToken token = default);

/// <summary>
/// 生成排行榜
Expand Down
8 changes: 8 additions & 0 deletions src/GZCTF/Repositories/Interface/IParticipationRepository.cs
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,14 @@ public interface IParticipationRepository : IRepository
/// <returns></returns>
public Task<Participation?> GetParticipation(Team team, Game game, CancellationToken token = default);

/// <summary>
/// 删除参与对象的 WriteUps
/// </summary>
/// <param name="part">参与对象</param>
/// <param name="token"></param>
/// <returns></returns>
public Task DeleteParticipationWriteUp(Participation part, CancellationToken token = default);

/// <summary>
/// 删除参与对象
/// </summary>
Expand Down
21 changes: 14 additions & 7 deletions src/GZCTF/Repositories/ParticipationRepository.cs
Original file line number Diff line number Diff line change
@@ -1,20 +1,21 @@
using GZCTF.Models.Request.Admin;
using GZCTF.Repositories.Interface;
using GZCTF.Services;
using Microsoft.EntityFrameworkCore;

namespace GZCTF.Repositories;

public class ParticipationRepository : RepositoryBase, IParticipationRepository
{
private readonly IGameRepository _gameRepository;
private readonly CacheHelper _cacheHelper;
private readonly IFileRepository _fileRepository;

public ParticipationRepository(
IGameRepository gameRepository,
CacheHelper cacheHelper,
IFileRepository fileRepository,
AppDbContext context) : base(context)
{
_gameRepository = gameRepository;
_cacheHelper = cacheHelper;
_fileRepository = fileRepository;
}

Expand Down Expand Up @@ -78,7 +79,7 @@ public async Task UpdateParticipationStatus(Participation part, ParticipationSta
// also flush scoreboard when a team is re-accepted
if (await EnsureInstances(part, part.Game, token) || oldStatus == ParticipationStatus.Suspended)
// flush scoreboard when instances are updated
await _gameRepository.FlushScoreboardCache(part.Game.Id, token);
await _cacheHelper.FlushScoreboardCache(part.Game.Id, token);

return;
}
Expand All @@ -88,7 +89,7 @@ public async Task UpdateParticipationStatus(Participation part, ParticipationSta

// flush scoreboard when a team is suspended
if (status == ParticipationStatus.Suspended && part.Game.IsActive)
await _gameRepository.FlushScoreboardCache(part.GameId, token);
await _cacheHelper.FlushScoreboardCache(part.GameId, token);
}

public Task<Participation[]> GetParticipationsByIds(IEnumerable<int> ids, CancellationToken token = default)
Expand All @@ -106,10 +107,16 @@ public async Task RemoveUserParticipations(UserInfo user, Team team, Cancellatio

public async Task RemoveParticipation(Participation part, CancellationToken token = default)
{
if (part.Writeup is not null)
await _fileRepository.DeleteFile(part.Writeup, token);
await DeleteParticipationWriteUp(part, token);

_context.Remove(part);
await SaveAsync(token);
}

public Task DeleteParticipationWriteUp(Participation part, CancellationToken token = default)
{
if (part.Writeup is not null)
return _fileRepository.DeleteFile(part.Writeup, token);
return Task.CompletedTask;
}
}
3 changes: 2 additions & 1 deletion src/GZCTF/Repositories/PostRepository.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using GZCTF.Repositories.Interface;
using GZCTF.Extensions;
using GZCTF.Repositories.Interface;
using GZCTF.Utils;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Caching.Distributed;
Expand Down
Loading

0 comments on commit 785c017

Please sign in to comment.