SpringBoot 集成 WebSocket 实现双屏实时消息互推(零基础可直接用)

前言

在项目开发中,实时消息推送是高频需求,比如双屏联动、大屏监控、在线聊天、订单状态推送等场景。WebSocket 作为 HTML5 的核心特性,实现了浏览器与服务器的全双工双向通信,相比传统的轮询 / 长轮询方式,大幅降低服务端压力,提升实时性和用户体验。

本文以SpringBoot 2.7.x (最稳定版本,零基础友好)为基础,手把手教大家从 0 到 1 集成 WebSocket,实现左屏 / 右屏双端实时消息互推功能。全程代码可直接复制使用,兼顾Jar 包内嵌 Tomcat和War 包外部 Tomcat两种部署方式,解决部署冲突问题,同时完善异常处理、连接管理、心跳检测等生产级细节,小白跟着步骤走就能跑通。

本文核心优势

  1. 零基础友好:代码全复制、步骤全拆解,无复杂配置,新手直接用;
  2. 部署无坑:自动适配 Jar/War 包部署,无需手动修改代码,避免容器冲突;
  3. 生产级健壮:完善的异常处理、失效连接清理、心跳检测,防止内存泄漏;
  4. 支持多端登录:同一用户多设备连接,所有端都能收到消息,避免 Session 覆盖;
  5. 双测试方式:在线工具快速验证 + 自定义 HTML 页面,前端后端全打通;
  6. 配套全补全:统一响应类、启动类改造等缺失代码全部补全,无需额外找依赖。

一、环境准备(新手必看)

1.1 基础开发环境

无需高版本,基础环境即可运行,推荐搭配:

  • JDK:1.8(兼容性最好,无版本问题)
  • SpringBoot:2.7.10(本文统一版本,避免依赖冲突)
  • Maven:3.6.0+
  • 开发工具:IDEA/Eclipse(推荐 IDEA,自带 Maven 管理)
  • 测试工具:浏览器、WebSocket 在线测试工具

1.2 核心依赖

在pom.xml中引入 SpringBoot 官方的 WebSocket Starter 依赖,无需额外引入其他包,Spring 已做封装:

bash 复制代码
<!-- SpringBoot集成WebSocket核心依赖 -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-websocket</artifactId>
</dependency>
<!-- 可选:SpringMVC基础依赖(项目已引入可忽略) -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>

二、核心配置类(解决 Jar/War 部署兼容)

SpringBoot 中使用@ServerEndpoint注解实现 WebSocket 时,必须注册ServerEndpointExporter 让 Spring 扫描并管理 WebSocket 端点,但内嵌 Tomcat(Jar 包)和外部 Tomcat(War 包) 对该 Bean 的要求不同:

Jar 包部署(内嵌 Tomcat):需要手动创建ServerEndpointExporter Bean;

War 包部署(外部 Tomcat):由容器自身初始化 WebSocket,手动创建会导致 Bean 冲突。

因此我们通过Spring 条件注解@Conditional 实现动态判断,自动适配两种部署方式。

  • Jar 包部署(内嵌 Tomcat):需要手动创建ServerEndpointExporter Bean;
  • War 包部署(外部 Tomcat):由容器自身初始化 WebSocket,手动创建会导致 Bean 冲突。

2.1 自定义条件判断类

创建包com.tydt.framework.config,编写WebSocketAutoWired类,实现Condition接口,核心逻辑是判断是否为内嵌 Tomcat 环境:

bash 复制代码
/**
 * All rights reserved.
 */
package com.itl.framework.config;

import org.springframework.context.annotation.Condition;
import org.springframework.context.annotation.ConditionContext;
import org.springframework.core.type.AnnotatedTypeMetadata;
import org.springframework.util.ClassUtils;

/**
 * 类描述:WebSocket条件判断类,控制ServerEndpointExporter是否创建
 * jar包部署(内嵌Tomcat)返回true,war包部署(外部Tomcat)返回false
 * @author itl
 * @version 1.0
 * 
 * 修订历史:
 * 日期			修订者		修订描述
 * 2026-02-05	xxx		修复matches方法固定返回false问题,实现jar/war包部署动态判断
 */
public class WebSocketAutoWired implements Condition {

