JienDa聊PHP:使用PHP编写Telegram Bot机器人完全指南

  • 时间:2025-12-04 23:14 作者: 来源: 阅读:0
  • 扫一扫,手机访问
摘要:使用PHP编写Telegram Bot机器人完全指南 第一章:Telegram Bot 开发基础 1.1 Telegram Bot 简介与优势 Telegram Bot 是基于 Telegram 平台的自动化程序,可以通过 API 与用户交互。相比其他聊天机器人,Telegram Bot 具有以下显著优势: 核心优势: 完全免费:Telegram Bot API 完全免费使用,无调用次数限制功能强

使用PHP编写Telegram Bot机器人完全指南

第一章:Telegram Bot 开发基础

1.1 Telegram Bot 简介与优势

Telegram Bot 是基于 Telegram 平台的自动化程序,可以通过 API 与用户交互。相比其他聊天机器人,Telegram Bot 具有以下显著优势:

核心优势:

完全免费:Telegram Bot API 完全免费使用,无调用次数限制功能强大:支持丰富的消息类型和交互方式全球覆盖:Telegram 在全球拥有超过 7 亿月活用户隐私安全:端到端加密,API 安全性高跨平台:支持所有主流操作系统

应用场景:

客户服务与支持内容推送和通知自动化任务处理数据查询和展示游戏和娱乐互动系统监控告警

1.2 Bot 开发前置要求

在开始开发之前,需要准备以下环境和技术基础:

技术栈要求:

PHP 7.4+(推荐 PHP 8.1+)cURL 扩展JSON 扩展Web 服务器(Apache/Nginx)HTTPS 域名(用于 Webhook)

必备知识:

PHP 面向对象编程HTTP 协议基础JSON 数据处理基本的 Linux 命令

第二章:Bot 创建与基础配置

2.1 创建你的第一个 Bot

2.1.1 通过 BotFather 创建 Bot

BotFather 是 Telegram 官方的 Bot 管理工具,用于创建和管理所有 Bot。

创建步骤:

