当前位置: 首页 > news >正文

博客:基本框架设计(下)

权限控制

前端权限控制

在商城运营时,我们可能是多个人员共同操作我们的系统,但是每个操作人员所具备的权限应该不同,权限的不同主要表现在两个部分,即导航菜单的查看权限和页面增删改操作按钮的操作权限。我们的把页面导航菜单查看权限和页面操作按钮统一存储在菜单数据库表中,菜单类型页面资源的类型。类型包括目录 、菜单 、按钮。

权限标识

权限标识用来进行权限控制的唯一标识,主要是进行增删改查的权限控制。

权限标识包括:新增 编辑 删除 查看等,格式结构类似xxx:xxx:xxx 如:admin:user:update

导航菜单权限流程

用户登录之后,跳转至首页,前端发送请求到后台获取该用户下的所有菜单权限与认证权限数据,认证权限为约束用户增删改查操作,在路由导航守卫路由时加载用户导航菜单并存储到本地存储中。导航栏从本地存储读取菜单列表并进行渲染。

页面按钮权限实现

用户登录系统之后,跳转到首页,在路由导航守卫路由时加载用户权限标识集合。返回结果是用户权限标识的集合,页面操作按钮提供权限标识,查询该权限标识是否在用户权限标识集合中,如有存在,则将按钮为可见状态,如不存在,则将按钮为不可见状态,根据需求,也可以设置成禁用状态。

加载导航菜单权限与页面按钮权限数据
动态路由与导航栏

router/index.js中,从后台加载导航菜单、页面按钮权限数据,并将数据保存到本地存储中,如下所示:

router.beforeEach((to, from, next) => {// 添加动态(菜单)路由if (router.options.isAddDynamicMenuRoutes || fnCurrentRouteType(to, globalRoutes) === 'global') {next()} else {http({url: http.adornUrl('/sys/menu/nav'),method: 'get',params: http.adornParams()}).then(({ data }) => {sessionStorage.setItem('authorities', JSON.stringify(data.authorities || '[]'))fnAddDynamicMenuRoutes(data.menuList)router.options.isAddDynamicMenuRoutes = truesessionStorage.setItem('menuList', JSON.stringify(data.menuList || '[]'))next({ ...to, replace: true })}).catch((e) => {console.log(`%c${e} 请求菜单列表和权限失败,跳转至登录页!!`, 'color:blue')router.push({ name: 'login' })})}
})

通过fnAddDynamicMenuRoutes()方法,动态加载菜单到路由中保存到本地存储sessionStorage中。但是现在只有路由,还需要将导航栏展示出来。在main-sidebar.vue中,我们将本地存储中菜单数据取出来,然后对导航栏动态渲染出来,并通过menuId与动态(菜单)路由进行匹配跳转至指定路由,这样,当我们点击菜单的时候,就会跳转至特定的路由。

 created () {this.menuList = JSON.parse(sessionStorage.getItem('menuList') || '[]')this.dynamicMenuRoutes = JSON.parse(sessionStorage.getItem('dynamicMenuRoutes') || '[]')this.routeHandle(this.$route)}
<sub-menu v-for="menu in menuList":key="menu.menuId":menu="menu":dynamicMenuRoutes="dynamicMenuRoutes">
</sub-menu>

sub-menu组件的部分代码

<template><el-submenu v-if="menu.list && menu.list.length >= 1":index="menu.menuId + ''":popper-class="'site-sidebar--' + sidebarLayoutSkin + '-popper'"><template slot="title"><icon-svg :name="menu.icon || ''" class="site-sidebar__menu-icon"></icon-svg><span>{{ menu.name }}</span></template><sub-menuv-for="item in menu.list" :key="item.menuId":menu="item":dynamicMenuRoutes="dynamicMenuRoutes"></sub-menu></el-submenu><el-menu-item v-else :index="menu.menuId + ''" @click="gotoRouteHandle(menu)"><icon-svg :name="menu.icon || ''" class="site-sidebar__menu-icon"></icon-svg><span>{{ menu.name }}</span></el-menu-item>
</template>
按钮权限

在组件中根据外部方法传入的权限标识进行权限判断,如果权限存在,则显示为可见状态,否则不可见。

 <el-button type="primary"icon="el-icon-plus"size="small"v-if="isAuth('admin:indexImg:save')"@click.stop="addOrUpdateHandle()">新增</el-button>

通过isAuth(“权限标识”),判断按钮是否有相同的标识,如果有则可见,否则不可见

/*** 是否有权限* @param {*} key*/
export function isAuth (key) {let authorities = JSON.parse(sessionStorage.getItem('authorities') || '[]')if (authorities.length) {for (const i in authorities) {const element = authorities[i]if (element.authority === key) {return true}}}return false
}