    /**
     * 核心判断方法:jar包部署(内嵌Tomcat)为true; war包部署(外部Tomcat)为false
     */
    @Override
    public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
        // 判断类加载器中是否存在内嵌Tomcat核心类 → 存在=jar包部署,不存在=war包部署
        return ClassUtils.isPresent(
                "org.apache.catalina.startup.Tomcat",
                context.getClassLoader()
        );
    }
}

2.2 WebSocket 核心配置类

编写WebSocketConfig类,通过@Conditional关联上面的条件判断类,动态创建ServerEndpointExporter:

bash 复制代码
/**
 *
 * All rights reserved.
 */
package com.itl.framework.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Conditional;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.socket.server.standard.ServerEndpointExporter;

/**
 * 类描述:WebSocket核心配置类
 * 动态创建ServerEndpointExporter,解决内嵌Tomcat/外部Tomcat部署兼容问题
 * @author itl
 * @version 1.0
 * 新增条件注解,适配内嵌/外部Tomcat
 */
@Configuration
public class WebSocketConfig {

    /**
     * 注册WebSocket端点处理器,仅内嵌Tomcat(jar包)时创建
     * 外部Tomcat(war包)由容器自身初始化,无需手动创建
     */
    @Bean
    @Conditional(WebSocketAutoWired.class)
    public ServerEndpointExporter serverEndpointExporter() {
        return new ServerEndpointExporter();
    }
}

核心原理:项目启动时,Spring 会根据WebSocketAutoWired的matches方法返回值,动态决定是否创建ServerEndpointExporter Bean,从根本上解决 Jar/War 部署的冲突问题。

三、WebSocket 工具类(连接管理 + 消息发送)

创建工具类WebSocketUtils,用于统一管理客户端 Session 连接、发送消息、移除连接等操作,使用ConcurrentHashMap 保证多线程下的线程安全,同时支持同一用户多端连接(避免 Session 被覆盖 )。

包路径:com.itl.common.utils

bash 复制代码
/**
 * All rights reserved.
 */
package com.itl.common.utils;

import java.util.Map;
import java.util.Set;
import java.util.Iterator;
import java.util.concurrent.ConcurrentHashMap;
import javax.websocket.Session;

/**
 * 类描述:WebSocket工具类,管理客户端Session和消息发送
 * @author itl
 * 
 * 修订历史:
 * 日期			修订者		修订描述
 * 优化Session管理,支持单用户多连接;增加异常处理和Session有效性判断
 */
public class WebSocketUtils {

    // 存储客户端连接:key=用户ID,value=该用户的所有Session连接(支持多端登录)
    public static Map<String, Set<Session>> clients = new ConcurrentHashMap<>();

    /**
     * 添加客户端连接
     * @param userId 用户唯一标识
     * @param session 客户端会话
     */
    public static void add(String userId, Session session) {
        // 不存在则创建新的Set,存在则直接添加;ConcurrentHashMap.newKeySet()保证线程安全
        clients.computeIfAbsent(userId, k -> ConcurrentHashMap.newKeySet()).add(session);
    }

    /**
     * 处理客户端发送的消息(可根据业务自定义)
     * @param userId 发送消息的用户ID
     * @param message 消息内容
     */
    public static void receive(String userId, String message) {
        // 示例:双屏联动,左屏消息推右屏,右屏消息推左屏
        if ("left".equals(userId)) {
            sendMessage("right", "左屏推送:" + message);
        } else if ("right".equals(userId)) {
            sendMessage("left", "右屏推送:" + message);
        }
        System.out.println("收到用户[" + userId + "]的消息:" + message);
    }

    /**
     * 精准移除某用户的某一个Session连接(连接关闭/异常时调用)
     * @param userId 用户唯一标识
     * @param session 要移除的会话
     */
    public static void remove(String userId, Session session) {
        Set<Session> sessions = clients.get(userId);
        if (sessions != null) {
            sessions.remove(session);
            // 若该用户无任何连接,移除key,避免空集合占用内存
            if (sessions.isEmpty()) {
                clients.remove(userId);
            }
        }
    }

    /**
     * 移除某用户的所有连接
     * @param userId 用户唯一标识
     */
    public static void remove(String userId) {
        clients.remove(userId);
    }

    /**
     * 向指定用户发送消息
     * @param userId 接收消息的用户ID
     * @param message 消息内容
     * @return 成功发送的连接数
     */
    public static int sendMessage(String userId, String message) {
        Set<Session> sessions = clients.get(userId);
        // 无该用户连接,直接返回0
        if (sessions == null || sessions.isEmpty()) {
            return 0;
        }
        int successCount = 0;
        Iterator<Session> it = sessions.iterator();
        while (it.hasNext()) {
            Session session = it.next();
            // 判断Session是否有效(连接未关闭)
            if (!session.isOpen()) {
                it.remove(); // 移除失效Session,避免内存泄漏
                continue;
            }
            try {
                // 异步发送消息(推荐),同步发送使用session.getBasicRemote().sendText(message)
                session.getAsyncRemote().sendText(message);
                successCount++;
            } catch (Exception e) {
                it.remove(); // 发送失败,移除失效Session
                e.printStackTrace(); // 实际项目建议使用日志框架(如Logback/Log4j2)
            }
        }
        // 清理空集合
        if (sessions.isEmpty()) {
            clients.remove(userId);
        }
        return successCount;
    }
}

关键优化点:

  1. 把原有的Map<String, Session>改为Map<String, Set>,支持同一用户多端登录,所有连接都能收到消息;
  2. 增加Session有效性判断(session.isOpen()),避免向失效连接发送消息;
  3. 完善的异常捕获,发送消息失败时自动移除失效 Session,防止内存泄漏;
  4. 提供精准移除(单 Session)和批量移除(全 Session)两种方法,适配不同场景。

四、WebSocket 服务端端点(核心业务处理)

创建WebSocketService类,使用@ServerEndpoint注解定义 WebSocket 服务端地址,通过@OnOpen、@OnMessage、@OnClose、@OnError注解处理 WebSocket 的连接打开、接收消息、连接关闭、连接异常 四大事件,同时通过@Component注解让 Spring 管理该 Bean。

包路径:com.itl.framework.web.service

bash 复制代码
/**
 * All rights reserved.
 */
package com.itl.framework.web.service;

import javax.websocket.OnClose;
import javax.websocket.OnError;
import javax.websocket.OnMessage;
import javax.websocket.OnOpen;
import javax.websocket.Session;
import javax.websocket.server.PathParam;
import javax.websocket.server.ServerEndpoint;
import org.springframework.stereotype.Component;
import com.itl.common.utils.WebSocketUtils;

/**
 * 类描述:WebSocket服务端端点,处理客户端连接和事件回调
 * 服务端地址:/connect/{userId}
 * @author itl
 * 修复onError方法参数注解问题;优化连接管理,精准移除Session
 */
@ServerEndpoint("/connect/{userId}") // WebSocket连接地址,{userId}为用户唯一标识
@Component // 必须交给Spring管理,否则无法扫描
public class WebSocketService {

    /**
     * 连接打开事件(客户端首次连接时调用)
     * @param userId 路径参数中的用户ID
     * @param session 客户端会话
     */
    @OnOpen
    public void onOpen(@PathParam("userId") String userId, Session session) {
        System.out.println("【WebSocket】连接打开成功!");
        WebSocketUtils.add(userId, session);
        System.out.println("【WebSocket】用户" + userId + "上线,当前在线人数:" + WebSocketUtils.clients.size());
    }

    /**
     * 接收客户端消息事件
     * @param userId 发送消息的用户ID
     * @param message 客户端发送的消息
     * @return 服务端向客户端的回执消息
     */
    @OnMessage
    public String onMessage(@PathParam("userId") String userId, String message) {
        // 心跳检测(可选),客户端发送&时,服务端回执&,避免连接被断开
        if (message.equals("&")) {
            return "&";
        } else {
            // 调用工具类处理消息
            WebSocketUtils.receive(userId, message);
            return "【服务端回执】已收到消息:" + message;
        }
    }

    /**
     * 连接异常事件(网络中断、客户端崩溃等)
     * 注意:@OnError注解不支持@PathParam参数,会导致参数解析异常
     * @param session 异常的客户端会话
     * @param throwable 异常信息
     */
    @OnError
    public void onError(Session session, Throwable throwable) {
        // 遍历移除该失效的Session
        WebSocketUtils.clients.forEach((userId, sessions) -> {
            WebSocketUtils.remove(userId, session);
        });
        throwable.printStackTrace();
        System.out.println("【WebSocket】连接异常,已移除失效会话");
    }

