You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.04.30 14:27:29