立即注册 登录
彼岸网 返回首页

天香公主的个人空间 http://www.bian-wang.com/discuz/?10005 [收藏] [复制] [分享] [RSS] txgz999@yahoo.com

日志

如何向用户推送通知

热度 3已有 648 次阅读2017-6-24 01:46 PM |个人分类:Discuz| 文章, 如何, 用户

本文旨在讨论网友a1980提出的问题:
希望如果有新的文章,只要開啟瀏覽器,不論有沒有進到我們的網站,他都會在右下角通知讀者有新文章
http://www.75271.com/3375.html

网友介绍的文章用的是HTML5里的网络通知功能 (Web Notifications API),参见( 链接链接 )。 其实Discuz的代码里就使用过这个功能:在 template/default/common/footer.htm 里有下面这段代码,它的作用是当用户有新提醒或新消息时,在屏幕右下角就会出现一个通知窗口:


但是至少我从没在用Discuz造的网站里看到过这个窗口。其原因是实现这个功能的代码(在文件 static/js/html5notification.js 里)用的是早期由谷歌自创的网络通知API,语法和现在的国际标准有些不同,较新的服务器已经不支持它了。但是不难把这个文件用当前的标准改写,这样我们就能在较新的Firefox和Chrome浏览器里看到这个功能了(下载链接)。

但是这种做法只在用户接触站内的某个网页时才会触发,它没法在网站有需要时(如网站里发了一篇新文章),主动向用户发送通知。要达到那样的效果,需要另外两个HTML5里的功能:网络推送API(Push API),以及服务工人(Service Worker),参见( 链接链接 )。

要实现这个功能,对网站有些要求。首先网站必须使用HTTPS,这是Service Worker的要求。其次网站用的PHP版本至少得是 5.6 而且要支持gmp,这是我用的一个第三方类库 web-push-php的要求。同时用户的浏览器不能是IE,而需要是较新版本的Firefox,Chrome,Safari,或Edge (我的测试环境是 Firefox54和Chrome59),这是Push API和Service Worker的要求。因此这个功能在现阶段不能用来完全替代Discuz里传统的消息传递方式,但可以成为一个有益的补充。

要实现网站向用户推送通知,光靠网站服务器和用户浏览器的力量还不够,还需要一个第三方提供推送服务(push service)。这是因为网站和浏览器的联系是由浏览器向网站发请求而开始,又由网站发回复给浏览器而结束。网站无法主动和某个浏览器联系。所以网站向浏览器推送通知的方法是它把通知的内容发给由浏览器决定的提供push service的服务器,再由这个服务器将内容转给浏览器。每种浏览器都使用一个提供push service的服务器。浏览器在启动那刻起就和提供push service的服务器保持着联系,直到浏览器停止运行为止。所以提供push service的服务器可以随时送通知给浏览器,而不需要用户去打开发通知的那个网站的网页。如果提供push service的服务器收到要传送的通知时,要送达的那个浏览器没在运行或不在线上,它会将通知保存,等浏览器上线后和它联系时再送达通知。

Service Worker是在浏览器里运行的不同于UI线程(thread)的单独的线程。它由浏览器依据网站的需要产生,运行网站提供的代码,完成网站要做的事。它的最常见的用途是在浏览器离线状态时,将浏览器对网站的请求用本地资源来满足,从而让用户在离线时继续使用该网站。在网络推送里它起的作用是当浏览器收到推送通知后,它会发给和该网站对应的Service Worker,由Service Worker运行网站提供的代码来显示通知。

要实现推送,需要两部分的工作。第一部分是一次性的准备工作
1)要征得用户对显示通知窗口的同意。用户会看到一个征得同意的窗口
2)网站要准备一个service worker要做的事的js代码文件,并向浏览器注册一个service worker来做这些事
3)由该service worker向浏览器使用的push service订阅推送,这样网站才能把要推送的内容发给push service传递。网站需要提供一个代表它的应用服务器的 VAPID public key,push service会回给一个专用的url,称为endpoint,来供网站发给它要传送的通知
4)将订阅成功后得到的信息包括push service提供的 endpoint,还有浏览器提供的代表它的客户 public key和token,送到服务器去保存。当网站要发通知时,需要这些信息
第二部分是发送和接受通知
1)网站在要发通知时,找到所有要发通知的浏览器的订阅信息
2)给每个订阅信息提供的endpoint发经过加密的通知,发的格式由web push protocol决定的。这部分protocol比较复杂,所以我用了第三方提供的类库 web-push-php
3)浏览器收到push service传送的通知后,会交给和网站对应的service worker来处理
4)该service worker会运行网站提供的处理代码来显示通知窗口
要进一步了解web push的原理和流程,建议阅读Matthew Gaunt的书(链接) 和他的示范代码(链接)。上面说的两部分工作也可以用他书中的两副插图来说明:



