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#

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();
}
}
}