aktueller stand

This commit is contained in:
Meik
2026-02-04 09:35:40 +01:00
parent d289fc4c21
commit f18d71e2b3
2 changed files with 405 additions and 32 deletions

View File

@@ -38,6 +38,7 @@ namespace C4IT.DataHistoryProvider
private const string constUrlGetPickupValues = "m42Services/api/c4itf4sdwebapi/getpickup/{0}?group={1}"; private const string constUrlGetPickupValues = "m42Services/api/c4itf4sdwebapi/getpickup/{0}?group={1}";
private const string constUrlGetRoleMeberships = "m42services/api/c4itf4sdwebapi/getrolememberships/?sid={0}"; private const string constUrlGetRoleMeberships = "m42services/api/c4itf4sdwebapi/getrolememberships/?sid={0}";
private const string constUrlGetTicketOverviewCounts = "m42Services/api/c4itf4sdwebapi/getticketoverviewcounts?sid={0}&scope={1}&keys={2}"; private const string constUrlGetTicketOverviewCounts = "m42Services/api/c4itf4sdwebapi/getticketoverviewcounts?sid={0}&scope={1}&keys={2}";
private const string constUrlGetTicketOverviewCountsByRoles = "m42Services/api/c4itf4sdwebapi/getticketoverviewcountsbyroles";
private const string constUrlGetTicketOverviewRelations = "m42Services/api/c4itf4sdwebapi/getticketoverviewrelations?sid={0}&scope={1}&key={2}&count={3}"; private const string constUrlGetTicketOverviewRelations = "m42Services/api/c4itf4sdwebapi/getticketoverviewrelations?sid={0}&scope={1}&key={2}&count={3}";
private const string constUrlGetDataQueryRelationItems = "m42Services/api/dataquery/relationitems"; private const string constUrlGetDataQueryRelationItems = "m42Services/api/dataquery/relationitems";
private const string constUrlGetDataQueryRelationItemsCount = "m42Services/api/dataquery/relationitems/count"; private const string constUrlGetDataQueryRelationItemsCount = "m42Services/api/dataquery/relationitems/count";
@@ -70,6 +71,11 @@ namespace C4IT.DataHistoryProvider
private Dictionary<WebClientCacheIndex, WebClientCacheEntry> UserWebClientCache = new Dictionary<WebClientCacheIndex, WebClientCacheEntry>(); private Dictionary<WebClientCacheIndex, WebClientCacheEntry> UserWebClientCache = new Dictionary<WebClientCacheIndex, WebClientCacheEntry>();
private readonly object _ticketOverviewCacheLock = new object();
private readonly Dictionary<string, TicketOverviewCountCacheEntry> _ticketOverviewPersonalCache = new Dictionary<string, TicketOverviewCountCacheEntry>(StringComparer.OrdinalIgnoreCase);
private readonly Dictionary<Guid, TicketOverviewCountCacheEntry> _ticketOverviewRoleCache = new Dictionary<Guid, TicketOverviewCountCacheEntry>();
private readonly Dictionary<string, TicketOverviewRoleListCacheEntry> _ticketOverviewRoleListCache = new Dictionary<string, TicketOverviewRoleListCacheEntry>(StringComparer.OrdinalIgnoreCase);
private readonly cDataHistoryCollector _collector; private readonly cDataHistoryCollector _collector;
private static class _OnlineCheckCriticalSection private static class _OnlineCheckCriticalSection
@@ -95,6 +101,32 @@ namespace C4IT.DataHistoryProvider
public DateTime ValidUntil; public DateTime ValidUntil;
} }
private sealed class TicketOverviewCountCacheEntry
{
public Dictionary<string, int> Counts { get; set; } = new Dictionary<string, int>(StringComparer.OrdinalIgnoreCase);
public DateTime ExpiresAtUtc { get; set; }
}
private sealed class TicketOverviewRoleListCacheEntry
{
public List<Guid> RoleIds { get; set; } = new List<Guid>();
public DateTime ExpiresAtUtc { get; set; }
}
private sealed class TicketOverviewCountsByRolesRequest
{
public string Sid { get; set; }
public List<Guid> RoleGuids { get; set; } = new List<Guid>();
public List<string> Keys { get; set; } = new List<string>();
}
private sealed class TicketOverviewCountsByRolesResponse
{
[JsonProperty("countsByRole")]
public Dictionary<string, Dictionary<string, int>> CountsByRole { get; set; }
= new Dictionary<string, Dictionary<string, int>>(StringComparer.OrdinalIgnoreCase);
}
public class cM42ApiTokenModel public class cM42ApiTokenModel
{ {
public string ApiToken { get; set; } public string ApiToken { get; set; }
@@ -601,34 +633,295 @@ namespace C4IT.DataHistoryProvider
return null; return null;
} }
public async Task<Dictionary<string, int>> GetTicketOverviewCountsAsync(IEnumerable<string> keys, bool useRoleScope, cF4sdWebRequestInfo requestInfo, int LogDeep, CancellationToken Token) private TimeSpan GetTicketOverviewCacheDuration(bool useRoleScope)
{
var ticketConfig = Collector?.GetGlobalConfig()?.TicketConfiguration;
var minutes = useRoleScope
? ticketConfig?.OverviewPollingRole ?? cF4sdTicketConfig.DefaultOverviewPollingRole
: ticketConfig?.OverviewPollingPersonal ?? cF4sdTicketConfig.DefaultOverviewPollingPersonal;
if (minutes < 1)
minutes = 1;
return TimeSpan.FromMinutes(minutes);
}
private bool TryGetTicketOverviewPersonalCache(string sid, DateTime now, out Dictionary<string, int> counts)
{
counts = null;
if (string.IsNullOrWhiteSpace(sid))
return false;
lock (_ticketOverviewCacheLock)
{
if (_ticketOverviewPersonalCache.TryGetValue(sid, out var entry))
{
if (entry.ExpiresAtUtc > now)
{
counts = new Dictionary<string, int>(entry.Counts, StringComparer.OrdinalIgnoreCase);
return true;
}
_ticketOverviewPersonalCache.Remove(sid);
}
}
return false;
}
private bool TryGetTicketOverviewRoleCache(Guid roleId, DateTime now, out Dictionary<string, int> counts)
{
counts = null;
if (roleId == Guid.Empty)
return false;
lock (_ticketOverviewCacheLock)
{
if (_ticketOverviewRoleCache.TryGetValue(roleId, out var entry))
{
if (entry.ExpiresAtUtc > now)
{
counts = new Dictionary<string, int>(entry.Counts, StringComparer.OrdinalIgnoreCase);
return true;
}
_ticketOverviewRoleCache.Remove(roleId);
}
}
return false;
}
private void SetTicketOverviewPersonalCache(string sid, Dictionary<string, int> counts, DateTime expiresAtUtc)
{
if (string.IsNullOrWhiteSpace(sid))
return;
lock (_ticketOverviewCacheLock)
{
_ticketOverviewPersonalCache[sid] = new TicketOverviewCountCacheEntry
{
Counts = new Dictionary<string, int>(counts ?? new Dictionary<string, int>(), StringComparer.OrdinalIgnoreCase),
ExpiresAtUtc = expiresAtUtc
};
}
}
private void SetTicketOverviewRoleCache(Guid roleId, Dictionary<string, int> counts, DateTime expiresAtUtc)
{
if (roleId == Guid.Empty)
return;
lock (_ticketOverviewCacheLock)
{
_ticketOverviewRoleCache[roleId] = new TicketOverviewCountCacheEntry
{
Counts = new Dictionary<string, int>(counts ?? new Dictionary<string, int>(), StringComparer.OrdinalIgnoreCase),
ExpiresAtUtc = expiresAtUtc
};
}
}
private bool TryGetTicketOverviewRoleIdsFromCache(string sid, DateTime now, out List<Guid> roleIds)
{
roleIds = null;
if (string.IsNullOrWhiteSpace(sid))
return false;
lock (_ticketOverviewCacheLock)
{
if (_ticketOverviewRoleListCache.TryGetValue(sid, out var entry))
{
if (entry.ExpiresAtUtc > now)
{
roleIds = new List<Guid>(entry.RoleIds);
return true;
}
_ticketOverviewRoleListCache.Remove(sid);
}
}
return false;
}
private void SetTicketOverviewRoleIdsCache(string sid, IEnumerable<Guid> roleIds, DateTime expiresAtUtc)
{
if (string.IsNullOrWhiteSpace(sid))
return;
var normalized = (roleIds ?? Enumerable.Empty<Guid>())
.Where(id => id != Guid.Empty)
.Distinct()
.ToList();
lock (_ticketOverviewCacheLock)
{
_ticketOverviewRoleListCache[sid] = new TicketOverviewRoleListCacheEntry
{
RoleIds = normalized,
ExpiresAtUtc = expiresAtUtc
};
}
}
private async Task<List<Guid>> GetTicketOverviewRoleIdsAsync(string sid, DateTime now, TimeSpan ttl, CancellationToken token)
{
if (TryGetTicketOverviewRoleIdsFromCache(sid, now, out var cached))
return cached;
var userInfo = await GetM42UserInfoAsync(sid, token);
var roles = userInfo?.Roles ?? new List<M42UserUserInfo.M42Role>();
var roleIds = roles
.Where(r => r != null && r.Id != Guid.Empty)
.Select(r => r.Id)
.Distinct()
.ToList();
SetTicketOverviewRoleIdsCache(sid, roleIds, now.Add(ttl));
return roleIds;
}
private async Task<Dictionary<Guid, Dictionary<string, int>>> FetchTicketOverviewCountsByRolesAsync(
IEnumerable<Guid> roleIds,
IReadOnlyCollection<string> requestedKeys,
cF4sdWebRequestInfo requestInfo,
int logDeep,
CancellationToken token)
{ {
MethodBase CM = null; if (cLogManager.DefaultLogger.IsDebug) { CM = MethodBase.GetCurrentMethod(); LogMethodBegin(CM); } MethodBase CM = null; if (cLogManager.DefaultLogger.IsDebug) { CM = MethodBase.GetCurrentMethod(); LogMethodBegin(CM); }
if (cPerformanceLogger.IsActive && requestInfo != null) { if (CM == null) CM = MethodBase.GetCurrentMethod(); cPerformanceLogger.LogPerformanceStart(LogDeep, CM, requestInfo.id, requestInfo.created); } if (cPerformanceLogger.IsActive && requestInfo != null) { if (CM == null) CM = MethodBase.GetCurrentMethod(); cPerformanceLogger.LogPerformanceStart(logDeep, CM, requestInfo.id, requestInfo.created); }
var _startTime = DateTime.UtcNow; var startTime = DateTime.UtcNow;
try try
{ {
if (!await CheckOnline()) var roleList = (roleIds ?? Enumerable.Empty<Guid>())
return new Dictionary<string, int>(StringComparer.OrdinalIgnoreCase); .Where(id => id != Guid.Empty)
.Distinct()
var sid = requestInfo?.userInfo?.AdSid;
if (string.IsNullOrWhiteSpace(sid))
return new Dictionary<string, int>(StringComparer.OrdinalIgnoreCase);
var scope = useRoleScope ? "role" : "personal";
var normalizedKeys = (keys ?? Enumerable.Empty<string>())
.Where(k => !string.IsNullOrWhiteSpace(k))
.Distinct(StringComparer.OrdinalIgnoreCase)
.ToList(); .ToList();
var keyParam = normalizedKeys.Count > 0
? HttpUtility.UrlEncode(string.Join(",", normalizedKeys))
: string.Empty;
var url = string.Format(constUrlGetTicketOverviewCounts, HttpUtility.UrlEncode(sid), scope, keyParam); if (roleList.Count == 0)
var wc = await GetWebClient(requestInfo, Token); return new Dictionary<Guid, Dictionary<string, int>>();
var res = await wc.HttpEnh.GetAsync(url, Token);
if (Token.IsCancellationRequested) var request = new TicketOverviewCountsByRolesRequest
{
Sid = requestInfo?.userInfo?.AdSid,
RoleGuids = roleList,
Keys = requestedKeys?.Where(k => !string.IsNullOrWhiteSpace(k)).ToList() ?? new List<string>()
};
var jsonBody = JsonConvert.SerializeObject(request, Formatting.None,
new JsonSerializerSettings { NullValueHandling = NullValueHandling.Ignore });
var content = new StringContent(jsonBody, Encoding.UTF8, "application/json");
var wc = await GetWebClient(requestInfo, token);
var res = await wc.HttpEnh.PostAsync(constUrlGetTicketOverviewCountsByRoles, content, token);
if (token.IsCancellationRequested)
return new Dictionary<Guid, Dictionary<string, int>>();
if (res?.IsSuccessStatusCode == true)
{
var json = await res.Content.ReadAsStringAsync();
if (!string.IsNullOrWhiteSpace(json))
{
var response = JsonConvert.DeserializeObject<TicketOverviewCountsByRolesResponse>(json);
if (response?.CountsByRole != null)
{
var result = new Dictionary<Guid, Dictionary<string, int>>();
foreach (var entry in response.CountsByRole)
{
if (!Guid.TryParse(entry.Key, out var roleId) || roleId == Guid.Empty)
continue;
var counts = entry.Value ?? new Dictionary<string, int>();
result[roleId] = new Dictionary<string, int>(counts, StringComparer.OrdinalIgnoreCase);
}
return result;
}
}
}
StartOnlineValidation();
}
catch (Exception E)
{
LogException(E);
}
finally
{
if (cPerformanceLogger.IsActive && requestInfo != null) { cPerformanceLogger.LogPerformanceEnd(logDeep, CM, requestInfo.id, requestInfo.created, startTime); }
if (CM != null) LogMethodEnd(CM);
}
return new Dictionary<Guid, Dictionary<string, int>>();
}
private Dictionary<string, int> SumTicketOverviewCounts(IEnumerable<Dictionary<string, int>> sources)
{
var result = new Dictionary<string, int>(StringComparer.OrdinalIgnoreCase);
if (sources == null)
return result;
foreach (var counts in sources)
{
if (counts == null)
continue;
foreach (var kvp in counts)
{
if (string.IsNullOrWhiteSpace(kvp.Key))
continue;
result[kvp.Key] = result.TryGetValue(kvp.Key, out var existing)
? existing + kvp.Value
: kvp.Value;
}
}
return result;
}
private Dictionary<string, int> FilterTicketOverviewCounts(Dictionary<string, int> counts, IReadOnlyCollection<string> requestedKeys)
{
if (counts == null)
return new Dictionary<string, int>(StringComparer.OrdinalIgnoreCase);
if (requestedKeys == null || requestedKeys.Count == 0)
return new Dictionary<string, int>(counts, StringComparer.OrdinalIgnoreCase);
var filtered = new Dictionary<string, int>(StringComparer.OrdinalIgnoreCase);
foreach (var key in requestedKeys)
{
if (string.IsNullOrWhiteSpace(key))
continue;
filtered[key] = counts.TryGetValue(key, out var value) ? value : 0;
}
return filtered;
}
private async Task<Dictionary<string, int>> FetchTicketOverviewCountsAsync(
string sid,
string scope,
IReadOnlyCollection<string> requestedKeys,
cF4sdWebRequestInfo requestInfo,
CancellationToken token)
{
try
{
var keyParam = requestedKeys != null && requestedKeys.Count > 0
? HttpUtility.UrlEncode(string.Join(",", requestedKeys))
: null;
var url = string.Format(constUrlGetTicketOverviewCounts, HttpUtility.UrlEncode(sid), scope, keyParam ?? string.Empty);
if (string.IsNullOrWhiteSpace(keyParam))
{
url = $"m42Services/api/c4itf4sdwebapi/getticketoverviewcounts?sid={HttpUtility.UrlEncode(sid)}&scope={scope}";
}
var wc = await GetWebClient(requestInfo, token);
var res = await wc.HttpEnh.GetAsync(url, token);
if (token.IsCancellationRequested)
return new Dictionary<string, int>(StringComparer.OrdinalIgnoreCase); return new Dictionary<string, int>(StringComparer.OrdinalIgnoreCase);
if (res?.IsSuccessStatusCode == true) if (res?.IsSuccessStatusCode == true)
@@ -661,6 +954,86 @@ namespace C4IT.DataHistoryProvider
{ {
LogException(E); LogException(E);
} }
return new Dictionary<string, int>(StringComparer.OrdinalIgnoreCase);
}
public async Task<Dictionary<string, int>> GetTicketOverviewCountsAsync(IEnumerable<string> keys, bool useRoleScope, cF4sdWebRequestInfo requestInfo, int LogDeep, CancellationToken Token)
{
MethodBase CM = null; if (cLogManager.DefaultLogger.IsDebug) { CM = MethodBase.GetCurrentMethod(); LogMethodBegin(CM); }
if (cPerformanceLogger.IsActive && requestInfo != null) { if (CM == null) CM = MethodBase.GetCurrentMethod(); cPerformanceLogger.LogPerformanceStart(LogDeep, CM, requestInfo.id, requestInfo.created); }
var _startTime = DateTime.UtcNow;
try
{
if (!await CheckOnline())
return new Dictionary<string, int>(StringComparer.OrdinalIgnoreCase);
var sid = requestInfo?.userInfo?.AdSid;
if (string.IsNullOrWhiteSpace(sid))
return new Dictionary<string, int>(StringComparer.OrdinalIgnoreCase);
var normalizedKeys = (keys ?? Enumerable.Empty<string>())
.Where(k => !string.IsNullOrWhiteSpace(k))
.Distinct(StringComparer.OrdinalIgnoreCase)
.ToList();
var now = DateTime.UtcNow;
var ttl = GetTicketOverviewCacheDuration(useRoleScope);
if (!useRoleScope)
{
if (TryGetTicketOverviewPersonalCache(sid, now, out var cachedPersonal))
return FilterTicketOverviewCounts(cachedPersonal, normalizedKeys);
var counts = await FetchTicketOverviewCountsAsync(sid, "personal", normalizedKeys, requestInfo, Token);
if (Token.IsCancellationRequested)
return new Dictionary<string, int>(StringComparer.OrdinalIgnoreCase);
SetTicketOverviewPersonalCache(sid, counts, now.Add(ttl));
return FilterTicketOverviewCounts(counts, normalizedKeys);
}
var roleIds = await GetTicketOverviewRoleIdsAsync(sid, now, ttl, Token);
if (roleIds == null || roleIds.Count == 0)
return new Dictionary<string, int>(StringComparer.OrdinalIgnoreCase);
var countsByRole = new Dictionary<Guid, Dictionary<string, int>>();
var missingRoles = new List<Guid>();
foreach (var roleId in roleIds)
{
if (TryGetTicketOverviewRoleCache(roleId, now, out var cachedRole))
{
countsByRole[roleId] = cachedRole;
}
else
{
missingRoles.Add(roleId);
}
}
if (missingRoles.Count > 0)
{
var fetched = await FetchTicketOverviewCountsByRolesAsync(missingRoles, normalizedKeys, requestInfo, LogDeep + 1, Token);
var expiresAt = DateTime.UtcNow.Add(ttl);
foreach (var roleId in missingRoles)
{
if (!fetched.TryGetValue(roleId, out var roleCounts))
roleCounts = new Dictionary<string, int>(StringComparer.OrdinalIgnoreCase);
SetTicketOverviewRoleCache(roleId, roleCounts, expiresAt);
countsByRole[roleId] = roleCounts;
}
}
var summed = SumTicketOverviewCounts(countsByRole.Values);
return FilterTicketOverviewCounts(summed, normalizedKeys);
}
catch (Exception E)
{
LogException(E);
}
finally finally
{ {
if (cPerformanceLogger.IsActive && requestInfo != null) { cPerformanceLogger.LogPerformanceEnd(LogDeep, CM, requestInfo.id, requestInfo.created, _startTime); } if (cPerformanceLogger.IsActive && requestInfo != null) { cPerformanceLogger.LogPerformanceEnd(LogDeep, CM, requestInfo.id, requestInfo.created, _startTime); }