Vue 路由权限控制

article/2025/9/18 3:42:48

当我们在做后台管理系统的时候,都会涉及到系统左侧的菜单树如何动态显示的问题。目前基本上都是RBAC的解决方案,即Role-Based Access Control,权限与角色相关联,用户通过成为适当角色的成员而得到这些角色的权限。这就极大地简化了权限的管理。

vue有很多优秀的后台管理系统模板,这些开源项目都提供了RBAC权限控制的思路,但是在实际项目中,写死角色的方式可能并不适合。

看了网上蛮多的解决方案,个人感觉都有弊端,好多都是前端先把完整的路由表注册到项目中,然后通过后台返回树过滤显示的方案,这样的做法其实只是隐藏了左侧菜单,但是路由还是已经注册进去了,用户猜到访问路径还是可以轻易进入页面,没有真正的做到动态路由加载


以下是我设计的解决方案, -。-

先看一下已经完成的系统结构,便于理解,用户绑定角色(一对多),角色绑定菜单(一对多)

用户菜单

图片

选择角色

角色菜单

图片

选择菜单,由于本项目是多系统,所以会有 ADMIN 和 HMI 两个子系统,后面再来解释

图片

资源管理(我这里没有叫做菜单管理,因为会涉及到各个子系统我称作模块,模块下面有菜单,菜单下面有按钮)

图片

好了,看完几张图大家估计也明白了这就是典型的RBAC。内部具体怎么运作的呢

要实现动态添加路由,即只有有权限的路由才会注册到Vue实例中,核心是vue-router的addRoutes和导航钩子beforeEach两个方法