在 Telegram 中搜索 @BotFather发送 /start 开始对话发送 /newbot 创建新 Bot按照提示设置 Bot 名称和用户名获取 API Token(格式: 123456789:ABCdefGHIjklMNOpqrsTUVwxyz
2.1.2 重要配置命令

创建完成后,建议进行以下配置:


// 通过 BotFather 配置 Bot 的常用命令
$botSettings = [
    '/setdescription' => '描述你的 Bot 功能',
    '/setabouttext' => '详细的关于信息',
    '/setuserpic' => '设置头像',
    '/setcommands' => '设置命令菜单'
];

2.2 环境搭建与基础类设计

2.2.1 项目结构规划

telegram-bot/
├── src/
│   ├── TelegramBot.php          # 主 Bot 类
│   ├── MessageHandler.php       # 消息处理器
│   ├── KeyboardManager.php      # 键盘管理器
│   └── Database.php             # 数据库操作
├── config/
│   ├── config.php               # 配置文件
│   └── webhook.php              # Webhook 入口
├── logs/
│   ├── bot.log                  # 日志文件
│   └── errors.log               # 错误日志
├── vendor/                      # Composer 依赖
├── public/
│   └── index.php                # 公开入口
└── composer.json
2.2.2 基础配置类

<?php
// config/config.php

class BotConfig {
    // Bot 基础信息
    const BOT_TOKEN = 'YOUR_BOT_TOKEN_HERE';
    const BOT_USERNAME = 'YOUR_BOT_USERNAME';
    
    // API 配置
    const TELEGRAM_API_URL = 'https://api.telegram.org/bot';
    const API_TIMEOUT = 30;
    
    // Webhook 配置
    const WEBHOOK_URL = 'https://yourdomain.com/webhook.php';
    const WEBHOOK_MAX_CONNECTIONS = 40;
    
    // 日志配置
    const LOG_ENABLED = true;
    const LOG_FILE = __DIR__ . '/../logs/bot.log';
    const ERROR_LOG_FILE = __DIR__ . '/../logs/errors.log';
    
    // 数据库配置
    const DB_HOST = 'localhost';
    const DB_NAME = 'telegram_bot';
    const DB_USER = 'username';
    const DB_PASS = 'password';
    const DB_CHARSET = 'utf8mb4';
    
    // 功能开关
    const ENABLE_WEBHOOK = true;
    const ENABLE_POLLING = false; // 长轮询备用方案
    
    // 安全配置
    const ALLOWED_IPS = ['149.154.160.0/20', '91.108.4.0/22'];
    const MAX_MESSAGE_LENGTH = 4096;
    
    public static function getApiUrl() {
        return self::TELEGRAM_API_URL . self::BOT_TOKEN . '/';
    }
    
    public static function validateIp($ip) {
        foreach (self::ALLOWED_IPS as $range) {
            if (self::ipInRange($ip, $range)) {
                return true;
            }
        }
        return false;
    }
    
    private static function ipInRange($ip, $range) {
        list($subnet, $bits) = explode('/', $range);
        $ip = ip2long($ip);
        $subnet = ip2long($subnet);
        $mask = -1 << (32 - $bits);
        $subnet &= $mask;
        return ($ip & $mask) == $subnet;
    }
}

第三章:核心 API 封装与实现

3.1 基础 HTTP 通信类


<?php
// src/TelegramRequest.php

class TelegramRequest {
    private $token;
    private $apiUrl;
    private $timeout;
    
    public function __construct($token, $timeout = 30) {
        $this->token = $token;
        $this->apiUrl = "https://api.telegram.org/bot{$token}/";
        $this->timeout = $timeout;
    }
    
    /**
     * 发送 API 请求
     */
    public function sendRequest($method, $params = [], $isMultipart = false) {
        $url = $this->apiUrl . $method;
        
        $ch = curl_init();
        curl_setopt($ch, CURLOPT_URL, $url);
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
        curl_setopt($ch, CURLOPT_TIMEOUT, $this->timeout);
        curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, true);
        curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 2);
        
        if (!empty($params)) {
            if ($isMultipart) {
                curl_setopt($ch, CURLOPT_POSTFIELDS, $params);
            } else {
                curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($params));
                curl_setopt($ch, CURLOPT_HTTPHEADER, [
                    'Content-Type: application/json'
                ]);
            }
            curl_setopt($ch, CURLOPT_POST, true);
        }
        
        $response = curl_exec($ch);
        $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
        $error = curl_error($ch);
        curl_close($ch);
        
        if ($error) {
            throw new TelegramException("cURL Error: " . $error);
        }
        
        if ($httpCode !== 200) {
            throw new TelegramException("HTTP Error: " . $httpCode);
        }
        
        $result = json_decode($response, true);
        
        if (!$result || !isset($result['ok'])) {
            throw new TelegramException("Invalid API response");
        }
        
        if (!$result['ok']) {
            throw new TelegramException("API Error: " . ($result['description'] ?? 'Unknown error'));
        }
        
        return $result['result'];
    }
    
    /**
     * 文件上传处理
     */
    public function sendFile($method, $filePath, $params = []) {
        if (!file_exists($filePath)) {
            throw new TelegramException("File not found: " . $filePath);
        }
        
        $params['document'] = new CURLFile(realpath($filePath));
        
        return $this->sendRequest($method, $params, true);
    }
    
    /**
     * 获取文件下载链接
     */
    public function getFileUrl($filePath) {
        return "https://api.telegram.org/file/bot{$this->token}/{$filePath}";
    }
}

class TelegramException extends Exception {
    // 自定义异常处理
    public function logError() {
        error_log("Telegram API Error: " . $this->getMessage());
    }
}

3.2 核心 Bot 类实现


<?php
// src/TelegramBot.php

class TelegramBot {
    private $token;
    private $request;
    private $update;
    private $handlers = [];
    private $middlewares = [];
    
    public function __construct($token) {
        $this->token = $token;
        $this->request = new TelegramRequest($token);
        $this->setupDefaultHandlers();
    }
    
    /**
     * 设置 Webhook
     */
    public function setWebhook($url, $options = []) {
        $params = array_merge([
            'url' => $url,
            'max_connections' => 40,
            'drop_pending_updates' => true
        ], $options);
        
        return $this->request->sendRequest('setWebhook', $params);
    }
    
    /**
     * 删除 Webhook
     */
    public function deleteWebhook() {
        return $this->request->sendRequest('deleteWebhook');
    }
    
    /**
     * 获取 Webhook 信息
     */
    public function getWebhookInfo() {
        return $this->request->sendRequest('getWebhookInfo');
    }
    
