江苏医保社保卡读取业务类.cs 25 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644
  1. using System;
  2. using System.Runtime.InteropServices;
  3. using System.Text;
  4. using Newtonsoft.Json;
  5. using Newtonsoft.Json.Linq;
  6. namespace ThCardReader
  7. {
  8. /// <summary>
  9. /// 江苏医保社保卡读取业务类
  10. /// 基于HeaSecReadInfo.dll作为主DLL,参考华视读卡器流程
  11. /// </summary>
  12. public class JiangSuSocialCardBusiness
  13. {
  14. #region DLL导入声明
  15. // 主DLL: HeaSecReadInfo.dll - 仅包含核心必需函数
  16. // 1.14.1 初始化
  17. [DllImport("HeaSecReadInfo.dll", EntryPoint = "Init", CharSet = CharSet.Ansi, CallingConvention = CallingConvention.StdCall)]
  18. private extern static Int32 Init(string pInitInfo, StringBuilder pErrMsg);
  19. // 1.14.2 读社保卡基本信息
  20. [DllImport("HeaSecReadInfo.dll", EntryPoint = "ReadCardBas", CharSet = CharSet.Ansi, CallingConvention = CallingConvention.StdCall)]
  21. private extern static Int32 ReadCardBas(StringBuilder pCardInfo, StringBuilder pBusiCardInfo);
  22. // 1.14.3 检验PIN码
  23. [DllImport("HeaSecReadInfo.dll", EntryPoint = "VerifyPIN", CharSet = CharSet.Ansi, CallingConvention = CallingConvention.StdCall)]
  24. private extern static Int32 VerifyPIN(StringBuilder pOutBuff);
  25. // 1.14.4 修改PIN码
  26. [DllImport("HeaSecReadInfo.dll", EntryPoint = "ChangePIN", CharSet = CharSet.Ansi, CallingConvention = CallingConvention.StdCall)]
  27. private extern static Int32 ChangePIN(StringBuilder pOutBuff);
  28. // 1.14.8 四合一介质获得个人信息
  29. [DllImport("HeaSecReadInfo.dll", EntryPoint = "GetPersonInfo", CharSet = CharSet.Ansi, CallingConvention = CallingConvention.StdCall)]
  30. private extern static Int32 GetPersonInfo(string pInData, StringBuilder pOutData);
  31. // 注意:已移除不存在的Close函数和其他非核心函数
  32. // 如需要其他功能,可根据实际DLL文件情况后续添加
  33. #endregion
  34. #region 配置和状态
  35. private static bool isInitialized = false;
  36. private static JiangSuConfig currentConfig = null;
  37. public class JiangSuConfig
  38. {
  39. public string IP { get; set; } = "192.168.100.100";
  40. public int PORT { get; set; } = 8080;
  41. public int TIMEOUT { get; set; } = 120;
  42. public string LOG_PATH { get; set; } = "C:\\logs\\jiangsu\\";
  43. public string CARD_PASSTYPE { get; set; } = "1";
  44. public string EC_URL { get; set; } = "https://fuwu-test.nhsa.gov.cn/localcfc/api/hsecfc/localQrCodeQuery";
  45. public string API_NAME { get; set; } = "api-powersi-test-pri";
  46. public string API_VERSION { get; set; } = "1.0.0";
  47. public string ACCESS_KEY { get; set; } = "";
  48. public string SECRETKEY { get; set; } = "";
  49. public string ORG_ID { get; set; } = "";
  50. public string AREA_CODE { get; set; } = "320100";
  51. public string EXT { get; set; } = "";
  52. public string ToJson()
  53. {
  54. return JsonConvert.SerializeObject(this, Formatting.None);
  55. }
  56. }
  57. #endregion
  58. #region 核心业务方法
  59. /// <summary>
  60. /// 初始化江苏医保社保卡读取系统
  61. /// 参考华视读卡器的初始化流程
  62. /// </summary>
  63. public static JObject Initialize(JiangSuConfig config = null)
  64. {
  65. var result = new JObject();
  66. try
  67. {
  68. // 使用默认配置或传入的配置
  69. if (config == null)
  70. {
  71. config = new JiangSuConfig();
  72. }
  73. // 构造初始化JSON参数
  74. string initJson = config.ToJson();
  75. StringBuilder errMsg = new StringBuilder(2048);
  76. // 调用DLL初始化函数
  77. int initResult = Init(initJson, errMsg);
  78. if (initResult == 0)
  79. {
  80. isInitialized = true;
  81. currentConfig = config;
  82. result["code"] = 200;
  83. result["message"] = "江苏医保社保卡系统初始化成功";
  84. result["device"] = "江苏医保社保卡读卡器";
  85. result["config"] = JObject.FromObject(config);
  86. result["timestamp"] = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss");
  87. }
  88. else
  89. {
  90. string errorMsg = errMsg.ToString();
  91. result["code"] = 1000 + initResult;
  92. result["message"] = $"江苏医保系统初始化失败: {errorMsg}";
  93. result["device"] = "江苏医保社保卡读卡器";
  94. result["errorCode"] = initResult;
  95. }
  96. }
  97. catch (Exception ex)
  98. {
  99. result["code"] = 1001;
  100. result["message"] = $"江苏医保系统初始化异常: {ex.Message}";
  101. result["device"] = "江苏医保社保卡读卡器";
  102. result["exception"] = ex.GetType().Name;
  103. }
  104. return result;
  105. }
  106. /// <summary>
  107. /// 读取社保卡基本信息
  108. /// 返回标准38号文格式数据
  109. /// </summary>
  110. public static JObject ReadSocialCard(bool autoVerifyPIN = false)
  111. {
  112. var result = new JObject();
  113. try
  114. {
  115. // 检查初始化状态
  116. if (!isInitialized)
  117. {
  118. result["code"] = 1002;
  119. result["message"] = "江苏医保系统未初始化,请先调用初始化接口";
  120. result["device"] = "江苏医保社保卡读卡器";
  121. return result;
  122. }
  123. // 如果需要自动验证PIN码
  124. if (autoVerifyPIN)
  125. {
  126. var pinResult = VerifyCardPIN();
  127. if ((int)pinResult["code"] != 200)
  128. {
  129. return pinResult; // 返回PIN验证失败结果
  130. }
  131. }
  132. // 读取社保卡信息
  133. StringBuilder cardInfo = new StringBuilder(2048);
  134. StringBuilder busiCardInfo = new StringBuilder(8192);
  135. int readResult = ReadCardBas(cardInfo, busiCardInfo);
  136. if (readResult == 0)
  137. {
  138. string rawCardData = cardInfo.ToString();
  139. string rawBusiData = busiCardInfo.ToString();
  140. // 解析38号文格式数据
  141. var parsedData = Parse38DocumentFormat(rawCardData, rawBusiData);
  142. result["code"] = 200;
  143. result["message"] = "社保卡读取成功";
  144. result["device"] = "江苏医保社保卡读卡器";
  145. result["data"] = parsedData;
  146. result["timestamp"] = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss");
  147. }
  148. else
  149. {
  150. result["code"] = 2000 + readResult;
  151. result["message"] = $"社保卡读取失败,错误码: {readResult}";
  152. result["device"] = "江苏医保社保卡读卡器";
  153. result["errorCode"] = readResult;
  154. }
  155. }
  156. catch (Exception ex)
  157. {
  158. result["code"] = 2001;
  159. result["message"] = $"社保卡读取异常: {ex.Message}";
  160. result["device"] = "江苏医保社保卡读卡器";
  161. result["exception"] = ex.GetType().Name;
  162. }
  163. return result;
  164. }
  165. /// <summary>
  166. /// 验证社保卡PIN码
  167. /// </summary>
  168. public static JObject VerifyCardPIN()
  169. {
  170. var result = new JObject();
  171. try
  172. {
  173. if (!isInitialized)
  174. {
  175. result["code"] = 1002;
  176. result["message"] = "江苏医保系统未初始化";
  177. return result;
  178. }
  179. StringBuilder outBuff = new StringBuilder(1024);
  180. int pinResult = VerifyPIN(outBuff);
  181. if (pinResult == 0)
  182. {
  183. result["code"] = 200;
  184. result["message"] = "PIN码验证成功";
  185. result["device"] = "江苏医保社保卡读卡器";
  186. }
  187. else
  188. {
  189. string errorMsg = outBuff.ToString();
  190. result["code"] = 3000 + pinResult;
  191. result["message"] = $"PIN码验证失败: {errorMsg}";
  192. result["device"] = "江苏医保社保卡读卡器";
  193. result["errorCode"] = pinResult;
  194. }
  195. }
  196. catch (Exception ex)
  197. {
  198. result["code"] = 3001;
  199. result["message"] = $"PIN码验证异常: {ex.Message}";
  200. result["device"] = "江苏医保社保卡读卡器";
  201. }
  202. return result;
  203. }
  204. /// <summary>
  205. /// 修改社保卡PIN码
  206. /// </summary>
  207. public static JObject ChangeCardPIN()
  208. {
  209. var result = new JObject();
  210. try
  211. {
  212. if (!isInitialized)
  213. {
  214. result["code"] = 1002;
  215. result["message"] = "江苏医保系统未初始化";
  216. return result;
  217. }
  218. StringBuilder outBuff = new StringBuilder(1024);
  219. int changeResult = ChangePIN(outBuff);
  220. if (changeResult == 0)
  221. {
  222. result["code"] = 200;
  223. result["message"] = "PIN码修改成功";
  224. result["device"] = "江苏医保社保卡读卡器";
  225. }
  226. else
  227. {
  228. string errorMsg = outBuff.ToString();
  229. result["code"] = 4000 + changeResult;
  230. result["message"] = $"PIN码修改失败: {errorMsg}";
  231. result["device"] = "江苏医保社保卡读卡器";
  232. result["errorCode"] = changeResult;
  233. }
  234. }
  235. catch (Exception ex)
  236. {
  237. result["code"] = 4001;
  238. result["message"] = $"PIN码修改异常: {ex.Message}";
  239. result["device"] = "江苏医保社保卡读卡器";
  240. }
  241. return result;
  242. }
  243. /// <summary>
  244. /// 四合一介质支持 - 获取个人信息
  245. /// 按照官方规范1.14.8实现,支持:社保卡、电子凭证、电子社保卡、身份证
  246. /// </summary>
  247. public static JObject GetPersonInfo(string mediaType = "socialcard", string inputData = "")
  248. {
  249. var result = new JObject();
  250. try
  251. {
  252. if (!isInitialized)
  253. {
  254. result["code"] = 1002;
  255. result["message"] = "江苏医保系统未初始化";
  256. return result;
  257. }
  258. // 按照官方规范1.14.8.4构造输入参数JSON
  259. string jsonInput = "";
  260. if (!string.IsNullOrEmpty(inputData))
  261. {
  262. // 使用提供的输入数据
  263. jsonInput = inputData;
  264. }
  265. else
  266. {
  267. // 构造默认的输入参数
  268. var defaultInput = new JObject
  269. {
  270. ["data"] = new JObject
  271. {
  272. ["orgId"] = currentConfig?.ORG_ID ?? "",
  273. ["businessType"] = "01101", // 默认:医院挂号
  274. ["operatorId"] = "test001",
  275. ["operatorName"] = "操作员",
  276. ["officeId"] = "32760",
  277. ["officeName"] = "医保科"
  278. },
  279. ["transType"] = "ec.query",
  280. ["orgId"] = currentConfig?.ORG_ID ?? ""
  281. };
  282. jsonInput = defaultInput.ToString(Formatting.None);
  283. }
  284. StringBuilder outData = new StringBuilder(8192);
  285. int getResult = GetPersonInfo(jsonInput, outData);
  286. if (getResult == 0)
  287. {
  288. string responseData = outData.ToString();
  289. result["code"] = 200;
  290. result["message"] = "个人信息获取成功";
  291. result["device"] = "江苏医保社保卡读卡器";
  292. result["mediaType"] = mediaType;
  293. result["data"] = ParsePersonInfo(responseData, mediaType);
  294. result["timestamp"] = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss");
  295. }
  296. else
  297. {
  298. result["code"] = 5000 + getResult;
  299. result["message"] = $"个人信息获取失败,错误码: {getResult}";
  300. result["device"] = "江苏医保社保卡读卡器";
  301. result["errorCode"] = getResult;
  302. }
  303. }
  304. catch (Exception ex)
  305. {
  306. result["code"] = 5001;
  307. result["message"] = $"个人信息获取异常: {ex.Message}";
  308. result["device"] = "江苏医保社保卡读卡器";
  309. }
  310. return result;
  311. }
  312. /// <summary>
  313. /// 获取设备状态
  314. /// </summary>
  315. public static JObject GetDeviceStatus()
  316. {
  317. var result = new JObject();
  318. try
  319. {
  320. result["code"] = 200;
  321. result["message"] = "设备状态正常";
  322. result["device"] = "江苏医保社保卡读卡器";
  323. result["status"] = new JObject
  324. {
  325. ["initialized"] = isInitialized,
  326. ["config"] = currentConfig != null ? JObject.FromObject(currentConfig) : null,
  327. ["timestamp"] = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss")
  328. };
  329. }
  330. catch (Exception ex)
  331. {
  332. result["code"] = 7001;
  333. result["message"] = $"获取设备状态异常: {ex.Message}";
  334. result["device"] = "江苏医保社保卡读卡器";
  335. }
  336. return result;
  337. }
  338. /// <summary>
  339. /// 重置系统内部状态
  340. /// 注意:江苏医保接口规范中没有Close函数,此方法仅重置本地缓存的状态信息
  341. /// </summary>
  342. public static JObject ResetSystemState()
  343. {
  344. var result = new JObject();
  345. try
  346. {
  347. // 江苏医保接口规范中没有Close函数,仅重置内部状态
  348. // 这里不调用任何DLL函数,只是清理本地状态
  349. isInitialized = false;
  350. currentConfig = null;
  351. result["code"] = 200;
  352. result["message"] = "江苏医保系统内部状态已重置";
  353. result["device"] = "江苏医保社保卡读卡器";
  354. result["timestamp"] = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss");
  355. result["note"] = "此操作仅重置本地状态缓存,不调用DLL关闭函数(规范中不存在)";
  356. }
  357. catch (Exception ex)
  358. {
  359. result["code"] = 6001;
  360. result["message"] = $"状态重置异常: {ex.Message}";
  361. result["device"] = "江苏医保社保卡读卡器";
  362. }
  363. return result;
  364. }
  365. #endregion
  366. #region 数据解析方法
  367. /// <summary>
  368. /// 解析38号文格式数据
  369. /// 格式:发卡地区|社保号|卡号|卡识别码|姓名|复位信息|版本|发卡日期|有效期|终端机号|设备号|
  370. /// </summary>
  371. private static JObject Parse38DocumentFormat(string cardData, string busiData)
  372. {
  373. var result = new JObject();
  374. try
  375. {
  376. if (string.IsNullOrEmpty(cardData))
  377. {
  378. result["error"] = "卡片数据为空";
  379. return result;
  380. }
  381. string[] parts = cardData.Split('|');
  382. if (parts.Length >= 11)
  383. {
  384. result["region"] = "JiangSu";
  385. result["issueArea"] = parts[0]; // 发卡地区
  386. result["socialSecurityNumber"] = parts[1]; // 社保号
  387. result["cardNumber"] = parts[2]; // 卡号
  388. result["cardIdentifier"] = parts[3]; // 卡识别码
  389. result["name"] = parts[4]; // 姓名
  390. result["resetInfo"] = parts[5]; // 复位信息
  391. result["version"] = parts[6]; // 版本
  392. result["issueDate"] = FormatDate(parts[7]); // 发卡日期
  393. result["validDate"] = FormatDate(parts[8]); // 有效期
  394. result["terminalId"] = parts[9]; // 终端机号
  395. result["deviceId"] = parts[10]; // 设备号
  396. // 业务数据
  397. result["busiCardInfo"] = busiData;
  398. result["rawData"] = cardData;
  399. // 提取身份证号(如果可能)
  400. result["idNumber"] = ExtractIdFromSSN(parts[1]);
  401. }
  402. else
  403. {
  404. result["error"] = $"数据格式不正确,期望11个字段,实际{parts.Length}个字段";
  405. result["rawData"] = cardData;
  406. }
  407. }
  408. catch (Exception ex)
  409. {
  410. result["error"] = $"数据解析异常: {ex.Message}";
  411. result["rawData"] = cardData;
  412. }
  413. return result;
  414. }
  415. /// <summary>
  416. /// 解析个人信息数据
  417. /// 按照官方规范1.14.8不同介质的输出格式解析
  418. /// </summary>
  419. private static JObject ParsePersonInfo(string responseData, string mediaType)
  420. {
  421. var result = new JObject();
  422. try
  423. {
  424. // 首先尝试解析为JSON格式(官方规范要求的输出格式)
  425. try
  426. {
  427. var jsonData = JObject.Parse(responseData);
  428. // 检查是否有code字段表示成功
  429. if (jsonData["code"] != null && jsonData["code"].Value<int>() == 0)
  430. {
  431. var data = jsonData["data"];
  432. if (data != null)
  433. {
  434. // 根据数据内容判断实际的介质类型
  435. if (data["CardInfo"] != null && data["BusiCardInfo"] != null)
  436. {
  437. // 社保卡格式:包含CardInfo和BusiCardInfo
  438. result = Parse38DocumentFormat(data["CardInfo"].ToString(), data["BusiCardInfo"].ToString());
  439. result["mediaType"] = "社保卡";
  440. }
  441. else if (data["idNo"] != null && data["ecToken"] != null)
  442. {
  443. // 电子凭证格式:包含idNo和ecToken
  444. result["mediaType"] = "电子凭证";
  445. result["idNo"] = data["idNo"];
  446. result["userName"] = data["userName"];
  447. result["idType"] = data["idType"];
  448. result["ecToken"] = data["ecToken"];
  449. result["insuOrg"] = data["insuOrg"];
  450. result["ecIndexNo"] = data["ecIndexNo"];
  451. result["gender"] = data["gender"];
  452. result["birthday"] = data["birthday"];
  453. result["nationality"] = data["nationality"];
  454. result["email"] = data["email"];
  455. result["extra"] = data["extra"];
  456. }
  457. else if (data["si_no"] != null && data["ecCardToken"] != null)
  458. {
  459. // 电子社保卡格式:包含si_no和ecCardToken
  460. result["mediaType"] = "电子社保卡";
  461. result["si_no"] = data["si_no"];
  462. result["si_card_no"] = data["si_card_no"];
  463. result["si_card_issue_area"] = data["si_card_issue_area"];
  464. result["name"] = data["name"];
  465. result["gender"] = data["gender"];
  466. result["id_type"] = data["id_type"];
  467. result["id_no"] = data["id_no"];
  468. result["ecCardToken"] = data["ecCardToken"];
  469. }
  470. else if (data["SFZInfo"] != null && data["BusiSFZInfo"] != null)
  471. {
  472. // 身份证格式:包含SFZInfo和BusiSFZInfo
  473. result["mediaType"] = "身份证";
  474. result["SFZInfo"] = data["SFZInfo"];
  475. result["BusiSFZInfo"] = data["BusiSFZInfo"];
  476. // 解析身份证信息字符串
  477. string[] sfzParts = data["SFZInfo"].ToString().Split('|');
  478. if (sfzParts.Length >= 9)
  479. {
  480. result["idNumber"] = sfzParts[0];
  481. result["name"] = sfzParts[1];
  482. result["gender"] = sfzParts[2];
  483. result["nation"] = sfzParts[3];
  484. result["birthday"] = FormatDate(sfzParts[4]);
  485. result["address"] = sfzParts[5];
  486. result["issueOrg"] = sfzParts[6];
  487. result["validFrom"] = FormatDate(sfzParts[7]);
  488. result["validTo"] = FormatDate(sfzParts[8]);
  489. }
  490. }
  491. else
  492. {
  493. // 未知格式,保存原始数据
  494. result["mediaType"] = "未知";
  495. result["data"] = data;
  496. }
  497. }
  498. result["code"] = jsonData["code"];
  499. result["message"] = jsonData["message"];
  500. }
  501. else
  502. {
  503. // DLL返回错误
  504. result["error"] = jsonData["message"]?.ToString() ?? "获取个人信息失败";
  505. result["code"] = jsonData["code"];
  506. }
  507. }
  508. catch (JsonException)
  509. {
  510. // 不是JSON格式,按照传统字符串格式处理
  511. switch (mediaType.ToLower())
  512. {
  513. case "socialcard":
  514. result = Parse38DocumentFormat(responseData, "");
  515. result["mediaType"] = "社保卡";
  516. break;
  517. default:
  518. result["mediaType"] = mediaType;
  519. result["rawData"] = responseData;
  520. break;
  521. }
  522. }
  523. result["rawData"] = responseData;
  524. }
  525. catch (Exception ex)
  526. {
  527. result["error"] = $"个人信息解析异常: {ex.Message}";
  528. result["mediaType"] = mediaType;
  529. result["rawData"] = responseData;
  530. }
  531. return result;
  532. }
  533. /// <summary>
  534. /// 从社保号提取身份证号
  535. /// </summary>
  536. private static string ExtractIdFromSSN(string ssn)
  537. {
  538. if (string.IsNullOrEmpty(ssn) || ssn.Length < 18)
  539. return "";
  540. // 简单假设:如果社保号是18位,可能就是身份证号
  541. if (ssn.Length == 18)
  542. return ssn;
  543. return "";
  544. }
  545. /// <summary>
  546. /// 格式化日期 YYYYMMDD -> YYYY-MM-DD
  547. /// </summary>
  548. private static string FormatDate(string dateStr)
  549. {
  550. if (string.IsNullOrEmpty(dateStr) || dateStr.Length != 8)
  551. return dateStr;
  552. try
  553. {
  554. return $"{dateStr.Substring(0, 4)}-{dateStr.Substring(4, 2)}-{dateStr.Substring(6, 2)}";
  555. }
  556. catch
  557. {
  558. return dateStr;
  559. }
  560. }
  561. #endregion
  562. }
  563. }