diff --git a/DependencyInjectionTest/appsettings.json b/DependencyInjectionTest/appsettings.json index 10f68b8..ffa0d27 100644 --- a/DependencyInjectionTest/appsettings.json +++ b/DependencyInjectionTest/appsettings.json @@ -5,5 +5,12 @@ "Microsoft.AspNetCore": "Warning" } }, - "AllowedHosts": "*" + "AllowedHosts": "*", + "Kestrel": { + "Endpoints": { + "Http": { + "Url": "http://localhost:5000" + } + } + } } diff --git a/NugetTest/rezero.db b/NugetTest/rezero.db new file mode 100644 index 0000000..2c2e613 Binary files /dev/null and b/NugetTest/rezero.db differ diff --git a/ReZero/SuperAPI/DatabseModels/Entities/STD/STD_METHOD_CONFIG.cs b/ReZero/SuperAPI/DatabseModels/Entities/STD/STD_METHOD_CONFIG.cs new file mode 100644 index 0000000..ee9402d --- /dev/null +++ b/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 +{ + /// + /// 方法信息表 + /// + [SugarTable("STD_METHOD_CONFIG")] + public class STD_METHOD_CONFIG + { + + + /// + /// 备 注:唯一标识符(UUID) + /// 默认值: + /// + [SugarColumn(ColumnName="METHOD_ID" ,IsPrimaryKey = true) ] + public string METHOD_ID { get; set; } = null!; + + /// + /// 备 注:方法名称(需符合医保规范) + /// 默认值: + /// + [SugarColumn(ColumnName="METHOD_NAME" ) ] + public string METHOD_NAME { get; set; } = null!; + + /// + /// 备 注:方法调用路径 + /// 默认值: + /// + [SugarColumn(ColumnName="METHOD_VALUE" ) ] + public string METHOD_VALUE { get; set; } = null!; + + /// + /// 备 注:是否保存入参(0-否,1-是) + /// 默认值: + /// + [SugarColumn(ColumnName="SAVE_INPUT" ) ] + public short SAVE_INPUT { get; set; } + + /// + /// 备 注:是否保存出参(0-否,1-是) + /// 默认值: + /// + [SugarColumn(ColumnName="SAVE_OUTPUT" ) ] + public short SAVE_OUTPUT { get; set; } + + /// + /// 备 注:所属对接对象(外键关联) + /// 默认值: + /// + [SugarColumn(ColumnName="BIND_OBJECT" ) ] + public long BIND_OBJECT { get; set; } + + + /// + /// 系统提供的方法代码 + /// 默认值: + /// + [SugarColumn(ColumnName = "BIND_SYS_CODE")] + public string? BIND_SYS_CODE { get; set; } + } + +} \ No newline at end of file diff --git a/ReZero/SuperAPI/DatabseModels/Entities/STD/STD_OBJECT_ASSEMBLY.cs b/ReZero/SuperAPI/DatabseModels/Entities/STD/STD_OBJECT_ASSEMBLY.cs new file mode 100644 index 0000000..7cad5f5 --- /dev/null +++ b/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 +{ + /// + /// 对象组装表 + /// + [SugarTable("STD_OBJECT_ASSEMBLY")] + public class STD_OBJECT_ASSEMBLY + { + + + /// + /// 备 注:属性路径(对象[属性名]) + /// 默认值: + /// + [SugarColumn(ColumnName="OBJECT_PATH" ) ] + public string OBJECT_PATH { get; set; } = null!; + + /// + /// 备 注:表名 + /// 默认值: + /// + [SugarColumn(ColumnName="MAPPING_TABLE" ) ] + public string MAPPING_TABLE { get; set; } = null!; + + /// + /// 备 注:表归属于那(例如:系统内、医保) + /// 默认值: + /// + [SugarColumn(ColumnName="ASSEMBLY_TYPE" ) ] + public string ASSEMBLY_TYPE { get; set; } = null!; + + /// + /// 备 注:STD_METHOD_CONFIG表ID + /// 默认值: + /// + [SugarColumn(ColumnName="METHOD_REF" ) ] + public string METHOD_REF { get; set; } = null!; + + /// + /// 备 注:1 对象 2 列表 + /// 默认值: + /// + [SugarColumn(ColumnName="OBJECT_TYPE" ) ] + public int OBJECT_TYPE { get; set; } + + /// + /// 备 注:1 入参 2 出参 + /// 默认值: + /// + [SugarColumn(ColumnName="PARAMETR_TYPE" ) ] + public int PARAMETR_TYPE { get; set; } + + /// + /// 备 注:id + /// 默认值: + /// + [SugarColumn(ColumnName="ID" ,IsPrimaryKey = true,IsIdentity = true) ] + public decimal ID { get; set; } + + + } + +} \ No newline at end of file diff --git a/ReZero/SuperAPI/DatabseModels/Entities/STD/STD_OBJECT_MAPPING.cs b/ReZero/SuperAPI/DatabseModels/Entities/STD/STD_OBJECT_MAPPING.cs new file mode 100644 index 0000000..f0c4b98 --- /dev/null +++ b/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 +{ + /// + /// 对象映射关系表 + /// + [SugarTable("STD_OBJECT_MAPPING")] + public class STD_OBJECT_MAPPING + { + + + /// + /// 备 注:本地系统名称(如姓名 年龄) + /// 默认值: + /// + [SugarColumn(ColumnName="SYSTEM_NAME" ) ] + public string? SYSTEM_NAME { get; set; } + + /// + /// 备 注:系统字段名(如name age) + /// 默认值: + /// + [SugarColumn(ColumnName="SYSTEM_FIELD" ) ] + public string? SYSTEM_FIELD { get; set; } + + /// + /// 备 注:医保接口字段名 + /// 默认值: + /// + [SugarColumn(ColumnName="INTERFACE_NAME" ) ] + public string? INTERFACE_NAME { get; set; } + + /// + /// 备 注:医保字段名(需符合国标) + /// 默认值: + /// + [SugarColumn(ColumnName="INTERFACE_FIELD" ) ] + public string? INTERFACE_FIELD { get; set; } + + /// + /// 备 注:所属对象名(外键) + /// 默认值: + /// + [SugarColumn(ColumnName="OBJECT_TABLE_NAME" ) ] + public string? OBJECT_TABLE_NAME { get; set; } + + /// + /// 备 注:所属对象存储表中文名 + /// 默认值: + /// + [SugarColumn(ColumnName="OBJECT_TABLE_CNNAME" ) ] + public string? OBJECT_TABLE_CNNAME { get; set; } + + /// + /// 备 注:系统表中文名 + /// 默认值: + /// + [SugarColumn(ColumnName="SYSTEM_TABLE_CNNAME" ) ] + public string? SYSTEM_TABLE_CNNAME { get; set; } + + /// + /// 备 注:系统表英文名 + /// 默认值: + /// + [SugarColumn(ColumnName="SYSTEM_TABLE_NAME" ) ] + public string? SYSTEM_TABLE_NAME { get; set; } + + /// + /// 备 注:系统字段类型 + /// 默认值: + /// + [SugarColumn(ColumnName="SYSTEM_FIELD_TYPE" ) ] + public decimal? SYSTEM_FIELD_TYPE { get; set; } + + /// + /// 备 注:系统字典名称 + /// 默认值: + /// + [SugarColumn(ColumnName="SYSTEM_DICT_NAME" ) ] + public string? SYSTEM_DICT_NAME { get; set; } + + /// + /// 备 注:对接字段类型 + /// 默认值: + /// + [SugarColumn(ColumnName="OBJECT_FIELD_TYPE" ) ] + public decimal? OBJECT_FIELD_TYPE { get; set; } + + /// + /// 备 注:对接字典名称 + /// 默认值: + /// + [SugarColumn(ColumnName="OBJECT_DICT_NAME" ) ] + public string? OBJECT_DICT_NAME { get; set; } + + + } + +} \ No newline at end of file diff --git a/SuperAPI/Models/Insurance/InsuranceRequest.cs b/SuperAPI/Models/Insurance/InsuranceRequest.cs new file mode 100644 index 0000000..6d49f22 --- /dev/null +++ b/SuperAPI/Models/Insurance/InsuranceRequest.cs @@ -0,0 +1,110 @@ +using System.ComponentModel.DataAnnotations; + +namespace YbTest.Models.Insurance +{ + public class InsuranceRequest + { + /// + /// 交易类型代码 + /// [必填] 固定4位数字,参考医保业务代码表 + /// 示例:1101(人员信息查询) + /// + [Required, StringLength(4)] + public string Infno { get; set; } + + /// + /// 交易流水号 + /// [必填] 30位复合格式:机构编号(12位)+yyyyMMddHHmmss(14位)+4位序列号 + /// 示例:H44123456202308141023001234 + /// + [Required, StringLength(30)] + public string MsgId { get; set; } + + /// + /// 参保地区行政区划代码 + /// [必填] 6位数字,参考最新医保区划代码 + /// 示例:440305(南山区) + /// + [Required, StringLength(6)] + public string MdtrtareaAdmvs { get; set; } + + /// + /// 参保地行政区划代码 + /// [条件必填] 6位数字,异地就医时必填 + /// 示例:440306(宝安区) + /// + [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; } + + /// + /// 操作员类型 + /// [必填] 1-医保经办人 2-医院工作人员 3-参保人 + /// + [Required] + public int OpterType { get; set; } + + /// + /// 操作员账号 + /// [必填] 30位医保经办人账号(区域编码+工作人员编号) + /// 示例:4403052021000123 + /// + [Required, StringLength(30)] + public string Opter { get; set; } + + /// + /// 操作员姓名 + /// [必填] 50位中文实名,与医保系统登记信息一致 + /// 示例:张三 + /// + [Required, StringLength(50)] + public string OpterName { get; set; } + + /// + /// 交易时间 + /// [必填] ISO 8601格式:yyyy-MM-ddTHH:mm:ss + /// 示例:2023-08-14T15:30:00 + /// + [Required] + public DateTime InfTime { get; set; } + + /// + /// 定点医疗机构编号 + /// [必填] 12位医保标准编码 + /// 示例:H441234567890 + /// + [Required, StringLength(12)] + public string FixmedinsCode { get; set; } + + /// + /// 定点医疗机构名称 + /// [必填] 20位中文标准名称 + /// 示例:深圳市人民医院 + /// + [Required, StringLength(20)] + public string FixmedinsName { get; set; } + + [StringLength(30)] + public string SignNo { get; set; } + + [Required, StringLength(40000)] + public string Input { get; set; } + } +} \ No newline at end of file diff --git a/SuperAPI/Models/Insurance/InsuranceResponse.cs b/SuperAPI/Models/Insurance/InsuranceResponse.cs new file mode 100644 index 0000000..4121f15 --- /dev/null +++ b/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; } + } +} \ No newline at end of file diff --git a/SuperAPI/Models/MedicalTransactionSystem.cs b/SuperAPI/Models/MedicalTransactionSystem.cs new file mode 100644 index 0000000..cf15b23 --- /dev/null +++ b/SuperAPI/Models/MedicalTransactionSystem.cs @@ -0,0 +1,155 @@ +using System.ComponentModel.DataAnnotations.Schema; +using System.ComponentModel.DataAnnotations; + +namespace YbTest.Models +{ + public class MedicalTransactionSystem + { + + /// + /// 医保交易业务对象(入参) + /// + public class MedicalTransaction + { + /// + /// 交易编号(详见接口列表) + /// + [Required] + [StringLength(4)] + public string Infno { get; set; } + + /// + /// 发送方报文ID(主键) + /// 格式:定点医药机构编号(12)+时间(14)+顺序号(4) + /// + [Key] + [StringLength(30)] + [RegularExpression(@"^\d{12}\d{14}\d{4}$", ErrorMessage = "报文ID格式无效")] + public string MsgId { get; set; } + + /// + /// 就医地医保区划 + /// + [Required] + [StringLength(6)] + public string MdtrtareaAdmvs { get; set; } + + /// + /// 参保地医保区划(人员编号存在时必填) + /// + [StringLength(6)] + public string InsuplcAdmdvs { get; set; } + + /// + /// 接收方系统代码 + /// + [Required] + [StringLength(10)] + public string RecerSysCode { get; set; } + + /// + /// 设备编号 + /// + [StringLength(100)] + public string DevNo { get; set; } + + /// + /// 设备安全信息(JSON/XML格式) + /// + [Column(TypeName = "text")] + public string DevSafeInfo { get; set; } + + /// + /// 数字签名信息(安全管理码) + /// + [Column(TypeName = "text")] + public string Cainfo { get; set; } + + /// + /// 签名类型 + /// + [StringLength(10)] + public SignType Signtype { get; set; } + + /// + /// 接口版本号 + /// + [Required] + [StringLength(6)] + public string Infver { get; set; } + + /// + /// 经办人类别 + /// + [Required] + public OperatorType OpterType { get; set; } + + /// + /// 经办人/终端编号 + /// + [Required] + [StringLength(30)] + public string Opter { get; set; } + + /// + /// 经办人姓名/终端名称 + /// + [Required] + [StringLength(50)] + public string OpterName { get; set; } + + /// + /// 交易时间 + /// + [Required] + public DateTime InfTime { get; set; } + + /// + /// 定点医药机构编号 + /// + [Required] + [StringLength(12)] + public string FixmedinsCode { get; set; } + + /// + /// 定点医药机构名称 + /// + [Required] + [StringLength(20)] + public string FixmedinsName { get; set; } + + /// + /// 交易签到流水号(来自9001交易) + /// + [StringLength(30)] + public string SignNo { get; set; } + + /// + /// 交易输入(.NET对象序列化) + /// + [Required] + [Column(TypeName = "text")] + public string Input { get; set; } + } + + /// + /// 签名类型枚举 + /// + public enum SignType + { + SM2, + SM3 + } + + /// + /// 经办人类别枚举 + /// + public enum OperatorType + { + 经办人 = 1, + 自助终端 = 2, + 移动终端 = 3 + } + + } +} diff --git a/SuperAPI/Models/MethodConfig.cs b/SuperAPI/Models/MethodConfig.cs new file mode 100644 index 0000000..e0c63a3 --- /dev/null +++ b/SuperAPI/Models/MethodConfig.cs @@ -0,0 +1,67 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using SqlSugar; +namespace YbTest.Models +{ + /// + /// 方法信息表 + /// + [SugarTable("STD_METHOD_CONFIG")] + public class MethodConfig + { + + + /// + /// 备 注:唯一标识符(UUID) + /// 默认值: + /// + [SugarColumn(ColumnName="METHOD_ID" ,IsPrimaryKey = true) ] + public string MethodId { get; set; } = null!; + + /// + /// 备 注:方法名称(需符合医保规范) + /// 默认值: + /// + [SugarColumn(ColumnName="METHOD_NAME" ) ] + public string MethodName { get; set; } = null!; + + /// + /// 备 注:方法调用路径 + /// 默认值: + /// + [SugarColumn(ColumnName="METHOD_VALUE" ) ] + public string MethodValue { get; set; } = null!; + + /// + /// 备 注:是否保存入参(0-否,1-是) + /// 默认值: + /// + [SugarColumn(ColumnName="SAVE_INPUT" ) ] + public short SaveInput { get; set; } + + /// + /// 备 注:是否保存出参(0-否,1-是) + /// 默认值: + /// + [SugarColumn(ColumnName="SAVE_OUTPUT" ) ] + public short SaveOutPut { get; set; } + + /// + /// 备 注:所属对接对象(外键关联) + /// 默认值: + /// + [SugarColumn(ColumnName="BIND_OBJECT" ) ] + public long BindObject { get; set; } + + /// + /// 备 注:系统方法名 + /// 默认值: + /// + [SugarColumn(ColumnName="BIND_SYS_CODE" ) ] + public string? BindSysCode { get; set; } + + + } + +} \ No newline at end of file diff --git a/SuperAPI/Models/ObjectAssembly.cs b/SuperAPI/Models/ObjectAssembly.cs new file mode 100644 index 0000000..3cfaa4b --- /dev/null +++ b/SuperAPI/Models/ObjectAssembly.cs @@ -0,0 +1,62 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using SqlSugar; +namespace YbTest.Models +{ + /// + /// 对象组装表 + /// + [SugarTable("STD_OBJECT_ASSEMBLY")] + public class ObjectAssembly + { + /// + /// 备 注:表名 + /// 默认值: + /// + [SugarColumn(ColumnName = "MAPPING_TABLE")] + public string MappingTable { get; set; } = null!; + + /// + /// 备 注:表归属于那(例如:系统内、医保) + /// 默认值: + /// + [SugarColumn(ColumnName = "ASSEMBLY_TYPE")] + public string AssemblyType { get; set; } = null!; + + /// + /// 备 注:STD_METHOD_CONFIG表ID + /// 默认值: + /// + [SugarColumn(ColumnName = "METHOD_REF")] + public string MethodRef { get; set; } = null!; + + /// + /// 备 注:1 对象 2 列表 + /// 默认值: + /// + [SugarColumn(ColumnName = "OBJECT_TYPE")] + public int ObjectType { get; set; } + + /// + /// 备 注:1 入参 2 出参 + /// 默认值: + /// + [SugarColumn(ColumnName = "PARAMETR_TYPE")] + public int ParameterType { get; set; } + + /// + /// 备 注:id + /// 默认值: + /// + [SugarColumn(ColumnName = "ID", IsPrimaryKey = true, IsIdentity = true)] + public decimal Id { get; set; } + + /// + /// 备 注:属性路径(对象[属性名]) + /// 默认值: + /// + [SugarColumn(ColumnName = "OBJECT_PATH")] + public string ObjectPath { get; set; } = null!; + } +} \ No newline at end of file diff --git a/SuperAPI/Models/ObjectMapping.cs b/SuperAPI/Models/ObjectMapping.cs new file mode 100644 index 0000000..46649aa --- /dev/null +++ b/SuperAPI/Models/ObjectMapping.cs @@ -0,0 +1,109 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using SqlSugar; +namespace YbTest.Models +{ + /// + /// 对象映射关系表 + /// + [SugarTable("STD_OBJECT_MAPPING")] + public class ObjectMapping + { + + + /// + /// 备 注:主键 + /// 默认值: + /// + [SugarColumn(ColumnName="ID" ,IsPrimaryKey = true,IsIdentity = true) ] + public decimal Id { get; set; } + + /// + /// 备 注:本地系统名称(如姓名 年龄) + /// 默认值: + /// + [SugarColumn(ColumnName="SYSTEM_NAME" ) ] + public string? SystemName { get; set; } + + /// + /// 备 注:本地系统字段名(如name age) + /// 默认值: + /// + [SugarColumn(ColumnName="SYSTEM_FIELD" ) ] + public string? SystemField { get; set; } + + /// + /// 备 注:对接系统名称 + /// 默认值: + /// + [SugarColumn(ColumnName="INTERFACE_NAME" ) ] + public string? InterfaceName { get; set; } + + /// + /// 备 注:对接系统字段名 + /// 默认值: + /// + [SugarColumn(ColumnName="INTERFACE_FIELD" ) ] + public string? InterfaceField { get; set; } + + /// + /// 备 注:对接系统表英文名 + /// 默认值: + /// + [SugarColumn(ColumnName="OBJECT_TABLE_NAME" ) ] + public string? ObjectTableName { get; set; } + + /// + /// 备 注:对接系统表中文名 + /// 默认值: + /// + [SugarColumn(ColumnName="OBJECT_TABLE_CNNAME" ) ] + public string? ObjectTableCnName { get; set; } + + /// + /// 备 注:本地系统表中文名 + /// 默认值: + /// + [SugarColumn(ColumnName="SYSTEM_TABLE_CNNAME" ) ] + public string? SystemTableCnName { get; set; } + + /// + /// 备 注:本地系统表英文名 + /// 默认值: + /// + [SugarColumn(ColumnName="SYSTEM_TABLE_NAME" ) ] + public string? SystemTableName { get; set; } + + /// + /// 备 注:本地系统字段类型 + /// 默认值: + /// + [SugarColumn(ColumnName="SYSTEM_FIELD_TYPE" ) ] + public string? SystemFieldType { get; set; } + + /// + /// 备 注:本地系统字典名称 + /// 默认值: + /// + [SugarColumn(ColumnName="SYSTEM_DICT_NAME" ) ] + public string? SystemDictName { get; set; } + + /// + /// 备 注:对接系统字段类型 + /// 默认值: + /// + [SugarColumn(ColumnName="OBJECT_FIELD_TYPE" ) ] + public string? ObjectFieldType { get; set; } + + /// + /// 备 注:对接系统字典名称 + /// 默认值: + /// + [SugarColumn(ColumnName="OBJECT_DICT_NAME" ) ] + public string? ObjectDictName { get; set; } + + + } + +} \ No newline at end of file diff --git a/SuperAPI/Models/TransformationConfig.cs b/SuperAPI/Models/TransformationConfig.cs new file mode 100644 index 0000000..3913e1a --- /dev/null +++ b/SuperAPI/Models/TransformationConfig.cs @@ -0,0 +1,16 @@ +using Models; +using ReZero.SuperAPI.STD; +namespace YbTest.Models +{ + public class TransformationConfig + { + // 方法配置集合(对应MethodConfigs属性) + public List MethodConfigs { get; set; } + + // 对象组装规则集合(对应ObjectAssemblies属性) + public List ObjectAssemblies { get; set; } + + // 对象映射规则集合(对应ObjectMappings属性) + public List ObjectMappings { get; set; } + } +} diff --git a/SuperAPI/Properties/launchSettings.json b/SuperAPI/Properties/launchSettings.json index 776dbc8..d76b360 100644 --- a/SuperAPI/Properties/launchSettings.json +++ b/SuperAPI/Properties/launchSettings.json @@ -14,7 +14,7 @@ "dotnetRunMessages": true, "launchBrowser": true, "launchUrl": "rezero", - "applicationUrl": "http://localhost:5267", + "applicationUrl": "http://localhost:5000", "environmentVariables": { "ASPNETCORE_ENVIRONMENT": "Development" } diff --git a/SuperAPI/Services/InsuranceBusinessServices/IInsuranceBusinessService.cs b/SuperAPI/Services/InsuranceBusinessServices/IInsuranceBusinessService.cs new file mode 100644 index 0000000..900965a --- /dev/null +++ b/SuperAPI/Services/InsuranceBusinessServices/IInsuranceBusinessService.cs @@ -0,0 +1,17 @@ +using YbTest.Models.Insurance; + + +namespace YbTest.Services.InsuranceBusinessServices; + +public interface IInsuranceBusinessService +{ + /// + /// 执行医保业务请求 + /// + Response Execute(InsuranceRequest request); + + /// + /// 业务校验方法 + /// + void ValidateBusinessRule(InsuranceRequest request); +} \ No newline at end of file diff --git a/SuperAPI/Services/MedicalInsuranceService.cs b/SuperAPI/Services/MedicalInsuranceService.cs new file mode 100644 index 0000000..172202e --- /dev/null +++ b/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 _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 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(); + } + + 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); + } + } + } +} diff --git a/SuperAPI/Services/TransformerService.cs b/SuperAPI/Services/TransformerService.cs new file mode 100644 index 0000000..881d5bf --- /dev/null +++ b/SuperAPI/Services/TransformerService.cs @@ -0,0 +1,450 @@ +// +// ReSharper disable All +// Disable StyleCop analysis for this file +// +// Copyright (c) Manus. All rights reserved. +// + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text.Json; +using System.Text.Json.Nodes; + +namespace YbTest.Models +{ + /// + /// JSON配置转换服务类 + /// + public class TransformerService + { + private readonly TransformationConfig _config; + private readonly IValueConverter _valueConverter; // 用于字典转换等 + + /// + /// 构造函数 + /// + /// 转换配置 + /// 值转换器实例 (可选) + public TransformerService(TransformationConfig configuration, IValueConverter valueConverter = null) + { + _config = configuration ?? throw new ArgumentNullException(nameof(configuration)); + _valueConverter = valueConverter ?? new DefaultValueConverter(); // 提供一个默认实现 + } + + /// + /// 从JSON字符串加载配置 + /// + /// 包含配置的JSON字符串 + /// TransformationConfig 实例 + 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(jsonConfig, new JsonSerializerOptions + { + PropertyNameCaseInsensitive = true // 允许属性名不区分大小写 + }); + } + catch (JsonException ex) + { + // 在实际应用中,这里应该记录更详细的错误日志 + throw new InvalidOperationException("Failed to deserialize JSON configuration.", ex); + } + } + + /// + /// 将源数据对象根据指定方法转换为目标JSON字符串 (系统对象 -> 接口JSON) + /// + /// 方法名称 (来自 method_config) + /// 源数据对象 (通常是一个C#对象或字典) + /// 转换后的JSON字符串 + 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 }); + } + + /// + /// 从源对象中获取指定属性的值 (简化实现) + /// + private object GetValueFromSourceObject(object source, string propertyName) + { + if (source == null || string.IsNullOrEmpty(propertyName)) return null; + + if (source is IDictionary 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(); + 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; + } + } + + /// + /// 根据JSONPath在JsonNode中设置值 (简化实现, 支持基本的路径) + /// + 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 dictValue) + { + valueNode = new JsonObject(); + foreach(var kvp in dictValue) + { + ((JsonObject)valueNode).Add(kvp.Key, JsonValue.Create(kvp.Value)); + } + } + else if (value is IEnumerable 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; + } + } + + /// + /// 将接口JSON字符串根据指定方法转换为目标系统对象 (接口JSON -> 系统对象) + /// + /// 期望的目标系统对象类型 + /// 方法名称 (来自 method_config) + /// 源JSON字符串 + /// 转换后的目标系统对象 + public T Transform(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; + } + + /// + /// 根据JSONPath从JsonNode中获取值 (简化实现) + /// + 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(); // GetValue 会尝试转换为合适的.NET类型 + return currentNode; //可能是JsonObject或JsonArray,调用者需要进一步处理 + } + + /// + /// 将值转换为目标属性类型 (简化实现) + /// + 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; + } + } + } + + /// + /// 值转换器接口 (用于字典翻译、类型转换等) + /// + public interface IValueConverter + { + /// + /// 执行值转换 + /// + /// 源值 + /// 源类型字符串 (来自配置) + /// 目标类型字符串 (来自配置) + /// 源字典名称 (来自配置) + /// 目标字典名称 (来自配置) + /// 参数方向 (1入参,2出参) + /// 转换后的值 + 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); + } + + /// + /// 默认的值转换器实现 (简单类型转换,无字典转换) + /// + 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; + } + } +} + diff --git a/SuperAPI/wwwroot/rezero/default_ui/std_method_config.html b/SuperAPI/wwwroot/rezero/default_ui/std_method_config.html index ca1da06..3f68a59 100644 --- a/SuperAPI/wwwroot/rezero/default_ui/std_method_config.html +++ b/SuperAPI/wwwroot/rezero/default_ui/std_method_config.html @@ -257,6 +257,20 @@ id="message-text" > +
+ + +