注:后台通过@PreAuthorize("@pms.hasPermission('admin:user:update')")来定义请求所需要的权限,如果用户没有该权限,后台就会抛出401未授权状态码,前端捕获到该状态码后,会登出当前的账号,让用户重新登陆。
在这里插入图片描述

后台菜单管理、角色管理与管理员列表
菜单管理

在【系统管理】-【菜单管理】中,我们可以通过类配置的方式,更直观的对菜单列表增删改查进行管理。

菜单类型包括目录 、菜单 、按钮。

目录为导航栏的大的分类,菜单为分类下的每一项,每个菜单需要绑定上级及填写对应跳转的路由,路由路径对应工程的目录如下图:
在这里插入图片描述

在新增按钮权限时,注意授权标识要与后台一致,新增完之后需要重启刷新生效。
在这里插入图片描述
在这里插入图片描述

角色管理

在【系统管理】-【角色管理】中,管理员可以新增角色,并且赋予该角色可以访问的权限项。

在这里插入图片描述

管理员管理

在【系统管理】- 【管理员列表】中,拥有该权限的管理员可以对其进行管理,该管理员可添加或修改管理权限,并可分配列表中的用户角色。
在这里插入图片描述

二、统一异常处理

后台异常处理

在开发过程中,不可避免的是需要处理各种异常,异常处理方法随处可见,所以代码中就会出现大量的try {...} catch {...} finally {...} 代码块,不仅会造成大量的冗余代码,而且还影响代码的可读性,所以对异常统一处理非常有必要。为此,我们定义了一个统一的异常类YamiShopBindException 与异常管理类 DefaultExceptionHandlerConfig

我们先来看下 YamiShopBindException的代码

@Getter
public class YamiShopBindException extends RuntimeException{/****/private static final long serialVersionUID = -4137688758944857209L;/*** http状态码*/private String code;private Object object;private ServerResponseEntity<?> serverResponseEntity;public YamiShopBindException(ResponseEnum responseEnum) {super(responseEnum.getMsg());this.code = responseEnum.value();}/*** @param responseEnum*/public YamiShopBindException(ResponseEnum responseEnum, String msg) {super(msg);this.code = responseEnum.value();}public YamiShopBindException(ServerResponseEntity<?> serverResponseEntity) {this.serverResponseEntity = serverResponseEntity;}public YamiShopBindException(String msg) {super(msg);this.code = ResponseEnum.SHOW_FAIL.value();}public YamiShopBindException(String msg, Object object) {super(msg);this.code = ResponseEnum.SHOW_FAIL.value();this.object = object;}}

ResponseEnum为我们自定义的返回状态码的枚举类,定义为一个枚举类,更直观处理异常返回的状态码及异常内容,以后每增加一种异常情况,只需增加一个枚举实例即可,不用每一种异常都定义一个异常类。

public enum ResponseEnum {/*** ok*/OK("00000", "ok"),SHOW_FAIL("A00001", ""),/*** 用于直接显示提示用户的错误,内容由输入内容决定*//*** 用于直接显示提示系统的成功,内容由输入内容决定*/SHOW_SUCCESS("A00002", ""),/*** 未授权*/UNAUTHORIZED("A00004", "Unauthorized"),/*** 服务器出了点小差*/EXCEPTION("A00005", "服务器出了点小差"),/*** 方法参数没有校验,内容由输入内容决定*/METHOD_ARGUMENT_NOT_VALID("A00014", "方法参数没有校验");private final String code;private final String msg;public String value() {return code;}public String getMsg() {return msg;}ResponseEnum(String code, String msg) {this.code = code;this.msg = msg;}@Overridepublic String toString() {return "ResponseEnum{" + "code='" + code + '\'' + ", msg='" + msg + '\'' + "} " + super.toString();}}

再来看看 DefaultExceptionHandlerConfig

