Ejabberd新手咨询:如何将离线消息推送至Node.js服务器并触发FCM安卓推送通知
嘿,刚理清你的需求,其实实现这个离线消息→Node.js→FCM推送的流程并不难,咱们一步步来拆解:
核心思路梳理
Ejabberd检测到用户离线时,会触发离线消息事件;咱们要做的就是让Ejabberd把这个事件的消息数据发送到你的Node.js服务,然后Node服务从MySQL里取出对应用户的FCM Token,最后调用FCM API完成推送。
步骤1:配置Ejabberd,让它把离线消息通知给Node服务
你提到了mod_push.erl,其实不用完全从零写模块,Ejabberd自带的mod_push或者扩展mod_offline的钩子就能搞定。
方案A:用自带的mod_push配置(最简单)
修改你的ejabberd.yml配置文件,启用mod_push并指向你的Node服务地址:
modules: # 确保mod_offline是启用的(默认应该开着) mod_offline: access_max_user_messages: max_user_offline_messages # 配置mod_push到你的Node服务 mod_push: push_service: custom_nodejs_push: url: "http://你的Node服务器IP:端口/api/ejabberd-offline" secret: "your-secret-key-here" # 用来验证请求合法性,自己设个密钥 headers: Content-Type: "application/json" # 可选:只推送特定类型的消息,比如只有带body的聊天消息 body_only: true
保存配置后重启Ejabberd,这样当有离线消息时,Ejabberd就会自动把消息数据POST到你配置的URL。
方案B:自定义离线消息钩子(更灵活)
如果需要对消息做更复杂的处理(比如过滤、修改内容),可以写一个简单的Erlang模块挂到offline_message_hook上:
-module(mod_custom_offline). -behaviour(gen_mod). -export([start/2, stop/1, on_offline_message/3]). start(Host, _Opts) -> % 注册离线消息钩子 ejabberd_hooks:add(offline_message_hook, Host, ?MODULE, on_offline_message, 50), ok. stop(Host) -> ejabberd_hooks:delete(offline_message_hook, Host, ?MODULE, on_offline_message, 50), ok. on_offline_message(From, To, Packet) -> % 解析消息里的body内容 BodyTag = xml:get_subtag(Packet, <<"body">>), Message = case BodyTag of undefined -> <<"新消息">>; _ -> xml:get_tag_cdata(BodyTag) end, % 构造要发送给Node服务的JSON数据 PostData = jsx:encode(#{ from_jid => binary_to_list(From#jid.luser) ++ "@" ++ binary_to_list(From#jid.lserver), to_jid => binary_to_list(To#jid.luser) ++ "@" ++ binary_to_list(To#jid.lserver), message_content => binary_to_list(Message) }), % 发送POST请求到Node服务 httpc:request(post, {"http://你的Node服务器IP:端口/api/ejabberd-offline", [{"X-Secret", "your-secret-key-here"}], "application/json", PostData}, [], []), ok.
把这个模块编译成.beam文件,放到Ejabberd的ebin目录,然后在ejabberd.yml里启用它:
modules: mod_custom_offline: {}
步骤2:Node.js服务接收Ejabberd的通知
用Express写一个简单的接口,接收Ejabberd发来的POST请求,先验证合法性,再处理消息:
const express = require('express'); const bodyParser = require('body-parser'); const app = express(); const VALID_SECRET = "your-secret-key-here"; // 和Ejabberd配置里的一致 app.use(bodyParser.json()); // 接收Ejabberd离线消息的接口 app.post('/api/ejabberd-offline', async (req, res) => { // 先验证请求是否合法 const requestSecret = req.headers['x-secret'] || req.headers['secret']; if (!requestSecret || requestSecret !== VALID_SECRET) { return res.status(403).send('Unauthorized'); } // 解析Ejabberd发来的消息数据 const { from_jid, to_jid, message_content } = req.body; console.log(`收到离线消息: 发件人${from_jid} → 收件人${to_jid},内容: ${message_content}`); try { // 下一步:从MySQL获取收件人的FCM Token const fcmToken = await getFCMTokenByJid(to_jid); if (!fcmToken) { return res.status(404).send('该用户未绑定设备Token'); } // 触发FCM推送 await sendFCMNotification(fcmToken, from_jid, message_content); res.status(200).send('推送请求已处理'); } catch (err) { console.error('处理推送失败:', err); res.status(500).send('服务器内部错误'); } }); app.listen(3000, () => { console.log('Node.js推送服务已启动在端口3000'); });
步骤3:从MySQL取Token并调用FCM推送
先安装依赖:npm install mysql2 firebase-admin,然后实现数据库查询和FCM推送逻辑:
const mysql = require('mysql2/promise'); const admin = require('firebase-admin'); // 初始化Firebase Admin(用你的服务账号JSON文件) const serviceAccount = require('./path/to/your-firebase-service-account.json'); admin.initializeApp({ credential: admin.credential.cert(serviceAccount) }); // MySQL数据库配置 const dbConfig = { host: '你的MySQL地址', user: '数据库用户名', password: '数据库密码', database: '你的数据库名' }; // 根据JID查询用户的FCM Token async function getFCMTokenByJid(jid) { const connection = await mysql.createConnection(dbConfig); try { // 假设你的表是`user_devices`,字段是`jid`和`fcm_token` const [rows] = await connection.execute( 'SELECT fcm_token FROM user_devices WHERE jid = ? LIMIT 1', [jid] ); return rows.length > 0 ? rows[0].fcm_token : null; } finally { await connection.end(); } } // 发送FCM通知 async function sendFCMNotification(token, senderJid, message) { const payload = { notification: { title: `来自${senderJid}的消息`, body: message, click_action: 'FLUTTER_NOTIFICATION_CLICK' // 根据你的客户端调整,比如安卓的Activity }, token: token }; const response = await admin.messaging().send(payload); console.log(`FCM推送成功,消息ID: ${response}`); return response; }
常见问题排查
- Ejabberd无法访问Node服务:检查Node服务器的防火墙、端口是否开放,确保Ejabberd所在机器能ping通Node服务器,或者用
curl测试POST请求是否能到达。 - 钩子不生效:如果用自定义Erlang模块,要确保编译正确,并且Ejabberd日志里没有报错。可以在
ejabberd.yml里开启debug日志,查看钩子触发情况。 - FCM推送失败:检查Firebase服务账号的权限是否正确,设备Token是否有效(用户卸载APP后Token会失效,要定期清理无效Token)。
- MySQL查不到Token:确保用户登录时已经把设备Token正确存储到数据库,并且JID和Ejabberd里的格式一致(比如
user@domain)。
如果中间遇到具体的细节问题,比如Erlang模块编译报错、FCM返回特定错误码,随时再细化提问就行~
内容的提问来源于stack exchange,提问作者Abhay Rawat
相关产品推荐
相关产品推荐

