この記事では、Node.jsとSocket.IOを使用してユーザーアクティビティを監視するシステムを実装する方法について説明します。 次のようになります。
![画像](https://habrastorage.org/getpro/habr/post_images/499/31b/2d4/49931b2d4d31370c6af0fcd76c327307.jpg)
これは、特に2人のオペレーターが1つの製品を編集する状況を回避するために、従業員の相互作用が重要なERPシステム用に作成されました。
挑戦する
そのため、Node.js(エクスプレス、接続)上のサーバーでは、この監視システムを実装する必要がありました。これは、ユーザーアクティビティをリアルタイムで通知します(つまり、ログアウトしたユーザーが「 オンラインユーザー 」リストからすぐに消えて、別のページ-「 このページを表示 」から消えて、それぞれ入力した人がリストに表示されます)。
システムを世界中から閉鎖しているため、認証が必須であるという予約をすぐに行います。
さて、始める前に-Socket.IOの基本にあまり詳しくない人のために、 典型的なチャットの実装を見てみることをお勧めします。
お客様
このビジネスのクライアント部分は非常に単純に実装されているため、外部の美しさに焦点を合わせません。 以下のコードがベーステンプレートに含まれていることだけに言及します。 ロードされたすべてのページに存在します。
必要なもののリストは次のとおりです。id=“ sockstat”(接続ステータスを表示する)、id =“ alsohere”(ページを表示しているユーザーのリスト)、id =“ online”(オンラインであるすべてのユーザーのリスト) )、もちろん、そのようなこと
<script type="text/javascript" src="/js/lib/jquery.js"></script> <script type="text/javascript" src="/socket.io/lib/socket.io.js"></script>
、
配列から箇条書き項目を作成する小さなヘルパー関数:
function lister(arr) { var s = ''; $.each(arr, function(key, value) { s += '<li><b>' + value.name + '</b> (login: ' + value.login + ', id: ' + value.user_id + ')</li>'; }); return s; }
これですべて準備が整いました。 メカニズムは次のとおりです。
$(document).ready(function() { var socket = io.connect("http://localhost:8080"); // socket.on("connect", function () { // , : $("#sockstat").text("connected ok"); // socket.emit("iamhere", { location: document.URL } ); // socket.on("alsohere", function (alsohere) { // , : $("#alsohere").html(' : <ul>' + lister(alsohere) + '</ul>'); }); socket.on("online", function (online) { // - $("#online").html(' : <ul>' + lister(online) + '</ul>'); }); socket.on("disconnect", function () { // - ( , ) setTimeout(function () { $("#sockstat").text("connection lost!"); }, 500); }); }); });
リンクをたどってページを離れると、サーバーとのソケット接続が最初に切断され、新しいページがレンダリングされてクライアントに提供され、接続が再開されます。 したがって、ページを離れるために、1秒間の無駄な中断に関するメッセージは表示されず、この遅延は0.5秒で行われました。
ちょっとしたトリックですが、ユーザーはいつでも自信を持って言うことができます:「中断はありませんでした!」サーバーが本当にクラッシュするまで。
簡単そう? そして、そうです。
サーバー:表面
起動されたプロジェクトファイルのコード内-index.js内、および次のようなあらゆる種類のもの
手書きのsockets.jsを接続します。これは、ビジネスに必要なソケットの操作を担当します。app.configure();
require('./sockets');
イベントの実際の処理に加えて、承認機能が含まれています。これは、特定の条件を満たす人に接続を作成する許可を与えます。 私たちの場合、これらは以前にログインした人です。
一般的には、 このように見えます( sio.set( 'authorization' ...)に注意を払ってください )。
そして今、アルゴリズム自体。 はじめに-ロシア語。
2つのシンボルがあります。
- online-オンラインのすべてのユーザーのリスト
- alsohere-クライアントと同じページのユーザーのリスト
アルゴリズムには2つの段階もあります-新しい接続の処理と、クライアントが切断したメッセージの処理。
それが私たち(サーバー)に接続するとすぐに、すなわち 認証に合格し、ページにアクセスして、彼の場所を報告します-
socket.emit("iamhere", { location: document.URL } );
顧客、私たちは:
- 誰がどこに行ったかを覚えておいてください(オンラインに追加してください)
- オンラインでアクティブに更新された全員を送信する
- 同じページを閲覧している全員に送信-こちらも更新
クライアントが切断したとき-
- 誰がどこに出てきたか、どこにいるかを覚えておいてください(オンラインから削除してください)
- オンラインでアクティブに更新された全員を送信する
- クライアントがちょうど去ったページを視聴者に送り、ここでも更新します
論理的ですか? 続けます。
サーバー:もう少し深く
上記を実装するために、私は特にsocket.jsファイルをロードしないことを決定し、「submit-bring」関数を別のファイルauth.jsにレンダリングしました。
次のように配置されます。
var auth = function () { "use strict"; // Private - / var __users = [], ..., return { ... // Public - / }; }(); module.exports = auth;
メインのsocket.jsブロックは次のとおりです。
sio.sockets.on('connection', function (socket) { var hs = socket.handshake; auth.addActiveUser({ login: hs.session.user, name: hs.session.username, id: hs.session.user_id }); // , - socket.on('iamhere', function (msg) { // - auth.addPageActiveUser({ login: hs.session.user, path: msg.location, path_id: socket.id }); auth.getListActiveUser(function (online) { // online ( 2) socket.emit('online', online); socket.broadcast.emit('online', online); }); auth.getListByPageActiveUser({ path: msg.location }, function (alsohere) { // - , // , , alsohere var i, len; auth.getListByPageConnection({ path: msg.location, users: alsohere }, function (connections) { len = connections.length; for (i = 0; i < len; i++) { sio.sockets.sockets[connections[i].id].emit('alsohere', alsohere); } }); }); }); socket.on('disconnect', function () { // : var s = auth.getPageByIdConnection(socket.id); // , , setTimeout(function () { auth.removeActiveUser({ login: hs.session.user }); // , auth.getListActiveUser(function (online) { // online, socket.broadcast.emit('online', online); }); auth.removePageActiveUser({ login: hs.session.user, path_id: socket.id }); // auth.getListByPageActiveUser({ path: s }, function (alsohere) { // , // alsohere var i, len; auth.getListByPageConnection({ path: s, users: alsohere }, function (connections) { len = connections.length; for (i = 0; i < len; i++) { sio.sockets.sockets[connections[i].id].emit('alsohere', alsohere); } }); }); }, 1000); // - // "" , }); });
出来上がり。
魔法の開示
authモジュールでは、ユーザーに関するすべてのデータ(接続およびページ、セッションに関する)は、プライベート変数__activeUsersのオブジェクトとして保存されます。
各ユーザーに対して、フィールドが作成されます-__activeUsers [login]、フィールドが含まれます:
- 名前 -ユーザー名-ログインと混同しないでください( "vasya" / "Vasily Ivanovich")
- user_id-内部ユーザーID-前のフィールドと同様に、クライアントへの後続の送信に使用されます。たとえば、リンク「/ users / user_id」を形成するために使用されます
- locations -2つのフィールドで構成されるオブジェクトの配列: path-開いているページへの実際のパスとid-このソケット接続のID
したがって、authモジュールは、上記の変数で機能するパブリックメソッドのみを公開します。 これらは、 for 、 for 、 in 、 push 'it and splice 'の検索で構成されるf-sを表します。
- addActiveUser-新しいユーザーデータを__activeUsersに追加します
- addPageActiveUser-新しい開いているページをユーザーに追加します( __activeUsers [sLogin] .locations.push({path:sPath、id:sPath_id}); )
- getListActiveUser-アクティブなユーザーのリストを返します( for(i in __activeUsers){list.push(...
- getListByPageActiveUser-指定されたパスで、同じページを表示しているユーザーのリストを返します
- getListByPageConnection-渡されたページアドレスで、同じページにリンクする接続識別子のリストを返します
- getPageByIdConnection-接続IDによってページのアドレスを返します(ページを離れると、クライアントはパスではなくIDのみを提供します)
- removeActiveUser-ユーザーの開いているページの数を減らします
- removePageActiveUser-開いているリストから閉じたページを削除します
サーバーに接続して認証を正常に渡すと、ユーザーはこのユーザーの一意のIDを持つsocket.handshakeを受け取ります。
新しいページを開くと、このページの一意のsocket.idがこの接続で巻き上げられます。 つまり、10の開いているページを持つ1人のユーザーは、1つのhandshake.idと10の異なるsocket.idです。
そして、それは技術的な問題です。このデータを操作することで、上記のデータ構造を使用して、誰が何を表示/編集しているかをいつでも知ることができます。
そして、実装の全体的な「複雑さ」は、オブジェクトと配列のフィールドを反復処理し、正しいものを取得することです。 そして、私たちはそれを行うことができます:)
これで、上記のコードをもう一度見ることができ、すべてが明らかになります。
どのIDを混同しないか、クライアントに送信される情報をスムーズにするためにタイムアウトを挿入する必要があるという、作業の原則を理解してください。 さて、特定のタスクの実装では、問題はないはずです。
がんばって。
PS Opera(v.11.62でテスト済み)は、FFやchromeとは異なり、ページを閉じるときに、クライアントの切断に関するサーバーメッセージを送信しません。 したがって、サーバーがタイムアウトで自動的に切断するまで、ページを切断/退出するユーザーは、アクティブなユーザーのリストでさらに数秒間ハングします。