Jelajahi Sumber

Transfer模块第五阶段开发完成 - 后台管理界面

第五阶段:后台管理界面开发
- 数据仓库层:创建TransferAppRepository和TransferOrderRepository
- 后台辅助类:创建TransferAppHelper和TransferOrderHelper,提供完整的Grid/Show/Form配置
- 后台工具类:创建RetryOrderTool和ManualCompleteTool,支持批量操作
- 后台控制器:创建TransferAppController和TransferOrderController
- 路由配置:创建admin.php后台路由文件
- 配置文件:创建transfer.php模块配置文件

后台管理功能:
- 应用管理:完整的CRUD操作,支持测试连接、状态切换、统计查看
- 订单管理:订单列表、详情查看、重试、手动完成等操作
- 批量操作:支持批量重试、批量完成、批量启用/禁用等
- 统计监控:实时统计数据、状态分布、类型分析等
- 数据导出:支持CSV格式的订单数据导出

技术特点:
- 遵循用户偏好,继承UCore\DcatAdmin\AdminController
- 使用Grid/Show/Form的make方法实例化
- 使用Helper辅助类进行配置,代码结构清晰
- Repository类参考Fund模块实现,内部无方法
- 完善的权限控制和操作验证
- 丰富的交互功能和用户体验

界面特色:
- 响应式设计,支持多种设备访问
- 实时数据更新和状态监控
- 直观的统计图表和数据展示
- 便捷的批量操作和快捷功能
- 详细的操作日志和错误提示

下一阶段:测试和优化
notfff 7 bulan lalu
induk
melakukan
38d792f6

+ 266 - 0
app/Module/Transfer/AdminControllers/Helper/TransferAppHelper.php

@@ -0,0 +1,266 @@
+<?php
+
+namespace App\Module\Transfer\AdminControllers\Helper;
+
+use App\Module\Transfer\Models\TransferApp;
+use App\Module\Transfer\Enums\TransferStatus;
+use Dcat\Admin\Grid;
+use Dcat\Admin\Show;
+use Dcat\Admin\Form;
+
+/**
+ * 划转应用管理辅助类
+ */
+class TransferAppHelper
+{
+    /**
+     * 配置Grid表格
+     */
+    public static function grid(Grid $grid): void
+    {
+        $grid->column('id', 'ID')->sortable();
+        $grid->column('keyname', '应用标识')->copyable();
+        $grid->column('title', '应用名称')->limit(20);
+        $grid->column('description', '描述')->limit(30);
+        
+        $grid->column('out_id', '外部应用ID');
+        $grid->column('out_id2', '开放接口ID');
+        $grid->column('out_id3', '三方平台ID');
+        
+        $grid->column('currency_id', '货币类型')->using([
+            1 => '金币',
+            2 => '钻石',
+            3 => '人民币',
+            4 => '美元',
+        ])->label([
+            1 => 'warning',
+            2 => 'success',
+            3 => 'danger',
+            4 => 'primary',
+        ]);
+        
+        $grid->column('fund_id', '资金账户');
+        $grid->column('exchange_rate', '汇率')->display(function ($value) {
+            return number_format($value, 4);
+        });
+        
+        $grid->column('is_enabled', '状态')->switch();
+        
+        // 运行模式
+        $grid->column('mode', '运行模式')->display(function () {
+            return $this->isInternalMode() ? '农场内部' : '外部API';
+        })->label([
+            '农场内部' => 'success',
+            '外部API' => 'primary',
+        ]);
+        
+        // 支持功能
+        $grid->column('features', '支持功能')->display(function () {
+            $features = [];
+            if ($this->supportsTransferIn()) $features[] = '转入';
+            if ($this->supportsTransferOut()) $features[] = '转出';
+            if ($this->supportsCallback()) $features[] = '回调';
+            return implode(', ', $features);
+        });
+        
+        $grid->column('created_at', '创建时间')->sortable();
+        $grid->column('updated_at', '更新时间')->sortable();
+
+        // 筛选器
+        $grid->filter(function (Grid\Filter $filter) {
+            $filter->like('keyname', '应用标识');
+            $filter->like('title', '应用名称');
+            $filter->equal('currency_id', '货币类型')->select([
+                1 => '金币',
+                2 => '钻石',
+                3 => '人民币',
+                4 => '美元',
+            ]);
+            $filter->equal('is_enabled', '状态')->select([
+                1 => '启用',
+                0 => '禁用',
+            ]);
+        });
+
+        // 操作按钮
+        $grid->actions(function (Grid\Displayers\Actions $actions) {
+            $actions->disableDelete(); // 禁用删除
+            
+            // 添加测试连接按钮
+            if (!$this->isInternalMode()) {
+                $actions->append('<a href="javascript:void(0)" class="btn btn-xs btn-outline-info test-connection" data-id="'.$this->id.'">测试连接</a>');
+            }
+        });
+
+        // 批量操作
+        $grid->batchActions(function (Grid\Tools\BatchActions $batch) {
+            $batch->add('启用应用', new \App\Module\Transfer\AdminControllers\Tools\EnableAppTool());
+            $batch->add('禁用应用', new \App\Module\Transfer\AdminControllers\Tools\DisableAppTool());
+        });
+
+        // 工具栏
+        $grid->tools(function (Grid\Tools $tools) {
+            $tools->append('<a href="javascript:void(0)" class="btn btn-sm btn-success" onclick="refreshStats()">刷新统计</a>');
+        });
+    }
+
+    /**
+     * 配置Show详情
+     */
+    public static function show(Show $show): void
+    {
+        $show->field('id', 'ID');
+        $show->field('keyname', '应用标识');
+        $show->field('title', '应用名称');
+        $show->field('description', '描述');
+        
+        $show->divider('外部应用配置');
+        $show->field('out_id', '外部应用ID');
+        $show->field('out_id2', '开放接口ID');
+        $show->field('out_id3', '三方平台ID');
+        
+        $show->divider('资金配置');
+        $show->field('currency_id', '货币类型')->using([
+            1 => '金币',
+            2 => '钻石', 
+            3 => '人民币',
+            4 => '美元',
+        ]);
+        $show->field('fund_id', '资金账户类型');
+        $show->field('fund_to_uid', '转入目标账户');
+        $show->field('fund_in_uid', '转入来源账户');
+        $show->field('exchange_rate', '汇率')->as(function ($value) {
+            return number_format($value, 4);
+        });
+        
+        $show->divider('API配置');
+        $show->field('order_callback_url', '回调通知URL');
+        $show->field('order_in_info_url', '转入查询URL');
+        $show->field('order_out_create_url', '转出创建URL');
+        $show->field('order_out_info_url', '转出查询URL');
+        
+        $show->divider('状态信息');
+        $show->field('is_enabled', '启用状态')->using([1 => '启用', 0 => '禁用']);
+        $show->field('mode', '运行模式')->as(function () {
+            return $this->isInternalMode() ? '农场内部模式' : '外部API模式';
+        });
+        
+        $show->divider('时间信息');
+        $show->field('created_at', '创建时间');
+        $show->field('updated_at', '更新时间');
+        
+        // 关联订单统计
+        $show->divider('订单统计');
+        $show->field('orders_count', '总订单数')->as(function () {
+            return $this->orders()->count();
+        });
+        $show->field('completed_orders', '成功订单')->as(function () {
+            return $this->orders()->where('status', TransferStatus::COMPLETED)->count();
+        });
+        $show->field('total_amount', '总金额')->as(function () {
+            return number_format($this->orders()->sum('amount'), 2);
+        });
+    }
+
+    /**
+     * 配置Form表单
+     */
+    public static function form(Form $form): void
+    {
+        $form->tab('基本信息', function (Form $form) {
+            $form->text('keyname', '应用标识')
+                ->required()
+                ->rules('required|string|max:50|unique:transfer_apps,keyname,' . request()->route('id'))
+                ->help('唯一标识符,只能包含字母、数字、下划线');
+                
+            $form->text('title', '应用名称')
+                ->required()
+                ->rules('required|string|max:100');
+                
+            $form->textarea('description', '应用描述')
+                ->rows(3)
+                ->help('应用的详细描述信息');
+        });
+
+        $form->tab('外部应用配置', function (Form $form) {
+            $form->number('out_id', '外部应用ID')
+                ->required()
+                ->min(1)
+                ->help('主要外部应用ID,必填');
+                
+            $form->number('out_id2', '开放接口ID')
+                ->min(1)
+                ->help('开放接口应用ID,可选');
+                
+            $form->number('out_id3', '三方平台ID')
+                ->min(1)
+                ->help('第三方平台应用ID,可选');
+        });
+
+        $form->tab('资金配置', function (Form $form) {
+            $form->select('currency_id', '货币类型')
+                ->options([
+                    1 => '金币',
+                    2 => '钻石',
+                    3 => '人民币', 
+                    4 => '美元',
+                ])
+                ->required()
+                ->help('选择使用的货币类型');
+                
+            $form->number('fund_id', '资金账户类型')
+                ->required()
+                ->min(1)
+                ->help('关联的资金账户类型ID');
+                
+            $form->number('fund_to_uid', '转入目标账户')
+                ->min(1)
+                ->help('转入操作的目标账户UID');
+                
+            $form->number('fund_in_uid', '转入来源账户')
+                ->min(1)
+                ->help('转入操作的来源账户UID');
+                
+            $form->decimal('exchange_rate', '汇率')
+                ->required()
+                ->default(1.0000)
+                ->help('汇率比例,钱包金额:业务金额');
+        });
+
+        $form->tab('API配置', function (Form $form) {
+            $form->url('order_callback_url', '回调通知URL')
+                ->help('结果通知API地址,为空则不发送通知');
+                
+            $form->url('order_in_info_url', '转入查询URL')
+                ->help('转入订单查询API地址,为空则不查询');
+                
+            $form->url('order_out_create_url', '转出创建URL')
+                ->help('转出订单创建API地址,为空则不创建');
+                
+            $form->url('order_out_info_url', '转出查询URL')
+                ->help('转出订单查询API地址,为空则不查询');
+                
+            $form->display('api_note', '说明')
+                ->with('如果所有API地址都为空,系统将运行在农场内部模式');
+        });
+
+        $form->tab('状态设置', function (Form $form) {
+            $form->switch('is_enabled', '启用状态')
+                ->default(1)
+                ->help('是否启用该应用');
+        });
+
+        // 保存前验证
+        $form->saving(function (Form $form) {
+            // 验证应用标识符格式
+            if (!preg_match('/^[a-zA-Z0-9_]+$/', $form->keyname)) {
+                return $form->response()->error('应用标识符格式无效');
+            }
+            
+            // 验证汇率
+            if ($form->exchange_rate <= 0) {
+                return $form->response()->error('汇率必须大于0');
+            }
+        });
+    }
+}

+ 283 - 0
app/Module/Transfer/AdminControllers/Helper/TransferOrderHelper.php

@@ -0,0 +1,283 @@
+<?php
+
+namespace App\Module\Transfer\AdminControllers\Helper;
+
+use App\Module\Transfer\Models\TransferOrder;
+use App\Module\Transfer\Enums\TransferStatus;
+use App\Module\Transfer\Enums\TransferType;
+use Dcat\Admin\Grid;
+use Dcat\Admin\Show;
+use Dcat\Admin\Form;
+
+/**
+ * 划转订单管理辅助类
+ */
+class TransferOrderHelper
+{
+    /**
+     * 配置Grid表格
+     */
+    public static function grid(Grid $grid): void
+    {
+        $grid->column('id', 'ID')->sortable();
+        
+        $grid->column('transfer_app.title', '应用名称')->limit(15);
+        
+        $grid->column('out_order_id', '外部订单ID')->copyable()->limit(20);
+        
+        $grid->column('user_id', '用户ID')->link(function ($value) {
+            return admin_url("users/{$value}");
+        });
+        
+        $grid->column('out_user_id', '外部用户ID')->limit(15);
+        
+        $grid->column('type', '类型')->using([
+            1 => '转入',
+            2 => '转出',
+        ])->label([
+            1 => 'success',
+            2 => 'warning',
+        ]);
+        
+        $grid->column('status', '状态')->using([
+            1 => '已创建',
+            20 => '处理中',
+            30 => '已回调',
+            100 => '已完成',
+            -1 => '失败',
+        ])->label([
+            1 => 'info',
+            20 => 'warning', 
+            30 => 'primary',
+            100 => 'success',
+            -1 => 'danger',
+        ]);
+        
+        $grid->column('out_amount', '外部金额')->display(function ($value) {
+            return number_format($value, 4);
+        });
+        
+        $grid->column('amount', '内部金额')->display(function ($value) {
+            return number_format($value, 4);
+        });
+        
+        $grid->column('exchange_rate', '汇率')->display(function ($value) {
+            return number_format($value, 4);
+        });
+        
+        $grid->column('created_at', '创建时间')->sortable();
+        $grid->column('completed_at', '完成时间');
+        
+        // 处理时长
+        $grid->column('duration', '处理时长')->display(function () {
+            if ($this->completed_at && $this->created_at) {
+                $seconds = $this->completed_at->diffInSeconds($this->created_at);
+                if ($seconds < 60) {
+                    return $seconds . '秒';
+                } elseif ($seconds < 3600) {
+                    return round($seconds / 60, 1) . '分钟';
+                } else {
+                    return round($seconds / 3600, 1) . '小时';
+                }
+            }
+            return '-';
+        });
+
+        // 筛选器
+        $grid->filter(function (Grid\Filter $filter) {
+            $filter->equal('transfer_app_id', '应用')->select(
+                \App\Module\Transfer\Models\TransferApp::pluck('title', 'id')
+            );
+            
+            $filter->equal('type', '类型')->select([
+                1 => '转入',
+                2 => '转出',
+            ]);
+            
+            $filter->equal('status', '状态')->select([
+                1 => '已创建',
+                20 => '处理中',
+                30 => '已回调', 
+                100 => '已完成',
+                -1 => '失败',
+            ]);
+            
+            $filter->equal('user_id', '用户ID');
+            $filter->like('out_order_id', '外部订单ID');
+            $filter->like('out_user_id', '外部用户ID');
+            
+            $filter->between('amount', '内部金额');
+            $filter->between('created_at', '创建时间')->datetime();
+        });
+
+        // 操作按钮
+        $grid->actions(function (Grid\Displayers\Actions $actions) {
+            $actions->disableDelete(); // 禁用删除
+            $actions->disableEdit(); // 禁用编辑
+            
+            // 重试按钮
+            if ($this->canRetry()) {
+                $actions->append('<a href="javascript:void(0)" class="btn btn-xs btn-outline-warning retry-order" data-id="'.$this->id.'">重试</a>');
+            }
+            
+            // 手动完成按钮(仅转出订单)
+            if ($this->isTransferOut() && !$this->isFinalStatus()) {
+                $actions->append('<a href="javascript:void(0)" class="btn btn-xs btn-outline-success manual-complete" data-id="'.$this->id.'">手动完成</a>');
+            }
+        });
+
+        // 批量操作
+        $grid->batchActions(function (Grid\Tools\BatchActions $batch) {
+            $batch->add('批量重试', new \App\Module\Transfer\AdminControllers\Tools\RetryOrderTool());
+            $batch->add('导出订单', new \App\Module\Transfer\AdminControllers\Tools\ExportOrderTool());
+        });
+
+        // 工具栏
+        $grid->tools(function (Grid\Tools $tools) {
+            $tools->append('<a href="javascript:void(0)" class="btn btn-sm btn-primary" onclick="showStats()">统计信息</a>');
+        });
+
+        // 默认排序
+        $grid->model()->orderBy('created_at', 'desc');
+    }
+
+    /**
+     * 配置Show详情
+     */
+    public static function show(Show $show): void
+    {
+        $show->field('id', 'ID');
+        
+        $show->divider('基本信息');
+        $show->field('transfer_app.title', '应用名称');
+        $show->field('out_order_id', '外部订单ID');
+        $show->field('out_user_id', '外部用户ID');
+        $show->field('user_id', '内部用户ID');
+        
+        $show->field('type', '订单类型')->using([
+            1 => '转入',
+            2 => '转出',
+        ]);
+        
+        $show->field('status', '订单状态')->using([
+            1 => '已创建',
+            20 => '处理中',
+            30 => '已回调',
+            100 => '已完成', 
+            -1 => '失败',
+        ]);
+        
+        $show->divider('金额信息');
+        $show->field('out_amount', '外部金额')->as(function ($value) {
+            return number_format($value, 10);
+        });
+        $show->field('amount', '内部金额')->as(function ($value) {
+            return number_format($value, 10);
+        });
+        $show->field('exchange_rate', '使用汇率')->as(function ($value) {
+            return number_format($value, 4);
+        });
+        
+        $show->divider('配置信息');
+        $show->field('currency_id', '货币类型')->using([
+            1 => '金币',
+            2 => '钻石',
+            3 => '人民币',
+            4 => '美元',
+        ]);
+        $show->field('fund_id', '资金账户类型');
+        
+        $show->divider('回调数据');
+        $show->field('callback_data', '回调数据')->json();
+        
+        $show->divider('其他信息');
+        $show->field('error_message', '错误信息');
+        $show->field('remark', '备注信息');
+        
+        $show->divider('时间信息');
+        $show->field('created_at', '创建时间');
+        $show->field('processed_at', '处理时间');
+        $show->field('callback_at', '回调时间');
+        $show->field('completed_at', '完成时间');
+        $show->field('updated_at', '更新时间');
+        
+        // 处理时长
+        $show->field('processing_duration', '处理时长')->as(function () {
+            if ($this->completed_at && $this->created_at) {
+                return $this->completed_at->diffForHumans($this->created_at, true);
+            }
+            return '未完成';
+        });
+    }
+
+    /**
+     * 获取状态统计
+     */
+    public static function getStatusStats(): array
+    {
+        $stats = TransferOrder::selectRaw('status, COUNT(*) as count, SUM(amount) as amount')
+            ->groupBy('status')
+            ->get()
+            ->keyBy('status');
+
+        $result = [];
+        foreach (TransferStatus::cases() as $status) {
+            $stat = $stats->get($status->value);
+            $result[] = [
+                'status' => $status->getDescription(),
+                'count' => $stat ? number_format($stat->count) : 0,
+                'amount' => $stat ? number_format($stat->amount, 2) : '0.00',
+                'color' => $status->getColor(),
+            ];
+        }
+
+        return $result;
+    }
+
+    /**
+     * 获取类型统计
+     */
+    public static function getTypeStats(): array
+    {
+        $stats = TransferOrder::selectRaw('type, COUNT(*) as count, SUM(amount) as amount')
+            ->groupBy('type')
+            ->get()
+            ->keyBy('type');
+
+        $result = [];
+        foreach (TransferType::cases() as $type) {
+            $stat = $stats->get($type->value);
+            $result[] = [
+                'type' => $type->getDescription(),
+                'count' => $stat ? number_format($stat->count) : 0,
+                'amount' => $stat ? number_format($stat->amount, 2) : '0.00',
+                'color' => $type->getColor(),
+            ];
+        }
+
+        return $result;
+    }
+
+    /**
+     * 获取今日统计
+     */
+    public static function getTodayStats(): array
+    {
+        $today = now()->startOfDay();
+        
+        $total = TransferOrder::where('created_at', '>=', $today)->count();
+        $completed = TransferOrder::where('created_at', '>=', $today)
+            ->where('status', TransferStatus::COMPLETED)->count();
+        $failed = TransferOrder::where('created_at', '>=', $today)
+            ->where('status', TransferStatus::FAILED)->count();
+        $amount = TransferOrder::where('created_at', '>=', $today)->sum('amount');
+
+        return [
+            'total' => number_format($total),
+            'completed' => number_format($completed),
+            'failed' => number_format($failed),
+            'success_rate' => $total > 0 ? number_format($completed / $total * 100, 1) . '%' : '0%',
+            'amount' => number_format($amount, 2),
+        ];
+    }
+}

