|
|
|
using System.Text;
|
|
|
|
using System.Text.RegularExpressions;
|
|
|
|
using System.ComponentModel.DataAnnotations;
|
|
|
|
using System.Collections.Concurrent;
|
|
|
|
using System.Security.Cryptography;
|
|
|
|
using System.Text.Json;
|
|
|
|
using System.Net.Http.Json;
|
|
|
|
using ReZero.DependencyInjection;
|
|
|
|
using Newtonsoft.Json.Linq;
|
|
|
|
using Microsoft.Extensions.Logging;
|
|
|
|
using System.Threading.Tasks;
|
|
|
|
using medical.transfomer.business;
|
|
|
|
|
|
|
|
namespace medical.insu.transfomer
|
|
|
|
{
|
|
|
|
/// <summary>
|
|
|
|
/// 定义InsuranceRequest类,用于替代缺失的引用
|
|
|
|
/// </summary>
|
|
|
|
public class InsuranceRequest
|
|
|
|
{
|
|
|
|
public string MsgId { get; set; } = string.Empty;
|
|
|
|
public string MdtrtareaAdmvs { get; set; } = string.Empty;
|
|
|
|
public string RecerSysCode { get; set; } = string.Empty;
|
|
|
|
public string Infver { get; set; } = string.Empty;
|
|
|
|
public int OpterType { get; set; }
|
|
|
|
public string Opter { get; set; } = string.Empty;
|
|
|
|
public string OpterName { get; set; } = string.Empty;
|
|
|
|
public DateTime InfTime { get; set; }
|
|
|
|
public string FixmedinsCode { get; set; } = string.Empty;
|
|
|
|
public string FixmedinsName { get; set; } = string.Empty;
|
|
|
|
public string Input { get; set; } = string.Empty;
|
|
|
|
public string InsuplcAdmdvs { get; set; } = string.Empty;
|
|
|
|
public string Signtype { get; set; } = string.Empty;
|
|
|
|
public string Infno { get; set; } = string.Empty;
|
|
|
|
}
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// 定义ResponseHeader类,用于替代缺失的引用
|
|
|
|
/// </summary>
|
|
|
|
public class ResponseHeader
|
|
|
|
{
|
|
|
|
public string Version { get; set; } = string.Empty;
|
|
|
|
public string SenderCode { get; set; } = string.Empty;
|
|
|
|
public string ReceiverCode { get; set; } = string.Empty;
|
|
|
|
public string MsgId { get; set; } = string.Empty;
|
|
|
|
public string InfTime { get; set; } = string.Empty;
|
|
|
|
public string ResultCode { get; set; } = string.Empty;
|
|
|
|
public string ResultMsg { get; set; } = string.Empty;
|
|
|
|
public string SignatureAlgorithm { get; set; } = string.Empty;
|
|
|
|
public string EncryptionAlgorithm { get; set; } = string.Empty;
|
|
|
|
}
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// 定义Response类,用于替代缺失的引用
|
|
|
|
/// </summary>
|
|
|
|
public class Response
|
|
|
|
{
|
|
|
|
public ResponseHeader Header { get; set; } = new ResponseHeader();
|
|
|
|
public string Body { get; set; } = string.Empty;
|
|
|
|
public string Signature { get; set; } = string.Empty;
|
|
|
|
}
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// 业务服务接口
|
|
|
|
/// </summary>
|
|
|
|
public interface IInsuranceBusinessService
|
|
|
|
{
|
|
|
|
object Execute(InsuranceRequest request);
|
|
|
|
}
|
|
|
|
|
|
|
|
public class MedicalInsuranceService: IScopeContract
|
|
|
|
{
|
|
|
|
private readonly ConcurrentDictionary<string, IInsuranceBusinessService> _serviceCache = new();
|
|
|
|
private readonly TransformerFactory _transformerFactory;
|
|
|
|
private readonly ILogger<MedicalInsuranceService> _logger;
|
|
|
|
|
|
|
|
public MedicalInsuranceService(
|
|
|
|
TransformerFactory transformerFactory,
|
|
|
|
ILogger<MedicalInsuranceService> logger)
|
|
|
|
{
|
|
|
|
_transformerFactory = transformerFactory;
|
|
|
|
_logger = logger;
|
|
|
|
}
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// 处理医保交易请求
|
|
|
|
/// </summary>
|
|
|
|
/// <param name="action">操作类型</param>
|
|
|
|
/// <param name="data">交易数据</param>
|
|
|
|
/// <returns>处理结果</returns>
|
|
|
|
public async Task<object> ProcessMedicalInsurance(string action, JObject data)
|
|
|
|
{
|
|
|
|
try
|
|
|
|
{
|
|
|
|
_logger.LogInformation($"开始处理医保交易请求: {action}");
|
|
|
|
|
|
|
|
// 使用转换工厂执行医保交易
|
|
|
|
var result = await _transformerFactory.ExecuteMethod(action, data);
|
|
|
|
|
|
|
|
_logger.LogInformation($"医保交易请求处理完成: {action}");
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
catch (Exception ex)
|
|
|
|
{
|
|
|
|
_logger.LogError(ex, $"处理医保交易请求异常: {action}");
|
|
|
|
return new { code = -1, msg = $"处理请求异常: {ex.Message}" };
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
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}")
|
|
|
|
_ => null
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// 入口方法
|
|
|
|
/// </summary>
|
|
|
|
/// <param name="request"></param>
|
|
|
|
/// <returns></returns>
|
|
|
|
public Response ProcessRequest(InsuranceRequest request)
|
|
|
|
{
|
|
|
|
ValidateRequest(request);
|
|
|
|
ValidateParameterFormat(request);
|
|
|
|
VerifySignature(request);
|
|
|
|
|
|
|
|
var service = GetServiceByInfoNo(request.Infno);
|
|
|
|
if (service == null)
|
|
|
|
{
|
|
|
|
throw new InvalidOperationException($"未找到处理服务: {request.Infno}");
|
|
|
|
}
|
|
|
|
|
|
|
|
var result = service.Execute(request);
|
|
|
|
|
|
|
|
return new Response
|
|
|
|
{
|
|
|
|
Header = GenerateResponseHeader(request),
|
|
|
|
Body = EncryptData(result),
|
|
|
|
Signature = GenerateSignature(result)
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// 新增参数格式验证
|
|
|
|
/// </summary>
|
|
|
|
/// <param name="request"></param>
|
|
|
|
/// <exception cref="ValidationException"></exception>
|
|
|
|
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个字符");
|
|
|
|
}
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// 增强版响应头生成
|
|
|
|
/// </summary>
|
|
|
|
/// <param name="request"></param>
|
|
|
|
/// <returns></returns>
|
|
|
|
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"
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// 完整签名验证流程
|
|
|
|
/// </summary>
|
|
|
|
/// <param name="request"></param>
|
|
|
|
/// <exception cref="InvalidOperationException"></exception>
|
|
|
|
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("数字签名验证失败");
|
|
|
|
}
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// 医保系统通信模块
|
|
|
|
/// </summary>
|
|
|
|
/// <param name="endpoint"></param>
|
|
|
|
/// <param name="payload"></param>
|
|
|
|
/// <returns></returns>
|
|
|
|
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);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|