大体思路为,在beforeEach方法中(即每次路由跳转之前做判断),如果已经加载了路由表,则把路由表注册到实例中,如果没有,则从后端拉取路由表注册到实例中。那为什么不在登录的时候加载一次就可以了呢?这是因为如果只是登录的时候加载一次,网页刷新的时候注册的路由表会丢失,所以我们统一在beforeEach这个钩子中去实现

 
// permission.js,此文件在main.js中直接导入即可,这边的思路是仿照的element-adminimport router from './router'
import store from './store'
import NProgress from 'nprogress' // progress bar
import 'nprogress/nprogress.css' // progress bar style
import Cookies from 'js-cookie'
import screenfull from 'screenfull'router.beforeEach(async (to, from, next) => {const token = Cookies.get('token')NProgress.start()if (token) {// 如果已经处于登录状态,跳到登录页重定向到首页if (to.path === '/login') {next({ path: '/' })NProgress.done()} else {if (!store.state.authorized) {try {router.addRoutes(await store.dispatch('setAccessRoutes'))store.dispatch('setAllDict')next({ ...to, replace: true })} catch (e) {Cookies.remove('token')Cookies.remove('userInfo')next({ path: '/login' })NProgress.done()}} else {next()// 全屏参数判断该页面是否全屏if (!screenfull.isEnabled) returnif (to.meta && to.meta.fullScreen) {screenfull.request().catch(() => null)} else {if (screenfull.isFullscreen) {screenfull.exit()}}}}} else {next(to.path !== '/login' ? { path: '/login' } : true)}
})router.afterEach(() => {NProgress.done()
})

由于路由是动态注册的,所以项目的初始路由就会很简洁,只要提供基础的路由,其他路由都是从服务器返回之后动态注册进来的

 
// router.jsimport Vue from 'vue'
import Router from 'vue-router'
import Login from 'modules/Login'
import NoPermission from 'modules/NoPermission'
Vue.use(Router)// Fixed NavigationDuplicated Problem
const originalPush = Router.prototype.push
Router.prototype.push = function push(location, onComplete, onAbort) {if (onComplete || onAbort) return originalPush.call(this, location, onComplete, onAbort)return originalPush.call(this, location).catch(err => err)
}const createRouter = () =>new Router({mode: 'history',scrollBehavior: () => ({ y: 0 }),routes: [{path: '/',redirect: '/platform'},{path: '/noPermission',component: NoPermission},{path: '/login',component: Login}]})const router = createRouter()export function resetRouter() {const newRouter = createRouter()router.matcher = newRouter.matcher // reset router
}export default routerwebpack之前不支持动态编译,所以很多项目都在路由表维护了一份映射表如下:const routerMap = {user: () => import('/views/user'),role: () => import('/views/role'),...
}

 

我觉得这样很不nice,最新版的vue-cli集成的webpack已经可以支持动态导入啦,因此可以把所有的路由信息全部放到数据库里面配置,前端不在需要维护一份router的映射关系表啦,如果你是老版的CLI,可以使用dynamic-import

我们再来看看下面这个神奇的文件,也可以大致浏览一下然后看下面的解释

 
// menu.json// id:随便是什么规则,只要唯一就行,这里前端写死ID而不是每次导入数据库时候再生成是因为如果每次入库的时候重新生成会丢失之前的关联关系
// title:菜单的标题
// name:唯一标识
// type:'MD'代表模块(子系统),'MN'代表菜单,'BT'代表按钮,如果需要控制到按钮权限则需要配置到BT级别
// icon:菜单的图标
// uri:菜单的路由地址
// componentPath:该菜单在对应前端项目的路径,在后续的store.js会看到用法,就是上述说的不需要在写一份routerMap
// hidden:作为菜单的时候是否在左侧显示,有些菜单比如某个列表的详情页,需要注册到实例中,但是并不需要在左侧菜单栏显示
// noCache:由于项目页面增加了缓存控制,因此该字段用于判断当前页面是否需要缓存
// fullScreen:有些菜单,进入的时候就是全屏展示的,例如某些大屏展示页面,通过该字段配置
// children:和上述字段一样[{"id": "00b82eb6e50a45a495df301b0a3cde8b","title": "SV ADMIN","name": "ADMIN","type": "MD","children": [{{"id": "06f1082640a0440b97009d536590cf4f","title": "系统管理","name": "system","icon": "el-icon-setting","uri": "/system","componentPath": "modules/Layout","type": "MN","children": [{"id": "b9bd920263bb47dbbfbf4c6e47cc087b","title": "用户管理","name": "principal","uri": "principal","componentPath": "views/system/principal","type": "MN","children": [{ "id": "b37f971139ca49ab8c6506d4b30eddb3", "title": "新增", "name": "create", "type": "BT" },{ "id": "d3bcee30ec03432db9db2da999bb210f", "title": "编辑", "name": "edit", "type": "BT" },{ "id": "7c2ce28dcedf439fabc4ae9ad94f6899", "title": "删除", "name": "delete", "type": "BT" },{ "id": "bdf4d9e8bf004e40a82b80f0e88c866c", "title": "修改密码", "name": "resetPwd", "type": "BT" },{ "id": "ba09f8a270e3420bb8877f8def455f6f", "title": "选择角色", "name": "setRole", "type": "BT" }]},{"id": "c47c8ad710774576871739504c6cd2a8","title": "角色管理","name": "role","uri": "role","componentPath": "views/system/role","type": "MN","children": [{ "id": "81c0dca0ed2c455d9e6b6d0c86d24b10", "title": "新增", "name": "create", "type": "BT" },{ "id": "19a2bf03e6834d3693d69a70e919d55e", "title": "编辑", "name": "edit", "type": "BT" },{ "id": "6136cc46c45a47f4b2f20e899308b097", "title": "删除", "name": "delete", "type": "BT" },{ "id": "ad5cf52a78b54a1da7c65be74817744b", "title": "设置菜单", "name": "setMenu", "type": "BT" }]},{"id": "8b5781640b9b4a5cb28ac616da32636c","title": "资源管理","name": "resource","uri": "resource","componentPath": "views/system/resource","type": "MN","children": [{ "id": "d4182147883f48069173b7d173e821dc", "title": "新增", "name": "create", "type": "BT" },{ "id": "935fcb52fffa45acb2891043ddb37ace", "title": "编辑", "name": "edit", "type": "BT" },{ "id": "3f99d47b4bfd402eb3c787ee10633f77", "title": "删除", "name": "delete", "type": "BT" }]}]},}]},{"id": "fc8194b529fa4e87b454f970a2e71899","title": "SV HMI","name": "HMI","type": "MD","children": [{ "id": "eb5370681213412d8541d171e9929c84", "title": "启动检测","name": "001" },{ "id": "06eb36e7224043ddbb591eb4d688f438", "title": "设备信息","name": "002" },{ "id": "76696598fd46432aa19d413bc15b5110", "title": "AI模型库","name": "003" },{ "id": "2896f3861d9e4506af8120d6fcb59ee1", "title": "保养维修","name": "004" },{ "id": "91825c6d7d7a457ebd70bfdc9a3a2d81", "title": "继续","name": "005" },{ "id": "24694d28b2c943c88487f6e44e7db626", "title": "暂停","name": "006" },{ "id": "225387753cf24781bb7c853ee538d087", "title": "结束","name": "007" }]}
]