下面介绍下我在Discuz网站里使用web push的尝试。目的不在于给Discuz提供一个完整的新的通知机制,而在于摸索和了解它的可行性。

界面的变化
1)用户在登录后,如果浏览器支持web push的话,在首页的左上方会出现一个"不收推送通知"链接,代表当前用户还没有同意接收通知。点击后这个链接会变成"接收推送通知",这时用户就可以收到网站推送的通知了。如果不想收到推送通知了,就再次点击这个链接。这个toggle过程类似于网站上已有的隐身/在线功能。
2)当浏览器收到通知后会在右下方的窗口里显示。作为测试,加了两类通知
a)当网站发布新文章时发通知给所有订阅通知的用户
b)当博主发新日志时,发通知给所有订阅通知的好友
用户点击通知窗口后就进入了该文章/日志页


数据库的修改
要添加一个新的数表来记载推送的订阅信息。如果在安装网站时给数表设置无前缀的话,可以用下述语句产生该数表:
CREATE TABLE `ext_push_subscription` ( `id` smallint(6) unsigned NOT NULL AUTO_INCREMENT, `uid` mediumint(8) unsigned NOT NULL DEFAULT '0', `subscription` mediumtext NOT NULL, `dateline` int(10) unsigned NOT NULL, PRIMARY KEY (`id`), KEY `uid` (`uid`) ) ENGINE=MyISAM DEFAULT CHARSET=utf8; 如果在安装网站时给数表设置了前缀的话,得给这个的新数表也加上同样的前缀。比如如果数表名都以pre_作为前缀(如pre_common_admingroup),那么新数表的名字就得改成pre_ext_push_subscription。

代码的修改
主干部分是下面这些新文件和文件夹:
push-setup.js在浏览器里运行负责为浏览器订阅推送
push-service-worker.js记载service-worker要做的时,包括如何显示收到的通知
push-subscription.php接受浏览器递交的推送订阅信息
push-message.php在网站上负责发通知的函数
source/class/table/ table_ext_push_subscription.php负责管理保存推送订阅的数表
web-push-php网上下载的帮助将通知发给 push service 的类库 (链接)
push service 用 VAPID keys 来确认要向用户推送通知的网站就是让用户订阅推送的网站。每个网站应该使用自己的VAPID keys。访问网页 https://web-push-codelab.appspot.com 就能得到自己的VAPID keys。然后将它们替换文件pub-message.php里的 VAPID_PUBLIC_KEY 和 VAPID_PRIVATE_KEY,以及文件 push-setup.js 里的 VAPID_PUBLIC_KEY

其次需要修改现有的文件
1。在语言包里添加要用的汉字字符串 修改文件 source/language/lang_template.php,在其中加入 'allow_push' => '接收推送通知', 'forbid_push' => '不收推送通知', 'newarticle_push_title' => '快去看看新到的文章吧', 'newblog_push_title' => '您的好友{author}刚发了新日志,快去看看吧',
2。修改界面使得用户可以接收推送通知 修改文件 template/default/common/header.htm, 在下面这句话 <!--{hook/global_cpnav_extra1}--> 后加入 < a id="subsriptionswitch" style="cursor: pointer;" onclick='toggleSubscription(this, $_G[uid]);'></a> <script type="text/javascript"> var FORM_HASH = "{FORMHASH}"; var ALLOW_PUSH_LABEL = "{lang allow_push}"; var FORBID_PUSH_LABEL = "{lang forbid_push}"; </script> <script type="text/javascript" src="push-setup.js"></script> <script type="text/javascript"> setUpSubscriptionSwitch($("subsriptionswitch"), $_G[uid]); </script> 注意在上面代码里<和a间的空格要去掉

