/****************************************************************************** * 文件名称: CloudImageService.cs * 文件说明: 个人医保云(影像云)数据采集全量接口封装 * 包含两套子系统共29个交易接口: * 1. 数据采集系统(11xxx系列): 患者信息/费用/病案/病历等 18个接口 * 2. 医疗机构前置(12xxx系列): 检查检验/结算/预约挂号 9个+2个预留 * 当前版本: V2.0 * 创建日期: 2026-05-06 * 更新日期: 2026-05-07 (补全数据采集系统全量接口) ******************************************************************************/ using Newtonsoft.Json; using Newtonsoft.Json.Linq; using PTMedicalInsurance.Common; using PTMedicalInsurance.Helper; using PTMedicalInsurance.Variables; using System; using System.Collections.Generic; using System.IO; using System.Net; using System.Text; namespace PTMedicalInsurance.Business { /// /// 云影像(个人医保云影像云)数据采集服务封装类 /// 对接两套子系统:数据采集系统 + 医疗机构前置 /// public class CloudImageService { #region 配置参数 /// /// 个人医保云(影像云)数据采集系统服务地址(IP:Port) /// 负责患者信息、费用、病案、病历等数据的采集 /// 可配置,每家医院可能不同 /// public string CollectServerUrl { get; set; } //= "http://10.188.1.44:8091"; /// /// 医疗机构前置服务地址(IP:Port) /// 负责检查检验报告、影像文件、结算信息等的采集 /// 可配置,每家医院可能不同 /// public string MedPreServerUrl { get; set; } //= "http://172.25.6.33:5002"; /// /// 医疗机构编码 /// public string FixmedinsCode { get; set; } //= "H65010200152"; /// /// 接口调用密钥 /// public string ApiKey { get; set; }// = "XC0Em467BOw9NjpH3OrS"; /// /// 接口版本号 /// private const string INF_VER = "V1.0"; /// /// 请求超时时间(毫秒) /// private const int TIMEOUT_MS = 60000; #endregion #region 接口URL映射字典(双系统) /// /// 交易编号与API目标系统的映射关系 /// key: 交易编号(infno), value: (系统类型, API路径) /// 系统类型: "collect"=数据采集系统, "medpre"=医疗机构前置 /// private static readonly Dictionary> ApiUrlMapping = new Dictionary> { // ========== 一、数据采集系统 (11xxx系列) ========== // 所有数据采集系统接口统一走 /collect/elecmedrcd // 来源:改造说明文档"个人医保云(影像云)数据采集系统"章节 // --- 患者与就诊基础信息 --- { "11101", Tuple.Create("collect", "/collect/elecmedrcd") }, // 患者基本信息上传 { "11102", Tuple.Create("collect", "/collect/elecmedrcd") }, // 患者费用明细信息上传 { "11103", Tuple.Create("collect", "/collect/elecmedrcd") }, // 患者费用结算信息上传 { "11201", Tuple.Create("collect", "/collect/elecmedrcd") }, // 住院病案首页上传 { "11202", Tuple.Create("collect", "/collect/elecmedrcd") }, // 住院病历记录上传 { "11301", Tuple.Create("collect", "/collect/elecmedrcd") }, // 门急诊诊疗记录上传 { "11302", Tuple.Create("collect", "/collect/elecmedrcd") }, // 急诊留观手术及抢救信息上传 { "11401", Tuple.Create("collect", "/collect/elecmedrcd") }, // 医嘱明细记录上传 { "11402", Tuple.Create("collect", "/collect/elecmedrcd") }, // 输血信息上传 { "11403", Tuple.Create("collect", "/collect/elecmedrcd") }, // 护理操作生命体征测量记录上传 // --- 删除类 --- { "11104", Tuple.Create("collect", "/collect/elecmedrcd") }, // 患者基本信息删除 { "11901", Tuple.Create("collect", "/collect/elecmedrcd") }, // 就诊业务数据删除 //// --- 特殊信息类 --- 暂无对应编号 //{ "11301", Tuple.Create("collect", "/collect/elecmedrcd") }, // 新生儿信息上传 //{ "11302", Tuple.Create("collect", "/collect/elecmedrcd") }, // 死亡医学证明上传 //{ "11401", Tuple.Create("collect", "/collect/elecmedrcd") }, // 透析治疗记录上传 //{ "11402", Tuple.Create("collect", "/collect/elecmedrcd") }, // 预防接种记录上传 //{ "11501", Tuple.Create("collect", "/collect/elecmedrcd") }, // 高危人群筛查记录上传 //{ "11502", Tuple.Create("collect", "/collect/elecmedrcd") }, // 体检登记信息上传 // ========== 二、医疗机构前置 (12xxx系列) ========== // 来源:改造说明文档"医疗机构前置"章节 // --- 检查检验报告类 --- { "12101", Tuple.Create("medpre", "/api/uploadInspection") }, // 临床检验报告上传 { "12102", Tuple.Create("medpre", "/api/uploadStudy") }, // 临床检查报告上传 { "12103", Tuple.Create("medpre", "/api/uploadPath") }, // 病理检查报告上传 { "12104", Tuple.Create("medpre", "/api/uploadBacterial") }, // 细菌培养报告上传 { "12109", Tuple.Create("medpre", "/api/deleteReport") }, // 检验检查报告删除 // --- 结算信息类 --- { "12201", Tuple.Create("medpre", "/api/uploadSettle") }, // 检查报告结算信息上传 { "12209", Tuple.Create("medpre", "/api/deleteSettle") }, // 检查报告结算信息删除 // --- 预约挂号类 --- { "12301", Tuple.Create("medpre", "/api/uploadAppoin") }, // 预约挂号信息上传 { "12309", Tuple.Create("medpre", "/api/deleteAppoin") }, // 预约挂号信息删除 //// --- 影像调阅(预留) --- AI自己加的? //{ "12401", Tuple.Create("medpre", "/api/queryImageIndex") }, // 影像索引查询(预留) //{ "12402", Tuple.Create("medpre", "/api/viewImage") }, // 影像调阅查看(预留) }; #endregion #region 交易编号名称映射 /// /// 交易编号与中文名称的映射(用于日志记录和错误提示) /// private static readonly Dictionary InfNoNameMapping = new Dictionary { // 数据采集系统 - 患者与就诊基础信息 { "11101", "患者基本信息上传" }, { "11102", "患者费用明细信息上传" }, { "11103", "患者费用结算信息上传" }, { "11201", "住院病案首页上传" }, { "11202", "住院病历记录上传" }, { "11301", "门急诊诊疗记录上传" }, { "11302", "急诊留观手术及抢救信息上传" }, { "11401", "医嘱明细记录上传" }, { "11402", "输血信息上传" }, { "11403", "护理操作生命体征测量记录上传" }, // 数据采集系统 - 删除类 { "11104", "患者基本信息删除" }, { "11901", "就诊业务数据删除" }, //// 数据采集系统 - 特殊信息类 //{ "11301", "新生儿信息上传" }, //{ "11302", "死亡医学证明上传" }, //{ "11401", "透析治疗记录上传" }, //{ "11402", "预防接种记录上传" }, //{ "11501", "高危人群筛查记录上传" }, //{ "11502", "体检登记信息上传" }, // 医疗机构前置 { "12101", "临床检验报告上传" }, { "12102", "临床检查报告上传" }, { "12103", "病理检查报告上传" }, { "12104", "细菌培养报告上传" }, { "12109", "检验检查报告删除" }, { "12201", "检查报告结算信息上传" }, { "12209", "检查报告结算信息删除" }, { "12301", "预约挂号信息上传" }, { "12309", "预约挂号信息删除" }, //{ "12401", "影像索引查询(预留)" }, //{ "12402", "影像调阅查看(预留)" }, }; #endregion #region 公开方法 - 统一入口 /// /// 执行云影像交易(统一入口) /// HIS端只需传入交易编号和input入参,外层报文头由本类自动组装 /// 根据交易编号自动路由到对应的数据采集系统或医疗机构前置 /// /// 交易编号(如11101患者基本信息, 12101临床检验等) /// HIS传入的交易入参(input部分),JSON字符串或JObject /// 统一格式的返回JSON public JObject Execute(string infno, object input) { string errMsg; JObject joRtn = new JObject(); string outPut = ""; try { Global.writeLog($"[CloudImage] 开始执行交易: {infno} ({GetInfNoName(infno)})"); // 1. 校验交易编号是否有效 if (!ApiUrlMapping.ContainsKey(infno)) { errMsg = $"不支持的交易编号: {infno},当前支持的交易编号范围: 11101~11502(数据采集), 12101~12402(医疗机构前置)"; Global.writeLog($"[CloudImage] {errMsg}"); return JsonHelper.setExceptionJson(-1, errMsg, errMsg); } // 2. 构造完整请求报文 JObject joRequest = BuildRequest(infno, input); Global.writeLog($"[CloudImage] 请求报文: {JsonHelper.Compress(joRequest.ToString())}"); // 3. 发送HTTP POST请求(自动路由到对应系统) string responseStr = SendRequest(infno, joRequest.ToString()); Global.writeLog($"[CloudImage] 响应报文: {JsonHelper.Compress(responseStr)}"); // 4. 解析返回结果 joRtn = ParseResponse(responseStr); Global.writeLog($"[CloudImage] 交易 {infno} 执行完成"); outPut = joRtn.ToString(); return joRtn; } catch (Exception ex) { errMsg = $"[CloudImage] 交易 {infno} 异常: {ex.Message}"; Global.writeLog(errMsg); outPut = JsonHelper.setExceptionJson(-1, GetInfNoName(infno), ex.Message).ToString(); return JsonHelper.setExceptionJson(-1, GetInfNoName(infno), ex.Message); } finally { } } #endregion #region 私有方法 - 报文构造 /// /// 构造完整的请求报文 /// 自动组装外层报文头(infno, msgid, infver, inf_time, fixmedins_code, key, rcd_num) /// HIS传入的数据作为input节点 /// private JObject BuildRequest(string infno, object input) { JObject joInput = (JObject)input; int rcdNum = int.Parse(joInput["rcd_num"].ToString()); joInput.Remove("rcd_num"); return BuildRequest( infno, rcdNum, joInput); } private JObject BuildRequest(string infno,int rcdNum, object input) { JObject joRequest = new JObject(); // 报文头信息 joRequest.Add("infno", infno); joRequest.Add("msgid", GenerateMsgId()); joRequest.Add("infver", INF_VER); joRequest.Add("inf_time", DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss")); joRequest.Add("fixmedins_code", FixmedinsCode); joRequest.Add("key", ApiKey); joRequest.Add("rcd_num", rcdNum); // 处理input部分 if (input is string) { string inputStr = input as string; if (!string.IsNullOrEmpty(inputStr) && (inputStr.StartsWith("{") || inputStr.StartsWith("["))) { joRequest.Add("input", JObject.Parse(inputStr)); } else { joRequest.Add("input", inputStr ?? ""); } } else if (input is JObject) { joRequest.Add("input", input as JObject); } else if (input is JToken) { joRequest.Add("input", input as JToken); } else { joRequest.Add("input", input != null ? input.ToString() : ""); } return joRequest; } /// /// 生成报文ID /// 格式:医疗机构编码 + 年月日时分秒 + 5位随机数 /// 示例:H65010200152202605070003012345 /// private string GenerateMsgId() { Random rnd = new Random(); int randNum = rnd.Next(10000, 99999); return $"{FixmedinsCode}{DateTime.Now:yyyyMMddHHmmss}{randNum}"; } #endregion #region 私有方法 - HTTP通信(双系统路由) /// /// 发送HTTP POST请求到对应的目标系统 /// 根据交易编号自动选择:数据采集系统 或 医疗机构前置 /// /// 交易编号 /// 请求数据(JSON字符串) /// 响应内容 //private string SendRequest(string infno, string requestData) //{ // var targetInfo = ApiUrlMapping[infno]; // string systemType = targetInfo.Item1; // "collect" 或 "medpre" // string apiPath = targetInfo.Item2; // // 根据系统类型选择服务器地址 // string serverUrl; // if (systemType == "collect") // { // serverUrl = CollectServerUrl.TrimEnd('/'); // } // else // medpre // { // serverUrl = MedPreServerUrl.TrimEnd('/'); // } // string apiUrl = serverUrl + apiPath; // string systemName = systemType == "collect" ? "数据采集系统" : "医疗机构前置"; // Global.writeLog($"[CloudImage] [{systemName}] 请求URL: {apiUrl}"); // ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12 | SecurityProtocolType.Tls11 | SecurityProtocolType.Tls; // ServicePointManager.ServerCertificateValidationCallback = delegate { return true; }; // HttpWebRequest request = (HttpWebRequest)WebRequest.Create(apiUrl); // request.Method = "POST"; // request.ContentType = "application/json; charset=utf-8"; // request.Timeout = TIMEOUT_MS; // byte[] data = Encoding.UTF8.GetBytes(requestData); // request.ContentLength = data.Length; // using (Stream reqStream = request.GetRequestStream()) // { // reqStream.Write(data, 0, data.Length); // } // using (HttpWebResponse response = (HttpWebResponse)request.GetResponse()) // { // using (StreamReader reader = new StreamReader(response.GetResponseStream(), Encoding.UTF8)) // { // return reader.ReadToEnd(); // } // } //} private string SendRequest(string infno, string requestData) { var targetInfo = ApiUrlMapping[infno]; string systemType = targetInfo.Item1; // "collect" 或 "medpre" string apiPath = targetInfo.Item2; // 根据系统类型选择服务器地址 string serverUrl = systemType == "collect" ? CollectServerUrl.TrimEnd('/') : MedPreServerUrl.TrimEnd('/'); string apiUrl = serverUrl + apiPath; string systemName = systemType == "collect" ? "数据采集系统" : "医疗机构前置"; Global.writeLog($"[CloudImage] [{systemName}] 请求URL: {apiUrl}"); string input = requestData; string output = ""; string url = apiUrl; // 用于 finally 中的日志 try { ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12 | SecurityProtocolType.Tls11 | SecurityProtocolType.Tls; ServicePointManager.ServerCertificateValidationCallback = delegate { return true; }; HttpWebRequest request = (HttpWebRequest)WebRequest.Create(apiUrl); request.Method = "POST"; request.ContentType = "application/json; charset=utf-8"; request.Timeout = TIMEOUT_MS; byte[] data = Encoding.UTF8.GetBytes(requestData); request.ContentLength = data.Length; using (Stream reqStream = request.GetRequestStream()) { reqStream.Write(data, 0, data.Length); } using (HttpWebResponse response = (HttpWebResponse)request.GetResponse()) using (StreamReader reader = new StreamReader(response.GetResponseStream(), Encoding.UTF8)) { output = reader.ReadToEnd(); return output; } } catch (Exception ex) { // 捕获异常,记录到 output 供日志使用 output = $"[ERROR] {ex.Message} (StackTrace: {ex.StackTrace?.Substring(0, Math.Min(500, ex.StackTrace.Length))})"; throw; // 重新抛出,保持原有异常行为 } finally { // 统一日志:无论成功或失败都记录 Global.writeLog($"{infno}:{url}", input, output); } } #endregion #region 私有方法 - 响应解析 /// /// 解析响应报文并转换为统一返回格式 /// 响应格式:{ infcode, msgid, refmsg_time, respond_time, err_msg, Output } /// private JObject ParseResponse(string responseStr) { try { JObject joResponse = JObject.Parse(responseStr); string infcode = JsonHelper.getDestValue(joResponse, "infcode"); string err_msg = JsonHelper.getDestValue(joResponse, "err_msg"); // infcode == "0" 表示成功 if (infcode == "0") { JObject joRtn = new JObject(); joRtn.Add("errorCode", 0); joRtn.Add("errorMessage", ""); // 将Output节点作为result返回给HIS if (joResponse.ContainsKey("Output")) { joRtn.Add("result", joResponse["Output"]); } else { joRtn.Add("result", new JObject()); } return joRtn; } else { // 返回错误信息 return JsonHelper.setExceptionJson(-1, "云影像接口错误", !string.IsNullOrEmpty(err_msg) ? err_msg : "未知错误"); } } catch (Exception ex) { return JsonHelper.setExceptionJson(-1, "解析响应异常", ex.Message); } } /// /// 获取交易名称(用于日志) /// private string GetInfNoName(string infno) { return InfNoNameMapping.ContainsKey(infno) ? InfNoNameMapping[infno] : "未知交易"; } #endregion #region 公开方法 - 便捷方法(数据采集系统) /// /// 11101 患者基本信息上传 /// public JObject UploadPatnBasInfo(object input) { return Execute("11101", input); } /// /// 11102 患者费用明细信息上传 /// public JObject UploadPatnFeeDetail(object input) { return Execute("11102", input); } /// /// 11103 患者费用结算信息上传 /// public JObject UploadPatnFeeSettle(object input) { return Execute("11103", input); } /// /// 11104 住院病案首页上传 /// public JObject UploadInpMedRec(object input) { return Execute("11104", input); } /// /// 11105 住院病历记录上传 /// public JObject UploadInpCaseRecord(object input) { return Execute("11105", input); } /// /// 11106 门急诊诊疗记录上传 /// public JObject UploadOPEmrRecord(object input) { return Execute("11106", input); } /// /// 11107 急诊留观手术及抢救信息上传 /// public JObject UploadEmrgObsSurgery(object input) { return Execute("11107", input); } /// /// 11108 医嘱明细记录上传 /// public JObject UploadMedOrderDetail(object input) { return Execute("11108", input); } /// /// 11109 输血信息上传 /// public JObject UploadBloodTransfusion(object input) { return Execute("11109", input); } /// /// 11110 护理操作生命体征测量记录上传 /// public JObject UploadNursingVitalsign(object input) { return Execute("11110", input); } /// /// 11201 患者基本信息删除 /// public JObject DeletePatnBasInfo(object input) { return Execute("11201", input); } /// /// 11202 就诊业务数据删除 /// public JObject DeleteVisitBizData(object input) { return Execute("11202", input); } /// /// 11301 新生儿信息上传 /// public JObject UploadNewbornInfo(object input) { return Execute("11301", input); } /// /// 11302 死亡医学证明上传 /// public JObject UploadDeathCert(object input) { return Execute("11302", input); } /// /// 11401 透析治疗记录上传 /// public JObject UploadDialysisRecord(object input) { return Execute("11401", input); } /// /// 11402 预防接种记录上传 /// public JObject UploadVaccinationRecord(object input) { return Execute("11402", input); } /// /// 11501 高危人群筛查记录上传 /// public JObject UploadHighRiskScreening(object input) { return Execute("11501", input); } /// /// 11502 体检登记信息上传 /// public JObject UploadHealthExamReg(object input) { return Execute("11502", input); } #endregion #region 公开方法 - 便捷方法(医疗机构前置) /// /// 12101 临床检验报告上传 /// public JObject UploadInspection(object input) { return Execute("12101", input); } /// /// 12102 临床检查报告上传(云影像核心接口) /// public JObject UploadStudy(object input) { return Execute("12102", input); } /// /// 12103 病理检查报告上传 /// public JObject UploadPathology(object input) { return Execute("12103", input); } /// /// 12104 细菌培养报告上传 /// public JObject UploadBacterial(object input) { return Execute("12104", input); } /// /// 12105 检验检查报告删除 /// public JObject DeleteReport(object input) { return Execute("12105", input); } /// /// 12201 检查报告结算信息上传 /// public JObject UploadSettle(object input) { return Execute("12201", input); } /// /// 12202 检查报告结算信息删除 /// public JObject DeleteSettle(object input) { return Execute("12202", input); } /// /// 12301 预约挂号信息上传 /// public JObject UploadAppointment(object input) { return Execute("12301", input); } /// /// 12302 预约挂号信息删除 /// public JObject DeleteAppointment(object input) { return Execute("12302", input); } /// /// 12401 影像索引查询(预留) /// public JObject QueryImageIndex(object input) { return Execute("12401", input); } /// /// 12402 影像调阅查看(预留) /// public JObject ViewImage(object input) { return Execute("12402", input); } #endregion #region 配置方法 /// /// 从外部设置数据采集系统服务器地址(支持运行时动态配置) /// /// 完整的服务器地址,如 http://192.168.1.100:8091 public void SetCollectServerUrl(string serverUrl) { if (!string.IsNullOrWhiteSpace(serverUrl)) { CollectServerUrl = serverUrl.TrimEnd('/'); } } /// /// 从外部设置医疗机构前置服务器地址(支持运行时动态配置) /// /// 完整的服务器地址,如 http://192.168.1.100:5002 public void SetMedPreServerUrl(string serverUrl) { if (!string.IsNullOrWhiteSpace(serverUrl)) { MedPreServerUrl = serverUrl.TrimEnd('/'); } } /// /// 从外部设置APIKEY /// /// public void SetAPIKey(string apikey) { if (!string.IsNullOrWhiteSpace(apikey)) { ApiKey = apikey; } } /// /// 从外部设置APIKEY /// /// public void SetFixmedinsCode(string fixmedinsCode) { if (!string.IsNullOrWhiteSpace(fixmedinsCode)) { FixmedinsCode = fixmedinsCode; } } /// /// 获取所有支持的交易编号列表 /// public List GetSupportedInfNos() { return new List(ApiUrlMapping.Keys); } /// /// 获取指定系统类型的交易编号列表 /// /// "collect"=数据采集系统, "medpre"=医疗机构前置 public List GetInfNosBySystem(string systemType) { List result = new List(); foreach (var kv in ApiUrlMapping) { if (kv.Value.Item1 == systemType) { result.Add(kv.Key); } } return result; } #endregion } }