Codeigniter 3中如何后台运行函数处理Telegram Bot批量推送
CodeIgniter 3 下Telegram Bot非阻塞批量推送实现方案
核心逻辑是把批量推送流程和处理用户请求的Web进程完全剥离,Web端只负责任务触发、即时返回响应,实际推送全部由离线后台进程执行,从根源解决请求阻塞、重复发消息的问题。
方案一:CLI命令+后台进程触发(零扩展依赖,稳定性最高,适合绝大多数场景)
CI3原生支持CLI模式访问控制器,不需要额外安装组件即可实现:
- 第一步:创建CLI专属推送控制器,仅允许命令行环境访问,避免外部恶意调用
新建application/controllers/Telegram_push.php,核心代码如下:<?php defined('BASEPATH') OR exit('No direct script access allowed'); class Telegram_push extends CI_Controller { public function __construct() { parent::__construct(); // 拦截非CLI访问 if (php_sapi_name() !== 'cli') exit('No direct script access allowed'); $this->load->model('telegram_user_model'); $this->load->library('telegram_api'); // 取消PHP运行时长限制 set_time_limit(0); } /** * 全量推送入口 * @param string $message 要推送的内容,urlencode格式传参避免特殊字符问题 */ public function send_all($message = '') { $message = urldecode($message); if (empty($message)) exit("Error: message content is empty\n"); // 加进程锁,彻底解决重复推送问题:同一时间只允许一个推送进程运行 $lockPath = APPPATH . 'cache/telegram_push.lock'; if (file_exists($lockPath)) { $oldPid = (int)file_get_contents($lockPath); // 校验旧进程是否真的在运行,防止异常退出导致死锁 if (file_exists("/proc/{$oldPid}")) exit("Error: push task already running, pid:{$oldPid}\n"); unlink($lockPath); } file_put_contents($lockPath, getmypid()); // 分批拉取用户,避免一次性查数千条数据占满内存 $lastUserId = 0; $pageSize = 100; while (true) { $userList = $this->telegram_user_model->get_users_after_id($lastUserId, $pageSize); if (empty($userList)) break; foreach ($userList as $user) { $lastUserId = $user->id; // 单次curl请求设置3秒超时,避免网络波动卡住整个进程 $this->telegram_api->sendMessage($user->chat_id, $message, 3); // 匹配Telegram API限流规则:每秒最多30次请求,间隔35ms避免触发封禁 usleep(35000); } } // 任务执行完成删除锁文件 unlink($lockPath); echo "Success: all message pushed\n"; } } - 第二步:Web端触发推送时,不直接执行推送逻辑,只负责唤起后台CLI进程,触发后立刻返回结果
在管理员触发推送的接口里加入如下代码:// 接收要推送的内容,做必要的安全过滤 $pushContent = urlencode($this->input->post('content', true)); // 拼接CLI启动命令,末尾加&表示后台运行,不等待执行结果 $command = PHP_BINDIR . "/php " . FCPATH . "index.php telegram_push send_all '{$pushContent}' > /dev/null 2>&1 &"; // 执行命令后立刻释放资源,不等待进程返回 pclose(popen($command, 'r')); // 直接给前端返回结果,用户不需要等待推送完成 return $this->output->set_content_type('application/json') ->set_output(json_encode(['code'=>0, 'msg'=>'推送任务已在后台启动'])); - 第三步:补充幂等校验,进一步避免重复发消息
可以在用户订阅表加last_push_hash字段,每次推送前给内容生成唯一hash值,调用发送接口前先判断该用户是否已经接收过同hash的内容,发送成功后更新hash记录,就算进程意外重启、重复触发任务也不会发重复消息。
方案二:Redis轻量队列(适合推送任务频繁、需要任务状态追踪的场景)
如果日常推送需求多、需要支持多类推送任务,可以用Redis做简单队列替代直接唤起进程的方案:
- Web端触发推送时,不直接启动CLI进程,而是把推送内容、目标用户范围、任务唯一标识序列化后存入Redis的List队列,直接返回响应给前端。
- 写一个常驻运行的CLI消费脚本,循环从Redis队列中拉取任务,按照方案一中的分批查询、频率控制、幂等校验逻辑执行推送。
- 用supervisor或者systemd守护消费进程,进程异常退出时自动重启,保证任务不丢失。
关键注意点
- 所有批量推送逻辑必须放在CLI模式下运行,绝对不能放到Web请求进程中执行:Web进程受Nginx/Apache超时、PHP超时限制,容易中途中断任务,且同步执行会阻塞其他用户请求。
- 调用Telegram API必须控制请求频率,不要无间隔发请求,否则会触发接口限流导致推送失败。
- 所有curl请求必须设置合理的连接、响应超时时间,避免网络问题导致整个推送进程卡死。
内容的提问来源于stack exchange,提问作者Venda 97
相关产品推荐
相关产品推荐

