From dfd0a476d8fe032ec097bdf0cdc629c2824ec76c Mon Sep 17 00:00:00 2001 From: GZTime Date: Wed, 23 Aug 2023 16:26:05 +0800 Subject: [PATCH] style(code): use primary constructors --- src/GZCTF.Test/GZCTF.Test.csproj | 2 +- src/GZCTF/Controllers/AccountController.cs | 171 +++++++--------- src/GZCTF/Controllers/AdminController.cs | 142 ++++++------- src/GZCTF/Controllers/AssetsController.cs | 22 +- src/GZCTF/Controllers/EditController.cs | 183 ++++++++--------- src/GZCTF/Controllers/GameController.cs | 190 +++++++----------- src/GZCTF/Controllers/InfoController.cs | 30 +-- src/GZCTF/Controllers/ProxyController.cs | 47 ++--- src/GZCTF/Controllers/TeamController.cs | 129 ++++++------ .../Middlewares/PrivilegeAuthentication.cs | 17 +- src/GZCTF/Models/AppDbContext.cs | 6 +- .../Providers/EntityConfigurationProvider.cs | 20 +- .../Providers/EntityConfigurationSource.cs | 12 +- src/GZCTF/Repositories/ChallengeRepository.cs | 39 ++-- src/GZCTF/Repositories/CheatInfoRepository.cs | 17 +- src/GZCTF/Repositories/ContainerRepository.cs | 29 +-- src/GZCTF/Repositories/FileRepository.cs | 33 ++- src/GZCTF/Repositories/GameEventRepository.cs | 21 +- .../Repositories/GameNoticeRepository.cs | 39 ++-- src/GZCTF/Repositories/GameRepository.cs | 88 ++++---- src/GZCTF/Repositories/InstanceRepository.cs | 85 ++++---- src/GZCTF/Repositories/LogRepository.cs | 8 +- .../Repositories/ParticipationRepository.cs | 49 ++--- src/GZCTF/Repositories/PostRepository.cs | 35 ++-- src/GZCTF/Repositories/RepositoryBase.cs | 17 +- .../Repositories/SubmissionRepository.cs | 12 +- src/GZCTF/Repositories/TeamRepository.cs | 20 +- src/GZCTF/Services/Cache/CacheHelper.cs | 11 +- src/GZCTF/Services/Cache/CacheMaker.cs | 69 +++---- src/GZCTF/Services/ConfigService.cs | 27 +-- src/GZCTF/Services/CronJobService.cs | 20 +- src/GZCTF/Services/FlagChecker.cs | 59 +++--- src/GZCTF/Services/MailSender.cs | 19 +- src/GZCTF/Utils/Codec.cs | 39 ++-- src/GZCTF/Utils/ExcelHelper.cs | 4 +- 35 files changed, 682 insertions(+), 1029 deletions(-) diff --git a/src/GZCTF.Test/GZCTF.Test.csproj b/src/GZCTF.Test/GZCTF.Test.csproj index 465169e6d..90138e654 100644 --- a/src/GZCTF.Test/GZCTF.Test.csproj +++ b/src/GZCTF.Test/GZCTF.Test.csproj @@ -16,7 +16,7 @@ - + runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/src/GZCTF/Controllers/AccountController.cs b/src/GZCTF/Controllers/AccountController.cs index 0bb67b8e5..a435f9d79 100644 --- a/src/GZCTF/Controllers/AccountController.cs +++ b/src/GZCTF/Controllers/AccountController.cs @@ -19,37 +19,16 @@ namespace GZCTF.Controllers; [ApiController] [Route("api/[controller]/[action]")] [Produces(MediaTypeNames.Application.Json)] -public class AccountController : ControllerBase +public class AccountController( + IMailSender mailSender, + IFileRepository fileService, + IHostEnvironment environment, + IRecaptchaExtension recaptcha, + IOptionsSnapshot accountPolicy, + UserManager userManager, + SignInManager signInManager, + ILogger logger) : ControllerBase { - private readonly ILogger _logger; - private readonly IMailSender _mailSender; - private readonly UserManager _userManager; - private readonly SignInManager _signInManager; - private readonly IFileRepository _fileService; - private readonly IRecaptchaExtension _recaptcha; - private readonly IHostEnvironment _environment; - private readonly IOptionsSnapshot _accountPolicy; - - public AccountController( - IMailSender mailSender, - IFileRepository fileService, - IHostEnvironment environment, - IRecaptchaExtension recaptcha, - IOptionsSnapshot accountPolicy, - UserManager userManager, - SignInManager signInManager, - ILogger logger) - { - _recaptcha = recaptcha; - _mailSender = mailSender; - _environment = environment; - _userManager = userManager; - _signInManager = signInManager; - _fileService = fileService; - _accountPolicy = accountPolicy; - _logger = logger; - } - /// /// 用户注册接口 /// @@ -65,19 +44,19 @@ public AccountController( [ProducesResponseType(typeof(RequestResponse), StatusCodes.Status400BadRequest)] public async Task Register([FromBody] RegisterModel model) { - if (!_accountPolicy.Value.AllowRegister) + if (!accountPolicy.Value.AllowRegister) return BadRequest(new RequestResponse("注册功能已禁用")); - if (_accountPolicy.Value.UseGoogleRecaptcha && ( + if (accountPolicy.Value.UseGoogleRecaptcha && ( model.GToken is null || HttpContext.Connection.RemoteIpAddress is null || - !await _recaptcha.VerifyAsync(model.GToken, HttpContext.Connection.RemoteIpAddress.ToString()) + !await recaptcha.VerifyAsync(model.GToken, HttpContext.Connection.RemoteIpAddress.ToString()) )) return BadRequest(new RequestResponse("Google reCAPTCHA 校验失败")); var mailDomain = model.Email!.Split('@')[1]; - if (!string.IsNullOrWhiteSpace(_accountPolicy.Value.EmailDomainList) && - _accountPolicy.Value.EmailDomainList.Split(',').All(d => d != mailDomain)) - return BadRequest(new RequestResponse($"可用邮箱后缀:{_accountPolicy.Value.EmailDomainList}")); + if (!string.IsNullOrWhiteSpace(accountPolicy.Value.EmailDomainList) && + accountPolicy.Value.EmailDomainList.Split(',').All(d => d != mailDomain)) + return BadRequest(new RequestResponse($"可用邮箱后缀:{accountPolicy.Value.EmailDomainList}")); var user = new UserInfo { @@ -88,48 +67,48 @@ model.GToken is null || HttpContext.Connection.RemoteIpAddress is null || user.UpdateByHttpContext(HttpContext); - var result = await _userManager.CreateAsync(user, model.Password); + var result = await userManager.CreateAsync(user, model.Password); if (!result.Succeeded) { - var current = await _userManager.FindByEmailAsync(model.Email); + var current = await userManager.FindByEmailAsync(model.Email); if (current is null) return BadRequest(new RequestResponse(result.Errors.FirstOrDefault()?.Description ?? "未知错误")); - if (await _userManager.IsEmailConfirmedAsync(current)) + if (await userManager.IsEmailConfirmedAsync(current)) return BadRequest(new RequestResponse("此账户已存在")); user = current; } - if (_accountPolicy.Value.ActiveOnRegister) + if (accountPolicy.Value.ActiveOnRegister) { user.EmailConfirmed = true; - await _userManager.UpdateAsync(user); - await _signInManager.SignInAsync(user, true); + await userManager.UpdateAsync(user); + await signInManager.SignInAsync(user, true); - _logger.Log("用户成功注册", user, TaskStatus.Success); + logger.Log("用户成功注册", user, TaskStatus.Success); return Ok(new RequestResponse("注册成功", RegisterStatus.LoggedIn, 200)); } - if (!_accountPolicy.Value.EmailConfirmationRequired) + if (!accountPolicy.Value.EmailConfirmationRequired) { - _logger.Log("用户成功注册,待审核", user, TaskStatus.Success); + logger.Log("用户成功注册,待审核", user, TaskStatus.Success); return Ok(new RequestResponse("注册成功,等待管理员审核", RegisterStatus.AdminConfirmationRequired, 200)); } - _logger.Log("发送用户邮箱验证邮件", user, TaskStatus.Pending); + logger.Log("发送用户邮箱验证邮件", user, TaskStatus.Pending); - var token = Codec.Base64.Encode(await _userManager.GenerateEmailConfirmationTokenAsync(user)); - if (_environment.IsDevelopment()) + var token = Codec.Base64.Encode(await userManager.GenerateEmailConfirmationTokenAsync(user)); + if (environment.IsDevelopment()) { - _logger.Log($"http://{HttpContext.Request.Host}/account/verify?token={token}&email={Codec.Base64.Encode(model.Email)}", user, TaskStatus.Pending, LogLevel.Debug); + logger.Log($"http://{HttpContext.Request.Host}/account/verify?token={token}&email={Codec.Base64.Encode(model.Email)}", user, TaskStatus.Pending, LogLevel.Debug); } else { - if (!_mailSender.SendConfirmEmailUrl(user.UserName, user.Email, + if (!mailSender.SendConfirmEmailUrl(user.UserName, user.Email, $"https://{HttpContext.Request.Host}/account/verify?token={token}&email={Codec.Base64.Encode(model.Email)}")) return BadRequest(new RequestResponse("邮件无法发送,请联系管理员")); } @@ -155,33 +134,33 @@ model.GToken is null || HttpContext.Connection.RemoteIpAddress is null || [ProducesResponseType(typeof(RequestResponse), StatusCodes.Status400BadRequest)] public async Task Recovery([FromBody] RecoveryModel model) { - if (_accountPolicy.Value.UseGoogleRecaptcha && ( + if (accountPolicy.Value.UseGoogleRecaptcha && ( model.GToken is null || HttpContext.Connection.RemoteIpAddress is null || - !await _recaptcha.VerifyAsync(model.GToken, HttpContext.Connection.RemoteIpAddress.ToString()) + !await recaptcha.VerifyAsync(model.GToken, HttpContext.Connection.RemoteIpAddress.ToString()) )) return BadRequest(new RequestResponse("Google reCAPTCHA 校验失败")); - var user = await _userManager.FindByEmailAsync(model.Email!); + var user = await userManager.FindByEmailAsync(model.Email!); if (user is null) return NotFound(new RequestResponse("用户不存在", 404)); if (!user.EmailConfirmed) return NotFound(new RequestResponse("账户未激活,请重新注册", 404)); - if (!_accountPolicy.Value.EmailConfirmationRequired) + if (!accountPolicy.Value.EmailConfirmationRequired) return BadRequest(new RequestResponse("请联系管理员重置密码")); - _logger.Log("发送用户密码重置邮件", HttpContext, TaskStatus.Pending); + logger.Log("发送用户密码重置邮件", HttpContext, TaskStatus.Pending); - var token = Codec.Base64.Encode(await _userManager.GeneratePasswordResetTokenAsync(user)); + var token = Codec.Base64.Encode(await userManager.GeneratePasswordResetTokenAsync(user)); - if (_environment.IsDevelopment()) + if (environment.IsDevelopment()) { - _logger.Log($"http://{HttpContext.Request.Host}/account/reset?token={token}&email={Codec.Base64.Encode(model.Email)}", user, TaskStatus.Pending, LogLevel.Debug); + logger.Log($"http://{HttpContext.Request.Host}/account/reset?token={token}&email={Codec.Base64.Encode(model.Email)}", user, TaskStatus.Pending, LogLevel.Debug); } else { - if (!_mailSender.SendResetPasswordUrl(user.UserName, user.Email, + if (!mailSender.SendResetPasswordUrl(user.UserName, user.Email, $"https://{HttpContext.Request.Host}/account/reset?token={token}&email={Codec.Base64.Encode(model.Email)}")) return BadRequest(new RequestResponse("邮件无法发送,请联系管理员")); } @@ -204,18 +183,18 @@ model.GToken is null || HttpContext.Connection.RemoteIpAddress is null || [ProducesResponseType(typeof(RequestResponse), StatusCodes.Status400BadRequest)] public async Task PasswordReset([FromBody] PasswordResetModel model) { - var user = await _userManager.FindByEmailAsync(Codec.Base64.Decode(model.Email)); + var user = await userManager.FindByEmailAsync(Codec.Base64.Decode(model.Email)); if (user is null) return BadRequest(new RequestResponse("无效的邮件地址")); user.UpdateByHttpContext(HttpContext); - var result = await _userManager.ResetPasswordAsync(user, Codec.Base64.Decode(model.RToken), model.Password); + var result = await userManager.ResetPasswordAsync(user, Codec.Base64.Decode(model.RToken), model.Password); if (!result.Succeeded) return BadRequest(new RequestResponse(result.Errors.FirstOrDefault()?.Description ?? "未知错误")); - _logger.Log("用户成功重置密码", user, TaskStatus.Success); + logger.Log("用户成功重置密码", user, TaskStatus.Success); return Ok(); } @@ -236,24 +215,24 @@ public async Task PasswordReset([FromBody] PasswordResetModel mod [ProducesResponseType(typeof(RequestResponse), StatusCodes.Status401Unauthorized)] public async Task Verify([FromBody] AccountVerifyModel model) { - var user = await _userManager.FindByEmailAsync(Codec.Base64.Decode(model.Email)); + var user = await userManager.FindByEmailAsync(Codec.Base64.Decode(model.Email)); if (user is null || user.EmailConfirmed) return BadRequest(new RequestResponse("无效的邮件地址")); - var result = await _userManager.ConfirmEmailAsync(user, Codec.Base64.Decode(model.Token)); + var result = await userManager.ConfirmEmailAsync(user, Codec.Base64.Decode(model.Token)); if (!result.Succeeded) return Unauthorized(new RequestResponse("邮箱验证失败", 401)); - _logger.Log("通过邮箱验证", user, TaskStatus.Success); - await _signInManager.SignInAsync(user, true); + logger.Log("通过邮箱验证", user, TaskStatus.Success); + await signInManager.SignInAsync(user, true); user.LastSignedInUTC = DateTimeOffset.UtcNow; user.LastVisitedUTC = DateTimeOffset.UtcNow; user.RegisterTimeUTC = DateTimeOffset.UtcNow; - result = await _userManager.UpdateAsync(user); + result = await userManager.UpdateAsync(user); if (!result.Succeeded) return BadRequest(new RequestResponse(result.Errors.FirstOrDefault()?.Description ?? "未知错误")); @@ -277,8 +256,8 @@ public async Task Verify([FromBody] AccountVerifyModel model) [ProducesResponseType(typeof(RequestResponse), StatusCodes.Status404NotFound)] public async Task LogIn([FromBody] LoginModel model) { - var user = await _userManager.FindByNameAsync(model.UserName); - user ??= await _userManager.FindByEmailAsync(model.UserName); + var user = await userManager.FindByNameAsync(model.UserName); + user ??= await userManager.FindByEmailAsync(model.UserName); if (user is null) return Unauthorized(new RequestResponse("用户名或密码错误", 401)); @@ -289,14 +268,14 @@ public async Task LogIn([FromBody] LoginModel model) user.LastSignedInUTC = DateTimeOffset.UtcNow; user.UpdateByHttpContext(HttpContext); - await _signInManager.SignOutAsync(); + await signInManager.SignOutAsync(); - var result = await _signInManager.PasswordSignInAsync(user, model.Password, true, false); + var result = await signInManager.PasswordSignInAsync(user, model.Password, true, false); if (!result.Succeeded) return Unauthorized(new RequestResponse("用户名或密码错误", 401)); - _logger.Log("用户成功登录", user, TaskStatus.Success); + logger.Log("用户成功登录", user, TaskStatus.Success); return Ok(); } @@ -314,7 +293,7 @@ public async Task LogIn([FromBody] LoginModel model) [ProducesResponseType(StatusCodes.Status200OK)] public async Task LogOut() { - await _signInManager.SignOutAsync(); + await signInManager.SignOutAsync(); return Ok(); } @@ -335,17 +314,17 @@ public async Task LogOut() [ProducesResponseType(typeof(RequestResponse), StatusCodes.Status400BadRequest)] public async Task Update([FromBody] ProfileUpdateModel model) { - var user = await _userManager.GetUserAsync(User); + var user = await userManager.GetUserAsync(User); var oname = user!.UserName; user.UpdateUserInfo(model); - var result = await _userManager.UpdateAsync(user); + var result = await userManager.UpdateAsync(user); if (!result.Succeeded) return BadRequest(new RequestResponse(result.Errors.FirstOrDefault()?.Description ?? "未知错误")); if (oname != user.UserName) - _logger.Log($"用户更新:{oname} => {model.UserName}", user, TaskStatus.Success); + logger.Log($"用户更新:{oname} => {model.UserName}", user, TaskStatus.Success); return Ok(); } @@ -366,13 +345,13 @@ public async Task Update([FromBody] ProfileUpdateModel model) [ProducesResponseType(typeof(RequestResponse), StatusCodes.Status401Unauthorized)] public async Task ChangePassword([FromBody] PasswordChangeModel model) { - var user = await _userManager.GetUserAsync(User); - var result = await _userManager.ChangePasswordAsync(user!, model.Old, model.New); + var user = await userManager.GetUserAsync(User); + var result = await userManager.ChangePasswordAsync(user!, model.Old, model.New); if (!result.Succeeded) return BadRequest(new RequestResponse(result.Errors.FirstOrDefault()?.Description ?? "未知错误")); - _logger.Log("用户更新密码", user, TaskStatus.Success); + logger.Log("用户更新密码", user, TaskStatus.Success); return Ok(); } @@ -395,25 +374,25 @@ public async Task ChangePassword([FromBody] PasswordChangeModel m [ProducesResponseType(typeof(RequestResponse), StatusCodes.Status401Unauthorized)] public async Task ChangeEmail([FromBody] MailChangeModel model) { - if (await _userManager.FindByEmailAsync(model.NewMail) is not null) + if (await userManager.FindByEmailAsync(model.NewMail) is not null) return BadRequest(new RequestResponse("邮箱已经被占用")); - var user = await _userManager.GetUserAsync(User); + var user = await userManager.GetUserAsync(User); - if (!_accountPolicy.Value.EmailConfirmationRequired) + if (!accountPolicy.Value.EmailConfirmationRequired) return BadRequest(new RequestResponse("请联系管理员修改邮箱", false)); - _logger.Log("发送用户邮箱更改邮件", user, TaskStatus.Pending); + logger.Log("发送用户邮箱更改邮件", user, TaskStatus.Pending); - var token = Codec.Base64.Encode(await _userManager.GenerateChangeEmailTokenAsync(user!, model.NewMail)); + var token = Codec.Base64.Encode(await userManager.GenerateChangeEmailTokenAsync(user!, model.NewMail)); - if (_environment.IsDevelopment()) + if (environment.IsDevelopment()) { - _logger.Log($"http://{HttpContext.Request.Host}/account/confirm?token={token}&email={Codec.Base64.Encode(model.NewMail)}", user, TaskStatus.Pending, LogLevel.Debug); + logger.Log($"http://{HttpContext.Request.Host}/account/confirm?token={token}&email={Codec.Base64.Encode(model.NewMail)}", user, TaskStatus.Pending, LogLevel.Debug); } else { - if (!_mailSender.SendConfirmEmailUrl(user!.UserName, user.Email, + if (!mailSender.SendConfirmEmailUrl(user!.UserName, user.Email, $"https://{HttpContext.Request.Host}/account/confirm?token={token}&email={Codec.Base64.Encode(model.NewMail)}")) return BadRequest(new RequestResponse("邮件无法发送,请联系管理员")); } @@ -439,13 +418,13 @@ public async Task ChangeEmail([FromBody] MailChangeModel model) [ProducesResponseType(typeof(RequestResponse), StatusCodes.Status401Unauthorized)] public async Task MailChangeConfirm([FromBody] AccountVerifyModel model) { - var user = await _userManager.GetUserAsync(User); - var result = await _userManager.ChangeEmailAsync(user!, Codec.Base64.Decode(model.Email), Codec.Base64.Decode(model.Token)); + var user = await userManager.GetUserAsync(User); + var result = await userManager.ChangeEmailAsync(user!, Codec.Base64.Decode(model.Email), Codec.Base64.Decode(model.Token)); if (!result.Succeeded) return BadRequest(new RequestResponse("无效邮箱")); - _logger.Log("更改邮箱成功", user, TaskStatus.Success); + logger.Log("更改邮箱成功", user, TaskStatus.Success); return Ok(); } @@ -465,7 +444,7 @@ public async Task MailChangeConfirm([FromBody] AccountVerifyModel [ProducesResponseType(typeof(RequestResponse), StatusCodes.Status403Forbidden)] public async Task Profile() { - var user = await _userManager.GetUserAsync(User); + var user = await userManager.GetUserAsync(User); return Ok(ProfileUserInfoModel.FromUserInfo(user!)); } @@ -492,23 +471,23 @@ public async Task Avatar(IFormFile file, CancellationToken token) if (file.Length > 3 * 1024 * 1024) return BadRequest(new RequestResponse("文件过大")); - var user = await _userManager.GetUserAsync(User); + var user = await userManager.GetUserAsync(User); if (user!.AvatarHash is not null) - await _fileService.DeleteFileByHash(user.AvatarHash, token); + await fileService.DeleteFileByHash(user.AvatarHash, token); - var avatar = await _fileService.CreateOrUpdateImage(file, "avatar", 300, token); + var avatar = await fileService.CreateOrUpdateImage(file, "avatar", 300, token); if (avatar is null) return BadRequest(new RequestResponse("用户头像更新失败")); user.AvatarHash = avatar.Hash; - var result = await _userManager.UpdateAsync(user); + var result = await userManager.UpdateAsync(user); if (result != IdentityResult.Success) return BadRequest(new RequestResponse("用户更新失败")); - _logger.Log($"更改新头像:[{avatar.Hash[..8]}]", user, TaskStatus.Success); + logger.Log($"更改新头像:[{avatar.Hash[..8]}]", user, TaskStatus.Success); return Ok(avatar.Url()); } diff --git a/src/GZCTF/Controllers/AdminController.cs b/src/GZCTF/Controllers/AdminController.cs index 763e56fed..0328c0661 100644 --- a/src/GZCTF/Controllers/AdminController.cs +++ b/src/GZCTF/Controllers/AdminController.cs @@ -24,46 +24,18 @@ namespace GZCTF.Controllers; [Produces(MediaTypeNames.Application.Json)] [ProducesResponseType(typeof(RequestResponse), StatusCodes.Status401Unauthorized)] [ProducesResponseType(typeof(RequestResponse), StatusCodes.Status403Forbidden)] -public class AdminController : ControllerBase +public class AdminController(UserManager userManager, + ILogger logger, + IFileRepository fileService, + ILogRepository logRepository, + IConfigService configService, + IGameRepository gameRepository, + ITeamRepository teamRepository, + IInstanceRepository instanceRepository, + IContainerRepository containerRepository, + IServiceProvider serviceProvider, + IParticipationRepository participationRepository) : ControllerBase { - private readonly ILogger _logger; - private readonly UserManager _userManager; - private readonly ILogRepository _logRepository; - private readonly IFileRepository _fileService; - private readonly IConfigService _configService; - private readonly IGameRepository _gameRepository; - private readonly ITeamRepository _teamRepository; - private readonly IServiceProvider _serviceProvider; - private readonly IContainerRepository _containerRepository; - private readonly IInstanceRepository _instanceRepository; - private readonly IParticipationRepository _participationRepository; - - public AdminController(UserManager userManager, - ILogger logger, - IFileRepository fileService, - ILogRepository logRepository, - IConfigService configService, - IGameRepository gameRepository, - ITeamRepository teamRepository, - IInstanceRepository instanceRepository, - IContainerRepository containerRepository, - IServiceProvider serviceProvider, - IConfiguration configuration, - IParticipationRepository participationRepository) - { - _logger = logger; - _userManager = userManager; - _fileService = fileService; - _configService = configService; - _logRepository = logRepository; - _teamRepository = teamRepository; - _gameRepository = gameRepository; - _instanceRepository = instanceRepository; - _containerRepository = containerRepository; - _serviceProvider = serviceProvider; - _participationRepository = participationRepository; - } - /// /// 获取配置 /// @@ -78,13 +50,13 @@ public AdminController(UserManager userManager, public IActionResult GetConfigs() { // always reload, ensure latest - _configService.ReloadConfig(); + configService.ReloadConfig(); ConfigEditModel config = new() { - AccountPolicy = _serviceProvider.GetRequiredService>().Value, - GlobalConfig = _serviceProvider.GetRequiredService>().Value, - GamePolicy = _serviceProvider.GetRequiredService>().Value + AccountPolicy = serviceProvider.GetRequiredService>().Value, + GlobalConfig = serviceProvider.GetRequiredService>().Value, + GamePolicy = serviceProvider.GetRequiredService>().Value }; return Ok(config); @@ -107,7 +79,7 @@ public async Task UpdateConfigs([FromBody] ConfigEditModel model, { var value = prop.GetValue(model); if (value is not null) - await _configService.SaveConfig(prop.PropertyType, value, token); + await configService.SaveConfig(prop.PropertyType, value, token); } return Ok(); @@ -126,9 +98,9 @@ public async Task UpdateConfigs([FromBody] ConfigEditModel model, [ProducesResponseType(typeof(ArrayResponse), StatusCodes.Status200OK)] public async Task Users([FromQuery] int count = 100, [FromQuery] int skip = 0, CancellationToken token = default) => Ok((await ( - from user in _userManager.Users.OrderBy(e => e.Id).Skip(skip).Take(count) + from user in userManager.Users.OrderBy(e => e.Id).Skip(skip).Take(count) select UserInfoModel.FromUserInfo(user) - ).ToArrayAsync(token)).ToResponse(await _userManager.Users.CountAsync(token))); + ).ToArrayAsync(token)).ToResponse(await userManager.Users.CountAsync(token))); /// /// 批量添加用户 @@ -145,8 +117,8 @@ select UserInfoModel.FromUserInfo(user) [ProducesResponseType(typeof(RequestResponse), StatusCodes.Status400BadRequest)] public async Task AddUsers([FromBody] UserCreateModel[] model, CancellationToken token = default) { - var currentUser = await _userManager.GetUserAsync(User); - var trans = await _teamRepository.BeginTransactionAsync(token); + var currentUser = await userManager.GetUserAsync(User); + var trans = await teamRepository.BeginTransactionAsync(token); try { @@ -154,17 +126,17 @@ public async Task AddUsers([FromBody] UserCreateModel[] model, Ca foreach (var user in model) { var userInfo = user.ToUserInfo(); - var result = await _userManager.CreateAsync(userInfo, user.Password); + var result = await userManager.CreateAsync(userInfo, user.Password); if (!result.Succeeded) { switch (result.Errors.FirstOrDefault()?.Code) { case "DuplicateEmail": - userInfo = await _userManager.FindByEmailAsync(user.Email); + userInfo = await userManager.FindByEmailAsync(user.Email); break; case "DuplicateUserName": - userInfo = await _userManager.FindByNameAsync(user.UserName); + userInfo = await userManager.FindByNameAsync(user.UserName); break; default: await trans.RollbackAsync(token); @@ -174,8 +146,8 @@ public async Task AddUsers([FromBody] UserCreateModel[] model, Ca if (userInfo is not null) { userInfo.UpdateUserInfo(user); - var code = await _userManager.GeneratePasswordResetTokenAsync(userInfo); - result = await _userManager.ResetPasswordAsync(userInfo, code, user.Password); + var code = await userManager.GeneratePasswordResetTokenAsync(userInfo); + result = await userManager.ResetPasswordAsync(userInfo, code, user.Password); } if (!result.Succeeded || userInfo is null) @@ -197,7 +169,7 @@ public async Task AddUsers([FromBody] UserCreateModel[] model, Ca var team = teams.Find(team => team.Name == teamName); if (team is null) { - team = await _teamRepository.CreateTeam(new(teamName), user, token); + team = await teamRepository.CreateTeam(new(teamName), user, token); teams.Add(team!); } else @@ -206,10 +178,10 @@ public async Task AddUsers([FromBody] UserCreateModel[] model, Ca } } - await _teamRepository.SaveAsync(token); + await teamRepository.SaveAsync(token); await trans.CommitAsync(token); - _logger.Log($"成功批量添加 {users.Count} 个用户", currentUser, TaskStatus.Success); + logger.Log($"成功批量添加 {users.Count} 个用户", currentUser, TaskStatus.Success); return Ok(); } @@ -233,7 +205,7 @@ public async Task AddUsers([FromBody] UserCreateModel[] model, Ca [ProducesResponseType(typeof(ArrayResponse), StatusCodes.Status200OK)] public async Task SearchUsers([FromQuery] string hint, CancellationToken token = default) => Ok((await ( - from user in _userManager.Users + from user in userManager.Users .Where(item => EF.Functions.Like(item.UserName!, $"%{hint}%") || EF.Functions.Like(item.StdNumber, $"%{hint}%") || @@ -257,9 +229,9 @@ select UserInfoModel.FromUserInfo(user) [HttpGet("Teams")] [ProducesResponseType(typeof(ArrayResponse), StatusCodes.Status200OK)] public async Task Teams([FromQuery] int count = 100, [FromQuery] int skip = 0, CancellationToken token = default) - => Ok((await _teamRepository.GetTeams(count, skip, token)) + => Ok((await teamRepository.GetTeams(count, skip, token)) .Select(team => TeamInfoModel.FromTeam(team)) - .ToResponse(await _teamRepository.CountAsync(token))); + .ToResponse(await teamRepository.CountAsync(token))); /// /// 搜索队伍 @@ -273,7 +245,7 @@ public async Task Teams([FromQuery] int count = 100, [FromQuery] [HttpPost("Teams/Search")] [ProducesResponseType(typeof(ArrayResponse), StatusCodes.Status200OK)] public async Task SearchTeams([FromQuery] string hint, CancellationToken token = default) - => Ok((await _teamRepository.SearchTeams(hint, token)) + => Ok((await teamRepository.SearchTeams(hint, token)) .Select(team => TeamInfoModel.FromTeam(team)) .ToResponse()); @@ -292,13 +264,13 @@ public async Task SearchTeams([FromQuery] string hint, Cancellati [ProducesResponseType(typeof(RequestResponse), StatusCodes.Status404NotFound)] public async Task UpdateTeam([FromRoute] int id, [FromBody] AdminTeamModel model, CancellationToken token = default) { - var team = await _teamRepository.GetTeamById(id, token); + var team = await teamRepository.GetTeamById(id, token); if (team is null) return BadRequest(new RequestResponse("队伍未找到")); team.UpdateInfo(model); - await _teamRepository.SaveAsync(token); + await teamRepository.SaveAsync(token); return Ok(); } @@ -318,13 +290,13 @@ public async Task UpdateTeam([FromRoute] int id, [FromBody] Admin [ProducesResponseType(typeof(RequestResponse), StatusCodes.Status404NotFound)] public async Task UpdateUserInfo(string userid, [FromBody] AdminUserInfoModel model) { - var user = await _userManager.FindByIdAsync(userid); + var user = await userManager.FindByIdAsync(userid); if (user is null) return NotFound(new RequestResponse("用户未找到", 404)); user.UpdateUserInfo(model); - await _userManager.UpdateAsync(user); + await userManager.UpdateAsync(user); return Ok(); } @@ -344,14 +316,14 @@ public async Task UpdateUserInfo(string userid, [FromBody] AdminU [ProducesResponseType(typeof(RequestResponse), StatusCodes.Status404NotFound)] public async Task ResetPassword(string userid, CancellationToken token = default) { - var user = await _userManager.FindByIdAsync(userid); + var user = await userManager.FindByIdAsync(userid); if (user is null) return NotFound(new RequestResponse("用户未找到", 404)); var pwd = Codec.RandomPassword(16); - var code = await _userManager.GeneratePasswordResetTokenAsync(user); - await _userManager.ResetPasswordAsync(user, code, pwd); + var code = await userManager.GeneratePasswordResetTokenAsync(user); + await userManager.ResetPasswordAsync(user, code, pwd); return Ok(pwd); } @@ -371,20 +343,20 @@ public async Task ResetPassword(string userid, CancellationToken [ProducesResponseType(typeof(RequestResponse), StatusCodes.Status404NotFound)] public async Task DeleteUser(string userid, CancellationToken token = default) { - var user = await _userManager.GetUserAsync(User); + var user = await userManager.GetUserAsync(User); if (user!.Id == userid) return BadRequest(new RequestResponse("不可以删除自己")); - user = await _userManager.FindByIdAsync(userid); + user = await userManager.FindByIdAsync(userid); if (user is null) return NotFound(new RequestResponse("用户未找到", 404)); - if (await _teamRepository.CheckIsCaptain(user, token)) + if (await teamRepository.CheckIsCaptain(user, token)) return BadRequest(new RequestResponse("不可以删除队长")); - await _userManager.DeleteAsync(user); + await userManager.DeleteAsync(user); return Ok(); } @@ -404,12 +376,12 @@ public async Task DeleteUser(string userid, CancellationToken tok [ProducesResponseType(typeof(RequestResponse), StatusCodes.Status404NotFound)] public async Task DeleteTeam(int id, CancellationToken token = default) { - var team = await _teamRepository.GetTeamById(id, token); + var team = await teamRepository.GetTeamById(id, token); if (team is null) return NotFound(new RequestResponse("队伍未找到", 404)); - await _teamRepository.DeleteTeam(team, token); + await teamRepository.DeleteTeam(team, token); return Ok(); } @@ -428,7 +400,7 @@ public async Task DeleteTeam(int id, CancellationToken token = de [ProducesResponseType(typeof(RequestResponse), StatusCodes.Status404NotFound)] public async Task UserInfo(string userid) { - var user = await _userManager.FindByIdAsync(userid); + var user = await userManager.FindByIdAsync(userid); if (user is null) return NotFound(new RequestResponse("用户未找到", 404)); @@ -448,7 +420,7 @@ public async Task UserInfo(string userid) [HttpGet("Logs")] [ProducesResponseType(typeof(LogMessageModel[]), StatusCodes.Status200OK)] public async Task Logs([FromQuery] string? level = "All", [FromQuery] int count = 50, [FromQuery] int skip = 0, CancellationToken token = default) - => Ok(await _logRepository.GetLogs(skip, count, level, token)); + => Ok(await logRepository.GetLogs(skip, count, level, token)); /// /// 更新参与状态 @@ -465,12 +437,12 @@ public async Task Logs([FromQuery] string? level = "All", [FromQu [ProducesResponseType(typeof(RequestResponse), StatusCodes.Status404NotFound)] public async Task Participation(int id, ParticipationStatus status, CancellationToken token = default) { - var participation = await _participationRepository.GetParticipationById(id, token); + var participation = await participationRepository.GetParticipationById(id, token); if (participation is null) return NotFound(new RequestResponse("参与状态未找到", 404)); - await _participationRepository.UpdateParticipationStatus(participation, status, token); + await participationRepository.UpdateParticipationStatus(participation, status, token); return Ok(); } @@ -490,12 +462,12 @@ public async Task Participation(int id, ParticipationStatus statu [ProducesResponseType(typeof(RequestResponse), StatusCodes.Status404NotFound)] public async Task Writeups(int id, CancellationToken token = default) { - var game = await _gameRepository.GetGameById(id, token); + var game = await gameRepository.GetGameById(id, token); if (game is null) return NotFound(new RequestResponse("比赛未找到", 404)); - return Ok(await _participationRepository.GetWriteups(game, token)); + return Ok(await participationRepository.GetWriteups(game, token)); } /// @@ -513,12 +485,12 @@ public async Task Writeups(int id, CancellationToken token = defa [ProducesResponseType(typeof(RequestResponse), StatusCodes.Status404NotFound)] public async Task DownloadAllWriteups(int id, CancellationToken token = default) { - var game = await _gameRepository.GetGameById(id, token); + var game = await gameRepository.GetGameById(id, token); if (game is null) return NotFound(new RequestResponse("比赛未找到", 404)); - var wps = await _participationRepository.GetWriteups(game, token); + var wps = await participationRepository.GetWriteups(game, token); var filename = $"Writeups-{game.Title}-{DateTimeOffset.UtcNow:yyyyMMdd-HH.mm.ssZ}"; var stream = await Codec.ZipFilesAsync(wps.Select(p => p.File), FilePath.Uploads, filename, token); stream.Seek(0, SeekOrigin.Begin); @@ -538,7 +510,7 @@ public async Task DownloadAllWriteups(int id, CancellationToken t [HttpGet("Instances")] [ProducesResponseType(typeof(ArrayResponse), StatusCodes.Status200OK)] public async Task Instances(CancellationToken token = default) - => Ok(new ArrayResponse(await _containerRepository.GetContainerInstances(token))); + => Ok(new ArrayResponse(await containerRepository.GetContainerInstances(token))); /// /// 删除容器实例 @@ -557,12 +529,12 @@ public async Task Instances(CancellationToken token = default) [ProducesResponseType(typeof(RequestResponse), StatusCodes.Status404NotFound)] public async Task DestroyInstance(string id, CancellationToken token = default) { - var container = await _containerRepository.GetContainerById(id, token); + var container = await containerRepository.GetContainerById(id, token); if (container is null) return NotFound(new RequestResponse("容器实例未找到", 404)); - if (await _instanceRepository.DestroyContainer(container, token)) + if (await instanceRepository.DestroyContainer(container, token)) return Ok(); else return BadRequest(new RequestResponse("容器实例销毁失败", 404)); @@ -580,5 +552,5 @@ public async Task DestroyInstance(string id, CancellationToken to [HttpGet("Files")] [ProducesResponseType(typeof(ArrayResponse), StatusCodes.Status200OK)] public async Task Files([FromQuery] int count = 50, [FromQuery] int skip = 0, CancellationToken token = default) - => Ok(new ArrayResponse(await _fileService.GetFiles(count, skip, token))); + => Ok(new ArrayResponse(await fileService.GetFiles(count, skip, token))); } diff --git a/src/GZCTF/Controllers/AssetsController.cs b/src/GZCTF/Controllers/AssetsController.cs index aea12f0e8..35ed7941a 100644 --- a/src/GZCTF/Controllers/AssetsController.cs +++ b/src/GZCTF/Controllers/AssetsController.cs @@ -14,18 +14,10 @@ namespace GZCTF.Controllers; [ApiController] [ProducesResponseType(typeof(RequestResponse), StatusCodes.Status401Unauthorized)] [ProducesResponseType(typeof(RequestResponse), StatusCodes.Status403Forbidden)] -public class AssetsController : ControllerBase +public class AssetsController(IFileRepository fileService, ILogger logger) : ControllerBase { - private readonly ILogger _logger; - private readonly IFileRepository _fileRepository; private readonly FileExtensionContentTypeProvider _extProvider = new(); - public AssetsController(IFileRepository fileService, ILogger logger) - { - _fileRepository = fileService; - _logger = logger; - } - /// /// 获取文件接口 /// @@ -46,7 +38,7 @@ public IActionResult GetFile([RegularExpression("[0-9a-f]{64}")] string hash, st if (!System.IO.File.Exists(path)) { - _logger.Log($"尝试获取不存在的文件 [{hash[..8]}] {filename}", HttpContext.Connection?.RemoteIpAddress?.ToString() ?? "0.0.0.0", TaskStatus.NotFound, LogLevel.Warning); + logger.Log($"尝试获取不存在的文件 [{hash[..8]}] {filename}", HttpContext.Connection?.RemoteIpAddress?.ToString() ?? "0.0.0.0", TaskStatus.NotFound, LogLevel.Warning); return NotFound(new RequestResponse("文件不存在", 404)); } @@ -85,8 +77,8 @@ public async Task Upload(List files, [FromQuery] strin { if (file.Length > 0) { - var res = await _fileRepository.CreateOrUpdateFile(file, filename, token); - _logger.SystemLog($"更新文件 [{res.Hash[..8]}] {filename ?? file.FileName} @ {file.Length} bytes", TaskStatus.Success, LogLevel.Debug); + var res = await fileService.CreateOrUpdateFile(file, filename, token); + logger.SystemLog($"更新文件 [{res.Hash[..8]}] {filename ?? file.FileName} @ {file.Length} bytes", TaskStatus.Success, LogLevel.Debug); results.Add(res); } } @@ -94,7 +86,7 @@ public async Task Upload(List files, [FromQuery] strin } catch (Exception ex) { - _logger.LogError(ex, ex.Message); + logger.LogError(ex, ex.Message); return BadRequest(new RequestResponse("遇到IO错误")); } } @@ -118,9 +110,9 @@ public async Task Upload(List files, [FromQuery] strin [ProducesResponseType(typeof(RequestResponse), StatusCodes.Status400BadRequest)] public async Task Delete(string hash, CancellationToken token) { - var result = await _fileRepository.DeleteFileByHash(hash, token); + var result = await fileService.DeleteFileByHash(hash, token); - _logger.SystemLog($"删除文件 [{hash[..8]}]...", result, LogLevel.Information); + logger.SystemLog($"删除文件 [{hash[..8]}]...", result, LogLevel.Information); return result switch { diff --git a/src/GZCTF/Controllers/EditController.cs b/src/GZCTF/Controllers/EditController.cs index 357f02630..930999b0c 100644 --- a/src/GZCTF/Controllers/EditController.cs +++ b/src/GZCTF/Controllers/EditController.cs @@ -22,43 +22,18 @@ namespace GZCTF.Controllers; [Produces(MediaTypeNames.Application.Json)] [ProducesResponseType(typeof(RequestResponse), StatusCodes.Status401Unauthorized)] [ProducesResponseType(typeof(RequestResponse), StatusCodes.Status403Forbidden)] -public class EditController : Controller +public class EditController( + CacheHelper cacheHelper, + UserManager userManager, + ILogger logger, + IPostRepository postRepository, + IContainerRepository containerRepository, + IChallengeRepository challengeRepository, + IGameNoticeRepository gameNoticeRepository, + IGameRepository gameRepository, + IContainerManager containerService, + IFileRepository fileService) : Controller { - private readonly ILogger _logger; - private readonly CacheHelper _cacheHelper; - private readonly UserManager _userManager; - private readonly IPostRepository _postRepository; - private readonly IGameNoticeRepository _gameNoticeRepository; - private readonly IGameRepository _gameRepository; - private readonly IChallengeRepository _challengeRepository; - private readonly IFileRepository _fileService; - private readonly IContainerManager _containerService; - private readonly IContainerRepository _containerRepository; - - public EditController( - CacheHelper cacheHelper, - UserManager userManager, - ILogger logger, - IPostRepository postRepository, - IContainerRepository containerRepository, - IChallengeRepository challengeRepository, - IGameNoticeRepository gameNoticeRepository, - IGameRepository gameRepository, - IContainerManager containerService, - IFileRepository fileService) - { - _logger = logger; - _cacheHelper = cacheHelper; - _fileService = fileService; - _userManager = userManager; - _gameRepository = gameRepository; - _postRepository = postRepository; - _containerService = containerService; - _challengeRepository = challengeRepository; - _containerRepository = containerRepository; - _gameNoticeRepository = gameNoticeRepository; - } - /// /// 添加文章 /// @@ -72,8 +47,8 @@ public EditController( [ProducesResponseType(typeof(string), StatusCodes.Status200OK)] public async Task AddPost([FromBody] PostEditModel model, CancellationToken token) { - var user = await _userManager.GetUserAsync(User); - var res = await _postRepository.CreatePost(new Post().Update(model, user!), token); + var user = await userManager.GetUserAsync(User); + var res = await postRepository.CreatePost(new Post().Update(model, user!), token); return Ok(res.Id); } @@ -93,14 +68,14 @@ public async Task AddPost([FromBody] PostEditModel model, Cancell [ProducesResponseType(typeof(RequestResponse), StatusCodes.Status404NotFound)] public async Task UpdatePost(string id, [FromBody] PostEditModel model, CancellationToken token) { - var post = await _postRepository.GetPostById(id, token); + var post = await postRepository.GetPostById(id, token); if (post is null) return NotFound(new RequestResponse("文章未找到", 404)); - var user = await _userManager.GetUserAsync(User); + var user = await userManager.GetUserAsync(User); - await _postRepository.UpdatePost(post.Update(model, user!), token); + await postRepository.UpdatePost(post.Update(model, user!), token); return Ok(PostDetailModel.FromPost(post)); } @@ -120,12 +95,12 @@ public async Task UpdatePost(string id, [FromBody] PostEditModel [ProducesResponseType(typeof(RequestResponse), StatusCodes.Status404NotFound)] public async Task DeletePost(string id, CancellationToken token) { - var post = await _postRepository.GetPostById(id, token); + var post = await postRepository.GetPostById(id, token); if (post is null) return NotFound(new RequestResponse("文章未找到", 404)); - await _postRepository.RemovePost(post, token); + await postRepository.RemovePost(post, token); return Ok(); } @@ -144,12 +119,12 @@ public async Task DeletePost(string id, CancellationToken token) [ProducesResponseType(typeof(RequestResponse), StatusCodes.Status400BadRequest)] public async Task AddGame([FromBody] GameInfoModel model, CancellationToken token) { - var game = await _gameRepository.CreateGame(new Game().Update(model), token); + var game = await gameRepository.CreateGame(new Game().Update(model), token); if (game is null) return BadRequest(new RequestResponse("比赛创建失败", 400)); - _gameRepository.FlushGameInfoCache(); + gameRepository.FlushGameInfoCache(); return Ok(GameInfoModel.FromGame(game)); } @@ -167,9 +142,9 @@ public async Task AddGame([FromBody] GameInfoModel model, Cancell [HttpGet("Games")] [ProducesResponseType(typeof(ArrayResponse), StatusCodes.Status200OK)] public async Task GetGames([FromQuery] int count, [FromQuery] int skip, CancellationToken token) - => Ok((await _gameRepository.GetGames(count, skip, token)) + => Ok((await gameRepository.GetGames(count, skip, token)) .Select(GameInfoModel.FromGame) - .ToResponse(await _gameRepository.CountAsync(token))); + .ToResponse(await gameRepository.CountAsync(token))); /// /// 获取比赛 @@ -185,7 +160,7 @@ public async Task GetGames([FromQuery] int count, [FromQuery] int [ProducesResponseType(typeof(RequestResponse), StatusCodes.Status404NotFound)] public async Task GetGame([FromRoute] int id, CancellationToken token) { - var game = await _gameRepository.GetGameById(id, token); + var game = await gameRepository.GetGameById(id, token); if (game is null) return NotFound(new RequestResponse("比赛未找到", 404)); @@ -207,7 +182,7 @@ public async Task GetGame([FromRoute] int id, CancellationToken t [ProducesResponseType(typeof(RequestResponse), StatusCodes.Status404NotFound)] public async Task GetTeamHashSalt([FromRoute] int id, CancellationToken token) { - var game = await _gameRepository.GetGameById(id, token); + var game = await gameRepository.GetGameById(id, token); if (game is null) return NotFound(new RequestResponse("比赛未找到", 404)); @@ -232,15 +207,15 @@ public async Task GetTeamHashSalt([FromRoute] int id, Cancellatio [ProducesResponseType(typeof(RequestResponse), StatusCodes.Status404NotFound)] public async Task UpdateGame([FromRoute] int id, [FromBody] GameInfoModel model, CancellationToken token) { - var game = await _gameRepository.GetGameById(id, token); + var game = await gameRepository.GetGameById(id, token); if (game is null) return NotFound(new RequestResponse("比赛未找到", 404)); game.Update(model); - await _gameRepository.SaveAsync(token); - _gameRepository.FlushGameInfoCache(); - await _cacheHelper.FlushScoreboardCache(game.Id, token); + await gameRepository.SaveAsync(token); + gameRepository.FlushGameInfoCache(); + await cacheHelper.FlushScoreboardCache(game.Id, token); return Ok(GameInfoModel.FromGame(game)); } @@ -260,12 +235,12 @@ public async Task UpdateGame([FromRoute] int id, [FromBody] GameI [ProducesResponseType(typeof(RequestResponse), StatusCodes.Status404NotFound)] public async Task DeleteGame([FromRoute] int id, CancellationToken token) { - var game = await _gameRepository.GetGameById(id, token); + var game = await gameRepository.GetGameById(id, token); if (game is null) return NotFound(new RequestResponse("比赛未找到", 404)); - return await _gameRepository.DeleteGame(game, token) switch + return await gameRepository.DeleteGame(game, token) switch { TaskStatus.Success => Ok(), TaskStatus.Failed => BadRequest(new RequestResponse("比赛删除失败,文件可能已受损,请重试")), @@ -287,12 +262,12 @@ public async Task DeleteGame([FromRoute] int id, CancellationToke [ProducesResponseType(typeof(RequestResponse), StatusCodes.Status404NotFound)] public async Task DeleteGameWriteUps([FromRoute] int id, CancellationToken token) { - var game = await _gameRepository.GetGameById(id, token); + var game = await gameRepository.GetGameById(id, token); if (game is null) return NotFound(new RequestResponse("比赛未找到", 404)); - await _gameRepository.DeleteAllWriteUps(game, token); + await gameRepository.DeleteAllWriteUps(game, token); return Ok(); } @@ -318,19 +293,19 @@ public async Task UpdateGamePoster([FromRoute] int id, IFormFile if (file.Length > 3 * 1024 * 1024) return BadRequest(new RequestResponse("文件过大")); - var game = await _gameRepository.GetGameById(id, token); + var game = await gameRepository.GetGameById(id, token); if (game is null) return NotFound(new RequestResponse("比赛未找到", 404)); - var poster = await _fileService.CreateOrUpdateImage(file, "poster", 0, token); + var poster = await fileService.CreateOrUpdateImage(file, "poster", 0, token); if (poster is null) return BadRequest(new RequestResponse("文件创建失败")); game.PosterHash = poster.Hash; - await _gameRepository.SaveAsync(token); - _gameRepository.FlushGameInfoCache(); + await gameRepository.SaveAsync(token); + gameRepository.FlushGameInfoCache(); return Ok(poster.Url()); } @@ -350,12 +325,12 @@ public async Task UpdateGamePoster([FromRoute] int id, IFormFile [ProducesResponseType(typeof(RequestResponse), StatusCodes.Status404NotFound)] public async Task AddGameNotice([FromRoute] int id, [FromBody] GameNoticeModel model, CancellationToken token) { - var game = await _gameRepository.GetGameById(id, token); + var game = await gameRepository.GetGameById(id, token); if (game is null) return NotFound(new RequestResponse("比赛未找到", 404)); - var res = await _gameNoticeRepository.AddNotice(new() + var res = await gameNoticeRepository.AddNotice(new() { Content = model.Content, GameId = game.Id, @@ -380,12 +355,12 @@ public async Task AddGameNotice([FromRoute] int id, [FromBody] Ga [ProducesResponseType(typeof(RequestResponse), StatusCodes.Status404NotFound)] public async Task GetGameNotices([FromRoute] int id, CancellationToken token = default) { - var game = await _gameRepository.GetGameById(id, token); + var game = await gameRepository.GetGameById(id, token); if (game is null) return NotFound(new RequestResponse("比赛未找到", 404)); - return Ok(await _gameNoticeRepository.GetNormalNotices(id, token)); + return Ok(await gameNoticeRepository.GetNormalNotices(id, token)); } /// @@ -404,7 +379,7 @@ public async Task GetGameNotices([FromRoute] int id, Cancellation [ProducesResponseType(typeof(RequestResponse), StatusCodes.Status404NotFound)] public async Task UpdateGameNotice([FromRoute] int id, [FromRoute] int noticeId, [FromBody] GameNoticeModel model, CancellationToken token = default) { - var notice = await _gameNoticeRepository.GetNoticeById(id, noticeId, token); + var notice = await gameNoticeRepository.GetNoticeById(id, noticeId, token); if (notice is null) return NotFound(new RequestResponse("通知未找到", 404)); @@ -413,7 +388,7 @@ public async Task UpdateGameNotice([FromRoute] int id, [FromRoute return BadRequest(new RequestResponse("不能更改系统通知")); notice.Content = model.Content; - return Ok(await _gameNoticeRepository.UpdateNotice(notice, token)); + return Ok(await gameNoticeRepository.UpdateNotice(notice, token)); } /// @@ -432,7 +407,7 @@ public async Task UpdateGameNotice([FromRoute] int id, [FromRoute [ProducesResponseType(typeof(RequestResponse), StatusCodes.Status404NotFound)] public async Task DeleteGameNotice([FromRoute] int id, [FromRoute] int noticeId, CancellationToken token) { - var notice = await _gameNoticeRepository.GetNoticeById(id, noticeId, token); + var notice = await gameNoticeRepository.GetNoticeById(id, noticeId, token); if (notice is null) return NotFound(new RequestResponse("通知未找到", 404)); @@ -440,7 +415,7 @@ public async Task DeleteGameNotice([FromRoute] int id, [FromRoute if (notice.Type != NoticeType.Normal) return BadRequest(new RequestResponse("不能删除系统通知")); - await _gameNoticeRepository.RemoveNotice(notice, token); + await gameNoticeRepository.RemoveNotice(notice, token); return Ok(); } @@ -460,12 +435,12 @@ public async Task DeleteGameNotice([FromRoute] int id, [FromRoute [ProducesResponseType(typeof(RequestResponse), StatusCodes.Status404NotFound)] public async Task AddGameChallenge([FromRoute] int id, [FromBody] ChallengeInfoModel model, CancellationToken token) { - var game = await _gameRepository.GetGameById(id, token); + var game = await gameRepository.GetGameById(id, token); if (game is null) return NotFound(new RequestResponse("比赛未找到", 404)); - var res = await _challengeRepository.CreateChallenge(game, new Challenge() + var res = await challengeRepository.CreateChallenge(game, new Challenge() { Title = model.Title, Type = model.Type, @@ -487,7 +462,7 @@ public async Task AddGameChallenge([FromRoute] int id, [FromBody] [HttpGet("Games/{id}/Challenges")] [ProducesResponseType(typeof(ChallengeInfoModel[]), StatusCodes.Status200OK)] public async Task GetGameChallenges([FromRoute] int id, CancellationToken token) - => Ok((await _challengeRepository.GetChallenges(id, token)).Select(ChallengeInfoModel.FromChallenge)); + => Ok((await challengeRepository.GetChallenges(id, token)).Select(ChallengeInfoModel.FromChallenge)); /// /// 获取比赛题目 @@ -504,12 +479,12 @@ public async Task GetGameChallenges([FromRoute] int id, Cancellat [ProducesResponseType(typeof(RequestResponse), StatusCodes.Status404NotFound)] public async Task GetGameChallenge([FromRoute] int id, [FromRoute] int cId, CancellationToken token) { - var game = await _gameRepository.GetGameById(id, token); + var game = await gameRepository.GetGameById(id, token); if (game is null) return NotFound(new RequestResponse("比赛未找到", 404)); - var res = await _challengeRepository.GetChallenge(id, cId, true, token); + var res = await challengeRepository.GetChallenge(id, cId, true, token); if (res is null) return NotFound(new RequestResponse("题目未找到", 404)); @@ -533,12 +508,12 @@ public async Task GetGameChallenge([FromRoute] int id, [FromRoute [ProducesResponseType(typeof(RequestResponse), StatusCodes.Status404NotFound)] public async Task UpdateGameChallenge([FromRoute] int id, [FromRoute] int cId, [FromBody] ChallengeUpdateModel model, CancellationToken token) { - var game = await _gameRepository.GetGameById(id, token); + var game = await gameRepository.GetGameById(id, token); if (game is null) return NotFound(new RequestResponse("比赛未找到", 404)); - var res = await _challengeRepository.GetChallenge(id, cId, true, token); + var res = await challengeRepository.GetChallenge(id, cId, true, token); if (res is null) return NotFound(new RequestResponse("题目未找到", 404)); @@ -563,11 +538,11 @@ public async Task UpdateGameChallenge([FromRoute] int id, [FromRo if (model.IsEnabled == true) { // will also update IsEnabled - await _challengeRepository.EnsureInstances(res, game, token); + await challengeRepository.EnsureInstances(res, game, token); if (game.IsActive) { - await _gameNoticeRepository.AddNotice(new() + await gameNoticeRepository.AddNotice(new() { Game = game, Type = NoticeType.NewChallenge, @@ -576,11 +551,11 @@ await _gameNoticeRepository.AddNotice(new() } } else - await _challengeRepository.SaveAsync(token); + await challengeRepository.SaveAsync(token); if (game.IsActive && res.IsEnabled && hintUpdated) { - await _gameNoticeRepository.AddNotice(new() + await gameNoticeRepository.AddNotice(new() { Game = game, Type = NoticeType.NewHint, @@ -589,7 +564,7 @@ await _gameNoticeRepository.AddNotice(new() } // always flush scoreboard - await _cacheHelper.FlushScoreboardCache(game.Id, token); + await cacheHelper.FlushScoreboardCache(game.Id, token); return Ok(ChallengeEditDetailModel.FromChallenge(res)); } @@ -609,12 +584,12 @@ await _gameNoticeRepository.AddNotice(new() [ProducesResponseType(typeof(RequestResponse), StatusCodes.Status404NotFound)] public async Task CreateTestContainer([FromRoute] int id, [FromRoute] int cId, CancellationToken token) { - var game = await _gameRepository.GetGameById(id, token); + var game = await gameRepository.GetGameById(id, token); if (game is null) return NotFound(new RequestResponse("比赛未找到", 404)); - var challenge = await _challengeRepository.GetChallenge(id, cId, true, token); + var challenge = await challengeRepository.GetChallenge(id, cId, true, token); if (challenge is null) return NotFound(new RequestResponse("题目未找到", 404)); @@ -625,9 +600,9 @@ public async Task CreateTestContainer([FromRoute] int id, [FromRo if (challenge.ContainerImage is null || challenge.ContainerExposePort is null) return BadRequest(new RequestResponse("容器配置错误")); - var user = await _userManager.GetUserAsync(User); + var user = await userManager.GetUserAsync(User); - var container = await _containerService.CreateContainerAsync(new() + var container = await containerService.CreateContainerAsync(new() { TeamId = "admin", UserId = user!.Id, @@ -643,9 +618,9 @@ public async Task CreateTestContainer([FromRoute] int id, [FromRo return BadRequest(new RequestResponse("容器创建失败")); challenge.TestContainer = container; - await _challengeRepository.SaveAsync(token); + await challengeRepository.SaveAsync(token); - _logger.Log($"成功创建测试容器 {container.ContainerId}", user, TaskStatus.Success); + logger.Log($"成功创建测试容器 {container.ContainerId}", user, TaskStatus.Success); return Ok(ContainerInfoModel.FromContainer(container)); } @@ -665,12 +640,12 @@ public async Task CreateTestContainer([FromRoute] int id, [FromRo [ProducesResponseType(typeof(RequestResponse), StatusCodes.Status404NotFound)] public async Task DestroyTestContainer([FromRoute] int id, [FromRoute] int cId, CancellationToken token) { - var game = await _gameRepository.GetGameById(id, token); + var game = await gameRepository.GetGameById(id, token); if (game is null) return NotFound(new RequestResponse("比赛未找到", 404)); - var challenge = await _challengeRepository.GetChallenge(id, cId, true, token); + var challenge = await challengeRepository.GetChallenge(id, cId, true, token); if (challenge is null) return NotFound(new RequestResponse("题目未找到", 404)); @@ -678,8 +653,8 @@ public async Task DestroyTestContainer([FromRoute] int id, [FromR if (challenge.TestContainer is null) return Ok(); - await _containerService.DestroyContainerAsync(challenge.TestContainer, token); - await _containerRepository.RemoveContainer(challenge.TestContainer, token); + await containerService.DestroyContainerAsync(challenge.TestContainer, token); + await containerRepository.RemoveContainer(challenge.TestContainer, token); return Ok(); } @@ -699,20 +674,20 @@ public async Task DestroyTestContainer([FromRoute] int id, [FromR [ProducesResponseType(typeof(RequestResponse), StatusCodes.Status404NotFound)] public async Task RemoveGameChallenge([FromRoute] int id, [FromRoute] int cId, CancellationToken token) { - var game = await _gameRepository.GetGameById(id, token); + var game = await gameRepository.GetGameById(id, token); if (game is null) return NotFound(new RequestResponse("比赛未找到", 404)); - var res = await _challengeRepository.GetChallenge(id, cId, true, token); + var res = await challengeRepository.GetChallenge(id, cId, true, token); if (res is null) return NotFound(new RequestResponse("题目未找到", 404)); - await _challengeRepository.RemoveChallenge(res, token); + await challengeRepository.RemoveChallenge(res, token); // always flush scoreboard - await _cacheHelper.FlushScoreboardCache(game.Id, token); + await cacheHelper.FlushScoreboardCache(game.Id, token); return Ok(); } @@ -733,12 +708,12 @@ public async Task RemoveGameChallenge([FromRoute] int id, [FromRo [ProducesResponseType(typeof(RequestResponse), StatusCodes.Status404NotFound)] public async Task UpdateAttachment([FromRoute] int id, [FromRoute] int cId, [FromBody] AttachmentCreateModel model, CancellationToken token) { - var game = await _gameRepository.GetGameById(id, token); + var game = await gameRepository.GetGameById(id, token); if (game is null) return NotFound(new RequestResponse("比赛未找到", 404)); - var challenge = await _challengeRepository.GetChallenge(id, cId, true, token); + var challenge = await challengeRepository.GetChallenge(id, cId, true, token); if (challenge is null) return NotFound(new RequestResponse("题目未找到", 404)); @@ -746,7 +721,7 @@ public async Task UpdateAttachment([FromRoute] int id, [FromRoute if (challenge.Type == ChallengeType.DynamicAttachment) return BadRequest(new RequestResponse("动态附件题目请使用 assets API 上传附件")); - await _challengeRepository.UpdateAttachment(challenge, model, token); + await challengeRepository.UpdateAttachment(challenge, model, token); return Ok(); } @@ -767,17 +742,17 @@ public async Task UpdateAttachment([FromRoute] int id, [FromRoute [ProducesResponseType(typeof(RequestResponse), StatusCodes.Status404NotFound)] public async Task AddFlags([FromRoute] int id, [FromRoute] int cId, [FromBody] FlagCreateModel[] models, CancellationToken token) { - var game = await _gameRepository.GetGameById(id, token); + var game = await gameRepository.GetGameById(id, token); if (game is null) return NotFound(new RequestResponse("比赛未找到", 404)); - var challenge = await _challengeRepository.GetChallenge(id, cId, true, token); + var challenge = await challengeRepository.GetChallenge(id, cId, true, token); if (challenge is null) return NotFound(new RequestResponse("题目未找到", 404)); - await _challengeRepository.AddFlags(challenge, models, token); + await challengeRepository.AddFlags(challenge, models, token); return Ok(); } @@ -798,16 +773,16 @@ public async Task AddFlags([FromRoute] int id, [FromRoute] int cI [ProducesResponseType(typeof(RequestResponse), StatusCodes.Status404NotFound)] public async Task RemoveFlag([FromRoute] int id, [FromRoute] int cId, [FromRoute] int fId, CancellationToken token) { - var game = await _gameRepository.GetGameById(id, token); + var game = await gameRepository.GetGameById(id, token); if (game is null) return NotFound(new RequestResponse("比赛未找到", 404)); - var challenge = await _challengeRepository.GetChallenge(id, cId, true, token); + var challenge = await challengeRepository.GetChallenge(id, cId, true, token); if (challenge is null) return NotFound(new RequestResponse("题目未找到", 404)); - return Ok(await _challengeRepository.RemoveFlag(challenge, fId, token)); + return Ok(await challengeRepository.RemoveFlag(challenge, fId, token)); } } diff --git a/src/GZCTF/Controllers/GameController.cs b/src/GZCTF/Controllers/GameController.cs index b73e3629f..72945d11b 100644 --- a/src/GZCTF/Controllers/GameController.cs +++ b/src/GZCTF/Controllers/GameController.cs @@ -1,20 +1,15 @@ using System.Net.Mime; using System.Security.Claims; -using System.Security.Policy; using System.Threading.Channels; using GZCTF.Middlewares; using GZCTF.Models.Request.Admin; using GZCTF.Models.Request.Edit; using GZCTF.Models.Request.Game; -using GZCTF.Repositories; using GZCTF.Repositories.Interface; using GZCTF.Utils; using Microsoft.AspNetCore.Identity; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.RateLimiting; -using NPOI.HSSF.Record; -using NPOI.OpenXmlFormats.Dml; -using YamlDotNet.Core.Tokens; namespace GZCTF.Controllers; @@ -27,58 +22,23 @@ namespace GZCTF.Controllers; [ProducesResponseType(typeof(RequestResponse), StatusCodes.Status400BadRequest)] [ProducesResponseType(typeof(RequestResponse), StatusCodes.Status401Unauthorized)] [ProducesResponseType(typeof(RequestResponse), StatusCodes.Status403Forbidden)] -public class GameController : ControllerBase +public class GameController( + ILogger logger, + UserManager userManager, + ChannelWriter channelWriter, + IFileRepository fileService, + IGameRepository gameRepository, + ITeamRepository teamRepository, + IGameEventRepository eventRepository, + IGameNoticeRepository noticeRepository, + IInstanceRepository instanceRepository, + ICheatInfoRepository cheatInfoRepository, + IChallengeRepository challengeRepository, + IContainerRepository containerRepository, + IGameEventRepository gameEventRepository, + ISubmissionRepository submissionRepository, + IParticipationRepository participationRepository) : ControllerBase { - private readonly ILogger _logger; - private readonly UserManager _userManager; - private readonly ChannelWriter _checkerChannelWriter; - private readonly IFileRepository _fileService; - private readonly IGameRepository _gameRepository; - private readonly ITeamRepository _teamRepository; - private readonly IGameEventRepository _eventRepository; - private readonly IInstanceRepository _instanceRepository; - private readonly IGameNoticeRepository _noticeRepository; - private readonly IGameEventRepository _gameEventRepository; - private readonly IContainerRepository _containerRepository; - private readonly ICheatInfoRepository _cheatInfoRepository; - private readonly IChallengeRepository _challengeRepository; - private readonly ISubmissionRepository _submissionRepository; - private readonly IParticipationRepository _participationRepository; - - public GameController( - ILogger logger, - UserManager userManager, - ChannelWriter channelWriter, - IFileRepository fileService, - IGameRepository gameRepository, - ITeamRepository teamRepository, - IGameEventRepository eventRepository, - IGameNoticeRepository noticeRepository, - IInstanceRepository instanceRepository, - ICheatInfoRepository cheatInfoRepository, - IChallengeRepository challengeRepository, - IContainerRepository containerRepository, - IGameEventRepository gameEventRepository, - ISubmissionRepository submissionRepository, - IParticipationRepository participationRepository) - { - _logger = logger; - _userManager = userManager; - _checkerChannelWriter = channelWriter; - _fileService = fileService; - _gameRepository = gameRepository; - _teamRepository = teamRepository; - _eventRepository = eventRepository; - _noticeRepository = noticeRepository; - _instanceRepository = instanceRepository; - _challengeRepository = challengeRepository; - _containerRepository = containerRepository; - _gameEventRepository = gameEventRepository; - _cheatInfoRepository = cheatInfoRepository; - _submissionRepository = submissionRepository; - _participationRepository = participationRepository; - } - /// /// 获取最新的比赛 /// @@ -90,7 +50,7 @@ public GameController( [HttpGet] [ProducesResponseType(typeof(BasicGameInfoModel[]), StatusCodes.Status200OK)] public async Task Games(CancellationToken token) - => Ok(await _gameRepository.GetBasicGameInfo(10, 0, token)); + => Ok(await gameRepository.GetBasicGameInfo(10, 0, token)); /// /// 获取比赛详细信息 @@ -112,7 +72,7 @@ public async Task Games(int id, CancellationToken token) if (context.Game is null) return NotFound(new RequestResponse("比赛未找到", 404)); - var count = await _participationRepository.GetParticipationCount(context.Game, token); + var count = await participationRepository.GetParticipationCount(context.Game, token); return Ok(DetailedGameInfoModel.FromGame(context.Game, count) .WithParticipation(context.Participation)); @@ -137,7 +97,7 @@ public async Task Games(int id, CancellationToken token) [ProducesResponseType(typeof(RequestResponse), StatusCodes.Status403Forbidden)] public async Task JoinGame(int id, [FromBody] GameJoinModel model, CancellationToken token) { - var game = await _gameRepository.GetGameById(id, token); + var game = await gameRepository.GetGameById(id, token); if (game is null) return NotFound(new RequestResponse("比赛未找到", 404)); @@ -151,8 +111,8 @@ public async Task JoinGame(int id, [FromBody] GameJoinModel model if (game.Organizations is { Count: > 0 } && game.Organizations.All(o => o != model.Organization)) return BadRequest(new RequestResponse("无效的参赛单位")); - var user = await _userManager.GetUserAsync(User); - var team = await _teamRepository.GetTeamById(model.TeamId, token); + var user = await userManager.GetUserAsync(User); + var team = await teamRepository.GetTeamById(model.TeamId, token); if (team is null) return NotFound(new RequestResponse("队伍未找到", 404)); @@ -161,14 +121,14 @@ public async Task JoinGame(int id, [FromBody] GameJoinModel model return BadRequest(new RequestResponse("您不是此队伍的队员")); // 如果已经报名(非拒绝状态) - if (await _participationRepository.CheckRepeatParticipation(user!, game, token)) + if (await participationRepository.CheckRepeatParticipation(user!, game, token)) return BadRequest(new RequestResponse("您已经在其他队伍报名参赛")); // 移除所有的已经存在的报名 - await _participationRepository.RemoveUserParticipations(user!, game, token); + await participationRepository.RemoveUserParticipations(user!, game, token); // 根据队伍获取报名信息 - var part = await _participationRepository.GetParticipation(team, game, token); + var part = await participationRepository.GetParticipation(team, game, token); // 如果队伍未报名 if (part is null) @@ -179,10 +139,10 @@ public async Task JoinGame(int id, [FromBody] GameJoinModel model Game = game, Team = team, Organization = model.Organization, - Token = _gameRepository.GetToken(game, team) + Token = gameRepository.GetToken(game, team) }; - _participationRepository.Add(part); + participationRepository.Add(part); } if (game.TeamMemberCountLimit > 0 && part.Members.Count >= game.TeamMemberCountLimit) @@ -196,12 +156,12 @@ public async Task JoinGame(int id, [FromBody] GameJoinModel model if (part.Status == ParticipationStatus.Rejected) part.Status = ParticipationStatus.Pending; - await _participationRepository.SaveAsync(token); + await participationRepository.SaveAsync(token); if (game.AcceptWithoutReview) - await _participationRepository.UpdateParticipationStatus(part, ParticipationStatus.Accepted, token); + await participationRepository.UpdateParticipationStatus(part, ParticipationStatus.Accepted, token); - _logger.Log($"[{team!.Name}] 成功报名了比赛 [{game.Title}]", user, TaskStatus.Success); + logger.Log($"[{team!.Name}] 成功报名了比赛 [{game.Title}]", user, TaskStatus.Success); return Ok(); } @@ -224,14 +184,14 @@ public async Task JoinGame(int id, [FromBody] GameJoinModel model [ProducesResponseType(typeof(RequestResponse), StatusCodes.Status403Forbidden)] public async Task LeaveGame(int id, CancellationToken token) { - var game = await _gameRepository.GetGameById(id, token); + var game = await gameRepository.GetGameById(id, token); if (game is null) return NotFound(new RequestResponse("比赛未找到", 404)); - var user = await _userManager.GetUserAsync(User); + var user = await userManager.GetUserAsync(User); - var part = await _participationRepository.GetParticipation(user!, game, token); + var part = await participationRepository.GetParticipation(user!, game, token); if (part is null || part.Members.All(u => u.UserId != user!.Id)) return BadRequest(new RequestResponse("无法退出未报名的比赛")); @@ -244,9 +204,9 @@ public async Task LeaveGame(int id, CancellationToken token) part.Members.RemoveWhere(u => u.UserId == user!.Id); if (part.Members.Count == 0) - await _participationRepository.RemoveParticipation(part, token); + await participationRepository.RemoveParticipation(part, token); else - await _participationRepository.SaveAsync(token); + await participationRepository.SaveAsync(token); return Ok(); } @@ -266,7 +226,7 @@ public async Task LeaveGame(int id, CancellationToken token) [ProducesResponseType(typeof(RequestResponse), StatusCodes.Status404NotFound)] public async Task Scoreboard([FromRoute] int id, CancellationToken token) { - var game = await _gameRepository.GetGameById(id, token); + var game = await gameRepository.GetGameById(id, token); if (game is null) return NotFound(new RequestResponse("比赛未找到", 404)); @@ -274,7 +234,7 @@ public async Task Scoreboard([FromRoute] int id, CancellationToke if (DateTimeOffset.UtcNow < game.StartTimeUTC) return BadRequest(new RequestResponse("比赛未开始")); - return Ok(await _gameRepository.GetScoreboard(game, token)); + return Ok(await gameRepository.GetScoreboard(game, token)); } /// @@ -294,7 +254,7 @@ public async Task Scoreboard([FromRoute] int id, CancellationToke [ProducesResponseType(typeof(RequestResponse), StatusCodes.Status404NotFound)] public async Task Notices([FromRoute] int id, [FromQuery] int count = 100, [FromQuery] int skip = 0, CancellationToken token = default) { - var game = await _gameRepository.GetGameById(id, token); + var game = await gameRepository.GetGameById(id, token); if (game is null) return NotFound(new RequestResponse("比赛未找到", 404)); @@ -302,7 +262,7 @@ public async Task Notices([FromRoute] int id, [FromQuery] int cou if (DateTimeOffset.UtcNow < game.StartTimeUTC) return BadRequest(new RequestResponse("比赛未开始")); - return Ok(await _noticeRepository.GetNotices(game.Id, count, skip, token)); + return Ok(await noticeRepository.GetNotices(game.Id, count, skip, token)); } /// @@ -324,7 +284,7 @@ public async Task Notices([FromRoute] int id, [FromQuery] int cou [ProducesResponseType(typeof(RequestResponse), StatusCodes.Status404NotFound)] public async Task Events([FromRoute] int id, [FromQuery] bool hideContainer = false, [FromQuery] int count = 100, [FromQuery] int skip = 0, CancellationToken token = default) { - var game = await _gameRepository.GetGameById(id, token); + var game = await gameRepository.GetGameById(id, token); if (game is null) return NotFound(new RequestResponse("比赛未找到", 404)); @@ -332,7 +292,7 @@ public async Task Events([FromRoute] int id, [FromQuery] bool hid if (DateTimeOffset.UtcNow < game.StartTimeUTC) return BadRequest(new RequestResponse("比赛未开始")); - return Ok(await _eventRepository.GetEvents(game.Id, hideContainer, count, skip, token)); + return Ok(await eventRepository.GetEvents(game.Id, hideContainer, count, skip, token)); } /// @@ -354,7 +314,7 @@ public async Task Events([FromRoute] int id, [FromQuery] bool hid [ProducesResponseType(typeof(RequestResponse), StatusCodes.Status404NotFound)] public async Task Submissions([FromRoute] int id, [FromQuery] AnswerResult? type = null, [FromQuery] int count = 100, [FromQuery] int skip = 0, CancellationToken token = default) { - var game = await _gameRepository.GetGameById(id, token); + var game = await gameRepository.GetGameById(id, token); if (game is null) return NotFound(new RequestResponse("比赛未找到", 404)); @@ -362,7 +322,7 @@ public async Task Submissions([FromRoute] int id, [FromQuery] Ans if (DateTimeOffset.UtcNow < game.StartTimeUTC) return BadRequest(new RequestResponse("比赛未开始")); - return Ok(await _submissionRepository.GetSubmissions(game, type, count, skip, token)); + return Ok(await submissionRepository.GetSubmissions(game, type, count, skip, token)); } /// @@ -381,7 +341,7 @@ public async Task Submissions([FromRoute] int id, [FromQuery] Ans [ProducesResponseType(typeof(RequestResponse), StatusCodes.Status404NotFound)] public async Task CheatInfo([FromRoute] int id, CancellationToken token = default) { - var game = await _gameRepository.GetGameById(id, token); + var game = await gameRepository.GetGameById(id, token); if (game is null) return NotFound(new RequestResponse("比赛未找到", 404)); @@ -389,7 +349,7 @@ public async Task CheatInfo([FromRoute] int id, CancellationToken if (DateTimeOffset.UtcNow < game.StartTimeUTC) return BadRequest(new RequestResponse("比赛未开始")); - return Ok((await _cheatInfoRepository.GetCheatInfoByGameId(game.Id, token)) + return Ok((await cheatInfoRepository.GetCheatInfoByGameId(game.Id, token)) .Select(CheatInfoModel.FromCheatInfo)); } @@ -407,7 +367,7 @@ public async Task CheatInfo([FromRoute] int id, CancellationToken [HttpGet("Games/{id}/Captures")] [ProducesResponseType(typeof(ChallengeTrafficModel[]), StatusCodes.Status200OK)] public async Task GetChallengesWithTrafficCapturing([FromRoute] int id, CancellationToken token) - => Ok((await _challengeRepository.GetChallengesWithTrafficCapturing(id, token)) + => Ok((await challengeRepository.GetChallengesWithTrafficCapturing(id, token)) .Select(ChallengeTrafficModel.FromChallenge)); /// @@ -436,7 +396,7 @@ public async Task GetChallengeTraffic([FromRoute] int challengeId if (participationIds.Count == 0) return NotFound(new RequestResponse("未找到相关捕获信息", 404)); - var participations = await _participationRepository.GetParticipationsByIds(participationIds, token); + var participations = await participationRepository.GetParticipationsByIds(participationIds, token); return Ok(participations.Select(p => TeamTrafficModel.FromParticipation(p, challengeId))); } @@ -553,7 +513,7 @@ public async Task ChallengesWithTeamInfo([FromRoute] int id, Canc if (context.Result is not null) return context.Result; - var scoreboard = await _gameRepository.GetScoreboard(context.Game!, token); + var scoreboard = await gameRepository.GetScoreboard(context.Game!, token); var boarditem = scoreboard.Items.FirstOrDefault(i => i.Id == context.Participation!.TeamId); @@ -599,7 +559,7 @@ public async Task Participations([FromRoute] int id, Cancellation if (context.Game is null) return NotFound(new RequestResponse("比赛未找到")); - return Ok((await _participationRepository.GetParticipations(context.Game!, token)) + return Ok((await participationRepository.GetParticipations(context.Game!, token)) .Select(ParticipationInfoModel.FromParticipation)); } @@ -621,7 +581,7 @@ public async Task Participations([FromRoute] int id, Cancellation [ProducesResponseType(typeof(RequestResponse), StatusCodes.Status404NotFound)] public async Task ScoreboardSheet([FromRoute] int id, CancellationToken token = default) { - var game = await _gameRepository.GetGameById(id, token); + var game = await gameRepository.GetGameById(id, token); if (game is null) return NotFound(new RequestResponse("比赛未找到")); @@ -631,7 +591,7 @@ public async Task ScoreboardSheet([FromRoute] int id, Cancellatio try { - var scoreboard = await _gameRepository.GetScoreboardWithMembers(game, token); + var scoreboard = await gameRepository.GetScoreboardWithMembers(game, token); var stream = ExcelHelper.GetScoreboardExcel(scoreboard, game); stream.Seek(0, SeekOrigin.Begin); @@ -641,8 +601,8 @@ public async Task ScoreboardSheet([FromRoute] int id, Cancellatio } catch (Exception ex) { - _logger.SystemLog("下载积分榜遇到错误", TaskStatus.Failed, LogLevel.Error); - _logger.LogError(ex, ex.Message); + logger.SystemLog("下载积分榜遇到错误", TaskStatus.Failed, LogLevel.Error); + logger.LogError(ex, ex.Message); return BadRequest(new RequestResponse("遇到问题,请重试")); } } @@ -665,7 +625,7 @@ public async Task ScoreboardSheet([FromRoute] int id, Cancellatio [ProducesResponseType(typeof(RequestResponse), StatusCodes.Status404NotFound)] public async Task SubmissionSheet([FromRoute] int id, CancellationToken token = default) { - var game = await _gameRepository.GetGameById(id, token); + var game = await gameRepository.GetGameById(id, token); if (game is null) return NotFound(new RequestResponse("比赛未找到")); @@ -673,7 +633,7 @@ public async Task SubmissionSheet([FromRoute] int id, Cancellatio if (DateTimeOffset.UtcNow < game.StartTimeUTC) return BadRequest(new RequestResponse("比赛未开始")); - var submissions = await _submissionRepository.GetSubmissions(game, count: 0, token: token); + var submissions = await submissionRepository.GetSubmissions(game, count: 0, token: token); var stream = ExcelHelper.GetSubmissionExcel(submissions); stream.Seek(0, SeekOrigin.Begin); @@ -710,7 +670,7 @@ public async Task GetChallenge([FromRoute] int id, [FromRoute] in if (context.Result is not null) return context.Result; - var instance = await _instanceRepository.GetInstance(context.Participation!, challengeId, token); + var instance = await instanceRepository.GetInstance(context.Participation!, challengeId, token); if (instance is null) return NotFound(new RequestResponse("题目未找到或动态附件分配失败", 404)); @@ -756,10 +716,10 @@ public async Task Submit([FromRoute] int id, [FromRoute] int chal SubmitTimeUTC = DateTimeOffset.UtcNow, }; - submission = await _submissionRepository.AddSubmission(submission, token); + submission = await submissionRepository.AddSubmission(submission, token); // send to flag checker service - await _checkerChannelWriter.WriteAsync(submission, token); + await channelWriter.WriteAsync(submission, token); return Ok(submission.Id); } @@ -784,7 +744,7 @@ public async Task Status([FromRoute] int id, [FromRoute] int chal { var userId = User.FindFirstValue(ClaimTypes.NameIdentifier); - var submission = await _submissionRepository.GetSubmission(id, challengeId, userId!, submitId, token); + var submission = await submissionRepository.GetSubmission(id, challengeId, userId!, submitId, token); if (submission is null) return NotFound(new RequestResponse("提交未找到", 404)); @@ -865,18 +825,18 @@ public async Task SubmitWriteup([FromRoute] int id, IFormFile fil var wp = context.Participation!.Writeup; if (wp is not null) - await _fileService.DeleteFile(wp, token); + await fileService.DeleteFile(wp, token); - wp = await _fileService.CreateOrUpdateFile(file, $"Writeup-{game.Id}-{team.Id}-{DateTimeOffset.Now:yyyyMMdd-HH.mm.ssZ}.pdf", token); + wp = await fileService.CreateOrUpdateFile(file, $"Writeup-{game.Id}-{team.Id}-{DateTimeOffset.Now:yyyyMMdd-HH.mm.ssZ}.pdf", token); if (wp is null) return BadRequest(new RequestResponse("保存文件失败")); part.Writeup = wp; - await _participationRepository.SaveAsync(token); + await participationRepository.SaveAsync(token); - _logger.Log($"{team.Name} 成功提交 {game.Title} 的 Writeup", context.User!, TaskStatus.Success); + logger.Log($"{team.Name} 成功提交 {game.Title} 的 Writeup", context.User!, TaskStatus.Success); return Ok(); } @@ -907,7 +867,7 @@ public async Task CreateContainer([FromRoute] int id, [FromRoute] if (context.Result is not null) return context.Result; - var instance = await _instanceRepository.GetInstance(context.Participation!, challengeId, token); + var instance = await instanceRepository.GetInstance(context.Participation!, challengeId, token); if (instance is null) return NotFound(new RequestResponse("题目未找到", 404)); @@ -926,10 +886,10 @@ public async Task CreateContainer([FromRoute] int id, [FromRoute] if (instance.Container.Status == ContainerStatus.Running) return BadRequest(new RequestResponse("题目已经创建容器")); - await _containerRepository.RemoveContainer(instance.Container, token); + await containerRepository.RemoveContainer(instance.Container, token); } - return await _instanceRepository.CreateContainer(instance, context.Participation!.Team, context.User!, context.Game!.ContainerCountLimit, token) switch + return await instanceRepository.CreateContainer(instance, context.Participation!.Team, context.User!, context.Game!.ContainerCountLimit, token) switch { null or (TaskStatus.Failed, null) => BadRequest(new RequestResponse("题目创建容器失败")), (TaskStatus.Denied, null) => BadRequest(new RequestResponse($"队伍容器数目不能超过 {context.Game.ContainerCountLimit}")), @@ -963,7 +923,7 @@ public async Task ProlongContainer([FromRoute] int id, [FromRoute if (context.Result is not null) return context.Result; - var instance = await _instanceRepository.GetInstance(context.Participation!, challengeId, token); + var instance = await instanceRepository.GetInstance(context.Participation!, challengeId, token); if (instance is null) return NotFound(new RequestResponse("题目未找到", 404)); @@ -977,7 +937,7 @@ public async Task ProlongContainer([FromRoute] int id, [FromRoute if (instance.Container.ExpectStopAt - DateTimeOffset.UtcNow > TimeSpan.FromMinutes(10)) return BadRequest(new RequestResponse("容器时间尚不可延长")); - await _instanceRepository.ProlongContainer(instance.Container, TimeSpan.FromHours(2), token); + await instanceRepository.ProlongContainer(instance.Container, TimeSpan.FromHours(2), token); return Ok(ContainerInfoModel.FromContainer(instance.Container)); } @@ -1008,7 +968,7 @@ public async Task DeleteContainer([FromRoute] int id, [FromRoute] if (context.Result is not null) return context.Result; - var instance = await _instanceRepository.GetInstance(context.Participation!, challengeId, token); + var instance = await instanceRepository.GetInstance(context.Participation!, challengeId, token); if (instance is null) return NotFound(new RequestResponse("题目未找到", 404)); @@ -1027,12 +987,12 @@ public async Task DeleteContainer([FromRoute] int id, [FromRoute] var destroyId = instance.Container.ContainerId; - if (!await _instanceRepository.DestroyContainer(instance.Container, token)) + if (!await instanceRepository.DestroyContainer(instance.Container, token)) return BadRequest(new RequestResponse("题目删除容器失败")); instance.LastContainerOperation = DateTimeOffset.UtcNow; - await _gameEventRepository.AddEvent(new() + await gameEventRepository.AddEvent(new() { Type = EventType.ContainerDestroy, GameId = context.Game!.Id, @@ -1041,7 +1001,7 @@ await _gameEventRepository.AddEvent(new() Content = $"{instance.Challenge.Title}#{instance.Challenge.Id} 销毁容器实例" }, token); - _logger.Log($"{context.Participation!.Team.Name} 销毁题目 {instance.Challenge.Title} 的容器实例 [{destroyId}]", context.User, TaskStatus.Success); + logger.Log($"{context.Participation!.Team.Name} 销毁题目 {instance.Challenge.Title} 的容器实例 [{destroyId}]", context.User, TaskStatus.Success); return Ok(); } @@ -1065,14 +1025,14 @@ private async Task GetContextInfo(int id, int challengeId = 0, bool { 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) return res.WithResult(NotFound(new RequestResponse("比赛未找到", 404))); - var part = await _participationRepository.GetParticipation(res.User!, res.Game, token); + var part = await participationRepository.GetParticipation(res.User!, res.Game, token); if (part is null) return res.WithResult(BadRequest(new RequestResponse("您尚未参赛"))); @@ -1090,7 +1050,7 @@ private async Task GetContextInfo(int id, int challengeId = 0, bool if (challengeId > 0) { - var challenge = await _challengeRepository.GetChallenge(id, challengeId, withFlag, token); + var challenge = await challengeRepository.GetChallenge(id, challengeId, withFlag, token); if (challenge is null) return res.WithResult(NotFound(new RequestResponse("题目未找到", 404))); diff --git a/src/GZCTF/Controllers/InfoController.cs b/src/GZCTF/Controllers/InfoController.cs index 0cc446df9..7776216e8 100644 --- a/src/GZCTF/Controllers/InfoController.cs +++ b/src/GZCTF/Controllers/InfoController.cs @@ -13,23 +13,11 @@ namespace GZCTF.Controllers; /// [Route("api")] [ApiController] -public class InfoController : ControllerBase +public class InfoController(IPostRepository postRepository, + IRecaptchaExtension recaptchaExtension, + IOptionsSnapshot globalConfig, + IOptionsSnapshot accountPolicy) : ControllerBase { - private readonly IOptionsSnapshot _accountPolicy; - private readonly IOptionsSnapshot _globalConfig; - private readonly IPostRepository _postRepository; - private readonly IRecaptchaExtension _recaptchaExtension; - - public InfoController(IPostRepository postRepository, - IRecaptchaExtension recaptchaExtension, - IOptionsSnapshot globalConfig, - IOptionsSnapshot accountPolicy) - { - _globalConfig = globalConfig; - _accountPolicy = accountPolicy; - _postRepository = postRepository; - _recaptchaExtension = recaptchaExtension; - } /// /// 获取最新文章 @@ -42,7 +30,7 @@ public InfoController(IPostRepository postRepository, [HttpGet("Posts/Latest")] [ProducesResponseType(typeof(PostInfoModel[]), StatusCodes.Status200OK)] public async Task GetLatestPosts(CancellationToken token) - => Ok((await _postRepository.GetPosts(token)).Take(20).Select(PostInfoModel.FromPost)); + => Ok((await postRepository.GetPosts(token)).Take(20).Select(PostInfoModel.FromPost)); /// /// 获取全部文章 @@ -55,7 +43,7 @@ public async Task GetLatestPosts(CancellationToken token) [HttpGet("Posts")] [ProducesResponseType(typeof(PostInfoModel[]), StatusCodes.Status200OK)] public async Task GetPosts(CancellationToken token) - => Ok((await _postRepository.GetPosts(token)).Select(PostInfoModel.FromPost)); + => Ok((await postRepository.GetPosts(token)).Select(PostInfoModel.FromPost)); /// /// 获取文章详情 @@ -71,7 +59,7 @@ public async Task GetPosts(CancellationToken token) [ProducesResponseType(typeof(RequestResponse), StatusCodes.Status404NotFound)] public async Task GetPost(string id, CancellationToken token) { - var post = await _postRepository.GetPostByIdFromCache(id, token); + var post = await postRepository.GetPostByIdFromCache(id, token); if (post is null) return NotFound(new RequestResponse("文章未找到", 404)); @@ -88,7 +76,7 @@ public async Task GetPost(string id, CancellationToken token) /// 成功获取配置信息 [HttpGet("Config")] [ProducesResponseType(typeof(GlobalConfig), StatusCodes.Status200OK)] - public IActionResult GetGlobalConfig() => Ok(_globalConfig.Value); + public IActionResult GetGlobalConfig() => Ok(globalConfig.Value); /// /// 获取 Recaptcha SiteKey @@ -100,5 +88,5 @@ public async Task GetPost(string id, CancellationToken token) [HttpGet("SiteKey")] [ProducesResponseType(typeof(string), StatusCodes.Status200OK)] public IActionResult GetRecaptchaSiteKey() - => Ok(_accountPolicy.Value.UseGoogleRecaptcha ? _recaptchaExtension.SiteKey() : "NOTOKEN"); + => Ok(accountPolicy.Value.UseGoogleRecaptcha ? recaptchaExtension.SiteKey() : "NOTOKEN"); } diff --git a/src/GZCTF/Controllers/ProxyController.cs b/src/GZCTF/Controllers/ProxyController.cs index 10786da17..ad555334c 100644 --- a/src/GZCTF/Controllers/ProxyController.cs +++ b/src/GZCTF/Controllers/ProxyController.cs @@ -16,14 +16,11 @@ namespace GZCTF.Controllers; /// [ApiController] [Route("api/[controller]")] -public class ProxyController : ControllerBase +public class ProxyController(ILogger logger, IDistributedCache cache, + IOptions provider, IContainerRepository containerRepository) : ControllerBase { - private readonly ILogger _logger; - private readonly IDistributedCache _cache; - private readonly IContainerRepository _containerRepository; - - private readonly bool _enablePlatformProxy = false; - private readonly bool _enableTrafficCapture = false; + private readonly bool _enablePlatformProxy = provider.Value.PortMappingType == ContainerPortMappingType.PlatformProxy; + private readonly bool _enableTrafficCapture = provider.Value.EnableTrafficCapture; private const int BUFFER_SIZE = 1024 * 4; private const uint CONNECTION_LIMIT = 64; private readonly JsonSerializerOptions _JsonOptions = new() @@ -32,16 +29,6 @@ public class ProxyController : ControllerBase WriteIndented = true, }; - public ProxyController(ILogger logger, IDistributedCache cache, - IOptions provider, IContainerRepository containerRepository) - { - _cache = cache; - _logger = logger; - _enablePlatformProxy = provider.Value.PortMappingType == ContainerPortMappingType.PlatformProxy; - _enableTrafficCapture = provider.Value.EnableTrafficCapture; - _containerRepository = containerRepository; - } - /// /// 采用 websocket 代理 TCP 流量 /// @@ -67,7 +54,7 @@ public async Task ProxyForInstance(string id, CancellationToken t if (!await IncrementConnectionCount(id)) return BadRequest(new RequestResponse("容器连接数已达上限")); - var container = await _containerRepository.GetContainerWithInstanceById(id, token); + var container = await containerRepository.GetContainerWithInstanceById(id, token); if (container is null || container.Instance is null || !container.IsProxy) return NotFound(new RequestResponse("不存在的容器")); @@ -135,7 +122,7 @@ public async Task ProxyForNoInstance(string id, CancellationToken if (!HttpContext.WebSockets.IsWebSocketRequest) return NoContent(); - var container = await _containerRepository.GetContainerById(id, token); + var container = await containerRepository.GetContainerById(id, token); if (container is null || container.InstanceId != 0 || !container.IsProxy) return NotFound(new RequestResponse("不存在的容器")); @@ -174,7 +161,7 @@ internal async Task DoContainerProxy(string id, IPEndPoint client } catch (SocketException e) { - _logger.SystemLog($"容器连接失败({e.SocketErrorCode}),可能正在启动中或请检查网络配置 -> {target.Address}:{target.Port}", TaskStatus.Failed, LogLevel.Warning); + logger.SystemLog($"容器连接失败({e.SocketErrorCode}),可能正在启动中或请检查网络配置 -> {target.Address}:{target.Port}", TaskStatus.Failed, LogLevel.Warning); return new JsonResult(new RequestResponse($"容器连接失败({e.SocketErrorCode})", 418)) { StatusCode = 418 }; } @@ -183,11 +170,11 @@ internal async Task DoContainerProxy(string id, IPEndPoint client try { var (tx, rx) = await RunProxy(stream, ws, token); - _logger.SystemLog($"[{id}] {client.Address} -> {target.Address}:{target.Port}, tx {tx}, rx {rx}", TaskStatus.Success, LogLevel.Debug); + logger.SystemLog($"[{id}] {client.Address} -> {target.Address}:{target.Port}, tx {tx}, rx {rx}", TaskStatus.Success, LogLevel.Debug); } catch (Exception e) { - _logger.LogError(e, "代理过程发生错误"); + logger.LogError(e, "代理过程发生错误"); } finally { @@ -281,15 +268,15 @@ internal async Task DoContainerProxy(string id, IPEndPoint client internal async Task ValidateContainer(string id, CancellationToken token = default) { var key = CacheKey.ConnectionCount(id); - var bytes = await _cache.GetAsync(key, token); + var bytes = await cache.GetAsync(key, token); // avoid DoS attack with cache -1 if (bytes is not null) return BitConverter.ToInt32(bytes) >= 0; - var valid = await _containerRepository.ValidateContainer(id, token); + var valid = await containerRepository.ValidateContainer(id, token); - await _cache.SetAsync(key, BitConverter.GetBytes(valid ? 0 : -1), _validOption, token); + await cache.SetAsync(key, BitConverter.GetBytes(valid ? 0 : -1), _validOption, token); return valid; } @@ -302,7 +289,7 @@ internal async Task ValidateContainer(string id, CancellationToken token = internal async Task IncrementConnectionCount(string id) { var key = CacheKey.ConnectionCount(id); - var bytes = await _cache.GetAsync(key); + var bytes = await cache.GetAsync(key); if (bytes is null) return false; @@ -312,7 +299,7 @@ internal async Task IncrementConnectionCount(string id) if (count > CONNECTION_LIMIT) return false; - await _cache.SetAsync(key, BitConverter.GetBytes(count + 1), _storeOption); + await cache.SetAsync(key, BitConverter.GetBytes(count + 1), _storeOption); return true; } @@ -325,7 +312,7 @@ internal async Task IncrementConnectionCount(string id) internal async Task DecrementConnectionCount(string id) { var key = CacheKey.ConnectionCount(id); - var bytes = await _cache.GetAsync(key); + var bytes = await cache.GetAsync(key); if (bytes is null) return; @@ -334,11 +321,11 @@ internal async Task DecrementConnectionCount(string id) if (count > 1) { - await _cache.SetAsync(key, BitConverter.GetBytes(count - 1), _storeOption); + await cache.SetAsync(key, BitConverter.GetBytes(count - 1), _storeOption); } else { - await _cache.SetAsync(key, BitConverter.GetBytes(0), _validOption); + await cache.SetAsync(key, BitConverter.GetBytes(0), _validOption); } } } diff --git a/src/GZCTF/Controllers/TeamController.cs b/src/GZCTF/Controllers/TeamController.cs index ef1c86c67..4e7757171 100644 --- a/src/GZCTF/Controllers/TeamController.cs +++ b/src/GZCTF/Controllers/TeamController.cs @@ -17,27 +17,12 @@ namespace GZCTF.Controllers; [ApiController] [Route("api/[controller]")] [Produces(MediaTypeNames.Application.Json)] -public class TeamController : ControllerBase +public class TeamController(UserManager userManager, + IFileRepository fileService, + ILogger logger, + ITeamRepository teamRepository, + IParticipationRepository participationRepository) : ControllerBase { - private readonly UserManager _userManager; - private readonly IFileRepository _fileService; - private readonly ITeamRepository _teamRepository; - private readonly IParticipationRepository _participationRepository; - private readonly ILogger _logger; - - public TeamController(UserManager userManager, - IFileRepository fileService, - ILogger logger, - ITeamRepository teamRepository, - IParticipationRepository participationRepository) - { - _logger = logger; - _userManager = userManager; - _fileService = fileService; - _teamRepository = teamRepository; - _participationRepository = participationRepository; - } - /// /// 获取队伍信息 /// @@ -53,7 +38,7 @@ public TeamController(UserManager userManager, [ProducesResponseType(typeof(RequestResponse), StatusCodes.Status400BadRequest)] public async Task GetBasicInfo(int id, CancellationToken token) { - var team = await _teamRepository.GetTeamById(id, token); + var team = await teamRepository.GetTeamById(id, token); if (team is null) return NotFound(new RequestResponse("队伍不存在", 404)); @@ -76,9 +61,9 @@ public async Task GetBasicInfo(int id, CancellationToken token) [ProducesResponseType(typeof(RequestResponse), StatusCodes.Status400BadRequest)] public async Task GetTeamsInfo(CancellationToken token) { - var user = await _userManager.GetUserAsync(User); + var user = await userManager.GetUserAsync(User); - return Ok((await _teamRepository.GetUserTeams(user!, token)).Select(t => TeamInfoModel.FromTeam(t))); + return Ok((await teamRepository.GetUserTeams(user!, token)).Select(t => TeamInfoModel.FromTeam(t))); } /// @@ -100,9 +85,9 @@ public async Task GetTeamsInfo(CancellationToken token) [ProducesResponseType(typeof(RequestResponse), StatusCodes.Status403Forbidden)] public async Task CreateTeam([FromBody] TeamUpdateModel model, CancellationToken token) { - var user = await _userManager.GetUserAsync(User); + var user = await userManager.GetUserAsync(User); - var teams = await _teamRepository.GetUserTeams(user!, token); + var teams = await teamRepository.GetUserTeams(user!, token); if (teams.Length > 1 && teams.Any(t => t.CaptainId == user!.Id)) return BadRequest(new RequestResponse("不允许创建多个队伍")); @@ -110,14 +95,14 @@ public async Task CreateTeam([FromBody] TeamUpdateModel model, Ca if (string.IsNullOrEmpty(model.Name)) return BadRequest(new RequestResponse("队伍名不能为空")); - var team = await _teamRepository.CreateTeam(model, user!, token); + var team = await teamRepository.CreateTeam(model, user!, token); if (team is null) return BadRequest(new RequestResponse("队伍创建失败")); - await _userManager.UpdateAsync(user!); + await userManager.UpdateAsync(user!); - _logger.Log($"创建队伍 {team.Name}", user, TaskStatus.Success); + logger.Log($"创建队伍 {team.Name}", user, TaskStatus.Success); return Ok(TeamInfoModel.FromTeam(team)); } @@ -143,8 +128,8 @@ public async Task CreateTeam([FromBody] TeamUpdateModel model, Ca [ProducesResponseType(typeof(RequestResponse), StatusCodes.Status403Forbidden)] public async Task UpdateTeam([FromRoute] int id, [FromBody] TeamUpdateModel model, CancellationToken token) { - var user = await _userManager.GetUserAsync(User); - var team = await _teamRepository.GetTeamById(id, token); + var user = await userManager.GetUserAsync(User); + var team = await teamRepository.GetTeamById(id, token); if (team is null) return BadRequest(new RequestResponse("队伍未找到")); @@ -154,7 +139,7 @@ public async Task UpdateTeam([FromRoute] int id, [FromBody] TeamU team.UpdateInfo(model); - await _teamRepository.SaveAsync(token); + await teamRepository.SaveAsync(token); return Ok(TeamInfoModel.FromTeam(team)); } @@ -180,8 +165,8 @@ public async Task UpdateTeam([FromRoute] int id, [FromBody] TeamU [ProducesResponseType(typeof(RequestResponse), StatusCodes.Status403Forbidden)] public async Task Transfer([FromRoute] int id, [FromBody] TeamTransferModel model, CancellationToken token) { - var user = await _userManager.GetUserAsync(User); - var team = await _teamRepository.GetTeamById(id, token); + var user = await userManager.GetUserAsync(User); + var team = await teamRepository.GetTeamById(id, token); if (team is null) return BadRequest(new RequestResponse("队伍未找到")); @@ -189,20 +174,20 @@ public async Task Transfer([FromRoute] int id, [FromBody] TeamTra if (team.CaptainId != user!.Id) return new JsonResult(new RequestResponse("无权访问", 403)) { StatusCode = 403 }; - if (team.Locked && await _teamRepository.AnyActiveGame(team, token)) + if (team.Locked && await teamRepository.AnyActiveGame(team, token)) return BadRequest(new RequestResponse("队伍已锁定")); - var newCaptain = await _userManager.Users.SingleOrDefaultAsync(u => u.Id == model.NewCaptainId); + var newCaptain = await userManager.Users.SingleOrDefaultAsync(u => u.Id == model.NewCaptainId); if (newCaptain is null) return BadRequest(new RequestResponse("移交的用户不存在")); - var newCaptainTeams = await _teamRepository.GetUserTeams(newCaptain, token); + var newCaptainTeams = await teamRepository.GetUserTeams(newCaptain, token); if (newCaptainTeams.Count(t => t.CaptainId == newCaptain.Id) >= 3) return BadRequest(new RequestResponse("被移交者所管理的队伍过多")); - await _teamRepository.Transfer(team, newCaptain, token); + await teamRepository.Transfer(team, newCaptain, token); return Ok(TeamInfoModel.FromTeam(team)); } @@ -227,8 +212,8 @@ public async Task Transfer([FromRoute] int id, [FromBody] TeamTra [ProducesResponseType(typeof(RequestResponse), StatusCodes.Status403Forbidden)] public async Task InviteCode([FromRoute] int id, CancellationToken token) { - var user = await _userManager.GetUserAsync(User); - var team = await _teamRepository.GetTeamById(id, token); + var user = await userManager.GetUserAsync(User); + var team = await teamRepository.GetTeamById(id, token); if (team is null) return BadRequest(new RequestResponse("队伍未找到")); @@ -259,8 +244,8 @@ public async Task InviteCode([FromRoute] int id, CancellationToke [ProducesResponseType(typeof(RequestResponse), StatusCodes.Status403Forbidden)] public async Task UpdateInviteToken([FromRoute] int id, CancellationToken token) { - var user = await _userManager.GetUserAsync(User); - var team = await _teamRepository.GetTeamById(id, token); + var user = await userManager.GetUserAsync(User); + var team = await teamRepository.GetTeamById(id, token); if (team is null) return BadRequest(new RequestResponse("队伍未找到")); @@ -270,7 +255,7 @@ public async Task UpdateInviteToken([FromRoute] int id, Cancellat team.UpdateInviteToken(); - await _teamRepository.SaveAsync(token); + await teamRepository.SaveAsync(token); return Ok(team.InviteCode); } @@ -296,8 +281,8 @@ public async Task UpdateInviteToken([FromRoute] int id, Cancellat [ProducesResponseType(typeof(RequestResponse), StatusCodes.Status403Forbidden)] public async Task KickUser([FromRoute] int id, [FromRoute] string userid, CancellationToken token) { - var user = await _userManager.GetUserAsync(User); - var team = await _teamRepository.GetTeamById(id, token); + var user = await userManager.GetUserAsync(User); + var team = await teamRepository.GetTeamById(id, token); if (team is null) return BadRequest(new RequestResponse("队伍未找到")); @@ -305,11 +290,11 @@ public async Task KickUser([FromRoute] int id, [FromRoute] string if (team.CaptainId != user!.Id) return new JsonResult(new RequestResponse("无权访问", 403)) { StatusCode = 403 }; - var trans = await _teamRepository.BeginTransactionAsync(token); + var trans = await teamRepository.BeginTransactionAsync(token); try { - if (team.Locked && await _teamRepository.AnyActiveGame(team, token)) + if (team.Locked && await teamRepository.AnyActiveGame(team, token)) return BadRequest(new RequestResponse("队伍已锁定")); var kickUser = team.Members.SingleOrDefault(m => m.Id == userid); @@ -317,12 +302,12 @@ public async Task KickUser([FromRoute] int id, [FromRoute] string return BadRequest(new RequestResponse("用户不在队伍中")); team.Members.Remove(kickUser); - await _participationRepository.RemoveUserParticipations(user, team, token); + await participationRepository.RemoveUserParticipations(user, team, token); - await _teamRepository.SaveAsync(token); + await teamRepository.SaveAsync(token); await trans.CommitAsync(token); - _logger.Log($"从队伍 {team.Name} 踢除 {kickUser.UserName}", user, TaskStatus.Success); + logger.Log($"从队伍 {team.Name} 踢除 {kickUser.UserName}", user, TaskStatus.Success); return Ok(TeamInfoModel.FromTeam(team)); } catch @@ -364,11 +349,11 @@ public async Task Accept([FromBody] string code, CancellationToke return BadRequest(new RequestResponse($"队伍 Id 转换错误:{preCode[(lastColon + 1)..]}")); var teamName = preCode[..lastColon]; - var trans = await _teamRepository.BeginTransactionAsync(cancelToken); + var trans = await teamRepository.BeginTransactionAsync(cancelToken); try { - var team = await _teamRepository.GetTeamById(teamId, cancelToken); + var team = await teamRepository.GetTeamById(teamId, cancelToken); if (team is null) return BadRequest(new RequestResponse($"{teamName} 队伍未找到")); @@ -376,20 +361,20 @@ public async Task Accept([FromBody] string code, CancellationToke if (team.InviteCode != code) return BadRequest(new RequestResponse($"{teamName} 邀请无效")); - if (team.Locked && await _teamRepository.AnyActiveGame(team, cancelToken)) + if (team.Locked && await teamRepository.AnyActiveGame(team, cancelToken)) return BadRequest(new RequestResponse($"{teamName} 队伍已锁定")); - var user = await _userManager.GetUserAsync(User); + var user = await userManager.GetUserAsync(User); if (team.Members.Any(m => m.Id == user!.Id)) return BadRequest(new RequestResponse("你已经加入此队伍,无需重复加入")); team.Members.Add(user!); - await _teamRepository.SaveAsync(cancelToken); + await teamRepository.SaveAsync(cancelToken); await trans.CommitAsync(cancelToken); - _logger.Log($"加入队伍 {team.Name}", user, TaskStatus.Success); + logger.Log($"加入队伍 {team.Name}", user, TaskStatus.Success); return Ok(); } catch @@ -419,29 +404,29 @@ public async Task Accept([FromBody] string code, CancellationToke [ProducesResponseType(typeof(RequestResponse), StatusCodes.Status403Forbidden)] public async Task Leave([FromRoute] int id, CancellationToken token) { - var trans = await _teamRepository.BeginTransactionAsync(token); + var trans = await teamRepository.BeginTransactionAsync(token); try { - var team = await _teamRepository.GetTeamById(id, token); + var team = await teamRepository.GetTeamById(id, token); if (team is null) return BadRequest(new RequestResponse("队伍未找到")); - var user = await _userManager.GetUserAsync(User); + var user = await userManager.GetUserAsync(User); if (team.Members.All(m => m.Id != user!.Id)) return BadRequest(new RequestResponse("你不在此队伍中,无法离队")); - if (team.Locked && await _teamRepository.AnyActiveGame(team, token)) + if (team.Locked && await teamRepository.AnyActiveGame(team, token)) return BadRequest(new RequestResponse("队伍已锁定")); team.Members.Remove(user!); - await _teamRepository.SaveAsync(token); + await teamRepository.SaveAsync(token); await trans.CommitAsync(token); - _logger.Log($"离开队伍 {team.Name}", user, TaskStatus.Success); + logger.Log($"离开队伍 {team.Name}", user, TaskStatus.Success); return Ok(); } catch @@ -468,8 +453,8 @@ public async Task Leave([FromRoute] int id, CancellationToken tok [ProducesResponseType(typeof(RequestResponse), StatusCodes.Status403Forbidden)] public async Task Avatar([FromRoute] int id, IFormFile file, CancellationToken token) { - var user = await _userManager.GetUserAsync(User); - var team = await _teamRepository.GetTeamById(id, token); + var user = await userManager.GetUserAsync(User); + var team = await teamRepository.GetTeamById(id, token); if (team is null) return BadRequest(new RequestResponse("队伍未找到")); @@ -484,17 +469,17 @@ public async Task Avatar([FromRoute] int id, IFormFile file, Canc return BadRequest(new RequestResponse("文件过大")); if (team.AvatarHash is not null) - _ = await _fileService.DeleteFileByHash(team.AvatarHash, token); + _ = await fileService.DeleteFileByHash(team.AvatarHash, token); - var avatar = await _fileService.CreateOrUpdateImage(file, "avatar", 300, token); + var avatar = await fileService.CreateOrUpdateImage(file, "avatar", 300, token); if (avatar is null) return BadRequest(new RequestResponse("队伍头像更新失败")); team.AvatarHash = avatar.Hash; - await _teamRepository.SaveAsync(token); + await teamRepository.SaveAsync(token); - _logger.Log($"队伍 {team.Name} 更改新头像:[{avatar.Hash[..8]}]", user, TaskStatus.Success); + logger.Log($"队伍 {team.Name} 更改新头像:[{avatar.Hash[..8]}]", user, TaskStatus.Success); return Ok(avatar.Url()); } @@ -517,8 +502,8 @@ public async Task Avatar([FromRoute] int id, IFormFile file, Canc [ProducesResponseType(typeof(RequestResponse), StatusCodes.Status403Forbidden)] public async Task DeleteTeam(int id, CancellationToken token) { - var user = await _userManager.GetUserAsync(User); - var team = await _teamRepository.GetTeamById(id, token); + var user = await userManager.GetUserAsync(User); + var team = await teamRepository.GetTeamById(id, token); if (team is null) return BadRequest(new RequestResponse("队伍未找到")); @@ -526,12 +511,12 @@ public async Task DeleteTeam(int id, CancellationToken token) if (team.CaptainId != user!.Id) return new JsonResult(new RequestResponse("无权访问", 403)) { StatusCode = 403 }; - if (team.Locked && await _teamRepository.AnyActiveGame(team, token)) + if (team.Locked && await teamRepository.AnyActiveGame(team, token)) return BadRequest(new RequestResponse("队伍已锁定")); - await _teamRepository.DeleteTeam(team!, token); + await teamRepository.DeleteTeam(team!, token); - _logger.Log($"删除队伍 {team!.Name}", user, TaskStatus.Success); + logger.Log($"删除队伍 {team!.Name}", user, TaskStatus.Success); return Ok(); } diff --git a/src/GZCTF/Middlewares/PrivilegeAuthentication.cs b/src/GZCTF/Middlewares/PrivilegeAuthentication.cs index 3b864ed91..34b1b5124 100644 --- a/src/GZCTF/Middlewares/PrivilegeAuthentication.cs +++ b/src/GZCTF/Middlewares/PrivilegeAuthentication.cs @@ -10,17 +10,12 @@ namespace GZCTF.Middlewares; /// /// 需要权限访问 /// +/// +/// 所需权限 +/// [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)] -public class RequirePrivilegeAttribute : Attribute, IAsyncAuthorizationFilter +public class RequirePrivilegeAttribute(Role privilege) : Attribute, IAsyncAuthorizationFilter { - /// - /// 所需权限 - /// - private readonly Role _requiredPrivilege; - - public RequirePrivilegeAttribute(Role privilege) - => _requiredPrivilege = privilege; - public static IActionResult GetResult(string msg, int code) => new JsonResult(new RequestResponse(msg, code)) { StatusCode = code }; @@ -50,9 +45,9 @@ public async Task OnAuthorizationAsync(AuthorizationFilterContext context) await dbcontext.SaveChangesAsync(); // avoid to update ConcurrencyStamp } - if (user.Role < _requiredPrivilege) + if (user.Role < privilege) { - if (_requiredPrivilege > Role.User) + if (privilege > Role.User) logger.Log($"未经授权的访问:{context.HttpContext.Request.Path}", user, TaskStatus.Denied); context.Result = ForbiddenResult; diff --git a/src/GZCTF/Models/AppDbContext.cs b/src/GZCTF/Models/AppDbContext.cs index 8ecd613bf..2133981a5 100644 --- a/src/GZCTF/Models/AppDbContext.cs +++ b/src/GZCTF/Models/AppDbContext.cs @@ -9,12 +9,8 @@ namespace GZCTF.Models; -public class AppDbContext : IdentityDbContext, IDataProtectionKeyContext +public class AppDbContext(DbContextOptions options) : IdentityDbContext(options), IDataProtectionKeyContext { - public AppDbContext(DbContextOptions options) : base(options) - { - } - private static ValueConverter GetJsonConverter() where T : class, new() { var options = new JsonSerializerOptions() { WriteIndented = false }; diff --git a/src/GZCTF/Providers/EntityConfigurationProvider.cs b/src/GZCTF/Providers/EntityConfigurationProvider.cs index 032b39197..bfc180d05 100644 --- a/src/GZCTF/Providers/EntityConfigurationProvider.cs +++ b/src/GZCTF/Providers/EntityConfigurationProvider.cs @@ -8,24 +8,16 @@ namespace GZCTF.Providers; -public class EntityConfigurationProvider : ConfigurationProvider, IDisposable +public class EntityConfigurationProvider(EntityConfigurationSource source) : ConfigurationProvider, IDisposable { - private readonly EntityConfigurationSource _source; - private readonly CancellationTokenSource _cancellationTokenSource; + private readonly CancellationTokenSource _cancellationTokenSource = new(); private Task? _databaseWatcher; - private byte[] _lastHash; + private byte[] _lastHash = []; private bool _disposed = false; - public EntityConfigurationProvider(EntityConfigurationSource source) - { - _source = source; - _lastHash = Array.Empty(); - _cancellationTokenSource = new(); - } - private static HashSet DefaultConfigs() { - HashSet configs = new(); + HashSet configs = []; configs.UnionWith(ConfigService.GetConfigs(new AccountPolicy())); configs.UnionWith(ConfigService.GetConfigs(new GlobalConfig())); @@ -40,7 +32,7 @@ private async Task WatchDatabase(CancellationToken token) { try { - await Task.Delay(_source.PollingInterval, token); + await Task.Delay(source.PollingInterval, token); IDictionary actualData = await GetDataAsync(token); byte[] computedHash = ConfigHash(actualData); @@ -61,7 +53,7 @@ private async Task WatchDatabase(CancellationToken token) private AppDbContext CreateAppDbContext() { var builder = new DbContextOptionsBuilder(); - _source.OptionsAction(builder); + source.OptionsAction(builder); return new AppDbContext(builder.Options); } diff --git a/src/GZCTF/Providers/EntityConfigurationSource.cs b/src/GZCTF/Providers/EntityConfigurationSource.cs index 1b7f573d1..8b665f8eb 100644 --- a/src/GZCTF/Providers/EntityConfigurationSource.cs +++ b/src/GZCTF/Providers/EntityConfigurationSource.cs @@ -2,16 +2,10 @@ namespace GZCTF.Providers; -public class EntityConfigurationSource : IConfigurationSource +public class EntityConfigurationSource(Action optionsAction, int pollingInterval = 180000) : IConfigurationSource { - public Action OptionsAction { get; set; } - public int PollingInterval { get; private set; } - - public EntityConfigurationSource(Action optionsAction, int pollingInterval = 180000) - { - OptionsAction = optionsAction; - PollingInterval = pollingInterval; // default to 3min - } + public Action OptionsAction { get; set; } = optionsAction; + public int PollingInterval { get; private set; } = pollingInterval; // default to 3min public IConfigurationProvider Build(IConfigurationBuilder builder) => new EntityConfigurationProvider(this); diff --git a/src/GZCTF/Repositories/ChallengeRepository.cs b/src/GZCTF/Repositories/ChallengeRepository.cs index cfe651e01..7b1301299 100644 --- a/src/GZCTF/Repositories/ChallengeRepository.cs +++ b/src/GZCTF/Repositories/ChallengeRepository.cs @@ -5,15 +5,8 @@ namespace GZCTF.Repositories; -public class ChallengeRepository : RepositoryBase, IChallengeRepository +public class ChallengeRepository(AppDbContext context, IFileRepository fileRepository) : RepositoryBase(context), IChallengeRepository { - private readonly IFileRepository _fileRepository; - - public ChallengeRepository(AppDbContext context, IFileRepository fileRepository) : base(context) - { - _fileRepository = fileRepository; - } - public async Task AddFlags(Challenge challenge, FlagCreateModel[] models, CancellationToken token = default) { foreach (var model in models) @@ -21,7 +14,7 @@ public async Task AddFlags(Challenge challenge, FlagCreateModel[] models, Cancel Attachment? attachment = model.AttachmentType == FileType.None ? null : new() { Type = model.AttachmentType, - LocalFile = await _fileRepository.GetFileByHash(model.FileHash, token), + LocalFile = await fileRepository.GetFileByHash(model.FileHash, token), RemoteUrl = model.RemoteUrl }; @@ -38,7 +31,7 @@ public async Task AddFlags(Challenge challenge, FlagCreateModel[] models, Cancel public async Task CreateChallenge(Game game, Challenge challenge, CancellationToken token = default) { - await _context.AddAsync(challenge, token); + await context.AddAsync(challenge, token); game.Challenges.Add(challenge); await SaveAsync(token); return challenge; @@ -46,8 +39,8 @@ public async Task CreateChallenge(Game game, Challenge challenge, Can public async Task EnsureInstances(Challenge challenge, Game game, CancellationToken token = default) { - await _context.Entry(challenge).Collection(c => c.Teams).LoadAsync(token); - await _context.Entry(game).Collection(g => g.Participations).LoadAsync(token); + await context.Entry(challenge).Collection(c => c.Teams).LoadAsync(token); + await context.Entry(game).Collection(g => g.Participations).LoadAsync(token); bool update = false; @@ -61,7 +54,7 @@ public async Task EnsureInstances(Challenge challenge, Game game, Cancella public Task GetChallenge(int gameId, int id, bool withFlag = false, CancellationToken token = default) { - var challenges = _context.Challenges + var challenges = context.Challenges .Where(c => c.Id == id && c.GameId == gameId); if (withFlag) @@ -71,19 +64,19 @@ public async Task EnsureInstances(Challenge challenge, Game game, Cancella } public Task GetChallenges(int gameId, CancellationToken token = default) - => _context.Challenges.Where(c => c.GameId == gameId).OrderBy(c => c.Id).ToArrayAsync(token); + => context.Challenges.Where(c => c.GameId == gameId).OrderBy(c => c.Id).ToArrayAsync(token); public Task GetChallengesWithTrafficCapturing(int gameId, CancellationToken token = default) - => _context.Challenges.IgnoreAutoIncludes().Where(c => c.GameId == gameId && c.EnableTrafficCapture).ToArrayAsync(token); + => context.Challenges.IgnoreAutoIncludes().Where(c => c.GameId == gameId && c.EnableTrafficCapture).ToArrayAsync(token); public Task VerifyStaticAnswer(Challenge challenge, string flag, CancellationToken token = default) - => _context.Entry(challenge).Collection(e => e.Flags).Query().AnyAsync(f => f.Flag == flag, token); + => context.Entry(challenge).Collection(e => e.Flags).Query().AnyAsync(f => f.Flag == flag, token); public async Task RemoveChallenge(Challenge challenge, CancellationToken token = default) { await DeleteAllAttachment(challenge, true, token); - _context.Remove(challenge); + context.Remove(challenge); await SaveAsync(token); } @@ -96,7 +89,7 @@ public async Task RemoveFlag(Challenge challenge, int flagId, Cancel await DeleteAttachment(flag.Attachment, token); - _context.Remove(flag); + context.Remove(flag); if (challenge.Flags.Count == 0) challenge.IsEnabled = false; @@ -111,14 +104,14 @@ public async Task UpdateAttachment(Challenge challenge, AttachmentCreateModel mo Attachment? attachment = model.AttachmentType == FileType.None ? null : new() { Type = model.AttachmentType, - LocalFile = await _fileRepository.GetFileByHash(model.FileHash, token), + LocalFile = await fileRepository.GetFileByHash(model.FileHash, token), RemoteUrl = model.RemoteUrl }; await DeleteAllAttachment(challenge, false, token); if (attachment is not null) - await _context.AddAsync(attachment, token); + await context.AddAsync(attachment, token); challenge.Attachment = attachment; @@ -134,7 +127,7 @@ internal async Task DeleteAllAttachment(Challenge challenge, bool purge = false, foreach (var flag in challenge.Flags) await DeleteAttachment(flag.Attachment, token); - _context.RemoveRange(challenge.Flags); + context.RemoveRange(challenge.Flags); } } @@ -144,8 +137,8 @@ internal async Task DeleteAttachment(Attachment? attachment, CancellationToken t return; if (attachment.Type == FileType.Local && attachment.LocalFile is not null) - await _fileRepository.DeleteFile(attachment.LocalFile, token); + await fileRepository.DeleteFile(attachment.LocalFile, token); - _context.Remove(attachment); + context.Remove(attachment); } } diff --git a/src/GZCTF/Repositories/CheatInfoRepository.cs b/src/GZCTF/Repositories/CheatInfoRepository.cs index 6da32a16b..9b0137d1c 100644 --- a/src/GZCTF/Repositories/CheatInfoRepository.cs +++ b/src/GZCTF/Repositories/CheatInfoRepository.cs @@ -4,19 +4,12 @@ namespace GZCTF.Repositories; -public class CheatInfoRepository : RepositoryBase, ICheatInfoRepository +public class CheatInfoRepository(AppDbContext context, + IParticipationRepository participationRepository) : RepositoryBase(context), ICheatInfoRepository { - private readonly IParticipationRepository _participationRepository; - - public CheatInfoRepository(AppDbContext context, - IParticipationRepository participationRepository) : base(context) - { - _participationRepository = participationRepository; - } - public async Task CreateCheatInfo(Submission submission, Instance source, CancellationToken token = default) { - var submit = await _participationRepository.GetParticipation(submission.Team, submission.Game, token); + var submit = await participationRepository.GetParticipation(submission.Team, submission.Game, token); if (submit is null) throw new NullReferenceException(nameof(submit)); @@ -29,13 +22,13 @@ public async Task CreateCheatInfo(Submission submission, Instance sou SourceTeam = source.Participation }; - await _context.AddAsync(info, token); + await context.AddAsync(info, token); return info; } public Task GetCheatInfoByGameId(int gameId, CancellationToken token = default) - => _context.CheatInfo.IgnoreAutoIncludes().Where(i => i.GameId == gameId) + => context.CheatInfo.IgnoreAutoIncludes().Where(i => i.GameId == gameId) .Include(i => i.SourceTeam).ThenInclude(t => t.Team) .Include(i => i.SubmitTeam).ThenInclude(t => t.Team) .Include(i => i.Submission).ThenInclude(s => s.User) diff --git a/src/GZCTF/Repositories/ContainerRepository.cs b/src/GZCTF/Repositories/ContainerRepository.cs index abf057e93..31ecb7a72 100644 --- a/src/GZCTF/Repositories/ContainerRepository.cs +++ b/src/GZCTF/Repositories/ContainerRepository.cs @@ -6,33 +6,26 @@ namespace GZCTF.Repositories; -public class ContainerRepository : RepositoryBase, IContainerRepository +public class ContainerRepository(IDistributedCache cache, + AppDbContext context) : RepositoryBase(context), IContainerRepository { - private readonly IDistributedCache _cache; - - public ContainerRepository(IDistributedCache cache, - AppDbContext context) : base(context) - { - _cache = cache; - } - - public override Task CountAsync(CancellationToken token = default) => _context.Containers.CountAsync(token); + public override Task CountAsync(CancellationToken token = default) => context.Containers.CountAsync(token); public Task GetContainerById(string guid, CancellationToken token = default) - => _context.Containers.FirstOrDefaultAsync(i => i.Id == guid, token); + => context.Containers.FirstOrDefaultAsync(i => i.Id == guid, token); public Task GetContainerWithInstanceById(string guid, CancellationToken token = default) - => _context.Containers.IgnoreAutoIncludes() + => context.Containers.IgnoreAutoIncludes() .Include(c => c.Instance).ThenInclude(i => i!.Challenge) .Include(c => c.Instance).ThenInclude(i => i!.FlagContext) .Include(c => c.Instance).ThenInclude(i => i!.Participation).ThenInclude(p => p.Team) .FirstOrDefaultAsync(i => i.Id == guid, token); public Task> GetContainers(CancellationToken token = default) - => _context.Containers.ToListAsync(token); + => context.Containers.ToListAsync(token); public async Task GetContainerInstances(CancellationToken token = default) - => (await _context.Containers + => (await context.Containers .Where(c => c.Instance != null) .Include(c => c.Instance).ThenInclude(i => i!.Participation) .OrderBy(c => c.StartedAt).ToArrayAsync(token)) @@ -42,7 +35,7 @@ public async Task GetContainerInstances(CancellationTo public Task> GetDyingContainers(CancellationToken token = default) { var now = DateTimeOffset.UtcNow; - return _context.Containers.Where(c => c.ExpectStopAt < now).ToListAsync(token); + return context.Containers.Where(c => c.ExpectStopAt < now).ToListAsync(token); } public async Task RemoveContainer(Container container, CancellationToken token = default) @@ -51,12 +44,12 @@ public async Task RemoveContainer(Container container, CancellationToken token = if (container.Status != ContainerStatus.Destroyed) return; - await _cache.RemoveAsync(CacheKey.ConnectionCount(container.Id), token); + await cache.RemoveAsync(CacheKey.ConnectionCount(container.Id), token); - _context.Containers.Remove(container); + context.Containers.Remove(container); await SaveAsync(token); } public async Task ValidateContainer(string guid, CancellationToken token = default) - => await _context.Containers.AnyAsync(c => c.Id == guid, token); + => await context.Containers.AnyAsync(c => c.Id == guid, token); } diff --git a/src/GZCTF/Repositories/FileRepository.cs b/src/GZCTF/Repositories/FileRepository.cs index e30b26619..eebdeed7c 100644 --- a/src/GZCTF/Repositories/FileRepository.cs +++ b/src/GZCTF/Repositories/FileRepository.cs @@ -6,16 +6,9 @@ namespace GZCTF.Repositories; -public class FileRepository : RepositoryBase, IFileRepository +public class FileRepository(AppDbContext context, ILogger logger) : RepositoryBase(context), IFileRepository { - private readonly ILogger _logger; - - public FileRepository(AppDbContext context, ILogger logger) : base(context) - { - _logger = logger; - } - - public override Task CountAsync(CancellationToken token = default) => _context.Files.CountAsync(token); + public override Task CountAsync(CancellationToken token = default) => context.Files.CountAsync(token); private static Stream GetStream(long bufferSize) => bufferSize <= 16 * 1024 * 1024 ? new MemoryStream() : File.Create(Path.GetTempFileName(), 4096, FileOptions.DeleteOnClose); @@ -35,14 +28,14 @@ private async Task StoreLocalFile(string fileName, Stream contentStre localFile.UploadTimeUTC = DateTimeOffset.UtcNow; // update upload time localFile.ReferenceCount++; // same hash, add ref count - _logger.SystemLog($"文件引用计数 [{localFile.Hash[..8]}] {localFile.Name} => {localFile.ReferenceCount}", TaskStatus.Success, LogLevel.Debug); + logger.SystemLog($"文件引用计数 [{localFile.Hash[..8]}] {localFile.Name} => {localFile.ReferenceCount}", TaskStatus.Success, LogLevel.Debug); - _context.Update(localFile); + context.Update(localFile); } else { localFile = new() { Hash = fileHash, Name = fileName, FileSize = contentStream.Length }; - await _context.AddAsync(localFile, token); + await context.AddAsync(localFile, token); var path = Path.Combine(FilePath.Uploads, localFile.Location); @@ -63,7 +56,7 @@ public async Task CreateOrUpdateFile(IFormFile file, string? fileName { using var tmp = GetStream(file.Length); - _logger.SystemLog($"缓存位置:{tmp.GetType()}", TaskStatus.Pending, LogLevel.Trace); + logger.SystemLog($"缓存位置:{tmp.GetType()}", TaskStatus.Pending, LogLevel.Trace); await file.CopyToAsync(tmp, token); return await StoreLocalFile(fileName ?? file.FileName, tmp, token); @@ -98,7 +91,7 @@ public async Task CreateOrUpdateFile(IFormFile file, string? fileName } catch { - _logger.SystemLog($"无法存储图像文件:{file.Name}", TaskStatus.Failed, LogLevel.Warning); + logger.SystemLog($"无法存储图像文件:{file.Name}", TaskStatus.Failed, LogLevel.Warning); return null; } } @@ -111,25 +104,25 @@ public async Task DeleteFile(LocalFile file, CancellationToken token { file.ReferenceCount--; // other ref exists, decrease ref count - _logger.SystemLog($"文件引用计数 [{file.Hash[..8]}] {file.Name} => {file.ReferenceCount}", TaskStatus.Success, LogLevel.Debug); + logger.SystemLog($"文件引用计数 [{file.Hash[..8]}] {file.Name} => {file.ReferenceCount}", TaskStatus.Success, LogLevel.Debug); await SaveAsync(token); return TaskStatus.Success; } - _logger.SystemLog($"删除文件 [{file.Hash[..8]}] {file.Name}", TaskStatus.Pending, LogLevel.Information); + logger.SystemLog($"删除文件 [{file.Hash[..8]}] {file.Name}", TaskStatus.Pending, LogLevel.Information); if (File.Exists(path)) { File.Delete(path); - _context.Files.Remove(file); + context.Files.Remove(file); await SaveAsync(token); return TaskStatus.Success; } - _context.Files.Remove(file); + context.Files.Remove(file); await SaveAsync(token); return TaskStatus.NotFound; @@ -146,8 +139,8 @@ public async Task DeleteFileByHash(string fileHash, CancellationToke } public Task GetFileByHash(string? fileHash, CancellationToken token = default) - => _context.Files.SingleOrDefaultAsync(f => f.Hash == fileHash, token); + => context.Files.SingleOrDefaultAsync(f => f.Hash == fileHash, token); public Task GetFiles(int count, int skip, CancellationToken token = default) - => _context.Files.OrderBy(e => e.Name).Skip(skip).Take(count).ToArrayAsync(token); + => context.Files.OrderBy(e => e.Name).Skip(skip).Take(count).ToArrayAsync(token); } diff --git a/src/GZCTF/Repositories/GameEventRepository.cs b/src/GZCTF/Repositories/GameEventRepository.cs index 89f824e14..4c46ff970 100644 --- a/src/GZCTF/Repositories/GameEventRepository.cs +++ b/src/GZCTF/Repositories/GameEventRepository.cs @@ -6,25 +6,18 @@ namespace GZCTF.Repositories; -public class GameEventRepository : RepositoryBase, IGameEventRepository +public class GameEventRepository( + IHubContext hub, + AppDbContext context) : RepositoryBase(context), IGameEventRepository { - private readonly IHubContext _hubContext; - - public GameEventRepository( - IHubContext hub, - AppDbContext context) : base(context) - { - _hubContext = hub; - } - public async Task AddEvent(GameEvent gameEvent, CancellationToken token = default) { - await _context.AddAsync(gameEvent, token); + await context.AddAsync(gameEvent, token); await SaveAsync(token); - gameEvent = await _context.GameEvents.SingleAsync(s => s.Id == gameEvent.Id, token); + gameEvent = await context.GameEvents.SingleAsync(s => s.Id == gameEvent.Id, token); - await _hubContext.Clients.Group($"Game_{gameEvent.GameId}") + await hub.Clients.Group($"Game_{gameEvent.GameId}") .ReceivedGameEvent(gameEvent); return gameEvent; @@ -32,7 +25,7 @@ await _hubContext.Clients.Group($"Game_{gameEvent.GameId}") public Task GetEvents(int gameId, bool hideContainer = false, int count = 50, int skip = 0, CancellationToken token = default) { - var data = _context.GameEvents.Where(e => e.GameId == gameId); + var data = context.GameEvents.Where(e => e.GameId == gameId); if (hideContainer) data = data.Where(e => e.Type != EventType.ContainerStart && e.Type != EventType.ContainerDestroy); diff --git a/src/GZCTF/Repositories/GameNoticeRepository.cs b/src/GZCTF/Repositories/GameNoticeRepository.cs index 1e55b10ad..d4a0e09c0 100644 --- a/src/GZCTF/Repositories/GameNoticeRepository.cs +++ b/src/GZCTF/Repositories/GameNoticeRepository.cs @@ -9,56 +9,45 @@ namespace GZCTF.Repositories; -public class GameNoticeRepository : RepositoryBase, IGameNoticeRepository +public class GameNoticeRepository(IDistributedCache cache, + ILogger logger, + IHubContext hub, + AppDbContext context) : RepositoryBase(context), IGameNoticeRepository { - private readonly IDistributedCache _cache; - private readonly ILogger _logger; - private readonly IHubContext _hubContext; - - public GameNoticeRepository(IDistributedCache cache, - ILogger logger, - IHubContext hub, - AppDbContext context) : base(context) - { - _cache = cache; - _logger = logger; - _hubContext = hub; - } - public async Task AddNotice(GameNotice notice, CancellationToken token = default) { - await _context.AddAsync(notice, token); + await context.AddAsync(notice, token); await SaveAsync(token); - _cache.Remove(CacheKey.GameNotice(notice.GameId)); + cache.Remove(CacheKey.GameNotice(notice.GameId)); - await _hubContext.Clients.Group($"Game_{notice.GameId}") + await hub.Clients.Group($"Game_{notice.GameId}") .ReceivedGameNotice(notice); return notice; } public Task GetNormalNotices(int gameId, CancellationToken token = default) - => _context.GameNotices + => context.GameNotices .Where(n => n.GameId == gameId && n.Type == NoticeType.Normal) .ToArrayAsync(token); public Task GetNoticeById(int gameId, int noticeId, CancellationToken token = default) - => _context.GameNotices.FirstOrDefaultAsync(e => e.Id == noticeId && e.GameId == gameId, token); + => context.GameNotices.FirstOrDefaultAsync(e => e.Id == noticeId && e.GameId == gameId, token); public Task GetNotices(int gameId, int count = 100, int skip = 0, CancellationToken token = default) - => _cache.GetOrCreateAsync(_logger, CacheKey.GameNotice(gameId), (entry) => + => cache.GetOrCreateAsync(logger, CacheKey.GameNotice(gameId), (entry) => { entry.AbsoluteExpirationRelativeToNow = TimeSpan.FromMinutes(30); - return _context.GameNotices.Where(e => e.GameId == gameId) + return context.GameNotices.Where(e => e.GameId == gameId) .OrderByDescending(e => e.Type == NoticeType.Normal ? DateTimeOffset.UtcNow : e.PublishTimeUTC) .Skip(skip).Take(count).ToArrayAsync(token); }, token); public Task RemoveNotice(GameNotice notice, CancellationToken token = default) { - _context.Remove(notice); - _cache.Remove(CacheKey.GameNotice(notice.GameId)); + context.Remove(notice); + cache.Remove(CacheKey.GameNotice(notice.GameId)); return SaveAsync(token); } @@ -68,7 +57,7 @@ public async Task UpdateNotice(GameNotice notice, CancellationToken notice.PublishTimeUTC = DateTimeOffset.UtcNow; await SaveAsync(token); - _cache.Remove(CacheKey.GameNotice(notice.GameId)); + cache.Remove(CacheKey.GameNotice(notice.GameId)); return notice; } diff --git a/src/GZCTF/Repositories/GameRepository.cs b/src/GZCTF/Repositories/GameRepository.cs index 815ec8c1e..7f158687d 100644 --- a/src/GZCTF/Repositories/GameRepository.cs +++ b/src/GZCTF/Repositories/GameRepository.cs @@ -1,5 +1,4 @@ -using System.Text; -using GZCTF.Extensions; +using GZCTF.Extensions; using GZCTF.Models.Request.Game; using GZCTF.Repositories.Interface; using GZCTF.Services; @@ -10,43 +9,26 @@ namespace GZCTF.Repositories; -public class GameRepository : RepositoryBase, IGameRepository +public class GameRepository(IDistributedCache cache, + ITeamRepository teamRepository, + IChallengeRepository challengeRepository, + IParticipationRepository participationRepository, + IConfiguration configuration, + ILogger logger, + AppDbContext context) : RepositoryBase(context), IGameRepository { - private readonly IDistributedCache _cache; - private readonly ITeamRepository _teamRepository; - private readonly IChallengeRepository _challengeRepository; - private readonly IParticipationRepository _participationRepository; - private readonly byte[]? _xorkey; - private readonly ILogger _logger; - - public GameRepository(IDistributedCache cache, - ITeamRepository teamRepository, - IChallengeRepository challengeRepository, - IParticipationRepository participationRepository, - IConfiguration configuration, - ILogger logger, - AppDbContext context) : base(context) - { - _cache = cache; - _logger = logger; - _teamRepository = teamRepository; - _challengeRepository = challengeRepository; - _participationRepository = participationRepository; - - var xorkeyStr = configuration["XorKey"]; - _xorkey = string.IsNullOrEmpty(xorkeyStr) ? null : Encoding.UTF8.GetBytes(xorkeyStr); - } + private readonly byte[]? _xorkey = configuration["XorKey"]?.ToUTF8Bytes(); - public override Task CountAsync(CancellationToken token = default) => _context.Games.CountAsync(token); + public override Task CountAsync(CancellationToken token = default) => context.Games.CountAsync(token); public async Task CreateGame(Game game, CancellationToken token = default) { game.GenerateKeyPair(_xorkey); if (_xorkey is null) - _logger.SystemLog("配置文件中的异或密钥未设置,比赛私钥将会被明文存储至数据库。", TaskStatus.Pending, LogLevel.Warning); + logger.SystemLog("配置文件中的异或密钥未设置,比赛私钥将会被明文存储至数据库。", TaskStatus.Pending, LogLevel.Warning); - await _context.AddAsync(game, token); + await context.AddAsync(game, token); await SaveAsync(token); return game; } @@ -55,24 +37,24 @@ public string GetToken(Game game, Team team) => $"{team.Id}:{game.Sign($"GZCTF_TEAM_{team.Id}", _xorkey)}"; public Task GetGameById(int id, CancellationToken token = default) - => _context.Games.FirstOrDefaultAsync(x => x.Id == id, token); + => context.Games.FirstOrDefaultAsync(x => x.Id == id, token); public Task GetUpcomingGames(CancellationToken token = default) - => _context.Games.Where(g => g.StartTimeUTC > DateTime.UtcNow + => context.Games.Where(g => g.StartTimeUTC > DateTime.UtcNow && g.StartTimeUTC - DateTime.UtcNow < TimeSpan.FromMinutes(5)) .OrderBy(g => g.StartTimeUTC).Select(g => g.Id).ToArrayAsync(token); public async Task GetBasicGameInfo(int count = 10, int skip = 0, CancellationToken token = default) - => await _cache.GetOrCreateAsync(_logger, CacheKey.BasicGameInfo, entry => + => await cache.GetOrCreateAsync(logger, CacheKey.BasicGameInfo, entry => { entry.AbsoluteExpirationRelativeToNow = TimeSpan.FromHours(2); - return _context.Games.Where(g => !g.Hidden) + return context.Games.Where(g => !g.Hidden) .OrderByDescending(g => g.StartTimeUTC).Skip(skip).Take(count) .Select(g => BasicGameInfoModel.FromGame(g)).ToArrayAsync(token); }, token); public Task GetScoreboard(Game game, CancellationToken token = default) - => _cache.GetOrCreateAsync(_logger, CacheKey.ScoreBoard(game.Id), entry => + => cache.GetOrCreateAsync(logger, CacheKey.ScoreBoard(game.Id), entry => { entry.AbsoluteExpirationRelativeToNow = TimeSpan.FromDays(7); return GenScoreboard(game, token); @@ -85,7 +67,7 @@ public async Task GetScoreboardWithMembers(Game game, Cancellat foreach (var item in scoreboard.Items) { - item.TeamInfo = await _teamRepository.GetTeamById(item.Id, token) ?? + item.TeamInfo = await teamRepository.GetTeamById(item.Id, token) ?? throw new ArgumentNullException("team", "Team not found"); } @@ -99,33 +81,33 @@ public async Task DeleteGame(Game game, CancellationToken token = de try { - await _context.Entry(game).Collection(g => g.Challenges).LoadAsync(token); + await context.Entry(game).Collection(g => g.Challenges).LoadAsync(token); - _logger.SystemLog($"正在清理比赛 {game.Title} 的 {game.Challenges.Count} 个题目的相关附件……", TaskStatus.Pending, LogLevel.Debug); + logger.SystemLog($"正在清理比赛 {game.Title} 的 {game.Challenges.Count} 个题目的相关附件……", TaskStatus.Pending, LogLevel.Debug); foreach (var chal in game.Challenges) - await _challengeRepository.RemoveChallenge(chal, token); + await challengeRepository.RemoveChallenge(chal, token); - await _context.Entry(game).Collection(g => g.Participations).LoadAsync(token); + await context.Entry(game).Collection(g => g.Participations).LoadAsync(token); - _logger.SystemLog($"正在清理比赛 {game.Title} 的 {game.Participations.Count} 个队伍相关文件……", TaskStatus.Pending, LogLevel.Debug); + logger.SystemLog($"正在清理比赛 {game.Title} 的 {game.Participations.Count} 个队伍相关文件……", TaskStatus.Pending, LogLevel.Debug); foreach (var part in game.Participations) - await _participationRepository.RemoveParticipation(part, token); + await participationRepository.RemoveParticipation(part, token); - _context.Remove(game); + context.Remove(game); await SaveAsync(token); await trans.CommitAsync(token); - _cache.Remove(CacheKey.BasicGameInfo); - _cache.Remove(CacheKey.ScoreBoard(game.Id)); + cache.Remove(CacheKey.BasicGameInfo); + cache.Remove(CacheKey.ScoreBoard(game.Id)); return TaskStatus.Success; } catch { - _logger.SystemLog($"删除比赛失败,相关文件可能已受损,请重新删除", TaskStatus.Pending, LogLevel.Debug); + logger.SystemLog($"删除比赛失败,相关文件可能已受损,请重新删除", TaskStatus.Pending, LogLevel.Debug); await trans.RollbackAsync(token); return TaskStatus.Failed; @@ -134,19 +116,19 @@ public async Task DeleteGame(Game game, CancellationToken token = de public async Task DeleteAllWriteUps(Game game, CancellationToken token = default) { - await _context.Entry(game).Collection(g => g.Participations).LoadAsync(token); + await context.Entry(game).Collection(g => g.Participations).LoadAsync(token); - _logger.SystemLog($"正在清理比赛 {game.Title} 的 {game.Participations.Count} 个队伍相关文件……", TaskStatus.Pending, LogLevel.Debug); + logger.SystemLog($"正在清理比赛 {game.Title} 的 {game.Participations.Count} 个队伍相关文件……", TaskStatus.Pending, LogLevel.Debug); foreach (var part in game.Participations) - await _participationRepository.DeleteParticipationWriteUp(part, token); + await participationRepository.DeleteParticipationWriteUp(part, token); } public Task GetGames(int count, int skip, CancellationToken token) - => _context.Games.OrderByDescending(g => g.Id).Skip(skip).Take(count).ToArrayAsync(token); + => context.Games.OrderByDescending(g => g.Id).Skip(skip).Take(count).ToArrayAsync(token); public void FlushGameInfoCache() - => _cache.Remove(CacheKey.BasicGameInfo); + => cache.Remove(CacheKey.BasicGameInfo); #region Generate Scoreboard @@ -168,14 +150,14 @@ public async Task GenScoreboard(Game game, CancellationToken to } private Task FetchData(Game game, CancellationToken token = default) - => _context.Instances + => context.Instances .Include(i => i.Challenge) .Where(i => i.Challenge.Game == game && i.Challenge.IsEnabled && i.Participation.Status == ParticipationStatus.Accepted) .Include(i => i.Participation).ThenInclude(p => p.Team).ThenInclude(t => t.Members) .GroupJoin( - _context.Submissions.Where(s => s.Status == AnswerResult.Accepted + context.Submissions.Where(s => s.Status == AnswerResult.Accepted && s.SubmitTimeUTC < game.EndTimeUTC), i => new { i.ChallengeId, i.ParticipationId }, s => new { s.ChallengeId, s.ParticipationId }, diff --git a/src/GZCTF/Repositories/InstanceRepository.cs b/src/GZCTF/Repositories/InstanceRepository.cs index aa04c0596..b5a73578a 100644 --- a/src/GZCTF/Repositories/InstanceRepository.cs +++ b/src/GZCTF/Repositories/InstanceRepository.cs @@ -7,43 +7,26 @@ namespace GZCTF.Repositories; -public class InstanceRepository : RepositoryBase, IInstanceRepository +public class InstanceRepository(AppDbContext context, + IContainerManager service, + ICheatInfoRepository cheatInfoRepository, + IContainerRepository containerRepository, + IGameEventRepository gameEventRepository, + IOptionsSnapshot gamePolicy, + ILogger logger) : RepositoryBase(context), IInstanceRepository { - private readonly IContainerManager _service; - private readonly ICheatInfoRepository _cheatInfoRepository; - private readonly IContainerRepository _containerRepository; - private readonly IGameEventRepository _gameEventRepository; - private readonly ILogger _logger; - private readonly IOptionsSnapshot _gamePolicy; - - public InstanceRepository(AppDbContext context, - IContainerManager service, - ICheatInfoRepository cheatInfoRepository, - IContainerRepository containerRepository, - IGameEventRepository gameEventRepository, - IOptionsSnapshot gamePolicy, - ILogger logger) : base(context) - { - _logger = logger; - _service = service; - _gamePolicy = gamePolicy; - _cheatInfoRepository = cheatInfoRepository; - _gameEventRepository = gameEventRepository; - _containerRepository = containerRepository; - } - public async Task GetInstance(Participation part, int challengeId, CancellationToken token = default) { - using var transaction = await _context.Database.BeginTransactionAsync(token); + using var transaction = await context.Database.BeginTransactionAsync(token); - var instance = await _context.Instances + var instance = await context.Instances .Include(i => i.FlagContext) .Where(e => e.ChallengeId == challengeId && e.Participation == part) .SingleOrDefaultAsync(token); if (instance is null) { - _logger.SystemLog($"队伍对应参与对象为空,这可能是非预期的情况 [{part.Id}, {challengeId}]", TaskStatus.NotFound, LogLevel.Warning); + logger.SystemLog($"队伍对应参与对象为空,这可能是非预期的情况 [{part.Id}, {challengeId}]", TaskStatus.NotFound, LogLevel.Warning); return null; } @@ -77,13 +60,13 @@ public InstanceRepository(AppDbContext context, }; break; case ChallengeType.DynamicAttachment: - var flags = await _context.FlagContexts + var flags = await context.FlagContexts .Where(e => e.Challenge == challenge && !e.IsOccupied) .ToListAsync(token); if (flags.Count == 0) { - _logger.SystemLog($"题目 {challenge.Title}#{challenge.Id} 请求分配的动态附件数量不足", TaskStatus.Failed, LogLevel.Warning); + logger.SystemLog($"题目 {challenge.Title}#{challenge.Id} 请求分配的动态附件数量不足", TaskStatus.Failed, LogLevel.Warning); return null; } @@ -105,7 +88,7 @@ public InstanceRepository(AppDbContext context, } catch { - _logger.SystemLog($"为队伍 {part.Team.Name} 获取题目 {challenge.Title}#{challenge.Id} 的实例时遇到问题(可能由于并发错误),回滚中", TaskStatus.Failed, LogLevel.Warning); + logger.SystemLog($"为队伍 {part.Team.Name} 获取题目 {challenge.Title}#{challenge.Id} 的实例时遇到问题(可能由于并发错误),回滚中", TaskStatus.Failed, LogLevel.Warning); await transaction.RollbackAsync(token); return null; } @@ -117,13 +100,13 @@ public async Task DestroyContainer(Container container, CancellationToken { try { - await _service.DestroyContainerAsync(container, token); - await _containerRepository.RemoveContainer(container, token); + await service.DestroyContainerAsync(container, token); + await containerRepository.RemoveContainer(container, token); return true; } catch (Exception ex) { - _logger.SystemLog($"销毁容器 [{container.ContainerId[..12]}] ({container.Image.Split("/").LastOrDefault()}): {ex.Message}", TaskStatus.Failed, LogLevel.Warning); + logger.SystemLog($"销毁容器 [{container.ContainerId[..12]}] ({container.Image.Split("/").LastOrDefault()}): {ex.Message}", TaskStatus.Failed, LogLevel.Warning); return false; } } @@ -132,29 +115,29 @@ public async Task> CreateContainer(Instance instance, Team { if (string.IsNullOrEmpty(instance.Challenge.ContainerImage) || instance.Challenge.ContainerExposePort is null) { - _logger.SystemLog($"无法为题目 {instance.Challenge.Title} 启动容器实例", TaskStatus.Denied, LogLevel.Warning); + logger.SystemLog($"无法为题目 {instance.Challenge.Title} 启动容器实例", TaskStatus.Denied, LogLevel.Warning); return new TaskResult(TaskStatus.Failed); } // containerLimit == 0 means unlimit if (containerLimit > 0) { - if (_gamePolicy.Value.AutoDestroyOnLimitReached) + if (gamePolicy.Value.AutoDestroyOnLimitReached) { - var running = await _context.Instances + var running = await context.Instances .Where(i => i.Participation == instance.Participation && i.Container != null) .OrderBy(i => i.Container!.StartedAt).ToListAsync(token); var first = running.FirstOrDefault(); if (running.Count >= containerLimit && first is not null) { - _logger.Log($"{team.Name} 自动销毁题目 {first.Challenge.Title} 的容器实例 [{first.Container!.ContainerId}]", user, TaskStatus.Success); + logger.Log($"{team.Name} 自动销毁题目 {first.Challenge.Title} 的容器实例 [{first.Container!.ContainerId}]", user, TaskStatus.Success); await DestroyContainer(running.First().Container!, token); } } else { - var count = await _context.Instances.CountAsync( + var count = await context.Instances.CountAsync( i => i.Participation == instance.Participation && i.Container != null, token); @@ -165,8 +148,8 @@ public async Task> CreateContainer(Instance instance, Team if (instance.Container is null) { - await _context.Entry(instance).Reference(e => e.FlagContext).LoadAsync(token); - var container = await _service.CreateContainerAsync(new ContainerConfig() + await context.Entry(instance).Reference(e => e.FlagContext).LoadAsync(token); + var container = await service.CreateContainerAsync(new ContainerConfig() { TeamId = team.Id.ToString(), UserId = user.Id, @@ -181,17 +164,17 @@ public async Task> CreateContainer(Instance instance, Team if (container is null) { - _logger.SystemLog($"为题目 {instance.Challenge.Title} 启动容器实例失败", TaskStatus.Failed, LogLevel.Warning); + logger.SystemLog($"为题目 {instance.Challenge.Title} 启动容器实例失败", TaskStatus.Failed, LogLevel.Warning); return new TaskResult(TaskStatus.Failed); } instance.Container = container; instance.LastContainerOperation = DateTimeOffset.UtcNow; - _logger.Log($"{team.Name} 启动题目 {instance.Challenge.Title} 的容器实例 [{container.ContainerId}]", user, TaskStatus.Success); + logger.Log($"{team.Name} 启动题目 {instance.Challenge.Title} 的容器实例 [{container.ContainerId}]", user, TaskStatus.Success); // will save instance together - await _gameEventRepository.AddEvent(new() + await gameEventRepository.AddEvent(new() { Type = EventType.ContainerStart, GameId = instance.Challenge.GameId, @@ -211,14 +194,14 @@ public async Task ProlongContainer(Container container, TimeSpan time, Cancellat } public Task GetInstances(Challenge challenge, CancellationToken token = default) - => _context.Instances.Where(i => i.Challenge == challenge).OrderBy(i => i.ParticipationId) + => context.Instances.Where(i => i.Challenge == challenge).OrderBy(i => i.ParticipationId) .Include(i => i.Participation).ThenInclude(i => i.Team).ToArrayAsync(token); public async Task CheckCheat(Submission submission, CancellationToken token = default) { CheatCheckInfo checkInfo = new(); - var instances = await _context.Instances.Where(i => i.ChallengeId == submission.ChallengeId && + var instances = await context.Instances.Where(i => i.ChallengeId == submission.ChallengeId && i.ParticipationId != submission.ParticipationId) .Include(i => i.FlagContext).Include(i => i.Participation) .ThenInclude(i => i.Team).ToArrayAsync(token); @@ -227,9 +210,9 @@ public async Task CheckCheat(Submission submission, Cancellation { if (instance.FlagContext?.Flag == submission.Answer) { - var updateSub = await _context.Submissions.Where(s => s.Id == submission.Id).SingleAsync(token); + var updateSub = await context.Submissions.Where(s => s.Id == submission.Id).SingleAsync(token); - var cheatInfo = await _cheatInfoRepository.CreateCheatInfo(updateSub, instance, token); + var cheatInfo = await cheatInfoRepository.CreateCheatInfo(updateSub, instance, token); checkInfo = CheatCheckInfo.FromCheatInfo(cheatInfo); if (updateSub is not null) @@ -246,11 +229,11 @@ public async Task CheckCheat(Submission submission, Cancellation public async Task VerifyAnswer(Submission submission, CancellationToken token = default) { - var trans = await _context.Database.BeginTransactionAsync(token); + var trans = await context.Database.BeginTransactionAsync(token); try { - var instance = await _context.Instances.IgnoreAutoIncludes().Include(i => i.FlagContext) + var instance = await context.Instances.IgnoreAutoIncludes().Include(i => i.FlagContext) .SingleOrDefaultAsync(i => i.ChallengeId == submission.ChallengeId && i.ParticipationId == submission.ParticipationId, token); @@ -264,11 +247,11 @@ public async Task VerifyAnswer(Submission submission, Cancellation // submission is from the queue, do not modify it directly // we need to requery the entity to ensure it is being tracked correctly - var updateSub = await _context.Submissions.SingleAsync(s => s.Id == submission.Id, token); + var updateSub = await context.Submissions.SingleAsync(s => s.Id == submission.Id, token); if (instance.FlagContext is null && submission.Challenge.Type.IsStatic()) { - updateSub.Status = await _context.FlagContexts + updateSub.Status = await context.FlagContexts .AsNoTracking() .AnyAsync( f => f.ChallengeId == submission.ChallengeId && f.Flag == submission.Answer, diff --git a/src/GZCTF/Repositories/LogRepository.cs b/src/GZCTF/Repositories/LogRepository.cs index 1ecb968d2..f65b0d1db 100644 --- a/src/GZCTF/Repositories/LogRepository.cs +++ b/src/GZCTF/Repositories/LogRepository.cs @@ -4,15 +4,11 @@ namespace GZCTF.Repositories; -public class LogRepository : RepositoryBase, ILogRepository +public class LogRepository(AppDbContext context) : RepositoryBase(context), ILogRepository { - public LogRepository(AppDbContext context) : base(context) - { - } - public Task GetLogs(int skip, int count, string? level, CancellationToken token) { - IQueryable data = _context.Logs; + IQueryable data = context.Logs; if (level is not null && level != "All") data = data.Where(x => x.Level == level); diff --git a/src/GZCTF/Repositories/ParticipationRepository.cs b/src/GZCTF/Repositories/ParticipationRepository.cs index 9f87ac624..c0e8d98ed 100644 --- a/src/GZCTF/Repositories/ParticipationRepository.cs +++ b/src/GZCTF/Repositories/ParticipationRepository.cs @@ -5,26 +5,17 @@ namespace GZCTF.Repositories; -public class ParticipationRepository : RepositoryBase, IParticipationRepository +public class ParticipationRepository( + CacheHelper cacheHelper, + IFileRepository fileRepository, + AppDbContext context) : RepositoryBase(context), IParticipationRepository { - private readonly CacheHelper _cacheHelper; - private readonly IFileRepository _fileRepository; - - public ParticipationRepository( - CacheHelper cacheHelper, - IFileRepository fileRepository, - AppDbContext context) : base(context) - { - _cacheHelper = cacheHelper; - _fileRepository = fileRepository; - } - public async Task EnsureInstances(Participation part, Game game, CancellationToken token = default) { - var challenges = await _context.Challenges.Where(c => c.Game == game && c.IsEnabled).ToArrayAsync(token); + var challenges = await context.Challenges.Where(c => c.Game == game && c.IsEnabled).ToArrayAsync(token); // requery instead of Entry - part = await _context.Participations.Include(p => p.Challenges).SingleAsync(p => p.Id == part.Id, token); + part = await context.Participations.Include(p => p.Challenges).SingleAsync(p => p.Id == part.Id, token); bool update = false; @@ -37,31 +28,31 @@ public async Task EnsureInstances(Participation part, Game game, Cancellat } public Task GetParticipationById(int id, CancellationToken token = default) - => _context.Participations.FirstOrDefaultAsync(p => p.Id == id, token); + => context.Participations.FirstOrDefaultAsync(p => p.Id == id, token); public Task GetParticipation(Team team, Game game, CancellationToken token = default) - => _context.Participations.FirstOrDefaultAsync(e => e.Team == team && e.Game == game, token); + => context.Participations.FirstOrDefaultAsync(e => e.Team == team && e.Game == game, token); public Task GetParticipation(UserInfo user, Game game, CancellationToken token = default) - => _context.Participations.FirstOrDefaultAsync(p => p.Members.Any(m => m.Game == game && m.User == user), token); + => context.Participations.FirstOrDefaultAsync(p => p.Members.Any(m => m.Game == game && m.User == user), token); public Task GetParticipationCount(Game game, CancellationToken token = default) - => _context.Participations.Where(p => p.GameId == game.Id).CountAsync(token); + => context.Participations.Where(p => p.GameId == game.Id).CountAsync(token); public Task GetParticipations(Game game, CancellationToken token = default) - => _context.Participations.Where(p => p.GameId == game.Id) + => context.Participations.Where(p => p.GameId == game.Id) .Include(p => p.Team) .ThenInclude(t => t.Members) .OrderBy(p => p.TeamId).ToArrayAsync(token); public Task GetWriteups(Game game, CancellationToken token = default) - => _context.Participations.Where(p => p.Game == game && p.Writeup != null) + => context.Participations.Where(p => p.Game == game && p.Writeup != null) .OrderByDescending(p => p.Writeup!.UploadTimeUTC) .Select(p => WriteupInfoModel.FromParticipation(p)!) .ToArrayAsync(token); public Task CheckRepeatParticipation(UserInfo user, Game game, CancellationToken token = default) - => _context.UserParticipations.Include(p => p.Participation) + => context.UserParticipations.Include(p => p.Participation) .AnyAsync(p => p.User == user && p.Game == game && p.Participation.Status != ParticipationStatus.Rejected, token); @@ -79,7 +70,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 _cacheHelper.FlushScoreboardCache(part.Game.Id, token); + await cacheHelper.FlushScoreboardCache(part.Game.Id, token); return; } @@ -89,34 +80,34 @@ public async Task UpdateParticipationStatus(Participation part, ParticipationSta // flush scoreboard when a team is suspended if (status == ParticipationStatus.Suspended && part.Game.IsActive) - await _cacheHelper.FlushScoreboardCache(part.GameId, token); + await cacheHelper.FlushScoreboardCache(part.GameId, token); } public Task GetParticipationsByIds(IEnumerable ids, CancellationToken token = default) - => _context.Participations.Where(p => ids.Contains(p.Id)) + => context.Participations.Where(p => ids.Contains(p.Id)) .Include(p => p.Team) .ToArrayAsync(token); public async Task RemoveUserParticipations(UserInfo user, Game game, CancellationToken token = default) - => _context.RemoveRange(await _context.UserParticipations + => context.RemoveRange(await context.UserParticipations .Where(p => p.User == user && p.Game == game).ToArrayAsync(token)); public async Task RemoveUserParticipations(UserInfo user, Team team, CancellationToken token = default) - => _context.RemoveRange(await _context.UserParticipations + => context.RemoveRange(await context.UserParticipations .Where(p => p.User == user && p.Team == team).ToArrayAsync(token)); public async Task RemoveParticipation(Participation part, CancellationToken token = default) { await DeleteParticipationWriteUp(part, token); - _context.Remove(part); + 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 fileRepository.DeleteFile(part.Writeup, token); return Task.CompletedTask; } } diff --git a/src/GZCTF/Repositories/PostRepository.cs b/src/GZCTF/Repositories/PostRepository.cs index 01d9c93ed..6ad6b5b27 100644 --- a/src/GZCTF/Repositories/PostRepository.cs +++ b/src/GZCTF/Repositories/PostRepository.cs @@ -6,59 +6,50 @@ namespace GZCTF.Repositories; -public class PostRepository : RepositoryBase, IPostRepository +public class PostRepository(IDistributedCache cache, + ILogger logger, + AppDbContext context) : RepositoryBase(context), IPostRepository { - private readonly IDistributedCache _cache; - private readonly ILogger _logger; - - public PostRepository(IDistributedCache cache, - ILogger logger, - AppDbContext context) : base(context) - { - _cache = cache; - _logger = logger; - } - - public override Task CountAsync(CancellationToken token = default) => _context.Posts.CountAsync(token); + public override Task CountAsync(CancellationToken token = default) => context.Posts.CountAsync(token); public async Task CreatePost(Post post, CancellationToken token = default) { post.UpdateKeyWithHash(); - await _context.AddAsync(post, token); + await context.AddAsync(post, token); await SaveAsync(token); - _cache.Remove(CacheKey.Posts); + cache.Remove(CacheKey.Posts); return post; } public Task GetPostById(string id, CancellationToken token = default) - => _context.Posts.FirstOrDefaultAsync(p => p.Id == id, token); + => context.Posts.FirstOrDefaultAsync(p => p.Id == id, token); public async Task GetPostByIdFromCache(string id, CancellationToken token = default) => (await GetPosts(token)).FirstOrDefault(p => p.Id == id); public Task GetPosts(CancellationToken token = default) - => _cache.GetOrCreateAsync(_logger, CacheKey.Posts, entry => + => cache.GetOrCreateAsync(logger, CacheKey.Posts, entry => { entry.AbsoluteExpirationRelativeToNow = TimeSpan.FromHours(12); - return _context.Posts.AsNoTracking().OrderByDescending(n => n.IsPinned) + return context.Posts.AsNoTracking().OrderByDescending(n => n.IsPinned) .ThenByDescending(n => n.UpdateTimeUTC).ToArrayAsync(token); }, token); public async Task RemovePost(Post post, CancellationToken token = default) { - _context.Remove(post); + context.Remove(post); await SaveAsync(token); - _cache.Remove(CacheKey.Posts); + cache.Remove(CacheKey.Posts); } public async Task UpdatePost(Post post, CancellationToken token = default) { - _context.Update(post); + context.Update(post); await SaveAsync(token); - _cache.Remove(CacheKey.Posts); + cache.Remove(CacheKey.Posts); } } diff --git a/src/GZCTF/Repositories/RepositoryBase.cs b/src/GZCTF/Repositories/RepositoryBase.cs index 8aafd365e..da8a8daf5 100644 --- a/src/GZCTF/Repositories/RepositoryBase.cs +++ b/src/GZCTF/Repositories/RepositoryBase.cs @@ -4,17 +4,14 @@ namespace GZCTF.Repositories; -public class RepositoryBase : IRepository +public class RepositoryBase(AppDbContext context) : IRepository { - protected readonly AppDbContext _context; - - public RepositoryBase(AppDbContext context) - => _context = context; + protected readonly AppDbContext context = context; public Task BeginTransactionAsync(CancellationToken token = default) - => _context.Database.BeginTransactionAsync(token); + => context.Database.BeginTransactionAsync(token); - public string ChangeTrackerView => _context.ChangeTracker.DebugView.LongView; + public string ChangeTrackerView => context.ChangeTracker.DebugView.LongView; public async Task SaveAsync(CancellationToken token = default) { @@ -23,7 +20,7 @@ public async Task SaveAsync(CancellationToken token = default) { try { - await _context.SaveChangesAsync(token); + await context.SaveChangesAsync(token); saved = true; } catch (DbUpdateConcurrencyException ex) @@ -39,9 +36,9 @@ public async Task SaveAsync(CancellationToken token = default) } } - public void Detach(object item) => _context.Entry(item).State = EntityState.Detached; + public void Detach(object item) => context.Entry(item).State = EntityState.Detached; - public void Add(object item) => _context.Add(item); + public void Add(object item) => context.Add(item); public virtual Task CountAsync(CancellationToken token = default) { diff --git a/src/GZCTF/Repositories/SubmissionRepository.cs b/src/GZCTF/Repositories/SubmissionRepository.cs index 78b2c0292..4c2ec57b6 100644 --- a/src/GZCTF/Repositories/SubmissionRepository.cs +++ b/src/GZCTF/Repositories/SubmissionRepository.cs @@ -19,25 +19,25 @@ public SubmissionRepository(IHubContext hub, public async Task AddSubmission(Submission submission, CancellationToken token = default) { - await _context.AddAsync(submission, token); - await _context.SaveChangesAsync(token); + await context.AddAsync(submission, token); + await context.SaveChangesAsync(token); return submission; } public Task GetSubmission(int gameId, int challengeId, string userId, int submitId, CancellationToken token = default) - => _context.Submissions.Where(s => s.Id == submitId && s.UserId == userId && s.GameId == gameId && s.ChallengeId == challengeId) + => context.Submissions.Where(s => s.Id == submitId && s.UserId == userId && s.GameId == gameId && s.ChallengeId == challengeId) .SingleOrDefaultAsync(token); public Task GetUncheckedFlags(CancellationToken token = default) - => _context.Submissions.Where(s => s.Status == AnswerResult.FlagSubmitted) + => context.Submissions.Where(s => s.Status == AnswerResult.FlagSubmitted) .AsNoTracking().Include(e => e.Game).ToArrayAsync(token); private IQueryable GetSubmissionsByType(AnswerResult? type = null) { var subs = type is not null - ? _context.Submissions.Where(s => s.Status == type.Value) - : _context.Submissions; + ? context.Submissions.Where(s => s.Status == type.Value) + : context.Submissions; return subs.OrderByDescending(s => s.SubmitTimeUTC); } diff --git a/src/GZCTF/Repositories/TeamRepository.cs b/src/GZCTF/Repositories/TeamRepository.cs index 94ffb4e0f..faac01ecb 100644 --- a/src/GZCTF/Repositories/TeamRepository.cs +++ b/src/GZCTF/Repositories/TeamRepository.cs @@ -13,7 +13,7 @@ public TeamRepository(AppDbContext context) : base(context) public async Task AnyActiveGame(Team team, CancellationToken token = default) { var current = DateTimeOffset.UtcNow; - var result = await _context.Participations + var result = await context.Participations .Where(p => p.Team == team && p.Game.EndTimeUTC > current) .AnyAsync(token); @@ -26,10 +26,10 @@ public async Task AnyActiveGame(Team team, CancellationToken token = defau return result; } - public override Task CountAsync(CancellationToken token = default) => _context.Teams.CountAsync(token); + public override Task CountAsync(CancellationToken token = default) => context.Teams.CountAsync(token); public Task CheckIsCaptain(UserInfo user, CancellationToken token = default) - => _context.Teams.AnyAsync(t => t.Captain == user, token); + => context.Teams.AnyAsync(t => t.Captain == user, token); public async Task CreateTeam(TeamUpdateModel model, UserInfo user, CancellationToken token = default) { @@ -40,7 +40,7 @@ public Task CheckIsCaptain(UserInfo user, CancellationToken token = defaul team.Members.Add(user); - await _context.AddAsync(team, token); + await context.AddAsync(team, token); await SaveAsync(token); return team; @@ -48,23 +48,23 @@ public Task CheckIsCaptain(UserInfo user, CancellationToken token = defaul public Task DeleteTeam(Team team, CancellationToken token = default) { - _context.Remove(team); + context.Remove(team); return SaveAsync(token); } public Task GetTeamById(int id, CancellationToken token = default) - => _context.Teams.Include(e => e.Members).FirstOrDefaultAsync(t => t.Id == id, token); + => context.Teams.Include(e => e.Members).FirstOrDefaultAsync(t => t.Id == id, token); public Task GetTeams(int count = 100, int skip = 0, CancellationToken token = default) - => _context.Teams.Include(t => t.Members).OrderBy(t => t.Id) + => context.Teams.Include(t => t.Members).OrderBy(t => t.Id) .Skip(skip).Take(count).ToArrayAsync(token); public Task GetUserTeams(UserInfo user, CancellationToken token = default) - => _context.Teams.Where(t => t.Members.Any(u => u.Id == user.Id)) + => context.Teams.Where(t => t.Members.Any(u => u.Id == user.Id)) .Include(t => t.Members).ToArrayAsync(token); public Task SearchTeams(string hint, CancellationToken token = default) - => _context.Teams.Include(t => t.Members).Where(item => EF.Functions.Like(item.Name, $"%{hint}%")) + => context.Teams.Include(t => t.Members).Where(item => EF.Functions.Like(item.Name, $"%{hint}%")) .OrderBy(t => t.Id).Take(30).ToArrayAsync(token); public Task Transfer(Team team, UserInfo user, CancellationToken token = default) @@ -75,7 +75,7 @@ public Task Transfer(Team team, UserInfo user, CancellationToken token = default public async Task VerifyToken(int id, string inviteCode, CancellationToken token = default) { - var team = await _context.Teams.FirstOrDefaultAsync(t => t.Id == id, token); + var team = await context.Teams.FirstOrDefaultAsync(t => t.Id == id, token); return team is not null && team.InviteCode == inviteCode; } } diff --git a/src/GZCTF/Services/Cache/CacheHelper.cs b/src/GZCTF/Services/Cache/CacheHelper.cs index 30dd8fe88..85528d27a 100644 --- a/src/GZCTF/Services/Cache/CacheHelper.cs +++ b/src/GZCTF/Services/Cache/CacheHelper.cs @@ -3,17 +3,10 @@ namespace GZCTF.Services { - public class CacheHelper + public class CacheHelper(ChannelWriter channelWriter) { - private readonly ChannelWriter _channelWriter; - - public CacheHelper(ChannelWriter channelWriter) - { - _channelWriter = channelWriter; - } - public async Task FlushScoreboardCache(int gameId, CancellationToken token) - => await _channelWriter.WriteAsync(ScoreboardCacheHandler.MakeCacheRequest(gameId), token); + => await channelWriter.WriteAsync(ScoreboardCacheHandler.MakeCacheRequest(gameId), token); } } diff --git a/src/GZCTF/Services/Cache/CacheMaker.cs b/src/GZCTF/Services/Cache/CacheMaker.cs index a3762cc2c..9d4d26ffc 100644 --- a/src/GZCTF/Services/Cache/CacheMaker.cs +++ b/src/GZCTF/Services/Cache/CacheMaker.cs @@ -8,18 +8,11 @@ namespace GZCTF.Services; /// /// 缓存更新请求 /// -public class CacheRequest +public class CacheRequest(string key, DistributedCacheEntryOptions? options = null, params string[] _params) { - public string Key { get; set; } = string.Empty; - public string[] Params { get; set; } = Array.Empty(); - public DistributedCacheEntryOptions? Options { get; set; } - - public CacheRequest(string key, DistributedCacheEntryOptions? options = null, params string[] _params) - { - Key = key; - Params = _params; - Options = options; - } + public string Key { get; set; } = key; + public string[] Params { get; set; } = _params; + public DistributedCacheEntryOptions? Options { get; set; } = options; } /// @@ -31,41 +24,29 @@ public interface ICacheRequestHandler public Task Handler(AsyncServiceScope scope, CacheRequest request, CancellationToken token = default); } -public class CacheMaker : IHostedService +public class CacheMaker( + ILogger logger, + IDistributedCache cache, + ChannelReader channelReader, + IServiceScopeFactory serviceScopeFactory) : IHostedService { - private readonly ILogger _logger; - private readonly IDistributedCache _cache; - private readonly ChannelReader _channelReader; - private readonly IServiceScopeFactory _serviceScopeFactory; private CancellationTokenSource _tokenSource { get; set; } = new CancellationTokenSource(); private readonly Dictionary _cacheHandlers = new(); - public CacheMaker( - ILogger logger, - IDistributedCache cache, - ChannelReader channelReader, - IServiceScopeFactory serviceScopeFactory) - { - _logger = logger; - _cache = cache; - _channelReader = channelReader; - _serviceScopeFactory = serviceScopeFactory; - } - public void AddCacheRequestHandler(string key) where T : ICacheRequestHandler, new() => _cacheHandlers.Add(key, new T()); private async Task Maker(CancellationToken token = default) { - _logger.SystemLog($"缓存更新线程已启动", TaskStatus.Pending, LogLevel.Debug); + logger.SystemLog($"缓存更新线程已启动", TaskStatus.Pending, LogLevel.Debug); try { - await foreach (var item in _channelReader.ReadAllAsync(token)) + await foreach (var item in channelReader.ReadAllAsync(token)) { if (!_cacheHandlers.ContainsKey(item.Key)) { - _logger.SystemLog($"缓存更新线程未找到匹配的请求:{item.Key}", TaskStatus.NotFound, LogLevel.Warning); + logger.SystemLog($"缓存更新线程未找到匹配的请求:{item.Key}", TaskStatus.NotFound, LogLevel.Warning); continue; } @@ -74,24 +55,24 @@ private async Task Maker(CancellationToken token = default) if (key is null) { - _logger.SystemLog($"无效的缓存更新请求:{item.Key}", TaskStatus.NotFound, LogLevel.Warning); + logger.SystemLog($"无效的缓存更新请求:{item.Key}", TaskStatus.NotFound, LogLevel.Warning); continue; } var updateLock = CacheKey.UpdateLock(key); - if (await _cache.GetAsync(updateLock, token) is not null) + if (await cache.GetAsync(updateLock, token) is not null) { // only one GZCTF instance will never encounter this problem - _logger.SystemLog($"缓存更新线程已锁定:{key}", TaskStatus.Pending, LogLevel.Debug); + logger.SystemLog($"缓存更新线程已锁定:{key}", TaskStatus.Pending, LogLevel.Debug); continue; } - await using var scope = _serviceScopeFactory.CreateAsyncScope(); + await using var scope = serviceScopeFactory.CreateAsyncScope(); try { - await _cache.SetAsync(updateLock, Array.Empty(), new DistributedCacheEntryOptions + await cache.SetAsync(updateLock, [], new DistributedCacheEntryOptions { AbsoluteExpirationRelativeToNow = TimeSpan.FromSeconds(60) }, token); @@ -100,21 +81,21 @@ private async Task Maker(CancellationToken token = default) if (bytes is not null && bytes.Length > 0) { - await _cache.SetAsync(key, bytes, item.Options ?? new DistributedCacheEntryOptions(), token); - _logger.SystemLog($"缓存已更新:{key} @ {bytes.Length} bytes", TaskStatus.Success, LogLevel.Debug); + await cache.SetAsync(key, bytes, item.Options ?? new DistributedCacheEntryOptions(), token); + logger.SystemLog($"缓存已更新:{key} @ {bytes.Length} bytes", TaskStatus.Success, LogLevel.Debug); } else { - _logger.SystemLog($"缓存生成失败:{key}", TaskStatus.Failed, LogLevel.Warning); + logger.SystemLog($"缓存生成失败:{key}", TaskStatus.Failed, LogLevel.Warning); } } catch (Exception e) { - _logger.SystemLog($"缓存更新线程更新失败:{key} @ {e.Message}", TaskStatus.Failed, LogLevel.Error); + logger.SystemLog($"缓存更新线程更新失败:{key} @ {e.Message}", TaskStatus.Failed, LogLevel.Error); } finally { - await _cache.RemoveAsync(updateLock, token); + await cache.RemoveAsync(updateLock, token); } token.ThrowIfCancellationRequested(); @@ -122,11 +103,11 @@ private async Task Maker(CancellationToken token = default) } catch (OperationCanceledException) { - _logger.SystemLog($"任务取消,缓存更新线程将退出", TaskStatus.Exit, LogLevel.Debug); + logger.SystemLog($"任务取消,缓存更新线程将退出", TaskStatus.Exit, LogLevel.Debug); } finally { - _logger.SystemLog($"缓存更新线程已退出", TaskStatus.Exit, LogLevel.Debug); + logger.SystemLog($"缓存更新线程已退出", TaskStatus.Exit, LogLevel.Debug); } } @@ -149,7 +130,7 @@ public Task StopAsync(CancellationToken token) { _tokenSource.Cancel(); - _logger.SystemLog("缓存更新已停用", TaskStatus.Success, LogLevel.Debug); + logger.SystemLog("缓存更新已停用", TaskStatus.Success, LogLevel.Debug); return Task.CompletedTask; } diff --git a/src/GZCTF/Services/ConfigService.cs b/src/GZCTF/Services/ConfigService.cs index f70ed4bbe..4b7409949 100644 --- a/src/GZCTF/Services/ConfigService.cs +++ b/src/GZCTF/Services/ConfigService.cs @@ -6,20 +6,11 @@ namespace GZCTF.Services; -public class ConfigService : IConfigService +public class ConfigService(AppDbContext context, + ILogger logger, + IConfiguration configuration) : IConfigService { - private readonly ILogger _logger; - private readonly IConfigurationRoot? _configuration; - private readonly AppDbContext _context; - - public ConfigService(AppDbContext context, - ILogger logger, - IConfiguration configuration) - { - _context = context; - _logger = logger; - _configuration = configuration as IConfigurationRoot; - } + private readonly IConfigurationRoot? _configuration = configuration as IConfigurationRoot; private static void MapConfigsInternal(string key, HashSet configs, Type? type, object? value) { @@ -70,7 +61,7 @@ public Task SaveConfig(T config, CancellationToken token = default) where T : private async Task SaveConfigInternal(HashSet configs, CancellationToken token = default) { - var dbConfigs = await _context.Configs.ToDictionaryAsync(c => c.ConfigKey, c => c, token); + var dbConfigs = await context.Configs.ToDictionaryAsync(c => c.ConfigKey, c => c, token); foreach (var conf in configs) { if (dbConfigs.TryGetValue(conf.ConfigKey, out var dbConf)) @@ -78,17 +69,17 @@ private async Task SaveConfigInternal(HashSet configs, CancellationToken if (dbConf.Value != conf.Value) { dbConf.Value = conf.Value; - _logger.SystemLog($"更新全局设置:{conf.ConfigKey} => {conf.Value}", TaskStatus.Success, LogLevel.Debug); + logger.SystemLog($"更新全局设置:{conf.ConfigKey} => {conf.Value}", TaskStatus.Success, LogLevel.Debug); } } else { - _logger.SystemLog($"添加全局设置:{conf.ConfigKey} => {conf.Value}", TaskStatus.Success, LogLevel.Debug); - await _context.Configs.AddAsync(conf, token); + logger.SystemLog($"添加全局设置:{conf.ConfigKey} => {conf.Value}", TaskStatus.Success, LogLevel.Debug); + await context.Configs.AddAsync(conf, token); } } - await _context.SaveChangesAsync(token); + await context.SaveChangesAsync(token); _configuration?.Reload(); } diff --git a/src/GZCTF/Services/CronJobService.cs b/src/GZCTF/Services/CronJobService.cs index d63fe800d..0b861bb3a 100644 --- a/src/GZCTF/Services/CronJobService.cs +++ b/src/GZCTF/Services/CronJobService.cs @@ -7,22 +7,14 @@ namespace GZCTF.Services; -public class CronJobService : IHostedService, IDisposable +public class CronJobService(IServiceScopeFactory provider, ILogger logger) : IHostedService, IDisposable { - private readonly ILogger _logger; - private readonly IServiceScopeFactory _serviceProvider; private Timer? _timer; - public CronJobService(IServiceScopeFactory provider, ILogger logger) - { - _serviceProvider = provider; - _logger = logger; - } - public Task StartAsync(CancellationToken token) { _timer = new Timer(Execute, null, TimeSpan.Zero, TimeSpan.FromMinutes(3)); - _logger.SystemLog("定时任务已启动", TaskStatus.Success, LogLevel.Debug); + logger.SystemLog("定时任务已启动", TaskStatus.Success, LogLevel.Debug); return Task.CompletedTask; } @@ -35,7 +27,7 @@ private async Task ContainerChecker(AsyncServiceScope scope) { await containerService.DestroyContainerAsync(container); await containerRepo.RemoveContainer(container); - _logger.SystemLog($"移除到期容器 [{container.ContainerId}]", TaskStatus.Success, LogLevel.Debug); + logger.SystemLog($"移除到期容器 [{container.ContainerId}]", TaskStatus.Success, LogLevel.Debug); } } @@ -57,14 +49,14 @@ private async Task BootstrapCache(AsyncServiceScope scope) if (value is null) { await channelWriter.WriteAsync(ScoreboardCacheHandler.MakeCacheRequest(game)); - _logger.SystemLog($"比赛 #{key} 即将开始,积分榜缓存已加入缓存队列", TaskStatus.Success, LogLevel.Debug); + logger.SystemLog($"比赛 #{key} 即将开始,积分榜缓存已加入缓存队列", TaskStatus.Success, LogLevel.Debug); } } } private async void Execute(object? state) { - await using var scope = _serviceProvider.CreateAsyncScope(); + await using var scope = provider.CreateAsyncScope(); await ContainerChecker(scope); await BootstrapCache(scope); @@ -73,7 +65,7 @@ private async void Execute(object? state) public Task StopAsync(CancellationToken token) { _timer?.Change(Timeout.Infinite, 0); - _logger.SystemLog("定时任务已停止", TaskStatus.Exit, LogLevel.Debug); + logger.SystemLog("定时任务已停止", TaskStatus.Exit, LogLevel.Debug); return Task.CompletedTask; } diff --git a/src/GZCTF/Services/FlagChecker.cs b/src/GZCTF/Services/FlagChecker.cs index 15b273e14..b01c3cc87 100644 --- a/src/GZCTF/Services/FlagChecker.cs +++ b/src/GZCTF/Services/FlagChecker.cs @@ -5,37 +5,24 @@ namespace GZCTF.Services; -public class FlagChecker : IHostedService +public class FlagChecker(ChannelReader channelReader, + ChannelWriter channelWriter, + ILogger logger, + IServiceScopeFactory serviceScopeFactory) : IHostedService { - private readonly ILogger _logger; - private readonly ChannelReader _channelReader; - private readonly ChannelWriter _channelWriter; - private readonly IServiceScopeFactory _serviceScopeFactory; - private CancellationTokenSource TokenSource { get; set; } = new CancellationTokenSource(); - public FlagChecker(ChannelReader channelReader, - ChannelWriter channelWriter, - ILogger logger, - IServiceScopeFactory serviceScopeFactory) - { - _logger = logger; - _channelReader = channelReader; - _channelWriter = channelWriter; - _serviceScopeFactory = serviceScopeFactory; - } - private async Task Checker(int id, CancellationToken token = default) { - _logger.SystemLog($"检查线程 #{id} 已启动", TaskStatus.Pending, LogLevel.Debug); + logger.SystemLog($"检查线程 #{id} 已启动", TaskStatus.Pending, LogLevel.Debug); try { - await foreach (var item in _channelReader.ReadAllAsync(token)) + await foreach (var item in channelReader.ReadAllAsync(token)) { - _logger.SystemLog($"检查线程 #{id} 开始处理提交:{item.Answer}", TaskStatus.Pending, LogLevel.Debug); + logger.SystemLog($"检查线程 #{id} 开始处理提交:{item.Answer}", TaskStatus.Pending, LogLevel.Debug); - await using var scope = _serviceScopeFactory.CreateAsyncScope(); + await using var scope = serviceScopeFactory.CreateAsyncScope(); var cacheHelper = scope.ServiceProvider.GetRequiredService(); var eventRepository = scope.ServiceProvider.GetRequiredService(); @@ -48,10 +35,10 @@ private async Task Checker(int id, CancellationToken token = default) var (type, ans) = await instanceRepository.VerifyAnswer(item, token); if (ans == AnswerResult.NotFound) - _logger.Log($"[实例未知] 未找到队伍 [{item.Team.Name}] 提交题目 [{item.Challenge.Title}] 的实例", item.User!, TaskStatus.NotFound, LogLevel.Warning); + logger.Log($"[实例未知] 未找到队伍 [{item.Team.Name}] 提交题目 [{item.Challenge.Title}] 的实例", item.User!, TaskStatus.NotFound, LogLevel.Warning); else if (ans == AnswerResult.Accepted) { - _logger.Log($"[提交正确] 队伍 [{item.Team.Name}] 提交题目 [{item.Challenge.Title}] 的答案 [{item.Answer}]", item.User!, TaskStatus.Success, LogLevel.Information); + logger.Log($"[提交正确] 队伍 [{item.Team.Name}] 提交题目 [{item.Challenge.Title}] 的答案 [{item.Answer}]", item.User!, TaskStatus.Success, LogLevel.Information); await eventRepository.AddEvent(GameEvent.FromSubmission(item, type, ans), token); @@ -61,7 +48,7 @@ private async Task Checker(int id, CancellationToken token = default) } else { - _logger.Log($"[提交错误] 队伍 [{item.Team.Name}] 提交题目 [{item.Challenge.Title}] 的答案 [{item.Answer}]", item.User!, TaskStatus.Failed, LogLevel.Information); + logger.Log($"[提交错误] 队伍 [{item.Team.Name}] 提交题目 [{item.Challenge.Title}] 的答案 [{item.Answer}]", item.User!, TaskStatus.Failed, LogLevel.Information); await eventRepository.AddEvent(GameEvent.FromSubmission(item, type, ans), token); @@ -70,7 +57,7 @@ private async Task Checker(int id, CancellationToken token = default) if (ans == AnswerResult.CheatDetected) { - _logger.Log($"[作弊检查] 队伍 [{item.Team.Name}] 疑似违规 [{item.Challenge.Title}],相关队伍 [{result.SourceTeamName}]", item.User!, TaskStatus.Success, LogLevel.Information); + logger.Log($"[作弊检查] 队伍 [{item.Team.Name}] 疑似违规 [{item.Challenge.Title}],相关队伍 [{result.SourceTeamName}]", item.User!, TaskStatus.Success, LogLevel.Information); await eventRepository.AddEvent(new() { Type = EventType.CheatDetected, @@ -92,13 +79,13 @@ await eventRepository.AddEvent(new() } catch (DbUpdateConcurrencyException) { - _logger.SystemLog($"[数据库并发] 未能更新提交 #{item.Id} 的状态", TaskStatus.Failed, LogLevel.Warning); - await _channelWriter.WriteAsync(item, token); + logger.SystemLog($"[数据库并发] 未能更新提交 #{item.Id} 的状态", TaskStatus.Failed, LogLevel.Warning); + await channelWriter.WriteAsync(item, token); } catch (Exception e) { - _logger.SystemLog($"检查线程 #{id} 发生异常", TaskStatus.Failed, LogLevel.Debug); - _logger.LogError(e.Message, e); + logger.SystemLog($"检查线程 #{id} 发生异常", TaskStatus.Failed, LogLevel.Debug); + logger.LogError(e.Message, e); } token.ThrowIfCancellationRequested(); @@ -106,11 +93,11 @@ await eventRepository.AddEvent(new() } catch (OperationCanceledException) { - _logger.SystemLog($"任务取消,检查线程 #{id} 将退出", TaskStatus.Exit, LogLevel.Debug); + logger.SystemLog($"任务取消,检查线程 #{id} 将退出", TaskStatus.Exit, LogLevel.Debug); } finally { - _logger.SystemLog($"检查线程 #{id} 已退出", TaskStatus.Exit, LogLevel.Debug); + logger.SystemLog($"检查线程 #{id} 已退出", TaskStatus.Exit, LogLevel.Debug); } } @@ -121,25 +108,25 @@ public async Task StartAsync(CancellationToken cancellationToken) for (int i = 0; i < 2; ++i) _ = Checker(i, TokenSource.Token); - await using var scope = _serviceScopeFactory.CreateAsyncScope(); + await using var scope = serviceScopeFactory.CreateAsyncScope(); var submissionRepository = scope.ServiceProvider.GetRequiredService(); var flags = await submissionRepository.GetUncheckedFlags(TokenSource.Token); foreach (var item in flags) - await _channelWriter.WriteAsync(item, TokenSource.Token); + await channelWriter.WriteAsync(item, TokenSource.Token); if (flags.Length > 0) - _logger.SystemLog($"重新开始检查 {flags.Length} 个 flag", TaskStatus.Pending, LogLevel.Debug); + logger.SystemLog($"重新开始检查 {flags.Length} 个 flag", TaskStatus.Pending, LogLevel.Debug); - _logger.SystemLog("Flag 检查已启用", TaskStatus.Success, LogLevel.Debug); + logger.SystemLog("Flag 检查已启用", TaskStatus.Success, LogLevel.Debug); } public Task StopAsync(CancellationToken cancellationToken) { TokenSource.Cancel(); - _logger.SystemLog("Flag 检查已停止", TaskStatus.Exit, LogLevel.Debug); + logger.SystemLog("Flag 检查已停止", TaskStatus.Exit, LogLevel.Debug); return Task.CompletedTask; } diff --git a/src/GZCTF/Services/MailSender.cs b/src/GZCTF/Services/MailSender.cs index 6b79321d8..a10cc3afd 100644 --- a/src/GZCTF/Services/MailSender.cs +++ b/src/GZCTF/Services/MailSender.cs @@ -9,16 +9,9 @@ namespace GZCTF.Services; -public class MailSender : IMailSender +public class MailSender(IOptions options, ILogger logger) : IMailSender { - private readonly EmailConfig? _options; - private readonly ILogger _logger; - - public MailSender(IOptions options, ILogger logger) - { - _options = options.Value; - _logger = logger; - } + private readonly EmailConfig? _options = options.Value; public async Task SendEmailAsync(string subject, string content, string to) { @@ -43,12 +36,12 @@ public async Task SendEmailAsync(string subject, string content, string to await client.SendAsync(msg); await client.DisconnectAsync(true); - _logger.SystemLog("发送邮件:" + to, TaskStatus.Success, LogLevel.Information); + logger.SystemLog("发送邮件:" + to, TaskStatus.Success, LogLevel.Information); return true; } catch (Exception e) { - _logger.LogError(e, "邮件发送遇到问题"); + logger.LogError(e, "邮件发送遇到问题"); return false; } } @@ -57,7 +50,7 @@ public async Task SendUrlAsync(string? title, string? information, string? btnms { if (email is null || userName is null || title is null) { - _logger.SystemLog("无效的邮件发送调用!", TaskStatus.Failed); + logger.SystemLog("无效的邮件发送调用!", TaskStatus.Failed); return; } @@ -79,7 +72,7 @@ public async Task SendUrlAsync(string? title, string? information, string? btnms .ToString(); if (!await SendEmailAsync(title, emailContent, email)) - _logger.SystemLog("邮件发送失败!", TaskStatus.Failed); + logger.SystemLog("邮件发送失败!", TaskStatus.Failed); } private bool SendUrlIfPossible(string? title, string? information, string? btnmsg, string? userName, string? email, string? url) diff --git a/src/GZCTF/Utils/Codec.cs b/src/GZCTF/Utils/Codec.cs index 1910f6aa6..fd0775dc1 100644 --- a/src/GZCTF/Utils/Codec.cs +++ b/src/GZCTF/Utils/Codec.cs @@ -46,7 +46,7 @@ public static string Encode(string? str, string type = "utf-8") public static byte[] EncodeToBytes(string? str, string type = "utf-8") { if (str is null) - return Array.Empty(); + return []; byte[] encoded; try @@ -55,27 +55,27 @@ public static byte[] EncodeToBytes(string? str, string type = "utf-8") } catch { - return Array.Empty(); + return []; } Span buffer = new char[encoded.Length * 4 / 3 + 8]; if (Convert.TryToBase64Chars(encoded, buffer, out var charsWritten)) - return Encoding.GetEncoding(type).GetBytes(buffer.Slice(0, charsWritten).ToArray()); + return Encoding.GetEncoding(type).GetBytes(buffer[..charsWritten].ToArray()); else - return Array.Empty(); + return []; } public static byte[] DecodeToBytes(string? str) { if (str is null) - return Array.Empty(); + return []; Span buffer = new byte[str.Length * 3 / 4 + 8]; if (Convert.TryFromBase64String(str, buffer, out int bytesWritten)) - return buffer.Slice(0, bytesWritten).ToArray(); + return buffer[..bytesWritten].ToArray(); - return Array.Empty(); + return []; } } @@ -314,9 +314,7 @@ public static string ToValidRFC1123String(this string str, string leading = "nam public static List ASCII(this string str) { var buff = Encoding.ASCII.GetBytes(str); - List res = new(); - foreach (var item in buff) - res.Add(item); + List res = [..buff]; return res; } @@ -340,7 +338,7 @@ public static string Reverse(this string s) /// public static string StrMD5(this string str, bool useBase64 = false) { - var output = MD5.HashData(Encoding.Default.GetBytes(str)); + var output = MD5.HashData(str.ToUTF8Bytes()); if (useBase64) return Convert.ToBase64String(output); return BitConverter.ToString(output).Replace("-", "").ToLowerInvariant(); @@ -354,26 +352,35 @@ public static string StrMD5(this string str, bool useBase64 = false) /// public static string StrSHA256(this string str, bool useBase64 = false) { - var output = SHA256.HashData(Encoding.Default.GetBytes(str)); + var output = SHA256.HashData(str.ToUTF8Bytes()); if (useBase64) return Convert.ToBase64String(output); return BitConverter.ToString(output).Replace("-", "").ToLowerInvariant(); } /// - /// 获取MD5哈希字节摘要 + /// 获取 MD5 哈希字节摘要 /// /// 原始字符串 /// public static byte[] BytesMD5(this string str) - => MD5.HashData(Encoding.Default.GetBytes(str)); + => MD5.HashData(str.ToUTF8Bytes()); /// - /// 获取SHA256哈希字节摘要 + /// 获取 SHA256 哈希字节摘要 /// /// 原始字符串 /// public static byte[] BytesSHA256(this string str) - => SHA256.HashData(Encoding.Default.GetBytes(str)); + => SHA256.HashData(str.ToUTF8Bytes()); + + + /// + /// 获取字符串 UTF-8 编码字节 + /// + /// 原始字符串 + /// + public static byte[] ToUTF8Bytes(this string str) + => Encoding.UTF8.GetBytes(str); } diff --git a/src/GZCTF/Utils/ExcelHelper.cs b/src/GZCTF/Utils/ExcelHelper.cs index 53e494333..13f96ca81 100644 --- a/src/GZCTF/Utils/ExcelHelper.cs +++ b/src/GZCTF/Utils/ExcelHelper.cs @@ -6,8 +6,8 @@ namespace GZCTF.Utils; public static class ExcelHelper { - private static readonly string[] CommonScoreboardHeader = { "排名", "战队", "队长", "队员", "学号", "手机号", "解题数量", "得分时间", "总分" }; - private static readonly string[] CommonSubmissionHeader = { "提交状态", "提交时间", "战队", "用户", "题目", "提交内容", "用户邮箱" }; + private static readonly string[] CommonScoreboardHeader = ["排名", "战队", "队长", "队员", "学号", "手机号", "解题数量", "得分时间", "总分"]; + private static readonly string[] CommonSubmissionHeader = ["提交状态", "提交时间", "战队", "用户", "题目", "提交内容", "用户邮箱"]; public static MemoryStream GetScoreboardExcel(ScoreboardModel scoreboard, Game game) {