管理系统权限篇

2023-10-27

目录

前言

cooike

登录篇

登录

router.beforeEach 登录拦截 

axios拦截器 token过期出处理

权限篇

router.js

store/permission.js


前言

  • 登录:当用户填写完账号和密码后向服务端验证是否正确,验证通过之后,服务端会返回一个token,拿到token之后(我会将这个token存贮到cookie中,保证刷新页面后能记住用户登录状态),前端会根据token再去拉取一个 user_info 的接口来获取用户的详细信息(如用户权限,用户名等等信息)。
  • 权限验证:通过token获取用户对应的 role,动态根据用户的 role 算出其对应有权限的路由,通过 router.addRoutes 动态挂载这些路由。

cooike

Cookie是由服务器端生成的,发送给User-Agent(一般是浏览器),(服务器告诉浏览器设置一下Cookie),浏览器会将cookie以key/value 的形式保存在某个目录下的文本文件内,下一次请求同一网站时就发送该Cookie服务器(前提是浏览器设置Cookie)

Cookie的特点

  •  http是无状态协议 状态都是由Cookie来控制的
  • 有生命周期
  • 满足同源策略
  • 内存大小收到限制(一般是4K左右)

生命周期:
我们可以通过设置cookie的Expires的值来设置一条Cookie信息的失效时间 默认是当浏览器关闭的时候失效 我们可以利用new Date() 的 setTime和getTime来设置失效时间

满足同源策略:
不同源的情况下,Cookie一样是无法传递的。但是我们会发现我们登录了百度账号之后,再邓丽百度知道或百度文库,账号都是登录状态,但是这几个网页之间的主机名是不同的。
js-cookie 

js-cookie是什么?
js-cookie是一个简单的,轻量级的处理cookies的js API,用来处理cookie相关的插件

js-cookie的使用方法

npm install --save js-cookie

import Cookies from 'js-cookie'

 js-cookie的添加 获取 删除

添加cookie

// 创建一个名称为name,对应值为value的cookie,由于没有设置失效时间,默认失效时间为该网站关闭时
Cookies.set(name, value)

// 创建一个有效时间为7天的cookie
Cookies.set(name, value, { expires: 7 })

// 创建一个带有路径的cookie
Cookies.set(name, value, { path: '' })

// 创建一个value为对象的cookie
const obj = { name: 'ryan' }
Cookies.set('user', obj)

 获取cookie

// 获取指定名称的cookie
Cookies.get(name) // value

// 获取value为对象的cookie
const obj = { name: 'ryan' }
Cookies.set('user', obj)
JSON.parse(Cookies.get('user'))

// 获取所有cookie
Cookies.get()

 删除cookie

// 删除指定名称的cookie
Cookies.remove(name) // value

// 删除带有路径的cookie
Cookies.set(name, value, { path: '' })
Cookies.remove(name, { path: '' })

 cooike操作的封装

import Cookies from 'js-cookie'

const TokenKey = 'Admin-Token'

export function getToken() {
  return Cookies.get(TokenKey)
}

export function setToken(token) {
  return Cookies.set(TokenKey, token)
}

export function removeToken() {
  return Cookies.remove(TokenKey)
}

登录篇

登录成功后,服务端会返回一个 token(该token的是一个能唯一标示用户身份的一个key),之后我们将token存储在本地cookie之中,这样下次打开页面或者刷新页面的时候能记住用户的登录状态,不用再去登录页面重新登录了。

tips

ps:为了保证安全性,后台所有token有效期(Expires/Max-Age)都是Session,就是当浏览器关闭了就丢失了。重新打开游览器都需要重新登录验证,后端也会在每周固定一个时间点重新刷新token,让后台用户全部重新登录一次,确保后台用户不会因为电脑遗失或者其它原因被人随意使用账号。

登录

click事件触发登录操作:

this.$store.dispatch('LoginByUsername', this.loginForm).then(() => {
  this.$router.push({ path: '/' }); //登录成功之后重定向到首页
}).catch(err => {
  this.$message.error(err); //登录失败提示错误
});

action:

LoginByUsername({ commit }, userInfo) {
  const username = userInfo.username.trim()
  return new Promise((resolve, reject) => {
    loginByUsername(username, userInfo.password).then(response => {
      const data = response.data
      Cookies.set('Token', response.data.token) //登录成功后将token存储在cookie之中
      commit('SET_TOKEN', data.token)
      resolve()
    }).catch(error => {
      reject(error)
    });
  });
}

