PHP 实时数据大屏后端

wen PHP项目 2

本文目录导读:

PHP 实时数据大屏后端

  1. 整体架构设计
  2. 核心功能实现
  3. 部署与配置

我来为您介绍PHP实时数据大屏后端的完整实现方案。

整体架构设计

┌─────────────────────────────────────────────────┐
│              数据大屏前端(WebSocket客户端)      │
└────────────────────┬────────────────────────────┘
                     │ WebSocket/HTTP
┌────────────────────▼────────────────────────────┐
│              PHP 后端服务(实时推送)             │
├────────────────────────────────────────────────┤
│  1. WebSocket服务    2. RESTful API接口          │
│  3. 数据聚合服务      4. 缓存服务                │
│  5. 定时任务服务      6. 消息队列                │
└────────────────────────────────────────────────┘

核心功能实现

WebSocket 服务(使用 Workerman)

<?php
use Workerman\Worker;
use Workerman\Timer;
use Workerman\Connection\TcpConnection;
require_once __DIR__ . '/vendor/autoload.php';
class RealtimeDataServer {
    private $worker;
    private $dataProvider;
    private $clients = [];
    public function __construct() {
        $this->worker = new Worker('websocket://0.0.0.0:2345');
        $this->worker->count = 4;
        $this->initEventHandlers();
        $this->initDataProvider();
    }
    private function initEventHandlers() {
        $this->worker->onConnect = function(TcpConnection $connection) {
            echo "Client connected: {$connection->getRemoteIp()}\n";
        };
        $this->worker->onMessage = function(TcpConnection $connection, $data) {
            $message = json_decode($data, true);
            switch ($message['type'] ?? '') {
                case 'subscribe':
                    $this->handleSubscribe($connection, $message);
                    break;
                case 'unsubscribe':
                    $this->handleUnsubscribe($connection, $message);
                    break;
                case 'ping':
                    $connection->send(json_encode(['type' => 'pong']));
                    break;
            }
        };
        $this->worker->onClose = function(TcpConnection $connection) {
            echo "Client disconnected\n";
            $this->removeClient($connection);
        };
    }
    private function initDataProvider() {
        // 每5秒推送一次实时数据
        Timer::add(5, function() {
            $this->broadcastData();
        });
        // 每分钟推送统计数据
        Timer::add(60, function() {
            $this->broadcastStatistics();
        });
    }
    private function handleSubscribe($connection, $message) {
        $channel = $message['channel'] ?? 'default';
        $clientId = spl_object_hash($connection);
        $this->clients[$clientId] = [
            'connection' => $connection,
            'channel' => $channel
        ];
        // 发送初始数据
        $initialData = $this->getChannelData($channel);
        $connection->send(json_encode([
            'type' => 'initial_data',
            'channel' => $channel,
            'data' => $initialData
        ]));
    }
    private function handleUnsubscribe($connection, $message) {
        $this->removeClient($connection);
    }
    private function removeClient($connection) {
        $clientId = spl_object_hash($connection);
        unset($this->clients[$clientId]);
    }
    private function broadcastData() {
        $data = $this->getRealtimeData();
        foreach ($this->clients as $client) {
            $client['connection']->send(json_encode([
                'type' => 'realtime_data',
                'data' => $data,
                'timestamp' => time()
            ]));
        }
    }
    private function broadcastStatistics() {
        $stats = $this->getStatistics();
        foreach ($this->clients as $client) {
            $client['connection']->send(json_encode([
                'type' => 'statistics',
                'data' => $stats,
                'timestamp' => time()
            ]));
        }
    }
    private function getChannelData($channel) {
        // 根据订阅频道返回对应数据
        switch ($channel) {
            case 'orders':
                return $this->getOrdersData();
            case 'users':
                return $this->getUsersData();
            default:
                return $this->getRealtimeData();
        }
    }
    private function getRealtimeData() {
        // 模拟实时数据,实际项目中从数据库/缓存获取
        return [
            'online_users' => rand(1000, 5000),
            'new_orders' => rand(50, 200),
            'sales_amount' => rand(10000, 50000),
            'visitors' => rand(500, 2000),
            'conversion_rate' => rand(1, 10) / 10,
            'regions' => $this->getRegionData()
        ];
    }
    private function getStatistics() {
        return [
            'total_users' => rand(50000, 100000),
            'total_orders' => rand(10000, 50000),
            'total_sales' => rand(1000000, 5000000),
            'avg_order_value' => rand(50, 200),
            'hourly_stats' => $this->getHourlyStats()
        ];
    }
    private function getRegionData() {
        $regions = ['北京', '上海', '广州', '深圳', '杭州', '成都'];
        $data = [];
        foreach ($regions as $region) {
            $data[] = [
                'name' => $region,
                'value' => rand(100, 1000)
            ];
        }
        return $data;
    }
    private function getHourlyStats() {
        $hours = [];
        for ($i = 0; $i < 24; $i++) {
            $hours[] = [
                'hour' => $i,
                'orders' => rand(10, 100),
                'sales' => rand(100, 1000)
            ];
        }
        return $hours;
    }
}
// 启动服务
$server = new RealtimeDataServer();
Worker::runAll();

