Skip to content

Commit

Permalink
New feature: visualize n user account relation
Browse files Browse the repository at this point in the history
  • Loading branch information
ganlvtech committed Nov 29, 2018
1 parent ca1a99e commit 0cd6c81
Show file tree
Hide file tree
Showing 6 changed files with 304 additions and 6 deletions.
55 changes: 55 additions & 0 deletions Models/UserFingerprint.php
Original file line number Diff line number Diff line change
Expand Up @@ -253,4 +253,59 @@ public function deleteRotated($max_log_count = 100000, $delete_ratio = 0.01)
$delete_id_quoted = DB::quote($delete_id);
return DB::delete($this->_table, "`id` <= $delete_id_quoted");
}

public function findUserByFingerprintOrSid($fingerprint_array, $sid_array)
{
if (!$fingerprint_array || !$fingerprint_array) {
return [];
}
$fingerprint_in = implode(', ', DB::quote($fingerprint_array));
$sid_in = implode(', ', DB::quote($sid_array));
return DB::fetch_all("SELECT DISTINCT(`uid`), `username` FROM `{$this->prefixed_table}` WHERE `fingerprint` IN ({$fingerprint_in}) OR `sid` IN ({$sid_in})");
}

public function findRelatedUser($uid)
{
$records = DB::fetch_all("SELECT `fingerprint`, `sid` FROM `{$this->prefixed_table}` WHERE `uid` = {$uid}");
$fingerprint_array = [];
$sid_array = [];
foreach ($records as $row) {
$fingerprint_array[] = $row['fingerprint'];
$sid_array[] = $row['sid'];
}
$related_users = $this->findUserByFingerprintOrSid($fingerprint_array, $sid_array);
return [
'fingerprint_array' => $fingerprint_array,
'sid_array' => $sid_array,
'related_users' => $related_users,
];
}

public function findMultiAccountUidArray($start = 0, $limit = 20)
{
$fingerprint_array = [];
$sid_array = [];
$records = DB::fetch_all("SELECT `fingerprint`, COUNT(DISTINCT(`uid`)) AS `count` FROM `pre_user_fingerprint_log` GROUP BY `fingerprint` ORDER BY `count` DESC LIMIT {$start}, {$limit}");;
foreach ($records as $row) {
if ((int)$row['count'] > 1) {
$fingerprint_array[] = $row['fingerprint'];
}
}
$records = DB::fetch_all("SELECT `sid`, COUNT(DISTINCT(`uid`)) AS `count` FROM `pre_user_fingerprint_log` GROUP BY `sid` ORDER BY `count` DESC LIMIT {$start}, {$limit}");
foreach ($records as $row) {
if ((int)$row['count'] > 1) {
$sid_array[] = $row['sid'];
}
}
return $this->findUserByFingerprintOrSid($fingerprint_array, $sid_array);
}

public function findUserByUid($uid_array)
{
if (!$uid_array) {
return [];
}
$in = implode(', ', DB::quote($uid_array));
return DB::fetch_all("SELECT DISTINCT(`uid`), `username` FROM `{$this->prefixed_table}` WHERE `uid` IN ({$in})");
}
}
131 changes: 131 additions & 0 deletions admin_chart.inc.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
<?php

namespace Ganlv\UserFingerprint;

use Ganlv\UserFingerprint\Models\UserFingerprint;

if (!defined('IN_DISCUZ') || !defined('IN_ADMINCP')) {
exit('Access Denied');
}

require_once __DIR__ . '/Models/UserFingerprint.php';
require_once __DIR__ . '/function/function_main.php';

$table = new UserFingerprint;

$categories = [
0 => ['name' => '1' . _(' Relation Account')],
1 => ['name' => '2' . _(' Relation Account')],
2 => ['name' => '3' . _(' Relation Account')],
3 => ['name' => _('Fingerprint')],
4 => ['name' => _('Session')],
];
$nodes = [];
$links = [];
$index = 0;
$all_uid_index = [];
$all_fingerprint_index = [];
$all_sid_index = [];


$uid = (int)$_GET['uid'];
if ($uid) {
$uid_to_search = [$uid];
} else {
$records = $table->findMultiAccountUidArray();
foreach ($records as $row) {
$uid_to_search[] = (int)$row['uid'];
}
}

$records = $table->findUserByUid($uid_to_search);
$uid_to_search_2 = [];
foreach ($records as $row) {
$row_uid = (int)$row['uid'];
if (!isset($all_uid_index[$row_uid])) {
$nodes[$index] = [
'name' => $row['username'],
'category' => 0,
'symbolSize' => 40,
];
$all_uid_index[$row_uid] = $index;
++$index;
$uid_to_search_2[] = $row_uid;
}
}
$uid_to_search = $uid_to_search_2;