以上是前端的路由配置信息,之前提到过,路由是后端返回的,为什么前端还有一份菜单文件呢?

因为路由里面的内容全部都是前端需要使用的,比如菜单显示的图表,菜单对应的前端路径等等...既然和前端关系比较大,所以前端维护该文件更适合,而不是让后端去配置XML或者liquibase。你每次菜单有修改的时候要通知你的后台要更新一下数据库,然后切换多个环境的时候每个后台都要通知一声...后台还不一定乐意X你...然后你想改个小图标都要小心翼翼被你的后台大佬怼...

当然如果是多环境部署的话还是让后台使用liquibase比较合适,但是开发模式下前端完全可以自己玩。等到需要多环境(不同数据库)同时部署的时候,你把sql语句给后端就OK了,文章下面会提到怎么导出sql...

Question:

既然前端有该文件,是不是意味着路由的源码又暴露出去了,那和别人的猜路径就可以访问有什么区别?不是说好了从数据库拉取菜单信息,你跟我直接用这个json,那数据库咋整?别急...

Answer:

  1. 这只是前端用来mock的配置文件,build的时候不会打包该内容。

  2. 在用户角色菜单这些关联关系还没有建立之前,菜单只能通过mock来建立,这个时候直接读取前端的配置文件...额,真香,具体是怎么做到的可以看下面store.js的做法

  3. 菜单始终由前端来维护,当需要上线的时候,前端可以通过node将menu.json生成SQL语句导入到数据库中。后面会介绍

