You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.09.02 01:42:36