parent
commit
bd0c5db6e7
  1. 9
      DependencyInjectionTest/appsettings.json
  2. BIN
      NugetTest/rezero.db
  3. 66
      ReZero/SuperAPI/DatabseModels/Entities/STD/STD_METHOD_CONFIG.cs
  4. 67
      ReZero/SuperAPI/DatabseModels/Entities/STD/STD_OBJECT_ASSEMBLY.cs
  5. 102
      ReZero/SuperAPI/DatabseModels/Entities/STD/STD_OBJECT_MAPPING.cs
  6. 110
      SuperAPI/Models/Insurance/InsuranceRequest.cs
  7. 24
      SuperAPI/Models/Insurance/InsuranceResponse.cs
  8. 155
      SuperAPI/Models/MedicalTransactionSystem.cs
  9. 67
      SuperAPI/Models/MethodConfig.cs
  10. 62
      SuperAPI/Models/ObjectAssembly.cs
  11. 109
      SuperAPI/Models/ObjectMapping.cs
  12. 16
      SuperAPI/Models/TransformationConfig.cs
  13. 2
      SuperAPI/Properties/launchSettings.json
  14. 17
      SuperAPI/Services/InsuranceBusinessServices/IInsuranceBusinessService.cs
  15. 213
      SuperAPI/Services/MedicalInsuranceService.cs
  16. 450
      SuperAPI/Services/TransformerService.cs
  17. 14
      SuperAPI/wwwroot/rezero/default_ui/std_method_config.html
  18. 110
      YbTest/Models/Insurance/InsuranceRequest.cs
  19. 24
      YbTest/Models/Insurance/InsuranceResponse.cs
  20. 155
      YbTest/Models/MedicalTransactionSystem.cs
  21. 67
      YbTest/Models/MethodConfig.cs
  22. 62
      YbTest/Models/ObjectAssembly.cs
  23. 109
      YbTest/Models/ObjectMapping.cs
  24. 14
      YbTest/Models/TransformationConfig.cs
  25. 26
      YbTest/Program.cs
  26. 38
      YbTest/Properties/launchSettings.json
  27. 17
      YbTest/Services/InsuranceBusinessServices/IInsuranceBusinessService.cs
  28. 210
      YbTest/Services/MedicalInsuranceService.cs
  29. 449
      YbTest/Services/TransformerService.cs
  30. 18
      YbTest/YbTest.csproj
  31. 9
      YbTest/appsettings.Development.json
  32. 9
      YbTest/appsettings.json

9
DependencyInjectionTest/appsettings.json

@ -5,5 +5,12 @@
"Microsoft.AspNetCore": "Warning" "Microsoft.AspNetCore": "Warning"
} }
}, },
"AllowedHosts": "*" "AllowedHosts": "*",
"Kestrel": {
"Endpoints": {
"Http": {
"Url": "http://localhost:5000"
}
}
}
} }

BIN
NugetTest/rezero.db

Binary file not shown.

66
ReZero/SuperAPI/DatabseModels/Entities/STD/STD_METHOD_CONFIG.cs

@ -0,0 +1,66 @@
using System;
using System.Collections.Generic;
using System.Linq;
using SqlSugar;
namespace ReZero.SuperAPI.STD
{
/// <summary>
/// 方法信息表
///</summary>
[SugarTable("STD_METHOD_CONFIG")]
public class STD_METHOD_CONFIG
{
/// <summary>
/// 备 注:唯一标识符(UUID)
/// 默认值:
///</summary>
[SugarColumn(ColumnName="METHOD_ID" ,IsPrimaryKey = true) ]
public string METHOD_ID { get; set; } = null!;
/// <summary>
/// 备 注:方法名称(需符合医保规范)
/// 默认值:
///</summary>
[SugarColumn(ColumnName="METHOD_NAME" ) ]
public string METHOD_NAME { get; set; } = null!;
/// <summary>
/// 备 注:方法调用路径
/// 默认值:
///</summary>
[SugarColumn(ColumnName="METHOD_VALUE" ) ]
public string METHOD_VALUE { get; set; } = null!;
/// <summary>
/// 备 注:是否保存入参(0-否,1-是)
/// 默认值:
///</summary>
[SugarColumn(ColumnName="SAVE_INPUT" ) ]
public short SAVE_INPUT { get; set; }
/// <summary>
/// 备 注:是否保存出参(0-否,1-是)
/// 默认值:
///</summary>
[SugarColumn(ColumnName="SAVE_OUTPUT" ) ]
public short SAVE_OUTPUT { get; set; }
/// <summary>
/// 备 注:所属对接对象(外键关联)
/// 默认值:
///</summary>
[SugarColumn(ColumnName="BIND_OBJECT" ) ]
public long BIND_OBJECT { get; set; }
/// <summary>
/// 系统提供的方法代码
/// 默认值:
///</summary>
[SugarColumn(ColumnName = "BIND_SYS_CODE")]
public string? BIND_SYS_CODE { get; set; }
}
}

67
ReZero/SuperAPI/DatabseModels/Entities/STD/STD_OBJECT_ASSEMBLY.cs

@ -0,0 +1,67 @@
using System;
using System.Collections.Generic;
using System.Linq;
using SqlSugar;
namespace ReZero.SuperAPI.STD
{
/// <summary>
/// 对象组装表
///</summary>
[SugarTable("STD_OBJECT_ASSEMBLY")]
public class STD_OBJECT_ASSEMBLY
{
/// <summary>
/// 备 注:属性路径(对象[属性名])
/// 默认值:
///</summary>
[SugarColumn(ColumnName="OBJECT_PATH" ) ]
public string OBJECT_PATH { get; set; } = null!;
/// <summary>
/// 备 注:表名
/// 默认值:
///</summary>
[SugarColumn(ColumnName="MAPPING_TABLE" ) ]
public string MAPPING_TABLE { get; set; } = null!;
/// <summary>
/// 备 注:表归属于那(例如:系统内、医保)
/// 默认值:
///</summary>
[SugarColumn(ColumnName="ASSEMBLY_TYPE" ) ]
public string ASSEMBLY_TYPE { get; set; } = null!;
/// <summary>
/// 备 注:STD_METHOD_CONFIG表ID
/// 默认值:
///</summary>
[SugarColumn(ColumnName="METHOD_REF" ) ]
public string METHOD_REF { get; set; } = null!;
/// <summary>
/// 备 注:1 对象 2 列表
/// 默认值:
///</summary>
[SugarColumn(ColumnName="OBJECT_TYPE" ) ]
public int OBJECT_TYPE { get; set; }
/// <summary>
/// 备 注:1 入参 2 出参
/// 默认值:
///</summary>
[SugarColumn(ColumnName="PARAMETR_TYPE" ) ]
public int PARAMETR_TYPE { get; set; }
/// <summary>
/// 备 注:id
/// 默认值:
///</summary>
[SugarColumn(ColumnName="ID" ,IsPrimaryKey = true,IsIdentity = true) ]
public decimal ID { get; set; }
}
}

102
ReZero/SuperAPI/DatabseModels/Entities/STD/STD_OBJECT_MAPPING.cs

