IssueService.php 47 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432
  1. <?php
  2. namespace App\Services;
  3. use App\Models\Cao;
  4. use App\Models\CaoHistory;
  5. use App\Models\PcIssue;
  6. use App\Models\Prediction;
  7. use App\Services\BaseService;
  8. use App\Models\Issue;
  9. use App\Models\Config;
  10. use Illuminate\Support\Facades\DB;
  11. use Illuminate\Support\Collection;
  12. use Illuminate\Support\Facades\Cache;
  13. use Illuminate\Support\Facades\Http;
  14. use Illuminate\Support\Facades\Log;
  15. use App\Services\GameplayRuleService;
  16. use App\Constants\GameplayRuleEnum;
  17. use App\Http\Controllers\admin\Lottery;
  18. use App\Services\KeyboardService;
  19. use App\Services\LotteryImageService;
  20. use Telegram\Bot\FileUpload\InputFile;
  21. use App\Jobs\SendTelegramMessageJob;
  22. use App\Jobs\SendTelegramGroupMessageJob;
  23. /**
  24. * 投注
  25. */
  26. class IssueService extends BaseService
  27. {
  28. const COUNTDOWN_TO_CLOSING_THE_MARKET = 60;//提前xx秒封盘
  29. const PLAYNOW_KENO_URL = 'https://www.playnow.com/services2/keno/nextdraw';
  30. const PLAYNOW_SOURCE_TIMEZONE = 'America/Vancouver';
  31. const BEIJING_TIMEZONE = 'Asia/Shanghai';
  32. const PLAYNOW_DRAW_INTERVAL_SECONDS = 210;
  33. public static function init($telegram, $data, $chatId, $firstName, $messageId): void
  34. {
  35. switch ($data) {
  36. default:
  37. //查看开奖历史图片
  38. $pattern = "/^showLotteryHistory@@\d+$/";
  39. if (preg_match($pattern, $data)) {
  40. $id = preg_replace('/^showLotteryHistory@@/', '', $data);
  41. IssueService::sendLotteryImage($chatId, $id);
  42. }
  43. break;
  44. }
  45. }
  46. /**
  47. * @description: 模型
  48. * @return {string}
  49. */
  50. public
  51. static function model(): string
  52. {
  53. return Issue::class;
  54. }
  55. /**
  56. * @description: 枚举
  57. * @return {*}
  58. */
  59. public
  60. static function enum(): string
  61. {
  62. return '';
  63. }
  64. /**
  65. * @description: 获取查询条件
  66. * @param {array} $search 查询内容
  67. * @return {array}
  68. */
  69. public
  70. static function getWhere(array $search = []): array
  71. {
  72. $where = [];
  73. if (isset($search['issue_no']) && !empty($search['issue_no'])) {
  74. $where[] = ['issue_no', '=', $search['issue_no']];
  75. }
  76. if (isset($search['id']) && !empty($search['id'])) {
  77. $where[] = ['id', '=', $search['id']];
  78. }
  79. if (isset($search['status']) && $search['status'] != '') {
  80. $where[] = ['status', '=', $search['status']];
  81. }
  82. if (isset($search['abnormal']) && !empty($search['abnormal'])) {
  83. $where[] = ['end_time', '<', date('Y-m-d H:i:s', time() - 1800)];
  84. $where[] = ['status', '!=', self::model()::STATUS_DRAW];
  85. }
  86. if (!empty($search['start_time'])) {
  87. $where[] = ['end_time', '>=', $search['start_time'].' 00:00:00'];
  88. }
  89. if (!empty($search['end_time'])) {
  90. $where[] = ['end_time', '<=', $search['end_time'].' 23:59:59'];
  91. }
  92. return $where;
  93. }
  94. /**
  95. * @description: 查询单条数据
  96. * @param array $search
  97. * @return \App\Models\Coin|null
  98. */
  99. public
  100. static function findOne(array $search): ?Issue
  101. {
  102. return self::model()::where(self::getWhere($search))->first();
  103. }
  104. /**
  105. * @description: 查询所有数据
  106. * @param array $search
  107. * @return \Illuminate\Database\Eloquent\Collection
  108. */
  109. public
  110. static function findAll(array $search = [])
  111. {
  112. return self::model()::where(self::getWhere($search))->get();
  113. }
  114. /**
  115. * @description: 分页查询
  116. * @param array $search
  117. * @return \Illuminate\Contracts\Pagination\LengthAwarePaginator
  118. */
  119. public
  120. static function paginate(array $search = [])
  121. {
  122. $limit = isset($search['limit']) ? $search['limit'] : 15;
  123. $paginator = self::model()::where(self::getWhere($search))->orderBy('issue_no', 'desc')->paginate($limit);
  124. return ['total' => $paginator->total(), 'data' => $paginator->items()];
  125. }
  126. /**
  127. * @description:
  128. * @param {*} $params
  129. * @return {*}
  130. */
  131. public
  132. static function submit($params = [])
  133. {
  134. $result = false;
  135. $msg['code'] = self::NOT;
  136. $msg['msg'] = '';
  137. // 2. 判断是否是更新
  138. if (!empty($params['id'])) {
  139. // 更新
  140. $info = self::findOne(['id' => $params['id']]);
  141. if (!$info) {
  142. $msg['msg'] = '期号不存在!';
  143. } else {
  144. $result = $info->update($params);
  145. $id = $params['id'];
  146. }
  147. } else {
  148. // 创建
  149. $result = $info = self::model()::create($params);
  150. $id = $result->id;
  151. }
  152. if ($result) {
  153. $msg['code'] = self::YES;
  154. $msg['msg'] = '设置成功';
  155. $msg['key'] = $id;
  156. } else {
  157. $msg['msg'] = empty($msg['msg']) ? '操作失败' : $msg['msg'];
  158. }
  159. return $msg;
  160. }
  161. /**
  162. * @description: 开始下注
  163. * @param {*} $id
  164. * @return {*}
  165. */
  166. public
  167. static function betting($id)
  168. {
  169. $info = self::findOne(['id' => $id]);
  170. if (!$info) {
  171. return ['code' => self::NOT, 'msg' => '期号不存在'];
  172. }
  173. if (!in_array($info->status, [self::model()::STATUS_DRAFT, self::model()::STATUS_BETTING])) {
  174. return ['code' => self::NOT, 'msg' => '期号状态不正确'];
  175. }
  176. $info->status = self::model()::STATUS_BETTING;
  177. $info->save();
  178. $pc28Switch = Config::where('field', 'pc28_switch')->first()->val;
  179. $wanFaGuiZeTime = Cache::get("玩法规则推送时间", 0);
  180. $now = time();
  181. $wanFaGuiZeTime = intval($wanFaGuiZeTime);
  182. if ($now - $wanFaGuiZeTime > (60 * 15)) {
  183. $replyInfo = KeyboardService::findOne(['button' => '玩法规则']);
  184. if ($replyInfo) {
  185. $text = $replyInfo->reply;
  186. $buttons = json_decode($replyInfo->buttons, true);
  187. $image = $replyInfo->image;
  188. if ($image) {
  189. $image = url($image);
  190. }
  191. if (empty($buttons)) {
  192. $buttons = self::getOperateButton();
  193. }
  194. Cache::put('玩法规则推送时间', time());
  195. if ($pc28Switch == 0) self::asyncBettingGroupNotice($text, $buttons, $image);
  196. }
  197. }
  198. $replyInfo = KeyboardService::findOne(['button' => '开始下注']);
  199. if ($replyInfo) {
  200. $text = $replyInfo->reply;
  201. $buttons = json_decode($replyInfo->buttons, true);
  202. $image = $replyInfo->image;
  203. if ($image) {
  204. $image = url($image);
  205. }
  206. if ($pc28Switch == 0) self::asyncBettingGroupNotice($text, $buttons, $image);
  207. }
  208. return ['code' => self::YES, 'msg' => '开始下注'];
  209. }
  210. /**
  211. * @description: 封盘
  212. * @param {*} $id
  213. * @return {*}
  214. */
  215. public
  216. static function closeBetting($id)
  217. {
  218. $info = self::findOne(['id' => $id]);
  219. if (!$info) {
  220. return ['code' => self::NOT, 'msg' => '期号不存在'];
  221. }
  222. if ($info->status != self::model()::STATUS_BETTING) {
  223. return ['code' => self::NOT, 'msg' => '期号状态不正确'];
  224. }
  225. $pc28Switch = Config::where('field', 'pc28_switch')->first()->val;
  226. $info->status = self::model()::STATUS_CLOSE;
  227. $info->save();
  228. if ($pc28Switch == 0) {
  229. $replyInfo = KeyboardService::findOne(['button' => '停止下注']);
  230. if ($replyInfo) {
  231. $text = $replyInfo->reply;
  232. $buttons = json_decode($replyInfo->buttons, true);
  233. $image = $replyInfo->image;
  234. if ($image) {
  235. $image = url($image);
  236. }
  237. //停止下注的信息不发了
  238. // self::asyncBettingGroupNotice($text, $buttons, $image);
  239. }
  240. // 投注情况通知 xxxx期投注统计
  241. BetService::statNotice($info->issue_no);
  242. $replyInfo = KeyboardService::findOne(['button' => '封盘开奖']);
  243. if ($replyInfo) {
  244. $text = $replyInfo->reply;
  245. $buttons = json_decode($replyInfo->buttons, true);
  246. $image = $replyInfo->image;
  247. if ($image) {
  248. $image = url($image);
  249. }
  250. // self::bettingGroupNotice($text, $buttons, $image);
  251. self::asyncBettingGroupNotice($text, $buttons, $image);
  252. }
  253. }
  254. return ['code' => self::YES, 'msg' => '封盘成功'];
  255. }
  256. /**
  257. * @description: 开奖失败
  258. * @param {*} $id
  259. * @return {*}
  260. */
  261. public
  262. static function lotteryDrawFail($id)
  263. {
  264. $result = false;
  265. $msg['code'] = self::NOT;
  266. $msg['msg'] = '';
  267. DB::beginTransaction();
  268. try {
  269. // 更新
  270. $info = self::findOne(['id' => $id]);
  271. if (!$info) {
  272. $msg['msg'] = '期号不存在!';
  273. } else {
  274. $params['status'] = self::model()::STATUS_FAIL;
  275. $result = $info->update($params);
  276. BetService::betFail($info->issue_no);
  277. }
  278. DB::commit();
  279. return ['code' => self::YES, 'msg' => '投注已退回'];
  280. } catch (\Exception $e) {
  281. DB::rollBack();
  282. return ['code' => self::NOT, 'msg' => '投注退回失败'];
  283. }
  284. if ($result) {
  285. $msg['code'] = self::YES;
  286. $msg['msg'] = '设置成功';
  287. } else {
  288. $msg['msg'] = empty($msg['msg']) ? '操作失败' : $msg['msg'];
  289. }
  290. return $msg;
  291. }
  292. /**
  293. * @description: 开奖
  294. * @param {*} $id
  295. * @param {*} $winning_numbers 开奖号码
  296. * @param {*} $combo 开奖组合
  297. * @param {*} $recordImage 开奖图片
  298. * @return {*}
  299. */
  300. public
  301. static function lotteryDraw($id, $winning_numbers, $combo, $recordImage)
  302. {
  303. $info = self::findOne(['id' => $id]);
  304. if (!$info) {
  305. return ['code' => self::NOT, 'msg' => '期号不存在'];
  306. }
  307. if ($info->status == self::model()::STATUS_DRAW) {
  308. return ['code' => self::NOT, 'msg' => '期号状态不正确'];
  309. }
  310. $winArr = array_map('intval', explode(',', $winning_numbers));
  311. // 计算中奖
  312. $awards = self::award(explode(',', $winning_numbers));
  313. DB::beginTransaction();
  314. try {
  315. $info->status = self::model()::STATUS_DRAW;
  316. $info->winning_numbers = $winning_numbers;
  317. $info->combo = $combo;
  318. $info->saveQuietly();
  319. $size = in_array("大", $awards);
  320. $size = $size ? "大" : "小";
  321. $oddOrEven = in_array("双", $awards);
  322. $oddOrEven = $oddOrEven ? "双" : "单";
  323. Prediction::result($info->issue_no, $size, $oddOrEven, $info->winning_numbers);
  324. Cao::updateData($awards);
  325. CaoHistory::updateData($awards);
  326. $pc28Switch = Config::where('field', 'pc28_switch')->first()->val;
  327. $replyInfo = KeyboardService::findOne(['button' => '本期开奖']);
  328. if ($replyInfo) {
  329. $text = $replyInfo->reply;
  330. $text .= "\n";
  331. $text .= $info->issue_no . ": " . implode('+', explode(',', $winning_numbers)) . "=" . array_sum($winArr) . " " . $combo;
  332. $buttons = json_decode($replyInfo->buttons, true);
  333. $image = $replyInfo->image;
  334. if ($image) {
  335. $image = url($image);
  336. }
  337. if (empty($buttons)) {
  338. $serviceAccount = Config::where('field', 'service_account')->first()->val;
  339. $buttons[] = [['text' => lang('✅唯一财务'), 'callback_data' => "", 'url' => "https://t.me/{$serviceAccount}"]];
  340. }
  341. // self::bettingGroupNotice($text, $buttons, $image, true);
  342. if ($pc28Switch == 0) SendTelegramGroupMessageJob::dispatch($text, $buttons, $image, true)->afterCommit();
  343. }
  344. $recordImage = self::lotteryImage($info->issue_no);
  345. if ($recordImage) {
  346. // self::bettingGroupNotice('', [], url($recordImage));
  347. if ($pc28Switch == 0) SendTelegramGroupMessageJob::dispatch('', [], url($recordImage), false)->afterCommit();
  348. }
  349. $info->image = $recordImage;
  350. $info->save();
  351. BetService::betSettled($info->issue_no, $awards);
  352. DB::commit();
  353. } catch (\Exception $e) {
  354. DB::rollBack();
  355. $message = "开奖失败:\n{$info->issue_no}\n";
  356. $message .= $e->getFile() . ':' . $e->getLine();
  357. $message .= "\n";
  358. $message .= $e->getMessage() . $winning_numbers;
  359. Log::error($message);
  360. return ['code' => self::NOT, 'msg' => '开奖失败', 'error' => $e->getMessage()];
  361. }
  362. return ['code' => self::YES, 'msg' => '开奖成功'];
  363. }
  364. // 虚拟开奖
  365. public
  366. static function fakeLotteryDraw($issue_no, $awards)
  367. {
  368. $fake_bet_list = Cache::get('fake_bet_' . $issue_no, []);
  369. $text = "";
  370. foreach ($fake_bet_list as $k => $v) {
  371. $lastStr = self::getLastChar($v['first_name'], 1);
  372. if (in_array($v['keywords'], $awards)) {
  373. $amount = (float)$v['amount'];
  374. $odds = (float)$v['odds'];
  375. $profit = $amount * $odds;
  376. if ($profit > 880000) {
  377. $profit = 880000; // 单注最高奖金880000
  378. }
  379. $item['profit'] = $profit;
  380. $yl = $profit - $amount;
  381. if ($k + 1 <= 30) {
  382. $text .= "私聊下注 【******" . $lastStr . "】 {$yl}\n";
  383. }
  384. } else {
  385. if ($k + 1 <= 30) {
  386. $text .= "私聊下注 【******" . $lastStr . "】 -{$v['amount']}\n";
  387. }
  388. }
  389. }
  390. return $text;
  391. }
  392. /**
  393. * @description: 获取中奖的奖项
  394. * @param {*} $winning_numbers
  395. * @return {*}
  396. */
  397. public
  398. static function award($winning_numbers)
  399. {
  400. $result = [];
  401. // 组合
  402. $sum = array_sum($winning_numbers);
  403. $section = self::getSection($sum); // 总和段位
  404. $result[] = $section;
  405. $sumOddEven = self::calculateOddEven($sum); // 总和单双
  406. $result[] = $sumOddEven;
  407. $sumSize = self::calculateSumSize($sum); // 总和大小
  408. $result[] = $sumSize;
  409. $sumExtremeSize = self::calculateSumExtremeSize($sum); // 总和极值
  410. if ($sumExtremeSize) {
  411. $result[] = $sumExtremeSize;
  412. }
  413. $sumCao = $sum . '操'; // 总和数字
  414. $result[] = $sumCao;
  415. $sumCombo = $sumSize . $sumOddEven; // 总和大小单双组合
  416. $result[] = $sumCombo;
  417. $sumBaoZi = self::isBaoZi($winning_numbers[0], $winning_numbers[1], $winning_numbers[2]); // 豹子
  418. if ($sumBaoZi) {
  419. $result[] = $sumBaoZi;
  420. }
  421. $sumPair = self::isPair($winning_numbers[0], $winning_numbers[1], $winning_numbers[2]); // 对子
  422. if ($sumPair) {
  423. $result[] = $sumPair;
  424. }
  425. $sumStraight = self::isStraight($winning_numbers[0], $winning_numbers[1], $winning_numbers[2]); // 顺子
  426. if ($sumStraight) {
  427. $result[] = $sumStraight;
  428. }
  429. $tail = self::getLastDigit($sum); // 总和尾数
  430. if (!in_array($tail, [0, 9])) {
  431. $result[] = $tail . '尾'; // 尾数
  432. $tailOddEven = self::calculateOddEven($tail); // 尾数单双
  433. $result[] = '尾' . $tailOddEven;
  434. $tailSize = self::calculateOneSize($tail); // 尾数大小
  435. $result[] = '尾' . $tailSize;
  436. $tailCombo = '尾' . $tailSize . $tailOddEven; // 尾数大小单双组合
  437. $result[] = $tailCombo;
  438. } else {
  439. $result[] = $tail . '尾'; // 尾数
  440. }
  441. $numA = $winning_numbers[0]; // A球
  442. $result[] = $numA . 'A';
  443. $numAOddEven = self::calculateOddEven($numA); // A球单双
  444. $result[] = 'A' . $numAOddEven;
  445. $numASize = self::calculateOneSize($numA); // A球大小
  446. $result[] = 'A' . $numASize;
  447. $result[] = 'A' . $numASize . $numAOddEven; // A球大小单双组合
  448. $numB = $winning_numbers[1]; // B球
  449. $result[] = $numB . 'B';
  450. $numBOddEven = self::calculateOddEven($numB); // B球
  451. $result[] = 'B' . $numBOddEven;
  452. $numBSize = self::calculateOneSize($numB); // B球大小
  453. $result[] = 'B' . $numBSize;
  454. $result[] = 'B' . $numBSize . $numBOddEven; // B球大小单双组合
  455. $numC = $winning_numbers[2];
  456. $result[] = $numC . 'C';
  457. $numCOddEven = self::calculateOddEven($numC); // C球单双
  458. $result[] = 'C' . $numCOddEven;
  459. $numCSize = self::calculateOneSize($numC); // C球大小
  460. $result[] = 'C' . $numCSize;
  461. $result[] = 'C' . $numCSize . $numCOddEven; // C球大小单双组合
  462. return $result;
  463. }
  464. /**
  465. * @description: 算单双
  466. * @param {*} $number
  467. * @return {*}
  468. */
  469. public
  470. static function calculateOddEven($number)
  471. {
  472. if ($number & 1) {
  473. return GameplayRuleEnum::SINGLE;
  474. } else {
  475. return GameplayRuleEnum::DOUBLE;
  476. }
  477. }
  478. /**
  479. * @description: 总和大小
  480. * @param {*} $number
  481. * @return {*}
  482. */
  483. public
  484. static function calculateSumSize($number)
  485. {
  486. if ($number >= GameplayRuleEnum::SUM_BIG) {
  487. return GameplayRuleEnum::BIG;
  488. }
  489. if ($number <= GameplayRuleEnum::SUM_SMALL) {
  490. return GameplayRuleEnum::SMALL;
  491. }
  492. }
  493. /**
  494. * @description: 总和极值
  495. * @param {*} $number
  496. * @return {*}
  497. */
  498. public
  499. static function calculateSumExtremeSize($number)
  500. {
  501. $result = '';
  502. if ($number >= GameplayRuleEnum::SUM_EXTREME_BIG) {
  503. $result = GameplayRuleEnum::EXTREME_BIG;
  504. }
  505. if ($number <= GameplayRuleEnum::SUM_EXTREME_SMALL) {
  506. $result = GameplayRuleEnum::EXTREME_SMALL;
  507. }
  508. return $result;
  509. }
  510. /**
  511. * @description: 豹子
  512. * @param {int} $a
  513. * @param {int} $b
  514. * @param {int} $c
  515. * @return {*}
  516. */
  517. public
  518. static function isBaoZi(int $a, int $b, int $c)
  519. {
  520. $result = '';
  521. if ($a === $b && $b === $c) {
  522. $result = GameplayRuleEnum::BAO_ZI;
  523. }
  524. return $result;
  525. }
  526. /**
  527. * @description: 对子
  528. * @param {int} $a
  529. * @param {int} $b
  530. * @param {int} $c
  531. * @return {*}
  532. */
  533. public
  534. static function isPair($a, $b, $c)
  535. {
  536. $result = '';
  537. // 确保输入都是个位数
  538. if (!is_numeric($a) || !is_numeric($b) || !is_numeric($c) ||
  539. $a < 0 || $a > 9 || $b < 0 || $b > 9 || $c < 0 || $c > 9) {
  540. return ''; // 或者抛出异常
  541. }
  542. if (($a == $b && $a != $c) ||
  543. ($a == $c && $a != $b) ||
  544. ($b == $c && $b != $a)) {
  545. $result = GameplayRuleEnum::PAIRS;
  546. }
  547. // 判断是否为对子情况
  548. return $result;
  549. }
  550. /**
  551. * @description: 顺子
  552. * @param {int} $a
  553. * @param {int} $b
  554. * @param {int} $c
  555. * @return {*}
  556. */
  557. public
  558. static function isStraight($a, $b, $c)
  559. {
  560. $result = '';
  561. // 确保输入都是个位数(0-9)
  562. if (!is_numeric($a) || !is_numeric($b) || !is_numeric($c) ||
  563. $a < 0 || $a > 9 || $b < 0 || $b > 9 || $c < 0 || $c > 9) {
  564. return '';
  565. }
  566. // 去重(顺子必须三个不同数字)
  567. if ($a == $b || $a == $c || $b == $c) {
  568. return '';
  569. }
  570. // 检查是否是完全升序或完全降序的连续数字
  571. $numbers = [$a, $b, $c];
  572. sort($numbers); // 排序后检查是否是 x, x+1, x+2
  573. list($x, $y, $z) = $numbers;
  574. // 情况1:升序连续(1,2,3)
  575. $isAscending = ($x + 1 == $y) && ($y + 1 == $z);
  576. // 情况2:降序连续(3,2,1)
  577. $isDescending = ($z + 1 == $y) && ($y + 1 == $x);
  578. if ($isAscending || $isDescending) {
  579. $result = GameplayRuleEnum::STRAIGHT;
  580. }
  581. return $result;
  582. }
  583. /**
  584. * 获取数字的尾数
  585. * @param int $number 输入数字
  586. * @return int 尾数
  587. */
  588. public
  589. static function getLastDigit($number)
  590. {
  591. // 确保输入是整数
  592. $number = (int)$number;
  593. // 取绝对值,处理负数情况
  594. $number = abs($number);
  595. // 取模10得到尾数
  596. return $number % 10;
  597. }
  598. /**
  599. * @description: 尾大小
  600. * @param {*} $number
  601. * @return {*}
  602. */
  603. public
  604. static function calculateOneSize($number)
  605. {
  606. if ($number >= GameplayRuleEnum::ONE_BIG) {
  607. return GameplayRuleEnum::BIG;
  608. }
  609. if ($number <= GameplayRuleEnum::ONE_SMALL) {
  610. return GameplayRuleEnum::SMALL;
  611. }
  612. }
  613. /**
  614. * @description: 获取段位
  615. * @param {*} $number
  616. * @return {*}
  617. */
  618. public
  619. static function getSection($number)
  620. {
  621. $result = '';
  622. if ($number >= GameplayRuleEnum::SECTION_1[0] && $number <= GameplayRuleEnum::SECTION_1[1]) {
  623. $result = GameplayRuleEnum::ONE;
  624. } elseif ($number >= GameplayRuleEnum::SECTION_2[0] && $number <= GameplayRuleEnum::SECTION_2[1]) {
  625. $result = GameplayRuleEnum::TWO;
  626. } elseif ($number >= GameplayRuleEnum::SECTION_3[0] && $number <= GameplayRuleEnum::SECTION_3[1]) {
  627. $result = GameplayRuleEnum::THREE;
  628. } elseif ($number >= GameplayRuleEnum::SECTION_4[0] && $number <= GameplayRuleEnum::SECTION_4[1]) {
  629. $result = GameplayRuleEnum::FOUR;
  630. }
  631. return $result; // 不在任何段中
  632. }
  633. /**
  634. * @description: 近期开奖记录
  635. * @return {*}
  636. */
  637. public
  638. static function currentLotteryResults($memberId)
  639. {
  640. // $result = self::model()::where('status', self::model()::STATUS_DRAW)->orderBy('id','desc')->take(16)->get();
  641. // $text = "📅 近期开奖记录\n";
  642. // $text .= "====================\n";
  643. // if($result){
  644. // foreach($result as $k => $v){
  645. // $winArr = explode(',',$v->winning_numbers);
  646. // // 组合
  647. // $sum = array_sum($winArr);
  648. // $combo = [];
  649. // $sumOddEven = self::calculateOddEven($sum); // 总和单双
  650. // $sumSize = self::calculateSumSize($sum); // 总和大小
  651. // $sumExtremeSize = self::calculateSumExtremeSize($sum); // 总和极值
  652. // if(empty($sumExtremeSize)){
  653. // $sumExtremeSize = "-";
  654. // }
  655. // $tail = self::getLastDigit($sum); // 总和尾数
  656. // if($tail == 0){
  657. // $tail = '-'; // 尾数
  658. // }else{
  659. // $tail = '尾'.$tail; // 尾数
  660. // }
  661. // $text .= "回合:{$v->issue_no}期 \n";
  662. // $text .= "结果:".implode('+',explode(',',$v->winning_numbers))."=".array_sum(explode(',',$v->winning_numbers))." \n";
  663. // $text .= "组合:{$sumSize} {$sumOddEven} \n";
  664. // $text .= "极值:{$sumExtremeSize} \n";
  665. // $text .= "尾数:{$tail} \n";
  666. // $text .= "---------------------------\n";
  667. // }
  668. // self::telegram()->sendMessage([
  669. // 'chat_id' => $memberId,
  670. // 'text' => $text,
  671. // ]);
  672. // }else{
  673. // self::telegram()->sendMessage([
  674. // 'chat_id' => $memberId,
  675. // 'text' => "暂无开奖记录",
  676. // ]);
  677. // }
  678. $result = self::model()::where('status', self::model()::STATUS_DRAW)->orderBy('id', 'desc')->first();
  679. if ($result) {
  680. if ($result->image) {
  681. // self::telegram()->sendPhoto([
  682. // 'chat_id' => $memberId,
  683. // 'photo' => InputFile::create(url($result->image)),
  684. // ]);
  685. return [
  686. 'chat_id' => $memberId,
  687. 'photo' => InputFile::create(url($result->image)),
  688. ];
  689. } else {
  690. // if($result->combo){
  691. // self::telegram()->sendMessage([
  692. // 'chat_id' => $memberId,
  693. // 'text' => "",
  694. // ]);
  695. // }else{
  696. // self::telegram()->sendMessage([
  697. // 'chat_id' => $memberId,
  698. // 'text' => lang("暂无开奖记录"),
  699. // ]);
  700. // }
  701. return
  702. [
  703. 'chat_id' => $memberId,
  704. 'text' => lang("暂无开奖记录"),
  705. ];
  706. }
  707. }
  708. }
  709. public
  710. static function getCombo($winArr)
  711. {
  712. // 组合
  713. $sum = array_sum($winArr);
  714. $combo = [];
  715. $sumSize = self::calculateSumSize($sum); // 总和大小
  716. $combo[] = $sumSize;
  717. $sumOddEven = self::calculateOddEven($sum); // 总和单双
  718. $combo[] = $sumOddEven;
  719. $sumExtremeSize = self::calculateSumExtremeSize($sum); // 总和极值
  720. if ($sumExtremeSize) {
  721. $combo[] = $sumExtremeSize;
  722. }
  723. $sumBaoZi = self::isBaoZi($winArr[0], $winArr[1], $winArr[2]); // 豹子
  724. if ($sumBaoZi) {
  725. $combo[] = $sumBaoZi;
  726. }
  727. $sumPair = self::isPair($winArr[0], $winArr[1], $winArr[2]); // 对子
  728. if ($sumPair) {
  729. $combo[] = $sumPair;
  730. }
  731. $sumStraight = self::isStraight($winArr[0], $winArr[1], $winArr[2]); // 顺子
  732. if ($sumStraight) {
  733. $combo[] = $sumStraight;
  734. }
  735. $tail = self::getLastDigit($sum); // 总和尾数
  736. if ($tail == 0 || $tail == 9) {
  737. } else {
  738. $combo[] = '尾' . $tail; // 尾数
  739. }
  740. return implode(' ', $combo);
  741. }
  742. private static function getPlayNowConfig($key, $default)
  743. {
  744. $value = config('services.playnow.' . $key, $default);
  745. return $value === null || $value === '' ? $default : $value;
  746. }
  747. private static function getPlayNowProxyUrl(): string
  748. {
  749. $scheme = self::getPlayNowConfig('proxy.scheme', 'http');
  750. $host = self::getPlayNowConfig('proxy.host', '155.138.141.119');
  751. $port = self::getPlayNowConfig('proxy.port', '3128');
  752. $username = (string)self::getPlayNowConfig('proxy.username', 'proxyuser');
  753. $password = (string)self::getPlayNowConfig('proxy.password', '');
  754. if ($username === '' && $password === '') {
  755. return "{$scheme}://{$host}:{$port}";
  756. }
  757. if ($password === '') {
  758. return "{$scheme}://" . rawurlencode($username) . "@{$host}:{$port}";
  759. }
  760. return "{$scheme}://" . rawurlencode($username) . ':' . rawurlencode($password) . "@{$host}:{$port}";
  761. }
  762. private static function getPlayNowProxyLogContext(): array
  763. {
  764. $host = self::getPlayNowConfig('proxy.host', '155.138.141.119');
  765. $password = self::getPlayNowConfig('proxy.password', '');
  766. return [
  767. 'proxy' => $host === '' ? 'missing' : 'configured',
  768. 'proxy_auth' => $password === '' ? 'missing' : 'configured',
  769. ];
  770. }
  771. private static function fetchPlayNowKenoResult()
  772. {
  773. $url = self::getPlayNowConfig('keno_url', self::PLAYNOW_KENO_URL);
  774. $response = Http::timeout(25)
  775. ->connectTimeout(10)
  776. ->withHeaders([
  777. 'Accept' => 'application/json, text/plain, */*',
  778. 'User-Agent' => 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0 Safari/537.36',
  779. ])
  780. ->withOptions([
  781. 'proxy' => self::getPlayNowProxyUrl(),
  782. 'curl' => [
  783. CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
  784. ],
  785. ])
  786. ->get($url);
  787. Log::channel('issue')->info('PlayNow接口响应', [
  788. 'status' => $response->status(),
  789. 'url' => $url,
  790. ] + self::getPlayNowProxyLogContext());
  791. if (!$response->successful()) {
  792. Log::channel('issue')->info('PlayNow接口请求失败', [
  793. 'status' => $response->status(),
  794. 'body' => substr($response->body(), 0, 500),
  795. ]);
  796. return null;
  797. }
  798. return $response->json();
  799. }
  800. private static function convertPlayNowDrawDateToBeijing($drawDate)
  801. {
  802. if (empty($drawDate)) {
  803. return null;
  804. }
  805. $sourceTimezone = new \DateTimeZone(self::getPlayNowConfig('timezone', self::PLAYNOW_SOURCE_TIMEZONE));
  806. $beijingTimezone = new \DateTimeZone(self::BEIJING_TIMEZONE);
  807. return (new \DateTimeImmutable($drawDate, $sourceTimezone))->setTimezone($beijingTimezone);
  808. }
  809. private static function getPlayNowDrawIntervalSeconds(array $result): int
  810. {
  811. $timeSinceDraw = isset($result['timeSinceDraw']) ? (int)$result['timeSinceDraw'] : 0;
  812. $nextDrawSeconds = isset($result['nextKenoDrawTime']) ? (int)$result['nextKenoDrawTime'] : 0;
  813. if ($timeSinceDraw > 0 && $nextDrawSeconds > 0) {
  814. return (int)round($timeSinceDraw / 1000) + $nextDrawSeconds;
  815. }
  816. return $nextDrawSeconds > 0 ? $nextDrawSeconds : self::PLAYNOW_DRAW_INTERVAL_SECONDS;
  817. }
  818. private static function calculatePlayNowPc28(array $numbers): array
  819. {
  820. $sortedNumbers = array_map('intval', $numbers);
  821. sort($sortedNumbers, SORT_NUMERIC);
  822. $sumByIndexes = function (array $indexes) use ($sortedNumbers) {
  823. $total = 0;
  824. foreach ($indexes as $index) {
  825. $total += $sortedNumbers[$index] ?? 0;
  826. }
  827. return $total % 10;
  828. };
  829. $firstNumber = $sumByIndexes([1, 4, 7, 10, 13, 16]);
  830. $secondNumber = $sumByIndexes([2, 5, 8, 11, 14, 17]);
  831. $thirdNumber = $sumByIndexes([3, 6, 9, 12, 15, 18]);
  832. return [
  833. 'numbers' => [$firstNumber, $secondNumber, $thirdNumber],
  834. 'total' => $firstNumber + $secondNumber + $thirdNumber,
  835. 'sorted_numbers' => $sortedNumbers,
  836. ];
  837. }
  838. private static function normalizePlayNowNumbers(array $numbers)
  839. {
  840. if (count($numbers) !== 20) {
  841. return null;
  842. }
  843. $normalized = [];
  844. foreach ($numbers as $number) {
  845. $value = filter_var($number, FILTER_VALIDATE_INT, [
  846. 'options' => [
  847. 'min_range' => 1,
  848. 'max_range' => 80,
  849. ],
  850. ]);
  851. if ($value === false) {
  852. return null;
  853. }
  854. $normalized[] = (int)$value;
  855. }
  856. if (count(array_unique($normalized)) !== 20) {
  857. return null;
  858. }
  859. return $normalized;
  860. }
  861. private static function isSamePlayNowIssueNo($issueNo, int $drawNo): bool
  862. {
  863. $issueNo = trim((string)$issueNo);
  864. return ctype_digit($issueNo) && (int)$issueNo === $drawNo;
  865. }
  866. private static function normalizePlayNowDrawNo($drawNo)
  867. {
  868. $value = filter_var($drawNo, FILTER_VALIDATE_INT, [
  869. 'options' => [
  870. 'min_range' => 1,
  871. ],
  872. ]);
  873. return $value === false ? null : (int)$value;
  874. }
  875. private static function parsePlayNowDrawInfo(array $result)
  876. {
  877. if (!isset($result['draw']) || empty($result['num']) || !is_array($result['num'])) {
  878. return null;
  879. }
  880. $drawNo = self::normalizePlayNowDrawNo($result['draw']);
  881. if (!$drawNo) {
  882. return null;
  883. }
  884. $sourceNumbers = self::normalizePlayNowNumbers($result['num']);
  885. if (!$sourceNumbers) {
  886. return null;
  887. }
  888. try {
  889. $drawDateBeijing = self::convertPlayNowDrawDateToBeijing($result['drawDate'] ?? null);
  890. } catch (\Throwable $exception) {
  891. Log::channel('issue')->info('PlayNow开奖时间解析失败', [
  892. 'draw_date_raw' => $result['drawDate'] ?? '',
  893. 'error' => $exception->getMessage(),
  894. ]);
  895. return null;
  896. }
  897. $nowBeijing = new \DateTimeImmutable('now', new \DateTimeZone(self::BEIJING_TIMEZONE));
  898. $drawIntervalSeconds = self::getPlayNowDrawIntervalSeconds($result);
  899. $startDateTime = $drawDateBeijing ?: $nowBeijing;
  900. $endDateTime = $startDateTime->modify('+' . $drawIntervalSeconds . ' seconds');
  901. $pc28 = self::calculatePlayNowPc28($sourceNumbers);
  902. return [
  903. 'draw_no' => $drawNo,
  904. 'draw_date_raw' => $result['drawDate'] ?? '',
  905. 'draw_date_beijing' => $drawDateBeijing ? $drawDateBeijing->format('Y-m-d H:i:s') : '',
  906. 'next_draw_seconds' => $result['nextKenoDrawTime'] ?? '',
  907. 'draw_interval_seconds' => $drawIntervalSeconds,
  908. 'source_numbers' => $sourceNumbers,
  909. 'sorted_numbers' => $pc28['sorted_numbers'],
  910. 'pc28' => $pc28,
  911. 'winning_numbers' => implode(',', $pc28['numbers']),
  912. 'combo' => self::getCombo($pc28['numbers']),
  913. 'start_time' => $startDateTime->format('Y-m-d H:i:s'),
  914. 'end_time' => $endDateTime->format('Y-m-d H:i:s'),
  915. ];
  916. }
  917. public static function getPlayNowWinningInfo($issueNo): array
  918. {
  919. Log::channel('issue')->info('后台手动开奖获取PlayNow数据', [
  920. 'issue_no' => $issueNo,
  921. ] + self::getPlayNowProxyLogContext());
  922. try {
  923. $result = self::fetchPlayNowKenoResult();
  924. } catch (\Throwable $exception) {
  925. Log::channel('issue')->info('后台手动开奖获取PlayNow数据异常', [
  926. 'issue_no' => $issueNo,
  927. 'error' => $exception->getMessage(),
  928. 'file' => $exception->getFile(),
  929. 'line' => $exception->getLine(),
  930. ]);
  931. return ['code' => self::NOT, 'msg' => '获取开奖信息失败'];
  932. }
  933. $drawInfo = self::parsePlayNowDrawInfo($result ?: []);
  934. if (!$drawInfo) {
  935. Log::channel('issue')->info('后台手动开奖PlayNow返回数据格式异常', [
  936. 'issue_no' => $issueNo,
  937. 'result' => $result,
  938. ]);
  939. return ['code' => self::NOT, 'msg' => '获取开奖信息失败'];
  940. }
  941. Log::channel('issue')->info('后台手动开奖PlayNow计算结果', [
  942. 'issue_no' => $issueNo,
  943. 'draw' => $drawInfo['draw_no'],
  944. 'draw_date_raw' => $drawInfo['draw_date_raw'],
  945. 'draw_date_beijing' => $drawInfo['draw_date_beijing'],
  946. 'source_numbers' => implode(',', $drawInfo['source_numbers']),
  947. 'sorted_numbers' => implode(',', $drawInfo['sorted_numbers']),
  948. 'pc28_numbers' => $drawInfo['winning_numbers'],
  949. 'pc28_total' => $drawInfo['pc28']['total'],
  950. 'combo' => $drawInfo['combo'],
  951. ]);
  952. if (!self::isSamePlayNowIssueNo($issueNo, $drawInfo['draw_no'])) {
  953. Log::channel('issue')->info('后台手动开奖期号不匹配', [
  954. 'issue_no' => $issueNo,
  955. 'playnow_draw' => $drawInfo['draw_no'],
  956. ]);
  957. return ['code' => self::NOT, 'msg' => '未查询到开奖信息,请手动开奖'];
  958. }
  959. return ['code' => self::YES, 'msg' => '获取成功'] + $drawInfo;
  960. }
  961. // 获取最新的开奖数据
  962. public static function getLatestIssue()
  963. {
  964. Log::channel('issue')->info('开始获取最新期号', [
  965. 'source' => 'playnow',
  966. ] + self::getPlayNowProxyLogContext());
  967. try {
  968. $result = self::fetchPlayNowKenoResult();
  969. } catch (\Throwable $exception) {
  970. Log::channel('issue')->info('获取PlayNow最新期号异常', [
  971. 'error' => $exception->getMessage(),
  972. 'file' => $exception->getFile(),
  973. 'line' => $exception->getLine(),
  974. ]);
  975. return ['code' => self::NOT, 'msg' => '获取最新期号失败'];
  976. }
  977. $drawInfo = self::parsePlayNowDrawInfo($result ?: []);
  978. if (!$drawInfo) {
  979. Log::channel('issue')->info('PlayNow返回数据格式异常', [
  980. 'result' => $result,
  981. ]);
  982. return ['code' => self::NOT, 'msg' => '获取最新期号失败'];
  983. }
  984. $drawNo = $drawInfo['draw_no'];
  985. $startTime = $drawInfo['start_time'];
  986. $endTime = $drawInfo['end_time'];
  987. $pc28 = $drawInfo['pc28'];
  988. Log::channel('issue')->info('PlayNow最新开奖数据', [
  989. 'draw' => $drawNo,
  990. 'draw_date_raw' => $drawInfo['draw_date_raw'],
  991. 'draw_date_beijing' => $drawInfo['draw_date_beijing'],
  992. 'next_draw_seconds' => $drawInfo['next_draw_seconds'],
  993. 'draw_interval_seconds' => $drawInfo['draw_interval_seconds'],
  994. 'source_numbers' => implode(',', $drawInfo['source_numbers']),
  995. 'sorted_numbers' => implode(',', $drawInfo['sorted_numbers']),
  996. 'pc28_numbers' => $drawInfo['winning_numbers'],
  997. 'pc28_total' => $pc28['total'],
  998. 'next_issue_start_time' => $startTime,
  999. 'next_issue_end_time' => $endTime,
  1000. ]);
  1001. $new = true;
  1002. $oldList = self::findAll(['status' => self::model()::STATUS_CLOSE]); // 获取所有封盘的期号
  1003. foreach ($oldList as $k => $v) {
  1004. if (self::isSamePlayNowIssueNo($v->issue_no, $drawNo)) {
  1005. $winning_numbers = $drawInfo['winning_numbers'];
  1006. $winArr = $pc28['numbers'];
  1007. $combo = $drawInfo['combo'];
  1008. $key = 'lottery_numbers_' . $v->issue_no;
  1009. if (Cache::add($key, $winning_numbers, 100)) {
  1010. Log::channel('issue')->info('开奖期号: ' . $v->issue_no . ' 开奖号码: ' . $winning_numbers, [
  1011. 'source' => 'playnow',
  1012. 'draw' => $drawNo,
  1013. 'combo' => $combo,
  1014. 'pc28_total' => $pc28['total'],
  1015. ]);
  1016. self::lotteryDraw($v->id, $winning_numbers, $combo, '');
  1017. $new = false;
  1018. } else {
  1019. Log::channel('issue')->info('开奖期号已处理,跳过重复开奖', [
  1020. 'issue_no' => $v->issue_no,
  1021. 'winning_numbers' => $winning_numbers,
  1022. ]);
  1023. }
  1024. $pc28Switch = Config::where('field', 'pc28_switch')->first()->val;
  1025. //更新游戏开关的切换
  1026. if ($pc28Switch == 0) Config::setPc28Switch();
  1027. }
  1028. }
  1029. // sleep(5); // 等待开奖完成
  1030. if ($new) {
  1031. $new_issue_no = $drawNo + 1; // 新期号
  1032. $newInfo = self::findOne(['issue_no' => $new_issue_no]); // 找新的期号
  1033. // 不存在
  1034. if (!$newInfo) {
  1035. Log::channel('issue')->info('新增期号: ' . $new_issue_no, [
  1036. 'source' => 'playnow',
  1037. 'start_time' => $startTime,
  1038. 'end_time' => $endTime,
  1039. ]);
  1040. $res = self::submit([
  1041. 'issue_no' => $new_issue_no,
  1042. 'status' => self::model()::STATUS_DRAFT,
  1043. 'start_time' => $startTime,
  1044. 'end_time' => $endTime,
  1045. ]);
  1046. Prediction::prediction($new_issue_no);
  1047. $id = $res['key'] ?? 0;
  1048. if ($id) {
  1049. self::betting($id); // 开始下注
  1050. }
  1051. Cache::set('new_issue_no', $new_issue_no, 10); // 缓存
  1052. } else {
  1053. Log::channel('issue')->info('期号已存在,无需新增', [
  1054. 'issue_no' => $new_issue_no,
  1055. 'status' => $newInfo->status,
  1056. 'start_time' => $newInfo->start_time,
  1057. 'end_time' => $newInfo->end_time,
  1058. ]);
  1059. }
  1060. }
  1061. return $result;
  1062. }
  1063. // 获取最新的开奖数据
  1064. public
  1065. static function getLatestIssue2()
  1066. {
  1067. $url = "https://ydpc28.co/api/pc28/list";
  1068. $result = file_get_contents($url);
  1069. $result = json_decode($result, true);
  1070. if ($result['errorCode'] != 0) {
  1071. return ['code' => self::NOT, 'msg' => '获取最新期号失败'];
  1072. }
  1073. $nextDrawInfo = $result['data']['nextDrawInfo'];
  1074. $startTime = $nextDrawInfo['currentBJTime'];
  1075. // if($nextDrawInfo['nextDrawTime'] >= date('H:i:s')) {
  1076. // $endTime = date('Y-m-d').' '.$nextDrawInfo['nextDrawTime']; // 下一期的截止时间
  1077. // }else{
  1078. // $endTime = date('Y-m-d',strtotime('+1 day')).' '.$nextDrawInfo['nextDrawTime']; // 下一期的截止时间
  1079. // }
  1080. $endTime = date('Y-m-d H:i:s', strtotime($startTime) + 210);
  1081. $new = true;
  1082. $list = $result['data']['list'];
  1083. $listKey = [];
  1084. foreach ($list as $k => $v) {
  1085. $listKey[$v['lotNumber']] = $v;
  1086. }
  1087. $oldList = self::findAll(['status' => self::model()::STATUS_CLOSE]); // 获取所有封盘的期号
  1088. foreach ($oldList as $k => $v) {
  1089. if (isset($listKey[$v->issue_no])) {
  1090. $issue = $listKey[$v->issue_no];
  1091. $winning_numbers = implode(',', str_split((string)$issue['openCode']));
  1092. $winArr = array_map('intval', explode(',', $winning_numbers));
  1093. // 组合
  1094. $sum = array_sum($winArr);
  1095. $combo = [];
  1096. $sumOddEven = self::calculateOddEven($sum); // 总和单双
  1097. $combo[] = $sumOddEven;
  1098. $sumSize = self::calculateSumSize($sum); // 总和大小
  1099. $combo[] = $sumSize;
  1100. $sumExtremeSize = self::calculateSumExtremeSize($sum); // 总和极值
  1101. if ($sumExtremeSize) {
  1102. $combo[] = $sumExtremeSize;
  1103. }
  1104. $sumBaoZi = self::isBaoZi($winArr[0], $winArr[1], $winArr[2]); // 豹子
  1105. if ($sumBaoZi) {
  1106. $combo[] = $sumBaoZi;
  1107. }
  1108. $sumPair = self::isPair($winArr[0], $winArr[1], $winArr[2]); // 对子
  1109. if ($sumPair) {
  1110. $combo[] = $sumPair;
  1111. }
  1112. $sumStraight = self::isStraight($winArr[0], $winArr[1], $winArr[2]); // 顺子
  1113. if ($sumStraight) {
  1114. $combo[] = $sumStraight;
  1115. }
  1116. $tail = self::getLastDigit($sum); // 总和尾数
  1117. if ($tail == 0 || $tail == 9) {
  1118. } else {
  1119. $combo[] = '尾' . $tail; // 尾数
  1120. }
  1121. $combo = implode(' ', $combo);
  1122. self::lotteryDraw($v->id, $winning_numbers, $combo, '');
  1123. }
  1124. }
  1125. return $result;
  1126. }
  1127. // 封盘倒数
  1128. public
  1129. static function syncCountdownIssue()
  1130. {
  1131. $info = self::model()::where('status', self::model()::STATUS_BETTING)->orderBy('end_time', 'asc')->first();
  1132. if ($info) {
  1133. $now_date = date('Y-m-d H:i:s', time() + IssueService::COUNTDOWN_TO_CLOSING_THE_MARKET);
  1134. if ($info['end_time'] < $now_date) {
  1135. $replyInfo = KeyboardService::findOne(['button' => '封盘倒数']);
  1136. if ($replyInfo) {
  1137. $text = $replyInfo->reply;
  1138. $buttons = json_decode($replyInfo->buttons, true);
  1139. $image = $replyInfo->image;
  1140. if ($image) {
  1141. $image = url($image);
  1142. }
  1143. if (Cache::has('issue_countdown_' . $info->issue_no)) {
  1144. } else {
  1145. $pc28Switch = Config::where('field', 'pc28_switch')->first()->val;
  1146. if ($pc28Switch == 0) {
  1147. self::asyncBettingGroupNotice($text, $buttons, $image);
  1148. Cache::put('issue_countdown_' . $info->issue_no, true, 60); // 缓存50秒,防止多次发送
  1149. }
  1150. }
  1151. }
  1152. }
  1153. }
  1154. }
  1155. // 停止下注
  1156. public
  1157. static function syncCloseIssue()
  1158. {
  1159. $now_date = date('Y-m-d H:i:s', time() + 30); // 提前30秒
  1160. $list = self::findAll(['status' => self::model()::STATUS_BETTING]);
  1161. foreach ($list as $k => $v) {
  1162. if ($v['end_time'] < $now_date) {
  1163. self::closeBetting($v->id);
  1164. }
  1165. }
  1166. }
  1167. // 生成开奖图片
  1168. public
  1169. static function lotteryImage($issue_no)
  1170. {
  1171. $list = self::model()::where('issue_no', '<=', $issue_no)->where(self::getWhere(['status' => self::model()::STATUS_DRAW]))->orderBy('issue_no', 'desc')->take(20)->get();
  1172. $records = $list->toArray();
  1173. foreach ($records as $k => $v) {
  1174. $winning_numbers = explode(',', $v['winning_numbers']);
  1175. $v['winning_numbers'] = $winning_numbers;
  1176. // 组合
  1177. $sum = array_sum($winning_numbers);
  1178. $v['sum'] = $sum;
  1179. $sumOddEven = self::calculateOddEven($sum); // 总和单双
  1180. $sumSize = self::calculateSumSize($sum); // 总和大小
  1181. $v['combo'] = $sumSize . ' ' . $sumOddEven;
  1182. $sumExtremeSize = self::calculateSumExtremeSize($sum); // 总和极值
  1183. if (!$sumExtremeSize) {
  1184. $sumExtremeSize = '-';
  1185. }
  1186. $v['extreme'] = $sumExtremeSize;
  1187. $tail = self::getLastDigit($sum); // 总和尾数
  1188. if ($tail === 0 || $tail === 9) {
  1189. $tailStr = '-';
  1190. } else {
  1191. $tailStr = '尾' . $tail;
  1192. }
  1193. $v['tail'] = $tailStr;
  1194. $records[$k] = $v;
  1195. }
  1196. $service = new LotteryImageService();
  1197. $url = $service->generate($records);
  1198. self::model()::where('issue_no', $issue_no)->update(['image' => $url]);
  1199. return $url;
  1200. }
  1201. // 发送开奖图片
  1202. public
  1203. static function sendLotteryImage($chatId, $issueNo)
  1204. {
  1205. $recordImage = self::lotteryImage($issueNo);
  1206. self::sendMessage($chatId, '', [], url($recordImage));
  1207. // dispatch(new SendTelegramMessageJob('', [], url($recordImage)));
  1208. }
  1209. }