Initial commit
This commit is contained in:
@@ -0,0 +1,73 @@
|
||||
@page
|
||||
@model ListmonkIntegration.Pages.Subscribers.UploadModel
|
||||
@{
|
||||
ViewData["Title"] = "Загрузка подписчиков";
|
||||
}
|
||||
|
||||
<h1>Загрузка списка подписчиков</h1>
|
||||
<hr />
|
||||
|
||||
@if (!string.IsNullOrEmpty(Model.Message))
|
||||
{
|
||||
<div class="alert @(Model.IsSuccess ? "alert-success" : "alert-danger")">
|
||||
@Model.Message
|
||||
</div>
|
||||
}
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<form method="post" enctype="multipart/form-data">
|
||||
<div class="mb-3">
|
||||
<label class="form-label">Выберите Excel файл (.xlsx)</label>
|
||||
<input type="file" name="excelFile" class="form-control" accept=".xlsx" required />
|
||||
<div class="form-text">
|
||||
Файл должен содержать столбцы: Email, FullName и другие атрибуты
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label class="form-label">Название списка в Listmonk</label>
|
||||
<input type="text" name="listName" class="form-control" value="Рассылка @DateTime.Now.ToString("dd.MM.yyyy")" required />
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<button type="submit" class="btn btn-primary">Загрузить и обработать</button>
|
||||
<a asp-page="/Index" class="btn btn-secondary">Отмена</a>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@if (Model.Subscribers != null && Model.Subscribers.Any())
|
||||
{
|
||||
<h3 class="mt-4">Загружено подписчиков: @Model.Subscribers.Count</h3>
|
||||
|
||||
@if (Model.ColumnHeaders != null && Model.ColumnHeaders.Any())
|
||||
{
|
||||
<div class="table-responsive">
|
||||
<table class="table table-striped table-sm">
|
||||
<thead class="table-dark">
|
||||
<tr>
|
||||
@foreach (var header in Model.ColumnHeaders)
|
||||
{
|
||||
<th>@header</th>
|
||||
}
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@foreach (var sub in Model.Subscribers)
|
||||
{
|
||||
<tr>
|
||||
<td>@sub.Email</td>
|
||||
<td>@sub.FullName</td>
|
||||
@foreach (var attr in sub.Attributes)
|
||||
{
|
||||
<td>@attr.Value</td>
|
||||
}
|
||||
</tr>
|
||||
}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,215 @@
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.AspNetCore.Mvc.RazorPages;
|
||||
using ListmonkIntegration.Services;
|
||||
using ListmonkIntegration.Models;
|
||||
using ClosedXML.Excel;
|
||||
|
||||
namespace ListmonkIntegration.Pages.Subscribers
|
||||
{
|
||||
public class UploadModel : PageModel
|
||||
{
|
||||
private readonly IListmonkService _listmonkService;
|
||||
private readonly ILogger<UploadModel> _logger;
|
||||
|
||||
[BindProperty]
|
||||
public IFormFile? excelFile { get; set; }
|
||||
|
||||
public List<Subscriber> Subscribers { get; set; } = new List<Subscriber>();
|
||||
public List<string> ColumnHeaders { get; set; } = new List<string>();
|
||||
public string Message { get; set; } = string.Empty;
|
||||
public bool IsSuccess { get; set; }
|
||||
|
||||
public UploadModel(IListmonkService listmonkService, ILogger<UploadModel> logger)
|
||||
{
|
||||
_listmonkService = listmonkService;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public void OnGet()
|
||||
{
|
||||
}
|
||||
|
||||
public async Task<IActionResult> OnPostAsync(string listName)
|
||||
{
|
||||
try
|
||||
{
|
||||
_logger.LogInformation("=== Upload started ===");
|
||||
|
||||
if (excelFile == null || excelFile.Length == 0)
|
||||
{
|
||||
Message = "Ôàéë íå âûáðàí";
|
||||
IsSuccess = false;
|
||||
return Page();
|
||||
}
|
||||
|
||||
_logger.LogInformation($"File received: {excelFile.FileName}, Size: {excelFile.Length} bytes");
|
||||
|
||||
// Îãðàíè÷åíèå ðàçìåðà ôàéëà (10 MB)
|
||||
if (excelFile.Length > 10 * 1024 * 1024)
|
||||
{
|
||||
Message = "Ôàéë ñëèøêîì áîëüøîé (ìàêñèìóì 10 MB)";
|
||||
IsSuccess = false;
|
||||
return Page();
|
||||
}
|
||||
|
||||
// ×òåíèå Excel ôàéëà
|
||||
_logger.LogInformation("Reading Excel file...");
|
||||
Subscribers = ReadExcelFile(excelFile);
|
||||
_logger.LogInformation($"Read {Subscribers.Count} subscribers");
|
||||
|
||||
if (!Subscribers.Any())
|
||||
{
|
||||
Message = "Íå íàéäåíî ïîäïèñ÷èêîâ â ôàéëå";
|
||||
IsSuccess = false;
|
||||
return Page();
|
||||
}
|
||||
|
||||
// Ïðîâåðÿåì ïîäêëþ÷åíèå
|
||||
_logger.LogInformation("Testing Listmonk connection...");
|
||||
if (!await _listmonkService.TestConnectionAsync())
|
||||
{
|
||||
Message = "Íåò ïîäêëþ÷åíèÿ ê Listmonk";
|
||||
IsSuccess = false;
|
||||
return Page();
|
||||
}
|
||||
|
||||
// Ñîçäàåì ñïèñîê
|
||||
_logger.LogInformation($"Creating list: {listName}");
|
||||
int listId = await _listmonkService.CreateListAsync(listName);
|
||||
|
||||
// Èìïîðòèðóåì
|
||||
_logger.LogInformation($"Importing to list {listId}...");
|
||||
await _listmonkService.ImportSubscribersAsync(new List<int> { listId }, Subscribers);
|
||||
|
||||
Message = $"Óñïåøíî çàãðóæåíî {Subscribers.Count} ïîäïèñ÷èêîâ â ñïèñîê \"{listName}\"";
|
||||
IsSuccess = true;
|
||||
_logger.LogInformation("=== Upload completed successfully ===");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Message = $"Îøèáêà: {ex.Message}";
|
||||
IsSuccess = false;
|
||||
_logger.LogError(ex, "=== Upload failed ===");
|
||||
}
|
||||
|
||||
return Page();
|
||||
}
|
||||
|
||||
private List<Subscriber> ReadExcelFile(IFormFile file)
|
||||
{
|
||||
var subscribers = new List<Subscriber>();
|
||||
|
||||
try
|
||||
{
|
||||
// Ñîçäà¸ì âðåìåííûé ôàéë ñ ïðàâèëüíûì ðàñøèðåíèåì .xlsx
|
||||
var tempFilePath = Path.Combine(Path.GetTempPath(), $"{Guid.NewGuid()}.xlsx");
|
||||
_logger.LogInformation($"Temp file: {tempFilePath}");
|
||||
|
||||
try
|
||||
{
|
||||
// Ñîõðàíÿåì çàãðóæåííûé ôàéë âî âðåìåííûé ôàéë
|
||||
using (var stream = new FileStream(tempFilePath, FileMode.Create))
|
||||
{
|
||||
file.CopyTo(stream);
|
||||
}
|
||||
|
||||
_logger.LogInformation("Opening workbook...");
|
||||
using (var workbook = new XLWorkbook(tempFilePath))
|
||||
{
|
||||
var worksheet = workbook.Worksheet(1);
|
||||
var firstRow = worksheet.FirstRowUsed();
|
||||
|
||||
if (firstRow == null)
|
||||
{
|
||||
_logger.LogWarning("Empty file");
|
||||
return subscribers;
|
||||
}
|
||||
|
||||
// Çàãîëîâêè
|
||||
var headers = new List<string>();
|
||||
foreach (var cell in firstRow.Cells())
|
||||
{
|
||||
var header = cell.Value.ToString().Trim();
|
||||
headers.Add(header);
|
||||
_logger.LogInformation($"Header: {header}");
|
||||
}
|
||||
|
||||
ColumnHeaders = headers;
|
||||
_logger.LogInformation($"Total headers: {headers.Count}");
|
||||
|
||||
if (headers.Count < 2)
|
||||
{
|
||||
_logger.LogError("Need at least 2 columns");
|
||||
return subscribers;
|
||||
}
|
||||
|
||||
// Äàííûå
|
||||
var lastRow = worksheet.LastRowUsed();
|
||||
_logger.LogInformation($"Last row: {lastRow?.RowNumber() ?? 0}");
|
||||
|
||||
for (int row = 2; row <= lastRow.RowNumber(); row++)
|
||||
{
|
||||
try
|
||||
{
|
||||
var email = worksheet.Cell(row, 1).GetValue<string>().Trim();
|
||||
if (string.IsNullOrWhiteSpace(email))
|
||||
{
|
||||
_logger.LogWarning($"Row {row}: empty email, skipping");
|
||||
continue;
|
||||
}
|
||||
|
||||
var name = worksheet.Cell(row, 2).GetValue<string>().Trim();
|
||||
|
||||
var attributes = new Dictionary<string, string>();
|
||||
for (int col = 3; col <= headers.Count; col++)
|
||||
{
|
||||
var value = worksheet.Cell(row, col).GetValue<string>().Trim();
|
||||
if (!string.IsNullOrWhiteSpace(value))
|
||||
{
|
||||
attributes[headers[col - 1]] = value;
|
||||
}
|
||||
}
|
||||
|
||||
subscribers.Add(new Subscriber
|
||||
{
|
||||
Email = email,
|
||||
FullName = name,
|
||||
Attributes = attributes
|
||||
});
|
||||
|
||||
_logger.LogInformation($"Row {row}: Added {email}");
|
||||
}
|
||||
catch (Exception rowEx)
|
||||
{
|
||||
_logger.LogWarning(rowEx, $"Skipping row {row}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
// Óäàëÿåì âðåìåííûé ôàéë
|
||||
if (System.IO.File.Exists(tempFilePath))
|
||||
{
|
||||
try
|
||||
{
|
||||
System.IO.File.Delete(tempFilePath);
|
||||
_logger.LogInformation($"Deleted temp file: {tempFilePath}");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogWarning(ex, $"Failed to delete temp file: {tempFilePath}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Error reading Excel");
|
||||
throw;
|
||||
}
|
||||
|
||||
return subscribers;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user