    /**
     * 连接关闭事件(客户端主动关闭连接)
     * @param userId 断开连接的用户ID
     * @param session 关闭的客户端会话
     */
    @OnClose
    public void onClose(@PathParam("userId") String userId, Session session) {
        System.out.println("【WebSocket】连接关闭成功!");
        WebSocketUtils.remove(userId, session);
        System.out.println("【WebSocket】用户" + userId + "下线,当前在线人数:" + WebSocketUtils.clients.size());
    }
}

核心注意点:

  1. @ServerEndpoint("/connect/{userId}"):定义 WebSocket 的服务端连接地址,前端通过ws://ip:port/connect/left连接左屏,ws://ip:port/connect/right连接右屏;
  2. @Component:必须添加,否则 Spring 无法扫描到该端点,配合配置类的ServerEndpointExporter完成注册;
  3. @OnError方法不支持@PathParam注解:原代码中该注解会导致运行时参数解析异常,直接通过 Session 遍历移除即可;
  4. 增加心跳检测:客户端定时发送&,服务端回执&,避免因长时间无交互导致连接被防火墙 / 服务器断开。

五、测试接口(HTTP 触发 WebSocket 消息推送)

创建 Controller,提供 HTTP 接口,用于通过后端接口触发 WebSocket 消息推送(比如业务系统调用接口向前端推送消息),实现左屏 / 右屏双端消息互推,同时使用AjaxResult返回统一的响应结果(SpringBoot 项目通用)。

bash 复制代码
import com.itl.common.utils.WebSocketUtils;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiImplicitParam;
import io.swagger.annotations.ApiOperation;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

/**
 * WebSocket测试控制器,双屏消息互推接口
 * @author itl
 * @date 2026-02-05
 */
@RestController
@RequestMapping("/websocket")
@Api(tags = "WebSocket测试接口")
public class WebSocketController {

    /**
     * 接收左屏消息并推送至右屏
     * @param message 消息内容
     * @return 推送结果(1=成功,0=失败)
     */
    @ApiOperation(value = "左屏推右屏", notes = "HTTP接口触发,向右屏推送消息")
    @ApiImplicitParam(name = "message", value = "推送的消息内容", required = true, dataType = "String")
    @GetMapping(value = "/right")
    public AjaxResult right(String message) {
        // toAjax:通用工具类,1=成功,0=失败
        return toAjax(WebSocketUtils.sendMessage("right", message));
    }

    /**
     * 接收右屏消息并推送至左屏
     * @param message 消息内容
     * @return 推送结果(1=成功,0=失败)
     */
    @ApiOperation(value = "右屏推左屏", notes = "HTTP接口触发,向左屏推送消息")
    @ApiImplicitParam(name = "message", value = "推送的消息内容", required = true, dataType = "String")
    @GetMapping(value = "/left")
    public AjaxResult left(String message) {
        return toAjax(WebSocketUtils.sendMessage("left", message));
    }

    /**
     * 通用响应结果封装(项目已实现可忽略)
     * @param rows 成功数
     * @return AjaxResult
     */
    private AjaxResult toAjax(int rows) {
        return rows > 0 ? AjaxResult.success() : AjaxResult.error();
    }
}

接口说明:

  1. 左屏推右屏:GET http://ip:port/websocket/right?message=测试消息
  2. 右屏推左屏:GET http://ip:port/websocket/left?message=测试消息
  3. 响应结果:成功返回{"code":200,"msg":"操作成功","data":null},失败返回{"code":500,"msg":"操作失败","data":null}。

六、前端测试(两种方式)

6.1 在线 WebSocket 测试工具(快速验证)

推荐使用在线工具:WebSocket 在线测试,无需编写前端代码,直接测试连接和消息推送。在线测试网站 https://wstool.js.org/

测试步骤:

  1. 打开两个浏览器窗口,分别访问在线测试工具;
  2. 第一个窗口连接地址填ws://localhost:8080/connect/left,点击连接,提示 "连接成功";
  3. 第二个窗口连接地址填ws://localhost:8080/connect/right,点击连接,提示 "连接成功";
  4. 左屏窗口发送消息Hello 右屏,右屏窗口会收到左屏推送:Hello 右屏;
  5. 右屏窗口发送消息Hello 左屏,左屏窗口会收到右屏推送:Hello 左屏;
  6. 调用 HTTP 接口http://localhost:8080/websocket/right?message=接口推右屏,右屏窗口会收到该消息。