    /**
     * 处理传入的更新
     */
    public function processUpdate($updateData) {
        try {
            $this->update = $updateData;
            
            // 执行中间件
            foreach ($this->middlewares as $middleware) {
                if ($middleware($this->update) === false) {
                    return false;
                }
            }
            
            // 查找匹配的处理器
            foreach ($this->handlers as $handler) {
                if ($this->matchHandler($handler['pattern'], $this->getMessageText())) {
                    return call_user_func($handler['callback'], $this);
                }
            }
            
            // 默认处理器
            return $this->handleDefault();
            
        } catch (Exception $e) {
            $this->logError($e);
            return false;
        }
    }
    
    /**
     * 发送文本消息
     */
    public function sendMessage($chatId, $text, $options = []) {
        $params = array_merge([
            'chat_id' => $chatId,
            'text' => $text,
            'parse_mode' => 'HTML',
            'disable_web_page_preview' => false
        ], $options);
        
        // 处理长消息分片
        if (mb_strlen($text) > BotConfig::MAX_MESSAGE_LENGTH) {
            return $this->sendLongMessage($chatId, $text, $options);
        }
        
        return $this->request->sendRequest('sendMessage', $params);
    }
    
    /**
     * 发送长消息(自动分片)
     */
    private function sendLongMessage($chatId, $text, $options) {
        $messages = [];
        $chunks = str_split($text, BotConfig::MAX_MESSAGE_LENGTH - 100);
        
        foreach ($chunks as $index => $chunk) {
            $chunkOptions = $options;
            if ($index > 0) {
                unset($chunkOptions['reply_markup']); // 只在第一条消息显示键盘
            }
            $messages[] = $this->sendMessage($chatId, $chunk, $chunkOptions);
            usleep(500000); // 防止频率限制
        }
        
        return $messages;
    }
    
    /**
     * 发送图片
     */
    public function sendPhoto($chatId, $photo, $caption = '', $options = []) {
        $params = array_merge([
            'chat_id' => $chatId,
            'photo' => $photo,
            'caption' => $caption,
            'parse_mode' => 'HTML'
        ], $options);
        
        return $this->request->sendRequest('sendPhoto', $params);
    }
    
    /**
     * 发送文档
     */
    public function sendDocument($chatId, $document, $caption = '', $options = []) {
        $params = array_merge([
            'chat_id' => $chatId,
            'document' => $document,
            'caption' => $caption
        ], $options);
        
        return $this->request->sendRequest('sendDocument', $params);
    }
    
    /**
     * 发送位置
     */
    public function sendLocation($chatId, $latitude, $longitude, $options = []) {
        $params = array_merge([
            'chat_id' => $chatId,
            'latitude' => $latitude,
            'longitude' => $longitude
        ], $options);
        
        return $this->request->sendRequest('sendLocation', $params);
    }
    
    /**
     * 编辑消息
     */
    public function editMessageText($chatId, $messageId, $text, $options = []) {
        $params = array_merge([
            'chat_id' => $chatId,
            'message_id' => $messageId,
            'text' => $text,
            'parse_mode' => 'HTML'
        ], $options);
        
        return $this->request->sendRequest('editMessageText', $params);
    }
    
    /**
     * 删除消息
     */
    public function deleteMessage($chatId, $messageId) {
        $params = [
            'chat_id' => $chatId,
            'message_id' => $messageId
        ];
        
        return $this->request->sendRequest('deleteMessage', $params);
    }
    
    /**
     * 获取用户信息
     */
    public function getChat($chatId) {
        $params = ['chat_id' => $chatId];
        return $this->request->sendRequest('getChat', $params);
    }
    
    /**
     * 获取聊天成员数
     */
    public function getChatMembersCount($chatId) {
        $params = ['chat_id' => $chatId];
        return $this->request->sendRequest('getChatMembersCount', $params);
    }
    
    // 更多消息类型方法...
    
    /**
     * 添加消息处理器
     */
    public function on($pattern, $callback) {
        $this->handlers[] = [
            'pattern' => $pattern,
            'callback' => $callback
        ];
    }
    
    /**
     * 添加中间件
     */
    public function use($middleware) {
        $this->middlewares[] = $middleware;
    }
    
    /**
     * 匹配处理器模式
     */
    private function matchHandler($pattern, $text) {
        if ($pattern === 'message' && $this->isMessage()) {
            return true;
        }
        
        if ($pattern === 'callback_query' && $this->isCallbackQuery()) {
            return true;
        }
        
        if (is_string($pattern) && preg_match($pattern, $text)) {
            return true;
        }
        
        if (is_callable($pattern) && $pattern($this->update)) {
            return true;
        }
        
        return false;
    }
    
