首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >ThinkPHP 服务器推送事件(SSE)示例,实现请求大模型案例,前端识别返回格式和根据标签显示内容!

ThinkPHP 服务器推送事件(SSE)示例,实现请求大模型案例,前端识别返回格式和根据标签显示内容!

作者头像
程序猿的栖息地
发布2025-09-02 12:02:32
发布2025-09-02 12:02:32
14100
代码可运行
举报
运行总次数:0
代码可运行

由于ThinkPHP是基于PHP的框架,而SSE(Server-Sent Events)是一种服务器向客户端推送事件的技术,我们可以通过创建一个控制器方法来输出SSE格式的响应。

在ThinkPHP中,我们可以通过设置响应头为`text/event-stream`,然后循环推送数据来实现SSE。

下面是一个简单的示例,演示如何在ThinkPHP中实现SSE:

1. 首先,创建一个控制器,比如`Sse.php`。

2. 在控制器中,创建一个方法,比如`index()`,用于处理SSE请求。

3. 在该方法中,设置响应头,然后循环发送数据。

注意:由于SSE是长连接,所以我们需要确保在推送数据时不会超时,并且需要手动刷新输出缓冲区。

效果:实现连接服务,断开服务,主动发送内容至大模型(模拟),前端动态追加显示内容

前端页面代码:

代码语言:javascript
代码运行次数:0
运行
复制
<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>ThinkPHP SSE 示例</title>
    <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0-alpha1/dist/css/bootstrap.min.css" rel="stylesheet">
    <style>
        body {
            background-color: #f8f9fa;
            font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
        }
        .container {
            max-width: 800px;
            margin-top: 30px;
        }
        .card {
            box-shadow: 0 4px 8px rgba(0,0,0,0.1);
            margin-bottom: 20px;
            border: none;
            border-radius: 10px;
        }
        .card-header {
            background-color: #4f68ff;
            color: white;
            border-radius: 10px 10px 0 0 !important;
        }
        .event-log {
            height: 300px;
            overflow-y: auto;
            background-color: #2d3748;
            color: #68d391;
            font-family: 'Courier New', monospace;
            padding: 15px;
            border-radius: 5px;
        }
        .btn-action {
            width: 120px;
        }
        .status-indicator {
            display: inline-block;
            width: 12px;
            height: 12px;
            border-radius: 50%;
            margin-right: 8px;
        }
        .status-connected {
            background-color: #28a745;
        }
        .status-disconnected {
            background-color: #dc3545;
        }
        .event-item {
            margin-bottom: 8px;
            padding-bottom: 8px;
            border-bottom: 1px solid #3c4858;
        }
        .timestamp {
            color: #a0aec0;
            font-size: 0.8em;
        }
    </style>
