简介:面向需要对接中控考勤机硬件的中初级 .NET 开发者,这份开发资料包把 SDK 接口、通信协议说明与多套现成工程整合在一起,用于解决设备连接、人员管理、考勤记录上传下载等常见开发需求。压缩包共 1264 个文件,约 11.08MB,以 221 个 cs 源码、214 个 dll 依赖库、133 个 exe 可执行示例为主体,另有 resx 与 resources 资源文件、txt 与 doc/docx 说明文档、sln 与 csproj 工程配置,以及 mdb 数据文件,涵盖程序集注册脚本和示例运行环境。已有 2623 人学习下载,说明其在同类对接资料中具备较高参考率。读者可获得 C# 与 VB.NET 双语言示例,覆盖设备连接、用户注册、考勤数据读取、短信与特殊记录处理等模块,配合 API 参数说明和错误排查文档,能较快理解 SDK 调用流程并搭建带图形界面的考勤管理程序骨架,是缩短开发周期、减少接口试错成本的可复用基础工程。
1. 考勤机 SDK 开发文件到手先别跑 Demo:三个文件决定你能不能开工
很多人拿到考勤机 SDK 开发文件包,第一反应是双击 C# 例子里的 exe,看到界面能连上设备就以为对接完成;真正进项目后才发现,连接只是第一道门,人员下发、记录拉取、异常码处理才是后面的大头。这套资源就是围绕考勤机二次开发打包的开发文件:SDK 动态库、接口文档、以及一批 C# 例子,覆盖连接、人员、考勤记录、设备参数这些常用场景。它适合做门禁考勤系统集成的 C# 开发者,也适合刚接手存量考勤机、需要把数据接进自己业务库的维护人员。判断能不能用,不看例子多少,先看文档里的接口说明和例子是否对得上,再看 SDK 的位数和你现有工程是否一致;这三件事对不上,后面全是返工。
2. SDK 目录拆解与 C# 工程挂载:从 DLL 到第一个连接句柄
2.1 开发包里到底有什么:SDK、文档、例子三件套的职责边界
解压开发包后,目录名可能各不相同,但职责基本逃不出三类:动态库和依赖、接口文档、C# 例子。我的习惯是先不碰例子,先把文档里的接口清单翻一遍,确认连接、断开、人员下发、记录读取、错误码这几个函数有没有、参数顺序是什么、返回值是 bool 还是 int。考勤机 SDK 的坑往往不在功能少,而在“文档写的参数顺序和例子里的调用顺序不一致”,直接抄例子很容易把错误参数带进生产代码。
可以用下面这张表快速给目录归位。目录名对不上没关系,按内容找:
| 目录/文件类型 | 典型内容 | 先看什么 |
|---|---|---|
| 动态库目录 | 主 SDK DLL、依赖 DLL、运行库 | 位数是 x86 还是 x64,依赖有哪些 |
| 文档目录 | 接口说明、返回码、结构体定义 | 函数签名、参数方向、错误码段 |
| 例子目录 | C# 控制台或 WinForm Demo | 调用顺序、初始化与释放位置 |
| 头文件/定义 | C 结构体、常量、枚举 | 字段长度、字符编码、对齐方式 |
选型理由不复杂:文档决定“能不能调”,例子决定“先调谁后调谁”。例子里的 IP、端口、设备号通常是写死的,只能当调用顺序参考,不能当配置模板。常见做法是把例子跑一遍,记录它每一步调了哪个函数,再回到文档核对参数,最后在自己的工程里重写一层薄封装。这样做的代价是前期慢半天,收益是后面出问题能定位到具体函数,而不是面对一个黑匣子式的 Demo 干瞪眼。
还有一点容易被忽略:例子工程的目标框架和平台目标。很多考勤机 SDK 的例子是 .NET Framework 的 WinForm,目标平台 x86;你新建一个 .NET 6 控制台并保持 Any CPU,调同一个 DLL 就可能报“试图加载格式不正确的程序”。这不是 SDK 坏了,是位数没对齐。先确认例子能跑,再确认例子的平台目标,最后照这个目标建自己的工程,顺序不要反。
2.2 新建 C# 工程并挂载 DLL:x86/x64 与平台目标怎么定
挂载 SDK 的第一步不是写代码,是把工程平台目标定死。我的做法是:新建 .NET Framework 控制台工程,平台目标先跟例子保持一致;如果例子是 x86,就把自己的工程也设成 x86,不要用 Any CPU 去赌。然后把主 DLL 和它旁边的依赖 DLL 一起复制到工程的输出目录,或者放在一个固定的lib目录,运行时用绝对路径加载。相对路径在调试时能用、发布后找不到,是常见的翻车点。
下面是一段 P/Invoke 声明示例。函数名和 DLL 名按你手里文档替换,这里用占位名说明结构:
using System; using System.Runtime.InteropServices; internal static class DeviceSdk { // DLL 名、函数名以文档为准,这里只演示声明方式 [DllImport("AttendanceSdk.dll", CallingConvention = CallingConvention.StdCall, CharSet = CharSet.Ansi)] public static extern bool Connect(string ip, int port, int timeoutMs, out IntPtr handle); [DllImport("AttendanceSdk.dll", CallingConvention = CallingConvention.StdCall)] public static extern bool Disconnect(IntPtr handle); [DllImport("AttendanceSdk.dll", CallingConvention = CallingConvention.StdCall)] public static extern int GetLastError(IntPtr handle); }这段代码只做三件事:声明连接、断开、取错误码三个入口;CallingConvention.StdCall对应大多数 Windows 动态库的调用约定,如果文档写的是Cdecl就必须改,否则调用栈会乱;CharSet.Ansi表示字符串按多字节传,考勤机 SDK 里很常见。参数说明:ip是设备地址,port按设备网络设置页填,timeoutMs建议显式给 3000 到 5000 毫秒,不要依赖 SDK 默认值;out IntPtr handle是会话句柄,后续所有调用都带它,句柄不要每次调用都重新连接。
提示:平台目标不一致时,报错通常不是“位数不匹配”这么直白,而是“找不到指定的模块”或“试图加载格式不正确的程序”。先用
dumpbin /headers看 DLL 的 machine 字段,再对照工程平台目标,比反复重装 SDK 有效。
如果必须用 Any CPU,也不是不行,但要在运行时判断进程位数并加载对应目录的 DLL,复杂度和出错面都会变大。一线做法更简单:对接考勤机这种存量设备,工程直接固定 x86 或 x64,部署时少一个变量。血泪经验是,调试机是 64 位系统不代表你的进程是 64 位,Any CPU在 64 位系统上默认跑 64 位进程,调 32 位 DLL 必挂。
2.3 第一个连接与断开会话:句柄、IP、端口、超时的参数表
连接代码不要写成“点一下按钮调一次 Connect”。正确顺序是:读配置、建句柄、连接、拿设备信息、业务操作、断开、释放句柄。连接失败时先取错误码,不要直接弹“网络异常”。下面这段是控制台里的最小闭环:
using System; class Program { static void Main() { var ip = "10.0.0.21"; // 设备地址,按现场填 var port = 5005; // 端口以设备网络页为准 var timeoutMs = 3000; if (!DeviceSdk.Connect(ip, port, timeoutMs, out var handle)) { var code = DeviceSdk.GetLastError(IntPtr.Zero); Console.WriteLine($"连接失败,错误码={code},先查网络和端口"); return; } try { Console.WriteLine("连接成功,句柄=" + handle); // 这里再调设备信息、人员、记录等接口 } finally { DeviceSdk.Disconnect(handle); Console.WriteLine("会话已断开"); } } }这段代码的逻辑是“连接成功才进业务,失败先看错误码,断开放在 finally”。参数上,ip和port不要写死在代码里,放到配置文件或数据库;timeoutMs太短会在设备忙时误判离线,太长会让界面卡住,3 到 5 秒是常见起点。handle是后续调用的通行证,连接成功后要复用,不要在每个方法里重新 Connect,否则设备会话数会被打满,表现就是“前面能用,跑一会儿连不上”。
连接层还要注意设备编号和密码。有些接口要求传MachineNumber,有些要求传通信密码,文档里叫法不同,但漏传的表现通常是返回 false 或错误码指向权限。我的习惯是把这些参数做成一个DeviceConnectionOptions类,连接前一次性校验:IP 不为空、端口在 1 到 65535、超时大于 0、密码按文档要求填。校验放在连接前,比连接失败后猜原因省时间。
3. 人员与考勤记录同步:C# 调用考勤机接口的完整链路
3.1 人员下发:工号、姓名、卡号、指纹模板的字段映射
人员下发是对接里最容易“看起来成功、实际没生效”的一步。C# 侧的结构体字段顺序、字符串长度、字符编码,必须和文档里的 C 结构体逐字段对齐。工号通常用整型,姓名用定长字符串,卡号有的设备用整型、有的用字符串,权限和启用状态各占一个字段。不要自己猜字段长度,文档写 32 字节就按 32 字节声明,写 24 就写 24,长度错了会踩到后面的字段,表现是姓名正常、卡号错乱。
下面是一个人员结构体和下发调用的示意:
using System.Runtime.InteropServices; [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)] public struct UserInfo { public int MachineNumber; // 设备号,按文档要求填 public int EnrollNumber; // 工号,业务主键 [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 32)] public string Name; // 姓名,长度按文档改 public int CardNumber; // 卡号,部分设备用字符串 public int Privilege; // 权限级别 public int Enabled; // 是否启用 } // 调用处:先查重,再下发 var user = new UserInfo { MachineNumber = 1, EnrollNumber = 1001, Name = "张三", CardNumber = 12345678, Privilege = 0, Enabled = 1 }; var ok = DeviceSdk.SetUserInfo(handle, ref user); if (!ok) { var code = DeviceSdk.GetLastError(handle); Console.WriteLine($"人员下发失败,工号={user.EnrollNumber},错误码={code}"); }这段代码的关键不在SetUserInfo这一行,而在结构体声明。LayoutKind.Sequential保证字段按声明顺序排列;ByValTStr加SizeConst对应 C 里的定长字符数组;CharSet.Ansi表示姓名按多字节传。参数说明:EnrollNumber是业务主键,重复下发同一个工号时,有的设备是覆盖、有的是报错,所以生产代码里要先按工号查设备上是否已存在,再决定新增还是更新;CardNumber如果文档要求字符串,就不能用int硬塞,否则卡号高位可能丢。
指纹模板不要自己在 C# 里生成。考勤机 SDK 通常只接受设备采集的模板,或者文档明确给出的模板格式。常见做法是人员在设备上录入指纹,上位机只下发工号和卡号;如果必须上位机下发指纹,先确认文档里有模板采集和模板下发的完整链路,再动手。把指纹当成一张图片传进去,是典型的想当然。
批量下发时不要在一个循环里逐条同步调用。设备处理每条人员记录需要时间,几百条连续调会把界面卡死,也容易超时。我的做法是分批 50 到 100 条,每批之间让出一点时间,失败记录单独落表重试。重试只重试网络类失败,业务类失败(比如工号重复、字段非法)重试多少次都没用。
3.2 考勤记录拉取:按时间片分页与去重策略
拉取考勤记录的核心不是“调一个读记录函数”,而是“怎么定义一次拉取任务”。设备里的记录是追加的,但设备存储空间有限,旧记录会被覆盖;如果每次全量拉,数据量大且容易重复;如果只拉增量,时间边界没处理好就会漏。我的做法是:以设备时间为基准,按时间片拉取,每个时间片左闭右开,落库时用“设备标识 + 工号 + 刷卡时间 + 验证方式 + 进出状态”做唯一键去重。
下面是一个按时间片读取的示意循环。具体函数名按文档替换:
using System; var start = new DateTime(2024, 6, 1, 0, 0, 0); var end = new DateTime(2024, 6, 2, 0, 0, 0); var pageStart = start; while (pageStart < end) { var pageEnd = pageStart.AddHours(2); // 时间片大小按记录量调 if (pageEnd > end) pageEnd = end; // 先设置读取时间范围,再循环读记录 if (!DeviceSdk.SetReadTime(handle, pageStart, pageEnd)) { Console.WriteLine($"设置时间范围失败:{pageStart} - {pageEnd}"); break; } while (DeviceSdk.ReadGeneralLogData(handle, out var log)) { // 落库前按唯一键去重,不要直接 Insert var key = $"{log.EnrollNumber}|{log.Year}-{log.Month}-{log.Day} {log.Hour}:{log.Minute}:{log.Second}|{log.VerifyMode}|{log.InOutMode}"; Console.WriteLine("读到记录:" + key); } pageStart = pageEnd; // 左闭右开,避免边界重复 }这段代码做三件事:把大时间段切成小片、每片先设置范围再循环读、用唯一键示意去重。参数说明:时间片大小不是固定的,记录量大的现场用 1 到 2 小时,记录量小的用 6 到 12 小时;pageStart = pageEnd保证下一片从上一片结束点开始,左闭右开可以避免同一秒的记录被两片都读到。ReadGeneralLogData返回 false 不一定是错误,很多 SDK 用它表示“没有下一条”,所以要结合错误码判断,不能一 false 就报异常。
提示:拉取前先读一次设备时间。设备长期运行后时间可能偏几分钟,直接用服务器时间切时间片,会把边界附近的记录切到两个任务里,去重键能兜住重复,但兜不住漏读。
去重策略要落到数据库唯一索引,不要只靠内存里的 HashSet。服务重启后内存去重失效,重复数据还是会进去。唯一索引的字段顺序按查询习惯排,通常工号和时间放前面。如果设备支持记录游标或最后一条记录号,优先用游标增量拉,时间片作为兜底;游标方案要在每次成功落库后持久化游标,失败时不要推进游标,否则中间那段记录就丢了。
3.3 实时事件订阅与轮询的取舍:什么时候用回调
考勤机 SDK 一般提供两种拿记录的方式:主动轮询和事件订阅。轮询实现简单,定时调拉取接口,缺点是实时性差、设备压力大;事件订阅由设备主动推,实时性好,但要处理回调线程、断线重连、事件丢失。选型上看业务:门禁实时开门、考场实时上传,用订阅;每天对账、批量同步,用轮询。不要为了“实时”两个字把所有场景都改成订阅,回调里的异常处理比轮询多一倍。
订阅的典型写法是注册一个回调函数,设备有事件时调用。回调线程不是 UI 线程,里面不能直接操作控件,也不能做长耗时数据库写入。我的做法是回调里只做一件事:把事件数据放进线程安全队列,后台线程消费。下面是一个示意:
using System; using System.Collections.Concurrent; using System.Runtime.InteropServices; var queue = new ConcurrentQueue<AttendanceLog>(); [UnmanagedFunctionPointer(CallingConvention.StdCall)] public delegate void DeviceEventCallback(int eventType, IntPtr data); public static void OnDeviceEvent(int eventType, IntPtr data) { // 回调里只入队,不查库、不弹窗 var log = Marshal.PtrToStructure<AttendanceLog>(data); queue.Enqueue(log); } // 注册回调,断线重连后要重新注册 DeviceSdk.SetEventCallback(handle, OnDeviceEvent);这段代码的重点是“回调只入队”。UnmanagedFunctionPointer要和 SDK 的调用约定一致;Marshal.PtrToStructure把非托管指针转成结构体,结构体定义必须和文档一致;queue用并发队列,避免回调和消费线程抢锁。参数说明:eventType用来区分刷卡、开门、报警等事件,不同值对应不同结构体时不能一律按考勤记录解析;data的生命周期只在回调期间有效,不要保存指针,要复制数据。
订阅模式必须配断线重连。设备重启、网络闪断后,原来的回调注册会失效,表现是“昨天能实时收,今天不推了”。重连逻辑里要重新连接、重新注册回调,并补拉断线期间的记录。补拉和订阅不要共用一个去重入口,否则实时记录先到、补拉记录后到,唯一键冲突会让补拉事务整批回滚。常见做法是实时记录走轻量插入,冲突忽略;补拉记录走批量插入,冲突更新或跳过。
4. 设备参数与异常处理:把“连不上、拉不到、写不进”拆开排查
4.1 连接层排查:网络、端口、防火墙、设备服务状态
连接失败先分三层:网络通不通、端口开不开、设备服务活不活。不要一上来就怀疑 SDK。先在现场用 ping 和端口探测确认前两层,再看设备屏幕或管理页确认服务状态。下面两条命令是最小排查集:
# 1. 确认网络可达,丢包率也能看出来 ping -c 3 10.0.0.21 # 2. 确认端口开放,端口号按设备网络设置页填 nc -vz 10.0.0.21 5005ping通只说明 IP 层可达,不代表 SDK 端口能连;nc -vz成功才说明 TCP 端口开放。参数说明:-c 3是发三个包,现场网络差时看丢包率;-v输出详细结果,-z只探测不发送数据。如果端口不通,先查设备网络配置、防火墙入站规则、交换机端口隔离,再查是否有别的程序占着设备会话。
连接层还有一个隐蔽问题:设备会话数上限。很多考勤机同时只允许少量上位机连接,你的程序反复 Connect 不 Disconnect,或者调试时开了多个客户端,设备会拒绝新连接。表现是“刚重启设备能连,过一会儿连不上”。解决不是加大超时,而是保证每次连接都有对应的断开,调试时关掉多余的客户端。我的习惯是在连接封装里加一个using或try/finally,把 Disconnect 放在 finally,不给会话泄漏留机会。
如果网络中间有防火墙,除了端口,还要看协议。有些 SDK 除了主端口还会用额外端口做事件推送或文件传输,只放行主端口会出现“能连上、拉不到记录”的半通状态。文档里通常会写通信端口范围,没有写就抓一次包看实际连接了哪些端口。抓包这件事听起来重,但比盲猜快。
4.2 数据层排查:返回码、结构体对齐、字符编码
连接成功但数据不对,先看返回码,再看结构体,再看编码。返回码不要只打印一个数字,要按文档映射成“网络类、权限类、数据类、存储类”。下面是一个错误结果封装示例,具体码值按文档填:
public readonly record struct SdkResult(bool Ok, int Code, string Message) { public static SdkResult From(bool ok, int code, string message) => new(ok, code, message); } // 调用处统一处理 var result = SdkResult.From(ok, DeviceSdk.GetLastError(handle), "人员下发"); if (!result.Ok) { // 日志里带函数名、工号、错误码,方便回溯 Console.WriteLine($"{result.Message}失败,码={result.Code}"); }这段代码不是要替代 SDK 的错误处理,而是把“布尔返回值 + 错误码”收成一个结果对象,日志里能带业务上下文。参数说明:Ok表示调用是否成功,Code是 SDK 原始错误码,Message写当前操作名。排查时最怕日志只有“失败”两个字,没有函数名、没有工号、没有设备 IP,事后只能猜。
结构体对齐是数据层的大坑。C# 默认按字段自然对齐,C 结构体可能有#pragma pack,两边不一致时字段会错位。表现是工号读出来正常、卡号变成另一个数,或者时间字段整体偏移。解决办法是照文档里的结构体定义逐字段核对,必要时用StructLayout(LayoutKind.Sequential, Pack = 1),但Pack不能乱加,文档没写就先按默认。字符编码同理,SDK 要 ANSI 就传 ANSI,C# 字符串默认 Unicode,直接传会乱码或截断。
4.3 写操作排查:权限、重复主键、设备存储满
写操作失败比读操作更难查,因为设备只回一个 false。我的排查顺序是:权限够不够、主键是否重复、设备存储是否满、字段是否超长。权限问题常见于通信密码没传或设备开了管理保护;重复主键常见于工号已存在但接口是新增语义;存储满常见于人员数或指纹数到上限,表现是“小批量能写,批量写到一半失败”。
写人员前先查重,不要直接 Set。下面是一个查重示意:
// 先按工号查设备上是否已存在 var exists = DeviceSdk.GetUserInfo(handle, enrollNumber, out var existing); if (exists) { // 已存在则走更新,避免重复主键报错 Console.WriteLine($"工号 {enrollNumber} 已存在,走更新"); } else { Console.WriteLine($"工号 {enrollNumber} 不存在,走新增"); }这段代码把“新增还是更新”显式化。参数说明:enrollNumber是业务主键,查重按它查;existing接设备返回的现有人员信息,用于判断是否需要覆盖字段。注意有些 SDK 的SetUserInfo本身就是覆盖语义,但文档没写清楚时,先查再写更稳。批量写入时每批结束后读一次数量或抽样回读,确认真的写进去了,不要只看返回值。
设备存储满要提前监控。人员下发接口返回失败时,如果错误码指向存储,先删无用人员或清理旧记录,再重试。不要在生产环境直接清空设备数据,先导出备份。考勤机不是数据库,删了没有回滚。
5. 避坑与常见问题:五个血泪踩坑记录
5.1 现象:Demo 能跑,自己工程一调就崩
现象:例子程序双击能连设备,自己新建的 C# 工程一调就报“找不到指定的模块”或直接闪退。原因通常不是代码写错,而是平台目标或依赖没对齐:例子是 x86,你的工程是 Any CPU;或者主 DLL 复制了,旁边的依赖 DLL 没复制;或者调试目录和运行目录不是同一个。解决顺序是先用dumpbin /headers看 DLL 位数,再把工程平台目标改成一致,然后把主 DLL 和依赖一起放到输出目录,最后用绝对路径加载。不要用“重装 SDK”掩盖路径问题。
5.2 现象:读记录有重复、漏读
现象:同一张卡同一时间在库里出现两条,或者换班时段的记录少了几条。原因常见于时间片边界重叠、设备时间不准、分页游标没持久化。解决是把时间片改成左闭右开,拉取前同步设备时间,落库加唯一索引;用游标方案时,成功落库后才推进游标,失败不推进。去重键至少包含工号、刷卡时间、验证方式、进出状态,少了任何一项都可能把合法记录误判成重复。
5.3 现象:中文姓名乱码
现象:设备上显示正常,拉到 C# 里姓名变成问号或乱码;下发中文姓名后设备上显示一半。原因多是 SDK 按 ANSI 或多字节编码处理字符串,C# 侧用了 Unicode 或默认 UTF-8。解决是 P/Invoke 声明里用CharSet.Ansi,结构体字符串用ByValTStr并给足长度;如果 .NET Core 下需要 GBK,先注册CodePagesEncodingProvider再取编码。不要用Encoding.Default赌运行环境。
using System.Text; Encoding.RegisterProvider(CodePagesEncodingProvider.Instance); var gbk = Encoding.GetEncoding("GBK"); var bytes = gbk.GetBytes("张三");这段代码只解决“C# 侧转码”这一半问题,另一半在结构体声明和 SDK 的字符集参数。参数说明:GBK按文档要求替换,有的 SDK 用ANSI代码页;GetBytes得到的字节数组长度不能超过结构体字段长度,超了会被截断。
5.4 现象:大批量下发人员超时
现象:下发几十条没问题,几百条时程序卡住,设备返回超时,重试后一部分成功一部分失败。原因是逐条同步调用把设备处理时间累加,加上界面线程被占,超时阈值被撑爆。解决是分批,每批 50 到 100 条,批间留间隔;把下发放到后台线程,界面只收进度;失败记录单独落表,只重试网络类失败。不要把所有失败都重试,字段非法和主键重复重试多少次都一样。
5.5 现象:Win10 以上系统调用 DLL 报找不到
现象:开发机是 Win10 或 Win11,DLL 明明在目录里,程序就是报找不到;换一台旧系统又能跑。原因可能是依赖的 VC 运行库没装、DLL 被系统安全策略拦截、或者进程位数和 DLL 不一致。解决是先用依赖查看工具看缺失的依赖 DLL,补运行库;再把 DLL 放到程序目录并用绝对路径加载;最后确认进程位数。不要把 DLL 丢到System32碰运气,部署到客户机时一定会出问题。
6. 进阶:把 SDK 封装成可复用 C# 服务,并用日志验证每一次调用
6.1 统一调用门面:连接池与重试边界
我不会在业务代码里直接调DeviceSdk.Connect。更稳的做法是抽一个IAttendanceDevice门面,里面管连接、断开、超时、错误码转换,业务层只关心“拉某段时间的记录”和“下发某个人员”。连接不要做池化,考勤机不是数据库,会话数有限,一个设备一个长连接足够;重试只放在门面里,并且只对网络超时、连接断开这类错误重试,业务错误直接返回。
6.2 用结构化日志验收:一次拉取任务的字段清单
日志是排查的黑匣子。每次拉取任务至少记这些字段,出问题时能还原现场:
| 日志字段 | 示例 | 用途 |
|---|---|---|
| DeviceIp | 10.0.0.21 | 定位哪台设备 |
| Function | ReadGeneralLogData | 定位哪个接口 |
| TimeRange | 2024-06-01 00:00 ~ 02:00 | 定位时间片 |
| ElapsedMs | 1832 | 判断是否接近超时 |
| ReturnCode | 0 | 区分成功与失败类型 |
| RecordCount | 126 | 判断是否漏拉 |
日志不要只写“拉取成功”。记录数、耗时、时间片、错误码四样齐全,复盘时才有依据。
6.3 升级 SDK 前先跑哪三个用例
升级 SDK 或换设备型号前,我强制跑三个用例:连接与断开连续 50 次不泄漏会话;人员下发后回读字段一致;增量拉取在边界时间不重不漏。三个用例都过,才允许上生产。从那以后我每次接新的考勤机开发包,都先把文档里的返回码和结构体长度抄到一张纸上,再对着例子跑通连接和拉记录两件事,最后才写业务代码。这套顺序看着慢,但能少返工。希望帮到你。
本文还有配套的精品资源,点击获取