6.2 自定义 HTML 测试页面(项目使用)

编写简单的 HTML 页面,通过原生 WebSocket API 实现连接和消息收发,可直接放入项目的resources/static目录下:

bash 复制代码
<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <title>WebSocket双屏测试</title>
</head>
<body>
    <h3>WebSocket双屏联动测试(<span id="screenType">左屏</span>)</h3>
    <input type="text" id="msgInput" placeholder="请输入消息内容">
    <button onclick="sendMsg()">发送消息</button>
    <div id="msgList" style="margin-top: 20px; width: 500px; height: 300px; border: 1px solid #ccc; padding: 10px; overflow-y: auto;"></div>

    <script>
        // 定义用户ID,left=左屏,right=右屏
        const userId = "left";
        document.getElementById("screenType").innerText = userId === "left" ? "左屏" : "右屏";
        // WebSocket连接地址,替换为自己的服务端地址
        const ws = new WebSocket("ws://localhost:8080/connect/" + userId);

        // 连接成功回调
        ws.onopen = function() {
            addMsg("【系统提示】WebSocket连接成功!");
        };

        // 接收消息回调
        ws.onmessage = function(event) {
            addMsg("【收到消息】" + event.data);
        };

        // 连接关闭回调
        ws.onclose = function() {
            addMsg("【系统提示】WebSocket连接关闭!");
        };

        // 连接异常回调
        ws.onerror = function() {
            addMsg("【系统提示】WebSocket连接异常!");
        };

        // 发送消息
        function sendMsg() {
            const msg = document.getElementById("msgInput").value;
            if (!msg) {
                alert("请输入消息内容!");
                return;
            }
            ws.send(msg);
            addMsg("【发送消息】" + msg);
            document.getElementById("msgInput").value = "";
        }

        // 追加消息到页面
        function addMsg(content) {
            const msgList = document.getElementById("msgList");
            const div = document.createElement("div");
            div.style.margin = "5px 0";
            div.innerText = new Date().toLocaleString() + " - " + content;
            msgList.appendChild(div);
            // 滚动到底部
            msgList.scrollTop = msgList.scrollHeight;
        }

        // 心跳检测,每30秒发送一次&,防止连接断开
        setInterval(() => {
            ws.send("&");
        }, 30000);
    </script>
</body>
</html>

使用说明:

复制两份页面,分别修改userId为left和right,命名为left.html和right.html;

启动项目后,访问http://localhost:8080/left.html和http://localhost:8080/right.html;

两个页面可互相发送消息,同时支持后端接口推送。

七、部署方式说明

本文的配置已完美适配Jar 包内嵌 Tomcat和War 包外部 Tomcat两种部署方式,无需修改任何代码。

7.1 Jar 包部署(推荐,SpringBoot 默认)

  1. pom.xml中打包方式为jar:
bash 复制代码
<packaging>jar</packaging >
  1. 执行 Maven 命令打包:mvn clean package -DskipTests;
  2. 运行 Jar 包:java -jar xxx.jar;
  3. 核心原理:内嵌 Tomcat 环境,WebSocketAutoWired返回true,创建ServerEndpointExporter,WebSocket 正常注册。

7.2 War 包部署(外部 Tomcat)

  1. pom.xml中修改打包方式为war,并排除内嵌 Tomcat:
bash 复制代码
<packaging>war</packaging>

<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
        <!-- 排除内嵌Tomcat -->
        <exclusions>
            <exclusion>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-tomcat</artifactId>
            </exclusion>
        </exclusions>
    </dependency>
    <!-- 引入servlet-api依赖 -->
    <dependency>
        <groupId>javax.servlet</groupId>
        <artifactId>javax.servlet-api</artifactId>
        <version>3.1.0</version>
        <scope>provided</scope>
    </dependency>
</dependencies>
  1. 修改启动类,继承SpringBootServletInitializer,重写configure方法:
