using System.ComponentModel.DataAnnotations; using DiffPlex; using DiffPlex.DiffBuilder; using DiffPlex.DiffBuilder.Model; using InterfaceForward.Application.Contracts.Dtos.Interface; using InterfaceForward.Application.Helpers; using InterfaceForward.Domain.Shared.Dtos; using InterfaceForward.Repositories; using InterfaceForward.Repositories.Interface.Entitys; using InterfaceForward.Repositories.Interface.Services; using InterfaceForward.Repositories.Interface.ValueObjects; using Microsoft.AspNetCore.Mvc; using Newtonsoft.Json; using Newtonsoft.Json.Converters; namespace InterfaceForward.Application.Services.InterfaceMap; /// /// 接口映射发布修改 /// [ApiExplorerSettings(GroupName = "接口映射服务")] public class InterfaceMapPublishService( IInterfaceMapPublishRepository interfaceMapPublishRepository, InterfaceForwardQuery interfaceForwardQuery, IInterfaceMapRepository interfaceMapRepository) : ApplicationService { private static readonly JsonSerializerSettings Settings = new() { Formatting = Formatting.Indented, // 设置格式化输出 Converters = { new StringEnumConverter() }, ContractResolver = new CustomContractResolver() // 设置自定义的ContractResolver }; /// /// 获取接口映射 发布记录列表 /// /// 系统接口id /// 服务商id /// /// /// [HttpGet("InterfaceMap/GetPublishedList")] public async Task> GetPublishedListAsync([Required] int interfaceId, [Required] int serviceProviderId, int pageIndex = 1, int pageSize = 50) { return await interfaceMapPublishRepository.GetPublishedListAsync(interfaceId, serviceProviderId, pageIndex, pageSize); } /// /// 获取接口映射 发布对照数据 /// /// 系统接口code /// 服务商id /// 要比对的版本,如果不指定就是比对数据库最新数据 /// [HttpGet("InterfaceMap/GetPublishCompare")] public async Task GetPublishCompareAsync([Required] string interfaceCode, [Required] int serviceProviderId, string version) { var systemInterface = await interfaceForwardQuery.GetInterfaceByCodesAsync(interfaceCode); if (systemInterface == null) throw new BusinessException(message: "系统接口不存在"); var serviceProvider = await interfaceForwardQuery.GetServiceProviderByIdAsync(serviceProviderId); if (serviceProvider == null) throw new BusinessException(message: "服务商不存在"); // 没有指定比对的版本,就是比对数据库最新数据 var dbContent = version.IsNullOrWhiteSpace() ? JsonConvert.SerializeObject( await interfaceForwardQuery.BuildForwardCoreContext(systemInterface, serviceProvider), Settings) : await interfaceMapPublishRepository.GetFirstAsync(a => a.InterfaceId == systemInterface.Id && a.ServiceProviderId == serviceProviderId && a.PublishedVersion == version, a => a.PublishedContent); var cacheKey = InterfaceForwardCommon.GetContextCacheKey(systemInterface.Code, serviceProvider.Code); var cache = (await RedisHelper.Client.GetAsync(cacheKey)).ToObject(); string redisContent = JsonConvert.SerializeObject(cache, Settings); var differenceContent = CompareTexts(redisContent, dbContent?? String.Empty); return new InterfaceMapPublishedDifferenceVO { PublishedContent = redisContent, DbContent = dbContent, DifferenceContent = differenceContent }; } /// /// 发布映射 /// /// /// /// [HttpPost("InterfaceMap/Publish")] public async Task PublishAsync(InterfaceMapPublishInput input) { var systemInterface = await interfaceForwardQuery.GetInterfaceByCodesAsync(input.InterfaceCode); if (systemInterface == null) throw new BusinessException(message: "系统接口不存在"); var serviceProvider = await interfaceForwardQuery.GetServiceProviderByIdAsync(input.ServiceProviderId); if (serviceProvider == null) throw new BusinessException(message: "服务商不存在"); // 确认比对期间数据是否被篡改 var differ = new Differ(); var inlineBuilder = new InlineDiffBuilder(differ); var context = await interfaceForwardQuery.BuildForwardCoreContext(systemInterface, serviceProvider); var content = JsonConvert.SerializeObject(context, Settings); var diffResult = inlineBuilder.BuildDiffModel(content, input.DBContent); if (diffResult.HasDifferences) throw new BusinessException(message: "数据库已经发生变更,请重新获取最新数据进行比对"); var mapsEntities = await interfaceMapRepository.GetListAsync(x => x.UpStreamId == systemInterface.Id && x.ServiceProviderId == serviceProvider.Id); var publishEntity = new InterfaceMapPublishedEntity { InterfaceId = systemInterface.Id, ServiceProviderId = input.ServiceProviderId, PublishedVersion = DateTime.Now.ToString("yyyyMMddHHmmss"), PublishedContent = content, PrePublishedVersion = mapsEntities.First().PublishedVersion, DifferenceContent = input.DifferenceContent, IsPublished = true, Description = input.Description }; using var ts = TransacationHelper.GetReadCommitted(); await interfaceMapPublishRepository.InsertAsync(publishEntity); // 更新版本号 mapsEntities.ForEach(x => x.PublishedVersion = publishEntity.PublishedVersion); await interfaceMapRepository.UpdateRangeAsync(mapsEntities, x => x.PublishedVersion); // update cache var redisKey = InterfaceForwardCommon.GetContextCacheKey(systemInterface.Code, serviceProvider.Code); await RedisHelper.Client.SetAsync(redisKey, content, InterfaceForwardCommon.DefaultCacheTtl); ts.Complete(); return true; } /// /// 发布回滚 /// /// /// /// [HttpPost("InterfaceMap/PublishRollback")] public async Task PublishRollbackAsync(InterfaceMapRollbackInput input) { var systemInterface = await interfaceForwardQuery.GetInterfaceByCodesAsync(input.InterfaceCode); if (systemInterface == null) throw new BusinessException(message: "系统接口不存在"); var serviceProvider = await interfaceForwardQuery.GetServiceProviderByIdAsync(input.ServiceProviderId); if (serviceProvider == null) throw new BusinessException(message: "服务商不存在"); var mapsEntities = await interfaceMapRepository.GetListAsync(x => x.UpStreamId == systemInterface.Id && x.ServiceProviderId == input.ServiceProviderId); if (mapsEntities == null || mapsEntities.Count == 0) { throw new BusinessException(message: "映射不存在"); } var targetVersionContent = await interfaceMapPublishRepository.GetFirstAsync(a => a.InterfaceId == systemInterface.Id && a.ServiceProviderId == input.ServiceProviderId && a.PublishedVersion == input.Version, a => a.PublishedContent); // tips:空是合法的,虽然在业务上不存在 if (targetVersionContent == null) throw new BusinessException(message: "要回滚的版本不存在"); // tips:回滚是缓存覆盖行为,不考虑脏数据问题 var redisKey = InterfaceForwardCommon.GetContextCacheKey(input.InterfaceCode, serviceProvider.Code); var cache = (await RedisHelper.Client.GetAsync(redisKey)).ToObject(); var redisContent = JsonConvert.SerializeObject(cache, Settings); var differenceContent = CompareTexts(redisContent, targetVersionContent); var publishEntity = new InterfaceMapPublishedEntity { InterfaceId = systemInterface.Id, ServiceProviderId = input.ServiceProviderId, PublishedVersion = DateTime.Now.ToString("yyyyMMddHHmmss"), PublishedContent = targetVersionContent, PrePublishedVersion = mapsEntities.First().PublishedVersion, DifferenceContent = differenceContent, IsPublished = false, Description = input.Description }; using var ts = TransacationHelper.GetReadCommitted(); await interfaceMapPublishRepository.InsertAsync(publishEntity); // 更新版本号 mapsEntities.ForEach(x => x.PublishedVersion = publishEntity.PublishedVersion); await interfaceMapRepository.UpdateRangeAsync(mapsEntities, x => x.PublishedVersion); await RedisHelper.Client.SetAsync(redisKey, publishEntity.PublishedContent, InterfaceForwardCommon.DefaultCacheTtl); ts.Complete(); return true; } private static string CompareTexts(string oldText, string newText) { var differ = new Differ(); var inlineBuilder = new InlineDiffBuilder(differ); var diffResult = inlineBuilder.BuildDiffModel(oldText, newText); var diffOutput = new List(); foreach (var line in diffResult.Lines) { switch (line.Type) { case ChangeType.Inserted: diffOutput.Add("+ " + line.Text); break; case ChangeType.Deleted: diffOutput.Add("- " + line.Text); break; case ChangeType.Modified: diffOutput.Add("* " + line.Text); break; case ChangeType.Unchanged: diffOutput.Add(" " + line.Text); break; case ChangeType.Imaginary: diffOutput.Add("? " + line.Text); break; default: throw new ArgumentOutOfRangeException(); } } return string.Join(Environment.NewLine, diffOutput); } }