remark, 'shop_buy') !== false) { if (preg_match('/ID:(\d+)/', $record->remark, $matches)) { $shopItemIds[] = (int)$matches[1]; } } } if (!empty($shopItemIds)) { $shopItemIds = array_unique($shopItemIds); $shopItems = \App\Module\Shop\Models\ShopItem::whereIn('id', $shopItemIds)->get(); foreach ($shopItems as $shopItem) { $this->shopItemNameCache[$shopItem->id] = $shopItem->name; } // 为未找到的商品设置默认名称 foreach ($shopItemIds as $itemId) { if (!isset($this->shopItemNameCache[$itemId])) { $this->shopItemNameCache[$itemId] = "商品{$itemId}"; } } } } /** * 获取新的记录 * * @param int $lastProcessedId 上次处理的最大ID * @return \Illuminate\Database\Eloquent\Collection */ protected function getNewRecords(int $lastProcessedId) { // 使用原始查询避免枚举转换问题 $records = FundLogModel::selectRaw('*') ->where('id', '>', $lastProcessedId) ->orderBy('id') ->limit($this->maxRecords) ->get() ->map(function ($record) { // 手动设置原始属性,避免枚举转换 $record->setRawAttributes($record->getAttributes(), true); return $record; }); // 预加载商店商品信息 $this->preloadShopItems($records); return $records; } /** * 获取源表的最大ID * * 重写父类方法,使用模型查询以获得更好的性能 * * @return int */ public function getSourceTableMaxId(): int { return FundLogModel::max('id') ?: 0; } /** * 转换记录为用户日志数据 * * @param FundLogModel $record 资金日志记录 * @return array|null 用户日志数据,null表示跳过 */ protected function convertToUserLog($record): ?array { try { // 获取资金名称 $fundName = $this->getFundName($record->fund_id); // 判断是获得还是消耗 $amount = abs($record->amount); $action = $record->amount > 0 ? '增加' : '减少'; // 解析备注信息,生成用户友好的消息 $message = $this->buildUserFriendlyMessage($record, $fundName, $action, $amount); // 使用原始记录的时间 $createdAt = date('Y-m-d H:i:s', $record->create_time); // 根据操作类型选择合适的source_type $sourceType = $this->getSourceTypeByRemark($record->remark); return $this->createUserLogDataWithSourceType( $record->user_id, $message, $record->id, $createdAt, $sourceType ); } catch (\Exception $e) { \Illuminate\Support\Facades\Log::error("转换资金日志失败", [ 'record_id' => $record->id, 'error' => $e->getMessage() ]); return null; } } /** * 构建用户友好的消息 * * @param FundLogModel $record 资金日志记录 * @param string $fundName 资金名称 * @param string $action 操作类型(获得/消耗) * @param float $amount 金额 * @return string */ private function buildUserFriendlyMessage(FundLogModel $record, string $fundName, string $action, float $amount): string { // 解析备注信息 $remarkInfo = $this->parseRemark($record->remark); // 根据来源类型生成不同的消息格式 if (isset($remarkInfo['source']) && isset($remarkInfo['id'])) { $sourceMessage = $this->getSourceMessage($remarkInfo['source'], $remarkInfo['id'], $action); if ($sourceMessage) { return "{$sourceMessage}{$action}{$fundName} {$amount}"; } } // 特殊处理operate_type=3(管理员操作)的情况 try { // 直接从属性数组获取值,避免枚举转换问题 $operateTypeValue = $record->getAttributes()['operate_type'] ?? null; if ($operateTypeValue == 3 || $operateTypeValue === '3') { $sourceMessage = $this->getAdminOperationMessage($record, $action); if ($sourceMessage) { return "{$sourceMessage}{$action}{$fundName} {$amount}"; } } } catch (\Exception $e) { // 如果类型转换失败,记录错误但继续处理 \Illuminate\Support\Facades\Log::warning("处理operate_type失败", [ 'record_id' => $record->id, 'operate_type_raw' => $record->getAttributes()['operate_type'] ?? 'null', 'error' => $e->getMessage(), 'collector' => $this->collectorName ]); } // 如果无法解析来源信息,使用默认格式 return "{$action}{$fundName} {$amount}"; } /** * 获取资金名称 * * @param mixed $fundId 资金ID * @return string */ private function getFundName(FUND_TYPE $fundId): string { return FUND_TYPE::getName($fundId); } /** * 解析备注信息 * * @param string $remark 备注内容 * @return array 解析后的信息数组 */ private function parseRemark(string $remark): array { $info = []; // 解析格式:币种消耗:2,消耗组:16,来源:shop_buy,ID:7 // 使用更宽松的正则表达式来匹配中文和英文字符 if (preg_match_all('/([^:,]+):([^,]+)/', $remark, $matches)) { for ($i = 0; $i < count($matches[1]); $i++) { $key = trim($matches[1][$i]); $value = trim($matches[2][$i]); // 转换中文键名为英文 switch ($key) { case '来源': $info['source'] = $value; break; case 'ID': $info['id'] = (int)$value; break; case '消耗组': $info['consume_group'] = (int)$value; break; case '币种消耗': $info['fund_type'] = (int)$value; break; default: $info[$key] = $value; } } } return $info; } /** * 根据来源信息获取操作描述 * * @param string $source 来源类型 * @param int $id 来源ID * @param string $action 操作类型 * @return string|null 操作描述,null表示使用默认格式 */ private function getSourceMessage(string $source, int $id, string $action): ?string { switch ($source) { case 'shop_buy': $itemName = $this->getShopItemName($id); return "购买{$itemName}"; case '开启宝箱': case 'chest_open': return "开启宝箱"; case 'house_upgrade': return "房屋升级"; case 'land_upgrade': return "土地升级"; case 'task_reward': return "任务奖励"; case 'system_gift': return "系统赠送"; case 'admin_operation': return "管理员操作"; case 'test_command': return "测试操作"; default: return null; } } /** * 获取管理员操作消息 * * @param FundLogModel $record 资金日志记录 * @param string $action 操作类型 * @return string|null 操作描述,null表示使用默认格式 */ private function getAdminOperationMessage(FundLogModel $record, string $action): ?string { try { // 尝试从fund_admin表获取详细信息 // 用户不需要知道这么多,隐藏详情 // $adminOperation = \App\Module\Fund\Models\FundAdminModel::find($record->operate_id); // // if ($adminOperation) { // // 如果找到管理员操作记录,使用其备注信息 // if (!empty($adminOperation->remark) && $adminOperation->remark !== 'Admin Approved') { // return "管理员操作({$adminOperation->remark})"; // } // // // 获取管理员信息 // $adminInfo = $this->getAdminInfo($adminOperation->admin_id); // return "管理员操作({$adminInfo})"; // } // 如果没有找到对应的管理员操作记录,尝试使用fund_logs的备注 if (!empty($record->remark) && $record->remark !== 'Admin Approved') { return "管理员操作({$record->remark})"; } // 如果备注也没有有用信息,显示操作ID return "管理员操作"; } catch (\Exception $e) { \Illuminate\Support\Facades\Log::warning("获取管理员操作信息失败", [ 'fund_log_id' => $record->id, 'operate_id' => $record->operate_id, 'error' => $e->getMessage(), 'collector' => $this->collectorName ]); return "管理员操作"; } } /** * 获取管理员信息 * * @param int $adminId 管理员ID * @return string */ private function getAdminInfo(int $adminId): string { try { // 尝试获取管理员用户信息 $adminUser = \Dcat\Admin\Models\Administrator::find($adminId); if ($adminUser && !empty($adminUser->name)) { return $adminUser->name; } return "管理员{$adminId}"; } catch (\Exception $e) { return "管理员{$adminId}"; } } /** * 获取商店商品名称 * * @param int $itemId 商品ID * @return string */ private function getShopItemName(int $itemId): string { // 检查缓存 if (isset($this->shopItemNameCache[$itemId])) { return $this->shopItemNameCache[$itemId]; } try { $shopItem = \App\Module\Shop\Models\ShopItem::find($itemId); if ($shopItem && $shopItem->name) { $this->shopItemNameCache[$itemId] = $shopItem->name; return $shopItem->name; } $defaultName = "商品{$itemId}"; $this->shopItemNameCache[$itemId] = $defaultName; return $defaultName; } catch (\Exception $e) { \Illuminate\Support\Facades\Log::error("获取商店商品名称失败", [ 'item_id' => $itemId, 'error' => $e->getMessage(), 'collector' => $this->collectorName ]); $defaultName = "商品{$itemId}"; $this->shopItemNameCache[$itemId] = $defaultName; return $defaultName; } } /** * 是否应该记录此日志 * * @param FundLogModel $record * @return bool */ private function shouldLogRecord(FundLogModel $record): bool { // 可以在这里添加过滤规则 // 例如:只记录金额大于某个值的变更 // 跳过金额为0的记录 if ($record->amount == 0) { return false; } // 可以添加更多过滤条件 // 例如:跳过某些操作类型 // if (in_array($record->operate_type, [...])) { // return false; // } return true; } /** * 重写转换方法,添加过滤逻辑 * * @param FundLogModel $record 资金日志记录 * @return array|null 用户日志数据,null表示跳过 */ protected function convertToUserLogWithFilter($record): ?array { // 检查是否应该记录此日志 if (!$this->shouldLogRecord($record)) { return null; } return $this->convertToUserLog($record); } /** * 根据备注信息获取合适的source_type * * @param string $remark 备注内容 * @return string */ private function getSourceTypeByRemark(string $remark): string { $remarkInfo = $this->parseRemark($remark); if (isset($remarkInfo['source'])) { switch ($remarkInfo['source']) { case 'shop_buy': return REWARD_SOURCE_TYPE::SHOP_PURCHASE->value; case 'chest_open': case '开启宝箱': return REWARD_SOURCE_TYPE::CHEST->value; case 'house_upgrade': case 'land_upgrade': return REWARD_SOURCE_TYPE::FARM_UPGRADE->value; case 'task_reward': return REWARD_SOURCE_TYPE::TASK->value; case 'system_gift': return REWARD_SOURCE_TYPE::SYSTEM->value; case 'admin_operation': return REWARD_SOURCE_TYPE::ADMIN_GRANT->value; case 'test_command': return REWARD_SOURCE_TYPE::TEST->value; default: return REWARD_SOURCE_TYPE::SYSTEM->value; } } return REWARD_SOURCE_TYPE::SYSTEM->value; } /** * 创建用户日志数据数组(带自定义source_type) * * @param int $userId 用户ID * @param string $message 日志消息 * @param int $sourceId 来源记录ID * @param string|null $originalTime 原始时间(业务发生时间),null则使用当前时间 * @param string $sourceType 来源类型 * @return array */ protected function createUserLogDataWithSourceType( int $userId, string $message, int $sourceId, ?string $originalTime = null, string $sourceType = null ): array { $now = now()->toDateTimeString(); $originalTime = $originalTime ?? $now; $sourceType = $sourceType ?? $this->sourceType; return [ 'user_id' => $userId, 'message' => $message, 'source_type' => $sourceType, 'source_id' => $sourceId, 'source_table' => $this->sourceTable, 'original_time' => $originalTime, // 原始业务时间 'collected_at' => $now, // 收集时间 'created_at' => $now, // 兼容字段 ]; } }