Bootstrap

服务端开发模式-vue-element-admin重新整理websocket

一、App.vue修改

<template>
  <div id="app">
    <router-view />
  </div>
</template>

<script>
  import store from './store'
  import {succ} from '@/utils/message'
export default {
  name: 'App',
  data() {
    return {
        reverseCount:0,
    }
  },
  created() {
    // 因为我的页面有缓存机制,用户下次有可能直接打开某个登录后才能访问的页面 比如F5刷新了某个页面 需要重连
    // 又比如后端服务器因为什么原因突然中断了一下 也需要重新连接WebSocket
    // 每3秒检测一次websocket连接状态 未连接 则尝试连接 尽量保证网站启动的时候 WebSocket都能正常长连接
    setInterval(this.WebSocket_StatusCheck, 2000)
  },
  methods: {
    // 1、WebSocket连接状态检测:
    WebSocket_StatusCheck() {
      //如果登录了情况下,验证是否已经绑定
      if(store.getters.token != undefined || store.getters.token != null){
        if (!this.$WebSocket.WebSocketHandle || this.$WebSocket.WebSocketHandle.readyState !== 1) {
          this.reverseCount++;
          this.WebSocketINI()
        }else{
          this.reverseCount = 0;
          this.$WebSocket.WebSocketHandle.send(JSON.stringify({type: 'ping'}))
        }
      }
    },

    // 2、WebSocket初始化:
    async WebSocketINI() {
      // 1、浏览器是否支持WebSocket检测
      if (!('WebSocket' in window)) {
        return
      }

      // 2、从后台提取WebScoket服务器连接地址:根据自己业务接口获取 或者直接跳过 下面直接写死
      var Base64 = require('js-base64').Base64
      const tokenData = Base64.decode(store.getters.token).split("|");
      const token = tokenData[0]
      const loginTime = new Date(tokenData[1]).getTime() / 1000
      const tmpWebsocketSrverAddress = 'ws://www.baidu.com?extremity_type=1&token='+ token + '&login_time=' +loginTime//可以直接赋值如:ws://127.0.0.1:1234
      // 3、创建Websocket连接
      const tmpWebsocket = new WebSocket(tmpWebsocketSrverAddress)
      // 4、全局保存WebSocket操作句柄:main.js 全局引用
      this.$WebSocket.WebsocketINI(tmpWebsocket)

      // 5、WebSocket连接成功提示
      tmpWebsocket.onopen = function(ev){

      }
      tmpWebsocket.onmessage = function(ev){
        const datas = JSON.parse(ev.data)
        switch (datas.type) {
            // 异地登录
            case "repeat_close":
                store.dispatch('user/resetToken',{type:"repeat"})
                // 延时
                setTimeout(() => {
                    window.location.reload()
                }, 1000)
                break
            // 正常退出
            case "artificial_close":
                store.dispatch('user/resetToken',{type:"artificial"})
                // 延时
                setTimeout(() => {
                    window.location.reload()
                }, 1000)
                break
            case "expire_close":
                store.dispatch('user/resetToken',{type:"expire"})
                // 延时
                setTimeout(() => {
                    window.location.reload()
                }, 1000)
                break
            default:
        }
      }
      //6、连接失败提示
      tmpWebsocket.onclose = (ev)=> {
        if(this.reverseCount >= 5){
            store.dispatch('user/logout')
            store.dispatch('user/resetToken',{type:"close"})
            window.location.reload()
        }
      }
    }
  }
}
</script>

二、Navbar.vue

<template>
  <div class="navbar">
    <hamburger id="hamburger-container" :is-active="sidebar.opened" class="hamburger-container" @toggleClick="toggleSideBar" />

    <breadcrumb id="breadcrumb-container" class="breadcrumb-container" />

    <div class="right-menu">
      <template v-if="device!=='mobile'">
        <search id="header-search" class="right-menu-item" />

        <error-log class="errLog-container right-menu-item hover-effect" />

        <screenfull id="screenfull" class="right-menu-item hover-effect" />

        <el-tooltip content="Global Size" effect="dark" placement="bottom">
          <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">
          <span>欢迎<b style="color:red;">{{username}}</b>使用后台</span>
          <i class="el-icon-caret-bottom" />
        </div>
        <el-dropdown-menu slot="dropdown">
          <router-link to="/profile/index">
            <el-dropdown-item>个人信息</el-dropdown-item>
          </router-link>
          <el-dropdown-item divided @click.native="logout">
            <span style="display:block;">退出</span>
          </el-dropdown-item>
        </el-dropdown-menu>
      </el-dropdown>
    </div>
  </div>
