简介:《众神彩票源码》是一套面向彩票行业运营者与开发者的完整系统开发框架,基于2018年前后可用的技术栈,支持按业务需求修改数据与功能,适合学习彩票平台架构、二次开发或既有业务改造。资源包为rar压缩,共7919个文件、约217.92MB,其中1451个PHP脚本承载后端业务逻辑,716个HTML页面与309个JS文件构成前端交互层,另有3376个PNG、285个JPG图片及698个MP3、410个M4A音频等素材,并含CSS、数据库脚本、配置与字体文件,覆盖页面、数据与多媒体资源。内容涉及数据库设计、接口通信、支付集成、安全防护、统计分析和服务器部署等环节,可帮助读者梳理从用户界面到后台逻辑的完整链路,理解投注、开奖、充值提现等模块的实现思路,并关注彩票业务的合法合规要求。已有4573人学习下载,适合具备一定Web开发基础、希望研究完整彩票系统结构的开发者参考。
1. 整站源码能不能跑起来,先判断它把你带进哪条链路
拿到这份众神彩票源码,别急着翻控制器。我第一件事是确认三样东西:根目录有没有.env.example、sql/下有没有初始化脚本、composer.json里的依赖版本在当前机器上装不装得上。三样齐了,它就是个能落地的整站工程;缺一样,多半只是个演示壳子。这类整站源码通常把前后端、定时开奖、实时推送、后台管理压在一个仓库里,适合想完整走一遍「接口—定时任务—长连接—后台权限」链路的开发者,不适合拿来当成品直接上线。真正决定它能不能跑起来的,往往不是代码量,而是配置项和定时任务这两处最容易翻车的地方。
2. 目录结构与本地起服务:从解压到第一个页面出来
这套工程的骨架是「后端多应用 + 前端独立工程 + 命令行任务」的三段式,后端负责接口和数据,前端负责展示,命令行负责开奖这类不需要用户触发的动作。我第一次拆的时候先画了一张调用关系:请求从public/index.php进来,按route/分发到api或admin应用,业务逻辑沉到common/service,定时任务则走think命令行直接调服务层,不经过 HTTP。这条链路理顺了,后面看代码就不会迷路。
2.1 目录结构长什么样
解压之后先看根目录,结构大致是下面这样。我习惯把注释直接写在树里,比另开一份文档更容易对照代码翻。
lottery-root/ ├── app/ # 后端应用目录(多应用模式) │ ├── api/ # 对外接口应用(H5 / 小程序共用) │ │ ├── controller/ # 期号、结果、记录等控制器 │ │ └── middleware/ # 签名校验、限流、跨域 │ ├── admin/ # 后台管理应用 │ │ ├── controller/ # 期号管理、开奖管理、账号管理 │ │ └── middleware/ # 登录态校验、RBAC 权限 │ ├── common/ # 公共层,被 api / admin / command 共用 │ │ ├── model/ # 数据模型 │ │ ├── service/ # 期号服务、开奖服务、归档服务 │ │ └── library/ # 随机数引擎、字典、异常类 │ └── command/ # 自定义命令:开奖扫描、数据归档 ├── config/ # 数据库、缓存、应用配置 ├── public/ # Web 入口与静态资源 │ ├── index.php # 唯一入口文件 │ └── static/ ├── route/ # 路由定义,api 与 admin 分开 ├── runtime/ # 运行时缓存与日志,必须可写 ├── vendor/ # Composer 依赖 ├── web/ # 前端工程 │ ├── src/ │ │ ├── api/ # 接口封装层 │ │ ├── views/ # 页面 │ │ └── store/ # 状态管理 │ └── package.json ├── sql/ # 初始化脚本 ├── .env.example # 环境变量样例 └── composer.json值得多说两句的是common/这一层。很多整站源码把业务逻辑直接写在控制器里,改一个开奖规则要动三个地方;这份源码把「取期号、算结果、写结果、发通知」抽到了service,控制器只做参数校验和返回,命令行也只调服务层。这种分层对二次开发很友好——你要换开奖算法,只动library里的引擎类,接口和后台都不用碰。我一般接手陌生工程时,会先看service目录有几个文件、每个文件多少行,基本就能判断这项目是「能改」还是「只能跑」。
app/command/是容易被忽略的目录,但它往往是整套系统的心跳。开奖扫描、超时期号置为失效、历史数据归档,这些动作都挂在这里,由 crontab 每分钟唤起一次。少了这个目录,页面能打开,但期号永远停在待开奖状态。
runtime/的权限是新手最容易踩的坑。Linux 下用www-data跑 PHP-FPM,但这个目录是 root 解压出来的,属主不对,日志写不进去,表现就是「接口 500 但看不到任何报错」。我第一次部署时在这个点上耗了半小时,后来养成习惯:解压完先chown -R www-data:www-data runtime,再干别的。
2.2 环境准备与启动步骤
环境这块,这套工程对版本比较敏感。PHP 建议 8.0 以上,MySQL 8.0,Redis 6 以上,Node 16 以上。低于这些版本不是装不上,而是依赖里某个包的语法会直接报 parse error,报错信息还指不到根因,属于典型的「版本玄学」。
# 1. 安装后端依赖(composer.lock 存在时用 install,别用 update) composer install --no-dev # 2. 复制环境变量样例,按实际情况改数据库和 Redis cp .env.example .env # 3. 导入初始化 SQL,库名与 .env 里的 DB_NAME 保持一致 mysql -uroot -p lottery < sql/install.sql # 4. 开发模式起后端,监听 8000 php think run --host 0.0.0.0 --port 8000 # 5. 另开一个终端起前端 cd web npm install npm run dev上面五步里,第 1 步用install而不是update是有讲究的:install严格按composer.lock装,版本和作者本地一致;update会按composer.json重新解析,容易把某个小版本升上去,然后出现「本地能跑、服务器跑不了」的经典问题。没有 lock 文件才退而求其次用update。
第 4 步的think run是开发服务器,单进程、无并发能力,只适合本机调页面。生产环境必须换成 Nginx + PHP-FPM,否则定时任务和 WebSocket 一上来就会互相抢进程。这个区别很多人第一次部署时会忽略,以为php think run也能扛住线上流量。
前端npm run dev起的是 Vite 或 Webpack Dev Server,默认会代理/api到后端 8000 端口。代理配置通常在web/vite.config.js或web/vue.config.js里,如果页面能打开但接口全 404,先看这里,而不是去翻后端路由——我见过太多人在后端路由文件里找半天,最后发现是前端代理写错了端口。
2.3 配置项与第一个能访问的页面
.env里的配置项不多,但每一项都影响启动。下面这张表是我实际改过的字段,照着填基本不会出问题。
| 配置项 | 作用 | 常见取值 | 改错后的表现 |
|---|---|---|---|
APP_DEBUG | 是否输出详细错误 | 本地true,线上false | 线上开着会暴露路径和 SQL |
DB_HOST/DB_PORT | 数据库地址端口 | 127.0.0.1/3306 | 页面白屏,日志报连接超时 |
DB_NAME | 库名 | 与install.sql一致 | 提示表不存在 |
REDIS_HOST/REDIS_PORT | 缓存与锁 | 127.0.0.1/6379 | 开奖锁失效,可能重复开奖 |
APP_URL | 后端对外地址 | 带协议和端口 | 前端请求拼出的地址错位 |
WS_PORT | WebSocket 端口 | 默认2346 | 页面能开但结果不刷新 |
配置改完,第一个该访问的地址是http://127.0.0.1:8000/api/issue/latest。这个接口不依赖登录态,返回当前期号和状态,能打通就说明「入口文件—路由—数据库」这条主链路是通的。如果它返回 404,问题在 Nginx 伪静态或入口文件路径;返回 500,去看runtime/log下当天的日志;返回空数据,说明 SQL 没导进去或者期号表是空的,去后台或直接写一条测试数据。
后台入口一般是http://127.0.0.1:8000/admin,默认账号密码写在sql/install.sql的注释里。第一次登录后建议立刻改密码,因为这类整站源码的默认账号在圈子里是公开的,不改等于门没锁。改密码的逻辑用的是password_hash,不是 md5,这一点后面讲鉴权时还会提。
3. 核心模块拆解:一期结果从生成到推到前端的完整链路
这套系统真正有技术含量的部分集中在三块:号码怎么生成、什么时候触发开奖、结果怎么在秒级内到达前端。三块串起来就是一条「定时扫描 → 生成结果 → 落库 → 广播」的链路,任何一环断了,用户看到的就是页面卡在旧期号上不动。我按执行顺序拆,方便你对照代码找位置。
3.1 号码生成:为什么不能用 rand()
生成逻辑在app/common/library/DrawEngine.php里。很多人第一反应是array_rand或者mt_rand,能出结果,但分布均匀性和可追溯性都差一截。这份源码用的是random_int,走系统的 CSPRNG,慢一点,但结果不容易被预测,也方便在日志里留种子做追溯。
<?php namespace app\common\library; class DrawEngine { /** * 从指定区间取不重复号码 * @param int $min 区间下限 * @param int $max 区间上限 * @param int $count 取几个 * @return array 升序排列的结果 */ public static function generate(int $min = 1, int $max = 10, int $count = 5): array { // 候选池,range 生成的是连续整数数组 $pool = range($min, $max); // 取的数量不能超过池子容量,否则死循环 if ($count > count($pool)) { throw new \InvalidArgumentException('取样数量超过区间容量'); } $result = []; for ($i = 0; $i < $count; $i++) { // random_int 走系统熵源,比 rand / mt_rand 更均匀 $idx = random_int(0, count($pool) - 1); $result[] = $pool[$idx]; // 取走一个就删一个,保证不重复 unset($pool[$idx]); // 重建连续索引,否则下一轮的随机下标会落到空洞上 $pool = array_values($pool); } sort($result); // 统一升序,前端不用再排一次 return $result; } }几个参数值得说清楚。$min和$max决定了号码区间,整站源码里一般从配置读,不写死在代码里,因为不同玩法区间不一样;$count是每期取几个号,和区间一起构成「几选几」的规则。unset之后再array_values是这段代码的关键,少了这一句,下一轮random_int(0, count($pool) - 1)取到的下标可能指向一个已经被删掉的空洞,PHP 会返回 null,结果数组里就混进空值——这种 bug 不会报错,只会让某一期结果少一个号,属于最难查的那类。
我一般还会在生成之后追加一步签名,把期号、结果、时间戳拼起来做一次 hash 存进lc_draw_log.seed。这不是为了防谁,而是出问题时能对得上「这一期到底是不是系统生成的」,相当于给开奖过程留了个黑匣子。
3.2 定时开奖:crontab 与 Workerman Timer 怎么选
触发方式有两种常见做法:crontab 每分钟扫一次数据库,或者用 Workerman 的 Timer 常驻进程。这份源码用的是前者,原因是简单、可观测、挂了不影响 Web 服务。缺点是精度只有分钟级,如果你的期号间隔精确到秒,就得换成常驻进程方案。
# 每分钟唤起一次开奖命令,日志重定向到 runtime 下便于排查 * * * * * /usr/bin/php /www/lottery/think draw:run >> /www/lottery/runtime/draw.log 2>&1命令行里写绝对路径是血泪经验。crontab 执行时的环境变量和登录 shell 不一样,php可能指向系统自带的 7.4 而不是你装的 8.0,表现就是「手动跑没问题,定时跑就报语法错误」。用/usr/bin/php或者which php查出来的绝对路径,能省掉大量排查时间。
命令本身的实现是这样的:
<?php namespace app\command; use think\console\Command; use think\console\Input; use think\console\Output; use app\common\service\IssueService; class DrawRun extends Command { protected function configure() { $this->setName('draw:run')->setDescription('扫描到期期号并开奖'); } protected function execute(Input $input, Output $output) { $service = new IssueService(); // Redis 分布式锁,防止多机部署时重复开奖 $lockKey = 'lottery:draw:lock'; if (!cache($lockKey)) { cache($lockKey, 1, 55); // 55 秒过期,留 5 秒给下一轮 } else { $output->writeln('已有开奖进程在执行,跳过本轮'); return; } try { // 单次最多处理 50 期,避免积压时一次性跑太久 $dueList = $service->findDueIssues(50); foreach ($dueList as $issue) { $service->drawOne($issue); $output->writeln("开奖完成:{$issue['issue_no']}"); } } finally { cache($lockKey, null); // 无论成功失败都释放锁 } } }findDueIssues的查询条件是status = 0 AND draw_at <= 当前时间,配合lc_issue上的idx_status_draw联合索引,扫起来很快。drawOne里做了三件事:调DrawEngine生成结果、更新期号状态、写开奖日志,三步必须在一个事务里,否则会出现「状态改了但结果没写」的脏数据。锁的过期时间设 55 秒而不是 60 秒,是给下一分钟的 cron 留出余量,避免两轮任务在边界上撞车。
3.3 WebSocket 推送:结果怎么在一秒内到前端
前端拿结果有两条路:轮询和长连接。轮询实现简单,但期号一多,请求量就是用户数乘以频率,服务器扛不住;长连接省资源,代价是要多维护一个进程。这份源码用 Workerman 起了一个独立的 WebSocket 服务,端口默认 2346。
<?php use Workerman\Worker; use Workerman\Timer; $ws = new Worker('websocket://0.0.0.0:2346'); $ws->count = 2; // 两个进程,兼顾并发和内存 $ws->onWorkerStart = function ($worker) { // 每 2 秒广播一次最新期号状态,客户端不用自己算时机 Timer::add(2, function () use ($worker) { $payload = json_encode([ 'type' => 'issue_state', 'data' => app\common\service\IssueService::latestState(), ], JSON_UNESCAPED_UNICODE); foreach ($worker->connections as $conn) { $conn->send($payload); } }); }; Worker::runAll();这段代码有三个参数直接影响体验。$ws->count是进程数,设 1 时所有连接挤在一个进程里,连接数上千就会开始卡;设 2 到 4 比较平衡,再多内存吃不消。Timer::add的 2 秒是广播间隔,太短浪费带宽,太长用户会觉得「开奖了但页面没动」。latestState()里一定要带缓存,比如把最新期号状态放 Redis,两秒一次全表查会把数据库打满。
前端这边的连接逻辑同样要写稳,断线重连是必须的:
let ws = null; function connect() { ws = new WebSocket(`ws://${location.hostname}:2346`); ws.onmessage = (evt) => { const msg = JSON.parse(evt.data); if (msg.type === 'issue_state') { // 期望结构:{ issue_no, status, numbers, next_time } this.issue = msg.data; } }; ws.onclose = () => { // 断线后 3 秒重连,避免页面停在旧期号上 setTimeout(connect, 3000); }; } connect();线上环境要把ws://换成wss://,否则 HTTPS 页面连明文 WebSocket 会被浏览器直接拦掉,控制台只留一行 mixed content 警告,不仔细看根本发现不了。这一步通常靠 Nginx 反代到 2346 端口,配置写在location /ws/里,把Upgrade和Connection两个头透传过去。
4. 后台管理与数据层:期号、结果、权限三块怎么落地
后台这块看起来平平无奇,但它是整套系统里改动最频繁的部分——运营要加字段、要导数据、要改展示,全在这里动。所以表结构和鉴权设计得留够扩展空间,别一上来就写死。
4.1 三张核心表的设计取舍
整站源码里表不少,但真正撑起业务的是三张:期号表、开奖日志表、后台账号表。我按实际建表语句讲,重点看索引和字段类型。
CREATE TABLE `lc_issue` ( `id` INT UNSIGNED NOT NULL AUTO_INCREMENT, `issue_no` VARCHAR(20) NOT NULL COMMENT '期号,如 20240501001', `start_at` INT UNSIGNED NOT NULL COMMENT '本期开始时间戳', `draw_at` INT UNSIGNED NOT NULL COMMENT '计划开奖时间戳', `status` TINYINT NOT NULL DEFAULT 0 COMMENT '0待开奖 1已开奖 2已失效', `numbers` VARCHAR(64) DEFAULT NULL COMMENT '开奖号码,逗号分隔', `created_at` INT UNSIGNED NOT NULL DEFAULT 0, PRIMARY KEY (`id`), UNIQUE KEY `uk_issue_no` (`issue_no`), KEY `idx_status_draw` (`status`, `draw_at`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='期号表'; CREATE TABLE `lc_draw_log` ( `id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT, `issue_no` VARCHAR(20) NOT NULL, `numbers` VARCHAR(64) NOT NULL, `seed` VARCHAR(64) DEFAULT NULL COMMENT '签名,便于追溯', `drawn_at` INT UNSIGNED NOT NULL, PRIMARY KEY (`id`), UNIQUE KEY `uk_issue_no` (`issue_no`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='开奖结果表';idx_status_draw这个联合索引是整套查询的性能关键。开奖命令每分钟都要执行WHERE status = 0 AND draw_at <= ?,没有这个索引就是全表扫,期号表一旦过百万行,单次扫描能到秒级,cron 就会开始堆积。字段顺序也有讲究,status放前面是因为它的区分度高(待开奖的永远只是少数),先过滤掉大部分数据再比时间,比反过来快得多。
issue_no用VARCHAR(20)而不是自增主键对外,是因为期号往往带日期前缀,运营和用户都靠它对齐,用数字 id 反而不好沟通。唯一索引保证同一期号不会被插两次,这是防止重复开奖的第二道闸。
lc_draw_log单独建表而不是塞进lc_issue,是为了归档方便。开奖日志只增不改,量大了可以整表冷备;期号表要频繁更新状态,混在一起会让表越来越大,更新越来越慢。这种「冷热分离」的思路在整站项目里很常见,新手容易图省事合成一张表,等数据涨起来再拆就麻烦了。
后台账号表相对简单,重点是密码字段:
CREATE TABLE `lc_admin_user` ( `id` INT UNSIGNED NOT NULL AUTO_INCREMENT, `username` VARCHAR(32) NOT NULL, `password` VARCHAR(255) NOT NULL COMMENT 'password_hash 结果', `role_id` INT UNSIGNED NOT NULL DEFAULT 0, `status` TINYINT NOT NULL DEFAULT 1, PRIMARY KEY (`id`), UNIQUE KEY `uk_username` (`username`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='后台账号表';password留 255 位是因为password_hash默认输出 60 位,但算法升级后可能变长,留够空间以后不用改表。role_id关联角色表,权限判断走角色而不是直接挂账号,这样加一个人只要给角色,不用一条条配权限。
4.2 后台鉴权与接口分层
后台的中间件只做一件事:验身份。权限判断放到路由层,职责分开,改起来不互相牵连。
<?php namespace app\admin\middleware; class Auth { public function handle($request, \Closure $next) { $token = $request->header('Authorization', ''); // 去掉 Bearer 前缀再验签 $token = str_replace('Bearer ', '', $token); $payload = $token ? jwt_verify($token) : null; if (!$payload) { return json(['code' => 401, 'msg' => '登录已失效'], 401); } // 挂到请求对象上,控制器直接取,不用重复解析 $request->adminId = $payload['uid']; $request->roleId = $payload['role']; return $next($request); } }中间件里不做权限判断,是因为同一套接口可能被不同角色调用,把权限写死在中间件会让逻辑越来越绕。更常见的做法是在路由定义上加权限节点,比如->middleware(\app\admin\middleware\Auth::class)->allow('issue.edit'),由框架统一校验。
登录接口本身要注意两点。一是密码比对用password_verify,不要自己写 md5 比对,前者自带盐值和算法版本,后者在彩虹表面前基本等于明文。二是 token 的过期时间别设太长,后台这种低频操作,两小时足够,过期了重新登一次的成本远低于 token 泄露的风险。
接口返回结构建议统一成{code, msg, data}三段式,前端封装一个拦截器就能统一处理错误提示。我见过一些整站源码每个接口返回格式都不一样,前端要写十几个判断分支,二次开发时改一个字段能牵出一串问题。
5. 避坑与排查:启动、开奖、推送三类高频翻车现场
这套系统跑起来不难,难的是跑稳。下面五条是我在实际部署和调试里反复遇到的,每条都按「现象—原因—解决」写,照着对照基本能定位大部分问题。
5.1 环境与启动类问题
现象:页面能打开,但所有接口返回 404。原因通常是 Nginx 没配伪静态,请求/api/issue/latest时被当成真实文件去找,找不到就 404。整站源码的入口只有一个public/index.php,所有路径都得重写到它。 解决:在站点配置里加location / { try_files $uri $uri/ /index.php?$query_string; },重载 Nginx 后再试。如果用的是 Apache,检查.htaccess有没有被AllowOverride None禁掉。
现象:命令行手动执行php think draw:run正常,crontab 跑就报错。原因是 cron 的环境变量极简,php命令可能指向系统自带的旧版本,工作目录也不是项目根目录。 解决:命令里全用绝对路径,/usr/bin/php /www/lottery/think draw:run,并在命令前加cd /www/lottery &&确保工作目录正确。排查时先看runtime/draw.log里有没有输出,没输出说明命令根本没执行,去看crontab -l和系统邮件日志。
5.2 开奖与数据类问题
现象:期号到了开奖时间,状态一直是待开奖。原因有三个常见方向:cron 没跑起来、draw_at存的是字符串而不是时间戳导致比较失败、或者runtime目录不可写让命令中途异常退出。 解决:先在命令行手动跑一次看报错;再查lc_issue里draw_at的字段类型,必须是INT且存的是秒级时间戳,存成DATETIME字符串时<=比较会出意外结果;最后确认目录权限。
现象:同一期出现两条开奖记录。原因是多台机器同时跑 cron,或者单机上 cron 和手动执行撞在一起,锁没起作用。 解决:确认锁走的是 Redis 而不是文件缓存,文件缓存跨机器不共享;锁的 key 要带业务前缀避免和其他缓存冲突;释放锁放在finally里,异常时也要放,否则锁会一直占着直到过期。单机部署也别省这一步,手动执行和 cron 撞车的事故我见过不止一次。
5.3 部署与推送类问题
现象:HTTPS 页面下 WebSocket 一直连不上,控制台只有一行警告。原因是页面是https://,WebSocket 却写的ws://,浏览器按混合内容策略直接拦截。 解决:改用wss://,并在 Nginx 里加一段反代:
# Nginx 反代 WebSocket,注意两个 Upgrade 头必须透传 location /ws/ { proxy_pass http://127.0.0.1:2346; proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection "upgrade"; proxy_read_timeout 600s; }proxy_read_timeout默认 60 秒,长连接空闲一会儿就被断开,表现是「页面放几分钟就不刷新了」。调到 600 秒以上,再配合前端重连,基本不会再断。
现象:前端显示的开奖时间和服务器差 8 小时。原因是数据库存的是 UTC 时间戳,前端按本地时区格式化,中间少了一次转换;或者 PHP 的date_default_timezone_set没设,默认走 UTC。 解决:统一口径——存储一律用秒级时间戳(与时区无关),展示时由前端按Asia/Shanghai格式化。别在数据库里存格式化好的时间字符串,跨时区部署时这种设计会让人抓狂。
6. 二次开发:把开奖节奏和展示层改成自己的形态
改这套源码,我建议从「开奖策略」和「推送方式」两个点切入,这两处抽象程度够,改动影响面可控。
先说开奖策略。原代码把生成逻辑写死在DrawEngine::generate里,区间和数量从配置读。如果你的规则不是「几选几」,而是带权重或者分段,可以抽一个接口出来:
<?php namespace app\common\library\strategy; interface DrawStrategy { // 输入期号,输出号码数组,具体规则由实现类决定 public function draw(string $issueNo): array; } // 等概率实现 class RandomStrategy implements DrawStrategy { public function draw(string $issueNo): array { return DrawEngine::generate(1, 10, 5); } }然后在配置里加一个draw_strategy键,服务层按配置实例化对应类。这样以后加新规则只要新增一个实现类,不用动调用方,也不用担心改坏原有逻辑。这种改造方式我一般称为「留后悔药」——上线后发现规则不对,改配置就能切回去。
再说推送。定时广播适合期号间隔固定的场景,如果你的节奏不规律,可以改成事件驱动:开奖服务写库成功后往 Redis 发一条消息,WebSocket 进程订阅这个频道,收到就推,没消息就不推。表里可以对照一下两种方案的差别:
| 方案 | 触发时机 | 延迟 | 实现成本 | 适用场景 |
|---|---|---|---|---|
| 定时广播 | 固定间隔 | 最长等于间隔 | 低 | 期号节奏固定 |
| 事件驱动 | 写库成功后 | 毫秒级 | 中,需要消息通道 | 节奏不规律 |
改完之后怎么验证?我的习惯是造一批「过去时间」的期号,把draw_at设成当前时间往前推几分钟,跑一次draw:run,看三个地方:lc_issue状态有没有变、lc_draw_log有没有新记录、前端有没有在几秒内刷新。三处都对了才算改通,只看数据库不看前端,很容易漏掉推送那一环。
最后提醒一句,改表结构之前先把sql/install.sql同步改掉。我早期接手项目时只顾着在数据库里ALTER,忘了更新初始化脚本,换台机器部署直接跑不起来,回头补脚本又对不齐字段顺序,白白折腾一下午。从那以后我每次动表都强制走一遍「改脚本 → 重建库 → 重新导入 → 再跑一遍开奖」,确认新环境能起来才继续往下写。希望帮到你。
本文还有配套的精品资源,点击获取