ReportGenerationHandler — Report Generator¶
Обзор¶
ReportGenerationHandler — микросервис для асинхронной генерации отчётов через RabbitMQ/MassTransit. Принимает запросы на генерацию и возвращает готовые файлы.
Архитектура¶
graph LR
WEB[FuelProcessing.Web] -->|Publish ReportData| MQ[RabbitMQ]
MQ -->|Consume| CONSUMER[ReportConsumer]
CONSUMER --> SERVICE[ReportService]
SERVICE --> HISTORY[HistoryTransaction]
SERVICE --> SELLING[SellingFuel]
SERVICE --> BALANCE[BalanceReports]
HISTORY --> NPOI[NPOI Excel]
SELLING --> NPOI
BALANCE --> NPOI
NPOI -->|Publish| MQ
MQ -->|Consume| WEB_ANS[ReportAnswerConsumer]
WEB_ANS --> WEB
Компоненты¶
| Компонент | Описание |
|---|---|
Program.cs |
Точка входа |
Startup.cs |
Конфигурация DI, MassTransit, БД |
ReportConsumer.cs |
MassTransit потребитель |
ReportService.cs |
Оркестратор генерации |
*ReportService.cs |
Специализированные сервисы |
Сообщения¶
ReportData — запрос на отчёт¶
public class ReportData
{
public int UserId { get; set; }
public Report Report { get; set; }
public ReportViewModel ReportViewModel { get; set; }
}
public class ReportViewModel
{
public int CompanyId { get; set; }
public int?[] DirectContractId { get; set; }
public DateTime DateStart { get; set; }
public DateTime DateEnd { get; set; }
public ReportType ReportType { get; set; }
}
ReportAnswerData — готовый отчёт¶
public class ReportAnswerData
{
public byte[] File { get; set; }
public string Title { get; set; }
public ReportItem ReportItem { get; set; }
public Report Report { get; set; }
public ReportViewModel ReportViewModel { get; set; }
}
Типы отчётов¶
| Код | Название | Описание |
|---|---|---|
HistoryTransactionReport |
Транзакции автопарка | Детализация по картам |
TransactonSellingFuelReport |
Продажа топлива | Для перекупщиков |
BalanceOverdraftAndNegativeBalances |
Овердрафты | Проблемные балансы |
BalnceAddReport |
Пополнение баланса | История пополнений |
IssuedCardsReport |
Выданные карты | Реестр карт |
TempOverdraftReport |
Временные овердрафты | По договорам |
DirectContractTermsReport |
Тарифы | Условия договоров |
ReportUsingFuelByDay |
Потребление | Агрегированные данные |
GasStationReports |
Заправки | Детализация по АЗС |
ContractTermTemplatesReport |
Тарифы по регионам | Шаблоны условий |
GasStationsFromServerVink |
Заправки ВИНКа | Данные из Vink |
TariffAtTheClient |
Тарифы клиентов | Для менеджеров |
ReportOnTheOrganization |
По организации | Сводная информация |
ReportTemporaryOverdraft |
Временные овердрафты | Детально |
Структура отчёта¶
Большинство отчётов содержат несколько листов:
| Лист | Содержимое |
|---|---|
Данные |
Детальные записи |
Водитель_день |
Агрегация по водителям/датам |
Водитель_карта_день |
По водителям/картам/датам |
Водитель_период |
Итоги за период |
Сгруппированные транзакции |
Сводные данные |
Генерация Excel (NPOI)¶
// Создание книги
var workbook = new XSSFWorkbook();
var sheet = workbook.CreateSheet("Данные");
// Заголовок
var headerRow = sheet.CreateRow(0);
headerRow.CreateCell(0).SetCellValue("Дата");
headerRow.CreateCell(1).SetCellValue("Объём");
// Форматирование чисел
var currencyStyle = workbook.CreateCellStyle();
currencyStyle.DataFormat = workbook.CreateDataFormat()
.GetFormat("#,##0.00");
cell.CellStyle = currencyStyle;
// Сохранение
using var stream = new MemoryStream();
workbook.Write(stream);
return stream.ToArray();
Конфигурация MassTransit¶
services.AddMassTransit(mt =>
{
mt.AddConsumers(typeof(ReportConsumer).Assembly);
mt.AddConsumer<ReportConsumer>(cc =>
{
cc.UseConcurrencyLimit(5); // До 5 отчётов одновременно
});
mt.UsingRabbitMq((context, mq) =>
{
mq.Host(rabbitMqOptions.Host, port, "/", h =>
{
h.Username(rabbitMqOptions.Username);
h.Password(rabbitMqOptions.Password);
h.ContinuationTimeout(TimeSpan.FromMinutes(180)); // 3 часа
});
mq.ConfigureJsonSerializerOptions(settings =>
{
settings.ReferenceHandler = ReferenceHandler.IgnoreCycles;
});
mq.ConfigureEndpoints(context);
});
});
RabbitMQ настройки¶
| Параметр | Значение | Описание |
|---|---|---|
ContinuationTimeout |
180 минут | Максимальное время ожидания ответа от RabbitMQ |
ReferenceHandler |
IgnoreCycles |
Игнорировать циклические ссылки при сериализации JSON |
DbContext изоляция¶
Каждое сообщение обрабатывается в отдельном scope для предотвращения конфликтов DbContext:
public async Task Consume(ConsumeContext<ReportData> context)
{
// Создаём scope для каждого сообщения
using var scope = _serviceProvider.CreateScope();
var scopedProvider = scope.ServiceProvider;
// Создаём новый DataContext через фабрику
var contextFactory = scopedProvider.GetRequiredService<IDbContextFactory<DataContext>>();
await using var dataContext = contextFactory.CreateDbContext();
// ... обработка отчёта
}
Зачем это нужно: - Изоляция изменений между concurrent запросами - Предотвращение EntityState conflicts - Правильное управление lifetime scoped сервисов
Обработка ошибок в ReportConsumer¶
При ошибке генерации отчёта выполняются следующие действия:
catch (Exception e)
{
// 1. Находим сущность Report через reflection
var reportType = obj.Report.GetType();
var report = await dataContext.FindAsync(reportType, obj.Report.Id);
// 2. Устанавливаем статус ошибки (ErrorReport = 3)
if (report != null)
{
var reportStatusProperty = reportType.GetProperty("ReportStatus");
if (reportStatusProperty != null)
{
reportStatusProperty.SetValue(report, 3);
await dataContext.SaveChangesAsync();
}
}
// 3. Логируем ошибку
_logUserActionService.SimpleLogData("ReportService.Consume.GetRerport.Error", ...);
// 4. Re-throw для MassTransit retry механизма
throw;
}
Стратегия retry¶
| Шаг | Действие |
|---|---|
| 1 | Установить ReportStatus = 3 (ошибка) в БД |
| 2 | Записать error log |
| 3 | Пробросить исключение (throw) |
| 4 | MassTransit автоматически retry (настраивается в политике retry) |
Важно: Exception re-throw необходим для активации MassTransit retry механизма. Без этого сообщение будет помечено как успешно обработано.
Логирование через LogUserActionService¶
Все операции генерации отчётов логируются через LogUserActionService:
// Устанавливаем флаг, что это отчёт
_logUserActionService.isReport = true;
// Лог начала обработки
_logUserActionService.SimpleLogData("ReportService.Consume.GetRerport",
$"Начало формирования отчета. ReportType: {type}; " +
$"Start: {dateStart}; End: {dateEnd}; CompanyId: {companyId}");
// Лог завершения
_logUserActionService.SimpleLogData("ReportService.Consume.GetRerport",
logForReport.Replace("Начало", "Завершение"));
// Лог ошибки
_logUserActionService.SimpleLogData("ReportService.Consume.GetRerport.Error",
logForReport.Replace("Начало", "Ошибка") + $". {e.Message}");
| Параметр | Значение | Описание |
|---|---|---|
isReport |
true |
Флаг для идентификации логов отчётов |
SimpleLogData |
метод | Логирование с текстовым сообщением |
Управление статусами¶
| Статус | Код | Описание |
|---|---|---|
| Новая | 0 | Отчёт создан, ожидает обработки |
| В обработке | 1 | Отчёт генерируется |
| Готов | 2 | Файл успешно создан |
| Ошибка | 3 | Ошибка при генерации |
При ошибке статус = 3, исключение пробрасывается для retry MassTransit.
Хранение файлов¶
Сгенерированные файлы сохраняются:
Файлы старше 2 дней удаляются автоматически (CleanOldFiles()).
Endpoints¶
| Endpoint | Метод | Описание |
|---|---|---|
/health |
GET | Health check |
/ |
GET | Главная страница |
Зависимости¶
| Пакет | Назначение |
|---|---|
MassTransit |
Message bus |
MassTransit.RabbitMQ |
Транспорт RabbitMQ |
NPOI |
Генерация Excel |
AutoMapper |
Маппинг сущностей |
EF Core |
Доступ к БД |
Serilog |
Логирование |
Конкурентность¶
Ограничение: до 5 отчётов одновременно.
Troubleshooting¶
Отчёт не генерируется¶
Проверьте: 1. Работает ли RabbitMQ 2. Запущен ли ReportGenerationHandler 3. Нет ли ошибок в логах
Файл не скачивается¶
Проверьте:
1. Права на директорию wwwroot/TempFiles/Reports/
2. Существует ли файл
3. Не истёк ли срок действия (2 дня)
Связанная документация¶
- Web Interface — MVC интерфейс
- Data Access Layer — репозитории