</template>

<script>
import { mapGetters } from 'vuex'
import Breadcrumb from '@/components/Breadcrumb'
import Hamburger from '@/components/Hamburger'
import ErrorLog from '@/components/ErrorLog'
import Screenfull from '@/components/Screenfull'
import SizeSelect from '@/components/SizeSelect'
import Search from '@/components/HeaderSearch'

export default {
  components: {
    Breadcrumb,
    Hamburger,
    ErrorLog,
    Screenfull,
    SizeSelect,
    Search
  },
  computed: {
    ...mapGetters([
      'sidebar',
      'username',
      'device'
    ])
  },
  methods: {
    toggleSideBar() {
      this.$store.dispatch('app/toggleSideBar')
    },
    async logout() {
      await this.$store.dispatch('user/logout')
      //this.$router.push(`/login?redirect=${this.$route.fullPath}`)
    }
  }
}
</script>

<style lang="scss" scoped>
.navbar {
  height: 50px;
  overflow: hidden;
  position: relative;
  background: #fff;
  box-shadow: 0 1px 4px rgba(0,21,41,.08);

  .hamburger-container {
    line-height: 46px;
    height: 100%;
    float: left;
    cursor: pointer;
    transition: background .3s;
    -webkit-tap-highlight-color:transparent;

    &:hover {
      background: rgba(0, 0, 0, .025)
    }
  }

  .breadcrumb-container {
    float: left;
  }

  .errLog-container {
    display: inline-block;
    vertical-align: top;
  }

  .right-menu {
    float: right;
    height: 100%;
    line-height: 50px;

    &: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 .3s;

        &:hover {
          background: rgba(0, 0, 0, .025)
        }
      }
    }

    .avatar-container {
      margin-right: 30px;

      .avatar-wrapper {
        margin-top: 5px;
        position: relative;

        .user-avatar {
          cursor: pointer;
          width: 40px;
          height: 40px;
          border-radius: 10px;
        }

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

三、user.js

import {getInfo, login, logout, repeatInfo} from '@/api/common'
import {getToken, removeToken, setToken} from '@/utils/auth'
import router, {resetRouter} from '@/router'
import moment from 'moment'
import {succ, warn, err} from '@/utils/message'
import Encrypt from 'encryptlong' // encryptlong是基于jsencrypt扩展的长文本分段加解密功能。

const state = {
    token: getToken(),
    username: '',
    avatar: '',
    email: '',
    realname: '',
    department_title: '',
    grade_title: '',
    rolename: '',
    roles: [],
    butts: []
}

const mutations = {
    SET_TOKEN: (state, token) => {
        state.token = token
    },
    SET_EMAIL: (state, email) => {
        state.email = email
    },
    SET_USERNAME: (state, username) => {
        state.username = username
    },
    SET_AVATAR: (state, avatar) => {
        state.avatar = avatar
    },
    SET_REALNAME: (state, realname) => {
        state.realname = realname
    },
    SET_DEPARTMENT_TITLE: (state, department_title) => {
        state.department_title = department_title
    },
    SET_GRADE_TITLE: (state, grade_title) => {
        state.grade_title = grade_title
    },
    SET_ROLENAME: (state, rolename) => {
        state.rolename = rolename
    },
    SET_BUTTS: (state, butts) => {
        state.butts = butts
    },
    SET_ROLES: (state, roles) => {
        state.roles = roles
    }
}
// 私钥Key
const privateKey = ''
const actions = {
    // user login
    login({commit}, userInfo) {
        const {username, password, captcha_code} = userInfo
        const login_time = moment().format('YYYY-MM-DD hh:mm:ss')
        return new Promise((resolve, reject) => {
            login({
                username: username.trim(),
                password: password.trim(),
                captcha_code: captcha_code.trim(),
                login_time: login_time.trim()
            }).then(response => {
                if (response.code === 50034) {
                    reject(response.message)
                } else if (response.code === 50000) {
                    warn(response.message)
                } else {
                    succ(response.message)
                    const PRIVATE_KEY = privateKey
                    var encryptor = new Encrypt()
                    encryptor.setPrivateKey(PRIVATE_KEY)
                    // 如果是对象/数组的话,需要先JSON.stringify转换成字符串
                    var result = encryptor.decryptLong(response.data)
                    // 引用加密
                    var Base64 = require('js-base64').Base64
                    // base64加密
                    var tokenBase64 = Base64.encode(result + '|' + login_time.trim())
                    commit('SET_TOKEN', tokenBase64)
                    setToken(tokenBase64)
                    resolve()
                }
            }).catch(error => {
                reject(error)
            })
        })
    },

    // get user info
    getInfo({commit, state}) {
        return new Promise((resolve, reject) => {
            getInfo().then(response => {
                const {data} = response
                if (!data) {
                    reject('验证失败,请重新登录。')
                }
                const {butt, key, username, avatar, email, realname, department_title, grade_title, rolename} = data
                if (!butt || butt.length <= 0) {
                    reject('您权限不足,请联系系统管理员')
                }
                commit('SET_BUTTS', butt)
                commit('SET_ROLES', key)
                commit('SET_USERNAME', username)
                commit('SET_AVATAR', avatar)
                commit('SET_EMAIL', email)
                commit('SET_REALNAME', realname)
                commit('SET_DEPARTMENT_TITLE', department_title)
                commit('SET_GRADE_TITLE', grade_title)
                commit('SET_ROLENAME', rolename)
                resolve(key)
            }).catch(error => {
                reject(error)
            })
        })
    },

    // 处理重复登录
    repeatInfo({commit, state, dispatch}) {
        return new Promise((resolve, reject) => {
            succ('账号已在异地登录')
            commit('SET_TOKEN', '')
            commit('SET_BUTTS', [])
            commit('SET_ROLES', [])
            commit('SET_USERNAME', '')
            commit('SET_AVATAR', '')
            commit('SET_EMAIL', '')
            commit('SET_REALNAME', '')
            commit('SET_DEPARTMENT_TITLE', '')
            commit('SET_GRADE_TITLE', '')
            commit('SET_ROLENAME', '')
            removeToken()
            resetRouter()
            dispatch('tagsView/delAllViews', null, {root: true})
            resolve()
        })
    },

    // user logout
    logout({commit, state, dispatch}) {
        return new Promise((resolve, reject) => {
            logout().then(res => {

            }).catch(error => {
                reject(error)
            })
        })
    },

    // 意外需要重置本地token
    resetToken({commit},infoType) {
        return new Promise(resolve => {
            if(infoType.type == 'artificial'){
                succ('退出成功')
            }else if(infoType.type == 'expire'){
                warn('登录过期,自动退出')
            }else if(infoType.type == 'close'){
                err('服务断链,强制退出')
            }else if(infoType.type == 'repeat'){
                warn('账号已在异地登录')
            }
            commit('SET_TOKEN', '')
            commit('SET_BUTTS', [])
            commit('SET_ROLES', [])
            commit('SET_USERNAME', '')
            commit('SET_AVATAR', '')
            commit('SET_EMAIL', '')
            commit('SET_REALNAME', '')
            commit('SET_DEPARTMENT_TITLE', '')
            commit('SET_GRADE_TITLE', '')
            commit('SET_ROLENAME', '')
            removeToken()
            resetRouter()
            resolve()
        })
    },

    // 切换权限
    async changeRoles({commit, dispatch}, role) {
        const token = role + '-token'
        commit('SET_TOKEN', token)
        setToken(token)
        const {roles} = await dispatch('getInfo')
        resetRouter()
        const accessRoutes = await dispatch('permission/generateRoutes', roles, {root: true})
        router.addRoutes(accessRoutes)
        dispatch('tagsView/delAllViews', null, {root: true})
    }
}

export default {
    namespaced: true,
    state,
    mutations,
    actions
}
;