    // 辅助方法
    public function getChatId() {
        if ($this->isCallbackQuery()) {
            return $this->update['callback_query']['message']['chat']['id'];
        }
        return $this->update['message']['chat']['id'] ?? null;
    }
    
    public function getMessageText() {
        return $this->update['message']['text'] ?? '';
    }
    
    public function getCallbackData() {
        return $this->update['callback_query']['data'] ?? '';
    }
    
    public function isMessage() {
        return isset($this->update['message']);
    }
    
    public function isCallbackQuery() {
        return isset($this->update['callback_query']);
    }
    
    public function isEditedMessage() {
        return isset($this->update['edited_message']);
    }
    
    private function setupDefaultHandlers() {
        // 默认命令处理器
        $this->on('/start', [$this, 'handleStart']);
        $this->on('/help', [$this, 'handleHelp']);
    }
    
    private function handleDefault() {
        $chatId = $this->getChatId();
        $this->sendMessage($chatId, "🤖 抱歉,我不理解这个命令。发送 /help 查看可用命令。");
    }
    
    private function logError($exception) {
        if (BotConfig::LOG_ENABLED) {
            $logMessage = sprintf(
                "[%s] Error: %s in %s:%d
",
                date('Y-m-d H:i:s'),
                $exception->getMessage(),
                $exception->getFile(),
                $exception->getLine()
            );
            file_put_contents(BotConfig::ERROR_LOG_FILE, $logMessage, FILE_APPEND);
        }
    }
}

第四章:高级功能实现

4.1 键盘管理器实现


<?php
// src/KeyboardManager.php

class KeyboardManager {
    /**
     * 创建回复键盘
     */
    public static function createReplyKeyboard($buttons, $options = []) {
        $keyboard = [
            'keyboard' => $buttons,
            'resize_keyboard' => $options['resize'] ?? true,
            'one_time_keyboard' => $options['one_time'] ?? false,
            'selective' => $options['selective'] ?? false
        ];
        
        return json_encode($keyboard);
    }
    
    /**
     * 创建内联键盘
     */
    public static function createInlineKeyboard($buttons) {
        $keyboard = ['inline_keyboard' => $buttons];
        return json_encode($keyboard);
    }
    
    /**
     * 移除回复键盘
     */
    public static function removeKeyboard() {
        return json_encode(['remove_keyboard' => true]);
    }
    
    /**
     * 创建内联按钮
     */
    public static function inlineButton($text, $data = null, $url = null) {
        $button = ['text' => $text];
        
        if ($url) {
            $button['url'] = $url;
        } elseif ($data) {
            $button['callback_data'] = $data;
        }
        
        return $button;
    }
    
    /**
     * 分页键盘生成器
     */
    public static function createPaginationKeyboard($items, $page, $pageSize, $callbackPrefix) {
        $totalPages = ceil(count($items) / $pageSize);
        $start = ($page - 1) * $pageSize;
        $pageItems = array_slice($items, $start, $pageSize);
        
        $buttons = [];
        foreach ($pageItems as $item) {
            $buttons[] = [[
                'text' => $item['name'],
                'callback_data' => $callbackPrefix . '_' . $item['id']
            ]];
        }
        
        // 分页控制
        $paginationButtons = [];
        if ($page > 1) {
            $paginationButtons[] = [
                'text' => '⬅️ 上一页',
                'callback_data' => $callbackPrefix . '_page_' . ($page - 1)
            ];
        }
        
        $paginationButtons[] = [
            'text' => "{$page}/{$totalPages}",
            'callback_data' => 'page_info'
        ];
        
        if ($page < $totalPages) {
            $paginationButtons[] = [
                'text' => '下一页 ➡️',
                'callback_data' => $callbackPrefix . '_page_' . ($page + 1)
            ];
        }
        
        $buttons[] = $paginationButtons;
        
        return self::createInlineKeyboard($buttons);
    }
}

4.2 消息处理器实现


<?php
// src/MessageHandler.php

class MessageHandler {
    private $bot;
    private $db;
    
    public function __construct($bot, $database) {
        $this->bot = $bot;
        $this->db = $database;
    }
    
