Files
sbnews/server/app/api/controller/LotteryController.php
T
2026-06-11 12:15:29 +08:00

642 lines
22 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<?php
namespace app\api\controller;
use app\common\model\lottery\LotteryCategory;
use app\common\model\lottery\LotteryDraw;
use app\common\model\lottery\LotteryGame;
use app\common\model\lottery\LotteryGameCategory;
use app\common\model\lottery\LotteryDrawResult;
use app\common\model\lottery\LotteryAiAnalysis;
use app\common\model\lottery\LotteryNumberMapping;
use app\common\model\lottery\LotteryRegion;
use app\common\service\ai\AiService;
class LotteryController extends BaseApiController
{
public array $notNeedLogin = ['categoryList', 'regionList', 'drawList', 'drawDetail', 'latestDraw', 'aiPredict', 'gameList', 'latestDrawResult', 'drawResultList', 'aiHistory', 'aiHotCold', 'aiTrend', 'aiStats', 'aiColorZodiac', 'aiAnalysisHistory'];
/**
* 彩种游戏列表(一级分类 + 二级彩种)
*/
public function gameList()
{
$categories = LotteryGameCategory::where('is_show', 1)
->order('sort', 'asc')
->field('id,label,value,sort')
->select()
->toArray();
$games = LotteryGame::where('is_show', 1)
->order('sort asc, id asc')
->field('id,code,template,name,icon,status,open_status,category,group_id,open_count,sort,has_video')
->select()
->toArray();
$gameMap = [];
foreach ($games as $game) {
$gameMap[$game['category']][] = $game;
}
$result = [];
foreach ($categories as $cat) {
$cat['games'] = $gameMap[$cat['value']] ?? [];
$result[] = $cat;
}
return $this->data($result);
}
/**
* 彩票分类列表
*/
public function categoryList()
{
$domain = request()->domain() . '/';
$list = LotteryCategory::where('is_show', 1)
->order('sort', 'asc')
->field('id,name,code,region,icon,description,draw_rule,draw_time')
->select()
->toArray();
foreach ($list as &$item) {
if (!empty($item['icon']) && !str_starts_with($item['icon'], 'http')) {
$item['icon'] = $domain . $item['icon'];
}
}
return $this->data($list);
}
/**
* 彩票地区Tab列表
*/
public function regionList()
{
$domain = request()->domain() . '/';
$list = LotteryRegion::where('is_show', 1)
->order('sort', 'asc')
->field('id,label,value,icon')
->select()
->toArray();
foreach ($list as &$item) {
if (!empty($item['icon']) && !str_starts_with($item['icon'], 'http')) {
$item['icon'] = $domain . $item['icon'];
}
}
return $this->data($list);
}
/**
* 开奖列表
*/
public function drawList()
{
$categoryId = $this->request->get('category_id/d', 0);
$pageNo = $this->request->get('page_no/d', 1);
$pageSize = $this->request->get('page_size/d', 20);
$query = LotteryDraw::where('is_show', 1);
if ($categoryId > 0) {
$query->where('category_id', $categoryId);
}
$countQuery = clone $query;
$count = $countQuery->count();
$list = $query->order('draw_date desc, period desc')
->page($pageNo, $pageSize)
->select()
->toArray();
// 解析JSON字段
foreach ($list as &$item) {
$item['numbers'] = is_string($item['numbers']) ? json_decode($item['numbers'], true) : $item['numbers'];
$item['zodiac'] = is_string($item['zodiac']) ? json_decode($item['zodiac'], true) : $item['zodiac'];
$item['elements'] = is_string($item['elements']) ? json_decode($item['elements'], true) : $item['elements'];
$item['color'] = is_string($item['color']) ? json_decode($item['color'], true) : $item['color'];
$item['prize_info'] = is_string($item['prize_info']) ? json_decode($item['prize_info'], true) : $item['prize_info'];
}
return $this->data([
'lists' => $list,
'count' => $count,
'page_no' => $pageNo,
'page_size' => $pageSize,
'last_page' => ceil($count / $pageSize),
]);
}
/**
* 开奖详情
*/
public function drawDetail()
{
$id = $this->request->get('id/d', 0);
if (empty($id)) {
return $this->fail('参数缺失');
}
$draw = LotteryDraw::where('id', $id)->where('is_show', 1)->findOrEmpty();
if ($draw->isEmpty()) {
return $this->fail('记录不存在');
}
$data = $draw->toArray();
$data['numbers'] = is_string($data['numbers']) ? json_decode($data['numbers'], true) : $data['numbers'];
$data['zodiac'] = is_string($data['zodiac']) ? json_decode($data['zodiac'], true) : $data['zodiac'];
$data['elements'] = is_string($data['elements']) ? json_decode($data['elements'], true) : $data['elements'];
$data['color'] = is_string($data['color']) ? json_decode($data['color'], true) : $data['color'];
$data['prize_info'] = is_string($data['prize_info']) ? json_decode($data['prize_info'], true) : $data['prize_info'];
// 附带分类信息
$category = LotteryCategory::where('id', $data['category_id'])->findOrEmpty();
if (!$category->isEmpty()) {
$data['category'] = $category->toArray();
}
return $this->data($data);
}
/**
* 各彩种最新一期开奖
*/
public function latestDraw()
{
$categoryId = $this->request->get('category_id/d', 0);
$query = LotteryDraw::where('is_show', 1)->where('status', 1);
if ($categoryId > 0) {
$query->where('category_id', $categoryId);
}
// 按分类分组取最新一期
if ($categoryId > 0) {
$draw = $query->order('draw_date', 'desc')->findOrEmpty();
if ($draw->isEmpty()) {
return $this->data(null);
}
$data = $draw->toArray();
$data['numbers'] = is_string($data['numbers']) ? json_decode($data['numbers'], true) : $data['numbers'];
$data['color'] = is_string($data['color']) ? json_decode($data['color'], true) : $data['color'];
return $this->data($data);
}
// 获取所有分类的最新开奖
$categories = LotteryCategory::where('is_show', 1)->order('sort', 'asc')->select()->toArray();
$result = [];
foreach ($categories as $cat) {
$draw = LotteryDraw::where('category_id', $cat['id'])
->where('is_show', 1)
->where('status', 1)
->order('draw_date', 'desc')
->findOrEmpty();
if (!$draw->isEmpty()) {
$d = $draw->toArray();
$d['numbers'] = is_string($d['numbers']) ? json_decode($d['numbers'], true) : $d['numbers'];
$d['color'] = is_string($d['color']) ? json_decode($d['color'], true) : $d['color'];
$d['category_name'] = $cat['name'];
$d['category_code'] = $cat['code'];
$result[] = $d;
}
}
return $this->data($result);
}
/**
* 为号码数组附加生肖(zodiac)和波色(color)映射(六合彩专用)
*/
private function enrichNumbers(array $numbers, string $template = ''): array
{
if (empty($numbers))
return [];
$isHk6 = stripos($template, 'hk6') !== false || stripos($template, 'lhc') !== false;
if (!$isHk6)
return $numbers;
$year = (int) date('Y');
$mapping = LotteryNumberMapping::where('year', $year)
->whereIn('type', [LotteryNumberMapping::TYPE_ZODIAC, LotteryNumberMapping::TYPE_COLOR])
->order('type asc, sort asc')
->select()->toArray();
$zodiacMap = [];
$colorMap = [];
foreach ($mapping as $row) {
$nums = is_string($row['numbers']) ? json_decode($row['numbers'], true) : $row['numbers'];
foreach ($nums as $n) {
if ((int) $row['type'] === LotteryNumberMapping::TYPE_ZODIAC) {
$zodiacMap[(int) $n] = $row['attr_name'];
} else {
$colorMap[(int) $n] = $row['attr_code'];
}
}
}
$enriched = [];
foreach ($numbers as $num) {
$n = (int) $num;
$enriched[] = [
'num' => $num,
'zodiac' => $zodiacMap[$n] ?? '',
'color' => $colorMap[$n] ?? 'red',
];
}
return $enriched;
}
/**
* 各彩种最新一期开奖(新表 lottery_draw_result
*/
public function latestDrawResult()
{
$code = $this->request->get('code', '');
if ($code) {
$draw = LotteryDrawResult::where('code', $code)
->order('draw_time desc')
->findOrEmpty();
if ($draw->isEmpty()) {
return $this->data(null);
}
$data = $draw->toArray();
if (!empty($data['draw_code'])) {
$data['status'] = 1;
}
$rawNumbers = $data['draw_code'] ? explode(',', $data['draw_code']) : [];
$game = LotteryGame::where('code', $code)->field('name,template')->findOrEmpty();
$data['game_name'] = $game->isEmpty() ? $code : $game['name'];
$template = $game->isEmpty() ? '' : $game['template'];
$data['numbers'] = $this->enrichNumbers($rawNumbers, $template);
$data['template'] = $template;
return $this->data($data);
}
// 全部彩种,每个取最新一条
$games = LotteryGame::where('is_show', 1)
->order('sort asc, id asc')
->field('code,name,template,category')
->select()
->toArray();
$result = [];
foreach ($games as $game) {
$draw = LotteryDrawResult::where('code', $game['code'])
->order('draw_time desc')
->findOrEmpty();
if (!$draw->isEmpty()) {
$d = $draw->toArray();
if (!empty($d['draw_code'])) {
$d['status'] = 1;
}
$rawNumbers = $d['draw_code'] ? explode(',', $d['draw_code']) : [];
$d['numbers'] = $this->enrichNumbers($rawNumbers, $game['template']);
$d['game_name'] = $game['name'];
$d['template'] = $game['template'];
$d['category'] = $game['category'];
$result[] = $d;
}
}
return $this->data($result);
}
/**
* 开奖历史列表(新表 lottery_draw_result
*/
public function drawResultList()
{
$code = $this->request->get('code', '');
$pageNo = $this->request->get('page_no/d', 1);
$pageSize = $this->request->get('page_size/d', 20);
if (empty($code)) {
return $this->fail('缺少彩种编码');
}
$query = LotteryDrawResult::where('code', $code);
$count = (clone $query)->count();
$list = $query->order('draw_time desc, issue desc')
->page($pageNo, $pageSize)
->select()
->toArray();
$game = LotteryGame::where('code', $code)->field('template')->findOrEmpty();
$template = $game->isEmpty() ? '' : $game['template'];
foreach ($list as &$item) {
if (!empty($item['draw_code'])) {
$item['status'] = 1;
}
$rawNumbers = $item['draw_code'] ? explode(',', $item['draw_code']) : [];
$item['numbers'] = $this->enrichNumbers($rawNumbers, $template);
}
return $this->data([
'lists' => $list,
'count' => $count,
'page_no' => $pageNo,
'page_size' => $pageSize,
'last_page' => ceil($count / $pageSize),
]);
}
/**
* AI预测分析
*/
public function aiPredict()
{
$categoryId = $this->request->get('category_id/d', 0);
$code = $this->request->get('code', '');
// 支持两种查找方式:category_id(旧表)或 code(新表)
if (!empty($code)) {
$game = LotteryGame::where('code', $code)->findOrEmpty();
if ($game->isEmpty()) {
return $this->fail('彩种不存在');
}
$recentDraws = LotteryDrawResult::where('code', $code)
->where('draw_code', '<>', '')
->order('draw_time desc, issue desc')
->limit(30)
->field('issue,draw_time,draw_code')
->select()
->toArray();
foreach ($recentDraws as &$d) {
$d['numbers'] = $d['draw_code'] ? explode(',', $d['draw_code']) : [];
}
$lotteryData = [
'lottery_type' => $code,
'lottery_name' => $game['name'],
'draw_rule' => $game['template'] ?? '',
'recent_draws' => $recentDraws,
];
$result = AiService::analyzeLottery($lotteryData, $this->userId);
if (!$result['success']) {
return $this->fail($result['error'] ?? 'AI分析失败');
}
return $this->data([
'analysis' => $result['data'],
'from_cache' => $result['from_cache'] ?? false,
'category_name' => $game['name'],
]);
}
if (empty($categoryId)) {
return $this->fail('参数缺失');
}
$category = LotteryCategory::where('id', $categoryId)->findOrEmpty();
if ($category->isEmpty()) {
return $this->fail('彩种不存在');
}
// 获取最近30期已开奖数据
$recentDraws = LotteryDraw::where('category_id', $categoryId)
->where('status', 1)
->where('is_show', 1)
->order('draw_date desc, period desc')
->limit(30)
->field('period,draw_date,numbers,special_number,zodiac,color')
->select()
->toArray();
foreach ($recentDraws as &$d) {
$d['numbers'] = is_string($d['numbers']) ? json_decode($d['numbers'], true) : $d['numbers'];
$d['zodiac'] = is_string($d['zodiac']) ? json_decode($d['zodiac'], true) : $d['zodiac'];
$d['color'] = is_string($d['color']) ? json_decode($d['color'], true) : $d['color'];
}
$lotteryData = [
'lottery_type' => $category['code'],
'lottery_name' => $category['name'],
'draw_rule' => $category['draw_rule'],
'recent_draws' => $recentDraws,
];
$result = AiService::analyzeLottery($lotteryData, $this->userId);
if (!$result['success']) {
return $this->fail($result['error'] ?? 'AI分析失败');
}
return $this->data([
'analysis' => $result['data'],
'from_cache' => $result['from_cache'] ?? false,
'category_name' => $category['name'],
]);
}
/**
* 获取彩种信息和历史开奖数据(纯数据库查询,不走AI)
*/
public function aiHistory()
{
$code = $this->request->get('code', '');
if (empty($code)) {
return $this->fail('参数缺失');
}
$game = LotteryGame::where('code', $code)->findOrEmpty();
if ($game->isEmpty()) {
return $this->fail('彩种不存在');
}
$recentDraws = LotteryDrawResult::where('code', $code)
->where('draw_code', '<>', '')
->order('draw_time desc, issue desc')
->limit(30)
->field('issue,draw_time,draw_code')
->select()
->toArray();
foreach ($recentDraws as &$d) {
$d['numbers'] = $d['draw_code'] ? explode(',', $d['draw_code']) : [];
unset($d['draw_code']);
}
return $this->data([
'game_name' => $game['name'],
'game_code' => $code,
'template' => $game['template'] ?? '',
'recent_draws' => $recentDraws,
]);
}
/**
* AI分析 - 热冷号
*/
public function aiHotCold()
{
$code = $this->request->get('code', '');
if (empty($code)) {
return $this->fail('参数缺失');
}
$game = LotteryGame::where('code', $code)->findOrEmpty();
if ($game->isEmpty()) {
return $this->fail('彩种不存在');
}
$recentDraws = $this->getRecentDrawCodes($code, 30);
$lotteryData = [
'lottery_type' => $code,
'lottery_name' => $game['name'],
'draw_rule' => $game['template'] ?? '',
'recent_draws' => $recentDraws,
];
$result = AiService::analyzeLotteryModule($lotteryData, 'hot_cold', $this->userId);
if (!$result['success']) {
return $this->fail($result['error'] ?? 'AI分析失败');
}
return $this->data($result['data']);
}
/**
* AI分析 - 趋势+推荐组合
*/
public function aiTrend()
{
$code = $this->request->get('code', '');
if (empty($code)) {
return $this->fail('参数缺失');
}
$game = LotteryGame::where('code', $code)->findOrEmpty();
if ($game->isEmpty()) {
return $this->fail('彩种不存在');
}
$recentDraws = $this->getRecentDrawCodes($code, 30);
$lotteryData = [
'lottery_type' => $code,
'lottery_name' => $game['name'],
'draw_rule' => $game['template'] ?? '',
'recent_draws' => $recentDraws,
];
$result = AiService::analyzeLotteryModule($lotteryData, 'trend', $this->userId);
if (!$result['success']) {
return $this->fail($result['error'] ?? 'AI分析失败');
}
return $this->data($result['data']);
}
/**
* AI分析 - 统计数据
*/
public function aiStats()
{
$code = $this->request->get('code', '');
if (empty($code)) {
return $this->fail('参数缺失');
}
$game = LotteryGame::where('code', $code)->findOrEmpty();
if ($game->isEmpty()) {
return $this->fail('彩种不存在');
}
$recentDraws = $this->getRecentDrawCodes($code, 30);
$lotteryData = [
'lottery_type' => $code,
'lottery_name' => $game['name'],
'draw_rule' => $game['template'] ?? '',
'recent_draws' => $recentDraws,
];
$result = AiService::analyzeLotteryModule($lotteryData, 'stats', $this->userId);
if (!$result['success']) {
return $this->fail($result['error'] ?? 'AI分析失败');
}
return $this->data($result['data']);
}
/**
* AI分析 - 波色+生肖(六合彩专属)
*/
public function aiColorZodiac()
{
$code = $this->request->get('code', '');
if (empty($code)) {
return $this->fail('参数缺失');
}
$game = LotteryGame::where('code', $code)->findOrEmpty();
if ($game->isEmpty()) {
return $this->fail('彩种不存在');
}
if (($game['template'] ?? '') !== 'HK6') {
return $this->fail('该彩种不支持波色生肖分析');
}
$recentDraws = $this->getRecentDrawCodes($code, 30);
$year = (int) date('Y');
$colorMap = LotteryNumberMapping::getMapByType($year, LotteryNumberMapping::TYPE_COLOR);
$zodiacMap = LotteryNumberMapping::getMapByType($year, LotteryNumberMapping::TYPE_ZODIAC);
$lotteryData = [
'lottery_type' => $code,
'lottery_name' => $game['name'],
'draw_rule' => $game['template'] ?? '',
'recent_draws' => $recentDraws,
'number_mapping' => [
'color' => $colorMap,
'zodiac' => $zodiacMap,
],
];
$result = AiService::analyzeLotteryModule($lotteryData, 'color_zodiac', $this->userId);
if (!$result['success']) {
return $this->fail($result['error'] ?? 'AI分析失败');
}
return $this->data($result['data']);
}
/**
* 获取最近N期开奖号码(格式化为号码数组)
*/
private function getRecentDrawCodes(string $code, int $limit = 30): array
{
$rows = LotteryDrawResult::where('code', $code)
->where('draw_code', '<>', '')
->order('draw_time desc, issue desc')
->limit($limit)
->field('issue,draw_time,draw_code')
->select()
->toArray();
foreach ($rows as &$row) {
$row['numbers'] = $row['draw_code'] ? array_map('intval', explode(',', $row['draw_code'])) : [];
unset($row['draw_code']);
}
return $rows;
}
/**
* 获取某期的AI分析历史记录
*/
public function aiAnalysisHistory()
{
$code = $this->request->get('code', '');
$issue = $this->request->get('issue', '');
if (empty($code) || empty($issue)) {
return $this->fail('参数缺失');
}
$rows = LotteryAiAnalysis::where('code', $code)
->where('issue', $issue)
->field('module,result')
->select()
->toArray();
$result = [];
foreach ($rows as $row) {
$result[$row['module']] = json_decode($row['result'], true);
}
return $this->data($result);
}
}