From eee5152f5244e87ef359a53f9878be1d7eeaec67 Mon Sep 17 00:00:00 2001 From: v2board Date: Wed, 3 May 2023 15:37:19 +0800 Subject: [PATCH] update: statistics service --- app/Console/Commands/V2boardStatistics.php | 68 +++++---- app/Http/Controllers/Admin/StatController.php | 72 +++++++--- .../Controllers/Admin/SystemController.php | 11 -- .../Server/DeepbworkController.php | 2 + .../Server/ShadowsocksTidalabController.php | 2 + .../Server/TrojanTidalabController.php | 2 + .../Controllers/Server/UniProxyController.php | 4 + app/Jobs/StatServerJob.php | 80 ----------- app/Jobs/StatUserJob.php | 80 ----------- app/Models/{StatOrder.php => Stat.php} | 4 +- app/Services/StatisticalService.php | 136 +++++++++++++++++- app/Services/UserService.php | 1 - config/horizon.php | 1 - database/install.sql | 35 +++-- database/update.sql | 14 ++ 15 files changed, 273 insertions(+), 239 deletions(-) delete mode 100644 app/Jobs/StatServerJob.php delete mode 100644 app/Jobs/StatUserJob.php rename app/Models/{StatOrder.php => Stat.php} (77%) diff --git a/app/Console/Commands/V2boardStatistics.php b/app/Console/Commands/V2boardStatistics.php index dec22368..bc907caf 100644 --- a/app/Console/Commands/V2boardStatistics.php +++ b/app/Console/Commands/V2boardStatistics.php @@ -2,11 +2,13 @@ namespace App\Console\Commands; +use App\Models\StatServer; use App\Models\StatUser; +use App\Models\User; use App\Services\StatisticalService; use Illuminate\Console\Command; use App\Models\Order; -use App\Models\StatOrder; +use App\Models\Stat; use App\Models\CommissionLog; use Illuminate\Support\Facades\DB; @@ -43,16 +45,47 @@ class V2boardStatistics extends Command */ public function handle() { + $startAt = microtime(true); ini_set('memory_limit', -1); - $this->statOrder(); $this->statUser(); + $this->statServer(); + $this->stat(); + $this->info('耗时' . (microtime(true) - $startAt)); + } + + private function statServer() + { + $createdAt = time(); + $recordAt = strtotime('-1 day', strtotime(date('Y-m-d'))); + $statService = new StatisticalService(); + $statService->setStartAt($recordAt); + $stats = $statService->getStatUser(); + DB::beginTransaction(); + foreach ($stats as $stat) { + if (!StatServer::insert([ + 'server_id' => $stat['server_id'], + 'server_type' => $stat['server_type'], + 'u' => $stat['u'], + 'd' => $stat['d'], + 'created_at' => $createdAt, + 'updated_at' => $createdAt, + 'record_type' => 'd', + 'record_at' => $recordAt + ])) { + DB::rollback(); + throw new \Exception('stat server fail'); + } + } + DB::commit(); + $statService->clearStatServer(); } private function statUser() { $createdAt = time(); $recordAt = strtotime('-1 day', strtotime(date('Y-m-d'))); - $statService = new StatisticalService($recordAt); + $statService = new StatisticalService(); + $statService->setStartAt($recordAt); $stats = $statService->getStatUser(); DB::beginTransaction(); foreach ($stats as $stat) { @@ -74,34 +107,19 @@ class V2boardStatistics extends Command $statService->clearStatUser(); } - private function statOrder() + private function stat() { - $endAt = strtotime(date('Y-m-d')); - $startAt = strtotime('-1 day', $endAt); - $orderBuilder = Order::where('paid_at', '>=', $startAt) - ->where('paid_at', '<', $endAt) - ->whereNotIn('status', [0, 2]); - $orderCount = $orderBuilder->count(); - $orderAmount = $orderBuilder->sum('total_amount'); - $commissionLogBuilder = CommissionLog::where('created_at', '>=', $startAt) - ->where('created_at', '<', $endAt); - $commissionCount = $commissionLogBuilder->count(); - $commissionAmount = $commissionLogBuilder->sum('get_amount'); - $data = [ - 'order_count' => $orderCount, - 'order_amount' => $orderAmount, - 'commission_count' => $commissionCount, - 'commission_amount' => $commissionAmount, - 'record_type' => 'd', - 'record_at' => $startAt - ]; - $statistic = StatOrder::where('record_at', $startAt) + $startAt = strtotime('-1 day', strtotime(date('Y-m-d'))); + $statisticalService = new StatisticalService(); + $statisticalService->setRecordAt($startAt); + $data = $statisticalService->generateStatData(); + $statistic = Stat::where('record_at', $startAt) ->where('record_type', 'd') ->first(); if ($statistic) { $statistic->update($data); return; } - StatOrder::create($data); + Stat::create($data); } } diff --git a/app/Http/Controllers/Admin/StatController.php b/app/Http/Controllers/Admin/StatController.php index a76f906c..0b7f91e1 100644 --- a/app/Http/Controllers/Admin/StatController.php +++ b/app/Http/Controllers/Admin/StatController.php @@ -7,6 +7,7 @@ use App\Models\ServerShadowsocks; use App\Models\ServerTrojan; use App\Models\StatUser; use App\Services\ServerService; +use App\Services\StatisticalService; use Illuminate\Http\Request; use App\Http\Controllers\Controller; use App\Models\ServerGroup; @@ -15,16 +16,50 @@ use App\Models\Plan; use App\Models\User; use App\Models\Ticket; use App\Models\Order; -use App\Models\StatOrder; +use App\Models\Stat; use App\Models\StatServer; use Illuminate\Support\Facades\Cache; use Illuminate\Support\Facades\DB; class StatController extends Controller { + public function getStat(Request $request) + { + $params = $request->validate([ + 'start_at' => '', + 'end_at' => '' + ]); + + if (isset($params['start_at']) && isset($params['end_at'])) { + $stats = Stat::where('record_at', '>=', $params['start_at']) + ->where('record_at', '<', $params['end_at']) + ->get() + ->makeHidden(['record_at', 'created_at', 'updated_at', 'id', 'record_type']) + ->toArray(); + $stats = array_reduce($stats, function($carry, $item) { + foreach($item as $key => $value) { + if(isset($carry[$key]) && $carry[$key]) { + $carry[$key] += $value; + } else { + $carry[$key] = $value; + } + } + return $carry; + }, []); + return [ + 'data' => $stats + ]; + } + + $statisticalService = new StatisticalService(); + return [ + 'data' => $statisticalService->generateStatData() + ]; + } + public function getOverride(Request $request) { - return response([ + return [ 'data' => [ 'month_income' => Order::where('created_at', '>=', strtotime(date('Y-m-1'))) ->where('created_at', '<', time()) @@ -55,12 +90,12 @@ class StatController extends Controller ->where('created_at', '<', strtotime(date('Y-m-1'))) ->sum('get_amount'), ] - ]); + ]; } public function getOrder(Request $request) { - $statistics = StatOrder::where('record_type', 'd') + $statistics = Stat::where('record_type', 'd') ->limit(31) ->orderBy('record_at', 'DESC') ->get() @@ -68,31 +103,31 @@ class StatController extends Controller $result = []; foreach ($statistics as $statistic) { $date = date('m-d', $statistic['record_at']); - array_push($result, [ + $result[] = [ 'type' => '收款金额', 'date' => $date, - 'value' => $statistic['order_amount'] / 100 - ]); - array_push($result, [ + 'value' => $statistic['order_total'] / 100 + ]; + $result[] = [ 'type' => '收款笔数', 'date' => $date, 'value' => $statistic['order_count'] - ]); - array_push($result, [ + ]; + $result[] = [ 'type' => '佣金金额(已发放)', 'date' => $date, - 'value' => $statistic['commission_amount'] / 100 - ]); - array_push($result, [ + 'value' => $statistic['commission_total'] / 100 + ]; + $result[] = [ 'type' => '佣金笔数(已发放)', 'date' => $date, 'value' => $statistic['commission_count'] - ]); + ]; } $result = array_reverse($result); - return response([ + return [ 'data' => $result - ]); + ]; } public function getServerLastRank() @@ -128,9 +163,9 @@ class StatController extends Controller $statistics[$k]['total'] = $statistics[$k]['total'] / 1073741824; } array_multisort(array_column($statistics, 'total'), SORT_DESC, $statistics); - return response([ + return [ 'data' => $statistics - ]); + ]; } public function getStatUser(Request $request) @@ -150,5 +185,6 @@ class StatController extends Controller 'total' => $total ]; } + } diff --git a/app/Http/Controllers/Admin/SystemController.php b/app/Http/Controllers/Admin/SystemController.php index 32f21323..c5e25c62 100644 --- a/app/Http/Controllers/Admin/SystemController.php +++ b/app/Http/Controllers/Admin/SystemController.php @@ -2,20 +2,9 @@ namespace App\Http\Controllers\Admin; -use App\Models\ServerShadowsocks; -use App\Models\ServerTrojan; -use App\Services\ServerService; use App\Utils\CacheKey; use Illuminate\Http\Request; use App\Http\Controllers\Controller; -use App\Models\ServerGroup; -use App\Models\ServerVmess; -use App\Models\Plan; -use App\Models\User; -use App\Models\Ticket; -use App\Models\Order; -use App\Models\StatOrder; -use App\Models\StatServer; use Illuminate\Support\Facades\Cache; use Illuminate\Support\Facades\DB; use Illuminate\Support\Facades\Http; diff --git a/app/Http/Controllers/Server/DeepbworkController.php b/app/Http/Controllers/Server/DeepbworkController.php index 99839fa9..c35268c2 100644 --- a/app/Http/Controllers/Server/DeepbworkController.php +++ b/app/Http/Controllers/Server/DeepbworkController.php @@ -91,6 +91,8 @@ class DeepbworkController extends Controller } $statService = new StatisticalService(); + $statService->setStartAt(strtotime(date('Y-m-d'))); + $statService->setUserStats(); $statService->statUser($server['rate'], $statData); return response([ diff --git a/app/Http/Controllers/Server/ShadowsocksTidalabController.php b/app/Http/Controllers/Server/ShadowsocksTidalabController.php index a575637d..a3cf0016 100644 --- a/app/Http/Controllers/Server/ShadowsocksTidalabController.php +++ b/app/Http/Controllers/Server/ShadowsocksTidalabController.php @@ -83,6 +83,8 @@ class ShadowsocksTidalabController extends Controller } $statService = new StatisticalService(); + $statService->setStartAt(strtotime(date('Y-m-d'))); + $statService->setUserStats(); $statService->statUser($server['rate'], $statData); return response([ diff --git a/app/Http/Controllers/Server/TrojanTidalabController.php b/app/Http/Controllers/Server/TrojanTidalabController.php index 2276917b..b5942f2d 100644 --- a/app/Http/Controllers/Server/TrojanTidalabController.php +++ b/app/Http/Controllers/Server/TrojanTidalabController.php @@ -88,6 +88,8 @@ class TrojanTidalabController extends Controller } $statService = new StatisticalService(); + $statService->setStartAt(strtotime(date('Y-m-d'))); + $statService->setUserStats(); $statService->statUser($server['rate'], $statData); return response([ diff --git a/app/Http/Controllers/Server/UniProxyController.php b/app/Http/Controllers/Server/UniProxyController.php index 3dfb10dd..dda55321 100644 --- a/app/Http/Controllers/Server/UniProxyController.php +++ b/app/Http/Controllers/Server/UniProxyController.php @@ -71,7 +71,11 @@ class UniProxyController extends Controller } $statService = new StatisticalService(); + $statService->setStartAt(strtotime(date('Y-m-d'))); + $statService->setUserStats(); $statService->statUser($this->nodeInfo->rate, $data); + $statService->setServerStats(); + $statService->statServer($this->nodeId, $this->nodeType, $data); return response([ 'data' => true diff --git a/app/Jobs/StatServerJob.php b/app/Jobs/StatServerJob.php deleted file mode 100644 index 8e56d9fe..00000000 --- a/app/Jobs/StatServerJob.php +++ /dev/null @@ -1,80 +0,0 @@ -onQueue('stat'); - $this->u = $u; - $this->d = $d; - $this->server = $server; - $this->protocol = $protocol; - $this->recordType = $recordType; - } - - /** - * Execute the job. - * - * @return void - */ - public function handle() - { - $recordAt = strtotime(date('Y-m-d')); - if ($this->recordType === 'm') { - // - } - - $data = StatServer::lockForUpdate() - ->where('record_at', $recordAt) - ->where('server_id', $this->server['id']) - ->where('server_type', $this->protocol) - ->first(); - - if (!$data) { - if (!StatServer::create([ - 'server_id' => $this->server['id'], - 'server_type' => $this->protocol, - 'u' => $this->u, - 'd' => $this->d, - 'record_type' => $this->recordType, - 'record_at' => $recordAt - ])) { - abort(500, '节点统计数据创建失败'); - } - return; - } - - try { - $data->update([ - 'u' => $data['u'] + $this->u, - 'd' => $data['d'] + $this->d - ]); - } catch (\Exception $e) { - abort(500, '节点统计数据更新失败'); - } - } -} diff --git a/app/Jobs/StatUserJob.php b/app/Jobs/StatUserJob.php deleted file mode 100644 index 79d908f0..00000000 --- a/app/Jobs/StatUserJob.php +++ /dev/null @@ -1,80 +0,0 @@ -onQueue('stat'); - $this->u = $u; - $this->d = $d; - $this->userId = $userId; - $this->server = $server; - $this->protocol = $protocol; - $this->recordType = $recordType; - } - - /** - * Execute the job. - * - * @return void - */ - public function handle() - { - $recordAt = strtotime(date('Y-m-d')); - if ($this->recordType === 'm') { - // - } - - $data = StatUser::where('record_at', $recordAt) - ->where('server_rate', $this->server['rate']) - ->where('user_id', $this->userId) - ->first(); - if ($data) { - try { - $data->update([ - 'u' => $data['u'] + ($this->u * $this->server['rate']), - 'd' => $data['d'] + ($this->d * $this->server['rate']) - ]); - } catch (\Exception $e) { - abort(500, '用户统计数据更新失败'); - } - } else { - if (!StatUser::create([ - 'user_id' => $this->userId, - 'server_rate' => $this->server['rate'], - 'u' => $this->u, - 'd' => $this->d, - 'record_type' => $this->recordType, - 'record_at' => $recordAt - ])) { - abort(500, '用户统计数据创建失败'); - } - } - } -} diff --git a/app/Models/StatOrder.php b/app/Models/Stat.php similarity index 77% rename from app/Models/StatOrder.php rename to app/Models/Stat.php index f0340a21..b0ed9ec6 100644 --- a/app/Models/StatOrder.php +++ b/app/Models/Stat.php @@ -4,9 +4,9 @@ namespace App\Models; use Illuminate\Database\Eloquent\Model; -class StatOrder extends Model +class Stat extends Model { - protected $table = 'v2_stat_order'; + protected $table = 'v2_stat'; protected $dateFormat = 'U'; protected $guarded = ['id']; protected $casts = [ diff --git a/app/Services/StatisticalService.php b/app/Services/StatisticalService.php index 5d4270c2..9426293f 100644 --- a/app/Services/StatisticalService.php +++ b/app/Services/StatisticalService.php @@ -1,23 +1,108 @@ recordAt = $recordAt ?? strtotime(date('Y-m-d')); - $this->userStats = Cache::get("stat_user_{$this->recordAt}"); + } + + public function setStartAt($timestamp) { + $this->startAt = $timestamp; + } + + public function setEndAt($timestamp) { + $this->endAt = $timestamp; + } + + public function setServerStats() { + $this->serverStats = Cache::get("stat_server_{$this->startAt}"); + $this->serverStats = json_decode($this->serverStats, true) ?? []; + if (!is_array($this->serverStats)) { + $this->serverStats = []; + } + } + + public function setUserStats() { + $this->userStats = Cache::get("stat_user_{$this->startAt}"); $this->userStats = json_decode($this->userStats, true) ?? []; if (!is_array($this->userStats)) { $this->userStats = []; } } + public function generateStatData(): array + { + $startAt = $this->startAt; + $endAt = $this->endAt; + if (!$startAt || !$endAt) { + $startAt = strtotime(date('Y-m-d')); + $endAt = strtotime('+1 day', $startAt); + } + $data = [ + 'record_type' => 'd', + 'paid_rate' => 0, + 'record_at' => $startAt + ]; + $data['order_count'] = Order::where('created_at', '>=', $startAt) + ->where('created_at', '<', $endAt) + ->count(); + $data['order_total'] = Order::where('created_at', '>=', $startAt) + ->where('created_at', '<', $endAt) + ->sum('total_amount'); + $data['paid_count'] = Order::where('paid_at', '>=', $startAt) + ->where('paid_at', '<', $endAt) + ->whereNotIn('status', [0, 2]) + ->count(); + $data['paid_total'] = Order::where('paid_at', '>=', $startAt) + ->where('paid_at', '<', $endAt) + ->whereNotIn('status', [0, 2]) + ->sum('total_amount'); + $commissionLogBuilder = CommissionLog::where('created_at', '>=', $startAt) + ->where('created_at', '<', $endAt); + $data['commission_count'] = $commissionLogBuilder->count(); + $data['commission_amount'] = $commissionLogBuilder->sum('get_amount'); + $data['register_count'] = User::where('created_at', '>=', $startAt) + ->where('created_at', '<', $endAt) + ->count(); + $data['invite_count'] = User::where('created_at', '>=', $startAt) + ->where('created_at', '<', $endAt) + ->whereNotNull('invite_user_id') + ->count(); + $data['transfer_used_total'] = StatServer::where('created_at', '>=', $startAt) + ->where('created_at', '<', $endAt) + ->select(DB::raw('SUM(u) + SUM(d) as total')) + ->value('total') ?? 0; + return $data; + } + + public function statServer($serverId, $serverType, $data) + { + foreach (array_keys($data) as $key) { + $this->serverStats[$serverType] = $this->serverStats[$serverType] ?? []; + if (isset($this->serverStats[$serverType][$serverId])) { + $this->serverStats[$serverType][$serverId][0] += $data[$key][0]; + $this->serverStats[$serverType][$serverId][1] += $data[$key][1]; + } else { + $this->serverStats[$serverType][$serverId] = $data[$key]; + } + } + Cache::set("stat_server_{$this->startAt}", json_encode($this->serverStats)); + } + public function statUser($rate, $data) { foreach (array_keys($data) as $key) { @@ -29,9 +114,24 @@ class StatisticalService { $this->userStats[$rate][$key] = $data[$key]; } } - Cache::set("stat_user_{$this->recordAt}", json_encode($this->userStats)); + Cache::set("stat_user_{$this->startAt}", json_encode($this->userStats)); } + public function getStatUserByUserID($userId): array + { + $stats = []; + foreach (array_keys($this->userStats) as $rate) { + if (!isset($this->userStats[$rate][$userId])) continue; + $stats[] = [ + 'record_at' => $this->startAt, + 'server_rate' => $rate, + 'u' => $this->userStats[$rate][$userId][0], + 'd' => $this->userStats[$rate][$userId][1], + 'user_id' => $userId + ]; + } + return $stats; + } public function getStatUser() { @@ -51,8 +151,32 @@ class StatisticalService { return $stats; } + + public function getStatServer() + { + $stats = []; + foreach ($this->serverStats as $serverType => $v) { + foreach (array_keys($v) as $serverId) { + if (isset($v[$serverId])) { + $stats[] = [ + 'server_id' => $serverId, + 'server_type' => $serverType, + 'u' => $v[$serverId][0], + 'd' => $v[$serverId][1], + ]; + } + } + } + return $stats; + } + public function clearStatUser() { - Cache::forget("stat_user_{$this->recordAt}"); + Cache::forget("stat_user_{$this->startAt}"); + } + + public function clearStatServer() + { + Cache::forget("stat_server_{$this->startAt}"); } } diff --git a/app/Services/UserService.php b/app/Services/UserService.php index e3da24d1..af625dee 100644 --- a/app/Services/UserService.php +++ b/app/Services/UserService.php @@ -171,6 +171,5 @@ class UserService public function trafficFetch(int $u, int $d, int $userId, array $server, string $protocol) { TrafficFetchJob::dispatch($u, $d, $userId, $server, $protocol); - StatServerJob::dispatch($u, $d, $server, $protocol, 'd'); } } diff --git a/config/horizon.php b/config/horizon.php index 9559a76b..4ad9e24a 100644 --- a/config/horizon.php +++ b/config/horizon.php @@ -175,7 +175,6 @@ return [ 'queue' => [ 'order_handle', 'traffic_fetch', - 'stat', 'send_email', 'send_email_mass', 'send_telegram', diff --git a/database/install.sql b/database/install.sql index b5eef6ae..b6611ec0 100644 --- a/database/install.sql +++ b/database/install.sql @@ -1,4 +1,4 @@ --- Adminer 4.8.1 MySQL 5.7.29 dump +-- Adminer 4.7.7 MySQL dump SET NAMES utf8; SET time_zone = '+00:00'; @@ -304,19 +304,24 @@ CREATE TABLE `v2_server_vmess` ( ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; -DROP TABLE IF EXISTS `v2_stat_order`; -CREATE TABLE `v2_stat_order` ( - `id` int(11) NOT NULL AUTO_INCREMENT, - `order_count` int(11) NOT NULL COMMENT '订单数量', - `order_amount` int(11) NOT NULL COMMENT '订单合计', - `commission_count` int(11) NOT NULL, - `commission_amount` int(11) NOT NULL COMMENT '佣金合计', - `record_type` char(1) NOT NULL, - `record_at` int(11) NOT NULL, - `created_at` int(11) NOT NULL, - `updated_at` int(11) NOT NULL, - PRIMARY KEY (`id`), - UNIQUE KEY `record_at` (`record_at`) +DROP TABLE IF EXISTS `v2_stat`; +CREATE TABLE `v2_stat` ( + `id` int(11) NOT NULL AUTO_INCREMENT, + `record_at` int(11) NOT NULL, + `record_type` char(1) NOT NULL, + `order_count` int(11) NOT NULL COMMENT '订单数量', + `order_total` int(11) NOT NULL COMMENT '订单合计', + `commission_count` int(11) NOT NULL, + `commission_total` int(11) NOT NULL COMMENT '佣金合计', + `paid_count` int(11) NOT NULL, + `paid_total` int(11) NOT NULL, + `register_count` int(11) NOT NULL, + `invite_count` int(11) NOT NULL, + `transfer_used_total` varchar(32) NOT NULL, + `created_at` int(11) NOT NULL, + `updated_at` int(11) NOT NULL, + PRIMARY KEY (`id`), + UNIQUE KEY `record_at` (`record_at`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='订单统计'; @@ -422,4 +427,4 @@ CREATE TABLE `v2_user` ( ) ENGINE=InnoDB DEFAULT CHARSET=utf8; --- 2023-03-08 06:17:49 +-- 2023-05-03 07:27:22 diff --git a/database/update.sql b/database/update.sql index 0f57b6bc..68258cf2 100644 --- a/database/update.sql +++ b/database/update.sql @@ -683,3 +683,17 @@ CREATE TABLE `v2_server_hysteria` ( ALTER TABLE `v2_plan` ADD `capacity_limit` int(11) NULL AFTER `reset_traffic_method`; + +ALTER TABLE `v2_stat_order` + CHANGE `record_at` `record_at` int(11) NOT NULL AFTER `id`, + CHANGE `record_type` `record_type` char(1) COLLATE 'utf8_general_ci' NOT NULL AFTER `record_at`, + CHANGE `order_count` `order_count` int(11) NOT NULL COMMENT '订单数量' AFTER `record_type`, + CHANGE `order_amount` `order_total` int(11) NOT NULL COMMENT '订单合计' AFTER `order_count`, + CHANGE `commission_count` `commission_count` int(11) NOT NULL AFTER `order_total`, + CHANGE `commission_amount` `commission_total` int(11) NOT NULL COMMENT '佣金合计' AFTER `commission_count`, + ADD `paid_count` int(11) NOT NULL AFTER `commission_total`, + ADD `paid_total` int(11) NOT NULL AFTER `paid_count`, + ADD `register_count` int(11) NOT NULL AFTER `paid_total`, + ADD `invite_count` int(11) NOT NULL AFTER `register_count`, + ADD `transfer_used_total` varchar(32) NOT NULL AFTER `invite_count`, + RENAME TO `v2_stat`;