3。在网站有新文章时通知用户
修改文件 source/include/portalcp/portalcp_article.php, 在下面这句话 C::t('portal_article_count')->insert(array('aid'=>$aid, 'catid'=>$setarr['catid'], 'viewnum'=>1)); 后加入 include_once libfile('function/home'); $pic = $setarr['pic']; if ($pic) $pic = pic_get($pic, '', $setarr['thumb'], $setarr['remote'], 1, 1); include_once(DISCUZ_ROOT.'push-message.php'); sendPushMessage('new-article', lang('template', 'newarticle_push_title'), $setarr['title'], $summary, $pic, 'portal.php?mod=view&aid='.$aid, null);
4。当发新日志时,给日志作者的好友发通知
修改文件 source/function/function_blog.php, 在下面这句话 C::t('common_member_field_home')->update($_G['uid'], array('recentnote'=>$POST['subject'])); 后加入 $summary = blog_bbcode($message); $summary = cutstr(strip_tags($summary), 140); require_once libfile('function/friend'); $frienduids = array(); if ($blogarr['friend']==0 || $blogarr['friend']==1) { $friendarray = friend_list($_G['uid'], 100); if($friendarray && is_array($friendarray)) { foreach($friendarray as $friend) { $frienduids[] = $friend['fuid']; } } } else if ($blogarr['friend']==2 && !empty($POST['target_ids'])) { $frienduids = explode(',', $POST['target_ids']); } if (!empty($frienduids)) { include_once(DISCUZ_ROOT.'push-message.php'); sendPushMessage('new-blog', lang('template', 'newblog_push_title', array('author' => $blogarr['username'])), $blogarr['subject'], $summary, $picurl, 'home.php?mod=space&uid='.$blogarr['uid'].'&do=blog&id='.$blogid, $frienduids); } }
测试网站: https://ngcorner.com/dz32wp,用户 test1/123451, test2/123451, test3/123451。可以用一个ID发日志,再用另一个好友ID看通知

下载链接: http://www.bian-wang.com/discuz/data/userupload/10005/webpush_discuz.zip

发表评论 评论 (33 个评论)

回复 a1980 2017-9-29 01:13 AM
天香公主: 没关系,我最近也忙,下班后也累得不想动了。不过,我会记得等有空了写个适用于不登录网民的版本。
天香,真的很謝謝你,辛苦你了,請記得多休息,多照顧好身體,我們可不想失去你
回复 天香公主 2017-9-28 09:38 AM
a1980: 天香:不好意思,最近實在是太忙了,還沒有時間測試,我最近會找時間來安裝一個新的網站測試,天香真的很謝謝你!一直在大家背後默默的支持與協助,感謝你! ...
没关系,我最近也忙,下班后也累得不想动了。不过,我会记得等有空了写个适用于不登录网民的版本。
回复 a1980 2017-9-25 03:48 AM
天香公主: 不知你找到问题了吗?

如果你是在一个新装的测试网站上发现有这问题的话,可以将source和template文件夹的内容打包后寄给我,让我试试 ...
天香:不好意思,最近實在是太忙了,還沒有時間測試,我最近會找時間來安裝一個新的網站測試,天香真的很謝謝你!一直在大家背後默默的支持與協助,感謝你!
回复 天香公主 2017-9-10 09:42 PM
a1980: 天香:我是用discuz X3.2 UTF8,我的狀況是在發文時按傳送就會出問題,但其實有發出去,只能從後台看到文章,原本的文章,點開來都是空白的

我剛剛有進你的網站 ...
不知你找到问题了吗?

如果你是在一个新装的测试网站上发现有这问题的话,可以将source和template文件夹的内容打包后寄给我,让我试试
回复 a1980 2017-9-6 02:53 AM
天香公主: 不知你用的discuz版本?还有在哪个浏览器里有这问题?我原来测试的是discuz32网站,刚装了个discuz33网站,在FF里试了试发文章,也没发现任何问题。会不会与文章 ...
天香:我是用discuz X3.2 UTF8,我的狀況是在發文時按傳送就會出問題,但其實有發出去,只能從後台看到文章,原本的文章,點開來都是空白的

