Эх сурвалжийг харах

实现Transfer模块手续费统计功能

- 创建手续费每日统计表(transfer_fee_daily_stats)
- 实现手续费统计模型TransferFeeDailyStats
- 创建手续费统计服务FeeStatisticsService和逻辑层FeeStatisticsLogic
- 添加手续费统计命令transfer:fee-statistics,支持每日统计、验证、重新统计等功能
- 配置每天22:00自动执行手续费统计的定时任务
- 创建后台管理界面FeeStatisticsController,支持查看统计数据
- 更新后台菜单,添加手续费统计菜单项
- 支持按应用ID记录最后统计的订单ID,避免重复统计
- 提供完整的数据验证和重新统计功能
- 包含数据库视图和存储过程用于复杂查询
AI Assistant 6 сар өмнө
parent
commit
f67fa2f59f

+ 291 - 0
app/Module/Transfer/AdminControllers/FeeStatisticsController.php

@@ -0,0 +1,291 @@
+<?php
+
+namespace App\Module\Transfer\AdminControllers;
+
+use App\Module\Transfer\Models\TransferFeeDailyStats;
+use App\Module\Transfer\Services\FeeStatisticsService;
+use UCore\DcatAdmin\AdminController;
+use Dcat\Admin\Grid;
+use Dcat\Admin\Show;
+use Dcat\Admin\Http\Controllers\AdminController as BaseAdminController;
+use Illuminate\Http\Request;
+use Carbon\Carbon;
+use Dcat\Admin\Layout\Content;
+use Dcat\Admin\Widgets\Card;
+use Dcat\Admin\Widgets\Table;
+use Spatie\RouteAttributes\Attributes\Get;
+use Spatie\RouteAttributes\Attributes\Post;
+
+/**
+ * 手续费统计后台控制器
+ */
+class FeeStatisticsController extends AdminController
+{
+    /**
+     * 标题
+     */
+    protected $title = '手续费统计';
+
+    /**
+     * 模型
+     */
+    protected string $model = TransferFeeDailyStats::class;
+
+    /**
+     * 列表页面
+     */
+    #[Get('transfer/fee-statistics', name: 'admin.transfer.fee-statistics.index')]
+    public function index(Content $content)
+    {
+        return $content
+            ->title('手续费统计')
+            ->description('查看Transfer模块手续费统计数据')
+            ->body($this->grid());
+    }
+
+    /**
+     * 详情页面
+     */
+    #[Get('transfer/fee-statistics/{id}', name: 'admin.transfer.fee-statistics.show')]
+    public function show($id, Content $content)
+    {
+        return $content
+            ->title('手续费统计详情')
+            ->description('查看详细统计信息')
+            ->body($this->detail($id));
+    }
+
+    /**
+     * 统计概览页面
+     */
+    #[Get('transfer/fee-statistics/overview', name: 'admin.transfer.fee-statistics.overview')]
+    public function overview(Content $content)
+    {
+        return $content
+            ->title('手续费统计概览')
+            ->description('查看手续费统计概览和趋势')
+            ->body($this->overviewCards())
+            ->body($this->trendChart());
+    }
+
+    /**
+     * 手动执行统计
+     */
+    #[Post('transfer/fee-statistics/run', name: 'admin.transfer.fee-statistics.run')]
+    public function runStatistics(Request $request)
+    {
+        try {
+            $date = $request->input('date', Carbon::yesterday()->format('Y-m-d'));
+            $rerun = $request->boolean('rerun', false);
+
+            if ($rerun) {
+                $result = FeeStatisticsService::restatistics($date);
+            } else {
+                $result = FeeStatisticsService::runDailyStatistics($date);
+            }
+
+            return response()->json([
+                'status' => true,
+                'message' => '统计执行成功',
+                'data' => $result
+            ]);
+
+        } catch (\Exception $e) {
+            return response()->json([
+                'status' => false,
+                'message' => '统计执行失败: ' . $e->getMessage()
+            ]);
+        }
+    }
+
+    /**
+     * 数据验证
+     */
+    #[Post('transfer/fee-statistics/validate', name: 'admin.transfer.fee-statistics.validate')]
+    public function validateStatistics(Request $request)
+    {
+        try {
+            $date = $request->input('date', Carbon::yesterday()->format('Y-m-d'));
+            $result = FeeStatisticsService::validateStatistics($date);
+
+            return response()->json([
+                'status' => true,
+                'message' => $result['valid'] ? '数据验证通过' : '数据验证失败',
+                'data' => $result
+            ]);
+
+        } catch (\Exception $e) {
+            return response()->json([
+                'status' => false,
+                'message' => '数据验证失败: ' . $e->getMessage()
+            ]);
+        }
+    }
+
+    /**
+     * 创建数据表格
+     */
+    protected function grid(): Grid
+    {
+        return Grid::make($this->model::with('transferApp'), function (Grid $grid) {
+            $grid->column('id', 'ID')->sortable();
+            $grid->column('stat_date', '统计日期')->display(function ($value) {
+                return Carbon::parse($value)->format('Y-m-d');
+            })->sortable();
+            
+            $grid->column('transferApp.title', '应用名称');
+            $grid->column('total_order_count', '总订单数')->sortable();
+            $grid->column('total_amount', '总交易金额')->display(function ($value) {
+                return number_format($value, 4);
+            })->sortable();
+            $grid->column('total_fee_amount', '总手续费')->display(function ($value) {
+                return number_format($value, 4);
+            })->sortable();
+            $grid->column('avg_fee_rate', '平均费率')->display(function ($value) {
+                return number_format($value * 100, 2) . '%';
+            });
+            
+            $grid->column('in_order_count', '转入订单数');
+            $grid->column('in_fee_amount', '转入手续费')->display(function ($value) {
+                return number_format($value, 4);
+            });
+            
+            $grid->column('out_order_count', '转出订单数');
+            $grid->column('out_fee_amount', '转出手续费')->display(function ($value) {
+                return number_format($value, 4);
+            });
+            
+            $grid->column('last_processed_order_id', '最后处理订单ID');
+            $grid->column('created_at', '创建时间')->sortable();
+
+            // 筛选器
+            $grid->filter(function (Grid\Filter $filter) {
+                $filter->equal('transfer_app_id', '应用')->select(
+                    \App\Module\Transfer\Models\TransferApp::pluck('title', 'id')
+                );
+                $filter->date('stat_date', '统计日期');
+                $filter->between('total_fee_amount', '手续费金额');
+            });
+
+            // 默认排序
+            $grid->model()->orderBy('stat_date', 'desc');
+            
+            // 禁用创建、编辑、删除按钮
+            $grid->disableCreateButton();
+            $grid->disableActions();
+            
+            // 添加工具栏按钮
+            $grid->tools(function (Grid\Tools $tools) {
+                $tools->append('<a href="' . route('admin.transfer.fee-statistics.overview') . '" class="btn btn-primary">统计概览</a>');
+            });
+        });
+    }
+
+    /**
+     * 创建详情显示
+     */
+    protected function detail($id): Show
+    {
+        return Show::make($id, $this->model::with('transferApp'), function (Show $show) {
+            $show->field('id', 'ID');
+            $show->field('stat_date', '统计日期');
+            $show->field('transferApp.title', '应用名称');
+            $show->field('transferApp.keyname', '应用标识');
+            
+            $show->divider('订单统计');
+            $show->field('total_order_count', '总订单数');
+            $show->field('total_amount', '总交易金额')->as(function ($value) {
+                return number_format($value, 4);
+            });
+            $show->field('total_fee_amount', '总手续费')->as(function ($value) {
+                return number_format($value, 4);
+            });
+            $show->field('avg_fee_rate', '平均费率')->as(function ($value) {
+                return number_format($value * 100, 2) . '%';
+            });
+            
+            $show->divider('转入统计');
+            $show->field('in_order_count', '转入订单数');
+            $show->field('in_total_amount', '转入总金额')->as(function ($value) {
+                return number_format($value, 4);
+            });
+            $show->field('in_fee_amount', '转入手续费')->as(function ($value) {
+                return number_format($value, 4);
+            });
+            $show->field('in_avg_fee_rate', '转入平均费率')->as(function ($value) {
+                return number_format($value * 100, 2) . '%';
+            });
+            
+            $show->divider('转出统计');
+            $show->field('out_order_count', '转出订单数');
+            $show->field('out_total_amount', '转出总金额')->as(function ($value) {
+                return number_format($value, 4);
+            });
+            $show->field('out_fee_amount', '转出手续费')->as(function ($value) {
+                return number_format($value, 4);
+            });
+            $show->field('out_avg_fee_rate', '转出平均费率')->as(function ($value) {
+                return number_format($value * 100, 2) . '%';
+            });
+            
+            $show->divider('处理信息');
+            $show->field('last_processed_order_id', '最后处理订单ID');
+            $show->field('created_at', '创建时间');
+            $show->field('updated_at', '更新时间');
+        });
+    }
+
+    /**
+     * 汇总卡片
+     */
+    protected function summaryCards()
+    {
+        // 获取最近7天的统计数据
+        $recentStats = FeeStatisticsService::getRecentTrend(7);
+
+        $totalOrders = $recentStats['summary']['total_orders'] ?? 0;
+        $totalFee = $recentStats['summary']['total_fee'] ?? 0;
+        $avgFeeRate = $recentStats['summary']['avg_fee_rate'] ?? 0;
+
+        return new Card('最近7天统计汇总', [
+            new Table(['指标', '数值'], [
+                ['总订单数', number_format($totalOrders)],
+                ['总手续费', number_format((float)$totalFee, 4)],
+                ['平均费率', number_format((float)$avgFeeRate * 100, 2) . '%'],
+                ['统计天数', count($recentStats['data'] ?? [])],
+            ])
+        ]);
+    }
+
+    /**
+     * 概览卡片
+     */
+    protected function overviewCards()
+    {
+        // 获取应用汇总统计
+        $appSummary = FeeStatisticsService::getAppSummary();
+        
+        $cards = [];
+        foreach ($appSummary['data'] as $app) {
+            $cards[] = new Card($app['transfer_app']['title'] ?? '未知应用', [
+                new Table(['指标', '数值'], [
+                    ['统计天数', $app['stat_days']],
+                    ['总订单数', number_format($app['total_orders'])],
+                    ['总手续费', number_format($app['total_fee'], 4)],
+                    ['平均费率', number_format($app['avg_fee_rate'] * 100, 2) . '%'],
+                ])
+            ]);
+        }
+        
+        return $cards;
+    }
+
+    /**
+     * 趋势图表
+     */
+    protected function trendChart()
+    {
+        // 这里可以添加图表组件,显示手续费趋势
+        return new Card('手续费趋势图', '图表功能待实现');
+    }
+}

