CharController.php 17 KB

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