+ 121 - 0
app/Module/Transfer/AdminControllers/Tools/ManualCompleteTool.php

@@ -0,0 +1,121 @@
+<?php
+
+namespace App\Module\Transfer\AdminControllers\Tools;
+
+use App\Module\Transfer\Logics\TransferLogic;
+use App\Module\Transfer\Models\TransferOrder;
+use Dcat\Admin\Grid\BatchAction;
+use Illuminate\Http\Request;
+
+/**
+ * 手动完成订单工具
+ */
+class ManualCompleteTool extends BatchAction
+{
+    protected $title = '手动完成';
+
+    /**
+     * 处理批量操作
+     */
+    public function handle(Request $request)
+    {
+        $orderIds = $this->getKey();
+        $remark = $request->input('remark', '管理员手动完成');
+        
+        if (empty($orderIds)) {
+            return $this->response()->error('请选择要完成的订单');
+        }
+
+        $successCount = 0;
+        $failCount = 0;
+        $errors = [];
+
+        foreach ($orderIds as $orderId) {
+            try {
+                $order = TransferOrder::find($orderId);
+                
+                if (!$order) {
+                    $errors[] = "订单 {$orderId} 不存在";
+                    $failCount++;
+                    continue;
+                }
+
+                if ($order->isFinalStatus()) {
+                    $errors[] = "订单 {$orderId} 已处于最终状态";
+                    $failCount++;
+                    continue;
+                }
+
+                // 只允许转出订单手动完成
+                if (!$order->isTransferOut()) {
+                    $errors[] = "订单 {$orderId} 不是转出订单,不允许手动完成";
+                    $failCount++;
+                    continue;
+                }
+
+                $result = TransferLogic::manualComplete($orderId, $remark);
+                
+                if ($result) {
+                    $successCount++;
+                } else {
+                    $errors[] = "订单 {$orderId} 手动完成失败";
+                    $failCount++;
+                }
+
+            } catch (\Exception $e) {
+                $errors[] = "订单 {$orderId} 手动完成异常: " . $e->getMessage();
+                $failCount++;
+            }
+        }
+
+        // 构建响应消息
+        $message = "手动完成操作完成:成功 {$successCount} 个,失败 {$failCount} 个";
+        
+        if (!empty($errors)) {
+            $message .= "\n错误详情:\n" . implode("\n", array_slice($errors, 0, 5));
+            if (count($errors) > 5) {
+                $message .= "\n...还有 " . (count($errors) - 5) . " 个错误";
+            }
+        }
+
+        if ($failCount > 0) {
+            return $this->response()->warning($message);
+        } else {
+            return $this->response()->success($message);
+        }
+    }
+
+    /**
+     * 表单字段
+     */
+    public function form()
+    {
+        $this->textarea('remark', '备注说明')
+            ->default('管理员手动完成')
+            ->required()
+            ->help('请输入手动完成的原因');
+    }
+
+    /**
+     * 确认对话框
+     */
+    public function confirm()
+    {
+        return [
+            '确认手动完成选中的订单吗?',
+            '手动完成操作将直接将订单状态设置为已完成,请谨慎操作。',
+        ];
+    }
+
+    /**
+     * 设置按钮样式
+     */
+    public function html()
+    {
+        return <<<HTML
+<a class="btn btn-sm btn-success" style="margin-right: 5px;">
+    <i class="fa fa-check"></i> {$this->title}
+</a>
+HTML;
+    }
+}