</head>
<body>
    <div class="container">
        <h1 class="text-center mb-4">ThinkPHP 服务器推送事件(SSE)示例</h1>

        <div class="card">
            <div class="card-header">
                <h5 class="mb-0">连接控制</h5>
            </div>
            <div class="card-body">
                <div class="d-flex justify-content-between align-items-center mb-3">
                    <div>
                        <span id="statusIndicator" class="status-indicator status-disconnected"></span>
                        <span id="statusText">未连接</span>
                    </div>
                    <div>
                        <button id="connectBtn" class="btn btn-success btn-action">连接</button>
                        <button id="disconnectBtn" class="btn btn-danger btn-action" disabled>断开</button>
                    </div>
                </div>

                <div class="mb-3">
                    <label for="eventType" class="form-label">事件类型</label>
                    <select class="form-select" id="eventType">
                        <option value="message">普通消息</option>
                        <option value="notification">通知</option>
                        <option value="alert">警报</option>
                        <option value="update">数据更新</option>
                    </select>
                </div>

                <div class="mb-3">
                    <label for="customMessage" class="form-label">自定义消息(可选)</label>
                    <input type="text" class="form-control" id="customMessage" placeholder="输入自定义消息内容">
                </div>

                <button id="sendEventBtn" class="btn btn-primary" disabled>发送事件</button>
            </div>
        </div>

        <div class="card">
            <div class="card-header">
                <h5 class="mb-0">事件日志</h5>
            </div>
            <div class="card-body">
                <div id="eventLog" class="event-log"></div>
            </div>
        </div>

        <div class="card">
            <div class="card-header">
                <h5 class="mb-0">说明</h5>
            </div>
            <div class="card-body">
                <p>这是一个使用ThinkPHP实现服务器推送事件(SSE)的示例。</p>
                <p><strong>功能特点:</strong></p>
                <ul>
                    <li>建立SSE连接并保持通信</li>
                    <li>发送不同类型的事件</li>
                    <li>自定义消息内容</li>
                    <li>实时显示服务器推送的事件</li>
                </ul>
                <p><strong>后端实现要点:</strong></p>
                <ul>
                    <li>设置正确的HTTP头:Content-Type: text/event-stream</li>
                    <li>禁用缓存:Cache-Control: no-cache</li>
                    <li>保持连接:Connection: keep-alive</li>
                    <li>使用循环和ob_flush()实现实时输出</li>
                </ul>
            </div>
        </div>
    </div>

    <script>
        document.addEventListener('DOMContentLoaded', function() {
            const eventLog = document.getElementById('eventLog');
            const connectBtn = document.getElementById('connectBtn');
            const disconnectBtn = document.getElementById('disconnectBtn');
            const sendEventBtn = document.getElementById('sendEventBtn');
            const statusIndicator = document.getElementById('statusIndicator');
            const statusText = document.getElementById('statusText');
            const eventTypeSelect = document.getElementById('eventType');
            const customMessageInput = document.getElementById('customMessage');

            let eventSource = null;

            // 添加日志条目
            function addLogEntry(message, type = 'info') {
                const now = new Date();
                const timestamp = now.toLocaleTimeString();
                const logEntry = document.createElement('div');
                logEntry.className = `event-item text-${type}`;
                logEntry.innerHTML = `<span class="timestamp">[${timestamp}]</span> ${message}`;
                eventLog.appendChild(logEntry);
                eventLog.scrollTop = eventLog.scrollHeight;
            }

            // 建立SSE连接
            function connectSSE() {
                if (eventSource) {
                    return;
                }

                try {
                    // 这里使用相对路径,实际项目中应替换为你的ThinkPHP SSE端点
                    eventSource = new EventSource('/gbi/sse/search');

                    eventSource.onopen = function() {
                        statusIndicator.className = 'status-indicator status-connected';
                        statusText.textContent = '已连接';
                        connectBtn.disabled = true;
                        disconnectBtn.disabled = false;
                        sendEventBtn.disabled = false;
                        addLogEntry('SSE连接已建立', 'success');
                    };

                    eventSource.onmessage = function(event) {
                        const data = JSON.parse(event.data);
                        addLogEntry(`消息: ${data.message} (时间: ${data.time})`);
                    };

                    eventSource.addEventListener('notification', function(event) {
                        const data = JSON.parse(event.data);
                        addLogEntry(`通知: ${data.message} (优先级: ${data.priority})`, 'warning');
                    });

                    eventSource.addEventListener('alert', function(event) {
                        const data = JSON.parse(event.data);
                        addLogEntry(`警报: ${data.message} (级别: ${data.level})`, 'danger');
                    });

                    eventSource.addEventListener('update', function(event) {
                        const data = JSON.parse(event.data);
                        addLogEntry(`数据更新: ${data.message} (ID: ${data.id})`, 'info');
                    });

                    eventSource.onerror = function() {
                        addLogEntry('SSE连接错误', 'danger');
                        disconnectSSE();
                    };
                } catch (error) {
                    addLogEntry(`连接失败: ${error.message}`, 'danger');
                }
            }

            // 断开SSE连接
            function disconnectSSE() {
                if (eventSource) {
                    eventSource.close();
                    eventSource = null;
                }

                statusIndicator.className = 'status-indicator status-disconnected';
                statusText.textContent = '未连接';
                connectBtn.disabled = false;
                disconnectBtn.disabled = true;
                sendEventBtn.disabled = true;
                addLogEntry('SSE连接已关闭', 'info');
            }

            // 发送自定义事件
            function sendCustomEvent() {
                if (!eventSource) {
                    addLogEntry('请先建立SSE连接', 'warning');
                    return;
                }

                const eventType = eventTypeSelect.value;
                const customMessage = customMessageInput.value || `这是一条${eventType}类型的测试消息`;

                // 在实际应用中,这里应该通过AJAX发送请求到服务器
                // 服务器接收到请求后会通过SSE连接推送事件

                addLogEntry(`已请求发送事件: ${eventType} - ${customMessage}`, 'info');

                // 模拟服务器响应
                setTimeout(() => {
                    const events = {
                        message: { message: customMessage, time: new Date().toLocaleTimeString() },
                        notification: { message: customMessage, priority: 'high' },
                        alert: { message: customMessage, level: 'warning' },
                        update: { message: customMessage, id: Math.floor(Math.random() * 1000) }
                    };

                    const event = new MessageEvent(eventType, { data: JSON.stringify(events[eventType]) });

                    if (eventType === 'message') {
                        eventSource.onmessage(event);
                    } else {
                        eventSource.dispatchEvent(event);
                    }
                }, 500);
            }

            // 绑定按钮事件
            connectBtn.addEventListener('click', connectSSE);
            disconnectBtn.addEventListener('click', disconnectSSE);
            sendEventBtn.addEventListener('click', sendCustomEvent);

            // 初始化日志
            addLogEntry('页面已加载,点击"连接"按钮开始SSE通信。');
        });
    </script>
