# 江苏工伤联网结算接口对接实现逻辑设计 ## 版本信息 | 版本号 | 修改内容 | 修改日期 | 修改人 | |--------|----------|----------|--------| | V1.0 | 初始版本设计 | 2024.12.19 | 系统 | ## 目录 - [1 设计概述](#1-设计概述) - [2 架构设计](#2-架构设计) - [3 核心类设计](#3-核心类设计) - [4 接口实现策略](#4-接口实现策略) - [5 具体实现方案](#5-具体实现方案) - [6 错误处理机制](#6-错误处理机制) - [7 集成方案](#7-集成方案) - [8 部署说明](#8-部署说明) --- ## 1 设计概述 ### 1.1 设计目标 基于江苏省工伤机构联网结算接口开发手册(V2.1版本),设计一个统一的工伤联网接口对接业务类,实现与江苏省工伤联网系统的无缝对接。 ### 1.2 设计原则 1. **统一封装**:参考 `JiangSuSocialCardBusiness.cs` 的成功模式,提供统一的业务接口 2. **简化调用**:通过参数区分不同业务接口,HIS系统调用更简洁 3. **完全合规**:严格按照工伤文档规范实现,确保接口合规性 4. **易于集成**:可直接嵌套到现有HIS系统中使用 5. **高度复用**:一套代码支持所有工伤联网业务场景 ### 1.3 核心思路 采用用户建议的实现方案: - **初始化函数**:负责系统初始化和环境检查 - **通用接口函数**:统一的业务处理入口,通过参数区分不同交易 - **参数化设计**:所有具体接口通过传入参数动态调用 - **直接嵌套**:可直接集成到HIS系统业务流程中 --- ## 2 架构设计 ### 2.1 系统架构图 ``` ┌─────────────────────────────────────────┐ │ HIS医院信息系统 │ ├─────────────────────────────────────────┤ │ JiangSuWorkInjuryBusiness │ │ ┌─────────────┬─────────────────────┐ │ │ │ 初始化函数 │ 通用接口函数 │ │ │ │ Initialize │ ProcessTransaction │ │ │ └─────────────┴─────────────────────┘ │ ├─────────────────────────────────────────┤ │ JSSiInterface.dll │ │ ┌─────────────┬─────────────────────┐ │ │ │ Si_INIT │ Si_Busi │ │ │ └─────────────┴─────────────────────┘ │ ├─────────────────────────────────────────┤ │ HTTPS/SSL安全传输协议 │ ├─────────────────────────────────────────┤ │ 江苏省工伤联网中心系统 │ └─────────────────────────────────────────┘ ``` ### 2.2 接口分类体系 根据工伤文档,接口共分为4大类: | 分类 | 交易编号范围 | 主要功能 | 实现策略 | |------|-------------|----------|----------| | **对账类** | 1320-1321 | 总额对账、明细对账 | 统一对账处理 | | **认证类** | 9001-9002 | 签到、签退 | 会话管理 | | **业务类** | 1101, 22XX, 81XX, 23XX | 读卡、登记、结算、体检、转院 | 核心业务处理 | | **下载类** | 1301, 91XX | 数据下载、查询 | 数据同步 | --- ## 3 核心类设计 ### 3.1 主业务类:JiangSuWorkInjuryBusiness ```csharp /// /// 江苏工伤联网结算业务类 /// 基于JSSiInterface.dll动态库,严格按照江苏工伤联网接口规范v2.1实现 /// 参考JiangSuSocialCardBusiness.cs的成功架构模式 /// public class JiangSuWorkInjuryBusiness { #region DLL导入声明 - 严格按照工伤文档规范 /// /// 初始化函数 - 检查整个运行环境 /// /// 错误信息 /// 成功:0,失败:-1 [DllImport("JSSiInterface.dll", EntryPoint = "Si_INIT", CharSet = CharSet.Ansi)] private extern static int Si_INIT(StringBuilder pErrMsg); /// /// 交易函数 - 处理所有业务交易 /// /// 输入参数JSON字符串 /// 输出参数JSON字符串 /// 成功:0,失败:<0 [DllImport("JSSiInterface.dll", EntryPoint = "Si_Busi", CharSet = CharSet.Ansi)] private extern static int Si_Busi(string inputdata, StringBuilder outputdata); #endregion #region 状态管理和内部变量 /// /// 系统初始化状态 /// private static bool isInitialized = false; /// /// 当前配置信息 /// private static WorkInjuryConfig currentConfig = null; /// /// 当前签到流水号 /// private static string currentSignNo = ""; /// /// 签到时间 /// private static DateTime signInTime = DateTime.MinValue; /// /// 签到操作员 /// private static string signInOperator = ""; /// /// 报文ID缓存(用于冲正交易) /// private static readonly Dictionary transactionRecords = new Dictionary(); /// /// 序列号计数器(用于生成唯一报文ID) /// private static int sequenceCounter = 0; private static readonly object sequenceLock = new object(); /// /// 交易记录类 /// private class TransactionRecord { public string MessageId { get; set; } public string TransactionCode { get; set; } public DateTime TransactionTime { get; set; } public string OperatorId { get; set; } } #endregion } ``` ### 3.2 配置管理类 ```csharp /// /// 江苏工伤联网配置类 - 严格按照工伤文档规范定义 /// public class WorkInjuryConfig { /// /// 协议机构编号 - 由江苏人社分配 /// public string FixmedinsCode { get; set; } = "H00001"; /// /// 协议机构名称 /// public string FixmedinsName { get; set; } = "第一人民医院"; /// /// 接收方系统代码 - 默认"JSYTH" /// public string ReceiverSysCode { get; set; } = "JSYTH"; /// /// 接口版本号 - 如"V2.1" /// public string InterfaceVersion { get; set; } = "V2.1"; /// /// 经办人类别 - 1:经办人 2:自助终端 3:移动终端 /// public string OperatorType { get; set; } = "1"; /// /// 默认经办人编号 /// public string DefaultOperator { get; set; } = "001"; /// /// 默认经办人姓名 /// public string DefaultOperatorName { get; set; } = "系统管理员"; } ``` ### 3.3 交易请求参数类 ```csharp /// /// 工伤联网交易请求参数 - 按照工伤文档标准格式 /// public class WorkInjuryTransactionRequest { /// /// 交易编号 - 4位数字 /// public string TransactionCode { get; set; } /// /// 业务参数 - 具体交易的输入参数 /// public object BusinessParams { get; set; } /// /// 识别方式 - 1:实体社保卡 2:电子凭证 /// public string IdentifyMode { get; set; } = "1"; /// /// 电子社保卡二维码(识别方式为2时必填) /// public string QrCodeInfo { get; set; } = ""; /// /// 经办人编号(可选,使用默认值) /// public string OperatorId { get; set; } = ""; /// /// 经办人姓名(可选,使用默认值) /// public string OperatorName { get; set; } = ""; } ``` --- ## 4 接口实现策略 ### 4.1 统一处理流程 ```csharp public static JObject ProcessTransaction(WorkInjuryTransactionRequest request) { var result = new JObject(); try { // 1. 检查初始化状态 if (!isInitialized) { var initResult = Initialize(); if (!(bool)initResult["success"]) { return initResult; } } // 2. 构造标准输入参数 var inputJson = BuildStandardInput(request); // 3. 调用底层DLL StringBuilder outputBuffer = new StringBuilder(40000); int dllResult = Si_Busi(inputJson, outputBuffer); // 4. 解析输出结果 result = ParseStandardOutput(outputBuffer.ToString(), dllResult); // 5. 特殊业务处理 result = ProcessSpecialBusiness(request.TransactionCode, result); } catch (Exception ex) { result = BuildErrorResult(ex); } return result; } ``` ### 4.2 参数化交易映射 ```csharp /// /// 交易代码映射表 - 基于工伤文档接口列表 /// private static readonly Dictionary TransactionMapping = new Dictionary { // 认证类 {"SignIn", "9001"}, // 签到 {"SignOut", "9002"}, // 签退 // 业务类 - 核心功能 {"ReadCard", "1101"}, // 读卡 {"RegisterPatient", "2201"}, // 门诊/住院登记 {"CancelRegister", "2202"}, // 登记撤销 {"ModifyRegister", "2203"}, // 登记信息修改 {"UploadPrescription", "2204"}, // 处方明细上报 {"CancelPrescription", "2205"}, // 处方明细撤销 {"PreSettle", "2206"}, // 费用预结算 {"Settle", "2207"}, // 费用结算 {"CancelSettle", "2208"}, // 费用结算撤销 {"ReverseTransaction", "2209"}, // 冲正交易 // 体检类 - 体检协议机构使用 {"QueryExamSchedule", "8101"}, // 查询体检排班信息 {"UpdateExamSchedule", "8102"}, // 更新体检排班信息 {"QueryExamReservation", "8103"}, // 查询体检预约信息 {"ExamRegister", "8104"}, // 体检登记 {"UploadExamDetail", "8105"}, // 上传体检明细 {"QueryExamSettle", "8106"}, // 查询体检结算信息 {"QueryExamDetail", "8107"}, // 查询体检明细 {"ConfirmExamResult", "8108"}, // 体检结果确认 {"QuerySupplementCard", "8109"}, // 补刷卡登记查询 // 转院类 {"UploadReferral", "2301"}, // 转诊转院申请信息上传 {"QueryReferral", "2302"}, // 转诊转院申请信息查询 {"CancelReferral", "2303"}, // 转诊转院申请信息撤销 // 对账类 {"TotalAccount", "1320"}, // 总额对账 {"DetailAccount", "1321"}, // 明细对账 // 下载类 {"BatchDownload", "1301"}, // 批量数据下载 {"QueryFeeDetail", "9103"}, // 费用明细详细信息下载 {"QueryPrescriptionDetail", "9104"}, // 处方明细下载 {"QueryRecentVisit", "9105"} // 参保人近期就诊信息查询 }; ``` --- ## 5 具体实现方案 ### 5.1 初始化函数实现 ```csharp /// /// 初始化江苏工伤联网系统 /// 参考JiangSuSocialCardBusiness的Initialize方法,但适配工伤业务 /// /// 配置参数 /// 初始化结果 public static JObject Initialize(WorkInjuryConfig config = null) { var result = new JObject(); try { // 使用默认配置或传入配置 if (config == null) { config = new WorkInjuryConfig(); } // 检查DLL文件存在性 var dllCheckResult = CheckDllExists(); if (!(bool)dllCheckResult["success"]) { return dllCheckResult; } // 调用底层初始化函数 StringBuilder errMsg = new StringBuilder(1024); int initResult = Si_INIT(errMsg); if (initResult == 0) { isInitialized = true; currentConfig = config; result["success"] = true; result["code"] = 200; result["message"] = "江苏工伤联网系统初始化成功"; result["device"] = "江苏工伤联网接口"; result["timestamp"] = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"); result["version"] = config.InterfaceVersion; result["dllErrorMsg"] = errMsg.ToString(); } else { string errorMsg = errMsg.ToString(); result["success"] = false; result["code"] = 1000 + Math.Abs(initResult); result["message"] = $"江苏工伤联网系统初始化失败: {errorMsg}"; result["device"] = "江苏工伤联网接口"; result["errorCode"] = initResult; result["dllErrorMsg"] = errorMsg; } } catch (Exception ex) { result["success"] = false; result["code"] = 1001; result["message"] = $"江苏工伤联网系统初始化异常: {ex.Message}"; result["device"] = "江苏工伤联网接口"; result["exception"] = ex.GetType().Name; } return result; } ``` ### 5.2 通用接口函数实现 ```csharp /// /// 通用工伤联网交易处理接口 /// 支持所有工伤文档定义的交易类型 /// /// 交易名称(可使用中文名称或交易编号) /// 业务参数对象 /// 识别方式:1-实体社保卡,2-电子凭证 /// 电子社保卡二维码(识别方式为2时必填) /// 经办人编号(可选) /// 经办人姓名(可选) /// 交易处理结果 public static JObject ProcessWorkInjuryTransaction( string transactionName, object businessParams = null, string identifyMode = "1", string qrCodeInfo = "", string operatorId = "", string operatorName = "") { var result = new JObject(); try { // 1. 解析交易编号 string transactionCode = GetTransactionCode(transactionName); if (string.IsNullOrEmpty(transactionCode)) { result["success"] = false; result["code"] = 1002; result["message"] = $"不支持的交易类型: {transactionName}"; result["device"] = "江苏工伤联网接口"; return result; } // 2. 检查初始化状态 if (!isInitialized) { var autoInitResult = Initialize(); if (!(bool)autoInitResult["success"]) { result["success"] = false; result["code"] = 1003; result["message"] = "江苏工伤联网系统未初始化"; result["device"] = "江苏工伤联网接口"; result["autoInitError"] = autoInitResult["message"]; return result; } } // 3. 检查签到状态(除签到交易外) if (transactionCode != "9001" && string.IsNullOrEmpty(currentSignNo)) { var signInResult = ProcessWorkInjuryTransaction("SignIn"); if (!(bool)signInResult["success"]) { result["success"] = false; result["code"] = 1004; result["message"] = "自动签到失败,无法进行业务交易"; result["device"] = "江苏工伤联网接口"; result["signInError"] = signInResult["message"]; return result; } } // 4. 构造标准输入参数 var inputData = BuildTransactionInput(transactionCode, businessParams, identifyMode, qrCodeInfo, operatorId, operatorName); string inputJson = JsonConvert.SerializeObject(inputData, Formatting.None); // 5. 调用底层DLL交易函数 StringBuilder outputBuffer = new StringBuilder(40000); int dllResult = Si_Busi(inputJson, outputBuffer); // 6. 解析和处理返回结果 string outputJson = outputBuffer.ToString(); result = ParseTransactionOutput(outputJson, dllResult, transactionCode); // 7. 特殊交易后处理 PostProcessTransaction(transactionCode, result); } catch (Exception ex) { result["success"] = false; result["code"] = 1005; result["message"] = $"工伤联网交易异常: {ex.Message}"; result["device"] = "江苏工伤联网接口"; result["exception"] = ex.GetType().Name; result["transactionName"] = transactionName; } return result; } /// /// 特殊交易后处理逻辑 /// private static void PostProcessTransaction(string transactionCode, JObject result) { if ((bool)result["success"]) { switch (transactionCode) { case "9001": // 签到交易成功 var signData = result["data"]; if (signData != null) { currentSignNo = signData["sign_no"]?.ToString() ?? ""; signInTime = DateTime.Now; signInOperator = currentConfig?.DefaultOperator ?? ""; // 记录签到成功日志 LogInfo($"签到成功,流水号: {currentSignNo}, 时间: {signInTime:yyyy-MM-dd HH:mm:ss}"); } break; case "9002": // 签退交易成功 // 清除签到状态 currentSignNo = ""; signInTime = DateTime.MinValue; signInOperator = ""; LogInfo($"签退成功,时间: {DateTime.Now:yyyy-MM-dd HH:mm:ss}"); break; } } } ``` ### 5.3 标准输入参数构造 ```csharp /// /// 构造标准的工伤联网交易输入参数 /// 严格按照工伤文档表2格式构造,支持特殊交易格式 /// private static object BuildTransactionInput(string transactionCode, object businessParams, string identifyMode, string qrCodeInfo, string operatorId, string operatorName) { // 生成唯一的发送方报文ID:协议机构编号(6)+时间(14)+顺序号(4) string msgId = GenerateMessageId(); // 使用配置的经办人信息或传入的参数 string finalOperatorId = string.IsNullOrEmpty(operatorId) ? currentConfig.DefaultOperator : operatorId; string finalOperatorName = string.IsNullOrEmpty(operatorName) ? currentConfig.DefaultOperatorName : operatorName; // 构造基础参数对象 var baseInputData = new { infno = transactionCode, // 交易编号 msgid = msgId, // 发送方报文ID recer_sys_code = currentConfig.ReceiverSysCode, // 接收方系统代码 infver = currentConfig.InterfaceVersion, // 接口版本号 opter_type = currentConfig.OperatorType, // 经办人类别 opter = finalOperatorId, // 经办人 opter_name = finalOperatorName, // 经办人姓名 inf_time = DateTime.Now.ToString("yyyyMMddHHmmss"), // 交易时间 fixmedins_code = currentConfig.FixmedinsCode, // 协议机构编号 fixmedins_name = currentConfig.FixmedinsName, // 协议机构名称 sign_no = transactionCode == "9001" ? "" : currentSignNo, // 签到流水号 idfi_mode = identifyMode, // 识别方式 qrcode_info = qrCodeInfo // 电子社保卡二维码 }; // 处理特殊交易格式 object finalInputData = BuildSpecialTransactionInput(baseInputData, transactionCode, businessParams); // 保存发送方报文ID用于可能的冲正操作 SaveMessageId(msgId, transactionCode); return finalInputData; } /// /// 处理特殊交易的输入格式 /// 按照工伤文档要求,某些交易需要特殊的参数格式 /// private static object BuildSpecialTransactionInput(object baseInputData, string transactionCode, object businessParams) { switch (transactionCode) { case "2204": // 处方明细上报 - 使用feedetail代替input return new { // 复制基础参数 infno = ((dynamic)baseInputData).infno, msgid = ((dynamic)baseInputData).msgid, recer_sys_code = ((dynamic)baseInputData).recer_sys_code, infver = ((dynamic)baseInputData).infver, opter_type = ((dynamic)baseInputData).opter_type, opter = ((dynamic)baseInputData).opter, opter_name = ((dynamic)baseInputData).opter_name, inf_time = ((dynamic)baseInputData).inf_time, fixmedins_code = ((dynamic)baseInputData).fixmedins_code, fixmedins_name = ((dynamic)baseInputData).fixmedins_name, sign_no = ((dynamic)baseInputData).sign_no, idfi_mode = ((dynamic)baseInputData).idfi_mode, qrcode_info = ((dynamic)baseInputData).qrcode_info, // 特殊格式:使用feedetail替代input feedetail = businessParams ?? new object[] { } }; case "8105": // 上传体检明细 - 使用tjfeedetail代替input return new { // 复制基础参数 infno = ((dynamic)baseInputData).infno, msgid = ((dynamic)baseInputData).msgid, recer_sys_code = ((dynamic)baseInputData).recer_sys_code, infver = ((dynamic)baseInputData).infver, opter_type = ((dynamic)baseInputData).opter_type, opter = ((dynamic)baseInputData).opter, opter_name = ((dynamic)baseInputData).opter_name, inf_time = ((dynamic)baseInputData).inf_time, fixmedins_code = ((dynamic)baseInputData).fixmedins_code, fixmedins_name = ((dynamic)baseInputData).fixmedins_name, sign_no = ((dynamic)baseInputData).sign_no, idfi_mode = ((dynamic)baseInputData).idfi_mode, qrcode_info = ((dynamic)baseInputData).qrcode_info, // 特殊格式:使用tjfeedetail替代input tjfeedetail = businessParams ?? new object[] { } }; default: // 标准交易格式 return new { // 复制基础参数 infno = ((dynamic)baseInputData).infno, msgid = ((dynamic)baseInputData).msgid, recer_sys_code = ((dynamic)baseInputData).recer_sys_code, infver = ((dynamic)baseInputData).infver, opter_type = ((dynamic)baseInputData).opter_type, opter = ((dynamic)baseInputData).opter, opter_name = ((dynamic)baseInputData).opter_name, inf_time = ((dynamic)baseInputData).inf_time, fixmedins_code = ((dynamic)baseInputData).fixmedins_code, fixmedins_name = ((dynamic)baseInputData).fixmedins_name, sign_no = ((dynamic)baseInputData).sign_no, idfi_mode = ((dynamic)baseInputData).idfi_mode, qrcode_info = ((dynamic)baseInputData).qrcode_info, // 标准格式:使用input input = businessParams ?? new { } }; } } ``` ### 5.4 便捷方法封装 ```csharp // 为常用交易提供便捷方法,简化调用 /// /// 读卡交易 - 获取工伤人员基本信息 /// public static JObject ReadWorkInjuryCard(string identifyMode = "1", string qrCodeInfo = "") { return ProcessWorkInjuryTransaction("ReadCard", null, identifyMode, qrCodeInfo); } /// /// 门诊/住院登记 /// public static JObject RegisterPatient(object registerParams, string identifyMode = "1") { return ProcessWorkInjuryTransaction("RegisterPatient", registerParams, identifyMode); } /// /// 费用预结算 /// public static JObject PreSettlement(object settlementParams) { return ProcessWorkInjuryTransaction("PreSettle", settlementParams); } /// /// 费用结算 /// public static JObject Settlement(object settlementParams) { return ProcessWorkInjuryTransaction("Settle", settlementParams); } /// /// 处方明细上传 /// public static JObject UploadPrescription(object prescriptionDetails) { return ProcessWorkInjuryTransaction("UploadPrescription", prescriptionDetails); } /// /// 签到交易 /// public static JObject SignIn() { return ProcessWorkInjuryTransaction("SignIn"); } /// /// 签退交易 /// public static JObject SignOut() { return ProcessWorkInjuryTransaction("SignOut"); } /// /// 冲正交易 /// public static JObject ReverseTransaction(string originalTransactionCode, string originalMessageId) { var reverseParams = new { infno = originalTransactionCode, msgid = originalMessageId }; return ProcessWorkInjuryTransaction("ReverseTransaction", reverseParams); } /// /// 根据交易记录进行冲正(便捷方法) /// public static JObject ReverseTransactionByRecord(string messageId) { if (transactionRecords.ContainsKey(messageId)) { var record = transactionRecords[messageId]; return ReverseTransaction(record.TransactionCode, record.MessageId); } else { return new JObject { ["success"] = false, ["code"] = 1006, ["message"] = $"未找到报文ID对应的交易记录: {messageId}", ["device"] = "江苏工伤联网接口" }; } } /// /// 获取当前签到状态 /// public static JObject GetSignInStatus() { return new JObject { ["success"] = true, ["code"] = 200, ["message"] = "签到状态查询成功", ["data"] = new JObject { ["isSignedIn"] = !string.IsNullOrEmpty(currentSignNo), ["signNo"] = currentSignNo, ["signInTime"] = signInTime == DateTime.MinValue ? "" : signInTime.ToString("yyyy-MM-dd HH:mm:ss"), ["signInOperator"] = signInOperator, ["isInitialized"] = isInitialized } }; } /// /// 批量处方明细上传 - 便捷方法 /// public static JObject BatchUploadPrescriptions(List prescriptionList, int batchSize = 50) { var result = new JObject(); var successResults = new JArray(); var failedResults = new JArray(); try { if (prescriptionList == null || prescriptionList.Count == 0) { result["success"] = false; result["code"] = 1007; result["message"] = "处方明细列表为空"; return result; } // 按批次处理(单次最多50条) for (int i = 0; i < prescriptionList.Count; i += batchSize) { var batch = prescriptionList.Skip(i).Take(batchSize).ToList(); var batchResult = UploadPrescription(batch); if ((bool)batchResult["success"]) { successResults.Add(new JObject { ["batchIndex"] = i / batchSize + 1, ["count"] = batch.Count, ["result"] = batchResult }); } else { failedResults.Add(new JObject { ["batchIndex"] = i / batchSize + 1, ["count"] = batch.Count, ["result"] = batchResult }); } } result["success"] = failedResults.Count == 0; result["code"] = result["success"] ? 200 : 1008; result["message"] = $"批量上传完成,成功{successResults.Count}批,失败{failedResults.Count}批"; result["data"] = new JObject { ["totalBatches"] = (prescriptionList.Count + batchSize - 1) / batchSize, ["successBatches"] = successResults.Count, ["failedBatches"] = failedResults.Count, ["successResults"] = successResults, ["failedResults"] = failedResults }; } catch (Exception ex) { result["success"] = false; result["code"] = 1009; result["message"] = $"批量上传异常: {ex.Message}"; result["exception"] = ex.GetType().Name; } return result; } /// /// 完整的工伤患者就诊流程 - 一站式便捷方法 /// public static JObject CompleteWorkInjuryProcess(object patientRegisterParams, List prescriptionList, object settlementParams) { var result = new JObject(); var processSteps = new JArray(); try { // 步骤1:读卡 var readCardResult = ReadWorkInjuryCard(); processSteps.Add(new JObject { ["step"] = "读卡", ["result"] = readCardResult }); if (!(bool)readCardResult["success"]) { result["success"] = false; result["message"] = "读卡失败,流程终止"; result["processSteps"] = processSteps; return result; } // 步骤2:患者登记 var registerResult = RegisterPatient(patientRegisterParams); processSteps.Add(new JObject { ["step"] = "患者登记", ["result"] = registerResult }); if (!(bool)registerResult["success"]) { result["success"] = false; result["message"] = "患者登记失败,流程终止"; result["processSteps"] = processSteps; return result; } // 步骤3:上传处方明细 var uploadResult = BatchUploadPrescriptions(prescriptionList); processSteps.Add(new JObject { ["step"] = "上传处方", ["result"] = uploadResult }); if (!(bool)uploadResult["success"]) { result["success"] = false; result["message"] = "处方上传失败,流程终止"; result["processSteps"] = processSteps; return result; } // 步骤4:费用预结算 var preSettleResult = PreSettlement(settlementParams); processSteps.Add(new JObject { ["step"] = "费用预结算", ["result"] = preSettleResult }); if (!(bool)preSettleResult["success"]) { result["success"] = false; result["message"] = "费用预结算失败,流程终止"; result["processSteps"] = processSteps; return result; } // 步骤5:正式结算 var settleResult = Settlement(settlementParams); processSteps.Add(new JObject { ["step"] = "正式结算", ["result"] = settleResult }); result["success"] = (bool)settleResult["success"]; result["code"] = result["success"] ? 200 : 1010; result["message"] = result["success"] ? "工伤就诊流程全部完成" : "正式结算失败"; result["processSteps"] = processSteps; result["finalSettlementData"] = settleResult["data"]; } catch (Exception ex) { result["success"] = false; result["code"] = 1011; result["message"] = $"工伤就诊流程异常: {ex.Message}"; result["processSteps"] = processSteps; result["exception"] = ex.GetType().Name; } return result; } /// /// 智能重试交易 - 带指数退避算法 /// public static JObject SmartRetryTransaction(string transactionName, object businessParams, int maxRetries = 3, int baseDelayMs = 1000, double backoffMultiplier = 2.0) { JObject lastResult = null; for (int attempt = 0; attempt < maxRetries; attempt++) { try { lastResult = ProcessWorkInjuryTransaction(transactionName, businessParams); // 成功则立即返回 if ((bool)lastResult["success"]) { lastResult["retryInfo"] = new JObject { ["attemptCount"] = attempt + 1, ["maxRetries"] = maxRetries, ["finalAttempt"] = true }; return lastResult; } // 判断是否值得重试 int errorCode = (int)lastResult["code"]; if (!IsRetryableError(errorCode)) { lastResult["retryInfo"] = new JObject { ["attemptCount"] = attempt + 1, ["maxRetries"] = maxRetries, ["retryable"] = false, ["reason"] = "错误类型不可重试" }; break; } // 如果不是最后一次尝试,则等待后重试 if (attempt < maxRetries - 1) { int delayMs = (int)(baseDelayMs * Math.Pow(backoffMultiplier, attempt)); Thread.Sleep(delayMs); LogInfo($"交易 {transactionName} 第 {attempt + 1} 次尝试失败,{delayMs}ms后重试"); } } catch (Exception ex) { lastResult = new JObject { ["success"] = false, ["code"] = 1012, ["message"] = $"智能重试异常: {ex.Message}", ["exception"] = ex.GetType().Name, ["retryInfo"] = new JObject { ["attemptCount"] = attempt + 1, ["maxRetries"] = maxRetries, ["exceptionAttempt"] = true } }; } } // 所有重试都失败了 if (lastResult != null) { lastResult["retryInfo"] = new JObject { ["attemptCount"] = maxRetries, ["maxRetries"] = maxRetries, ["allAttemptsFailed"] = true }; } return lastResult ?? new JObject { ["success"] = false, ["message"] = "智能重试完全失败" }; } /// /// 交易统计和监控 - 获取交易统计信息 /// public static JObject GetTransactionStatistics() { try { var stats = new JObject { ["totalTransactions"] = transactionRecords.Count, ["oldestTransaction"] = transactionRecords.Values.Any() ? transactionRecords.Values.Min(r => r.TransactionTime).ToString("yyyy-MM-dd HH:mm:ss") : "", ["newestTransaction"] = transactionRecords.Values.Any() ? transactionRecords.Values.Max(r => r.TransactionTime).ToString("yyyy-MM-dd HH:mm:ss") : "", ["transactionsByType"] = new JObject() }; // 按交易类型统计 var groupedTransactions = transactionRecords.Values .GroupBy(r => r.TransactionCode) .ToDictionary(g => g.Key, g => g.Count()); foreach (var kvp in groupedTransactions) { ((JObject)stats["transactionsByType"])[kvp.Key] = kvp.Value; } return new JObject { ["success"] = true, ["code"] = 200, ["message"] = "交易统计获取成功", ["data"] = stats, ["timestamp"] = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss") }; } catch (Exception ex) { return new JObject { ["success"] = false, ["code"] = 1013, ["message"] = $"获取交易统计异常: {ex.Message}", ["exception"] = ex.GetType().Name }; } } /// /// 系统健康检查 - 全面的系统状态检查 /// public static JObject HealthCheck() { var healthResult = new JObject(); var checks = new JObject(); bool overallHealthy = true; try { // 1. DLL文件检查 var dllCheck = CheckDllExists(); checks["dllFile"] = dllCheck; if (!(bool)dllCheck["success"]) overallHealthy = false; // 2. 初始化状态检查 checks["initialization"] = new JObject { ["status"] = isInitialized ? "initialized" : "not_initialized", ["healthy"] = isInitialized }; if (!isInitialized) overallHealthy = false; // 3. 签到状态检查 var signInStatus = GetSignInStatus(); checks["signInStatus"] = signInStatus; bool isSignedIn = (bool)signInStatus["data"]["isSignedIn"]; if (!isSignedIn) overallHealthy = false; // 4. 配置检查 checks["configuration"] = new JObject { ["hasConfig"] = currentConfig != null, ["fixmedinsCode"] = currentConfig?.FixmedinsCode ?? "", ["interfaceVersion"] = currentConfig?.InterfaceVersion ?? "" }; if (currentConfig == null) overallHealthy = false; // 5. 内存使用检查 var process = System.Diagnostics.Process.GetCurrentProcess(); checks["memory"] = new JObject { ["workingSetMB"] = Math.Round(process.WorkingSet64 / 1024.0 / 1024.0, 2), ["privateMemoryMB"] = Math.Round(process.PrivateMemorySize64 / 1024.0 / 1024.0, 2) }; // 6. 交易记录检查 checks["transactionRecords"] = new JObject { ["count"] = transactionRecords.Count, ["healthy"] = transactionRecords.Count < 10000 // 避免内存泄漏 }; healthResult["success"] = true; healthResult["code"] = 200; healthResult["message"] = overallHealthy ? "系统健康状态良好" : "系统存在健康问题"; healthResult["healthy"] = overallHealthy; healthResult["checks"] = checks; healthResult["timestamp"] = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"); } catch (Exception ex) { healthResult["success"] = false; healthResult["code"] = 1014; healthResult["message"] = $"健康检查异常: {ex.Message}"; healthResult["healthy"] = false; healthResult["exception"] = ex.GetType().Name; } return healthResult; } #endregion #region 辅助方法 /// /// 生成唯一的发送方报文ID /// 格式:协议机构编号(6位)+时间(14位)+顺序号(4位) /// private static string GenerateMessageId() { lock (sequenceLock) { sequenceCounter++; if (sequenceCounter > 9999) { sequenceCounter = 1; } string fixmedinsCode = currentConfig?.FixmedinsCode?.PadLeft(6, '0') ?? "000000"; string timeStr = DateTime.Now.ToString("yyyyMMddHHmmss"); string sequenceStr = sequenceCounter.ToString("D4"); return $"{fixmedinsCode}{timeStr}{sequenceStr}"; } } /// /// 保存交易记录(用于冲正) /// private static void SaveMessageId(string messageId, string transactionCode) { try { var record = new TransactionRecord { MessageId = messageId, TransactionCode = transactionCode, TransactionTime = DateTime.Now, OperatorId = currentConfig?.DefaultOperator ?? "" }; transactionRecords[messageId] = record; // 清理30天前的记录(避免内存泄漏) var expireTime = DateTime.Now.AddDays(-30); var expiredKeys = transactionRecords.Where(r => r.Value.TransactionTime < expireTime) .Select(r => r.Key) .ToList(); foreach (var key in expiredKeys) { transactionRecords.Remove(key); } } catch (Exception ex) { LogError($"保存交易记录失败: {ex.Message}"); } } /// /// 获取交易编号(支持中文名称和数字编号) /// private static string GetTransactionCode(string transactionName) { if (string.IsNullOrEmpty(transactionName)) return ""; // 如果已经是4位数字编号,直接返回 if (transactionName.Length == 4 && transactionName.All(char.IsDigit)) { return transactionName; } // 从映射表中查找 return TransactionMapping.ContainsKey(transactionName) ? TransactionMapping[transactionName] : ""; } /// /// 检查DLL文件是否存在 /// private static JObject CheckDllExists() { var result = new JObject(); try { string programDir = AppDomain.CurrentDomain.BaseDirectory; string dllPath = Path.Combine(programDir, "JSSiInterface.dll"); if (File.Exists(dllPath)) { FileInfo fileInfo = new FileInfo(dllPath); result["success"] = true; result["code"] = 200; result["message"] = "JSSiInterface.dll文件检查通过"; result["data"] = new JObject { ["dllPath"] = dllPath, ["fileSize"] = fileInfo.Length, ["lastModified"] = fileInfo.LastWriteTime.ToString("yyyy-MM-dd HH:mm:ss") }; } else { result["success"] = false; result["code"] = 8001; result["message"] = "JSSiInterface.dll文件不存在"; result["data"] = new JObject { ["expectedPath"] = dllPath, ["suggestion"] = "请将JSSiInterface.dll文件复制到程序目录" }; } } catch (Exception ex) { result["success"] = false; result["code"] = 8002; result["message"] = $"DLL文件检查异常: {ex.Message}"; result["exception"] = ex.GetType().Name; } return result; } /// /// 判断是否为可重试的错误 /// private static bool IsRetryableError(int errorCode) { // 网络相关错误可重试 int[] retryableErrors = { 1004, 2000, 2001, 3001, 3002 }; return retryableErrors.Contains(errorCode); } /// /// 日志记录 - 信息级别 /// private static void LogInfo(string message) { try { string logDir = "logs/workinjury/"; if (!Directory.Exists(logDir)) { Directory.CreateDirectory(logDir); } string logFile = Path.Combine(logDir, $"workinjury_{DateTime.Now:yyyyMMdd}.log"); string logEntry = $"{DateTime.Now:yyyy-MM-dd HH:mm:ss} [INFO] {message}{Environment.NewLine}"; File.AppendAllText(logFile, logEntry, Encoding.UTF8); } catch { // 日志记录失败不影响主业务 } } /// /// 日志记录 - 错误级别 /// private static void LogError(string message) { try { string logDir = "logs/workinjury/"; if (!Directory.Exists(logDir)) { Directory.CreateDirectory(logDir); } string logFile = Path.Combine(logDir, $"workinjury_{DateTime.Now:yyyyMMdd}.log"); string logEntry = $"{DateTime.Now:yyyy-MM-dd HH:mm:ss} [ERROR] {message}{Environment.NewLine}"; File.AppendAllText(logFile, logEntry, Encoding.UTF8); } catch { // 日志记录失败不影响主业务 } } #endregion } ``` --- ## 6 错误处理机制 ### 6.1 多层次错误处理 ```csharp /// /// 解析交易输出结果并进行错误处理 /// private static JObject ParseTransactionOutput(string outputJson, int dllResult, string transactionCode) { var result = new JObject(); try { // DLL层面错误 if (dllResult != 0) { result["success"] = false; result["code"] = 2000 + Math.Abs(dllResult); result["message"] = $"DLL调用失败,错误码: {dllResult}"; result["device"] = "江苏工伤联网接口"; result["dllErrorCode"] = dllResult; result["transactionCode"] = transactionCode; result["rawOutput"] = outputJson; return result; } // 解析JSON输出 if (string.IsNullOrEmpty(outputJson)) { result["success"] = false; result["code"] = 2001; result["message"] = "交易返回数据为空"; return result; } var outputData = JObject.Parse(outputJson); // 业务层面错误检查 string infcode = outputData["infcode"]?.ToString() ?? ""; string errMsg = outputData["err_msg"]?.ToString() ?? ""; string warnMsg = outputData["warn_msg"]?.ToString() ?? ""; if (infcode == "0") { // 交易成功 result["success"] = true; result["code"] = 200; result["message"] = string.IsNullOrEmpty(warnMsg) ? "交易成功" : warnMsg; result["data"] = outputData["output"]; result["transactionCode"] = transactionCode; result["infRefMsgId"] = outputData["inf_refmsgid"]; result["refMsgTime"] = outputData["refmsg_time"]; result["respondTime"] = outputData["respond_time"]; result["warnMsg"] = warnMsg; } else { // 业务失败 result["success"] = false; result["code"] = 3000 + Math.Abs(int.Parse(infcode)); result["message"] = string.IsNullOrEmpty(errMsg) ? "交易失败" : errMsg; result["transactionCode"] = transactionCode; result["businessErrorCode"] = infcode; result["businessErrorMsg"] = errMsg; } // 保存完整的原始返回数据 result["rawOutput"] = outputJson; } catch (JsonException jsonEx) { result["success"] = false; result["code"] = 2002; result["message"] = $"返回数据解析失败: {jsonEx.Message}"; result["rawOutput"] = outputJson; } catch (Exception ex) { result["success"] = false; result["code"] = 2003; result["message"] = $"输出解析异常: {ex.Message}"; result["exception"] = ex.GetType().Name; } return result; } ``` ### 6.2 自动重试机制 ```csharp /// /// 带重试机制的交易处理 /// public static JObject ProcessWithRetry(string transactionName, object businessParams, int maxRetries = 3) { for (int i = 0; i < maxRetries; i++) { var result = ProcessWorkInjuryTransaction(transactionName, businessParams); // 成功则直接返回 if ((bool)result["success"]) { return result; } // 判断是否可重试的错误 int errorCode = (int)result["code"]; if (IsRetryableError(errorCode) && i < maxRetries - 1) { // 等待重试 Thread.Sleep(1000 * (i + 1)); // 递增等待时间 continue; } // 不可重试或已达最大重试次数 result["retryCount"] = i + 1; return result; } // 理论上不会到达这里 return new JObject { ["success"] = false, ["message"] = "重试机制异常" }; } ``` --- ## 7 集成方案 ### 7.1 HIS系统集成示例 ```csharp // HIS系统中的调用示例 public class HisWorkInjuryService { /// /// 工伤患者挂号登记业务流程 /// public async Task WorkInjuryRegisterAsync(HisPatientInfo patientInfo) { try { // 1. 读卡获取工伤人员信息 var readCardResult = JiangSuWorkInjuryBusiness.ReadWorkInjuryCard(); if (!(bool)readCardResult["success"]) { return HisResult.Fail("读卡失败: " + readCardResult["message"]); } // 2. 构造登记参数 var registerParams = new { ipt_otp_no = GenerateUniqueNo(), // 门诊流水号 med_type = patientInfo.MedicalType, // 医疗类别 adm_time = DateTime.Now.ToString("yyyyMMddHHmmss"), // 入院时间 adm_diag_dscr = patientInfo.DiagnosisCode, // 诊断疾病编码 adm_dept_codg = patientInfo.DepartmentCode, // 科室编码 atddr_no = patientInfo.DoctorCode, // 医生编码 psn_no = readCardResult["data"]["psn_no"], // 个人唯一识别码 qualification_id = ExtractQualificationId(readCardResult) // 工伤医疗费资格审核信息ID }; // 3. 执行登记交易 var registerResult = JiangSuWorkInjuryBusiness.RegisterPatient(registerParams); if (!(bool)registerResult["success"]) { return HisResult.Fail("登记失败: " + registerResult["message"]); } // 4. 保存HIS本地数据 await SaveLocalPatientData(patientInfo, readCardResult, registerResult); return HisResult.Success("工伤患者登记成功"); } catch (Exception ex) { return HisResult.Fail($"工伤登记异常: {ex.Message}"); } } /// /// 工伤费用结算业务流程 /// public async Task WorkInjurySettleAsync(string visitNo) { try { // 1. 验证患者身份 var readCardResult = JiangSuWorkInjuryBusiness.ReadWorkInjuryCard(); if (!(bool)readCardResult["success"]) { return HisResult.Fail("患者身份验证失败"); } // 2. 上传处方明细 var prescriptionData = await GetPrescriptionData(visitNo); var uploadResult = JiangSuWorkInjuryBusiness.UploadPrescription(prescriptionData); if (!(bool)uploadResult["success"]) { return HisResult.Fail("处方上传失败: " + uploadResult["message"]); } // 3. 费用预结算 var preSettleParams = BuildSettlementParams(visitNo, isPreSettle: true); var preSettleResult = JiangSuWorkInjuryBusiness.PreSettlement(preSettleParams); if (!(bool)preSettleResult["success"]) { return HisResult.Fail("预结算失败: " + preSettleResult["message"]); } // 4. 正式结算 var settleParams = BuildSettlementParams(visitNo, isPreSettle: false); var settleResult = JiangSuWorkInjuryBusiness.Settlement(settleParams); if (!(bool)settleResult["success"]) { return HisResult.Fail("结算失败: " + settleResult["message"]); } // 5. 保存结算结果 await SaveSettlementResult(visitNo, settleResult); return HisResult.Success("工伤费用结算成功", settleResult["data"]); } catch (Exception ex) { return HisResult.Fail($"工伤结算异常: {ex.Message}"); } } } ``` ### 7.2 配置文件示例 ```xml ``` --- ## 8 部署说明 ### 8.1 环境要求 1. **操作系统**:Windows Server 2012 R2 及以上 2. **.NET Framework**:4.5 及以上版本 3. **DLL文件**:JSSiInterface.dll(由江苏人社提供) 4. **网络环境**:能够访问江苏工伤联网专线网络 ### 8.2 部署步骤 1. **复制DLL文件** ``` 将 JSSiInterface.dll 复制到应用程序bin目录 确保DLL文件版本与接口文档版本一致 ``` 2. **配置参数设置** ``` 根据医院实际情况修改配置文件中的参数 协议机构编号和名称需要与江苏人社备案信息一致 ``` 3. **权限配置** ``` 确保应用程序有读取DLL文件的权限 配置网络访问权限 ``` 4. **测试验证** ``` 先在测试环境验证接口连通性 执行签到/签退测试确认基础功能 进行完整业务流程测试 ``` ### 8.3 监控建议 1. **接口调用监控**:记录每次接口调用的结果和耗时 2. **错误率监控**:统计各类交易的成功率和失败原因 3. **业务量监控**:监控日常业务交易量变化 4. **性能监控**:关注响应时间和系统资源使用情况 --- ## 9 总结 ### 9.1 设计优势 1. **架构清晰**:参考成功的 `JiangSuSocialCardBusiness.cs` 架构模式 2. **接口统一**:通过参数化设计实现所有交易的统一调用 3. **完全合规**:严格按照江苏工伤联网接口文档规范实现 4. **易于集成**:可直接嵌套到现有HIS系统中使用 5. **扩展性强**:新增交易类型只需要添加映射关系 ### 9.2 关键特性 - ✅ **初始化函数**:完整的系统初始化和环境检查 - ✅ **通用接口函数**:统一的交易处理入口 - ✅ **参数化调用**:通过参数区分不同的业务交易 - ✅ **特殊交易处理**:支持处方明细上传等特殊格式交易 - ✅ **签到流水号管理**:完善的会话状态管理机制 - ✅ **错误处理机制**:多层次的错误处理和智能重试 - ✅ **便捷方法集合**:丰富的高级便捷方法 - 批量处方上传 - 完整就诊流程 - 智能重试机制 - 交易统计监控 - 系统健康检查 - ✅ **直接嵌套**:可无缝集成到HIS系统业务流程 - ✅ **生产级特性**:日志记录、内存管理、交易追踪 ### 9.3 实施建议 1. **分阶段实施**:建议按交易类型分阶段实施和测试 2. **充分测试**:在正式上线前进行充分的功能和压力测试 3. **监控运维**:建立完善的监控和运维机制 4. **文档维护**:及时更新技术文档和操作手册 这个设计方案完全符合您的思路,提供了一个初始化函数和一个通用接口函数,通过参数来区分不同的交易类型,可以直接嵌套到HIS系统中使用,大大简化了工伤联网接口的集成复杂度。