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.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.GetInterfaceByCodeAsync(interfaceCode);
if (systemInterface == null) throw new BusinessException("系统接口不存在");
var serviceProvider = await interfaceForwardQuery.GetServiceProviderByIdAsync(serviceProviderId);
if (serviceProvider == null) throw new BusinessException("服务商不存在");
// 没有指定比对的版本,就是比对数据库最新数据
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.GetInterfaceByCodeAsync(input.InterfaceCode);
if (systemInterface == null) throw new BusinessException("系统接口不存在");
var serviceProvider = await interfaceForwardQuery.GetServiceProviderByIdAsync(input.ServiceProviderId);
if (serviceProvider == null) throw new BusinessException("服务商不存在");
// 确认比对期间数据是否被篡改
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("数据库已经发生变更,请重新获取最新数据进行比对");
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.GetInterfaceByCodeAsync(input.InterfaceCode);
if (systemInterface == null) throw new BusinessException("系统接口不存在");
var serviceProvider = await interfaceForwardQuery.GetServiceProviderByIdAsync(input.ServiceProviderId);
if (serviceProvider == null) throw new BusinessException("服务商不存在");
var mapsEntities = await interfaceMapRepository.GetListAsync(x =>
x.UpStreamId == systemInterface.Id && x.ServiceProviderId == input.ServiceProviderId);
if (mapsEntities == null || mapsEntities.Count == 0)
{
throw new BusinessException("映射不存在");
}
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("要回滚的版本不存在");
// 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);
}
}