CharController.php 14 KB

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