我剛剛有進你的網站測試,是正常的>.<
目前還在尋找問題...
回复 天香公主 2017-9-5 10:15 PM
a1980: 天香:我剛剛發現一件事情,想跟您求證一下,經過剛剛的測試,我發表一篇新文章時,發現有錯誤,原因來自portalcp_article.php的修改,不論是用天香給的檔案或是 ...
不知你用的discuz版本?还有在哪个浏览器里有这问题?我原来测试的是discuz32网站,刚装了个discuz33网站,在FF里试了试发文章,也没发现任何问题。会不会与文章的内容有关,还是任何简单的内容也出现问题?

看我的私信,你可以在我的网站上试试看能否发文章?
回复 a1980 2017-9-1 03:07 AM
天香公主: 哈,不必客气。现在我好象明白问题所在了,你是要在用户发主题贴时,向所有订阅用户发通知,那光用我这点代码是不行的,因为我只是作为示范处理了发文章和日志两 ...
天香:我剛剛發現一件事情,想跟您求證一下,經過剛剛的測試,我發表一篇新文章時,發現有錯誤,原因來自portalcp_article.php的修改,不論是用天香給的檔案或是我照天香的方式,修改自己的檔,就會造成發佈出問題,然後該文章會沒有畫面,其實已經發表出來,從discuz後台去點擊文章來看,發現有標題,但沒內文,如果是早期已經發表的文章,http下可看見標題無內容,https下,畫面是整個空白,然後不管在哪種方式下面,都沒有任何的通知,所以不知道之前是否有人也有遇到跟我一樣的問題呢?感謝天香
回复 a1980 2017-8-31 01:12 AM
天香公主: 哈,不必客气。现在我好象明白问题所在了,你是要在用户发主题贴时,向所有订阅用户发通知,那光用我这点代码是不行的,因为我只是作为示范处理了发文章和日志两 ...
感謝天香幫忙,謝謝!
真的是辛苦你了,感激不盡!
回复 天香公主 2017-8-30 05:02 AM
a1980: 天香,真的是萬般的感激你的幫忙,真的很謝謝你
我是用發新帖子來做測試,還是說我一開始就搞錯方向了呢,因為我是希望不論是發帖子還是文章或是新日誌,都會通 ...
哈,不必客气。现在我好象明白问题所在了,你是要在用户发主题贴时,向所有订阅用户发通知,那光用我这点代码是不行的,因为我只是作为示范处理了发文章和日志两种情形。但要达到你的要求也不难,无非就是在论坛帖子代码的合适的地方加上类似于文中3)和4)的那样一段代码。等过个星期我帮你写一下吧
回复 a1980 2017-8-29 09:12 PM
天香公主: 好,说明现在订阅通知部分没问题了。关于有两个uid=1的record的问题, 我看了订阅时间分别是2017/8/29 16:39:10和2017/8/29 16:43:04,也许是你用了两个电脑在试 ...
天香,真的是萬般的感激你的幫忙,真的很謝謝你
我是用發新帖子來做測試,還是說我一開始就搞錯方向了呢,因為我是希望不論是發帖子還是文章或是新日誌,都會通知。
網站網址如下:
https://www.audionet.com.tw/

謝謝天香^_^
回复 天香公主 2017-8-29 05:23 AM
a1980: 天香有資料了,但很怪,竟然有兩個UID是1
1就是我點的
http://i.imgur.com/PP03E8Y.jpg
而且還是沒有收到提醒...
好,说明现在订阅通知部分没问题了。关于有两个uid=1的record的问题, 我看了订阅时间分别是2017/8/29 16:39:10和2017/8/29 16:43:04,也许是你用了两个电脑在试?不管怎么说这不影响发送通知。所以我想接下来检查下为何没收到通知的问题。不知你测试的是发新文章还是发新日志?注意文章一般是管理员在添加了频道后才能发的,而日志的通知则只有该日志作者的好友才能收到通知。

