在浏览器端直接处理NFC标签数据的场景中,SQLite提供了一种比键值存储更灵活的结构化方案。WebNFC接口允许网页通过NDEFReader对象读取和写入符合NDEF标准的标签,而SQLite可以以WASM形式运行在页面中,将扫描记录持久保存。这种组合特别适合仓储盘点、会议签到等需要离线作业且后续统一上传的系统。

WebNFC基础与标签数据模型
WebNFC目前主要在Chrome系浏览器中通过NDEFReader提供能力,它基于Near Field Communication论坛定义的NDEF消息格式。一个NDEF消息可包含多条记录,每条记录有类型、载荷与标识符。常见用法是读取标签中的文本或URL,并在网页中展示或处理。由于每次靠近标签才会触发读取,若不做本地存储,用户离开页面后历史数据无法追溯。
使用SQLite管理这类数据,第一步是设计合理的表结构。我们通常需要保存标签的唯一标识、读取时间、记录类型与解码后的内容。下面给出一种简单的建表语句,利用sql.js在内存或文件化数据库中执行。该结构兼顾了检索效率与扩展能力,后续可按tag_type做分类统计。
CREATE TABLE IF NOT EXISTS nfc_tags ( id INTEGER PRIMARY KEY AUTOINCREMENT, tag_id TEXT NOT NULL, tag_type TEXT NOT NULL, payload TEXT, scan_time DATETIME DEFAULT CURRENT_TIMESTAMP ); CREATE INDEX IF NOT EXISTS idx_tag_time ON nfc_tags(scan_time);
上述表中tag_id来自NDEF消息的序列号或自定义标识符,tag_type区分是文本、URI还是自定义MIME。实际项目中,如果标签频繁被同一设备读取,可以再加上设备编号字段以便多端协同。SQLite的索引机制让按时间区间查询变得轻松,这是纯localStorage难以实现的。
在页面中集成SQLite与读写标签
要在网页里跑SQLite,常用方案有sql.js和WebAssembly版官方SQLite。sql.js通过加载wasm文件提供同步API,适合小型库;若数据量大,可采用WA SQLite获得更接近原生的异步接口。初始化时,我们一般把数据库文件存放在IndexedDB,避免每次刷新重新建库。下面的代码展示如何用sql.js载入并插入一条WebNFC读取结果。
import initSqlJs from './sql-wasm.js';
async function setupDB() {
const SQL = await initSqlJs();
// 假设已从IndexedDB取得bytes,这里简化为新建
const db = new SQL.Database();
db.run('CREATE TABLE IF NOT EXISTS nfc_tags (id INTEGER PRIMARY KEY, tag_id TEXT, payload TEXT, scan_time TEXT)');
return db;
}
async function saveTag(db, tagId, payload) {
db.run('INSERT INTO nfc_tags (tag_id, payload, scan_time) VALUES (?, ?, datetime('now'))', [tagId, payload]);
}
读取WebNFC标签时,我们实例化NDEFReader并监听reading事件。在回调里把解码后的文本传给saveTag函数即可落库。需要注意的是,浏览器要求WebNFC必须在用户手势后激活,因此扫描按钮的点击处理中要调用reader.scan()。写入标签也类似,调用write方法前同样需用户确认。
const reader = new NDEFReader();
document.getElementById('scanBtn').addEventListener('click', async () => {
try {
await reader.scan();
reader.addEventListener('reading', event => {
const tag = event.tag;
const textRecord = tag.records.find(r => r.recordType === 'text');
const payload = textRecord ? new TextDecoder().decode(textRecord.data) : '';
saveTag(db, tag.id || 'unknown', payload);
});
} catch (err) {
console.error('扫描失败', err);
}
});
这种本地优先的写法让用户即便处于地下仓库等无网环境,也能连续盘点上百个标签而不丢数据。相比直接调接口,SQLite缓冲层显著降低服务端压力,也避免了网络抖动造成的记录丢失。后续只需定时把未同步标记的数据打包上传。
离线数据同步与冲突处理策略
当设备重新联网,就要把SQLite里累积的标签记录提交到中心服务器。简单做法是查询出所有本地新增行,通过fetch批量发送,成功后再打上同步标志。若服务端也允许移动端写标签,就要考虑冲突:同一标签被不同设备修改谁优先。常用方案是最后写入获胜,或按设备号加版本号合并。
下面示例用一条SQL查出自上次同步后的记录,并构造上传数组。我们在表中隐含一个synced字段(可ALTER TABLE添加),初始为0。上传完毕后执行UPDATE置1。这样即便中途失败,重启后仍能续传,不会出现重复提交漏网之鱼。
function getUnsynced(db) {
const res = db.exec('SELECT id, tag_id, payload, scan_time FROM nfc_tags WHERE synced = 0');
if (!res.length) return [];
return res[0].values.map(row => ({id: row[0], tagId: row[1], payload: row[2], time: row[3]}));
}
async function syncToServer(list) {
const resp = await fetch('https://ipipp.com/api/nfc/sync', {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify(list)
});
return resp.ok;
}
在冲突规避上,如果业务要求标签状态绝对一致,可改为每次写标签前先从服务端拉取最新值,再在SQLite中更新。但这样牺牲了离线自由度。多数盘点类应用接受短暂不一致,只需在同步时上报全部扫描流水,由后台按时间线还原即可。SQLite的事务特性保证本地插入和标记同步要么都成要么都败,避免半同步态。
整体来看,SQLite加WebNFC的组合用很少的代码量撑起了完整的离线采集闭环。开发者只要留意浏览器兼容性与用户手势限制,就能在巡检、票务等场景快速落地。后续还可将数据库文件导出为文件发给管理员,或用Worker跑SQLite以免阻塞UI,体验会进一步改善。