本文目录导读:

这是一个比较宽泛的问题,我为你整理了两套方案:一套是适合学习和演示的简易内存看板,另一套是适合生产环境的Spring Boot + WebSocket 实时看板,你可以根据需要选择。
简易内存看板(零依赖,适合快速验证)
这个方案不依赖数据库和外部中间件,直接在 Java 内存中维护数据,通过一个 HTTP 接口返回 JSON,前端用简单 HTML 轮询展示。
后端核心代码 (一个 Servlet)
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Random;
@WebServlet("/api/dashboard")
public class DashboardServlet extends HttpServlet {
// 模拟数据存储 (实际项目替换为数据库/缓存)
private static final Map<String, Object> data = new LinkedHashMap<>();
private static final Random random = new Random();
@Override
public void init() throws ServletException {
// 初始化一些模拟数据
data.put("title", "系统运行看板");
data.put("status", "正常");
data.put("uptime", "0d 0h 0m");
updateMetrics();
}
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException {
resp.setContentType("application/json;charset=UTF-8");
resp.setHeader("Access-Control-Allow-Origin", "*"); // 允许跨域访问
// 每次请求更新一次模拟数据(模拟实时变化)
updateMetrics();
PrintWriter out = resp.getWriter();
out.write(toJson());
out.flush();
}
// 模拟更新指标
private void updateMetrics() {
data.put("userCount", 100 + random.nextInt(900));
data.put("orderCount", 50 + random.nextInt(950));
data.put("errorCount", random.nextInt(30));
data.put("cpuUsage", String.format("%.1f", 20 + random.nextDouble() * 60));
data.put("memoryUsage", String.format("%.1f", 30 + random.nextDouble() * 50));
data.put("timestamp", LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));
}
// 手动拼接简单JSON(生产用Jackson/Gson)
private String toJson() {
StringBuilder sb = new StringBuilder("{");
int i = 0;
for (Map.Entry<String, Object> entry : data.entrySet()) {
if (i++ > 0) sb.append(",");
sb.append("\"").append(entry.getKey()).append("\":");
Object val = entry.getValue();
if (val instanceof Number) {
sb.append(val);
} else {
sb.append("\"").append(val).append("\"");
}
}
sb.append("}");
return sb.toString();
}
}
前端看板 (一个HTML文件)
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">简易服务看板</title>
<style>
body { font-family: Arial; background: #1a1a2e; color: #fff; padding: 20px; }
.card { background: #16213e; border-radius: 10px; padding: 20px; margin: 10px; display: inline-block; min-width: 150px; }
.value { font-size: 2.5em; font-weight: bold; color: #00d2ff; }
.label { font-size: 0.9em; color: #aaa; }
</style>
</head>
<body>
<h1 id="title">加载中...</h1>
<div id="timestamp" style="color: #888;"></div>
<div id="cards">
<div class="card"><div class="label">用户数</div><div class="value" id="userCount">-</div></div>
<div class="card"><div class="label">订单数</div><div class="value" id="orderCount">-</div></div>
<div class="card"><div class="label">错误数</div><div class="value" id="errorCount" style="color: #ff4757;">-</div></div>
<div class="card"><div class="label">CPU使用率</div><div class="value" id="cpuUsage">-</div></div>
<div class="card"><div class="label">内存使用率</div><div class="value" id="memoryUsage">-</div></div>
</div>
<div style="margin-top:20px;color:#555;">每2秒自动刷新...</div>
<script>
function loadDashboard() {
fetch('/api/dashboard')
.then(res => res.json())
.then(data => {
document.getElementById('title').textContent = data.title + ' | 状态:' + data.status;
document.getElementById('timestamp').textContent = '更新时间:' + data.timestamp;
document.getElementById('userCount').textContent = data.userCount;
document.getElementById('orderCount').textContent = data.orderCount;
document.getElementById('errorCount').textContent = data.errorCount;
document.getElementById('cpuUsage').textContent = data.cpuUsage + '%';
document.getElementById('memoryUsage').textContent = data.memoryUsage + '%';
})
.catch(err => console.error('看板加载失败:', err));
}
// 首次加载
loadDashboard();
// 每2秒轮询更新
setInterval(loadDashboard, 2000);
</script>
</body>
</html>
运行方式:将后端代码放入一个 Java Web 项目(Tomcat/嵌入式服务器),前端 HTML 放在 webapp 目录或静态资源目录下,访问 http://localhost:8080/你的项目名/ 即可看到看板。
Spring Boot + WebSocket 实时看板(生产级推荐)
这个方案适合需要高实时性和后端主动推送的场景(如运维监控大屏、股票行情、设备状态等)。
项目依赖 (Maven)
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-websocket</artifactId>
</dependency>
</dependencies>
WebSocket 配置
import org.springframework.context.annotation.Configuration;
import org.springframework.web.socket.config.annotation.EnableWebSocket;
import org.springframework.web.socket.config.annotation.WebSocketConfigurer;
import org.springframework.web.socket.config.annotation.WebSocketHandlerRegistry;
@Configuration
@EnableWebSocket
public class WebSocketConfig implements WebSocketConfigurer {
@Override
public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
// 注册 WebSocket 端点,前端连接 ws://host:port/ws/dashboard
registry.addHandler(new DashboardHandler(), "/ws/dashboard")
.setAllowedOrigins("*");
}
}
WebSocket 处理器
import org.springframework.web.socket.CloseStatus;
import org.springframework.web.socket.TextMessage;
import org.springframework.web.socket.WebSocketSession;
import org.springframework.web.socket.handler.TextWebSocketHandler;
import java.io.IOException;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.Map;
import java.util.Random;
import java.util.concurrent.CopyOnWriteArraySet;
public class DashboardHandler extends TextWebSocketHandler {
// 保存所有连接的客户端(线程安全集合)
private static final CopyOnWriteArraySet<WebSocketSession> sessions = new CopyOnWriteArraySet<>();
private static final Random random = new Random();
@Override
public void afterConnectionEstablished(WebSocketSession session) throws Exception {
sessions.add(session);
System.out.println("新客户端连接,当前在线:" + sessions.size());
// 连接后立即发送一次初始数据
session.sendMessage(new TextMessage(buildDashboardJson()));
}
@Override
public void afterConnectionClosed(WebSocketSession session, CloseStatus status) throws Exception {
sessions.remove(session);
// 实际项目中,可在这里停止为该客户端单独推送的线程
}
@Override
protected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception {
// 可以处理客户端发来的消息,开启告警”、“关闭通知”等
}
// 模拟构建看板数据
private String buildDashboardJson() {
// 这里可以用Jackson/Gson序列化
return "{"
+ "\"time\":\"" + LocalDateTime.now().format(DateTimeFormatter.ofPattern("HH:mm:ss")) + "\","
+ "\"users\":" + (100 + random.nextInt(900)) + ","
+ "\"orders\":" + (50 + random.nextInt(950)) + ","
+ "\"errors\":" + random.nextInt(30) + ","
+ "\"cpu\":\"" + String.format("%.1f", 20 + random.nextDouble() * 60) + "%\""
+ "}";
}
// 提供一个方法,供定时任务调用,向所有客户端推送数据
public static void broadcast() {
String json = new DashboardHandler().buildDashboardJson(); // 实际应复用
// 实际项目中,可以注入一个Bean来持有JSON构建逻辑
for (WebSocketSession session : sessions) {
if (session.isOpen()) {
try {
session.sendMessage(new TextMessage(json));
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
定时推送任务(模拟数据源变化)
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
@Component
@EnableScheduling
public class DashboardScheduler {
// 每2秒推送一次看板数据到所有WebSocket客户端
@Scheduled(fixedRate = 2000)
public void pushDashboardData() {
DashboardHandler.broadcast();
}
}
前端看板 (HTML + WebSocket)
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">实时服务看板 (WebSocket)</title>
<style>
body { background: #0f0f23; color: #ccc; font-family: 'Segoe UI', Tahoma, sans-serif; padding: 20px; }
.card {
background: #1e1e3a; border: 1px solid #3a3a5c; border-radius: 8px;
padding: 20px; margin: 10px; display: inline-block; min-width: 120px;
box-shadow: 0 4px 6px rgba(0,0,0,0.3);
}
.value { font-size: 2em; font-weight: bold; color: #00ff88; }
.label { font-size: 0.9em; color: #888; margin-top: 5px; }
.alert { color: #ff5555; }
</style>
</head>
<body>
<h1>🚀 系统实时看板</h1>
<div id="clock" style="color: #666; margin-bottom: 15px;">上次更新: --:--:--</div>
<div id="dashboard">
<div class="card"><div class="value" id="users">-</div><div class="label">👤 在线用户</div></div>
<div class="card"><div class="value" id="orders">-</div><div class="label">📦 今日订单</div></div>
<div class="card"><div class="value" id="errors" class="alert">-</div><div class="label">⚠️ 错误数</div></div>
<div class="card"><div class="value" id="cpu">-</div><div class="label">⚙️ CPU</div></div>
</div>
<div style="margin-top:20px;color:#555;">连接状态: <span id="status">未连接</span></div>
<script>
// 生产地址应与后端保持同域或配置跨域
const wsUrl = 'ws://' + window.location.host + '/ws/dashboard';
let socket = new WebSocket(wsUrl);
let reconnectTimer = null;
let isConnected = false;
function connect() {
socket = new WebSocket(wsUrl);
socket.onopen = () => {
console.log('WebSocket 已连接');
document.getElementById('status').textContent = '已连接';
document.getElementById('status').style.color = '#00ff88';
isConnected = true;
clearTimeout(reconnectTimer);
};
socket.onmessage = (event) => {
const data = JSON.parse(event.data);
document.getElementById('clock').textContent = '上次更新: ' + data.time;
document.getElementById('users').textContent = data.users;
document.getElementById('orders').textContent = data.orders;
document.getElementById('errors').textContent = data.errors;
document.getElementById('cpu').textContent = data.cpu;
// 错误数过高时高亮
if (data.errors > 20) {
document.getElementById('errors').style.color = '#ff5555';
} else {
document.getElementById('errors').style.color = '#00ff88';
}
};
socket.onclose = () => {
console.log('WebSocket 断开');
document.getElementById('status').textContent = '断开,即将重连...';
document.getElementById('status').style.color = '#ff5555';
isConnected = false;
// 自动重连
reconnectTimer = setTimeout(() => {
connect();
}, 3000);
};
socket.onerror = (err) => {
console.error('WebSocket 错误:', err);
socket.close();
};
}
// 初始连接
connect();
// 页面关闭时主动断开
window.addEventListener('beforeunload', () => {
socket.close();
clearTimeout(reconnectTimer);
});
</script>
</body>
</html>
运行方式:使用 mvn spring-boot:run 启动,访问 http://localhost:8080/ 下放置的 HTML 文件即可看到实时推送的数据。
两种方案对比与适用场景
| 特性 | 方案一(HTTP轮询) | 方案二(WebSocket推送) |
|---|---|---|
| 实时性 | 2秒级(受轮询间隔限制) | 毫秒级(后端主动推送) |
| 服务器压力 | 较高(每次请求都需要建立HTTP连接、处理请求) | 较低(长连接,仅在数据变化时推送) |
| 代码复杂度 | 简单,零依赖 | 中等,需要WebSocket支持 |
| 适用场景 | 简单看板、数据更新不频繁、并发量小 | 实时监控大屏、金融行情、在线大屏、游戏排行榜 |
| 浏览器兼容性 | 全部浏览器,无兼容问题 | 现代浏览器 (IE10+) |
进阶建议
- 数据持久化:实际项目中,看板数据应来自数据库、Redis 或 Kafka 等实时流,而不是内存中的随机数。
- 性能优化:如果看板数据量大且更新频繁,可以使用图表库(如 ECharts、Chart.js)来替换数字卡片,并配合WebWorker或requestAnimationFrame进行平滑渲染。
- 安全增强:生产环境中 WebSocket 端点应添加鉴权(如 Token 校验),避免未授权访问,HTTP 接口建议添加限流和缓存。
- 集群部署:若应用多实例部署,WebSocket 推送需要引入消息中间件(如 Redis Pub/Sub)来实现跨实例广播,HTTP 轮询则相对简单一些。
选择哪种方案,主要取决于你的实时性需求、服务器资源以及未来扩展的规划,如果你描述得更具体(比如是运维看板、业务监控还是大屏展示),我可以给出更具针对性的架构建议。