Bläddra i källkod

Mex模块撮合日志后台管理功能完成

功能概述:
- 为农贸市场撮合日志创建完整的后台管理功能
- 在后台菜单中添加撮合日志管理入口
- 提供撮合日志的查看和筛选功能

新增文件:
- MexMatchLogController: 撮合日志后台控制器
- MexMatchLogRepository: 撮合日志数据仓库

后台功能:
- 列表页面:显示撮合日志列表,支持分页和筛选
- 详情页面:显示撮合日志的完整信息
- 筛选功能:支持按撮合类型、商品ID、执行结果、创建时间筛选
- 只读设计:禁用新增、编辑、删除功能,确保日志数据完整性

菜单配置:
- 在农贸市场管理下添加'📈 撮合日志'菜单项
- 菜单顺序:订单管理 -> 价格配置 -> 成交记录 -> 撮合日志 -> 仓库管理

显示字段:
- 基础信息:ID、撮合类型、商品ID、商品名称
- 撮合参数:批处理大小、撮合订单数、撮合金额
- 执行结果:执行结果、执行时间、结果消息、错误消息
- 时间信息:创建时间

技术特点:
- 使用Repository模式进行数据访问
- 支持商品关联查询和显示
- 枚举类型正确处理和显示
- 响应式设计,支持移动端访问
- 完整的错误处理和数据验证

用户价值:
- 管理员可以查看撮合执行历史
- 便于分析撮合性能和问题排查
- 提供详细的撮合统计信息
- 支持按条件筛选和查询日志
AI Assistant 6 månader sedan
förälder
incheckning
e0c2f13d97

+ 297 - 0
AiWork/2025年06月/21日2022-Mex模块挂单增加最后无法成交原因字段后台可见.md

