using Cowain.Base.DBContext; using Cowain.Base.Models; using Cowain.Base.Services; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Caching.Memory; using Microsoft.Extensions.Logging; using Plugin.Cowain.Wcs.IServices; using Plugin.Cowain.Wcs.Models.Dto; using Plugin.Cowain.Wcs.ViewModels; using System.Collections.Concurrent; namespace Plugin.Cowain.Wcs.Services; public class WcsParamService : BaseService, IWcsParamService { private readonly IMemoryCache _memoryCache; private readonly ILogger _logger; // 参数级别的锁,避免不同参数之间的锁竞争 private static readonly ConcurrentDictionary _paramLocks = new(); // 缓存键前缀 private const string CACHE_KEY_PREFIX = "WcsParam_"; // 缓存过期时间(5分钟) private static readonly TimeSpan CACHE_EXPIRATION = TimeSpan.FromMinutes(5); public WcsParamService(IDbContextFactory dbContextFactory, ILogger logger, IMemoryCache memoryCache) : base(dbContextFactory) { _memoryCache = memoryCache; _logger = logger; } /// /// 获取参数级别的锁 /// private SemaphoreSlim GetParamLock(string paramName) { return _paramLocks.GetOrAdd(paramName, _ => new SemaphoreSlim(1, 1)); } /// /// 获取缓存键 /// private string GetCacheKey(string paramName) { return $"{CACHE_KEY_PREFIX}{paramName}"; } /// /// 清除指定参数的缓存 /// private void ClearParamCache(string paramName) { string cacheKey = GetCacheKey(paramName); _memoryCache.Remove(cacheKey); _logger.LogInformation($"已清除参数 {paramName} 的缓存"); } public async Task AddAsync(WcsParamViewModel? viewModel) { if (viewModel == null) { return ResultModel.Error("viewModel不能为空"); } if (string.IsNullOrWhiteSpace(viewModel.Name)) { return ResultModel.Error("viewModel.Name不能为空"); } if (string.IsNullOrWhiteSpace(viewModel.Param)) { return ResultModel.Error("viewModel.Param不能为空"); } var paramLock = GetParamLock(viewModel.Name); await paramLock.WaitAsync(); try { using var dbContext = _dbContextFactory.CreateDbContext(); var dbSet = dbContext.GetDbSet(); // 检查Name是否重复 var nameExists = await dbSet.AnyAsync(x => x.Name == viewModel.Name); if (nameExists) { return ResultModel.Error("名称已存在"); } var entity = new WcsParamDto { Name = viewModel.Name, Param = viewModel.Param }; await dbSet.AddAsync(entity); var count = await dbContext.SaveChangesAsync(); if (count > 0) { // 清除相关缓存 ClearParamCache(viewModel.Name); return ResultModel.Success("参数添加成功"); } else { return ResultModel.Error("参数添加失败"); } } finally { paramLock.Release(); } } public async Task DeleteAsync(int id) { if (id <= 0) { return ResultModel.Error("id不能小于0"); } using var dbContext = _dbContextFactory.CreateDbContext(); var DbSet = dbContext.GetDbSet(); var existingModel = await DbSet.FirstOrDefaultAsync(x => x.Id == id); if (existingModel == null) { return ResultModel.Error("id不存在"); } string paramName = existingModel.Name; var paramLock = GetParamLock(paramName); await paramLock.WaitAsync(); try { DbSet.Remove(existingModel); int count = await dbContext.SaveChangesAsync(); if (count > 0) { // 清除相关缓存 ClearParamCache(paramName); return ResultModel.Success("参数删除成功"); } else { return ResultModel.Error("参数删除失败"); } } finally { paramLock.Release(); } } public async Task> GetParamAsync(string? name) { if (string.IsNullOrWhiteSpace(name)) { return ResultModel.Error("参数名称不能为空"); } string cacheKey = GetCacheKey(name); // 第一次尝试从缓存获取 if (_memoryCache.TryGetValue(cacheKey, out WcsParamViewModel? cachedValue)) { _logger.LogInformation($"从缓存获取参数 {name}: {cachedValue?.Param}"); return ResultModel.Success(cachedValue!); } // 缓存不存在,使用锁保护数据库查询和缓存更新 var paramLock = GetParamLock(name); await paramLock.WaitAsync(); try { // 双重检查,防止在等待锁期间其他线程已经更新了缓存 if (_memoryCache.TryGetValue(cacheKey, out cachedValue)) { _logger.LogInformation($"双重检查:从缓存获取参数 {name}: {cachedValue?.Param}"); return ResultModel.Success(cachedValue!); } // 从数据库获取 using var dbContext = _dbContextFactory.CreateDbContext(); var DbSet = dbContext.GetDbSet(); var existingModel = await DbSet.FirstOrDefaultAsync(x => x.Name == name); if (existingModel == null) { return ResultModel.Error("名称不存在"); } var result = new WcsParamViewModel { Id = existingModel.Id, Name = existingModel.Name, Param = existingModel.Param }; // 将结果存入缓存 var cacheOptions = new MemoryCacheEntryOptions { AbsoluteExpirationRelativeToNow = CACHE_EXPIRATION, SlidingExpiration = CACHE_EXPIRATION }; _memoryCache.Set(cacheKey, result, cacheOptions); _logger.LogInformation($"从数据库获取参数 {name}: {result.Param},已缓存"); return ResultModel.Success(result); } finally { paramLock.Release(); } } public async Task UpdateAsync(WcsParamViewModel? viewModel) { if (viewModel == null) { return ResultModel.Error("viewModel不能为空"); } if (viewModel.Id <= 0) { return ResultModel.Error("ID不能小于0"); } if (string.IsNullOrWhiteSpace(viewModel.Name)) { return ResultModel.Error("名称不能为空"); } if (string.IsNullOrWhiteSpace(viewModel.Param)) { return ResultModel.Error("参数不能为空"); } using var dbContext = _dbContextFactory.CreateDbContext(); var DbSet = dbContext.GetDbSet(); var existingModel = await DbSet.FirstOrDefaultAsync(x => x.Id == viewModel.Id); if (existingModel == null) { return ResultModel.Error("id不存在"); } // 检查是否有同名(排除自己) var nameExists = await DbSet.AnyAsync(x => x.Name == viewModel.Name && x.Id != viewModel.Id); if (nameExists) { return ResultModel.Error("名称已存在"); } // 记录旧名称,用于清除缓存 string oldName = existingModel.Name; string newName = viewModel.Name; // 获取两个参数的锁(如果名称不同) var oldParamLock = GetParamLock(oldName); var newParamLock = oldName == newName ? oldParamLock : GetParamLock(newName); // 如果名称不同,需要同时获取两个锁,避免死锁 if (oldName != newName) { // 按字典序获取锁,避免死锁 var locks = new[] { oldParamLock, newParamLock }.OrderBy(l => l.GetHashCode()).ToArray(); await locks[0].WaitAsync(); await locks[1].WaitAsync(); } else { await oldParamLock.WaitAsync(); } try { // 更新字段 existingModel.Name = viewModel.Name; existingModel.Param = viewModel.Param; var count = await dbContext.SaveChangesAsync(); if (count > 0) { // 清除相关缓存(包括旧名称和新名称) ClearParamCache(oldName); if (oldName != newName) { ClearParamCache(newName); } return ResultModel.Success("参数更新成功"); } else { return ResultModel.Error("参数更新失败"); } } finally { // 释放锁 if (oldName != newName) { var locks = new[] { oldParamLock, newParamLock }.OrderBy(l => l.GetHashCode()).ToArray(); locks[1].Release(); locks[0].Release(); } else { oldParamLock.Release(); } } } }