diff --git a/app/Http/Admin/Controllers/StatController.php b/app/Http/Admin/Controllers/StatController.php new file mode 100644 index 0000000000000000000000000000000000000000..cb80569bc8a020166f272377fee1c55d19422300 --- /dev/null +++ b/app/Http/Admin/Controllers/StatController.php @@ -0,0 +1,98 @@ +getYearOptions(); + $months = $statService->getMonthOptions(); + $items = $statService->hotSales(); + + $this->view->pick('stat/hot_sales'); + $this->view->setVar('years', $years); + $this->view->setVar('months', $months); + $this->view->setVar('items', $items); + } + + /** + * @Get("/sales", name="admin.stat.sales") + */ + public function salesAction() + { + $statService = new StatService(); + + $years = $statService->getYearOptions(); + $months = $statService->getMonthOptions(); + $data = $statService->sales(); + + $this->view->pick('stat/sales'); + $this->view->setVar('years', $years); + $this->view->setVar('months', $months); + $this->view->setVar('data', $data); + } + + /** + * @Get("/refunds", name="admin.stat.refunds") + */ + public function refundsAction() + { + $statService = new StatService(); + + $years = $statService->getYearOptions(); + $months = $statService->getMonthOptions(); + $data = $statService->refunds(); + + $this->view->pick('stat/refunds'); + $this->view->setVar('years', $years); + $this->view->setVar('months', $months); + $this->view->setVar('data', $data); + } + + /** + * @Get("/users/registered", name="admin.stat.reg_users") + */ + public function registeredUsersAction() + { + $statService = new StatService(); + + $years = $statService->getYearOptions(); + $months = $statService->getMonthOptions(); + $data = $statService->registeredUsers(); + + $this->view->pick('stat/registered_users'); + $this->view->setVar('years', $years); + $this->view->setVar('months', $months); + $this->view->setVar('data', $data); + } + + /** + * @Get("/users/online", name="admin.stat.online_users") + */ + public function onlineUsersAction() + { + $statService = new StatService(); + + $years = $statService->getYearOptions(); + $months = $statService->getMonthOptions(); + $data = $statService->onlineUsers(); + + $this->view->pick('stat/online_users'); + $this->view->setVar('years', $years); + $this->view->setVar('months', $months); + $this->view->setVar('data', $data); + } + +} diff --git a/app/Http/Admin/Services/AuthNode.php b/app/Http/Admin/Services/AuthNode.php index 44482bb07d0d55c1f116c5ad40e01aaf4d1078a4..86b54761d5e71aa32c666fe097e7df27decae354 100644 --- a/app/Http/Admin/Services/AuthNode.php +++ b/app/Http/Admin/Services/AuthNode.php @@ -438,6 +438,43 @@ class AuthNode extends Service ], ], ], + [ + 'id' => '2-7', + 'title' => '数据统计', + 'type' => 'menu', + 'children' => [ + [ + 'id' => '2-7-1', + 'title' => '热卖商品', + 'type' => 'menu', + 'route' => 'admin.stat.hot_sales', + ], + [ + 'id' => '2-7-2', + 'title' => '成交订单', + 'type' => 'menu', + 'route' => 'admin.stat.sales', + ], + [ + 'id' => '2-7-3', + 'title' => '售后退款', + 'type' => 'menu', + 'route' => 'admin.stat.refunds', + ], + [ + 'id' => '2-7-4', + 'title' => '注册用户', + 'type' => 'menu', + 'route' => 'admin.stat.reg_users', + ], + [ + 'id' => '2-7-5', + 'title' => '活跃用户', + 'type' => 'menu', + 'route' => 'admin.stat.online_users', + ], + ], + ], ], ]; } diff --git a/app/Http/Admin/Services/Stat.php b/app/Http/Admin/Services/Stat.php new file mode 100644 index 0000000000000000000000000000000000000000..bacb6858e15027237bb326c79571840d4cdfd89c --- /dev/null +++ b/app/Http/Admin/Services/Stat.php @@ -0,0 +1,405 @@ +request->getQuery('type', 'int', OrderModel::ITEM_COURSE); + $year = $this->request->getQuery('year', 'int', date('Y')); + $month = $this->request->getQuery('month', 'int', date('m')); + + $prev = $this->getPrevMonth($year, $month); + + return [ + [ + 'title' => "{$year}-{$month}", + 'sales' => $this->handleHotSales($type, $year, $month), + ], + [ + 'title' => "{$prev['year']}-{$prev['month']}", + 'sales' => $this->handleHotSales($type, $prev['year'], $prev['month']), + ], + ]; + } + + public function sales() + { + $year = $this->request->getQuery('year', 'int', date('Y')); + $month = $this->request->getQuery('month', 'int', date('m')); + + $prev = $this->getPrevMonth($year, $month); + $currSales = $this->handleSales($year, $month); + $prevSales = $this->handleSales($prev['year'], $prev['month']); + + $items = []; + + foreach (range(1, 31) as $day) { + $date = sprintf('%02d', $day); + $prevMonth = "{$prev['year']}-{$prev['month']}"; + $currMonth = "{$year}-{$month}"; + $items[] = [ + 'date' => $date, + $currMonth => $currSales[$date] ?? 0, + $prevMonth => $prevSales[$date] ?? 0, + ]; + } + + return $items; + } + + public function refunds() + { + $year = $this->request->getQuery('year', 'int', date('Y')); + $month = $this->request->getQuery('month', 'int', date('m')); + + $prev = $this->getPrevMonth($year, $month); + $currRefunds = $this->handleRefunds($year, $month); + $prevRefunds = $this->handleRefunds($prev['year'], $prev['month']); + + $items = []; + + foreach (range(1, 31) as $day) { + $date = sprintf('%02d', $day); + $prevMonth = "{$prev['year']}-{$prev['month']}"; + $currMonth = "{$year}-{$month}"; + $items[] = [ + 'date' => $date, + $currMonth => $currRefunds[$date] ?? 0, + $prevMonth => $prevRefunds[$date] ?? 0, + ]; + } + + return $items; + } + + public function registeredUsers() + { + $year = $this->request->getQuery('year', 'int', date('Y')); + $month = $this->request->getQuery('month', 'int', date('m')); + + $prev = $this->getPrevMonth($year, $month); + $currUsers = $this->handleRegisteredUsers($year, $month); + $prevUsers = $this->handleRegisteredUsers($prev['year'], $prev['month']); + + $items = []; + + foreach (range(1, 31) as $day) { + $date = sprintf('%02d', $day); + $prevMonth = "{$prev['year']}-{$prev['month']}"; + $currMonth = "{$year}-{$month}"; + $items[] = [ + 'date' => $date, + $currMonth => $currUsers[$date] ?? 0, + $prevMonth => $prevUsers[$date] ?? 0, + ]; + } + + return $items; + } + + public function onlineUsers() + { + $year = $this->request->getQuery('year', 'int', date('Y')); + $month = $this->request->getQuery('month', 'int', date('m')); + + $prev = $this->getPrevMonth($year, $month); + $currUsers = $this->handleOnlineUsers($year, $month); + $prevUsers = $this->handleOnlineUsers($prev['year'], $prev['month']); + + $items = []; + + foreach (range(1, 31) as $day) { + $date = sprintf('%02d', $day); + $prevMonth = "{$prev['year']}-{$prev['month']}"; + $currMonth = "{$year}-{$month}"; + $items[] = [ + 'date' => $date, + $currMonth => $currUsers[$date] ?? 0, + $prevMonth => $prevUsers[$date] ?? 0, + ]; + } + + return $items; + } + + public function getYearOptions() + { + $end = date('Y'); + + $start = $end - 3; + + return range($start, $end); + } + + public function getMonthOptions() + { + $options = []; + + foreach (range(1, 12) as $value) { + $options[] = sprintf('%02d', $value); + } + return $options; + } + + protected function isCurrMonth($year, $month) + { + return date('Y-m') == "{$year}-{$month}"; + } + + protected function getPrevMonth($year, $month) + { + $currentMonthTime = strtotime("{$year}-{$month}"); + + $prevMonthTime = strtotime('-1 month', $currentMonthTime); + + return [ + 'year' => date('Y', $prevMonthTime), + 'month' => date('m', $prevMonthTime), + ]; + } + + protected function getMonthDates($year, $month) + { + $startTime = strtotime("{$year}-{$month}-01"); + + $days = date('t', $startTime); + + $result = []; + + foreach (range(1, $days) as $day) { + $result[] = sprintf('%04d-%02d-%02d', $year, $month, $day); + } + + return $result; + } + + protected function handleHotSales($type, $year, $month) + { + $keyName = "stat_hot_sales:{$type}_{$year}_{$month}"; + + $cache = $this->getCache(); + + $items = $cache->get($keyName); + + if (!$items) { + + $statRepo = new StatRepo(); + + $orders = $statRepo->findMonthlyOrders($type, $year, $month); + + $items = []; + + if ($orders->count() > 0) { + + foreach ($orders as $order) { + $key = $order->item_id; + if (!isset($items[$key])) { + $items[$key] = [ + 'title' => $order->subject, + 'total_count' => 1, + 'total_amount' => $order->amount, + ]; + } else { + $items[$key]['total_count'] += 1; + $items[$key]['total_amount'] += $order->amount; + } + } + + $totalCount = array_column($items, 'total_count'); + + array_multisort($totalCount, SORT_DESC, $items); + } + + $queryMonth = "{$year}-{$month}"; + + $currMonth = date('Y-m'); + + if ($queryMonth < $currMonth) { + $cache->save($keyName, $items, 7 * 86400); + } else { + $cache->save($keyName, $items, 2 * 3600); + } + } + + return $items; + } + + protected function handleSales($year, $month) + { + $keyName = "stat_sales:{$year}_{$month}"; + + $redis = $this->getRedis(); + + $list = $redis->hGetAll($keyName); + + $statRepo = new StatRepo(); + + $currDate = date('Y-m-d'); + $currDay = date('d'); + + if (!$list) { + $dates = $this->getMonthDates($year, $month); + foreach ($dates as $date) { + $key = substr($date, -2); + if ($date < $currDate) { + $list[$key] = $statRepo->sumDailySales($date); + } elseif ($date == $currDate) { + $list[$key] = -999; + } else { + $list[$key] = 0; + } + } + $redis->hMSet($keyName, $list); + $redis->expire($keyName, 7 * 86400); + } + + foreach ($list as $key => $value) { + if ($value < 0) { + $list[$key] = $statRepo->sumDailySales("{$year}-{$month}-{$key}"); + $redis->hSet($keyName, $key, $list[$key]); + } + } + + if ($this->isCurrMonth($year, $month)) { + $list[$currDay] = $statRepo->sumDailySales($currDate); + } + + return $list; + } + + protected function handleRefunds($year, $month) + { + $keyName = "stat_refunds:{$year}_{$month}"; + + $redis = $this->getRedis(); + + $list = $redis->hGetAll($keyName); + + $statRepo = new StatRepo(); + + $currDate = date('Y-m-d'); + $currDay = date('d'); + + if (!$list) { + $dates = $this->getMonthDates($year, $month); + foreach ($dates as $date) { + $key = substr($date, -2); + if ($date < $currDate) { + $list[$key] = $statRepo->sumDailyRefunds($date); + } elseif ($date == $currDate) { + $list[$key] = -999; + } else { + $list[$key] = 0; + } + } + $redis->hMSet($keyName, $list); + $redis->expire($keyName, 7 * 86400); + } + + foreach ($list as $key => $value) { + if ($value < 0) { + $list[$key] = $statRepo->sumDailyRefunds("{$year}-{$month}-{$key}"); + $redis->hSet($keyName, $key, $list[$key]); + } + } + + if ($this->isCurrMonth($year, $month)) { + $list[$currDay] = $statRepo->sumDailyRefunds($currDate); + } + + return $list; + } + + protected function handleRegisteredUsers($year, $month) + { + $keyName = "stat_reg_users:{$year}_{$month}"; + + $redis = $this->getRedis(); + + $list = $redis->hGetAll($keyName); + + $statRepo = new StatRepo(); + + $currDate = date('Y-m-d'); + $currDay = date('d'); + + if (!$list) { + $dates = $this->getMonthDates($year, $month); + foreach ($dates as $date) { + $key = substr($date, -2); + if ($date < $currDate) { + $list[$key] = $statRepo->countDailyRegisteredUser($date); + } elseif ($date == $currDate) { + $list[$key] = -999; + } else { + $list[$key] = 0; + } + } + $redis->hMSet($keyName, $list); + $redis->expire($keyName, 7 * 86400); + } + + foreach ($list as $key => $value) { + if ($value < 0) { + $list[$key] = $statRepo->countDailyRegisteredUser("{$year}-{$month}-{$key}"); + $redis->hSet($keyName, $key, $list[$key]); + } + } + + if ($this->isCurrMonth($year, $month)) { + $list[$currDay] = $statRepo->countDailyRegisteredUser($currDate); + } + + return $list; + } + + protected function handleOnlineUsers($year, $month) + { + $keyName = "stat_online_users:{$year}_{$month}"; + + $redis = $this->getRedis(); + + $list = $redis->hGetAll($keyName); + + $statRepo = new StatRepo(); + + $currDate = date('Y-m-d'); + $currDay = date('d'); + + if (!$list) { + $dates = $this->getMonthDates($year, $month); + foreach ($dates as $date) { + $key = substr($date, -2); + if ($date < $currDate) { + $list[$key] = $statRepo->countDailyOnlineUser($date); + } elseif ($date == $currDate) { + $list[$key] = -999; + } else { + $list[$key] = 0; + } + } + $redis->hMSet($keyName, $list); + $redis->expire($keyName, 7 * 86400); + } + + foreach ($list as $key => $value) { + if ($value < 0) { + $list[$key] = $statRepo->countDailyOnlineUser("{$year}-{$month}-{$key}"); + $redis->hSet($keyName, $key, $list[$key]); + } + } + + if ($this->isCurrMonth($year, $month)) { + $list[$currDay] = $statRepo->countDailyOnlineUser($currDate); + } + + return $list; + } + +} diff --git a/app/Http/Admin/Views/stat/hot_sales.volt b/app/Http/Admin/Views/stat/hot_sales.volt new file mode 100644 index 0000000000000000000000000000000000000000..124769851246de04a7e891fcb95d9f2b73f5ae66 --- /dev/null +++ b/app/Http/Admin/Views/stat/hot_sales.volt @@ -0,0 +1,101 @@ +{% extends 'templates/main.volt' %} + +{% block content %} + + {%- macro show_sales(sales) %} + + + + + + + + + + + + + + + + + {% for sale in sales %} + + + + + + + {% endfor %} + +
排序名称数量金额
{{ loop.index }}{{ sale.title }}{{ sale.total_count }}{{ '¥%0.2f'|format(sale.total_amount) }}
+ {%- endmacro %} + + {% set types = {'1':'课程','2':'套餐','3':'赞赏','4':'会员'} %} + {% set year = request.get('year','int',date('Y')) %} + {% set month = request.get('month','int',date('m')) %} + {% set type = request.get('type','int',1) %} + +
+
+ + 热卖商品统计 + +
+
+ +
+
+ +
+ +
+ +
+ +
+ +
+ +
+
+ +
+
+
+ +
+ {% for item in items %} +
+
+
{{ item.title }}
+
{{ show_sales(item.sales) }}
+
+
+ {% endfor %} +
+ +{% endblock %} + +{% block inline_css %} + + + +{% endblock %} \ No newline at end of file diff --git a/app/Http/Admin/Views/stat/online_users.volt b/app/Http/Admin/Views/stat/online_users.volt new file mode 100644 index 0000000000000000000000000000000000000000..68e18b329e6ee5ec3aaf27990fdd87c4bfb330d3 --- /dev/null +++ b/app/Http/Admin/Views/stat/online_users.volt @@ -0,0 +1,74 @@ +{% extends 'templates/main.volt' %} + +{% block content %} + + {% set year = request.get('year','int',date('Y')) %} + {% set month = request.get('month','int',date('m')) %} + +
+
+ + 活跃用户统计 + +
+
+ +
+
+ +
+ +
+ +
+ +
+
+ +
+
+
+ +
+ +{% endblock %} + +{% block include_js %} + + {{ js_include('https://cdn.bootcdn.net/ajax/libs/echarts/4.8.0/echarts.min.js', false) }} + +{% endblock %} + +{% block inline_js %} + + + +{% endblock %} \ No newline at end of file diff --git a/app/Http/Admin/Views/stat/refunds.volt b/app/Http/Admin/Views/stat/refunds.volt new file mode 100644 index 0000000000000000000000000000000000000000..6c25c75f19196e4c86b23e86d59cba558c6cc079 --- /dev/null +++ b/app/Http/Admin/Views/stat/refunds.volt @@ -0,0 +1,74 @@ +{% extends 'templates/main.volt' %} + +{% block content %} + + {% set year = request.get('year','int',date('Y')) %} + {% set month = request.get('month','int',date('m')) %} + +
+
+ + 售后退款统计 + +
+
+ +
+
+ +
+ +
+ +
+ +
+
+ +
+
+
+ +
+ +{% endblock %} + +{% block include_js %} + + {{ js_include('https://cdn.bootcdn.net/ajax/libs/echarts/4.8.0/echarts.min.js', false) }} + +{% endblock %} + +{% block inline_js %} + + + +{% endblock %} \ No newline at end of file diff --git a/app/Http/Admin/Views/stat/registered_users.volt b/app/Http/Admin/Views/stat/registered_users.volt new file mode 100644 index 0000000000000000000000000000000000000000..1fd7cec0ae528e1329acaf9a6d0ecccfcb9d17c2 --- /dev/null +++ b/app/Http/Admin/Views/stat/registered_users.volt @@ -0,0 +1,74 @@ +{% extends 'templates/main.volt' %} + +{% block content %} + + {% set year = request.get('year','int',date('Y')) %} + {% set month = request.get('month','int',date('m')) %} + +
+
+ + 注册用户统计 + +
+
+ +
+
+ +
+ +
+ +
+ +
+
+ +
+
+
+ +
+ +{% endblock %} + +{% block include_js %} + + {{ js_include('https://cdn.bootcdn.net/ajax/libs/echarts/4.8.0/echarts.min.js', false) }} + +{% endblock %} + +{% block inline_js %} + + + +{% endblock %} \ No newline at end of file diff --git a/app/Http/Admin/Views/stat/sales.volt b/app/Http/Admin/Views/stat/sales.volt new file mode 100644 index 0000000000000000000000000000000000000000..d2eb18ba9b216d592565c547c1192ac9062cd0d9 --- /dev/null +++ b/app/Http/Admin/Views/stat/sales.volt @@ -0,0 +1,74 @@ +{% extends 'templates/main.volt' %} + +{% block content %} + + {% set year = request.get('year','int',date('Y')) %} + {% set month = request.get('month','int',date('m')) %} + +
+
+ + 成交订单统计 + +
+
+ +
+
+ +
+ +
+ +
+ +
+
+ +
+
+
+ +
+ +{% endblock %} + +{% block include_js %} + + {{ js_include('https://cdn.bootcdn.net/ajax/libs/echarts/4.8.0/echarts.min.js', false) }} + +{% endblock %} + +{% block inline_js %} + + + +{% endblock %} \ No newline at end of file diff --git a/app/Listeners/User.php b/app/Listeners/User.php new file mode 100644 index 0000000000000000000000000000000000000000..4d60f7a5dd838d245b8fe92b475d7b789aef0550 --- /dev/null +++ b/app/Listeners/User.php @@ -0,0 +1,52 @@ +active_time > 600) { + + $user->active_time = $now; + + $user->update(); + + $onlineRepo = new OnlineRepo(); + + $online = $onlineRepo->findByUserDate($user->id, date('Y-m-d')); + + if ($online) { + + $online->active_time = $now; + $online->client_type = $this->getClientType(); + $online->client_ip = $this->getClientIp(); + + $online->update(); + + } else { + + $online = new OnlineModel(); + + $online->user_id = $user->id; + $online->active_time = $now; + $online->client_type = $this->getClientType(); + $online->client_ip = $this->getClientIp(); + + $online->create(); + } + } + } + +} \ No newline at end of file diff --git a/app/Models/Online.php b/app/Models/Online.php new file mode 100644 index 0000000000000000000000000000000000000000..8d181b09dc64df401b76cf5c307bf7fa2bc04375 --- /dev/null +++ b/app/Models/Online.php @@ -0,0 +1,79 @@ +create_time = time(); + } + + public function beforeUpdate() + { + $this->update_time = time(); + } + +} diff --git a/app/Repos/Audit.php b/app/Repos/Audit.php index 0cd111691fb78bfc3fb569578d0b3bf2480066c8..98dda9ec62149967f5978a23a002061309bf829f 100644 --- a/app/Repos/Audit.php +++ b/app/Repos/Audit.php @@ -63,7 +63,7 @@ class Audit extends Repository } /** - * @param string $id + * @param int $id * @return AuditModel|Model|bool */ public function findById($id) diff --git a/app/Repos/Online.php b/app/Repos/Online.php new file mode 100644 index 0000000000000000000000000000000000000000..1d833cbf7522e4e49af4d88938a21695bdc1b507 --- /dev/null +++ b/app/Repos/Online.php @@ -0,0 +1,30 @@ + 'user_id = ?1 AND active_time BETWEEN ?2 AND ?3', + 'bind' => [ + 1 => $userId, + 2 => $activeTime, + 3 => $activeTime + 86400, + ], + ]); + } + +} \ No newline at end of file diff --git a/app/Repos/Stat.php b/app/Repos/Stat.php new file mode 100644 index 0000000000000000000000000000000000000000..bb5731ceb36075079dddd97060ede81812fb949c --- /dev/null +++ b/app/Repos/Stat.php @@ -0,0 +1,139 @@ + 'create_time BETWEEN :start_time: AND :end_time:', + 'bind' => ['start_time' => $startTime, 'end_time' => $endTime], + ]); + } + + public function countDailyOnlineUser($date) + { + $startTime = strtotime($date); + + $endTime = $startTime + 86400; + + return (int)OnlineModel::count([ + 'conditions' => 'active_time BETWEEN :start_time: AND :end_time:', + 'bind' => ['start_time' => $startTime, 'end_time' => $endTime], + ]); + } + + public function countDailySales($date) + { + $sql = "SELECT count(*) AS total_count FROM %s AS os JOIN %s AS o ON os.order_id = o.id "; + + $sql .= "WHERE os.status = ?1 AND o.create_time BETWEEN ?2 AND ?3"; + + $phql = sprintf($sql, OrderStatusModel::class, OrderModel::class); + + $startTime = strtotime($date); + + $endTime = $startTime + 86400; + + $result = $this->modelsManager->executeQuery($phql, [ + 1 => OrderModel::STATUS_FINISHED, + 2 => $startTime, + 3 => $endTime, + ]); + + return (float)$result[0]['total_count']; + } + + public function countDailyRefunds($date) + { + $startTime = strtotime($date); + + $endTime = $startTime + 86400; + + return (int)RefundModel::count([ + 'conditions' => 'status = ?1 AND create_time BETWEEN ?2 AND ?3', + 'bind' => [ + 1 => RefundModel::STATUS_FINISHED, + 2 => $startTime, + 3 => $endTime, + ], + ]); + } + + public function sumDailySales($date) + { + $sql = "SELECT sum(o.amount) AS total_amount FROM %s AS os JOIN %s AS o ON os.order_id = o.id "; + + $sql .= "WHERE os.status = ?1 AND o.create_time BETWEEN ?2 AND ?3"; + + $phql = sprintf($sql, OrderStatusModel::class, OrderModel::class); + + $startTime = strtotime($date); + + $endTime = $startTime + 86400; + + $result = $this->modelsManager->executeQuery($phql, [ + 1 => OrderModel::STATUS_FINISHED, + 2 => $startTime, + 3 => $endTime, + ]); + + return (float)$result[0]['total_amount']; + } + + public function sumDailyRefunds($date) + { + $startTime = strtotime($date); + + $endTime = $startTime + 86400; + + return (float)RefundModel::sum([ + 'column' => 'amount', + 'conditions' => 'status = ?1 AND create_time BETWEEN ?2 AND ?3', + 'bind' => [ + 1 => RefundModel::STATUS_FINISHED, + 2 => $startTime, + 3 => $endTime, + ], + ]); + } + + /** + * @param int $type + * @param int $year + * @param int $month + * @return ResultsetInterface|Resultset|OrderModel[] + */ + public function findMonthlyOrders($type, $year, $month) + { + $startTime = strtotime("{$year}-{$month}"); + + $endTime = strtotime('+1 month', $startTime); + + $status = OrderModel::STATUS_FINISHED; + + return $this->modelsManager->createBuilder() + ->addFrom(OrderStatusModel::class, 'os') + ->join(OrderModel::class, 'os.order_id = o.id', 'o') + ->columns('o.*') + ->where('o.item_type = :type:', ['type' => $type]) + ->andWhere('os.status = :status:', ['status' => $status]) + ->betweenWhere('o.create_time', $startTime, $endTime) + ->getQuery()->execute(); + } + +} diff --git a/app/Traits/Auth.php b/app/Traits/Auth.php index 57d71e7d69dac749792b6fe1dbfe18f59534952a..1524fa9082b7c5d60d9553aa81b81f617612d7f3 100644 --- a/app/Traits/Auth.php +++ b/app/Traits/Auth.php @@ -6,7 +6,8 @@ use App\Models\User as UserModel; use App\Repos\User as UserRepo; use App\Services\Auth as AuthService; use App\Validators\Validator as AppValidator; -use Phalcon\Di; +use Phalcon\Di as Di; +use Phalcon\Events\Manager as EventsManager; trait Auth { @@ -24,7 +25,16 @@ trait Auth $userRepo = new UserRepo(); - return $userRepo->findById($authUser['id']); + $user = $userRepo->findById($authUser['id']); + + /** + * @var EventsManager $eventsManager + */ + $eventsManager = Di::getDefault()->getShared('eventsManager'); + + $eventsManager->fire('user:online', $this, $user); + + return $user; } /** @@ -40,13 +50,7 @@ trait Auth $userRepo = new UserRepo(); - $user = $userRepo->findById($authUser['id']); - - if (time() - $user->active_time > 600) { - $user->update(['active_time' => time()]); - } - - return $user; + return $userRepo->findById($authUser['id']); } /** diff --git a/config/events.php b/config/events.php index c4628c05bda5d331349125383ad5253a425dbe6f..f42117792f4d591e82184d1aa72a1c89acc3c305 100644 --- a/config/events.php +++ b/config/events.php @@ -1,9 +1,11 @@ Pay::class, + 'user' => User::class, 'userDailyCounter' => UserDailyCounter::class, ]; \ No newline at end of file diff --git a/db/migrations/20200827063842_init_table.php b/db/migrations/20200827063842_init_table.php index f11f2b42a54f0448b0af646d1d5481fb19b1949c..dc9c703cd1a4ff858d968d05c2a1dafad7b7a48b 100644 --- a/db/migrations/20200827063842_init_table.php +++ b/db/migrations/20200827063842_init_table.php @@ -2517,7 +2517,7 @@ class InitTable extends Phinx\Migration\AbstractMigration ->addColumn('client_ip', 'string', [ 'null' => false, 'default' => '', - 'limit' => 30, + 'limit' => 64, 'collation' => 'utf8mb4_general_ci', 'encoding' => 'utf8mb4', 'comment' => '终端IP', diff --git a/db/migrations/20201004095647_create_online_table.php b/db/migrations/20201004095647_create_online_table.php new file mode 100644 index 0000000000000000000000000000000000000000..9a5e64facba828c0f030c5af1bef0fb05bb47c37 --- /dev/null +++ b/db/migrations/20201004095647_create_online_table.php @@ -0,0 +1,107 @@ +table('kg_online', [ + 'id' => false, + 'primary_key' => ['id'], + 'engine' => 'InnoDB', + 'encoding' => 'utf8mb4', + 'collation' => 'utf8mb4_general_ci', + 'comment' => '', + 'row_format' => 'DYNAMIC', + ]) + ->addColumn('id', 'integer', [ + 'null' => false, + 'limit' => MysqlAdapter::INT_REGULAR, + 'identity' => 'enable', + 'comment' => '主键编号', + ]) + ->addColumn('user_id', 'integer', [ + 'null' => false, + 'default' => '0', + 'limit' => MysqlAdapter::INT_REGULAR, + 'comment' => '用户编号', + 'after' => 'id', + ]) + ->addColumn('client_type', 'integer', [ + 'null' => false, + 'default' => '1', + 'limit' => MysqlAdapter::INT_REGULAR, + 'comment' => '终端类型', + 'after' => 'user_id', + ]) + ->addColumn('client_ip', 'string', [ + 'null' => false, + 'default' => '', + 'limit' => 64, + 'collation' => 'utf8mb4_general_ci', + 'encoding' => 'utf8mb4', + 'comment' => '终端IP', + 'after' => 'client_type', + ]) + ->addColumn('active_time', 'integer', [ + 'null' => false, + 'default' => '0', + 'limit' => MysqlAdapter::INT_REGULAR, + 'comment' => '活跃时间', + 'after' => 'client_ip', + ]) + ->addColumn('create_time', 'integer', [ + 'null' => false, + 'default' => '0', + 'limit' => MysqlAdapter::INT_REGULAR, + 'comment' => '创建时间', + 'after' => 'active_time', + ]) + ->addColumn('update_time', 'integer', [ + 'null' => false, + 'default' => '0', + 'limit' => MysqlAdapter::INT_REGULAR, + 'comment' => '更新时间', + 'after' => 'create_time', + ]) + ->addIndex(['active_time'], [ + 'name' => 'active_time', + 'unique' => false, + ]) + ->addIndex(['user_id'], [ + 'name' => 'user_id', + 'unique' => false, + ]) + ->create(); + $this->table('kg_task', [ + 'id' => false, + 'primary_key' => ['id'], + 'engine' => 'InnoDB', + 'encoding' => 'utf8mb4', + 'collation' => 'utf8mb4_general_ci', + 'comment' => '', + 'row_format' => 'DYNAMIC', + ]) + ->save(); + $this->table('kg_trade', [ + 'id' => false, + 'primary_key' => ['id'], + 'engine' => 'InnoDB', + 'encoding' => 'utf8mb4', + 'collation' => 'utf8mb4_general_ci', + 'comment' => '', + 'row_format' => 'DYNAMIC', + ]) + ->changeColumn('channel_sn', 'string', [ + 'null' => false, + 'default' => '', + 'limit' => 64, + 'collation' => 'utf8mb4_general_ci', + 'encoding' => 'utf8mb4', + 'comment' => '平台序号', + 'after' => 'channel', + ]) + ->save(); + } +} diff --git a/db/migrations/schema.php b/db/migrations/schema.php index b43fdd2886639c4c3c62a5e8ab752bb7dae0dc36..6e7477b4237c4935ca5065373caf750b60c14b95 100644 --- a/db/migrations/schema.php +++ b/db/migrations/schema.php @@ -8822,14 +8822,14 @@ return array( 'COLUMN_DEFAULT' => '', 'IS_NULLABLE' => 'NO', 'DATA_TYPE' => 'varchar', - 'CHARACTER_MAXIMUM_LENGTH' => '30', - 'CHARACTER_OCTET_LENGTH' => '120', + 'CHARACTER_MAXIMUM_LENGTH' => '64', + 'CHARACTER_OCTET_LENGTH' => '256', 'NUMERIC_PRECISION' => NULL, 'NUMERIC_SCALE' => NULL, 'DATETIME_PRECISION' => NULL, 'CHARACTER_SET_NAME' => 'utf8mb4', 'COLLATION_NAME' => 'utf8mb4_general_ci', - 'COLUMN_TYPE' => 'varchar(30)', + 'COLUMN_TYPE' => 'varchar(64)', 'COLUMN_KEY' => '', 'EXTRA' => '', 'PRIVILEGES' => 'select,insert,update,references', @@ -9514,6 +9514,247 @@ return array( ), 'foreign_keys' => NULL, ), + 'kg_online' => + array( + 'table' => + array( + 'table_name' => 'kg_online', + 'engine' => 'InnoDB', + 'table_comment' => '', + 'table_collation' => 'utf8mb4_general_ci', + 'character_set_name' => 'utf8mb4', + 'row_format' => 'Dynamic', + ), + 'columns' => + array( + 'id' => + array( + 'TABLE_CATALOG' => 'def', + 'TABLE_NAME' => 'kg_online', + 'COLUMN_NAME' => 'id', + 'ORDINAL_POSITION' => '1', + 'COLUMN_DEFAULT' => NULL, + 'IS_NULLABLE' => 'NO', + 'DATA_TYPE' => 'int', + 'CHARACTER_MAXIMUM_LENGTH' => NULL, + 'CHARACTER_OCTET_LENGTH' => NULL, + 'NUMERIC_PRECISION' => '10', + 'NUMERIC_SCALE' => '0', + 'DATETIME_PRECISION' => NULL, + 'CHARACTER_SET_NAME' => NULL, + 'COLLATION_NAME' => NULL, + 'COLUMN_TYPE' => 'int', + 'COLUMN_KEY' => 'PRI', + 'EXTRA' => 'auto_increment', + 'PRIVILEGES' => 'select,insert,update,references', + 'COLUMN_COMMENT' => '主键编号', + 'GENERATION_EXPRESSION' => '', + 'SRS_ID' => NULL, + ), + 'user_id' => + array( + 'TABLE_CATALOG' => 'def', + 'TABLE_NAME' => 'kg_online', + 'COLUMN_NAME' => 'user_id', + 'ORDINAL_POSITION' => '2', + 'COLUMN_DEFAULT' => '0', + 'IS_NULLABLE' => 'NO', + 'DATA_TYPE' => 'int', + 'CHARACTER_MAXIMUM_LENGTH' => NULL, + 'CHARACTER_OCTET_LENGTH' => NULL, + 'NUMERIC_PRECISION' => '10', + 'NUMERIC_SCALE' => '0', + 'DATETIME_PRECISION' => NULL, + 'CHARACTER_SET_NAME' => NULL, + 'COLLATION_NAME' => NULL, + 'COLUMN_TYPE' => 'int unsigned', + 'COLUMN_KEY' => 'MUL', + 'EXTRA' => '', + 'PRIVILEGES' => 'select,insert,update,references', + 'COLUMN_COMMENT' => '用户编号', + 'GENERATION_EXPRESSION' => '', + 'SRS_ID' => NULL, + ), + 'client_type' => + array( + 'TABLE_CATALOG' => 'def', + 'TABLE_NAME' => 'kg_online', + 'COLUMN_NAME' => 'client_type', + 'ORDINAL_POSITION' => '3', + 'COLUMN_DEFAULT' => '1', + 'IS_NULLABLE' => 'NO', + 'DATA_TYPE' => 'int', + 'CHARACTER_MAXIMUM_LENGTH' => NULL, + 'CHARACTER_OCTET_LENGTH' => NULL, + 'NUMERIC_PRECISION' => '10', + 'NUMERIC_SCALE' => '0', + 'DATETIME_PRECISION' => NULL, + 'CHARACTER_SET_NAME' => NULL, + 'COLLATION_NAME' => NULL, + 'COLUMN_TYPE' => 'int unsigned', + 'COLUMN_KEY' => '', + 'EXTRA' => '', + 'PRIVILEGES' => 'select,insert,update,references', + 'COLUMN_COMMENT' => '终端类型', + 'GENERATION_EXPRESSION' => '', + 'SRS_ID' => NULL, + ), + 'client_ip' => + array( + 'TABLE_CATALOG' => 'def', + 'TABLE_NAME' => 'kg_online', + 'COLUMN_NAME' => 'client_ip', + 'ORDINAL_POSITION' => '4', + 'COLUMN_DEFAULT' => '', + 'IS_NULLABLE' => 'NO', + 'DATA_TYPE' => 'varchar', + 'CHARACTER_MAXIMUM_LENGTH' => '64', + 'CHARACTER_OCTET_LENGTH' => '256', + 'NUMERIC_PRECISION' => NULL, + 'NUMERIC_SCALE' => NULL, + 'DATETIME_PRECISION' => NULL, + 'CHARACTER_SET_NAME' => 'utf8mb4', + 'COLLATION_NAME' => 'utf8mb4_general_ci', + 'COLUMN_TYPE' => 'varchar(64)', + 'COLUMN_KEY' => '', + 'EXTRA' => '', + 'PRIVILEGES' => 'select,insert,update,references', + 'COLUMN_COMMENT' => '终端IP', + 'GENERATION_EXPRESSION' => '', + 'SRS_ID' => NULL, + ), + 'active_time' => + array( + 'TABLE_CATALOG' => 'def', + 'TABLE_NAME' => 'kg_online', + 'COLUMN_NAME' => 'active_time', + 'ORDINAL_POSITION' => '5', + 'COLUMN_DEFAULT' => '0', + 'IS_NULLABLE' => 'NO', + 'DATA_TYPE' => 'int', + 'CHARACTER_MAXIMUM_LENGTH' => NULL, + 'CHARACTER_OCTET_LENGTH' => NULL, + 'NUMERIC_PRECISION' => '10', + 'NUMERIC_SCALE' => '0', + 'DATETIME_PRECISION' => NULL, + 'CHARACTER_SET_NAME' => NULL, + 'COLLATION_NAME' => NULL, + 'COLUMN_TYPE' => 'int unsigned', + 'COLUMN_KEY' => 'MUL', + 'EXTRA' => '', + 'PRIVILEGES' => 'select,insert,update,references', + 'COLUMN_COMMENT' => '活跃时间', + 'GENERATION_EXPRESSION' => '', + 'SRS_ID' => NULL, + ), + 'create_time' => + array( + 'TABLE_CATALOG' => 'def', + 'TABLE_NAME' => 'kg_online', + 'COLUMN_NAME' => 'create_time', + 'ORDINAL_POSITION' => '6', + 'COLUMN_DEFAULT' => '0', + 'IS_NULLABLE' => 'NO', + 'DATA_TYPE' => 'int', + 'CHARACTER_MAXIMUM_LENGTH' => NULL, + 'CHARACTER_OCTET_LENGTH' => NULL, + 'NUMERIC_PRECISION' => '10', + 'NUMERIC_SCALE' => '0', + 'DATETIME_PRECISION' => NULL, + 'CHARACTER_SET_NAME' => NULL, + 'COLLATION_NAME' => NULL, + 'COLUMN_TYPE' => 'int unsigned', + 'COLUMN_KEY' => '', + 'EXTRA' => '', + 'PRIVILEGES' => 'select,insert,update,references', + 'COLUMN_COMMENT' => '创建时间', + 'GENERATION_EXPRESSION' => '', + 'SRS_ID' => NULL, + ), + 'update_time' => + array( + 'TABLE_CATALOG' => 'def', + 'TABLE_NAME' => 'kg_online', + 'COLUMN_NAME' => 'update_time', + 'ORDINAL_POSITION' => '7', + 'COLUMN_DEFAULT' => '0', + 'IS_NULLABLE' => 'NO', + 'DATA_TYPE' => 'int', + 'CHARACTER_MAXIMUM_LENGTH' => NULL, + 'CHARACTER_OCTET_LENGTH' => NULL, + 'NUMERIC_PRECISION' => '10', + 'NUMERIC_SCALE' => '0', + 'DATETIME_PRECISION' => NULL, + 'CHARACTER_SET_NAME' => NULL, + 'COLLATION_NAME' => NULL, + 'COLUMN_TYPE' => 'int unsigned', + 'COLUMN_KEY' => '', + 'EXTRA' => '', + 'PRIVILEGES' => 'select,insert,update,references', + 'COLUMN_COMMENT' => '更新时间', + 'GENERATION_EXPRESSION' => '', + 'SRS_ID' => NULL, + ), + ), + 'indexes' => + array( + 'active_time' => + array( + 1 => + array( + 'Table' => 'kg_online', + 'Non_unique' => '1', + 'Key_name' => 'active_time', + 'Seq_in_index' => '1', + 'Column_name' => 'active_time', + 'Collation' => 'A', + 'Sub_part' => NULL, + 'Packed' => NULL, + 'Null' => '', + 'Index_type' => 'BTREE', + 'Comment' => '', + 'Index_comment' => '', + ), + ), + 'PRIMARY' => + array( + 1 => + array( + 'Table' => 'kg_online', + 'Non_unique' => '0', + 'Key_name' => 'PRIMARY', + 'Seq_in_index' => '1', + 'Column_name' => 'id', + 'Collation' => 'A', + 'Sub_part' => NULL, + 'Packed' => NULL, + 'Null' => '', + 'Index_type' => 'BTREE', + 'Comment' => '', + 'Index_comment' => '', + ), + ), + 'user_id' => + array( + 1 => + array( + 'Table' => 'kg_online', + 'Non_unique' => '1', + 'Key_name' => 'user_id', + 'Seq_in_index' => '1', + 'Column_name' => 'user_id', + 'Collation' => 'A', + 'Sub_part' => NULL, + 'Packed' => NULL, + 'Null' => '', + 'Index_type' => 'BTREE', + 'Comment' => '', + 'Index_comment' => '', + ), + ), + ), + 'foreign_keys' => NULL, + ), 'kg_order' => array( 'table' => @@ -9752,14 +9993,14 @@ return array( 'COLUMN_DEFAULT' => '', 'IS_NULLABLE' => 'NO', 'DATA_TYPE' => 'varchar', - 'CHARACTER_MAXIMUM_LENGTH' => '30', - 'CHARACTER_OCTET_LENGTH' => '120', + 'CHARACTER_MAXIMUM_LENGTH' => '64', + 'CHARACTER_OCTET_LENGTH' => '256', 'NUMERIC_PRECISION' => NULL, 'NUMERIC_SCALE' => NULL, 'DATETIME_PRECISION' => NULL, 'CHARACTER_SET_NAME' => 'utf8mb4', 'COLLATION_NAME' => 'utf8mb4_general_ci', - 'COLUMN_TYPE' => 'varchar(30)', + 'COLUMN_TYPE' => 'varchar(64)', 'COLUMN_KEY' => '', 'EXTRA' => '', 'PRIVILEGES' => 'select,insert,update,references', @@ -12811,7 +13052,7 @@ return array( 'CHARACTER_SET_NAME' => NULL, 'COLLATION_NAME' => NULL, 'COLUMN_TYPE' => 'int', - 'COLUMN_KEY' => 'MUL', + 'COLUMN_KEY' => '', 'EXTRA' => '', 'PRIVILEGES' => 'select,insert,update,references', 'COLUMN_COMMENT' => '', @@ -13429,14 +13670,14 @@ return array( 'COLUMN_DEFAULT' => '', 'IS_NULLABLE' => 'NO', 'DATA_TYPE' => 'varchar', - 'CHARACTER_MAXIMUM_LENGTH' => '32', - 'CHARACTER_OCTET_LENGTH' => '128', + 'CHARACTER_MAXIMUM_LENGTH' => '64', + 'CHARACTER_OCTET_LENGTH' => '256', 'NUMERIC_PRECISION' => NULL, 'NUMERIC_SCALE' => NULL, 'DATETIME_PRECISION' => NULL, 'CHARACTER_SET_NAME' => 'utf8mb4', 'COLLATION_NAME' => 'utf8mb4_general_ci', - 'COLUMN_TYPE' => 'varchar(32)', + 'COLUMN_TYPE' => 'varchar(64)', 'COLUMN_KEY' => '', 'EXTRA' => '', 'PRIVILEGES' => 'select,insert,update,references', diff --git a/public/static/admin/css/common.css b/public/static/admin/css/common.css index 270217cc8abbef0bf751cea2943ab485e2fde6d6..23231fefd2d120c4e1f3a39c35e7a1ca1fcce4e1 100644 --- a/public/static/admin/css/common.css +++ b/public/static/admin/css/common.css @@ -258,3 +258,20 @@ img.kg-qrcode { font-size: 20px; color: rgb(0, 150, 136); } + +.kg-search-form { + margin-bottom: 30px; +} + +.kg-search-form .layui-form-label { + width: auto; +} + +.kg-search-form .layui-input-inline:last-child { + margin-left: 10px; +} + +.kg-chart { + width: 100%; + height: 480px; +}