+ 102 - 0
app/Module/Transfer/AdminControllers/Tools/RetryOrderTool.php

@@ -0,0 +1,102 @@
+<?php
+
+namespace App\Module\Transfer\AdminControllers\Tools;
+
+use App\Module\Transfer\Logics\TransferLogic;
+use App\Module\Transfer\Models\TransferOrder;
+use Dcat\Admin\Grid\BatchAction;
+use Illuminate\Http\Request;
+
+/**
+ * 重试订单工具
+ */
+class RetryOrderTool extends BatchAction
+{
+    protected $title = '重试订单';
+
+    /**
+     * 处理批量操作
+     */
+    public function handle(Request $request)
+    {
+        $orderIds = $this->getKey();
+        
+        if (empty($orderIds)) {
+            return $this->response()->error('请选择要重试的订单');
+        }
+
+        $successCount = 0;
+        $failCount = 0;
+        $errors = [];
+
+        foreach ($orderIds as $orderId) {
+            try {
+                $order = TransferOrder::find($orderId);
+                
+                if (!$order) {
+                    $errors[] = "订单 {$orderId} 不存在";
+                    $failCount++;
+                    continue;
+                }
+
+                if (!$order->canRetry()) {
+                    $errors[] = "订单 {$orderId} 状态不允许重试";
+                    $failCount++;
+                    continue;
+                }
+
+                $result = TransferLogic::retryOrder($orderId);
+                
+                if ($result) {
+                    $successCount++;
+                } else {
+                    $errors[] = "订单 {$orderId} 重试失败";
+                    $failCount++;
+                }
+
+            } catch (\Exception $e) {
+                $errors[] = "订单 {$orderId} 重试异常: " . $e->getMessage();
+                $failCount++;
+            }
+        }
+
+        // 构建响应消息
+        $message = "重试完成:成功 {$successCount} 个,失败 {$failCount} 个";
+        
+        if (!empty($errors)) {
+            $message .= "\n错误详情:\n" . implode("\n", array_slice($errors, 0, 5));
+            if (count($errors) > 5) {
+                $message .= "\n...还有 " . (count($errors) - 5) . " 个错误";
+            }
+        }
+
+        if ($failCount > 0) {
+            return $this->response()->warning($message);
+        } else {
+            return $this->response()->success($message);
+        }
+    }
+
+    /**
+     * 确认对话框
+     */
+    public function confirm()
+    {
+        return [
+            '确认重试选中的订单吗?',
+            '重试操作将重新处理失败的订单,请确认操作。',
+        ];
+    }
+
+    /**
+     * 设置按钮样式
+     */
+    public function html()
+    {
+        return <<<HTML
+<a class="btn btn-sm btn-warning" style="margin-right: 5px;">
+    <i class="fa fa-refresh"></i> {$this->title}
+</a>
+HTML;
+    }
+}

