1. 程式人生 > 實用技巧 >springboot+websocket+vue 服務端向前端推送訊息

springboot+websocket+vue 服務端向前端推送訊息

最近專案中需要進行線上使用者管理,故採用了websocket來實現訊息推送至前端

pom依賴

<!-- WebSocket -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-websocket</artifactId>
    <version>2.1.0.RELEASE</version>
</dependency>
後端
@Service
public
class UserOnlineInfoServiceImpl implements UserOnlineInfoService { @Autowired private UserDao userDao; @Autowired private RoleDao roleDao; @Autowired private UserOnlineInfoDao userOnlineInfoDao; /** * 根據條件查詢 * @param userOnlineQuery * @return */ @Override
public List<UserOnlineInfoDTO> query(UserOnlineQuery userOnlineQuery) { try { UserOnlineInfo onlineInfo = new UserOnlineInfo(); if (userOnlineQuery != null) { if (StrUtil.isBlank(userOnlineQuery.getName())) { userOnlineQuery.setName(
null); } if (StrUtil.isBlank(userOnlineQuery.getCode())) { userOnlineQuery.setCode(null); } BeanUtils.copyProperties(userOnlineQuery, onlineInfo); } List<UserOnlineInfo> onlineInfos = userOnlineInfoDao.queryByCondition(onlineInfo); List<UserOnlineInfoDTO> onlineInfoDTOs = new ArrayList<>(onlineInfos.size()); onlineInfos.forEach(info -> { UserOnlineInfoDTO infoDTO = new UserOnlineInfoDTO(); BeanUtils.copyProperties(info, infoDTO); onlineInfoDTOs.add(infoDTO); }); return onlineInfoDTOs; } catch (Exception e) { throw new ServiceException(SystemManageExceptionEnum.ONLINEUSER_MANAGE_QUERY_FALSE, e); } } /** * 型別轉換 * * @Param: [dto] * @Return: com.boss.bes.user.permission.dao.entity.UserOnlineInfoPo * @Date: 2020/9/2 15:27 */ public UserOnlineInfo doObjectTransfer(UserOnlineInfoDTO dto) { if (dto == null) { return null; } UserOnlineInfo userOnlineInfoPo = new UserOnlineInfo(); BeanUtil.copyProperties(dto, userOnlineInfoPo); return userOnlineInfoPo; } /** * 型別轉換 * * @Param: [entity] * @Return: com.boss.bes.user.permission.pojo.dto.UserOnlineInfoDto * @Date: 2020/9/2 15:27 */ public UserOnlineInfoDTO doObjectTransfer(UserOnlineInfo entity) { if (entity == null) { return null; } UserOnlineInfoDTO userOnlineInfoDto = new UserOnlineInfoDTO(); BeanUtil.copyProperties(entity, userOnlineInfoDto); return userOnlineInfoDto; } @Override public UserOnlineInfoDTO userLogin(UserOnlineInfoDTO userOnlineInfoDTO) { if (userOnlineInfoDTO == null || userOnlineInfoDTO.getUserId() == null) { return null; } try { User user = userDao.get(userOnlineInfoDTO.getUserId()); if (user == null) { return null; } UserOnlineInfo userOnlineInfo = new UserOnlineInfo(); userOnlineInfo.setUserId(user.getId()); userOnlineInfo.setCode(user.getCode()); userOnlineInfo.setName(user.getName()); userOnlineInfo.setIp(userOnlineInfoDTO.getIp()); userOnlineInfo.setOnlineTime(new Date()); userOnlineInfoDao.save(userOnlineInfo); BeanUtils.copyProperties(userOnlineInfo, userOnlineInfoDTO); return userOnlineInfoDTO; } catch (Exception e) { throw new ServiceException(SystemManageExceptionEnum.ONLINEUSER_MANAGE_LOGIN_FALSE, e); } } @Override public List<UserOnlineInfoDTO> forceUserLogout(List<UserOnlineInfoDTO> infoDTOs) { if (CollectionUtil.isEmpty(infoDTOs)) { return new ArrayList<>(); } //過濾重複的,或為null的使用者id Set<Long> userIdSet = infoDTOs.stream() .filter(dto -> dto != null && dto.getUserId() != null) .map(UserOnlineInfoDTO::getUserId) .collect(Collectors.toSet()); try { if (CollectionUtil.isEmpty(userIdSet)) { return new ArrayList<>(); } //過濾非本公司或者本機構的使用者 Set<Long> safeUserIdSet = roleDao.queryUserIdIn(userIdSet).stream() .map(BaseEntity::getId) .collect(Collectors.toSet()); if (CollectionUtil.isEmpty(safeUserIdSet)) { return new ArrayList<>(); } infoDTOs.removeIf(infoDTO -> !safeUserIdSet.contains(infoDTO.getUserId())); if (CollectionUtil.isEmpty(infoDTOs)) { return new ArrayList<>(); } List<UserOnlineInfo> infos = new ArrayList<>(infoDTOs.size()); Date currentDate = new Date(); infoDTOs.forEach(infoDTO -> { UserOnlineInfo onlineInfo = new UserOnlineInfo(); infoDTO.setOfflineTime(currentDate); //下線狀態 infoDTO.setStatus((byte) 1); BeanUtils.copyProperties(infoDTO, onlineInfo); long duration = (currentDate.getTime() - infoDTO.getOnlineTime().getTime()) / (1000); onlineInfo.setStopTime((int) duration); infos.add(onlineInfo); }); userOnlineInfoDao.batchUpdate(infos); return infoDTOs; } catch (Exception e) { throw new ServiceException(SystemManageExceptionEnum.ONLINEUSER_MANAGE_LOGOUT_FALSE, e); } } @Override public boolean userLogout(UserOnlineInfoDTO infoDTO) { if (infoDTO == null) { return false; } try { infoDTO.setOfflineTime(new Date()); long duration = (infoDTO.getOfflineTime().getTime() - infoDTO.getOnlineTime().getTime()) / (1000); infoDTO.setStopTime((int) duration); //下線狀態 infoDTO.setStatus((byte) 1); UserOnlineInfo onlineInfo = new UserOnlineInfo(); BeanUtils.copyProperties(infoDTO, onlineInfo); return userOnlineInfoDao.unsafeUpdate(onlineInfo) > 0; } catch (Exception e) { throw new ServiceException(SystemManageExceptionEnum.ONLINEUSER_MANAGE_LOGOUT_FALSE, e); } } }


前端
 Vuex中在使用者登入呼叫的方法中加入建立websocket的程式碼,在使用者登入的時候就傳送請求,與伺服器進行連線,全程一直保持連線,接受伺服器的資訊。
     // 與伺服器建立連線
          if (WebSocket) {
            const socket = new WebSocket(`${BASE_URL}/permission/websocket/${user.id}`)
            commit('SET_SOCKET', socket)
            socket.onopen = (e) => {
              heartCheck.start()
            }
            socket.onmessage = (e) => {
              console.log(e)
              try {
                const res = JSON.parse(e.data)
                if (res && res.body) {
                  // 收到強制下線請求
                  if (res.body === 'logout') {
                    if (state.socket) {
                      const socket = state.socket
                      commit('SET_SOCKET', null)
                      socket.close()
                    }
                    MessageBox.confirm('你已經被管理員強制下線了', '強制下線通知', {
                      confirmButtonText: '重新登入',
                      cancelButtonText: '停留在此頁',
                      type: 'warning'
                    }).then(() => {
                      // 登出
                      dispatch('logout').then(() => {
                        location.reload()
                      })
                    }).catch(() => {
                      // 清除token資訊
                      dispatch('resetToken').then(() => {
                      })
                    })
                  }
                  // 收到被頂下線請求
                  if (res.body === 'replaceLogout') {
                    if (state.socket) {
                      const socket = state.socket
                      commit('SET_SOCKET', null)
                      socket.close()
                    }
                    MessageBox.confirm('你已經其他地方登入', '強制下線通知', {
                      confirmButtonText: '重新登入',
                      cancelButtonText: '停留在此頁',
                      type: 'warning'
                    }).then(() => {
                      // 登出
                      dispatch('logout').then(() => {
                        location.reload()
                      })
                    }).catch(() => {
                      // 清除token資訊
                      dispatch('resetToken').then(() => {
                      })
                    })
                  }
                }
              } catch (err) {
                console.log(err)
              }
            }
            socket.onclose = (e) => {
              console.log(e)
              heartCheck.clear()
              dispatch('resetToken').then(() => {})
            }
            socket.onerror = (e) => {
              console.log(e)
              Message({
                message: '連線伺服器失敗',
                type: 'error',
                duration: 5 * 1000
              })
            }
          }