</body>
</html>

ThinkPHP代码

代码语言:javascript
代码运行次数:0
运行
复制
<?php
declare (strict_types = 1);

namespace app\gbi\controller;

use app\BaseController;
use think\Request;

/**
 * 测试sse
 */
class Sse extends BaseController
{
    public function index()
    {
        return view();
    }


    public function search(Request $request)
    {
        // 设置SSE所需的HTTP头
        header('Content-Type: text/event-stream');
        header('Cache-Control: no-cache');
        header('Connection: keep-alive');
        header('Access-Control-Allow-Origin: *');

        // 防止超时
        set_time_limit(0);

        // 获取客户端传递的lastEventId
        $lastEventId = $request->header('Last-Event-ID');

        // 发送初始注释消息(可选)
        echo ": SSE连接已建立\n\n";
        ob_flush();
        flush();

        $eventId = $lastEventId;
        $counter = 0;

        // 模拟持续发送事件
        while (true) {
            // 检查客户端是否仍然连接
            if (connection_aborted()) {
                break;
            }

            // 生成事件数据
            $eventId++;
            $data = [
                'message' => '服务器时间: ' . date('Y-m-d H:i:s'),
                'time' => date('H:i:s'),
                'counter' => $counter
            ];

            // 发送不同的事件类型
            if ($counter % 10 == 0) {
                // 每10秒发送一个通知事件
                $this->sendEvent($eventId, 'notification', [
                    'message' => '系统通知 #' . $counter,
                    'priority' => 'medium',
                    'time' => date('H:i:s')
                ]);
            } elseif ($counter % 7 == 0) {
                // 每7秒发送一个警报事件
                $this->sendEvent($eventId, 'alert', [
                    'message' => '系统警报 #' . $counter,
                    'level' => 'warning',
                    'time' => date('H:i:s')
                ]);
            } elseif ($counter % 5 == 0) {
                // 每5秒发送一个更新事件
                $this->sendEvent($eventId, 'update', [
                    'message' => '数据已更新 #' . $counter,
                    'id' => rand(1000, 9999),
                    'time' => date('H:i:s')
                ]);
            } else {
                // 发送普通消息事件
                $this->sendEvent($eventId, 'message', $data);
            }

            $counter++;

            // 休眠1秒
            sleep(1);
        }

        // 结束执行,避免ThinkPHP继续处理
        exit;
    }

    /**
     * 发送SSE事件
     * @param int $id 事件ID
     * @param string $event 事件类型
     * @param array $data 事件数据
     */
    private function sendEvent($id, $event, $data)
    {
        // 发送事件ID
        echo "id: {$id}\n";

        // 发送事件类型
        if ($event && $event != 'message') {
            echo "event: {$event}\n";
        }

        // 发送事件数据
        echo "data: " . json_encode($data) . "\n\n";

        // 刷新输出缓冲区
        ob_flush();
        flush();
    }

    /**
     * 接收客户端发送的事件请求
     */
    public function receive(Request $request)
    {
        // 在实际应用中,这里可以接收前端发送的事件请求
        // 然后通过SSE连接向客户端推送事件

        $eventType = $request->param('eventType', 'message');
        $message = $request->param('message', '默认消息');

        // 这里只是示例,实际应用中需要将事件推送给已连接的客户端
        return json([
            'code' => 200,
            'message' => '事件已接收',
            'eventType' => $eventType,
            'data' => $message
        ]);
    }
}
本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2025-08-20,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 程序猿的栖息地 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档