diff --git a/src/AgileConfig.Server.Apisite/AgileConfig.Server.Apisite.csproj b/src/AgileConfig.Server.Apisite/AgileConfig.Server.Apisite.csproj index ea552e2a..3d9585a5 100644 --- a/src/AgileConfig.Server.Apisite/AgileConfig.Server.Apisite.csproj +++ b/src/AgileConfig.Server.Apisite/AgileConfig.Server.Apisite.csproj @@ -3,11 +3,11 @@ net8.0 InProcess - 1.9.15 - 1.9.15 - 1.9.15 + 1.10.0 + 1.10.0 + 1.10.0 Linux - 1.9.15 + 1.10.0 kklldog kklldog diff --git a/src/AgileConfig.Server.Apisite/Controllers/api/ConfigController.cs b/src/AgileConfig.Server.Apisite/Controllers/api/ConfigController.cs index 31f47b90..802d7b0a 100644 --- a/src/AgileConfig.Server.Apisite/Controllers/api/ConfigController.cs +++ b/src/AgileConfig.Server.Apisite/Controllers/api/ConfigController.cs @@ -9,6 +9,7 @@ using AgileConfig.Server.Apisite.Models.Mapping; using AgileConfig.Server.Data.Entity; using AgileConfig.Server.IService; +using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Caching.Memory; @@ -60,24 +61,32 @@ public async Task>> GetAppConfig(string appId, [F } var cacheKey = $"ConfigController_AppConfig_{appId}_{env.Value}"; - List configs = null; - _cacheMemory?.TryGetValue(cacheKey, out configs); - if (configs != null) + AppConfigsCache cache = null; + _cacheMemory?.TryGetValue(cacheKey, out cache); + + if (cache == null) { - return configs; - } + cache = new AppConfigsCache(); - var appConfigs = await _configService.GetPublishedConfigsByAppIdWithInheritanced(appId, env.Value); - var vms = appConfigs.Select(x => x.ToApiConfigVM()).ToList(); + var publishTimelineId = await _configService.GetLastPublishTimelineVirtualIdAsync(appId, env.Value); + var appConfigs = await _configService.GetPublishedConfigsByAppIdWithInheritance(appId, env.Value); + var vms = appConfigs.Select(x => x.ToApiConfigVM()).ToList(); - //增加5s的缓存,防止同一个app同时启动造成db的压力过大 - var cacheOp = new MemoryCacheEntryOptions() - .SetAbsoluteExpiration(TimeSpan.FromSeconds(5)); - _cacheMemory?.Set(cacheKey, vms, cacheOp); + cache.Key = cacheKey; + cache.Configs = vms; + cache.VirtualId = publishTimelineId; + + //cache 5 seconds to avoid too many db query + var cacheOp = new MemoryCacheEntryOptions() + .SetAbsoluteExpiration(TimeSpan.FromSeconds(5)); + _cacheMemory?.Set(cacheKey, cache, cacheOp); + } + + Response?.Headers?.Append("publish-time-line-id", cache.VirtualId); _meterService.PullAppConfigCounter?.Add(1, new("appId", appId), new("env", env)); - return vms; + return cache.Configs; } /// diff --git a/src/AgileConfig.Server.Apisite/Controllers/api/Models/ApiConfigVM.cs b/src/AgileConfig.Server.Apisite/Controllers/api/Models/ApiConfigVM.cs index ee21cc94..ced6e0d4 100644 --- a/src/AgileConfig.Server.Apisite/Controllers/api/Models/ApiConfigVM.cs +++ b/src/AgileConfig.Server.Apisite/Controllers/api/Models/ApiConfigVM.cs @@ -1,9 +1,6 @@ -using AgileConfig.Server.Apisite.Models; +using System.Collections.Generic; +using AgileConfig.Server.Apisite.Models; using AgileConfig.Server.Data.Entity; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; namespace AgileConfig.Server.Apisite.Controllers.api.Models { @@ -53,6 +50,16 @@ public class ApiConfigVM : IAppIdModel public string Description { get; set; } } + + public class AppConfigsCache + { + public string Key { get; set; } + + public string VirtualId { get; set; } + + public List Configs { get; set; } + } + public static class ApiConfigVMExtension { public static ConfigVM ToConfigVM(this ApiConfigVM model) diff --git a/src/AgileConfig.Server.Apisite/Websocket/MessageHandlers/MessageHandler.cs b/src/AgileConfig.Server.Apisite/Websocket/MessageHandlers/MessageHandler.cs index 833ef1dd..53440cf6 100644 --- a/src/AgileConfig.Server.Apisite/Websocket/MessageHandlers/MessageHandler.cs +++ b/src/AgileConfig.Server.Apisite/Websocket/MessageHandlers/MessageHandler.cs @@ -19,17 +19,19 @@ internal class MessageHandler : IMessageHandler private readonly IConfigService _configService; private readonly IRegisterCenterService _registerCenterService; private readonly IServiceInfoService _serviceInfoService; - + + private int ClientVersion { get; set; } + public MessageHandler( - IConfigService configService, - IRegisterCenterService registerCenterService, + IConfigService configService, + IRegisterCenterService registerCenterService, IServiceInfoService serviceInfoService) { _configService = configService; _registerCenterService = registerCenterService; _serviceInfoService = serviceInfoService; } - + public bool Hit(HttpRequest request) { var ver = request.Headers["client-v"]; @@ -40,6 +42,8 @@ public bool Hit(HttpRequest request) if (int.TryParse(ver.ToString().Replace(".", ""), out var verInt)) { + ClientVersion = verInt; + return verInt >= 160; } @@ -63,12 +67,14 @@ public async Task Handle(string message, HttpRequest request, WebsocketClient cl appId = HttpUtility.UrlDecode(appId); var env = request.Headers["env"].ToString(); ISettingService.IfEnvEmptySetDefault(ref env); - var md5 = await _configService.AppPublishedConfigsMd5CacheWithInheritanced(appId, env); + + var data = await GetCPingData(appId, env); + await SendMessage(client.Client, JsonConvert.SerializeObject(new WebsocketAction() { Action = ActionConst.Ping, Module = ActionModule.ConfigCenter, - Data = md5 + Data = data })); } else if (message.StartsWith("s:ping:")) @@ -98,4 +104,22 @@ await SendMessage(client.Client, JsonConvert.SerializeObject(new WebsocketAction await SendMessage(client.Client, "0"); } } + + private async Task GetCPingData(string appId, string env) + { + if (ClientVersion <= 176) + { + // 1.7.6及以前的版本,返回V:md5 + var md5 = await _configService.AppPublishedConfigsMd5CacheWithInheritance(appId, env); + + return md5; + } + else + { + // 1.7.7及以后的版本,返回 publish time line id + var publishTimeLineId = await _configService.GetLastPublishTimelineVirtualIdAsyncWithCache(appId, env); + + return publishTimeLineId; + } + } } \ No newline at end of file diff --git a/src/AgileConfig.Server.Apisite/Websocket/MessageHandlers/OldMessageHandler.cs b/src/AgileConfig.Server.Apisite/Websocket/MessageHandlers/OldMessageHandler.cs index e7d56f37..e2fe38f4 100644 --- a/src/AgileConfig.Server.Apisite/Websocket/MessageHandlers/OldMessageHandler.cs +++ b/src/AgileConfig.Server.Apisite/Websocket/MessageHandlers/OldMessageHandler.cs @@ -46,7 +46,7 @@ public async Task Handle(string message, HttpRequest request, WebsocketClient cl var appId = request.Headers["appid"]; var env = request.Headers["env"].ToString(); env = ISettingService.IfEnvEmptySetDefault(ref env); - var md5 = await _configService.AppPublishedConfigsMd5CacheWithInheritanced(appId, env); + var md5 = await _configService.AppPublishedConfigsMd5CacheWithInheritance(appId, env); await SendMessage(client.Client, $"V:{md5}"); } else diff --git a/src/AgileConfig.Server.Data.Abstraction/IPublishTimelineRepository.cs b/src/AgileConfig.Server.Data.Abstraction/IPublishTimelineRepository.cs index 8c6154fb..606115fd 100644 --- a/src/AgileConfig.Server.Data.Abstraction/IPublishTimelineRepository.cs +++ b/src/AgileConfig.Server.Data.Abstraction/IPublishTimelineRepository.cs @@ -4,5 +4,6 @@ namespace AgileConfig.Server.Data.Abstraction { public interface IPublishTimelineRepository : IRepository { + Task GetLastPublishTimelineNodeIdAsync(string appId, string env); } } diff --git a/src/AgileConfig.Server.Data.Repository.Freesql/PublishTimelineRepository.cs b/src/AgileConfig.Server.Data.Repository.Freesql/PublishTimelineRepository.cs index d9ac0b93..4361f343 100644 --- a/src/AgileConfig.Server.Data.Repository.Freesql/PublishTimelineRepository.cs +++ b/src/AgileConfig.Server.Data.Repository.Freesql/PublishTimelineRepository.cs @@ -12,5 +12,15 @@ public PublishTimelineRepository(IFreeSql freeSql) : base(freeSql) { this.freeSql = freeSql; } + + public async Task GetLastPublishTimelineNodeIdAsync(string appId, string env) + { + var node = await freeSql.Select() + .Where(x => x.AppId == appId && x.Env == env) + .OrderByDescending(x => x.Version) + .FirstAsync(); + + return node?.Id; + } } } diff --git a/src/AgileConfig.Server.Data.Repository.Mongodb/PublishTimelineRepository.cs b/src/AgileConfig.Server.Data.Repository.Mongodb/PublishTimelineRepository.cs index 402b1247..531036e6 100644 --- a/src/AgileConfig.Server.Data.Repository.Mongodb/PublishTimelineRepository.cs +++ b/src/AgileConfig.Server.Data.Repository.Mongodb/PublishTimelineRepository.cs @@ -1,6 +1,7 @@ -namespace AgileConfig.Server.Data.Repository.Mongodb; + +namespace AgileConfig.Server.Data.Repository.Mongodb; -public class PublishTimelineRepository: MongodbRepository, IPublishTimelineRepository +public class PublishTimelineRepository : MongodbRepository, IPublishTimelineRepository { public PublishTimelineRepository(string? connectionString) : base(connectionString) { @@ -10,4 +11,11 @@ public PublishTimelineRepository(string? connectionString) : base(connectionStri public PublishTimelineRepository(IConfiguration configuration) : base(configuration) { } + + public async Task GetLastPublishTimelineNodeIdAsync(string appId, string env) + { + var nodes = await this.QueryPageAsync(x => x.AppId == appId && x.Env == env, 1, 1, nameof(PublishTimeline.Version), "DESC"); + + return nodes?.FirstOrDefault()?.Id; + } } \ No newline at end of file diff --git a/src/AgileConfig.Server.IService/IConfigService.cs b/src/AgileConfig.Server.IService/IConfigService.cs index 2e7dd22c..302cf5de 100644 --- a/src/AgileConfig.Server.IService/IConfigService.cs +++ b/src/AgileConfig.Server.IService/IConfigService.cs @@ -36,13 +36,13 @@ public interface IConfigService: IDisposable /// /// /// - Task> GetPublishedConfigsByAppIdWithInheritanced(string appId, string env); + Task> GetPublishedConfigsByAppIdWithInheritance(string appId, string env); /// /// 获取app的配置项继承的app配置合并进来转换为字典 /// /// /// - Task> GetPublishedConfigsByAppIdWithInheritanced_Dictionary(string appId, string env); + Task> GetPublishedConfigsByAppIdWithInheritance_Dictionary(string appId, string env); Task AddAsync(Config config, string env); Task AddRangeAsync(List configs, string env); @@ -77,7 +77,7 @@ public interface IConfigService: IDisposable /// /// /// - Task AppPublishedConfigsMd5WithInheritanced(string appId, string env); + Task AppPublishedConfigsMd5WithInheritance(string appId, string env); /// /// 计算已发布配置项的MD5进行缓存 @@ -91,7 +91,7 @@ public interface IConfigService: IDisposable /// /// /// - Task AppPublishedConfigsMd5CacheWithInheritanced(string appId, string env); + Task AppPublishedConfigsMd5CacheWithInheritance(string appId, string env); /// /// 构造key @@ -213,5 +213,10 @@ public interface IConfigService: IDisposable /// clear all cache /// void ClearCache(); + + Task GetLastPublishTimelineVirtualIdAsync(string appId, string env); + + Task GetLastPublishTimelineVirtualIdAsyncWithCache(string appId, string env); + } } diff --git a/src/AgileConfig.Server.Service/ConfigService.cs b/src/AgileConfig.Server.Service/ConfigService.cs index d5dd4632..4ee3cbae 100644 --- a/src/AgileConfig.Server.Service/ConfigService.cs +++ b/src/AgileConfig.Server.Service/ConfigService.cs @@ -308,9 +308,14 @@ private string AppPublishedConfigsMd5CacheKey(string appId, string env) return $"ConfigService_AppPublishedConfigsMd5Cache_{appId}_{env}"; } - private string AppPublishedConfigsMd5CacheKeyWithInheritanced(string appId, string env) + private string AppPublishedConfigsMd5CacheKeyWithInheritance(string appId, string env) { - return $"ConfigService_AppPublishedConfigsMd5CacheWithInheritanced_{appId}_{env}"; + return $"ConfigService_AppPublishedConfigsMd5CacheWithInheritance_{appId}_{env}"; + } + + private string AppPublishTimelineVirtualIdCacheKey(string appId, string env) + { + return $"ConfigService_AppPublishTimelineVirtualIdWithCache_{appId}_{env}"; } private void ClearAppPublishedConfigsMd5Cache(string appId, string env) @@ -319,9 +324,15 @@ private void ClearAppPublishedConfigsMd5Cache(string appId, string env) _memoryCache?.Remove(cacheKey); } - private void ClearAppPublishedConfigsMd5CacheWithInheritanced(string appId, string env) + private void ClearAppPublishedConfigsMd5CacheWithInheritance(string appId, string env) + { + var cacheKey = AppPublishedConfigsMd5CacheKeyWithInheritance(appId, env); + _memoryCache?.Remove(cacheKey); + } + + private void ClearAppPublishTimelineVirtualIdCache(string appId, string env) { - var cacheKey = AppPublishedConfigsMd5CacheKeyWithInheritanced(appId, env); + var cacheKey = AppPublishTimelineVirtualIdCacheKey(appId, env); _memoryCache?.Remove(cacheKey); } @@ -338,8 +349,10 @@ public async Task AddRangeAsync(List configs, string env) using var repository = _configRepositoryAccessor(env); await repository.InsertAsync(configs); - ClearAppPublishedConfigsMd5Cache(configs.First().AppId, env); - ClearAppPublishedConfigsMd5CacheWithInheritanced(configs.First().AppId, env); + var appId = configs.First().AppId; + ClearAppPublishedConfigsMd5Cache(appId,env); + ClearAppPublishedConfigsMd5CacheWithInheritance(appId, env); + ClearAppPublishTimelineVirtualIdCache(appId, env); return true; } @@ -349,9 +362,9 @@ public async Task AddRangeAsync(List configs, string env) /// /// /// - public async Task> GetPublishedConfigsByAppIdWithInheritanced(string appId, string env) + public async Task> GetPublishedConfigsByAppIdWithInheritance(string appId, string env) { - var configs = await GetPublishedConfigsByAppIdWithInheritanced_Dictionary(appId, env); + var configs = await GetPublishedConfigsByAppIdWithInheritance_Dictionary(appId, env); return configs.Values.ToList(); } @@ -361,7 +374,7 @@ public async Task> GetPublishedConfigsByAppIdWithInheritanced(strin /// /// /// - public async Task> GetPublishedConfigsByAppIdWithInheritanced_Dictionary( + public async Task> GetPublishedConfigsByAppIdWithInheritance_Dictionary( string appId, string env) { var apps = new List(); @@ -401,9 +414,9 @@ public async Task> GetPublishedConfigsByAppIdWithInhe return configs; } - public async Task AppPublishedConfigsMd5WithInheritanced(string appId, string env) + public async Task AppPublishedConfigsMd5WithInheritance(string appId, string env) { - var configs = await GetPublishedConfigsByAppIdWithInheritanced(appId, env); + var configs = await GetPublishedConfigsByAppIdWithInheritance(appId, env); var keyStr = string.Join('&', configs.Select(c => GenerateKey(c)).ToArray().OrderBy(k => k, StringComparer.Ordinal)); var valStr = string.Join('&', configs.Select(c => c.Value).ToArray().OrderBy(v => v, StringComparer.Ordinal)); @@ -417,15 +430,15 @@ public async Task AppPublishedConfigsMd5WithInheritanced(string appId, s /// /// /// - public async Task AppPublishedConfigsMd5CacheWithInheritanced(string appId, string env) + public async Task AppPublishedConfigsMd5CacheWithInheritance(string appId, string env) { - var cacheKey = AppPublishedConfigsMd5CacheKeyWithInheritanced(appId, env); + var cacheKey = AppPublishedConfigsMd5CacheKeyWithInheritance(appId, env); if (_memoryCache != null && _memoryCache.TryGetValue(cacheKey, out string md5)) { return md5; } - md5 = await AppPublishedConfigsMd5WithInheritanced(appId, env); + md5 = await AppPublishedConfigsMd5WithInheritance(appId, env); var cacheOp = new MemoryCacheEntryOptions() .SetAbsoluteExpiration(TimeSpan.FromSeconds(60)); @@ -614,7 +627,8 @@ public void Dispose() await uow?.SaveChangesAsync(); ClearAppPublishedConfigsMd5Cache(appId, env); - ClearAppPublishedConfigsMd5CacheWithInheritanced(appId, env); + ClearAppPublishedConfigsMd5CacheWithInheritance(appId, env); + ClearAppPublishTimelineVirtualIdCache(appId, env); return (true, publishTimelineNode.Id); } @@ -777,7 +791,8 @@ public async Task RollbackAsync(string publishTimelineId, string env) await uow?.SaveChangesAsync(); ClearAppPublishedConfigsMd5Cache(appId, env); - ClearAppPublishedConfigsMd5CacheWithInheritanced(appId, env); + ClearAppPublishedConfigsMd5CacheWithInheritance(appId, env); + ClearAppPublishTimelineVirtualIdCache(appId,env); return true; } @@ -1077,5 +1092,61 @@ public async Task SaveKvListAsync(string kvString, string appId, string en return ("", key); } + + /// + /// Generate the virtual id representing the last publish timeline node of the app and its inherited apps. + /// + /// + /// + /// + public async Task GetLastPublishTimelineVirtualIdAsync(string appId, string env) + { + using var publishTimelineRepository = _publishTimelineRepositoryAccsssor(env); + + var apps = new List(); + var inheritanceApps = await _appService.GetInheritancedAppsAsync(appId); + for (int i = 0; i < inheritanceApps.Count; i++) + { + if (inheritanceApps[i].Enabled) + { + apps.Add(inheritanceApps[i].Id as string); //后继承的排在后面 + } + } + + apps.Add(appId); //本应用放在最后 + + var ids = new List(); + + foreach (var app in apps) + { + var id = await publishTimelineRepository.GetLastPublishTimelineNodeIdAsync(app, env); + ids.Add(id); + } + + return string.Join('|', ids); + } + + /// + /// Generate the virtual id representing the last publish timeline node of the app and its inherited apps, with cache. + /// + /// + /// + /// + public async Task GetLastPublishTimelineVirtualIdAsyncWithCache(string appId, string env) + { + var cacheKey = AppPublishTimelineVirtualIdCacheKey(appId, env); + if (_memoryCache != null && _memoryCache.TryGetValue(cacheKey, out string vId)) + { + return vId; + } + + vId = await GetLastPublishTimelineVirtualIdAsync(appId, env); + + var cacheOp = new MemoryCacheEntryOptions() + .SetAbsoluteExpiration(TimeSpan.FromSeconds(60)); + _memoryCache?.Set(cacheKey, vId, cacheOp); + + return vId; + } } } \ No newline at end of file diff --git a/test/AgileConfig.Server.ServiceTests/sqlite/ConfigServiceTests.cs b/test/AgileConfig.Server.ServiceTests/sqlite/ConfigServiceTests.cs index 91eff636..179d2b4f 100644 --- a/test/AgileConfig.Server.ServiceTests/sqlite/ConfigServiceTests.cs +++ b/test/AgileConfig.Server.ServiceTests/sqlite/ConfigServiceTests.cs @@ -860,7 +860,7 @@ public async Task GetPublishedConfigsByAppIdWithInheritanced_DictionaryTest() await _service.Publish(app1.Id, new string[] { }, "", "", env); await _service.Publish(app.Id, new string[] { }, "", "", env); - var dict = await _service.GetPublishedConfigsByAppIdWithInheritanced_Dictionary(app.Id, env); + var dict = await _service.GetPublishedConfigsByAppIdWithInheritance_Dictionary(app.Id, env); Assert.IsNotNull(dict); Assert.AreEqual(4, dict.Keys.Count); @@ -907,7 +907,7 @@ public async Task GetPublishedConfigsByAppIdWithInheritanced_DictionaryTest() await _service.Publish(app1.Id, new string[] { }, "", "", env); await _service.Publish(app.Id, new string[] { }, "", "", env); - dict = await _service.GetPublishedConfigsByAppIdWithInheritanced_Dictionary(app.Id, env); + dict = await _service.GetPublishedConfigsByAppIdWithInheritance_Dictionary(app.Id, env); Assert.IsNotNull(dict); Assert.AreEqual(5, dict.Keys.Count); @@ -949,7 +949,7 @@ public async Task GetPublishedConfigsByAppIdWithInheritanced_DictionaryTest() await _service.AddAsync(source6, env); await _service.Publish(app2.Id, new string[] { }, "", "", env); // 发布app 003 - dict = await _service.GetPublishedConfigsByAppIdWithInheritanced_Dictionary(app.Id, env); + dict = await _service.GetPublishedConfigsByAppIdWithInheritance_Dictionary(app.Id, env); Assert.IsNotNull(dict); Assert.AreEqual(4, dict.Keys.Count); diff --git a/test/ApiSiteTests/TestApiConfigController.cs b/test/ApiSiteTests/TestApiConfigController.cs index 23ffc691..f1b387db 100644 --- a/test/ApiSiteTests/TestApiConfigController.cs +++ b/test/ApiSiteTests/TestApiConfigController.cs @@ -56,7 +56,7 @@ List newConfigs() var configService = new Mock(); //configService.Setup(s => s.GetPublishedConfigsAsync("001")) // .ReturnsAsync(newConfigs); - configService.Setup(s => s.GetPublishedConfigsByAppIdWithInheritanced(It.IsAny(), It.IsAny())) + configService.Setup(s => s.GetPublishedConfigsByAppIdWithInheritance(It.IsAny(), It.IsAny())) .ReturnsAsync(newConfigs); IMemoryCache memoryCache = null;