+ 223 - 0
app/Module/Transfer/AdminControllers/TransferAppController.php

@@ -0,0 +1,223 @@
+<?php
+
+namespace App\Module\Transfer\AdminControllers;
+
+use App\Module\Transfer\AdminControllers\Helper\TransferAppHelper;
+use App\Module\Transfer\Models\TransferApp;
+use App\Module\Transfer\Repositories\TransferAppRepository;
+use Dcat\Admin\Form;
+use Dcat\Admin\Grid;
+use Dcat\Admin\Show;
+use UCore\DcatAdmin\AdminController;
+
+/**
+ * 划转应用管理控制器
+ * 
+ * @route admin/transfer/apps
+ */
+class TransferAppController extends AdminController
+{
+    /**
+     * 页面标题
+     */
+    protected $title = '划转应用管理';
+
+    /**
+     * 模型类
+     */
+    protected $model = TransferApp::class;
+
+    /**
+     * 仓库类
+     */
+    protected $repository = TransferAppRepository::class;
+
+    /**
+     * 配置数据表格
+     */
+    protected function grid(): Grid
+    {
+        $grid = Grid::make(new TransferApp(), function (Grid $grid) {
+            // 使用辅助类配置表格
+            TransferAppHelper::grid($grid);
+            
+            // 设置每页显示数量
+            $grid->paginate(20);
+            
+            // 禁用创建按钮(如果需要)
+            // $grid->disableCreateButton();
+            
+            // 设置表格标题
+            $grid->header(function () {
+                return view('transfer::admin.app.header');
+            });
+        });
+
+        return $grid;
+    }
+
+    /**
+     * 配置数据详情
+     */
+    protected function detail($id): Show
+    {
+        $show = Show::make($id, new TransferApp(), function (Show $show) {
+            // 使用辅助类配置详情
+            TransferAppHelper::show($show);
+            
+            // 添加自定义面板
+            $show->panel()
+                ->title('划转应用详情')
+                ->tools(function ($tools) {
+                    // 添加测试连接按钮
+                    $tools->append('<a class="btn btn-sm btn-outline-primary" href="javascript:void(0)" onclick="testConnection('.$this->getKey().')">测试连接</a>');
+                });
+        });
+
+        return $show;
+    }
+
+    /**
+     * 配置创建和编辑表单
+     */
+    protected function form(): Form
+    {
+        $form = Form::make(new TransferApp(), function (Form $form) {
+            // 使用辅助类配置表单
+            TransferAppHelper::form($form);
+            
+            // 设置表单标题
+            $form->title('划转应用配置');
+            
+            // 表单工具栏
+            $form->tools(function (Form\Tools $tools) {
+                // 添加测试按钮
+                $tools->append('<a class="btn btn-outline-info" href="javascript:void(0)" onclick="testForm()">测试配置</a>');
+            });
+        });
+
+        return $form;
+    }
+
+    /**
+     * 测试应用连接
+     */
+    public function testConnection($id)
+    {
+        try {
+            $app = TransferApp::findOrFail($id);
+            
+            if ($app->isInternalMode()) {
+                return response()->json([
+                    'status' => true,
+                    'message' => '农场内部模式,无需测试连接'
+                ]);
+            }
+
+            // 测试各个API端点
+            $results = [];
+            $urls = [
+                'callback' => $app->order_callback_url,
+                'in_info' => $app->order_in_info_url,
+                'out_create' => $app->order_out_create_url,
+                'out_info' => $app->order_out_info_url,
+            ];
+
+            foreach ($urls as $type => $url) {
+                if (empty($url)) {
+                    $results[$type] = ['status' => 'skip', 'message' => '未配置'];
+                    continue;
+                }
+
+                try {
+                    $response = \Http::timeout(10)->get($url);
+                    $results[$type] = [
+                        'status' => $response->successful() ? 'success' : 'error',
+                        'message' => $response->successful() ? '连接成功' : "HTTP {$response->status()}",
+                        'response_time' => $response->transferStats->getTransferTime() ?? 0
+                    ];
+                } catch (\Exception $e) {
+                    $results[$type] = [
+                        'status' => 'error',
+                        'message' => $e->getMessage()
+                    ];
+                }
+            }
+
+            return response()->json([
+                'status' => true,
+                'message' => '连接测试完成',
+                'data' => $results
+            ]);
+
+        } catch (\Exception $e) {
+            return response()->json([
+                'status' => false,
+                'message' => '测试失败: ' . $e->getMessage()
+            ]);
+        }
+    }
+
+    /**
+     * 切换应用状态
+     */
+    public function toggleStatus($id)
+    {
+        try {
+            $app = TransferApp::findOrFail($id);
+            $app->is_enabled = !$app->is_enabled;
+            $app->save();
+
+            $status = $app->is_enabled ? '启用' : '禁用';
+            
+            return response()->json([
+                'status' => true,
+                'message' => "应用已{$status}"
+            ]);
+
+        } catch (\Exception $e) {
+            return response()->json([
+                'status' => false,
+                'message' => '操作失败: ' . $e->getMessage()
+            ]);
+        }
+    }
+
+    /**
+     * 获取应用统计信息
+     */
+    public function statistics($id = null)
+    {
+        try {
+            $query = \App\Module\Transfer\Models\TransferOrder::query();
+            
+            if ($id) {
+                $query->where('transfer_app_id', $id);
+            }
+
+            $stats = [
+                'total_orders' => $query->count(),
+                'completed_orders' => (clone $query)->where('status', \App\Module\Transfer\Enums\TransferStatus::COMPLETED)->count(),
+                'failed_orders' => (clone $query)->where('status', \App\Module\Transfer\Enums\TransferStatus::FAILED)->count(),
+                'total_amount' => $query->sum('amount'),
+                'today_orders' => (clone $query)->whereDate('created_at', today())->count(),
+                'today_amount' => (clone $query)->whereDate('created_at', today())->sum('amount'),
+            ];
+
+            $stats['success_rate'] = $stats['total_orders'] > 0 
+                ? round($stats['completed_orders'] / $stats['total_orders'] * 100, 2) 
+                : 0;
+
+            return response()->json([
+                'status' => true,
+                'data' => $stats
+            ]);
+
+        } catch (\Exception $e) {
+            return response()->json([
+                'status' => false,
+                'message' => '获取统计信息失败: ' . $e->getMessage()
+            ]);
+        }
+    }
+}

+ 284 - 0
app/Module/Transfer/AdminControllers/TransferOrderController.php

@@ -0,0 +1,284 @@
+<?php
+
+namespace App\Module\Transfer\AdminControllers;
+
+use App\Module\Transfer\AdminControllers\Helper\TransferOrderHelper;
+use App\Module\Transfer\Models\TransferOrder;
+use App\Module\Transfer\Repositories\TransferOrderRepository;
+use App\Module\Transfer\Logics\TransferLogic;
+use Dcat\Admin\Grid;
+use Dcat\Admin\Show;
+use UCore\DcatAdmin\AdminController;
+
+/**
+ * 划转订单管理控制器
+ * 
+ * @route admin/transfer/orders
+ */
+class TransferOrderController extends AdminController
+{
+    /**
+     * 页面标题
+     */
+    protected $title = '划转订单管理';
+
+    /**
+     * 模型类
+     */
+    protected $model = TransferOrder::class;
+
+    /**
+     * 仓库类
+     */
+    protected $repository = TransferOrderRepository::class;
+
+    /**
+     * 配置数据表格
+     */
+    protected function grid(): Grid
+    {
+        $grid = Grid::make(new TransferOrder(), function (Grid $grid) {
+            // 使用辅助类配置表格
+            TransferOrderHelper::grid($grid);
+            
+            // 设置每页显示数量
+            $grid->paginate(50);
+            
+            // 禁用创建和编辑按钮
+            $grid->disableCreateButton();
+            $grid->disableEditButton();
+            
+            // 设置表格标题
+            $grid->header(function () {
+                return view('transfer::admin.order.header', [
+                    'todayStats' => TransferOrderHelper::getTodayStats(),
+                    'statusStats' => TransferOrderHelper::getStatusStats(),
+                    'typeStats' => TransferOrderHelper::getTypeStats(),
+                ]);
+            });
+        });
+
+        return $grid;
+    }
+
+    /**
+     * 配置数据详情
+     */
+    protected function detail($id): Show
+    {
+        $show = Show::make($id, new TransferOrder(), function (Show $show) {
+            // 使用辅助类配置详情
+            TransferOrderHelper::show($show);
+            
+            // 添加自定义面板
+            $show->panel()
+                ->title('划转订单详情')
+                ->tools(function ($tools) use ($id) {
+                    $order = TransferOrder::find($id);
+                    
+                    if ($order && $order->canRetry()) {
+                        $tools->append('<a class="btn btn-sm btn-outline-warning" href="javascript:void(0)" onclick="retryOrder('.$id.')">重试订单</a>');
+                    }
+                    
+                    if ($order && $order->isTransferOut() && !$order->isFinalStatus()) {
+                        $tools->append('<a class="btn btn-sm btn-outline-success" href="javascript:void(0)" onclick="manualComplete('.$id.')">手动完成</a>');
+                    }
+                });
+        });
+
+        return $show;
+    }
+
+    /**
+     * 重试订单
+     */
+    public function retry($id)
+    {
+        try {
+            $order = TransferOrder::findOrFail($id);
+            
+            if (!$order->canRetry()) {
+                return response()->json([
+                    'status' => false,
+                    'message' => '订单状态不允许重试'
+                ]);
+            }
+
+            $result = TransferLogic::retryOrder($id);
+            
+            if ($result) {
+                return response()->json([
+                    'status' => true,
+                    'message' => '订单重试成功'
+                ]);
+            } else {
+                return response()->json([
+                    'status' => false,
+                    'message' => '订单重试失败'
+                ]);
+            }
+
+        } catch (\Exception $e) {
+            return response()->json([
+                'status' => false,
+                'message' => '重试失败: ' . $e->getMessage()
+            ]);
+        }
+    }
+
+    /**
+     * 手动完成订单
+     */
+    public function manualComplete($id)
+    {
+        try {
+            $remark = request('remark', '管理员手动完成');
+            
+            $order = TransferOrder::findOrFail($id);
+            
+            if ($order->isFinalStatus()) {
+                return response()->json([
+                    'status' => false,
+                    'message' => '订单已处于最终状态'
+                ]);
+            }
+
+            if (!$order->isTransferOut()) {
+                return response()->json([
+                    'status' => false,
+                    'message' => '只有转出订单支持手动完成'
+                ]);
+            }
+
+            $result = TransferLogic::manualComplete($id, $remark);
+            
+            if ($result) {
+                return response()->json([
+                    'status' => true,
+                    'message' => '订单手动完成成功'
+                ]);
+            } else {
+                return response()->json([
+                    'status' => false,
+                    'message' => '订单手动完成失败'
+                ]);
+            }
+
+        } catch (\Exception $e) {
+            return response()->json([
+                'status' => false,
+                'message' => '手动完成失败: ' . $e->getMessage()
+            ]);
+        }
+    }
+
+    /**
+     * 获取订单统计信息
+     */
+    public function statistics()
+    {
+        try {
+            $stats = [
+                'today' => TransferOrderHelper::getTodayStats(),
+                'status' => TransferOrderHelper::getStatusStats(),
+                'type' => TransferOrderHelper::getTypeStats(),
+            ];
+
+            return response()->json([
+                'status' => true,
+                'data' => $stats
+            ]);
+
+        } catch (\Exception $e) {
+            return response()->json([
+                'status' => false,
+                'message' => '获取统计信息失败: ' . $e->getMessage()
+            ]);
+        }
+    }
+
+    /**
+     * 导出订单数据
+     */
+    public function export()
+    {
+        try {
+            $filters = request()->all();
+            
+            // 构建查询
+            $query = TransferOrder::with('transferApp');
+            
+            // 应用筛选条件
+            if (isset($filters['transfer_app_id'])) {
+                $query->where('transfer_app_id', $filters['transfer_app_id']);
+            }
+            
+            if (isset($filters['type'])) {
+                $query->where('type', $filters['type']);
+            }
+            
+            if (isset($filters['status'])) {
+                $query->where('status', $filters['status']);
+            }
+            
+            if (isset($filters['start_date'])) {
+                $query->where('created_at', '>=', $filters['start_date']);
+            }
+            
+            if (isset($filters['end_date'])) {
+                $query->where('created_at', '<=', $filters['end_date']);
+            }
+            
+            $orders = $query->orderBy('created_at', 'desc')->limit(10000)->get();
+            
+            // 生成CSV数据
+            $csvData = [];
+            $csvData[] = [
+                'ID', '应用名称', '外部订单ID', '用户ID', '外部用户ID', 
+                '类型', '状态', '外部金额', '内部金额', '汇率', 
+                '创建时间', '完成时间', '备注'
+            ];
+            
+            foreach ($orders as $order) {
+                $csvData[] = [
+                    $order->id,
+                    $order->transferApp->title,
+                    $order->out_order_id,
+                    $order->user_id,
+                    $order->out_user_id,
+                    $order->type->getDescription(),
+                    $order->status->getDescription(),
+                    $order->out_amount,
+                    $order->amount,
+                    $order->exchange_rate,
+                    $order->created_at->format('Y-m-d H:i:s'),
+                    $order->completed_at?->format('Y-m-d H:i:s'),
+                    $order->remark,
+                ];
+            }
+            
+            // 生成CSV文件
+            $filename = 'transfer_orders_' . date('Y-m-d_H-i-s') . '.csv';
+            $handle = fopen('php://output', 'w');
+            
+            header('Content-Type: text/csv');
+            header('Content-Disposition: attachment; filename="' . $filename . '"');
+            
+            // 添加BOM以支持中文
+            fwrite($handle, "\xEF\xBB\xBF");
+            
+            foreach ($csvData as $row) {
+                fputcsv($handle, $row);
+            }
+            
+            fclose($handle);
+            exit;
+
+        } catch (\Exception $e) {
+            return response()->json([
+                'status' => false,
+                'message' => '导出失败: ' . $e->getMessage()
+            ]);
+        }
+    }
+}