for ($level = 1; $level <= 2; ++$level) {
$uid_to_search_2 = [];
foreach ($uid_to_search as $uid) {
$records = $table->findRelatedUser($uid);

foreach ($records['fingerprint_array'] as $fingerprint) {
if (!isset($all_fingerprint_index[$fingerprint])) {
$nodes[$index] = [
'name' => $fingerprint,
'category' => 3,
'symbolSize' => 10,
];
$all_fingerprint_index[$fingerprint] = $index;
++$index;
}
$links[] = [
'source' => $all_uid_index[$uid],
'target' => $all_fingerprint_index[$fingerprint],
];
}

foreach ($records['sid_array'] as $sid) {
if (!isset($all_sid_index[$sid])) {
$nodes[$index] = [
'name' => $sid,
'category' => 4,
'symbolSize' => 10,
];
$all_sid_index[$sid] = $index;
++$index;
}
$links[] = [
'source' => $all_uid_index[$uid],
'target' => $all_sid_index[$sid],
];
}

foreach ($records['related_users'] as $row) {
$row_uid = (int)$row['uid'];
if (!isset($all_uid_index[$row_uid])) {
$nodes[$index] = [
'name' => $row['username'],
'category' => $level,
'symbolSize' => (4 - $level) * 10,
];
$all_uid_index[$row_uid] = $index;
++$index;
$uid_to_search_2[] = $row_uid;
}
$links[] = [
'source' => $all_uid_index[$uid],
'target' => $all_uid_index[$row_uid],
];
}
}
$uid_to_search = $uid_to_search_2;
}

$data = [
'title' => [
'text' => _('User Account Relation Visualization')
],
'categories' => $categories,
'nodes' => $nodes,
'links' => $links,
];


echo '<script>window.user_relation_data = ', json_encode($data), ';</script>';

echo <<<'EOD'
<div class="echarts-container"><div id="chart"></div></div>
<script src="source/plugin/user_fingerprint/js/admin.min.js"></script>
EOD;
13 changes: 11 additions & 2 deletions build/build_js.sh
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,16 @@ cd ../js/
if [[ ! -f fingerprint2.min.js ]]; then
wget https://cdn.jsdelivr.net/npm/fingerprintjs2@2.0.3/dist/fingerprint2.min.js
fi
cp fingerprint2.min.js bundle.min.js
uglifyjs --compress --mangle --comments "/^!/" -- script.js >> bundle.min.js
uglifyjs --compress --mangle --comments "/^!/" -- script.js > script.min.js
cat fingerprint2.min.js script.min.js > bundle.min.js

if [[ ! -f echarts.min.js ]]; then
wget https://cdn.jsdelivr.net/npm/echarts@4.2.0-rc.2/dist/echarts.min.js
fi
if [[ ! -f axios.min.js ]]; then
wget https://cdn.jsdelivr.net/npm/axios@0.18.0/dist/axios.min.js
fi
uglifyjs --compress --mangle --comments "/^!/" -- admin_chart.js > admin_chart.min.js
cat axios.min.js echarts.min.js admin_chart.min.js > admin.min.js