    /**
     * 处理 /start 命令
     */
    public function handleStart() {
        $chatId = $this->bot->getChatId();
        $firstName = $this->bot->update['message']['chat']['first_name'] ?? '用户';
        
        $welcomeText = "👋 欢迎, {$firstName}!

";
        $welcomeText .= "🤖 我是功能丰富的 Telegram Bot

";
        $welcomeText .= "📋 <b>可用命令:</b>
";
        $welcomeText .= "/help - 显示帮助信息
";
        $welcomeText .= "/weather - 查询天气
";
        $welcomeText .= "/news - 最新新闻
";
        $welcomeText .= "/settings - 个人设置

";
        $welcomeText .= "💡 点击下方按钮开始使用!";
        
        $keyboard = KeyboardManager::createReplyKeyboard([
            ['🌤️ 天气查询', '📰 今日新闻'],
            ['⚙️ 设置', 'ℹ️ 关于']
        ], ['resize' => true]);
        
        // 保存用户信息
        $this->saveUserInfo();
        
        return $this->bot->sendMessage($chatId, $welcomeText, [
            'reply_markup' => $keyboard
        ]);
    }
    
    /**
     * 处理帮助命令
     */
    public function handleHelp() {
        $chatId = $this->bot->getChatId();
        
        $helpText = "📚 <b>帮助手册</b>

";
        $helpText .= "<b>基础命令:</b>
";
        $helpText .= "• /start - 启动机器人
";
        $helpText .= "• /help - 显示此帮助
";
        $helpText .= "• /cancel - 取消当前操作

";
        
        $helpText .= "<b>实用功能:</b>
";
        $helpText .= "• 天气查询 - 发送位置或城市名
";
        $helpText .= "• 新闻推送 - 获取最新资讯
";
        $helpText .= "• 文件处理 - 支持多种格式

";
        
        $helpText .= "💡 <i>提示:使用菜单按钮快速操作</i>";
        
        return $this->bot->sendMessage($chatId, $helpText);
    }
    
    /**
     * 处理文本消息
     */
    public function handleTextMessage() {
        $text = $this->bot->getMessageText();
        $chatId = $this->bot->getChatId();
        
        // 根据内容类型处理
        switch (true) {
            case preg_match('/^天气|weather$/i', $text):
                return $this->handleWeatherRequest();
                
            case preg_match('/^新闻|news$/i', $text):
                return $this->handleNewsRequest();
                
            case preg_match('/^时间|time$/i', $text):
                return $this->handleTimeRequest();
                
            default:
                return $this->handleUnknownMessage();
        }
    }
    
    /**
     * 处理回调查询
     */
    public function handleCallbackQuery() {
        $data = $this->bot->getCallbackData();
        $callbackQueryId = $this->bot->update['callback_query']['id'];
        
        // 回答回调查询(防止客户端等待)
        $this->bot->answerCallbackQuery($callbackQueryId);
        
        list($action, $param) = explode('_', $data . '_');
        
        switch ($action) {
            case 'weather':
                return $this->handleWeatherCallback($param);
            case 'news':
                return $this->handleNewsCallback($param);
            case 'settings':
                return $this->handleSettingsCallback($param);
        }
    }
    
    /**
     * 天气查询处理
     */
    private function handleWeatherRequest() {
        $chatId = $this->bot->getChatId();
        
        $keyboard = KeyboardManager::createInlineKeyboard([
            [
                KeyboardManager::inlineButton('📍 发送位置', 'weather_location'),
                KeyboardManager::inlineButton('🏙️ 选择城市', 'weather_city')
            ]
        ]);
        
        return $this->bot->sendMessage($chatId, 
            "🌤️ <b>天气查询</b>

请选择查询方式:", 
            ['reply_markup' => $keyboard]
        );
    }
    
    /**
     * 保存用户信息
     */
    private function saveUserInfo() {
        $message = $this->bot->update['message'];
        $user = $message['from'];
        $chat = $message['chat'];
        
        $userData = [
            'user_id' => $user['id'],
            'username' => $user['username'] ?? '',
            'first_name' => $user['first_name'] ?? '',
            'last_name' => $user['last_name'] ?? '',
            'language_code' => $user['language_code'] ?? 'zh',
            'chat_id' => $chat['id'],
            'chat_type' => $chat['type'],
            'joined_at' => date('Y-m-d H:i:s')
        ];
        
        // 保存到数据库
        $this->db->insertOrUpdateUser($userData);
    }
}

第五章:数据库设计与用户管理

5.1 数据库结构设计


