469 lines
16 KiB
PHP
469 lines
16 KiB
PHP
<?php
|
|
|
|
namespace app\admin\model;
|
|
|
|
use think\Model;
|
|
|
|
use app\admin\model\Num;
|
|
|
|
|
|
class History extends Model
|
|
{
|
|
|
|
|
|
|
|
|
|
|
|
// 表名
|
|
protected $name = 'history';
|
|
|
|
// 自动写入时间戳字段
|
|
protected $autoWriteTimestamp = false;
|
|
|
|
// 定义时间戳字段名
|
|
protected $createTime = false;
|
|
protected $updateTime = false;
|
|
protected $deleteTime = false;
|
|
|
|
// 追加属性
|
|
protected $append = [
|
|
|
|
];
|
|
|
|
/**
|
|
* 获取走势图数据
|
|
* @param int $periods 查询最近多少期
|
|
* @param string $type 查询类型 all=全部号码 special=仅特码
|
|
* @return array {expects: [], data: [[num1,...], ...], colorMap: []}
|
|
*/
|
|
public function getTrendData($periods = 30, $type = 'all')
|
|
{
|
|
// 查询波色映射
|
|
$num_model = new Num();
|
|
$colorMap = $num_model->column('color', 'num');
|
|
|
|
$history = $this
|
|
->field('expect,num1,num2,num3,num4,num5,num6,num7,openTime')
|
|
->order('openTime', 'desc')
|
|
->limit($periods)
|
|
->select();
|
|
|
|
if (empty($history)) {
|
|
return ['expects' => [], 'data' => [], 'colorMap' => []];
|
|
}
|
|
|
|
$expects = [];
|
|
$data = [];
|
|
foreach ($history as $row) {
|
|
$expects[] = (string)$row['expect'];
|
|
if ($type === 'special') {
|
|
$data[] = ['num7' => (int)$row['num7']];
|
|
} else {
|
|
$row_data = [];
|
|
for ($i = 1; $i <= 7; $i++) {
|
|
$row_data['num' . $i] = (int)$row['num' . $i];
|
|
}
|
|
$data[] = $row_data;
|
|
}
|
|
}
|
|
|
|
// 反转数组,使最远的数据在左边,最近的数据在右边(从左往右,从远到近)
|
|
$expects = array_reverse($expects);
|
|
$data = array_reverse($data);
|
|
|
|
return [
|
|
'expects' => $expects,
|
|
'data' => $data,
|
|
'colorMap' => $colorMap
|
|
];
|
|
}
|
|
|
|
/**
|
|
* 获取冷热号码
|
|
* @param int $periods 查询最近多少期
|
|
* @param string $type 查询类型 all=全部号码 special=仅特码
|
|
* @return array {hot: [], cold: [], all: []}
|
|
*/
|
|
public function getHotColdNumbers($periods = 30, $type = 'all')
|
|
{
|
|
$num_model = new Num();
|
|
$colorMap = $num_model->column('color', 'num');
|
|
$animalMap = $num_model->column('animal', 'num');
|
|
|
|
$history = $this
|
|
->field('expect,num1,num2,num3,num4,num5,num6,num7,openTime')
|
|
->order('openTime', 'desc')
|
|
->limit($periods)
|
|
->select();
|
|
|
|
if (empty($history)) {
|
|
return ['hot' => [], 'cold' => [], 'all' => []];
|
|
}
|
|
|
|
$fields = ($type === 'special') ? ['num7'] : ['num1', 'num2', 'num3', 'num4', 'num5', 'num6', 'num7'];
|
|
|
|
// 统计每个号码的出现次数
|
|
$count = array_fill(1, 49, 0);
|
|
$totalAppearances = 0;
|
|
foreach ($history as $row) {
|
|
foreach ($fields as $field) {
|
|
$num = (int)$row[$field];
|
|
if ($num >= 1 && $num <= 49) {
|
|
$count[$num]++;
|
|
$totalAppearances++;
|
|
}
|
|
}
|
|
}
|
|
|
|
$all = [];
|
|
for ($num = 1; $num <= 49; $num++) {
|
|
$percent = $totalAppearances > 0 ? round($count[$num] / $totalAppearances * 100, 1) : 0;
|
|
$all[] = [
|
|
'num' => $num,
|
|
'count' => $count[$num],
|
|
'percent' => $percent,
|
|
'color' => $colorMap[$num] ?? '—',
|
|
'animal' => $animalMap[$num] ?? '—'
|
|
];
|
|
}
|
|
|
|
// 按出现次数降序排序
|
|
$sorted = $all;
|
|
usort($sorted, function ($a, $b) {
|
|
return $b['count'] - $a['count'];
|
|
});
|
|
|
|
// 热号: top 10, 冷号: bottom 10
|
|
$hot = array_slice($sorted, 0, 10);
|
|
$cold = array_slice($sorted, -10);
|
|
$cold = array_reverse($cold);
|
|
|
|
return ['hot' => $hot, 'cold' => $cold, 'all' => $all];
|
|
}
|
|
|
|
/**
|
|
* 计算遗漏号码
|
|
* @param int $periods 查询最近多少期
|
|
* @param string $type 查询类型 all=全部号码 special=仅特码
|
|
* @return array [{num: int, omit: int, color: string}, ...]
|
|
*/
|
|
public function getMissingNumbers($periods = 10, $type = 'all')
|
|
{
|
|
// 查询最近 $periods 期开奖数据
|
|
if ($type === 'special') {
|
|
$history = $this
|
|
->field('expect,num7')
|
|
->order('openTime', 'desc')
|
|
->limit($periods)
|
|
->select();
|
|
} else {
|
|
$history = $this
|
|
->field('expect,num1,num2,num3,num4,num5,num6,num7')
|
|
->order('openTime', 'desc')
|
|
->limit($periods)
|
|
->select();
|
|
}
|
|
|
|
// 收集最近 $periods 期出现过的号码
|
|
$appeared = [];
|
|
foreach ($history as $row) {
|
|
if ($type === 'special') {
|
|
$fields = ['num7'];
|
|
} else {
|
|
$fields = ['num1', 'num2', 'num3', 'num4', 'num5', 'num6', 'num7'];
|
|
}
|
|
foreach ($fields as $field) {
|
|
if ($row[$field] !== null && $row[$field] !== '') {
|
|
$appeared[(int)$row[$field]] = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
// 获取遗漏号码(1-49中未出现的)
|
|
$missing = [];
|
|
for ($num = 1; $num <= 49; $num++) {
|
|
if (!isset($appeared[$num])) {
|
|
$missing[] = $num;
|
|
}
|
|
}
|
|
|
|
// 查询更多历史数据用于计算遗漏期数
|
|
if ($type === 'special') {
|
|
$allHistory = $this
|
|
->field('num7')
|
|
->order('openTime', 'desc')
|
|
->limit(500)
|
|
->select();
|
|
} else {
|
|
$allHistory = $this
|
|
->field('num1,num2,num3,num4,num5,num6,num7')
|
|
->order('openTime', 'desc')
|
|
->limit(500)
|
|
->select();
|
|
}
|
|
|
|
// 查询波色映射
|
|
$num_model = new Num();
|
|
$colorMap = $num_model->column('color', 'num');
|
|
|
|
// 计算遗漏期数并组装结果
|
|
$result = [];
|
|
foreach ($missing as $num) {
|
|
$omitCount = $this->calcOmitCount($num, $allHistory, $type);
|
|
$result[] = [
|
|
'num' => $num,
|
|
'omit' => $omitCount,
|
|
'color' => $colorMap[$num] ?? '—'
|
|
];
|
|
}
|
|
|
|
// 按遗漏期数降序排序
|
|
usort($result, function ($a, $b) {
|
|
return $b['omit'] - $a['omit'];
|
|
});
|
|
|
|
return $result;
|
|
}
|
|
|
|
/**
|
|
* 计算某个号码的遗漏期数
|
|
* @param int $num 号码
|
|
* @param array $allHistory 历史数据(已按openTime DESC排序)
|
|
* @param string $type 查询类型 all=全部号码 special=仅特码
|
|
* @return int 遗漏期数
|
|
*/
|
|
private function calcOmitCount($num, $allHistory, $type = 'all')
|
|
{
|
|
foreach ($allHistory as $idx => $row) {
|
|
if ($type === 'special') {
|
|
if ((int)$row['num7'] === $num) {
|
|
return $idx;
|
|
}
|
|
} else {
|
|
for ($i = 1; $i <= 7; $i++) {
|
|
if ((int)$row['num' . $i] === $num) {
|
|
return $idx;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return count($allHistory);
|
|
}
|
|
|
|
/**
|
|
* 波色分析
|
|
*/
|
|
public function getColorWaveAnalysis($periods = 30, $type = 'all')
|
|
{
|
|
$num_model = new Num();
|
|
$colorMap = $num_model->column('color', 'num');
|
|
|
|
$history = $this->field('expect,num1,num2,num3,num4,num5,num6,num7')->order('openTime', 'desc')->limit($periods)->select();
|
|
if (empty($history)) return ['red' => 0, 'blue' => 0, 'green' => 0, 'red_pct' => 0, 'blue_pct' => 0, 'green_pct' => 0, 'details' => []];
|
|
|
|
$fields = ($type === 'special') ? ['num7'] : ['num1','num2','num3','num4','num5','num6','num7'];
|
|
$colors = ['红' => 0, '蓝' => 0, '绿' => 0];
|
|
$total = 0;
|
|
foreach ($history as $row) {
|
|
foreach ($fields as $f) {
|
|
$num = (int)$row[$f];
|
|
$color = $colorMap[$num] ?? '';
|
|
if (strpos($color, '红') !== false) { $colors['红']++; $total++; }
|
|
elseif (strpos($color, '蓝') !== false) { $colors['蓝']++; $total++; }
|
|
elseif (strpos($color, '绿') !== false) { $colors['绿']++; $total++; }
|
|
}
|
|
}
|
|
return [
|
|
'red' => $colors['红'], 'blue' => $colors['蓝'], 'green' => $colors['绿'],
|
|
'red_pct' => $total ? round($colors['红']/$total*100,1) : 0,
|
|
'blue_pct' => $total ? round($colors['蓝']/$total*100,1) : 0,
|
|
'green_pct' => $total ? round($colors['绿']/$total*100,1) : 0,
|
|
'total' => $total
|
|
];
|
|
}
|
|
|
|
/**
|
|
* 生肖分析
|
|
*/
|
|
public function getZodiacAnalysis($periods = 30, $type = 'all')
|
|
{
|
|
$num_model = new Num();
|
|
$animalMap = $num_model->column('animal', 'num');
|
|
$colorMap = $num_model->column('color', 'num');
|
|
|
|
$history = $this->field('expect,num1,num2,num3,num4,num5,num6,num7')->order('openTime', 'desc')->limit($periods)->select();
|
|
if (empty($history)) return ['list' => []];
|
|
|
|
$fields = ($type === 'special') ? ['num7'] : ['num1','num2','num3','num4','num5','num6','num7'];
|
|
$counts = [];
|
|
foreach ($history as $row) {
|
|
foreach ($fields as $f) {
|
|
$num = (int)$row[$f];
|
|
$animal = $animalMap[$num] ?? '未知';
|
|
if (!isset($counts[$animal])) $counts[$animal] = ['animal' => $animal, 'count' => 0, 'color' => $colorMap[$num] ?? '—'];
|
|
$counts[$animal]['count']++;
|
|
}
|
|
}
|
|
$list = array_values($counts);
|
|
usort($list, function ($a, $b) { return $b['count'] - $a['count']; });
|
|
$total = array_sum(array_column($list, 'count'));
|
|
foreach ($list as &$item) { $item['percent'] = $total ? round($item['count']/$total*100, 1) : 0; }
|
|
return ['list' => $list];
|
|
}
|
|
|
|
/**
|
|
* 奇偶分析
|
|
*/
|
|
public function getOddEvenAnalysis($periods = 30, $type = 'all')
|
|
{
|
|
$history = $this->field('expect,num1,num2,num3,num4,num5,num6,num7')->order('openTime', 'desc')->limit($periods)->select();
|
|
if (empty($history)) return ['odd' => 0, 'even' => 0, 'odd_pct' => 0, 'even_pct' => 0, 'per_period' => []];
|
|
|
|
$fields = ($type === 'special') ? ['num7'] : ['num1','num2','num3','num4','num5','num6','num7'];
|
|
$odd = 0; $even = 0; $perPeriod = [];
|
|
foreach ($history as $row) {
|
|
$p_odd = 0; $p_even = 0;
|
|
foreach ($fields as $f) {
|
|
$num = (int)$row[$f];
|
|
if ($num % 2 == 0) { $even++; $p_even++; } else { $odd++; $p_odd++; }
|
|
}
|
|
$perPeriod[] = ['expect' => $row['expect'], 'odd' => $p_odd, 'even' => $p_even];
|
|
}
|
|
$total = $odd + $even;
|
|
return [
|
|
'odd' => $odd, 'even' => $even,
|
|
'odd_pct' => $total ? round($odd/$total*100, 1) : 0,
|
|
'even_pct' => $total ? round($even/$total*100, 1) : 0,
|
|
'per_period' => $perPeriod
|
|
];
|
|
}
|
|
|
|
/**
|
|
* 大小分析(1-24为小,25-49为大)
|
|
*/
|
|
public function getBigSmallAnalysis($periods = 30, $type = 'all')
|
|
{
|
|
$history = $this->field('expect,num1,num2,num3,num4,num5,num6,num7')->order('openTime', 'desc')->limit($periods)->select();
|
|
if (empty($history)) return ['big' => 0, 'small' => 0, 'big_pct' => 0, 'small_pct' => 0, 'per_period' => []];
|
|
|
|
$fields = ($type === 'special') ? ['num7'] : ['num1','num2','num3','num4','num5','num6','num7'];
|
|
$big = 0; $small = 0; $perPeriod = [];
|
|
foreach ($history as $row) {
|
|
$p_big = 0; $p_small = 0;
|
|
foreach ($fields as $f) {
|
|
$num = (int)$row[$f];
|
|
if ($num >= 25) { $big++; $p_big++; } else { $small++; $p_small++; }
|
|
}
|
|
$perPeriod[] = ['expect' => $row['expect'], 'big' => $p_big, 'small' => $p_small];
|
|
}
|
|
$total = $big + $small;
|
|
return [
|
|
'big' => $big, 'small' => $small,
|
|
'big_pct' => $total ? round($big/$total*100, 1) : 0,
|
|
'small_pct' => $total ? round($small/$total*100, 1) : 0,
|
|
'per_period' => $perPeriod
|
|
];
|
|
}
|
|
|
|
/**
|
|
* 特码走势
|
|
*/
|
|
public function getSpecialTrend($periods = 30)
|
|
{
|
|
$history = $this->field('expect,num7')->order('openTime', 'asc')->limit($periods)->select();
|
|
if (empty($history)) return ['expects' => [], 'specials' => [], 'colors' => []];
|
|
|
|
$num_model = new Num();
|
|
$colorMap = $num_model->column('color', 'num');
|
|
|
|
$expects = []; $specials = []; $colors = [];
|
|
foreach ($history as $row) {
|
|
$expects[] = (string)$row['expect'];
|
|
$num = (int)$row['num7'];
|
|
$specials[] = $num;
|
|
$colors[] = $colorMap[$num] ?? '';
|
|
}
|
|
return ['expects' => $expects, 'specials' => $specials, 'colors' => $colors];
|
|
}
|
|
|
|
/**
|
|
* 连号分析
|
|
*/
|
|
public function getConsecutiveNumbers($periods = 30)
|
|
{
|
|
$history = $this->field('expect,num1,num2,num3,num4,num5,num6,num7')->order('openTime', 'desc')->limit($periods)->select();
|
|
if (empty($history)) return ['pairs' => [], 'triples' => []];
|
|
|
|
$pairCount = []; $tripleCount = [];
|
|
foreach ($history as $row) {
|
|
$nums = [];
|
|
for ($i = 1; $i <= 7; $i++) $nums[] = (int)$row['num' . $i];
|
|
sort($nums);
|
|
// 找连号对
|
|
for ($i = 0; $i < count($nums) - 1; $i++) {
|
|
if ($nums[$i + 1] - $nums[$i] === 1) {
|
|
$pair = $nums[$i] . '-' . $nums[$i + 1];
|
|
$pairCount[$pair] = isset($pairCount[$pair]) ? $pairCount[$pair] + 1 : 1;
|
|
}
|
|
}
|
|
// 找连号三连
|
|
for ($i = 0; $i < count($nums) - 2; $i++) {
|
|
if ($nums[$i + 1] - $nums[$i] === 1 && $nums[$i + 2] - $nums[$i + 1] === 1) {
|
|
$triple = $nums[$i] . '-' . $nums[$i + 1] . '-' . $nums[$i + 2];
|
|
$tripleCount[$triple] = isset($tripleCount[$triple]) ? $tripleCount[$triple] + 1 : 1;
|
|
}
|
|
}
|
|
}
|
|
arsort($pairCount); arsort($tripleCount);
|
|
return ['pairs' => $pairCount, 'triples' => $tripleCount];
|
|
}
|
|
|
|
/**
|
|
* 尾数分析
|
|
*/
|
|
public function getTailNumbers($periods = 30, $type = 'all')
|
|
{
|
|
$num_model = new Num();
|
|
$colorMap = $num_model->column('color', 'num');
|
|
$animalMap = $num_model->column('animal', 'num');
|
|
|
|
$history = $this->field('expect,num1,num2,num3,num4,num5,num6,num7')->order('openTime', 'desc')->limit($periods)->select();
|
|
if (empty($history)) return ['tails' => [], 'all' => []];
|
|
|
|
$fields = ($type === 'special') ? ['num7'] : ['num1','num2','num3','num4','num5','num6','num7'];
|
|
$tailCount = array_fill(0, 10, 0);
|
|
$all = [];
|
|
foreach ($history as $row) {
|
|
foreach ($fields as $f) {
|
|
$num = (int)$row[$f];
|
|
$tail = $num % 10;
|
|
$tailCount[$tail]++;
|
|
}
|
|
}
|
|
$total = array_sum($tailCount);
|
|
for ($t = 0; $t <= 9; $t++) {
|
|
$all[] = ['tail' => $t, 'count' => $tailCount[$t], 'percent' => $total ? round($tailCount[$t]/$total*100, 1) : 0];
|
|
}
|
|
usort($all, function ($a, $b) { return $b['count'] - $a['count']; });
|
|
return ['all' => $all];
|
|
}
|
|
|
|
/**
|
|
* 综合统计面板
|
|
*/
|
|
public function getDashboardData($periods = 30, $type = 'all')
|
|
{
|
|
return [
|
|
'hotcold' => $this->getHotColdNumbers($periods, $type),
|
|
'colorwave' => $this->getColorWaveAnalysis($periods, $type),
|
|
'zodiac' => $this->getZodiacAnalysis($periods, $type),
|
|
'oddeven' => $this->getOddEvenAnalysis($periods, $type),
|
|
'bigsmall' => $this->getBigSmallAnalysis($periods, $type),
|
|
'special' => $this->getSpecialTrend($periods),
|
|
'tailnumbers' => $this->getTailNumbers($periods, $type)
|
|
];
|
|
}
|
|
|
|
|
|
}
|