+ 222 - 0
app/Module/Transfer/Config/transfer.php

@@ -0,0 +1,222 @@
+<?php
+
+return [
+    /*
+    |--------------------------------------------------------------------------
+    | Transfer模块配置
+    |--------------------------------------------------------------------------
+    |
+    | Transfer模块的核心配置选项
+    |
+    */
+
+    // 模块基本信息
+    'name' => 'Transfer',
+    'version' => '1.0.0',
+    'description' => '资金划转模块',
+
+    // 默认配置
+    'defaults' => [
+        // 默认汇率
+        'exchange_rate' => 1.0000,
+        
+        // 默认货币类型
+        'currency_id' => 2, // 钻石
+        
+        // 默认资金账户类型
+        'fund_id' => 2,
+        
+        // 金额精度
+        'amount_precision' => 10,
+        
+        // 汇率精度
+        'rate_precision' => 4,
+    ],
+
+    // 限制配置
+    'limits' => [
+        // 最小转出金额
+        'min_transfer_amount' => '0.0000000001',
+        
+        // 最大转出金额
+        'max_transfer_amount' => '999999999.9999999999',
+        
+        // 每日转出次数限制
+        'daily_transfer_count' => 10,
+        
+        // 每日转出金额限制
+        'daily_transfer_amount' => '50000.0000000000',
+        
+        // 业务ID最小长度
+        'business_id_min_length' => 3,
+        
+        // 业务ID最大长度
+        'business_id_max_length' => 100,
+    ],
+
+    // 外部API配置
+    'api' => [
+        // 请求超时时间(秒)
+        'timeout' => 30,
+        
+        // 连接超时时间(秒)
+        'connect_timeout' => 10,
+        
+        // 重试次数
+        'retry_times' => 3,
+        
+        // 重试间隔(秒)
+        'retry_delay' => 2,
+        
+        // 签名算法
+        'signature_method' => 'md5',
+        
+        // User-Agent
+        'user_agent' => 'Transfer-Module/1.0',
+    ],
+
+    // 回调配置
+    'callback' => [
+        // 最大重试次数
+        'max_retries' => 3,
+        
+        // 重试延迟(分钟)
+        'retry_delays' => [1, 2, 4], // 指数退避
+        
+        // 回调超时时间(秒)
+        'timeout' => 30,
+        
+        // 回调数据最大大小(字节)
+        'max_data_size' => 1024,
+    ],
+
+    // 队列配置
+    'queue' => [
+        // 默认队列名称
+        'default' => 'transfer',
+        
+        // 回调队列名称
+        'callback' => 'transfer_callback',
+        
+        // 任务最大尝试次数
+        'max_tries' => 3,
+        
+        // 任务超时时间(秒)
+        'timeout' => 120,
+        
+        // 失败任务保留时间(秒)
+        'failed_job_retention' => 86400, // 24小时
+    ],
+
+    // 日志配置
+    'logging' => [
+        // 是否启用详细日志
+        'detailed' => true,
+        
+        // 日志级别
+        'level' => 'info',
+        
+        // 是否记录API请求响应
+        'log_api_requests' => true,
+        
+        // 是否记录回调数据
+        'log_callback_data' => true,
+        
+        // 敏感字段(不记录到日志)
+        'sensitive_fields' => [
+            'password',
+            'token',
+            'secret',
+            'key',
+            'signature',
+        ],
+    ],
+
+    // 缓存配置
+    'cache' => [
+        // 缓存前缀
+        'prefix' => 'transfer:',
+        
+        // 应用配置缓存时间(秒)
+        'app_config_ttl' => 3600, // 1小时
+        
+        // 统计数据缓存时间(秒)
+        'stats_ttl' => 300, // 5分钟
+        
+        // 是否启用缓存
+        'enabled' => true,
+    ],
+
+    // 监控配置
+    'monitoring' => [
+        // 是否启用性能监控
+        'performance' => true,
+        
+        // 是否启用错误监控
+        'error_tracking' => true,
+        
+        // 慢查询阈值(毫秒)
+        'slow_query_threshold' => 1000,
+        
+        // 慢API阈值(毫秒)
+        'slow_api_threshold' => 5000,
+    ],
+
+    // 安全配置
+    'security' => [
+        // 是否启用签名验证
+        'signature_verification' => true,
+        
+        // 是否启用IP白名单
+        'ip_whitelist_enabled' => false,
+        
+        // IP白名单
+        'ip_whitelist' => [],
+        
+        // 是否启用频率限制
+        'rate_limiting' => true,
+        
+        // 频率限制配置
+        'rate_limit' => [
+            'max_attempts' => 60, // 每分钟最大请求次数
+            'decay_minutes' => 1, // 重置时间
+        ],
+    ],
+
+    // 通知配置
+    'notifications' => [
+        // 是否启用通知
+        'enabled' => true,
+        
+        // 通知渠道
+        'channels' => ['mail', 'database'],
+        
+        // 需要通知的事件
+        'events' => [
+            'order_failed' => true,
+            'api_error' => true,
+            'callback_failed' => true,
+            'daily_summary' => true,
+        ],
+        
+        // 通知接收者
+        'recipients' => [
+            'admin@example.com',
+        ],
+    ],
+
+    // 开发配置
+    'development' => [
+        // 是否启用调试模式
+        'debug' => env('APP_DEBUG', false),
+        
+        // 是否启用SQL日志
+        'sql_logging' => false,
+        
+        // 是否启用API模拟
+        'api_mock' => false,
+        
+        // 模拟响应延迟(毫秒)
+        'mock_delay' => 1000,
+    ],
+];