@ -0,0 +1,102 @@
using System;
using System.Collections.Generic;
using System.Linq;
using SqlSugar;
namespace Models
{
/// <summary>
/// 对象映射关系表
///</summary>
[SugarTable("STD_OBJECT_MAPPING")]
public class STD_OBJECT_MAPPING
{
/// <summary>
/// 备 注:本地系统名称(如姓名 年龄)
/// 默认值:
///</summary>
[SugarColumn(ColumnName="SYSTEM_NAME" ) ]
public string? SYSTEM_NAME { get; set; }
/// <summary>
/// 备 注:系统字段名(如name age)
/// 默认值:
///</summary>
[SugarColumn(ColumnName="SYSTEM_FIELD" ) ]
public string? SYSTEM_FIELD { get; set; }
/// <summary>
/// 备 注:医保接口字段名
/// 默认值:
///</summary>
[SugarColumn(ColumnName="INTERFACE_NAME" ) ]
public string? INTERFACE_NAME { get; set; }
/// <summary>
/// 备 注:医保字段名(需符合国标)
/// 默认值:
///</summary>
[SugarColumn(ColumnName="INTERFACE_FIELD" ) ]
public string? INTERFACE_FIELD { get; set; }
/// <summary>
/// 备 注:所属对象名(外键)
/// 默认值:
///</summary>
[SugarColumn(ColumnName="OBJECT_TABLE_NAME" ) ]
public string? OBJECT_TABLE_NAME { get; set; }
/// <summary>
/// 备 注:所属对象存储表中文名
/// 默认值:
///</summary>
[SugarColumn(ColumnName="OBJECT_TABLE_CNNAME" ) ]
public string? OBJECT_TABLE_CNNAME { get; set; }
/// <summary>
/// 备 注:系统表中文名
/// 默认值:
///</summary>
[SugarColumn(ColumnName="SYSTEM_TABLE_CNNAME" ) ]
public string? SYSTEM_TABLE_CNNAME { get; set; }
/// <summary>
/// 备 注:系统表英文名
/// 默认值:
///</summary>
[SugarColumn(ColumnName="SYSTEM_TABLE_NAME" ) ]
public string? SYSTEM_TABLE_NAME { get; set; }
/// <summary>
/// 备 注:系统字段类型
/// 默认值:
///</summary>
[SugarColumn(ColumnName="SYSTEM_FIELD_TYPE" ) ]
public decimal? SYSTEM_FIELD_TYPE { get; set; }
/// <summary>
/// 备 注:系统字典名称
/// 默认值:
///</summary>
[SugarColumn(ColumnName="SYSTEM_DICT_NAME" ) ]
public string? SYSTEM_DICT_NAME { get; set; }
/// <summary>
/// 备 注:对接字段类型
/// 默认值:
///</summary>
[SugarColumn(ColumnName="OBJECT_FIELD_TYPE" ) ]
public decimal? OBJECT_FIELD_TYPE { get; set; }
/// <summary>
/// 备 注:对接字典名称
/// 默认值:
///</summary>
[SugarColumn(ColumnName="OBJECT_DICT_NAME" ) ]
public string? OBJECT_DICT_NAME { get; set; }
}
}

110
SuperAPI/Models/Insurance/InsuranceRequest.cs

@ -0,0 +1,110 @@
using System.ComponentModel.DataAnnotations;
namespace YbTest.Models.Insurance
{
public class InsuranceRequest
{
/// <summary>
/// 交易类型代码
/// [必填] 固定4位数字,参考医保业务代码表
/// 示例:1101(人员信息查询)
/// </summary>
[Required, StringLength(4)]
public string Infno { get; set; }
/// <summary>
/// 交易流水号
/// [必填] 30位复合格式:机构编号(12位)+yyyyMMddHHmmss(14位)+4位序列号
/// 示例:H44123456202308141023001234
/// </summary>
[Required, StringLength(30)]
public string MsgId { get; set; }
/// <summary>
/// 参保地区行政区划代码
/// [必填] 6位数字,参考最新医保区划代码
/// 示例:440305(南山区)
/// </summary>
[Required, StringLength(6)]
public string MdtrtareaAdmvs { get; set; }
/// <summary>
/// 参保地行政区划代码
/// [条件必填] 6位数字,异地就医时必填
/// 示例:440306(宝安区)
/// </summary>
[StringLength(6)]
public string InsuplcAdmdvs { get; set; }
[Required, StringLength(10)]
public string RecerSysCode { get; set; }
[StringLength(100)]
public string DevNo { get; set; }
[StringLength(2000)]
public string DevSafeInfo { get; set; }
[StringLength(1024)]
public string Cainfo { get; set; }
[StringLength(10)]
public string Signtype { get; set; }
[Required, StringLength(6)]
public string Infver { get; set; }
/// <summary>
/// 操作员类型
/// [必填] 1-医保经办人 2-医院工作人员 3-参保人
/// </summary>
[Required]
public int OpterType { get; set; }
/// <summary>
/// 操作员账号
/// [必填] 30位医保经办人账号(区域编码+工作人员编号)
/// 示例:4403052021000123
/// </summary>
[Required, StringLength(30)]
public string Opter { get; set; }
/// <summary>
/// 操作员姓名
/// [必填] 50位中文实名,与医保系统登记信息一致
/// 示例:张三
/// </summary>
[Required, StringLength(50)]
public string OpterName { get; set; }
/// <summary>
/// 交易时间
/// [必填] ISO 8601格式:yyyy-MM-ddTHH:mm:ss
/// 示例:2023-08-14T15:30:00
/// </summary>
[Required]
public DateTime InfTime { get; set; }
/// <summary>
/// 定点医疗机构编号
/// [必填] 12位医保标准编码
/// 示例:H441234567890
/// </summary>
[Required, StringLength(12)]
public string FixmedinsCode { get; set; }
/// <summary>
/// 定点医疗机构名称
/// [必填] 20位中文标准名称
/// 示例:深圳市人民医院
/// </summary>
[Required, StringLength(20)]
public string FixmedinsName { get; set; }
[StringLength(30)]
public string SignNo { get; set; }
[Required, StringLength(40000)]
public string Input { get; set; }
}
}

24
SuperAPI/Models/Insurance/InsuranceResponse.cs

@ -0,0 +1,24 @@
using System;
namespace YbTest.Models.Insurance
{
public class Response
{
public ResponseHeader Header { get; set; }
public string Body { get; set; }
public string Signature { get; set; }
}
public class ResponseHeader
{
public string Version { get; set; }
public string SenderCode { get; set; }
public string ReceiverCode { get; set; }
public string MsgId { get; set; }
public string InfTime { get; set; }
public string ResultCode { get; set; }
public string ResultMsg { get; set; }
public string SignatureAlgorithm { get; set; }
public string EncryptionAlgorithm { get; set; }
}
}

155
SuperAPI/Models/MedicalTransactionSystem.cs

@ -0,0 +1,155 @@
using System.ComponentModel.DataAnnotations.Schema;
using System.ComponentModel.DataAnnotations;
namespace YbTest.Models
{
public class MedicalTransactionSystem
{
/// <summary>
/// 医保交易业务对象(入参)
/// </summary>
public class MedicalTransaction
{
/// <summary>
/// 交易编号(详见接口列表)
/// </summary>
[Required]
[StringLength(4)]
public string Infno { get; set; }
/// <summary>
/// 发送方报文ID(主键)
/// 格式:定点医药机构编号(12)+时间(14)+顺序号(4)
/// </summary>
[Key]
[StringLength(30)]
[RegularExpression(@"^\d{12}\d{14}\d{4}$", ErrorMessage = "报文ID格式无效")]
public string MsgId { get; set; }
/// <summary>
/// 就医地医保区划
/// </summary>
[Required]
[StringLength(6)]
public string MdtrtareaAdmvs { get; set; }
/// <summary>
/// 参保地医保区划(人员编号存在时必填)
/// </summary>
[StringLength(6)]
public string InsuplcAdmdvs { get; set; }
/// <summary>
/// 接收方系统代码
/// </summary>
[Required]
[StringLength(10)]
public string RecerSysCode { get; set; }
/// <summary>
/// 设备编号
/// </summary>
[StringLength(100)]
public string DevNo { get; set; }
/// <summary>
/// 设备安全信息(JSON/XML格式)
/// </summary>
[Column(TypeName = "text")]
public string DevSafeInfo { get; set; }
/// <summary>
/// 数字签名信息(安全管理码)
/// </summary>
[Column(TypeName = "text")]
public string Cainfo { get; set; }
/// <summary>
/// 签名类型
/// </summary>
[StringLength(10)]
public SignType Signtype { get; set; }
/// <summary>
/// 接口版本号
/// </summary>
[Required]
[StringLength(6)]
public string Infver { get; set; }
/// <summary>
/// 经办人类别
/// </summary>
[Required]
public OperatorType OpterType { get; set; }
/// <summary>
/// 经办人/终端编号
/// </summary>
[Required]
[StringLength(30)]
public string Opter { get; set; }
/// <summary>
/// 经办人姓名/终端名称
/// </summary>
[Required]
[StringLength(50)]
public string OpterName { get; set; }
/// <summary>
/// 交易时间
/// </summary>
[Required]
public DateTime InfTime { get; set; }
/// <summary>
/// 定点医药机构编号
/// </summary>
[Required]
[StringLength(12)]
public string FixmedinsCode { get; set; }
/// <summary>
/// 定点医药机构名称
/// </summary>
[Required]
[StringLength(20)]
public string FixmedinsName { get; set; }
/// <summary>
/// 交易签到流水号(来自9001交易)
/// </summary>
[StringLength(30)]
public string SignNo { get; set; }
/// <summary>
/// 交易输入(.NET对象序列化)
/// </summary>
[Required]
[Column(TypeName = "text")]
public string Input { get; set; }
}
/// <summary>
/// 签名类型枚举
/// </summary>
public enum SignType
{
SM2,
SM3
}
/// <summary>
/// 经办人类别枚举
/// </summary>
public enum OperatorType
{
= 1,
= 2,
= 3
}
}
}

67
SuperAPI/Models/MethodConfig.cs

@ -0,0 +1,67 @@
using System;
using System.Collections.Generic;
using System.Linq;
using SqlSugar;
namespace YbTest.Models
{
/// <summary>
/// 方法信息表
///</summary>
[SugarTable("STD_METHOD_CONFIG")]
public class MethodConfig
{
/// <summary>
/// 备 注:唯一标识符(UUID)
/// 默认值:
///</summary>
[SugarColumn(ColumnName="METHOD_ID" ,IsPrimaryKey = true) ]
public string MethodId { get; set; } = null!;
/// <summary>
/// 备 注:方法名称(需符合医保规范)
/// 默认值:
///</summary>
[SugarColumn(ColumnName="METHOD_NAME" ) ]
public string MethodName { get; set; } = null!;
/// <summary>
/// 备 注:方法调用路径
/// 默认值:
///</summary>
[SugarColumn(ColumnName="METHOD_VALUE" ) ]
public string MethodValue { get; set; } = null!;
/// <summary>
/// 备 注:是否保存入参(0-否,1-是)
/// 默认值:
///</summary>
[SugarColumn(ColumnName="SAVE_INPUT" ) ]
public short SaveInput { get; set; }
/// <summary>
/// 备 注:是否保存出参(0-否,1-是)
/// 默认值:
///</summary>
[SugarColumn(ColumnName="SAVE_OUTPUT" ) ]
public short SaveOutPut { get; set; }
/// <summary>
/// 备 注:所属对接对象(外键关联)
/// 默认值:
///</summary>
[SugarColumn(ColumnName="BIND_OBJECT" ) ]
public long BindObject { get; set; }
/// <summary>
/// 备 注:系统方法名
/// 默认值:
///</summary>
[SugarColumn(ColumnName="BIND_SYS_CODE" ) ]
public string? BindSysCode { get; set; }
}
}

62
SuperAPI/Models/ObjectAssembly.cs

@ -0,0 +1,62 @@
using System;
using System.Collections.Generic;
using System.Linq;
using SqlSugar;
namespace YbTest.Models
{
/// <summary>
/// 对象组装表
///</summary>
[SugarTable("STD_OBJECT_ASSEMBLY")]
public class ObjectAssembly
{
/// <summary>
/// 备 注:表名
/// 默认值:
///</summary>
[SugarColumn(ColumnName = "MAPPING_TABLE")]
public string MappingTable { get; set; } = null!;
/// <summary>
/// 备 注:表归属于那(例如:系统内、医保)
/// 默认值:
///</summary>
[SugarColumn(ColumnName = "ASSEMBLY_TYPE")]
public string AssemblyType { get; set; } = null!;
/// <summary>
/// 备 注:STD_METHOD_CONFIG表ID
/// 默认值:
///</summary>
[SugarColumn(ColumnName = "METHOD_REF")]
public string MethodRef { get; set; } = null!;
/// <summary>
/// 备 注:1 对象 2 列表
/// 默认值:
///</summary>
[SugarColumn(ColumnName = "OBJECT_TYPE")]
public int ObjectType { get; set; }
/// <summary>
/// 备 注:1 入参 2 出参
/// 默认值:
///</summary>
[SugarColumn(ColumnName = "PARAMETR_TYPE")]
public int ParameterType { get; set; }
/// <summary>
/// 备 注:id
/// 默认值:
///</summary>
[SugarColumn(ColumnName = "ID", IsPrimaryKey = true, IsIdentity = true)]
public decimal Id { get; set; }
/// <summary>
/// 备 注:属性路径(对象[属性名])
/// 默认值:
///</summary>
[SugarColumn(ColumnName = "OBJECT_PATH")]
public string ObjectPath { get; set; } = null!;
}
}

109
SuperAPI/Models/ObjectMapping.cs

@ -0,0 +1,109 @@
using System;
using System.Collections.Generic;
using System.Linq;
using SqlSugar;
namespace YbTest.Models
{
/// <summary>
/// 对象映射关系表
///</summary>
[SugarTable("STD_OBJECT_MAPPING")]
public class ObjectMapping
{
/// <summary>
/// 备 注:主键
/// 默认值:
///</summary>
[SugarColumn(ColumnName="ID" ,IsPrimaryKey = true,IsIdentity = true) ]
public decimal Id { get; set; }
/// <summary>
/// 备 注:本地系统名称(如姓名 年龄)
/// 默认值:
///</summary>
[SugarColumn(ColumnName="SYSTEM_NAME" ) ]
public string? SystemName { get; set; }
/// <summary>
/// 备 注:本地系统字段名(如name age)
/// 默认值:
///</summary>
[SugarColumn(ColumnName="SYSTEM_FIELD" ) ]
public string? SystemField { get; set; }
/// <summary>
/// 备 注:对接系统名称
/// 默认值:
///</summary>
[SugarColumn(ColumnName="INTERFACE_NAME" ) ]
public string? InterfaceName { get; set; }
/// <summary>
/// 备 注:对接系统字段名
/// 默认值:
///</summary>
[SugarColumn(ColumnName="INTERFACE_FIELD" ) ]
public string? InterfaceField { get; set; }
/// <summary>
/// 备 注:对接系统表英文名
/// 默认值:
///</summary>
[SugarColumn(ColumnName="OBJECT_TABLE_NAME" ) ]
public string? ObjectTableName { get; set; }
/// <summary>
/// 备 注:对接系统表中文名
/// 默认值:
///</summary>
[SugarColumn(ColumnName="OBJECT_TABLE_CNNAME" ) ]
public string? ObjectTableCnName { get; set; }
/// <summary>
/// 备 注:本地系统表中文名
/// 默认值:
///</summary>
[SugarColumn(ColumnName="SYSTEM_TABLE_CNNAME" ) ]
public string? SystemTableCnName { get; set; }
/// <summary>
/// 备 注:本地系统表英文名
/// 默认值:
///</summary>
[SugarColumn(ColumnName="SYSTEM_TABLE_NAME" ) ]
public string? SystemTableName { get; set; }
/// <summary>
/// 备 注:本地系统字段类型
/// 默认值:
///</summary>
[SugarColumn(ColumnName="SYSTEM_FIELD_TYPE" ) ]
public string? SystemFieldType { get; set; }
/// <summary>
/// 备 注:本地系统字典名称
/// 默认值:
///</summary>
[SugarColumn(ColumnName="SYSTEM_DICT_NAME" ) ]
public string? SystemDictName { get; set; }
/// <summary>
/// 备 注:对接系统字段类型
/// 默认值:
///</summary>
[SugarColumn(ColumnName="OBJECT_FIELD_TYPE" ) ]
public string? ObjectFieldType { get; set; }
/// <summary>
/// 备 注:对接系统字典名称
/// 默认值:
///</summary>
[SugarColumn(ColumnName="OBJECT_DICT_NAME" ) ]
public string? ObjectDictName { get; set; }
}
}