router.beforeEach 登录拦截 

用户登录成功之后,我们会在全局钩子router.beforeEach中拦截路由,判断是否已获得token,在获得token之后我们就要去获取用户的基本信息了

就如前面所说的,我只在本地存储了一个用户的token,并没有存储别的用户信息(如用户权限,用户名,用户头像等)。有些人会问为什么不把一些其它的用户信息也存一下?主要出于如下的考虑:

假设我把用户权限和用户名也存在了本地,但我这时候用另一台电脑登录修改了自己的用户名,之后再用这台存有之前用户信息的电脑登录,它默认会去读取本地 cookie 中的名字,并不会去拉去新的用户信息。

所以现在的策略是:页面会先从 cookie 中查看是否存有 token,没有,就走一遍上一部分的流程重新登录,如果有token,就会把这个 token 返给后端去拉取user_info,保证用户信息是最新的。 当然如果是做了单点登录得功能的话,用户信息存储在本地也是可以的。当你一台电脑登录时,另一台会被提下线,所以总会重新登录获取最新的内容。

而且从代码层面我建议还是把 loginget_user_info两件事分开比较好,在这个后端全面微服务的年代,后端同学也想写优雅的代码~

import router from './router'
import store from './store'
import { Message } from 'element-ui'
import NProgress from 'nprogress' // 进度条
import 'nprogress/nprogress.css' // 进度条样式
import { getToken } from '@/utils/auth' // 获取身份信息
import getPageTitle from '@/utils/get-page-title'

NProgress.configure({ showSpinner: false }) // NProgress Configuration

const whiteList = ['/login', '/auth-redirect'] // 无重定向白名单
// 路由前置守卫
router.beforeEach(async (to, from, next) => {
  // 启动进度条
  NProgress.start()

  //设置页面标题
  document.title = getPageTitle(to.meta.title)

  //确定用户是否已登录
  const hasToken = getToken()

  if (hasToken) {
    if (to.path === '/login') {
      //如果已登录,请重定向到主页
      next({ path: '/' })
      NProgress.done()
    } else {
      // 确定用户是否已通过getInfo获得其权限角色
      const hasRoles = store.getters.roles && store.getters.roles.length > 0
      if (hasRoles) {
        next()
      } else {
        try {
          //获取用户信息
          const { roles } = await store.dispatch('user/getInfo')

          //基于角色生成可访问的路线图
          const accessRoutes = await store.dispatch('permission/generateRoutes', roles)

          //动态添加可访问路由
          router.addRoutes(accessRoutes)

          // 以确保addRoutes是完整的
          next({ ...to, replace: true })
        } catch (error) {
          //删除令牌并转到登录页面重新登录
          await store.dispatch('user/resetToken')
          Message.error(error || 'Has Error')
          next(`/login?redirect=${to.path}`)
          NProgress.done()
        }
      }
    }
  } else {

    /*它没有令牌*/

    if (whiteList.indexOf(to.path) !== -1) {
      //在免费登录白名单中,直接进入
      next()
    } else {
      //其他没有访问权限的页面将重定向到登录页面。
      next(`/login?redirect=${to.path}`)
      NProgress.done()
    }
  }
})

router.afterEach(() => {
  //完成进度条
  NProgress.done()
})

 用户信息

没有把用户信息存储到本地 ,页面会先从 cookie 中查看是否存有 token,没有,就走一遍上一部分的流程重新登录,如果有token,就会把这个 token 返给后端去拉取user_info,保证用户信息是最新的。(vuex刷新会丢失数据 写到路由拦截器里 每次去判断有没有用户信息)

  // get user info
  getInfo({ commit, state }) {
    // 获取用户信息及权限并保存
    return new Promise((resolve, reject) => {
      getInfo(state.token).then(response => {
        const { data } = response
        console.log(response);
        if (!data) {
          reject('Verification failed, please Login again.')
        }

        const { role, name, avatar, introduction } = data

        // roles must be a non-empty array
        if (!role || role.length <= 0) {
          reject('getInfo: roles must be a non-null array!')
        }

        commit('SET_ROLES', data.role)
        commit('SET_NAME', name)
        commit('SET_AVATAR', avatar)
        commit('SET_INTRODUCTION', introduction)
        resolve(data)
      }).catch(error => {
        reject(error)
      })
    })
  },

