7. Генерация отчётов¶
Отчёт помогает выгрузить данные для анализа в Excel, LibreOffice Calc или BI-систему. В практике используется CSV, потому что это простой текстовый формат.
В производственных системах данные часто нужны не только внутри API. Руководитель может попросить таблицу за неделю, аналитик — выгрузку ошибок, преподаватель — доказательство того, что система действительно накопила события. CSV закрывает эти сценарии без сложных библиотек.
CSV — это текст, где каждая строка является строкой таблицы, а значения разделяются запятыми. Его можно открыть в табличном редакторе. Но у формата есть нюанс: если внутри значения есть запятая, кавычки или перенос строки, значение нужно экранировать.
DTO строки отчёта¶
DTO отчёта лучше отделить от сущности ActivityEvent. В отчёте может быть имя пользователя, человекочитаемая роль или вычисляемое поле, которых нет напрямую в таблице события.
Пример ниже задаёт форму одной строки CSV-файла. Это не таблица базы данных, а удобное представление данных для выгрузки и чтения человеком.
public record ActivityEventReportRow(
int Id,
string UserName,
string Type,
string Source,
string Severity,
DateTime CreatedAt,
string Description);
Генератор CSV¶
Генератор получает уже подготовленные строки и не обращается к базе. Это хорошее разделение ответственности: сервис отчётов выбирает данные, генератор только превращает их в файл.
public class CsvReportGenerator
{
public byte[] GenerateEventsReport(IEnumerable<ActivityEventReportRow> rows)
{
var builder = new StringBuilder();
builder.AppendLine("Id,UserName,Type,Source,Severity,CreatedAt,Description");
foreach (var row in rows)
{
builder.Append(row.Id).Append(',');
builder.Append(Escape(row.UserName)).Append(',');
builder.Append(Escape(row.Type)).Append(',');
builder.Append(Escape(row.Source)).Append(',');
builder.Append(Escape(row.Severity)).Append(',');
builder.Append(row.CreatedAt.ToString("O")).Append(',');
builder.AppendLine(Escape(row.Description));
}
return Encoding.UTF8.GetBytes(builder.ToString());
}
private static string Escape(string value)
{
if (value.Contains(',') || value.Contains('"') || value.Contains('\n'))
return $"\"{value.Replace("\"", "\"\"")}\"";
return value;
}
}
Контроллер отчёта¶
Контроллер возвращает не JSON, а файл. Метод File(...) задаёт содержимое, MIME-тип и имя файла. Браузер или HTTP-клиент сможет скачать результат как activity-events.csv.
[ApiController]
[Route("api/reports")]
public class ReportsController : ControllerBase
{
private readonly ReportsService _reportsService;
public ReportsController(ReportsService reportsService)
{
_reportsService = reportsService;
}
[Authorize(Roles = "Admin,Analyst")]
[HttpGet("events.csv")]
public async Task<IActionResult> EventsCsv(DateTime? from, DateTime? to, CancellationToken ct)
{
var bytes = await _reportsService.BuildEventsCsvAsync(from, to, ct);
return File(bytes, "text/csv; charset=utf-8", "activity-events.csv");
}
}
TXT-отчёт¶
TXT удобен для краткой сводки.
Такой формат подходит, когда нужен не подробный список строк, а короткое текстовое резюме для отчёта, письма или проверки результата практики.
public string GenerateSummaryText(AnalyticsSummaryDto summary)
{
return $"""
Отчёт по активности
Всего событий: {summary.TotalEvents}
Уникальных пользователей: {summary.UniqueUsers}
Ошибок: {summary.Errors}
Дата формирования: {DateTime.UtcNow:O}
""";
}
CSV применяется для таблиц, TXT — для текстовой сводки.
Как проверить отчёт¶
- Создайте несколько событий или запустите генератор.
- Войдите под ролью
AdminилиAnalyst. - Выполните
GET /api/reports/events.csv. - Откройте файл в табличном редакторе.
- Проверьте, что строки соответствуют событиям в базе.