Skip to content

Mercure 实时推送

Doggy 基于 Mercure 协议实现服务端实时推送。Mercure Hub 运行在独立 Docker 容器中(dunglas/mercure),应用通过 Caddyfile 配置集成。

架构

客户端 (EventSource)
    ↑ SSE
    └── http://localhost:8000/.well-known/mercure

    Caddyfile (FrankenPHP)
        ↑ 代理 Hub 请求
    docker:mercure (dunglas/mercure)

Docker 部署

yaml
# docker-compose.yml
mercure:
  image: dunglas/mercure
  environment:
    MERCURE_PUBLISHER_JWT_KEY: '!ChangeThisMercureHubJWTSecretKey!'
    MERCURE_SUBSCRIBER_JWT_KEY: '!ChangeThisMercureHubJWTSecretKey!'
    MERCURE_EXTRA_DIRECTIVES: cors_origins http://127.0.0.1:8000
  command: /usr/bin/caddy run --config /etc/caddy/dev.Caddyfile

Caddyfile 配置

FrankenPHP 的 Caddyfile 中嵌入 Mercure Hub:

caddy
http://localhost:8000 {
    route /.well-known/mercure {
        mercure {
            transport bolt {
                path /var/www/var/mercure.db
            }
            publisher_jwt !ChangeThisMercureHubJWTSecretKey!
            subscriber_jwt !ChangeThisMercureHubJWTSecretKey!
            cors_origins http://localhost:8000
            publish_origins *
            anonymous
        }
    }
}

JWT 令牌

MercureTokenFactory 生成带订阅权限的 JWT:

php
// src/Security/MercureTokenFactory.php
$token = $factory->createSubscribeToken(
    topics: ['/user/123/*', '/entity/*'],
    roles: ['ROLE_USER'],
    expiresIn: 3600
);

MercureCookieSubscriber 在每个响应中注入 mercureAuthorization Cookie:

php
// 自动生成的订阅主题
$topics = [
    'https://enterprise.local/user/' . $userId . '/export',
    '/user/' . $userId . '/*',
    '/entity/*',
];

前端订阅

javascript
const url = new URL('/.well-known/mercure', window.location.origin);
url.searchParams.append('topic', '/user/' + userId + '/*');
const eventSource = new EventSource(url);
eventSource.onmessage = (event) => {
    const data = JSON.parse(event.data);
    // 处理推送消息
};

核心应用

  • 导出进度:大文件导出实时进度推送
  • 在线状态PresenceService + PresenceApiController
  • 实时通知:系统消息、审批提醒
  • 数据同步:多用户协作实时更新

基于 MIT 协议开源 | Copyright © 2026 Doggy