tips

假设我把用户权限和用户名也存在了本地,但我这时候用另一台电脑登录修改了自己的用户名,之后再用这台存有之前用户信息的电脑登录,它默认会去读取本地 cookie 中的名字,并不会去拉去新的用户信息。

页面会先从 cookie 中查看是否存有 token,没有,就走一遍上一部分的流程重新登录,如果有token,就会把这个 token 返给后端去拉取user_info,保证用户信息是最新的。 

axios拦截器 token过期出处理

我们通过request拦截器在每个请求头里面塞入token,好让后端对请求进行权限验证。并创建一个respone拦截器,当服务端返回特殊的状态码,我们统一做处理,如没权限或者token失效等操作。或者来做一些统一的异常处理,一劳永逸。 而且因为我们的 api 是根据 env 环境变量动态切换的,如果以后线上出现了bug,我们只需配置一下 @/config/dev.env.js 再重启一下服务,就能在本地模拟线上的环境了

环境变量

设置webpack配置文件 运行时可以检测环境(webpack对外暴露了 process可以拿到 env为环境变量。设置的值必须VUE_APP开头)

  • .env.development 开发
  • .env.production 生产
  • .env.staging 测试
  • process.env读取环境变量
  • 设置文件时值必须以VUE_APP开头
//开发
# just a flag
ENV = 'development'
 
# base api
VUE_APP_BASE_API = '/dev-api'

//生产
# just a flag
ENV = 'production'

# base api
VUE_APP_BASE_API = '/prod-api'

//测试
NODE_ENV = production

# just a flag
ENV = 'staging'

# base api
VUE_APP_BASE_API = '/stage-api'

 axios 封装

import axios from 'axios'
import { Message } from 'element-ui'
import store from '@/store'
import { getToken } from '@/utils/auth'

// 创建axios实例
const service = axios.create({
  baseURL: process.env.BASE_API, // api的base_url
  timeout: 5000 // 请求超时时间
})

// request拦截器
service.interceptors.request.use(config => {
  // Do something before request is sent
  if (store.getters.token) {
    config.headers['X-Token'] = getToken() // 让每个请求携带token--['X-Token']为自定义key 请根据实际情况自行修改
  }
  return config
}, error => {
  // Do something with request error
  console.log(error) // for debug
  Promise.reject(error)
})

// respone拦截器
service.interceptors.response.use(
  response => response,
  /**
  * 下面的注释为通过response自定义code来标示请求状态,当code返回如下情况为权限有问题,登出并返回到登录页
  * 如通过xmlhttprequest 状态码标识 逻辑可写在下面error中
  */
  //  const res = response.data;
  //     if (res.code !== 20000) {
  //       Message({
  //         message: res.message,
  //         type: 'error',
  //         duration: 5 * 1000
  //       });
  //       // 50008:非法的token; 50012:其他客户端登录了;  50014:Token 过期了;
  //       if (res.code === 50008 || res.code === 50012 || res.code === 50014) {
  //         MessageBox.confirm('你已被登出,可以取消继续留在该页面,或者重新登录', '确定登出', {
  //           confirmButtonText: '重新登录',
  //           cancelButtonText: '取消',
  //           type: 'warning'
  //         }).then(() => {
  //           store.dispatch('FedLogOut').then(() => {
  //             location.reload();// 为了重新实例化vue-router对象 避免bug
  //           });
  //         })
  //       }
  //       return Promise.reject('error');
  //     } else {
  //       return response.data;
  //     }
  error => {
    console.log('err' + error)// for debug
    Message({
      message: error.message,
      type: 'error',
      duration: 5 * 1000
    })
    return Promise.reject(error)
  })

export default service

权限篇

前端会有一份路由表,它表示了每一个路由可访问的权限。当用户登录之后,通过 token 获取用户的 role ,动态根据用户的 role 算出其对应有权限的路由,再通过router.addRoutes动态挂载路由。

  • 创建vue实例的时候将vue-router挂载,但这个时候vue-router挂载一些登录或者不用权限的公用的页面。
  • 当用户登录后,获取用role,将role和路由表每个页面的需要的权限作比较,生成最终用户可访问的路由表。
  • 调用router.addRoutes(store.getters.addRouters)添加用户可访问的路由。
  • 使用vuex管理路由表,根据vuex中可访问的路由渲染侧边栏组件。

tips

addRouter 是 router 实例的一个方法, 可以 动态添加更多的路由规则

这个 api ,可以让你的 router 配置,不用在初始化router实例的时候就写进去

可以在 vue 实例化 以后,动态的添加更多的路由

router.js

// router.js
import Vue from 'vue';
import Router from 'vue-router';

import Login from '../views/login/';
const dashboard = resolve => require(['../views/dashboard/index'], resolve);
//使用了vue-routerd的[Lazy Loading Routes
](https://router.vuejs.org/en/advanced/lazy-loading.html)

//所有权限通用路由表 
//如首页和登录页和一些不用权限的公用页面
export const constantRouterMap = [
  { path: '/login', component: Login },
  {
    path: '/',
    component: Layout,
    redirect: '/dashboard',
    name: '首页',
    children: [{ path: 'dashboard', component: dashboard }]
  },
]

//实例化vue的时候只挂载constantRouter
export default new Router({
  routes: constantRouterMap
});

//异步挂载的路由
//动态需要根据权限加载的路由表 
export const asyncRouterMap = [
  {
    path: '/permission',
    component: Layout,
    name: '权限测试',
    meta: { role: ['admin','super_editor'] }, //页面需要的权限
    children: [
    { 
      path: 'index',
      component: Permission,
      name: '权限测试页',
      meta: { role: ['admin','super_editor'] }  //页面需要的权限
    }]
  },
  { path: '*', redirect: '/404', hidden: true }
];

通过meta标签来标示改页面能访问的权限有哪些。如meta: { role: ['admin','super_editor'] }表示该页面只有admin和超级编辑才能有资格进入。里有一个需要非常注意的地方就是 404 页面一定要最后加载

main.js

关键的main.js

router.beforeEach((to, from, next) => {
  if (store.getters.token) { // 判断是否有token
    if (to.path === '/login') {
      next({ path: '/' });
    } else {
      if (store.getters.roles.length === 0) { // 判断当前用户是否已拉取完user_info信息
        store.dispatch('GetInfo').then(res => { // 拉取info
          const roles = res.data.role;
          store.dispatch('GenerateRoutes', { roles }).then(() => { // 生成可访问的路由表
            router.addRoutes(store.getters.addRouters) // 动态添加可访问路由表
            next({ ...to, replace: true }) // hack方法 确保addRoutes已完成 ,set the replace: true so the navigation will not leave a history record
          })
        }).catch(err => {
          console.log(err);
        });
      } else {
        next() //当有用户权限的时候,说明所有可访问路由已生成 如访问没权限的全面会自动进入404页面
      }
    }
  } else {
    if (whiteList.indexOf(to.path) !== -1) { // 在免登录白名单,直接进入
      next();
    } else {
      next('/login'); // 否则全部重定向到登录页
    }
  }
});

上面一张图就是在使用addRoutes方法之前的权限判断,非常的繁琐,因为我是把所有的路由都挂在了上去,所有我要各种判断当前的用户是否有权限进入该页面,各种if/else的嵌套,维护起来相当的困难。但现在有了addRoutes之后就非常的方便,我只挂载了用户有权限进入的页面,没权限,路由自动帮我跳转的404,省去了不少的判断。

这里还有一个小hack的地方,就是router.addRoutes之后的next()可能会失效,因为可能next()的时候路由并没有完全add完成,好在查阅文档发现

next('/') or next({ path: '/' }): redirect to a different location. The current navigation will be aborted and a new one will be started.

这样我们就可以简单的通过next(to)巧妙的避开之前的那个问题了。这行代码重新进入router.beforeEach这个钩子,这时候再通过next()来释放钩子,就能确保所有的路由都已经挂在完成了。

store/permission.js

通过用户的权限和之前在router.js里面asyncRouterMap的每一个页面所需要的权限做匹配,最后返回一个该用户能够访问路由有哪些。

改造后

import { asyncRoutes, constantRoutes } from '@/router'


/**
 * Use meta.role to determine if the current user has permission
 * @param roles
 * @param route
 */
function hasPermission(roles, route) {
  if (route.meta && route.meta.roles) {
    return roles.some(role => route.meta.roles.includes(role))
  } else {
    return true
  }
}

/**
 * Filter asynchronous routing tables by recursion
 * @param routes asyncRoutes
 * @param roles
 */
export function filterAsyncRoutes(routes, roles) {
  const res = []

  routes.forEach(route => {
    const tmp = { ...route }
    if (hasPermission(roles, tmp)) {
      if (tmp.children) {
        tmp.children = filterAsyncRoutes(tmp.children, roles)
      }
      res.push(tmp)
    }
  })

  return res
}

const state = {
  routes: [],
  addRoutes: []
}

const mutations = {
  SET_ROUTES: (state, routes) => {
    state.addRoutes = routes
    state.routes = constantRoutes.concat(routes)
  }
}
//动态路由
const actions = {
  generateRoutes({ commit }, roles) {
    return new Promise(resolve => {
      let accessedRoutes
      if (roles.includes('admin')) {
        var arr = []
        asyncRoutes.forEach((item) => {

          if (item.hasOwnProperty('meta')) {

            if (item.meta.hasOwnProperty('roles')) {

              if (item.meta.roles.includes('admin')) {
                arr.push(item)
              }
            }
          }
        })
        accessedRoutes = arr || []

        // accessedRoutes = asyncRoutes || []
      } else {

        accessedRoutes = filterAsyncRoutes(asyncRoutes, roles)
      }
      commit('SET_ROUTES', accessedRoutes)

      resolve(accessedRoutes)
    })
  }
}

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

 源码

// store/permission.js
import { asyncRouterMap, constantRouterMap } from 'src/router';

function hasPermission(roles, route) {
  if (route.meta && route.meta.role) {
    return roles.some(role => route.meta.role.indexOf(role) >= 0)
  } else {
    return true
  }
}

const permission = {
  state: {
    routers: constantRouterMap,
    addRouters: []
  },
  mutations: {
    SET_ROUTERS: (state, routers) => {
      state.addRouters = routers;
      state.routers = constantRouterMap.concat(routers);
    }
  },
  actions: {
    GenerateRoutes({ commit }, data) {
      return new Promise(resolve => {
        const { roles } = data;
        const accessedRouters = asyncRouterMap.filter(v => {
          if (roles.indexOf('admin') >= 0) return true;
          if (hasPermission(roles, v)) {
            if (v.children && v.children.length > 0) {
              v.children = v.children.filter(child => {
                if (hasPermission(roles, child)) {
                  return child
                }
                return false;
              });
              return v
            } else {
              return v
            }
          }
          return false;
        });
        commit('SET_ROUTERS', accessedRouters);
        resolve();
      })
    }
  }
};

export default permission;

转载借鉴 vue-element-admin

作者:花裤衩
链接:https://juejin.cn/post/6844903478880370701

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

管理系统权限篇 的相关文章

随机推荐

  • 不同项目中,S7-300 DP 和 S7-1200 PROFINET 的profibus通信(300做主站,1200做从站)

    使用 S7 1200 与 S7 300 的集成 DP 接口进行主从通信 这里是将 S7 300 做为主站 将 S7 1200 做为从站 即 S7 300 集成的 DP 接口做主站 S7 1200 通过 CM1242 5 做从站 两个PLC在
  • Mysql-Galera Cluster

    使用Galera Cluster需要下载包含wsrep补丁的mysql版本 官网下载地址 http galeracluster com downloads 安装前要卸载之前安装的mariadb或者mysql 或者迁移也可以 不过就是另一套操
  • elementui的el-table的插槽功能,添加判断值,在单元格添加输入框,点击事件等等

    elementui的el table的插槽功能 添加判断值 在单元格添加输入框 点击事件等等
  • 三、 HBuilderX运行到手机上看效果

    以下均已录制 点击查看B站视频 1 运行 gt 运行到手机或模拟器 出现如下问题 未检测到手机或模拟器 请稍后重试 2 这时需要手机上做一些设置 设置 gt 关于手机 gt 连续多次点击版本号 就能打开开发者模式 设置 gt 系统和更新 g
  • 优化pxe网启动时tftp的传输速度 --- 针对pxelinux和bootmgr

    作为一名IT人士 一般的计算机维护当然不好意思找别人 于是自己用pxelinux搭了个网络启动环境 可以启动各种WinPE 以供折腾电脑系统 刷新固件的需要 只是一般的网络启动都是基于tftp协议的 传输文件那叫一个慢 启动时光是加载映像文
  • 交易中间件消息中间件_什么是中间件

    交易中间件消息中间件 什么是中间件 What Is Middleware In network architecture a middleware is a layer of software that creates a network
  • 答题小程序常用脚本整理

    答题小程序常用脚本整理 本文主要描述答题活动小程序运营过程中 高频使用的 几个脚本操作 1 如何清理当前题库 在开发控制台的高级操作右侧有个加号按钮 点击下 选择空白模板即可 将下面的脚本复制进去 db collection questio
  • 聚簇索引和二级索引

    原文链接 https blog csdn net jijianshuai article details 79084874
  • SpringCloud项目如何成功打包以及其中的一些坑

    我的项目结构 其中edu online和edu admin是前端项目 其他是后端模块 首先需要在父工程中添加需要打包的模块和打包依赖 如果在父工程中配置过打包依赖则子模块中不需要配置打包依赖 但是如果有子模块需要被其他模块依赖 则需要在被依
  • ROS:解决Error:cannot launch node of type [map_server/map_server]: can't locate node [map_server] in......

    写在前面 本文为原创 如需转载请注明出处 https www jianshu com p e9981bc35cff 欢迎大家留言共同探讨 有误的地方也希望指出 另如果有好的SLAM ROS等相关交流群也希望可以留言给我 在此先谢过了 1 E
  • Gof23设计模式之建造者模式

    1 概述 建造者模式 Builder Pattern 又叫生成器模式 是一种对象构建模式 它可以将复杂对象的建造过程抽象出来 抽象类别 使这个抽象过程的不同实现方法可以构造出不同表现 属性 的对象 建造者模式是一步一步创建一个复杂的对象 它
  • 要求用成员函数实现以下功能由键盘输入,计算长方体的体积,输出3个长方体的体积。

    题目 需要求三个长方体的体积 请编写一个基于对象的程序 数据成员包括length 长 width 宽 height 高 要求用成员函数实现以下功能 1 由键盘输入3个长方体的长 宽 高 2 计算长方体的体积 3 输出3个长方体的体积 请编程
  • linux 关于修改命令提示符

    1 首先 进入root 用户获得权限 输入 su root 2 进入修改提示符的文件 输入 vim etc profile 3 进入文件 不要修改任何地方 在最后加入命令 1 输入 export PS1 e 1 32 40m 孔子曰 e 1
  • Flink将本地数据写入Redis

    第一步 配置文件redis conf cd usr apps redis vim redis conf 先输入 set nu 打开行号标识 69行 bind 127 0 0 1加上注释 取消IP绑定 否则其他主机不能连接 88行 prote
  • sqli-labs第十八十九关

    这两关为头注入 Less 18 POST Header Injection Uagent field Error based 手工注入 这关和下一关必须要抓包才能完成 因为在这里怎么是都没有反应 全是报错的状态 那么我估计就要抓包了 根本判
  • pythonqt对比_用 Python 和 C++ 创建 Qt 程序的简单对比

    假设要做一个简单的小窗口 如下图所示 PyQt 和 C 要用多少代码可以完成呢 效果图 注 本文内容较多 主要是 C 的部分 若有必要请直接跳到最后看结论 一 C 版本 除了最基础的 pro 文件之外 我一共创建了 5 个文件 custom
  • 电脑固定ip地址之后重启却失效了的解决办法

    开始 运行 cmd 回车 英文状态下输入 netsh winsock reset 回车后会提示重启 先不重启 继续输入 netsh int ip reset reset log 回车后会提示重启 此时先重启电脑 重启之后再次设置好固定ip地
  • SQL 映射文件

    SQL 映射文件 SQL 映射文件只有很少的几个顶级元素 按照应被定义的顺序列出 cache 对给定命名空间的缓存配置 cache ref 对其他命名空间缓存配置的引用 resultMap 是最复杂也是最强大的元素 用来描述如何从数据库结果
  • 使用LeNet-5识别手写数字MNIST

    LeNet5 LeNet 5卷积神经网络模型 LeNet 5 是Yann LeCun在1998年设计的用于手写数字识别的卷积神经网络 当年美国大多数银行就是用它来识别支票上面的手写数字的 它是早期卷积神经网络中最有代表性的实验系统之一 Le
  • 管理系统权限篇

    目录 前言 cooike 登录篇 登录 router beforeEach 登录拦截 axios拦截器 token过期出处理 权限篇 router js store permission js 前言 登录 当用户填写完账号和密码后向服务端验