Config.php 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509
  1. <?php
  2. namespace App\Http\Controllers\admin;
  3. use Illuminate\Http\JsonResponse;
  4. use App\Constants\HttpStatus;
  5. use App\Http\Controllers\Controller;
  6. use App\Models\Config as ConfigModel;
  7. use Illuminate\Support\Facades\Cache;
  8. use Illuminate\Support\Facades\DB;
  9. use Illuminate\Validation\ValidationException;
  10. use Exception;
  11. use App\Services\ConfigService;
  12. use App\Services\MenuService;
  13. use App\Services\TelegramChannelService;
  14. use App\Services\UsdtRechargeConfigService;
  15. class Config extends Controller
  16. {
  17. public function usdt(UsdtRechargeConfigService $service): JsonResponse
  18. {
  19. return $this->success($service->get())->header('Cache-Control', 'no-store');
  20. }
  21. public function saveUsdt(UsdtRechargeConfigService $service): JsonResponse
  22. {
  23. try {
  24. return $this->success($service->save(request()->all()))->header('Cache-Control', 'no-store');
  25. } catch (ValidationException $e) {
  26. return $this->error(HttpStatus::CUSTOM_ERROR, $e->validator->errors()->first());
  27. }
  28. }
  29. /** 设置配置
  30. */
  31. public function setField()
  32. {
  33. try {
  34. $params = request()->validate([
  35. 'field' => ['required', 'string', 'max:255'],
  36. 'val' => ['nullable'],
  37. 'remark' => ['nullable'],
  38. ]);
  39. $val = $params['val'] ?? '';
  40. if (strcasecmp($params['field'], TelegramChannelService::LEGACY_FIELD) === 0) {
  41. $this->saveLegacyChannelValue($val);
  42. return $this->success();
  43. }
  44. if ($params['field'] === 'sport_list') $val = self::normalizeSportList($val);
  45. $update_data=[
  46. 'val' => $val,
  47. ];
  48. if (isset($params['remark'])) {
  49. $update_data['remark'] = $params['remark'];
  50. }
  51. ConfigModel::updateOrCreate(['field' => $params['field']], $update_data);
  52. } catch (ValidationException $e) {
  53. return $this->error(HttpStatus::VALIDATION_FAILED, $e->validator->errors()->first());
  54. } catch (Exception $e) {
  55. return $this->error(intval($e->getCode()));
  56. }
  57. return $this->success();
  58. }
  59. /** Sport codes become maintenance scopes (self:sport:<code>), so they must be stable and unique. */
  60. private static function normalizeSportList($val): string
  61. {
  62. $list = is_string($val) ? json_decode($val, true) : $val;
  63. if (!is_array($list) || !array_is_list($list)) throw ValidationException::withMessages(['val' => '体育列表格式错误']);
  64. $codes = [];
  65. foreach ($list as $item) {
  66. $code = is_array($item) ? strtolower(trim((string) ($item['code'] ?? ''))) : '';
  67. if (!preg_match('/^[a-z0-9]{1,32}$/', $code)) throw ValidationException::withMessages(['val' => '体育代码只能是1-32位小写字母或数字']);
  68. if (isset($codes[$code])) throw ValidationException::withMessages(['val' => "体育代码 {$code} 重复"]);
  69. if (trim((string) ($item['name'] ?? '')) === '') throw ValidationException::withMessages(['val' => '体育名称不能为空']);
  70. $codes[$code] = true;
  71. }
  72. return json_encode($list, JSON_UNESCAPED_UNICODE);
  73. }
  74. /**
  75. * @api {post} /admin/config/pc28Switch 游戏切换(0:pc28 1:急速28)
  76. * @apiGroup 配置
  77. * @apiUse result
  78. * @apiUse header
  79. * @apiVersion 1.0.0
  80. *
  81. * @apiParam {int=0,1} val (0:pc28 1:极速28)
  82. */
  83. public function pc28Switch()
  84. {
  85. try {
  86. $params = request()->validate([
  87. 'val' => ['required', 'integer', 'min:0', 'in:0,1']
  88. ]);
  89. ConfigModel::updateOrCreate(['field' => 'pc28_switch'], ['val' => $params['val']]);
  90. Cache::put('pc28_switch', $params['val']);
  91. } catch (ValidationException $e) {
  92. return $this->error(HttpStatus::VALIDATION_FAILED, $e->validator->errors()->first());
  93. } catch (Exception $e) {
  94. return $this->error(intval($e->getCode()));
  95. }
  96. return $this->success();
  97. }
  98. /**
  99. * @api {get} /admin/config/pcConfig 极速PC28相关配置
  100. * @apiGroup 配置
  101. * @apiUse result
  102. * @apiUse header
  103. * @apiVersion 1.0.0
  104. *
  105. * @apiParam {int} [page=1]
  106. * @apiParam {int} [limit=15]
  107. */
  108. public function pcConfig(): JsonResponse
  109. {
  110. try {
  111. $search['group_id'] = 4;
  112. $result = ConfigService::paginate($search);
  113. foreach ($result['data'] as $item) {
  114. if ($item['field'] == 'pc28_switch') {
  115. $item['val'] = Cache::get('pc28_switch', $item['val']);
  116. }
  117. }
  118. } catch (ValidationException $e) {
  119. return $this->error(HttpStatus::VALIDATION_FAILED, $e->validator->errors()->first());
  120. } catch
  121. (Exception $e) {
  122. return $this->error(intval($e->getCode()));
  123. }
  124. return $this->success($result);
  125. }
  126. /**
  127. * @api {get} /admin/config/gameList 彩票配置
  128. */
  129. public function gameList(): JsonResponse
  130. {
  131. return $this->sortedList('game_list');
  132. }
  133. /**
  134. * @api {get} /admin/config/sportList F1体育配置(与 gameList 相同,用 setField 的 sport_list 保存)
  135. */
  136. public function sportList(): JsonResponse
  137. {
  138. return $this->sortedList('sport_list');
  139. }
  140. private function sortedList(string $field): JsonResponse
  141. {
  142. try {
  143. $data = ConfigService::sortedList($field);
  144. } catch (Exception $e) {
  145. return $this->error(intval($e->getCode()));
  146. }
  147. return $this->success($data);
  148. }
  149. /**
  150. * 分页数据
  151. *
  152. */
  153. function index()
  154. {
  155. try {
  156. request()->validate([
  157. 'field' => ['nullable', 'string'],
  158. 'id' => ['nullable', 'string'],
  159. ]);
  160. $search = request()->all();
  161. $search['in_group_id'] = [1, 3];
  162. $result = ConfigService::paginate($search);
  163. } catch (ValidationException $e) {
  164. return $this->error(HttpStatus::VALIDATION_FAILED, $e->validator->errors()->first());
  165. } catch (Exception $e) {
  166. return $this->error(intval($e->getCode()));
  167. }
  168. return $this->success($result);
  169. }
  170. /**
  171. * 修改|新增
  172. *
  173. */
  174. public
  175. function store()
  176. {
  177. try {
  178. $id = request()->post('id', '');
  179. if ($id) {
  180. $validator = [
  181. 'id' => ['required', 'integer', 'min:1'],
  182. 'val' => ['required', 'string'],
  183. 'remark' => 'required|nullable|string',
  184. ];
  185. } else {
  186. $validator = [
  187. 'field' => 'required|string|max:50|alpha_dash|unique:config,field',
  188. 'val' => 'required|string',
  189. 'remark' => 'required|nullable|string',
  190. ];
  191. $field = request()->input("field", 'aaa');
  192. switch ($field) {
  193. case 'group_language':
  194. $validator['val'] = ['required', 'string', 'in:zh,vi,en'];
  195. break;
  196. case 'pc28_switch':
  197. $validator['val'] = ['required', 'integer', 'in:0,1'];
  198. break;
  199. case 'pc28_time':
  200. //格式如:09:00-21:00
  201. $validator['val'] = ['nullable', 'string', 'regex:/^\d{2}:\d{2}-\d{2}:\d{2}$/i'];
  202. break;
  203. }
  204. }
  205. $params = request()->validate($validator);
  206. $field = isset($params['id']) ? ConfigModel::whereKey($params['id'])->value('field') : $params['field'];
  207. if (strcasecmp($field ?? '', TelegramChannelService::LEGACY_FIELD) === 0) {
  208. $this->saveLegacyChannelValue($params['val']);
  209. return $this->success();
  210. }
  211. if (!isset($params['id'])) $params['group_id'] = 3;
  212. $ret = ConfigService::submit($params);
  213. if ($ret['code'] == ConfigService::NOT) {
  214. throw new Exception(HttpStatus::CUSTOM_ERROR, $ret['code']);
  215. }
  216. } catch (ValidationException $e) {
  217. return $this->error(HttpStatus::CUSTOM_ERROR, $e->validator->errors()->first());
  218. } catch (Exception $e) {
  219. return $this->error(intval($e->getCode()));
  220. }
  221. return $this->success([], $ret['msg']);
  222. }
  223. /**
  224. * 删除
  225. */
  226. public
  227. function destroy()
  228. {
  229. $id = request()->post('id');
  230. // 示例:通过 ID 删除
  231. $info = ConfigService::findOne(['id' => $id]);
  232. if (!$info) {
  233. return $this->error(HttpStatus::CUSTOM_ERROR, '配置不存在');
  234. }
  235. if ($info->field === TelegramChannelService::LEGACY_FIELD || $info->group_id !== 3) {
  236. return $this->error(HttpStatus::CUSTOM_ERROR, '禁止删除');
  237. }
  238. $info->delete();
  239. return $this->success([], '删除成功');
  240. }
  241. /**
  242. * @api {get} /admin/config/get 获取指定配置
  243. * @apiGroup 配置
  244. * @apiUse result
  245. * @apiUse header
  246. * @apiVersion 1.0.0
  247. *
  248. * @apiParam {string} field 配置项
  249. * - base_score 房间底分数组
  250. * - brokerage 抽佣比例
  251. * - service_charge 提现手续费
  252. * - service_account 客服账号
  253. * - receiving_address 手动收款 的地址
  254. * - receiving_type 收款方式 1-自动 2-手动
  255. * - channel_message 频道消息
  256. *
  257. * @apiSuccess (data) {Object} data
  258. * @apiSuccess (data) {int[]} [data.base_score] 房间底分数组
  259. * @apiSuccess (data) {float} [data.brokerage] 抽佣比例
  260. * @apiSuccess (data) {int} [data.service_charge] 提现手续费
  261. * @apiSuccess (data) {string} [data.service_account] 客服账号
  262. * @apiSuccess (data) {string} [data.receiving_address] 手动收款 的地址
  263. * @apiSuccess (data) {int} [data.receiving_type] 收款方式 1-自动 2-手动
  264. * @apiSuccess (data) {Object} [data.channel_message] 频道消息
  265. * @apiSuccess (data) {string} data.channel_message.chatId 频道账号或频道ID
  266. * @apiSuccess (data) {string} data.channel_message.image 要发送频道消息 的图片URL
  267. * @apiSuccess (data) {string} data.channel_message.text 要发送频道消息文 的本内容
  268. * @apiSuccess (data) {array} data.channel_message.button 要发送频道消息 的内联按钮,具体结构见下方
  269. * @apiSuccessExample {js} 内联按钮结构
  270. * //button 的数据格式如下
  271. * [
  272. * [ //第一行
  273. * { //第一行按钮 的第一个按钮
  274. * "text": "百度", //按钮文字
  275. * "url": "https://baidu.com" //按钮跳转的链接
  276. * },
  277. * { //第一行按钮 的第二个按钮
  278. * "text": "百度",
  279. * "url": "https://baidu.com"
  280. * }
  281. * //更多按钮...
  282. * ],
  283. * [ //第二行
  284. * {
  285. * "text": "百度",
  286. * "url": "https://baidu.com"
  287. * }
  288. * ]
  289. * //更多行...
  290. * ]
  291. *
  292. */
  293. public function get()
  294. {
  295. try {
  296. request()->validate([
  297. 'field' => ['required', 'string', 'min:1'],
  298. ]);
  299. $field = request()->input('field');
  300. if ($field === TelegramChannelService::LEGACY_FIELD) {
  301. $channel = app(TelegramChannelService::class)->legacyMessage();
  302. return $this->success($channel === null ? [] : [$field => $channel]);
  303. }
  304. $config = ConfigModel::where('field', $field)->first();
  305. $res = [];
  306. if ($config) {
  307. $val = $config->val;
  308. switch ($field) {
  309. case "base_score":
  310. $val = json_decode($config->val);
  311. break;
  312. case "brokerage":
  313. case "service_charge":
  314. $val = floatval($config->val);
  315. break;
  316. }
  317. $res[$field] = $val;
  318. }
  319. } catch (ValidationException $e) {
  320. return $this->error(HttpStatus::CUSTOM_ERROR, $e->validator->errors()->first());
  321. } catch (Exception $e) {
  322. return $this->error(intval($e->getCode()));
  323. }
  324. return $this->success($res);
  325. }
  326. /**
  327. * @api {get} /admin/config/getAll 获取所有配置
  328. * @apiGroup 配置
  329. * @apiUse result
  330. * @apiUse header
  331. * @apiVersion 1.0.0
  332. */
  333. public
  334. function getAll()
  335. {
  336. $list = ConfigModel::where('id', '>', 0)->where('field', '!=', TelegramChannelService::LEGACY_FIELD)->get();
  337. $arr = [];
  338. foreach ($list as $item) {
  339. $val = $item['val'];
  340. switch ($item['field']) {
  341. case "base_score":
  342. $val = json_decode($item['val'], true);
  343. break;
  344. case "brokerage":
  345. case "service_charge":
  346. $val = floatval($item['val']);
  347. break;
  348. }
  349. $arr[$item['field']] = $val;
  350. }
  351. $channel = app(TelegramChannelService::class)->legacyMessage();
  352. if ($channel !== null) {
  353. $arr[TelegramChannelService::LEGACY_FIELD] = $channel;
  354. }
  355. return $this->success($arr);
  356. }
  357. /**
  358. * @api {post} /admin/config/sendChannelMessage 发送频道消息
  359. * @apiGroup 配置
  360. * @apiUse result
  361. * @apiUse header
  362. * @apiVersion 1.0.0
  363. * @apiDescription 该接口会保存配置,并发送频道消息; 创建频道之后需要将 bot 拉进频道内,然后将 bot 设置为管理员
  364. *
  365. *
  366. * @apiParam {String} chatId 频道的username - 创建频道时候填写的 username
  367. * @apiParam {String} type 发送的类型 - image:图片 - video:视频
  368. * @apiParam {String} image 要发送的图片
  369. * @apiParam {String} text 要发送的文字
  370. * @apiParam {Array} button 消息中的按钮 具体结构请看示例代码
  371. * @apiParam {String} video 视频
  372. * @apiParam {String} video_caption 视频文案
  373. * @apiParam {Boolean} [isSend=false] 是否发送
  374. * @apiParam {Boolean} [isTop=false] 是否置顶
  375. */
  376. public function sendChannelMessage(TelegramChannelService $channels): JsonResponse
  377. {
  378. try {
  379. $data = request()->validate(['isSend' => 'nullable|boolean', 'isTop' => 'nullable|boolean']);
  380. $send = (bool) ($data['isSend'] ?? false);
  381. $this->requireChannelPermission(TelegramChannelService::MANAGE);
  382. if ($send) {
  383. $this->requireChannelPermission(TelegramChannelService::PUBLISH);
  384. }
  385. $channel = $channels->save(request()->all(), legacy: true);
  386. if (!$send) {
  387. return $this->success();
  388. }
  389. $result = $channels->publish($channel['id'], (bool) ($data['isTop'] ?? false));
  390. if ($result['sent'] !== true) {
  391. return $this->error(HttpStatus::CUSTOM_ERROR, '配置已保存,' . $result['error'], $result);
  392. }
  393. return $this->success($result);
  394. } catch (ValidationException $e) {
  395. return $this->error(HttpStatus::CUSTOM_ERROR, $e->validator->errors()->first());
  396. }
  397. }
  398. private function saveLegacyChannelValue(mixed $value): void
  399. {
  400. $this->requireChannelPermission(TelegramChannelService::MANAGE);
  401. $value = is_string($value) ? json_decode($value, true) : $value;
  402. if (!is_array($value)) {
  403. throw ValidationException::withMessages(['val' => '频道消息配置格式错误']);
  404. }
  405. $value['type'] ??= !empty($value['video']) ? 'video' : (!empty($value['image']) ? 'image' : 'text');
  406. app(TelegramChannelService::class)->save($value, legacy: true);
  407. }
  408. private function requireChannelPermission(string $permission): void
  409. {
  410. $userId = request()->user->id ?? 0;
  411. if (!$userId || !MenuService::checkMenu($userId, $permission)) {
  412. throw ValidationException::withMessages(['permission' => '无权限访问']);
  413. }
  414. }
  415. /**
  416. * @api {post} /admin/config/set 修改配置
  417. * @apiGroup 配置
  418. * @apiUse result
  419. * @apiUse header
  420. * @apiVersion 1.0.0
  421. *
  422. * @apiParam {int[]} base_score 房间底分数组
  423. * @apiParam {string} brokerage 抽佣比例
  424. * @apiParam {string} service_charge 提现手续费
  425. * @apiParam {string} service_account 客服账号
  426. * @apiParam {string} receiving_address 充值收款地址
  427. * @apiParam {string} receiving_type 收款方式 1-自动 2-手动
  428. *
  429. */
  430. public function set()
  431. {
  432. DB::beginTransaction();
  433. try {
  434. request()->validate([
  435. 'base_score' => ['required', 'array', 'min:1', 'max:30'],
  436. 'base_score.*' => ['integer', 'min:1'],
  437. 'brokerage' => ['required', 'numeric', 'min:0.01', 'max:1', 'regex:/^\d*(\.\d{1,2})?$/'],
  438. 'service_charge' => ['required', 'integer', 'min:1'],
  439. 'service_account' => ['required', 'string', 'min:1'],
  440. 'receiving_address' => ['required', 'string', 'min:34'],
  441. 'receiving_type' => ['required', 'integer', 'in:1,2']
  442. ]);
  443. $baseScore = request()->input('base_score');
  444. sort($baseScore);
  445. $baseScore = array_unique($baseScore);
  446. ConfigModel::where('field', 'base_score')
  447. ->update(['val' => json_encode($baseScore)]);
  448. $val = request()->input('brokerage');
  449. ConfigModel::where('field', 'brokerage')
  450. ->update(['val' => $val]);
  451. $val = request()->input('service_charge');
  452. ConfigModel::where('field', 'service_charge')
  453. ->update(['val' => $val]);
  454. $val = request()->input('service_account');
  455. ConfigModel::where('field', 'service_account')
  456. ->update(['val' => $val]);
  457. $val = request()->input('receiving_address');
  458. ConfigModel::where('field', 'receiving_address')
  459. ->update(['val' => $val]);
  460. $val = request()->input('receiving_type');
  461. ConfigModel::where('field', 'receiving_type')
  462. ->update(['val' => $val]);
  463. DB::commit();
  464. } catch (ValidationException $e) {
  465. DB::rollBack();
  466. return $this->error(HttpStatus::CUSTOM_ERROR, $e->validator->errors()->first());
  467. } catch (Exception $e) {
  468. DB::rollBack();
  469. return $this->error(intval($e->getCode()), $e->getMessage());
  470. }
  471. return $this->success();
  472. }
  473. }