16
SuperAPI/Models/TransformationConfig.cs

@ -0,0 +1,16 @@
using Models;
using ReZero.SuperAPI.STD;
namespace YbTest.Models
{
public class TransformationConfig
{
// 方法配置集合(对应MethodConfigs属性)
public List<STD_METHOD_CONFIG> MethodConfigs { get; set; }
// 对象组装规则集合(对应ObjectAssemblies属性)
public List<STD_OBJECT_ASSEMBLY> ObjectAssemblies { get; set; }
// 对象映射规则集合(对应ObjectMappings属性)
public List<STD_OBJECT_MAPPING> ObjectMappings { get; set; }
}
}

2
SuperAPI/Properties/launchSettings.json

@ -14,7 +14,7 @@
"dotnetRunMessages": true, "dotnetRunMessages": true,
"launchBrowser": true, "launchBrowser": true,
"launchUrl": "rezero", "launchUrl": "rezero",
"applicationUrl": "http://localhost:5267", "applicationUrl": "http://localhost:5000",
"environmentVariables": { "environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development" "ASPNETCORE_ENVIRONMENT": "Development"
} }

17
SuperAPI/Services/InsuranceBusinessServices/IInsuranceBusinessService.cs

@ -0,0 +1,17 @@
using YbTest.Models.Insurance;
namespace YbTest.Services.InsuranceBusinessServices;
public interface IInsuranceBusinessService
{
/// <summary>
/// 执行医保业务请求
/// </summary>
Response Execute(InsuranceRequest request);
/// <summary>
/// 业务校验方法
/// </summary>
void ValidateBusinessRule(InsuranceRequest request);
}

213
SuperAPI/Services/MedicalInsuranceService.cs

@ -0,0 +1,213 @@
using System.Text;
using System.Text.RegularExpressions;
using System.ComponentModel.DataAnnotations;
using YbTest.Models.Insurance;
using YbTest.Services.InsuranceBusinessServices;
using System.Collections.Concurrent;
using System.Security.Cryptography;
using System.Text.Json;
namespace YbTest.Services
{
public class MedicalInsuranceService
{
private readonly ConcurrentDictionary<string, IInsuranceBusinessService> _serviceCache = new();
public void ValidateRequest(InsuranceRequest request)
{
if (string.IsNullOrEmpty(request.MsgId))
throw new ValidationException("报文ID不能为空");
if (string.IsNullOrEmpty(request.MdtrtareaAdmvs))
throw new ValidationException("就医地医保区划不能为空");
if (string.IsNullOrEmpty(request.RecerSysCode))
throw new ValidationException("接收方系统代码不能为空");
if (string.IsNullOrEmpty(request.Infver))
throw new ValidationException("接口版本号不能为空");
if (string.IsNullOrEmpty(request.OpterType.ToString()))
throw new ValidationException("经办人类别不能为空");
if (string.IsNullOrEmpty(request.Opter))
throw new ValidationException("经办人不能为空");
if (string.IsNullOrEmpty(request.OpterName))
throw new ValidationException("经办人姓名不能为空");
// 假设 request.InfTime 现在是 DateTime 类型,将其转换为字符串进行判断
if (string.IsNullOrEmpty(request.InfTime.ToString("yyyyMMddHHmmss")))
throw new ValidationException("交易时间不能为空");
if (string.IsNullOrEmpty(request.FixmedinsCode))
throw new ValidationException("定点医药机构编号不能为空");
if (string.IsNullOrEmpty(request.FixmedinsName))
throw new ValidationException("定点医药机构名称不能为空");
}
public IInsuranceBusinessService GetServiceByInfoNo(string infoNo)
{
return _serviceCache.GetOrAdd(infoNo, key => key switch
{
// "1101" => new PersonInfoService(),
// "2101" => new RegistrationService(),
// "2201" => new PreSettlementService(),
// _ => throw new BusinessException($"未知的交易编号: {key}")
});
}
//入口方法
public Response ProcessRequest(InsuranceRequest request)
{
ValidateRequest(request);
ValidateParameterFormat(request);
VerifySignature(request);
var service = GetServiceByInfoNo(request.Infno);
var result = service.Execute(request);
//方法调用路径?
return new Response
{
Header = GenerateResponseHeader(request),
Body = EncryptData(result),
Signature = GenerateSignature(result)
};
}
// 新增参数格式验证
public static void ValidateParameterFormat(InsuranceRequest request)
{
// 增强msgid格式校验
if (!Regex.IsMatch(request.MsgId, @"^\d{12}\d{14}\d{4}$"))
throw new ValidationException("报文ID格式必须为12位机构编号+14位时间+4位流水号");
// 条件必填验证
if (!string.IsNullOrEmpty(request.Input) && string.IsNullOrEmpty(request.InsuplcAdmdvs))
throw new ValidationException("当交易输入包含人员编号时,参保地医保区划为必填项");
// 版本号格式校验
if (!Regex.IsMatch(request.Infver, @"^V\d+\.\d+$"))
throw new ValidationException("接口版本号格式应为VX.X");
// 签名类型校验
if (!string.IsNullOrEmpty(request.Signtype) && !new[] { "SM2", "SM3" }.Contains(request.Signtype))
throw new ValidationException("签名类型只支持SM2/SM3");
// 由于 request.InfTime 是 DateTime 类型,先将其转换为指定格式的字符串再进行正则匹配
if (!Regex.IsMatch(request.InfTime.ToString("yyyyMMddHHmmss"), @"^\d{14}$"))
throw new ValidationException("交易时间格式必须为yyyyMMddHHmmss");
if (request.OpterName?.Length > 50)
throw new ValidationException("经办人姓名不能超过50个字符");
}
// 增强版响应头生成
public static ResponseHeader GenerateResponseHeader(InsuranceRequest request)
{
return new ResponseHeader
{
Version = "1.0",
// 由于 _config 不存在,这里假设添加一个默认值,实际使用时需要根据业务逻辑修改
SenderCode = "DefaultInstitutionCode",
ReceiverCode = request.FixmedinsCode,
MsgId = Guid.NewGuid().ToString("N").ToUpper(),
InfTime = DateTime.Now.ToString("yyyyMMddHHmmss"),
ResultCode = "1000",
ResultMsg = "处理成功",
SignatureAlgorithm = "HMAC-SHA256",
EncryptionAlgorithm = "AES-256-GCM"
};
}
// 完整签名验证流程
public static void VerifySignature(InsuranceRequest request)
{
var rawData = $"{request.MsgId}{request.InfTime}{request.FixmedinsCode}";
var hmac = new HMACSHA256(Encoding.UTF8.GetBytes("DefaultSignKey"));
var computedSignature = hmac.ComputeHash(Encoding.UTF8.GetBytes(rawData));
// 由于 InsuranceRequest 未包含 Signature 的定义,这里假设添加一个默认空字符串处理
if (!computedSignature.SequenceEqual(Convert.FromBase64String("")))
// 由于 SecurityException 可能未找到,使用更通用的 InvalidOperationException 替代
throw new InvalidOperationException("数字签名验证失败");
}
// 医保系统通信模块
public async Task<Response> CallInsuranceSystem(string endpoint, object payload)
{
// 由于 _httpClientFactory 不存在,使用新创建的 HttpClient 实例替代
using var client = new HttpClient();
// 由于 _config 不存在,暂时使用一个默认的 API Key,实际使用时需根据业务逻辑修改
client.DefaultRequestHeaders.Add("X-Medicare-API-Key", "DefaultApiKey");
var content = new StringContent(
// 引入 System.Text.Json 命名空间以使用 JsonSerializer
System.Text.Json.JsonSerializer.Serialize(payload),
Encoding.UTF8,
"application/json");
var response = await client.PostAsync(endpoint, content);
response.EnsureSuccessStatusCode();
return await response.Content.ReadFromJsonAsync<Response>();
}
public static string EncryptData(object data)
{
var json = JsonSerializer.Serialize(data);
// 由于 AesUtility 不存在,使用 .NET 内置的 AES 加密实现
// 定义一个临时的 AES 加密方法
static string EncryptWithAes(string plainText, string key)
{
byte[] encrypted;
using var aesAlg = Aes.Create();
{
aesAlg.Key = Encoding.UTF8.GetBytes(key);
aesAlg.GenerateIV();
ICryptoTransform encryptor = aesAlg.CreateEncryptor(aesAlg.Key, aesAlg.IV);
using (MemoryStream msEncrypt = new MemoryStream())
{
using (CryptoStream csEncrypt = new CryptoStream(msEncrypt, encryptor, CryptoStreamMode.Write))
{
using (StreamWriter swEncrypt = new StreamWriter(csEncrypt))
{
swEncrypt.Write(plainText);
}
encrypted = msEncrypt.ToArray();
}
}
// 将 IV 和加密后的数据拼接,以方便后续解密
byte[] combinedIvCt = new byte[aesAlg.IV.Length + encrypted.Length];
Array.Copy(aesAlg.IV, 0, combinedIvCt, 0, aesAlg.IV.Length);
Array.Copy(encrypted, 0, combinedIvCt, aesAlg.IV.Length, encrypted.Length);
return Convert.ToBase64String(combinedIvCt);
}
}
// 由于 _config 不存在,使用一个默认的加密密钥替代,实际使用时需根据业务逻辑修改
return EncryptWithAes(json, "DefaultEncryptionKey");
}
public static string GenerateSignature(object data)
{
var json = JsonSerializer.Serialize(data);
// 由于 HMACSHA256.HashData 没有单参数重载,需要创建 HMACSHA256 实例来计算哈希值
using var hmac = new HMACSHA256();
{
byte[] hashBytes = hmac.ComputeHash(Encoding.UTF8.GetBytes(json));
return Convert.ToBase64String(hashBytes);
}
}
}
}

