📦 数据库 - lottery_issues(期次表) - lottery_tickets(购票记录表) - lottery_pool_logs(奖池流水表,透明展示) 🔩 核心组件 - LotteryIssue / LotteryTicket / LotteryPoolLog 完整 Model - LotteryService:购票/机选/开奖/奖池派发/滚存/超级期预热/公屏广播 - LotteryController:current/buy/quickPick/history/my 五个接口 - DrawLotteryJob(每日定时开奖)/ OpenLotteryIssueJob(初始化首期) 💰 货币日志 - CurrencySource 新增 LOTTERY_BUY / LOTTERY_WIN - 所有金币变动均通过 UserCurrencyService::change() 记录流水 🗓️ 调度器 - 每分钟检查停售/开奖时机 - 每日 18:00 超级期预热广播 🔧 配置 - GameConfigSeeder 追加 lottery 默认配置(默认关闭) - /games/enabled 接口追加 lottery 开关状态 - 新增 /lottery/* 路由组(auth 保护)
165 lines
4.6 KiB
PHP
165 lines
4.6 KiB
PHP
<?php
|
||
|
||
/**
|
||
* 文件功能:双色球期次 Model
|
||
*
|
||
* 管理每期彩票的生命周期:open(购票中)→ closed(停售)→ settled(已开奖)。
|
||
* 提供静态查询方法、奖级判定辅助及号码排序工具。
|
||
*
|
||
* @author ChatRoom Laravel
|
||
*
|
||
* @version 1.0.0
|
||
*/
|
||
|
||
namespace App\Models;
|
||
|
||
use Illuminate\Database\Eloquent\Model;
|
||
use Illuminate\Database\Eloquent\Relations\HasMany;
|
||
|
||
class LotteryIssue extends Model
|
||
{
|
||
protected $fillable = [
|
||
'issue_no',
|
||
'status',
|
||
'red1', 'red2', 'red3', 'blue',
|
||
'pool_amount',
|
||
'carry_amount',
|
||
'is_super_issue',
|
||
'no_winner_streak',
|
||
'total_tickets',
|
||
'payout_amount',
|
||
'sell_closes_at',
|
||
'draw_at',
|
||
];
|
||
|
||
/**
|
||
* 字段类型转换。
|
||
*/
|
||
protected function casts(): array
|
||
{
|
||
return [
|
||
'is_super_issue' => 'boolean',
|
||
'pool_amount' => 'integer',
|
||
'carry_amount' => 'integer',
|
||
'payout_amount' => 'integer',
|
||
'total_tickets' => 'integer',
|
||
'sell_closes_at' => 'datetime',
|
||
'draw_at' => 'datetime',
|
||
];
|
||
}
|
||
|
||
// ─── 关联 ──────────────────────────────────────────────────────────
|
||
|
||
/**
|
||
* 本期所有购票记录。
|
||
*/
|
||
public function tickets(): HasMany
|
||
{
|
||
return $this->hasMany(LotteryTicket::class, 'issue_id');
|
||
}
|
||
|
||
/**
|
||
* 本期奖池流水。
|
||
*/
|
||
public function poolLogs(): HasMany
|
||
{
|
||
return $this->hasMany(LotteryPoolLog::class, 'issue_id');
|
||
}
|
||
|
||
// ─── 静态查询 ──────────────────────────────────────────────────────
|
||
|
||
/**
|
||
* 获取当前正在购票的期次(status=open)。
|
||
*/
|
||
public static function currentIssue(): ?static
|
||
{
|
||
return static::query()->where('status', 'open')->latest()->first();
|
||
}
|
||
|
||
/**
|
||
* 获取最新一期(不论状态)。
|
||
*/
|
||
public static function latestIssue(): ?static
|
||
{
|
||
return static::query()->latest()->first();
|
||
}
|
||
|
||
/**
|
||
* 生成下一期的期号(格式:年份 + 三位序号,如 2026001)。
|
||
*/
|
||
public static function nextIssueNo(): string
|
||
{
|
||
$year = now()->year;
|
||
$last = static::query()
|
||
->whereYear('created_at', $year)
|
||
->latest()
|
||
->first();
|
||
|
||
$seq = $last ? ((int) substr($last->issue_no, -3)) + 1 : 1;
|
||
|
||
return $year.str_pad($seq, 3, '0', STR_PAD_LEFT);
|
||
}
|
||
|
||
// ─── 业务方法 ──────────────────────────────────────────────────────
|
||
|
||
/**
|
||
* 判断当前期次是否仍在售票中。
|
||
*/
|
||
public function isOpen(): bool
|
||
{
|
||
return $this->status === 'open' && now()->lt($this->sell_closes_at);
|
||
}
|
||
|
||
/**
|
||
* 距离开奖剩余秒数。
|
||
*/
|
||
public function secondsUntilDraw(): int
|
||
{
|
||
if (! $this->draw_at) {
|
||
return 0;
|
||
}
|
||
|
||
return max(0, (int) now()->diffInSeconds($this->draw_at, false));
|
||
}
|
||
|
||
/**
|
||
* 判断给定号码是否与开奖号码匹配并返回奖级(0=未中)。
|
||
*
|
||
* @param int $r1 用户红球1(已排序)
|
||
* @param int $r2 用户红球2
|
||
* @param int $r3 用户红球3
|
||
* @param int $b 用户蓝球
|
||
*/
|
||
public function calcPrizeLevel(int $r1, int $r2, int $r3, int $b): int
|
||
{
|
||
$userReds = [$r1, $r2, $r3];
|
||
$drawReds = [$this->red1, $this->red2, $this->red3];
|
||
$redMatches = count(array_intersect($userReds, $drawReds));
|
||
$blueMatch = ($b === $this->blue);
|
||
|
||
return match (true) {
|
||
$redMatches === 3 && $blueMatch => 1, // 一等奖
|
||
$redMatches === 3 && ! $blueMatch => 2, // 二等奖
|
||
$redMatches === 2 && $blueMatch => 3, // 三等奖
|
||
$redMatches === 2 && ! $blueMatch => 4, // 四等奖
|
||
$redMatches === 1 && $blueMatch => 5, // 五等奖
|
||
default => 0, // 未中奖
|
||
};
|
||
}
|
||
|
||
/**
|
||
* 返回奖级的中文标签。
|
||
*/
|
||
public static function prizeLevelLabel(int $level): string
|
||
{
|
||
return match ($level) {
|
||
1 => '一等奖',
|
||
2 => '二等奖',
|
||
3 => '三等奖',
|
||
4 => '四等奖',
|
||
5 => '五等奖',
|
||
default => '未中奖',
|
||
};
|
||
}
|
||
}
|