A szállítólevélhez csatolt fájl tartalma lekérhető a dróton

- Új SignalR endpoint (tag 69) a Files rekord tartalmához, a kliens beépített
  megjelenítőjének: a blob a wwwroot-on kívül él, az admin preview-endpoint pedig
  nop admin sütit vár, ami a MAUI kliensnek nincs.
- FileStorageService.GetFileContentAsync: az útvonalat a tárolt FileSubPath-ból
  oldja fel, nem a hívó userId-jából. Az útvonal első szegmense a feltöltőé, ezért
  más admin alatt eddig nem lehetett megtalálni a fájlt; a kitömörítést a ".gz"
  utótag dönti el, nem az IsCompressed jelző.
This commit is contained in:
2026-08-24 12:39:36 +02:00
parent 95ba9cbef9
commit df5bf989c2
2 changed files with 66 additions and 0 deletions
@@ -30,6 +30,7 @@ using Nop.Core.Domain.Orders;
using Nop.Plugin.Misc.FruitBankPlugin.Domains.DataLayer;
using Nop.Plugin.Misc.FruitBankPlugin.Factories;
using Nop.Plugin.Misc.FruitBankPlugin.Services;
using Nop.Plugin.Misc.FruitBankPlugin.Services.FileStorage;
using Nop.Services.Customers;
using Nop.Services.Localization;
using Nop.Web.Framework.Controllers;
@@ -54,6 +55,7 @@ namespace Nop.Plugin.Misc.FruitBankPlugin.Controllers
IEkaerSettings ekaerSettings,
INavCredentials navCredentials,
ShippingCostService shippingCostService,
FileStorageService fileStorageService,
IEnumerable<IAcLogWriterBase> logWriters)
: BasePluginController, IFruitBankDataControllerServer
{
@@ -1459,6 +1461,25 @@ namespace Nop.Plugin.Misc.FruitBankPlugin.Controllers
return await ctx.ShippingDocuments.GetByIdAsync(shippingDocument.Id, shippingDocument.Shipping != null || shippingDocument.Partner != null);
}
/// <summary>A szállítólevélhez csatolt fájl nyers tartalma, a kliens beépített megjelenítőjének.</summary>
/// <remarks>
/// A fájl a dróton jön, nem URL-en: a blob a wwwroot-on KÍVÜL él, a kiszolgáló admin-endpointja
/// (<c>FileStorageController.PreviewFile</c>) pedig nop admin sütit vár, ami a MAUI kliensnek nincs.
/// Ugyanez a hívás így weben és mobilon azonosan működik.
/// <para>Egy üzenetben, darabolás nélkül — a mai szállítólevél-szkennek ezt bőven bírják. Ha egyszer
/// nagyobb fájl kerül be, a darabolás külön munka.</para>
/// </remarks>
[SignalR(SignalRTags.GetShippingDocumentFileContent)]
public async Task<byte[]> GetShippingDocumentFileContent(int filesId)
{
_logger.Detail($"GetShippingDocumentFileContent invoked; filesId: {filesId}");
var file = await ctx.Files.GetByIdAsync(filesId);
if (file == null) return null;
return await fileStorageService.GetFileContentAsync(file);
}
#region Adattisztító törlés (őrzött, fizikai)
// A visszatérés szándékosan string?: üres/null = mehet (illetve sikerült), egyébként az admin
@@ -358,6 +358,51 @@ namespace Nop.Plugin.Misc.FruitBankPlugin.Services.FileStorage
return foundPath;
}
/// <summary>
/// Returns a file's content, located by the stored path instead of the original upload coordinates.
/// <para>
/// Deliberately not <see cref="GetFileByIdAsync"/>: that one rebuilds the path from the CALLING user's
/// id, while the first path segment is the UPLOADING user's — so it cannot find a file another user
/// uploaded. The same gap covers the hash-deduplicated file, whose blob stayed in the ORIGINAL entity's
/// folder while the mapping points at a newer document.
/// </para>
/// </summary>
/// <returns>The file bytes, or null when the blob cannot be located.</returns>
public async Task<byte[]> GetFileContentAsync(Files fileEntity)
{
if (fileEntity == null) return null;
var relativePath = await ResolveStoragePathAsync(fileEntity);
if (string.IsNullOrWhiteSpace(relativePath))
{
Console.Error.WriteLine($"⚠️ File blob not found for Files.Id {fileEntity.Id} ('{fileEntity.FileName}{fileEntity.FileExtension}').");
return null;
}
// A ".gz" utótag dönt, nem a Files.IsCompressed jelző: az útvonal azt írja le, ami a lemezen VAN,
// a jelző pedig azt, aminek a mentéskor lennie kellett volna. PDF-nél és képnél nincs tömörítés.
var isGzipped = relativePath.EndsWith(".gz", StringComparison.OrdinalIgnoreCase);
using (var storedStream = await _storageProvider.GetFileAsync(relativePath))
using (var buffer = new MemoryStream())
{
if (isGzipped)
{
using (var gzipStream = new GZipStream(storedStream, CompressionMode.Decompress, leaveOpen: true))
{
await gzipStream.CopyToAsync(buffer);
}
}
else
{
await storedStream.CopyToAsync(buffer);
}
return buffer.ToArray();
}
}
/// <summary>
/// Deletes a file record and its blob without needing the original upload coordinates.
/// <para>