using AgileConfig.Server.Data.Entity; using AgileConfig.Server.IService; using System; using System.Collections.Generic; using System.Threading.Tasks; using System.Linq; using Microsoft.Extensions.Caching.Memory; using AgileConfig.Server.Data.Freesql; using AgileConfig.Server.Common; namespace AgileConfig.Server.Service { public class ConfigService : IConfigService { private readonly FreeSqlContext _dbContext; private readonly IMemoryCache _memoryCache; private readonly IAppService _appService; public ConfigService(FreeSqlContext context, IMemoryCache memoryCache, IAppService appService) { _dbContext = context; _memoryCache = memoryCache; _appService = appService; } public async Task AddAsync(Config config) { await _dbContext.Configs.AddAsync(config); int x = await _dbContext.SaveChangesAsync(); var result = x > 0; return result; } public async Task UpdateAsync(Config config) { await _dbContext.UpdateAsync(config); var x = await _dbContext.SaveChangesAsync(); var result = x > 0; return result; } public async Task UpdateAsync(List configs) { await _dbContext.UpdateRangeAsync(configs); var x = await _dbContext.SaveChangesAsync(); var result = x > 0; return result; } public async Task CancelEdit(List ids) { foreach (var configId in ids) { var config = await _dbContext.Configs.Where(c => c.Id == configId).ToOneAsync(); ; if (config == null) { throw new Exception("Can not find config by id " + configId); } if (config.EditStatus == EditStatus.Commit) { continue; } if (config.EditStatus == EditStatus.Add) { await _dbContext.Configs.RemoveAsync(x => x.Id == configId); } if (config.EditStatus == EditStatus.Deleted || config.EditStatus == EditStatus.Edit) { config.OnlineStatus = OnlineStatus.Online; config.EditStatus = EditStatus.Commit; config.UpdateTime = DateTime.Now; var publishedConfig = await GetPublishedConfigAsync(configId); if (publishedConfig == null) { // throw new Exception("Can not find published config by id " + configId); } else { //reset value config.Value = publishedConfig.Value; config.OnlineStatus = OnlineStatus.Online; } await _dbContext.Configs.UpdateAsync(config); } } var result = await _dbContext.SaveChangesAsync(); return result > 0; } public async Task DeleteAsync(Config config) { config = await _dbContext.Configs.Where(c => c.Id == config.Id).ToOneAsync(); if (config != null) { _dbContext.Configs.Remove(config); } int x = await _dbContext.SaveChangesAsync(); var result = x > 0; return result; } public async Task DeleteAsync(string configId) { var config = await _dbContext.Configs.Where(c => c.Id == configId).ToOneAsync(); if (config != null) { _dbContext.Configs.Remove(config); } int x = await _dbContext.SaveChangesAsync(); var result = x > 0; return result; } public async Task GetAsync(string id) { var config = await _dbContext.Configs.Where(c => c.Id == id).ToOneAsync(); return config; } public async Task> GetAllConfigsAsync() { return await _dbContext.Configs.Where(c => c.Status == ConfigStatus.Enabled).ToListAsync(); } public async Task GetByAppIdKey(string appId, string group, string key) { var q = _dbContext.Configs.Where(c => c.AppId == appId && c.Key == key && c.Status == ConfigStatus.Enabled ); if (string.IsNullOrEmpty(group)) { q = q.Where(c => c.Group == "" || c.Group == null); } else { q = q.Where(c => c.Group == group); } return await q.FirstAsync(); } public async Task> GetByAppIdAsync(string appId) { return await _dbContext.Configs.Where(c => c.AppId == appId && c.Status == ConfigStatus.Enabled ).ToListAsync(); } public async Task> Search(string appId, string group, string key) { var q = _dbContext.Configs.Where(c => c.Status == ConfigStatus.Enabled); if (!string.IsNullOrEmpty(appId)) { q = q.Where(c => c.AppId == appId); } if (!string.IsNullOrEmpty(group)) { q = q.Where(c => c.Group.Contains(group)); } if (!string.IsNullOrEmpty(key)) { q = q.Where(c => c.Key.Contains(key)); } return await q.ToListAsync(); } public async Task CountEnabledConfigsAsync() { var q = await _dbContext.Configs.Where(c => c.Status == ConfigStatus.Enabled).CountAsync(); return (int)q; } public string GenerateKey(Config config) { if (string.IsNullOrEmpty(config.Group)) { return config.Key; } return $"{config.Group}:{config.Key}"; } public string GenerateKey(ConfigPublished config) { if (string.IsNullOrEmpty(config.Group)) { return config.Key; } return $"{config.Group}:{config.Key}"; } /// /// 获取当前app的配置集合的md5版本 /// /// /// public async Task AppPublishedConfigsMd5(string appId) { var configs = await _dbContext.ConfigPublished.Where(c => c.AppId == appId && c.Status == ConfigStatus.Enabled ).ToListAsync(); var keyStr = string.Join('&', configs.Select(c => GenerateKey(c)).ToArray().OrderBy(k => k)); var valStr = string.Join('&', configs.Select(c => c.Value).ToArray().OrderBy(v => v)); var txt = $"{keyStr}&{valStr}"; return Encrypt.Md5(txt); } /// /// 获取当前app的配置集合的md5版本,1分钟缓存 /// /// /// public async Task AppPublishedConfigsMd5Cache(string appId) { var cacheKey = AppPublishedConfigsMd5CacheKey(appId); if (_memoryCache != null && _memoryCache.TryGetValue(cacheKey, out string md5)) { return md5; } md5 = await AppPublishedConfigsMd5(appId); var cacheOp = new MemoryCacheEntryOptions() .SetAbsoluteExpiration(TimeSpan.FromSeconds(60)); _memoryCache?.Set(cacheKey, md5, cacheOp); return md5; } private string AppPublishedConfigsMd5CacheKey(string appId) { return $"ConfigService_AppPublishedConfigsMd5Cache_{appId}"; } private string AppPublishedConfigsMd5CacheKeyWithInheritanced(string appId) { return $"ConfigService_AppPublishedConfigsMd5CacheWithInheritanced_{appId}"; } private void ClearAppPublishedConfigsMd5Cache(string appId) { var cacheKey = AppPublishedConfigsMd5CacheKey(appId); _memoryCache?.Remove(cacheKey); } private void ClearAppPublishedConfigsMd5CacheWithInheritanced(string appId) { var cacheKey = AppPublishedConfigsMd5CacheKeyWithInheritanced(appId); _memoryCache?.Remove(cacheKey); } public async Task AddRangeAsync(List configs) { await _dbContext.Configs.AddRangeAsync(configs); int x = await _dbContext.SaveChangesAsync(); var result = x > 0; if (result) { ClearAppPublishedConfigsMd5Cache(configs.First().AppId); ClearAppPublishedConfigsMd5CacheWithInheritanced(configs.First().AppId); } return result; } /// /// 获取app的配置项继承的app配置合并进来 /// /// /// public async Task> GetPublishedConfigsByAppIdWithInheritanced(string appId) { var configs = await GetPublishedConfigsByAppIdWithInheritanced_Dictionary(appId); return configs.Values.ToList(); } /// /// 获取app的配置项继承的app配置合并进来转换为字典 /// /// /// public async Task> GetPublishedConfigsByAppIdWithInheritanced_Dictionary(string appId) { 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);//后继承的排在后面 } } apps.Add(appId);//本应用放在最后 var configs = new Dictionary(); //读取所有继承的配置跟本app的配置 for (int i = 0; i < apps.Count; i++) { var id = apps[i]; var publishConfigs = await GetPublishedConfigsAsync(id); for (int j = 0; j < publishConfigs.Count; j++) { var config = publishConfigs[j].Convert(); var key = GenerateKey(config); if (configs.ContainsKey(key)) { //后面的覆盖前面的 configs[key] = config; } else { configs.Add(key, config); } } } return configs; } public async Task AppPublishedConfigsMd5WithInheritanced(string appId) { var configs = await GetPublishedConfigsByAppIdWithInheritanced(appId); var keyStr = string.Join('&', configs.Select(c => GenerateKey(c)).ToArray().OrderBy(k => k)); var valStr = string.Join('&', configs.Select(c => c.Value).ToArray().OrderBy(v => v)); var txt = $"{keyStr}&{valStr}"; return Encrypt.Md5(txt); } /// /// 获取当前app的配置集合的md5版本,1分钟缓存 集合了继承app的配置 /// /// /// public async Task AppPublishedConfigsMd5CacheWithInheritanced(string appId) { var cacheKey = AppPublishedConfigsMd5CacheKeyWithInheritanced(appId); if (_memoryCache != null && _memoryCache.TryGetValue(cacheKey, out string md5)) { return md5; } md5 = await AppPublishedConfigsMd5WithInheritanced(appId); var cacheOp = new MemoryCacheEntryOptions() .SetAbsoluteExpiration(TimeSpan.FromSeconds(60)); _memoryCache?.Set(cacheKey, md5, cacheOp); return md5; } public void Dispose() { _dbContext?.Dispose(); _appService?.Dispose(); } private static readonly object Lockobj = new object(); /// /// 发布当前待发布的配置项 /// /// 应用id /// 发布日志 /// 操作员 /// public (bool result, string publishTimelineId) Publish(string appId, string log, string operatorr) { lock (Lockobj) { var waitPublishConfigs = _dbContext.Configs.Where(x => x.AppId == appId && x.Status == ConfigStatus.Enabled && x.EditStatus != EditStatus.Commit).ToList(); //这里默认admin console 实例只部署一个,如果部署多个同步操作,高并发的时候这个version会有问题 var versionMax = _dbContext.PublishTimeline.Select.Where(x => x.AppId == appId).Max(x => x.Version); var user = _dbContext.Users.Where(x => x.Id == operatorr).ToOne(); var publishTimelineNode = new PublishTimeline(); publishTimelineNode.AppId = appId; publishTimelineNode.Id = Guid.NewGuid().ToString("N"); publishTimelineNode.PublishTime = DateTime.Now; publishTimelineNode.PublishUserId = user.Id; publishTimelineNode.PublishUserName = user.UserName; publishTimelineNode.Version = versionMax + 1; publishTimelineNode.Log = log; var publishDetails = new List(); waitPublishConfigs.ForEach(x => { publishDetails.Add(new PublishDetail() { AppId = appId, ConfigId = x.Id, Description = x.Description, EditStatus = x.EditStatus, Group = x.Group, Id = Guid.NewGuid().ToString("N"), Key = x.Key, Value = x.Value, PublishTimelineId = publishTimelineNode.Id, Version = publishTimelineNode.Version }); if (x.EditStatus == EditStatus.Deleted) { x.OnlineStatus = OnlineStatus.WaitPublish; x.Status = ConfigStatus.Deleted; } else { x.OnlineStatus = OnlineStatus.Online; x.Status = ConfigStatus.Enabled; } x.EditStatus = EditStatus.Commit; x.OnlineStatus = OnlineStatus.Online; }); //当前发布的配置 var publishedConfigs = _dbContext.ConfigPublished.Where(x => x.Status == ConfigStatus.Enabled && x.AppId == appId).ToList(); //复制一份新版本,最后插入发布表 var publishedConfigsCopy = new List(); publishedConfigs.ForEach(x => { publishedConfigsCopy.Add(new ConfigPublished() { AppId = x.AppId, ConfigId = x.ConfigId, Group = x.Group, Id = Guid.NewGuid().ToString("N"), Key = x.Key, PublishTimelineId = publishTimelineNode.Id, PublishTime = publishTimelineNode.PublishTime, Status = ConfigStatus.Enabled, Version = publishTimelineNode.Version, Value = x.Value }); x.Status = ConfigStatus.Deleted; }); publishDetails.ForEach(x => { if (x.EditStatus == EditStatus.Add) { publishedConfigsCopy.Add(new ConfigPublished() { AppId = x.AppId, ConfigId = x.ConfigId, Group = x.Group, Id = Guid.NewGuid().ToString("N"), Key = x.Key, PublishTimelineId = publishTimelineNode.Id, PublishTime = publishTimelineNode.PublishTime, Status = ConfigStatus.Enabled, Value = x.Value, Version = publishTimelineNode.Version }); } if (x.EditStatus == EditStatus.Edit) { var oldEntity = publishedConfigsCopy.FirstOrDefault(c => c.ConfigId == x.ConfigId); if (oldEntity == null) { //do nothing } else { //edit oldEntity.Version = publishTimelineNode.Version; oldEntity.Group = x.Group; oldEntity.Key = x.Key; oldEntity.Value = x.Value; oldEntity.PublishTime = publishTimelineNode.PublishTime; } } if (x.EditStatus == EditStatus.Deleted) { var oldEntity = publishedConfigsCopy.FirstOrDefault(c => c.ConfigId == x.ConfigId); if (oldEntity == null) { //do nothing } else { //remove publishedConfigsCopy.Remove(oldEntity); } } }); _dbContext.Configs.UpdateRange(waitPublishConfigs); _dbContext.PublishTimeline.Add(publishTimelineNode); _dbContext.PublishDetail.AddRange(publishDetails); _dbContext.ConfigPublished.UpdateRange(publishedConfigs); _dbContext.ConfigPublished.AddRange(publishedConfigsCopy); var result = _dbContext.SaveChanges(); ClearAppPublishedConfigsMd5Cache(appId); ClearAppPublishedConfigsMd5CacheWithInheritanced(appId); return (result > 0, publishTimelineNode.Id); } } public async Task IsPublishedAsync(string configId) { var any = await _dbContext.ConfigPublished.Select.AnyAsync( x => x.ConfigId == configId && x.Status == ConfigStatus.Enabled); return any; } public async Task> GetPublishDetailByPublishTimelineIdAsync(string publishTimelineId) { var list = await _dbContext.PublishDetail.Where(x => x.PublishTimelineId == publishTimelineId).ToListAsync(); return list; } public async Task GetPublishTimeLineNodeAsync(string publishTimelineId) { var one = await _dbContext.PublishTimeline.Where(x => x.Id == publishTimelineId).FirstAsync(); return one; } public async Task> GetPublishTimelineHistoryAsync(string appId) { var list = await _dbContext.PublishTimeline.Where(x => x.AppId == appId).ToListAsync(); return list; } public async Task> GetPublishDetailListAsync(string appId) { var list = await _dbContext.PublishDetail.Where(x => x.AppId == appId).ToListAsync(); return list; } public async Task> GetConfigPublishedHistory(string configId) { var list = await _dbContext.PublishDetail.Where(x => x.ConfigId == configId).ToListAsync(); return list; } public async Task> GetPublishedConfigsAsync(string appId) { var list = await _dbContext.ConfigPublished.Where(x => x.AppId == appId && x.Status == ConfigStatus.Enabled).ToListAsync(); return list; } public async Task GetPublishedConfigAsync(string configId) { var one = await _dbContext.ConfigPublished.Where(x => x.ConfigId == configId && x.Status == ConfigStatus.Enabled).ToOneAsync(); return one; } public async Task RollbackAsync(string publishTimelineId) { var publishNode = await _dbContext.PublishTimeline.Where(x => x.Id == publishTimelineId).ToOneAsync(); var version = publishNode.Version; var appId = publishNode.AppId; var publishedConfigs = await _dbContext.ConfigPublished.Where(x => x.AppId == appId && x.Version == version).ToListAsync(); var currentConfigs = await _dbContext.Configs.Where(x => x.AppId == appId && x.Status == ConfigStatus.Enabled).ToListAsync(); //把当前的全部软删除 foreach (var item in currentConfigs) { item.Status = ConfigStatus.Deleted; } await _dbContext.Configs.UpdateRangeAsync(currentConfigs); //根据id把所有发布项目设置为启用 var now = DateTime.Now; foreach (var item in publishedConfigs) { var config = await _dbContext.Configs.Where(x => x.AppId == appId && x.Id == item.ConfigId).ToOneAsync(); config.Status = ConfigStatus.Enabled; config.Value = item.Value; config.UpdateTime = now; config.EditStatus = EditStatus.Commit; config.OnlineStatus = OnlineStatus.Online; await _dbContext.Configs.UpdateAsync(config); } //删除version之后的版本 await _dbContext.ConfigPublished.RemoveAsync(x => x.AppId == appId && x.Version > version); //设置为发布状态 foreach (var item in publishedConfigs) { item.Status = ConfigStatus.Enabled; await _dbContext.ConfigPublished.UpdateAsync(item); } //删除发布时间轴version之后的版本 await _dbContext.PublishTimeline.RemoveAsync(x => x.AppId == appId && x.Version > version); await _dbContext.PublishDetail.RemoveAsync(x => x.AppId == appId && x.Version > version); ClearAppPublishedConfigsMd5Cache(appId); ClearAppPublishedConfigsMd5CacheWithInheritanced(appId); return await _dbContext.SaveChangesAsync() > 0; } } }