You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
655 lines
22 KiB
C#
655 lines
22 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.Linq;
|
|
using System.Threading;
|
|
using System.Threading.Tasks;
|
|
using Microsoft.Extensions.Caching.Memory;
|
|
using Haoliang.Core.Services;
|
|
using Haoliang.Models.Models.Device;
|
|
using Haoliang.Models.Models.Production;
|
|
using Haoliang.Models.Models.System;
|
|
|
|
namespace Haoliang.Core.Services
|
|
{
|
|
public interface ICacheService
|
|
{
|
|
/// <summary>
|
|
/// Get cached value or execute factory if not exists
|
|
/// </summary>
|
|
Task<T> GetOrSetAsync<T>(string key, Func<Task<T>> factory, MemoryCacheEntryOptions options = null);
|
|
|
|
/// <summary>
|
|
/// Get cached value synchronously
|
|
/// </summary>
|
|
T Get<T>(string key);
|
|
|
|
/// <summary>
|
|
/// Set cache value
|
|
/// </summary>
|
|
void Set<T>(string key, T value, MemoryCacheEntryOptions options = null);
|
|
|
|
/// <summary>
|
|
/// Remove cached value
|
|
/// </summary>
|
|
bool Remove(string key);
|
|
|
|
/// <summary>
|
|
/// Check if key exists in cache
|
|
/// </summary>
|
|
bool Exists(string key);
|
|
|
|
/// <summary>
|
|
/// Clear all cache
|
|
/// </summary>
|
|
void Clear();
|
|
|
|
/// <summary>
|
|
/// Get cache statistics
|
|
/// </summary>
|
|
CacheStatistics GetStatistics();
|
|
|
|
/// <summary>
|
|
/// Get cache keys matching pattern
|
|
/// </summary>
|
|
IEnumerable<string> GetKeys(string pattern);
|
|
|
|
/// <summary>
|
|
/// Refresh cached value
|
|
/// </summary>
|
|
bool Refresh<T>(string key);
|
|
}
|
|
|
|
public class CacheStatistics
|
|
{
|
|
public long TotalItems { get; set; }
|
|
public long Hits { get; set; }
|
|
public long Misses { get; set; }
|
|
public double HitRate => Hits + Misses > 0 ? (double)Hits / (Hits + Misses) : 0;
|
|
public long MemoryUsageBytes { get; set; }
|
|
public DateTime LastCleared { get; set; }
|
|
public Dictionary<string, long> ItemsByType { get; set; }
|
|
}
|
|
|
|
public class MemoryCacheService : ICacheService
|
|
{
|
|
private readonly IMemoryCache _memoryCache;
|
|
private readonly ReaderWriterLockSlim _lock = new ReaderWriterLockSlim();
|
|
private long _hits = 0;
|
|
private long _misses = 0;
|
|
private long _memoryUsage = 0;
|
|
|
|
public MemoryCacheService(IMemoryCache memoryCache)
|
|
{
|
|
_memoryCache = memoryCache;
|
|
}
|
|
|
|
public Task<T> GetOrSetAsync<T>(string key, Func<Task<T>> factory, MemoryCacheEntryOptions options = null)
|
|
{
|
|
return Task.Run(async () =>
|
|
{
|
|
// Try to get from cache first
|
|
if (_memoryCache.TryGetValue(key, out T value))
|
|
{
|
|
Interlocked.Increment(ref _hits);
|
|
return value;
|
|
}
|
|
|
|
Interlocked.Increment(ref _misses);
|
|
|
|
// Create new value
|
|
value = await factory();
|
|
|
|
if (value != null)
|
|
{
|
|
// Set cache options if not provided
|
|
options = options ?? new MemoryCacheEntryOptions
|
|
{
|
|
SlidingExpiration = TimeSpan.FromMinutes(30),
|
|
AbsoluteExpirationRelativeToNow = TimeSpan.FromHours(2),
|
|
Size = 1024 // 1KB
|
|
};
|
|
|
|
// Set cache with lock
|
|
_lock.EnterWriteLock();
|
|
try
|
|
{
|
|
_memoryCache.Set(key, value, options);
|
|
UpdateMemoryUsage(options.Size.GetValueOrDefault(1024));
|
|
}
|
|
finally
|
|
{
|
|
_lock.ExitWriteLock();
|
|
}
|
|
}
|
|
|
|
return value;
|
|
});
|
|
}
|
|
|
|
public T Get<T>(string key)
|
|
{
|
|
if (_memoryCache.TryGetValue(key, out T value))
|
|
{
|
|
Interlocked.Increment(ref _hits);
|
|
return value;
|
|
}
|
|
|
|
Interlocked.Increment(ref _misses);
|
|
return default(T);
|
|
}
|
|
|
|
public void Set<T>(string key, T value, MemoryCacheEntryOptions options = null)
|
|
{
|
|
if (value == null)
|
|
return;
|
|
|
|
options = options ?? new MemoryCacheEntryOptions
|
|
{
|
|
SlidingExpiration = TimeSpan.FromMinutes(30),
|
|
AbsoluteExpirationRelativeToNow = TimeSpan.FromHours(2),
|
|
Size = 1024 // 1KB
|
|
};
|
|
|
|
_lock.EnterWriteLock();
|
|
try
|
|
{
|
|
_memoryCache.Set(key, value, options);
|
|
UpdateMemoryUsage(options.Size.GetValueOrDefault(1024));
|
|
}
|
|
finally
|
|
{
|
|
_lock.ExitWriteLock();
|
|
}
|
|
}
|
|
|
|
public bool Remove(string key)
|
|
{
|
|
_lock.EnterWriteLock();
|
|
try
|
|
{
|
|
if (_memoryCache.TryGetValue(key, out object value))
|
|
{
|
|
_memoryCache.Remove(key);
|
|
UpdateMemoryUsage(-(GetEstimatedSize(value)));
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
finally
|
|
{
|
|
_lock.ExitWriteLock();
|
|
}
|
|
}
|
|
|
|
public bool Exists(string key)
|
|
{
|
|
return _memoryCache.TryGetValue(key, out _);
|
|
}
|
|
|
|
public void Clear()
|
|
{
|
|
_lock.EnterWriteLock();
|
|
try
|
|
{
|
|
// This is a simplified implementation
|
|
// In a real scenario, you might need a more sophisticated way to clear the cache
|
|
_memoryCache.Compact(1.0); // Remove all entries
|
|
_memoryUsage = 0;
|
|
Interlocked.Exchange(ref _hits, 0);
|
|
Interlocked.Exchange(ref _misses, 0);
|
|
}
|
|
finally
|
|
{
|
|
_lock.ExitWriteLock();
|
|
}
|
|
}
|
|
|
|
public CacheStatistics GetStatistics()
|
|
{
|
|
_lock.EnterReadLock();
|
|
try
|
|
{
|
|
var stats = new CacheStatistics
|
|
{
|
|
TotalItems = GetCacheSize(),
|
|
Hits = _hits,
|
|
Misses = _misses,
|
|
MemoryUsageBytes = _memoryUsage,
|
|
LastCleared = DateTime.Now,
|
|
ItemsByType = new Dictionary<string, long>()
|
|
};
|
|
|
|
// Count items by type (simplified)
|
|
var deviceCacheCount = GetKeys("device:*").Count();
|
|
var productionCacheCount = GetKeys("production:*").Count();
|
|
var configCacheCount = GetKeys("config:*").Count();
|
|
var templateCacheCount = GetKeys("template:*").Count();
|
|
|
|
stats.ItemsByType["device"] = deviceCacheCount;
|
|
stats.ItemsByType["production"] = productionCacheCount;
|
|
stats.ItemsByType["config"] = configCacheCount;
|
|
stats.ItemsByType["template"] = templateCacheCount;
|
|
|
|
return stats;
|
|
}
|
|
finally
|
|
{
|
|
_lock.ExitReadLock();
|
|
}
|
|
}
|
|
|
|
public IEnumerable<string> GetKeys(string pattern)
|
|
{
|
|
// This is a simplified implementation
|
|
// In a real scenario, you might need a more sophisticated key pattern matching
|
|
return _memoryCache.Keys
|
|
.Cast<string>()
|
|
.Where(key => key.StartsWith(pattern.Replace("*", "")));
|
|
}
|
|
|
|
public bool Refresh<T>(string key)
|
|
{
|
|
if (!_memoryCache.TryGetValue(key, out T value))
|
|
return false;
|
|
|
|
// Remove and re-add to refresh
|
|
_lock.EnterWriteLock();
|
|
try
|
|
{
|
|
_memoryCache.Remove(key);
|
|
_memoryCache.Set(key, value, new MemoryCacheEntryOptions
|
|
{
|
|
SlidingExpiration = TimeSpan.FromMinutes(30),
|
|
AbsoluteExpirationRelativeToNow = TimeSpan.FromHours(2)
|
|
});
|
|
return true;
|
|
}
|
|
finally
|
|
{
|
|
_lock.ExitWriteLock();
|
|
}
|
|
}
|
|
|
|
#region Private Methods
|
|
|
|
private int GetCacheSize()
|
|
{
|
|
// This is an approximation
|
|
return _memoryCache.Count;
|
|
}
|
|
|
|
private void UpdateMemoryUsage(long delta)
|
|
{
|
|
Interlocked.Add(ref _memoryUsage, delta);
|
|
}
|
|
|
|
private int GetEstimatedSize(object obj)
|
|
{
|
|
// Simplified size estimation
|
|
if (obj == null) return 0;
|
|
|
|
var type = obj.GetType();
|
|
if (type.IsValueType || type == typeof(string))
|
|
{
|
|
return System.Text.Json.JsonSerializer.Serialize(obj).Length;
|
|
}
|
|
|
|
// For complex objects, estimate based on type
|
|
return 1024; // Default 1KB for complex objects
|
|
}
|
|
|
|
#endregion
|
|
}
|
|
|
|
// Extension methods for common caching patterns
|
|
public static class CacheServiceExtensions
|
|
{
|
|
/// <summary>
|
|
/// Cache device information
|
|
/// </summary>
|
|
public static Task<CNCDevice> GetOrSetDeviceAsync(this ICacheService cache, int deviceId,
|
|
Func<Task<CNCDevice>> factory, TimeSpan? expiration = null)
|
|
{
|
|
var options = new MemoryCacheEntryOptions();
|
|
if (expiration.HasValue)
|
|
{
|
|
options.SlidingExpiration = expiration.Value;
|
|
options.AbsoluteExpirationRelativeToNow = expiration.Value + TimeSpan.FromMinutes(30);
|
|
}
|
|
else
|
|
{
|
|
options.SlidingExpiration = TimeSpan.FromMinutes(30);
|
|
options.AbsoluteExpirationRelativeToNow = TimeSpan.FromHours(2);
|
|
}
|
|
|
|
return cache.GetOrSetAsync($"device:{deviceId}", factory, options);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Cache device list
|
|
/// </summary>
|
|
public static Task<List<CNCDevice>> GetOrSetAllDevicesAsync(this ICacheService cache,
|
|
Func<Task<List<CNCDevice>>> factory, TimeSpan? expiration = null)
|
|
{
|
|
var options = new MemoryCacheEntryOptions
|
|
{
|
|
SlidingExpiration = expiration ?? TimeSpan.FromMinutes(15),
|
|
AbsoluteExpirationRelativeToNow = TimeSpan.FromHours(1)
|
|
};
|
|
|
|
return cache.GetOrSetAsync("devices:all", factory, options);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Cache device status
|
|
/// </summary>
|
|
public static Task<DeviceCurrentStatus> GetOrSetDeviceStatusAsync(this ICacheService cache, int deviceId,
|
|
Func<Task<DeviceCurrentStatus>> factory, TimeSpan? expiration = null)
|
|
{
|
|
var options = new MemoryCacheEntryOptions
|
|
{
|
|
SlidingExpiration = expiration ?? TimeSpan.FromMinutes(5),
|
|
AbsoluteExpirationRelativeToNow = TimeSpan.FromMinutes(10)
|
|
};
|
|
|
|
return cache.GetOrSetAsync($"device:status:{deviceId}", factory, options);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Cache production records
|
|
/// </summary>
|
|
public static Task<List<ProductionRecord>> GetOrSetProductionRecordsAsync(this ICacheService cache,
|
|
int deviceId, DateTime date, Func<Task<List<ProductionRecord>>> factory, TimeSpan? expiration = null)
|
|
{
|
|
var options = new MemoryCacheEntryOptions
|
|
{
|
|
SlidingExpiration = expiration ?? TimeSpan.FromMinutes(10),
|
|
AbsoluteExpirationRelativeToNow = TimeSpan.FromHours(1)
|
|
};
|
|
|
|
return cache.GetOrSetAsync($"production:records:{deviceId}:{date:yyyy-MM-dd}", factory, options);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Cache production summary
|
|
/// </summary>
|
|
public static Task<ProgramProductionSummary> GetOrSetProductionSummaryAsync(this ICacheService cache,
|
|
int deviceId, string programName, Func<Task<ProgramProductionSummary>> factory, TimeSpan? expiration = null)
|
|
{
|
|
var options = new MemoryCacheEntryOptions
|
|
{
|
|
SlidingExpiration = expiration ?? TimeSpan.FromMinutes(30),
|
|
AbsoluteExpirationRelativeToNow = TimeSpan.FromHours(2)
|
|
};
|
|
|
|
return cache.GetOrSetAsync($"production:summary:{deviceId}:{programName}", factory, options);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Cache system configuration
|
|
/// </summary>
|
|
public static Task<SystemConfiguration> GetOrSetSystemConfigurationAsync(this ICacheService cache,
|
|
Func<Task<SystemConfiguration>> factory, TimeSpan? expiration = null)
|
|
{
|
|
var options = new MemoryCacheEntryOptions
|
|
{
|
|
SlidingExpiration = expiration ?? TimeSpan.FromHours(1),
|
|
AbsoluteExpirationRelativeToNow = TimeSpan.FromHours(6)
|
|
};
|
|
|
|
return cache.GetOrSetAsync("config:system", factory, options);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Cache template
|
|
/// </summary>
|
|
public static Task<CNCBrandTemplate> GetOrSetTemplateAsync(this ICacheService cache,
|
|
int templateId, Func<Task<CNCBrandTemplate>> factory, TimeSpan? expiration = null)
|
|
{
|
|
var options = new MemoryCacheEntryOptions
|
|
{
|
|
SlidingExpiration = expiration ?? TimeSpan.FromMinutes(30),
|
|
AbsoluteExpirationRelativeToNow = TimeSpan.FromHours(2)
|
|
};
|
|
|
|
return cache.GetOrSetAsync($"template:{templateId}", factory, options);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Cache template list
|
|
/// </summary>
|
|
public static Task<List<CNCBrandTemplate>> GetOrSetAllTemplatesAsync(this ICacheService cache,
|
|
Func<Task<List<CNCBrandTemplate>>> factory, TimeSpan? expiration = null)
|
|
{
|
|
var options = new MemoryCacheEntryOptions
|
|
{
|
|
SlidingExpiration = expiration ?? TimeSpan.FromMinutes(30),
|
|
AbsoluteExpirationRelativeToNow = TimeSpan.FromHours(2)
|
|
};
|
|
|
|
return cache.GetOrSetAsync("templates:all", factory, options);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Cache alert configuration
|
|
/// </summary>
|
|
public static Task<AlertConfiguration> GetOrSetAlertConfigurationAsync(this ICacheService cache,
|
|
Func<Task<AlertConfiguration>> factory, TimeSpan? expiration = null)
|
|
{
|
|
var options = new MemoryCacheEntryOptions
|
|
{
|
|
SlidingExpiration = expiration ?? TimeSpan.FromMinutes(30),
|
|
AbsoluteExpirationRelativeToNow = TimeSpan.FromHours(1)
|
|
};
|
|
|
|
return cache.GetOrSetAsync("config:alerts", factory, options);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Cache dashboard summary
|
|
/// </summary>
|
|
public static Task<DashboardSummary> GetOrSetDashboardSummaryAsync(this ICacheService cache,
|
|
DateTime date, Func<Task<DashboardSummary>> factory, TimeSpan? expiration = null)
|
|
{
|
|
var options = new MemoryCacheEntryOptions
|
|
{
|
|
SlidingExpiration = expiration ?? TimeSpan.FromMinutes(10),
|
|
AbsoluteExpirationRelativeToNow = TimeSpan.FromMinutes(30)
|
|
};
|
|
|
|
return cache.GetOrSetAsync($"dashboard:summary:{date:yyyy-MM-dd}", factory, options);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Invalidate device-related cache
|
|
/// </summary>
|
|
public static void InvalidateDeviceCache(this ICacheService cache, int deviceId, params string[] additionalKeys)
|
|
{
|
|
var keys = new[]
|
|
{
|
|
$"device:{deviceId}",
|
|
$"device:status:{deviceId}",
|
|
$"production:records:{deviceId}",
|
|
$"production:summary:{deviceId}"
|
|
};
|
|
|
|
foreach (var key in keys.Concat(additionalKeys))
|
|
{
|
|
cache.Remove(key);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Invalidate production-related cache
|
|
/// </summary>
|
|
public static void InvalidateProductionCache(this ICacheService cache, int deviceId, string programName, DateTime date)
|
|
{
|
|
cache.Remove($"production:records:{deviceId}:{date:yyyy-MM-dd}");
|
|
cache.Remove($"production:summary:{deviceId}:{programName}");
|
|
}
|
|
|
|
/// <summary>
|
|
/// Invalidate template-related cache
|
|
/// </summary>
|
|
public static void InvalidateTemplateCache(this ICacheService cache, int templateId)
|
|
{
|
|
cache.Remove($"template:{templateId}");
|
|
cache.Remove("templates:all");
|
|
}
|
|
|
|
/// <summary>
|
|
/// Invalidate system configuration cache
|
|
/// </summary>
|
|
public static void InvalidateSystemConfigCache(this ICacheService cache)
|
|
{
|
|
cache.Remove("config:system");
|
|
cache.Remove("config:alerts");
|
|
cache.Remove("dashboard:summary");
|
|
}
|
|
|
|
/// <summary>
|
|
/// Invalidate dashboard cache
|
|
/// </summary>
|
|
public static void InvalidateDashboardCache(this ICacheService cache, DateTime date)
|
|
{
|
|
cache.Remove($"dashboard:summary:{date:yyyy-MM-dd}");
|
|
}
|
|
|
|
/// <summary>
|
|
/// Get or set with sliding expiration for frequently accessed data
|
|
/// </summary>
|
|
public static Task<T> GetOrSetWithSlidingExpiration<T>(this ICacheService cache, string key,
|
|
Func<Task<T>> factory, TimeSpan slidingExpiration)
|
|
{
|
|
var options = new MemoryCacheEntryOptions
|
|
{
|
|
SlidingExpiration = slidingExpiration
|
|
};
|
|
|
|
return cache.GetOrSetAsync(key, factory, options);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Get or set with absolute expiration for time-sensitive data
|
|
/// </summary>
|
|
public static Task<T> GetOrSetWithAbsoluteExpiration<T>(this ICacheService cache, string key,
|
|
Func<Task<T>> factory, TimeSpan absoluteExpiration)
|
|
{
|
|
var options = new MemoryCacheEntryOptions
|
|
{
|
|
AbsoluteExpirationRelativeToNow = absoluteExpiration
|
|
};
|
|
|
|
return cache.GetOrSetAsync(key, factory, options);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Get or set with priority for important data
|
|
/// </summary>
|
|
public static Task<T> GetOrSetWithPriority<T>(this ICacheService cache, string key,
|
|
Func<Task<T>> factory, CacheItemPriority priority)
|
|
{
|
|
var options = new MemoryCacheEntryOptions
|
|
{
|
|
Priority = priority
|
|
};
|
|
|
|
return cache.GetOrSetAsync(key, factory, options);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Cache service for distributed caching
|
|
/// </summary>
|
|
public interface IDistributedCacheService : ICacheService
|
|
{
|
|
/// <summary>
|
|
/// Get distributed lock
|
|
/// </summary>
|
|
Task<IDistributedLock> AcquireLockAsync(string key, TimeSpan timeout);
|
|
|
|
/// <summary>
|
|
/// Refresh distributed cache
|
|
/// </summary>
|
|
Task RefreshAsync(string key);
|
|
|
|
/// <summary>
|
|
/// Get distributed cache statistics
|
|
/// </summary>
|
|
Task<DistributedCacheStatistics> GetDistributedStatisticsAsync();
|
|
}
|
|
|
|
public interface IDistributedLock : IDisposable
|
|
{
|
|
bool IsAcquired { get; }
|
|
void Release();
|
|
}
|
|
|
|
public class DistributedCacheStatistics : CacheStatistics
|
|
{
|
|
public int ConnectedNodes { get; set; }
|
|
public long NetworkCalls { get; set; }
|
|
public long SynchronizationErrors { get; set; }
|
|
}
|
|
|
|
/// <summary>
|
|
/// Cache manager for managing multiple cache instances
|
|
/// </summary>
|
|
public interface ICacheManager
|
|
{
|
|
ICacheService LocalCache { get; }
|
|
IDistributedCacheService DistributedCache { get; }
|
|
void ConfigureCacheSettings(CacheSettings settings);
|
|
void InitializeCaches();
|
|
}
|
|
|
|
public class CacheSettings
|
|
{
|
|
public bool EnableLocalCache { get; set; } = true;
|
|
public bool EnableDistributedCache { get; set; } = false;
|
|
public TimeSpan DefaultSlidingExpiration { get; set; } = TimeSpan.FromMinutes(30);
|
|
public TimeSpan DefaultAbsoluteExpiration { get; set; } = TimeSpan.FromHours(2);
|
|
public long MaxMemorySizeBytes { get; set; } = 1024 * 1024 * 100; // 100MB
|
|
public CacheEvictionPolicy EvictionPolicy { get; set; } = CacheEvictionPolicy.LRU;
|
|
public bool EnableCacheLogging { get; set; } = false;
|
|
public TimeSpan CacheRefreshInterval { get; set; } = TimeSpan.FromMinutes(5);
|
|
}
|
|
|
|
public enum CacheEvictionPolicy
|
|
{
|
|
LRU, // Least Recently Used
|
|
LFU, // Least Frequently Used
|
|
FIFO, // First In First Out
|
|
Random
|
|
}
|
|
|
|
public class CacheManager : ICacheManager
|
|
{
|
|
public ICacheService LocalCache { get; private set; }
|
|
public IDistributedCacheService DistributedCache { get; private set; }
|
|
|
|
public CacheManager(ICacheService localCache, IDistributedCacheService distributedCache)
|
|
{
|
|
LocalCache = localCache;
|
|
DistributedCache = distributedCache;
|
|
}
|
|
|
|
public void ConfigureCacheSettings(CacheSettings settings)
|
|
{
|
|
// Configure cache settings based on provided configuration
|
|
// This would involve setting up the cache instances with the specified settings
|
|
}
|
|
|
|
public void InitializeCaches()
|
|
{
|
|
// Initialize caches with default settings
|
|
ClearAllCaches();
|
|
}
|
|
|
|
public void ClearAllCaches()
|
|
{
|
|
LocalCache?.Clear();
|
|
DistributedCache?.Clear();
|
|
}
|
|
}
|
|
} |