Инициализировать репозиторий «Дейл»
Первый коммит: модульный монолит ядра (.NET 10) и gRPC-сервисы ai/ml/telegram, фронтенд Vue 3/Vite/Tailwind, документация (ТЗ, инструкция пользователя, техдокументация, код-стайл), бэклог, скрипты развёртывания и архив прототипа LeadRadar.
This commit is contained in:
@@ -0,0 +1,96 @@
|
||||
using Deal.Modules.Tenants.Application;
|
||||
|
||||
namespace Deal.Api.Hosting;
|
||||
|
||||
/// <summary>
|
||||
/// Hosted-шаг bootstrap оператора при старте (Ruling 1 этапа 7): env DEAL_OPERATOR_* → OperatorBootstrapService.EnsureOperatorAsync.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Регистрируется после <see cref="TenantBootstrapService"/> и работает тем же паттерном: OperatorBootstrapService —
|
||||
/// scoped (его IOperatorAuthStore живёт на scoped DealDbContext), поэтому резолвится в собственном scope из
|
||||
/// IServiceScopeFactory (как TenantService в TenantBootstrapService). Идемпотентен: существующего оператора
|
||||
/// не пересоздаёт и пароль не перезаписывает. В Development без env-кред используется dev-дефолт
|
||||
/// operator/operator (зеркало dev-seed admin/admin, логируется как dev-режим); в Production без env-кред
|
||||
/// шаг пропускается с warning — оператора заводит админ позже через env и рестарт хоста (кода регистрации
|
||||
/// оператора нет). Частичная конфигурация (задана ровно одна из DEAL_OPERATOR_LOGIN/DEAL_OPERATOR_PASSWORD)
|
||||
/// логируется warning — не молчаливый дефолт (решение ревью Task 2): в Development используются dev-дефолты,
|
||||
/// в Production шаг пропускается. Секреты (пароли) в логи не пишутся (правило этапов 1–6).
|
||||
/// </remarks>
|
||||
public sealed class OperatorBootstrapHostedService(
|
||||
IServiceScopeFactory scopeFactory,
|
||||
IConfiguration configuration,
|
||||
IHostEnvironment environment,
|
||||
ILogger<OperatorBootstrapHostedService> logger) : IHostedService
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public async Task StartAsync(CancellationToken ct)
|
||||
{
|
||||
await using var scope = scopeFactory.CreateAsyncScope();
|
||||
var operatorBootstrapService = scope.ServiceProvider.GetRequiredService<OperatorBootstrapService>();
|
||||
|
||||
var login = configuration[OperatorBootstrapService.LoginEnvKey];
|
||||
var password = configuration[OperatorBootstrapService.PasswordEnvKey];
|
||||
bool hasLogin = !string.IsNullOrWhiteSpace(login);
|
||||
bool hasPassword = !string.IsNullOrWhiteSpace(password);
|
||||
bool allowDevelopmentDefaults = environment.IsDevelopment();
|
||||
|
||||
// Предупреждения о пропуске/частичной конфигурации логируются ДО вызова шага — сам шаг
|
||||
// в этих случаях ничего не создаёт (EnsureOperatorAsync возвращает null, хранилище не тронуто).
|
||||
if (!hasLogin || !hasPassword)
|
||||
{
|
||||
if (hasLogin != hasPassword)
|
||||
{
|
||||
LogPartialConfigurationWarning(hasLogin, allowDevelopmentDefaults);
|
||||
}
|
||||
else if (!allowDevelopmentDefaults)
|
||||
{
|
||||
// Production без кред: пропуск с warning (Ruling 1) — кода регистрации оператора нет.
|
||||
logger.LogWarning(
|
||||
"DEAL_OPERATOR_LOGIN/DEAL_OPERATOR_PASSWORD не заданы (Production) — bootstrap оператора "
|
||||
+ "пропущен. Оператор заводится позже: задайте env DEAL_OPERATOR_* и перезапустите хост.");
|
||||
}
|
||||
else
|
||||
{
|
||||
// Development без кред: штатный dev-дефолт (зеркало dev-seed admin/admin).
|
||||
logger.LogInformation(
|
||||
"DEAL_OPERATOR_LOGIN/DEAL_OPERATOR_PASSWORD не заданы (Development) — используется "
|
||||
+ "dev-дефолт оператора {DefaultLogin}.",
|
||||
OperatorBootstrapService.DefaultOperatorLogin);
|
||||
}
|
||||
}
|
||||
|
||||
var ensuredLogin = await operatorBootstrapService.EnsureOperatorAsync(
|
||||
login, password, allowDevelopmentDefaults, ct);
|
||||
if (ensuredLogin is not null)
|
||||
{
|
||||
logger.LogInformation("Bootstrap оператора: оператор {Login} присутствует.", ensuredLogin);
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task StopAsync(CancellationToken ct) => Task.CompletedTask;
|
||||
|
||||
// Логирует warning о неполной env-конфигурации (задана одна из двух переменных).
|
||||
// hasLogin: Задан ли логин (пароль при этом пуст).
|
||||
// allowDevelopmentDefaults: Разрешены ли dev-дефолты (Development).
|
||||
private void LogPartialConfigurationWarning(bool hasLogin, bool allowDevelopmentDefaults)
|
||||
{
|
||||
string missingEnv = hasLogin
|
||||
? OperatorBootstrapService.PasswordEnvKey
|
||||
: OperatorBootstrapService.LoginEnvKey;
|
||||
if (allowDevelopmentDefaults)
|
||||
{
|
||||
logger.LogWarning(
|
||||
"Env-конфигурация оператора неполна: не задан {MissingEnv}. В Development используются "
|
||||
+ "dev-дефолты оператора {DefaultLogin}.",
|
||||
missingEnv, OperatorBootstrapService.DefaultOperatorLogin);
|
||||
}
|
||||
else
|
||||
{
|
||||
logger.LogWarning(
|
||||
"Env-конфигурация оператора неполна: не задан {MissingEnv}. Bootstrap оператора в Production "
|
||||
+ "пропущен — оператор заводится позже через env DEAL_OPERATOR_* и рестарт хоста.",
|
||||
missingEnv);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user