popd
21 changes: 19 additions & 2 deletions discuz_plugin_user_fingerprint.xml
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
<item id="datatables"><![CDATA[]]></item>
<item id="directory"><![CDATA[user_fingerprint/]]></item>
<item id="copyright"><![CDATA[Ganlv (https://github.com/ganlvtech)]]></item>
<item id="version"><![CDATA[1.0.1.0]]></item>
<item id="version"><![CDATA[1.2.0.4]]></item>
<item id="__modules">
<item id="0">
<item id="name"><![CDATA[user_fingerprint]]></item>
Expand Down Expand Up @@ -58,7 +58,7 @@
<item id="3">
<item id="name"><![CDATA[admin_sid]]></item>
<item id="param"><![CDATA[]]></item>
<item id="menu"><![CDATA[Session ID]]></item>
<item id="menu"><![CDATA[会话]]></item>
<item id="url"><![CDATA[]]></item>
<item id="type"><![CDATA[3]]></item>
<item id="adminid"><![CDATA[0]]></item>
Expand All @@ -81,6 +81,19 @@
<item id="navsubname"><![CDATA[]]></item>
<item id="navsuburl"><![CDATA[]]></item>
</item>
<item id="5">
<item id="name"><![CDATA[admin_chart]]></item>
<item id="param"><![CDATA[]]></item>
<item id="menu"><![CDATA[可视化]]></item>
<item id="url"><![CDATA[]]></item>
<item id="type"><![CDATA[3]]></item>
<item id="adminid"><![CDATA[0]]></item>
<item id="displayorder"><![CDATA[5]]></item>
<item id="navtitle"><![CDATA[]]></item>
<item id="navicon"><![CDATA[]]></item>
<item id="navsubname"><![CDATA[]]></item>
<item id="navsuburl"><![CDATA[]]></item>
</item>
</item>
</item>
<item id="version"><![CDATA[X3.4]]></item>
Expand Down Expand Up @@ -130,6 +143,10 @@
<item id="Fingerprint Session Count Top"><![CDATA[同一指纹的会话数排行]]></item>
<item id="Session Fingerprint Count Top"><![CDATA[同一会话的指纹数排行]]></item>
<item id="User Log Count Top"><![CDATA[同一用户的记录数排行]]></item>
<item id="User Account Relation Visualization"><![CDATA[用户账号关系可视化]]></item>
<item id="Visualization"><![CDATA[可视化]]></item>
<item id=" Relation Account"><![CDATA[ 级账号]]></item>
<item id="Session"><![CDATA[会话]]></item>
</item>
</item>
<item id="installfile"><![CDATA[install.php]]></item>
Expand Down
5 changes: 3 additions & 2 deletions function/function_admin.php
Original file line number Diff line number Diff line change
Expand Up @@ -131,7 +131,8 @@ function admin_show_table_row_content($row, $item)
dhtmlspecialchars($row['hit']),
dhtmlspecialchars(date('m-d H:i', $row['created_at'])),
dhtmlspecialchars(date('m-d H:i', $row['last_online_time'])),
'<a target="_blank" rel="noopener" href="' . ADMINSCRIPT . '?frames=yes&action=members&operation=search&submit=1&uid=' . $row['uid'] . '">' . _('Find user') . '</a> '
. '<a target="_blank" rel="noopener" href="home.php?mod=space&uid=' . $row['uid'] . '">' . _('User space') . '</a>',
'<a target="_blank" rel="noopener" href="' . ADMINSCRIPT . '?frames=yes&action=members&operation=search&submit=1&uid=' . $row['uid'] . '">' . _('Find user') . '</a>'
. ' <a target="_blank" rel="noopener" href="home.php?mod=space&uid=' . $row['uid'] . '">' . _('User space') . '</a>'
. ' <a target="_blank" rel="noopener" href="' . ADMINSCRIPT . '?action=plugins&operation=config&identifier=user_fingerprint&pmod=admin_chart&uid=' . $row['uid'] . '">' . _('Visualization') . '</a>',
]);
}
85 changes: 85 additions & 0 deletions js/admin_chart.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
/*!
* User Fingerprint Discuz! X plugin <https://github.com/ganlvtech/discuzx-user-fingerprint>
* Copyright (C) 2018 Ganlv <https://github.com/ganlvtech>
* License: GPL 3.0
*/
var el = document.getElementById('chart');

function resizeChart() {
var width = el.parentElement.clientWidth;
var height = window.innerHeight - el.parentElement.offsetTop - 20;
if (width < 800) {
width = 800;
}
if (height / width < 0.3) {
height = width * 0.3;
}
el.style.width = width + 'px';
el.style.height = height + 'px';
}

resizeChart();

var myChart = echarts.init(el);

window.onresize = function () {
resizeChart();
if (myChart) {
myChart.resize();
}
};


function handle(data) {
myChart.hideLoading();

var option = {
title: data.title,
legend: {
data: data.categories
},
series: [{
type: 'graph',
layout: 'force',
animation: false,
draggable: true,
roam: true,
focusNodeAdjacency: true,
force: {
initLayout: 'circular',
repulsion: 20,
edgeLength: 200,
gravity: 0.1
},
label: {
position: 'bottom',
formatter: '{b}'
},
itemStyle: {
normal: {
borderColor: '#fff',
borderWidth: 1,
shadowBlur: 10,
shadowColor: 'rgba(0, 0, 0, 0.3)'
}
},
lineStyle: {
color: 'source'
},
emphasis: {
lineStyle: {
width: 10
}
},
data: data.nodes,
categories: data.categories,
edges: data.links
}]
};
myChart.setOption(option);
}

myChart.showLoading();

handle(window.user_relation_data);

0 comments on commit 0cd6c81

Please sign in to comment.