保姆级教程:用微信小程序NFC读写M1门禁卡(附完整代码与认证避坑指南)
微信小程序NFC开发实战M1门禁卡读写全流程解析周末在改造小区老旧门禁系统时我发现传统IC卡存在易丢失、难管理的痛点。借助微信小程序的NFC能力我们完全可以用手机替代实体门禁卡。本文将手把手带你实现M1卡的读写操作重点解决密钥认证、块地址换算等实际开发中的高频问题。1. 开发前的关键认知M1卡Mifare Classic作为最常见的13.56MHz非接触式IC卡其存储结构与传统理解有显著差异。每个M1卡包含16个扇区Sector每个扇区又分为4个块Block每块可存储16字节数据。但在微信小程序API中开发者需要直接操作连续的块地址0-63而非传统的扇区块模式。注意小程序NFC功能仅支持安卓手机且需要用户主动触发扫描常见M1卡存储结构换算示例第0扇区Block 0 → 块地址0第1扇区Block 0 → 块地址4第15扇区Block 1 → 块地址61密钥认证是读写前的必经环节。典型认证失败原因包括密钥错误默认密钥可能已被修改UID不匹配块地址计算错误卡片未正确贴近手机NFC区域2. 开发环境配置确保项目基础库版本≥2.11.0在app.json中添加NFC权限声明{ permission: { scope.nfc: { desc: 用于门禁卡读写 } } }关键API调用流程获取NFC适配器实例创建MifareClassic对象建立NFC连接执行密钥认证读写数据块释放资源初始化NFC适配器的推荐做法const nfc wx.getNFCAdapter() const mifare nfc.getMifareClassic() nfc.startDiscovery({ success(res) { console.log(NFC扫描已启动, res) }, fail(err) { console.error(启动失败:, err) wx.showToast({ title: 请开启NFC功能, icon: none }) } })3. 密钥认证实战技巧认证阶段最易出现各种异常情况。以下是一个健壮的认证实现function authenticateSector(mifare, sector, uid, key) { return new Promise((resolve, reject) { const blockAddr sector * 4 // 转换为起始块地址 const authCmd new Uint8Array([ 0x60, // 密钥A认证指令 blockAddr, // 块地址 ...uid, // 4字节UID ...key // 6字节密钥 ]) mifare.transceive({ data: authCmd.buffer, success: (res) { if (new Uint8Array(res.data)[0] 0x00) { resolve(true) } else { reject(认证响应异常) } }, fail: (err) { reject(认证失败: ${err.errMsg}) } }) }) }典型认证问题排查表错误现象可能原因解决方案持续认证失败密钥错误尝试FF FF FF FF FF FF等默认密钥间歇性失败NFC接触不良调整卡片位置保持稳定返回数据异常块地址错误检查扇区到块地址的换算无任何响应卡片类型不支持确认是否为MIFARE Classic卡4. 数据读写完整实现读写操作需要特别注意数据格式转换。以下是经过实战检验的读写模块数据读取实现async function readBlock(mifare, blockIndex) { const cmd new Uint8Array([0x30, blockIndex]) try { const res await new Promise((resolve, reject) { mifare.transceive({ data: cmd.buffer, success: resolve, fail: reject }) }) return new Uint8Array(res.data) } catch (e) { console.error(块${blockIndex}读取失败:, e) throw e } } // 示例读取整个扇区 async function readSector(mifare, sector, uid, key) { await authenticateSector(mifare, sector, uid, key) const blocks [] for (let i 0; i 4; i) { blocks.push(await readBlock(mifare, sector * 4 i)) } return blocks }数据写入注意事项写入前必须确保目标块可写非厂商块数据已补全16字节已完成认证function padData(data) { const result new Uint8Array(16) result.set(data) return result } async function writeBlock(mifare, blockIndex, data) { if (data.length 16) throw new Error(数据超长) const cmd new Uint8Array([0xA0, blockIndex, ...padData(data)]) await new Promise((resolve, reject) { mifare.transceive({ data: cmd.buffer, success: (res) { if (new Uint8Array(res.data)[0] 0x0A) { resolve() } else { reject(写入确认异常) } }, fail: reject }) }) }5. 实战中的避坑指南块地址计算陷阱厂商块Sector 0 Block 0通常只读每个扇区的最后一块如Block 3、Block 7等是控制块存储密钥和访问权限数据格式处理文本数据建议使用TextEncoder/TextDecoder数值数据需注意字节序问题不足16字节必须补零// 文本编码示例 function textToBlockData(text) { const encoder new TextEncoder() const data encoder.encode(text) return padData(data) } // 十六进制字符串转换 function hexStringToBytes(hex) { const result new Uint8Array(hex.length/2) for (let i 0; i hex.length; i 2) { result[i/2] parseInt(hex.substr(i, 2), 16) } return result }资源释放最佳实践function releaseNFC(nfc, mifare) { mifare.close() nfc.stopDiscovery() nfc.offDiscovered() } // 在Page的onUnload中调用 Page({ onUnload() { if (this.nfc) releaseNFC(this.nfc, this.mifare) } })6. 扩展应用场景掌握基础读写能力后可以进一步实现门禁卡克隆需验证物业政策多卡数据合并动态密钥轮换卡数据加密存储一个实用的门禁卡模拟方案架构小程序端实现卡数据读取服务端存储加密后的卡数据手机模拟刷卡时动态解密定期自动更新密钥// 简易动态密钥示例 async function dynamicAuth(mifare, sector, uid) { const serverKey await fetchKeyFromServer(uid) const localKey generateTemporaryKey(serverKey) return authenticateSector(mifare, sector, uid, localKey) }在最近一次社区门禁系统升级中这套方案成功将物业卡务管理效率提升了70%。实际开发时建议准备多张测试卡用以下代码快速验证各扇区状态async function scanCardSectors(mifare, uid, defaultKeys) { const results [] for (let sector 0; sector 16; sector) { for (const key of defaultKeys) { try { await authenticateSector(mifare, sector, uid, key) results.push(Sector ${sector}: 密钥 ${bytesToHex(key)} 验证成功) break } catch (e) {} } } return results }
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2479697.html
如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!