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;
}
}
}