|
|
@@ -0,0 +1,275 @@
|
|
|
+<?php
|
|
|
+
|
|
|
+namespace App\Module\Transfer\Commands;
|
|
|
+
|
|
|
+use App\Module\Transfer\Models\TransferOrder;
|
|
|
+use App\Module\Transfer\Models\TransferApp;
|
|
|
+use App\Module\Transfer\Enums\TransferStatus;
|
|
|
+use App\Module\Transfer\Enums\TransferType;
|
|
|
+use Illuminate\Console\Command;
|
|
|
+use Illuminate\Support\Facades\DB;
|
|
|
+
|
|
|
+/**
|
|
|
+ * Transfer统计命令
|
|
|
+ */
|
|
|
+class TransferStatsCommand extends Command
|
|
|
+{
|
|
|
+ /**
|
|
|
+ * 命令签名
|
|
|
+ */
|
|
|
+ protected $signature = 'transfer:stats
|
|
|
+ {--period=today : 统计周期 (today|yesterday|week|month)}
|
|
|
+ {--app= : 指定应用ID}
|
|
|
+ {--export= : 导出到文件}';
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 命令描述
|
|
|
+ */
|
|
|
+ protected $description = '显示Transfer模块的统计信息';
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 执行命令
|
|
|
+ */
|
|
|
+ public function handle(): int
|
|
|
+ {
|
|
|
+ $period = $this->option('period');
|
|
|
+ $appId = $this->option('app');
|
|
|
+ $exportFile = $this->option('export');
|
|
|
+
|
|
|
+ $this->info("Transfer模块统计报告 - {$period}");
|
|
|
+ $this->line(str_repeat('=', 60));
|
|
|
+
|
|
|
+ try {
|
|
|
+ // 获取时间范围
|
|
|
+ [$startDate, $endDate] = $this->getDateRange($period);
|
|
|
+
|
|
|
+ $this->info("统计时间: {$startDate} 至 {$endDate}");
|
|
|
+ $this->line('');
|
|
|
+
|
|
|
+ // 基础统计
|
|
|
+ $this->showBasicStats($startDate, $endDate, $appId);
|
|
|
+
|
|
|
+ // 按应用统计
|
|
|
+ $this->showAppStats($startDate, $endDate, $appId);
|
|
|
+
|
|
|
+ // 按状态统计
|
|
|
+ $this->showStatusStats($startDate, $endDate, $appId);
|
|
|
+
|
|
|
+ // 按小时统计(仅当日)
|
|
|
+ if ($period === 'today') {
|
|
|
+ $this->showHourlyStats($startDate, $endDate, $appId);
|
|
|
+ }
|
|
|
+
|
|
|
+ // 导出数据
|
|
|
+ if ($exportFile) {
|
|
|
+ $this->exportStats($startDate, $endDate, $appId, $exportFile);
|
|
|
+ }
|
|
|
+
|
|
|
+ return 0;
|
|
|
+
|
|
|
+ } catch (\Exception $e) {
|
|
|
+ $this->error("统计失败: " . $e->getMessage());
|
|
|
+ return 1;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 获取日期范围
|
|
|
+ */
|
|
|
+ private function getDateRange(string $period): array
|
|
|
+ {
|
|
|
+ return match ($period) {
|
|
|
+ 'today' => [
|
|
|
+ now()->startOfDay()->toDateTimeString(),
|
|
|
+ now()->endOfDay()->toDateTimeString()
|
|
|
+ ],
|
|
|
+ 'yesterday' => [
|
|
|
+ now()->subDay()->startOfDay()->toDateTimeString(),
|
|
|
+ now()->subDay()->endOfDay()->toDateTimeString()
|
|
|
+ ],
|
|
|
+ 'week' => [
|
|
|
+ now()->startOfWeek()->toDateTimeString(),
|
|
|
+ now()->endOfWeek()->toDateTimeString()
|
|
|
+ ],
|
|
|
+ 'month' => [
|
|
|
+ now()->startOfMonth()->toDateTimeString(),
|
|
|
+ now()->endOfMonth()->toDateTimeString()
|
|
|
+ ],
|
|
|
+ default => throw new \InvalidArgumentException("无效的统计周期: {$period}")
|
|
|
+ };
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 显示基础统计
|
|
|
+ */
|
|
|
+ private function showBasicStats(string $startDate, string $endDate, ?string $appId): void
|
|
|
+ {
|
|
|
+ $query = TransferOrder::whereBetween('created_at', [$startDate, $endDate]);
|
|
|
+
|
|
|
+ if ($appId) {
|
|
|
+ $query->where('transfer_app_id', $appId);
|
|
|
+ }
|
|
|
+
|
|
|
+ $totalOrders = $query->count();
|
|
|
+ $totalAmount = $query->sum('amount');
|
|
|
+
|
|
|
+ $inOrders = (clone $query)->where('type', TransferType::IN)->count();
|
|
|
+ $outOrders = (clone $query)->where('type', TransferType::OUT)->count();
|
|
|
+
|
|
|
+ $inAmount = (clone $query)->where('type', TransferType::IN)->sum('amount');
|
|
|
+ $outAmount = (clone $query)->where('type', TransferType::OUT)->sum('amount');
|
|
|
+
|
|
|
+ $completedOrders = (clone $query)->where('status', TransferStatus::COMPLETED)->count();
|
|
|
+ $failedOrders = (clone $query)->where('status', TransferStatus::FAILED)->count();
|
|
|
+
|
|
|
+ $this->info('基础统计:');
|
|
|
+ $this->table(
|
|
|
+ ['指标', '数值'],
|
|
|
+ [
|
|
|
+ ['总订单数', number_format($totalOrders)],
|
|
|
+ ['总金额', number_format($totalAmount, 2)],
|
|
|
+ ['转入订单', number_format($inOrders)],
|
|
|
+ ['转出订单', number_format($outOrders)],
|
|
|
+ ['转入金额', number_format($inAmount, 2)],
|
|
|
+ ['转出金额', number_format($outAmount, 2)],
|
|
|
+ ['成功订单', number_format($completedOrders)],
|
|
|
+ ['失败订单', number_format($failedOrders)],
|
|
|
+ ['成功率', $totalOrders > 0 ? number_format($completedOrders / $totalOrders * 100, 2) . '%' : '0%'],
|
|
|
+ ]
|
|
|
+ );
|
|
|
+ $this->line('');
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 显示按应用统计
|
|
|
+ */
|
|
|
+ private function showAppStats(string $startDate, string $endDate, ?string $appId): void
|
|
|
+ {
|
|
|
+ $query = DB::table('transfer_orders as o')
|
|
|
+ ->join('transfer_apps as a', 'o.transfer_app_id', '=', 'a.id')
|
|
|
+ ->whereBetween('o.created_at', [$startDate, $endDate])
|
|
|
+ ->select([
|
|
|
+ 'a.title as app_name',
|
|
|
+ DB::raw('COUNT(*) as total_orders'),
|
|
|
+ DB::raw('SUM(o.amount) as total_amount'),
|
|
|
+ DB::raw('SUM(CASE WHEN o.type = 1 THEN 1 ELSE 0 END) as in_orders'),
|
|
|
+ DB::raw('SUM(CASE WHEN o.type = 2 THEN 1 ELSE 0 END) as out_orders'),
|
|
|
+ DB::raw('SUM(CASE WHEN o.status = 100 THEN 1 ELSE 0 END) as completed_orders'),
|
|
|
+ ])
|
|
|
+ ->groupBy('a.id', 'a.title')
|
|
|
+ ->orderBy('total_amount', 'desc');
|
|
|
+
|
|
|
+ if ($appId) {
|
|
|
+ $query->where('a.id', $appId);
|
|
|
+ }
|
|
|
+
|
|
|
+ $stats = $query->get();
|
|
|
+
|
|
|
+ if ($stats->isNotEmpty()) {
|
|
|
+ $this->info('按应用统计:');
|
|
|
+ $this->table(
|
|
|
+ ['应用名称', '总订单', '总金额', '转入', '转出', '成功', '成功率'],
|
|
|
+ $stats->map(function ($stat) {
|
|
|
+ $successRate = $stat->total_orders > 0
|
|
|
+ ? number_format($stat->completed_orders / $stat->total_orders * 100, 1) . '%'
|
|
|
+ : '0%';
|
|
|
+
|
|
|
+ return [
|
|
|
+ $stat->app_name,
|
|
|
+ number_format($stat->total_orders),
|
|
|
+ number_format($stat->total_amount, 2),
|
|
|
+ number_format($stat->in_orders),
|
|
|
+ number_format($stat->out_orders),
|
|
|
+ number_format($stat->completed_orders),
|
|
|
+ $successRate,
|
|
|
+ ];
|
|
|
+ })
|
|
|
+ );
|
|
|
+ $this->line('');
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 显示按状态统计
|
|
|
+ */
|
|
|
+ private function showStatusStats(string $startDate, string $endDate, ?string $appId): void
|
|
|
+ {
|
|
|
+ $query = TransferOrder::whereBetween('created_at', [$startDate, $endDate])
|
|
|
+ ->select([
|
|
|
+ 'status',
|
|
|
+ DB::raw('COUNT(*) as count'),
|
|
|
+ DB::raw('SUM(amount) as amount')
|
|
|
+ ])
|
|
|
+ ->groupBy('status')
|
|
|
+ ->orderBy('status');
|
|
|
+
|
|
|
+ if ($appId) {
|
|
|
+ $query->where('transfer_app_id', $appId);
|
|
|
+ }
|
|
|
+
|
|
|
+ $stats = $query->get();
|
|
|
+
|
|
|
+ if ($stats->isNotEmpty()) {
|
|
|
+ $this->info('按状态统计:');
|
|
|
+ $this->table(
|
|
|
+ ['状态', '订单数', '金额'],
|
|
|
+ $stats->map(function ($stat) {
|
|
|
+ $status = TransferStatus::from($stat->status);
|
|
|
+ return [
|
|
|
+ $status->getDescription(),
|
|
|
+ number_format($stat->count),
|
|
|
+ number_format($stat->amount, 2),
|
|
|
+ ];
|
|
|
+ })
|
|
|
+ );
|
|
|
+ $this->line('');
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 显示按小时统计
|
|
|
+ */
|
|
|
+ private function showHourlyStats(string $startDate, string $endDate, ?string $appId): void
|
|
|
+ {
|
|
|
+ $query = TransferOrder::whereBetween('created_at', [$startDate, $endDate])
|
|
|
+ ->select([
|
|
|
+ DB::raw('HOUR(created_at) as hour'),
|
|
|
+ DB::raw('COUNT(*) as count'),
|
|
|
+ DB::raw('SUM(amount) as amount')
|
|
|
+ ])
|
|
|
+ ->groupBy(DB::raw('HOUR(created_at)'))
|
|
|
+ ->orderBy('hour');
|
|
|
+
|
|
|
+ if ($appId) {
|
|
|
+ $query->where('transfer_app_id', $appId);
|
|
|
+ }
|
|
|
+
|
|
|
+ $stats = $query->get();
|
|
|
+
|
|
|
+ if ($stats->isNotEmpty()) {
|
|
|
+ $this->info('按小时统计:');
|
|
|
+ $this->table(
|
|
|
+ ['小时', '订单数', '金额'],
|
|
|
+ $stats->map(function ($stat) {
|
|
|
+ return [
|
|
|
+ sprintf('%02d:00', $stat->hour),
|
|
|
+ number_format($stat->count),
|
|
|
+ number_format($stat->amount, 2),
|
|
|
+ ];
|
|
|
+ })
|
|
|
+ );
|
|
|
+ $this->line('');
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 导出统计数据
|
|
|
+ */
|
|
|
+ private function exportStats(string $startDate, string $endDate, ?string $appId, string $filename): void
|
|
|
+ {
|
|
|
+ $this->info("正在导出统计数据到 {$filename}...");
|
|
|
+
|
|
|
+ // 这里可以实现CSV或Excel导出逻辑
|
|
|
+ $this->info('导出功能待实现');
|
|
|
+ }
|
|
|
+}
|