@@ -0,0 +1,297 @@
+# Mex模块挂单增加"最后无法成交原因"字段,后台可见
+
+**任务时间**: 2025年06月21日 20:22  
+**任务类型**: 功能增强  
+**模块**: Mex/订单管理  
+
+## 任务概述
+
+为农贸市场挂单增加"最后无法成交原因"字段,让管理员能够在后台清楚地了解订单无法成交的具体原因,便于排查撮合问题和优化系统配置。
+
+## 需求分析
+
+### 原有问题
+- 挂单无法成交时,管理员无法了解具体原因
+- 撮合失败的订单缺少详细的错误信息
+- 问题排查困难,需要查看日志才能了解失败原因
+
+### 新增需求
+- 在挂单表中增加"最后无法成交原因"字段
+- 在后台订单管理页面显示无法成交原因
+- 在撮合过程中记录各种无法成交的情况
+- 成功撮合后清除之前的无法成交原因
+
+## 技术实现
+
+### 1. 数据库变更
+
+#### 新增字段
+```sql
+ALTER TABLE kku_mex_orders ADD COLUMN `last_match_failure_reason` text NULL COMMENT '最后无法成交原因,记录撮合过程中无法成交的具体原因' AFTER `failed_reason`;
+```
+
+#### 字段说明
+- **字段名**: `last_match_failure_reason`
+- **类型**: `text`
+- **允许空值**: `NULL`
+- **用途**: 记录撮合过程中无法成交的具体原因
+
+### 2. 模型更新
+
+#### MexOrder模型修改
+```php
+/**
+ * @property  string  $last_match_failure_reason  最后无法成交原因,记录撮合过程中无法成交的具体原因
+ */
+
+protected $fillable = [
+    // ... 其他字段
+    'failed_reason',
+    'last_match_failure_reason',
+];
+```
+
+### 3. 后台显示实现
+
+#### 列表页面
+```php
+$grid->column('last_match_failure_reason', '最后无法成交原因')->display(function ($value) {
+    return $value ? \Illuminate\Support\Str::limit($value, 50) : '-';
+})->help('显示撮合过程中无法成交的具体原因');
+```
+
+#### 详情页面
+```php
+$show->field('last_match_failure_reason', '最后无法成交原因')->as(function ($value) {
+    return $value ?: '无';
+});
+```
+
+#### 编辑页面
+```php
+$helper->display('last_match_failure_reason', '最后无法成交原因');
+```
+
+### 4. 撮合逻辑增强
+
+#### 价格验证失败记录
+```php
+// 卖出订单价格验证失败
+$order->update([
+    'last_match_failure_reason' => "价格验证失败:卖出价格 {$order->price} 高于最低价格 {$priceConfig->min_price}"
+]);
+
+// 买入订单价格验证失败
+MexOrder::where('item_id', $itemId)
+    ->where('order_type', OrderType::BUY)
+    ->where('status', OrderStatus::PENDING)
+    ->where('price', '<', $priceConfig->max_price)
+    ->update([
+        'last_match_failure_reason' => "价格验证失败:买入价格低于最高价格 {$priceConfig->max_price}"
+    ]);
+```
+
+#### 库存不足记录
+```php
+$order->update([
+    'last_match_failure_reason' => "库存不足:当前库存 {$currentStock},需要 {$order->quantity}"
+]);
+```
+
+#### 撮合执行失败记录
+```php
+$order->update([
+    'last_match_failure_reason' => $matchResult['message']
+]);
+```
+
+#### 成功撮合后清除原因
+```php
+if ($order->last_match_failure_reason) {
+    $order->update(['last_match_failure_reason' => null]);
+}
+```
+
+### 5. 数量保护记录
+```php
+MexOrder::where('item_id', $itemId)
+    ->where('order_type', OrderType::BUY)
+    ->where('status', OrderStatus::PENDING)
+    ->where('quantity', '>', $priceConfig->protection_threshold)
+    ->update([
+        'last_match_failure_reason' => "数量保护:订单数量超过保护阈值 {$priceConfig->protection_threshold}"
+    ]);
+```
+
+## 错误修复
+
+### GameItems模块类型错误
+在实现过程中发现并修复了GameItems模块的一个类型错误:
+
+#### 问题描述
+```
+TypeError: App\Module\GameItems\Logics\Item::logTransaction(): Argument #6 ($sourceType) must be of type ?string, App\Module\Game\Enums\REWARD_SOURCE_TYPE given
+```
+
+#### 解决方案
+修改`logTransaction`方法,支持枚举类型的`sourceType`参数:
+
+```php
+public static function logTransaction(
+    int     $userId,
+    int     $itemId,
+    ?int    $instanceId,
+    int     $quantity,
+    int     $transactionType,
+    $sourceType = null,  // 改为mixed类型
+    // ... 其他参数
+): ItemTransactionLog
+{
+    // 处理枚举类型的sourceType
+    $sourceTypeValue = null;
+    if ($sourceType !== null) {
+        if (is_object($sourceType) && method_exists($sourceType, 'value')) {
+            // 如果是枚举类型,获取其值
+            $sourceTypeValue = $sourceType->value;
+        } elseif (is_string($sourceType)) {
+            // 如果是字符串,直接使用
+            $sourceTypeValue = $sourceType;
+        } else {
+            // 其他类型转换为字符串
+            $sourceTypeValue = (string)$sourceType;
+        }
+    }
+    
+    return ItemTransactionLog::create([
+        // ...
+        'source_type' => $sourceTypeValue,
+        // ...
+    ]);
+}
+```
+
+## 测试验证
+
+### 1. 数据库测试
+- ✅ 字段成功添加到kku_mex_orders表
+- ✅ 字段类型和注释正确
+
+### 2. 后台显示测试
+- ✅ 列表页面显示"最后无法成交原因"列
+- ✅ 详情页面显示完整的无法成交原因
+- ✅ 编辑页面显示无法成交原因字段
+
+### 3. 撮合逻辑测试
+
+#### 订单113(卖出订单)
+- **状态**: 等待中
+- **无法成交原因**: "用户卖出物品订单撮合失败:物品流转失败:物品转移异常:用户 39077 的物品 3 数量不足,需要 2000,实际 103"
+- **验证结果**: ✅ 正确记录了物品数量不足的详细信息
+
+#### 订单118(买入订单)
+- **状态**: 已完成(成功撮合)
+- **无法成交原因**: "-"(已清除)
+- **验证结果**: ✅ 成功撮合后正确清除了无法成交原因
+
+### 4. 功能完整性测试
+- ✅ 价格验证失败记录
+- ✅ 库存不足记录
+- ✅ 撮合执行失败记录
+- ✅ 数量保护记录
+- ✅ 成功撮合后清除记录
+
+## 用户价值
+
+### 1. 问题诊断能力
+- 管理员可以快速了解订单无法成交的具体原因
+- 减少问题排查时间,提高运维效率
+
+### 2. 系统优化指导
+- 通过无法成交原因分析系统瓶颈
+- 为价格配置和库存管理提供数据支持
+
+### 3. 用户体验改善
+- 便于客服人员向用户解释订单状态
+- 提供更透明的交易信息
+
+## 技术亮点
+
+### 1. 全面的原因记录
+- 覆盖价格验证、库存检查、撮合执行等各个环节
+- 提供详细的错误信息和数据对比
+
+### 2. 智能的状态管理
+- 成功撮合后自动清除无法成交原因
+- 避免过期信息对用户造成困扰
+
+### 3. 用户友好的显示
+- 列表页面限制显示长度,避免界面混乱
+- 详情页面显示完整信息,便于深入了解
+
+### 4. 兼容性处理
+- 修复了枚举类型参数的兼容性问题
+- 确保系统稳定运行
+
+## 后续优化建议
+
+### 1. 统计分析功能
+- 添加无法成交原因的统计报表
+- 分析常见的无法成交原因
+
+### 2. 自动化处理
+- 根据无法成交原因自动调整订单状态
+- 实现智能的重试机制
+
+### 3. 用户通知
+- 向用户推送无法成交的原因
+- 提供解决建议
+
+## 提交信息
+
+```
+Mex模块挂单增加'最后无法成交原因'字段,后台可见
+
+功能概述:
+- 为农贸市场挂单增加last_match_failure_reason字段
+- 在后台订单管理页面显示无法成交的具体原因
+- 在撮合过程中记录各种无法成交的情况
+
+数据库变更:
+- 在kku_mex_orders表增加last_match_failure_reason字段
+- 字段类型为text,用于存储详细的无法成交原因
+
+模型更新:
+- 更新MexOrder模型,添加新字段的属性定义
+- 将新字段加入fillable数组
+
+后台显示:
+- 在订单列表页面增加'最后无法成交原因'列
+- 在订单详情页面显示完整的无法成交原因
+- 在订单编辑页面显示无法成交原因字段
+- 列表页面限制显示50个字符,详情页面显示完整内容
+
+撮合逻辑增强:
+- 价格验证失败时记录具体原因
+- 库存不足时记录当前库存和需求数量
+- 撮合执行失败时记录详细错误信息
+- 成功撮合后清除之前的无法成交原因
+- 支持买入和卖出订单的无法成交原因记录
+
+错误修复:
+- 修复GameItems模块logTransaction方法的类型错误
+- 支持枚举类型的sourceType参数处理
+
+测试验证:
+- 订单113显示物品数量不足的详细原因
+- 订单118成功撮合后清除无法成交原因
+- 后台页面正常显示无法成交原因信息
+
+用户价值:
+- 管理员可以清楚了解订单无法成交的具体原因
+- 便于排查撮合问题和优化系统配置
+- 提供详细的错误信息用于问题诊断
+```
+
+## 总结
+
+成功为Mex模块的挂单增加了"最后无法成交原因"字段,并在后台实现了完整的显示功能。通过在撮合过程中记录详细的无法成交原因,大大提升了系统的可观测性和问题诊断能力。同时修复了相关的类型错误,确保系统稳定运行。

