| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591 |
- <?php
- namespace App\Http\Controllers;
- use Illuminate\Http\Request;
- use App\Models\Char\CharBase;
- use App\Models\Char\CharCashItem_OutputBox;
- use App\Models\Char\CharItem;
- use App\Models\Char\CharFellow;
- use App\Models\Char\CharQuest;
- use App\Models\Char\CharMail;
- use Illuminate\Support\Facades\Log;
- use App\Models\Char\CharQuest_History;
- class CharController extends Controller
- {
- /**
- * Получение всех персонажей из базы данных с пагинацией
- * (по умолчанию - только активные персонажи, если в запросе with_deleted=true - в том числе удаленные).
- *
- * @return \Illuminate\Http\JsonResponse
- */
- public function GetAllCharacters(Request $request)
- {
- // 1. Валидация и параметры запроса
- $validated = $request->validate([
- 'per_page' => 'sometimes|integer|min:1|max:100',
- 'search' => 'sometimes|string|nullable|max:50',
- 'with_deleted' => 'sometimes'
- ]);
- // Флаг: включать ли удаленных персонажей
- // Используем $request->boolean(), который корректно приводит строки ('true', '1', 'on', 'yes') к true
- $withDeleted = $request->boolean('with_deleted');
- $perPage = $validated['per_page'] ?? 15;
- $search = trim($validated['search'] ?? '');
- $sortField = $request->input('sort_field', 'DBKey');
- $sortOrder = strtolower($request->input('sort_order', 'asc')) === 'desc' ? 'desc' : 'asc';
- // whitelist для полей сортировки и выборки
- $allowedSortFields = ['DBKey', 'Name', 'Level', 'AccountName', 'Deleted'];
- if (!in_array($sortField, $allowedSortFields, true)) {
- $sortField = 'DBKey';
- }
- // 2. Запрос с выборкой только нужных полей
- $query = CharBase::query()->select($allowedSortFields);
- // Если не запрошено with_deleted=true — возвращаем только активных персонажей (Deleted = 0)
- if (!$withDeleted) {
- $query->where('Deleted', 0);
- }
- if ($search !== '') {
- $query->where(function ($q) use ($search) {
- $q->where('Name', 'like', "%{$search}%")
- ->orWhere('AccountName', 'like', "%{$search}%");
- if (ctype_digit($search)) {
- $q->orWhere('DBKey', (int) $search);
- }
- });
- }
- // 3. Сортировка и пагинация
- $charactersPaginator = $query->orderBy($sortField, $sortOrder)->paginate($perPage);
- // 4. Ответ
- // Если по запросу не найдено ни одного персонажа, возвращаем специальный ответ
- // для сохранения обратной совместимости API.
- if ($charactersPaginator->total() === 0) {
- return response()->json([
- 'characters' => (object) [],
- 'code' => -2,
- 'msg' => 'Characters not found.'
- ], 200);
- }
- // Laravel автоматически преобразует пагинатор в корректный JSON,
- // включая только выбранные через select() поля.
- return response()->json([
- 'code' => 0,
- 'msg' => 'Characters successfully received.',
- 'characters' => $charactersPaginator,
- ], 200);
- }
- /**
- * Получение всех персонажей пользователя по имени пользователя. *
- * (по умолчанию - только активные персонажи, если в запросе with_deleted=true - в том числе удаленные персонажи)
- *
- * @param string $username
- * @return \Illuminate\Http\JsonResponse
- */
- public function GetUserCharacters($username)
- {
- // Флаг: включать ли удаленных персонажей (with_deleted=true/1/on/yes)
- $withDeleted = request()->boolean('with_deleted');
- // Получаем всех персонажей по $username
- $characters = CharBase::where('AccountName', $username)
- ->when(!$withDeleted, function ($q) {
- // Если не запрошены удаленные персонажи — фильтруем только активных
- $q->where('Deleted', 0);
- })
- ->get();
- // Проверяем, найдены ли персонажи
- if ($characters->isEmpty()) {
- return response()->json(['characters' => [], 'code' => -2, 'msg' => 'Characters not found for this user.'], 200);
- }
- // Подсчитываем общее время в игре для всех персонажей пользователя
- $totalPlayTime = $characters->sum('TotalPlayTime');
- // Возвращаем только DBKey, Name, Level, GuildDBKey, TotalPlayTime, Deleted для всех персонажей
- $characters = $characters->map(function ($character) {
- return [
- 'DBKey' => $character->DBKey,
- 'Name' => $character->Name,
- 'Level' => $character->Level,
- 'GuildDBKey' => $character->GuildDBKey,
- 'CharPlayTime' => $character->TotalPlayTime,
- 'Deleted' => $character->Deleted,
- ];
- });
- return response()->json([
- 'code' => 0,
- 'msg' => 'Characters successfully received.',
- 'characters' => $characters,
- 'totalPlayTime' => $totalPlayTime
- ], 200);
- }
- /**
- * Отправка предмета на персонажа.
- *
- * @param \Illuminate\Http\Request $request
- * @return \Illuminate\Http\JsonResponse
- */
- public function SendItemToCharacter(Request $request)
- {
- $character = CharBase::where('DBKey', $request->Owner)->first();
- if (!$character) {
- return response()->json(['code' => -1, 'msg' => 'Character not found.'], 404);
- }
- $validator = \Validator::make($request->all(), [
- 'Owner' => 'required',
- 'Kind' => 'required',
- 'RecId' => 'required',
- 'Amount' => 'required|numeric',
- 'Period' => 'required|numeric',
- 'evPType' => 'required',
- 'Comment' => 'required'
- ], [], [
- 'Owner' => '',
- 'Kind' => '',
- 'RecId' => '',
- 'Amount' => '',
- 'Period' => '',
- 'evPType' => '',
- 'Comment' => ''
- ]);
- if (!$validator->passes())
- return response()->json(['code' => -1, 'msg' => $validator->errors()], 400);
- CharCashItem_OutputBox::SENDITEMOUTPUTBOX($request);
- return response()->json([
- 'code' => 0,
- 'msg' => 'Items successfully sent.'
- ], 200);
- }
- /**
- * Получение всех данных персонажа из указанной таблицы.
- *
- * @param int $char_id
- * @param string $table_name
- * @return \Illuminate\Http\JsonResponse
- */
- public function GetCharacterData($char_id, $table_name)
- {
- // Получаем конфигурацию таблицы через приватный метод
- $config = $this->resolveTable($table_name);
- if (!$config) {
- return response()->json(['code' => -1, 'msg' => 'Table not allowed or does not exist.'], 400);
- }
- $modelClass = $config['model'];
- $key = $config['key'];
- // Проверяем существование персонажа
- if (!CharBase::find($char_id)) {
- return response()->json(['code' => -2, 'msg' => 'Character not found.'], 404);
- }
- // Получаем данные
- $data = $modelClass::where($key, $char_id)->get();
- if ($data->isEmpty()) {
- return response()->json(['code' => -3, 'msg' => 'No data found for this character in the specified table.'], 200);
- }
- return response()->json([
- 'code' => 0,
- 'msg' => 'Data successfully received.',
- 'data' => $data
- ], 200);
- }
- /**
- * Обновление данных персонажа в указанной таблице.
- *
- * @param Request $request
- * @param int $char_id
- * @param string $table_name
- * @return \Illuminate\Http\JsonResponse
- */
- public function UpdateCharacterData(Request $request, $char_id, $table_name)
- {
- // 1. Получаем конфигурацию таблицы через приватный метод
- $config = $this->resolveTable($table_name);
- if (!$config) {
- return response()->json(['code' => -1, 'msg' => 'Table not allowed or does not exist.'], 400);
- }
- $modelClass = $config['model'];
- $ownerKey = $config['key']; // Ключ, по которому связывается с CharBase - id персонажа
- $primaryKey = $config['pk']; // Для CharBase - DBKey
- // 2. Определяем, какую запись обновляем
- if ($table_name === 'CharBase') {
- $model = $modelClass::find($char_id);
- } else {
- $recordId = $request->input($primaryKey);
- if (!$recordId) {
- return response()->json(['code' => -4, 'msg' => "Primary key '$primaryKey' is required for this table."], 400);
- }
- $model = $modelClass::find($recordId);
- }
- if (!$model) {
- return response()->json(['code' => -2, 'msg' => 'Record not found.'], 404);
- }
- // Проверяем принадлежность записи персонажу
- if ($model->{$ownerKey} != $char_id) {
- return response()->json(['code' => -5, 'msg' => 'Record does not belong to the character.'], 403);
- }
- // 3. Подготавливаем данные к обновлению – защищаем PK и владение
- $data = $request->except([$primaryKey, $ownerKey, 'DBKey', 'Owner', 'Account']);
- // 4. Обновляем модель и возвращаем ответ
- return $this->_updateAndRespond($model, $data);
- }
- /**
- * Получает конкретный предмет по $char_item_id из указанной таблицы $table предметов.
- *
- * @param string $table
- * @param int $char_item_id
- * @return \Illuminate\Http\JsonResponse
- */
- public function GetCharItem(string $table, int $char_item_id)
- {
- // Получаем конфигурацию таблицы (модель и первичный ключ)
- $config = $this->resolveTable($table);
- if (!$config) {
- return response()->json(['code' => -1, 'msg' => 'Table not allowed or does not exist.'], 400);
- }
- $modelClass = $config['model'];
- $pk = $config['pk'];
- // Ищем предмет по первичному ключу из конфигурации
- $item = $modelClass::where($pk, $char_item_id)->first();
- if (!$item) {
- return response()->json([
- 'code' => -2,
- 'msg' => 'Item not found.',
- ], 404);
- }
- return response()->json([
- 'code' => 0,
- 'msg' => 'Item successfully received.',
- 'data' => $item,
- ], 200);
- }
- /**
- * Обновляет конкретный предмет по $char_item_id в указанной таблице $table предметов.
- *
- * @param \Illuminate\Http\Request $request
- * @param string $table
- * @param int $char_item_id
- * @return \Illuminate\Http\JsonResponse
- */
- public function UpdateCharItem(Request $request, string $table, int $char_item_id)
- {
- // Получаем конфигурацию таблицы (модель и первичный ключ)
- $config = $this->resolveTable($table);
- if (!$config) {
- return response()->json(['code' => -1, 'msg' => 'Table not allowed or does not exist.'], 400);
- }
- $modelClass = $config['model'];
- $pk = $config['pk'];
- // Ищем предмет по первичному ключу из конфигурации
- $item = $modelClass::where($pk, $char_item_id)->first();
- if (!$item) {
- return response()->json([
- 'code' => -2,
- 'msg' => 'Item not found.',
- ], 404);
- }
- // Определяем защищённые поля динамически, чтобы не дать изменить PK и владельца
- $protected = [$pk, 'Owner', 'Account'];
- $data = $request->except($protected);
- return $this->_updateAndRespond($item, $data, 'Item successfully updated.');
- }
- /**
- * Получение квеста персонажа по char_id и RecId.
- * Поддерживается выбор таблицы (CharQuest или CharQuest_History) через опциональный параметр $table.
- *
- * @param string $char_id
- * @param string $rec_id
- * @param string|null $table
- * @return \Illuminate\Http\JsonResponse
- */
- public function GetCharQuest(string $char_id, string $rec_id, ?string $table = null)
- {
- // По умолчанию используем CharQuest
- $table = $table ?? 'CharQuest';
- // Проверяем допустимость таблицы для данной операции
- $allowedQuestTables = ['CharQuest', 'CharQuest_History'];
- if (!in_array($table, $allowedQuestTables, true)) {
- return response()->json(['code' => -1, 'msg' => 'Table not allowed for this operation.'], 400);
- }
- // Проверяем существование персонажа
- if (!CharBase::find($char_id)) {
- return response()->json(['code' => -2, 'msg' => 'Character not found.'], 404);
- }
- // Получаем конфиг модели по таблице и делаем выборку по Owner и RecId
- $config = $this->resolveTable($table);
- $modelClass = $config['model'];
- $quest = $modelClass::where('Owner', $char_id)
- ->where('RecId', $rec_id)
- ->first();
- if (!$quest) {
- return response()->json([
- 'code' => -3,
- 'msg' => 'Quest not found for this character.',
- ], 404);
- }
- return response()->json([
- 'code' => 0,
- 'msg' => 'Quest successfully received.',
- 'data' => $quest,
- ], 200);
- }
- /**
- * Обновление квеста персонажа по char_id и RecId.
- * Поддерживается выбор таблицы (CharQuest или CharQuest_History) через опциональный параметр $table.
- *
- * @param \Illuminate\Http\Request $request
- * @param string $char_id
- * @param string $rec_id
- * @param string|null $table
- * @return \Illuminate\Http\JsonResponse
- */
- public function UpdateCharQuest(Request $request, string $char_id, string $rec_id, ?string $table = null)
- {
- // По умолчанию используем CharQuest
- $table = $table ?? 'CharQuest';
- // Проверяем допустимость таблицы для данной операции
- $allowedQuestTables = ['CharQuest', 'CharQuest_History'];
- if (!in_array($table, $allowedQuestTables, true)) {
- return response()->json(['code' => -1, 'msg' => 'Table not allowed for this operation.'], 400);
- }
- // Проверяем существование персонажа
- if (!CharBase::find($char_id)) {
- return response()->json(['code' => -2, 'msg' => 'Character not found.'], 404);
- }
- // Получаем конфиг модели по таблице и делаем выборку по Owner и RecId
- $config = $this->resolveTable($table);
- $modelClass = $config['model'];
- $quest = $modelClass::where('Owner', $char_id)
- ->where('RecId', $rec_id)
- ->first();
- if (!$quest) {
- return response()->json([
- 'code' => -3,
- 'msg' => 'Quest not found for this character.',
- ], 404);
- }
- // Защищаем поля Owner и RecId от изменения
- $protected = ['Owner', 'RecId'];
- $data = $request->except($protected);
- return $this->_updateAndRespond($quest, $data, 'Quest successfully updated.');
- }
- /**
- * Получение письма персонажа по char_id и MailDBKey.
- * Возвращает ту же структуру записи, что и GetCharacterData/CharMail.
- *
- * @param string $char_id
- * @param string $mail_id
- * @return \Illuminate\Http\JsonResponse
- */
- public function GetCharMail(string $char_id, string $mail_id)
- {
- // Проверяем существование персонажа
- if (!CharBase::find($char_id)) {
- return response()->json(['code' => -2, 'msg' => 'Character not found.'], 404);
- }
- // Получаем письмо, принадлежащее персонажу
- $mail = CharMail::where('RecverDBKey', $char_id)
- ->where('MailDBKey', $mail_id)
- ->first();
- if (!$mail) {
- return response()->json([
- 'code' => -3,
- 'msg' => 'Mail not found for this character.',
- ], 404);
- }
- return response()->json([
- 'code' => 0,
- 'msg' => 'Mail successfully received.',
- 'data' => $mail,
- ], 200);
- }
- /**
- * Обновление письма персонажа по char_id и MailDBKey.
- * Принимает те же поля, что и UpdateCharacterData/CharMail, защищая ключи владения и PK.
- *
- * @param \Illuminate\Http\Request $request
- * @param string $char_id
- * @param string $mail_id
- * @return \Illuminate\Http\JsonResponse
- */
- public function UpdateCharMail(Request $request, string $char_id, string $mail_id)
- {
- // Проверяем существование персонажа
- if (!CharBase::find($char_id)) {
- return response()->json(['code' => -2, 'msg' => 'Character not found.'], 404);
- }
- // Получаем письмо, принадлежащее персонажу
- $mail = CharMail::where('RecverDBKey', $char_id)
- ->where('MailDBKey', $mail_id)
- ->first();
- if (!$mail) {
- return response()->json([
- 'code' => -3,
- 'msg' => 'Mail not found for this character.',
- ], 404);
- }
- // Защищаем поля связи и первичный ключ
- $protected = ['RecverDBKey', 'MailDBKey', 'Owner', 'Account'];
- $data = $request->except($protected);
- return $this->_updateAndRespond($mail, $data, 'Mail successfully updated.');
- }
- //------------------------------ ПРИВАТНЫЕ МЕТОДЫ -----------------------------------------
- /**
- * Возвращает конфигурацию таблицы или null, если таблица не разрешена.
- *
- * @param string $table
- * @return array|null
- */
- private function resolveTable(string $table): ?array
- {
- return self::TABLES[$table] ?? null;
- }
- /**
- * Белый список доступных для обновления таблиц.
- * Используется в resolveTable(), GetCharacterData(), UpdateCharacterData().
- */
- private const TABLES = [
- 'CharBase' => ['key' => 'DBKey', 'model' => CharBase::class, 'pk' => 'DBKey'],
- 'CharItem' => ['key' => 'Owner', 'model' => CharItem::class, 'pk' => 'CharItemID'],
- 'CharFellow' => ['key' => 'Owner', 'model' => CharFellow::class, 'pk' => 'FellowID'],
- 'CharCashItem_OutputBox' => ['key' => 'Owner', 'model' => CharCashItem_OutputBox::class, 'pk' => 'ItemDBIndex'],
- 'CharQuest' => ['key' => 'Owner', 'model' => CharQuest::class, 'pk' => 'RecId'],
- 'CharQuest_History' => ['key' => 'Owner', 'model' => CharQuest_History::class, 'pk' => 'RecId'],
- 'CharMail' => ['key' => 'RecverDBKey', 'model' => CharMail::class, 'pk' => 'MailDBKey'],
- ];
- /**
- * Общий метод для обновления модели и формирования ответа.
- *
- * @param \Illuminate\Database\Eloquent\Model $model
- * @param array $data
- * @param string $successMsg
- * @return \Illuminate\Http\JsonResponse
- */
- private function _updateAndRespond(\Illuminate\Database\Eloquent\Model $model, array $data, string $successMsg = 'Data successfully updated.')
- {
- // 1. Фильтруем поля для массового присвоения
- $fillable = $model->getFillable();
- $guarded = $model->getGuarded();
- if (!empty($fillable)) {
- // Если есть fillable - используем только их
- $filteredData = array_intersect_key($data, array_flip($fillable));
- } elseif ($guarded === ['*']) {
- // Если guarded = ['*'] - запрещено массовое присвоение
- return response()->json(['code' => -6, 'msg' => 'Mass assignment is not allowed for this model.'], 400);
- } else {
- // Если есть guarded (но не ['*']) - исключаем только guarded поля
- $filteredData = array_diff_key($data, array_flip($guarded));
- }
- // 2. Конвертируем null-поля в '', чтобы вместо NULL в БД сохранялась пустая строка
- $filteredData = array_map(static fn($v) => $v === null ? '' : $v, $filteredData);
- if (empty($filteredData)) {
- return response()->json(['code' => -6, 'msg' => 'No valid fields to update.'], 400);
- }
- // 3. Пытаемся обновить запись
- try {
- $ok = $model->update($filteredData);
- } catch (\Throwable $e) {
- Log::error('Database error while updating record:', ['error' => $e->getMessage()]);
- return response()->json([
- 'code' => -7,
- 'msg' => 'Database error while updating record.',
- 'error' => $e->getMessage(),
- ], 500);
- }
- if (!$ok) {
- return response()->json([
- 'code' => -8,
- 'msg' => 'Update failed, record not modified.',
- ], 500);
- }
- // 4. Возвращаем успешный ответ
- return response()->json([
- 'code' => 0,
- 'msg' => $successMsg,
- 'data' => $model->fresh()
- ], 200);
- }
- }
|