using Newtonsoft.Json.Linq; using System; using System.Linq; using System.Runtime.InteropServices; using System.Text; namespace ThCardReader { /// /// 华视电子身份证读卡器业务类 /// 独立于现有的SSCard.dll和NationECCode.dll /// class HuaShiIdCardBusiness { #region 华视SDK P/Invoke声明 [DllImport("Termb.dll", CallingConvention = CallingConvention.StdCall)] private extern static int CVR_InitComm(int Port); [DllImport("Termb.dll", CallingConvention = CallingConvention.StdCall)] private extern static int CVR_Authenticate(); [DllImport("Termb.dll", CallingConvention = CallingConvention.StdCall)] private extern static int CVR_AuthenticateForNoJudge(); [DllImport("Termb.dll", CallingConvention = CallingConvention.StdCall)] private extern static int CVR_Read_Content(int active); [DllImport("Termb.dll", CallingConvention = CallingConvention.StdCall)] private extern static int CVR_Read_FPContent(); [DllImport("Termb.dll", CallingConvention = CallingConvention.StdCall)] private extern static int CVR_CloseComm(); [DllImport("Termb.dll", CallingConvention = CallingConvention.StdCall)] private extern static int CVR_GetStatus(); [DllImport("Termb.dll", CallingConvention = CallingConvention.StdCall)] private extern static int GetPeopleName(StringBuilder strTmp, ref int strLen); [DllImport("Termb.dll", CallingConvention = CallingConvention.StdCall)] private extern static int GetPeopleSex(StringBuilder strTmp, ref int strLen); [DllImport("Termb.dll", CallingConvention = CallingConvention.StdCall)] private extern static int GetPeopleNation(StringBuilder strTmp, ref int strLen); [DllImport("Termb.dll", CallingConvention = CallingConvention.StdCall)] private extern static int GetPeopleBirthday(StringBuilder strTmp, ref int strLen); [DllImport("Termb.dll", CallingConvention = CallingConvention.StdCall)] private extern static int GetPeopleIDCode(StringBuilder strTmp, ref int strLen); [DllImport("Termb.dll", CallingConvention = CallingConvention.StdCall)] private extern static int GetPeopleAddress(StringBuilder strTmp, ref int strLen); [DllImport("Termb.dll", CallingConvention = CallingConvention.StdCall)] private extern static int GetDepartment(StringBuilder strTmp, ref int strLen); [DllImport("Termb.dll", CallingConvention = CallingConvention.StdCall)] private extern static int GetStartDate(StringBuilder strTmp, ref int strLen); [DllImport("Termb.dll", CallingConvention = CallingConvention.StdCall)] private extern static int GetEndDate(StringBuilder strTmp, ref int strLen); [DllImport("Termb.dll", CallingConvention = CallingConvention.StdCall)] private extern static int GetBMPData(byte[] pData, ref int pLen); [DllImport("Termb.dll", CallingConvention = CallingConvention.StdCall)] private extern static int GetJpgData(byte[] jpgData, ref int pLen); [DllImport("Termb.dll", CallingConvention = CallingConvention.StdCall)] private extern static int GetSexCode(byte[] sexData, ref int pLen); [DllImport("Termb.dll", CallingConvention = CallingConvention.StdCall)] private extern static int GetNationCode(byte[] nationData, ref int pLen); [DllImport("Termb.dll", CallingConvention = CallingConvention.StdCall)] private extern static int GetCertType(byte[] certData, ref int pLen); [DllImport("Termb.dll", CallingConvention = CallingConvention.StdCall)] private extern static int CVR_GetSAMID(StringBuilder SAMID); // 新增:获取照片BASE64编码的函数 [DllImport("Termb.dll", CallingConvention = CallingConvention.StdCall)] private extern static int Getbase64BMPData(byte[] pData, ref int pLen); [DllImport("Termb.dll", CallingConvention = CallingConvention.StdCall)] private extern static int Getbase64JpgData(byte[] pData, ref int pLen); #endregion #region 静态变量 private static bool initialized = false; private static int currentPort = 1001; // 默认USB端口1 #endregion #region 公共方法 /// /// 初始化华视读卡器 /// /// 端口号:1-16为串口COM1-COM16,1001-1016为USB口1-16 /// 初始化结果 public static JObject Initialize(int port = 1001) { JObject result = new JObject(); try { currentPort = port; int initResult = CVR_InitComm(port); if (initResult == 1) { initialized = true; result.Add("code", 200); result.Add("message", $"华视读卡器初始化成功,端口: {GetPortDescription(port)}"); result.Add("port", port); result.Add("device", "华视电子身份证读卡器"); } else { initialized = false; result.Add("code", 1001); result.Add("message", GetInitErrorMessage(initResult)); result.Add("errorCode", initResult); } } catch (Exception ex) { initialized = false; result.Add("code", 1001); result.Add("message", $"初始化异常: {ex.Message}"); } return result; } /// /// 读取身份证信息 /// /// 照片保存路径,为空则不保存照片 /// 读卡后是否自动关闭连接(默认false,但URL调用时建议true) /// 身份证信息 public static JObject ReadIdCard(string savePath = "", bool autoClose = false) { if (!initialized) { var initResult = Initialize(); if ((int)initResult["code"] != 200) { return initResult; } } JObject result = new JObject(); try { // 1. 检测设备状态 int status = CVR_GetStatus(); if (status != 1) { result.Add("code", 1001); result.Add("message", "读卡器设备状态异常"); return result; } // 2. 卡认证 int authResult = CVR_Authenticate(); if (authResult != 1) { result.Add("code", 1001); result.Add("message", GetAuthErrorMessage(authResult)); result.Add("errorCode", authResult); return result; } // 3. 读取身份证 int readResult = CVR_Read_FPContent(); if (readResult != 1) { result.Add("code", 1001); result.Add("message", GetReadErrorMessage(readResult)); result.Add("errorCode", readResult); return result; } // 4. 获取身份证信息 var idCardInfo = GetIdCardInformation(); // 5. 如果需要照片且上面读取失败,尝试使用CVR_Read_Content读取照片 if (idCardInfo["photoBase64"] == null || string.IsNullOrEmpty(idCardInfo["photoBase64"].ToString())) { Console.WriteLine("使用CVR_Read_Content重新读取以获取照片数据..."); int contentReadResult = CVR_Read_Content(1); // active=1表示读取照片 if (contentReadResult == 1) { Console.WriteLine("CVR_Read_Content读取成功,重新获取照片数据"); // 重新获取照片信息 string newPhotoBase64 = GetIdCardPhotoBase64(); if (!string.IsNullOrEmpty(newPhotoBase64) && newPhotoBase64.Length > 50) { idCardInfo["photoBase64"] = newPhotoBase64; idCardInfo["hasPhoto"] = true; // 重新判断格式 if (newPhotoBase64.StartsWith("/9j/") || newPhotoBase64.StartsWith("iVBORw0KGgo")) { idCardInfo["photoFormat"] = "JPEG"; } else if (newPhotoBase64.StartsWith("Qk") || newPhotoBase64.StartsWith("BM")) { idCardInfo["photoFormat"] = "BMP"; } else { idCardInfo["photoFormat"] = "UNKNOWN"; } idCardInfo["photoSize"] = newPhotoBase64.Length; } } } // 6. 保存照片(如果指定了路径) if (!string.IsNullOrEmpty(savePath)) { SaveIdCardPhoto(savePath); } result.Add("code", 200); result.Add("type", "huashi_idcard"); result.Add("message", "华视读卡器读取身份证成功"); result.Add("data", idCardInfo); result.Add("device", "华视电子身份证读卡器"); // 7. 自动关闭连接(如果启用) if (autoClose) { Close(); result.Add("autoClose", true); result.Add("closeMessage", "读卡完成后已自动关闭连接"); } } catch (Exception ex) { // 异常时重置初始化状态,下次自动重新初始化(模仿SSCard.dll的自愈机制) initialized = false; result.Add("code", 1001); result.Add("message", $"读取身份证异常: {ex.Message}(设备将在下次使用时自动重新初始化)"); } return result; } /// /// 连续读卡模式(不需要每次拿起放下) /// /// 照片保存路径 /// 读卡后是否自动关闭连接(默认false,但URL调用时建议true) /// 身份证信息 public static JObject ReadIdCardContinuous(string savePath = "", bool autoClose = false) { if (!initialized) { var initResult = Initialize(); if ((int)initResult["code"] != 200) { return initResult; } } JObject result = new JObject(); try { // 使用连续读卡认证 int authResult = CVR_AuthenticateForNoJudge(); if (authResult != 1) { result.Add("code", 1001); result.Add("message", "连续读卡认证失败"); return result; } // 读取身份证 int readResult = CVR_Read_FPContent(); if (readResult != 1) { result.Add("code", 1001); result.Add("message", GetReadErrorMessage(readResult)); return result; } // 获取信息 var idCardInfo = GetIdCardInformation(); // 保存照片 if (!string.IsNullOrEmpty(savePath)) { SaveIdCardPhoto(savePath); } result.Add("code", 200); result.Add("type", "huashi_idcard_continuous"); result.Add("message", "华视读卡器连续读取身份证成功"); result.Add("data", idCardInfo); result.Add("device", "华视电子身份证读卡器"); // 自动关闭连接(如果启用) if (autoClose) { Close(); result.Add("autoClose", true); result.Add("closeMessage", "读卡完成后已自动关闭连接"); } } catch (Exception ex) { // 异常时重置初始化状态,下次自动重新初始化(模仿SSCard.dll的自愈机制) initialized = false; result.Add("code", 1001); result.Add("message", $"连续读取身份证异常: {ex.Message}(设备将在下次使用时自动重新初始化)"); } return result; } /// /// 获取设备状态 /// /// 设备状态信息 public static JObject GetDeviceStatus() { JObject result = new JObject(); try { if (!initialized) { // 支持自动初始化,模仿SSCard.dll的行为 var initResult = Initialize(); if ((int)initResult["code"] != 200) { result.Add("code", 1001); result.Add("message", "设备未初始化且初始化失败"); result.Add("initialized", false); result.Add("initError", initResult["message"].ToString()); return result; } } int status = CVR_GetStatus(); result.Add("code", 200); result.Add("initialized", true); result.Add("port", currentPort); result.Add("portDescription", GetPortDescription(currentPort)); result.Add("deviceStatus", status == 1 ? "正常" : "异常"); result.Add("statusCode", status); result.Add("device", "华视电子身份证读卡器"); // 获取安全模块号 StringBuilder samId = new StringBuilder(256); int samResult = CVR_GetSAMID(samId); if (samResult == 1) { result.Add("samId", samId.ToString().Trim()); } } catch (Exception ex) { // 异常时重置初始化状态 initialized = false; result.Add("code", 1001); result.Add("message", $"获取设备状态异常: {ex.Message}(设备将在下次使用时自动重新初始化)"); } return result; } /// /// 关闭华视读卡器连接 /// /// 关闭结果 public static JObject Close() { JObject result = new JObject(); try { if (initialized) { int closeResult = CVR_CloseComm(); initialized = false; result.Add("code", 200); result.Add("message", "华视读卡器连接已关闭"); result.Add("closeResult", closeResult); } else { result.Add("code", 200); result.Add("message", "华视读卡器未初始化,无需关闭"); } } catch (Exception ex) { result.Add("code", 1001); result.Add("message", $"关闭连接异常: {ex.Message}"); } return result; } #endregion #region 私有辅助方法 /// /// 获取身份证完整信息 /// /// 身份证信息对象 private static JObject GetIdCardInformation() { JObject info = new JObject(); // 基本信息 info.Add("name", GetStringData("name")); info.Add("sex", GetStringData("sex")); info.Add("nation", GetStringData("nation")); info.Add("birthday", GetStringData("birthday")); info.Add("idCode", GetStringData("idCode")); info.Add("address", GetStringData("address")); info.Add("department", GetStringData("department")); info.Add("startDate", GetStringData("startDate")); info.Add("endDate", GetStringData("endDate")); // 代码信息 info.Add("sexCode", GetCodeData("sexCode")); info.Add("nationCode", GetCodeData("nationCode")); info.Add("certType", GetCodeData("certType")); // 照片信息(BASE64编码) string photoBase64 = GetIdCardPhotoBase64(); info.Add("photoBase64", photoBase64); info.Add("hasPhoto", !string.IsNullOrEmpty(photoBase64)); // 如果有照片,添加照片格式信息 if (!string.IsNullOrEmpty(photoBase64)) { // 根据BASE64前缀判断图片格式 if (photoBase64.StartsWith("/9j/") || photoBase64.StartsWith("iVBORw0KGgo")) { info.Add("photoFormat", "JPEG"); } else if (photoBase64.StartsWith("Qk") || photoBase64.StartsWith("BM")) { info.Add("photoFormat", "BMP"); } else { info.Add("photoFormat", "UNKNOWN"); } info.Add("photoSize", photoBase64.Length); } return info; } /// /// 安全获取字符串数据 /// /// 字段名 /// 获取到的字符串 private static string GetStringData(string fieldName) { try { StringBuilder buffer = new StringBuilder(512); int len = 512; int result = 0; switch (fieldName) { case "name": result = GetPeopleName(buffer, ref len); break; case "sex": result = GetPeopleSex(buffer, ref len); break; case "nation": result = GetPeopleNation(buffer, ref len); break; case "birthday": result = GetPeopleBirthday(buffer, ref len); break; case "idCode": result = GetPeopleIDCode(buffer, ref len); break; case "address": result = GetPeopleAddress(buffer, ref len); break; case "department": result = GetDepartment(buffer, ref len); break; case "startDate": result = GetStartDate(buffer, ref len); break; case "endDate": result = GetEndDate(buffer, ref len); break; default: return ""; } return result == 1 ? buffer.ToString().Trim() : ""; } catch { return ""; } } /// /// 安全获取代码数据 /// /// 字段名 /// 获取到的代码 private static string GetCodeData(string fieldName) { try { byte[] buffer = new byte[32]; int len = 32; int result = 0; switch (fieldName) { case "sexCode": result = GetSexCode(buffer, ref len); break; case "nationCode": result = GetNationCode(buffer, ref len); break; case "certType": result = GetCertType(buffer, ref len); break; default: return ""; } if (result == 1) { return Encoding.Default.GetString(buffer, 0, len).Trim('\0'); } return ""; } catch { return ""; } } /// /// 获取身份证照片的BASE64编码 /// /// 照片的BASE64编码字符串 private static string GetIdCardPhotoBase64() { try { Console.WriteLine("开始获取照片BASE64编码..."); // 首先尝试备用方法(获取二进制数据然后手动转换) Console.WriteLine("优先尝试备用方法获取二进制数据..."); string fallbackResult = GetPhotoBase64Fallback(); if (!string.IsNullOrEmpty(fallbackResult) && fallbackResult.Length > 50 && !fallbackResult.All(c => c == 'A')) { Console.WriteLine($"备用方法成功,返回长度: {fallbackResult.Length}"); return fallbackResult; } // 如果备用方法失败,尝试直接获取BASE64(可能某些SDK版本支持) Console.WriteLine("备用方法失败,尝试直接获取BASE64..."); // 尝试获取JPG格式的BASE64编码 byte[] jpgBase64Data = new byte[38862 * 2]; // 文档说明:不超过38862*2字节 int jpgLen = jpgBase64Data.Length; Console.WriteLine($"尝试调用Getbase64JpgData,缓冲区大小: {jpgLen}"); int jpgResult = Getbase64JpgData(jpgBase64Data, ref jpgLen); Console.WriteLine($"Getbase64JpgData返回: {jpgResult}, 数据长度: {jpgLen}"); if (jpgResult == 1 && jpgLen > 0) { // 转换为字符串并去除多余的空字节 string jpgBase64 = Encoding.Default.GetString(jpgBase64Data, 0, jpgLen).Trim('\0'); Console.WriteLine($"JPG BASE64长度: {jpgBase64.Length}, 前20字符: {(jpgBase64.Length > 20 ? jpgBase64.Substring(0, 20) : jpgBase64)}"); // 检查是否是有效数据(不全是相同字符) if (!string.IsNullOrEmpty(jpgBase64) && jpgBase64.Length > 10 && !jpgBase64.All(c => c == jpgBase64[0])) { return jpgBase64; } } // 尝试获取BMP格式的BASE64编码 byte[] bmpBase64Data = new byte[38862 * 2]; // 文档说明:不超过38862*2字节 int bmpLen = bmpBase64Data.Length; Console.WriteLine($"尝试调用Getbase64BMPData,缓冲区大小: {bmpLen}"); int bmpResult = Getbase64BMPData(bmpBase64Data, ref bmpLen); Console.WriteLine($"Getbase64BMPData返回: {bmpResult}, 数据长度: {bmpLen}"); if (bmpResult == 1 && bmpLen > 0) { // 转换为字符串并去除多余的空字节 string bmpBase64 = Encoding.Default.GetString(bmpBase64Data, 0, bmpLen).Trim('\0'); Console.WriteLine($"BMP BASE64长度: {bmpBase64.Length}, 前20字符: {(bmpBase64.Length > 20 ? bmpBase64.Substring(0, 20) : bmpBase64)}"); // 检查是否是有效数据(不全是相同字符) if (!string.IsNullOrEmpty(bmpBase64) && bmpBase64.Length > 10 && !bmpBase64.All(c => c == bmpBase64[0])) { return bmpBase64; } } Console.WriteLine("所有方法都未能获取到有效的照片数据"); return ""; } catch (Exception ex) { Console.WriteLine($"获取照片BASE64编码异常: {ex.Message}"); return ""; } } /// /// 备用方法:获取二进制照片数据并手动转换为BASE64 /// /// 照片的BASE64编码字符串 private static string GetPhotoBase64Fallback() { try { Console.WriteLine("开始备用照片获取方法..."); // 尝试获取JPG二进制数据 byte[] jpgData = new byte[38862]; int jpgLen = jpgData.Length; Console.WriteLine($"尝试调用GetJpgData,缓冲区大小: {jpgLen}"); int jpgResult = GetJpgData(jpgData, ref jpgLen); Console.WriteLine($"GetJpgData返回: {jpgResult}, 数据长度: {jpgLen}"); if (jpgResult == 1 && jpgLen > 0) { byte[] actualJpgData = new byte[jpgLen]; Array.Copy(jpgData, actualJpgData, jpgLen); string jpgBase64 = Convert.ToBase64String(actualJpgData); Console.WriteLine($"JPG备用方法成功,BASE64长度: {jpgBase64.Length}, 前20字符: {(jpgBase64.Length > 20 ? jpgBase64.Substring(0, 20) : jpgBase64)}"); return jpgBase64; } // 如果JPG失败,尝试获取BMP二进制数据 byte[] bmpData = new byte[38862]; int bmpLen = bmpData.Length; Console.WriteLine($"尝试调用GetBMPData,缓冲区大小: {bmpLen}"); int bmpResult = GetBMPData(bmpData, ref bmpLen); Console.WriteLine($"GetBMPData返回: {bmpResult}, 数据长度: {bmpLen}"); if (bmpResult == 1 && bmpLen > 0) { byte[] actualBmpData = new byte[bmpLen]; Array.Copy(bmpData, actualBmpData, bmpLen); string bmpBase64 = Convert.ToBase64String(actualBmpData); Console.WriteLine($"BMP备用方法成功,BASE64长度: {bmpBase64.Length}, 前20字符: {(bmpBase64.Length > 20 ? bmpBase64.Substring(0, 20) : bmpBase64)}"); return bmpBase64; } Console.WriteLine("备用照片获取方法:所有尝试都失败"); return ""; } catch (Exception ex) { Console.WriteLine($"备用照片获取方法异常: {ex.Message}"); return ""; } } /// /// 保存身份证照片 /// /// 保存路径 private static void SaveIdCardPhoto(string savePath) { try { if (!System.IO.Directory.Exists(savePath)) { System.IO.Directory.CreateDirectory(savePath); } // 尝试获取BMP格式照片 byte[] bmpData = new byte[38862]; int bmpLen = 38862; int bmpResult = GetBMPData(bmpData, ref bmpLen); if (bmpResult == 1 && bmpLen > 0) { string bmpPath = System.IO.Path.Combine(savePath, $"idcard_photo_{DateTime.Now:yyyyMMddHHmmss}.bmp"); byte[] actualBmpData = new byte[bmpLen]; Array.Copy(bmpData, actualBmpData, bmpLen); System.IO.File.WriteAllBytes(bmpPath, actualBmpData); } // 尝试获取JPG格式照片 byte[] jpgData = new byte[38862]; int jpgLen = 38862; int jpgResult = GetJpgData(jpgData, ref jpgLen); if (jpgResult == 1 && jpgLen > 0) { string jpgPath = System.IO.Path.Combine(savePath, $"idcard_photo_{DateTime.Now:yyyyMMddHHmmss}.jpg"); byte[] actualJpgData = new byte[jpgLen]; Array.Copy(jpgData, actualJpgData, jpgLen); System.IO.File.WriteAllBytes(jpgPath, actualJpgData); } } catch (Exception ex) { // 照片保存失败不影响主要功能 System.Diagnostics.Debug.WriteLine($"保存照片失败: {ex.Message}"); } } /// /// 获取端口描述 /// /// 端口号 /// 端口描述 private static string GetPortDescription(int port) { if (port >= 1 && port <= 16) { return $"COM{port}"; } else if (port >= 1001 && port <= 1016) { return $"USB{port - 1000}"; } else { return $"端口{port}"; } } /// /// 获取初始化错误信息 /// /// 错误代码 /// 错误信息 private static string GetInitErrorMessage(int errorCode) { switch (errorCode) { case 1: return "初始化成功"; case 2: return "端口打开失败,请检查读卡器连接"; case -1: return "未知错误"; case -2: return "动态库加载失败,请检查termb.dll文件"; default: return $"初始化失败,错误代码: {errorCode}"; } } /// /// 获取认证错误信息 /// /// 错误代码 /// 错误信息 private static string GetAuthErrorMessage(int errorCode) { switch (errorCode) { case 1: return "卡片认证成功"; case 2: return "寻卡失败,请重新放置卡片"; case 3: return "选卡失败,请重新放置卡片"; case 4: return "未连接读卡器,请检查设备"; case 0: return "动态库未加载"; default: return $"卡片认证失败,错误代码: {errorCode}"; } } /// /// 获取读卡错误信息 /// /// 错误代码 /// 错误信息 private static string GetReadErrorMessage(int errorCode) { switch (errorCode) { case 1: return "读卡成功"; case 0: return "读身份证失败,请重新尝试"; case 4: return "身份证读卡器未连接"; case 99: return "动态库未加载"; default: return $"读卡失败,错误代码: {errorCode}"; } } #endregion } } // 为了支持LINQ扩展方法 namespace System.Linq { internal static class EnumerableExtensions { public static T[] Take(this T[] source, int count) { if (count >= source.Length) return source; T[] result = new T[count]; Array.Copy(source, result, count); return result; } } }