CharController.php 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499
  1. <?php
  2. namespace App\Http\Controllers;
  3. use Illuminate\Http\Request;
  4. use App\Models\Char\CharBase;
  5. use App\Models\Char\CharCashItem_OutputBox;
  6. use App\Models\Char\CharItem;
  7. use App\Models\Char\CharFellow;
  8. use App\Models\Char\CharQuest;
  9. use Illuminate\Support\Facades\Log;
  10. use App\Models\Char\CharQuest_History;
  11. class CharController extends Controller
  12. {
  13. /**
  14. * Получение всех персонажей из базы данных с пагинацией.
  15. * *
  16. * @return \Illuminate\Http\JsonResponse
  17. */
  18. public function GetAllCharacters(Request $request)
  19. {
  20. // 1. Валидация и параметры запроса
  21. $validated = $request->validate([
  22. 'per_page' => 'sometimes|integer|min:1|max:100',
  23. 'search' => 'sometimes|string|nullable|max:50',
  24. ]);
  25. $perPage = $validated['per_page'] ?? 15;
  26. $search = trim($validated['search'] ?? '');
  27. $sortField = $request->input('sort_field', 'DBKey');
  28. $sortOrder = strtolower($request->input('sort_order', 'asc')) === 'desc' ? 'desc' : 'asc';
  29. // whitelist для полей сортировки и выборки
  30. $allowedSortFields = ['DBKey', 'Name', 'Level', 'AccountName'];
  31. if (!in_array($sortField, $allowedSortFields, true)) {
  32. $sortField = 'DBKey';
  33. }
  34. // 2. Запрос с выборкой только нужных полей
  35. $query = CharBase::query()->select($allowedSortFields);
  36. if ($search !== '') {
  37. $query->where(function ($q) use ($search) {
  38. $q->where('Name', 'like', "%{$search}%")
  39. ->orWhere('AccountName', 'like', "%{$search}%");
  40. if (ctype_digit($search)) {
  41. $q->orWhere('DBKey', (int) $search);
  42. }
  43. });
  44. }
  45. // 3. Сортировка и пагинация
  46. $charactersPaginator = $query->orderBy($sortField, $sortOrder)->paginate($perPage);
  47. // 4. Ответ
  48. // Если по запросу не найдено ни одного персонажа, возвращаем специальный ответ
  49. // для сохранения обратной совместимости API.
  50. if ($charactersPaginator->total() === 0) {
  51. return response()->json([
  52. 'characters' => (object) [],
  53. 'code' => -2,
  54. 'msg' => 'Characters not found.'
  55. ], 200);
  56. }
  57. // Laravel автоматически преобразует пагинатор в корректный JSON,
  58. // включая только выбранные через select() поля.
  59. return response()->json([
  60. 'code' => 0,
  61. 'msg' => 'Characters successfully received.',
  62. 'characters' => $charactersPaginator,
  63. ], 200);
  64. }
  65. /**
  66. * Получение всех персонажей пользователя по имени пользователя.
  67. *
  68. * @param string $username
  69. * @return \Illuminate\Http\JsonResponse
  70. */
  71. public function GetUserCharacters($username)
  72. {
  73. // Получаем всех персонажей по $username
  74. $characters = CharBase::where('AccountName', $username)->get();
  75. // Проверяем, найдены ли персонажи
  76. if ($characters->isEmpty()) {
  77. return response()->json(['characters' => [], 'code' => -2, 'msg' => 'Characters not found for this user.'], 200);
  78. }
  79. // Подсчитываем общее время в игре для всех персонажей пользователя
  80. $totalPlayTime = $characters->sum('TotalPlayTime');
  81. // Возвращаем только DBKey, Name, Level, GuildDBKey, TotalPlayTime персонажей
  82. $characters = $characters->map(function ($character) {
  83. return [
  84. 'DBKey' => $character->DBKey,
  85. 'Name' => $character->Name,
  86. 'Level' => $character->Level,
  87. 'GuildDBKey' => $character->GuildDBKey,
  88. 'CharPlayTime' => $character->TotalPlayTime
  89. ];
  90. });
  91. return response()->json([
  92. 'code' => 0,
  93. 'msg' => 'Characters successfully received.',
  94. 'characters' => $characters,
  95. 'totalPlayTime' => $totalPlayTime
  96. ], 200);
  97. }
  98. /**
  99. * Отправка предмета на персонажа.
  100. *
  101. * @param \Illuminate\Http\Request $request
  102. * @return \Illuminate\Http\JsonResponse
  103. */
  104. public function SendItemToCharacter(Request $request)
  105. {
  106. $character = CharBase::where('DBKey', $request->Owner)->first();
  107. if (!$character) {
  108. return response()->json(['code' => -1, 'msg' => 'Character not found.'], 404);
  109. }
  110. $validator = \Validator::make($request->all(), [
  111. 'Owner' => 'required',
  112. 'Kind' => 'required',
  113. 'RecId' => 'required',
  114. 'Amount' => 'required|numeric',
  115. 'Period' => 'required|numeric',
  116. 'evPType' => 'required',
  117. 'Comment' => 'required'
  118. ], [], [
  119. 'Owner' => '',
  120. 'Kind' => '',
  121. 'RecId' => '',
  122. 'Amount' => '',
  123. 'Period' => '',
  124. 'evPType' => '',
  125. 'Comment' => ''
  126. ]);
  127. if (!$validator->passes())
  128. return response()->json(['code' => -1, 'msg' => $validator->errors()], 400);
  129. CharCashItem_OutputBox::SENDITEMOUTPUTBOX($request);
  130. return response()->json([
  131. 'code' => 0,
  132. 'msg' => 'Items successfully sent.'
  133. ], 200);
  134. }
  135. /**
  136. * Получение всех данных персонажа из указанной таблицы.
  137. *
  138. * @param int $char_id
  139. * @param string $table_name
  140. * @return \Illuminate\Http\JsonResponse
  141. */
  142. public function GetCharacterData($char_id, $table_name)
  143. {
  144. // Получаем конфигурацию таблицы через приватный метод
  145. $config = $this->resolveTable($table_name);
  146. if (!$config) {
  147. return response()->json(['code' => -1, 'msg' => 'Table not allowed or does not exist.'], 400);
  148. }
  149. $modelClass = $config['model'];
  150. $key = $config['key'];
  151. // Проверяем существование персонажа
  152. if (!CharBase::find($char_id)) {
  153. return response()->json(['code' => -2, 'msg' => 'Character not found.'], 404);
  154. }
  155. // Получаем данные
  156. $data = $modelClass::where($key, $char_id)->get();
  157. if ($data->isEmpty()) {
  158. return response()->json(['code' => -3, 'msg' => 'No data found for this character in the specified table.'], 200);
  159. }
  160. return response()->json([
  161. 'code' => 0,
  162. 'msg' => 'Data successfully received.',
  163. 'data' => $data
  164. ], 200);
  165. }
  166. /**
  167. * Обновление данных персонажа в указанной таблице.
  168. *
  169. * @param Request $request
  170. * @param int $char_id
  171. * @param string $table_name
  172. * @return \Illuminate\Http\JsonResponse
  173. */
  174. public function UpdateCharacterData(Request $request, $char_id, $table_name)
  175. {
  176. // 1. Получаем конфигурацию таблицы через приватный метод
  177. $config = $this->resolveTable($table_name);
  178. if (!$config) {
  179. return response()->json(['code' => -1, 'msg' => 'Table not allowed or does not exist.'], 400);
  180. }
  181. $modelClass = $config['model'];
  182. $ownerKey = $config['key']; // Ключ, по которому связывается с CharBase - id персонажа
  183. $primaryKey = $config['pk']; // Для CharBase - DBKey
  184. // 2. Определяем, какую запись обновляем
  185. if ($table_name === 'CharBase') {
  186. $model = $modelClass::find($char_id);
  187. } else {
  188. $recordId = $request->input($primaryKey);
  189. if (!$recordId) {
  190. return response()->json(['code' => -4, 'msg' => "Primary key '$primaryKey' is required for this table."], 400);
  191. }
  192. $model = $modelClass::find($recordId);
  193. }
  194. if (!$model) {
  195. return response()->json(['code' => -2, 'msg' => 'Record not found.'], 404);
  196. }
  197. // Проверяем принадлежность записи персонажу
  198. if ($model->{$ownerKey} != $char_id) {
  199. return response()->json(['code' => -5, 'msg' => 'Record does not belong to the character.'], 403);
  200. }
  201. // 3. Подготавливаем данные к обновлению – защищаем PK и владение
  202. $data = $request->except([$primaryKey, $ownerKey, 'DBKey', 'Owner', 'Account']);
  203. // 4. Обновляем модель и возвращаем ответ
  204. return $this->_updateAndRespond($model, $data);
  205. }
  206. /**
  207. * Получает конкретный предмет по $char_item_id из указанной таблицы $table предметов.
  208. *
  209. * @param string $table
  210. * @param int $char_item_id
  211. * @return \Illuminate\Http\JsonResponse
  212. */
  213. public function GetCharItem(string $table, int $char_item_id)
  214. {
  215. // Получаем конфигурацию таблицы (модель и первичный ключ)
  216. $config = $this->resolveTable($table);
  217. if (!$config) {
  218. return response()->json(['code' => -1, 'msg' => 'Table not allowed or does not exist.'], 400);
  219. }
  220. $modelClass = $config['model'];
  221. $pk = $config['pk'];
  222. // Ищем предмет по первичному ключу из конфигурации
  223. $item = $modelClass::where($pk, $char_item_id)->first();
  224. if (!$item) {
  225. return response()->json([
  226. 'code' => -2,
  227. 'msg' => 'Item not found.',
  228. ], 404);
  229. }
  230. return response()->json([
  231. 'code' => 0,
  232. 'msg' => 'Item successfully received.',
  233. 'data' => $item,
  234. ], 200);
  235. }
  236. /**
  237. * Обновляет конкретный предмет по $char_item_id в указанной таблице $table предметов.
  238. *
  239. * @param \Illuminate\Http\Request $request
  240. * @param string $table
  241. * @param int $char_item_id
  242. * @return \Illuminate\Http\JsonResponse
  243. */
  244. public function UpdateCharItem(Request $request, string $table, int $char_item_id)
  245. {
  246. // Получаем конфигурацию таблицы (модель и первичный ключ)
  247. $config = $this->resolveTable($table);
  248. if (!$config) {
  249. return response()->json(['code' => -1, 'msg' => 'Table not allowed or does not exist.'], 400);
  250. }
  251. $modelClass = $config['model'];
  252. $pk = $config['pk'];
  253. // Ищем предмет по первичному ключу из конфигурации
  254. $item = $modelClass::where($pk, $char_item_id)->first();
  255. if (!$item) {
  256. return response()->json([
  257. 'code' => -2,
  258. 'msg' => 'Item not found.',
  259. ], 404);
  260. }
  261. // Определяем защищённые поля динамически, чтобы не дать изменить PK и владельца
  262. $protected = [$pk, 'Owner', 'Account'];
  263. $data = $request->except($protected);
  264. return $this->_updateAndRespond($item, $data, 'Item successfully updated.');
  265. }
  266. /**
  267. * Получение квеста персонажа по char_id и RecId.
  268. * Поддерживается выбор таблицы (CharQuest или CharQuest_History) через опциональный параметр $table.
  269. *
  270. * @param string $char_id
  271. * @param string $rec_id
  272. * @param string|null $table
  273. * @return \Illuminate\Http\JsonResponse
  274. */
  275. public function GetCharQuest(string $char_id, string $rec_id, ?string $table = null)
  276. {
  277. // По умолчанию используем CharQuest
  278. $table = $table ?? 'CharQuest';
  279. // Проверяем допустимость таблицы для данной операции
  280. $allowedQuestTables = ['CharQuest', 'CharQuest_History'];
  281. if (!in_array($table, $allowedQuestTables, true)) {
  282. return response()->json(['code' => -1, 'msg' => 'Table not allowed for this operation.'], 400);
  283. }
  284. // Проверяем существование персонажа
  285. if (!CharBase::find($char_id)) {
  286. return response()->json(['code' => -2, 'msg' => 'Character not found.'], 404);
  287. }
  288. // Получаем конфиг модели по таблице и делаем выборку по Owner и RecId
  289. $config = $this->resolveTable($table);
  290. $modelClass = $config['model'];
  291. $quest = $modelClass::where('Owner', $char_id)
  292. ->where('RecId', $rec_id)
  293. ->first();
  294. if (!$quest) {
  295. return response()->json([
  296. 'code' => -3,
  297. 'msg' => 'Quest not found for this character.',
  298. ], 404);
  299. }
  300. return response()->json([
  301. 'code' => 0,
  302. 'msg' => 'Quest successfully received.',
  303. 'data' => $quest,
  304. ], 200);
  305. }
  306. /**
  307. * Обновление квеста персонажа по char_id и RecId.
  308. * Поддерживается выбор таблицы (CharQuest или CharQuest_History) через опциональный параметр $table.
  309. *
  310. * @param \Illuminate\Http\Request $request
  311. * @param string $char_id
  312. * @param string $rec_id
  313. * @param string|null $table
  314. * @return \Illuminate\Http\JsonResponse
  315. */
  316. public function UpdateCharQuest(Request $request, string $char_id, string $rec_id, ?string $table = null)
  317. {
  318. // По умолчанию используем CharQuest
  319. $table = $table ?? 'CharQuest';
  320. // Проверяем допустимость таблицы для данной операции
  321. $allowedQuestTables = ['CharQuest', 'CharQuest_History'];
  322. if (!in_array($table, $allowedQuestTables, true)) {
  323. return response()->json(['code' => -1, 'msg' => 'Table not allowed for this operation.'], 400);
  324. }
  325. // Проверяем существование персонажа
  326. if (!CharBase::find($char_id)) {
  327. return response()->json(['code' => -2, 'msg' => 'Character not found.'], 404);
  328. }
  329. // Получаем конфиг модели по таблице и делаем выборку по Owner и RecId
  330. $config = $this->resolveTable($table);
  331. $modelClass = $config['model'];
  332. $quest = $modelClass::where('Owner', $char_id)
  333. ->where('RecId', $rec_id)
  334. ->first();
  335. if (!$quest) {
  336. return response()->json([
  337. 'code' => -3,
  338. 'msg' => 'Quest not found for this character.',
  339. ], 404);
  340. }
  341. // Защищаем поля Owner и RecId от изменения
  342. $protected = ['Owner', 'RecId'];
  343. $data = $request->except($protected);
  344. return $this->_updateAndRespond($quest, $data, 'Quest successfully updated.');
  345. }
  346. //------------------------------ ПРИВАТНЫЕ МЕТОДЫ -----------------------------------------
  347. /**
  348. * Возвращает конфигурацию таблицы или null, если таблица не разрешена.
  349. *
  350. * @param string $table
  351. * @return array|null
  352. */
  353. private function resolveTable(string $table): ?array
  354. {
  355. return self::TABLES[$table] ?? null;
  356. }
  357. /**
  358. * Белый список доступных для обновления таблиц.
  359. * Используется в resolveTable(), GetCharacterData(), UpdateCharacterData().
  360. */
  361. private const TABLES = [
  362. 'CharBase' => ['key' => 'DBKey', 'model' => CharBase::class, 'pk' => 'DBKey'],
  363. 'CharItem' => ['key' => 'Owner', 'model' => CharItem::class, 'pk' => 'CharItemID'],
  364. 'CharFellow' => ['key' => 'Owner', 'model' => CharFellow::class, 'pk' => 'FellowID'],
  365. 'CharCashItem_OutputBox' => ['key' => 'Owner', 'model' => CharCashItem_OutputBox::class, 'pk' => 'ItemDBIndex'],
  366. 'CharQuest' => ['key' => 'Owner', 'model' => CharQuest::class, 'pk' => 'RecId'],
  367. 'CharQuest_History' => ['key' => 'Owner', 'model' => CharQuest_History::class, 'pk' => 'RecId'],
  368. ];
  369. /**
  370. * Общий метод для обновления модели и формирования ответа.
  371. *
  372. * @param \Illuminate\Database\Eloquent\Model $model
  373. * @param array $data
  374. * @param string $successMsg
  375. * @return \Illuminate\Http\JsonResponse
  376. */
  377. private function _updateAndRespond(\Illuminate\Database\Eloquent\Model $model, array $data, string $successMsg = 'Data successfully updated.')
  378. {
  379. // 1. Фильтруем поля для массового присвоения
  380. $fillable = $model->getFillable();
  381. $guarded = $model->getGuarded();
  382. if (!empty($fillable)) {
  383. // Если есть fillable - используем только их
  384. $filteredData = array_intersect_key($data, array_flip($fillable));
  385. } elseif ($guarded === ['*']) {
  386. // Если guarded = ['*'] - запрещено массовое присвоение
  387. return response()->json(['code' => -6, 'msg' => 'Mass assignment is not allowed for this model.'], 400);
  388. } else {
  389. // Если есть guarded (но не ['*']) - исключаем только guarded поля
  390. $filteredData = array_diff_key($data, array_flip($guarded));
  391. }
  392. // 2. Конвертируем null-поля в '', чтобы вместо NULL в БД сохранялась пустая строка
  393. $filteredData = array_map(static fn($v) => $v === null ? '' : $v, $filteredData);
  394. if (empty($filteredData)) {
  395. return response()->json(['code' => -6, 'msg' => 'No valid fields to update.'], 400);
  396. }
  397. // 3. Пытаемся обновить запись
  398. try {
  399. $ok = $model->update($filteredData);
  400. } catch (\Throwable $e) {
  401. Log::error('Database error while updating record:', ['error' => $e->getMessage()]);
  402. return response()->json([
  403. 'code' => -7,
  404. 'msg' => 'Database error while updating record.',
  405. 'error' => $e->getMessage(),
  406. ], 500);
  407. }
  408. if (!$ok) {
  409. return response()->json([
  410. 'code' => -8,
  411. 'msg' => 'Update failed, record not modified.',
  412. ], 500);
  413. }
  414. // 4. Возвращаем успешный ответ
  415. return response()->json([
  416. 'code' => 0,
  417. 'msg' => $successMsg,
  418. 'data' => $model->fresh()
  419. ], 200);
  420. }
  421. }