@Slf4j
@RestController
@RestControllerAdvice
public class DefaultExceptionHandlerConfig {@ExceptionHandler({ MethodArgumentNotValidException.class, BindException.class })public ResponseEntity<ServerResponseEntity<List<String>>> methodArgumentNotValidExceptionHandler(Exception e) {log.error("methodArgumentNotValidExceptionHandler", e);List<FieldError> fieldErrors = null;if (e instanceof MethodArgumentNotValidException) {fieldErrors = ((MethodArgumentNotValidException) e).getBindingResult().getFieldErrors();}if (e instanceof BindException) {fieldErrors = ((BindException) e).getBindingResult().getFieldErrors();}if (fieldErrors == null) {return ResponseEntity.status(HttpStatus.OK).body(ServerResponseEntity.fail(ResponseEnum.METHOD_ARGUMENT_NOT_VALID));}List<String> defaultMessages = new ArrayList<>(fieldErrors.size());for (FieldError fieldError : fieldErrors) {defaultMessages.add(fieldError.getField() + ":" + fieldError.getDefaultMessage());}return ResponseEntity.status(HttpStatus.OK).body(ServerResponseEntity.fail(ResponseEnum.METHOD_ARGUMENT_NOT_VALID, defaultMessages));}@ExceptionHandler(YamiShopBindException.class)public ResponseEntity<ServerResponseEntity<?>> unauthorizedExceptionHandler(YamiShopBindException e){log.error("mall4jExceptionHandler", e);ServerResponseEntity<?> serverResponseEntity = e.getServerResponseEntity();if (serverResponseEntity!=null) {return ResponseEntity.status(HttpStatus.OK).body(serverResponseEntity);}// 失败返回消息 状态码固定为直接显示消息的状态码return ResponseEntity.status(HttpStatus.OK).body(ServerResponseEntity.fail(e.getCode(),e.getMessage()));}@ExceptionHandler(Exception.class)public ResponseEntity<ServerResponseEntity<Object>> exceptionHandler(Exception e){log.error("exceptionHandler", e);return ResponseEntity.status(HttpStatus.OK).body(ServerResponseEntity.fail(ResponseEnum.EXCEPTION));}
}

前台异常处理

前端请求与相应做了封装,请求响应的内容会被拦截器所拦截,当后台返回给前台特定的状态码,前台将显示不同报错信息。请求响应非常常见,我们查看在src\utils\httpRequest.js里面的其中一段代码

http.interceptors.response.use(response => {return response
}, error => {switch (error.response.status) {case 400:Message.error(error.response.data)breakcase 401:clearLoginInfo()router.push({ name: 'login' })breakcase 405:Message.error('http请求方式有误')breakcase 500:Message.error('服务器出了点小差,请稍后再试')breakcase 501:Message.error('服务器不支持当前请求所需要的某个功能')break}return Promise.reject(error)
})

这里将会统一拦截返回的状态码如400,进行错误提示。

RESTful 风格

我们的上述代码使用http状态码对请求进行统一响应,其中最大的

RESTful架构,就是目前最流行的一种互联网软件架构。它结构清晰、符合标准、易于理解、扩展方便,所以正得到越来越多网站的采用。

RESTful概述

http://www.xdnf.cn/news/980731.html

相关文章:

  • 牛市与熊市:市场周期的双面镜
  • 力扣上C语言编程题:最大子数组和(涉及数组)
  • TikTok数据采集软件避坑指南:代理/IP轮换/账号封禁问题一站解决
  • CAMEL中涉及获取 GOOGLE_API_KEY 和 SEARCH_ENGINE_ID 这两个值
  • 【时时三省】(C语言基础)寄存器变量( register变量)和全局变量的存储类别
  • 曼昆《经济学原理》第九版 第十七章寡头垄断
  • (简单介绍)反事实场景counterfactual scenarios
  • 树莓派超全系列教程文档--(63)rpicam-apps可用选项介绍之常用选项
  • 绝缘胶垫怎么选择,耐压、防滑、厚度、质量参数如何选择?
  • [学习] C语言结构体与联合体的对比分析
  • 网络层 IP协议(第一部分)
  • Web前端基础之HTML
  • 通过Docker和内网穿透技术在Linux上搭建远程Logseq笔记系统
  • 对比学习(Contrastive Learning)方法详解
  • Docker Swarm overlay 和 docker_gwbridge
  • 我们来学mysql -- keepalive主从高可用
  • 线 性 数 据 结 构 双 雄:栈 与 队 列 的 原 理、实 现 与 应 用
  • K8S多维度问题排查
  • argocd部署cli工具并添加k8s集群
  • Spring cloud-k8s容器化部署
  • 6.10【Q】网络安全期末复习
  • 动态多目标进化算法:VARE(Vector Autoregressive Evolution)求解DF1-DF14,提供完整MATLAB代码
  • 线程(下)【Linux操作系统】
  • 鸿蒙Next仓颉语言开发实战教程:订单列表
  • 削皮刨结构化网格划分
  • Ubuntu 24.04 systemd-journald日志系统 journalctl 查看日志
  • 与AI联手,ModbusTCP 转Ethercat控制系统升级解决刚需新思路
  • 区块链世界的“破冰“之旅:比特币与以太坊首次组网全解析
  • 【MySQL进阶】MySQL程序
  • Spring Cache+Redis缓存方案 vs 传统redis缓存直接使用RedisTemplate 方案对比