vaadin 和 spring 引导中的最佳实践推送通知

Best practice push notifications in vaadin and spring boot

我正在开发一个使用 vaadin 作为前端并使用 spring-boot 作为后端的项目。在这个应用程序中,我有一个特定的用例,其中一组用户(操作员)在不同的站点工作,在那里他们可以生成应该通知另一组不同的用户(主管)的事件 - 但仅限于他们被分配到的站点。 通常没什么大不了的,但我想在有新事件发生时使用网络推送通知来通知主管。

我开始阅读一些关于 Notification API 的内容,并设法在 vaadin 中创建一个视图来检查浏览器是否被授予发送通知的权限(在我的 windows 10 示例中)和如果尚未授予权限,则显示一个按钮来请求它。如果授予了权限,按钮将被隐藏,并且标签确认已授予权限。同时发送测试通知(让我测试它是否有效)。 到目前为止一切正常。

我使用 java/vaadin 和本机 java 脚本的混合实现了所有内容

window.sendNotification = function(element, body, icon, title, type, id) {
    var options = {
        body: body,
        icon: icon
    }
    var notification = new Notification(title, options);
    notification.onclick = (e) => element.$server.notificationClicked(type, id);
}


window.checkNotificationPermission = function() {
    return Notification.permission;
}

window.askNotificationPermission = function(element) {
    function handlePermission(permission) {
        element.$server.permissionAsked(permission);
    }

    // Let's check if the browser supports notifications
    if (!('Notification' in window)) {
        console.log("This browser does not support notifications.");
    } else {
        if (checkNotificationPromise()) {
            Notification.requestPermission()
                .then((permission) => {
                    handlePermission(permission);
                })
        } else {
            Notification.requestPermission(function(permission) {
                handlePermission(permission);
            });
        }
    }
}
function checkNotificationPromise() {
    // safari supported permission or all other browsers?
    try {
        Notification.requestPermission().then();
    } catch (e) {
        return false;
    }

    return true;
}

这是 js 端,在 java/vaadin 中,我使用 @PWA(使用生成的 sw.js 和清单)、@Push 和 @JsModule 注释视图以包含 js 代码。另外,我使用 page.executeJs() 调用每个 js 函数,例如:

    Page page = UI.getCurrent().getPage();
    page.executeJs("askNotificationPermission([=12=])", this.getElement());

我还提供回调方法来处理何时授予权限或何时单击通知。例如:

@ClientCallable
private void permissionAsked(String permission) {
    sendSuccessNotification();
    permissionChecked(permission);
}

到这里为止一切正常。

现在我最大的问题是如何自动通知每个用户(他们中的一些人甚至可能在不同的机器上登录,例如 PC 和智能手机)。我想在视图上放置另一个线程 运行 并让它轮询后端以获取新通知:

像这样:

@Override
protected void onAttach(AttachEvent attachEvent) {
    // Start the data feed thread
    thread = new NotificationThread();
    thread.start();
}

@Override
protected void onDetach(DetachEvent detachEvent) {
    // Cleanup
    thread.interrupt();
    thread = null;
}

private class NotificationThread extends Thread {

    @Override
    public void run() {
        try {
            while (true) {
                Thread.sleep(10000); // update every 10 seconds

                List<ResponseNotification> findNewNotifications = notificationController.findNewNotifications();
                if (findNewNotifications.size() > 1) {
                    sendGeneralNotification("You have new notifications", "New Notifications!", NavigationTarget.NOTIFICATIONS, "");
                } else if (findNewNotifications.size() == 1) {
                    sendGeneralNotification("An operator requires your attention", "Operator", NavigationTarget.OPERATOR, notification.operator.id);
                }
            }
        } catch (InterruptedException e) {
            // ...
            // handle 
        }
    }
}

此方法的最大问题是身份验证在后端不再可用,因为它是从另一个线程调用的,因此 SecurityContextHolder.getContext().getAuthentication() 将 return 为空。 但是我也不确定一般的方法是否合理,因为视图总是需要打开,而且我觉得每 10 秒轮询一次可能会给后端带来一些不必要的负载,一旦有不止一堆主管。

事件存储在我的数据库中,除了通知之外我还想发送一封电子邮件,所以如果有一种方法可以在事件发生时从后端发送通知并推送给客户端,那就太完美了无论如何处理。 也许有人已经有经验可以帮助我找到解决这个问题的方法。

提前致谢!

如果您需要在应用程序未打开时通知用户,您需要查看 Web Push. Unfortunately this requires a custom (but similar) solution on Safari, and is not available on iOS, which is the main reason Vaadin does not have a built-in solution yet. Marcus Hellberg made a prototype 几年前可能会提供一些提示。

要通知用户当前打开了应用程序,请考虑这是否适用于您的情况: 与其让每个用户每 10 秒轮询一次相关事件,不如考虑“订阅”有关用户感兴趣的每个电台的事件,然后在事件发生时向所有订阅者发送一个“事件”。用户只会在登录后保持订阅状态,因此发布线程不需要跟踪权限。

这需要你为 pub/sub 机制使用某种事件总线,为此你可以看看 Collaboration Engine - 主要是因为它与 Vaadin 集成,并带来了其他协作功能您可能会发现有用(可能是事件日志,或者每个站点的聊天记录,以便每个操作员可以添加一些额外的详细信息)。当您使用 high-level API 和组件时它最强大,但您可以使用 low-level API 做很多漂亮的事情。

您可以通过多种方式进行设置,例如创建一个名为“通知”的主题,使用 CollaborativeMap 共享每个站点的更新。 (或者您可以根据需要为每个站创建一个单独的主题。)

每个用户的类似内容:

CollaborationEngine.getInstance().openTopicConnection(this, "notifications", localUser, topic -> {
            CollaborationMap stations = topic.getNamedMap("stations");
            return stations.subscribe(event -> {
                if (MY_STATIONS.contains(event.getKey())) {
                    Notification notification = new Notification(event.getValue(String.class));
                    notification.setDuration(5000);
                    notification.open();
                }
            });
        });

然后当有站点更新时,同理获取stationMap,只是更新一下:

stationMap.put(STATION_ID,  STATION_ID + “: An update at " + new Date());

这当然与 pseudocode-brief 接壤,因为这可能不是您要查找的内容,但代码确实生成了一个工作示例: