NFC
一个需求,要NFC封签唤起小程序,期望用小程序来做一些管理工作。又因为NFC的可复制性过于便捷,还要做加密处理,防止被复制盗用。
业务流程
- Scheme唤起小程序
- 小程序获取NFC实例、开启监听(仅支持安卓)
- 监听到NFC实例,读取卡片信息
- 监听到NFC实例,读取卡片内容
- 处理业务逻辑
本文主要围绕1234开展。
Scheme唤起小程序
Scheme唤起小程序,是微信官方支持的一个场景。NFC封签只要写入了Scheme,就能唤起。(写入不难,微信里搜一下NFC,就有很多小程序可以用)
主要是要先生成Scheme,需要小程序申请生成Scheme接口。申请到了,用相应接口生成就好。简单贴一下需要的传参。
js
//请求地址
//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
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都会垂询用户,是否要重新唤起小程序
需要读取的NFC卡片类型是"MIFARE Classic"
。不是常规类型"NFC-A"
、"MIFARE Ultralight"
、"NDEF"
。
人生南北多歧路,微信官方文档说明约等于没说,也没有示例代码。
"MIFARE Classic"
,M1是加密卡,有密钥、分扇区,所以一定要问清楚写入卡数据的同学,让他提供扇区
、第三方密钥
。
js
// 连接不成功,先放着
// 读取数据
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'
})
}
})
}
});
},
参考文档: