补发团队项目04-消息提醒(websocket和springTask结合使用 苍穹外卖改编实现)

这边项目需求对于未审核工单超时提醒
我们也做了简单实现
首先给出前端代码 这边的苍穹外卖前端实现消息提醒是在顶部导航栏 为了完成需求 做了简单改编 这边仅作参考
src\layout\components\Navbar\index.vue

<template>
  <div class="navbar">
    <div class="statusBox">
      <hamburger id="hamburger-container"
                 :is-active="sidebar.opened"
                 class="hamburger-container"
                 @toggleClick="toggleSideBar" />
      
      <!-- 添加面包屑导航 -->
      <breadcrumb class="breadcrumb-container" />
      
      <!-- 添加系统标题 -->
      <div class="system-title">设备管理系统</div>
    </div>

    <div :key="restKey"
         class="right-menu">
      <!-- 添加通知中心 -->
      <div class="notification-center">
        <el-badge :value="ountUnread > 0 ? ountUnread : ''" :max="99" class="item">
          <div class="navicon mesCenter" @click="goToMessageCenter">
            <i></i>
            <span>消息中心</span>
          </div>
        </el-badge>
      </div>
      
      <!-- 添加帮助中心 -->
      <div class="help-center">
        <div class="navicon helpCenter" @click="openHelpCenter">
          <i class="el-icon-question"></i>
          <span>帮助中心</span>
        </div>
      </div>
      
      <!-- 添加主题切换 -->
      <div class="theme-switch">
        <div class="navicon themeSwitch" @click="toggleTheme">
          <i :class="isDarkTheme ? 'el-icon-sunny' : 'el-icon-moon'"></i>
          <span>{{ isDarkTheme ? '浅色模式' : '深色模式' }}</span>
        </div>
      </div>
      
      <div class="rightStatus">
        <audio ref="audioVo"
               hidden>
          <source src="./../../../assets/preview.mp3" type="audio/mp3" />
        </audio>
        <audio ref="audioVo2"
               hidden>
          <source src="./../../../assets/reminder.mp3" type="audio/mp3" />
        </audio>
      </div>
      <div class="avatar-wrapper">
        <div :class="shopShow?'userInfo':''"
             @mouseenter="toggleShow"
             @mouseleave="mouseLeaves">
          <el-button type="primary" 
                     :class="shopShow?'active':''">
            {{ realName }}<i class="el-icon-arrow-down" />
          </el-button>
          <div v-if="shopShow"
               class="userList">
            <p class="userInfoIcon"
               @click="viewUserInfo">
              个人信息<i class="el-icon-user" />
            </p>
            <p class="amendPwdIcon"
               @click="handlePwd">
              修改密码<i />
            </p>
            <p class="outLogin"
               @click="logout">
              退出登录<i />
            </p>
          </div>
        </div>
      </div>
    </div>
    
    <!-- 修改密码 -->
    <Password :dialog-form-visible="dialogFormVisible"
              @handleclose="handlePwdClose" />
    <!-- end -->
    
    <!-- 帮助中心弹窗 -->
    <el-dialog
      title="帮助中心"
      :visible.sync="helpDialogVisible"
      width="50%">
      <div class="help-content">
        <h3>设备管理系统使用指南</h3>
        <el-collapse>
          <el-collapse-item title="基本操作" name="1">
            <div>系统导航:使用左侧菜单进行功能导航</div>
            <div>消息通知:点击顶部导航栏的"消息中心"查看系统通知</div>
            <div>个人设置:点击右上角用户名可进行密码修改和退出登录</div>
          </el-collapse-item>
          <el-collapse-item title="设备管理" name="2">
            <div>设备登记:记录设备基本信息、规格参数等</div>
            <div>设备维护:记录设备维护保养情况</div>
            <div>设备报废:处理报废设备的流程</div>
          </el-collapse-item>
          <el-collapse-item title="常见问题" name="3">
            <div>Q: 如何添加新设备?</div>
            <div>A: 进入"设备管理"页面,点击"添加设备"按钮,填写相关信息后保存。</div>
            <div>Q: 如何处理设备故障?</div>
            <div>A: 进入"故障管理"页面,点击"报修"按钮,填写故障信息并提交。</div>
          </el-collapse-item>
        </el-collapse>
      </div>
      <span slot="footer" class="dialog-footer">
        <el-button @click="helpDialogVisible = false">关闭</el-button>
      </span>
    </el-dialog>
    <!-- end -->
    
    <!-- 个人信息弹窗 -->
    <el-dialog
      title="个人信息"
      :visible.sync="userInfoDialogVisible"
      width="30%">
      <div class="user-info-content">
        <el-form label-width="80px">
          <el-form-item label="用户名">
            <span>{{ realName }}</span>
          </el-form-item>
          <el-form-item label="角色">
            <span>{{ userRoleName }}</span>
          </el-form-item>
          <el-form-item label="部门">
            <span>{{ userDepartment }}</span>
          </el-form-item>
          <el-form-item label="最后登录">
            <span>{{ lastLoginTime }}</span>
          </el-form-item>
        </el-form>
      </div>
      <span slot="footer" class="dialog-footer">
        <el-button @click="userInfoDialogVisible = false">关闭</el-button>
      </span>
    </el-dialog>
    <!-- end -->
  </div>