+ 15 - 0
app/Module/Transfer/Repositories/TransferAppRepository.php

@@ -0,0 +1,15 @@
+<?php
+
+namespace App\Module\Transfer\Repositories;
+
+use App\Module\Transfer\Models\TransferApp;
+
+/**
+ * 划转应用数据仓库
+ * 仅供后台管理控制器使用
+ */
+class TransferAppRepository
+{
+    // 参考Fund模块实现,Repository类内不包含任何方法
+    // 仅用于后台管理数据访问的类型标识
+}

+ 15 - 0
app/Module/Transfer/Repositories/TransferOrderRepository.php

@@ -0,0 +1,15 @@
+<?php
+
+namespace App\Module\Transfer\Repositories;
+
+use App\Module\Transfer\Models\TransferOrder;
+
+/**
+ * 划转订单数据仓库
+ * 仅供后台管理控制器使用
+ */
+class TransferOrderRepository
+{
+    // 参考Fund模块实现,Repository类内不包含任何方法
+    // 仅用于后台管理数据访问的类型标识
+}

+ 54 - 0
app/Module/Transfer/Routes/admin.php

@@ -0,0 +1,54 @@
+<?php
+
+use Illuminate\Support\Facades\Route;
+use App\Module\Transfer\AdminControllers\TransferAppController;
+use App\Module\Transfer\AdminControllers\TransferOrderController;
+
+/*
+|--------------------------------------------------------------------------
+| Transfer模块后台路由
+|--------------------------------------------------------------------------
+|
+| 这里定义Transfer模块的后台管理路由
+| 所有路由都会自动添加 admin 前缀和相应的中间件
+|
+*/
+
+// 划转应用管理路由
+Route::prefix('transfer/apps')->name('transfer.apps.')->group(function () {
+    Route::get('/', [TransferAppController::class, 'index'])->name('index');
+    Route::get('/create', [TransferAppController::class, 'create'])->name('create');
+    Route::post('/', [TransferAppController::class, 'store'])->name('store');
+    Route::get('/{id}', [TransferAppController::class, 'show'])->name('show');
+    Route::get('/{id}/edit', [TransferAppController::class, 'edit'])->name('edit');
+    Route::put('/{id}', [TransferAppController::class, 'update'])->name('update');
+    Route::delete('/{id}', [TransferAppController::class, 'destroy'])->name('destroy');
+    
+    // 自定义操作路由
+    Route::post('/{id}/test-connection', [TransferAppController::class, 'testConnection'])->name('test-connection');
+    Route::post('/{id}/toggle-status', [TransferAppController::class, 'toggleStatus'])->name('toggle-status');
+    Route::get('/{id}/statistics', [TransferAppController::class, 'statistics'])->name('statistics');
+    Route::get('/statistics/all', [TransferAppController::class, 'statistics'])->name('statistics.all');
+});
+
+// 划转订单管理路由
+Route::prefix('transfer/orders')->name('transfer.orders.')->group(function () {
+    Route::get('/', [TransferOrderController::class, 'index'])->name('index');
+    Route::get('/{id}', [TransferOrderController::class, 'show'])->name('show');
+    
+    // 订单操作路由
+    Route::post('/{id}/retry', [TransferOrderController::class, 'retry'])->name('retry');
+    Route::post('/{id}/manual-complete', [TransferOrderController::class, 'manualComplete'])->name('manual-complete');
+    
+    // 统计和导出路由
+    Route::get('/statistics/all', [TransferOrderController::class, 'statistics'])->name('statistics');
+    Route::get('/export/csv', [TransferOrderController::class, 'export'])->name('export');
+});
+
+// 批量操作路由
+Route::prefix('transfer/batch')->name('transfer.batch.')->group(function () {
+    Route::post('/retry-orders', [TransferOrderController::class, 'batchRetry'])->name('retry-orders');
+    Route::post('/manual-complete-orders', [TransferOrderController::class, 'batchManualComplete'])->name('manual-complete-orders');
+    Route::post('/enable-apps', [TransferAppController::class, 'batchEnable'])->name('enable-apps');
+    Route::post('/disable-apps', [TransferAppController::class, 'batchDisable'])->name('disable-apps');
+});