You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
455 lines
18 KiB
455 lines
18 KiB
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[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[path] != null) |
|
{ |
|
currentToken = currentToken[path]; |
|
} |
|
else |
|
{ |
|
currentToken = null; |
|
break; |
|
} |
|
} |
|
|
|
if (currentToken != null && currentToken is JArray sourceArray) |
|
{ |
|
foreach (JObject sourceItem in sourceArray) |
|
{ |
|
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 (sourceItem[mapping.SYSTEM_FIELD] != null) |
|
{ |
|
targetItem[mapping.INTERFACE_FIELD] = sourceItem[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[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[path] != null) |
|
{ |
|
currentToken = currentToken[path]; |
|
} |
|
else |
|
{ |
|
currentToken = null; |
|
break; |
|
} |
|
} |
|
|
|
if (currentToken != null && currentToken is JArray sourceArray) |
|
{ |
|
foreach (JObject sourceItem in sourceArray) |
|
{ |
|
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 (sourceItem[mapping.INTERFACE_FIELD] != null) |
|
{ |
|
targetItem[mapping.SYSTEM_FIELD] = sourceItem[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, "保存交易日志异常"); |
|
} |
|
} |
|
} |
|
} |