</template>

<script lang="ts">
import { Component, Vue, Watch } from 'vue-property-decorator'
import { AppModule } from '@/store/modules/app'
import { UserModule } from '@/store/modules/user'
import Breadcrumb from '@/components/Breadcrumb/index.vue'
import Hamburger from '@/components/Hamburger/index.vue'
import { setStatus } from '@/api/users'
import Cookies from 'js-cookie'
import { debounce, throttle } from '@/utils/common'
import { setNewData, getNewData } from '@/utils/cookies'


// 修改密码弹层
import Password from '../components/password.vue'

@Component({
  name: 'Navbar',
  components: {
    Breadcrumb,
    Hamburger,
    Password,
  },
})
export default class extends Vue {
  private storeId = this.getStoreId
  private restKey: number = 0
  private websocket = null
  private newOrder = ''
  private message = ''
  private audioIsPlaying = false
  private audioPaused = false
  private statusValue = true
  private audioUrl: './../../../assets/preview.mp3'
  private shopShow = false
  private dialogVisible = false
  private status = 1
  private setStatus = 1
  private dialogFormVisible = false
  private ountUnread = 0
  private roleId =  parseInt(localStorage.getItem('roleId') || '0'); // 从 localStorage 中读取
  private isDarkTheme = false
  private helpDialogVisible = false
  private userInfoDialogVisible = false
  private userRoleName = '管理员' // 示例数据,实际应从用户信息中获取
  private userDepartment = '技术部' // 示例数据,实际应从用户信息中获取
  private lastLoginTime = new Date().toLocaleString() // 示例数据,实际应从用户信息中获取
  
  get sidebar() {
    return AppModule.sidebar
  }

  get device() {
    return AppModule.device.toString()
  }

  getuserInfo() {
    return UserModule.userInfo
  }

  get realName() {
    return (UserModule.userInfo as any).realName
      ? (UserModule.userInfo as any).realName
      : JSON.parse(Cookies.get('user_info') as any).realName
  }

  get getStoreId() {
    let storeId = ''
    if (UserModule.storeId) {
      storeId = UserModule.storeId
    } else if ((UserModule.userInfo as any).stores != null) {
      storeId = (UserModule.userInfo as any).stores[0].storeId
    }
    return storeId
  }
  
  mounted() {
    document.addEventListener('click', this.handleClose)
  }
  
  created() {
    this.webSocket()
  }
  
  onload() {
  }
  
  destroyed() {
    this.websocket.close() //离开路由之后断开websocket连接
  }

