24 changed files with 1986 additions and 604 deletions
@ -0,0 +1,31 @@
|
||||
using System; |
||||
|
||||
namespace medical.insu.transfomer.Attributes |
||||
{ |
||||
/// <summary> |
||||
/// 标记不需要认证的API方法 |
||||
/// 可以应用于类(控制器)或方法 |
||||
/// </summary> |
||||
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = false, Inherited = true)] |
||||
public class SkipAuthAttribute : Attribute |
||||
{ |
||||
/// <summary> |
||||
/// 说明 |
||||
/// </summary> |
||||
public string Description { get; set; } |
||||
|
||||
/// <summary> |
||||
/// 构造函数 |
||||
/// </summary> |
||||
public SkipAuthAttribute() { } |
||||
|
||||
/// <summary> |
||||
/// 带说明的构造函数 |
||||
/// </summary> |
||||
/// <param name="description">免验证的说明</param> |
||||
public SkipAuthAttribute(string description) |
||||
{ |
||||
Description = description; |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,46 @@
|
||||
using Microsoft.AspNetCore.Mvc; |
||||
using medical.insu.transfomer.Attributes; |
||||
using ReZero.SuperAPI; |
||||
using System; |
||||
using System.Threading.Tasks; |
||||
|
||||
namespace medical.insu.transfomer.Controllers |
||||
{ |
||||
/// <summary> |
||||
/// 公共网关控制器,所有方法都不需要身份验证 |
||||
/// </summary> |
||||
[ApiController] |
||||
[Api(200200, GroupName = "公共接口")] |
||||
[SkipAuth("整个控制器都不需要验证")] |
||||
public class GatewayController : ControllerBase |
||||
{ |
||||
/// <summary> |
||||
/// 健康检查接口 |
||||
/// </summary> |
||||
/// <returns>服务健康状态</returns> |
||||
[ApiMethod("健康检查")] |
||||
[HttpGet("health")] |
||||
public IActionResult HealthCheck() |
||||
{ |
||||
return Ok(new { status = "healthy", timestamp = DateTime.Now }); |
||||
} |
||||
|
||||
/// <summary> |
||||
/// 获取服务版本信息 |
||||
/// </summary> |
||||
/// <returns>版本信息</returns> |
||||
[ApiMethod("获取版本")] |
||||
[HttpGet("version")] |
||||
public IActionResult GetVersion() |
||||
{ |
||||
var version = new |
||||
{ |
||||
version = "1.0.0", |
||||
buildDate = "2023-08-01", |
||||
apiVersion = "v1" |
||||
}; |
||||
|
||||
return Ok(version); |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,48 @@
|
||||
using Microsoft.AspNetCore.Http; |
||||
using Microsoft.AspNetCore.Mvc; |
||||
using Newtonsoft.Json.Linq; |
||||
using ReZero.DependencyInjection; |
||||
using ReZero.SuperAPI; |
||||
using System; |
||||
using System.Threading.Tasks; |
||||
using medical.transfomer.business; |
||||
|
||||
namespace medical.insu.transfomer.Controllers |
||||
{ |
||||
[Api(200100, GroupName = "分组0")] |
||||
public class MedCommonController |
||||
{ |
||||
[DI] |
||||
public TransformerFactory? transformerFactory { get; set; } |
||||
|
||||
//执行国家医保接口 |
||||
[HttpPost] |
||||
public async Task<object> execPublic(JObject value) |
||||
{ |
||||
try |
||||
{ |
||||
if (transformerFactory == null) |
||||
{ |
||||
return new { code = -1, msg = "医保转换服务未初始化" }; |
||||
} |
||||
|
||||
string action = value["action"]?.ToString(); |
||||
if (string.IsNullOrEmpty(action)) |
||||
{ |
||||
return new { code = -1, msg = "缺少action参数" }; |
||||
} |
||||
|
||||
// 获取数据部分 |
||||
JObject data = value["data"] as JObject ?? new JObject(); |
||||
|
||||
// 使用转换工厂处理医保交易 |
||||
return await transformerFactory.ExecuteMethod(action, data); |
||||
} |
||||
catch (Exception ex) |
||||
{ |
||||
return new { code = -1, msg = $"处理请求发生异常: {ex.Message}" }; |
||||
} |
||||
} |
||||
} |
||||
} |
||||
|
@ -0,0 +1,240 @@
|
||||
using Microsoft.AspNetCore.Mvc; |
||||
using Newtonsoft.Json.Linq; |
||||
using ReZero.DependencyInjection; |
||||
using ReZero.SuperAPI; |
||||
using System; |
||||
using System.Collections.Generic; |
||||
using System.Threading.Tasks; |
||||
using medical.transfomer.service; |
||||
using medical.transfomer.business; |
||||
using medical.transfomer.entity; |
||||
using SqlSugar; |
||||
using medical.insu.transfomer.Attributes; |
||||
|
||||
namespace medical.insu.transfomer.Controllers |
||||
{ |
||||
[Api(200100, GroupName = "分组0")] |
||||
|
||||
public class MedicalInsuranceController:ControllerBase |
||||
{ |
||||
[DI] |
||||
public MedicalInsuranceTransactionService? MedicalInsuranceService { get; set; } |
||||
|
||||
[DI] |
||||
public TransformerFactory? TransformerFactory { get; set; } |
||||
|
||||
[DI] |
||||
public ISqlSugarClient? Db { get; set; } |
||||
|
||||
/// <summary> |
||||
/// 执行医保交易 |
||||
/// </summary> |
||||
/// <param name="value">包含交易参数的JSON对象</param> |
||||
/// <returns>交易处理结果</returns> |
||||
[ApiMethod("执行医保交易")] |
||||
[HttpPost] |
||||
[SkipAuth("医保交易接口,无需身份验证")] |
||||
public async Task<object> ExecuteTransaction([FromBody] JObject value) |
||||
{ |
||||
try |
||||
{ |
||||
if (MedicalInsuranceService == null) |
||||
{ |
||||
return new { code = -1, msg = "医保交易服务未初始化" }; |
||||
} |
||||
|
||||
// 确保value不为null |
||||
if (value == null) |
||||
{ |
||||
return new { code = -1, msg = "请求数据为空" }; |
||||
} |
||||
|
||||
string methodName = value["methodName"]?.ToString(); |
||||
if (string.IsNullOrEmpty(methodName)) |
||||
{ |
||||
return new { code = -1, msg = "缺少methodName参数" }; |
||||
} |
||||
|
||||
// 获取数据部分 |
||||
JObject data = value["data"] as JObject ?? new JObject(); |
||||
|
||||
// 使用医保交易服务处理 |
||||
return await MedicalInsuranceService.ExecuteTransaction(methodName, data); |
||||
} |
||||
catch (Exception ex) |
||||
{ |
||||
return new { code = -1, msg = $"处理请求发生异常: {ex.Message}" }; |
||||
} |
||||
} |
||||
|
||||
/// <summary> |
||||
/// 获取可用的医保交易方法列表 |
||||
/// </summary> |
||||
/// <returns>医保交易方法列表</returns> |
||||
[HttpGet] |
||||
public async Task<object> GetTransactionMethods() |
||||
{ |
||||
try |
||||
{ |
||||
if (Db == null) |
||||
{ |
||||
return new { code = -1, msg = "数据库服务未初始化" }; |
||||
} |
||||
|
||||
// 获取方法配置列表 |
||||
var methods = await Db.Queryable<STD_METHOD_CONFIG>() |
||||
.Select(m => new |
||||
{ |
||||
id = m.METHOD_ID, |
||||
name = m.METHOD_NAME, |
||||
path = m.METHOD_VALUE, |
||||
saveInput = m.SAVE_INPUT, |
||||
saveOutput = m.SAVE_OUTPUT |
||||
}) |
||||
.ToListAsync(); |
||||
|
||||
return new { code = 0, msg = "获取成功", data = methods }; |
||||
} |
||||
catch (Exception ex) |
||||
{ |
||||
return new { code = -1, msg = $"获取医保交易方法列表异常: {ex.Message}" }; |
||||
} |
||||
} |
||||
|
||||
/// <summary> |
||||
/// 获取交易日志列表 |
||||
/// </summary> |
||||
/// <param name="methodName">方法名称(可选)</param> |
||||
/// <param name="status">状态(可选, 1:成功, 0:失败)</param> |
||||
/// <param name="startTime">开始时间(可选)</param> |
||||
/// <param name="endTime">结束时间(可选)</param> |
||||
/// <param name="pageIndex">页索引(从1开始)</param> |
||||
/// <param name="pageSize">页大小</param> |
||||
/// <returns>交易日志列表</returns> |
||||
[HttpGet] |
||||
public async Task<object> GetTransactionLogs( |
||||
string? methodName, |
||||
int? status, |
||||
DateTime? startTime, |
||||
DateTime? endTime, |
||||
int pageIndex = 1, |
||||
int pageSize = 20) |
||||
{ |
||||
try |
||||
{ |
||||
if (Db == null) |
||||
{ |
||||
return new { code = -1, msg = "数据库服务未初始化" }; |
||||
} |
||||
|
||||
// 构建查询条件 |
||||
var query = Db.Queryable<STD_TRANSACTION_LOG>(); |
||||
|
||||
if (!string.IsNullOrEmpty(methodName)) |
||||
{ |
||||
query = query.Where(l => l.METHOD_NAME == methodName); |
||||
} |
||||
|
||||
if (status.HasValue) |
||||
{ |
||||
query = query.Where(l => l.STATUS == status.Value); |
||||
} |
||||
|
||||
if (startTime.HasValue) |
||||
{ |
||||
query = query.Where(l => l.REQUEST_TIME >= startTime.Value); |
||||
} |
||||
|
||||
if (endTime.HasValue) |
||||
{ |
||||
query = query.Where(l => l.REQUEST_TIME <= endTime.Value); |
||||
} |
||||
|
||||
// 执行分页查询 |
||||
var result = await query |
||||
.OrderByDescending(l => l.REQUEST_TIME) |
||||
.Select(l => new |
||||
{ |
||||
id = l.LOG_ID, |
||||
methodId = l.METHOD_ID, |
||||
methodName = l.METHOD_NAME, |
||||
requestTime = l.REQUEST_TIME, |
||||
responseTime = l.RESPONSE_TIME, |
||||
status = l.STATUS, |
||||
errorMessage = l.ERROR_MESSAGE, |
||||
elapsedTime = l.ELAPSED_TIME |
||||
}) |
||||
.ToPageListAsync(pageIndex, pageSize); |
||||
|
||||
// 获取总记录数 |
||||
var total = await query.CountAsync(); |
||||
|
||||
return new |
||||
{ |
||||
code = 0, |
||||
msg = "获取成功", |
||||
data = result, |
||||
total = total, |
||||
pageIndex = pageIndex, |
||||
pageSize = pageSize, |
||||
pageCount = (total + pageSize - 1) / pageSize |
||||
}; |
||||
} |
||||
catch (Exception ex) |
||||
{ |
||||
return new { code = -1, msg = $"获取交易日志列表异常: {ex.Message}" }; |
||||
} |
||||
} |
||||
|
||||
/// <summary> |
||||
/// 获取交易日志详情 |
||||
/// </summary> |
||||
/// <param name="logId">日志ID</param> |
||||
/// <returns>交易日志详情</returns> |
||||
[HttpGet] |
||||
public async Task<object> GetTransactionLogDetail(decimal logId) |
||||
{ |
||||
try |
||||
{ |
||||
if (Db == null) |
||||
{ |
||||
return new { code = -1, msg = "数据库服务未初始化" }; |
||||
} |
||||
|
||||
// 获取日志详情 |
||||
var log = await Db.Queryable<STD_TRANSACTION_LOG>() |
||||
.FirstAsync(l => l.LOG_ID == logId); |
||||
|
||||
if (log == null) |
||||
{ |
||||
return new { code = -1, msg = $"未找到ID为{logId}的交易日志" }; |
||||
} |
||||
|
||||
return new |
||||
{ |
||||
code = 0, |
||||
msg = "获取成功", |
||||
data = new |
||||
{ |
||||
id = log.LOG_ID, |
||||
methodId = log.METHOD_ID, |
||||
methodName = log.METHOD_NAME, |
||||
requestTime = log.REQUEST_TIME, |
||||
requestData = log.REQUEST_DATA, |
||||
responseTime = log.RESPONSE_TIME, |
||||
responseData = log.RESPONSE_DATA, |
||||
status = log.STATUS, |
||||
errorMessage = log.ERROR_MESSAGE, |
||||
elapsedTime = log.ELAPSED_TIME, |
||||
clientIp = log.CLIENT_IP, |
||||
userId = log.USER_ID |
||||
} |
||||
}; |
||||
} |
||||
catch (Exception ex) |
||||
{ |
||||
return new { code = -1, msg = $"获取交易日志详情异常: {ex.Message}" }; |
||||
} |
||||
} |
||||
} |
||||
} |
@ -1,43 +0,0 @@
|
||||
using Microsoft.AspNetCore.Mvc.RazorPages; |
||||
using ReZero.SuperAPI; |
||||
using System.Security.Policy; |
||||
namespace medical.insu.transfomer |
||||
{ |
||||
/// <summary> |
||||
/// 动态接口 |
||||
/// </summary> |
||||
[Api(200100, GroupName = "分组1",Url= "/api/MyApiController")] |
||||
public class MyApiController |
||||
{ |
||||
[ApiMethod("我是A方法")] |
||||
public int A(int num,int num2) |
||||
{ |
||||
return num+num2; |
||||
} |
||||
|
||||
[ApiMethod("我是B方法")] |
||||
public string B(byte[] file) |
||||
{ |
||||
return "文件长度"+ file.Length; |
||||
} |
||||
|
||||
[ApiMethod("我是C方法", HttpMethod = HttpType.Get)] |
||||
public Object C(SqlSugar.PageModel classA) |
||||
{ |
||||
return classA; |
||||
} |
||||
|
||||
[ApiMethod("我是D方法")] |
||||
[UrlParameters] |
||||
public int D(int num, int num2) |
||||
{ |
||||
return num + num2; |
||||
} |
||||
} |
||||
|
||||
public class ClassA |
||||
{ |
||||
public int Id { get; set; } |
||||
public string? Name { get; set; } |
||||
} |
||||
} |
@ -1,29 +0,0 @@
|
||||
using ReZero.DependencyInjection; |
||||
using ReZero.SuperAPI; |
||||
namespace medical.insu.transfomer |
||||
{ |
||||
/// <summary> |
||||
/// 动态接口+IOC |
||||
/// </summary> |
||||
[Api(200100,GroupName = "分组2")] |
||||
public class MyApiWithIocController |
||||
{ |
||||
//属性注入 |
||||
[DI] |
||||
public MyService? MyService { get; set; } |
||||
|
||||
[ApiMethod("我是A方法")] |
||||
public int A(int num, int num2) |
||||
{ |
||||
return this.MyService!.CalculateSum(num, num2); |
||||
} |
||||
} |
||||
//继承IScopeContract 、ISingletonContract或者ITransientContract就可以自动注入 |
||||
public class MyService : IScopeContract |
||||
{ |
||||
public int CalculateSum(int num, int num2) |
||||
{ |
||||
return num2 + num; |
||||
} |
||||
} |
||||
} |
@ -1,26 +0,0 @@
|
||||
using ReZero.DependencyInjection; |
||||
using ReZero.SuperAPI; |
||||
using SqlSugar; |
||||
|
||||
namespace medical.insu.transfomer |
||||
{ |
||||
/// <summary> |
||||
/// 动态接口+工作单元 |
||||
/// </summary> |
||||
[Api(200100, GroupName = "分组3")] |
||||
public class MyApiWithUnitOfWorkController |
||||
{ |
||||
//属性注入 |
||||
[DI] |
||||
public ISqlSugarClient? db { get; set; } |
||||
|
||||
//工作单元,可以用自带的也可以重新写 |
||||
[UnitOfWork] |
||||
[ApiMethod("我是QueryTest方法")] |
||||
public bool QueryTest() |
||||
{ |
||||
db!.Ado.ExecuteCommand("select 1 as id"); |
||||
return true; |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,110 @@
|
||||
using medical.insu.transfomer.Attributes; |
||||
using ReZero.SuperAPI; |
||||
using System; |
||||
using System.Linq; |
||||
using System.Reflection; |
||||
|
||||
namespace medical.insu.transfomer |
||||
{ |
||||
/// <summary> |
||||
/// 定义不需要登录验证的API路径 |
||||
/// </summary> |
||||
public static class NoAuthPaths |
||||
{ |
||||
/// <summary> |
||||
/// 医保交易接口 |
||||
/// </summary> |
||||
public const string MedicalInsuranceExecuteTransaction = "/api/200100/medicalinsurancecontroller/executetransaction"; |
||||
|
||||
/// <summary> |
||||
/// 检查路径是否在免验证列表中 |
||||
/// </summary> |
||||
/// <param name="path">请求路径</param> |
||||
/// <returns>true表示不需要验证</returns> |
||||
public static bool IsNoAuthPath(string path) |
||||
{ |
||||
path = path.ToLower(); |
||||
|
||||
// 检查是否匹配医保交易接口 |
||||
if (path.Contains(MedicalInsuranceExecuteTransaction)) |
||||
{ |
||||
return true; |
||||
} |
||||
|
||||
// 如果需要添加更多免验证路径,可以在这里扩展 |
||||
|
||||
return false; |
||||
} |
||||
|
||||
/// <summary> |
||||
/// 通过特性检查是否需要验证 |
||||
/// </summary> |
||||
/// <param name="context">接口上下文</param> |
||||
/// <returns>true表示不需要验证</returns> |
||||
public static bool IsSkipAuthByAttribute(InterfaceContext context) |
||||
{ |
||||
// 先检查路径 |
||||
if (IsNoAuthPath(context.HttpContext.Request.Path)) |
||||
{ |
||||
return true; |
||||
} |
||||
|
||||
try |
||||
{ |
||||
// 检查具体接口信息 |
||||
if (context.InterfaceInfo?.DataModel?.MyMethodInfo != null) |
||||
{ |
||||
var methodInfo = context.InterfaceInfo.DataModel.MyMethodInfo; |
||||
var classFullName = methodInfo.MethodClassFullName; |
||||
var methodName = methodInfo.MethodName; |
||||
|
||||
if (!string.IsNullOrEmpty(classFullName) && !string.IsNullOrEmpty(methodName)) |
||||
{ |
||||
// 获取所有程序集 |
||||
var assemblies = AppDomain.CurrentDomain.GetAssemblies(); |
||||
|
||||
foreach (var assembly in assemblies) |
||||
{ |
||||
try |
||||
{ |
||||
// 查找类型 |
||||
var type = assembly.GetType(classFullName); |
||||
if (type != null) |
||||
{ |
||||
// 检查类是否有SkipAuthAttribute |
||||
if (type.GetCustomAttributes(typeof(SkipAuthAttribute), true).Any()) |
||||
{ |
||||
return true; |
||||
} |
||||
|
||||
// 查找方法 |
||||
var method = type.GetMethod(methodName); |
||||
if (method != null) |
||||
{ |
||||
// 检查方法是否有SkipAuthAttribute |
||||
if (method.GetCustomAttributes(typeof(SkipAuthAttribute), true).Any()) |
||||
{ |
||||
return true; |
||||
} |
||||
} |
||||
} |
||||
} |
||||
catch |
||||
{ |
||||
// 忽略查找过程中的异常 |
||||
continue; |
||||
} |
||||
} |
||||
} |
||||
} |
||||
} |
||||
catch (Exception ex) |
||||
{ |
||||
// 捕获任何异常,防止验证过程崩溃 |
||||
Console.WriteLine($"检查免验证特性时发生异常: {ex.Message}"); |
||||
} |
||||
|
||||
return false; |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,98 @@
|
||||
using System; |
||||
using System.Collections.Generic; |
||||
using System.Linq; |
||||
using SqlSugar; |
||||
namespace medical.transfomer.entity |
||||
|
||||
{ |
||||
/// <summary> |
||||
/// 医保交易日志表 |
||||
///</summary> |
||||
[SugarTable("STD_TRANSACTION_LOG")] |
||||
public class STD_TRANSACTION_LOG |
||||
{ |
||||
/// <summary> |
||||
/// 备 注:日志ID |
||||
/// 默认值: |
||||
///</summary> |
||||
[SugarColumn(ColumnName="LOG_ID", IsPrimaryKey = true, IsIdentity = true)] |
||||
public decimal? LOG_ID { get; set; } |
||||
|
||||
/// <summary> |
||||
/// 备 注:方法ID |
||||
/// 默认值: |
||||
///</summary> |
||||
[SugarColumn(ColumnName="METHOD_ID")] |
||||
public string METHOD_ID { get; set; } = null!; |
||||
|
||||
/// <summary> |
||||
/// 备 注:方法名称 |
||||
/// 默认值: |
||||
///</summary> |
||||
[SugarColumn(ColumnName="METHOD_NAME")] |
||||
public string METHOD_NAME { get; set; } = null!; |
||||
|
||||
/// <summary> |
||||
/// 备 注:请求时间 |
||||
/// 默认值: |
||||
///</summary> |
||||
[SugarColumn(ColumnName="REQUEST_TIME")] |
||||
public DateTime REQUEST_TIME { get; set; } |
||||
|
||||
/// <summary> |
||||
/// 备 注:请求数据 |
||||
/// 默认值: |
||||
///</summary> |
||||
[SugarColumn(ColumnName="REQUEST_DATA", ColumnDataType = "CLOB", IsNullable = true)] |
||||
public string? REQUEST_DATA { get; set; } |
||||
|
||||
/// <summary> |
||||
/// 备 注:响应时间 |
||||
/// 默认值: |
||||
///</summary> |
||||
[SugarColumn(ColumnName="RESPONSE_TIME")] |
||||
public DateTime RESPONSE_TIME { get; set; } |
||||
|
||||
/// <summary> |
||||
/// 备 注:响应数据 |
||||
/// 默认值: |
||||
///</summary> |
||||
[SugarColumn(ColumnName="RESPONSE_DATA", ColumnDataType = "CLOB", IsNullable = true)] |
||||
public string? RESPONSE_DATA { get; set; } |
||||
|
||||
/// <summary> |
||||
/// 备 注:状态(1:成功, 0:失败) |
||||
/// 默认值: |
||||
///</summary> |
||||
[SugarColumn(ColumnName="STATUS")] |
||||
public int STATUS { get; set; } |
||||
|
||||
/// <summary> |
||||
/// 备 注:错误信息 |
||||
/// 默认值: |
||||
///</summary> |
||||
[SugarColumn(ColumnName="ERROR_MESSAGE", IsNullable = true)] |
||||
public string? ERROR_MESSAGE { get; set; } |
||||
|
||||
/// <summary> |
||||
/// 备 注:交易耗时(毫秒) |
||||
/// 默认值: |
||||
///</summary> |
||||
[SugarColumn(ColumnName="ELAPSED_TIME", IsNullable = true)] |
||||
public long? ELAPSED_TIME { get; set; } |
||||
|
||||
/// <summary> |
||||
/// 备 注:IP地址 |
||||
/// 默认值: |
||||
///</summary> |
||||
[SugarColumn(ColumnName="CLIENT_IP", IsNullable = true)] |
||||
public string? CLIENT_IP { get; set; } |
||||
|
||||
/// <summary> |
||||
/// 备 注:用户ID |
||||
/// 默认值: |
||||
///</summary> |
||||
[SugarColumn(ColumnName="USER_ID", IsNullable = true)] |
||||
public string? USER_ID { get; set; } |
||||
} |
||||
} |
@ -0,0 +1,66 @@
|
||||
using Microsoft.Extensions.DependencyInjection; |
||||
using Polly; |
||||
using Polly.Extensions.Http; |
||||
using System; |
||||
using System.Net.Http; |
||||
using Polly.Retry; |
||||
using Polly.Timeout; |
||||
using Polly.CircuitBreaker; |
||||
using Microsoft.Extensions.Http.Resilience; |
||||
|
||||
namespace medical.transfomer.business |
||||
{ |
||||
/// <summary> |
||||
/// 应用程序启动扩展方法 |
||||
/// </summary> |
||||
public static class StartupExtensions |
||||
{ |
||||
/// <summary> |
||||
/// 注册医保服务所需的服务 |
||||
/// </summary> |
||||
public static IServiceCollection AddMedicalInsuranceServices(this IServiceCollection services, string baseUrl) |
||||
{ |
||||
// 注册HttpClient工厂 |
||||
services.AddHttpClient("MedicalInsurance", client => |
||||
{ |
||||
client.BaseAddress = new Uri(baseUrl); |
||||
client.DefaultRequestHeaders.Add("Accept", "application/json"); |
||||
client.DefaultRequestHeaders.Add("User-Agent", "MedicalInsuranceClient"); |
||||
client.Timeout = TimeSpan.FromSeconds(30); |
||||
}) |
||||
.AddResilienceHandler("MedicalInsuranceResilienceHandler", builder => |
||||
{ |
||||
// 添加重试策略 |
||||
builder.AddRetry(new RetryStrategyOptions<HttpResponseMessage> |
||||
{ |
||||
ShouldHandle = new PredicateBuilder<HttpResponseMessage>() |
||||
.Handle<HttpRequestException>() |
||||
.HandleResult(r => !r.IsSuccessStatusCode), |
||||
MaxRetryAttempts = 3, |
||||
Delay = TimeSpan.FromSeconds(1), |
||||
BackoffType = DelayBackoffType.Exponential |
||||
}); |
||||
|
||||
// 添加断路器策略 |
||||
builder.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) // 断路时间 |
||||
}); |
||||
|
||||
// 添加超时策略 |
||||
builder.AddTimeout(TimeSpan.FromSeconds(30)); |
||||
}); |
||||
|
||||
// 注册转换工厂 |
||||
services.AddScoped<TransformerFactory>(); |
||||
|
||||
return services; |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,455 @@
|
||||
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, "保存交易日志异常"); |
||||
} |
||||
} |
||||
} |
||||
} |
@ -1,451 +0,0 @@
|
||||
// <auto-generated/> |
||||
// ReSharper disable All |
||||
// Disable StyleCop analysis for this file |
||||
// <copyright file="TransformerService.cs" company="Manus"> |
||||
// Copyright (c) Manus. All rights reserved. |
||||
// </copyright> |
||||
|
||||
using medical.transfomer.dto; |
||||
using System; |
||||
using System.Collections.Generic; |
||||
using System.Linq; |
||||
using System.Text.Json; |
||||
using System.Text.Json.Nodes; |
||||
|
||||
namespace medical.insu.transfomer |
||||
{ |
||||
/// <summary> |
||||
/// JSON配置转换服务类 |
||||
/// </summary> |
||||
public class TransformerService |
||||
{ |
||||
private readonly TransformationConfig _config; |
||||
private readonly IValueConverter _valueConverter; // 用于字典转换等 |
||||
|
||||
/// <summary> |
||||
/// 构造函数 |
||||
/// </summary> |
||||
/// <param name="configuration">转换配置</param> |
||||
/// <param name="valueConverter">值转换器实例 (可选)</param> |
||||
public TransformerService(TransformationConfig configuration, IValueConverter valueConverter = null) |
||||
{ |
||||
_config = configuration ?? throw new ArgumentNullException(nameof(configuration)); |
||||
_valueConverter = valueConverter ?? new DefaultValueConverter(); // 提供一个默认实现 |
||||
} |
||||
|
||||
/// <summary> |
||||
/// 从JSON字符串加载配置 |
||||
/// </summary> |
||||
/// <param name="jsonConfig">包含配置的JSON字符串</param> |
||||
/// <returns>TransformationConfig 实例</returns> |
||||
public static TransformationConfig LoadConfigFromJson(string jsonConfig) |
||||
{ |
||||
if (string.IsNullOrWhiteSpace(jsonConfig)) |
||||
{ |
||||
throw new ArgumentException("JSON configuration string cannot be null or empty.", nameof(jsonConfig)); |
||||
} |
||||
try |
||||
{ |
||||
return JsonSerializer.Deserialize<TransformationConfig>(jsonConfig, new JsonSerializerOptions |
||||
{ |
||||
PropertyNameCaseInsensitive = true // 允许属性名不区分大小写 |
||||
}); |
||||
} |
||||
catch (JsonException ex) |
||||
{ |
||||
// 在实际应用中,这里应该记录更详细的错误日志 |
||||
throw new InvalidOperationException("Failed to deserialize JSON configuration.", ex); |
||||
} |
||||
} |
||||
|
||||
/// <summary> |
||||
/// 将源数据对象根据指定方法转换为目标JSON字符串 (系统对象 -> 接口JSON) |
||||
/// </summary> |
||||
/// <param name="methodName">方法名称 (来自 method_config)</param> |
||||
/// <param name="sourceObject">源数据对象 (通常是一个C#对象或字典)</param> |
||||
/// <returns>转换后的JSON字符串</returns> |
||||
public string Transform(string methodName, object sourceObject) |
||||
{ |
||||
// 1. 查找方法配置 |
||||
var method = _config.MethodConfigs.FirstOrDefault(m => m.METHOD_NAME == methodName); |
||||
if (method == null) |
||||
{ |
||||
throw new ArgumentException($"Method '{methodName}' not found in configuration.", nameof(methodName)); |
||||
} |
||||
|
||||
// 2. 筛选出适用于当前方法和出参的组装规则 (parametr_type = 2 代表出参) |
||||
var assemblyRules = _config.ObjectAssemblies |
||||
.Where(a => a.METHOD_REF == method.METHOD_ID && a.PARAMETR_TYPE == 2 && a.ASSEMBLY_TYPE == "interface") // 假设是系统到接口的转换 |
||||
.OrderBy(a => a.OBJECT_PATH) // 排序以确保父节点先创建 |
||||
.ToList(); |
||||
|
||||
if (!assemblyRules.Any()) |
||||
{ |
||||
// 如果没有找到针对出参的interface类型组装规则,可能需要抛出错误或返回空JSON |
||||
// 根据实际需求,这里也可以查找 'sys' 类型的规则,取决于转换方向的定义 |
||||
Console.WriteLine($"Warning: No 'interface' assembly rules found for method '{methodName}' and parameter type 2 (output)."); |
||||
return "{}"; // 或者抛出异常 |
||||
} |
||||
|
||||
var rootNode = new JsonObject(); // 创建JSON根节点 |
||||
|
||||
// 3. 遍历组装规则,构建JSON |
||||
foreach (var rule in assemblyRules) |
||||
{ |
||||
// 3.1 查找对应的映射规则 (通过MappingTable关联到ObjectMapping的ObjectTableName或MappingId) |
||||
// 假设 MappingTable 直接对应 ObjectMapping 中的 ObjectTableName |
||||
var mapping = _config.ObjectMappings.FirstOrDefault(om => om.OBJECT_TABLE_NAME == rule.MAPPING_TABLE && om.INTERFACE_FIELD != null); |
||||
|
||||
// 如果MappingTable也可能是MappingId, 则需要调整查找逻辑 |
||||
// var mapping = _config.ObjectMappings.FirstOrDefault(om => |
||||
// (om.ObjectTableName == rule.MappingTable || om.MappingId == rule.MappingTable) && |
||||
// om.InterfaceField != null); |
||||
|
||||
if (mapping == null) |
||||
{ |
||||
Console.WriteLine($"Warning: No object mapping found for MappingTable '{rule.MAPPING_TABLE}' in assembly rule '{rule.ID}'. Skipping path '{rule.OBJECT_PATH}'."); |
||||
continue; |
||||
} |
||||
|
||||
// 3.2 从源对象获取值 |
||||
// 这里简化处理,假设sourceObject是字典或可以通过反射获取属性 |
||||
// 实际应用中需要更健壮的取值逻辑 |
||||
object sourceValue = GetValueFromSourceObject(sourceObject, mapping.SYSTEM_FIELD); |
||||
|
||||
if (sourceValue == null && rule.OBJECT_PATH.Contains("patientId")) // 示例:特定字段的空值处理 |
||||
{ |
||||
// Console.WriteLine($"Debug: Source value for {mapping.SystemField} is null."); |
||||
} |
||||
|
||||
// 3.3 值转换 (字典翻译、类型转换等) |
||||
object convertedValue = _valueConverter.Convert( |
||||
sourceValue, |
||||
mapping.SYSTEM_FIELD_TYPE?.ToString(), // Convert decimal? to string |
||||
mapping.OBJECT_FIELD_TYPE?.ToString(), // Convert decimal? to string |
||||
mapping.SYSTEM_DICT_NAME, |
||||
mapping.OBJECT_DICT_NAME, |
||||
rule.PARAMETR_TYPE |
||||
); |
||||
|
||||
// 3.4 根据ObjectPath设置值到JSON节点 |
||||
SetValueByJsonPath(rootNode, rule.OBJECT_PATH, convertedValue, rule.OBJECT_TYPE); |
||||
} |
||||
|
||||
return rootNode.ToJsonString(new JsonSerializerOptions { WriteIndented = true }); |
||||
} |
||||
|
||||
/// <summary> |
||||
/// 从源对象中获取指定属性的值 (简化实现) |
||||
/// </summary> |
||||
private object GetValueFromSourceObject(object source, string propertyName) |
||||
{ |
||||
if (source == null || string.IsNullOrEmpty(propertyName)) return null; |
||||
|
||||
if (source is IDictionary<string, object> dictSource) |
||||
{ |
||||
return dictSource.TryGetValue(propertyName, out var val) ? val : null; |
||||
} |
||||
if (source is JsonElement jsonElementSource && jsonElementSource.ValueKind == JsonValueKind.Object) |
||||
{ |
||||
return jsonElementSource.TryGetProperty(propertyName, out var prop) ? GetValueFromJsonElement(prop) : null; |
||||
} |
||||
|
||||
// 尝试通过反射获取属性值 |
||||
var propInfo = source.GetType().GetProperty(propertyName); |
||||
if (propInfo != null) |
||||
{ |
||||
return propInfo.GetValue(source); |
||||
} |
||||
// 如果是JsonNode,尝试获取 |
||||
if (source is JsonNode jnSource) |
||||
{ |
||||
var parts = propertyName.Split(new[] { '.' }, StringSplitOptions.RemoveEmptyEntries); |
||||
JsonNode currentNode = jnSource; |
||||
foreach (var part in parts) |
||||
{ |
||||
if (currentNode is JsonObject jo && jo.ContainsKey(part)) |
||||
{ |
||||
currentNode = jo[part]; |
||||
} |
||||
else |
||||
{ |
||||
return null; // 路径不存在 |
||||
} |
||||
} |
||||
if (currentNode is JsonValue jv) return jv.GetValue<object>(); |
||||
return currentNode; //可能是JsonObject或JsonArray |
||||
} |
||||
|
||||
Console.WriteLine($"Warning: Property '{propertyName}' not found in source object of type '{source.GetType().Name}'."); |
||||
return null; |
||||
} |
||||
|
||||
private object GetValueFromJsonElement(JsonElement element) |
||||
{ |
||||
switch (element.ValueKind) |
||||
{ |
||||
case JsonValueKind.String: return element.GetString(); |
||||
case JsonValueKind.Number: |
||||
if (element.TryGetInt32(out int i)) return i; |
||||
if (element.TryGetInt64(out long l)) return l; |
||||
if (element.TryGetDouble(out double d)) return d; |
||||
return element.GetDecimal(); // Fallback |
||||
case JsonValueKind.True: return true; |
||||
case JsonValueKind.False: return false; |
||||
case JsonValueKind.Null: return null; |
||||
case JsonValueKind.Object: return element; // Or parse to dictionary |
||||
case JsonValueKind.Array: return element; // Or parse to list |
||||
default: return null; |
||||
} |
||||
} |
||||
|
||||
/// <summary> |
||||
/// 根据JSONPath在JsonNode中设置值 (简化实现, 支持基本的路径) |
||||
/// </summary> |
||||
private void SetValueByJsonPath(JsonNode rootNode, string path, object value, int objectType) |
||||
{ |
||||
if (string.IsNullOrWhiteSpace(path) || rootNode == null) return; |
||||
|
||||
// 移除起始的 "$." |
||||
if (path.StartsWith("$.")) |
||||
{ |
||||
path = path.Substring(2); |
||||
} |
||||
|
||||
var segments = path.Split('.'); |
||||
JsonNode currentNode = rootNode; |
||||
|
||||
for (int i = 0; i < segments.Length - 1; i++) |
||||
{ |
||||
var segment = segments[i]; |
||||
if (currentNode[segment] == null) |
||||
{ |
||||
// 根据下一个segment是否是数组索引来决定创建JsonObject还是JsonArray |
||||
// 此处简化:如果下一个路径是数字,则认为是数组索引,但设计文档中object_type更可靠 |
||||
// 暂时简单处理,都创建JsonObject,实际应根据object_assembly的object_type判断 |
||||
currentNode[segment] = new JsonObject(); |
||||
} |
||||
currentNode = currentNode[segment]; |
||||
if (currentNode == null) // 防御性编程,如果中间节点创建失败或路径无效 |
||||
{ |
||||
Console.WriteLine($"Error: Could not navigate or create path segment '{segment}' in JSON path '{path}'."); |
||||
return; |
||||
} |
||||
} |
||||
|
||||
var lastSegment = segments.Last(); |
||||
JsonNode valueNode = value == null ? null : JsonValue.Create(value); // JsonValue.Create可以处理多种基本类型 |
||||
|
||||
if (value is JsonElement je) |
||||
{ |
||||
valueNode = JsonNode.Parse(je.GetRawText()); |
||||
} |
||||
else if (value is IDictionary<string, object> dictValue) |
||||
{ |
||||
valueNode = new JsonObject(); |
||||
foreach(var kvp in dictValue) |
||||
{ |
||||
((JsonObject)valueNode).Add(kvp.Key, JsonValue.Create(kvp.Value)); |
||||
} |
||||
} |
||||
else if (value is IEnumerable<object> listValue && !(value is string)) // 确保不是字符串 |
||||
{ |
||||
var jsonArray = new JsonArray(); |
||||
foreach (var item in listValue) |
||||
{ |
||||
jsonArray.Add(JsonValue.Create(item)); |
||||
} |
||||
valueNode = jsonArray; |
||||
} |
||||
|
||||
if (objectType == 2) // 列表类型 |
||||
{ |
||||
if (currentNode[lastSegment] == null || !(currentNode[lastSegment] is JsonArray)) |
||||
{ |
||||
currentNode[lastSegment] = new JsonArray(); |
||||
} |
||||
((JsonArray)currentNode[lastSegment]).Add(valueNode); |
||||
} |
||||
else // 对象类型 |
||||
{ |
||||
((JsonObject)currentNode)[lastSegment] = valueNode; |
||||
} |
||||
} |
||||
|
||||
/// <summary> |
||||
/// 将接口JSON字符串根据指定方法转换为目标系统对象 (接口JSON -> 系统对象) |
||||
/// </summary> |
||||
/// <typeparam name="T">期望的目标系统对象类型</typeparam> |
||||
/// <param name="methodName">方法名称 (来自 method_config)</param> |
||||
/// <param name="sourceJson">源JSON字符串</param> |
||||
/// <returns>转换后的目标系统对象</returns> |
||||
public T Transform<T>(string methodName, string sourceJson) where T : new() |
||||
{ |
||||
// 1. 查找方法配置 |
||||
var method = _config.MethodConfigs.FirstOrDefault(m => m.METHOD_NAME == methodName); |
||||
if (method == null) |
||||
{ |
||||
throw new ArgumentException($"Method '{methodName}' not found in configuration.", nameof(methodName)); |
||||
} |
||||
|
||||
// 2. 解析源JSON |
||||
JsonNode sourceNode; |
||||
try |
||||
{ |
||||
sourceNode = JsonNode.Parse(sourceJson); |
||||
} |
||||
catch (JsonException ex) |
||||
{ |
||||
throw new ArgumentException("Invalid source JSON string.", nameof(sourceJson), ex); |
||||
} |
||||
if (sourceNode == null) throw new ArgumentException("Parsed source JSON is null.", nameof(sourceJson)); |
||||
|
||||
// 3. 筛选出适用于当前方法和入参的组装规则 (parametr_type = 1 代表入参) |
||||
var assemblyRules = _config.ObjectAssemblies |
||||
.Where(a => a.METHOD_REF == method.METHOD_ID && a.PARAMETR_TYPE == 1 && a.ASSEMBLY_TYPE == "sys") // 假设是接口到系统的转换 |
||||
.OrderBy(a => a.OBJECT_PATH) |
||||
.ToList(); |
||||
|
||||
if (!assemblyRules.Any()) |
||||
{ |
||||
Console.WriteLine($"Warning: No 'sys' assembly rules found for method '{methodName}' and parameter type 1 (input)."); |
||||
return new T(); // 或者抛出异常 |
||||
} |
||||
|
||||
T targetObject = new T(); |
||||
|
||||
// 4. 遍历组装规则, 填充目标对象 |
||||
foreach (var rule in assemblyRules) |
||||
{ |
||||
var mapping = _config.ObjectMappings.FirstOrDefault(om => om.OBJECT_TABLE_NAME == rule.MAPPING_TABLE && om.SYSTEM_FIELD != null); |
||||
if (mapping == null) |
||||
{ |
||||
Console.WriteLine($"Warning: No object mapping found for MappingTable '{rule.MAPPING_TABLE}' in assembly rule '{rule.ID}'. Skipping path '{rule.OBJECT_PATH}'."); |
||||
continue; |
||||
} |
||||
|
||||
// 4.1 从源JSON获取值 |
||||
object sourceValue = GetValueByJsonPath(sourceNode, rule.OBJECT_PATH); |
||||
|
||||
// 4.2 值转换 |
||||
object convertedValue = _valueConverter.Convert(sourceValue, mapping.OBJECT_FIELD_TYPE, mapping.SYSTEM_FIELD_TYPE, mapping.OBJECT_DICT_NAME, mapping.SYSTEM_DICT_NAME, rule.PARAMETR_TYPE); |
||||
|
||||
// 4.3 设置到目标对象属性 (简化实现, 假设属性名与SystemField一致) |
||||
// 实际应用中可能需要更复杂的属性设置逻辑,例如处理嵌套对象 |
||||
var propInfo = typeof(T).GetProperty(mapping.SYSTEM_FIELD); |
||||
if (propInfo != null && propInfo.CanWrite) |
||||
{ |
||||
try |
||||
{ |
||||
// 类型转换,确保赋的值与属性类型匹配 |
||||
var typedValue = ConvertToPropertyType(convertedValue, propInfo.PropertyType); |
||||
propInfo.SetValue(targetObject, typedValue); |
||||
} |
||||
catch (Exception ex) |
||||
{ |
||||
Console.WriteLine($"Error setting property '{mapping.SYSTEM_FIELD}': {ex.Message}"); |
||||
} |
||||
} |
||||
else |
||||
{ |
||||
Console.WriteLine($"Warning: Property '{mapping.SYSTEM_FIELD}' not found or not writable on type '{typeof(T).Name}'."); |
||||
} |
||||
} |
||||
return targetObject; |
||||
} |
||||
|
||||
/// <summary> |
||||
/// 根据JSONPath从JsonNode中获取值 (简化实现) |
||||
/// </summary> |
||||
private object GetValueByJsonPath(JsonNode rootNode, string path) |
||||
{ |
||||
if (string.IsNullOrWhiteSpace(path) || rootNode == null) return null; |
||||
if (path.StartsWith("$.")) path = path.Substring(2); |
||||
|
||||
var segments = path.Split('.'); |
||||
JsonNode currentNode = rootNode; |
||||
foreach (var segment in segments) |
||||
{ |
||||
if (currentNode is JsonObject jo && jo.ContainsKey(segment)) |
||||
{ |
||||
currentNode = jo[segment]; |
||||
} |
||||
// TODO: Add support for array indexing if needed, e.g., "items[0].name" |
||||
else |
||||
{ |
||||
return null; // 路径不存在 |
||||
} |
||||
} |
||||
if (currentNode is JsonValue jv) return jv.GetValue<object>(); // GetValue<object> 会尝试转换为合适的.NET类型 |
||||
return currentNode; //可能是JsonObject或JsonArray,调用者需要进一步处理 |
||||
} |
||||
|
||||
/// <summary> |
||||
/// 将值转换为目标属性类型 (简化实现) |
||||
/// </summary> |
||||
private object ConvertToPropertyType(object value, Type targetType) |
||||
{ |
||||
if (value == null) return null; |
||||
if (targetType.IsAssignableFrom(value.GetType())) return value; |
||||
try |
||||
{ |
||||
return Convert.ChangeType(value, targetType); |
||||
} |
||||
catch |
||||
{ |
||||
// 对于复杂类型或JsonElement,可能需要特殊处理 |
||||
if (value is JsonElement je && targetType == typeof(string)) return je.ToString(); |
||||
// 添加更多转换逻辑... |
||||
return null; |
||||
} |
||||
} |
||||
} |
||||
|
||||
/// <summary> |
||||
/// 值转换器接口 (用于字典翻译、类型转换等) |
||||
/// </summary> |
||||
public interface IValueConverter |
||||
{ |
||||
/// <summary> |
||||
/// 执行值转换 |
||||
/// </summary> |
||||
/// <param name="sourceValue">源值</param> |
||||
/// <param name="sourceType">源类型字符串 (来自配置)</param> |
||||
/// <param name="targetType">目标类型字符串 (来自配置)</param> |
||||
/// <param name="sourceDictName">源字典名称 (来自配置)</param> |
||||
/// <param name="targetDictName">目标字典名称 (来自配置)</param> |
||||
/// <param name="paramDirection">参数方向 (1入参,2出参)</param> |
||||
/// <returns>转换后的值</returns> |
||||
object Convert(object sourceValue, string sourceType, string targetType, string sourceDictName, string targetDictName, int paramDirection); |
||||
object Convert(object sourceValue, decimal? oBJECT_FIELD_TYPE, decimal? sYSTEM_FIELD_TYPE, string oBJECT_DICT_NAME, string sYSTEM_DICT_NAME, int pARAMETR_TYPE); |
||||
} |
||||
|
||||
/// <summary> |
||||
/// 默认的值转换器实现 (简单类型转换,无字典转换) |
||||
/// </summary> |
||||
public class DefaultValueConverter : IValueConverter |
||||
{ |
||||
public object Convert(object sourceValue, string sourceType, string targetType, string sourceDictName, string targetDictName, int paramDirection) |
||||
{ |
||||
// 简单示例:如果类型字符串匹配C#类型,尝试转换 |
||||
if (sourceValue == null) return null; |
||||
|
||||
try |
||||
{ |
||||
Type targetTypeResolved = Type.GetType(targetType) ?? typeof(object); |
||||
return System.Convert.ChangeType(sourceValue, targetTypeResolved); |
||||
} |
||||
catch |
||||
{ |
||||
return sourceValue; |
||||
} |
||||
} |
||||
|
||||
public object Convert(object sourceValue, decimal? objectFieldType, decimal? systemFieldType, string objectDictName, string systemDictName, int paramDirection) |
||||
{ |
||||
// 示例实现:直接返回源值,实际应用中需要根据字段类型和字典名称进行转换 |
||||
return sourceValue; |
||||
} |
||||
} |
||||
} |
||||
|
@ -1,7 +0,0 @@
|
||||
namespace medical.transfomer.service |
||||
{ |
||||
public class Class1 |
||||
{ |
||||
|
||||
} |
||||
} |
@ -0,0 +1,238 @@
|
||||
using System; |
||||
using System.Text; |
||||
using System.Net.Http; |
||||
using System.Text.Json; |
||||
using System.Threading.Tasks; |
||||
using System.Collections.Generic; |
||||
using System.Diagnostics; |
||||
using Microsoft.Extensions.Logging; |
||||
using Newtonsoft.Json.Linq; |
||||
using ReZero.DependencyInjection; |
||||
using medical.transfomer.business; |
||||
using medical.transfomer.entity; |
||||
using SqlSugar; |
||||
|
||||
namespace medical.transfomer.service |
||||
{ |
||||
/// <summary> |
||||
/// 医保交易服务,提供医保接口的请求处理功能 |
||||
/// </summary> |
||||
public class MedicalInsuranceTransactionService : IScopeContract |
||||
{ |
||||
private readonly TransformerFactory _transformerFactory; |
||||
private readonly ILogger<MedicalInsuranceTransactionService> _logger; |
||||
private readonly ISqlSugarClient _db; |
||||
private readonly IHttpClientFactory _httpClientFactory; |
||||
|
||||
public MedicalInsuranceTransactionService( |
||||
TransformerFactory transformerFactory, |
||||
ILogger<MedicalInsuranceTransactionService> logger, |
||||
ISqlSugarClient db, |
||||
IHttpClientFactory httpClientFactory) |
||||
{ |
||||
_transformerFactory = transformerFactory; |
||||
_logger = logger; |
||||
_db = db; |
||||
_httpClientFactory = httpClientFactory; |
||||
} |
||||
|
||||
/// <summary> |
||||
/// 执行医保交易 |
||||
/// </summary> |
||||
/// <param name="methodName">方法名称</param> |
||||
/// <param name="inputData">输入数据</param> |
||||
/// <returns>处理结果</returns> |
||||
public async Task<object> ExecuteTransaction(string methodName, JObject inputData) |
||||
{ |
||||
Stopwatch stopwatch = new Stopwatch(); |
||||
stopwatch.Start(); |
||||
|
||||
try |
||||
{ |
||||
_logger.LogInformation($"开始执行医保交易: {methodName}"); |
||||
|
||||
// 调用转换工厂执行方法 |
||||
var result = await _transformerFactory.ExecuteMethod(methodName, inputData); |
||||
|
||||
_logger.LogInformation($"医保交易执行完成: {methodName}"); |
||||
|
||||
// 计算耗时 |
||||
stopwatch.Stop(); |
||||
long elapsedTime = stopwatch.ElapsedMilliseconds; |
||||
|
||||
// 保存成功日志 |
||||
await SaveTransactionLog( |
||||
methodName, |
||||
inputData.ToString(), |
||||
result == null ? null : JsonSerializer.Serialize(result), |
||||
1, |
||||
"", |
||||
elapsedTime); |
||||
|
||||
return result; |
||||
} |
||||
catch (Exception ex) |
||||
{ |
||||
_logger.LogError(ex, $"执行医保交易异常: {methodName}"); |
||||
|
||||
// 计算耗时 |
||||
stopwatch.Stop(); |
||||
long elapsedTime = stopwatch.ElapsedMilliseconds; |
||||
|
||||
// 保存失败日志 |
||||
await SaveTransactionLog( |
||||
methodName, |
||||
inputData.ToString(), |
||||
null, |
||||
0, |
||||
ex.Message, |
||||
elapsedTime); |
||||
|
||||
return new { code = -1, msg = $"交易执行异常: {ex.Message}" }; |
||||
} |
||||
} |
||||
|
||||
/// <summary> |
||||
/// 根据方法ID获取方法配置信息 |
||||
/// </summary> |
||||
/// <param name="methodId">方法ID</param> |
||||
/// <returns>方法配置信息</returns> |
||||
public async Task<STD_METHOD_CONFIG> GetMethodConfigById(string methodId) |
||||
{ |
||||
return await _db.Queryable<STD_METHOD_CONFIG>() |
||||
.FirstAsync(m => m.METHOD_ID == methodId); |
||||
} |
||||
|
||||
/// <summary> |
||||
/// 根据方法名称获取方法配置信息 |
||||
/// </summary> |
||||
/// <param name="methodName">方法名称</param> |
||||
/// <returns>方法配置信息</returns> |
||||
public async Task<STD_METHOD_CONFIG> GetMethodConfigByName(string methodName) |
||||
{ |
||||
return await _db.Queryable<STD_METHOD_CONFIG>() |
||||
.FirstAsync(m => m.METHOD_NAME == methodName); |
||||
} |
||||
|
||||
/// <summary> |
||||
/// 获取方法的映射配置 |
||||
/// </summary> |
||||
/// <param name="methodId">方法ID</param> |
||||
/// <param name="parameterType">参数类型(1:入参, 2:出参)</param> |
||||
/// <returns>对象装配配置列表</returns> |
||||
public async Task<List<STD_OBJECT_ASSEMBLY>> GetMethodAssemblies(string methodId, int parameterType) |
||||
{ |
||||
return await _db.Queryable<STD_OBJECT_ASSEMBLY>() |
||||
.Where(a => a.METHOD_REF == methodId && a.PARAMETR_TYPE == parameterType) |
||||
.ToListAsync(); |
||||
} |
||||
|
||||
/// <summary> |
||||
/// 获取映射表的字段映射关系 |
||||
/// </summary> |
||||
/// <param name="tableName">表名</param> |
||||
/// <param name="isSystemToInterface">是否为系统到接口的映射</param> |
||||
/// <returns>字段映射关系列表</returns> |
||||
public async Task<List<STD_OBJECT_MAPPING>> GetFieldMappings(string tableName, bool isSystemToInterface) |
||||
{ |
||||
if (isSystemToInterface) |
||||
{ |
||||
return await _db.Queryable<STD_OBJECT_MAPPING>() |
||||
.Where(m => m.SYSTEM_TABLE_NAME == tableName) |
||||
.ToListAsync(); |
||||
} |
||||
else |
||||
{ |
||||
return await _db.Queryable<STD_OBJECT_MAPPING>() |
||||
.Where(m => m.OBJECT_TABLE_NAME == tableName) |
||||
.ToListAsync(); |
||||
} |
||||
} |
||||
|
||||
/// <summary> |
||||
/// 保存交易日志 |
||||
/// </summary> |
||||
/// <param name="methodName">方法名称</param> |
||||
/// <param name="requestData">请求数据</param> |
||||
/// <param name="responseData">响应数据</param> |
||||
/// <param name="status">状态(1:成功, 0:失败)</param> |
||||
/// <param name="errorMessage">错误信息</param> |
||||
/// <param name="elapsedTime">耗时(毫秒)</param> |
||||
/// <returns></returns> |
||||
public async Task SaveTransactionLog( |
||||
string methodName, |
||||
string requestData, |
||||
string responseData, |
||||
int status, |
||||
string errorMessage, |
||||
long elapsedTime) |
||||
{ |
||||
try |
||||
{ |
||||
// 获取方法配置 |
||||
var methodConfig = await GetMethodConfigByName(methodName); |
||||
|
||||
if (methodConfig == null) |
||||
{ |
||||
_logger.LogWarning($"未找到方法配置,无法保存交易日志: {methodName}"); |
||||
return; |
||||
} |
||||
|
||||
// 创建日志实体 |
||||
var transLog = new STD_TRANSACTION_LOG |
||||
{ |
||||
METHOD_ID = methodConfig.METHOD_ID, |
||||
METHOD_NAME = methodName, |
||||
REQUEST_TIME = DateTime.Now, |
||||
REQUEST_DATA = methodConfig.SAVE_INPUT == 1 ? requestData : null, |
||||
RESPONSE_TIME = DateTime.Now, |
||||
RESPONSE_DATA = methodConfig.SAVE_OUTPUT == 1 ? responseData : null, |
||||
STATUS = status, |
||||
ERROR_MESSAGE = errorMessage, |
||||
ELAPSED_TIME = elapsedTime, |
||||
CLIENT_IP = "", // TODO: 获取客户端IP |
||||
USER_ID = "" // TODO: 获取当前用户ID |
||||
}; |
||||
|
||||
// 保存到数据库 |
||||
await _db.Insertable(transLog).ExecuteCommandAsync(); |
||||
|
||||
_logger.LogInformation($"保存交易日志成功: {methodName}, 状态: {status}"); |
||||
} |
||||
catch (Exception ex) |
||||
{ |
||||
_logger.LogError(ex, "保存交易日志异常"); |
||||
} |
||||
} |
||||
|
||||
/// <summary> |
||||
/// 直接调用医保接口 |
||||
/// </summary> |
||||
/// <param name="endpoint">接口地址</param> |
||||
/// <param name="requestData">请求数据</param> |
||||
/// <returns>响应结果</returns> |
||||
public async Task<JObject> CallMedicalInsuranceApi(string endpoint, JObject requestData) |
||||
{ |
||||
try |
||||
{ |
||||
var httpClient = _httpClientFactory.CreateClient("MedicalInsurance"); |
||||
|
||||
var content = new StringContent( |
||||
requestData.ToString(), |
||||
Encoding.UTF8, |
||||
"application/json"); |
||||
|
||||
var response = await httpClient.PostAsync(endpoint, content); |
||||
response.EnsureSuccessStatusCode(); |
||||
|
||||
var responseString = await response.Content.ReadAsStringAsync(); |
||||
return JObject.Parse(responseString); |
||||
} |
||||
catch (Exception ex) |
||||
{ |
||||
_logger.LogError(ex, $"调用医保接口异常: {endpoint}"); |
||||
throw; |
||||
} |
||||
} |
||||
} |
||||
} |
Loading…
Reference in new issue