450
SuperAPI/Services/TransformerService.cs

@ -0,0 +1,450 @@
// <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 System;
using System.Collections.Generic;
using System.Linq;
using System.Text.Json;
using System.Text.Json.Nodes;
namespace YbTest.Models
{
/// <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;
}
}
}

14
SuperAPI/wwwroot/rezero/default_ui/std_method_config.html

@ -257,6 +257,20 @@
id="message-text" id="message-text"
></textarea> ></textarea>
</div> </div>
<div class="form-group">
<label
for="bind-sys-code"
class="control-label"
>系统方法名:</label
>
<input
type="text"
v-model="formData.BIND_SYS_CODE"
name="BIND_SYS_CODE"
class="form-control"
id="bind-sys-code"
/>
</div>
</form> </form>
</div> </div>
<div class="modal-footer"> <div class="modal-footer">

110
YbTest/Models/Insurance/InsuranceRequest.cs

@ -0,0 +1,110 @@
using System.ComponentModel.DataAnnotations;
namespace YbTest.Models.Insurance
{
public class InsuranceRequest
{
/// <summary>
/// 交易类型代码
/// [必填] 固定4位数字,参考医保业务代码表
/// 示例:1101(人员信息查询)
/// </summary>
[Required, StringLength(4)]
public string Infno { get; set; }
/// <summary>
/// 交易流水号
/// [必填] 30位复合格式:机构编号(12位)+yyyyMMddHHmmss(14位)+4位序列号
/// 示例:H44123456202308141023001234
/// </summary>
[Required, StringLength(30)]
public string MsgId { get; set; }
/// <summary>
/// 参保地区行政区划代码
/// [必填] 6位数字,参考最新医保区划代码
/// 示例:440305(南山区)
/// </summary>
[Required, StringLength(6)]
public string MdtrtareaAdmvs { get; set; }
/// <summary>
/// 参保地行政区划代码
/// [条件必填] 6位数字,异地就医时必填
/// 示例:440306(宝安区)
/// </summary>
[StringLength(6)]
public string InsuplcAdmdvs { get; set; }
[Required, StringLength(10)]
public string RecerSysCode { get; set; }
[StringLength(100)]
public string DevNo { get; set; }
[StringLength(2000)]
public string DevSafeInfo { get; set; }
[StringLength(1024)]
public string Cainfo { get; set; }
[StringLength(10)]
public string Signtype { get; set; }
[Required, StringLength(6)]
public string Infver { get; set; }
/// <summary>
/// 操作员类型
/// [必填] 1-医保经办人 2-医院工作人员 3-参保人
/// </summary>
[Required]
public int OpterType { get; set; }
/// <summary>
/// 操作员账号
/// [必填] 30位医保经办人账号(区域编码+工作人员编号)
/// 示例:4403052021000123
/// </summary>
[Required, StringLength(30)]
public string Opter { get; set; }
/// <summary>
/// 操作员姓名
/// [必填] 50位中文实名,与医保系统登记信息一致
/// 示例:张三
/// </summary>
[Required, StringLength(50)]
public string OpterName { get; set; }
/// <summary>
/// 交易时间
/// [必填] ISO 8601格式:yyyy-MM-ddTHH:mm:ss
/// 示例:2023-08-14T15:30:00
/// </summary>
[Required]
public DateTime InfTime { get; set; }
/// <summary>
/// 定点医疗机构编号
/// [必填] 12位医保标准编码
/// 示例:H441234567890
/// </summary>
[Required, StringLength(12)]
public string FixmedinsCode { get; set; }
/// <summary>
/// 定点医疗机构名称
/// [必填] 20位中文标准名称
/// 示例:深圳市人民医院
/// </summary>
[Required, StringLength(20)]
public string FixmedinsName { get; set; }
[StringLength(30)]
public string SignNo { get; set; }
[Required, StringLength(40000)]
public string Input { get; set; }
}
}

24
YbTest/Models/Insurance/InsuranceResponse.cs

@ -0,0 +1,24 @@
using System;
namespace YbTest.Models.Insurance
{
public class Response
{
public ResponseHeader Header { get; set; }
public string Body { get; set; }
public string Signature { get; set; }
}
public class ResponseHeader
{
public string Version { get; set; }
public string SenderCode { get; set; }
public string ReceiverCode { get; set; }
public string MsgId { get; set; }
public string InfTime { get; set; }
public string ResultCode { get; set; }
public string ResultMsg { get; set; }
public string SignatureAlgorithm { get; set; }
public string EncryptionAlgorithm { get; set; }
}
}

155
YbTest/Models/MedicalTransactionSystem.cs