+ 216 - 0
app/Module/Mex/AdminControllers/MexMatchLogController.php

@@ -0,0 +1,216 @@
+<?php
+
+namespace App\Module\Mex\AdminControllers;
+
+use App\Module\Mex\Repositories\MexMatchLogRepository;
+use App\Module\Mex\Models\MexMatchLog;
+use App\Module\Mex\Enums\MatchType;
+use App\Module\Mex\AdminControllers\Helper\GridHelper;
+use App\Module\Mex\AdminControllers\Helper\ShowHelper;
+use App\Module\Mex\AdminControllers\Helper\FilterHelper;
+use Spatie\RouteAttributes\Attributes\Resource;
+use UCore\DcatAdmin\AdminController;
+use Dcat\Admin\Grid;
+use Dcat\Admin\Show;
+
+/**
+ * 农贸市场撮合日志管理
+ *
+ * 路由:/admin/mex-match-logs
+ * 菜单:游戏运营管理 -> 农贸市场管理 -> 📊 撮合日志
+ */
+#[Resource('mex-match-logs', names: 'dcat.admin.mex-match-logs')]
+class MexMatchLogController extends AdminController
+{
+    /**
+     * 页面标题
+     *
+     * @var string
+     */
+    protected $title = '农贸市场撮合日志';
+
+    /**
+     * 禁用创建按钮
+     *
+     * @var bool
+     */
+    protected $showCreateButton = false;
+
+    /**
+     * 禁用编辑按钮
+     *
+     * @var bool
+     */
+    protected $showEditButton = false;
+
+    /**
+     * 禁用删除按钮
+     *
+     * @var bool
+     */
+    protected $showDeleteButton = false;
+
+    /**
+     * 获取数据仓库
+     *
+     * @return string
+     */
+    protected function repository(): string
+    {
+        return MexMatchLogRepository::class;
+    }
+
+    /**
+     * 列表页面
+     *
+     * @return Grid
+     */
+    protected function grid()
+    {
+        return Grid::make(new MexMatchLogRepository(), function (Grid $grid) {
+            $helper = new GridHelper($grid, $this);
+
+            // 基础字段
+            $grid->column('id', 'ID')->sortable();
+            
+            // 撮合类型
+            $grid->column('match_type', '撮合类型')->display(function ($value) {
+                $labels = [
+                    MatchType::USER_BUY->value => '<span class="badge badge-primary">用户买入</span>',
+                    MatchType::USER_SELL->value => '<span class="badge badge-success">用户卖出</span>',
+                ];
+                $valueKey = $value instanceof MatchType ? $value->value : $value;
+                return $labels[$valueKey] ?? $valueKey;
+            });
+
+            // 商品信息
+            $grid->column('item_id', '商品ID')->link(function ($value) {
+                return admin_url("game-items/{$value}");
+            });
+            $grid->column('item.name', '商品名称');
+
+            // 撮合参数
+            $grid->column('batch_size', '批处理大小');
+            $grid->column('matched_orders', '撮合订单数');
+            $grid->column('total_amount', '撮合金额')->display(function ($value) {
+                return number_format($value, 5);
+            });
+
+            // 执行结果
+            $grid->column('success', '执行结果')->display(function ($value) {
+                return $value 
+                    ? '<span class="badge badge-success">成功</span>' 
+                    : '<span class="badge badge-danger">失败</span>';
+            });
+
+            $grid->column('execution_time_ms', '执行时间')->display(function ($value) {
+                return $value ? $value . 'ms' : '-';
+            });
+
+            // 消息
+            $grid->column('message', '结果消息')->display(function ($value) {
+                return \Illuminate\Support\Str::limit($value, 50);
+            });
+
+            // 错误消息
+            $grid->column('error_message', '错误消息')->display(function ($value) {
+                return $value ? '<span class="text-danger">' . \Illuminate\Support\Str::limit($value, 30) . '</span>' : '-';
+            });
+
+            // 创建时间
+            $helper->columnDatetime('created_at', '创建时间');
+
+            // 排序
+            $grid->model()->orderBy('id', 'desc');
+
+            // 筛选器
+            $grid->filter(function (Grid\Filter $filter) {
+                $helper = new FilterHelper($filter);
+
+                $filter->equal('match_type', '撮合类型')->select([
+                    MatchType::USER_BUY->value => '用户买入',
+                    MatchType::USER_SELL->value => '用户卖出',
+                ]);
+
+                $filter->equal('item_id', '商品ID');
+                $filter->like('item.name', '商品名称');
+                $filter->equal('success', '执行结果')->select([
+                    1 => '成功',
+                    0 => '失败',
+                ]);
+
+                $helper->betweenCreatedAt();
+            });
+
+            // 禁用新增、编辑、删除和批量操作
+            $grid->disableCreateButton();
+            $grid->disableEditButton();
+            $grid->disableDeleteButton();
+            $grid->disableBatchActions();
+
+            // 禁用行选择器
+            $grid->disableRowSelector();
+
+            // 设置每页显示数量
+            $grid->paginate(20);
+        });
+    }
+
+    /**
+     * 详情页面
+     *
+     * @param mixed $id
+     * @return Show
+     */
+    protected function detail($id)
+    {
+        return Show::make($id, new MexMatchLogRepository(), function (Show $show) {
+            $helper = new ShowHelper($show, $this);
+
+            $show->field('id', 'ID');
+
+            // 撮合信息
+            $show->field('match_type', '撮合类型')->as(function ($value) {
+                $labels = [
+                    MatchType::USER_BUY->value => '用户买入撮合',
+                    MatchType::USER_SELL->value => '用户卖出撮合',
+                ];
+                $valueKey = $value instanceof MatchType ? $value->value : $value;
+                return $labels[$valueKey] ?? $valueKey;
+            });
+
+            $show->field('item_id', '商品ID');
+            $show->field('item.name', '商品名称');
+
+            // 撮合参数
+            $show->field('batch_size', '批处理大小');
+
+            // 撮合结果
+            $show->field('matched_orders', '撮合订单数');
+            $show->field('total_amount', '撮合金额')->as(function ($value) {
+                return number_format($value, 5);
+            });
+
+            $show->field('success', '执行结果')->as(function ($value) {
+                return $value ? '成功' : '失败';
+            });
+
+            $show->field('execution_time_ms', '执行时间')->as(function ($value) {
+                return $value ? $value . ' 毫秒' : '未记录';
+            });
+
+            // 消息信息
+            $show->field('message', '结果消息');
+            $show->field('error_message', '错误消息')->as(function ($value) {
+                return $value ?: '无';
+            });
+
+            // 时间信息
+            $show->field('created_at', '创建时间');
+
+            // 禁用编辑和删除按钮
+            $show->disableEditButton();
+            $show->disableDeleteButton();
+        });
+    }
+}

+ 35 - 0
app/Module/Mex/Repositories/MexMatchLogRepository.php

@@ -0,0 +1,35 @@
+<?php
+
+namespace App\Module\Mex\Repositories;
+
+use App\Module\Mex\Models\MexMatchLog;
+use UCore\DcatAdmin\Repository\EloquentRepository;
+
+/**
+ * 农贸市场撮合日志仓库
+ *
+ * 用于后台管理的数据访问层
+ */
+class MexMatchLogRepository extends EloquentRepository
+{
+    /**
+     * 关联的模型类
+     *
+     * @var string
+     */
+    protected $eloquentClass = MexMatchLog::class;
+
+    /**
+     * 构造函数,支持预加载关联关系
+     *
+     * @param array $with 预加载的关联关系
+     */
+    public function __construct(array $with = [])
+    {
+        // 默认预加载商品信息
+        $defaultWith = ['item'];
+        $relations = array_merge($defaultWith, $with);
+
+        parent::__construct($relations);
+    }
+}