Spring Boot + Vue3 消息通知中心教程:站内信、邮件与 WebSocket 本地联调后,如何用 cpolar 给测试同事验收 Spring Boot Vue3 消息通知中心教程站内信、邮件与 WebSocket 本地联调后如何用 cpolar 给测试同事验收公司后台一做起来通知中心迟早会冒出来审批通过要提醒工单被指派要提醒告警触发也要提醒。只做一个弹窗很快就不够用站内信要能留痕重要消息要发邮件页面还要实时弹出 WebSocket 通知。这篇直接做一个本地可跑的最小版本Spring Boot 负责通知接口、站内信记录、邮件发送和 WebSocket 推送Vue3 负责通知列表和实时提醒本地跑通后再用 cpolar 把前端页面和后端接口临时开放给测试同事验收。1 什么是消息通知中心这里的“消息通知中心”不是单纯的弹窗组件而是一条完整链路业务系统产生通知后端保存一条站内信再按需要发邮件同时把实时消息推到在线用户页面。本教程只做三个核心能力站内信保存通知记录页面可查询邮件用本地 MailHog 接收测试邮件不打扰真实邮箱WebSocket用户在线时实时收到通知这套做法适合后台管理系统、审批系统、客服工单、设备告警面板。提醒一句先把本地链路跑顺再谈复杂权限和消息模板不然排错会很痛苦。2 环境准备启动后端、前端和本地邮件服务本地需要准备 JDK 17、Node.js 18、Docker 和 cpolar。后端用 Spring Boot 3前端用 Vue3 Vite邮件测试用 MailHog。先创建工作目录mkdir notify-center-demo cd notify-center-demo启动 MailHog本地 SMTP 端口是1025邮件查看页面是8025docker run -d --name notify-mailhog -p 1025:1025 -p 8025:8025 mailhog/mailhog验证邮件页面curl -I http://127.0.0.1:8025看到HTTP/1.1 200 OK就说明邮件测试服务已经启动。这里别急着接真实 SMTP测试阶段用 MailHog 更安全误发邮件这类事故非常烦。3 创建 Spring Boot 后端站内信、邮件和 WebSocket先用 Spring Initializr 生成后端项目curl https://start.spring.io/starter.zip \ -d typemaven-project \ -d languagejava \ -d dependenciesweb,websocket,data-jpa,h2,mail,lombok \ -d groupIdcom.example \ -d artifactIdnotify-api \ -d namenotify-api \ -o notify-api.zip unzip notify-api.zip -d notify-api cd notify-api3.1 配置 H2 和邮件测试服务编辑src/main/resources/application.ymlserver: port: 8080 spring: datasource: url: jdbc:h2:mem:notifydb;MODEMySQL;DB_CLOSE_DELAY-1 driver-class-name: org.h2.Driver username: sa password: jpa: hibernate: ddl-auto: update show-sql: false mail: host: 127.0.0.1 port: 1025 username: password:H2 用内存库重启后数据会清空。教程阶段这样反而省事测试同事验收的重点是链路不是持久化策略。3.2 写通知实体和接口新建src/main/java/com/example/notifyapi/Notification.javapackage com.example.notifyapi; import jakarta.persistence.*; import java.time.LocalDateTime; Entity public class Notification { Id GeneratedValue(strategy GenerationType.IDENTITY) private Long id; private String receiver; private String title; private String content; private boolean readFlag false; private LocalDateTime createdAt LocalDateTime.now(); public Long getId() { return id; } public String getReceiver() { return receiver; } public void setReceiver(String receiver) { this.receiver receiver; } public String getTitle() { return title; } public void setTitle(String title) { this.title title; } public String getContent() { return content; } public void setContent(String content) { this.content content; } public boolean isReadFlag() { return readFlag; } public void setReadFlag(boolean readFlag) { this.readFlag readFlag; } public LocalDateTime getCreatedAt() { return createdAt; } }新建NotificationRepository.javapackage com.example.notifyapi; import org.springframework.data.jpa.repository.JpaRepository; import java.util.List; public interface NotificationRepository extends JpaRepositoryNotification, Long { ListNotification findByReceiverOrderByCreatedAtDesc(String receiver); }3.3 开启 WebSocket 推送新建WebSocketConfig.javapackage com.example.notifyapi; import org.springframework.context.annotation.Configuration; import org.springframework.messaging.simp.config.MessageBrokerRegistry; import org.springframework.web.socket.config.annotation.*; Configuration EnableWebSocketMessageBroker public class WebSocketConfig implements WebSocketMessageBrokerConfigurer { Override public void registerStompEndpoints(StompEndpointRegistry registry) { registry.addEndpoint(/ws).setAllowedOriginPatterns(*).withSockJS(); } Override public void configureMessageBroker(MessageBrokerRegistry registry) { registry.enableSimpleBroker(/topic); registry.setApplicationDestinationPrefixes(/app); } }这里为了方便本地和外网验收先放开跨域来源。正式环境要收紧到自己的域名别把后台接口长期裸露给所有来源。3.4 保存站内信、发送邮件、推送实时消息新建NotificationController.javapackage com.example.notifyapi; import org.springframework.mail.SimpleMailMessage; import org.springframework.mail.javamail.JavaMailSender; import org.springframework.messaging.simp.SimpMessagingTemplate; import org.springframework.web.bind.annotation.*; import java.util.List; RestController RequestMapping(/api/notifications) CrossOrigin(originPatterns *) public class NotificationController { private final NotificationRepository repository; private final JavaMailSender mailSender; private final SimpMessagingTemplate messagingTemplate; public NotificationController(NotificationRepository repository, JavaMailSender mailSender, SimpMessagingTemplate messagingTemplate) { this.repository repository; this.mailSender mailSender; this.messagingTemplate messagingTemplate; } GetMapping(/{receiver}) public ListNotification list(PathVariable String receiver) { return repository.findByReceiverOrderByCreatedAtDesc(receiver); } PostMapping public Notification create(RequestBody Notification request) { Notification saved repository.save(request); SimpleMailMessage mail new SimpleMailMessage(); mail.setFrom(notifyexample.com); mail.setTo(request.getReceiver()); mail.setSubject(request.getTitle()); mail.setText(request.getContent()); mailSender.send(mail); messagingTemplate.convertAndSend(/topic/notifications/ request.getReceiver(), saved); return saved; } }启动后端./mvnw spring-boot:run另开一个终端发一条测试通知curl -X POST http://127.0.0.1:8080/api/notifications \ -H Content-Type: application/json \ -d {receiver:testexample.com,title:工单已分配,content:你有一个新的工单需要处理}如果接口返回 JSON再打开http://127.0.0.1:8025能看到主题为“工单已分配”的邮件。接口不通就先检查8080端口邮件没出现就先检查 MailHog 容器和1025端口。4 创建 Vue3 前端通知列表和实时弹窗回到项目根目录创建 Vue3 项目cd .. npm create vitelatest notify-web -- --template vue cd notify-web npm install npm install axios stomp/stompjs sockjs-client编辑src/App.vuescript setup import { ref, onMounted } from vue import axios from axios import SockJS from sockjs-client/dist/sockjs import { Client } from stomp/stompjs const receiver testexample.com const apiBase import.meta.env.VITE_API_BASE || http://127.0.0.1:8080 const notifications ref([]) const toast ref() async function loadList() { const res await axios.get(${apiBase}/api/notifications/${receiver}) notifications.value res.data } async function sendDemo() { await axios.post(${apiBase}/api/notifications, { receiver, title: 审批结果提醒, content: 你提交的报销单已经审批通过 }) } onMounted(() { loadList() const client new Client({ webSocketFactory: () new SockJS(${apiBase}/ws), reconnectDelay: 3000, onConnect: () { client.subscribe(/topic/notifications/${receiver}, message { const body JSON.parse(message.body) toast.value ${body.title}${body.content} loadList() }) } }) client.activate() }) /script template main stylemax-width: 760px; margin: 40px auto; font-family: sans-serif; h1消息通知中心/h1 p当前用户{{ receiver }}/p button clicksendDemo发送一条测试通知/button p v-iftoast stylepadding: 12px; background: #e8fff1; border: 1px solid #7ddc9b; {{ toast }} /p ul li v-foritem in notifications :keyitem.id strong{{ item.title }}/strong - {{ item.content }} /li /ul /main /template启动前端npm run dev -- --host 0.0.0.0打开http://127.0.0.1:5173点击“发送一条测试通知”。页面出现绿色提示同时列表新增一条记录就说明 Vue3、接口、邮件和 WebSocket 已经串起来了。这里有个小提醒如果页面列表更新了但实时提示没弹出来优先看浏览器控制台里的 WebSocket 连接地址。端口、协议和/ws路径任意一项写错实时链路都会断。还有一个经常被忽略的点本地联调时前端调用的是http://127.0.0.1:8080浏览器和后端都在自己的电脑上路径很好理解。换成测试同事访问公网页面后浏览器运行在测试同事电脑里127.0.0.1就变成了测试同事自己的机器所以必须把VITE_API_BASE改成后端公网地址。这一步不是为了“适配 cpolar”而是前后端分离项目在远程验收时必须处理的访问位置问题。5 用 cpolar 给测试同事做外网验收本地跑通以后测试同事不在同一局域网就访问不到你的127.0.0.1。这时用 cpolar 开临时 HTTP 隧道把 Vue 页面和 Spring Boot 接口各开放一个公网地址测试同事直接点链接验收。macOS 可用 Homebrew 安装brew tap probezy/core brew install cpolar cpolar versionLinux 可用官方脚本安装curl -L https://www.cpolar.com/static/downloads/install-release-cpolar.sh | sudo bash cpolar version启动 cpolar 后访问本地管理面板curl -s http://127.0.0.1:9200 || echo cpolar 服务未启动在 Web UI 里创建两个 HTTP 隧道notify-api协议选http本地地址填8080notify-web协议选http本地地址填5173创建后到“状态 → 在线隧道列表”查看两个公网地址。把后端公网地址写进前端环境变量再重启 VueVITE_API_BASEhttps://你的后端公网地址 npm run dev -- --host 0.0.0.0如果你的终端不方便临时写环境变量也可以在notify-web目录新建.env.localcat .env.local EOF VITE_API_BASEhttps://你的后端公网地址 EOF npm run dev -- --host 0.0.0.0这里别把前端公网地址填进去VITE_API_BASE指向的是 Spring Boot 后端。填错以后页面照样能打开但按钮一点击就会报接口错误这种问题看起来像后端挂了其实只是地址写反了。再把前端公网地址发给测试同事。测试同事打开页面点击按钮后要看到三件事页面弹出实时通知、列表新增站内信、MailHog 页面收到邮件。测试只需要访问前端链接后端地址由前端环境变量调用。免费随机公网地址 24 小时内会变化临时验收够用如果每天都要给同一个测试入口固定二级子域名需要基础套餐或以上。验收结束后关闭隧道后台接口别长时间暴露在公网。6 验收清单和常见排错给测试同事之前自己先按这张清单走一遍本机访问http://127.0.0.1:8080/api/notifications/testexample.com有 JSON 返回本机访问http://127.0.0.1:5173页面正常加载MailHog 的8025页面能看到测试邮件cpolar 在线隧道列表里能看到5173和8080两个地址外网前端页面点击按钮后列表、弹窗、邮件三处都有结果如果外网页面打不开先看 cpolar 隧道是否在线再看 Vite 是否用了--host 0.0.0.0。如果页面打开了但接口失败检查VITE_API_BASE是否指向后端公网地址。若 WebSocket 断开浏览器控制台会直接显示连接失败的地址照着地址查端口和路径最快。邮件这块也建议单独验一次。测试同事点击按钮后开发电脑上的 MailHog 页面应该马上出现新邮件如果页面和站内信都有结果只有邮件没有结果重点检查 Spring Boot 日志里的 SMTP 连接信息以及 Docker 容器是否还在运行。别一上来就改邮件代码先确认docker ps里能看到notify-mailhog。docker ps --filter namenotify-mailhog验收完成后可以清理本地邮件服务docker stop notify-mailhog docker rm notify-mailhog这两个命令只清理本教程启动的测试容器不会影响你的 Spring Boot 和 Vue 项目。7 总结到这里一个最小可用的消息通知中心已经跑起来了Spring Boot 保存站内信、发送测试邮件、推送 WebSocket 消息Vue3 展示通知列表和实时提示cpolar 负责把本地联调环境临时交给测试同事验收。关键步骤就三块先用 H2 和 MailHog 降低本地依赖把通知链路跑通再用 Vue3 接入 REST 接口和 WebSocket确认页面实时更新需要外部验收时用 cpolar 暴露前端和后端 HTTP 端口验收结束及时关闭后续要扩展也很清晰把 H2 换成 MySQL把 MailHog 换成企业 SMTP把接收人从邮箱字符串升级成用户 ID再给通知模板、已读状态和权限校验补上。先从这套最小链路开始排错成本最低也最适合拿给测试同事快速验收。