在Web应用里实现消息推送,很多人会直接调用Push API接收服务端下发的内容,却忽略了客户端持久化这一环。当推送到达浏览器时,如果仅仅保存在内存或者临时变量中,一旦用户关闭标签页、刷新页面,甚至Service Worker被浏览器回收,这些消息就彻底消失了。把SQLite引入到推送处理流程中,可以让每一条到达的推送都落盘存储,后续无论页面如何重载都能稳定读取。

为什么需要SQLite来配合Push API
Push API本身只负责把消息从推送服务投递到浏览器的Service Worker,它并不提供任何本地数据库能力。Service Worker虽然可以暂存少量数据到Cache Storage,但那是为静态资源设计的,用来存结构化消息既不便查询,也难以维护状态。SQLite作为嵌入式关系数据库,不需要独立进程,直接运行在客户端线程或Service Worker可用的上下文中,用熟悉的SQL语法就能完成增删改查。
另外一个现实问题是,推送消息往往伴随已读、未读、分类、时间戳等字段。如果只用键值对存储,随着消息增多,统计未读数或按时间区间拉取都会变得非常麻烦。SQLite支持建表、索引和事务,能够保证写入的原子性,避免出现只存了一半数据的情况。对于离线优先的应用来说,这种可靠性是内存方案给不了的。
基础表结构设计
在客户端初始化时,我们应该先创建一张消息表,把推送里的重要内容拆成列来存储。下面这段建表语句可以在页面加载或Service Worker安装阶段执行,使用sql.js或者原生SQLite WASM都适用。
CREATE TABLE IF NOT EXISTS push_messages ( id INTEGER PRIMARY KEY AUTOINCREMENT, payload_text TEXT NOT NULL, send_time INTEGER NOT NULL, is_read INTEGER DEFAULT 0, category TEXT DEFAULT 'default' ); CREATE INDEX IF NOT EXISTS idx_unread ON push_messages(is_read);
上面的表把消息正文、发送时间、是否已读和分类都单独成列,并为未读状态建了索引。这样在统计红点数量时,直接查WHERE is_read = 0就能利用索引快速返回,不需要遍历全表。实际项目中还可以加上用户标识列,支持多账号切换场景下的数据隔离。
在Service Worker中接收并写入
Push API的入口是Service Worker的push事件,我们在这里拿到推送内容后,应当先打开SQLite数据库,再执行插入。下面用伪代码展示核心逻辑,真实环境可替换为具体的SQLite驱动调用。
self.addEventListener('push', function(event) {
const data = event.data ? event.data.text() : 'no payload';
const now = Date.now();
// 假设db是已经初始化好的SQLite连接
db.run(
'INSERT INTO push_messages (payload_text, send_time, is_read) VALUES (?, ?, 0)',
[data, now]
);
event.waitUntil(
self.registration.showNotification('新消息', { body: data })
);
});
这段代码在收到推送后,先把内容写进SQLite,再弹出系统通知。由于SQLite写入是同步事务,只要插入成功,这条消息就永远不会因页面刷新而丢失。如果写入失败,我们可以把原始内容暂存到备用队列,等数据库恢复后再补写,从而保证不丢消息。
需要注意的是,部分浏览器环境里Service Worker不能直接跑某些SQLite实现,此时可以把写入动作通过postMessage发给主页面,由主页面里的SQLite实例完成落盘,然后再回传结果。这种跨上下文协作虽然多一步通信,但能兼容更多运行环境。
读取与状态更新
当客户端的消息中心页面打开时,直接从SQLite查询即可。下面示例展示如何拉取最近二十条未读消息,并把它们标记为已读。
function loadUnread() {
const rows = db.exec(
'SELECT id, payload_text, send_time FROM push_messages WHERE is_read = 0 ORDER BY send_time DESC LIMIT 20'
);
// 假设rows[0].values是结果数组
const ids = rows[0].values.map(function(r) { return r[0]; });
if (ids.length > 0) {
db.run(
'UPDATE push_messages SET is_read = 1 WHERE id IN (' + ids.join(',') + ')'
);
}
return rows[0].values;
}
通过这种方式,用户每次进入应用都能看到历史推送,而不会重复提示。相比单纯依赖Push API的内存回调,这种方案在用户体验上完整很多。我们也可以定时清理三十天前的已读消息,避免数据库文件无限膨胀。
方案对比与注意事项
如果只使用Push API加Cache Storage,开发量小但查询弱;引入SQLite后,工程复杂度略有上升,却换来结构化能力与可靠性。对于金融通知、工单提醒这类不能丢消息的场景,SQLite几乎是必选项。下表简单对比两种思路。
| 维度 | 仅Push API | Push API加SQLite |
|---|---|---|
| 消息持久化 | 否 | 是 |
| 复杂查询 | 困难 | 方便 |
| 离线可靠性 | 低 | 高 |
落地时还要注意,SQLite数据库文件应当放在浏览器提供的持久化目录中,避免使用会随会话清理的临时空间。另外,推送量大时建议批量插入,减少事务开销。只要把这些细节处理好,SQLite与Push API的组合就能支撑起一套稳健的离线消息系统。
SQLitePush_APIoffline_message修改时间:2026-08-12 02:06:27