32 changed files with 2798 additions and 2 deletions
Binary file not shown.
@ -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; } |
||||
} |
||||
|
||||
} |
@ -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; } |
||||
|
||||
|
||||
} |
||||
|
||||
} |
@ -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; } |
||||
|
||||
|
||||
} |
||||
|
||||
} |
@ -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; } |
||||
} |
||||
} |
@ -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; } |
||||
} |
||||
} |
@ -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 |
||||
} |
||||
|
||||
} |
||||
} |
@ -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; } |
||||
|
||||
|
||||
} |
||||
|
||||
} |
@ -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!; |
||||
} |
||||
} |
@ -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; } |
||||
|
||||
|
||||
} |
||||
|
||||
} |
@ -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; } |
||||
} |
||||
} |
@ -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); |
||||
} |
@ -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); |
||||
} |
||||
} |
||||
} |
||||
} |
@ -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; |
||||
} |
||||
} |
||||
} |
||||
|
@ -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; } |
||||
} |
||||
} |
@ -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; } |
||||
} |
||||
} |
@ -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 |
||||
} |
||||
|
||||
} |
||||
} |
@ -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; } |
||||
|
||||
|
||||
} |
||||
|
||||
} |
@ -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!; |
||||
} |
||||
} |
@ -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; } |
||||
|
||||
|
||||
} |
||||
|
||||
} |
@ -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; } |
||||
} |
||||
} |
@ -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(); |
@ -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" |
||||
} |
||||
} |
||||
} |
||||
} |
@ -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); |
||||
} |
@ -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); |
||||
} |
||||
} |
||||
} |
||||
} |
@ -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; |
||||
} |
||||
} |
||||
} |
||||
} |
||||
|
@ -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> |
@ -0,0 +1,9 @@
|
||||
{ |
||||
"DetailedErrors": true, |
||||
"Logging": { |
||||
"LogLevel": { |
||||
"Default": "Information", |
||||
"Microsoft.AspNetCore": "Warning" |
||||
} |
||||
} |
||||
} |
Loading…
Reference in new issue