RESTful API 接口

<?php
use Slim\Factory\AppFactory;
use Psr\Http\Message\ResponseInterface as Response;
use Psr\Http\Message\ServerRequestInterface as Request;
require __DIR__ . '/vendor/autoload.php';
class DashboardAPI {
    private $app;
    private $cache;
    private $db;
    public function __construct() {
        $this->app = AppFactory::create();
        $this->initDatabase();
        $this->initRoutes();
    }
    private function initDatabase() {
        // 使用PDO连接数据库
        $this->db = new PDO(
            'mysql:host=localhost;dbname=dashboard;charset=utf8mb4',
            'username',
            'password'
        );
        $this->db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
        // 初始化Redis缓存
        $this->cache = new Redis();
        $this->cache->connect('localhost', 6379);
    }
    private function initRoutes() {
        $this->app->get('/api/dashboard/summary', function(Request $request, Response $response) {
            $data = $this->getSummaryData();
            return $this->jsonResponse($response, $data);
        });
        $this->app->get('/api/dashboard/realtime', function(Request $request, Response $response) {
            $data = $this->getRealtimeData();
            return $this->jsonResponse($response, $data);
        });
        $this->app->get('/api/dashboard/statistics', function(Request $request, Response $response) {
            $params = $request->getQueryParams();
            $startDate = $params['start_date'] ?? date('Y-m-d', strtotime('-7 days'));
            $endDate = $params['end_date'] ?? date('Y-m-d');
            $data = $this->getStatistics($startDate, $endDate);
            return $this->jsonResponse($response, $data);
        });
        $this->app->get('/api/dashboard/{metric}', function(Request $request, Response $response, $args) {
            $metric = $args['metric'];
            $data = $this->getMetricData($metric);
            return $this->jsonResponse($response, $data);
        });
    }
    private function getSummaryData() {
        // 检查缓存
        $cacheKey = 'dashboard:summary';
        $cached = $this->cache->get($cacheKey);
        if ($cached !== false) {
            return json_decode($cached, true);
        }
        // 从数据库获取数据
        $today = date('Y-m-d');
        $sql = "SELECT 
                    (SELECT COUNT(*) FROM users WHERE created_at >= ?) as new_users,
                    (SELECT COUNT(*) FROM orders WHERE created_at >= ?) as today_orders,
                    (SELECT SUM(total_amount) FROM orders WHERE created_at >= ?) as today_sales,
                    (SELECT COUNT(*) FROM orders) as total_orders,
                    (SELECT SUM(total_amount) FROM orders) as total_sales";
        $stmt = $this->db->prepare($sql);
        $stmt->execute([$today, $today, $today]);
        $data = $stmt->fetch(PDO::FETCH_ASSOC);
        // 缓存数据(5分钟)
        $this->cache->setex($cacheKey, 300, json_encode($data));
        return $data;
    }
    private function getRealtimeData() {
        // 实时数据直接从缓存获取(由WebSocket服务更新)
        $cacheKey = 'dashboard:realtime';
        $cached = $this->cache->get($cacheKey);
        if ($cached !== false) {
            return json_decode($cached, true);
        }
        return $this->getFallbackRealtimeData();
    }
    private function getStatistics($startDate, $endDate) {
        $cacheKey = "dashboard:stats:{$startDate}:{$endDate}";
        $cached = $this->cache->get($cacheKey);
        if ($cached !== false) {
            return json_decode($cached, true);
        }
        // 按天统计数据
        $sql = "SELECT 
                    DATE(created_at) as date,
                    COUNT(*) as orders,
                    SUM(total_amount) as sales,
                    COUNT(DISTINCT user_id) as users
                FROM orders 
                WHERE created_at BETWEEN ? AND ?
                GROUP BY DATE(created_at)
                ORDER BY date";
        $stmt = $this->db->prepare($sql);
        $stmt->execute([$startDate, $endDate . ' 23:59:59']);
        $data = $stmt->fetchAll(PDO::FETCH_ASSOC);
        // 缓存数据(1小时)
        $this->cache->setex($cacheKey, 3600, json_encode($data));
        return $data;
    }
    private function getMetricData($metric) {
        // 根据不同指标返回不同数据
        switch ($metric) {
            case 'sales':
                $data = $this->getSalesMetrics();
                break;
            case 'users':
                $data = $this->getUserMetrics();
                break;
            case 'system':
                $data = $this->getSystemMetrics();
                break;
            default:
                $data = ['status' => 'error', 'message' => 'Unknown metric'];
        }
        return $data;
    }
    private function getSalesMetrics() {
        return [
            'today' => [
                'amount' => $this->getTodaySales(),
                'orders' => $this->getTodayOrders()
            ],
            'weekly' => $this->getWeeklySales(),
            'monthly' => $this->getMonthlySales()
        ];
    }
    private function getUserMetrics() {
        return [
            'active_today' => $this->getActiveUsersToday(),
            'active_online' => $this->getOnlineUsers(),
            'growth_rate' => $this->getUserGrowthRate()
        ];
    }
    private function getSystemMetrics() {
        return [
            'cpu_usage' => sys_getloadavg()[0],
            'memory_usage' => memory_get_usage(true) / 1024 / 1024,
            'db_connections' => $this->db->getAttribute(PDO::ATTR_CONNECTION_STATUS)
        ];
    }
    private function getTodaySales() {
        $stmt = $this->db->query("SELECT SUM(total_amount) FROM orders WHERE DATE(created_at) = CURDATE()");
        return (float)$stmt->fetchColumn();
    }
    private function getTodayOrders() {
        $stmt = $this->db->query("SELECT COUNT(*) FROM orders WHERE DATE(created_at) = CURDATE()");
        return (int)$stmt->fetchColumn();
    }
    private function getWeeklySales() {
        $stmt = $this->db->query("SELECT DAYOFWEEK(created_at) as day, SUM(total_amount) as sales FROM orders WHERE created_at >= DATE_SUB(CURDATE(), INTERVAL 7 DAY) GROUP BY DAYOFWEEK(created_at)");
        return $stmt->fetchAll(PDO::FETCH_ASSOC);
    }
    private function getMonthlySales() {
        $stmt = $this->db->query("SELECT DATE_FORMAT(created_at, '%Y-%m') as month, SUM(total_amount) as sales FROM orders WHERE created_at >= DATE_SUB(CURDATE(), INTERVAL 6 MONTH) GROUP BY DATE_FORMAT(created_at, '%Y-%m')");
        return $stmt->fetchAll(PDO::FETCH_ASSOC);
    }
    private function getActiveUsersToday() {
        $stmt = $this->db->query("SELECT COUNT(DISTINCT user_id) FROM user_activity WHERE DATE(created_at) = CURDATE()");
        return (int)$stmt->fetchColumn();
    }
    private function getOnlineUsers() {
        $cacheKey = 'dashboard:online_users';
        $online = $this->cache->get($cacheKey);
        return $online !== false ? (int)$online : 0;
    }
    private function getUserGrowthRate() {
        $today = $this->getUserCountToday();
        $monthAgo = $this->getUserCountMonthAgo();
        if ($monthAgo > 0) {
            return (($today - $monthAgo) / $monthAgo) * 100;
        }
        return 0;
    }
    private function getUserCountToday() {
        $stmt = $this->db->query("SELECT COUNT(*) FROM users WHERE DATE(created_at) = CURDATE()");
        return (int)$stmt->fetchColumn();
    }
    private function getUserCountMonthAgo() {
        $stmt = $this->db->query("SELECT COUNT(*) FROM users WHERE DATE(created_at) = DATE_SUB(CURDATE(), INTERVAL 30 DAY)");
        return (int)$stmt->fetchColumn();
    }
    private function getFallbackRealtimeData() {
        // 从数据库获取最近的实时数据
        $data = [
            'online_users' => 0,
            'new_orders' => 0,
            'sales_amount' => 0,
            'visitors' => 0,
            'timestamp' => time(),
            'source' => 'fallback'
        ];
        try {
            $stmt = $this->db->query("
                SELECT 
                    (SELECT COUNT(*) FROM active_sessions WHERE online = 1) as online_users,
                    (SELECT COUNT(*) FROM orders WHERE created_at >= DATE_SUB(NOW(), INTERVAL 5 MINUTE)) as new_orders,
                    (SELECT SUM(total_amount) FROM orders WHERE created_at >= DATE_SUB(NOW(), INTERVAL 5 MINUTE)) as sales_amount,
                    (SELECT COUNT(*) FROM visitors WHERE last_seen >= DATE_SUB(NOW(), INTERVAL 5 MINUTE)) as visitors
            ");
            $result = $stmt->fetch(PDO::FETCH_ASSOC);
            if ($result) {
                $data = array_merge($data, $result);
            }
        } catch (Exception $e) {
            // 记录错误日志
            error_log("Error getting fallback data: " . $e->getMessage());
        }
        return $data;
    }
    private function jsonResponse(Response $response, $data) {
        $response->getBody()->write(json_encode([
            'status' => 'success',
            'data' => $data
        ]));
        return $response->withHeader('Content-Type', 'application/json');
    }
    public function run() {
        $this->app->run();
    }
}
// 启动API服务
$api = new DashboardAPI();
$api->run();

数据提供者(数据源)

<?php
class DataProvider {
    private $db;
    private $cache;
    private $messageQueue;
    public function __construct($db, $cache, $messageQueue) {
        $this->db = $db;
        $this->cache = $cache;
        $this->messageQueue = $messageQueue;
    }
    public function updateRealtimeData() {
        // 更新实时数据到缓存
        $data = [
            'online_users' => $this->getOnlineUsers(),
            'new_orders' => $this->getRecentOrders(),
            'sales_amount' => $this->getRecentSales(),
            'visitors' => $this->getRecentVisitors(),
            'timestamp' => time()
        ];
        $this->cache->setex('dashboard:realtime', 5, json_encode($data));
        // 发布到消息队列
        $this->messageQueue->publish('dashboard', 'realtime', $data);
    }
    private function getOnlineUsers() {
        $stmt = $this->db->query("SELECT COUNT(*) FROM active_sessions WHERE last_activity > DATE_SUB(NOW(), INTERVAL 5 MINUTE)");
        return (int)$stmt->fetchColumn();
    }
    private function getRecentOrders() {
        $stmt = $this->db->query("SELECT COUNT(*) FROM orders WHERE created_at > DATE_SUB(NOW(), INTERVAL 5 MINUTE)");
        return (int)$stmt->fetchColumn();
    }
    private function getRecentSales() {
        $stmt = $this->db->query("SELECT COALESCE(SUM(total_amount), 0) FROM orders WHERE created_at > DATE_SUB(NOW(), INTERVAL 5 MINUTE)");
        return (float)$stmt->fetchColumn();
    }
    private function getRecentVisitors() {
        $stmt = $this->db->query("SELECT COUNT(*) FROM visitors WHERE last_seen > DATE_SUB(NOW(), INTERVAL 5 MINUTE)");
        return (int)$stmt->fetchColumn();
    }
    public function startUpdater() {
        // 使用定时任务更新数据
        while (true) {
            $this->updateRealtimeData();
            sleep(5); // 每5秒更新一次
        }
    }
}

消息队列配置(使用Redis)

<?php
use Predis\Client;
class MessageQueue {
    private $redis;
    private $subscriptions;
    public function __construct(Client $redis) {
        $this->redis = $redis;
        $this->subscriptions = [];
    }
    public function publish($channel, $event, $data) {
        $message = json_encode([
            'event' => $event,
            'data' => $data,
            'timestamp' => time()
        ]);
        $this->redis->publish($channel, $message);
    }
    public function subscribe($channel, callable $callback) {
        $this->subscriptions[$channel][] = $callback;
    }
    public function listen() {
        $loop = new \React\EventLoop\StreamSelectLoop();
        $client = new \React\Socket\SocketClient('127.0.0.1:6379');
        $this->redis->psubscribe(['dashboard_*'], function($redis, $pattern, $channel, $message) {
            foreach ($this->subscriptions[$channel] ?? [] as $callback) {
                $callback(json_decode($message, true));
            }
        });
    }
}

数据库表结构设计

-- 用户表
CREATE TABLE users (
    id INT PRIMARY KEY AUTO_INCREMENT,
    username VARCHAR(50) NOT NULL UNIQUE,
    email VARCHAR(100) NOT NULL,
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    last_login TIMESTAMP NULL
);
-- 订单表
CREATE TABLE orders (
    id INT PRIMARY KEY AUTO_INCREMENT,
    user_id INT NOT NULL,
    total_amount DECIMAL(10,2) NOT NULL,
    status VARCHAR(20) DEFAULT 'pending',
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    paid_at TIMESTAMP NULL,
    FOREIGN KEY (user_id) REFERENCES users(id)
);
-- 访问者表
CREATE TABLE visitors (
    id INT PRIMARY KEY AUTO_INCREMENT,
    session_id VARCHAR(64) NOT NULL UNIQUE,
    ip_address VARCHAR(45),
    page_url VARCHAR(255),
    visited_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    last_seen TIMESTAMP NULL
);
-- 实时统计数据表
CREATE TABLE realtime_stats (
    id INT PRIMARY KEY AUTO_INCREMENT,
    metric_name VARCHAR(50) NOT NULL,
    metric_value DECIMAL(15,2) NOT NULL,
    recorded_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    INDEX idx_metric_name_time (metric_name, recorded_at)
);
-- 用户活跃记录表
CREATE TABLE user_activity (
    id INT PRIMARY KEY AUTO_INCREMENT,
    user_id INT NOT NULL,
    action VARCHAR(50) NOT NULL,
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    FOREIGN KEY (user_id) REFERENCES users(id)
);

部署与配置

启动脚本

<?php
// start_server.php - 主服务启动脚本
require_once __DIR__ . '/vendor/autoload.php';
use Workerman\Worker;
// 启动WebSocket服务
$realtimeServer = new RealtimeDataServer();
Worker::runAll();
// 启动RESTful API
$api = new DashboardAPI();

Nginx 配置

server {
    listen 80;
    server_name dashboard.example.com;
    # RESTful API
    location /api {
        proxy_pass http://127.0.0.1:8080;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header Host $http_host;
    }
    # WebSocket
    location /ws {
        proxy_pass http://127.0.0.1:2345;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";
        proxy_set_header Host $http_host;
        proxy_set_header X-Real-IP $remote_addr;
    }
    # 静态文件
    location / {
        root /var/www/dashboard/frontend;
        index index.html;
    }
}

Docker 部署配置

# Dockerfile
FROM php:8.0-apache
RUN apt-get update && apt-get install -y \
    libpq-dev \
    redis-server \
    supervisor \
    && docker-php-ext-install pdo pdo_mysql \
    && docker-php-ext-enable pdo pdo_mysql
WORKDIR /var/www/html
COPY . /var/www/html
RUN curl -sS https://getcomposer.org/installer | php \
    && mv composer.phar /usr/local/bin/composer \
    && composer install --no-dev
# 安装 Workerman
RUN composer require workerman/workerman \ 
    && composer require slim/slim:^4.0
# 启动脚本
COPY docker-entrypoint.sh /docker-entrypoint.sh
RUN chmod +x /docker-entrypoint.sh
EXPOSE 80 2345 6379
CMD ["/docker-entrypoint.sh"]
# docker-compose.yml
version: '3.8'
services:
  db:
    image: mysql:8.0
    environment:
      MYSQL_ROOT_PASSWORD: rootpassword
      MYSQL_DATABASE: dashboard
      MYSQL_USER: dashboard
      MYSQL_PASSWORD: dashboard123
    volumes:
      - db_data:/var/lib/mysql
    ports:
      - "3306:3306"
  redis:
    image: redis:alpine
    ports:
      - "6379:6379"
  app:
    build: .
    ports:
      - "80:80"
      - "2345:2345"
    environment:
      DB_HOST: db
      DB_NAME: dashboard
      DB_USER: dashboard
      DB_PASS: dashboard123
      REDIS_HOST: redis
      CACHE_TTL: 300
    depends_on:
      - db
      - redis
    volumes:
      - ./:/var/www/html
    restart: unless-stopped
  workers:
    build: .
    command: php websocket_server.php
    environment:
      DB_HOST: db
      REDIS_HOST: redis
    depends_on:
      - app
      - db
      - redis
    volumes:
      - ./:/var/www/html
    restart: unless-stopped
volumes:
  db_data:

性能优化建议