忘了问你的测试网站是在网上的吧?电脑上本地的网站是没法发通知的。请把你的测试网站的url告诉我,我可以帮你测试。
回复 a1980 2017-8-29 04:47 AM
天香公主: 那现在 pre_ext_push_subscription 这个数表里还是空的?
天香有資料了,但很怪,竟然有兩個UID是1
1就是我點的
http://i.imgur.com/PP03E8Y.jpg
而且還是沒有收到提醒...
回复 天香公主 2017-8-25 06:51 AM
a1980: 謝謝天香,是類似的圖,但還是沒有收到訊息,好奇怪喔!
那现在 pre_ext_push_subscription 这个数表里还是空的?
回复 a1980 2017-8-24 11:03 PM
天香公主: 我猜就是这原因了。因为我的习惯是不加前缀,就忘了说明加前缀的情形。
如果加了前缀后问题依然存在,那就先查查客户端,在Firefox里当你点击"接收网站推送"时 ...
謝謝天香,是類似的圖,但還是沒有收到訊息,好奇怪喔!
回复 天香公主 2017-8-24 09:07 AM
a1980: 感謝天香,我已經將資料表改為pre_ext_push_subscription,我在測試看看是否能收到訊息,謝謝天香提醒
我猜就是这原因了。因为我的习惯是不加前缀,就忘了说明加前缀的情形。
如果加了前缀后问题依然存在,那就先查查客户端,在Firefox里当你点击"接收网站推送"时,是否在它的Developer Tools的Console Tab里看看是看到了类似下图里的内容还是看到了错误信息。
http://www.bian-wang.com/discuz/data/attachment/album/201708/24/085706wsdwhlwpfd0lwgkk.png
回复 a1980 2017-8-23 10:22 PM
天香公主:    你的数表名都有前缀吗?如果有的话,得给我的新数表也加上同样的前缀。比如如果你的数表名都以pre_作为前缀(如pre_common_admingroup),那我的新数表的 ...
感謝天香,我已經將資料表改為pre_ext_push_subscription,我在測試看看是否能收到訊息,謝謝天香提醒
回复 天香公主 2017-8-23 08:18 AM
a1980: 感謝天香回覆和幫忙
1.上次問過天香後,就是在處理將HTTP轉HTTPS,現在已經轉成HTTPS
2.看樣子應該是前者,如是前者請問天香該如何處理,感謝賜教,數據圖如下列 ...
   你的数表名都有前缀吗?如果有的话,得给我的新数表也加上同样的前缀。比如如果你的数表名都以pre_作为前缀(如pre_common_admingroup),那我的新数表的名字就得改成pre_ext_push_subscription
回复 a1980 2017-8-23 01:43 AM
天香公主: 可以支持游客。对用户注册的要求不是必须的,我加上它只是想显示下对不同的用户可以发不同的推送,这点在我介绍的那本书里没涉及。

要支持游客的话,把我文件里 ...
天香真的很用心,所以有考慮到不同的用戶可以發送不同的推送。
感謝天香的教學,我會再試看看!
回复 a1980 2017-8-23 01:41 AM
天香公主: 几点建议:
1)你的网站用https吧?
2)查查数表ext_push_subscription是否有数据来分辨是订阅时的问题还是发通知时的问题。没数据的话应该是前者,有的话是后者 ...
感謝天香回覆和幫忙
1.上次問過天香後,就是在處理將HTTP轉HTTPS,現在已經轉成HTTPS
2.看樣子應該是前者,如是前者請問天香該如何處理,感謝賜教,數據圖如下列網址
http://i.imgur.com/XKSR6FF.jpg
3.目前的話,我會再找一台機器來試看看,(謝謝天香)現在的話,我是將extra以外的文件和文件夾都COPY進去到根目錄,extra的文件則是照天香的教學修改完成了。目前找了好幾台電腦測試,都沒有收到通知
回复 天香公主 2017-8-22 11:15 PM
a1980: 天香,請問一下,我一樣照做了但一樣沒收到通知,另外想請問一下,如果是遊客也可以選擇接受通知嗎?我應該如何修改呢?感謝天香! ...
可以支持游客。对用户注册的要求不是必须的,我加上它只是想显示下对不同的用户可以发不同的推送,这点在我介绍的那本书里没涉及。

要支持游客的话,把我文件里几处检查uid的代码去掉即可:
1)文件push-setup.js 里 uid>0 的条件
2)文件push-subscription.php 里去掉 !empty($_G['uid']) 的条件
3)数表ext_push_subscription里对uid要求NOT NULL改成NULL
12下一页

facelist doodle 涂鸦板

您需要登录后才可以评论 登录 | 立即注册

小黑屋|Archiver|彼岸网  

Powered by Discuz! X3.1 © 2001-2014 Comsenz Inc.
GMT-4, 2017-11-25 12:06 AM , Processed in 0.067687 second(s), 21 queries.

返回顶部