Browse Source

医保入口方法测试版

master
DX 6 days ago
parent
commit
829760d519
  1. BIN
      NugetTest/rezero.db
  2. 6
      ReZero.sln
  3. 110
      YbTest/Models/Insurance/InsuranceRequest.cs
  4. 24
      YbTest/Models/Insurance/InsuranceResponse.cs
  5. 155
      YbTest/Models/MedicalTransactionSystem.cs
  6. 26
      YbTest/Program.cs
  7. 38
      YbTest/Properties/launchSettings.json
  8. 17
      YbTest/Services/InsuranceBusinessServices/IInsuranceBusinessService.cs
  9. 210
      YbTest/Services/MedicalInsuranceService.cs
  10. 14
      YbTest/YbTest.csproj
  11. 9
      YbTest/appsettings.Development.json
  12. 9
      YbTest/appsettings.json

BIN
NugetTest/rezero.db

Binary file not shown.

6
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

110
YbTest/Models/Insurance/InsuranceRequest.cs

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

24
YbTest/Models/Insurance/InsuranceResponse.cs

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

155
YbTest/Models/MedicalTransactionSystem.cs

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

26
YbTest/Program.cs

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

38
YbTest/Properties/launchSettings.json

@ -0,0 +1,38 @@
{
"$schema": "http://json.schemastore.org/launchsettings.json",
"iisSettings": {
"windowsAuthentication": false,
"anonymousAuthentication": true,
"iisExpress": {
"applicationUrl": "http://localhost:22272",
"sslPort": 44362
}
},
"profiles": {
"http": {
"commandName": "Project",
"dotnetRunMessages": true,
"launchBrowser": true,
"applicationUrl": "http://localhost: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"
}
}
}
}

17
YbTest/Services/InsuranceBusinessServices/IInsuranceBusinessService.cs

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

210
YbTest/Services/MedicalInsuranceService.cs

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

14
YbTest/YbTest.csproj

@ -0,0 +1,14 @@
<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>
</Project>

9
YbTest/appsettings.Development.json

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

9
YbTest/appsettings.json

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