  • 使用Redis/APCu缓存减少数据库查询
  • 数据库索引优化 - 定期运行EXPLAIN分析查询
  • 启用OPcache提高PHP执行效率
  • 使用CDN缓存静态资源
  • 数据库读写分离 - 主库负责写入,从库负责读取
  • 使用连接池管理数据库连接
  • 消息队列解耦数据产生和处理逻辑

监控与日志

<?php
class DashboardLogger {
    private $logDir;
    private $file;
    public function __construct($logDir = '/var/log/dashboard') {
        $this->logDir = $logDir;
        if (!is_dir($logDir)) {
            mkdir($logDir, 0777, true);
        }
        $this->file = $logDir . '/dashboard_' . date('Y-m-d') . '.log';
    }
    public function log($message, $level = 'INFO') {
        $logEntry = sprintf(
            "[%s] [%s] %s\n",
            date('Y-m-d H:i:s'),
            $level,
            $message
        );
        file_put_contents($this->file, $logEntry, FILE_APPEND);
    }
    public function error($message) {
        $this->log($message, 'ERROR');
    }
    public function warning($message) {
        $this->log($message, 'WARNING');
    }
    public function info($message) {
        $this->log($message, 'INFO');
    }
}

这个完整的PHP实时数据大屏后端方案提供了:

  • 实时数据推送(WebSocket)
  • RESTful API
  • 数据缓存
  • 消息队列
  • 数据库设计
  • 部署配置
  • 日志监控

您可以根据实际需求调整数据源、推送频率和接口设计。

抱歉,评论功能暂时关闭!