/
Kovalenko
/
TODO
Обзор
Документация
Войти
/
Kovalenko
/
TODO
Код
Запросы
0
Задачи
Вики
Пакеты
0
Релизы
0
CI/CD
Аналитика
Безопасность
lab4
TaskApi/Services/MessageHandler.cs
452 строки
16 KB
Kovalenko1
add indController
07 мар 2026, 11:30
07 мар 2026, 11:30
a82b55c
Код
Авторство
О чём код?
using System.Text.Json; using Microsoft.AspNetCore.Identity; using Microsoft.EntityFrameworkCore; using TaskApi.Data; using TaskApi.Dtos.Internal; using TaskApi.Messages; using TaskApi.Models; using System.Security.Claims; namespace TaskApi.Services; public class MessageHandler { private readonly IServiceProvider _serviceProvider; private readonly ILogger<MessageHandler> _logger; public MessageHandler(IServiceProvider serviceProvider, ILogger<MessageHandler> logger) { _serviceProvider = serviceProvider; _logger = logger; } public async Task<ApiResponse> HandleMessage(ApiRequest request) { try { // Проверка идемпотентности using var scope = _serviceProvider.CreateScope(); var db = scope.ServiceProvider.GetRequiredService<AppDbContext>(); var existingKey = await db.IdempotencyKeys .FirstOrDefaultAsync(k => k.RequestId == request.Id); if (existingKey != null) { _logger.LogInformation("Запрос {RequestId} уже обработан, возвращаем кэшированный результат", request.Id); return new ApiResponse { CorrelationId = request.Id, Status = "ok", Data = existingKey.ResponseData != null ? JsonSerializer.Deserialize<object>(existingKey.ResponseData) : null }; } // Обработка запроса в зависимости от версии и действия object? responseData = request.Version.ToLower() switch { "v1" => await HandleV1Request(request), "v2" => await HandleV2Request(request), _ => throw new Exception($"Неподдерживаемая версия API: {request.Version}") }; // Сохранение идемпотентного ключа var idempotencyKey = new IdempotencyKey { RequestId = request.Id, ResponseData = responseData != null ? JsonSerializer.Serialize(responseData) : null, CreatedAt = DateTimeOffset.UtcNow }; db.IdempotencyKeys.Add(idempotencyKey); await db.SaveChangesAsync(); return new ApiResponse { CorrelationId = request.Id, Status = "ok", Data = responseData }; } catch (Exception ex) { _logger.LogError(ex, "Ошибка при обработке запроса {RequestId}", request.Id); return new ApiResponse { CorrelationId = request.Id, Status = "error", Error = ex.Message }; } } private async Task<object?> HandleV1Request(ApiRequest request) { return request.Action.ToLower() switch { "register" => await HandleRegister(request), "login" => await HandleLogin(request), "get_tasks" => await HandleGetTasks(request), "get_task" => await HandleGetTask(request), "create_task" => await HandleCreateTask(request), "update_task" => await HandleUpdateTask(request), "delete_task" => await HandleDeleteTask(request), "idempotency_probe" => HandleIdempotencyProbe(), _ => throw new Exception($"Неизвестное действие: {request.Action}") }; } private async Task<object?> HandleV2Request(ApiRequest request) { return request.Action.ToLower() switch { "register" => await HandleRegister(request), "login" => await HandleLogin(request), "get_tasks" => await HandleGetTasks(request), "get_task" => await HandleGetTask(request), "create_task" => await HandleCreateTask(request), "update_task" => await HandleUpdateTask(request), "delete_task" => await HandleDeleteTask(request), "get_summary" => await HandleGetSummary(), "idempotency_probe" => HandleIdempotencyProbe(), _ => throw new Exception($"Неизвестное действие: {request.Action}") }; } private static object HandleIdempotencyProbe() { return new { nonce = Guid.NewGuid().ToString("N"), generatedAt = DateTimeOffset.UtcNow }; } private async Task<AuthResponseData> HandleRegister(ApiRequest request) { using var scope = _serviceProvider.CreateScope(); var db = scope.ServiceProvider.GetRequiredService<AppDbContext>(); var passwordHasher = scope.ServiceProvider.GetRequiredService<PasswordHasher<User>>(); var jwtService = scope.ServiceProvider.GetRequiredService<JwtTokenService>(); var data = JsonSerializer.Deserialize<RegisterData>(request.Data.GetRawText()); if (data == null || string.IsNullOrWhiteSpace(data.Email) || string.IsNullOrWhiteSpace(data.Password)) { throw new Exception("Некорректные данные для регистрации"); } var existingUser = await db.Users.FirstOrDefaultAsync(u => u.Email == data.Email); if (existingUser != null) { throw new Exception("Пользователь уже существует"); } var user = new User { Id = Guid.NewGuid(), Email = data.Email, CreatedAt = DateTimeOffset.UtcNow }; user.PasswordHash = passwordHasher.HashPassword(user, data.Password); db.Users.Add(user); await db.SaveChangesAsync(); var token = jwtService.GenerateToken(user.Id, user.Username); return new AuthResponseData { Token = token, UserId = user.Id.ToString(), Username = user.Username }; } private async Task<AuthResponseData> HandleLogin(ApiRequest request) { using var scope = _serviceProvider.CreateScope(); var db = scope.ServiceProvider.GetRequiredService<AppDbContext>(); var passwordHasher = scope.ServiceProvider.GetRequiredService<PasswordHasher<User>>(); var jwtService = scope.ServiceProvider.GetRequiredService<JwtTokenService>(); var data = JsonSerializer.Deserialize<LoginData>(request.Data.GetRawText()); if (data == null || string.IsNullOrWhiteSpace(data.Email) || string.IsNullOrWhiteSpace(data.Password)) { throw new Exception("Некорректные данные для входа"); } var user = await db.Users.FirstOrDefaultAsync(u => u.Email == data.Email); if (user == null) { throw new Exception("Неверное имя пользователя или пароль"); } var verificationResult = passwordHasher.VerifyHashedPassword(user, user.PasswordHash, data.Password); if (verificationResult == PasswordVerificationResult.Failed) { throw new Exception("Неверное имя пользователя или пароль"); } var token = jwtService.GenerateToken(user.Id, user.Username); return new AuthResponseData { Token = token, UserId = user.Id.ToString(), Username = user.Username }; } private async Task<List<TaskResponseData>> HandleGetTasks(ApiRequest request) { using var scope = _serviceProvider.CreateScope(); var db = scope.ServiceProvider.GetRequiredService<AppDbContext>(); var queryData = JsonSerializer.Deserialize<GetTasksData>(request.Data.GetRawText()) ?? new GetTasksData(); var userId = GetUserIdFromAuth(request.Auth); var query = db.Tasks.AsNoTracking().Where(t => t.UserId == userId); if (string.Equals(request.Version, "v2", StringComparison.OrdinalIgnoreCase) && !string.IsNullOrWhiteSpace(queryData.Status)) { if (!Enum.TryParse<Models.TaskStatus>(queryData.Status, true, out var parsedStatus)) { throw new Exception("Invalid status filter."); } query = query.Where(t => t.Status == parsedStatus); } var page = queryData.Page ?? 1; var pageSize = queryData.PageSize ?? 20; if (page < 1 || pageSize < 1 || pageSize > 100) { throw new Exception("Invalid pagination parameters."); } var tasks = await query .OrderByDescending(t => t.CreatedAt) .Skip((page - 1) * pageSize) .Take(pageSize) .ToListAsync(); return tasks.Select(MapTaskToResponse).ToList(); } private async Task<TaskResponseData> HandleGetTask(ApiRequest request) { using var scope = _serviceProvider.CreateScope(); var db = scope.ServiceProvider.GetRequiredService<AppDbContext>(); var data = JsonSerializer.Deserialize<GetTaskData>(request.Data.GetRawText()); if (data == null || string.IsNullOrWhiteSpace(data.TaskId)) { throw new Exception("Не указан ID задачи"); } var taskId = Guid.Parse(data.TaskId); var userId = GetUserIdFromAuth(request.Auth); var task = await db.Tasks .AsNoTracking() .FirstOrDefaultAsync(t => t.Id == taskId && t.UserId == userId); if (task == null) { throw new Exception("Задача не найдена"); } return MapTaskToResponse(task); } private async Task<TaskResponseData> HandleCreateTask(ApiRequest request) { using var scope = _serviceProvider.CreateScope(); var db = scope.ServiceProvider.GetRequiredService<AppDbContext>(); var data = JsonSerializer.Deserialize<TaskData>(request.Data.GetRawText()); if (data == null || string.IsNullOrWhiteSpace(data.Title)) { throw new Exception("Не указан заголовок задачи"); } var userId = GetUserIdFromAuth(request.Auth); var task = new TaskItem { Id = Guid.NewGuid(), UserId = userId, Title = data.Title, Description = data.Description ?? "", Status = ParseTaskStatus(data.Status), DueDate = ParseDate(data.DueDate), Assignee = data.Assignee, Priority = data.Priority, CreatedAt = DateTimeOffset.UtcNow, UpdatedAt = DateTimeOffset.UtcNow }; db.Tasks.Add(task); await db.SaveChangesAsync(); return MapTaskToResponse(task); } private async Task<TaskResponseData> HandleUpdateTask(ApiRequest request) { using var scope = _serviceProvider.CreateScope(); var db = scope.ServiceProvider.GetRequiredService<AppDbContext>(); var data = JsonSerializer.Deserialize<UpdateTaskData>(request.Data.GetRawText()); if (data == null || string.IsNullOrWhiteSpace(data.TaskId)) { throw new Exception("Не указан ID задачи"); } var taskId = Guid.Parse(data.TaskId); var userId = GetUserIdFromAuth(request.Auth); var task = await db.Tasks.FirstOrDefaultAsync(t => t.Id == taskId && t.UserId == userId); if (task == null) { throw new Exception("Задача не найдена"); } if (!string.IsNullOrWhiteSpace(data.Title)) task.Title = data.Title; if (data.Description != null) task.Description = data.Description; if (data.Status != null) task.Status = ParseTaskStatus(data.Status); if (data.DueDate != null) task.DueDate = ParseDate(data.DueDate); if (data.Assignee != null) task.Assignee = data.Assignee; if (data.Priority.HasValue) task.Priority = data.Priority; task.UpdatedAt = DateTimeOffset.UtcNow; await db.SaveChangesAsync(); return MapTaskToResponse(task); } private async Task<object?> HandleDeleteTask(ApiRequest request) { using var scope = _serviceProvider.CreateScope(); var db = scope.ServiceProvider.GetRequiredService<AppDbContext>(); var data = JsonSerializer.Deserialize<DeleteTaskData>(request.Data.GetRawText()); if (data == null || string.IsNullOrWhiteSpace(data.TaskId)) { throw new Exception("Не указан ID задачи"); } var taskId = Guid.Parse(data.TaskId); var userId = GetUserIdFromAuth(request.Auth); var task = await db.Tasks.FirstOrDefaultAsync(t => t.Id == taskId && t.UserId == userId); if (task == null) { throw new Exception("Задача не найдена"); } db.Tasks.Remove(task); await db.SaveChangesAsync(); return null; } private async Task<TaskSummaryResponse> HandleGetSummary() { using var scope = _serviceProvider.CreateScope(); var db = scope.ServiceProvider.GetRequiredService<AppDbContext>(); var counts = await db.Tasks.AsNoTracking() .GroupBy(task => task.Status) .Select(group => new { Status = group.Key, Count = group.Count() }) .ToListAsync(); var byStatus = Enum.GetValues<Models.TaskStatus>() .ToDictionary(status => status.ToString(), _ => 0, StringComparer.OrdinalIgnoreCase); foreach (var item in counts) { byStatus[item.Status.ToString()] = item.Count; } return new TaskSummaryResponse { TotalTasks = counts.Sum(item => item.Count), ByStatus = byStatus, LatestCreatedAt = await db.Tasks.AsNoTracking().MaxAsync(task => (DateTimeOffset?)task.CreatedAt), LatestUpdatedAt = await db.Tasks.AsNoTracking().MaxAsync(task => (DateTimeOffset?)task.UpdatedAt) }; } private Guid GetUserIdFromAuth(string auth) { // Извлекаем userId из JWT токена using var scope = _serviceProvider.CreateScope(); var jwtService = scope.ServiceProvider.GetRequiredService<JwtTokenService>(); var principal = jwtService.ValidateToken(auth); // JwtSecurityTokenHandler may map "sub" to ClaimTypes.NameIdentifier; check both. var userIdClaim = principal?.FindFirst("sub")?.Value ?? principal?.FindFirst(ClaimTypes.NameIdentifier)?.Value; if (string.IsNullOrWhiteSpace(userIdClaim) || !Guid.TryParse(userIdClaim, out var userId)) { throw new Exception("Недействительный токен аутентификации"); } return userId; } private static TaskResponseData MapTaskToResponse(TaskItem task) { return new TaskResponseData { Id = task.Id.ToString(), Title = task.Title, Description = task.Description, Status = task.Status.ToString(), DueDate = task.DueDate?.ToString("O"), Assignee = task.Assignee, Priority = task.Priority, CreatedAt = task.CreatedAt.ToString("O"), UpdatedAt = task.UpdatedAt.ToString("O") }; } private static Models.TaskStatus ParseTaskStatus(string? status) { if (string.IsNullOrWhiteSpace(status)) return Models.TaskStatus.New; return status.ToLower() switch { "new" => Models.TaskStatus.New, "inprogress" => Models.TaskStatus.InProgress, "completed" => Models.TaskStatus.Completed, "cancelled" => Models.TaskStatus.Cancelled, _ => Models.TaskStatus.New }; } private static DateTimeOffset? ParseDate(string? date) { if (string.IsNullOrWhiteSpace(date)) return null; if (DateTimeOffset.TryParse(date, out var result)) return result; return null; } }