@ -0,0 +1,155 @@
using System.ComponentModel.DataAnnotations.Schema;
using System.ComponentModel.DataAnnotations;
namespace YbTest.Models
{
public class MedicalTransactionSystem
{
/// <summary>
/// 医保交易业务对象(入参)
/// </summary>
public class MedicalTransaction
{
/// <summary>
/// 交易编号(详见接口列表)
/// </summary>
[Required]
[StringLength(4)]
public string Infno { get; set; }
/// <summary>
/// 发送方报文ID(主键)
/// 格式:定点医药机构编号(12)+时间(14)+顺序号(4)
/// </summary>
[Key]
[StringLength(30)]
[RegularExpression(@"^\d{12}\d{14}\d{4}$", ErrorMessage = "报文ID格式无效")]
public string MsgId { get; set; }
/// <summary>
/// 就医地医保区划
/// </summary>
[Required]
[StringLength(6)]
public string MdtrtareaAdmvs { get; set; }
/// <summary>
/// 参保地医保区划(人员编号存在时必填)
/// </summary>
[StringLength(6)]
public string InsuplcAdmdvs { get; set; }
/// <summary>
/// 接收方系统代码
/// </summary>
[Required]
[StringLength(10)]
public string RecerSysCode { get; set; }
/// <summary>
/// 设备编号
/// </summary>
[StringLength(100)]
public string DevNo { get; set; }
/// <summary>
/// 设备安全信息(JSON/XML格式)
/// </summary>
[Column(TypeName = "text")]
public string DevSafeInfo { get; set; }
/// <summary>
/// 数字签名信息(安全管理码)
/// </summary>
[Column(TypeName = "text")]
public string Cainfo { get; set; }
/// <summary>
/// 签名类型
/// </summary>
[StringLength(10)]
public SignType Signtype { get; set; }
/// <summary>
/// 接口版本号
/// </summary>
[Required]
[StringLength(6)]
public string Infver { get; set; }
/// <summary>
/// 经办人类别
/// </summary>
[Required]
public OperatorType OpterType { get; set; }
/// <summary>
/// 经办人/终端编号
/// </summary>
[Required]
[StringLength(30)]
public string Opter { get; set; }
/// <summary>
/// 经办人姓名/终端名称
/// </summary>
[Required]
[StringLength(50)]
public string OpterName { get; set; }
/// <summary>
/// 交易时间
/// </summary>
[Required]
public DateTime InfTime { get; set; }
/// <summary>
/// 定点医药机构编号
/// </summary>
[Required]
[StringLength(12)]
public string FixmedinsCode { get; set; }
/// <summary>
/// 定点医药机构名称
/// </summary>
[Required]
[StringLength(20)]
public string FixmedinsName { get; set; }
/// <summary>
/// 交易签到流水号(来自9001交易)
/// </summary>
[StringLength(30)]
public string SignNo { get; set; }
/// <summary>
/// 交易输入(.NET对象序列化)
/// </summary>
[Required]
[Column(TypeName = "text")]
public string Input { get; set; }
}
/// <summary>
/// 签名类型枚举
/// </summary>
public enum SignType
{
SM2,
SM3
}
/// <summary>
/// 经办人类别枚举
/// </summary>
public enum OperatorType
{
= 1,
= 2,
= 3
}
}
}

67
YbTest/Models/MethodConfig.cs

@ -0,0 +1,67 @@
using System;
using System.Collections.Generic;
using System.Linq;
using SqlSugar;
namespace YbTest.Models
{
/// <summary>
/// 方法信息表
///</summary>
[SugarTable("STD_METHOD_CONFIG")]
public class MethodConfig
{
/// <summary>
/// 备 注:唯一标识符(UUID)
/// 默认值:
///</summary>
[SugarColumn(ColumnName="METHOD_ID" ,IsPrimaryKey = true) ]
public string MethodId { get; set; } = null!;
/// <summary>
/// 备 注:方法名称(需符合医保规范)
/// 默认值:
///</summary>
[SugarColumn(ColumnName="METHOD_NAME" ) ]
public string MethodName { get; set; } = null!;
/// <summary>
/// 备 注:方法调用路径
/// 默认值:
///</summary>
[SugarColumn(ColumnName="METHOD_VALUE" ) ]
public string MethodValue { get; set; } = null!;
/// <summary>
/// 备 注:是否保存入参(0-否,1-是)
/// 默认值:
///</summary>
[SugarColumn(ColumnName="SAVE_INPUT" ) ]
public short SaveInput { get; set; }
/// <summary>
/// 备 注:是否保存出参(0-否,1-是)
/// 默认值:
///</summary>
[SugarColumn(ColumnName="SAVE_OUTPUT" ) ]
public short SaveOutPut { get; set; }
/// <summary>
/// 备 注:所属对接对象(外键关联)
/// 默认值:
///</summary>
[SugarColumn(ColumnName="BIND_OBJECT" ) ]
public long BindObject { get; set; }
/// <summary>
/// 备 注:系统方法名
/// 默认值:
///</summary>
[SugarColumn(ColumnName="BIND_SYS_CODE" ) ]
public string? BindSysCode { get; set; }
}
}

62
YbTest/Models/ObjectAssembly.cs

@ -0,0 +1,62 @@
using System;
using System.Collections.Generic;
using System.Linq;
using SqlSugar;
namespace YbTest.Models
{
/// <summary>
/// 对象组装表
///</summary>
[SugarTable("STD_OBJECT_ASSEMBLY")]
public class ObjectAssembly
{
/// <summary>
/// 备 注:表名
/// 默认值:
///</summary>
[SugarColumn(ColumnName = "MAPPING_TABLE")]
public string MappingTable { get; set; } = null!;
/// <summary>
/// 备 注:表归属于那(例如:系统内、医保)
/// 默认值:
///</summary>
[SugarColumn(ColumnName = "ASSEMBLY_TYPE")]
public string AssemblyType { get; set; } = null!;
/// <summary>
/// 备 注:STD_METHOD_CONFIG表ID
/// 默认值:
///</summary>
[SugarColumn(ColumnName = "METHOD_REF")]
public string MethodRef { get; set; } = null!;
/// <summary>
/// 备 注:1 对象 2 列表
/// 默认值:
///</summary>
[SugarColumn(ColumnName = "OBJECT_TYPE")]
public int ObjectType { get; set; }
/// <summary>
/// 备 注:1 入参 2 出参
/// 默认值:
///</summary>
[SugarColumn(ColumnName = "PARAMETR_TYPE")]
public int ParameterType { get; set; }
/// <summary>
/// 备 注:id
/// 默认值:
///</summary>
[SugarColumn(ColumnName = "ID", IsPrimaryKey = true, IsIdentity = true)]
public decimal Id { get; set; }
/// <summary>
/// 备 注:属性路径(对象[属性名])
/// 默认值:
///</summary>
[SugarColumn(ColumnName = "OBJECT_PATH")]
public string ObjectPath { get; set; } = null!;
}
}

109
YbTest/Models/ObjectMapping.cs

@ -0,0 +1,109 @@
using System;
using System.Collections.Generic;
using System.Linq;
using SqlSugar;
namespace YbTest.Models
{
/// <summary>
/// 对象映射关系表
///</summary>
[SugarTable("STD_OBJECT_MAPPING")]
public class ObjectMapping
{
/// <summary>
/// 备 注:主键
/// 默认值:
///</summary>
[SugarColumn(ColumnName="ID" ,IsPrimaryKey = true,IsIdentity = true) ]
public decimal Id { get; set; }
/// <summary>
/// 备 注:本地系统名称(如姓名 年龄)
/// 默认值:
///</summary>
[SugarColumn(ColumnName="SYSTEM_NAME" ) ]
public string? SystemName { get; set; }
/// <summary>
/// 备 注:本地系统字段名(如name age)
/// 默认值:
///</summary>
[SugarColumn(ColumnName="SYSTEM_FIELD" ) ]
public string? SystemField { get; set; }
/// <summary>
/// 备 注:对接系统名称
/// 默认值:
///</summary>
[SugarColumn(ColumnName="INTERFACE_NAME" ) ]
public string? InterfaceName { get; set; }
/// <summary>
/// 备 注:对接系统字段名
/// 默认值:
///</summary>
[SugarColumn(ColumnName="INTERFACE_FIELD" ) ]
public string? InterfaceField { get; set; }
/// <summary>
/// 备 注:对接系统表英文名
/// 默认值:
///</summary>
[SugarColumn(ColumnName="OBJECT_TABLE_NAME" ) ]
public string? ObjectTableName { get; set; }
/// <summary>
/// 备 注:对接系统表中文名
/// 默认值:
///</summary>
[SugarColumn(ColumnName="OBJECT_TABLE_CNNAME" ) ]
public string? ObjectTableCnName { get; set; }
/// <summary>
/// 备 注:本地系统表中文名
/// 默认值:
///</summary>
[SugarColumn(ColumnName="SYSTEM_TABLE_CNNAME" ) ]
public string? SystemTableCnName { get; set; }
/// <summary>
/// 备 注:本地系统表英文名
/// 默认值:
///</summary>
[SugarColumn(ColumnName="SYSTEM_TABLE_NAME" ) ]
public string? SystemTableName { get; set; }
/// <summary>
/// 备 注:本地系统字段类型
/// 默认值:
///</summary>
[SugarColumn(ColumnName="SYSTEM_FIELD_TYPE" ) ]
public string? SystemFieldType { get; set; }
/// <summary>
/// 备 注:本地系统字典名称
/// 默认值:
///</summary>
[SugarColumn(ColumnName="SYSTEM_DICT_NAME" ) ]
public string? SystemDictName { get; set; }
/// <summary>
/// 备 注:对接系统字段类型
/// 默认值:
///</summary>
[SugarColumn(ColumnName="OBJECT_FIELD_TYPE" ) ]
public string? ObjectFieldType { get; set; }
/// <summary>
/// 备 注:对接系统字典名称
/// 默认值:
///</summary>
[SugarColumn(ColumnName="OBJECT_DICT_NAME" ) ]
public string? ObjectDictName { get; set; }
}
}

