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