-- 用户表
CREATE TABLE users (
    id INT AUTO_INCREMENT PRIMARY KEY,
    user_id BIGINT UNIQUE NOT NULL,
    username VARCHAR(255),
    first_name VARCHAR(255),
    last_name VARCHAR(255),
    language_code VARCHAR(10) DEFAULT 'zh',
    chat_id BIGINT NOT NULL,
    chat_type ENUM('private', 'group', 'supergroup') DEFAULT 'private',
    is_bot BOOLEAN DEFAULT FALSE,
    last_activity DATETIME,
    joined_at DATETIME,
    settings JSON,
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
    INDEX idx_user_id (user_id),
    INDEX idx_chat_id (chat_id)
);

-- 消息日志表
CREATE TABLE message_logs (
    id INT AUTO_INCREMENT PRIMARY KEY,
    update_id BIGINT UNIQUE,
    user_id BIGINT,
    chat_id BIGINT,
    message_id BIGINT,
    message_type ENUM('text', 'photo', 'document', 'location', 'callback'),
    content TEXT,
    timestamp DATETIME,
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    FOREIGN KEY (user_id) REFERENCES users(user_id) ON DELETE SET NULL,
    INDEX idx_timestamp (timestamp)
);

-- 用户会话表
CREATE TABLE user_sessions (
    id INT AUTO_INCREMENT PRIMARY KEY,
    user_id BIGINT,
    current_state VARCHAR(100),
    session_data JSON,
    expires_at DATETIME,
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    FOREIGN KEY (user_id) REFERENCES users(user_id) ON DELETE CASCADE,
    INDEX idx_user_state (user_id, current_state)
);

