You can not select more than 25 topics
			Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
		
		
		
		
			
				
					215 lines
				
				9.2 KiB
			
		
		
			
		
	
	
					215 lines
				
				9.2 KiB
			| 
								 
											6 months ago
										 
									 | 
							
								using System.Text;
							 | 
						||
| 
								 | 
							
								using System.Text.RegularExpressions;
							 | 
						||
| 
								 | 
							
								using System.ComponentModel.DataAnnotations;
							 | 
						||
| 
								 | 
							
								using System.Collections.Concurrent;
							 | 
						||
| 
								 | 
							
								using System.Security.Cryptography;
							 | 
						||
| 
								 | 
							
								using System.Text.Json;
							 | 
						||
| 
								 
											6 months ago
										 
									 | 
							
								using medical.insu.transfomer.InsuranceBusinessServices;
							 | 
						||
| 
								 | 
							
								using System.Net.Http.Json;
							 | 
						||
| 
								 | 
							
								using medical.transfomer.dto;
							 | 
						||
| 
								 
											6 months ago
										 
									 | 
							
								
							 | 
						||
| 
								 
											6 months ago
										 
									 | 
							
								namespace medical.insu.transfomer
							 | 
						||
| 
								 
											6 months ago
										 
									 | 
							
								{
							 | 
						||
| 
								 | 
							
								    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);
							 | 
						||
| 
								 | 
							
								            }
							 | 
						||
| 
								 | 
							
								        }
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								}
							 |