14
YbTest/Models/TransformationConfig.cs

@ -0,0 +1,14 @@
namespace YbTest.Models
{
public class TransformationConfig
{
// 方法配置集合(对应MethodConfigs属性)
public List<MethodConfig> MethodConfigs { get; set; }
// 对象组装规则集合(对应ObjectAssemblies属性)
public List<ObjectAssembly> ObjectAssemblies { get; set; }
// 对象映射规则集合(对应ObjectMappings属性)
public List<ObjectMapping> ObjectMappings { get; set; }
}
}

26
YbTest/Program.cs

@ -0,0 +1,26 @@
var builder = WebApplication.CreateBuilder(args);
// Add services to the container.
builder.Services.AddRazorPages();
var app = builder.Build();
// Configure the HTTP request pipeline.
if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler("/Error");
// The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();
app.UseAuthorization();
app.MapRazorPages();
app.Run();

38
YbTest/Properties/launchSettings.json

@ -0,0 +1,38 @@
{
"$schema": "http://json.schemastore.org/launchsettings.json",
"iisSettings": {
"windowsAuthentication": false,
"anonymousAuthentication": true,
"iisExpress": {
"applicationUrl": "http://localhost:22272",
"sslPort": 44362
}
},
"profiles": {
"http": {
"commandName": "Project",
"dotnetRunMessages": true,
"launchBrowser": true,
"applicationUrl": "http://localhost:5000",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
},
"https": {
"commandName": "Project",
"dotnetRunMessages": true,
"launchBrowser": true,
"applicationUrl": "https://localhost:7083;http://localhost:5283",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
},
"IIS Express": {
"commandName": "IISExpress",
"launchBrowser": true,
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
}
}
}

17
YbTest/Services/InsuranceBusinessServices/IInsuranceBusinessService.cs

@ -0,0 +1,17 @@
using YbTest.Models.Insurance;
namespace YbTest.Services.InsuranceBusinessServices;
public interface IInsuranceBusinessService
{
/// <summary>
/// 执行医保业务请求
/// </summary>
Response Execute(InsuranceRequest request);
/// <summary>
/// 业务校验方法
/// </summary>
void ValidateBusinessRule(InsuranceRequest request);
}

210
YbTest/Services/MedicalInsuranceService.cs

@ -0,0 +1,210 @@
using System.Text;
using System.Text.RegularExpressions;
using System.ComponentModel.DataAnnotations;
using YbTest.Models.Insurance;
using YbTest.Services.InsuranceBusinessServices;
using System.Collections.Concurrent;
using System.Security.Cryptography;
using System.Text.Json;
namespace YbTest.Services
{
public class MedicalInsuranceService
{
private readonly ConcurrentDictionary<string, IInsuranceBusinessService> _serviceCache = new();
public void ValidateRequest(InsuranceRequest request)
{
if (string.IsNullOrEmpty(request.MsgId))
throw new ValidationException("报文ID不能为空");
if (string.IsNullOrEmpty(request.MdtrtareaAdmvs))
throw new ValidationException("就医地医保区划不能为空");
if (string.IsNullOrEmpty(request.RecerSysCode))
throw new ValidationException("接收方系统代码不能为空");
if (string.IsNullOrEmpty(request.Infver))
throw new ValidationException("接口版本号不能为空");
if (string.IsNullOrEmpty(request.OpterType.ToString()))
throw new ValidationException("经办人类别不能为空");
if (string.IsNullOrEmpty(request.Opter))
throw new ValidationException("经办人不能为空");
if (string.IsNullOrEmpty(request.OpterName))
throw new ValidationException("经办人姓名不能为空");
// 假设 request.InfTime 现在是 DateTime 类型,将其转换为字符串进行判断
if (string.IsNullOrEmpty(request.InfTime.ToString("yyyyMMddHHmmss")))
throw new ValidationException("交易时间不能为空");
if (string.IsNullOrEmpty(request.FixmedinsCode))
throw new ValidationException("定点医药机构编号不能为空");
if (string.IsNullOrEmpty(request.FixmedinsName))
throw new ValidationException("定点医药机构名称不能为空");
}
public IInsuranceBusinessService GetServiceByInfoNo(string infoNo)
{
return _serviceCache.GetOrAdd(infoNo, key => key switch
{
// "1101" => new PersonInfoService(),
// "2101" => new RegistrationService(),
// "2201" => new PreSettlementService(),
// _ => throw new BusinessException($"未知的交易编号: {key}")
});
}
//入口方法
public Response ProcessRequest(InsuranceRequest request)
{
ValidateRequest(request);
ValidateParameterFormat(request);
VerifySignature(request);
var service = GetServiceByInfoNo(request.Infno);
var result = service.Execute(request);
return new Response
{
Header = GenerateResponseHeader(request),
Body = EncryptData(result),
Signature = GenerateSignature(result)
};
}
// 新增参数格式验证
public static void ValidateParameterFormat(InsuranceRequest request)
{
// 增强msgid格式校验
if (!Regex.IsMatch(request.MsgId, @"^\d{12}\d{14}\d{4}$"))
throw new ValidationException("报文ID格式必须为12位机构编号+14位时间+4位流水号");
// 条件必填验证
if (!string.IsNullOrEmpty(request.Input) && string.IsNullOrEmpty(request.InsuplcAdmdvs))
throw new ValidationException("当交易输入包含人员编号时,参保地医保区划为必填项");
// 版本号格式校验
if (!Regex.IsMatch(request.Infver, @"^V\d+\.\d+$"))
throw new ValidationException("接口版本号格式应为VX.X");
// 签名类型校验
if (!string.IsNullOrEmpty(request.Signtype) && !new[] { "SM2", "SM3" }.Contains(request.Signtype))
throw new ValidationException("签名类型只支持SM2/SM3");
// 由于 request.InfTime 是 DateTime 类型,先将其转换为指定格式的字符串再进行正则匹配
if (!Regex.IsMatch(request.InfTime.ToString("yyyyMMddHHmmss"), @"^\d{14}$"))
throw new ValidationException("交易时间格式必须为yyyyMMddHHmmss");
if (request.OpterName?.Length > 50)
throw new ValidationException("经办人姓名不能超过50个字符");
}
// 增强版响应头生成
public static ResponseHeader GenerateResponseHeader(InsuranceRequest request)
{
return new ResponseHeader
{
Version = "1.0",
// 由于 _config 不存在,这里假设添加一个默认值,实际使用时需要根据业务逻辑修改
SenderCode = "DefaultInstitutionCode",
ReceiverCode = request.FixmedinsCode,
MsgId = Guid.NewGuid().ToString("N").ToUpper(),
InfTime = DateTime.Now.ToString("yyyyMMddHHmmss"),
ResultCode = "1000",
ResultMsg = "处理成功",
SignatureAlgorithm = "HMAC-SHA256",
EncryptionAlgorithm = "AES-256-GCM"
};
}
// 完整签名验证流程
public static void VerifySignature(InsuranceRequest request)
{
var rawData = $"{request.MsgId}{request.InfTime}{request.FixmedinsCode}";
var hmac = new HMACSHA256(Encoding.UTF8.GetBytes("DefaultSignKey"));
var computedSignature = hmac.ComputeHash(Encoding.UTF8.GetBytes(rawData));
// 由于 InsuranceRequest 未包含 Signature 的定义,这里假设添加一个默认空字符串处理
if (!computedSignature.SequenceEqual(Convert.FromBase64String("")))
// 由于 SecurityException 可能未找到,使用更通用的 InvalidOperationException 替代
throw new InvalidOperationException("数字签名验证失败");
}
// 医保系统通信模块
public async Task<Response> CallInsuranceSystem(string endpoint, object payload)
{
// 由于 _httpClientFactory 不存在,使用新创建的 HttpClient 实例替代
using var client = new HttpClient();
// 由于 _config 不存在,暂时使用一个默认的 API Key,实际使用时需根据业务逻辑修改
client.DefaultRequestHeaders.Add("X-Medicare-API-Key", "DefaultApiKey");
var content = new StringContent(
// 引入 System.Text.Json 命名空间以使用 JsonSerializer
System.Text.Json.JsonSerializer.Serialize(payload),
Encoding.UTF8,
"application/json");
var response = await client.PostAsync(endpoint, content);
response.EnsureSuccessStatusCode();
return await response.Content.ReadFromJsonAsync<Response>();
}
public static string EncryptData(object data)
{
var json = JsonSerializer.Serialize(data);
// 由于 AesUtility 不存在,使用 .NET 内置的 AES 加密实现
// 定义一个临时的 AES 加密方法
static string EncryptWithAes(string plainText, string key)
{
byte[] encrypted;
using var aesAlg = Aes.Create();
{
aesAlg.Key = Encoding.UTF8.GetBytes(key);
aesAlg.GenerateIV();
ICryptoTransform encryptor = aesAlg.CreateEncryptor(aesAlg.Key, aesAlg.IV);
using (MemoryStream msEncrypt = new MemoryStream())
{
using (CryptoStream csEncrypt = new CryptoStream(msEncrypt, encryptor, CryptoStreamMode.Write))
{
using (StreamWriter swEncrypt = new StreamWriter(csEncrypt))
{
swEncrypt.Write(plainText);
}
encrypted = msEncrypt.ToArray();
}
}
// 将 IV 和加密后的数据拼接,以方便后续解密
byte[] combinedIvCt = new byte[aesAlg.IV.Length + encrypted.Length];
Array.Copy(aesAlg.IV, 0, combinedIvCt, 0, aesAlg.IV.Length);
Array.Copy(encrypted, 0, combinedIvCt, aesAlg.IV.Length, encrypted.Length);
return Convert.ToBase64String(combinedIvCt);
}
}
// 由于 _config 不存在,使用一个默认的加密密钥替代,实际使用时需根据业务逻辑修改
return EncryptWithAes(json, "DefaultEncryptionKey");
}
public static string GenerateSignature(object data)
{
var json = JsonSerializer.Serialize(data);
// 由于 HMACSHA256.HashData 没有单参数重载,需要创建 HMACSHA256 实例来计算哈希值
using var hmac = new HMACSHA256();
{
byte[] hashBytes = hmac.ComputeHash(Encoding.UTF8.GetBytes(json));
return Convert.ToBase64String(hashBytes);
}
}
}
}

