IssueService.php 48 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466
  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, $keno = null)
  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. $kenoJson = self::encodePlayNowKeno($keno);
  319. if ($kenoJson) {
  320. $info->keno = $kenoJson;
  321. }
  322. $info->saveQuietly();
  323. $size = in_array("大", $awards);
  324. $size = $size ? "大" : "小";
  325. $oddOrEven = in_array("双", $awards);
  326. $oddOrEven = $oddOrEven ? "双" : "单";
  327. Prediction::result($info->issue_no, $size, $oddOrEven, $info->winning_numbers);
  328. Cao::updateData($awards);
  329. CaoHistory::updateData($awards);
  330. $pc28Switch = Config::where('field', 'pc28_switch')->first()->val;
  331. $replyInfo = KeyboardService::findOne(['button' => '本期开奖']);
  332. if ($replyInfo) {
  333. $text = $replyInfo->reply;
  334. $text .= "\n";
  335. $text .= $info->issue_no . ": " . implode('+', explode(',', $winning_numbers)) . "=" . array_sum($winArr) . " " . $combo;
  336. $buttons = json_decode($replyInfo->buttons, true);
  337. $image = $replyInfo->image;
  338. if ($image) {
  339. $image = url($image);
  340. }
  341. if (empty($buttons)) {
  342. $serviceAccount = Config::where('field', 'service_account')->first()->val;
  343. $buttons[] = [['text' => lang('✅唯一财务'), 'callback_data' => "", 'url' => "https://t.me/{$serviceAccount}"]];
  344. }
  345. // self::bettingGroupNotice($text, $buttons, $image, true);
  346. if ($pc28Switch == 0) SendTelegramGroupMessageJob::dispatch($text, $buttons, $image, true, "\n", [
  347. 'message_type' => 'draw_result',
  348. 'issue_no' => $info->issue_no,
  349. ])->afterCommit();
  350. }
  351. $recordImage = self::lotteryImage($info->issue_no);
  352. if ($recordImage) {
  353. // self::bettingGroupNotice('', [], url($recordImage));
  354. if ($pc28Switch == 0) SendTelegramGroupMessageJob::dispatch('', [], url($recordImage), false, "\n", [
  355. 'message_type' => 'draw_history_image',
  356. 'issue_no' => $info->issue_no,
  357. ])->afterCommit();
  358. }
  359. $info->image = $recordImage;
  360. $info->save();
  361. BetService::betSettled($info->issue_no, $awards);
  362. DB::commit();
  363. } catch (\Exception $e) {
  364. DB::rollBack();
  365. $message = "开奖失败:\n{$info->issue_no}\n";
  366. $message .= $e->getFile() . ':' . $e->getLine();
  367. $message .= "\n";
  368. $message .= $e->getMessage() . $winning_numbers;
  369. Log::error($message);
  370. return ['code' => self::NOT, 'msg' => '开奖失败', 'error' => $e->getMessage()];
  371. }
  372. return ['code' => self::YES, 'msg' => '开奖成功'];
  373. }
  374. // 虚拟开奖
  375. public
  376. static function fakeLotteryDraw($issue_no, $awards)
  377. {
  378. $fake_bet_list = Cache::get('fake_bet_' . $issue_no, []);
  379. $text = "";
  380. foreach ($fake_bet_list as $k => $v) {
  381. $lastStr = self::getLastChar($v['first_name'], 1);
  382. if (in_array($v['keywords'], $awards)) {
  383. $amount = (float)$v['amount'];
  384. $odds = (float)$v['odds'];
  385. $profit = $amount * $odds;
  386. if ($profit > 880000) {
  387. $profit = 880000; // 单注最高奖金880000
  388. }
  389. $item['profit'] = $profit;
  390. $yl = $profit - $amount;
  391. if ($k + 1 <= 30) {
  392. $text .= "私聊下注 【******" . $lastStr . "】 {$yl}\n";
  393. }
  394. } else {
  395. if ($k + 1 <= 30) {
  396. $text .= "私聊下注 【******" . $lastStr . "】 -{$v['amount']}\n";
  397. }
  398. }
  399. }
  400. return $text;
  401. }
  402. /**
  403. * @description: 获取中奖的奖项
  404. * @param {*} $winning_numbers
  405. * @return {*}
  406. */
  407. public
  408. static function award($winning_numbers)
  409. {
  410. $result = [];
  411. // 组合
  412. $sum = array_sum($winning_numbers);
  413. $section = self::getSection($sum); // 总和段位
  414. $result[] = $section;
  415. $sumOddEven = self::calculateOddEven($sum); // 总和单双
  416. $result[] = $sumOddEven;
  417. $sumSize = self::calculateSumSize($sum); // 总和大小
  418. $result[] = $sumSize;
  419. $sumExtremeSize = self::calculateSumExtremeSize($sum); // 总和极值
  420. if ($sumExtremeSize) {
  421. $result[] = $sumExtremeSize;
  422. }
  423. $sumCao = $sum . '操'; // 总和数字
  424. $result[] = $sumCao;
  425. $sumCombo = $sumSize . $sumOddEven; // 总和大小单双组合
  426. $result[] = $sumCombo;
  427. $sumBaoZi = self::isBaoZi($winning_numbers[0], $winning_numbers[1], $winning_numbers[2]); // 豹子
  428. if ($sumBaoZi) {
  429. $result[] = $sumBaoZi;
  430. }
  431. $sumPair = self::isPair($winning_numbers[0], $winning_numbers[1], $winning_numbers[2]); // 对子
  432. if ($sumPair) {
  433. $result[] = $sumPair;
  434. }
  435. $sumStraight = self::isStraight($winning_numbers[0], $winning_numbers[1], $winning_numbers[2]); // 顺子
  436. if ($sumStraight) {
  437. $result[] = $sumStraight;
  438. }
  439. $tail = self::getLastDigit($sum); // 总和尾数
  440. if (!in_array($tail, [0, 9])) {
  441. $result[] = $tail . '尾'; // 尾数
  442. $tailOddEven = self::calculateOddEven($tail); // 尾数单双
  443. $result[] = '尾' . $tailOddEven;
  444. $tailSize = self::calculateOneSize($tail); // 尾数大小
  445. $result[] = '尾' . $tailSize;
  446. $tailCombo = '尾' . $tailSize . $tailOddEven; // 尾数大小单双组合
  447. $result[] = $tailCombo;
  448. } else {
  449. $result[] = $tail . '尾'; // 尾数
  450. }
  451. $numA = $winning_numbers[0]; // A球
  452. $result[] = $numA . 'A';
  453. $numAOddEven = self::calculateOddEven($numA); // A球单双
  454. $result[] = 'A' . $numAOddEven;
  455. $numASize = self::calculateOneSize($numA); // A球大小
  456. $result[] = 'A' . $numASize;
  457. $result[] = 'A' . $numASize . $numAOddEven; // A球大小单双组合
  458. $numB = $winning_numbers[1]; // B球
  459. $result[] = $numB . 'B';
  460. $numBOddEven = self::calculateOddEven($numB); // B球
  461. $result[] = 'B' . $numBOddEven;
  462. $numBSize = self::calculateOneSize($numB); // B球大小
  463. $result[] = 'B' . $numBSize;
  464. $result[] = 'B' . $numBSize . $numBOddEven; // B球大小单双组合
  465. $numC = $winning_numbers[2];
  466. $result[] = $numC . 'C';
  467. $numCOddEven = self::calculateOddEven($numC); // C球单双
  468. $result[] = 'C' . $numCOddEven;
  469. $numCSize = self::calculateOneSize($numC); // C球大小
  470. $result[] = 'C' . $numCSize;
  471. $result[] = 'C' . $numCSize . $numCOddEven; // C球大小单双组合
  472. return $result;
  473. }
  474. /**
  475. * @description: 算单双
  476. * @param {*} $number
  477. * @return {*}
  478. */
  479. public
  480. static function calculateOddEven($number)
  481. {
  482. if ($number & 1) {
  483. return GameplayRuleEnum::SINGLE;
  484. } else {
  485. return GameplayRuleEnum::DOUBLE;
  486. }
  487. }
  488. /**
  489. * @description: 总和大小
  490. * @param {*} $number
  491. * @return {*}
  492. */
  493. public
  494. static function calculateSumSize($number)
  495. {
  496. if ($number >= GameplayRuleEnum::SUM_BIG) {
  497. return GameplayRuleEnum::BIG;
  498. }
  499. if ($number <= GameplayRuleEnum::SUM_SMALL) {
  500. return GameplayRuleEnum::SMALL;
  501. }
  502. }
  503. /**
  504. * @description: 总和极值
  505. * @param {*} $number
  506. * @return {*}
  507. */
  508. public
  509. static function calculateSumExtremeSize($number)
  510. {
  511. $result = '';
  512. if ($number >= GameplayRuleEnum::SUM_EXTREME_BIG) {
  513. $result = GameplayRuleEnum::EXTREME_BIG;
  514. }
  515. if ($number <= GameplayRuleEnum::SUM_EXTREME_SMALL) {
  516. $result = GameplayRuleEnum::EXTREME_SMALL;
  517. }
  518. return $result;
  519. }
  520. /**
  521. * @description: 豹子
  522. * @param {int} $a
  523. * @param {int} $b
  524. * @param {int} $c
  525. * @return {*}
  526. */
  527. public
  528. static function isBaoZi(int $a, int $b, int $c)
  529. {
  530. $result = '';
  531. if ($a === $b && $b === $c) {
  532. $result = GameplayRuleEnum::BAO_ZI;
  533. }
  534. return $result;
  535. }
  536. /**
  537. * @description: 对子
  538. * @param {int} $a
  539. * @param {int} $b
  540. * @param {int} $c
  541. * @return {*}
  542. */
  543. public
  544. static function isPair($a, $b, $c)
  545. {
  546. $result = '';
  547. // 确保输入都是个位数
  548. if (!is_numeric($a) || !is_numeric($b) || !is_numeric($c) ||
  549. $a < 0 || $a > 9 || $b < 0 || $b > 9 || $c < 0 || $c > 9) {
  550. return ''; // 或者抛出异常
  551. }
  552. if (($a == $b && $a != $c) ||
  553. ($a == $c && $a != $b) ||
  554. ($b == $c && $b != $a)) {
  555. $result = GameplayRuleEnum::PAIRS;
  556. }
  557. // 判断是否为对子情况
  558. return $result;
  559. }
  560. /**
  561. * @description: 顺子
  562. * @param {int} $a
  563. * @param {int} $b
  564. * @param {int} $c
  565. * @return {*}
  566. */
  567. public
  568. static function isStraight($a, $b, $c)
  569. {
  570. $result = '';
  571. // 确保输入都是个位数(0-9)
  572. if (!is_numeric($a) || !is_numeric($b) || !is_numeric($c) ||
  573. $a < 0 || $a > 9 || $b < 0 || $b > 9 || $c < 0 || $c > 9) {
  574. return '';
  575. }
  576. // 去重(顺子必须三个不同数字)
  577. if ($a == $b || $a == $c || $b == $c) {
  578. return '';
  579. }
  580. // 检查是否是完全升序或完全降序的连续数字
  581. $numbers = [$a, $b, $c];
  582. sort($numbers); // 排序后检查是否是 x, x+1, x+2
  583. list($x, $y, $z) = $numbers;
  584. // 情况1:升序连续(1,2,3)
  585. $isAscending = ($x + 1 == $y) && ($y + 1 == $z);
  586. // 情况2:降序连续(3,2,1)
  587. $isDescending = ($z + 1 == $y) && ($y + 1 == $x);
  588. if ($isAscending || $isDescending) {
  589. $result = GameplayRuleEnum::STRAIGHT;
  590. }
  591. return $result;
  592. }
  593. /**
  594. * 获取数字的尾数
  595. * @param int $number 输入数字
  596. * @return int 尾数
  597. */
  598. public
  599. static function getLastDigit($number)
  600. {
  601. // 确保输入是整数
  602. $number = (int)$number;
  603. // 取绝对值,处理负数情况
  604. $number = abs($number);
  605. // 取模10得到尾数
  606. return $number % 10;
  607. }
  608. /**
  609. * @description: 尾大小
  610. * @param {*} $number
  611. * @return {*}
  612. */
  613. public
  614. static function calculateOneSize($number)
  615. {
  616. if ($number >= GameplayRuleEnum::ONE_BIG) {
  617. return GameplayRuleEnum::BIG;
  618. }
  619. if ($number <= GameplayRuleEnum::ONE_SMALL) {
  620. return GameplayRuleEnum::SMALL;
  621. }
  622. }
  623. /**
  624. * @description: 获取段位
  625. * @param {*} $number
  626. * @return {*}
  627. */
  628. public
  629. static function getSection($number)
  630. {
  631. $result = '';
  632. if ($number >= GameplayRuleEnum::SECTION_1[0] && $number <= GameplayRuleEnum::SECTION_1[1]) {
  633. $result = GameplayRuleEnum::ONE;
  634. } elseif ($number >= GameplayRuleEnum::SECTION_2[0] && $number <= GameplayRuleEnum::SECTION_2[1]) {
  635. $result = GameplayRuleEnum::TWO;
  636. } elseif ($number >= GameplayRuleEnum::SECTION_3[0] && $number <= GameplayRuleEnum::SECTION_3[1]) {
  637. $result = GameplayRuleEnum::THREE;
  638. } elseif ($number >= GameplayRuleEnum::SECTION_4[0] && $number <= GameplayRuleEnum::SECTION_4[1]) {
  639. $result = GameplayRuleEnum::FOUR;
  640. }
  641. return $result; // 不在任何段中
  642. }
  643. /**
  644. * @description: 近期开奖记录
  645. * @return {*}
  646. */
  647. public
  648. static function currentLotteryResults($memberId)
  649. {
  650. // $result = self::model()::where('status', self::model()::STATUS_DRAW)->orderBy('id','desc')->take(16)->get();
  651. // $text = "📅 近期开奖记录\n";
  652. // $text .= "====================\n";
  653. // if($result){
  654. // foreach($result as $k => $v){
  655. // $winArr = explode(',',$v->winning_numbers);
  656. // // 组合
  657. // $sum = array_sum($winArr);
  658. // $combo = [];
  659. // $sumOddEven = self::calculateOddEven($sum); // 总和单双
  660. // $sumSize = self::calculateSumSize($sum); // 总和大小
  661. // $sumExtremeSize = self::calculateSumExtremeSize($sum); // 总和极值
  662. // if(empty($sumExtremeSize)){
  663. // $sumExtremeSize = "-";
  664. // }
  665. // $tail = self::getLastDigit($sum); // 总和尾数
  666. // if($tail == 0){
  667. // $tail = '-'; // 尾数
  668. // }else{
  669. // $tail = '尾'.$tail; // 尾数
  670. // }
  671. // $text .= "回合:{$v->issue_no}期 \n";
  672. // $text .= "结果:".implode('+',explode(',',$v->winning_numbers))."=".array_sum(explode(',',$v->winning_numbers))." \n";
  673. // $text .= "组合:{$sumSize} {$sumOddEven} \n";
  674. // $text .= "极值:{$sumExtremeSize} \n";
  675. // $text .= "尾数:{$tail} \n";
  676. // $text .= "---------------------------\n";
  677. // }
  678. // self::telegram()->sendMessage([
  679. // 'chat_id' => $memberId,
  680. // 'text' => $text,
  681. // ]);
  682. // }else{
  683. // self::telegram()->sendMessage([
  684. // 'chat_id' => $memberId,
  685. // 'text' => "暂无开奖记录",
  686. // ]);
  687. // }
  688. $result = self::model()::where('status', self::model()::STATUS_DRAW)->orderBy('id', 'desc')->first();
  689. if ($result) {
  690. if ($result->image) {
  691. // self::telegram()->sendPhoto([
  692. // 'chat_id' => $memberId,
  693. // 'photo' => InputFile::create(url($result->image)),
  694. // ]);
  695. return [
  696. 'chat_id' => $memberId,
  697. 'photo' => InputFile::create(url($result->image)),
  698. ];
  699. } else {
  700. // if($result->combo){
  701. // self::telegram()->sendMessage([
  702. // 'chat_id' => $memberId,
  703. // 'text' => "",
  704. // ]);
  705. // }else{
  706. // self::telegram()->sendMessage([
  707. // 'chat_id' => $memberId,
  708. // 'text' => lang("暂无开奖记录"),
  709. // ]);
  710. // }
  711. return
  712. [
  713. 'chat_id' => $memberId,
  714. 'text' => lang("暂无开奖记录"),
  715. ];
  716. }
  717. }
  718. }
  719. public
  720. static function getCombo($winArr)
  721. {
  722. // 组合
  723. $sum = array_sum($winArr);
  724. $combo = [];
  725. $sumSize = self::calculateSumSize($sum); // 总和大小
  726. $combo[] = $sumSize;
  727. $sumOddEven = self::calculateOddEven($sum); // 总和单双
  728. $combo[] = $sumOddEven;
  729. $sumExtremeSize = self::calculateSumExtremeSize($sum); // 总和极值
  730. if ($sumExtremeSize) {
  731. $combo[] = $sumExtremeSize;
  732. }
  733. $sumBaoZi = self::isBaoZi($winArr[0], $winArr[1], $winArr[2]); // 豹子
  734. if ($sumBaoZi) {
  735. $combo[] = $sumBaoZi;
  736. }
  737. $sumPair = self::isPair($winArr[0], $winArr[1], $winArr[2]); // 对子
  738. if ($sumPair) {
  739. $combo[] = $sumPair;
  740. }
  741. $sumStraight = self::isStraight($winArr[0], $winArr[1], $winArr[2]); // 顺子
  742. if ($sumStraight) {
  743. $combo[] = $sumStraight;
  744. }
  745. $tail = self::getLastDigit($sum); // 总和尾数
  746. if ($tail == 0 || $tail == 9) {
  747. } else {
  748. $combo[] = '尾' . $tail; // 尾数
  749. }
  750. return implode(' ', $combo);
  751. }
  752. private static function getPlayNowConfig($key, $default)
  753. {
  754. $value = config('services.playnow.' . $key, $default);
  755. return $value === null || $value === '' ? $default : $value;
  756. }
  757. private static function getPlayNowProxyUrl(): string
  758. {
  759. $scheme = self::getPlayNowConfig('proxy.scheme', 'http');
  760. $host = self::getPlayNowConfig('proxy.host', '155.138.141.119');
  761. $port = self::getPlayNowConfig('proxy.port', '3128');
  762. $username = (string)self::getPlayNowConfig('proxy.username', 'proxyuser');
  763. $password = (string)self::getPlayNowConfig('proxy.password', '');
  764. if ($username === '' && $password === '') {
  765. return "{$scheme}://{$host}:{$port}";
  766. }
  767. if ($password === '') {
  768. return "{$scheme}://" . rawurlencode($username) . "@{$host}:{$port}";
  769. }
  770. return "{$scheme}://" . rawurlencode($username) . ':' . rawurlencode($password) . "@{$host}:{$port}";
  771. }
  772. private static function getPlayNowProxyLogContext(): array
  773. {
  774. $host = self::getPlayNowConfig('proxy.host', '155.138.141.119');
  775. $password = self::getPlayNowConfig('proxy.password', '');
  776. return [
  777. 'proxy' => $host === '' ? 'missing' : 'configured',
  778. 'proxy_auth' => $password === '' ? 'missing' : 'configured',
  779. ];
  780. }
  781. private static function fetchPlayNowKenoResult()
  782. {
  783. $url = self::getPlayNowConfig('keno_url', self::PLAYNOW_KENO_URL);
  784. $response = Http::timeout(25)
  785. ->connectTimeout(10)
  786. ->withHeaders([
  787. 'Accept' => 'application/json, text/plain, */*',
  788. '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',
  789. ])
  790. ->withOptions([
  791. 'proxy' => self::getPlayNowProxyUrl(),
  792. 'curl' => [
  793. CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
  794. ],
  795. ])
  796. ->get($url);
  797. Log::channel('issue')->info('PlayNow接口响应', [
  798. 'status' => $response->status(),
  799. 'url' => $url,
  800. ] + self::getPlayNowProxyLogContext());
  801. if (!$response->successful()) {
  802. Log::channel('issue')->info('PlayNow接口请求失败', [
  803. 'status' => $response->status(),
  804. 'body' => substr($response->body(), 0, 500),
  805. ]);
  806. return null;
  807. }
  808. return $response->json();
  809. }
  810. private static function convertPlayNowDrawDateToBeijing($drawDate)
  811. {
  812. if (empty($drawDate)) {
  813. return null;
  814. }
  815. $sourceTimezone = new \DateTimeZone(self::getPlayNowConfig('timezone', self::PLAYNOW_SOURCE_TIMEZONE));
  816. $beijingTimezone = new \DateTimeZone(self::BEIJING_TIMEZONE);
  817. return (new \DateTimeImmutable($drawDate, $sourceTimezone))->setTimezone($beijingTimezone);
  818. }
  819. private static function getPlayNowDrawIntervalSeconds(array $result): int
  820. {
  821. $timeSinceDraw = isset($result['timeSinceDraw']) ? (int)$result['timeSinceDraw'] : 0;
  822. $nextDrawSeconds = isset($result['nextKenoDrawTime']) ? (int)$result['nextKenoDrawTime'] : 0;
  823. if ($timeSinceDraw > 0 && $nextDrawSeconds > 0) {
  824. return (int)round($timeSinceDraw / 1000) + $nextDrawSeconds;
  825. }
  826. return $nextDrawSeconds > 0 ? $nextDrawSeconds : self::PLAYNOW_DRAW_INTERVAL_SECONDS;
  827. }
  828. private static function calculatePlayNowPc28(array $numbers): array
  829. {
  830. $sortedNumbers = array_map('intval', $numbers);
  831. sort($sortedNumbers, SORT_NUMERIC);
  832. $sumByIndexes = function (array $indexes) use ($sortedNumbers) {
  833. $total = 0;
  834. foreach ($indexes as $index) {
  835. $total += $sortedNumbers[$index] ?? 0;
  836. }
  837. return $total % 10;
  838. };
  839. $firstNumber = $sumByIndexes([1, 4, 7, 10, 13, 16]);
  840. $secondNumber = $sumByIndexes([2, 5, 8, 11, 14, 17]);
  841. $thirdNumber = $sumByIndexes([3, 6, 9, 12, 15, 18]);
  842. return [
  843. 'numbers' => [$firstNumber, $secondNumber, $thirdNumber],
  844. 'total' => $firstNumber + $secondNumber + $thirdNumber,
  845. 'sorted_numbers' => $sortedNumbers,
  846. ];
  847. }
  848. private static function normalizePlayNowNumbers(array $numbers)
  849. {
  850. if (count($numbers) !== 20) {
  851. return null;
  852. }
  853. $normalized = [];
  854. foreach ($numbers as $number) {
  855. $value = filter_var($number, FILTER_VALIDATE_INT, [
  856. 'options' => [
  857. 'min_range' => 1,
  858. 'max_range' => 80,
  859. ],
  860. ]);
  861. if ($value === false) {
  862. return null;
  863. }
  864. $normalized[] = (int)$value;
  865. }
  866. if (count(array_unique($normalized)) !== 20) {
  867. return null;
  868. }
  869. return $normalized;
  870. }
  871. private static function encodePlayNowKeno($numbers)
  872. {
  873. if (!is_array($numbers)) {
  874. return null;
  875. }
  876. $numbers = self::normalizePlayNowNumbers($numbers);
  877. return $numbers ? json_encode(array_values($numbers)) : null;
  878. }
  879. private static function isSamePlayNowIssueNo($issueNo, int $drawNo): bool
  880. {
  881. $issueNo = trim((string)$issueNo);
  882. return ctype_digit($issueNo) && (int)$issueNo === $drawNo;
  883. }
  884. private static function normalizePlayNowDrawNo($drawNo)
  885. {
  886. $value = filter_var($drawNo, FILTER_VALIDATE_INT, [
  887. 'options' => [
  888. 'min_range' => 1,
  889. ],
  890. ]);
  891. return $value === false ? null : (int)$value;
  892. }
  893. private static function parsePlayNowDrawInfo(array $result)
  894. {
  895. if (!isset($result['draw']) || empty($result['num']) || !is_array($result['num'])) {
  896. return null;
  897. }
  898. $drawNo = self::normalizePlayNowDrawNo($result['draw']);
  899. if (!$drawNo) {
  900. return null;
  901. }
  902. $sourceNumbers = self::normalizePlayNowNumbers($result['num']);
  903. if (!$sourceNumbers) {
  904. return null;
  905. }
  906. try {
  907. $drawDateBeijing = self::convertPlayNowDrawDateToBeijing($result['drawDate'] ?? null);
  908. } catch (\Throwable $exception) {
  909. Log::channel('issue')->info('PlayNow开奖时间解析失败', [
  910. 'draw_date_raw' => $result['drawDate'] ?? '',
  911. 'error' => $exception->getMessage(),
  912. ]);
  913. return null;
  914. }
  915. $nowBeijing = new \DateTimeImmutable('now', new \DateTimeZone(self::BEIJING_TIMEZONE));
  916. $drawIntervalSeconds = self::getPlayNowDrawIntervalSeconds($result);
  917. $startDateTime = $drawDateBeijing ?: $nowBeijing;
  918. $endDateTime = $startDateTime->modify('+' . $drawIntervalSeconds . ' seconds');
  919. $pc28 = self::calculatePlayNowPc28($sourceNumbers);
  920. return [
  921. 'draw_no' => $drawNo,
  922. 'draw_date_raw' => $result['drawDate'] ?? '',
  923. 'draw_date_beijing' => $drawDateBeijing ? $drawDateBeijing->format('Y-m-d H:i:s') : '',
  924. 'next_draw_seconds' => $result['nextKenoDrawTime'] ?? '',
  925. 'draw_interval_seconds' => $drawIntervalSeconds,
  926. 'source_numbers' => $sourceNumbers,
  927. 'sorted_numbers' => $pc28['sorted_numbers'],
  928. 'pc28' => $pc28,
  929. 'winning_numbers' => implode(',', $pc28['numbers']),
  930. 'combo' => self::getCombo($pc28['numbers']),
  931. 'start_time' => $startDateTime->format('Y-m-d H:i:s'),
  932. 'end_time' => $endDateTime->format('Y-m-d H:i:s'),
  933. ];
  934. }
  935. public static function getPlayNowWinningInfo($issueNo): array
  936. {
  937. Log::channel('issue')->info('后台手动开奖获取PlayNow数据', [
  938. 'issue_no' => $issueNo,
  939. ] + self::getPlayNowProxyLogContext());
  940. try {
  941. $result = self::fetchPlayNowKenoResult();
  942. } catch (\Throwable $exception) {
  943. Log::channel('issue')->info('后台手动开奖获取PlayNow数据异常', [
  944. 'issue_no' => $issueNo,
  945. 'error' => $exception->getMessage(),
  946. 'file' => $exception->getFile(),
  947. 'line' => $exception->getLine(),
  948. ]);
  949. return ['code' => self::NOT, 'msg' => '获取开奖信息失败'];
  950. }
  951. $drawInfo = self::parsePlayNowDrawInfo($result ?: []);
  952. if (!$drawInfo) {
  953. Log::channel('issue')->info('后台手动开奖PlayNow返回数据格式异常', [
  954. 'issue_no' => $issueNo,
  955. 'result' => $result,
  956. ]);
  957. return ['code' => self::NOT, 'msg' => '获取开奖信息失败'];
  958. }
  959. Log::channel('issue')->info('后台手动开奖PlayNow计算结果', [
  960. 'issue_no' => $issueNo,
  961. 'draw' => $drawInfo['draw_no'],
  962. 'draw_date_raw' => $drawInfo['draw_date_raw'],
  963. 'draw_date_beijing' => $drawInfo['draw_date_beijing'],
  964. 'source_numbers' => implode(',', $drawInfo['source_numbers']),
  965. 'sorted_numbers' => implode(',', $drawInfo['sorted_numbers']),
  966. 'pc28_numbers' => $drawInfo['winning_numbers'],
  967. 'pc28_total' => $drawInfo['pc28']['total'],
  968. 'combo' => $drawInfo['combo'],
  969. ]);
  970. if (!self::isSamePlayNowIssueNo($issueNo, $drawInfo['draw_no'])) {
  971. Log::channel('issue')->info('后台手动开奖期号不匹配', [
  972. 'issue_no' => $issueNo,
  973. 'playnow_draw' => $drawInfo['draw_no'],
  974. ]);
  975. return ['code' => self::NOT, 'msg' => '未查询到开奖信息,请手动开奖'];
  976. }
  977. return ['code' => self::YES, 'msg' => '获取成功'] + $drawInfo;
  978. }
  979. // 获取最新的开奖数据
  980. public static function getLatestIssue()
  981. {
  982. Log::channel('issue')->info('开始获取最新期号', [
  983. 'source' => 'playnow',
  984. ] + self::getPlayNowProxyLogContext());
  985. try {
  986. $result = self::fetchPlayNowKenoResult();
  987. } catch (\Throwable $exception) {
  988. Log::channel('issue')->info('获取PlayNow最新期号异常', [
  989. 'error' => $exception->getMessage(),
  990. 'file' => $exception->getFile(),
  991. 'line' => $exception->getLine(),
  992. ]);
  993. return ['code' => self::NOT, 'msg' => '获取最新期号失败'];
  994. }
  995. $drawInfo = self::parsePlayNowDrawInfo($result ?: []);
  996. if (!$drawInfo) {
  997. Log::channel('issue')->info('PlayNow返回数据格式异常', [
  998. 'result' => $result,
  999. ]);
  1000. return ['code' => self::NOT, 'msg' => '获取最新期号失败'];
  1001. }
  1002. $drawNo = $drawInfo['draw_no'];
  1003. $startTime = $drawInfo['start_time'];
  1004. $endTime = $drawInfo['end_time'];
  1005. $pc28 = $drawInfo['pc28'];
  1006. Log::channel('issue')->info('PlayNow最新开奖数据', [
  1007. 'draw' => $drawNo,
  1008. 'draw_date_raw' => $drawInfo['draw_date_raw'],
  1009. 'draw_date_beijing' => $drawInfo['draw_date_beijing'],
  1010. 'next_draw_seconds' => $drawInfo['next_draw_seconds'],
  1011. 'draw_interval_seconds' => $drawInfo['draw_interval_seconds'],
  1012. 'source_numbers' => implode(',', $drawInfo['source_numbers']),
  1013. 'sorted_numbers' => implode(',', $drawInfo['sorted_numbers']),
  1014. 'pc28_numbers' => $drawInfo['winning_numbers'],
  1015. 'pc28_total' => $pc28['total'],
  1016. 'next_issue_start_time' => $startTime,
  1017. 'next_issue_end_time' => $endTime,
  1018. ]);
  1019. $kenoJson = self::encodePlayNowKeno($drawInfo['source_numbers']);
  1020. if ($kenoJson) {
  1021. $drawIssue = self::findOne(['issue_no' => (string)$drawNo]);
  1022. if ($drawIssue && $drawIssue->status == self::model()::STATUS_DRAW && empty($drawIssue->keno)) {
  1023. $drawIssue->keno = $kenoJson;
  1024. $drawIssue->saveQuietly();
  1025. Log::channel('issue')->info('PlayNow源头号码已补写', [
  1026. 'issue_no' => $drawNo,
  1027. 'source_numbers' => implode(',', $drawInfo['source_numbers']),
  1028. ]);
  1029. }
  1030. }
  1031. $new = true;
  1032. $oldList = self::findAll(['status' => self::model()::STATUS_CLOSE]); // 获取所有封盘的期号
  1033. foreach ($oldList as $k => $v) {
  1034. if (self::isSamePlayNowIssueNo($v->issue_no, $drawNo)) {
  1035. $winning_numbers = $drawInfo['winning_numbers'];
  1036. $winArr = $pc28['numbers'];
  1037. $combo = $drawInfo['combo'];
  1038. $key = 'lottery_numbers_' . $v->issue_no;
  1039. if (Cache::add($key, $winning_numbers, 100)) {
  1040. Log::channel('issue')->info('开奖期号: ' . $v->issue_no . ' 开奖号码: ' . $winning_numbers, [
  1041. 'source' => 'playnow',
  1042. 'draw' => $drawNo,
  1043. 'combo' => $combo,
  1044. 'pc28_total' => $pc28['total'],
  1045. ]);
  1046. self::lotteryDraw($v->id, $winning_numbers, $combo, '', $drawInfo['source_numbers']);
  1047. $new = false;
  1048. } else {
  1049. Log::channel('issue')->info('开奖期号已处理,跳过重复开奖', [
  1050. 'issue_no' => $v->issue_no,
  1051. 'winning_numbers' => $winning_numbers,
  1052. ]);
  1053. }
  1054. $pc28Switch = Config::where('field', 'pc28_switch')->first()->val;
  1055. //更新游戏开关的切换
  1056. if ($pc28Switch == 0) Config::setPc28Switch();
  1057. }
  1058. }
  1059. // sleep(5); // 等待开奖完成
  1060. if ($new) {
  1061. $new_issue_no = $drawNo + 1; // 新期号
  1062. $newInfo = self::findOne(['issue_no' => $new_issue_no]); // 找新的期号
  1063. // 不存在
  1064. if (!$newInfo) {
  1065. Log::channel('issue')->info('新增期号: ' . $new_issue_no, [
  1066. 'source' => 'playnow',
  1067. 'start_time' => $startTime,
  1068. 'end_time' => $endTime,
  1069. ]);
  1070. $res = self::submit([
  1071. 'issue_no' => $new_issue_no,
  1072. 'status' => self::model()::STATUS_DRAFT,
  1073. 'start_time' => $startTime,
  1074. 'end_time' => $endTime,
  1075. ]);
  1076. Prediction::prediction($new_issue_no);
  1077. $id = $res['key'] ?? 0;
  1078. if ($id) {
  1079. self::betting($id); // 开始下注
  1080. }
  1081. Cache::set('new_issue_no', $new_issue_no, 10); // 缓存
  1082. } else {
  1083. Log::channel('issue')->info('期号已存在,无需新增', [
  1084. 'issue_no' => $new_issue_no,
  1085. 'status' => $newInfo->status,
  1086. 'start_time' => $newInfo->start_time,
  1087. 'end_time' => $newInfo->end_time,
  1088. ]);
  1089. }
  1090. }
  1091. return $result;
  1092. }
  1093. // 获取最新的开奖数据
  1094. public
  1095. static function getLatestIssue2()
  1096. {
  1097. $url = "https://ydpc28.co/api/pc28/list";
  1098. $result = file_get_contents($url);
  1099. $result = json_decode($result, true);
  1100. if ($result['errorCode'] != 0) {
  1101. return ['code' => self::NOT, 'msg' => '获取最新期号失败'];
  1102. }
  1103. $nextDrawInfo = $result['data']['nextDrawInfo'];
  1104. $startTime = $nextDrawInfo['currentBJTime'];
  1105. // if($nextDrawInfo['nextDrawTime'] >= date('H:i:s')) {
  1106. // $endTime = date('Y-m-d').' '.$nextDrawInfo['nextDrawTime']; // 下一期的截止时间
  1107. // }else{
  1108. // $endTime = date('Y-m-d',strtotime('+1 day')).' '.$nextDrawInfo['nextDrawTime']; // 下一期的截止时间
  1109. // }
  1110. $endTime = date('Y-m-d H:i:s', strtotime($startTime) + 210);
  1111. $new = true;
  1112. $list = $result['data']['list'];
  1113. $listKey = [];
  1114. foreach ($list as $k => $v) {
  1115. $listKey[$v['lotNumber']] = $v;
  1116. }
  1117. $oldList = self::findAll(['status' => self::model()::STATUS_CLOSE]); // 获取所有封盘的期号
  1118. foreach ($oldList as $k => $v) {
  1119. if (isset($listKey[$v->issue_no])) {
  1120. $issue = $listKey[$v->issue_no];
  1121. $winning_numbers = implode(',', str_split((string)$issue['openCode']));
  1122. $winArr = array_map('intval', explode(',', $winning_numbers));
  1123. // 组合
  1124. $sum = array_sum($winArr);
  1125. $combo = [];
  1126. $sumOddEven = self::calculateOddEven($sum); // 总和单双
  1127. $combo[] = $sumOddEven;
  1128. $sumSize = self::calculateSumSize($sum); // 总和大小
  1129. $combo[] = $sumSize;
  1130. $sumExtremeSize = self::calculateSumExtremeSize($sum); // 总和极值
  1131. if ($sumExtremeSize) {
  1132. $combo[] = $sumExtremeSize;
  1133. }
  1134. $sumBaoZi = self::isBaoZi($winArr[0], $winArr[1], $winArr[2]); // 豹子
  1135. if ($sumBaoZi) {
  1136. $combo[] = $sumBaoZi;
  1137. }
  1138. $sumPair = self::isPair($winArr[0], $winArr[1], $winArr[2]); // 对子
  1139. if ($sumPair) {
  1140. $combo[] = $sumPair;
  1141. }
  1142. $sumStraight = self::isStraight($winArr[0], $winArr[1], $winArr[2]); // 顺子
  1143. if ($sumStraight) {
  1144. $combo[] = $sumStraight;
  1145. }
  1146. $tail = self::getLastDigit($sum); // 总和尾数
  1147. if ($tail == 0 || $tail == 9) {
  1148. } else {
  1149. $combo[] = '尾' . $tail; // 尾数
  1150. }
  1151. $combo = implode(' ', $combo);
  1152. self::lotteryDraw($v->id, $winning_numbers, $combo, '');
  1153. }
  1154. }
  1155. return $result;
  1156. }
  1157. // 封盘倒数
  1158. public
  1159. static function syncCountdownIssue()
  1160. {
  1161. $info = self::model()::where('status', self::model()::STATUS_BETTING)->orderBy('end_time', 'asc')->first();
  1162. if ($info) {
  1163. $now_date = date('Y-m-d H:i:s', time() + IssueService::COUNTDOWN_TO_CLOSING_THE_MARKET);
  1164. if ($info['end_time'] < $now_date) {
  1165. $replyInfo = KeyboardService::findOne(['button' => '封盘倒数']);
  1166. if ($replyInfo) {
  1167. $text = $replyInfo->reply;
  1168. $buttons = json_decode($replyInfo->buttons, true);
  1169. $image = $replyInfo->image;
  1170. if ($image) {
  1171. $image = url($image);
  1172. }
  1173. if (Cache::has('issue_countdown_' . $info->issue_no)) {
  1174. } else {
  1175. $pc28Switch = Config::where('field', 'pc28_switch')->first()->val;
  1176. if ($pc28Switch == 0) {
  1177. self::asyncBettingGroupNotice($text, $buttons, $image);
  1178. Cache::put('issue_countdown_' . $info->issue_no, true, 60); // 缓存50秒,防止多次发送
  1179. }
  1180. }
  1181. }
  1182. }
  1183. }
  1184. }
  1185. // 停止下注
  1186. public
  1187. static function syncCloseIssue()
  1188. {
  1189. $now_date = date('Y-m-d H:i:s', time() + 30); // 提前30秒
  1190. $list = self::findAll(['status' => self::model()::STATUS_BETTING]);
  1191. foreach ($list as $k => $v) {
  1192. if ($v['end_time'] < $now_date) {
  1193. self::closeBetting($v->id);
  1194. }
  1195. }
  1196. }
  1197. // 生成开奖图片
  1198. public
  1199. static function lotteryImage($issue_no)
  1200. {
  1201. $list = self::model()::where('issue_no', '<=', $issue_no)->where(self::getWhere(['status' => self::model()::STATUS_DRAW]))->orderBy('issue_no', 'desc')->take(20)->get();
  1202. $records = $list->toArray();
  1203. foreach ($records as $k => $v) {
  1204. $winning_numbers = explode(',', $v['winning_numbers']);
  1205. $v['winning_numbers'] = $winning_numbers;
  1206. // 组合
  1207. $sum = array_sum($winning_numbers);
  1208. $v['sum'] = $sum;
  1209. $sumOddEven = self::calculateOddEven($sum); // 总和单双
  1210. $sumSize = self::calculateSumSize($sum); // 总和大小
  1211. $v['combo'] = $sumSize . ' ' . $sumOddEven;
  1212. $sumExtremeSize = self::calculateSumExtremeSize($sum); // 总和极值
  1213. if (!$sumExtremeSize) {
  1214. $sumExtremeSize = '-';
  1215. }
  1216. $v['extreme'] = $sumExtremeSize;
  1217. $tail = self::getLastDigit($sum); // 总和尾数
  1218. if ($tail === 0 || $tail === 9) {
  1219. $tailStr = '-';
  1220. } else {
  1221. $tailStr = '尾' . $tail;
  1222. }
  1223. $v['tail'] = $tailStr;
  1224. $records[$k] = $v;
  1225. }
  1226. $service = new LotteryImageService();
  1227. $url = $service->generate($records);
  1228. self::model()::where('issue_no', $issue_no)->update(['image' => $url]);
  1229. return $url;
  1230. }
  1231. // 发送开奖图片
  1232. public
  1233. static function sendLotteryImage($chatId, $issueNo)
  1234. {
  1235. $recordImage = self::lotteryImage($issueNo);
  1236. self::sendMessage($chatId, '', [], url($recordImage));
  1237. // dispatch(new SendTelegramMessageJob('', [], url($recordImage)));
  1238. }
  1239. }