+ 218 - 0
app/Module/Transfer/Commands/FeeStatisticsCommand.php

@@ -0,0 +1,218 @@
+<?php
+
+namespace App\Module\Transfer\Commands;
+
+use App\Module\Transfer\Services\FeeStatisticsService;
+use Illuminate\Console\Command;
+use Carbon\Carbon;
+
+/**
+ * 手续费统计命令
+ *
+ * 用于执行每日手续费统计任务
+ */
+class FeeStatisticsCommand extends Command
+{
+    /**
+     * 命令签名
+     */
+    protected $signature = 'transfer:fee-statistics 
+                            {--date= : 统计日期,格式:Y-m-d,默认为昨天}
+                            {--app-id=0 : 应用ID,0表示所有应用}
+                            {--rerun : 重新统计(会删除已有数据)}
+                            {--validate : 验证统计数据完整性}
+                            {--cleanup : 清理过期统计数据}
+                            {--retention-days=365 : 数据保留天数}';
+
+    /**
+     * 命令描述
+     */
+    protected $description = '执行Transfer模块手续费每日统计';
+
+    /**
+     * 执行命令
+     */
+    public function handle()
+    {
+        $this->info('开始执行手续费统计任务...');
+        $startTime = microtime(true);
+
+        try {
+            // 清理过期数据
+            if ($this->option('cleanup')) {
+                $this->handleCleanup();
+                return 0;
+            }
+
+            // 验证数据完整性
+            if ($this->option('validate')) {
+                $this->handleValidation();
+                return 0;
+            }
+
+            // 执行统计
+            $this->handleStatistics();
+
+            $endTime = microtime(true);
+            $duration = round($endTime - $startTime, 2);
+            $this->info("手续费统计任务完成,耗时:{$duration}秒");
+
+            return 0;
+
+        } catch (\Exception $e) {
+            $this->error("手续费统计任务失败:" . $e->getMessage());
+            $this->error("错误详情:" . $e->getTraceAsString());
+            return 1;
+        }
+    }
+
+    /**
+     * 处理统计任务
+     */
+    protected function handleStatistics()
+    {
+        $date = $this->option('date') ?: Carbon::yesterday()->format('Y-m-d');
+        $appId = (int) $this->option('app-id');
+        $rerun = $this->option('rerun');
+
+        $this->info("统计日期:{$date}");
+        $this->info("应用ID:" . ($appId > 0 ? $appId : '所有应用'));
+
+        if ($rerun) {
+            $this->warn('重新统计模式:将删除已有统计数据');
+            if (!$this->confirm('确认要重新统计吗?')) {
+                $this->info('已取消操作');
+                return;
+            }
+
+            $result = FeeStatisticsService::restatistics($date, $appId);
+            $this->displayRestatisticsResult($result);
+        } else {
+            $result = FeeStatisticsService::runDailyStatistics($date);
+            $this->displayStatisticsResult($result);
+        }
+    }
+
+    /**
+     * 处理数据验证
+     */
+    protected function handleValidation()
+    {
+        $date = $this->option('date') ?: Carbon::yesterday()->format('Y-m-d');
+        
+        $this->info("验证统计数据完整性,日期:{$date}");
+        
+        $result = FeeStatisticsService::validateStatistics($date);
+        
+        if ($result['valid']) {
+            $this->info('✅ 统计数据验证通过');
+        } else {
+            $this->error('❌ 统计数据验证失败');
+        }
+
+        // 显示详细结果
+        $headers = ['应用ID', '应用名称', '状态', '记录订单数', '实际订单数', '记录手续费', '实际手续费', '说明'];
+        $rows = [];
+
+        foreach ($result['details'] as $detail) {
+            $rows[] = [
+                $detail['app_id'],
+                $detail['app_name'],
+                $detail['valid'] ? '✅ 正常' : '❌ 异常',
+                $detail['recorded_orders'] ?? '-',
+                $detail['actual_orders'] ?? '-',
+                $detail['recorded_fee'] ?? '-',
+                $detail['actual_fee'] ?? '-',
+                $detail['message']
+            ];
+        }
+
+        $this->table($headers, $rows);
+    }
+
+    /**
+     * 处理数据清理
+     */
+    protected function handleCleanup()
+    {
+        $retentionDays = (int) $this->option('retention-days');
+        
+        $this->info("清理过期统计数据,保留天数:{$retentionDays}");
+        
+        if (!$this->confirm('确认要清理过期数据吗?')) {
+            $this->info('已取消操作');
+            return;
+        }
+
+        $deletedCount = FeeStatisticsService::cleanupExpiredStats($retentionDays);
+        
+        $this->info("清理完成,删除了 {$deletedCount} 条过期记录");
+    }
+
+    /**
+     * 显示统计结果
+     */
+    protected function displayStatisticsResult(array $result)
+    {
+        $this->info("统计完成!");
+        $this->info("统计日期:{$result['date']}");
+        $this->info("处理应用数:{$result['summary']['processed_apps']}");
+        $this->info("总订单数:{$result['summary']['total_orders']}");
+        $this->info("总手续费:{$result['summary']['total_fee']}");
+
+        // 显示各应用统计详情
+        if (!empty($result['apps'])) {
+            $headers = ['应用ID', '应用名称', '订单数', '手续费金额', '最后处理订单ID', '状态'];
+            $rows = [];
+
+            foreach ($result['apps'] as $app) {
+                $rows[] = [
+                    $app['app_id'],
+                    $app['app_name'],
+                    $app['total_orders'],
+                    $app['total_fee'],
+                    $app['last_processed_order_id'],
+                    $app['message']
+                ];
+            }
+
+            $this->table($headers, $rows);
+        }
+    }
+
+    /**
+     * 显示重新统计结果
+     */
+    protected function displayRestatisticsResult(array $result)
+    {
+        $this->info("重新统计完成!");
+        
+        if (isset($result['app_id'])) {
+            // 单个应用重新统计
+            $this->info("统计日期:{$result['date']}");
+            $this->info("应用ID:{$result['app_id']}");
+            $this->info("结果:{$result['result']['message']}");
+            $this->info("订单数:{$result['result']['total_orders']}");
+            $this->info("手续费:{$result['result']['total_fee']}");
+        } else {
+            // 所有应用重新统计
+            $this->displayStatisticsResult($result);
+        }
+    }
+
+    /**
+     * 显示配置信息
+     */
+    protected function showConfig()
+    {
+        $config = FeeStatisticsService::getStatisticsConfig();
+        
+        $this->info('手续费统计配置信息:');
+        $this->info("执行时间:{$config['schedule_time']}");
+        $this->info("时区:{$config['timezone']}");
+        $this->info("数据保留天数:{$config['retention_days']}");
+        $this->info("批量处理大小:{$config['batch_size']}");
+        $this->info("启用应用数:{$config['enabled_apps']}");
+        $this->info("总应用数:{$config['total_apps']}");
+    }
+}

