diff --git a/src/SJZY.Track.Application.Contract/Dtos/Forewarning/ClearRulCacheEvent.cs b/src/SJZY.Track.Application.Contract/Dtos/Forewarning/ClearRulCacheEvent.cs new file mode 100644 index 00000000..e875e4b8 --- /dev/null +++ b/src/SJZY.Track.Application.Contract/Dtos/Forewarning/ClearRulCacheEvent.cs @@ -0,0 +1,9 @@ +using SJZY.Track.Domain.Dtos.Forewarning; + +namespace SJZY.Track.Application.Contract.Dtos.Forewarning; + + +public class ClearRulCacheEvent +{ + public List Items { get; set; } +} \ No newline at end of file diff --git a/src/SJZY.Track.Application.Contract/Services/IForewarningCacheService.cs b/src/SJZY.Track.Application.Contract/Services/IForewarningCacheService.cs index d7743c32..08d8fafb 100644 --- a/src/SJZY.Track.Application.Contract/Services/IForewarningCacheService.cs +++ b/src/SJZY.Track.Application.Contract/Services/IForewarningCacheService.cs @@ -24,11 +24,19 @@ public interface IForewarningCacheService /// 重置本地缓存 /// /// - Task Clear(); + Task Clear(ClearRulCacheEvent @event); /// /// 清除redis 缓存 /// /// - Task ClearRedis(); + Task ClearRedisByAreaId(int areaId); + + /// + /// 清除redis 缓存 + /// + /// 子系统id + /// 客户id + /// + Task ClearRedisBySystemIdAndCustomer(int subSystemId, int customerId); } \ No newline at end of file diff --git a/src/SJZY.Track.Application/HostServices/RedisHostService.cs b/src/SJZY.Track.Application/HostServices/RedisHostService.cs index 702afb51..0a7237e8 100644 --- a/src/SJZY.Track.Application/HostServices/RedisHostService.cs +++ b/src/SJZY.Track.Application/HostServices/RedisHostService.cs @@ -1,8 +1,11 @@ using Microsoft.Extensions.Hosting; using SJZY.Expand.ABP.Core; +using SJZY.Track.Application.Contract.Dtos.Forewarning; using SJZY.Track.Application.Contract.Events; using SJZY.Track.Application.Contract.Services; using SJZY.Track.Domain.Consts; +using SJZY.Track.Domain.Dtos.Forewarning; +using SJZY.UserCenter.SharedModel.Dtos.PHP.User; namespace SJZY.Track.Application.HostServices; @@ -15,7 +18,7 @@ public class RedisHostService:IHostedService _forewarningCacheService = forewarningCacheService; } - public Task StartAsync(CancellationToken cancellationToken) + public Task StartAsync(CancellationToken cancellationToken) { // 订阅频道 RedisHelper.Client.Subscribe(GetKey(), Subscribe); @@ -34,9 +37,10 @@ public class RedisHostService:IHostedService { // 暂时只处理一个事件,不做反序列化 //var instance = Newtonsoft.Json.JsonConvert.DeserializeObject(value as string); + var instance = Newtonsoft.Json.JsonConvert.DeserializeObject(value as string); + if(instance!=null) + _forewarningCacheService.Clear(instance); } - - _forewarningCacheService.Clear(); } private string GetKey() diff --git a/src/SJZY.Track.Application/Services/ForewarningServices/ForewarningCacheService.cs b/src/SJZY.Track.Application/Services/ForewarningServices/ForewarningCacheService.cs index a79684d6..effa72e3 100644 --- a/src/SJZY.Track.Application/Services/ForewarningServices/ForewarningCacheService.cs +++ b/src/SJZY.Track.Application/Services/ForewarningServices/ForewarningCacheService.cs @@ -1,3 +1,4 @@ +using System.Collections.Concurrent; using FreeRedis; using SJZY.Expand.ABP.Core; using SJZY.Expand.ABP.Core.PublicMethords; @@ -11,46 +12,36 @@ using Volo.Abp.DependencyInjection; namespace SJZY.Track.Application.Services.ForewarningServices; -public class ForewarningCacheService: IForewarningCacheService, ISingletonDependency +public class ForewarningCacheService : IForewarningCacheService, ISingletonDependency { readonly IKpiAreaRepository _kpiAreaRepository; readonly IWarningRepository _warningRepository; - const string GlobalKpiAreaLockKey = "KKpiAreaLockKey_GlobalLock"; private const string GlobalWaningLockKey = "GlobalWaningLockKey_GlobalLock"; - - private Dictionary> _rules = new(); - private Dictionary>? _areas = new(); - readonly object _lock = new object(); + private readonly ConcurrentDictionary> _rules = new(); + private readonly ConcurrentDictionary> _areas = new(); + object _lock = new object(); public ForewarningCacheService(IKpiAreaRepository kpiAreaRepository, IWarningRepository warningRepository) { _kpiAreaRepository = kpiAreaRepository; _warningRepository = warningRepository; } - - - string WarningKey(int subSystemId, int customerId) => $"{GlobalConst.TrackWarningKeyPrefix}:{subSystemId}:{customerId}"; - string WarningKey() => $"{GlobalConst.TrackWarningKeyPrefix}:all"; + string WarningKey(int subSystemId, int customerId) => + $"{GlobalConst.TrackWarningKeyPrefix}:{subSystemId}:{customerId}"; - string AreaKey() - { - return $"{GlobalConst.TrackAreaKeyPrefix}"; - } + string AreaKey(int areaId) => $"{GlobalConst.TrackAreaKeyPrefix}:{areaId}"; - // 30w => 1w => local public async Task?> GetRulesAsync(int subSystemId, int customerId) { var key = WarningKey(subSystemId, customerId); - var rules = _rules.GetValueOrDefault(key); - - if (rules == null) - { - return await GetRules(subSystemId, customerId); - } - - return rules; + if( _rules.TryGetValue(key,out var rules)) + { + return rules; + } + + return await GetRules(subSystemId, customerId); } private List WriteRules(List rules) @@ -63,7 +54,7 @@ public class ForewarningCacheService: IForewarningCacheService, ISingletonDepend { areaList = GetAreaInfoByAreaId(item.AreaId!.Value); } - + var entry = new ForewarningRuleCacheEntry { Id = item.Id, @@ -74,13 +65,15 @@ public class ForewarningCacheService: IForewarningCacheService, ISingletonDepend StartTrackStatusId = item.StartTrackStatusId, EndTrackStatusId = item.EndTrackStatusId, CargoType = item.CargoType, - Months = string.IsNullOrEmpty(item.Months)?new List():item.Months.Split('/').Select(int.Parse).ToList(), + Months = string.IsNullOrEmpty(item.Months) + ? new List() + : item.Months.Split('/').Select(int.Parse).ToList(), IsTransit = item.IsTransit, Areas = areaList, ProductCode = item.ProductCode, LeavePort = item.LeavePort }; - + result.Add(entry); } @@ -89,64 +82,60 @@ public class ForewarningCacheService: IForewarningCacheService, ISingletonDepend private async Task?> GetRules(int systemId, int customerId) { - var key = WarningKey(); + var key = WarningKey(systemId, customerId); var result = RedisHelper.Client.Get>(key); if (result != null) { // 非线程安全,需要加锁 lock (_lock) { - _rules = result.GroupBy(val => WarningKey(val.SystemId, val.CustomerId)) - .ToDictionary(val => val.Key, val => WriteRules(val.ToList())); + if (_rules.TryGetValue(key, out var ruleResult)) + { + return ruleResult; + } - return _rules.GetValueOrDefault(WarningKey(systemId,customerId)); + ruleResult = WriteRules(result); + _rules.TryAdd(key,ruleResult); + return ruleResult; } } - + // 非线程安全,需要加锁 using (RedisHelper.Client.Lock(GlobalWaningLockKey, 20)) { - var ruleResult = _rules.GetValueOrDefault(WarningKey(systemId,customerId)); - if (ruleResult == null) - { - var warnings = await _warningRepository.GetAllWarningRule(); - if (warnings == null || warnings.Count == 0) - { - RedisHelper.Client.Set(key, new List(), TimeSpan.FromSeconds(20)); - return new List(); - } - - RedisHelper.Client.Set(key, warnings); - _rules = warnings.GroupBy(val => WarningKey(val.SystemId, val.CustomerId)) - .ToDictionary(val => val.Key, val => WriteRules(val.ToList())); + if (_rules.TryGetValue(key, out var ruleResult)) return ruleResult; - return _rules.GetValueOrDefault(WarningKey(systemId,customerId)); + var warnings = await _warningRepository.GetWarningRule(systemId, customerId); + if (warnings == null || warnings.Count == 0) + { + RedisHelper.Client.Set(key, new List(), TimeSpan.FromSeconds(20)); + return new List(); } + RedisHelper.Client.Set(key, warnings); + ruleResult = WriteRules(warnings); + _rules.TryAdd(key, ruleResult); return ruleResult; } - } - - // a b c del local cusomer 3 del redis cache pub del redis cache + public Task DeleteRulesAsync(int subSystemId, int customerId) { - _rules.Remove(WarningKey(subSystemId, customerId)); + _rules.Remove(WarningKey(subSystemId, customerId),out _); return Task.CompletedTask; } private List? GetAreaInfoByAreaId(int areaId) { - if (_areas == null || _areas.Count == 0) - { - var areas = GetAreas(); - if(areas==null || areas.Count==0) return null; - _areas = areas.GroupBy(x => x.AreaId) - .ToDictionary(x => x.Key, x => x.Select(KpiAreaInfoToEntry).ToList()); - } + if (_areas.TryGetValue(areaId, out var areaInfos)) return areaInfos; + + var areas = GetAreas(areaId); + if (areas == null || areas.Count == 0) return null; - return _areas.GetValueOrDefault(areaId); + areaInfos = areas.Select(KpiAreaInfoToEntry).ToList(); + _areas.TryAdd(areaId, areaInfos); + return areaInfos; } AreaCacheEntry KpiAreaInfoToEntry(KpiAreaInfo model) @@ -162,47 +151,74 @@ public class ForewarningCacheService: IForewarningCacheService, ISingletonDepend } /// - /// 获取所有区域 + /// 获取区域 /// /// - private List? GetAreas() + private List? GetAreas(int areaId) { - var key = AreaKey(); + var key = AreaKey(areaId); var result = RedisHelper.Client.Get>(key); if (result == null) { - using (RedisHelper.Client.Lock(GlobalKpiAreaLockKey, 20)) + result = RedisHelper.Client.Get>(key); + if (result != null) return new List(); + + var kpiArea = _kpiAreaRepository.GetAreaInfosByAreaId(areaId); + if (kpiArea == null) { - result = RedisHelper.Client.Get>(key); - if (result != null) return new List(); - - var kpiArea = _kpiAreaRepository.GetAllAreaInfos(); - if (kpiArea == null) - { - RedisHelper.Client.Set(key, new List(), TimeSpan.FromSeconds(20)); - return new List(); - } - - RedisHelper.Client.Set(key, kpiArea); - return kpiArea; + RedisHelper.Client.Set(key, new List(), TimeSpan.FromSeconds(20)); + return new List(); } + + RedisHelper.Client.Set(key, kpiArea); + return kpiArea; } return result; } - - public Task Clear() + + public Task Clear(ClearRulCacheEvent @event) { - _rules?.Clear(); - _areas?.Clear(); + foreach (var item in @event.Items) + { + DeleteRulesAsync(item.SystemId, item.CustomerId); + } return Task.CompletedTask; } - public Task ClearRedis() + public async Task ClearRedisBySystemIdAndCustomer(int subSystemId,int customerId) { - RedisHelper.Client.Del(WarningKey()); - RedisHelper.Client.Del(AreaKey()); - return Task.CompletedTask; + await RedisHelper.Client.DelAsync(WarningKey(subSystemId, customerId)); + var eventModel = new ClearRulCacheEvent + { + Items = new List() + { + new RuleAreaBaseInfo(subSystemId, customerId) + } + }; + + await PublishEvent(eventModel); } + public async Task ClearRedisByAreaId(int areaId) + { + var baseInfos = await _warningRepository.GetWarningRuleByAreaId(areaId); + if(baseInfos==null||baseInfos.Count==0) return; + baseInfos = baseInfos.DistinctBy(val => WarningKey(val.SystemId, val.CustomerId)).ToList(); + + var willDelKeys = baseInfos.Select(val => WarningKey(val.SystemId, val.CustomerId)).ToArray(); + await RedisHelper.Client.DelAsync(willDelKeys); + await RedisHelper.Client.DelAsync(AreaKey(areaId)); + var eventModel = new ClearRulCacheEvent + { + Items = baseInfos + }; + + await PublishEvent(eventModel); + } + + private async Task PublishEvent(ClearRulCacheEvent @event) + { + await RedisHelper.Client.PublishAsync(GlobalConst.PublishNotifyChannelKey, @event.ToJson()); + } } \ No newline at end of file diff --git a/src/SJZY.Track.Application/Services/ForewarningServices/KpiAreaService.cs b/src/SJZY.Track.Application/Services/ForewarningServices/KpiAreaService.cs index 3b693a1c..354e177d 100644 --- a/src/SJZY.Track.Application/Services/ForewarningServices/KpiAreaService.cs +++ b/src/SJZY.Track.Application/Services/ForewarningServices/KpiAreaService.cs @@ -42,8 +42,7 @@ public class KpiAreaService : ApplicationService { var ruleAreaEntity = ObjectMapper.Map(input); var ruleInfoId = await _ruleAreaRepository.AddAreaInfoAsync(ruleAreaEntity); - await _forewarningCacheService.ClearRedis(); - await RedisHelper.Client.PublishAsync(GlobalConst.PublishNotifyChannelKey, "clear local cache"); + await _forewarningCacheService.ClearRedisByAreaId(input.AreaId); return new UnifyResultDto(true, ruleInfoId, "添加成功"); } @@ -56,8 +55,7 @@ public class KpiAreaService : ApplicationService public async ValueTask DeleteKpiArea(int id) { await _ruleAreaRepository.DeleteArea(id); - await _forewarningCacheService.ClearRedis(); - await RedisHelper.Client.PublishAsync(GlobalConst.PublishNotifyChannelKey, "clear local cache"); + await _forewarningCacheService.ClearRedisByAreaId(id); return new UnifyResultDto(true, "删除成功"); } @@ -69,9 +67,10 @@ public class KpiAreaService : ApplicationService [HttpDelete("KpiArea/DeleteKpiAreaInfo")] public async ValueTask DeleteKpiAreaInfo(int id) { + var info=await _ruleAreaRepository.GetAreaInfoById(id); + if (info == null) return new UnifyResultDto(false,"未找到该区域信息"); await _ruleAreaRepository.DeleteAreaInfo(id); - await _forewarningCacheService.ClearRedis(); - await RedisHelper.Client.PublishAsync(GlobalConst.PublishNotifyChannelKey, "clear local cache"); + await _forewarningCacheService.ClearRedisByAreaId(info.AreaId); return new UnifyResultDto(true, "删除成功"); } @@ -113,7 +112,7 @@ public class KpiAreaService : ApplicationService var ruleAreaEntity = ObjectMapper.Map(input, areaInfo); await _ruleAreaRepository.UpdateAreaInfoAsync(ruleAreaEntity); - await _forewarningCacheService.ClearRedis(); + await _forewarningCacheService.ClearRedisByAreaId(areaInfo.AreaId); await RedisHelper.Client.PublishAsync(GlobalConst.PublishNotifyChannelKey, "clear local cache"); return new UnifyResultDto(true, "更新成功"); } diff --git a/src/SJZY.Track.Domain/Aggregates/ForewarningAggregate/IWarningRepository.cs b/src/SJZY.Track.Domain/Aggregates/ForewarningAggregate/IWarningRepository.cs index a864c05a..e06ea6ae 100644 --- a/src/SJZY.Track.Domain/Aggregates/ForewarningAggregate/IWarningRepository.cs +++ b/src/SJZY.Track.Domain/Aggregates/ForewarningAggregate/IWarningRepository.cs @@ -64,6 +64,13 @@ public interface IWarningRepository : IBasicRepository /// 获取预警规则列表 /// /// - Task?> GetAllWarningRule(); + Task?> GetWarningRule(int systemId, int customerId); + + /// + /// 获取预警规则基础信息 + /// + /// + /// + Task?> GetWarningRuleByAreaId(int areaId); } \ No newline at end of file diff --git a/src/SJZY.Track.Domain/Aggregates/KPIAreaAggregate/IKpiAreaRepository.cs b/src/SJZY.Track.Domain/Aggregates/KPIAreaAggregate/IKpiAreaRepository.cs index 21d15342..3b3a00b0 100644 --- a/src/SJZY.Track.Domain/Aggregates/KPIAreaAggregate/IKpiAreaRepository.cs +++ b/src/SJZY.Track.Domain/Aggregates/KPIAreaAggregate/IKpiAreaRepository.cs @@ -64,5 +64,5 @@ public interface IKpiAreaRepository : IBasicRepository /// 获取所有区域信息 /// /// - List? GetAllAreaInfos(); + List? GetAreaInfosByAreaId(int areaId); } \ No newline at end of file diff --git a/src/SJZY.Track.Domain/Dtos/Forewarning/RuleAreaBaseInfo.cs b/src/SJZY.Track.Domain/Dtos/Forewarning/RuleAreaBaseInfo.cs new file mode 100644 index 00000000..a8fef8d3 --- /dev/null +++ b/src/SJZY.Track.Domain/Dtos/Forewarning/RuleAreaBaseInfo.cs @@ -0,0 +1,3 @@ +namespace SJZY.Track.Domain.Dtos.Forewarning; + +public record RuleAreaBaseInfo(int SystemId, int CustomerId); \ No newline at end of file diff --git a/src/SJZY.Track.Repository/Repositories/KpiAreaRepository.cs b/src/SJZY.Track.Repository/Repositories/KpiAreaRepository.cs index e2008c27..3eb4a25d 100644 --- a/src/SJZY.Track.Repository/Repositories/KpiAreaRepository.cs +++ b/src/SJZY.Track.Repository/Repositories/KpiAreaRepository.cs @@ -94,17 +94,19 @@ public class KpiAreaRepository : BasicRepository, IKpiAreaReposi /// 获取所有区域信息 /// /// - public List GetAllAreaInfos() + public List GetAreaInfosByAreaId(int areaId) { return Context.Queryable() - .Select((x)=>new KpiAreaInfo + .InnerJoin((r,i)=>r.AreaId==i.Id) + .Where((r,i)=>r.AreaId==areaId) + .Select((r,i)=>new KpiAreaInfo { - AreaId = x.AreaId, - Country = x.CountryCode, - LeftPostcode = x.LeftPostcode, - City = x.City, - Province = x.Province, - RightPostcode = x.RightPostcode + AreaId = r.AreaId, + Country = r.CountryCode, + LeftPostcode = r.LeftPostcode, + City = r.City, + Province = r.Province, + RightPostcode = r.RightPostcode }) .ToList(); } diff --git a/src/SJZY.Track.Repository/Repositories/WarningRepository.cs b/src/SJZY.Track.Repository/Repositories/WarningRepository.cs index 23286a59..61463fe0 100644 --- a/src/SJZY.Track.Repository/Repositories/WarningRepository.cs +++ b/src/SJZY.Track.Repository/Repositories/WarningRepository.cs @@ -110,11 +110,11 @@ public class WarningRepository : BasicRepository, IWarningReposit return warning; } - public Task?> GetAllWarningRule() + public Task?> GetWarningRule(int systemId, int customerId) { return Context.Queryable() .InnerJoin((a, b) => a.WarningId == b.Id) - .Where((a,b)=>b.IsEnable) + .Where((a,b)=>b.IsEnable && b.SubSystemId == systemId && b.CustomerId == customerId) .Select((a,b)=>new WarningRuleCacheDto { Id = a.Id, @@ -135,4 +135,14 @@ public class WarningRepository : BasicRepository, IWarningReposit }) .ToListAsync(); } + + public Task?> GetWarningRuleByAreaId(int areaId) + { + return Context.Queryable() + .InnerJoin((a, b) => a.WarningId == b.Id) + .Where((a,b)=>b.IsEnable && a.AreaId==areaId) + .Select((a,b)=>new RuleAreaBaseInfo(b.CustomerId,b.SubSystemId)) + .ToListAsync(); + } + } \ No newline at end of file