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);
|
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
|
* 缓存Map
|
||||||
*
|
*
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,7 @@ import org.springframework.context.annotation.ComponentScan;
|
||||||
import org.springframework.context.annotation.EnableAspectJAutoProxy;
|
import org.springframework.context.annotation.EnableAspectJAutoProxy;
|
||||||
import org.springframework.context.annotation.Import;
|
import org.springframework.context.annotation.Import;
|
||||||
import org.springframework.scheduling.annotation.EnableAsync;
|
import org.springframework.scheduling.annotation.EnableAsync;
|
||||||
|
import org.springframework.transaction.annotation.EnableTransactionManagement;
|
||||||
|
|
||||||
import java.lang.annotation.*;
|
import java.lang.annotation.*;
|
||||||
|
|
||||||
|
|
@ -24,6 +25,7 @@ import java.lang.annotation.*;
|
||||||
@Import({ ApplicationConfig.class, FeignAutoConfiguration.class })
|
@Import({ ApplicationConfig.class, FeignAutoConfiguration.class })
|
||||||
//自定义bean扫描,添加xjs路径下的bean
|
//自定义bean扫描,添加xjs路径下的bean
|
||||||
@ComponentScan(basePackages = {"com.ruoyi","com.xjs"})
|
@ComponentScan(basePackages = {"com.ruoyi","com.xjs"})
|
||||||
|
@EnableTransactionManagement
|
||||||
public @interface EnableCustomConfig
|
public @interface EnableCustomConfig
|
||||||
{
|
{
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@ VUE_APP_TITLE = 管理平台
|
||||||
# 开发环境配置
|
# 开发环境配置
|
||||||
ENV = 'development'
|
ENV = 'development'
|
||||||
|
|
||||||
# 若依管理系统/开发环境
|
# 管理系统/开发环境
|
||||||
VUE_APP_BASE_API = '/dev-api'
|
VUE_APP_BASE_API = '/dev-api'
|
||||||
|
|
||||||
# 路由懒加载
|
# 路由懒加载
|
||||||
|
|
|
||||||
|
|
@ -1,28 +1,34 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="navbar">
|
<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"/>
|
<breadcrumb id="breadcrumb-container" class="breadcrumb-container" v-if="!topNav"/>
|
||||||
<top-nav id="topmenu-container" class="topmenu-container" v-if="topNav"/>
|
<top-nav id="topmenu-container" class="topmenu-container" v-if="topNav"/>
|
||||||
|
|
||||||
<div class="right-menu">
|
<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'">
|
<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">
|
<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>
|
</el-tooltip>
|
||||||
|
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<el-dropdown class="avatar-container right-menu-item hover-effect" trigger="click">
|
<el-dropdown class="avatar-container right-menu-item hover-effect" trigger="click">
|
||||||
<div class="avatar-wrapper">
|
<div class="avatar-wrapper">
|
||||||
<img :src="avatar" class="user-avatar">
|
<img :src="avatar" class="user-avatar">
|
||||||
<i class="el-icon-caret-bottom" />
|
<i class="el-icon-caret-bottom"/>
|
||||||
</div>
|
</div>
|
||||||
<el-dropdown-menu slot="dropdown">
|
<el-dropdown-menu slot="dropdown">
|
||||||
<router-link to="/user/profile">
|
<router-link to="/user/profile">
|
||||||
|
|
@ -41,7 +47,7 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import { mapGetters } from 'vuex'
|
import {mapGetters} from 'vuex'
|
||||||
import Breadcrumb from '@/components/Breadcrumb'
|
import Breadcrumb from '@/components/Breadcrumb'
|
||||||
import TopNav from '@/components/TopNav'
|
import TopNav from '@/components/TopNav'
|
||||||
import Hamburger from '@/components/Hamburger'
|
import Hamburger from '@/components/Hamburger'
|
||||||
|
|
@ -62,11 +68,19 @@ export default {
|
||||||
RuoYiGit,
|
RuoYiGit,
|
||||||
RuoYiDoc
|
RuoYiDoc
|
||||||
},
|
},
|
||||||
|
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
warnData: {},
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
computed: {
|
computed: {
|
||||||
...mapGetters([
|
...mapGetters([
|
||||||
'sidebar',
|
'sidebar',
|
||||||
'avatar',
|
'avatar',
|
||||||
'device'
|
'device',
|
||||||
|
"$socket",
|
||||||
]),
|
]),
|
||||||
setting: {
|
setting: {
|
||||||
get() {
|
get() {
|
||||||
|
|
@ -85,7 +99,21 @@ export default {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
mounted() {
|
||||||
|
this.$socket.registerCallBack(
|
||||||
|
"apiWarning",
|
||||||
|
this.getData
|
||||||
|
);
|
||||||
|
},
|
||||||
|
|
||||||
methods: {
|
methods: {
|
||||||
|
getData(data) {
|
||||||
|
if (data) {
|
||||||
|
this.warnData = data
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
toggleSideBar() {
|
toggleSideBar() {
|
||||||
this.$store.dispatch('app/toggleSideBar')
|
this.$store.dispatch('app/toggleSideBar')
|
||||||
},
|
},
|
||||||
|
|
@ -98,19 +126,41 @@ export default {
|
||||||
this.$store.dispatch('LogOut').then(() => {
|
this.$store.dispatch('LogOut').then(() => {
|
||||||
location.href = '/index';
|
location.href = '/index';
|
||||||
})
|
})
|
||||||
}).catch(() => {});
|
}).catch(() => {
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
|
|
||||||
|
beforeDestroy() {
|
||||||
|
// 在组件销毁的时候, 进行回调函数的取消
|
||||||
|
this.$socket.unRegisterCallBack();
|
||||||
|
},
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<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 {
|
.navbar {
|
||||||
height: 50px;
|
height: 50px;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
position: relative;
|
position: relative;
|
||||||
background: #fff;
|
background: #fff;
|
||||||
box-shadow: 0 1px 4px rgba(0,21,41,.08);
|
box-shadow: 0 1px 4px rgba(0, 21, 41, .08);
|
||||||
|
|
||||||
.hamburger-container {
|
.hamburger-container {
|
||||||
line-height: 46px;
|
line-height: 46px;
|
||||||
|
|
@ -118,7 +168,7 @@ export default {
|
||||||
float: left;
|
float: left;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
transition: background .3s;
|
transition: background .3s;
|
||||||
-webkit-tap-highlight-color:transparent;
|
-webkit-tap-highlight-color: transparent;
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
background: rgba(0, 0, 0, .025)
|
background: rgba(0, 0, 0, .025)
|
||||||
|
|
|
||||||
|
|
@ -1,107 +1,128 @@
|
||||||
<template>
|
<template>
|
||||||
<div :class="classObj" class="app-wrapper" :style="{'--current-color': theme}">
|
<div :class="classObj" class="app-wrapper" :style="{'--current-color': theme}">
|
||||||
<div v-if="device==='mobile'&&sidebar.opened" class="drawer-bg" @click="handleClickOutside"/>
|
<div v-if="device==='mobile'&&sidebar.opened" class="drawer-bg" @click="handleClickOutside"/>
|
||||||
<sidebar class="sidebar-container"/>
|
<sidebar class="sidebar-container"/>
|
||||||
<div :class="{hasTagsView:needTagsView}" class="main-container">
|
<div :class="{hasTagsView:needTagsView}" class="main-container">
|
||||||
<div :class="{'fixed-header':fixedHeader}">
|
<div :class="{'fixed-header':fixedHeader}">
|
||||||
<navbar />
|
<navbar/>
|
||||||
<tags-view v-if="needTagsView" />
|
<tags-view v-if="needTagsView"/>
|
||||||
</div>
|
</div>
|
||||||
<app-main />
|
<app-main/>
|
||||||
<right-panel>
|
<right-panel>
|
||||||
<settings />
|
<settings/>
|
||||||
</right-panel>
|
</right-panel>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import RightPanel from '@/components/RightPanel'
|
import RightPanel from '@/components/RightPanel'
|
||||||
import { AppMain, Navbar, Settings, Sidebar, TagsView } from './components'
|
import {AppMain, Navbar, Settings, Sidebar, TagsView} from './components'
|
||||||
import ResizeMixin from './mixin/ResizeHandler'
|
import ResizeMixin from './mixin/ResizeHandler'
|
||||||
import { mapState } from 'vuex'
|
import {mapState} from 'vuex'
|
||||||
import variables from '@/assets/styles/variables.scss'
|
import variables from '@/assets/styles/variables.scss'
|
||||||
|
|
||||||
export default {
|
import SocketService from "@/utils/socket-server";
|
||||||
name: 'Layout',
|
|
||||||
components: {
|
export default {
|
||||||
AppMain,
|
name: 'Layout',
|
||||||
Navbar,
|
components: {
|
||||||
RightPanel,
|
AppMain,
|
||||||
Settings,
|
Navbar,
|
||||||
Sidebar,
|
RightPanel,
|
||||||
TagsView
|
Settings,
|
||||||
},
|
Sidebar,
|
||||||
mixins: [ResizeMixin],
|
TagsView
|
||||||
computed: {
|
},
|
||||||
...mapState({
|
mixins: [ResizeMixin],
|
||||||
theme: state => state.settings.theme,
|
computed: {
|
||||||
sideTheme: state => state.settings.sideTheme,
|
...mapState({
|
||||||
sidebar: state => state.app.sidebar,
|
theme: state => state.settings.theme,
|
||||||
device: state => state.app.device,
|
sideTheme: state => state.settings.sideTheme,
|
||||||
needTagsView: state => state.settings.tagsView,
|
sidebar: state => state.app.sidebar,
|
||||||
fixedHeader: state => state.settings.fixedHeader
|
device: state => state.app.device,
|
||||||
}),
|
needTagsView: state => state.settings.tagsView,
|
||||||
classObj() {
|
fixedHeader: state => state.settings.fixedHeader
|
||||||
return {
|
}),
|
||||||
hideSidebar: !this.sidebar.opened,
|
classObj() {
|
||||||
openSidebar: this.sidebar.opened,
|
return {
|
||||||
withoutAnimation: this.sidebar.withoutAnimation,
|
hideSidebar: !this.sidebar.opened,
|
||||||
mobile: this.device === 'mobile'
|
openSidebar: this.sidebar.opened,
|
||||||
}
|
withoutAnimation: this.sidebar.withoutAnimation,
|
||||||
},
|
mobile: this.device === 'mobile'
|
||||||
variables() {
|
}
|
||||||
return variables;
|
},
|
||||||
}
|
variables() {
|
||||||
},
|
return variables;
|
||||||
methods: {
|
}
|
||||||
handleClickOutside() {
|
},
|
||||||
this.$store.dispatch('app/closeSideBar', { withoutAnimation: false })
|
created() {
|
||||||
}
|
this.connectWebsocket();
|
||||||
}
|
},
|
||||||
}
|
|
||||||
</script>
|
methods: {
|
||||||
|
//连接websocket
|
||||||
<style lang="scss" scoped>
|
connectWebsocket() {
|
||||||
@import "~@/assets/styles/mixin.scss";
|
if (!this.$socket) {
|
||||||
@import "~@/assets/styles/variables.scss";
|
SocketService.Instance.connect();
|
||||||
|
this.$store.dispatch("app/set$Socket", SocketService.Instance);
|
||||||
.app-wrapper {
|
}
|
||||||
@include clearfix;
|
},
|
||||||
position: relative;
|
|
||||||
height: 100%;
|
handleClickOutside() {
|
||||||
width: 100%;
|
this.$store.dispatch('app/closeSideBar', {withoutAnimation: false})
|
||||||
|
}
|
||||||
&.mobile.openSidebar {
|
},
|
||||||
position: fixed;
|
|
||||||
top: 0;
|
beforeDestroy() {
|
||||||
}
|
if (this.$socket) {
|
||||||
}
|
this.$socket.closeWebsocket();
|
||||||
|
this.$store.dispatch("app/set$Socket", null);
|
||||||
.drawer-bg {
|
}
|
||||||
background: #000;
|
},
|
||||||
opacity: 0.3;
|
}
|
||||||
width: 100%;
|
</script>
|
||||||
top: 0;
|
|
||||||
height: 100%;
|
<style lang="scss" scoped>
|
||||||
position: absolute;
|
@import "~@/assets/styles/mixin.scss";
|
||||||
z-index: 999;
|
@import "~@/assets/styles/variables.scss";
|
||||||
}
|
|
||||||
|
.app-wrapper {
|
||||||
.fixed-header {
|
@include clearfix;
|
||||||
position: fixed;
|
position: relative;
|
||||||
top: 0;
|
height: 100%;
|
||||||
right: 0;
|
width: 100%;
|
||||||
z-index: 9;
|
|
||||||
width: calc(100% - #{$base-sidebar-width});
|
&.mobile.openSidebar {
|
||||||
transition: width 0.28s;
|
position: fixed;
|
||||||
}
|
top: 0;
|
||||||
|
}
|
||||||
.hideSidebar .fixed-header {
|
}
|
||||||
width: calc(100% - 54px);
|
|
||||||
}
|
.drawer-bg {
|
||||||
|
background: #000;
|
||||||
.mobile .fixed-header {
|
opacity: 0.3;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
top: 0;
|
||||||
</style>
|
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 = {
|
const getters = {
|
||||||
sidebar: state => state.app.sidebar,
|
sidebar: state => state.app.sidebar,
|
||||||
size: state => state.app.size,
|
size: state => state.app.size,
|
||||||
device: state => state.app.device,
|
device: state => state.app.device,
|
||||||
visitedViews: state => state.tagsView.visitedViews,
|
visitedViews: state => state.tagsView.visitedViews,
|
||||||
cachedViews: state => state.tagsView.cachedViews,
|
cachedViews: state => state.tagsView.cachedViews,
|
||||||
token: state => state.user.token,
|
token: state => state.user.token,
|
||||||
avatar: state => state.user.avatar,
|
avatar: state => state.user.avatar,
|
||||||
name: state => state.user.name,
|
name: state => state.user.name,
|
||||||
introduction: state => state.user.introduction,
|
introduction: state => state.user.introduction,
|
||||||
roles: state => state.user.roles,
|
roles: state => state.user.roles,
|
||||||
permissions: state => state.user.permissions,
|
permissions: state => state.user.permissions,
|
||||||
permission_routes: state => state.permission.routes,
|
permission_routes: state => state.permission.routes,
|
||||||
topbarRouters:state => state.permission.topbarRouters,
|
topbarRouters:state => state.permission.topbarRouters,
|
||||||
defaultRoutes:state => state.permission.defaultRoutes,
|
defaultRoutes:state => state.permission.defaultRoutes,
|
||||||
sidebarRouters:state => state.permission.sidebarRouters,
|
sidebarRouters:state => state.permission.sidebarRouters,
|
||||||
}
|
$socket: state => state.app.$socket,
|
||||||
export default getters
|
}
|
||||||
|
export default getters
|
||||||
|
|
|
||||||
|
|
@ -1,56 +1,64 @@
|
||||||
import Cookies from 'js-cookie'
|
import Cookies from 'js-cookie'
|
||||||
|
|
||||||
const state = {
|
const state = {
|
||||||
sidebar: {
|
sidebar: {
|
||||||
opened: Cookies.get('sidebarStatus') ? !!+Cookies.get('sidebarStatus') : true,
|
opened: Cookies.get('sidebarStatus') ? !!+Cookies.get('sidebarStatus') : true,
|
||||||
withoutAnimation: false
|
withoutAnimation: false
|
||||||
},
|
},
|
||||||
device: 'desktop',
|
device: 'desktop',
|
||||||
size: Cookies.get('size') || 'medium'
|
size: Cookies.get('size') || 'medium',
|
||||||
}
|
// websocket实例
|
||||||
|
$socket: null
|
||||||
const mutations = {
|
}
|
||||||
TOGGLE_SIDEBAR: state => {
|
|
||||||
state.sidebar.opened = !state.sidebar.opened
|
const mutations = {
|
||||||
state.sidebar.withoutAnimation = false
|
TOGGLE_SIDEBAR: state => {
|
||||||
if (state.sidebar.opened) {
|
state.sidebar.opened = !state.sidebar.opened
|
||||||
Cookies.set('sidebarStatus', 1)
|
state.sidebar.withoutAnimation = false
|
||||||
} else {
|
if (state.sidebar.opened) {
|
||||||
Cookies.set('sidebarStatus', 0)
|
Cookies.set('sidebarStatus', 1)
|
||||||
}
|
} else {
|
||||||
},
|
Cookies.set('sidebarStatus', 0)
|
||||||
CLOSE_SIDEBAR: (state, withoutAnimation) => {
|
}
|
||||||
Cookies.set('sidebarStatus', 0)
|
},
|
||||||
state.sidebar.opened = false
|
CLOSE_SIDEBAR: (state, withoutAnimation) => {
|
||||||
state.sidebar.withoutAnimation = withoutAnimation
|
Cookies.set('sidebarStatus', 0)
|
||||||
},
|
state.sidebar.opened = false
|
||||||
TOGGLE_DEVICE: (state, device) => {
|
state.sidebar.withoutAnimation = withoutAnimation
|
||||||
state.device = device
|
},
|
||||||
},
|
TOGGLE_DEVICE: (state, device) => {
|
||||||
SET_SIZE: (state, size) => {
|
state.device = device
|
||||||
state.size = size
|
},
|
||||||
Cookies.set('size', size)
|
SET_SIZE: (state, size) => {
|
||||||
}
|
state.size = size
|
||||||
}
|
Cookies.set('size', size)
|
||||||
|
},
|
||||||
const actions = {
|
SET_$SOCKET: (state, socket) => {
|
||||||
toggleSideBar({ commit }) {
|
state.$socket = socket
|
||||||
commit('TOGGLE_SIDEBAR')
|
}
|
||||||
},
|
}
|
||||||
closeSideBar({ commit }, { withoutAnimation }) {
|
|
||||||
commit('CLOSE_SIDEBAR', withoutAnimation)
|
const actions = {
|
||||||
},
|
toggleSideBar({commit}) {
|
||||||
toggleDevice({ commit }, device) {
|
commit('TOGGLE_SIDEBAR')
|
||||||
commit('TOGGLE_DEVICE', device)
|
},
|
||||||
},
|
closeSideBar({commit}, {withoutAnimation}) {
|
||||||
setSize({ commit }, size) {
|
commit('CLOSE_SIDEBAR', withoutAnimation)
|
||||||
commit('SET_SIZE', size)
|
},
|
||||||
}
|
toggleDevice({commit}, device) {
|
||||||
}
|
commit('TOGGLE_DEVICE', device)
|
||||||
|
},
|
||||||
export default {
|
setSize({commit}, size) {
|
||||||
namespaced: true,
|
commit('SET_SIZE', size)
|
||||||
state,
|
},
|
||||||
mutations,
|
set$Socket({commit}, socket) {
|
||||||
actions
|
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";
|
public static final String HOT = "hot";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* websocket常量key
|
||||||
|
*/
|
||||||
|
public static final String WEBSOCKET= "WEBSOCKET";
|
||||||
|
|
||||||
|
|
||||||
//-------------------有效时间-----------------------
|
//-------------------有效时间-----------------------
|
||||||
public static final Integer TRAN_DICT_EXPIRE = 7; //天
|
public static final Integer TRAN_DICT_EXPIRE = 7; //天
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
package com.xjs.controller;
|
package com.xjs.controller;
|
||||||
|
|
||||||
|
import com.alibaba.fastjson.JSONObject;
|
||||||
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
|
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
|
||||||
import com.ruoyi.common.core.domain.R;
|
import com.ruoyi.common.core.domain.R;
|
||||||
import com.ruoyi.common.core.utils.poi.ExcelUtil;
|
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.core.web.page.TableDataInfo;
|
||||||
import com.ruoyi.common.log.annotation.Log;
|
import com.ruoyi.common.log.annotation.Log;
|
||||||
import com.ruoyi.common.log.enums.BusinessType;
|
import com.ruoyi.common.log.enums.BusinessType;
|
||||||
|
import com.ruoyi.common.redis.service.RedisService;
|
||||||
import com.ruoyi.common.security.annotation.RequiresPermissions;
|
import com.ruoyi.common.security.annotation.RequiresPermissions;
|
||||||
import com.xjs.domain.ApiRecord;
|
import com.xjs.domain.ApiRecord;
|
||||||
import com.xjs.domain.ApiWarning;
|
import com.xjs.domain.ApiWarning;
|
||||||
|
import com.xjs.server.WebSocketServer;
|
||||||
import com.xjs.service.ApiWarningService;
|
import com.xjs.service.ApiWarningService;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
import org.springframework.web.bind.annotation.*;
|
import org.springframework.web.bind.annotation.*;
|
||||||
|
|
||||||
import javax.servlet.http.HttpServletResponse;
|
import javax.servlet.http.HttpServletResponse;
|
||||||
|
import java.io.IOException;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
import static com.xjs.consts.RedisConst.WEBSOCKET;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author xiejs
|
* @author xiejs
|
||||||
|
|
@ -30,6 +38,8 @@ public class ApiWarningController extends BaseController {
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
private ApiWarningService apiWarningService;
|
private ApiWarningService apiWarningService;
|
||||||
|
@Autowired
|
||||||
|
private RedisService redisService;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 远程保存 apiRecord
|
* 远程保存 apiRecord
|
||||||
|
|
@ -66,17 +76,41 @@ public class ApiWarningController extends BaseController {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 远程保存api预警信息
|
* 远程保存api预警信息并websocket推送
|
||||||
*
|
*
|
||||||
* @param apiWarning 预警实体类
|
* @param apiWarning 预警实体类
|
||||||
* @return R
|
* @return R
|
||||||
*/
|
*/
|
||||||
@PostMapping("saveApiwarningForRPC")
|
@PostMapping("saveApiwarningForRPC")
|
||||||
|
@Transactional
|
||||||
public R<ApiWarning> saveApiWarningForRPC(@RequestBody ApiWarning apiWarning) {
|
public R<ApiWarning> saveApiWarningForRPC(@RequestBody ApiWarning apiWarning) {
|
||||||
boolean save = apiWarningService.save(apiWarning);
|
boolean save = apiWarningService.save(apiWarning);
|
||||||
|
|
||||||
|
this.websocketPush(apiWarning);
|
||||||
|
|
||||||
return save ? R.ok() : R.fail();
|
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预警列表
|
* 查询api预警列表
|
||||||
|
|
|
||||||
|
|
@ -1,15 +1,23 @@
|
||||||
package com.xjs.server;
|
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 lombok.extern.log4j.Log4j2;
|
||||||
import org.apache.commons.lang3.StringUtils;
|
import org.apache.commons.lang3.StringUtils;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.stereotype.Component;
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
import javax.websocket.*;
|
import javax.websocket.*;
|
||||||
import javax.websocket.server.PathParam;
|
import javax.websocket.server.PathParam;
|
||||||
import javax.websocket.server.ServerEndpoint;
|
import javax.websocket.server.ServerEndpoint;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.Set;
|
||||||
import java.util.concurrent.ConcurrentHashMap;
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
|
||||||
|
import static com.xjs.consts.RedisConst.WEBSOCKET;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* api预警websocket
|
* api预警websocket
|
||||||
*
|
*
|
||||||
|
|
@ -17,9 +25,25 @@ import java.util.concurrent.ConcurrentHashMap;
|
||||||
* @since 2022-01-13
|
* @since 2022-01-13
|
||||||
*/
|
*/
|
||||||
@Log4j2
|
@Log4j2
|
||||||
@ServerEndpoint("warning/api/{userId}")
|
@ServerEndpoint("/warning/api/{userId}")
|
||||||
@Component
|
@Component
|
||||||
public class WebSocketServer {
|
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());
|
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 {
|
try {
|
||||||
sendMessage("连接成功");
|
sendMessage(jsonData.toJSONString());
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
log.error("用户:" + userId + ",网络异常!!!!!!");
|
e.printStackTrace();
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -73,6 +106,9 @@ public class WebSocketServer {
|
||||||
webSocketMap.remove(userId);
|
webSocketMap.remove(userId);
|
||||||
//从set中删除
|
//从set中删除
|
||||||
subOnlineCount();
|
subOnlineCount();
|
||||||
|
|
||||||
|
//退出移出用户
|
||||||
|
redisService.removeSet(WEBSOCKET, this.userId);
|
||||||
}
|
}
|
||||||
log.info("用户退出:" + userId + ",当前在线人数为:" + getOnlineCount());
|
log.info("用户退出:" + userId + ",当前在线人数为:" + getOnlineCount());
|
||||||
}
|
}
|
||||||
|
|
@ -107,6 +143,7 @@ public class WebSocketServer {
|
||||||
@OnError
|
@OnError
|
||||||
public void onError(Session session, Throwable error) {
|
public void onError(Session session, Throwable error) {
|
||||||
log.error("用户错误:" + this.userId + ",原因:" + error.getMessage());
|
log.error("用户错误:" + this.userId + ",原因:" + error.getMessage());
|
||||||
|
redisService.removeSet(WEBSOCKET, this.userId);
|
||||||
error.printStackTrace();
|
error.printStackTrace();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue