为了使用 WebSocket、MySQL、Go 的 Gin 框架和 GORM 实现用户实时统计大屏概览,以下是分步指南:
mkdir realtime-dashboard && cd realtime-dashboard
go mod init realtime-dashboard
go get -u github.com/gin-gonic/gin
go get -u gorm.io/gorm
go get -u gorm.io/driver/mysql
go get -u github.com/gorilla/websocket
-- 用户表
CREATE TABLE users (
id INT PRIMARY KEY AUTO_INCREMENT,
name VARCHAR(100),
created_at DATETIME
);
-- 用户活动表
CREATE TABLE user_activities (
id INT PRIMARY KEY AUTO_INCREMENT,
user_id INT,
activity VARCHAR(50),
created_at DATETIME
);
// models.go
package main
import "time"
type User struct {
ID uint `gorm:"primaryKey"`
Name string
CreatedAt time.Time
}
type UserActivity struct {
ID uint `gorm:"primaryKey"`
UserID uint
Activity string
CreatedAt time.Time
}
// websocket.go
package main
import (
"net/http"
"sync"
"github.com/gin-gonic/gin"
"github.com/gorilla/websocket"
)
var upgrader = websocket.Upgrader{
CheckOrigin: func(r *http.Request) bool { return true },
}
var clients = make(map[*websocket.Conn]struct{})
var clientsMutex sync.Mutex
func handleWebSocket(c *gin.Context) {
conn, err := upgrader.Upgrade(c.Writer, c.Request, nil)
if err != nil {
return
}
defer conn.Close()
// 注册客户端
clientsMutex.Lock()
clients[conn] = struct{}{}
clientsMutex.Unlock()
// 保持连接
for {
if _, _, err := conn.ReadMessage(); err != nil {
clientsMutex.Lock()
delete(clients, conn)
clientsMutex.Unlock()
break
}
}
}
func broadcastStats(stats map[string]interface{}) {
clientsMutex.Lock()
defer clientsMutex.Unlock()
for client := range clients {
if err := client.WriteJSON(stats); err != nil {
client.Close()
delete(clients, client)
}
}
}
// stats.go
package main
import (
"time"
"gorm.io/gorm"
)
func getRealTimeStats(db *gorm.DB) map[string]interface{} {
var stats = make(map[string]interface{})
// 在线用户数(最近5分钟活跃)
var onlineUsers int64
fiveMinutesAgo := time.Now().Add(-5 * time.Minute)
db.Model(&UserActivity{}).
Where("activity = 'login' AND created_at >= ?", fiveMinutesAgo).
Count(&onlineUsers)
// 当日新增用户
var todayNewUsers int64
today := time.Now().Truncate(24 * time.Hour)
db.Model(&User{}).
Where("created_at >= ?", today).
Count(&todayNewUsers)
stats["online_users"] = onlineUsers
stats["today_new_users"] = todayNewUsers
return stats
}
// handlers.go
package main
import (
"github.com/gin-gonic/gin"
)
type LoginRequest struct {
UserID uint `json:"user_id"`
}
func loginHandler(c *gin.Context, db *gorm.DB) {
var req LoginRequest
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(400, gin.H{"error": "Invalid request"})
return
}
// 记录登录活动
activity := UserActivity{
UserID: req.UserID,
Activity: "login",
CreatedAt: time.Now(),
}
db.Create(&activity)
// 立即广播最新统计
stats := getRealTimeStats(db)
broadcastStats(stats)
c.JSON(200, gin.H{"status": "success"})
}
// main.go
package main
import (
"time"
"gorm.io/driver/mysql"
"gorm.io/gorm"
"github.com/gin-gonic/gin"
)
func main() {
// 初始化数据库
dsn := "user:password@tcp(localhost:3306)/dbname?charset=utf8mb4&parseTime=True&loc=Local"
db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{})
if err != nil {
panic("Failed to connect database")
}
db.AutoMigrate(&User{}, &UserActivity{})
// 初始化 Gin
r := gin.Default()
// 路由配置
r.GET("/ws", handleWebSocket)
r.POST("/api/login", func(c *gin.Context) { loginHandler(c, db) })
// 静态页面服务
r.StaticFile("/", "./public/index.html")
// 定时推送任务
go func() {
ticker := time.NewTicker(5 * time.Second)
for range ticker.C {
stats := getRealTimeStats(db)
broadcastStats(stats)
}
}()
// 启动服务
r.Run(":8080")
}
public/index.html
)DOCTYPE html>
<html>
<head>
<title>实时统计大屏title>
<script src="https://cdn.jsdelivr.net/npm/echarts@5.3.0/dist/echarts.min.js">script>
head>
<body>
<div id="dashboard" style="width: 100%; height: 100vh;">div>
<script>
let chart = echarts.init(document.getElementById('dashboard'));
let option = {
title: { text: '实时用户统计', left: 'center' },
tooltip: { trigger: 'axis' },
legend: { data: ['在线用户', '今日新增'], top: 30 },
xAxis: { type: 'category', data: ['统计指标'] },
yAxis: { type: 'value' },
series: [
{ name: '在线用户', type: 'bar', data: [0] },
{ name: '今日新增', type: 'line', data: [0] }
]
};
// WebSocket 连接
const ws = new WebSocket('ws://' + window.location.host + '/ws');
ws.onmessage = (event) => {
const stats = JSON.parse(event.data);
option.series[0].data = [stats.online_users];
option.series[1].data = [stats.today_new_users];
chart.setOption(option);
};
script>
body>
html>
go run main.go
curl -X POST http://localhost:8080/api/login \
-H "Content-Type: application/json" \
-d '{"user_id": 1}'
打开浏览器访问 http://localhost:8080
,实时数据将每5秒更新。
通过上述步骤,即可构建一个基于 WebSocket 的实时用户统计大屏系统。