+ 6 - 0
app/Module/Transfer/Commands/InsertTransferAdminMenuCommand.php

@@ -53,6 +53,12 @@ class InsertTransferAdminMenuCommand extends Command
                 'uri' => 'transfer-metrics',
                 'order' => 30,
             ],
+            [
+                'title' => '手续费统计',
+                'icon' => 'fa-money',
+                'uri' => 'transfer/fee-statistics',
+                'order' => 40,
+            ],
         ],
     ];
 

+ 162 - 0
app/Module/Transfer/Database/fee_daily_stats.sql

@@ -0,0 +1,162 @@
+-- Transfer模块手续费每日统计表
+-- 用于记录每日手续费统计数据,以最后统计的ID记录进度
+
+-- =====================================================
+-- 手续费每日统计表
+-- =====================================================
+
+CREATE TABLE `kku_transfer_fee_daily_stats` (
+  `id` bigint unsigned NOT NULL AUTO_INCREMENT COMMENT '主键ID',
+  `stat_date` date NOT NULL COMMENT '统计日期',
+  `transfer_app_id` int unsigned NOT NULL COMMENT '划转应用ID',
+  `last_processed_order_id` bigint unsigned NOT NULL DEFAULT 0 COMMENT '最后处理的订单ID',
+  
+  -- 转入统计
+  `in_order_count` int unsigned NOT NULL DEFAULT 0 COMMENT '转入订单数量',
+  `in_total_amount` decimal(30,10) NOT NULL DEFAULT 0.0000000000 COMMENT '转入总金额',
+  `in_fee_amount` decimal(30,10) NOT NULL DEFAULT 0.0000000000 COMMENT '转入手续费总额',
+  `in_avg_fee_rate` decimal(8,5) NOT NULL DEFAULT 0.00000 COMMENT '转入平均手续费率',
+  
+  -- 转出统计
+  `out_order_count` int unsigned NOT NULL DEFAULT 0 COMMENT '转出订单数量',
+  `out_total_amount` decimal(30,10) NOT NULL DEFAULT 0.0000000000 COMMENT '转出总金额',
+  `out_fee_amount` decimal(30,10) NOT NULL DEFAULT 0.0000000000 COMMENT '转出手续费总额',
+  `out_avg_fee_rate` decimal(8,5) NOT NULL DEFAULT 0.00000 COMMENT '转出平均手续费率',
+  
+  -- 汇总统计
+  `total_order_count` int unsigned NOT NULL DEFAULT 0 COMMENT '总订单数量',
+  `total_amount` decimal(30,10) NOT NULL DEFAULT 0.0000000000 COMMENT '总交易金额',
+  `total_fee_amount` decimal(30,10) NOT NULL DEFAULT 0.0000000000 COMMENT '总手续费金额',
+  `avg_fee_rate` decimal(8,5) NOT NULL DEFAULT 0.00000 COMMENT '平均手续费率',
+  
+  -- 时间戳
+  `created_at` timestamp NULL DEFAULT NULL COMMENT '创建时间',
+  `updated_at` timestamp NULL DEFAULT NULL COMMENT '更新时间',
+  
+  PRIMARY KEY (`id`),
+  UNIQUE KEY `uk_stat_date_app` (`stat_date`, `transfer_app_id`),
+  KEY `idx_stat_date` (`stat_date`),
+  KEY `idx_transfer_app_id` (`transfer_app_id`),
+  KEY `idx_last_processed_order_id` (`last_processed_order_id`),
+  KEY `idx_created_at` (`created_at`),
+  
+  CONSTRAINT `fk_fee_daily_stats_app` FOREIGN KEY (`transfer_app_id`) REFERENCES `kku_transfer_apps` (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='手续费每日统计表';
+
+-- =====================================================
+-- 索引优化
+-- =====================================================
+
+-- 手续费金额统计索引
+CREATE INDEX `idx_total_fee_amount` ON `kku_transfer_fee_daily_stats` (`total_fee_amount`, `stat_date`);
+
+-- 应用统计查询索引
+CREATE INDEX `idx_app_date_range` ON `kku_transfer_fee_daily_stats` (`transfer_app_id`, `stat_date`, `total_fee_amount`);
+
+-- =====================================================
+-- 统计视图
+-- =====================================================
+
+-- 手续费月度统计视图
+CREATE VIEW `v_transfer_fee_monthly_stats` AS
+SELECT 
+    YEAR(stat_date) as year,
+    MONTH(stat_date) as month,
+    transfer_app_id,
+    ta.keyname as app_keyname,
+    ta.title as app_title,
+    SUM(total_order_count) as monthly_order_count,
+    SUM(total_amount) as monthly_total_amount,
+    SUM(total_fee_amount) as monthly_fee_amount,
+    AVG(avg_fee_rate) as monthly_avg_fee_rate,
+    SUM(in_order_count) as monthly_in_orders,
+    SUM(in_fee_amount) as monthly_in_fee,
+    SUM(out_order_count) as monthly_out_orders,
+    SUM(out_fee_amount) as monthly_out_fee,
+    COUNT(*) as stat_days
+FROM `kku_transfer_fee_daily_stats` tfs
+LEFT JOIN `kku_transfer_apps` ta ON tfs.transfer_app_id = ta.id
+GROUP BY YEAR(stat_date), MONTH(stat_date), transfer_app_id
+ORDER BY year DESC, month DESC, monthly_fee_amount DESC;
+
+-- 手续费应用汇总视图
+CREATE VIEW `v_transfer_fee_app_summary` AS
+SELECT 
+    transfer_app_id,
+    ta.keyname as app_keyname,
+    ta.title as app_title,
+    COUNT(*) as stat_days,
+    SUM(total_order_count) as total_orders,
+    SUM(total_amount) as total_amount,
+    SUM(total_fee_amount) as total_fee,
+    AVG(avg_fee_rate) as avg_fee_rate,
+    SUM(in_order_count) as total_in_orders,
+    SUM(in_fee_amount) as total_in_fee,
+    SUM(out_order_count) as total_out_orders,
+    SUM(out_fee_amount) as total_out_fee,
+    MIN(stat_date) as first_stat_date,
+    MAX(stat_date) as last_stat_date,
+    MAX(last_processed_order_id) as last_processed_order_id
+FROM `kku_transfer_fee_daily_stats` tfs
+LEFT JOIN `kku_transfer_apps` ta ON tfs.transfer_app_id = ta.id
+GROUP BY transfer_app_id
+ORDER BY total_fee DESC;
+
+-- =====================================================
+-- 存储过程:获取指定日期范围的手续费统计
+-- =====================================================
+
+DELIMITER $$
+
+CREATE PROCEDURE `GetFeeStatsByDateRange`(
+    IN p_app_id INT,
+    IN p_start_date DATE,
+    IN p_end_date DATE
+)
+READS SQL DATA
+COMMENT '获取指定应用和日期范围的手续费统计'
+BEGIN
+    SELECT 
+        stat_date,
+        transfer_app_id,
+        ta.keyname as app_name,
+        ta.title as app_title,
+        total_order_count,
+        total_amount,
+        total_fee_amount,
+        avg_fee_rate,
+        in_order_count,
+        in_fee_amount,
+        out_order_count,
+        out_fee_amount,
+        last_processed_order_id,
+        created_at
+    FROM `kku_transfer_fee_daily_stats` tfs
+    LEFT JOIN `kku_transfer_apps` ta ON tfs.transfer_app_id = ta.id
+    WHERE (p_app_id = 0 OR transfer_app_id = p_app_id)
+    AND stat_date BETWEEN p_start_date AND p_end_date
+    ORDER BY stat_date DESC, total_fee_amount DESC;
+END$$
+
+DELIMITER ;
+
+-- =====================================================
+-- 示例查询
+-- =====================================================
+
+-- 查询最近7天的手续费统计
+-- SELECT * FROM `kku_transfer_fee_daily_stats` 
+-- WHERE stat_date >= DATE_SUB(CURDATE(), INTERVAL 7 DAY)
+-- ORDER BY stat_date DESC;
+
+-- 查询指定应用的手续费统计
+-- SELECT * FROM `kku_transfer_fee_daily_stats` 
+-- WHERE transfer_app_id = 1
+-- ORDER BY stat_date DESC;
+
+-- 查询手续费收入最高的日期
+-- SELECT stat_date, SUM(total_fee_amount) as daily_fee
+-- FROM `kku_transfer_fee_daily_stats`
+-- GROUP BY stat_date
+-- ORDER BY daily_fee DESC
+-- LIMIT 10;

+ 3 - 0
app/Module/Transfer/Docs/手续费统计.md

@@ -0,0 +1,3 @@
+# 手续费统计
+1. 增加一个表,记录每日的手续费统计,以最后统计的ID记录,而不是时间
+2. 每天22.00进行统计,现在到上次统计ID的

+ 338 - 0
app/Module/Transfer/Logics/FeeStatisticsLogic.php

@@ -0,0 +1,338 @@
+<?php
+
+namespace App\Module\Transfer\Logics;
+
+use App\Module\Transfer\Models\TransferApp;
+use App\Module\Transfer\Models\TransferOrder;
+use App\Module\Transfer\Models\TransferFeeDailyStats;
+use App\Module\Transfer\Enums\TransferStatus;
+use App\Module\Transfer\Enums\TransferType;
+use Illuminate\Support\Facades\DB;
+use Illuminate\Support\Facades\Log;
+use Carbon\Carbon;
+
+/**
+ * 手续费统计逻辑类
+ *
+ * 处理手续费统计的核心业务逻辑
+ */
+class FeeStatisticsLogic
+{
+    /**
+     * 执行每日手续费统计
+     *
+     * @param string $date 统计日期
+     * @return array
+     */
+    public static function runDailyStatistics(string $date): array
+    {
+        $apps = TransferApp::where('is_enabled', true)->get();
+        $processedApps = [];
+        $totalOrders = 0;
+        $totalFee = '0.0000000000';
+
+        foreach ($apps as $app) {
+            $result = self::processAppStatistics($app, $date);
+            $processedApps[] = $result;
+            $totalOrders += $result['total_orders'];
+            $totalFee = bcadd($totalFee, $result['total_fee'], 10);
+        }
+
+        return [
+            'date' => $date,
+            'apps' => $processedApps,
+            'summary' => [
+                'processed_apps' => count($processedApps),
+                'total_orders' => $totalOrders,
+                'total_fee' => $totalFee
+            ]
+        ];
+    }
+
+    /**
+     * 处理单个应用的统计
+     *
+     * @param TransferApp $app 应用
+     * @param string $date 统计日期
+     * @return array
+     */
+    public static function processAppStatistics(TransferApp $app, string $date): array
+    {
+        // 获取上次统计的最后订单ID
+        $lastProcessedOrderId = TransferFeeDailyStats::getLastProcessedOrderId($app->id);
+
+        // 查询需要统计的订单
+        $orders = TransferOrder::where('transfer_app_id', $app->id)
+            ->where('status', TransferStatus::COMPLETED)
+            ->where('id', '>', $lastProcessedOrderId)
+            ->whereDate('completed_at', $date)
+            ->orderBy('id')
+            ->get();
+
+        if ($orders->isEmpty()) {
+            return [
+                'app_id' => $app->id,
+                'app_name' => $app->keyname,
+                'total_orders' => 0,
+                'total_fee' => '0.0000000000',
+                'last_processed_order_id' => $lastProcessedOrderId,
+                'message' => '无新订单需要统计'
+            ];
+        }
+
+        // 计算统计数据
+        $stats = self::calculateOrderStats($orders);
+        $stats['stat_date'] = $date;
+        $stats['transfer_app_id'] = $app->id;
+        $stats['last_processed_order_id'] = $orders->last()->id;
+
+        // 保存统计数据
+        TransferFeeDailyStats::createOrUpdate($stats);
+
+        return [
+            'app_id' => $app->id,
+            'app_name' => $app->keyname,
+            'total_orders' => $stats['total_order_count'],
+            'total_fee' => $stats['total_fee_amount'],
+            'last_processed_order_id' => $stats['last_processed_order_id'],
+            'message' => '统计完成'
+        ];
+    }
+
+    /**
+     * 计算订单统计数据
+     *
+     * @param \Illuminate\Database\Eloquent\Collection $orders 订单集合
+     * @return array
+     */
+    public static function calculateOrderStats($orders): array
+    {
+        $inOrders = $orders->where('type', TransferType::IN);
+        $outOrders = $orders->where('type', TransferType::OUT);
+
+        // 转入统计
+        $inOrderCount = $inOrders->count();
+        $inTotalAmount = $inOrders->sum('amount');
+        $inFeeAmount = $inOrders->sum('fee_amount');
+        $inAvgFeeRate = $inOrderCount > 0 ? $inOrders->avg('fee_rate') : 0;
+
+        // 转出统计
+        $outOrderCount = $outOrders->count();
+        $outTotalAmount = $outOrders->sum('amount');
+        $outFeeAmount = $outOrders->sum('fee_amount');
+        $outAvgFeeRate = $outOrderCount > 0 ? $outOrders->avg('fee_rate') : 0;
+
+        // 总计统计
+        $totalOrderCount = $orders->count();
+        $totalAmount = $orders->sum('amount');
+        $totalFeeAmount = $orders->sum('fee_amount');
+        $avgFeeRate = $totalOrderCount > 0 ? $orders->avg('fee_rate') : 0;
+
+        return [
+            'in_order_count' => $inOrderCount,
+            'in_total_amount' => $inTotalAmount,
+            'in_fee_amount' => $inFeeAmount,
+            'in_avg_fee_rate' => $inAvgFeeRate,
+            'out_order_count' => $outOrderCount,
+            'out_total_amount' => $outTotalAmount,
+            'out_fee_amount' => $outFeeAmount,
+            'out_avg_fee_rate' => $outAvgFeeRate,
+            'total_order_count' => $totalOrderCount,
+            'total_amount' => $totalAmount,
+            'total_fee_amount' => $totalFeeAmount,
+            'avg_fee_rate' => $avgFeeRate,
+        ];
+    }
+
+    /**
+     * 获取指定日期范围的统计数据
+     *
+     * @param string $startDate 开始日期
+     * @param string $endDate 结束日期
+     * @param int $appId 应用ID
+     * @return array
+     */
+    public static function getStatsByDateRange(string $startDate, string $endDate, int $appId = 0): array
+    {
+        $data = TransferFeeDailyStats::getByDateRange($startDate, $endDate, $appId);
+
+        // 计算汇总数据
+        $summary = [
+            'total_days' => $data->count(),
+            'total_orders' => $data->sum('total_order_count'),
+            'total_amount' => $data->sum('total_amount'),
+            'total_fee' => $data->sum('total_fee_amount'),
+            'avg_fee_rate' => $data->avg('avg_fee_rate'),
+            'total_in_orders' => $data->sum('in_order_count'),
+            'total_in_fee' => $data->sum('in_fee_amount'),
+            'total_out_orders' => $data->sum('out_order_count'),
+            'total_out_fee' => $data->sum('out_fee_amount'),
+        ];
+
+        return [
+            'data' => $data->toArray(),
+            'summary' => $summary,
+            'date_range' => [
+                'start' => $startDate,
+                'end' => $endDate
+            ]
+        ];
+    }
+
+    /**
+     * 获取月度统计数据
+     *
+     * @param int $year 年份
+     * @param int $month 月份
+     * @param int $appId 应用ID
+     * @return array
+     */
+    public static function getMonthlyStats(int $year, int $month, int $appId = 0): array
+    {
+        return TransferFeeDailyStats::getMonthlyStats($year, $month, $appId);
+    }
+
+    /**
+     * 获取应用汇总统计
+     *
+     * @param int $appId 应用ID
+     * @return array
+     */
+    public static function getAppSummary(int $appId = 0): array
+    {
+        $data = TransferFeeDailyStats::getAppSummary($appId);
+
+        return [
+            'data' => $data,
+            'total_apps' => count($data)
+        ];
+    }
+
+    /**
+     * 获取手续费收入排行榜
+     *
+     * @param string $startDate 开始日期
+     * @param string $endDate 结束日期
+     * @param int $limit 限制数量
+     * @return array
+     */
+    public static function getFeeRanking(string $startDate, string $endDate, int $limit = 10): array
+    {
+        $data = TransferFeeDailyStats::with('transferApp')
+            ->whereBetween('stat_date', [$startDate, $endDate])
+            ->selectRaw('
+                transfer_app_id,
+                SUM(total_fee_amount) as total_fee,
+                SUM(total_order_count) as total_orders,
+                AVG(avg_fee_rate) as avg_fee_rate
+            ')
+            ->groupBy('transfer_app_id')
+            ->orderBy('total_fee', 'desc')
+            ->limit($limit)
+            ->get();
+
+        return [
+            'data' => $data->toArray(),
+            'date_range' => [
+                'start' => $startDate,
+                'end' => $endDate
+            ]
+        ];
+    }
+
+    /**
+     * 重新统计指定日期的数据
+     *
+     * @param string $date 统计日期
+     * @param int $appId 应用ID
+     * @return array
+     */
+    public static function restatistics(string $date, int $appId = 0): array
+    {
+        if ($appId > 0) {
+            // 重新统计指定应用
+            $app = TransferApp::findOrFail($appId);
+            
+            // 删除原有统计数据
+            TransferFeeDailyStats::where('stat_date', $date)
+                ->where('transfer_app_id', $appId)
+                ->delete();
+            
+            // 重新统计
+            $result = self::processAppStatistics($app, $date);
+            
+            return [
+                'date' => $date,
+                'app_id' => $appId,
+                'result' => $result
+            ];
+        } else {
+            // 重新统计所有应用
+            TransferFeeDailyStats::where('stat_date', $date)->delete();
+            
+            return self::runDailyStatistics($date);
+        }
+    }
+
+    /**
+     * 验证统计数据的完整性
+     *
+     * @param string $date 检查日期
+     * @return array
+     */
+    public static function validateStatistics(string $date): array
+    {
+        $apps = TransferApp::where('is_enabled', true)->get();
+        $validationResults = [];
+        $allValid = true;
+
+        foreach ($apps as $app) {
+            $statsRecord = TransferFeeDailyStats::getByDateAndApp($date, $app->id);
+            
+            if (!$statsRecord) {
+                $validationResults[] = [
+                    'app_id' => $app->id,
+                    'app_name' => $app->keyname,
+                    'valid' => false,
+                    'message' => '缺少统计记录'
+                ];
+                $allValid = false;
+                continue;
+            }
+
+            // 验证订单数据一致性
+            $actualOrders = TransferOrder::where('transfer_app_id', $app->id)
+                ->where('status', TransferStatus::COMPLETED)
+                ->whereDate('completed_at', $date)
+                ->get();
+
+            $actualStats = self::calculateOrderStats($actualOrders);
+            
+            $isValid = (
+                $statsRecord->total_order_count == $actualStats['total_order_count'] &&
+                bccomp($statsRecord->total_fee_amount, $actualStats['total_fee_amount'], 10) == 0
+            );
+
+            $validationResults[] = [
+                'app_id' => $app->id,
+                'app_name' => $app->keyname,
+                'valid' => $isValid,
+                'message' => $isValid ? '数据一致' : '数据不一致',
+                'recorded_orders' => $statsRecord->total_order_count,
+                'actual_orders' => $actualStats['total_order_count'],
+                'recorded_fee' => $statsRecord->total_fee_amount,
+                'actual_fee' => $actualStats['total_fee_amount']
+            ];
+
+            if (!$isValid) {
+                $allValid = false;
+            }
+        }
+
+        return [
+            'date' => $date,
+            'valid' => $allValid,
+            'details' => $validationResults
+        ];
+    }
+}

+ 278 - 0
app/Module/Transfer/Models/TransferFeeDailyStats.php

@@ -0,0 +1,278 @@
+<?php
+
+namespace App\Module\Transfer\Models;
+
+use UCore\ModelCore;
+use Illuminate\Database\Eloquent\Relations\BelongsTo;
+
+/**
+ * 手续费每日统计模型
+ *
+ * field start
+ * @property  int  $id  主键ID
+ * @property  string  $stat_date  统计日期
+ * @property  int  $transfer_app_id  划转应用ID
+ * @property  int  $last_processed_order_id  最后处理的订单ID
+ * @property  int  $in_order_count  转入订单数量
+ * @property  float  $in_total_amount  转入总金额
+ * @property  float  $in_fee_amount  转入手续费总额
+ * @property  float  $in_avg_fee_rate  转入平均手续费率
+ * @property  int  $out_order_count  转出订单数量
+ * @property  float  $out_total_amount  转出总金额
+ * @property  float  $out_fee_amount  转出手续费总额
+ * @property  float  $out_avg_fee_rate  转出平均手续费率
+ * @property  int  $total_order_count  总订单数量
+ * @property  float  $total_amount  总交易金额
+ * @property  float  $total_fee_amount  总手续费金额
+ * @property  float  $avg_fee_rate  平均手续费率
+ * @property  \Carbon\Carbon  $created_at  创建时间
+ * @property  \Carbon\Carbon  $updated_at  更新时间
+ * field end
+ *
+ * relation start
+ * @property  \App\Module\Transfer\Models\TransferApp  $transferApp  划转应用
+ * relation end
+ */
+class TransferFeeDailyStats extends ModelCore
+{
+    /**
+     * 表名
+     */
+    protected $table = 'transfer_fee_daily_stats';
+
+    // attrlist start
+    protected $fillable = [
+        'id',
+        'stat_date',
+        'transfer_app_id',
+        'last_processed_order_id',
+        'in_order_count',
+        'in_total_amount',
+        'in_fee_amount',
+        'in_avg_fee_rate',
+        'out_order_count',
+        'out_total_amount',
+        'out_fee_amount',
+        'out_avg_fee_rate',
+        'total_order_count',
+        'total_amount',
+        'total_fee_amount',
+        'avg_fee_rate',
+    ];
+    // attrlist end
+
+    /**
+     * 属性类型转换
+     */
+    protected $casts = [
+        'id' => 'integer',
+        'stat_date' => 'date',
+        'transfer_app_id' => 'integer',
+        'last_processed_order_id' => 'integer',
+        'in_order_count' => 'integer',
+        'in_total_amount' => 'decimal:10',
+        'in_fee_amount' => 'decimal:10',
+        'in_avg_fee_rate' => 'decimal:5',
+        'out_order_count' => 'integer',
+        'out_total_amount' => 'decimal:10',
+        'out_fee_amount' => 'decimal:10',
+        'out_avg_fee_rate' => 'decimal:5',
+        'total_order_count' => 'integer',
+        'total_amount' => 'decimal:10',
+        'total_fee_amount' => 'decimal:10',
+        'avg_fee_rate' => 'decimal:5',
+        'created_at' => 'datetime',
+        'updated_at' => 'datetime',
+    ];
+
+    /**
+     * 隐藏字段
+     */
+    protected $hidden = [];
+
+    /**
+     * 关联划转应用
+     */
+    public function transferApp(): BelongsTo
+    {
+        return $this->belongsTo(TransferApp::class, 'transfer_app_id');
+    }
+
+    /**
+     * 获取指定日期和应用的统计记录
+     *
+     * @param string $date 统计日期
+     * @param int $appId 应用ID
+     * @return static|null
+     */
+    public static function getByDateAndApp(string $date, int $appId): ?static
+    {
+        return static::where('stat_date', $date)
+            ->where('transfer_app_id', $appId)
+            ->first();
+    }
+
+    /**
+     * 获取指定应用的最后处理订单ID
+     *
+     * @param int $appId 应用ID
+     * @return int
+     */
+    public static function getLastProcessedOrderId(int $appId): int
+    {
+        $record = static::where('transfer_app_id', $appId)
+            ->orderBy('stat_date', 'desc')
+            ->first();
+
+        return $record ? $record->last_processed_order_id : 0;
+    }
+
+    /**
+     * 获取指定日期范围的统计数据
+     *
+     * @param string $startDate 开始日期
+     * @param string $endDate 结束日期
+     * @param int $appId 应用ID(0表示所有应用)
+     * @return \Illuminate\Database\Eloquent\Collection
+     */
+    public static function getByDateRange(string $startDate, string $endDate, int $appId = 0)
+    {
+        $query = static::with('transferApp')
+            ->whereBetween('stat_date', [$startDate, $endDate]);
+
+        if ($appId > 0) {
+            $query->where('transfer_app_id', $appId);
+        }
+
+        return $query->orderBy('stat_date', 'desc')
+            ->orderBy('total_fee_amount', 'desc')
+            ->get();
+    }
+
+    /**
+     * 获取月度统计汇总
+     *
+     * @param int $year 年份
+     * @param int $month 月份
+     * @param int $appId 应用ID(0表示所有应用)
+     * @return array
+     */
+    public static function getMonthlyStats(int $year, int $month, int $appId = 0): array
+    {
+        $query = static::whereYear('stat_date', $year)
+            ->whereMonth('stat_date', $month);
+
+        if ($appId > 0) {
+            $query->where('transfer_app_id', $appId);
+        }
+
+        $stats = $query->selectRaw('
+            COUNT(*) as stat_days,
+            SUM(total_order_count) as total_orders,
+            SUM(total_amount) as total_amount,
+            SUM(total_fee_amount) as total_fee,
+            AVG(avg_fee_rate) as avg_fee_rate,
+            SUM(in_order_count) as total_in_orders,
+            SUM(in_fee_amount) as total_in_fee,
+            SUM(out_order_count) as total_out_orders,
+            SUM(out_fee_amount) as total_out_fee
+        ')->first();
+
+        return [
+            'year' => $year,
+            'month' => $month,
+            'stat_days' => $stats->stat_days ?? 0,
+            'total_orders' => $stats->total_orders ?? 0,
+            'total_amount' => $stats->total_amount ?? '0.0000000000',
+            'total_fee' => $stats->total_fee ?? '0.0000000000',
+            'avg_fee_rate' => $stats->avg_fee_rate ?? '0.00000',
+            'total_in_orders' => $stats->total_in_orders ?? 0,
+            'total_in_fee' => $stats->total_in_fee ?? '0.0000000000',
+            'total_out_orders' => $stats->total_out_orders ?? 0,
+            'total_out_fee' => $stats->total_out_fee ?? '0.0000000000',
+        ];
+    }
+
+    /**
+     * 获取应用汇总统计
+     *
+     * @param int $appId 应用ID(0表示所有应用)
+     * @return array
+     */
+    public static function getAppSummary(int $appId = 0): array
+    {
+        $query = static::with('transferApp');
+
+        if ($appId > 0) {
+            $query->where('transfer_app_id', $appId);
+        }
+
+        $stats = $query->selectRaw('
+            transfer_app_id,
+            COUNT(*) as stat_days,
+            SUM(total_order_count) as total_orders,
+            SUM(total_amount) as total_amount,
+            SUM(total_fee_amount) as total_fee,
+            AVG(avg_fee_rate) as avg_fee_rate,
+            SUM(in_order_count) as total_in_orders,
+            SUM(in_fee_amount) as total_in_fee,
+            SUM(out_order_count) as total_out_orders,
+            SUM(out_fee_amount) as total_out_fee,
+            MIN(stat_date) as first_stat_date,
+            MAX(stat_date) as last_stat_date,
+            MAX(last_processed_order_id) as last_processed_order_id
+        ')
+        ->groupBy('transfer_app_id')
+        ->orderBy('total_fee', 'desc')
+        ->get();
+
+        return $stats->toArray();
+    }
+
+    /**
+     * 创建或更新统计记录
+     *
+     * @param array $data 统计数据
+     * @return static
+     */
+    public static function createOrUpdate(array $data): static
+    {
+        return static::updateOrCreate(
+            [
+                'stat_date' => $data['stat_date'],
+                'transfer_app_id' => $data['transfer_app_id'],
+            ],
+            $data
+        );
+    }
+
+    /**
+     * 格式化手续费金额显示
+     *
+     * @return string
+     */
+    public function getFormattedTotalFeeAttribute(): string
+    {
+        return number_format($this->total_fee_amount, 4);
+    }
+
+    /**
+     * 格式化手续费率显示
+     *
+     * @return string
+     */
+    public function getFormattedAvgFeeRateAttribute(): string
+    {
+        return number_format($this->avg_fee_rate * 100, 2) . '%';
+    }
+
+    /**
+     * 获取统计日期的中文格式
+     *
+     * @return string
+     */
+    public function getFormattedStatDateAttribute(): string
+    {
+        return $this->stat_date->format('Y年m月d日');
+    }
+}

+ 306 - 0
app/Module/Transfer/Services/FeeStatisticsService.php

@@ -0,0 +1,306 @@
+<?php
+
+namespace App\Module\Transfer\Services;
+
+use App\Module\Transfer\Models\TransferApp;
+use App\Module\Transfer\Models\TransferOrder;
+use App\Module\Transfer\Models\TransferFeeDailyStats;
+use App\Module\Transfer\Enums\TransferStatus;
+use App\Module\Transfer\Enums\TransferType;
+use App\Module\Transfer\Logics\FeeStatisticsLogic;
+use Illuminate\Support\Facades\Log;
+use Carbon\Carbon;
+
+/**
+ * 手续费统计服务类
+ *
+ * 负责处理Transfer模块的手续费统计功能
+ */
+class FeeStatisticsService
+{
+    /**
+     * 执行每日手续费统计
+     *
+     * @param string|null $date 统计日期,默认为昨天
+     * @return array 统计结果
+     */
+    public static function runDailyStatistics(?string $date = null): array
+    {
+        try {
+            $date = $date ?: Carbon::yesterday()->format('Y-m-d');
+            
+            Log::info("开始执行手续费每日统计", ['date' => $date]);
+            
+            $result = FeeStatisticsLogic::runDailyStatistics($date);
+            
+            Log::info("手续费每日统计完成", [
+                'date' => $date,
+                'processed_apps' => count($result['apps']),
+                'total_orders' => $result['summary']['total_orders'],
+                'total_fee' => $result['summary']['total_fee']
+            ]);
+            
+            return $result;
+            
+        } catch (\Exception $e) {
+            Log::error("手续费每日统计失败", [
+                'date' => $date ?? 'unknown',
+                'error' => $e->getMessage(),
+                'trace' => $e->getTraceAsString()
+            ]);
+            
+            throw $e;
+        }
+    }
+
+    /**
+     * 获取指定日期范围的统计数据
+     *
+     * @param string $startDate 开始日期
+     * @param string $endDate 结束日期
+     * @param int $appId 应用ID(0表示所有应用)
+     * @return array
+     */
+    public static function getStatsByDateRange(string $startDate, string $endDate, int $appId = 0): array
+    {
+        try {
+            return FeeStatisticsLogic::getStatsByDateRange($startDate, $endDate, $appId);
+        } catch (\Exception $e) {
+            Log::error("获取手续费统计数据失败", [
+                'start_date' => $startDate,
+                'end_date' => $endDate,
+                'app_id' => $appId,
+                'error' => $e->getMessage()
+            ]);
+            
+            return [
+                'error' => $e->getMessage(),
+                'data' => [],
+                'summary' => [
+                    'total_days' => 0,
+                    'total_orders' => 0,
+                    'total_amount' => '0.0000000000',
+                    'total_fee' => '0.0000000000',
+                    'avg_fee_rate' => '0.00000'
+                ]
+            ];
+        }
+    }
+
+    /**
+     * 获取月度统计数据
+     *
+     * @param int $year 年份
+     * @param int $month 月份
+     * @param int $appId 应用ID(0表示所有应用)
+     * @return array
+     */
+    public static function getMonthlyStats(int $year, int $month, int $appId = 0): array
+    {
+        try {
+            return FeeStatisticsLogic::getMonthlyStats($year, $month, $appId);
+        } catch (\Exception $e) {
+            Log::error("获取月度手续费统计失败", [
+                'year' => $year,
+                'month' => $month,
+                'app_id' => $appId,
+                'error' => $e->getMessage()
+            ]);
+            
+            return [
+                'error' => $e->getMessage(),
+                'year' => $year,
+                'month' => $month,
+                'stat_days' => 0,
+                'total_orders' => 0,
+                'total_amount' => '0.0000000000',
+                'total_fee' => '0.0000000000',
+                'avg_fee_rate' => '0.00000'
+            ];
+        }
+    }
+
+    /**
+     * 获取应用汇总统计
+     *
+     * @param int $appId 应用ID(0表示所有应用)
+     * @return array
+     */
+    public static function getAppSummary(int $appId = 0): array
+    {
+        try {
+            return FeeStatisticsLogic::getAppSummary($appId);
+        } catch (\Exception $e) {
+            Log::error("获取应用汇总统计失败", [
+                'app_id' => $appId,
+                'error' => $e->getMessage()
+            ]);
+            
+            return [
+                'error' => $e->getMessage(),
+                'data' => []
+            ];
+        }
+    }
+
+    /**
+     * 获取最近N天的统计趋势
+     *
+     * @param int $days 天数
+     * @param int $appId 应用ID(0表示所有应用)
+     * @return array
+     */
+    public static function getRecentTrend(int $days = 7, int $appId = 0): array
+    {
+        try {
+            $endDate = Carbon::yesterday()->format('Y-m-d');
+            $startDate = Carbon::yesterday()->subDays($days - 1)->format('Y-m-d');
+            
+            return self::getStatsByDateRange($startDate, $endDate, $appId);
+            
+        } catch (\Exception $e) {
+            Log::error("获取手续费趋势数据失败", [
+                'days' => $days,
+                'app_id' => $appId,
+                'error' => $e->getMessage()
+            ]);
+            
+            return [
+                'error' => $e->getMessage(),
+                'data' => [],
+                'summary' => []
+            ];
+        }
+    }
+
+    /**
+     * 获取手续费收入排行榜
+     *
+     * @param string $startDate 开始日期
+     * @param string $endDate 结束日期
+     * @param int $limit 限制数量
+     * @return array
+     */
+    public static function getFeeRanking(string $startDate, string $endDate, int $limit = 10): array
+    {
+        try {
+            return FeeStatisticsLogic::getFeeRanking($startDate, $endDate, $limit);
+        } catch (\Exception $e) {
+            Log::error("获取手续费排行榜失败", [
+                'start_date' => $startDate,
+                'end_date' => $endDate,
+                'limit' => $limit,
+                'error' => $e->getMessage()
+            ]);
+            
+            return [
+                'error' => $e->getMessage(),
+                'data' => []
+            ];
+        }
+    }
+
+    /**
+     * 重新统计指定日期的数据
+     *
+     * @param string $date 统计日期
+     * @param int $appId 应用ID(0表示所有应用)
+     * @return array
+     */
+    public static function restatistics(string $date, int $appId = 0): array
+    {
+        try {
+            Log::info("开始重新统计手续费数据", ['date' => $date, 'app_id' => $appId]);
+            
+            $result = FeeStatisticsLogic::restatistics($date, $appId);
+            
+            Log::info("重新统计手续费数据完成", [
+                'date' => $date,
+                'app_id' => $appId,
+                'result' => $result
+            ]);
+            
+            return $result;
+            
+        } catch (\Exception $e) {
+            Log::error("重新统计手续费数据失败", [
+                'date' => $date,
+                'app_id' => $appId,
+                'error' => $e->getMessage()
+            ]);
+            
+            throw $e;
+        }
+    }
+
+    /**
+     * 获取统计配置信息
+     *
+     * @return array
+     */
+    public static function getStatisticsConfig(): array
+    {
+        return [
+            'schedule_time' => '22:00',
+            'timezone' => config('app.timezone', 'Asia/Shanghai'),
+            'retention_days' => 365, // 保留365天的统计数据
+            'batch_size' => 1000, // 批量处理大小
+            'enabled_apps' => TransferApp::where('is_enabled', true)->count(),
+            'total_apps' => TransferApp::count(),
+        ];
+    }
+
+    /**
+     * 清理过期的统计数据
+     *
+     * @param int $retentionDays 保留天数
+     * @return int 清理的记录数
+     */
+    public static function cleanupExpiredStats(int $retentionDays = 365): int
+    {
+        try {
+            $cutoffDate = Carbon::now()->subDays($retentionDays)->format('Y-m-d');
+            
+            $deletedCount = TransferFeeDailyStats::where('stat_date', '<', $cutoffDate)->delete();
+            
+            Log::info("清理过期手续费统计数据", [
+                'cutoff_date' => $cutoffDate,
+                'deleted_count' => $deletedCount
+            ]);
+            
+            return $deletedCount;
+            
+        } catch (\Exception $e) {
+            Log::error("清理过期手续费统计数据失败", [
+                'retention_days' => $retentionDays,
+                'error' => $e->getMessage()
+            ]);
+            
+            throw $e;
+        }
+    }
+
+    /**
+     * 验证统计数据的完整性
+     *
+     * @param string $date 检查日期
+     * @return array
+     */
+    public static function validateStatistics(string $date): array
+    {
+        try {
+            return FeeStatisticsLogic::validateStatistics($date);
+        } catch (\Exception $e) {
+            Log::error("验证统计数据完整性失败", [
+                'date' => $date,
+                'error' => $e->getMessage()
+            ]);
+            
+            return [
+                'valid' => false,
+                'error' => $e->getMessage(),
+                'details' => []
+            ];
+        }
+    }
+}

+ 22 - 0
app/Module/Transfer/TransferServiceProvider.php

@@ -7,7 +7,9 @@ use App\Module\Transfer\Commands\TransferStatsCommand;
 use App\Module\Transfer\Commands\TransferCallbackCommand;
 use App\Module\Transfer\Commands\TransferCleanCommand;
 use App\Module\Transfer\Commands\InsertTransferAdminMenuCommand;
+use App\Module\Transfer\Commands\FeeStatisticsCommand;
 use Illuminate\Support\ServiceProvider;
+use Illuminate\Support\Facades\Schedule;
 
 /**
  * Transfer模块服务提供者
@@ -42,6 +44,9 @@ class TransferServiceProvider extends ServiceProvider
         // 注册命令
         $this->registerCommands();
 
+        // 注册定时任务
+        $this->registerSchedules();
+
         // 注册事件监听器
         $this->registerEventListeners();
 
@@ -77,10 +82,27 @@ class TransferServiceProvider extends ServiceProvider
                 TransferCallbackCommand::class,
                 TransferCleanCommand::class,
                 InsertTransferAdminMenuCommand::class,
+                FeeStatisticsCommand::class,
             ]);
         }
     }
 
+    /**
+     * 注册定时任务
+     */
+    protected function registerSchedules(): void
+    {
+        // 在应用完全启动后注册定时任务
+        $this->app->booted(function () {
+            // 每天22:00执行手续费统计
+            Schedule::command('transfer:fee-statistics')
+                ->dailyAt('22:00')
+                ->description('Transfer模块手续费每日统计')
+                ->withoutOverlapping() // 防止重复执行
+                ->runInBackground(); // 后台运行
+        });
+    }
+
     /**
      * 注册事件监听器
      */