微信小程序NFC监听、读取;Scheme唤起小程序
一个需求,要NFC封签唤起小程序,期望用小程序来做一些管理工作。又因为NFC的可复制性过于便捷,还要做加密处理,防止被复制盗用。
业务流程
Scheme唤起小程序
小程序获取NFC实例、开启监听(仅支持安卓)
监听到NFC实例,读取卡片信息
监听到NFC实例,读取卡片内容
处理业务逻辑
本文主要围绕1234开展。
Scheme唤起小程序
Scheme唤起小程序,是微信官方支持的一个场景。NFC封签只要写入了Scheme,就能唤起。(写入不难,微信里搜一下NFC,就有很多小程序可以用)
主要是要先生成Scheme,需要小程序申请生成Scheme接口。申请到了,用相应接口生成就好。简单贴一下需要的传参。
js
体验AI代码助手
代码解读
复制代码
//请求地址
//POST https://api.weixin.qq.com/wxa/generatenfcscheme?access_token=ACCESS_TOKEN
{
"jump_wxa": {
"path": "非必填, 请输入字符串, 例如'a'",
"query": "非必填, 请输入字符串, 例如'a'",
"env_version": "非必填, 请输入字符串, 例如'a'"
},
"model_id": "必填, 请输入字符串, 例如'a'",
"sn": "非必填, 请输入字符串, 例如'a'"
}
小程序获取NFC实例、开启监听
js
体验AI代码助手
代码解读
复制代码
onLoad(options) {
this.initialNFC()
}
initialNFC() {
// 获取NFC实例
const nfc = wx.getNFCAdapter()
this.setData({ nfcAdapter: nfc})
const _this = this
nfc.startDiscovery({
success(res) {
console.log('NFC读取功能已开启')
nfc.onDiscovered(_this.discoverHandler)
},
fail(err) {
if (!err.errCode) {
return console.log('微信(IOS)不支持NFC识别,请使用微信(安卓)')
}
}
})
},
// 监听方法,监听到NFC实例,读取卡片信息,res里就是卡片信息,不过需要转换一下
discoverHandler(res) {
const { nfcAdapter:adapter } = this.data
const str = this.buf2hex(res.id)
if (str) {
// 这里拿到UID,可以做一点业务逻辑了
}
},
// ArrayBuffer转16进制
buf2hex(arrayBuffer) {
return Array.prototype.map.call(new Uint8Array(arrayBuffer), x => ('00' + x.toString(16)).slice(-2)).join('');
},
读取卡片内容
有卡片信息(物理载体)还不够,还需要卡片中的内容(载体里写入的数据)。
(之所以走到这一步,是因为Scheme唤起小程序,会在onLoad(options)方法中的 options里可以拿到写在Scheme上的参数,在小程序初次加载时被动获取的)
一旦监听开启,用户刷一下NFC卡,能拿到的是卡片信息。我们业务逻辑中,需要用到 写在Scheme上的参数 和 卡片id。
也不能告诉用户,麻烦你关掉小程序再打开,就能正确展示页面内容了。
在这里隆重地吐槽一下,安卓和微信,这两个残疾人,一个能用一半,要是能拼起来我就没那么多事情了。
安卓:可以读取NFC,但是Scheme一旦唤起页面,若手机保持在小程序页面,即使读取到Scheme,也不会再唤起小程序
IOS:不可以读取NFC,但是每次检测到Scheme都会垂询用户,是否要重新唤起小程序
经试验,无法读取卡片内容,下面是翻车现场
在回调函数中,根据返回的res.techs字段匹配到卡片支持的NFC标准 见下图
支持的技术标准是"NFC-A"、"MIFARE Ultralight"、"NDEF"。
点击查看文章→NfcA/NfcB/IsoDep/Ndef/Mifare 都是些什么玩意?
微信官方文档说明约等于没说,也没有示例代码。
adapter中的方法 参数
js
体验AI代码助手
代码解读
复制代码
getIsoDep: ƒ ()
getMifareClassic: ƒ ()
getMifareUltralight: ƒ ()
getNdef: ƒ ()
getNfcA: ƒ ()
getNfcB: ƒ ()
getNfcF: ƒ ()
getNfcV: ƒ ()
offDiscovered: ƒ ()
onDiscovered: ƒ ()
startDiscovery: ƒ ()
stopDiscovery: ƒ ()
tech: {ndef: "NDEF", nfcA: "NFC-A", nfcB: "NFC-B", isoDep: "ISO-DEP", nfcF: "NFC-F", …}
_nfcA: gme {techType: "NFC-A"}
尝试1 getMifareClassic,失败。
"MIFARE Classic",M1是加密卡,有密钥、分扇区,所以一定要问清楚写入卡数据的同学,让他提供扇区、第三方密钥。
js
体验AI代码助手
代码解读
复制代码
// 连接不成功,先放着
// 读取数据
readNFC(res) {
const { nfcAdapter:adapter } = this.data
// res.techs 只有三个"NFC-A"、"MIFARE Ultralight"、"NDEF"。
// 没有"MIFARE Classic",所以关闭这个判断
// if (res.techs.includes(adapter.tech.mifareClassic)) {
// }
console.log('发现' + adapter.tech.mifareClassic + '卡');
let mifareClassic = adapter.getMifareClassic();
debugger
mifareClassic.connect({
success: res => {
console.log("设备已连接", res)
console.log("开始拼接验密指令。。。");
debugger
// var arr = [0x60, 0x04, 0x11, 0x22, 0x33, 0x44, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF];
let shanqu = [0x60, 0x00] //查询扇,后边需要拼接上秘钥,
let newCardPsd = new Uint8Array(cardPsd) //转换命令格式
let arr = [...shanqu,...newCardPsd] //把查询扇区的初始跟查询ID拼接到一起
var arrayBuffer = new Uint8Array(arr).buffer
console.log("解密指令为:", arrayBuffer);
mifareClassic.transceive({
data: arrayBuffer,
success: function (res) {
console.log('发送数据并解密成功, 接收数据如下:', res);
},
fail: function (err) {
console.log('发送数据失败A', err);
wx.showToast({
title: 'nfc失败!',
icon:'error'
})
}
})
},
fail: function (err) {
debugger
console.log('设备连接失败', err);
}
})
mifareClassic.isConnected({
success: function (isConnected) {
debugger
console.log('成功连接', isConnected);
var arr01 = [0x30, 0x04];
var arrayBuffer01 = new Uint8Array(arr01).buffer
console.log('arrayBuffer02',arrayBuffer01);
var strList = {
orgId: '',
orgUserId: ''
}
mifareClassic.transceive({
data: arrayBuffer01,
success: function (res) {
console.log('读取数据:', res);
wx.showLoading({
title: '识别中...',
mask: true,
success: (res) => {},
fail: (res) => {},
complete: (res) => {},
})
const arrayBuffer = res.data // 获取通讯数据,类型为ArrayBuffer
const data16 = that.buf2hex(arrayBuffer) // ArrayBuffer转16进制
const requestData = that.hexToStr(data16) // 16进制转字符串
let arr = requestData.split('')
let str = ''
arr.forEach((item,index)=>{
console.log(item,item.length);
if(item>=0){
str += item
}
})
strList.orgId = str
console.log('requestData',str,'arr',arr)
},
fail: function (err) {
console.log('失败', err);
wx.showToast({
title: 'nfc失败!',
icon:'error'
})
}
})
var arr02 = [0x30, 0x05];
var arrayBuffer02 = new Uint8Array(arr02).buffer
console.log('arrayBuffer02',arrayBuffer02);
mifareClassic.transceive({
data: arrayBuffer02,
success: function (res) {
debugger
console.log('读取数据:', res);
wx.showLoading({
title: '识别中...',
mask: true,
success: (res) => {},
fail: (res) => {},
complete: (res) => {},
})
const arrayBuffer = res.data // 获取通讯数据,类型为ArrayBuffer
const data16 = that.buf2hex(arrayBuffer) // ArrayBuffer转16进制
const requestData = that.hexToStr(data16) // 16进制转字符串
let arr = requestData.split('')
let str = ''
arr.forEach((item,index)=>{
console.log(item,item.length);
if(item>=0){
str += item
}
})
strList.orgUserId = str
console.log('requestData2',strList)
that.getNfcCardList(strList)
},
fail: function (err) {
console.log('失败', err);
wx.showToast({
title: 'nfc失败!',
icon:'error'
})
}
})
}
});
},
方式一报错,这个报错是对的,因为咱们确实不支持M1。
尝试2 getNdef
失败方式二
js
体验AI代码助手
代码解读
复制代码
// 读取数据
readNFC(res) {
const { nfcAdapter:adapter } = this.data
console.log('res.techs', res.techs)
// 获取 NDEF 类型的 NFC 适配器
const ndefAdapter = adapter.getNdef()
// 连接到 NFC 标签
ndefAdapter.connect({
success() {
console.log('已连接到 NFC 标签');
// 在这里可以进行与 NFC 标签的通信操作
// 例如读取 NDEF 记录、写入 NDEF 记录等
// 断开与 NFC 标签的连接
ndefAdapter.close({
success() {
console.log('已断开与 NFC 标签的连接');
},
fail(err) {
console.error('断开与 NFC 标签的连接失败:', err);
}
});
},
fail(err) {
console.error('连接到 NFC 标签失败:', err);
}
});
},
方式二报错
尝试3 getNfcA
NFC-A 类型的 NFC 适配器
js
体验AI代码助手
代码解读
复制代码
// 读取数据
readNFC(res) {
const { nfcAdapter:adapter } = this.data
console.log('res.techs', res.techs)
debugger
// 获取 NFC-A 类型的 NFC 适配器
const nfcAAdapter = adapter.getNfcA()
// 连接到 NFC 标签
nfcAAdapter.connect({
success() {
console.log('已连接到 NFC 标签');
// 在这里可以进行与 NFC 标签的通信操作
// 例如发送读取命令,获取数据等
// 断开与 NFC 标签的连接
nfcAAdapter.close({
success() {
console.log('已断开与 NFC 标签的连接');
},
fail(err) {
console.error('断开与 NFC 标签的连接失败:', err);
}
});
},
fail(err) {
console.error('连接到 NFC 标签失败:', err);
}
});
},
连接到 NFC 标签失败: {errMsg: "connect:fail:system internal error: NFC service died", errCode: 13017, errno: 1504210}
尝试4 gtIsoDep
js
体验AI代码助手
代码解读
复制代码
const isoDepAdapter = adapter.getIsoDep();
// 连接到 NFC 标签
isoDepAdapter.connect({
success() {
console.log('已连接到 NFC 标签');
// 在这里可以进行与 NFC 标签的通信操作
// 例如发送 APDU 指令,读取或写入数据等
// 断开与 NFC 标签的连接
isoDepAdapter.close({
success() {
console.log('已断开与 NFC 标签的连接');
},
fail(err) {
console.error('断开与 NFC 标签的连接失败:', err);
}
});
},
fail(err) {
console.error('连接到 NFC 标签失败:', err);
}
});
尝试5 getMifareUltralight
js
体验AI代码助手
代码解读
复制代码
const mifareUltralightAdapter = adapter.getMifareUltralight()
// 连接到 NFC 标签
mifareUltralightAdapter.connect({
success() {
console.log('已连接到 NFC 标签');
// 在这里可以进行与 NFC 标签的通信操作
// 例如读取 NDEF 记录、写入 NDEF 记录等
// 断开与 NFC 标签的连接
mifareUltralightAdapter.close({
success() {
console.log('已断开与 NFC 标签的连接');
},
fail(err) {
console.error('断开与 NFC 标签的连接失败:', err);
}
});
},
fail(err) {
console.error('连接到 NFC 标签失败:', err);
}
});
参考文档:
参考博客一
参考博客二
待参考博客三