接下来就是如何注册这些路由表了

 
// store.jsimport Vue from 'vue'
import Vuex from 'vuex'
import Cookie from 'js-cookie'
import NotFound from 'modules/NotFound'
import { resetRouter } from '../router'
import { getUserResourceTree, getDictAllModel } from 'apis'
import { deepClone } from 'utils/tools'// 此处的IS_TESTING就是用来判断当前是拉取数据库的真实菜单还是直接用前端的menu.json,在资源的关联关系还没有建立之前非常有用
import { IS_TESTING } from '@/config'
import { Message } from 'element-ui'Vue.use(Vuex)// 生产可访问的路由表
const createRouter = (routes, cname = '') => {return routes.reduce((prev, { type, uri: path, componentPath, name, title, icon, redirectUri: redirect, hidden, fullScreen, noCache, children = [] }) => {// 是菜单项就注册到路由进去if (type === 'MN') {prev.push({path,// 此处就是webpack动态导入啦,是不是so easy,妈妈再用不用担心我再写一份routerMap放到源码里了component: () => import(`@/${componentPath}`),name: (cname + '-' + name).slice(1),props: true,redirect,meta: { title, icon, hidden: hidden === 'Y', type, fullScreen: fullScreen === 'Y', noCache: noCache === 'Y' },children: children.length ? createRouter(children, cname + '-' + name) : []})}return prev}, [])
}// 生产权限按钮表
const createPermissionBtns = router => {let btns = []const c = (router, name = '') => {router.forEach(v => {v.type === 'BT' && btns.push((name + '-' + v.name).slice(1))return v.children && v.children.length ? c(v.children, name + '-' + v.name) : null})return btns}return c(router)
}export default new Vuex.Store({state: {collapse: false, // 菜单栏是否收缩authorized: false, // 是否拉取了授权菜单dict: {},accsessRoutes: [], // 已注册的路由permissionBtns: [], // 有权限的按钮navTags: [], // 标签导航列表cachedViews: [] // 缓存的页面},getters: {collapse: state => state.collapse,cachedViews: state => state.cachedViews,accsessRoutes: state => state.accsessRoutes,// 菜单栏(过滤掉hidden)menuList: state => {const filterMenus = menus => {return menus.filter(item => {if (item.children && item.children.length) {item.children = filterMenus(item.children)}return item.meta && !item.meta.hidden})}return filterMenus(deepClone(state.accsessRoutes))},navTags: state => state.navTags},mutations: {SET_ACCSESS_ROUTES(state, accsessRoutes) {state.authorized = truestate.accsessRoutes = accsessRoutes},SET_ALL_DICT(state, dict) {state.dict = dict},SET_PERMISSION_BTNS(state, btns) {state.permissionBtns = btns},SET_COLLAPSE(state, flag) {state.collapse = flag},SET_CACHED_VIEWS(state, cachedViews) {state.cachedViews = cachedViews},// 退出登录LOGOUT: state => {state.cachedViews = []state.authorized = falseresetRouter()Cookie.remove('token')Cookie.remove('userInfo')}},actions: {setAccessRoutes: ({ commit }) => {return new Promise(async (resolve, reject) => {// 404页面选择在动态添加路由之后再注册进来,是因为如果开始就注册到项目中,在addRoutes之后会有限匹配该404,造成BUGconst routerExt = [{ path: '*', redirect: '/404' },{ path: '/404', component: NotFound }]// getUserResourceTree这个接口逻辑是查询当前登录人角色所包含的资源,过滤出模块名(这里是ADMIN)下面的子节点(包含菜单和按钮)const res = await (IS_TESTING ? import('@/mock/menu.json') : getUserResourceTree('ADMIN'))if (!res) return reject()let routerif (IS_TESTING) {// 这里取第0个是因为我这个系统是属于大系统的第一个子系统,在菜单menu.json可以看到router = res[0].children} else {if (!res.data.length) {reject()return Message.error('用户未配置菜单或菜单配置不正确,请检查后重试~')} else {router = res.data}}const accessRoutes = createRouter(router).concat(routerExt)commit('SET_ACCSESS_ROUTES', accessRoutes)commit('SET_PERMISSION_BTNS', createPermissionBtns(router))resolve(accessRoutes)})},setAllDict: async ({ commit }) => {if (IS_TESTING) returnconst res = await getDictAllModel()if (!res) returncommit('SET_ALL_DICT', res.data)},logout: ({ commit }) => {return new Promise(resolve => {commit('LOGOUT')resolve()})}}
})

好了,最后一步就是在上线的时候如何把menu.json变成数据库的SQL,之后就可以把IS_TESTING改为false,真正拉取数据库的菜单啦

 

// createMenu.js
const fs = require('fs')
const path = require('path')
const chalk = require('chalk')
const execSync = require('child_process').execSync //同步子进程
const resolve = dir => path.join(__dirname, dir)
const format = (data = new Date(), fmt = 'yyyy-MM-dd') => {let o = {'M+': data.getMonth() + 1, // 月份'd+': data.getDate(), // 日'h+': data.getHours(), // 小时'm+': data.getMinutes(), // 分's+': data.getSeconds(), // 秒'q+': Math.floor((data.getMonth() + 3) / 3), // 季度S: data.getMilliseconds() // 毫秒}if (/(y+)/.test(fmt)) {fmt = fmt.replace(RegExp.$1, (data.getFullYear() + '').substr(4 - RegExp.$1.length))}for (var k in o) {if (new RegExp('(' + k + ')').test(fmt)) {fmt = fmt.replace(RegExp.$1, RegExp.$1.length === 1 ? o[k] : ('00' + o[k]).substr(('' + o[k]).length))}}return fmt
}
// 导出的文件目录位置
const SQL_PATH = resolve('./menu.sql')
// 获取全局配置的git的用户名,用来追踪SQL是谁导出的(出问题锅是谁的QAQ)
const myname = execSync('git show -s --format=%cn').toString().trim()
// 导出SQL的函数
function createSQL(data, name = '', pid = '0', arr = []) {data.forEach(function(v, d) {if (v.children && v.children.length) {createSQL(v.children, name + '-' + v.name, v.id, arr)}arr.push({id: v.id,created_at: format(new Date(), 'yyyy-MM-dd hh:mm:ss'),modified_at: format(new Date(), 'yyyy-MM-dd hh:mm:ss'),created_by: myname,modified_by: myname,version: 1,is_delete: 'N',code: (name + '-' + v.name).slice(1),name: v.name,title: v.title,icon: v.icon,uri: v.uri,sort: d + 1,parent_id: pid,type: v.type,component_path: v.componentPath,redirect_uri: v.redirectUri,full_screen: v.fullScreen === 'Y' ? 'Y' : 'N',hidden: v.hidden === 'Y' ? 'Y' : 'N',no_cache: v.noCache === 'Y' ? 'Y' : 'N'})})return arr
}fs.readFile(resolve('src/mock/menu.json'), 'utf-8', (err, data) => {const menuList = createSQL(JSON.parse(data))const sql = menuList.map(sql => {let value = ''for (const v of Object.values(sql)) {value += ','value += v ? `'${v}'` : null}return 'INSERT INTO `t_sys_resource` VALUES (' + value.slice(1) + ')' + '\n'}).join(';')const mySQL ='DROP TABLE IF EXISTS `t_sys_resource`;' +'\n' +'CREATE TABLE `t_sys_resource` (' +'\n' +'`id` varchar(64) NOT NULL,' +'\n' +"`created_at` timestamp NULL DEFAULT NULL COMMENT '创建时间'," +'\n' +"`modified_at` timestamp NULL DEFAULT NULL COMMENT '更新时间'," +'\n' +"`created_by` varchar(64) DEFAULT NULL COMMENT '创建人'," +'\n' +"`modified_by` varchar(64) DEFAULT NULL COMMENT '更新人'," +'\n' +"`version` int(11) DEFAULT NULL COMMENT '版本(乐观锁)'," +'\n' +"`is_delete` char(1) DEFAULT NULL COMMENT '逻辑删除'," +'\n' +"`code` varchar(150) NOT NULL COMMENT '编码'," +'\n' +"`name` varchar(50) DEFAULT NULL COMMENT '名称'," +'\n' +"`title` varchar(50) DEFAULT NULL COMMENT '标题'," +'\n' +"`icon` varchar(50) DEFAULT NULL COMMENT '图标'," +'\n' +"`uri` varchar(250) DEFAULT NULL COMMENT '路径'," +'\n' +"`sort` int(11) DEFAULT NULL COMMENT '排序'," +'\n' +"`parent_id` varchar(64) DEFAULT NULL COMMENT '父id'," +'\n' +"`type` char(2) DEFAULT NULL COMMENT '类型'," +'\n' +"`component_path` varchar(250) DEFAULT NULL COMMENT '组件路径'," +'\n' +"`redirect_uri` varchar(250) DEFAULT NULL COMMENT '重定向路径'," +'\n' +"`full_screen` char(1) DEFAULT NULL COMMENT '全屏'," +'\n' +"`hidden` char(1) DEFAULT NULL COMMENT '隐藏'," +'\n' +"`no_cache` char(1) DEFAULT NULL COMMENT '缓存'," +'\n' +'PRIMARY KEY (`id`),' +'\n' +'UNIQUE KEY `code` (`code`) USING BTREE' +'\n' +") ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='资源';" +'\n' +sqlfs.writeFile(SQL_PATH, mySQL, err => {if (err) return console.log(err)console.log(chalk.cyanBright(`恭喜你,创建sql语句成功,位置:${SQL_PATH}`))})
})

 
// package.json
"scripts": {"build": "vue-cli-service build","lint": "vue-cli-service lint","dev": "vue-cli-service serve","menu": "node createMenu"},

需要生成SQL的时候执行一下 npm run menu 就好啦

为了方便,以上SQL是会先删除资源表再重新创建,导入数据库之前记得备份一下。

整个流程是不是so easy?so easy?so easy?

后台表建好了之后前端自己玩,自给自足的感觉香不香?

转自https://mp.weixin.qq.com/s/tauwwpFT2XhDk2G6e5ejpA


http://chatgpt.dhexx.cn/article/Ue87bLnv.shtml

相关文章

【Git CMD】Git常用命令总结

目录 0 git的工作区、暂存区、本地仓库和远程仓库0.1 图解0.2 解析 1 本地仓库1.1 创建版本库1.2 分支1.2.1 查看本地仓库的分支信息1.2.2 创建分支1.2.3 切换分支1.2.4 重命名分支1.2.5 合并分支1.2.6 删除分支 1.3 添加文件到暂存区1.3.1 添加单个文件1.3.2 添加多个文件1.3.…

Git常用命令大全(从入门到使用,学不会评论区骂我)

Git常用命令大全 1:Git全局设置 当安装Git后首先要做的事情是设置用户名称和email地址。这是非常重要的,因为每次Git提交都会使用该用户信息。在Git 命令行中执行下面命令: 设置用户信息 git config --global user.name “你的用户名” …

Git常用命令及方法大全

下面是我整理的常用 Git 命令清单。几个专用名词的译名如下。 Workspace:工作区Index / Stage:暂存区Repository:仓库区(或本地仓库)Remote:远程仓库 本地分支关联远程:git branch --set-upstre…

Git 常用命令大全

一、 Git 常用命令速查 git branch 查看本地所有分支git status 查看当前状态 git commit 提交 git branch -a 查看所有的分支git branch -r 查看远程所有分支git commit -am "init" 提交并且加注释 git remote add origin git192.168.1.119:ndshowgit push origin …

Git常用命令大全

Git常用命令大全 下面是我整理的常用 Git 命令清单。几个专用名词的译名如下。 Workspace:工作区Index / Stage:暂存区Repository:仓库区(或本地仓库)Remote:远程仓库 本地分支关联远程 git branch --set-u…

git常用命令总结

1 git概述 1.1 简介 git是分布式版本控制系统(Distributed Version Control System,简称DVCS),分为两种仓库 :本地仓库和远程仓库。 本地仓库:是在开发人员自己电脑上的Git仓库远程仓库:是在…

20 个最常用的 Git 命令用法说明及示例

在这篇文章中,我将介绍在使用 Git 时最常使用的 20 个命令。 作者 | Sahiti Kappagantula 译者 | 弯月,责编 | 屠敏 出品 | CSDN(ID:CSDNnews) 以下为译文: 以下是这些Git命令: git config git…

Git基本命令大全

点击上方“小白学视觉”&#xff0c;选择加"星标"或“置顶” 重磅干货&#xff0c;第一时间送达 1、git clone -b <指定分支名> <远程仓库地址> 克隆指定分支 如&#xff1a; git clone -b bestore_master ssh://gitgit-ssh.xxx.com/xxx.git 2、 git bra…

常用git命令总结大全

目录 一、常用命令 1、git init 2、git add 文件名 3、git commit -m “备注” 4、git status 与 git diff 5、git show commit_id 查看某次修改 6、git log 与 git reflow 7、git pull (--rebase) 8、git push (-u) 与 git branch (-u) 9、git reset --hard 与 git…

Git常用命令

这是一篇笔记 //查看某个命令文档 git help <command> git <command> -h git <command> --help1.基本操作 用户配置 git config --global user.name "bettyaner" git config --global user.email bettyaner163.com配置级别 –local&#xff08…

Git 常用命令速查表(收藏大全)

目录 一、新建代码库 二、配置 三、增加/删除/修改文件 四、代码提交 五、分支 六、标签 七、查看信息 八、远程操作 九、撤销 十、其他 名词 master: 默认开发分支 origin: 默认远程版本库 Index / Stage&#xff1a;暂存区 Workspace&#xff1a;工作区 Reposito…

【深度学习】ResNet50

结构 ResNet50结构&#xff1a; 推荐查看&#xff1a;caffe可视化版 resnet50中1x1filter的作用&#xff1a; 1、在shortcut connection block的残差层中使用1x1的fiter先降维&#xff08;channel&#xff09;&#xff0c;然后再使用1x1的fiter升维,使残差层输出与恒等映射…

ResNet-50 结构

ResNet有2个基本的block&#xff0c;一个是Identity Block&#xff0c;输入和输出的dimension是一样的&#xff0c;所以可以串联多个&#xff1b;另外一个基本block是Conv Block&#xff0c;输入和输出的dimension是不一样的&#xff0c;所以不能连续串联&#xff0c;它的作用本…

ResNet 简介

ResNet 本文对resnet进行介绍&#xff0c;文章目录如下&#xff1a; ResNet 历史ResNet 亮点为何层数不能太深residual 残差模块介绍网络结构BN 层迁移学习 本文参考资料有&#xff1a; 6.1 ResNet网络结构&#xff0c;BN以及迁移学习详解 https://www.bilibili.com/video/…

Resnet

再上一偏博文中我们说到越复杂的问题需要越深层的神经网络拟合&#xff0c;但是越深层的神经网络越难训练&#xff0c;原因可能是过拟合以及损失函数的局部最优解过多&#xff08;鞍点过多&#xff1f;导致经过相同的epoch更深的网络的trainerror大于较浅的网络&#xff0c;因为…

ResNet网络详解

ResNet ResNet在2015年由微软实验室提出&#xff0c;斩获当年lmageNet竞赛中分类任务第一名&#xff0c;目标检测第一名。获得coco数据集中目标检测第一名&#xff0c;图像分割第一名。 ResNet亮点 1.超深的网络结构(突破1000层) 2.提出residual模块 3.使用Batch Normalizat…

1 通俗易懂解释Resnet50

通俗易懂Resnet50网络结构分析 1 Why(该网络要解决什么样的问题)1.1 什么叫梯度消失和梯度爆炸 2 How(如何解决该问题)2.1 直观解释2.2 残差是什么2.3 网络结构 3 what 结果怎么样 1 Why(该网络要解决什么样的问题) 理论上网络越来越深&#xff0c;获取的信息越多&#xff0c;…

ResNet50网络结构

代码&#xff1a; import keras keras.utils.plot_model(keras.applications.ResNet50(include_topTrue,input_shape(224,224,3),weightsNone), to_fileimage_model.png, show_shapesTrue) ResNet50的标准输入为224x224&#xff0c;avg_pool&#xff08;-3层&#xff09;及之…

resnet18与resnet50

ResNet18的18层代表的是带有权重的 18层&#xff0c;包括卷积层和全连接层&#xff0c;不包括池化层和BN层。 Resnet论文给出的结构图 参考ResNet详细解读 结构解析&#xff1a; 首先是第一层卷积使用7∗77∗7大小的模板&#xff0c;步长为2&#xff0c;padding为3。之后进行…