尧图建网站 尧图建网站 YAOTU WEB BUILD 免费咨询
ARTICLE DETAIL

资讯详情

深耕网站建设与建站编程的一线实战洞察。

C#门禁SDK Demo源码解析:登录、权限下发与记录闭环

C#门禁SDK Demo源码解析:登录、权限下发与记录闭环 简介面向海康威视门禁系统二次开发的C#示例包聚合演示程序源码、设备网络SDK使用手册与门禁主机编程指南适合具备一定C#基础、希望快速接入门禁设备的开发者与项目团队。压缩包共229个文件约19.43MB以65个cs源码、44个resources资源、39个dll依赖库和27个resx界面资源为主并含exe、config、sln等项目文件可在Visual Studio中梳理工程结构与调试流程chm手册与pdf指南分别覆盖SDK调用、通信协议、用户权限、事件记录和报警处理等要点。已有3072人学习说明其在门禁对接场景中具有参考价值。读者可结合示例理解读卡验证、远程开门、设备控制与数据交换的实现逻辑通过阅读源码掌握SDK函数调用和界面组织方式也可借文档排查通信异常、权限配置与报警联动问题在模仿与修正中建立从Demo到实际项目的开发路径。1. 从一台门禁一体机说起这份 C# 门禁 SDK Demo 到底能省掉哪些对接活现场最怕的不是门禁设备不联网而是联上了却不知道卡权限到底有没有下发成功。一份能跑的 C# 门禁 SDK Demo含源码和开发文档价值不在于“能开门”这个动作而在于把设备登录、人员卡号建模、权限下发、事件回调、记录拉取串成可验证的闭环。它适合正在做门禁对接的 C# 开发、系统集成实施和需要二次封装的平台侧人员新手能照顺序编译跑通熟手能直接看参数边界和异常分支。下面按源码结构、登录、回调、权限、记录、避坑和验证逐层拆。2. 把 SDK 跑起来工程结构、依赖与设备登录的最小闭环2.1 先看源码目录哪些文件决定能不能编译拿到一份 C# 门禁 Demo不要急着改业务代码。先看目录里谁负责“调 SDK”、谁负责“存本地状态”、谁负责“界面展示”。常见结构会把设备访问封装在一个 Core 类库里Demo 只做调用示例如果所有逻辑都堆在窗体代码里二次封装时就要先把登录、下发、回调抽出来。判断能不能编译重点看三处SDK 封装库的引用路径、目标平台的位数、配置文件的读取方式。设备 SDK 的 DLL 往往分 x86 和 x64进程位数不一致时表现不是编译报错而是运行时加载失败或登录直接返回空句柄这种翻车在现场很常见。下面这段构建命令只做一件事在解决方案根目录先还原依赖再按 x64 平台编译 Debug 版本。命令里的平台参数必须和libs目录下 DLL 的位数一致如果源码是旧式工程文件用开发工具打开解决方案生成即可不必强行套命令。# 在解决方案根目录执行先确认 SDK 封装库的位数与进程位数一致 dotnet restore dotnet build -c Debug -p:Platformx64这段命令的逻辑是先解决 NuGet 依赖再按指定平台编译。-c Debug用于保留调试符号方便在登录失败时单步看返回值-p:Platformx64不是随便写的它决定生成进程是 64 位还是 32 位。参数改错时常见现象是编译通过但运行到LoadLibrary或Login就异常。如果项目引用的不是 NuGet 包而是本地 DLL编译前还要检查引用属性里的“复制到输出目录”是否为 true否则运行目录缺少依赖排错会变成黑匣子。目录里通常还有docs和config。前者是接口文档后者是设备地址、端口、账号等配置。先不要把这些值写死在代码里后面第 5 章会把它抽成配置。此阶段只确认一件事用 Demo 自带的示例配置能登录一台测试设备或者至少能走到“网络连接失败”而不是“方法找不到”。如果连方法签名都对不上说明源码和文档不是同一套封装先停下来对齐版本不要继续改业务。2.2 设备登录与长连接参数、返回码和超时门禁 SDK 的登录不是数据库连接它通常会在本地 SDK 里维护一个设备句柄后续下发权限、开门、查记录都依赖这个句柄。所以正确的顺序是初始化 SDK、设置日志回调、登录设备、注册事件、启动监听。不要每次刷卡都重新登录也不要在回调线程里反复调用登录。登录参数里最容易被忽略的是超时和重连超时太短现场网络抖动会误判离线没有重连设备重启后句柄失效后续操作全部返回“未登录”。using DoorAccess.Sdk; // 按你拿到的封装库替换命名空间 var sdk new DoorSdk(); sdk.SetLogCallback((level, msg) { // SDK 内部日志先落到控制台排错时比业务日志更靠前 Console.WriteLine($[SDK][{level}] {msg}); }); var loginParam new LoginParam { Address 192.168.1.64, Port 8000, UserName admin, Password ******, ConnectTimeoutMs 5000, Reconnect true, DeviceId gate-a-01 // 本地自定义设备标识用于记录去重和日志追踪 }; var loginResult sdk.Login(loginParam); if (!loginResult.IsSuccess) { Console.WriteLine($登录失败 Code{loginResult.Code}, Msg{loginResult.Message}); return; } sdk.StartListen(); // 先登录再启动监听避免回调拿不到有效句柄这段代码的关键不在方法名而在调用顺序和参数含义。Address、Port、UserName、Password是设备侧参数必须和现场配置一致ConnectTimeoutMs控制首次连接等待时间现场无线网桥或跨网段时不要压到一两秒Reconnect打开后SDK 通常会尝试恢复长连接但业务层仍要处理“重连成功后的重新订阅”。DeviceId不是设备返回的而是本地给这台设备起的稳定标识后面记录去重、日志排查都靠它。返回码不要只看“成功/失败”两个分支。常见做法是按类别处理认证类错误先查账号密码和设备是否被锁定网络类错误先查 IP、端口、防火墙和网段资源类错误先查设备是否达到人数、卡数或权限上限SDK 类错误先查初始化是否完成、DLL 位数是否匹配。文档里的返回码表要对照源码里的常量类看不要凭记忆判断。下面这张表是排查时的分类口径具体数值以你手上的接口文档为准。错误类别常见现象先查什么认证失败登录立即返回失败账号密码、设备是否允许该账号登录、是否被其他平台占用网络超时等待数秒后失败IP 端口、防火墙、跨网段路由、设备是否休眠SDK 未初始化调用任何接口都报错初始化顺序、DLL 加载路径、进程位数设备忙下发或查询偶发失败并发调用数、批量任务是否未结束、设备负载权限不足登录成功但配置类接口失败账号角色、设备工作模式、功能授权提示登录成功后先把设备信息、SDK 版本、当前工作模式读出来写进日志。后面出现“登录成功但下发失败”时这三项能省掉大量猜测。2.3 用回调接门禁事件刷卡、开门、报警怎么分流门禁事件不要靠轮询记录接口硬扫。轮询适合补历史记录实时性差还会把设备压出重复记录。Demo 里通常会注册一个事件回调刷卡、密码开门、二维码、人脸、门磁、报警都从同一个回调进来业务侧要做的是“轻回调、重队列”回调线程只做参数拷贝和入队数据库写入、HTTP 上报、界面刷新放到后台线程。否则一次慢 SQL 就能把 SDK 回调线程堵住表现就是“刷卡有记录但平台没反应”。using System.Collections.Concurrent; var eventQueue new ConcurrentQueueAccessEvent(); sdk.OnAccessEvent (sender, e) { // 不要在回调线程里做数据库或网络请求只复制需要的最小字段 eventQueue.Enqueue(new AccessEvent { DeviceId loginParam.DeviceId, PersonId e.PersonId, CardNo NormalizeCardNo(e.CardNo), DoorNo e.DoorNo, EventType MapEventType(e.EventType), EventTime e.EventTime, RawCode e.RawCode }); }; // 后台消费失败可重试不阻塞 SDK 回调 _ Task.Run(async () { while (true) { if (eventQueue.TryDequeue(out var evt)) { await _eventStore.SaveAsync(evt); } else { await Task.Delay(50); } } });这段代码的逻辑是把 SDK 回调转换成业务事件并立刻脱离回调线程。NormalizeCardNo用于统一卡号格式十六进制大小写、前导零、十进制转字符串都要在入口处定死否则同一张卡会在记录里变成多个卡号。MapEventType把 SDK 枚举映射成业务枚举不要直接把厂商枚举存进数据库后续换设备或升级 SDK 时会很被动。RawCode保留原始事件码排错时能回查文档。队列消费失败要有重试和死信记录不能吞异常。事件分流要按“是否影响通行”区分。刷卡、密码、二维码、人脸属于通行事件重点看人员、卡号、门号、时间门磁、出门按钮、报警属于状态事件重点是持续时间和恢复时间。两类事件混在一张表里会导致查询很痛苦常见做法是通行记录一张表设备事件一张表用DeviceId EventTime RawCode关联。回调注册时机也要注意有的封装要求登录后注册有的要求初始化后注册以文档为准重连后如果 SDK 不自动恢复订阅就要在重连回调里重新注册。3. 权限与记录卡片下发、人脸/二维码、记录拉取的实现顺序3.1 人员与卡号模型先建本地映射再下发门禁对接最忌讳“直接把界面上的字符串丢给设备”。设备侧通常只认人员编号、卡号、门权限和时间窗平台侧还有姓名、部门、有效期、照片、二维码等信息。正确做法是先建本地人员表和卡号表把设备返回的唯一标识或本地生成的稳定 ID 作为主键再下发。卡号要单独建映射因为一个人可能多张卡一张卡也可能换人如果卡号直接塞在人员表里换卡时历史记录会对不上。public sealed class PersonCard { public string PersonId { get; set; } // 本地稳定人员编号 public string PersonName { get; set; } public string CardNo { get; set; } // 统一格式后的卡号 public int CardType { get; set; } // 普通卡、VIP 卡等按文档映射 public DateTime BeginTime { get; set; } public DateTime EndTime { get; set; } public string SyncState { get; set; } // Pending/Synced/Failed } public static string NormalizeCardNo(string raw) { if (string.IsNullOrWhiteSpace(raw)) return string.Empty; var text raw.Trim().Replace( , ).ToUpperInvariant(); // 常见设备卡号为十六进制去掉前导零但保留至少一位 text text.TrimStart(0); return string.IsNullOrEmpty(text) ? 0 : text; }这段代码的重点是PersonId和CardNo分离以及SyncState记录同步状态。NormalizeCardNo做了去空格、转大写、去前导零目的是让“00A1B2”和“a1b2”落到同一个卡号上但具体规则要按设备文档确认有的设备要求保留固定长度有的要求十进制。SyncState不要只存成功/失败最好有Pending、Syncing、Synced、Failed四态失败时记录返回码和重试次数。人员下发和设备权限下发要分开先UpsertPerson成功后再SetDoorAuth任何一步失败都回滚本地状态或标记待补偿。参数上BeginTime、EndTime要统一用设备支持的格式和时区。常见坑是平台用本地时间设备用 UTC 或设备本地时间跨时区项目会出现“卡在有效期内却开不了门”。我一般会在本地表里存 UTC展示时再转本地下发前按文档要求转换。人员姓名不要超过设备字段长度中文按字节截断而不是按字符截断否则会出现半个汉字。3.2 权限下发流程增删改和批量任务权限下发不是一次 API 调用就结束。单门下发的接口通常简单但多门、多人、多时间段会走批量任务创建任务、查任务进度、处理部分失败。Demo 里为了演示可能只写单条下发现场用的时候要补三件事幂等、重试、补偿。幂等的意思是同一人员同一门同一时间窗重复下发不产生重复权限重试要区分可重试错误和不可重试错误补偿是本地任务表里失败记录能下次启动继续跑。var personResult sdk.UpsertPerson(new PersonInfo { PersonId card.PersonId, Name card.PersonName, CardNo card.CardNo, CardType card.CardType, BeginTime card.BeginTime, EndTime card.EndTime }); if (!personResult.IsSuccess) { MarkFailed(card.PersonId, UpsertPerson, personResult.Code, personResult.Message); return; } var authResult sdk.SetDoorAuth(new DoorAuthParam { PersonId card.PersonId, DoorIds new[] { 1, 2 }, BeginTime card.BeginTime, EndTime card.EndTime, VerifyMode VerifyMode.CardOrFace // 按设备支持能力映射 }); if (!authResult.IsSuccess) { MarkFailed(card.PersonId, SetDoorAuth, authResult.Code, authResult.Message); return; } MarkSynced(card.PersonId);这段代码先写人员再写门权限。DoorIds是设备侧门号不是平台的门 ID中间要有一层映射VerifyMode表示验证方式刷卡、人脸、二维码、卡人脸等组合要按设备能力配置不要给不支持人脸的设备下发人脸模式。MarkFailed要把操作名、返回码、返回消息一起落库否则只知道“失败了”不知道失败在人员还是权限。批量场景下不要在一个事务里循环调用 SDK设备侧事务和数据库事务不是一回事正确做法是本地任务表驱动单条结果回写失败进入重试队列。重试要有退避。设备忙、网络超时这类错误可以等几秒再试参数错误、权限不足这类错误重试一百次也没用应直接转人工。批量任务如果 SDK 提供了任务查询接口要记录任务号并轮询进度不要在客户端死等。轮询间隔不要太短常见做法是首轮 500 毫秒之后逐步拉长直到任务结束或超时。超时后不要立刻重发整个批量任务先查设备侧实际结果避免重复下发。3.3 记录拉取与断点续传分页、去重、时区记录拉取是门禁平台最容易出错的部分。设备记录接口通常支持按时间范围和分页查询但分页边界、设备时间、网络重试会导致重复或漏拉。稳妥的做法是本地保存每台设备的拉取游标查询时向前重叠几秒入库时用业务唯一键去重。游标不要只用DateTime.Now因为设备时间和服务器时间有偏差可以用“设备最后一条记录时间 本地入库时间”双游标设备时间用于查本地时间用于补偿。var cursor LoadCursor(loginParam.DeviceId); var query new RecordQuery { BeginTime cursor.LastEventTime.AddSeconds(-5), // 重叠窗口防止边界漏记录 EndTime DateTime.UtcNow, PageNo 1, PageSize 100 }; while (true) { var page sdk.QueryAccessRecords(query); if (!page.IsSuccess || page.Items.Count 0) break; foreach (var item in page.Items) { var key ${loginParam.DeviceId}|{item.PersonId}|{item.CardNo}| ${item.DoorNo}|{item.EventTime:yyyyMMddHHmmss}|{item.RawCode}; if (_recordRepo.TryInsert(key, item)) { cursor.LastEventTime item.EventTime cursor.LastEventTime ? item.EventTime : cursor.LastEventTime; } } if (page.Items.Count query.PageSize) break; query.PageNo; } SaveCursor(loginParam.DeviceId, cursor);这段代码做了三件事重叠查询、唯一键去重、游标推进。BeginTime向前减 5 秒是给设备时间抖动留窗口代价是可能重复拉到几条记录靠唯一键挡住。唯一键里必须包含DeviceId否则两台设备同一人同一秒刷卡会互相覆盖RawCode也要进键区分刷卡、密码、远程开门等不同事件。PageNo递增时要注意设备是否支持稳定排序如果不支持分页可能漏数据这时改用“上一页最后一条时间”作为下一页起点更稳。时区处理要单独说。设备记录时间可能是设备本地时间也可能是 UTC文档里一般会写。不要直接拿字符串截取存库先转成DateTimeOffset或统一 UTC展示层再转。跨天查询、夏令时地区、设备断电后时间复位都会让记录时间异常遇到明显早于上次游标的时间不要直接丢弃标记为“时间异常”单独入表后续人工核对。记录表要有原始时间、入库时间、设备时间三个字段排错时能还原现场。4. 避坑与排查门禁 SDK 对接最常见的五类翻车4.1 登录成功但下发失败权限位和平台模式现象登录接口返回成功读设备信息也正常但调用人员下发或门权限下发一直失败换一张卡偶尔又能成功。原因常见有三类。设备处于某种平台托管模式SDK 写入被限制当前登录账号没有配置权限人员还没建成功就直接下发门权限。还有一种是设备人数或卡数达到上限返回码看起来像参数错误实际是资源不足。解决先用单个人、单张卡、单门、最小时间窗做一次下发确认链路再查设备工作模式和账号角色最后查设备容量。把每次下发的操作名、人员 ID、门号、返回码写进日志不要只记“下发失败”。如果单条能成功、批量失败问题通常在并发或任务未结束不在权限模型。4.2 回调丢事件线程模型与重连现象设备本地能看到刷卡记录平台回调偶尔不触发重启程序后又恢复。原因回调注册在登录之前句柄无效回调线程里做了慢查询把 SDK 回调堵死设备重连后没有重新订阅事件。解决固定“初始化、登录、注册回调、启动监听”的顺序回调里只入队不入库在重连回调里重新注册并打日志。判断是不是线程阻塞看回调时间戳和业务入库时间的差值如果入库时间总是晚几秒且越晚越多基本就是消费不过来。回调队列要有上限和丢弃策略宁可记录丢弃日志也不要让内存无限涨。4.3 中文乱码编码与结构体对齐现象人员姓名下发后在设备端显示乱码或者查回来变成问号。原因SDK 结构体字符串编码与文档不一致常见是 UTF-8 和本地编码混用字符串字段按字符截断导致半个汉字结构体字段对齐或长度定义错误。解决按文档指定编码不要全局改控制台编码蒙混姓名字段按字节截断并保证不截断半个汉字结构体定义用文档给的长度和对齐方式不要自己加字段。排查时先下发英文名英文正常、中文异常就是编码问题英文也异常先查字段长度和结构体布局。4.4 记录重复或缺失时间窗口和去重键现象同一张卡同一秒出现两条记录或者跨天查询少了几条。原因分页边界重复、重试导致重复拉取、唯一键缺设备号或事件码、查询时间用了本地时间而设备用 UTC。解决查询向前重叠几秒入库用DeviceId PersonId CardNo DoorNo EventTime RawCode做唯一键分页不稳定时改用时间游标所有时间统一转 UTC 再比较。记录表加唯一索引重复插入直接忽略并计数计数异常升高时回查拉取窗口。4.5 现场断网本地缓存与幂等现象现场网络恢复后平台重复下发权限或者设备重启后部分权限丢失。原因没有本地任务表失败就丢重试没有幂等判断设备重启后本地状态与设备状态不一致。解决本地建同步任务表记录Pending/Syncing/Synced/Failed重试前先查设备侧是否已存在程序启动时做一次全量对账按人员和门维度比对。断网期间不要疯狂重试登录退避到十秒或三十秒一次避免把设备打挂。5. 从 Demo 到现场配置化、日志与部署检查5.1 把硬编码抽成配置文件Demo 里把设备地址、端口、账号写在代码里很常见现场一换设备就要重新编译这不是能不能跑的问题是维护成本的问题。把设备参数、SDK 路径、分页大小、重叠秒数抽到 JSON 配置里启动时校验必填项缺项直接报错退出不要等到登录时才报空引用。密码不要明文长期存放常见做法是配置里放环境变量名启动时从环境变量读取如果必须落盘用当前用户或服务账号的本地加密能力至少不要提交到代码仓库。{ Device: { Address: 192.168.1.64, Port: 8000, UserName: admin, PasswordEnv: DOOR_DEVICE_PASSWORD, ConnectTimeoutMs: 5000, Reconnect: true, DeviceId: gate-a-01 }, Sdk: { DllDir: libs/x64, LogLevel: Info }, Sync: { RecordPageSize: 100, OverlapSeconds: 5, RetryBaseMs: 1000, RetryMaxMs: 30000 } }这份配置把设备参数和同步参数分开。PasswordEnv存环境变量名而不是密码DllDir指向与进程位数一致的目录OverlapSeconds控制记录拉取重叠窗口RetryBaseMs和RetryMaxMs控制退避上限。读取配置后要做类型和范围校验比如端口必须在 1 到 65535分页大小不能为 0重叠秒数不能大于查询窗口。校验失败时打印字段名不要只打印“配置错误”。5.2 日志要记什么请求号、设备返回码、耗时门禁对接的日志不是给开发自己看的是给现场实施和后续排障看的。每条关键操作至少记请求号、设备 ID、操作名、人员 ID、门号、返回码、返回消息、耗时。请求号用于把一次业务操作里的登录、下发、查询串起来设备 ID 用于区分多台设备返回码和消息必须原样记录不要只记“失败”。日志级别要克制回调事件用 Debug 或 Info失败用 Warning影响通行的错误用 Error。密码、完整卡号、人员姓名要做脱敏卡号保留后四位即可。using (_logger.BeginScope(new Dictionarystring, object { [TraceId] Guid.NewGuid().ToString(N), [DeviceId] loginParam.DeviceId, [Op] SetDoorAuth })) { var sw System.Diagnostics.Stopwatch.StartNew(); var ret sdk.SetDoorAuth(authParam); sw.Stop(); _logger.LogInformation( door auth finished person{PersonId} doors{DoorIds} code{Code} cost{Cost}ms, authParam.PersonId, string.Join(,, authParam.DoorIds), ret.Code, sw.ElapsedMilliseconds); }这段日志用BeginScope把请求号、设备 ID、操作名绑到一次调用上后面查日志可以按TraceId聚合。cost是排查设备忙和网络慢的关键指标耗时突然从几十毫秒涨到几秒通常不是代码问题而是设备负载或网络抖动。ret.Code必须记录不要只记录异常消息。日志落地要按天切割并保留足够天数现场问题往往不是当天发现。5.3 部署与自启动Windows 服务还是托盘程序Demo 通常是控制台或窗体程序现场部署要决定跑成 Windows 服务还是托盘程序。服务适合无人值守、开机自启、不依赖用户登录托盘程序适合需要看界面、手动重连的场景。无论哪种先确认 SDK 封装库是否允许在服务会话里加载有的设备 SDK 依赖用户会话或界面消息循环服务里回调不触发这种就要改成托盘或单独进程。部署目录要固定DLL 路径用相对路径解析不要依赖当前工作目录。自启动前做三项检查进程位数与 DLL 一致、端口和防火墙放行、配置文件路径可读。服务账号要有权限读取配置和写日志目录不要用网络路径当日志目录。健康检查不要只 ping 设备要真实调用一次轻量接口比如读设备时间或查一条记录连续失败再触发重连避免偶发超时导致频繁重连。升级时先停服务、替换 DLL、再启动替换前备份配置和日志出问题能回退。6. 进阶验证用一套压测脚本验证门禁链路是否真的可用6.1 构造测试数据卡号、人员、权限验证不要只点一次“开门成功”就结束。我会构造三类测试数据正常卡、过期卡、无权限卡。正常卡用于验证下发和记录闭环过期卡验证时间窗无权限卡验证拒绝逻辑。卡号准备十六进制大小写混合、带前导零、带空格的变体确认归一化规则生效人员准备中英文姓名和超长姓名确认截断不产生乱码。测试数据单独放在测试库不要污染正式人员表。6.2 压测与断言登录、下发、开门、记录闭环压测脚本不要一上来就打几百并发。先串行跑通“登录、下发、开门、查记录”四段再加并发。断言点包括登录返回成功、下发返回成功、记录能在重叠窗口内查到、同一事件不重复入库、失败任务能进入重试。下面这段伪代码展示的是串行自检的骨架方法名按你手上的 SDK 映射。var smoke new DoorSmokeTest(sdk, config); var login await smoke.LoginAsync(); Assert(login.IsSuccess, $login failed: {login.Code}); var push await smoke.PushCardAsync(P1001, 00A1B2C3, DateTime.UtcNow, DateTime.UtcNow.AddDays(1)); Assert(push.IsSuccess, $push failed: {push.Code}); var open await smoke.RemoteOpenAsync(doorNo: 1); Assert(open.IsSuccess, $open failed: {open.Code}); var records await smoke.QueryRecordsAsync(DateTime.UtcNow.AddMinutes(-2), DateTime.UtcNow); Assert(records.Any(r r.CardNo A1B2C3), record not found in window);这段脚本把四段链路串起来任何一段失败都打印返回码。PushCardAsync的时间窗用 UTC查询窗口向前多给两分钟避免设备时间偏差。并发测试时每张卡只对应一个人记录断言按唯一键计数不要按总数断言因为其他测试数据会干扰。压测频率逐步加观察 SDK 日志里的设备忙和超时比例比例上升就停不要硬压。从那以后我每次接门禁 SDK都先跑一遍登录、回调、下发、记录四段自检再谈业务功能这套习惯比任何“现场调试技巧”都管用。希望帮到你。资源包里是源码、依赖说明和接口文档拿到后先按第 2 章顺序编译再对照第 3 章参数表改配置需要的人按资源页给出的获取方式下载即可。本文还有配套的精品资源点击获取
返回列表