449
YbTest/Services/TransformerService.cs

@ -0,0 +1,449 @@
// <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 System;
using System.Collections.Generic;
using System.Linq;
using System.Text.Json;
using System.Text.Json.Nodes;
namespace YbTest.Models
{
/// <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.MethodName == 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.MethodRef == method.MethodId && a.ParameterType == 2 && a.AssemblyType == "interface") // 假设是系统到接口的转换
.OrderBy(a => a.ObjectPath) // 排序以确保父节点先创建
.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.ObjectTableName == rule.MappingTable && om.InterfaceField != 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.MappingTable}' in assembly rule '{rule.Id}'. Skipping path '{rule.ObjectPath}'.");
continue;
}
// 3.2 从源对象获取值
// 这里简化处理,假设sourceObject是字典或可以通过反射获取属性
// 实际应用中需要更健壮的取值逻辑
object sourceValue = GetValueFromSourceObject(sourceObject, mapping.SystemField);
if (sourceValue == null && rule.ObjectPath.Contains("patientId")) // 示例:特定字段的空值处理
{
// Console.WriteLine($"Debug: Source value for {mapping.SystemField} is null.");
}
// 3.3 值转换 (字典翻译、类型转换等)
object convertedValue = _valueConverter.Convert(sourceValue, mapping.SystemFieldType, mapping.ObjectFieldType, mapping.SystemDictName, mapping.ObjectDictName, rule.ParameterType);
// 3.4 根据ObjectPath设置值到JSON节点
SetValueByJsonPath(rootNode, rule.ObjectPath, convertedValue, rule.ObjectType);
}
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.MethodName == 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.MethodRef == method.MethodId && a.ParameterType == 1 && a.AssemblyType == "sys") // 假设是接口到系统的转换
.OrderBy(a => a.ObjectPath)
.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.ObjectTableName == rule.MappingTable && om.SystemField != null);
if (mapping == null)
{
Console.WriteLine($"Warning: No object mapping found for MappingTable '{rule.MappingTable}' in assembly rule '{rule.Id}'. Skipping path '{rule.ObjectPath}'.");
continue;
}
// 4.1 从源JSON获取值
object sourceValue = GetValueByJsonPath(sourceNode, rule.ObjectPath);
// 4.2 值转换
object convertedValue = _valueConverter.Convert(sourceValue, mapping.ObjectFieldType, mapping.SystemFieldType, mapping.ObjectDictName, mapping.SystemDictName, rule.ParameterType);
// 4.3 设置到目标对象属性 (简化实现, 假设属性名与SystemField一致)
// 实际应用中可能需要更复杂的属性设置逻辑,例如处理嵌套对象
var propInfo = typeof(T).GetProperty(mapping.SystemField);
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.SystemField}': {ex.Message}");
}
}
else
{
Console.WriteLine($"Warning: Property '{mapping.SystemField}' 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);
}
/// <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;
// TODO: 实现基于 sourceType, targetType, sourceDictName, targetDictName 的转换逻辑
// 例如,如果提供了字典名称,则查询字典进行值的映射。
// 如果类型不匹配,则尝试进行类型转换。
// 示例:简单的类型保持或尝试ChangeType
Type sysType = Type.GetType(sourceType) ?? typeof(object); // 假设sourceType是有效的.NET类型名
Type objType = Type.GetType(targetType) ?? typeof(object);
if (objType.IsAssignableFrom(sourceValue.GetType()))
{
return sourceValue;
}
try
{
return System.Convert.ChangeType(sourceValue, objType);
}
catch
{
// 转换失败,返回原值或null,或抛出异常
return sourceValue;
}
}
}
}

18
YbTest/YbTest.csproj

@ -0,0 +1,18 @@
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
</PropertyGroup>
<ItemGroup>
<Folder Include="Controllers\" />
<Folder Include="Services\" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="SqlSugarCore" Version="5.1.4.193" />
</ItemGroup>
</Project>

9
YbTest/appsettings.Development.json

@ -0,0 +1,9 @@
{
"DetailedErrors": true,
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
}
}

9
YbTest/appsettings.json

@ -0,0 +1,9 @@
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
},
"AllowedHosts": "*"
}
Loading…
Cancel
Save