1、前端引入websocket
2、实现预警数据通过websocket统计在右上角 3、使用redis记录websocket客户端用户
This commit is contained in:
parent
472af60970
commit
05cd60971c
|
|
@ -164,6 +164,25 @@ public class RedisService {
|
|||
return redisTemplate.opsForSet().members(key);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* set中移除指定元素
|
||||
* @param key redis键
|
||||
* @param value set值
|
||||
* @param <T> obj
|
||||
* @return 删除数量
|
||||
*/
|
||||
public <T> Long removeSet(String key, T value) {
|
||||
Long size = null;
|
||||
try {
|
||||
size = redisTemplate.opsForSet().remove(key, value);
|
||||
} catch (Exception e) {
|
||||
return size;
|
||||
}
|
||||
return size;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 缓存Map
|
||||
*
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ import org.springframework.context.annotation.ComponentScan;
|
|||
import org.springframework.context.annotation.EnableAspectJAutoProxy;
|
||||
import org.springframework.context.annotation.Import;
|
||||
import org.springframework.scheduling.annotation.EnableAsync;
|
||||
import org.springframework.transaction.annotation.EnableTransactionManagement;
|
||||
|
||||
import java.lang.annotation.*;
|
||||
|
||||
|
|
@ -24,6 +25,7 @@ import java.lang.annotation.*;
|
|||
@Import({ ApplicationConfig.class, FeignAutoConfiguration.class })
|
||||
//自定义bean扫描,添加xjs路径下的bean
|
||||
@ComponentScan(basePackages = {"com.ruoyi","com.xjs"})
|
||||
@EnableTransactionManagement
|
||||
public @interface EnableCustomConfig
|
||||
{
|
||||
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ VUE_APP_TITLE = 管理平台
|
|||
# 开发环境配置
|
||||
ENV = 'development'
|
||||
|
||||
# 若依管理系统/开发环境
|
||||
# 管理系统/开发环境
|
||||
VUE_APP_BASE_API = '/dev-api'
|
||||
|
||||
# 路由懒加载
|
||||
|
|
|
|||
|
|
@ -1,28 +1,34 @@
|
|||
<template>
|
||||
<div class="navbar">
|
||||
<hamburger id="hamburger-container" :is-active="sidebar.opened" class="hamburger-container" @toggleClick="toggleSideBar" />
|
||||
|
||||
<hamburger id="hamburger-container" :is-active="sidebar.opened" class="hamburger-container"
|
||||
@toggleClick="toggleSideBar"/>
|
||||
|
||||
<breadcrumb id="breadcrumb-container" class="breadcrumb-container" v-if="!topNav"/>
|
||||
<top-nav id="topmenu-container" class="topmenu-container" v-if="topNav"/>
|
||||
|
||||
<div class="right-menu">
|
||||
<el-badge :value="warnData.count" class=" hover-effect share-button">
|
||||
<el-button type="warning" icon="el-icon-check" circle style="max-width: 22px;max-height: 22px;"></el-button>
|
||||
</el-badge>
|
||||
|
||||
<template v-if="device!=='mobile'">
|
||||
<search id="header-search" class="right-menu-item" />
|
||||
<search id="header-search" class="right-menu-item"/>
|
||||
|
||||
<!--todo 右上角添加信息提示等功能-->
|
||||
|
||||
<screenfull id="screenfull" class="right-menu-item hover-effect" />
|
||||
<screenfull id="screenfull" class="right-menu-item hover-effect"/>
|
||||
|
||||
<el-tooltip content="布局大小" effect="dark" placement="bottom">
|
||||
<size-select id="size-select" class="right-menu-item hover-effect" />
|
||||
<size-select id="size-select" class="right-menu-item hover-effect"/>
|
||||
</el-tooltip>
|
||||
|
||||
</template>
|
||||
|
||||
|
||||
|
||||
<el-dropdown class="avatar-container right-menu-item hover-effect" trigger="click">
|
||||
<div class="avatar-wrapper">
|
||||
<img :src="avatar" class="user-avatar">
|
||||
<i class="el-icon-caret-bottom" />
|
||||
<i class="el-icon-caret-bottom"/>
|
||||
</div>
|
||||
<el-dropdown-menu slot="dropdown">
|
||||
<router-link to="/user/profile">
|
||||
|
|
@ -41,7 +47,7 @@
|
|||
</template>
|
||||
|
||||
<script>
|
||||
import { mapGetters } from 'vuex'
|
||||
import {mapGetters} from 'vuex'
|
||||
import Breadcrumb from '@/components/Breadcrumb'
|
||||
import TopNav from '@/components/TopNav'
|
||||
import Hamburger from '@/components/Hamburger'
|
||||
|
|
@ -62,11 +68,19 @@ export default {
|
|||
RuoYiGit,
|
||||
RuoYiDoc
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
warnData: {},
|
||||
}
|
||||
},
|
||||
|
||||
computed: {
|
||||
...mapGetters([
|
||||
'sidebar',
|
||||
'avatar',
|
||||
'device'
|
||||
'device',
|
||||
"$socket",
|
||||
]),
|
||||
setting: {
|
||||
get() {
|
||||
|
|
@ -85,7 +99,21 @@ export default {
|
|||
}
|
||||
}
|
||||
},
|
||||
|
||||
mounted() {
|
||||
this.$socket.registerCallBack(
|
||||
"apiWarning",
|
||||
this.getData
|
||||
);
|
||||
},
|
||||
|
||||
methods: {
|
||||
getData(data) {
|
||||
if (data) {
|
||||
this.warnData = data
|
||||
}
|
||||
},
|
||||
|
||||
toggleSideBar() {
|
||||
this.$store.dispatch('app/toggleSideBar')
|
||||
},
|
||||
|
|
@ -98,19 +126,41 @@ export default {
|
|||
this.$store.dispatch('LogOut').then(() => {
|
||||
location.href = '/index';
|
||||
})
|
||||
}).catch(() => {});
|
||||
}).catch(() => {
|
||||
});
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
beforeDestroy() {
|
||||
// 在组件销毁的时候, 进行回调函数的取消
|
||||
this.$socket.unRegisterCallBack();
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.share-button {
|
||||
margin-right: 23px;
|
||||
color: #5a5e66;
|
||||
padding-bottom: 22px;
|
||||
|
||||
&.hover-effect {
|
||||
cursor: pointer;
|
||||
transition: background .3s;
|
||||
|
||||
&:hover {
|
||||
background: rgba(0, 0, 0, .025)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
.navbar {
|
||||
height: 50px;
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
background: #fff;
|
||||
box-shadow: 0 1px 4px rgba(0,21,41,.08);
|
||||
box-shadow: 0 1px 4px rgba(0, 21, 41, .08);
|
||||
|
||||
.hamburger-container {
|
||||
line-height: 46px;
|
||||
|
|
@ -118,7 +168,7 @@ export default {
|
|||
float: left;
|
||||
cursor: pointer;
|
||||
transition: background .3s;
|
||||
-webkit-tap-highlight-color:transparent;
|
||||
-webkit-tap-highlight-color: transparent;
|
||||
|
||||
&:hover {
|
||||
background: rgba(0, 0, 0, .025)
|
||||
|
|
|
|||
|
|
@ -1,107 +1,128 @@
|
|||
<template>
|
||||
<div :class="classObj" class="app-wrapper" :style="{'--current-color': theme}">
|
||||
<div v-if="device==='mobile'&&sidebar.opened" class="drawer-bg" @click="handleClickOutside"/>
|
||||
<sidebar class="sidebar-container"/>
|
||||
<div :class="{hasTagsView:needTagsView}" class="main-container">
|
||||
<div :class="{'fixed-header':fixedHeader}">
|
||||
<navbar />
|
||||
<tags-view v-if="needTagsView" />
|
||||
</div>
|
||||
<app-main />
|
||||
<right-panel>
|
||||
<settings />
|
||||
</right-panel>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import RightPanel from '@/components/RightPanel'
|
||||
import { AppMain, Navbar, Settings, Sidebar, TagsView } from './components'
|
||||
import ResizeMixin from './mixin/ResizeHandler'
|
||||
import { mapState } from 'vuex'
|
||||
import variables from '@/assets/styles/variables.scss'
|
||||
|
||||
export default {
|
||||
name: 'Layout',
|
||||
components: {
|
||||
AppMain,
|
||||
Navbar,
|
||||
RightPanel,
|
||||
Settings,
|
||||
Sidebar,
|
||||
TagsView
|
||||
},
|
||||
mixins: [ResizeMixin],
|
||||
computed: {
|
||||
...mapState({
|
||||
theme: state => state.settings.theme,
|
||||
sideTheme: state => state.settings.sideTheme,
|
||||
sidebar: state => state.app.sidebar,
|
||||
device: state => state.app.device,
|
||||
needTagsView: state => state.settings.tagsView,
|
||||
fixedHeader: state => state.settings.fixedHeader
|
||||
}),
|
||||
classObj() {
|
||||
return {
|
||||
hideSidebar: !this.sidebar.opened,
|
||||
openSidebar: this.sidebar.opened,
|
||||
withoutAnimation: this.sidebar.withoutAnimation,
|
||||
mobile: this.device === 'mobile'
|
||||
}
|
||||
},
|
||||
variables() {
|
||||
return variables;
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
handleClickOutside() {
|
||||
this.$store.dispatch('app/closeSideBar', { withoutAnimation: false })
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import "~@/assets/styles/mixin.scss";
|
||||
@import "~@/assets/styles/variables.scss";
|
||||
|
||||
.app-wrapper {
|
||||
@include clearfix;
|
||||
position: relative;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
|
||||
&.mobile.openSidebar {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.drawer-bg {
|
||||
background: #000;
|
||||
opacity: 0.3;
|
||||
width: 100%;
|
||||
top: 0;
|
||||
height: 100%;
|
||||
position: absolute;
|
||||
z-index: 999;
|
||||
}
|
||||
|
||||
.fixed-header {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
right: 0;
|
||||
z-index: 9;
|
||||
width: calc(100% - #{$base-sidebar-width});
|
||||
transition: width 0.28s;
|
||||
}
|
||||
|
||||
.hideSidebar .fixed-header {
|
||||
width: calc(100% - 54px);
|
||||
}
|
||||
|
||||
.mobile .fixed-header {
|
||||
width: 100%;
|
||||
}
|
||||
</style>
|
||||
<template>
|
||||
<div :class="classObj" class="app-wrapper" :style="{'--current-color': theme}">
|
||||
<div v-if="device==='mobile'&&sidebar.opened" class="drawer-bg" @click="handleClickOutside"/>
|
||||
<sidebar class="sidebar-container"/>
|
||||
<div :class="{hasTagsView:needTagsView}" class="main-container">
|
||||
<div :class="{'fixed-header':fixedHeader}">
|
||||
<navbar/>
|
||||
<tags-view v-if="needTagsView"/>
|
||||
</div>
|
||||
<app-main/>
|
||||
<right-panel>
|
||||
<settings/>
|
||||
</right-panel>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import RightPanel from '@/components/RightPanel'
|
||||
import {AppMain, Navbar, Settings, Sidebar, TagsView} from './components'
|
||||
import ResizeMixin from './mixin/ResizeHandler'
|
||||
import {mapState} from 'vuex'
|
||||
import variables from '@/assets/styles/variables.scss'
|
||||
|
||||
import SocketService from "@/utils/socket-server";
|
||||
|
||||
export default {
|
||||
name: 'Layout',
|
||||
components: {
|
||||
AppMain,
|
||||
Navbar,
|
||||
RightPanel,
|
||||
Settings,
|
||||
Sidebar,
|
||||
TagsView
|
||||
},
|
||||
mixins: [ResizeMixin],
|
||||
computed: {
|
||||
...mapState({
|
||||
theme: state => state.settings.theme,
|
||||
sideTheme: state => state.settings.sideTheme,
|
||||
sidebar: state => state.app.sidebar,
|
||||
device: state => state.app.device,
|
||||
needTagsView: state => state.settings.tagsView,
|
||||
fixedHeader: state => state.settings.fixedHeader
|
||||
}),
|
||||
classObj() {
|
||||
return {
|
||||
hideSidebar: !this.sidebar.opened,
|
||||
openSidebar: this.sidebar.opened,
|
||||
withoutAnimation: this.sidebar.withoutAnimation,
|
||||
mobile: this.device === 'mobile'
|
||||
}
|
||||
},
|
||||
variables() {
|
||||
return variables;
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.connectWebsocket();
|
||||
},
|
||||
|
||||
methods: {
|
||||
//连接websocket
|
||||
connectWebsocket() {
|
||||
if (!this.$socket) {
|
||||
SocketService.Instance.connect();
|
||||
this.$store.dispatch("app/set$Socket", SocketService.Instance);
|
||||
}
|
||||
},
|
||||
|
||||
handleClickOutside() {
|
||||
this.$store.dispatch('app/closeSideBar', {withoutAnimation: false})
|
||||
}
|
||||
},
|
||||
|
||||
beforeDestroy() {
|
||||
if (this.$socket) {
|
||||
this.$socket.closeWebsocket();
|
||||
this.$store.dispatch("app/set$Socket", null);
|
||||
}
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import "~@/assets/styles/mixin.scss";
|
||||
@import "~@/assets/styles/variables.scss";
|
||||
|
||||
.app-wrapper {
|
||||
@include clearfix;
|
||||
position: relative;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
|
||||
&.mobile.openSidebar {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.drawer-bg {
|
||||
background: #000;
|
||||
opacity: 0.3;
|
||||
width: 100%;
|
||||
top: 0;
|
||||
height: 100%;
|
||||
position: absolute;
|
||||
z-index: 999;
|
||||
}
|
||||
|
||||
.fixed-header {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
right: 0;
|
||||
z-index: 9;
|
||||
width: calc(100% - #{$base-sidebar-width});
|
||||
transition: width 0.28s;
|
||||
}
|
||||
|
||||
.hideSidebar .fixed-header {
|
||||
width: calc(100% - 54px);
|
||||
}
|
||||
|
||||
.mobile .fixed-header {
|
||||
width: 100%;
|
||||
}
|
||||
</style>
|
||||
|
|
|
|||
|
|
@ -1,18 +1,19 @@
|
|||
const getters = {
|
||||
sidebar: state => state.app.sidebar,
|
||||
size: state => state.app.size,
|
||||
device: state => state.app.device,
|
||||
visitedViews: state => state.tagsView.visitedViews,
|
||||
cachedViews: state => state.tagsView.cachedViews,
|
||||
token: state => state.user.token,
|
||||
avatar: state => state.user.avatar,
|
||||
name: state => state.user.name,
|
||||
introduction: state => state.user.introduction,
|
||||
roles: state => state.user.roles,
|
||||
permissions: state => state.user.permissions,
|
||||
permission_routes: state => state.permission.routes,
|
||||
topbarRouters:state => state.permission.topbarRouters,
|
||||
defaultRoutes:state => state.permission.defaultRoutes,
|
||||
sidebarRouters:state => state.permission.sidebarRouters,
|
||||
}
|
||||
export default getters
|
||||
const getters = {
|
||||
sidebar: state => state.app.sidebar,
|
||||
size: state => state.app.size,
|
||||
device: state => state.app.device,
|
||||
visitedViews: state => state.tagsView.visitedViews,
|
||||
cachedViews: state => state.tagsView.cachedViews,
|
||||
token: state => state.user.token,
|
||||
avatar: state => state.user.avatar,
|
||||
name: state => state.user.name,
|
||||
introduction: state => state.user.introduction,
|
||||
roles: state => state.user.roles,
|
||||
permissions: state => state.user.permissions,
|
||||
permission_routes: state => state.permission.routes,
|
||||
topbarRouters:state => state.permission.topbarRouters,
|
||||
defaultRoutes:state => state.permission.defaultRoutes,
|
||||
sidebarRouters:state => state.permission.sidebarRouters,
|
||||
$socket: state => state.app.$socket,
|
||||
}
|
||||
export default getters
|
||||
|
|
|
|||
|
|
@ -1,56 +1,64 @@
|
|||
import Cookies from 'js-cookie'
|
||||
|
||||
const state = {
|
||||
sidebar: {
|
||||
opened: Cookies.get('sidebarStatus') ? !!+Cookies.get('sidebarStatus') : true,
|
||||
withoutAnimation: false
|
||||
},
|
||||
device: 'desktop',
|
||||
size: Cookies.get('size') || 'medium'
|
||||
}
|
||||
|
||||
const mutations = {
|
||||
TOGGLE_SIDEBAR: state => {
|
||||
state.sidebar.opened = !state.sidebar.opened
|
||||
state.sidebar.withoutAnimation = false
|
||||
if (state.sidebar.opened) {
|
||||
Cookies.set('sidebarStatus', 1)
|
||||
} else {
|
||||
Cookies.set('sidebarStatus', 0)
|
||||
}
|
||||
},
|
||||
CLOSE_SIDEBAR: (state, withoutAnimation) => {
|
||||
Cookies.set('sidebarStatus', 0)
|
||||
state.sidebar.opened = false
|
||||
state.sidebar.withoutAnimation = withoutAnimation
|
||||
},
|
||||
TOGGLE_DEVICE: (state, device) => {
|
||||
state.device = device
|
||||
},
|
||||
SET_SIZE: (state, size) => {
|
||||
state.size = size
|
||||
Cookies.set('size', size)
|
||||
}
|
||||
}
|
||||
|
||||
const actions = {
|
||||
toggleSideBar({ commit }) {
|
||||
commit('TOGGLE_SIDEBAR')
|
||||
},
|
||||
closeSideBar({ commit }, { withoutAnimation }) {
|
||||
commit('CLOSE_SIDEBAR', withoutAnimation)
|
||||
},
|
||||
toggleDevice({ commit }, device) {
|
||||
commit('TOGGLE_DEVICE', device)
|
||||
},
|
||||
setSize({ commit }, size) {
|
||||
commit('SET_SIZE', size)
|
||||
}
|
||||
}
|
||||
|
||||
export default {
|
||||
namespaced: true,
|
||||
state,
|
||||
mutations,
|
||||
actions
|
||||
}
|
||||
import Cookies from 'js-cookie'
|
||||
|
||||
const state = {
|
||||
sidebar: {
|
||||
opened: Cookies.get('sidebarStatus') ? !!+Cookies.get('sidebarStatus') : true,
|
||||
withoutAnimation: false
|
||||
},
|
||||
device: 'desktop',
|
||||
size: Cookies.get('size') || 'medium',
|
||||
// websocket实例
|
||||
$socket: null
|
||||
}
|
||||
|
||||
const mutations = {
|
||||
TOGGLE_SIDEBAR: state => {
|
||||
state.sidebar.opened = !state.sidebar.opened
|
||||
state.sidebar.withoutAnimation = false
|
||||
if (state.sidebar.opened) {
|
||||
Cookies.set('sidebarStatus', 1)
|
||||
} else {
|
||||
Cookies.set('sidebarStatus', 0)
|
||||
}
|
||||
},
|
||||
CLOSE_SIDEBAR: (state, withoutAnimation) => {
|
||||
Cookies.set('sidebarStatus', 0)
|
||||
state.sidebar.opened = false
|
||||
state.sidebar.withoutAnimation = withoutAnimation
|
||||
},
|
||||
TOGGLE_DEVICE: (state, device) => {
|
||||
state.device = device
|
||||
},
|
||||
SET_SIZE: (state, size) => {
|
||||
state.size = size
|
||||
Cookies.set('size', size)
|
||||
},
|
||||
SET_$SOCKET: (state, socket) => {
|
||||
state.$socket = socket
|
||||
}
|
||||
}
|
||||
|
||||
const actions = {
|
||||
toggleSideBar({commit}) {
|
||||
commit('TOGGLE_SIDEBAR')
|
||||
},
|
||||
closeSideBar({commit}, {withoutAnimation}) {
|
||||
commit('CLOSE_SIDEBAR', withoutAnimation)
|
||||
},
|
||||
toggleDevice({commit}, device) {
|
||||
commit('TOGGLE_DEVICE', device)
|
||||
},
|
||||
setSize({commit}, size) {
|
||||
commit('SET_SIZE', size)
|
||||
},
|
||||
set$Socket({commit}, socket) {
|
||||
commit('SET_$SOCKET', socket)
|
||||
}
|
||||
}
|
||||
|
||||
export default {
|
||||
namespaced: true,
|
||||
state,
|
||||
mutations,
|
||||
actions
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,134 @@
|
|||
import { Message } from "element-ui";
|
||||
import store from '@/store'
|
||||
// let wsUrl = 'ws://127.0.0.1:9002/warning/api';
|
||||
|
||||
export default class SocketService {
|
||||
/**
|
||||
* 单例
|
||||
*/
|
||||
static instance = null;
|
||||
static get Instance() {
|
||||
if (!this.instance) {
|
||||
this.instance = new SocketService();
|
||||
}
|
||||
return this.instance;
|
||||
}
|
||||
|
||||
// 和服务端连接的socket对象
|
||||
ws = null;
|
||||
|
||||
// 存储回调函数
|
||||
callBackMapping = {};
|
||||
|
||||
// 标识是否连接成功
|
||||
connected = false;
|
||||
|
||||
// 记录重试的次数
|
||||
sendRetryCount = 0;
|
||||
|
||||
// 重新连接尝试的次数
|
||||
connectRetryCount = 0;
|
||||
|
||||
// 是否手动关闭websocket连接
|
||||
isManualClose = false
|
||||
|
||||
// 定义连接服务器的方法
|
||||
connect() {
|
||||
// 连接服务器
|
||||
if (!window.WebSocket) {
|
||||
return console.log("您的浏览器不支持WebSocket");
|
||||
}
|
||||
//网关转发
|
||||
let wsUrl = 'ws://localhost:8080/warning/warning/api';
|
||||
wsUrl += `/${store.getters.name}`
|
||||
this.ws = new WebSocket(wsUrl)
|
||||
|
||||
// 连接成功的事件
|
||||
this.ws.onopen = () => {
|
||||
console.log("连接服务端成功了");
|
||||
this.connected = true;
|
||||
// 重置重新连接的次数
|
||||
this.connectRetryCount = 0;
|
||||
};
|
||||
// 1.连接服务端失败
|
||||
// 2.当连接成功之后, 服务器关闭的情况
|
||||
this.ws.onclose = (event) => {
|
||||
// e.code === 1000 表示正常关闭。 无论为何目的而创建, 该链接都已成功完成任务。
|
||||
// e.code !== 1000 表示非正常关闭。
|
||||
console.log("onclose event: ", event);
|
||||
this.connected = false;
|
||||
if (event && event.code !== 1000) {
|
||||
console.log("连接服务端失败");
|
||||
// 如果不是手动关闭,这里的重连会执行;如果调用了手动关闭函数,这里重连不会执行
|
||||
this.connectRetryCount++;
|
||||
setTimeout(() => {
|
||||
this.connect();
|
||||
}, 500 * this.connectRetryCount);
|
||||
}
|
||||
};
|
||||
// 得到服务端发送过来的数据
|
||||
this.ws.onmessage = (msg) => {
|
||||
console.log("从服务端获取到了数据");
|
||||
// 真正服务端发送过来的原始数据时在msg中的data字段
|
||||
const recvData = JSON.parse(msg.data);
|
||||
console.log(recvData)
|
||||
const socketType = recvData.socketType;
|
||||
if (this.callBackMapping[socketType]){
|
||||
this.callBackMapping[socketType].call(this, recvData);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// 回调函数的注册
|
||||
registerCallBack(socketType, callBack) {
|
||||
this.callBackMapping[socketType] = callBack;
|
||||
}
|
||||
|
||||
// 取消某一个回调函数
|
||||
unRegisterCallBack(socketType) {
|
||||
this.callBackMapping[socketType] = null;
|
||||
}
|
||||
|
||||
|
||||
// 发送数据的方法
|
||||
send(data) {
|
||||
// 判断此时此刻有没有连接成功
|
||||
if (this.connected) {
|
||||
this.sendRetryCount = 0;
|
||||
this.ws.send(JSON.stringify(data));
|
||||
console.log('xxxxxxxxxxxx')
|
||||
} else if (!this.isManualClose) {
|
||||
console.log('=============')
|
||||
this.sendRetryCount++;
|
||||
setTimeout(() => {
|
||||
this.send(data);
|
||||
}, this.sendRetryCount * 500);
|
||||
}
|
||||
}
|
||||
|
||||
// 手动关闭websocket (这里手动关闭会执行onclose事件)
|
||||
closeWebsocket() {
|
||||
if (this.ws) {
|
||||
try {
|
||||
this.ws.close(); // 关闭websocket
|
||||
this.ws = null
|
||||
this.callBackMapping = {}
|
||||
this.connected = false
|
||||
this.sendRetryCount = 0
|
||||
this.connectRetryCount = 0
|
||||
this.isManualClose = true
|
||||
this.instance = null
|
||||
SocketService.instance = null
|
||||
} catch (error) {
|
||||
console.log(error)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const MessageEvent = { // onmessage回调函数的event
|
||||
data: { // websocket通讯接收到的数据
|
||||
socketType: '', // websocket双方通讯的名称
|
||||
data: {}, // 返回的真实数据
|
||||
}
|
||||
}
|
||||
|
|
@ -24,6 +24,11 @@ public class RedisConst {
|
|||
*/
|
||||
public static final String HOT = "hot";
|
||||
|
||||
/**
|
||||
* websocket常量key
|
||||
*/
|
||||
public static final String WEBSOCKET= "WEBSOCKET";
|
||||
|
||||
|
||||
//-------------------有效时间-----------------------
|
||||
public static final Integer TRAN_DICT_EXPIRE = 7; //天
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
package com.xjs.controller;
|
||||
|
||||
import com.alibaba.fastjson.JSONObject;
|
||||
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
|
||||
import com.ruoyi.common.core.domain.R;
|
||||
import com.ruoyi.common.core.utils.poi.ExcelUtil;
|
||||
|
|
@ -8,16 +9,23 @@ import com.ruoyi.common.core.web.domain.AjaxResult;
|
|||
import com.ruoyi.common.core.web.page.TableDataInfo;
|
||||
import com.ruoyi.common.log.annotation.Log;
|
||||
import com.ruoyi.common.log.enums.BusinessType;
|
||||
import com.ruoyi.common.redis.service.RedisService;
|
||||
import com.ruoyi.common.security.annotation.RequiresPermissions;
|
||||
import com.xjs.domain.ApiRecord;
|
||||
import com.xjs.domain.ApiWarning;
|
||||
import com.xjs.server.WebSocketServer;
|
||||
import com.xjs.service.ApiWarningService;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import java.io.IOException;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
|
||||
import static com.xjs.consts.RedisConst.WEBSOCKET;
|
||||
|
||||
/**
|
||||
* @author xiejs
|
||||
|
|
@ -30,6 +38,8 @@ public class ApiWarningController extends BaseController {
|
|||
|
||||
@Autowired
|
||||
private ApiWarningService apiWarningService;
|
||||
@Autowired
|
||||
private RedisService redisService;
|
||||
|
||||
/**
|
||||
* 远程保存 apiRecord
|
||||
|
|
@ -66,17 +76,41 @@ public class ApiWarningController extends BaseController {
|
|||
}
|
||||
|
||||
/**
|
||||
* 远程保存api预警信息
|
||||
* 远程保存api预警信息并websocket推送
|
||||
*
|
||||
* @param apiWarning 预警实体类
|
||||
* @return R
|
||||
*/
|
||||
@PostMapping("saveApiwarningForRPC")
|
||||
@Transactional
|
||||
public R<ApiWarning> saveApiWarningForRPC(@RequestBody ApiWarning apiWarning) {
|
||||
boolean save = apiWarningService.save(apiWarning);
|
||||
|
||||
this.websocketPush(apiWarning);
|
||||
|
||||
return save ? R.ok() : R.fail();
|
||||
}
|
||||
|
||||
/**
|
||||
* websocket推送
|
||||
*/
|
||||
private void websocketPush(ApiWarning apiWarning) {
|
||||
long count = apiWarningService.count();
|
||||
Set<String> cacheSet = redisService.getCacheSet(WEBSOCKET);
|
||||
JSONObject jsonData =new JSONObject();
|
||||
JSONObject jsonObject = (JSONObject) JSONObject.toJSON(apiWarning);
|
||||
jsonData.put("count", count);
|
||||
jsonData.put("data", jsonObject.toJSONString());
|
||||
jsonData.put("socketType", "apiWarning");
|
||||
for (String userId : cacheSet) {
|
||||
try {
|
||||
WebSocketServer.sendInfo(jsonData.toString(),userId);
|
||||
} catch (IOException e) {
|
||||
logger.error(e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 查询api预警列表
|
||||
|
|
|
|||
|
|
@ -1,15 +1,23 @@
|
|||
package com.xjs.server;
|
||||
|
||||
import com.alibaba.fastjson.JSONObject;
|
||||
import com.ruoyi.common.redis.service.RedisService;
|
||||
import com.xjs.service.ApiWarningService;
|
||||
import lombok.extern.log4j.Log4j2;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import javax.websocket.*;
|
||||
import javax.websocket.server.PathParam;
|
||||
import javax.websocket.server.ServerEndpoint;
|
||||
import java.io.IOException;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
import static com.xjs.consts.RedisConst.WEBSOCKET;
|
||||
|
||||
/**
|
||||
* api预警websocket
|
||||
*
|
||||
|
|
@ -17,9 +25,25 @@ import java.util.concurrent.ConcurrentHashMap;
|
|||
* @since 2022-01-13
|
||||
*/
|
||||
@Log4j2
|
||||
@ServerEndpoint("warning/api/{userId}")
|
||||
@ServerEndpoint("/warning/api/{userId}")
|
||||
@Component
|
||||
public class WebSocketServer {
|
||||
/**
|
||||
* 之所有需要set注入,因为WebSocketServer是静态类,需要注入属性就需要用set注入
|
||||
*/
|
||||
private static RedisService redisService;
|
||||
@Autowired
|
||||
public void setRedisService(RedisService redisService) {
|
||||
WebSocketServer.redisService = redisService;
|
||||
}
|
||||
|
||||
private static ApiWarningService apiWarningService;
|
||||
@Autowired
|
||||
public void setApiWarningService(ApiWarningService apiWarningService) {
|
||||
WebSocketServer.apiWarningService = apiWarningService;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 静态变量,用来记录当前在线连接数。应该把它设计成线程安全的。
|
||||
*/
|
||||
|
|
@ -56,12 +80,21 @@ public class WebSocketServer {
|
|||
}
|
||||
|
||||
log.info("用户连接:" + userId + ",当前在线人数为:" + getOnlineCount());
|
||||
Set<String> set = new HashSet<>();
|
||||
set.add(userId);
|
||||
redisService.setCacheSet(WEBSOCKET, set);
|
||||
|
||||
long count = apiWarningService.count();
|
||||
JSONObject jsonData =new JSONObject();
|
||||
jsonData.put("count", count);
|
||||
jsonData.put("socketType", "apiWarning");
|
||||
jsonData.put("data", "{}");
|
||||
try {
|
||||
sendMessage("连接成功");
|
||||
sendMessage(jsonData.toJSONString());
|
||||
} catch (IOException e) {
|
||||
log.error("用户:" + userId + ",网络异常!!!!!!");
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -73,6 +106,9 @@ public class WebSocketServer {
|
|||
webSocketMap.remove(userId);
|
||||
//从set中删除
|
||||
subOnlineCount();
|
||||
|
||||
//退出移出用户
|
||||
redisService.removeSet(WEBSOCKET, this.userId);
|
||||
}
|
||||
log.info("用户退出:" + userId + ",当前在线人数为:" + getOnlineCount());
|
||||
}
|
||||
|
|
@ -107,6 +143,7 @@ public class WebSocketServer {
|
|||
@OnError
|
||||
public void onError(Session session, Throwable error) {
|
||||
log.error("用户错误:" + this.userId + ",原因:" + error.getMessage());
|
||||
redisService.removeSet(WEBSOCKET, this.userId);
|
||||
error.printStackTrace();
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue