CharController.php 15 KB

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