  // 添加新订单提示弹窗
  // 添加新订单提示弹窗
  webSocket() {
    const that = this as any
    let clientId = Math.random().toString(36).substr(2)
    let socketUrl = process.env.VUE_APP_SOCKET_URL + clientId
    console.log(socketUrl, 'socketUrl')
    if (typeof WebSocket == 'undefined') {
      that.$notify({
        title: '提示',
        message: '当前浏览器无法接收实时报警信息,请使用谷歌浏览器!',
        type: 'warning',
        duration: 0,
      })
    } else {
      this.websocket = new WebSocket(socketUrl)
      // 监听socket打开
      this.websocket.onopen = function () {
        console.log('浏览器WebSocket已打开')
      }
      // 监听socket消息接收
      this.websocket.onmessage = function (msg) {
        // 转换为json对象
        that.$refs.audioVo.currentTime = 0
        that.$refs.audioVo2.currentTime = 0

        console.log(msg, JSON.parse(msg.data), 'msg')
        // const h = this.$createElement
        const jsonMsg = JSON.parse(msg.data)
        if (String(jsonMsg.roleId) !== String(that.roleId)) { // 这里改为that.roleId
         return;
        }
        if (jsonMsg.type === 1) {
          that.$refs.audioVo.play()
        } else if (jsonMsg.type === 2) {
          that.$refs.audioVo2.play()
        }
        that.$notify({
          title: jsonMsg.type === 1 ? '待审核' : '催单',
          duration: 0,
          dangerouslyUseHTMLString: true,
          onClick: () => {
            // 根据消息类型跳转到不同页面
            switch (jsonMsg.type) {
              case 1: // 待审核
                if (jsonMsg.faultId) {
                  that.$router.push(`/teamLeader/deviceReview/faultClear?faultId=${jsonMsg.faultId}`)
                } else if (jsonMsg.maintenanceId) {
                  that.$router.push(`/teamLeader/deviceReview/maintenanceAcceptance?maintenanceId=${jsonMsg.maintenanceId}`)
                } else if (jsonMsg.inspectionId) {
                  that.$router.push(`/teamLeader/deviceReview/inspectionAcceptance?inspectionId=${jsonMsg.inspectionId}`)
                } else if (jsonMsg.detectionId) {
                  that.$router.push(`/teamLeader/deviceReview/testingAcceptance?detectionId=${jsonMsg.detectionId}`)
                }
                break
              case 2: // 催单
                if (jsonMsg.purchaseId) {
                  that.$router.push(`/sectionLeader/purchase?purchaseId=${jsonMsg.purchaseId}`)
                } else if (jsonMsg.outboundId) {
                  that.$router.push(`/sectionLeader/outbound?outboundId=${jsonMsg.outboundId}`)
                }
                break
              default:
                // 默认跳转到消息中心
                that.$router.push('/sectionLeader/inform')
            }
            setTimeout(() => {
              location.reload()
            }, 100)
          },
          // 这里也可以把返回信息加入到message中显示
          message: `${jsonMsg.type === 1
            ? `<span>您有1个<span style=color:#419EFF>审核处理</span>,${jsonMsg.content},请及时审核</span>`
            : `${jsonMsg.content}<span style='color:#419EFF;cursor: pointer'>去处理</span>`
          }`,
        })
      }
      // 监听socket错误
      this.websocket.onerror = function () {
        that.$notify({
          title: '错误',
          message: '服务器错误,无法接收实时报警信息',
          type: 'error',
          duration: 0,
        })
      }
      // 监听socket关闭
      this.websocket.onclose = function () {
        console.log('WebSocket已关闭')
      }
    }
  }

  private toggleSideBar() {
    AppModule.ToggleSideBar(false)
  }
  
  // 退出
  private async logout() {
    this.$store.dispatch('LogOut').then(() => {
      this.$router.replace({ path: '/' })
    })
  }
  
  
  
  // 下拉菜单显示
  toggleShow() {
    this.shopShow = true
  }
  
  // 下拉菜单隐藏
  mouseLeaves() {
    this.shopShow = false
  }
  
  // 触发空白处下来菜单关闭
  handleClose() {
    // clearTimeout(this.leave)
    // this.shopShow = false
  }
  
  // 跳转到消息中心
  goToMessageCenter() {
    // 根据用户角色跳转到不同的消息中心页面
    const roleId = parseInt(localStorage.getItem('roleId') || '0');
    if (roleId === 2) { // 科长
      this.$router.push('/sectionLeader/inform');
    } else if (roleId === 3) { // 班组长
      this.$router.push('/teamLeader/inform');
    } else if (roleId === 4) { // 仓库管理员
      this.$router.push('/WarehouseManager/inform');
    } else if (roleId === 5) { // 主任
      this.$router.push('/director/inform');
    } else {
      this.$router.push('/sectionLeader/inform'); // 默认跳转
    }
    this.ountUnread = 0; // 清空未读消息数量
  }
  
  // 修改密码
  handlePwd() {
    this.dialogFormVisible = true
  }
  
  // 关闭密码编辑弹层
  handlePwdClose() {
    this.dialogFormVisible = false
  }
  
  // 切换主题
  toggleTheme() {
    this.isDarkTheme = !this.isDarkTheme;
    // 这里可以添加实际的主题切换逻辑
    document.body.classList.toggle('dark-theme');
    this.$message.success(`已切换到${this.isDarkTheme ? '深色' : '浅色'}模式`);
  }
  
  // 打开帮助中心
  openHelpCenter() {
    this.helpDialogVisible = true;
  }
  
  // 查看个人信息
  viewUserInfo() {
    this.userInfoDialogVisible = true;
  }
}
</script>

<style lang="scss" scoped>
.navbar {
  height: 80px;
  position: relative;
  background: #ffffff;
  box-shadow: 0 1px 4px rgba(0, 21, 41, 0.08);

  .statusBox {
    float: left;
    height: 100%;
    align-items: center;
    display: flex;
  }
  
  .system-title {
    font-size: 20px;
    font-weight: bold;
    color: #333;
    margin-left: 15px;
    position: relative;
    padding-left: 15px;
    
    &:before {
      content: '';
      position: absolute;
      left: 0;
      top: 50%;
      transform: translateY(-50%);
      width: 3px;
      height: 20px;
      background-color: #409EFF;
    }
  }
  
  .hamburger-container {
    padding: 0 12px 0 20px;
    cursor: pointer;
    transition: background 0.3s;
    -webkit-tap-highlight-color: transparent;

    &:hover {
      background: rgba(0, 0, 0, 0.025);
    }
  }

  .breadcrumb-container {
    float: left;
  }
  
  .right-menu {
    float: right;
    margin-right: 20px;
    color: #333333;
    font-size: 16px;
    padding: 10px 0;
    height: auto;
    line-height: 20px;
    display: flex;
    align-items: center;

    .notification-center, .help-center, .theme-switch {
      margin-right: 20px;
      cursor: pointer;
    }
    
    span {
      padding: 0 10px;
      width: 130px;
      display: inline-block;
      cursor: pointer;
      &:hover {
        background: rgba(255, 255, 255, 0.52);
      }
    }
    
    .amendPwdIcon {
      i {
        width: 18px;
        height: 18px;
        background: url(./../../../assets/icons/btn_gaimi@2x.png) no-repeat;
        background-size: contain;
        margin-top: 8px;
      }
    }
    
    .outLogin {
      i {
        width: 18px;
        height: 18px;
        background: url(./../../../assets/icons/btn_close@2x.png) no-repeat 100% 100%;
        background-size: contain;
        margin-top: 8px;
      }
    }
    
    .userInfoIcon {
      i {
        font-size: 18px;
        margin-top: 8px;
      }
    }
    
    .outLogin {
      cursor: pointer;
    }

    &:focus {
      outline: none;
    }

    .right-menu-item {
      display: inline-block;
      padding: 0 8px;
      height: 100%;
      font-size: 18px;
      color: #5a5e66;
      vertical-align: text-bottom;

      &.hover-effect {
        cursor: pointer;
        transition: background 0.3s;

        &:hover {
          background: rgba(0, 0, 0, 0.025);
        }
      }
    }
  }
  
  .rightStatus {
    height: 100%;
    line-height: 60px;
    display: flex;
    align-items: center;
    float: left;
  }
  
  .avatar-wrapper {
    margin-top: 14px;
    margin-left: 18px;
    position: relative;
    float: right;
    width: 120px;
    text-align: left;
    
    .user-avatar {
      cursor: pointer;
      width: 40px;
      height: 40px;
      border-radius: 10px;
    }

    .el-icon-caret-bottom {
      cursor: pointer;
      position: absolute;
      right: -20px;
      top: 25px;
      font-size: 12px;
    }

    .el-button--primary {
      background: rgba(255, 255, 255, 0.52);
      border-radius: 4px;
      padding-top: 0px;
      padding-bottom: 0px;
      position: relative;
      width: 120px;
      padding-left: 12px;
      text-align: left;
      border: 0 none;
      height: 32px;
      line-height: 32px;
      &.active {
        background: rgba(250, 250, 250, 0);
        border: 0 none;
        .el-icon-arrow-down {
          transform: rotate(-180deg);
        }
      }
    }
  }
  
  .navicon {
    i {
      display: inline-block;
      width: 18px;
      height: 18px;
      vertical-align: sub;
      margin: 0 4px 0 0;
    }
  }
  
  .mesCenter {
    i {
      background: url('./../../../assets/icons/msg.png') no-repeat;
      background-size: contain;
    }
  }
  
  .helpCenter {
    i {
      font-size: 18px;
      vertical-align: middle;
    }
  }
  
  .themeSwitch {
    i {
      font-size: 18px;
      vertical-align: middle;
    }
  }
}

.help-content {
  max-height: 400px;
  overflow-y: auto;
  
  h3 {
    margin-top: 0;
    margin-bottom: 20px;
    text-align: center;
    color: #409EFF;
  }
}

.user-info-content {
  padding: 20px;
}

// 深色模式样式
:global(.dark-theme) {
  .navbar {
    background: #1f2d3d;
    color: #fff;
    
    .system-title {
      color: #fff;
    }
    
    .right-menu {
      color: #fff;
    }
  }
}
</style>

<style lang="scss">
.el-notification {
  // background: rgba(255, 255, 255, 0.71);
  width: 419px !important;
  .el-notification__title {
    margin-bottom: 14px;
    color: #333;
    .el-notification__content {
      color: #333;
    }
  }
}
.navbar {
  .el-dialog {
    min-width: auto !important;
  }
  .el-dialog__header {
    height: 61px;
    line-height: 60px;
    background: #fbfbfa;
    padding: 0 30px;
    font-size: 16px;
    color: #333;
    border: 0 none;
  }
  .el-dialog__body {
    padding: 10px 30px 30px;
    .el-radio,
    .el-radio__input {
      white-space: normal;
    }
    .el-radio__label {
      padding-left: 5px;
      color: #333;
      font-weight: 700;
      span {
        display: block;
        line-height: 20px;
        padding-top: 12px;
        color: #666;
        font-weight: normal;
      }
    }
    .el-radio__input.is-checked .el-radio__inner {
      &::after {
        background: #333;
      }
    }
    .el-radio-group {
      & > .is-checked {
        border: 1px solid #409EFF; // 原值为 #ffc200,修改为蓝色
      }
    }
    .el-radio {
      width: 100%;
      background: #fbfbfa;
      border: 1px solid #e5e4e4;
      border-radius: 4px;
      padding: 14px 22px;
      margin-top: 20px;
    }
    .el-radio__input.is-checked + .el-radio__label {
      span {
      }
    }
  }
  .el-badge__content.is-fixed {
    top: 24px;
    right: 2px;
    width: 18px;
    height: 18px;
    font-size: 10px;
    line-height: 16px;
    font-size: 10px;
    border-radius: 50%;
    padding: 0;
  }
  .badgeW {
    .el-badge__content.is-fixed {
      width: 30px;
      border-radius: 20px;
    }
  }
}
.el-icon-arrow-down {
  background: url('./../../../assets/icons/up.png') no-repeat 50% 50%;
  background-size: contain;
  width: 8px;
  height: 8px;
  transform: rotate(0eg);
  margin-left: 16px;
  position: absolute;
  right: 16px;
  top: 12px;
  &:before {
    content: '';
  }
}

.userInfo {
  background: #fff;
  position: absolute;
  top: 0px;
  left: 0;
  z-index: 99;
  box-shadow: 0 2px 4px 0 rgba(0, 0, 0, 0.14);
  width: 100%;
  border-radius: 4px;
  line-height: 32px;
  padding: 0 0 5px;
  height: 105px;
  // .active {
  //   top: 0;
  //   left: 0;
  // }
  .userList {
    width: 95%;
    // // margin-top: -5px;
    // position: absolute;
    // top: 35px;
    padding-left: 5px;
  }
  p {
    cursor: pointer;
    height: 32px;
    line-height: 32px;
    padding: 0 5px 0 7px;
    i {
      margin-left: 10px;

      vertical-align: middle;
      margin-top: 4px;
      float: right;
    }
    &:hover {
      background: #f6f1e1;
    }
  }
}
.msgTip {
  color: #419eff;
  padding: 0 5px;
}
// .el-dropdown{
//   .el-button--primary{
//     height: 32px;
//     background: rgba(255,255,255,0.52);
//     border-radius: 4px;
//     padding-top: 0px;
//     padding-bottom: 0px;
//   }
//   margin-top: 2px;
// }
// .el-popper{
//   top: 45px !important;
//   padding-top: 50px !important;
//   border-radius: 0 0 4px 4px;
// }
// .el-popper[x-placement^=bottom] .popper__arrow::after,.popper__arrow{
//   display: none !important;
// }
</style>

对应的跳转页面关键代码为


  mounted() {
    if (
      this.$route.query.faultId &&
      this.$route.query.faultId !== 'undefined'
    ) {
      this.goDetail(this.$route.query.faultId, 2)
    }
    if (this.$route.query.status) {
      this.defaultActivity = Number(this.$route.query.status)
    }
  }

后端实现这边直接给出来


    /**
     * 查询未审核审核清单
     */
    @Scheduled(cron = "0 * * * * ?")
    @Transactional
    public void listenMaintenanceVerify() {
        System.out.println("定期查审核");

        LocalDateTime listenTime = LocalDateTime.now().minusMinutes(15);
        log.info("定时检查保养任务状态,检测时间(当前时间-15min):{}", listenTime);
        //检查超时未审核的 先检查工组长 设置步骤为1
        List<Long> maintenanceIds = teamLeaderMapper.getMaintenanceIdByTime(listenTime,1);
        //根据id集合获取maintenanceCode集合
        List<String> maintenanceCodes = teamLeaderMapper.getMaintenanceCodeByMaintenanceId(maintenanceIds);
        //若存在websocket群发消息
        if (maintenanceIds.isEmpty()){
            return;
        }

        Map map = new HashMap();
        map.put("type", 1);//1 表示审核提醒
        map.put("roleId", 3);
        for (int i = 0; i < maintenanceIds.size(); i++) {
            //发送websocket消息
            map.put("maintenanceId", maintenanceIds.get(i));
            map.put("content", "请及时审核保养单"+maintenanceCodes.get(i));
            String json = JSON.toJSONString(map);
            webSocketServer.sendToAllClient(json);
            log.info("已发送消息:{}", json);
        }



        //检查超时未审核的 先检查设备管理员 步骤为2
        maintenanceIds = teamLeaderMapper.getMaintenanceIdByTime(listenTime,2);
        maintenanceCodes = teamLeaderMapper.getMaintenanceCodeByMaintenanceId(maintenanceIds);
        if (maintenanceIds.isEmpty()){
            return;
        }


        map.put("roleId", 2);
        for (int i = 0; i < maintenanceIds.size(); i++) {
            //发送websocket消息
            map.put("maintenanceId", maintenanceIds.get(i));
            map.put("content", "请及时审核保养单"+maintenanceCodes.get(i));
            String json = JSON.toJSONString(map);
            webSocketServer.sendToAllClient(json);
            log.info("已发送消息:{}", json);
        }

    }

使用的websokcetserver如下
`package com.device.websocket;

import org.springframework.stereotype.Component;
import javax.websocket.OnClose;
import javax.websocket.OnMessage;
import javax.websocket.OnOpen;
import javax.websocket.Session;
import javax.websocket.server.PathParam;
import javax.websocket.server.ServerEndpoint;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;

/**

  • WebSocket服务
    */
    @Component
    @ServerEndpoint("/ws/{sid}")
    public class WebSocketServer {

    //存放会话对象
    private static Map<String, Session> sessionMap = new HashMap();

    /**

    • 连接建立成功调用的方法
      */
      @OnOpen
      public void onOpen(Session session, @PathParam("sid") String sid) {
      System.out.println("客户端:" + sid + "建立连接");
      sessionMap.put(sid, session);
      }

    /**

    • 收到客户端消息后调用的方法
    • @param message 客户端发送过来的消息
      */
      @OnMessage
      public void onMessage(String message, @PathParam("sid") String sid) {
      System.out.println("收到来自客户端:" + sid + "的信息:" + message);
      }

    /**

    • 连接关闭调用的方法
    • @param sid
      */
      @OnClose
      public void onClose(@PathParam("sid") String sid) {
      System.out.println("连接断开:" + sid);
      sessionMap.remove(sid);
      }

    /**

    • 群发
    • @param message
      */
      public void sendToAllClient(String message) {
      Collection sessions = sessionMap.values();
      for (Session session : sessions) {
      try {
      //服务器向客户端发送消息
      session.getBasicRemote().sendText(message);
      } catch (Exception e) {
      e.printStackTrace();
      }
      }
      }

}
`
依赖导入参考黑马即可
这边省略

posted @ 2025-06-08 15:48  好像是Cwk  阅读(52)  评论(0)    收藏  举报