|
@@ -113,6 +113,57 @@ public class JiangSuWorkInjuryBusiness
|
|
|
private extern static int Si_Busi(string inputdata, StringBuilder outputdata);
|
|
|
|
|
|
#endregion
|
|
|
+
|
|
|
+ #region 状态管理和内部变量
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// 系统初始化状态
|
|
|
+ /// </summary>
|
|
|
+ private static bool isInitialized = false;
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// 当前配置信息
|
|
|
+ /// </summary>
|
|
|
+ private static WorkInjuryConfig currentConfig = null;
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// 当前签到流水号
|
|
|
+ /// </summary>
|
|
|
+ private static string currentSignNo = "";
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// 签到时间
|
|
|
+ /// </summary>
|
|
|
+ private static DateTime signInTime = DateTime.MinValue;
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// 签到操作员
|
|
|
+ /// </summary>
|
|
|
+ private static string signInOperator = "";
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// 报文ID缓存(用于冲正交易)
|
|
|
+ /// </summary>
|
|
|
+ private static readonly Dictionary<string, TransactionRecord> transactionRecords = new Dictionary<string, TransactionRecord>();
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// 序列号计数器(用于生成唯一报文ID)
|
|
|
+ /// </summary>
|
|
|
+ private static int sequenceCounter = 0;
|
|
|
+ private static readonly object sequenceLock = new object();
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// 交易记录类
|
|
|
+ /// </summary>
|
|
|
+ private class TransactionRecord
|
|
|
+ {
|
|
|
+ public string MessageId { get; set; }
|
|
|
+ public string TransactionCode { get; set; }
|
|
|
+ public DateTime TransactionTime { get; set; }
|
|
|
+ public string OperatorId { get; set; }
|
|
|
+ }
|
|
|
+
|
|
|
+ #endregion
|
|
|
}
|
|
|
```
|
|
|
|
|
@@ -467,6 +518,40 @@ public static JObject ProcessWorkInjuryTransaction(
|
|
|
|
|
|
return result;
|
|
|
}
|
|
|
+
|
|
|
+/// <summary>
|
|
|
+/// 特殊交易后处理逻辑
|
|
|
+/// </summary>
|
|
|
+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 标准输入参数构造
|
|
@@ -474,7 +559,7 @@ public static JObject ProcessWorkInjuryTransaction(
|
|
|
```csharp
|
|
|
/// <summary>
|
|
|
/// 构造标准的工伤联网交易输入参数
|
|
|
-/// 严格按照工伤文档表2格式构造
|
|
|
+/// 严格按照工伤文档表2格式构造,支持特殊交易格式
|
|
|
/// </summary>
|
|
|
private static object BuildTransactionInput(string transactionCode, object businessParams,
|
|
|
string identifyMode, string qrCodeInfo, string operatorId, string operatorName)
|
|
@@ -486,7 +571,8 @@ private static object BuildTransactionInput(string transactionCode, object busin
|
|
|
string finalOperatorId = string.IsNullOrEmpty(operatorId) ? currentConfig.DefaultOperator : operatorId;
|
|
|
string finalOperatorName = string.IsNullOrEmpty(operatorName) ? currentConfig.DefaultOperatorName : operatorName;
|
|
|
|
|
|
- var inputData = new
|
|
|
+ // 构造基础参数对象
|
|
|
+ var baseInputData = new
|
|
|
{
|
|
|
infno = transactionCode, // 交易编号
|
|
|
msgid = msgId, // 发送方报文ID
|
|
@@ -500,14 +586,89 @@ private static object BuildTransactionInput(string transactionCode, object busin
|
|
|
fixmedins_name = currentConfig.FixmedinsName, // 协议机构名称
|
|
|
sign_no = transactionCode == "9001" ? "" : currentSignNo, // 签到流水号
|
|
|
idfi_mode = identifyMode, // 识别方式
|
|
|
- qrcode_info = qrCodeInfo, // 电子社保卡二维码
|
|
|
- input = businessParams ?? new { } // 交易输入参数
|
|
|
+ qrcode_info = qrCodeInfo // 电子社保卡二维码
|
|
|
};
|
|
|
|
|
|
+ // 处理特殊交易格式
|
|
|
+ object finalInputData = BuildSpecialTransactionInput(baseInputData, transactionCode, businessParams);
|
|
|
+
|
|
|
// 保存发送方报文ID用于可能的冲正操作
|
|
|
SaveMessageId(msgId, transactionCode);
|
|
|
|
|
|
- return inputData;
|
|
|
+ return finalInputData;
|
|
|
+}
|
|
|
+
|
|
|
+/// <summary>
|
|
|
+/// 处理特殊交易的输入格式
|
|
|
+/// 按照工伤文档要求,某些交易需要特殊的参数格式
|
|
|
+/// </summary>
|
|
|
+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 { }
|
|
|
+ };
|
|
|
+ }
|
|
|
}
|
|
|
```
|
|
|
|
|
@@ -584,6 +745,596 @@ public static JObject ReverseTransaction(string originalTransactionCode, string
|
|
|
};
|
|
|
return ProcessWorkInjuryTransaction("ReverseTransaction", reverseParams);
|
|
|
}
|
|
|
+
|
|
|
+/// <summary>
|
|
|
+/// 根据交易记录进行冲正(便捷方法)
|
|
|
+/// </summary>
|
|
|
+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"] = "江苏工伤联网接口"
|
|
|
+ };
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+/// <summary>
|
|
|
+/// 获取当前签到状态
|
|
|
+/// </summary>
|
|
|
+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
|
|
|
+ }
|
|
|
+ };
|
|
|
+}
|
|
|
+
|
|
|
+/// <summary>
|
|
|
+/// 批量处方明细上传 - 便捷方法
|
|
|
+/// </summary>
|
|
|
+public static JObject BatchUploadPrescriptions(List<object> 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;
|
|
|
+}
|
|
|
+
|
|
|
+/// <summary>
|
|
|
+/// 完整的工伤患者就诊流程 - 一站式便捷方法
|
|
|
+/// </summary>
|
|
|
+public static JObject CompleteWorkInjuryProcess(object patientRegisterParams, List<object> 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;
|
|
|
+}
|
|
|
+
|
|
|
+/// <summary>
|
|
|
+/// 智能重试交易 - 带指数退避算法
|
|
|
+/// </summary>
|
|
|
+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"] = "智能重试完全失败"
|
|
|
+ };
|
|
|
+}
|
|
|
+
|
|
|
+/// <summary>
|
|
|
+/// 交易统计和监控 - 获取交易统计信息
|
|
|
+/// </summary>
|
|
|
+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
|
|
|
+ };
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+/// <summary>
|
|
|
+/// 系统健康检查 - 全面的系统状态检查
|
|
|
+/// </summary>
|
|
|
+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 辅助方法
|
|
|
+
|
|
|
+/// <summary>
|
|
|
+/// 生成唯一的发送方报文ID
|
|
|
+/// 格式:协议机构编号(6位)+时间(14位)+顺序号(4位)
|
|
|
+/// </summary>
|
|
|
+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}";
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+/// <summary>
|
|
|
+/// 保存交易记录(用于冲正)
|
|
|
+/// </summary>
|
|
|
+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}");
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+/// <summary>
|
|
|
+/// 获取交易编号(支持中文名称和数字编号)
|
|
|
+/// </summary>
|
|
|
+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]
|
|
|
+ : "";
|
|
|
+}
|
|
|
+
|
|
|
+/// <summary>
|
|
|
+/// 检查DLL文件是否存在
|
|
|
+/// </summary>
|
|
|
+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;
|
|
|
+}
|
|
|
+
|
|
|
+/// <summary>
|
|
|
+/// 判断是否为可重试的错误
|
|
|
+/// </summary>
|
|
|
+private static bool IsRetryableError(int errorCode)
|
|
|
+{
|
|
|
+ // 网络相关错误可重试
|
|
|
+ int[] retryableErrors = { 1004, 2000, 2001, 3001, 3002 };
|
|
|
+ return retryableErrors.Contains(errorCode);
|
|
|
+}
|
|
|
+
|
|
|
+/// <summary>
|
|
|
+/// 日志记录 - 信息级别
|
|
|
+/// </summary>
|
|
|
+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
|
|
|
+ {
|
|
|
+ // 日志记录失败不影响主业务
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+/// <summary>
|
|
|
+/// 日志记录 - 错误级别
|
|
|
+/// </summary>
|
|
|
+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
|
|
|
+
|
|
|
+}
|
|
|
```
|
|
|
|
|
|
---
|
|
@@ -910,9 +1661,17 @@ public class HisWorkInjuryService
|
|
|
- ✅ **初始化函数**:完整的系统初始化和环境检查
|
|
|
- ✅ **通用接口函数**:统一的交易处理入口
|
|
|
- ✅ **参数化调用**:通过参数区分不同的业务交易
|
|
|
-- ✅ **错误处理机制**:多层次的错误处理和自动重试
|
|
|
-- ✅ **便捷方法**:为常用交易提供简化调用接口
|
|
|
+- ✅ **特殊交易处理**:支持处方明细上传等特殊格式交易
|
|
|
+- ✅ **签到流水号管理**:完善的会话状态管理机制
|
|
|
+- ✅ **错误处理机制**:多层次的错误处理和智能重试
|
|
|
+- ✅ **便捷方法集合**:丰富的高级便捷方法
|
|
|
+ - 批量处方上传
|
|
|
+ - 完整就诊流程
|
|
|
+ - 智能重试机制
|
|
|
+ - 交易统计监控
|
|
|
+ - 系统健康检查
|
|
|
- ✅ **直接嵌套**:可无缝集成到HIS系统业务流程
|
|
|
+- ✅ **生产级特性**:日志记录、内存管理、交易追踪
|
|
|
|
|
|
### 9.3 实施建议
|
|
|
|