bash 复制代码
@SpringBootApplication
public class Application extends SpringBootServletInitializer {
    @Override
    protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
        return application.sources(Application.class);
    }

    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }
}
  1. 执行 Maven 命令打包:mvn clean package -DskipTests;
  2. 将 war 包放入外部 Tomcat 的webapps目录,启动 Tomcat 即可;
  3. 核心原理:外部 Tomcat 环境,WebSocketAutoWired返回false,不创建ServerEndpointExporter,由 Tomcat 容器自身初始化 WebSocket,避免冲突。

八、常见问题及解决方案

8.1 客户端连接报 404 错误

原因:未创建ServerEndpointExporter Bean,Spring 未扫描到@ServerEndpoint注解;

解决方案:检查配置类WebSocketConfig和条件判断类WebSocketAutoWired是否正确,Jar 包部署时确保matches方法返回true。

8.2 War 包部署到外部 Tomcat 启动报 Bean 冲突

原因:外部 Tomcat 环境下创建了ServerEndpointExporter Bean,与容器自身的 WebSocket 初始化冲突;

解决方案:确保条件判断类WebSocketAutoWired在 War 包部署时返回false,不创建该 Bean。

8.3 发送消息时报 IO 异常

原因:向失效的 Session(连接已关闭 / 网络中断)发送消息,或未做异常捕获;

解决方案:在sendMessage方法中增加session.isOpen()判断,同时捕获异常并移除失效 Session(本文工具类已实现)。

8.4 同一用户多端登录,只有最后一个连接能收到消息

原因:原代码使用Map<String, Session>存储连接,新连接会覆盖旧连接;

解决方案:改为Map<String, Set>存储,同一用户的所有连接都加入 Set(本文工具类已实现)。
8.5 长时间无交互,连接被断开

原因:防火墙 / 服务器会断开长时间无数据交互的 TCP 连接;

解决方案:实现心跳检测,客户端定时发送心跳包(如&),服务端回执,保持连接活跃(本文代码已实现)。

九、总结

本文详细讲解了 SpringBoot 集成 WebSocket 的全流程,从核心依赖引入→配置类编写(解决 Jar/War 兼容)→工具类封装(连接管理 + 消息发送)→服务端端点实现(事件处理)→测试接口开发→前端测试,一步一步实现了双屏实时消息互推的功能,同时解决了项目开发和部署中的常见问题。

本文的代码具有以下特点:

  • 高可用性:完善的异常处理、Session 有效性判断、失效连接清理,避免内存泄漏;
  • 高扩展性:工具类和服务端端点解耦,可根据业务需求自定义消息处理逻辑;
  • 高兼容性:支持 Jar 包和 War 包两种部署方式,无需手动修改代码;
  • 线程安全:使用 ConcurrentHashMap 和 ConcurrentHashSet 保证多线程下的连接管理安全。

WebSocket 的应用场景非常广泛,除了双屏联动,还可以用于在线聊天、实时监控、订单推送、弹幕等场景,只需在本文代码的基础上,根据业务需求修改WebSocketUtils的receive方法和消息发送逻辑即可。

如果本文对你有帮助,欢迎点赞 + 收藏 + 关注,博主会持续更新 SpringBoot、微服务、分布式等系列文章!

相关推荐
青云计划8 小时前
知光项目知文发布模块
java·后端·spring·mybatis
Victor3568 小时前
MongoDB(9)什么是MongoDB的副本集(Replica Set)?
后端
Victor3568 小时前
MongoDB(8)什么是聚合(Aggregation)?
后端
yeyeye1119 小时前
Spring Cloud Data Flow 简介
后端·spring·spring cloud
Tony Bai10 小时前
告别 Flaky Tests:Go 官方拟引入 testing/nettest,重塑内存网络测试标准
开发语言·网络·后端·golang·php
+VX:Fegn089510 小时前
计算机毕业设计|基于springboot + vue鲜花商城系统(源码+数据库+文档)
数据库·vue.js·spring boot·后端·课程设计
程序猿阿伟10 小时前
《GraphQL批处理与全局缓存共享的底层逻辑》
后端·缓存·graphql
小小张说故事11 小时前
SQLAlchemy 技术入门指南
后端·python
识君啊11 小时前
SpringBoot 事务管理解析 - @Transactional 的正确用法与常见坑
java·数据库·spring boot·后端
CaracalTiger11 小时前
如何解决Unexpected token ‘<’, “<!doctype “… is not valid JSON 报错问题
java·开发语言·jvm·spring boot·python·spring cloud·json