diff --git a/NugetTest/rezero.db b/NugetTest/rezero.db
new file mode 100644
index 0000000..1d14aaa
Binary files /dev/null and b/NugetTest/rezero.db differ
diff --git a/ReZero.sln b/ReZero.sln
index fc9ce5b..30e3226 100644
--- a/ReZero.sln
+++ b/ReZero.sln
@@ -17,6 +17,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "DemoTest", "DemoTest", "{10
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DependencyInjectionTest", "DependencyInjectionTest\DependencyInjectionTest.csproj", "{E7355DEA-E652-4C6A-960C-CECF4B4EB673}"
EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "YbTest", "YbTest\YbTest.csproj", "{0E79520C-8BBF-4B4E-9BAB-79E4B913162F}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -43,6 +45,10 @@ Global
{E7355DEA-E652-4C6A-960C-CECF4B4EB673}.Debug|Any CPU.Build.0 = Debug|Any CPU
{E7355DEA-E652-4C6A-960C-CECF4B4EB673}.Release|Any CPU.ActiveCfg = Release|Any CPU
{E7355DEA-E652-4C6A-960C-CECF4B4EB673}.Release|Any CPU.Build.0 = Release|Any CPU
+ {0E79520C-8BBF-4B4E-9BAB-79E4B913162F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {0E79520C-8BBF-4B4E-9BAB-79E4B913162F}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {0E79520C-8BBF-4B4E-9BAB-79E4B913162F}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {0E79520C-8BBF-4B4E-9BAB-79E4B913162F}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
diff --git a/YbTest/Models/Insurance/InsuranceRequest.cs b/YbTest/Models/Insurance/InsuranceRequest.cs
new file mode 100644
index 0000000..6d49f22
--- /dev/null
+++ b/YbTest/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/YbTest/Models/Insurance/InsuranceResponse.cs b/YbTest/Models/Insurance/InsuranceResponse.cs
new file mode 100644
index 0000000..4121f15
--- /dev/null
+++ b/YbTest/Models/Insurance/InsuranceResponse.cs
@@ -0,0 +1,24 @@
+using System;
+
+namespace YbTest.Models.Insurance
+{
+ public class Response
+ {
+ public ResponseHeader Header { get; set; }
+ public string Body { get; set; }
+ public string Signature { get; set; }
+ }
+
+ public class ResponseHeader
+ {
+ public string Version { get; set; }
+ public string SenderCode { get; set; }
+ public string ReceiverCode { get; set; }
+ public string MsgId { get; set; }
+ public string InfTime { get; set; }
+ public string ResultCode { get; set; }
+ public string ResultMsg { get; set; }
+ public string SignatureAlgorithm { get; set; }
+ public string EncryptionAlgorithm { get; set; }
+ }
+}
\ No newline at end of file
diff --git a/YbTest/Models/MedicalTransactionSystem.cs b/YbTest/Models/MedicalTransactionSystem.cs
new file mode 100644
index 0000000..cf15b23
--- /dev/null
+++ b/YbTest/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/YbTest/Program.cs b/YbTest/Program.cs
new file mode 100644
index 0000000..017b164
--- /dev/null
+++ b/YbTest/Program.cs
@@ -0,0 +1,26 @@
+
+var builder = WebApplication.CreateBuilder(args);
+
+// Add services to the container.
+builder.Services.AddRazorPages();
+
+var app = builder.Build();
+
+// Configure the HTTP request pipeline.
+if (!app.Environment.IsDevelopment())
+{
+ app.UseExceptionHandler("/Error");
+ // The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
+ app.UseHsts();
+}
+
+app.UseHttpsRedirection();
+app.UseStaticFiles();
+
+app.UseRouting();
+
+app.UseAuthorization();
+
+app.MapRazorPages();
+
+app.Run();
diff --git a/YbTest/Properties/launchSettings.json b/YbTest/Properties/launchSettings.json
new file mode 100644
index 0000000..2c9ea18
--- /dev/null
+++ b/YbTest/Properties/launchSettings.json
@@ -0,0 +1,38 @@
+{
+ "$schema": "http://json.schemastore.org/launchsettings.json",
+ "iisSettings": {
+ "windowsAuthentication": false,
+ "anonymousAuthentication": true,
+ "iisExpress": {
+ "applicationUrl": "http://localhost:22272",
+ "sslPort": 44362
+ }
+ },
+ "profiles": {
+ "http": {
+ "commandName": "Project",
+ "dotnetRunMessages": true,
+ "launchBrowser": true,
+ "applicationUrl": "http://localhost:5283",
+ "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"
+ }
+ }
+ }
+}
diff --git a/YbTest/Services/InsuranceBusinessServices/IInsuranceBusinessService.cs b/YbTest/Services/InsuranceBusinessServices/IInsuranceBusinessService.cs
new file mode 100644
index 0000000..900965a
--- /dev/null
+++ b/YbTest/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/YbTest/Services/MedicalInsuranceService.cs b/YbTest/Services/MedicalInsuranceService.cs
new file mode 100644
index 0000000..5114015
--- /dev/null
+++ b/YbTest/Services/MedicalInsuranceService.cs
@@ -0,0 +1,210 @@
+using System.Text;
+using System.Text.RegularExpressions;
+using System.ComponentModel.DataAnnotations;
+using YbTest.Models.Insurance;
+using YbTest.Services.InsuranceBusinessServices;
+using System.Collections.Concurrent;
+using System.Security.Cryptography;
+using System.Text.Json;
+
+namespace YbTest.Services
+{
+ public class MedicalInsuranceService
+ {
+ private readonly ConcurrentDictionary _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/YbTest/YbTest.csproj b/YbTest/YbTest.csproj
new file mode 100644
index 0000000..075d1bc
--- /dev/null
+++ b/YbTest/YbTest.csproj
@@ -0,0 +1,14 @@
+
+
+
+ net8.0
+ enable
+ enable
+
+
+
+
+
+
+
+
diff --git a/YbTest/appsettings.Development.json b/YbTest/appsettings.Development.json
new file mode 100644
index 0000000..770d3e9
--- /dev/null
+++ b/YbTest/appsettings.Development.json
@@ -0,0 +1,9 @@
+{
+ "DetailedErrors": true,
+ "Logging": {
+ "LogLevel": {
+ "Default": "Information",
+ "Microsoft.AspNetCore": "Warning"
+ }
+ }
+}
diff --git a/YbTest/appsettings.json b/YbTest/appsettings.json
new file mode 100644
index 0000000..10f68b8
--- /dev/null
+++ b/YbTest/appsettings.json
@@ -0,0 +1,9 @@
+{
+ "Logging": {
+ "LogLevel": {
+ "Default": "Information",
+ "Microsoft.AspNetCore": "Warning"
+ }
+ },
+ "AllowedHosts": "*"
+}