115 lines
3.8 KiB
C#
115 lines
3.8 KiB
C#
using System.Collections.Concurrent;
|
|
using System.Text;
|
|
using MySqlConnector;
|
|
|
|
namespace UtopiaCanteenSystem.Services;
|
|
|
|
/// <summary>
|
|
/// Fetches employee photo from hrms.employee_photo by parent_document_id (employee document id from lookup).
|
|
/// Handles data URI (data:image/...;base64,...) or raw image bytes. Caches by key to avoid repeated DB calls.
|
|
/// </summary>
|
|
public class EmployeePhotoService : IEmployeePhotoService
|
|
{
|
|
private readonly IConfigService _configService;
|
|
private readonly ConcurrentDictionary<string, byte[]> _cache = new(StringComparer.OrdinalIgnoreCase);
|
|
private const int MaxCacheEntries = 50;
|
|
|
|
public EmployeePhotoService(IConfigService configService)
|
|
{
|
|
_configService = configService;
|
|
}
|
|
|
|
/// <inheritdoc />
|
|
public async Task<byte[]?> GetPhotoBytesAsync(string parentDocumentId, CancellationToken cancellationToken = default)
|
|
{
|
|
if (string.IsNullOrWhiteSpace(parentDocumentId))
|
|
return null;
|
|
|
|
var key = parentDocumentId.Trim();
|
|
if (_cache.TryGetValue(key, out var cached))
|
|
return cached;
|
|
|
|
var connectionString = _configService.GetHrmsLookupConnectionString();
|
|
if (string.IsNullOrWhiteSpace(connectionString))
|
|
return null;
|
|
|
|
try
|
|
{
|
|
await using var conn = new MySqlConnection(connectionString);
|
|
await conn.OpenAsync(cancellationToken).ConfigureAwait(false);
|
|
|
|
// employee_photo.employee_id stores the employee document id (parent_document_id from employee lookup)
|
|
const string sql = "SELECT photo_blob FROM hrms.employee_photo WHERE employee_id = @parentDocumentId LIMIT 1";
|
|
await using var cmd = new MySqlCommand(sql, conn);
|
|
cmd.Parameters.AddWithValue("@parentDocumentId", key);
|
|
|
|
await using var reader = await cmd.ExecuteReaderAsync(cancellationToken).ConfigureAwait(false);
|
|
if (!await reader.ReadAsync(cancellationToken).ConfigureAwait(false))
|
|
return null;
|
|
|
|
if (reader.IsDBNull(0))
|
|
return null;
|
|
|
|
byte[] blob;
|
|
try
|
|
{
|
|
var len = reader.GetBytes(0, 0, null, 0, 0);
|
|
blob = new byte[len];
|
|
reader.GetBytes(0, 0, blob, 0, (int)len);
|
|
}
|
|
catch
|
|
{
|
|
return null;
|
|
}
|
|
|
|
var imageBytes = DecodeBlobToImageBytes(blob);
|
|
if (imageBytes == null || imageBytes.Length == 0)
|
|
return null;
|
|
|
|
// Cache (evict old if needed)
|
|
while (_cache.Count >= MaxCacheEntries && _cache.Count > 0)
|
|
{
|
|
var first = _cache.Keys.FirstOrDefault();
|
|
if (first != null)
|
|
_cache.TryRemove(first, out _);
|
|
else
|
|
break;
|
|
}
|
|
_cache[key] = imageBytes;
|
|
return imageBytes;
|
|
}
|
|
catch
|
|
{
|
|
return null;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Converts blob to image bytes: if blob is UTF-8 "data:image...;base64,<payload>", decodes base64; else treats as raw image.
|
|
/// </summary>
|
|
internal static byte[]? DecodeBlobToImageBytes(byte[] blob)
|
|
{
|
|
if (blob == null || blob.Length == 0)
|
|
return null;
|
|
|
|
try
|
|
{
|
|
var str = Encoding.UTF8.GetString(blob);
|
|
if (str.StartsWith("data:image", StringComparison.OrdinalIgnoreCase))
|
|
{
|
|
var commaIndex = str.IndexOf(',');
|
|
if (commaIndex < 0)
|
|
return null;
|
|
var base64 = str.Substring(commaIndex + 1).Trim();
|
|
return Convert.FromBase64String(base64);
|
|
}
|
|
|
|
return blob;
|
|
}
|
|
catch
|
|
{
|
|
return null;
|
|
}
|
|
}
|
|
}
|