-- 统计表
CREATE TABLE bot_stats (
    id INT AUTO_INCREMENT PRIMARY KEY,
    stat_date DATE UNIQUE,
    active_users INT DEFAULT 0,
    new_users INT DEFAULT 0,
    messages_received INT DEFAULT 0,
    messages_sent INT DEFAULT 0,
    commands_executed INT DEFAULT 0,
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

5.2 数据库操作类


<?php
// src/Database.php

class Database {
    private $pdo;
    private $config;
    
    public function __construct($config) {
        $this->config = $config;
        $this->connect();
    }
    
    private function connect() {
        try {
            $dsn = "mysql:host={$this->config['host']};dbname={$this->config['database']};charset={$this->config['charset']}";
            $this->pdo = new PDO($dsn, $this->config['username'], $this->config['password'], [
                PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
                PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
                PDO::ATTR_EMULATE_PREPARES => false
            ]);
        } catch (PDOException $e) {
            throw new DatabaseException("数据库连接失败: " . $e->getMessage());
        }
    }
    
    /**
     * 插入或更新用户信息
     */
    public function insertOrUpdateUser($userData) {
        $sql = "INSERT INTO users (user_id, username, first_name, last_name, language_code, chat_id, chat_type, last_activity, joined_at) 
                VALUES (:user_id, :username, :first_name, :last_name, :language_code, :chat_id, :chat_type, NOW(), NOW())
                ON DUPLICATE KEY UPDATE 
                username = VALUES(username),
                first_name = VALUES(first_name),
                last_name = VALUES(last_name),
                language_code = VALUES(language_code),
                last_activity = NOW()";
        
        $stmt = $this->pdo->prepare($sql);
        return $stmt->execute($userData);
    }
    
    /**
     * 记录消息日志
     */
    public function logMessage($messageData) {
        $sql = "INSERT INTO message_logs (update_id, user_id, chat_id, message_id, message_type, content, timestamp) 
                VALUES (:update_id, :user_id, :chat_id, :message_id, :message_type, :content, NOW())";
        
        $stmt = $this->pdo->prepare($sql);
        return $stmt->execute($messageData);
    }
    
    /**
     * 获取用户会话状态
     */
    public function getUserSession($userId) {
        $sql = "SELECT * FROM user_sessions WHERE user_id = ? AND expires_at > NOW()";
        $stmt = $this->pdo->prepare($sql);
        $stmt->execute([$userId]);
        
        return $stmt->fetch();
    }
    
    /**
     * 更新用户会话
     */
    public function updateUserSession($userId, $state, $sessionData = null) {
        $sql = "INSERT INTO user_sessions (user_id, current_state, session_data, expires_at) 
                VALUES (?, ?, ?, DATE_ADD(NOW(), INTERVAL 1 HOUR))
                ON DUPLICATE KEY UPDATE 
                current_state = VALUES(current_state),
                session_data = VALUES(session_data),
                expires_at = VALUES(expires_at)";
        
        $stmt = $this->pdo->prepare($sql);
        return $stmt->execute([$userId, $state, json_encode($sessionData)]);
    }
    
    /**
     * 清理过期会话
     */
    public function cleanupExpiredSessions() {
        $sql = "DELETE FROM user_sessions WHERE expires_at <= NOW()";
        return $this->pdo->exec($sql);
    }
    
    /**
     * 获取统计信息
     */
    public function getStats($days = 7) {
        $sql = "SELECT 
                COUNT(DISTINCT user_id) as total_users,
                COUNT(*) as total_messages,
                MAX(joined_at) as last_join
                FROM users
                WHERE joined_at >= DATE_SUB(NOW(), INTERVAL ? DAY)";
        
        $stmt = $this->pdo->prepare($sql);
        $stmt->execute([$days]);
        
        return $stmt->fetch();
    }
}

class DatabaseException extends Exception {
    // 数据库异常处理
}

第六章:Webhook 配置与部署

6.1 Webhook 入口文件


<?php
// webhook.php

require_once __DIR__ . '/vendor/autoload.php';
require_once __DIR__ . '/config/config.php';

// 设置错误处理
set_error_handler([BotErrorHandler::class, 'handleError']);
set_exception_handler([BotErrorHandler::class, 'handleException']);

class BotWebhook {
    private $bot;
    private $db;
    
    public function __construct() {
        // 验证请求来源
        $this->validateRequest();
        
        // 初始化组件
        $this->initializeComponents();
    }
    
    /**
     * 验证请求安全性
     */
    private function validateRequest() {
        // 验证 IP 地址
        $clientIp = $_SERVER['REMOTE_ADDR'] ?? '';
        if (!BotConfig::validateIp($clientIp)) {
            http_response_code(403);
            exit('Forbidden');
        }
        
        // 验证请求方法
        if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
            http_response_code(405);
            exit('Method Not Allowed');
        }
        
        // 简单的 Token 验证(可选)
        if (isset($_GET['secret']) && $_GET['secret'] !== 'YOUR_SECRET_TOKEN') {
            http_response_code(401);
            exit('Unauthorized');
        }
    }
    
    /**
     * 初始化组件
     */
    private function initializeComponents() {
        try {
            // 初始化数据库
            $dbConfig = [
                'host' => BotConfig::DB_HOST,
                'database' => BotConfig::DB_NAME,
                'username' => BotConfig::DB_USER,
                'password' => BotConfig::DB_PASS,
                'charset' => BotConfig::DB_CHARSET
            ];
            
            $this->db = new Database($dbConfig);
            
            // 初始化 Bot
            $this->bot = new TelegramBot(BotConfig::BOT_TOKEN);
            
            // 注册处理器
            $this->registerHandlers();
            
        } catch (Exception $e) {
            error_log("初始化失败: " . $e->getMessage());
            http_response_code(500);
            exit('Internal Server Error');
        }
    }
    
    /**
     * 注册消息处理器
     */
    private function registerHandlers() {
        $handler = new MessageHandler($this->bot, $this->db);
        
        // 命令处理器
        $this->bot->on('/start', [$handler, 'handleStart']);
        $this->bot->on('/help', [$handler, 'handleHelp']);
        $this->bot->on('/weather', [$handler, 'handleWeatherRequest']);
        $this->bot->on('/news', [$handler, 'handleNewsRequest']);
        
        // 消息类型处理器
        $this->bot->on('message', [$handler, 'handleTextMessage']);
        $this->bot->on('callback_query', [$handler, 'handleCallbackQuery']);
        
        // 中间件
        $this->bot->use([$this, 'logRequest']);
        $this->bot->use([$this, 'checkMaintenance']);
    }
    
    /**
     * 记录请求日志
     */
    public function logRequest($update) {
        if (BotConfig::LOG_ENABLED) {
            $logData = [
                'timestamp' => date('Y-m-d H:i:s'),
                'update_id' => $update['update_id'],
                'message_type' => $this->getUpdateType($update),
                'chat_id' => $this->getChatIdFromUpdate($update),
                'content' => json_encode($update, JSON_UNESCAPED_UNICODE)
            ];
            
            file_put_contents(BotConfig::LOG_FILE, 
                json_encode($logData, JSON_UNESCAPED_UNICODE) . PHP_EOL, 
                FILE_APPEND
            );
        }
        
        return true;
    }
    
    /**
     * 检查维护模式
     */
    public function checkMaintenance($update) {
        if (file_exists(__DIR__ . '/maintenance.lock')) {
            $chatId = $this->getChatIdFromUpdate($update);
            $this->bot->sendMessage($chatId, '🔧 机器人正在维护中,请稍后再试。');
            return false;
        }
        return true;
    }
    
    /**
     * 处理 Webhook 请求
     */
    public function process() {
        try {
            // 获取输入数据
            $input = file_get_contents('php://input');
            $update = json_decode($input, true);
            
            if (!$update) {
                throw new Exception('Invalid JSON input');
            }
            
            // 处理更新
            $result = $this->bot->processUpdate($update);
            
            // 记录消息到数据库
            if (isset($update['message'])) {
                $this->db->logMessage([
                    'update_id' => $update['update_id'],
                    'user_id' => $update['message']['from']['id'],
                    'chat_id' => $update['message']['chat']['id'],
                    'message_id' => $update['message']['message_id'],
                    'message_type' => 'text',
                    'content' => $update['message']['text'] ?? ''
                ]);
            }
            
            http_response_code(200);
            echo 'OK';
            
        } catch (Exception $e) {
            error_log("Webhook处理错误: " . $e->getMessage());
            http_response_code(200); // 始终返回200,避免Telegram重试
            echo 'Error';
        }
    }
    
    private function getUpdateType($update) {
        if (isset($update['message'])) return 'message';
        if (isset($update['callback_query'])) return 'callback_query';
        if (isset($update['edited_message'])) return 'edited_message';
        return 'unknown';
    }
    
    private function getChatIdFromUpdate($update) {
        if (isset($update['message'])) return $update['message']['chat']['id'];
        if (isset($update['callback_query'])) return $update['callback_query']['message']['chat']['id'];
        return null;
    }
}

