'视讯', 2 => '电子', 3 => '彩票', 4 => '体育', 5 => '电竞', 6 => '捕鱼', 7 => '棋牌', ]; public function items(): JsonResponse { try { request()->validate([ 'item_type' => ['nullable', Rule::in([ EgameItem::TYPE_CATEGORY, EgameItem::TYPE_PLATFORM, EgameItem::TYPE_GAME, ])], 'platform_scope' => ['nullable', Rule::in([ EgameItem::PLATFORM_SCOPE_GLOBAL, EgameItem::PLATFORM_SCOPE_CATEGORY, ])], 'plat_type' => ['nullable', 'string', 'max:32'], 'game_type' => ['nullable', 'integer', 'min:0', 'max:255'], 'game_code' => ['nullable', 'string', 'max:128'], 'keyword' => ['nullable', 'string', 'max:128'], 'status' => ['nullable', 'integer', Rule::in([ EgameItem::STATUS_DISABLED, EgameItem::STATUS_ENABLED, ])], 'page' => ['nullable', 'integer', 'min:1'], 'limit' => ['nullable', 'integer', 'min:1', 'max:200'], ]); $query = EgameItem::query(); foreach (['item_type', 'plat_type', 'game_type', 'game_code', 'status'] as $field) { $value = request()->input($field); if ($value !== null && $value !== '') { $query->where($field, $value); } } $platformScope = (string) request()->input('platform_scope', ''); if ($platformScope !== '') { $itemType = (string) request()->input('item_type', ''); if ($itemType !== '' && $itemType !== EgameItem::TYPE_PLATFORM) { throw new Exception('platform_scope 只适用于平台配置', HttpStatus::CUSTOM_ERROR); } $query->where('item_type', EgameItem::TYPE_PLATFORM); if ($platformScope === EgameItem::PLATFORM_SCOPE_GLOBAL) { $query->where('game_type', 0); } else { $query->where('game_type', '>', 0); } } $keyword = trim((string) request()->input('keyword', '')); if ($keyword !== '') { $query->where(function ($query) use ($keyword) { $query->where('name', 'like', "%{$keyword}%") ->orWhere('plat_type', 'like', "%{$keyword}%") ->orWhere('game_code', 'like', "%{$keyword}%"); }); } $limit = (int) request()->input('limit', 15); $page = (int) request()->input('page', 1); $total = (clone $query)->count(); $states = $this->catalogStates(); $list = $query->orderByDesc('sort') ->orderByDesc('id') ->forPage($page, $limit) ->get() ->map(fn (EgameItem $item) => $this->decorateItem($item, $states)) ->values(); } catch (ValidationException $e) { return $this->error(HttpStatus::VALIDATION_FAILED, $e->validator->errors()->first()); } catch (Exception $e) { return $this->error(HttpStatus::CUSTOM_ERROR, $e->getMessage()); } return $this->success(['total' => $total, 'data' => $list]); } public function options(): JsonResponse { try { $states = $this->catalogStates(); $categoryPlatformCounts = $this->groupedCounts( EgameItem::query() ->where('item_type', EgameItem::TYPE_PLATFORM) ->where('game_type', '>', 0), 'game_type', 'plat_type' ); $categoryGameCounts = $this->groupedCounts( EgameItem::query()->where('item_type', EgameItem::TYPE_GAME), 'game_type', 'id' ); $platformCategoryCounts = $this->groupedCounts( EgameItem::query() ->where('item_type', EgameItem::TYPE_PLATFORM) ->where('game_type', '>', 0), 'plat_type', 'game_type' ); $platformGameCounts = $this->groupedCounts( EgameItem::query()->where('item_type', EgameItem::TYPE_GAME), 'plat_type', 'id' ); $categories = EgameItem::query() ->where('item_type', EgameItem::TYPE_CATEGORY) ->orderByDesc('sort') ->orderBy('game_type') ->get() ->map(function (EgameItem $item) use ($states, $categoryPlatformCounts, $categoryGameCounts) { $this->decorateItem($item, $states); $key = (string) $item->game_type; $item->setAttribute('platform_count', $categoryPlatformCounts[$key] ?? 0); $item->setAttribute('game_count', $categoryGameCounts[$key] ?? 0); return $item; }) ->values(); $platforms = EgameItem::query() ->where('item_type', EgameItem::TYPE_PLATFORM) ->where('game_type', 0) ->orderByDesc('sort') ->orderBy('name') ->orderBy('plat_type') ->get() ->map(function (EgameItem $item) use ($states, $platformCategoryCounts, $platformGameCounts) { $this->decorateItem($item, $states); $key = strtolower((string) $item->plat_type); $item->setAttribute('category_count', $platformCategoryCounts[$key] ?? 0); $item->setAttribute('game_count', $platformGameCounts[$key] ?? 0); return $item; }) ->values(); } catch (Exception $e) { return $this->error(HttpStatus::CUSTOM_ERROR, $e->getMessage()); } return $this->success([ 'categories' => $categories, 'platforms' => $platforms, 'statuses' => [ ['value' => EgameItem::STATUS_ENABLED, 'label' => '开启'], ['value' => EgameItem::STATUS_DISABLED, 'label' => '关闭'], ], 'ingress' => [ ['value' => '1', 'label' => '仅电脑'], ['value' => '2', 'label' => '仅手机'], ['value' => '3', 'label' => '通用'], ], ]); } public function update(): JsonResponse { try { request()->validate([ 'id' => ['nullable', 'integer', 'min:0'], 'item_type' => ['nullable', Rule::in([ EgameItem::TYPE_CATEGORY, EgameItem::TYPE_PLATFORM, EgameItem::TYPE_GAME, ])], 'platform_scope' => ['nullable', Rule::in([ EgameItem::PLATFORM_SCOPE_GLOBAL, EgameItem::PLATFORM_SCOPE_CATEGORY, ])], 'plat_type' => ['nullable', 'string', 'max:32'], 'game_type' => ['nullable', 'integer', 'min:0', 'max:255'], 'game_code' => ['nullable', 'string', 'max:128'], 'name' => ['nullable', 'string', 'max:128'], 'ingress' => ['nullable', Rule::in(['1', '2', '3'])], 'lobby_enabled' => ['nullable', 'boolean'], 'logo' => ['nullable', 'string', 'max:500'], 'logo_langs' => ['nullable', 'array'], 'status' => ['nullable', 'integer', Rule::in([ EgameItem::STATUS_DISABLED, EgameItem::STATUS_ENABLED, ])], 'sort' => ['nullable', 'integer', 'min:-999999', 'max:999999'], ]); $id = (int) request()->input('id', 0); $item = DB::transaction(function () use ($id) { if ($id > 0) { $item = EgameItem::query()->find($id); if (!$item) { throw new Exception('配置不存在', HttpStatus::CUSTOM_ERROR); } $this->assertIdentityUnchanged($item); } else { $identity = $this->normalizedIdentityForCreate(); $item = EgameItem::query()->firstOrNew($identity); } $data = $this->mutableItemData($item, !$item->exists); $name = array_key_exists('name', $data) ? $data['name'] : (string) $item->name; if ($item->isCategory() && trim($name) === '') { throw new Exception('分类名称不能为空', HttpStatus::CUSTOM_ERROR); } if (!empty($data) || !$item->exists) { $item->fill($data)->save(); } return $item->fresh(); }); } catch (ValidationException $e) { return $this->error(HttpStatus::VALIDATION_FAILED, $e->validator->errors()->first()); } catch (Exception $e) { return $this->error((int) $e->getCode(), $e->getMessage()); } return $this->success($this->decorateItem($item, $this->catalogStates())); } public function setStatus(): JsonResponse { try { request()->validate([ 'id' => ['required', 'integer', 'min:1'], 'status' => ['required', 'integer', Rule::in([ EgameItem::STATUS_DISABLED, EgameItem::STATUS_ENABLED, ])], ]); $item = EgameItem::query()->find((int) request()->input('id')); if (!$item) { throw new Exception('配置不存在', HttpStatus::CUSTOM_ERROR); } $item->status = (int) request()->input('status'); $item->save(); $item = $item->fresh(); } catch (ValidationException $e) { return $this->error(HttpStatus::VALIDATION_FAILED, $e->validator->errors()->first()); } catch (Exception $e) { return $this->error((int) $e->getCode(), $e->getMessage()); } return $this->success($this->decorateItem($item, $this->catalogStates())); } public function delete(): JsonResponse { try { request()->validate([ 'id' => ['required', 'integer', 'min:1'], ]); if (!EgameItem::query()->whereKey((int) request()->input('id'))->exists()) { throw new Exception('配置不存在', HttpStatus::CUSTOM_ERROR); } } catch (ValidationException $e) { return $this->error(HttpStatus::VALIDATION_FAILED, $e->validator->errors()->first()); } catch (Exception $e) { return $this->error((int) $e->getCode(), $e->getMessage()); } return $this->error( HttpStatus::CUSTOM_ERROR, '第三方游戏目录由同步任务维护,不能删除,请将状态设为关闭' ); } private function normalizedIdentityForCreate(): array { $input = request()->all(); if (!array_key_exists('item_type', $input) || !array_key_exists('game_type', $input)) { throw new Exception('新增配置必须填写 item_type 和 game_type', HttpStatus::CUSTOM_ERROR); } $itemType = (string) request()->input('item_type', ''); $platformScope = (string) request()->input('platform_scope', ''); if (!in_array($itemType, [ EgameItem::TYPE_CATEGORY, EgameItem::TYPE_PLATFORM, EgameItem::TYPE_GAME, ], true)) { throw new Exception('item_type 参数错误', HttpStatus::CUSTOM_ERROR); } if ($platformScope !== '' && $itemType !== EgameItem::TYPE_PLATFORM) { throw new Exception('platform_scope 只适用于平台配置', HttpStatus::CUSTOM_ERROR); } $identity = [ 'item_type' => $itemType, 'plat_type' => strtolower(trim((string) request()->input('plat_type', ''))), 'game_type' => (int) request()->input('game_type'), 'game_code' => trim((string) request()->input('game_code', '')), ]; if ($itemType === EgameItem::TYPE_CATEGORY) { if ($identity['game_type'] <= 0) { throw new Exception('分类 game_type 必须大于 0', HttpStatus::CUSTOM_ERROR); } $identity['plat_type'] = ''; $identity['game_code'] = ''; return $identity; } if ($identity['plat_type'] === '') { throw new Exception('平台代码不能为空', HttpStatus::CUSTOM_ERROR); } if ($itemType === EgameItem::TYPE_PLATFORM) { if ($platformScope === EgameItem::PLATFORM_SCOPE_GLOBAL) { $identity['game_type'] = 0; } if ($platformScope === EgameItem::PLATFORM_SCOPE_CATEGORY && $identity['game_type'] <= 0) { throw new Exception('平台分类关联的 game_type 必须大于 0', HttpStatus::CUSTOM_ERROR); } $identity['game_code'] = ''; return $identity; } if ($identity['game_type'] <= 0) { throw new Exception('游戏 game_type 必须大于 0', HttpStatus::CUSTOM_ERROR); } if ($identity['game_code'] === '') { throw new Exception('游戏配置必须填写 game_code', HttpStatus::CUSTOM_ERROR); } return $identity; } private function assertIdentityUnchanged(EgameItem $item): void { $input = request()->all(); $checks = [ 'item_type' => (string) $item->item_type, 'plat_type' => strtolower((string) $item->plat_type), 'game_type' => (int) $item->game_type, 'game_code' => (string) $item->game_code, ]; foreach ($checks as $field => $expected) { if (!array_key_exists($field, $input) || $input[$field] === null) { continue; } $actual = $input[$field]; if ($field === 'plat_type') { $actual = strtolower(trim((string) $actual)); } elseif ($field === 'game_type') { $actual = (int) $actual; } else { $actual = trim((string) $actual); } if ($actual !== $expected) { throw new Exception('目录标识不允许修改:' . $field, HttpStatus::CUSTOM_ERROR); } } if ( array_key_exists('platform_scope', $input) && $input['platform_scope'] !== null && $input['platform_scope'] !== '' ) { $expectedScope = ''; if ($item->isGlobalPlatform()) { $expectedScope = EgameItem::PLATFORM_SCOPE_GLOBAL; } elseif ($item->isPlatformRelation()) { $expectedScope = EgameItem::PLATFORM_SCOPE_CATEGORY; } if ((string) $input['platform_scope'] !== $expectedScope) { throw new Exception('目录标识不允许修改:platform_scope', HttpStatus::CUSTOM_ERROR); } } } private function mutableItemData(EgameItem $item, bool $isNew): array { $input = request()->all(); $data = $isNew ? [ 'name' => '', 'ingress' => '3', 'lobby_enabled' => 0, 'logo' => '', 'logo_langs' => null, 'status' => EgameItem::STATUS_ENABLED, 'sort' => 0, ] : []; foreach (['name', 'ingress', 'logo'] as $field) { if (array_key_exists($field, $input)) { $data[$field] = trim((string) $input[$field]); } } if (array_key_exists('logo_langs', $input)) { $data['logo_langs'] = $item->item_type === EgameItem::TYPE_GAME ? $input['logo_langs'] : null; } if (array_key_exists('status', $input)) { $data['status'] = (int) $input['status']; } if (array_key_exists('sort', $input)) { $data['sort'] = (int) $input['sort']; } if (array_key_exists('lobby_enabled', $input)) { $data['lobby_enabled'] = $item->isPlatformRelation() ? (int) request()->boolean('lobby_enabled') : 0; } if ($item->item_type !== EgameItem::TYPE_GAME && array_key_exists('ingress', $data)) { $data['ingress'] = '3'; } if ($item->item_type !== EgameItem::TYPE_GAME && array_key_exists('logo_langs', $data)) { $data['logo_langs'] = null; } if (!$item->isPlatformRelation() && array_key_exists('lobby_enabled', $data)) { $data['lobby_enabled'] = 0; } return $data; } private function catalogStates(): array { $states = [ 'categories' => [], 'global_platforms' => [], 'platform_relations' => [], ]; $rows = EgameItem::query() ->whereIn('item_type', [EgameItem::TYPE_CATEGORY, EgameItem::TYPE_PLATFORM]) ->get(); foreach ($rows as $row) { if ($row->isCategory()) { $states['categories'][(string) $row->game_type] = $row; continue; } $platType = strtolower((string) $row->plat_type); if ($row->isGlobalPlatform()) { $states['global_platforms'][$platType] = $row; continue; } if ($row->isPlatformRelation()) { $states['platform_relations'][$platType][(string) $row->game_type] = $row; } } return $states; } private function decorateItem(EgameItem $item, array $states): EgameItem { $platType = strtolower((string) $item->plat_type); $gameType = (string) $item->game_type; $disabledBy = null; $category = $states['categories'][$gameType] ?? null; $globalPlatform = $states['global_platforms'][$platType] ?? null; $platformRelation = $states['platform_relations'][$platType][$gameType] ?? null; if ($item->isCategory()) { if (!$this->enabled($item)) { $disabledBy = 'category'; } } elseif ($item->isGlobalPlatform()) { if (!$this->enabled($item)) { $disabledBy = 'platform'; } } elseif ($item->isPlatformRelation()) { if (!$this->enabled($category)) { $disabledBy = 'category'; } elseif (!$this->enabled($globalPlatform)) { $disabledBy = 'platform'; } elseif (!$this->enabled($item)) { $disabledBy = 'platform_category'; } } else { if (!$this->enabled($category)) { $disabledBy = 'category'; } elseif (!$this->enabled($globalPlatform)) { $disabledBy = 'platform'; } elseif (!$this->enabled($platformRelation)) { $disabledBy = 'platform_category'; } elseif (!$this->enabled($item)) { $disabledBy = 'game'; } } $item->setAttribute('game_type_name', $this->gameTypeName((int) $item->game_type, $states)); $item->setAttribute( 'platform_name', $this->platformName($item, $globalPlatform, $platformRelation) ); if ($item->item_type === EgameItem::TYPE_PLATFORM) { $item->setAttribute( 'platform_scope', $item->isGlobalPlatform() ? EgameItem::PLATFORM_SCOPE_GLOBAL : EgameItem::PLATFORM_SCOPE_CATEGORY ); } else { $item->setAttribute('platform_scope', null); } $item->setAttribute('effective_status', $disabledBy === null ? 1 : 0); $item->setAttribute('disabled_by', $disabledBy); return $item; } private function enabled(?EgameItem $item): bool { return $item !== null && $item->status === EgameItem::STATUS_ENABLED; } private function platformName( EgameItem $item, ?EgameItem $globalPlatform, ?EgameItem $platformRelation ): string { if ($item->isCategory()) { return ''; } if ($item->isGlobalPlatform() && trim((string) $item->name) !== '') { return (string) $item->name; } if ($globalPlatform && trim((string) $globalPlatform->name) !== '') { return (string) $globalPlatform->name; } if ($platformRelation && trim((string) $platformRelation->name) !== '') { return (string) $platformRelation->name; } if ($item->item_type === EgameItem::TYPE_PLATFORM && trim((string) $item->name) !== '') { return (string) $item->name; } return (string) $item->plat_type; } private function gameTypeName(int $gameType, array $states): string { if ($gameType <= 0) { return ''; } $category = $states['categories'][(string) $gameType] ?? null; if ($category && trim((string) $category->name) !== '') { return (string) $category->name; } return self::FALLBACK_GAME_TYPE_NAMES[$gameType] ?? ('分类' . $gameType); } private function groupedCounts($query, string $groupColumn, string $countColumn): array { return $query ->select($groupColumn) ->selectRaw("COUNT(DISTINCT {$countColumn}) AS aggregate") ->groupBy($groupColumn) ->pluck('aggregate', $groupColumn) ->map(fn ($count) => (int) $count) ->all(); } }