|
|
|
using System;
|
|
|
|
using System.Net.Http;
|
|
|
|
using System.Text;
|
|
|
|
using System.Threading.Tasks;
|
|
|
|
using System.Text.Json;
|
|
|
|
using System.Collections.Generic;
|
|
|
|
using medical.transfomer.entity;
|
|
|
|
using Newtonsoft.Json.Linq;
|
|
|
|
using Polly;
|
|
|
|
using Polly.Retry;
|
|
|
|
using Polly.CircuitBreaker;
|
|
|
|
using Microsoft.Extensions.Logging;
|
|
|
|
using SqlSugar;
|
|
|
|
using ReZero.DependencyInjection;
|
|
|
|
using System.Reflection;
|
|
|
|
using System.Linq;
|
|
|
|
using Polly.Timeout;
|
|
|
|
using System.Threading;
|
|
|
|
|
|
|
|
namespace medical.transfomer.business
|
|
|
|
{
|
|
|
|
/// <summary>
|
|
|
|
/// 医保转换工厂,负责动态转换和HTTP请求处理
|
|
|
|
/// </summary>
|
|
|
|
public class TransformerFactory : IScopeContract
|
|
|
|
{
|
|
|
|
private readonly IHttpClientFactory _httpClientFactory;
|
|
|
|
private readonly ILogger<TransformerFactory> _logger;
|
|
|
|
private readonly ISqlSugarClient _db;
|
|
|
|
private readonly ResiliencePipeline<HttpResponseMessage> _resiliencePipeline;
|
|
|
|
|
|
|
|
public TransformerFactory(IHttpClientFactory httpClientFactory, ILogger<TransformerFactory> logger, ISqlSugarClient db)
|
|
|
|
{
|
|
|
|
_httpClientFactory = httpClientFactory;
|
|
|
|
_logger = logger;
|
|
|
|
_db = db;
|
|
|
|
|
|
|
|
// 在Polly 8.x中,我们使用ResiliencePipelineBuilder来创建弹性管道
|
|
|
|
var pipelineBuilder = new ResiliencePipelineBuilder<HttpResponseMessage>();
|
|
|
|
|
|
|
|
// 添加重试策略
|
|
|
|
pipelineBuilder.AddRetry(new RetryStrategyOptions<HttpResponseMessage>
|
|
|
|
{
|
|
|
|
ShouldHandle = new PredicateBuilder<HttpResponseMessage>()
|
|
|
|
.Handle<HttpRequestException>()
|
|
|
|
.HandleResult(r => !r.IsSuccessStatusCode),
|
|
|
|
MaxRetryAttempts = 3,
|
|
|
|
Delay = TimeSpan.FromSeconds(1),
|
|
|
|
BackoffType = DelayBackoffType.Exponential,
|
|
|
|
OnRetry = args =>
|
|
|
|
{
|
|
|
|
_logger.LogWarning($"重试第 {args.AttemptNumber} 次,等待 {args.RetryDelay.TotalSeconds} 秒");
|
|
|
|
return ValueTask.CompletedTask;
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
// 添加断路器策略
|
|
|
|
pipelineBuilder.AddCircuitBreaker(new CircuitBreakerStrategyOptions<HttpResponseMessage>
|
|
|
|
{
|
|
|
|
ShouldHandle = new PredicateBuilder<HttpResponseMessage>()
|
|
|
|
.Handle<HttpRequestException>()
|
|
|
|
.HandleResult(r => !r.IsSuccessStatusCode),
|
|
|
|
FailureRatio = 0.5, // 50%失败率
|
|
|
|
MinimumThroughput = 10, // 最小样本数
|
|
|
|
SamplingDuration = TimeSpan.FromSeconds(30), // 采样时间窗口
|
|
|
|
BreakDuration = TimeSpan.FromMinutes(1), // 断路时间
|
|
|
|
OnOpened = args =>
|
|
|
|
{
|
|
|
|
_logger.LogError($"断路器已断开,将在 {args.BreakDuration.TotalSeconds} 秒后尝试恢复");
|
|
|
|
return ValueTask.CompletedTask;
|
|
|
|
},
|
|
|
|
OnClosed = _ =>
|
|
|
|
{
|
|
|
|
_logger.LogInformation("断路器已关闭,恢复正常操作");
|
|
|
|
return ValueTask.CompletedTask;
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
// 添加超时策略
|
|
|
|
pipelineBuilder.AddTimeout(TimeSpan.FromSeconds(30));
|
|
|
|
|
|
|
|
// 构建弹性管道
|
|
|
|
_resiliencePipeline = pipelineBuilder.Build();
|
|
|
|
}
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// 根据方法名执行医保交易
|
|
|
|
/// </summary>
|
|
|
|
/// <param name="methodName">方法名称</param>
|
|
|
|
/// <param name="inputData">输入数据</param>
|
|
|
|
/// <returns>处理结果</returns>
|
|
|
|
public async Task<object> ExecuteMethod(string methodName, JObject inputData)
|
|
|
|
{
|
|
|
|
try
|
|
|
|
{
|
|
|
|
// 1. 获取方法配置
|
|
|
|
var methodConfig = await _db.Queryable<STD_METHOD_CONFIG>()
|
|
|
|
.FirstAsync(m => m.BIND_SYS_CODE == methodName);
|
|
|
|
|
|
|
|
if (methodConfig == null)
|
|
|
|
{
|
|
|
|
_logger.LogError($"未找到方法配置: {methodName}");
|
|
|
|
return new { code = -1, msg = $"未找到方法配置: {methodName}" };
|
|
|
|
}
|
|
|
|
|
|
|
|
// 2. 获取输入对象的映射配置
|
|
|
|
var inputAssembly = await _db.Queryable<STD_OBJECT_ASSEMBLY>()
|
|
|
|
.Where(a => a.METHOD_REF == methodConfig.METHOD_ID && a.PARAMETR_TYPE == 1)
|
|
|
|
.ToListAsync();
|
|
|
|
|
|
|
|
// 3. 转换输入数据
|
|
|
|
var convertedInputData = ConvertToMedicalInsuranceObject(inputData, inputAssembly);
|
|
|
|
|
|
|
|
// 4. 调用医保接口
|
|
|
|
var responseData = await CallMedicalInsuranceService(methodConfig.METHOD_VALUE, convertedInputData);
|
|
|
|
|
|
|
|
// 5. 获取输出对象的映射配置
|
|
|
|
var outputAssembly = await _db.Queryable<STD_OBJECT_ASSEMBLY>()
|
|
|
|
.Where(a => a.METHOD_REF == methodConfig.METHOD_ID && a.PARAMETR_TYPE == 2)
|
|
|
|
.ToListAsync();
|
|
|
|
|
|
|
|
// 6. 转换输出数据
|
|
|
|
var convertedOutputData = ConvertToSystemObject(responseData, outputAssembly);
|
|
|
|
|
|
|
|
// 7. 保存交易记录
|
|
|
|
if (methodConfig.SAVE_INPUT == 1 || methodConfig.SAVE_OUTPUT == 1)
|
|
|
|
{
|
|
|
|
await SaveTransactionLog(methodConfig, inputData, responseData);
|
|
|
|
}
|
|
|
|
|
|
|
|
return new { code = 0, msg = "处理成功", data = convertedOutputData };
|
|
|
|
}
|
|
|
|
catch (Exception ex)
|
|
|
|
{
|
|
|
|
_logger.LogError(ex, $"执行方法 {methodName} 异常");
|
|
|
|
return new { code = -1, msg = $"执行异常: {ex.Message}" };
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// 将系统对象转换为医保对象
|
|
|
|
/// </summary>
|
|
|
|
private JObject ConvertToMedicalInsuranceObject(JObject systemObject, List<STD_OBJECT_ASSEMBLY> assemblies)
|
|
|
|
{
|
|
|
|
JObject medicalObject = new JObject();
|
|
|
|
|
|
|
|
foreach (var assembly in assemblies)
|
|
|
|
{
|
|
|
|
// 获取映射字段
|
|
|
|
var mappings = _db.Queryable<STD_OBJECT_MAPPING>()
|
|
|
|
.Where(m => m.SYSTEM_TABLE_NAME == assembly.MAPPING_TABLE)
|
|
|
|
.ToList();
|
|
|
|
|
|
|
|
if (assembly.OBJECT_TYPE == 1) // 单个对象
|
|
|
|
{
|
|
|
|
JObject targetObject = new JObject();
|
|
|
|
|
|
|
|
foreach (var mapping in mappings)
|
|
|
|
{
|
|
|
|
if (mapping.SYSTEM_FIELD != null && mapping.INTERFACE_FIELD != null &&
|
|
|
|
!string.IsNullOrEmpty(mapping.SYSTEM_FIELD) && !string.IsNullOrEmpty(mapping.INTERFACE_FIELD))
|
|
|
|
{
|
|
|
|
// 从系统对象中提取值
|
|
|
|
var paths = assembly.OBJECT_PATH.Split('.');
|
|
|
|
JToken currentToken = systemObject;
|
|
|
|
|
|
|
|
foreach (var path in paths)
|
|
|
|
{
|
|
|
|
if (currentToken != null && currentToken[path] != null)
|
|
|
|
{
|
|
|
|
currentToken = currentToken[path];
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
currentToken = null;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (currentToken != null && currentToken[mapping.SYSTEM_FIELD] != null)
|
|
|
|
{
|
|
|
|
targetObject[mapping.INTERFACE_FIELD] = currentToken[mapping.SYSTEM_FIELD];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// 设置到医保对象中
|
|
|
|
var objectPath = assembly.OBJECT_PATH;
|
|
|
|
if (objectPath.Contains('.'))
|
|
|
|
{
|
|
|
|
var paths = objectPath.Split('.');
|
|
|
|
JObject current = medicalObject;
|
|
|
|
|
|
|
|
for (int i = 0; i < paths.Length - 1; i++)
|
|
|
|
{
|
|
|
|
var path = paths[i];
|
|
|
|
if (current[path] == null || !(current[path] is JObject))
|
|
|
|
{
|
|
|
|
current[path] = new JObject();
|
|
|
|
}
|
|
|
|
current = (JObject)current[path];
|
|
|
|
}
|
|
|
|
|
|
|
|
current[paths[paths.Length - 1]] = targetObject;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
medicalObject[objectPath] = targetObject;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else if (assembly.OBJECT_TYPE == 2) // 列表对象
|
|
|
|
{
|
|
|
|
JArray targetArray = new JArray();
|
|
|
|
|
|
|
|
// 从系统对象中提取列表
|
|
|
|
var paths = assembly.OBJECT_PATH.Split('.');
|
|
|
|
JToken currentToken = systemObject;
|
|
|
|
|
|
|
|
foreach (var path in paths)
|
|
|
|
{
|
|
|
|
if (currentToken != null && currentToken[path] != null)
|
|
|
|
{
|
|
|
|
currentToken = currentToken[path];
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
currentToken = null;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (currentToken != null)
|
|
|
|
{
|
|
|
|
JArray sourceArray;
|
|
|
|
|
|
|
|
// 处理不同类型的JToken
|
|
|
|
if (currentToken is JArray jArray)
|
|
|
|
{
|
|
|
|
sourceArray = jArray;
|
|
|
|
}
|
|
|
|
else if (currentToken is JObject jObject)
|
|
|
|
{
|
|
|
|
// 如果是JObject,将其作为单个元素的数组处理
|
|
|
|
sourceArray = new JArray { jObject };
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
// 创建包含当前Token的数组
|
|
|
|
sourceArray = new JArray { currentToken };
|
|
|
|
}
|
|
|
|
|
|
|
|
foreach (JToken sourceItem in sourceArray)
|
|
|
|
{
|
|
|
|
if (sourceItem is JObject sourceObject)
|
|
|
|
{
|
|
|
|
JObject targetItem = new JObject();
|
|
|
|
|
|
|
|
foreach (var mapping in mappings)
|
|
|
|
{
|
|
|
|
if (mapping.SYSTEM_FIELD != null && mapping.INTERFACE_FIELD != null &&
|
|
|
|
!string.IsNullOrEmpty(mapping.SYSTEM_FIELD) && !string.IsNullOrEmpty(mapping.INTERFACE_FIELD))
|
|
|
|
{
|
|
|
|
if (sourceObject[mapping.SYSTEM_FIELD] != null)
|
|
|
|
{
|
|
|
|
targetItem[mapping.INTERFACE_FIELD] = sourceObject[mapping.SYSTEM_FIELD];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
targetArray.Add(targetItem);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// 设置到医保对象中
|
|
|
|
var objectPath = assembly.OBJECT_PATH;
|
|
|
|
if (objectPath.Contains('.'))
|
|
|
|
{
|
|
|
|
var paths2 = objectPath.Split('.');
|
|
|
|
JObject current = medicalObject;
|
|
|
|
|
|
|
|
for (int i = 0; i < paths2.Length - 1; i++)
|
|
|
|
{
|
|
|
|
var path = paths2[i];
|
|
|
|
if (current[path] == null || !(current[path] is JObject))
|
|
|
|
{
|
|
|
|
current[path] = new JObject();
|
|
|
|
}
|
|
|
|
current = (JObject)current[path];
|
|
|
|
}
|
|
|
|
|
|
|
|
current[paths2[paths2.Length - 1]] = targetArray;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
medicalObject[objectPath] = targetArray;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return medicalObject;
|
|
|
|
}
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// 将医保对象转换为系统对象
|
|
|
|
/// </summary>
|
|
|
|
private JObject ConvertToSystemObject(JObject medicalObject, List<STD_OBJECT_ASSEMBLY> assemblies)
|
|
|
|
{
|
|
|
|
JObject systemObject = new JObject();
|
|
|
|
|
|
|
|
foreach (var assembly in assemblies)
|
|
|
|
{
|
|
|
|
// 获取映射字段
|
|
|
|
var mappings = _db.Queryable<STD_OBJECT_MAPPING>()
|
|
|
|
.Where(m => m.OBJECT_TABLE_NAME == assembly.MAPPING_TABLE)
|
|
|
|
.ToList();
|
|
|
|
|
|
|
|
if (assembly.OBJECT_TYPE == 1) // 单个对象
|
|
|
|
{
|
|
|
|
JObject targetObject = new JObject();
|
|
|
|
|
|
|
|
// 从医保对象中提取值
|
|
|
|
var paths = assembly.OBJECT_PATH.Split('.');
|
|
|
|
JToken currentToken = medicalObject;
|
|
|
|
|
|
|
|
foreach (var path in paths)
|
|
|
|
{
|
|
|
|
if (currentToken != null && currentToken[path] != null)
|
|
|
|
{
|
|
|
|
currentToken = currentToken[path];
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
currentToken = null;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (currentToken != null && currentToken is JObject sourceObject)
|
|
|
|
{
|
|
|
|
foreach (var mapping in mappings)
|
|
|
|
{
|
|
|
|
if (mapping.INTERFACE_FIELD != null && mapping.SYSTEM_FIELD != null &&
|
|
|
|
!string.IsNullOrEmpty(mapping.INTERFACE_FIELD) && !string.IsNullOrEmpty(mapping.SYSTEM_FIELD))
|
|
|
|
{
|
|
|
|
if (sourceObject[mapping.INTERFACE_FIELD] != null)
|
|
|
|
{
|
|
|
|
targetObject[mapping.SYSTEM_FIELD] = sourceObject[mapping.INTERFACE_FIELD];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// 设置到系统对象中
|
|
|
|
systemObject[assembly.MAPPING_TABLE] = targetObject;
|
|
|
|
}
|
|
|
|
else if (assembly.OBJECT_TYPE == 2) // 列表对象
|
|
|
|
{
|
|
|
|
JArray targetArray = new JArray();
|
|
|
|
|
|
|
|
// 从医保对象中提取列表
|
|
|
|
var paths = assembly.OBJECT_PATH.Split('.');
|
|
|
|
JToken currentToken = medicalObject;
|
|
|
|
|
|
|
|
foreach (var path in paths)
|
|
|
|
{
|
|
|
|
if (currentToken != null && currentToken[path] != null)
|
|
|
|
{
|
|
|
|
currentToken = currentToken[path];
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
currentToken = null;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (currentToken != null)
|
|
|
|
{
|
|
|
|
JArray sourceArray;
|
|
|
|
|
|
|
|
// 处理不同类型的JToken
|
|
|
|
if (currentToken is JArray jArray)
|
|
|
|
{
|
|
|
|
sourceArray = jArray;
|
|
|
|
}
|
|
|
|
else if (currentToken is JObject jObject)
|
|
|
|
{
|
|
|
|
// 如果是JObject,将其作为单个元素的数组处理
|
|
|
|
sourceArray = new JArray { jObject };
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
// 创建包含当前Token的数组
|
|
|
|
sourceArray = new JArray { currentToken };
|
|
|
|
}
|
|
|
|
|
|
|
|
foreach (JToken sourceItem in sourceArray)
|
|
|
|
{
|
|
|
|
if (sourceItem is JObject sourceObject)
|
|
|
|
{
|
|
|
|
JObject targetItem = new JObject();
|
|
|
|
|
|
|
|
foreach (var mapping in mappings)
|
|
|
|
{
|
|
|
|
if (mapping.INTERFACE_FIELD != null && mapping.SYSTEM_FIELD != null &&
|
|
|
|
!string.IsNullOrEmpty(mapping.INTERFACE_FIELD) && !string.IsNullOrEmpty(mapping.SYSTEM_FIELD))
|
|
|
|
{
|
|
|
|
if (sourceObject[mapping.INTERFACE_FIELD] != null)
|
|
|
|
{
|
|
|
|
targetItem[mapping.SYSTEM_FIELD] = sourceObject[mapping.INTERFACE_FIELD];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
targetArray.Add(targetItem);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// 设置到系统对象中
|
|
|
|
systemObject[assembly.MAPPING_TABLE] = targetArray;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return systemObject;
|
|
|
|
}
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// 调用医保服务接口
|
|
|
|
/// </summary>
|
|
|
|
private async Task<JObject> CallMedicalInsuranceService(string endpoint, JObject requestData)
|
|
|
|
{
|
|
|
|
var httpClient = _httpClientFactory.CreateClient("MedicalInsurance");
|
|
|
|
|
|
|
|
var content = new StringContent(
|
|
|
|
requestData.ToString(),
|
|
|
|
Encoding.UTF8,
|
|
|
|
"application/json");
|
|
|
|
|
|
|
|
try
|
|
|
|
{
|
|
|
|
// 使用弹性管道执行HTTP请求
|
|
|
|
var response = await _resiliencePipeline.ExecuteAsync(async cancellationToken =>
|
|
|
|
{
|
|
|
|
return await httpClient.PostAsync(endpoint, content, cancellationToken);
|
|
|
|
}, CancellationToken.None);
|
|
|
|
|
|
|
|
if (response.IsSuccessStatusCode)
|
|
|
|
{
|
|
|
|
var responseString = await response.Content.ReadAsStringAsync();
|
|
|
|
return JObject.Parse(responseString);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
_logger.LogError($"医保接口调用失败: {response.StatusCode}, {await response.Content.ReadAsStringAsync()}");
|
|
|
|
throw new Exception($"医保接口调用失败: {response.StatusCode}");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
catch (Exception ex)
|
|
|
|
{
|
|
|
|
_logger.LogError(ex, "调用医保服务异常");
|
|
|
|
throw;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// 保存交易日志
|
|
|
|
/// </summary>
|
|
|
|
private async Task SaveTransactionLog(STD_METHOD_CONFIG methodConfig, JObject inputData, JObject outputData)
|
|
|
|
{
|
|
|
|
try
|
|
|
|
{
|
|
|
|
// 创建日志对象
|
|
|
|
var transLog = new
|
|
|
|
{
|
|
|
|
MethodId = methodConfig.METHOD_ID,
|
|
|
|
MethodName = methodConfig.METHOD_NAME,
|
|
|
|
RequestTime = DateTime.Now,
|
|
|
|
RequestData = methodConfig.SAVE_INPUT == 1 ? inputData.ToString() : null,
|
|
|
|
ResponseTime = DateTime.Now,
|
|
|
|
ResponseData = methodConfig.SAVE_OUTPUT == 1 ? outputData.ToString() : null,
|
|
|
|
Status = 1, // 成功
|
|
|
|
ErrorMessage = ""
|
|
|
|
};
|
|
|
|
|
|
|
|
// 保存到数据库
|
|
|
|
// TODO: 实际实现中需要定义日志表并保存数据
|
|
|
|
_logger.LogInformation($"保存交易日志: {JsonSerializer.Serialize(transLog)}");
|
|
|
|
}
|
|
|
|
catch (Exception ex)
|
|
|
|
{
|
|
|
|
_logger.LogError(ex, "保存交易日志异常");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|