// 启动Webhook处理
$webhook = new BotWebhook();
$webhook->process();

6.2 部署脚本


#!/bin/bash
# deploy.sh

echo "🚀 开始部署 Telegram Bot..."

# 检查环境
if ! command -v php &> /dev/null; then
    echo "❌ PHP 未安装"
    exit 1
fi

if ! command -v composer &> /dev/null; then
    echo "❌ Composer 未安装"
    exit 1
fi

# 安装依赖
echo "📦 安装 Composer 依赖..."
composer install --no-dev --optimize-autoloader

# 设置文件权限
echo "🔧 设置文件权限..."
chmod 755 webhook.php
chmod 777 logs/

# 数据库迁移
echo "🗃️ 初始化数据库..."
php database/migrate.php

# 设置 Webhook
echo "🌐 设置 Telegram Webhook..."
php scripts/set_webhook.php

echo "✅ 部署完成!"
echo "📊 检查服务状态: https://api.telegram.org/bot<YOUR_TOKEN>/getWebhookInfo"

第七章:高级特性与优化

7.1 速率限制与队列处理


<?php
// src/RateLimiter.php

class RateLimiter {
    private $redis;
    private $limits = [
        'message_per_second' => 1,
        'message_per_minute' => 20,
        'callback_per_second' => 10
    ];
    
    public function __construct($redisConfig) {
        $this->redis = new Redis();
        $this->redis->connect($redisConfig['host'], $redisConfig['port']);
        if (isset($redisConfig['password'])) {
            $this->redis->auth($redisConfig['password']);
        }
    }
    
    public function checkLimit($userId, $action) {
        $key = "rate_limit:{$userId}:{$action}";
        $current = $this->redis->get($key);
        
        if ($current >= $this->limits[$action]) {
            return false;
        }
        
        $this->redis->incr($key);
        $this->redis->expire($key, 60); // 60秒过期
        
        return true;
    }
}

7.2 消息队列实现


<?php
// src/MessageQueue.php

class MessageQueue {
    private $redis;
    private $queueName = 'telegram_messages';
    
    public function __construct($redisConfig) {
        $this->redis = new Redis();
        $this->redis->connect($redisConfig['host'], $redisConfig['port']);
    }
    
    public function pushMessage($message) {
        return $this->redis->lpush($this->queueName, json_encode($message));
    }
    
    public function processQueue($bot) {
        while ($message = $this->redis->rpop($this->queueName)) {
            $messageData = json_decode($message, true);
            $bot->sendMessage(
                $messageData['chat_id'],
                $messageData['text'],
                $messageData['options'] ?? []
            );
            usleep(100000); // 100ms 延迟
        }
    }
}

这个完整的 Telegram Bot 实现包含了从基础配置到高级特性的所有内容,可以满足大多数业务场景的需求。代码结构清晰,易于扩展和维护。

  • 全部评论(0)
手机二维码手机访问领取大礼包
返回顶部