/******************************************************************************
* 文件名称: 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
}
}