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

Ejabberd自定义模块优化:消息确认与离线推送逻辑问题

解决Ejabberd自定义模块中消息确认触发问题

你的问题核心在于当前仅在离线消息钩子中处理确认逻辑,而在线消息不会触发该钩子,导致只有接收方离线时才会返回确认。下面是单模块内的解决方案,同时保留离线推送功能:

1. 调整钩子注册

新版Ejabberd中,user_send_message钩子会在用户发送消息时触发(无论接收方在线与否),我们需要注册这个钩子来处理全局的消息确认,同时保留offline_message_hook处理离线推送:

start(_Host, _Opts) ->
    % 注册用户发送消息钩子,优先级50(合理范围,避免干扰其他模块)
    ejabberd_hooks:add(user_send_message, _Host, ?MODULE, handle_user_send_message, 50),
    % 保留原离线推送钩子
    ejabberd_hooks:add(offline_message_hook, _Host, ?MODULE, handle_offline_push, 50),
    ok.

2. 实现全局消息确认逻辑

在handle_user_send_message函数中,统一处理所有聊天消息的确认返回,确保无论接收方状态如何,发送方都能收到服务器确认:

handle_user_send_message(From, To, Packet) ->
    % 仅处理聊天类型消息,过滤其他类型(如组聊、系统通知)
    case xml:get_tag_attr_s(<<"type">>, Packet) of
        <<"chat">> ->
            % 构造符合Java客户端预期的确认Stanza
            ConfirmStanza = build_confirm_packet(From, To, OriginalPacket),
            % 发送确认消息给原发送方(注意路由方向:服务器 -> 发送方)
            ejabberd_router:route(To#jid.lserver, From, ConfirmStanza);
        _ ->
            ok
    end,
    % 必须返回原始消息,保证正常消息流程不受影响
    {From, To, Packet}.

% 构造客户端要求的独特确认结构,根据实际需求调整元素
build_confirm_packet(From, To, OriginalPacket) ->
    % 关联原消息ID,方便客户端匹配
    OriginalMsgId = xml:get_tag_attr_s(<<"id">>, OriginalPacket),
    ConfirmMsgId = <<OriginalMsgId/binary, "-server-ack">>,
    
    xml:element(<<"message">>,
        [{<<"from">>, To#jid.lserver},
         {<<"to">>, jid:to_binary(From)},
         {<<"type">>, <<"chat">>},
         {<<"id">>, ConfirmMsgId}],
        [xml:element(<<"body">>, [], [xml:cdata(<<"Message received by server">>)]),
         % 这里添加Java客户端需要的自定义元素,比如<server-ack status="received" />
         xml:element(<<"server-ack">>, [{<<"status">>, <<"received">>}], [])]
    ).

3. 保留离线推送逻辑

原有的离线推送功能正常,直接保留handle_offline_push函数即可:

handle_offline_push(From, To, Packet) ->
    case xml:get_tag_attr_s(<<"type">>, Packet) of
        <<"chat">> ->
            send_app_server_push(From, To, Packet);
        _ ->
            ok
    end.

send_app_server_push(From, To, Packet) ->
    % 这里实现向应用服务器发送推送的逻辑,比如HTTP请求
    PushContent = #{
        from_user => jid:to_binary(From),
        to_user => jid:to_binary(To),
        message_body => xml:get_tag_cdata(xml:get_subtag(Packet, <<"body">>))
    },
    % 调用HTTP客户端发送(需确保ejabberd开启httpc支持)
    httpc:request(post,
        {"https://your-app-server/push-endpoint", [], "application/json", jsx:encode(PushContent)},
        [], []),
    ok.

关键说明

  • 为什么用user_send_message:这个钩子覆盖所有用户主动发送的消息场景,无论接收方在线/离线,完美解决你之前仅离线触发的问题。
  • 钩子优先级:设置为50是常规选择,既保证我们的逻辑能执行,又不会抢占核心模块的处理顺序。
  • 消息流程:处理完确认后必须返回原始消息,否则会阻断正常的消息投递。

内容的提问来源于stack exchange,提问作者Benny Abramovici

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.16 16:50:17