【开源风云】从若依系列脚手架汲取编程之道(四)

news2024/11/25 18:51:55

📕开源风云系列

  • 🍊本系列将从开源名将若依出发,探究优质开源项目脚手架汲取编程之道。
  • 🍉从不分离版本开写到前后端分离版,再到微服务版本,乃至其中好玩的一系列增强Plus操作
  • 🍈希望你具备如下技术栈
    • 🍎Spring
    • 🍍SpringMVC
    • 🍐Mybatis/Mybatis-plus
    • 🍅Thymeleaf
    • 🥝SpringBoot
    • 🍓Shiro
    • 🍏SpringSecurity
    • 🍌SpringCloud
    • 🍒云服务器相关知识
  • 本篇是分离版第一篇

在这里插入图片描述

  • 文章同时同步到我的个人站点🍊欢迎来访!
    • 🍎国内:https://www.linxiaoqin.netlify.app
    • 🍏国际:https://www.linxiaoqin.vercel.app

目录

  • 📕开源风云系列
  • 1、若依践行SpringSecurity
    • 1.1、Apifox如何调接口
  • 2、RBAC
    • 2.1、权限
    • 2.2、何时获得权限
    • 2.3、页面权限的获取
    • 2.4、按钮权限
  • 3、第三方登录
  • 4、新建模块
  • 5、前端通用方法
    • 5.1、$tab
      • 5.1.1、打开新标签页
      • 5.1.2、修改标签页
      • 5.1.3、关闭标签
      • 5.1.4、刷新标签
      • 5.1.5、关闭所有页签
      • 5.1.6、关闭左侧页签
    • 5.2、$modal
    • 5.3、$auth
    • 5.4、$download
      • 5.4.1、根据名称下载`upload`路径下的文件
  • 6、Https实现

1、若依践行SpringSecurity

若依前后端分离采用SpringSecurity来做认证和授权操作,使用SpringSecurity,我们只需要authenticationManager.authenticate(new UsernamePasswordAuthenticationToken(username, password))

即:

  • 认证管理器调用认证方法,将我们的用户名和密码封装成UsernamePasswordAuthenticationToken传入认证方法
  • 认证管理器调用认证方法执行后会自动调用UserDetailsServiceImpl.loadUserByUsername()方法,我们自己写的类实现了UserDetailsService接口,并重写loadUserByUsername方法

在这里插入图片描述

我们简单 debug 认证方法一下看看,可以发现的是:

  • 在用户登录的时候,对于任何异常都会记录进日志
  • 然后获取当前的登录对象,通过它来生成token

在这里插入图片描述

其中recordLoginInfo(loginUser.getUserId())是记录登录的信息的,包括登录的 ip、登录时间,然后将信息更新到数据库里面,我们可以进去方法里面查看

在这里插入图片描述

那么最后在tokenService.createToken(loginUser)生成token是怎么生成的呢?

首先生成一个uuid,将uuid设置给LoginUser的token字段,这个uuid其实也就是tokenid,setUserAgent(loginUser)设置用户代理,这个用户代理干了什么呢?

在这里插入图片描述

这个设置用户代理方法其实就是获得了很多信息,包括操作系统、浏览器信息、ip等,然后将其更新进数据库中。

在这里插入图片描述

最后执行refreshToken(loginUser)刷新令牌有效期,首先设置登录时间为系统当前时间,设置过期时间,expireTime时间是读取yml配置的信息,然后将键值存入 redis 中,键是userKey,值是loginUser

在这里插入图片描述

再回头来看我们创建令牌的代码,创建一个HashMap,将键值对login_user_key:token存入HashMap,然后创建token,并将HashMap传入。

在这里插入图片描述

我们进入createToken方法,设置主体信息为claims,也就是我们上面HashMap的键值对,然后设置签名方式,加密方式为HS512,密钥secret通过yml配置文件读取,密钥随便填即可。

在这里插入图片描述

1.1、Apifox如何调接口

现在有一个这样的问题,遇到认证失败,但是自己想用postman、apifox、apipost等软件就想要调通API,怎么办?可以使用超级管理员的token,当我们登录超级管理员,随便请求一个页面,就可以在两个地方发现token:

  1. 一个是请求头的Authorization
  2. 另一个是Cookie里面

在这里插入图片描述

包括Swagger,也需要添加token才能调试的通

在这里插入图片描述

2、RBAC

RBAC是一种基于角色权限控制,进而控制用户的权限。也就是给用户角色,给角色权限

具体而言,对系统操作的各种权限不是直接授予具体的用户,而是在用户集合与权限集合之间建立一个角色集合。每一种角色对应一组相应的权限。一旦用户被分配了适当的角色后,该用户就拥有此角色的所有操作权限。

这样做的好处是,不必在每次创建用户时都进行分配权限的操作,只要分配用户相应的角色即可,而且角色的权限变更比用户的权限变更要少得多,这样将简化用户的权限管理,减少系统的开销。

2.1、权限

  • 页面权限,就是进入某个页面所需要的权限
  • 按钮权限则是能点击某个按钮所需要的权限

对于权限,在程序中往往表现为一个字符串,比如说system:user:add表示对用户具有增加权限,当然也可以说dsadsaddsadsadsa表示对用户有增加权限,字符串是随意定的,但是为了符合人类的想法,要做到尽量的见名知意。

对于若依而言,在系统管理→菜单管理对每一个菜单对应了某种权限,如下图所示:

在这里插入图片描述

下面我们以岗位管理为例子,说明岗位管理的每个字符串的作用,岗位管理的权限字符串如上:上图的岗位管理对应的字符串为system:post:list,该字符串的意思是,有了这个字符串才能查看岗位管理的菜单。

  • system:post:query 字符串表示对单个岗位详情的查询权限
  • system:post:add 表示增权限
  • system:post:edit 表示修改权限
  • system:post:remove 表示删岗位权限
  • system:post:export 表示导出岗位数据Excel文件的权限

岗位管理的system:post:list页面权限,如果没有该权限,连菜单都看不到,自然页面也看不到。其他的都是按钮权限

按钮权限在前端页面的表现就是能不能看到按钮,在后端的表现就是能不能执行按钮对应的controller的增删改查方法。

在若依中,页面菜单、目录菜单和按钮菜单基本上都是存在数据库中(首页、404等特殊页面除外),在系统管理→菜单管理可以填写每一个菜单对应的权限字符串,不过目前目录菜单不能添加权限字符串。

在这里插入图片描述

当我们在菜单管理写好权限字符串之后,在角色管理我们可以添加角色,并把相关的权限赋予给角色,然后给用户分配角色。

[!NOTE]

给用户赋予角色,给角色赋予权限。

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

2.2、何时获得权限

若依用户是在 登录的时候就在数据库得到了属于自己的权限,并将其存在了 redis 中。

在这里插入图片描述

我们来看一下代码:在用户验证处理UserDetailsServiceImpl类中,创建了LoginUser,并且传入了对应的权限。

在这里插入图片描述

permissionService.getMenuPermission(user)则是根据当前登录的用户查询该用户的权限,转到方法进去看看:

在这里插入图片描述

从上面的代码可以看到,如果是超级管理员就给权限字符串为*:*:*,那么什么样的人是超级管理员呢?

在这里插入图片描述

userId=1的用户就是超级管理员,也就是说超级管理员是代码写死的,前端判断字符串也要先看看权限字符串是不是*:*:*。如果不是超级管理员,代码是执行了perms.addAll(menuService.selectMenuPermsByUserId(user.getUserId())),也就是获得当前用户的userId,拿着userId去数据库查询权限。

在这里插入图片描述

查看Service层的代码,可以发现,调用Mapper去数据库查询权限,迭代查询的每个权限并且split分割了一下,通过,逗号分隔,所以我们写权限的时候可以这样写system:user:add,system:user:edit,可以在一个菜单的权限标识写好几个权限字符串,用逗号分割。

话说回来,去数据库怎么查询的权限呢?我们看到传入的只有userId,查看SQL语句,并手动在 navicat 运行userid=2的用户权限SQL:

在这里插入图片描述

在这里插入图片描述

可以看到SQL实际上就是不断在左连接,所谓左连接通俗而言就是左边表展示全部数据,右边表若匹配成功就连接,没有匹配成功就右边表空着。

那么这里就涉及到一个问题:当我用超级管理员修改了角色的权限,对于已经登录的和该角色相关联的用户,是不是立刻生效呢?

  • 其实对于页面权限是立刻生效的,但是对于按钮权限并不是立刻生效的,而是要重新登录重新从数据库获取权限,然后再存入Redis

2.3、页面权限的获取

当用户登录完毕之后,左侧菜单栏展示出来了大量的菜单,对于不同权限的用户,展示的菜单不尽相同。那么,左侧菜单是怎么来的呢?这就需要打开Layout组件,然后找到侧边栏组件,如下:

在这里插入图片描述

Layout组件的菜单栏组件是Sidebar,里面有一个ElMenu组件(对应整个菜单整体),SidebarItem全部都是一级菜单,ElSubmenu 对应我们的二级菜单整体,里面的SidebarItem组件为具体的每一个二级菜单。

ElMenu   # 整体菜单
  -- SidebarItem   # 一级菜单
  	-- ElSubmenu  # 二级菜单

如下图,我们可以发现,ElMenu中的菜单项的迭代数据来自变量sidebarRouters

在这里插入图片描述

sidebarRouters变量的数据一定是后端查询来的菜单数据。我们继续深入,它是vuex的getters:

在这里插入图片描述

查看Vuex的目录下的 store/getters.js

sidebarRouters:state => state.permission.sidebarRouters,

在这里插入图片描述

可以发现,sidebarRouters 的数据是从 permission.js中管理的,我们进去permission.js中查看:发现sidebarRouters初始化是空的,是被SET_SIDEBAR_ROUTERS方法赋值的

在这里插入图片描述

我们继续深入SET_SIDEBAR_ROUTERS方法,会发现这么一句代码:

constantRoutes.concat(sidebarRoutes)

也就是我们的sidebarRouters 的数据,是由constantRoutes拼接了sidebarRoutes

在这里插入图片描述

其中constantRoutes点进去发现其实它就是公共路由,不存在数据库中,任何角色都可以有:

在这里插入图片描述

从数据库中取的sidebarRoutes数据其实是请求后端返回的data数据

在这里插入图片描述

![NOTE]

sidebarRouters的数据是来自两部分:

  • 一部分来自getRouters方法向后端发请求,返回的res的data经过filterAsyncRouter方法过滤后的路由
  • 另一部分来自constantRoutes,constantRoutes是写死的,来自router文件夹下的index.js文件

前端点击岗位管理的时候,调用了岗位管理的system/post/list接口,调用该接口是需要权限的,并且权限为system/post/list,每一个接口的调用需要什么权限,我们都可以在上面的注解可以看到

在这里插入图片描述

![NOTE]

@PreAuthorize注解是SpringSecurity框架的注解,注解的value值所需要填写一个表达式,如果表达式计算的结果是true,那么就允许访问对应的接口,如果表达式计算的结果是false,则表示没有权限。

  • 当我们需要用到@PreAuthorize注解,先需要写另一个注解来让@PreAuthorize注解能生效,ry程序在SecurityConfig类已经写了

  • @EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true) 表示**@PreAuthorize和@PostAuthorize注解要不要启用**

  • @PreAuthorize表示在执行方法前验证权限,@PostAuthorize表示在执行方法后验证权限。一般用@PreAuthorize就够了。

在这里插入图片描述

刚刚我们说到,@PreAuthorize的value值写SpEL表达式,并且返回值为true则可以执行方法,返回值为false则表示不能执行方法。现在我们来分析SpEL表达式。SpEL表达式能写方法调用,ry程序就是写的方法表达式。岗位管理的表达式如下:

@PreAuthorize("@ss.hasPermi('system:post:list')")

SpEL表达式可以通过@来引用bean,所以@ss在引用名为ss的Bean,然后调用该Bean的hasPermi方法,并传入了参数。@ss的bean为:

在这里插入图片描述

综上所述,表达式@ss.hasPermi('system:post:list') 的意思是调用下面的方法,并传参数为system:post:list,如下:

在这里插入图片描述

2.4、按钮权限

按钮权限在前端的表现为vue组件、HTML标签是否显示。在ry程序中,最常用的方式就是控制按钮显示还是不显示,如下:

在这里插入图片描述

也就是说,当用户具有system:post:add权限时,新增的<el-button>组件才会显示。

v-hasPermi是一个新指令,vue本身自身没有的,那么只能是ry自己写的一个新指令。ry所有自定义指令在src/directive下,在main.js引入了src/directive/index.js

import directive from './directive' // directive
Vue.use(directive)

自定义指令是以插件的形式包装的,Vue.use方法会自动执行插件directive的install方法:

在这里插入图片描述

install方法中,我们看到v-hasPermi被注册。

在这里插入图片描述

可以看出,对象里面只有一个方法inserted,也就是在被绑定元素插入父节点时调用,这里正好可以判断有没有权限,有就插入,没有就直接移除dom即可。

3、第三方登录

当今社会,微信登录、QQ登录、抖音登录等等三方登录已经层出不穷,学会三方登录势在必行。微信登录要认证开发者,必须为企业,个人不行,而且还要交300块钱。一听到钱,白嫖党的钱是不可能被赚的,不学微信登录。QQ登录也要申请、微博登录也要申请。

还好Gitee给力,申请轻轻松松,谁都能轻松让Gitee作为第三方登录,JustAuth能让我们第三方登录写少一些代码,它包装了国内外30多种三方登录。

使用教程:这应该是史上最全的整合第三方登录的开源库

使用JustAuth总共分三步(这三步也适合于JustAuth支持的任何一个平台):

  1. 申请注册第三方平台的开发者账号 - 设置 - 第三方应用

在这里插入图片描述

  1. 创建应用

在这里插入图片描述

  • 应用名称 一般填写自己的网站名称即可

  • 应用描述 一般填写自己的应用描述即可

  • 应用主页 填写自己的网站首页地址

  • 应用回调地址 重点,应用回调不能乱填,当我们gitee登录成功之后,gitee会自动跳转到应用回调地址,并且gitee会带上code,利用code可以得到所登录gitee用户信息。

  • 权限 根据页面提示操作,默认勾选第一个就行。

以上信息输入完成后,点击确定按钮创建应用。创建完成后,点击进入应用详情页,可以看到应用的密钥等信息

  1. 引入依赖
<!--JustAuth第三方登录-->
<dependency>
    <groupId>me.zhyd.oauth</groupId>
    <artifactId>JustAuth</artifactId>
    <version>${justAuth.version}</version>
</dependency>
  • 我们可以在根的pom.xml引入
  • 必须要在framework包下继续引入,但是不用带版本号,会自动继承根的pom.xml,否则Maven一直报错

在这里插入图片描述

  1. 接下来我们改 login.vue
<!-- Gitee登录 -->
<div style="width: 32px;height: 32px;margin-top: 5px;cursor: pointer;" title="利用Gitee登录" @click="giteeLogin">
  <img style="height: 100%;width: 100%;" src="../assets/logo/profile.jpg">
</div>

我们添加的代码如图,@click="giteeLogin"点击事件的方法是giteeLogin

在这里插入图片描述

页面呈现的样子是:

在这里插入图片描述

我们看一下点击事件giteeLogin,将其补充在 methods 下:

// Gitee登录
giteeLogin() {
  PreLoginByGitee().then(res => {
    Cookies.set("user-uuid", res.uuid)
    window.location = res.authorizeUrl
  })
},

在这里插入图片描述

我们在src/api/login.js里面写入PreLoginByGitee方法

// 获得跳转到Gitee登录的地址
export function PreLoginByGitee() {
  return request({
    url: '/PreLoginByGitee',
    headers: {
      isToken: false
    },
    method: 'get',
  })
}

对应的后端接口:

在这里插入图片描述

@RestController
public class GiteeLoginController {

    // 创建授权request
    @GetMapping("/PreLoginByGitee")
    public AjaxResult PreLoginByGitee() {
        AjaxResult ajax = AjaxResult.success();
        AuthRequest authRequest = new AuthGiteeRequest(AuthConfig.builder()
                .clientId("clientId")
                .clientSecret("clientSecret")
                .redirectUri("redirectUri")
                .build());
        String uuid = IdUtils.fastUUID();
        String authorizeUrl = authRequest.authorize(uuid);
        //返回给前端一个跳转路径url和uuid
        ajax.put("authorizeUrl", authorizeUrl);
        ajax.put("uuid", uuid);
        return ajax;
    }
}

这样就OK了,但是后端接口还得放行:

在这里插入图片描述

在这里插入图片描述

我们点击同意授权,就会调用我们的回调地址:http://localhost/callback ,并且带上code和state,也就是会跳转到http://localhost/callback&code=xxx&state=xxx,这个 state 其实就是 uuid,我们在前端创建路由,让前端回调到我们的loginByGitee.vue组件

// Gitee
{
    path: '/callback',
    component: () => import('@/views/loginByGitee'),
    hidden: true
},

在这里插入图片描述

同时我们也准备好了loginByGitee.vue, 里面做的操作就是先从 cookie 中取出 uuid,然后将 uuid 和 路由中的 code封装成一个请求体,去调用LoginByGitee 方法登录,一旦登录成功后就重定向到/首页。

[!NOTE]

其实 uuid 也可以不从cookie中拿到,可以从路由中拿到:this.$route.query.state

<template>
  <div v-loading="loading" style="height: 100%;width: 100%;">
    正在加载中...
  </div>
</template>

<script>

import Cookies from "js-cookie";

export default {
  name: "loginByGitee",
  data() {
    return {
      loading: true
    }
  },
  // 立即执行的钩子函数
  mounted() {
      this.loading = true;
    // 从 cookie 中取出 uuid
      console.log("uuid", Cookies.get("user-uuid"))
    // 封装请求体
    const formBody = {
      uuid: Cookies.get("user-uuid"),
      code: this.$route.query.code
    }
    // vuex中调用actions的方法
    this.$store.dispatch("LoginByGitee", formBody).then(() => {
      this.$router.push({path: this.redirect || "/"}).catch(() => {
      });
    }).catch(() => {
      this.loading = false;
    });
  }
}
</script>

<style scoped>

</style>

记得前端放行/callback路由

在这里插入图片描述

所以需要在 actions 中定义 LoginByGitee 方法,LoginByGitee 通过提交 commit 来更新Vuex中store的状态,并且传递 body 上方封装的请求体,将操作放在一个 Promise 中,并且在成功或者失败之后,调用对应的 resolve 或 reject。

在这里插入图片描述

 // Gitee登录
LoginByGitee({commit}, body) {
  return new Promise((resolve, reject) => {
    loginByGitee(body.code, body.uuid).then(res => {
      setToken(res.token)
      commit('SET_TOKEN', res.token)
      resolve()
    }).catch(error => {
      reject(error)
    })
  })
},

里面才真正调用了loginByGitee方法,所以我们在 src/api/login.js补充方法

// Gitee登录
export function loginByGitee(code, uuid) {
  const data = {
    code,
    source: "Gitee",
    uuid
  }
  return request({
    url: '/loginByGitee',
    headers: {
      isToken: false
    },
    method: 'post',
    data: data
  })
}

在这里插入图片描述

前端调用的后端的/loginByGitee方法,所以我们需要在后端放行/loginByGitee方法

在这里插入图片描述

/**
 * 真正登录逻辑
 * @param loginByOtherSourceBody
 * @return
 */
@PostMapping("/loginByGitee")
public AjaxResult loginByGitee(@RequestBody LoginByOtherSourceBody loginByOtherSourceBody) {
    AjaxResult ajax = AjaxResult.success();
    String token = sysLoginService
            .loginByOtherSource(loginByOtherSourceBody.getCode(), loginByOtherSourceBody.getSource(), loginByOtherSourceBody.getUuid());
    ajax.put(Constants.TOKEN, token);
    return ajax;
}

在这里插入图片描述

这里需要创建一个LoginByOtherSourceBody类,并且加上getter和setter方法

public class LoginByOtherSourceBody {
    private String code;
    private String source;
    private String uuid;
}

并且sysLoginService.loginByOtherSource Service层是没有这个方法的,加上。

public String loginByOtherSource(String code, String source, String uuid) {
    //先到数据库查询这个人曾经有没有登录过,没有就注册
    // 创建授权request
    AuthRequest authRequest = new AuthGiteeRequest(AuthConfig.builder()
            .clientId("clientId")
            .clientSecret("clientSecret")
            .redirectUri("http://localhost/callback")
            .build());
    // Gitee登录成功
    AuthResponse<AuthUser> login = authRequest.login(AuthCallback.builder().state(uuid).code(code).build());
    System.out.println(login);
    //先查询数据库有没有该用户(从登录成功的login中拿到登录的信息)
    AuthUser authUser = login.getData();
    SysUser sysUser = new SysUser();
    sysUser.setUserName(authUser.getUsername());
    sysUser.setSource(authUser.getSource());
    List<SysUser> sysUsers = userService.selectUserListNoDataScope(sysUser);
    if (sysUsers.size() > 1) {
        throw new ServiceException("第三方登录异常,账号重叠");
    } else if (sysUsers.size() == 0) {
        //相当于注册
        sysUser.setNickName(authUser.getNickname());
        sysUser.setAvatar(authUser.getAvatar());
        sysUser.setEmail(authUser.getEmail());
        sysUser.setRemark(authUser.getRemark());
        userService.registerUserAndGetUserId(sysUser);
        AsyncManager.me().execute(AsyncFactory.recordLogininfor(sysUser.getUserName(), Constants.REGISTER,
                MessageUtils.message("user.register.success")));
    } else {
        sysUser = sysUsers.get(0);
    }
    AsyncManager.me().execute(AsyncFactory.recordLogininfor(sysUser.getUserName(), Constants.LOGIN_SUCCESS, MessageUtils.message("user.login.success")));
    //注册成功或者是已经存在的用户
    LoginUser loginUser =
            new LoginUser(sysUser.getUserId(), sysUser.getDeptId(), sysUser, permissionService.getMenuPermission(sysUser));
    recordLoginInfo(loginUser.getUserId());
    // 生成token
    return tokenService.createToken(loginUser);
}

同时我们在三方登录的时候,还需要维护一个source字段,表示你是通过QQ、Github、Gitee还是哪个来登录的。

所以我们在sysUser类里面加上source字段,并且生成对应的 getter 和 setter,数据库表sys_user表中也需要加 source 字段。

/** 登录源 */
private String source;

在这里插入图片描述

同时 userService.selectUserListNoDataScope Service层没有这个方法 ,所以需要写一个查询方法在ISysUserService,SysUserServiceImpl中,

在这里插入图片描述

同时Mapper.xml里面也需要加上我们的source字段,涉及到的查询sql语句我们都可以进行加啦

在这里插入图片描述

我们再来看我们三方登录的逻辑,根据用户名 username 和 源source 在数据库进行查询,如果查到1条以上,那么说明第三方登录异常,账号重叠了,如果1条也查不到,那么相当于是在注册了,这里重新写了一个注册方法userService.registerUserAndGetUserId 因为我们需要拿到注册的用户的ID

在这里插入图片描述

其中我们自己的注册用户信息返回用户ID如下

在这里插入图片描述

我们来看看效果,登录进去后头像不显示,很明显,我们的头像是一个 http请求,它自动加上了dev-api前缀

在这里插入图片描述

所以我们修改一下获取用户信息的头像逻辑:

在这里插入图片描述

[!NOTE]

至此,我们三方登录正式完成!梳理一下

  1. 首先去前端写一个 Gitee 的按钮,给按钮点击事件
  2. 点击按钮就执行PreLoginByGitee方法,这个方法会去请求后端/PreLoginByGitee接口,后端会生成 url,也就是跳转到Gitee登录的那个 url,这样点击按钮就可以跳转到 Gitee 的登录认证的窗口了
  3. 认证成功会回调到路由/callback,这个路由又跳转到LoginByGitee.vue组件,这个组件开始加载中转圈圈,然后准备code、uuid直接去请求后端LoginByGitee接口。
  4. 登录成功后我们可以拿到登录用户的所有数据,比如 Gitee 的名字、Gitee的头像等,然后我们将这些数据与数据库进行比对,通过用户名username和source源进行比对,如果没有比对成功的,那么就直接使用这个用户数据进行注册。如果有1条比对成功的,我们直接拿出来进行登录即可。

4、新建模块

  1. 对准项目根文件右键新建选择模块:

在这里插入图片描述

在这里插入图片描述

然后新建模块,注意父模块要选ruoyi,我这里是因为更改了包名,新建模块我的叫KuangStudy_Vue-core

在这里插入图片描述

[!NOTE]

需要注意的是:

  1. 我们新建的模块需要在根的 pom.xml下引用

在这里插入图片描述

  1. 并且需要在admin模块下引入,这样的话是直接继承根的依赖,就不用写版本号了

在这里插入图片描述

现在已经有了新的模块了,那么,我需要添加我自己的代码有什么注意事项吗?

  1. 因为RuoYiApplication在com.ruoyi包下,所以我们也需要手动建立com.ruoyi包,不然自己写的类无法注入Spring容器。

  2. 当我们自己新建的模块需要依赖ruoyi-system和ruoyi-framework的时候,只需要引入ruoyi-framework即可,因为ruoyi-framework里面引入了ruoyi-system。

  3. 创建的新模块没有引入spring,而spring都在common包下,所以必须引入common

在这里插入图片描述

接下来就可以写自己的方法了,我这里写了一个控制层接口:

在这里插入图片描述

这是后端的接口,所以得访问http://localhost:8080/kuang,如果通过前端访问的话,需要走前端的代理,也就是需要访问的url是http://localhost:80/dev-api/kuang

[!NOTE]

思考:我们为什么在控制层方法上添加了@Anonymous注解,不用之前的token就可以访问到方法了?

回答:@Anonymous注解放到类上,该类所有方法都可以匿名访问;放到方法上,那么就该方法可以被匿名访问。

5、前端通用方法

所谓通用方法,就是随时能调用的方法。例如我们的Vue的methods中随时可写:

this.$tab.openPage("用户管理", "/system/user");
this.$modal.msg("默认反馈");
this.$auth.hasPermi("system:user:add");
this.$auth.hasRole("admin");
this.$cache.local.set('key', 'local value')
this.$download.name(name);

这些方法为什么可以随时调用呢?是因为若依准备了插件,在main.js中引入了plugins,并且使用Vue.use(plugins)

在这里插入图片描述

Vue.use 会去调用 plugins 的 install方法,给Vue原型 prototype 加上了 $xxx,所以在任意的组件实例里面都可以取到了。

在这里插入图片描述

5.1、$tab

5.1.1、打开新标签页

可以在方法中调用this.$tab.openPage(title,url)来打开新的标签页,标签页的标题是title,要打开的标签url是url

<template>
  <div>
    <h1>打开标签页</h1>
    <el-button type="primary" @click="openNotice">通知公告1</el-button>
    <el-button type="primary" @click="openNoticeAndWindows">通知公告2</el-button>


  </div>
</template>

<script>
export default {
  name: "kuang",
  methods: {
    // 单纯打开新页面
    openNotice() {
      this.$tab.openPage("打开的通知公告", "/system/notice");
    },
    // 打开页面之后,并且做一点其他事情。
    openNoticeAndWindows() {
      this.$tab.openPage("打开的通知公告并且弹窗", "/system/notice").then(() => {
        this.$modal.msg("我是弹窗")
      })
    }
  }

}
</script>

<style>

</style>

如上代码,我们给两个按钮加了两个方法,分别是单纯打开一个新页面,新标签页的标题是 打开的通知公告,另一个是打开标签页并且提示弹框

在这里插入图片描述

在这里插入图片描述

我们看看若依封装的openPage方法

在这里插入图片描述

从上面的源码可以看出,做了两件事:

  1. 调用Vuex的tagsView/addView方法,并传参数obj。**该方法是为了把新打开的页签添加到Vuex维护的页签数组visitedViews。**当添加成功,组件TagsView的计算属性visitedViews变化,页面重新渲染,页签自然多一个。
  2. 第二件事就是将路由做简单的跳转。

5.1.2、修改标签页

  • this.$tab.updatePage可以修改标签
<template>
  <div>
    <h1>打开标签页</h1>
    <el-button type="primary" @click="openNotice">通知公告1</el-button>
    <el-button type="primary" @click="openNoticeAndWindows">通知公告2</el-button>

    <el-divider></el-divider>
    <h1>修改标签页</h1>
    <el-button type="info" @click="updateSelf">修改自己标签</el-button>
    <el-button type="info" @click="updateOther">修改其他标签</el-button>


  </div>
</template>

<script>
export default {
  name: "kuang",
  methods: {
    // 单纯打开新页面
    openNotice() {
      this.$tab.openPage("打开的通知公告", "/system/notice");
    },
    // 打开页面之后,并且做一点其他事情。
    openNoticeAndWindows() {
      this.$tab.openPage("打开的通知公告并且弹窗", "/system/notice").then(() => {
        this.$modal.msg("我是弹窗")
      })
    },

    updateSelf() {
      //修改自己
      const obj = Object.assign({}, this.$route, {title: "自定义标题"})
      this.$tab.updatePage(obj);
    },
    updateOther() {
      // 修改别的标签页
      const obj = Object.assign({}, {path: "/system/notice"}, {title: "自定义标题222"})
      this.$tab.updatePage(obj).then(() => {
        this.$modal.msg("修改页签完毕")
      })
    },


  }

}
</script>

<style>

</style>

在这里插入图片描述

5.1.3、关闭标签

  • this.$tab.closeOpenPage :关闭当前页签并且打开新页面
  • this.$tab.closePage : 关闭当前页签并回到首页
//关闭当前并且打开新页面
closeAndOpen() {
  const obj = {path: "/system/user"};
  this.$tab.closeOpenPage(obj);
},
//关闭当前并回到首页
closeAndGoIndex() {
  this.$tab.closePage();
},
//关闭指定的页面
closeSpecifyPage() {
  const obj = {path: "/system/user"};
  this.$tab.closePage(obj);
}

5.1.4、刷新标签

  • this.$tab.refreshPage() 刷新标签
//刷新当前页签
refreshPage() {
  this.$tab.refreshPage();
}

刷新页签相当于页面关闭,重新打开。

5.1.5、关闭所有页签

  • this.$tab.closeAllPage() : 关闭所有页签
//关闭所有页签
closeAll() {
  this.$tab.closeAllPage();
},

5.1.6、关闭左侧页签

this.$tab.closeLeftPage();

const obj = { path: "/system/user", name: "User" };
this.$tab.closeLeftPage(obj);

this.$tab.closeLeftPage(obj).then(() => {
  // 执行结束的逻辑
})

[!NOTE]

抛砖引玉,更多请查看官方文档,文档的东西更详细!

5.2、$modal

$modal对象用于做消息提示、通知提示、对话框提醒、二次确认、遮罩等。

<template>
  <div>
    <h1>打开标签页</h1>
    <el-button type="primary" @click="openNotice">通知公告1</el-button>
    <el-button type="primary" @click="openNoticeAndWindows">通知公告2</el-button>

    <el-divider></el-divider>

    <h1>修改标签页</h1>
    <el-button type="info" @click="updateSelf">修改自己标签</el-button>
    <el-button type="info" @click="updateOther">修改其他标签</el-button>

    <el-divider></el-divider>
    <h1>消息提示弹窗</h1>
    <el-button type="text" @click="msg">反馈信息</el-button>
    <el-button type="text" @click="alert">提示信息</el-button>
    <el-button type="text" @click="notify">通知信息</el-button>

  </div>
</template>

<script>
export default {
  name: "kuang",
  methods: {
    // 单纯打开新页面
    openNotice() {
      this.$tab.openPage("打开的通知公告", "/system/notice");
    },
    // 打开页面之后,并且做一点其他事情。
    openNoticeAndWindows() {
      this.$tab.openPage("打开的通知公告并且弹窗", "/system/notice").then(() => {
        this.$modal.msg("我是弹窗")
      })
    },

    updateSelf() {
      //修改自己
      const obj = Object.assign({}, this.$route, {title: "自定义标题"})
      this.$tab.updatePage(obj);
    },
    updateOther() {
      // 修改别的标签页
      const obj = Object.assign({}, {path: "/system/notice"}, {title: "自定义标题222"})
      this.$tab.updatePage(obj).then(() => {
        this.$modal.msg("修改页签完毕")
      })
    },

    //  提供成功、警告和错误等反馈信息
    msg() {
      this.$modal.msg("默认反馈");
      // this.$modal.msgError("错误反馈");
      // this.$modal.msgSuccess("成功反馈");
      // this.$modal.msgWarning("警告反馈");
    },
    // 提供成功、警告和错误等提示信息
    alert() {
      this.$modal.alert("默认提示");
      // this.$modal.alertError("错误提示");
      // this.$modal.alertSuccess("成功提示");
      // this.$modal.alertWarning("警告提示");
    },
    // 提供成功、警告和错误等通知信息
    notify() {
      this.$modal.notify("默认通知");
      // this.$modal.notifyError("错误通知");
      // this.$modal.notifySuccess("成功通知");
      // this.$modal.notifyWarning("警告通知");
    },
    


  }

}
</script>

<style>

</style>

在这里插入图片描述

增加一个确认窗口信息

confirm() {
  this.$modal.confirm('你确定要确定吗').then(function () {
  }).then(() => {
    this.$modal.notify("点了确定")
  }).catch(() => {
    this.$modal.notify("点了取消")
  });
},

在这里插入图片描述

[!NOTE]

玩个好玩的,开启遮罩层和关闭遮罩层

//遮罩层
startLoading() {
  // 打开遮罩层
  this.$modal.loading("正在Loading,请稍后...");
  wait5Second().then(res => {
    console.log(res)
    this.$modal.closeLoading();
  })
},
//如若已经loading则只能程序自己调用
stopLoading() {
  this.$modal.closeLoading();
}

前端点击按钮开启遮罩层,调用前端的wait5Second请求,等待5s后关闭遮罩层。

  1. src/api/kuang/kuang.js下新增请求
import request from "../../utils/request";


// 等5s
export function wait5Second() {
  return request({
    url: '/wait5Second',
    headers: {
      isToken: false
    },
    method: 'get',
  })
}

在这里插入图片描述

  1. 在后端新增接口wait5Second

在这里插入图片描述

5.3、$auth

$auth对象用于验证用户是否拥有某(些)权限或角色,它定义在plugins/auth.js文件中,它有如下方法

  • 验证用户权限
// 验证用户是否具备某权限
this.$auth.hasPermi("system:user:add");
// 验证用户是否含有指定权限,只需包含其中一个
this.$auth.hasPermiOr(["system:user:add", "system:user:update"]);
// 验证用户是否含有指定权限,必须全部拥有
this.$auth.hasPermiAnd(["system:user:add", "system:user:update"]);
  • 验证用户角色
// 验证用户是否具备某角色
this.$auth.hasRole("admin");
// 验证用户是否含有指定角色,只需包含其中一个
this.$auth.hasRoleOr(["admin", "common"]);
// 验证用户是否含有指定角色,必须全部拥有
this.$auth.hasRoleAnd(["admin", "common"]);

我们来使用一下:增加一个按钮,点击按钮判断用户是否具有system:user:list权限

<template>
  <div>
      
    <h1>验证权限</h1>
    <el-button type="text" @click="validMyPermission">验证权限</el-button>

  </div>
</template>

<script>

export default {
  name: "kuang",
  methods: {

    //验证权限
    validMyPermission() {
      console.log(this.$auth.hasPermi("system:user:list"));
    }


  }

}
</script>

<style>

</style>

在这里插入图片描述

我们看一下hasPermi的源码,它调用了authPermission方法,可以发现,它是从Vuex的 store 中获取权限,而Vuex的store中的 permission 又是后端传过来的

在这里插入图片描述

5.4、$download

$download对象用于文件下载,它定义在plugins/download.js文件中。

对于图片,用户上传头像,若依的处理是将用户上传的头像存放在D:/ruoyi/upload目录下,这个目录可以在yml中配置。

[!NOTE]

用户上传头像,我们可以将头像的URI存在数据库中,这样当使用用户头像或者下载用户头像的时候,我们可以通过数据库中的URI进行下载。

5.4.1、根据名称下载upload路径下的文件

[!NOTE]

后期更新

6、Https实现

因为http自身是明码传输,我们通过网卡抓包可以轻而易举得到token。甚至包括登录的账号密码,所以要上https!

[!NOTE]

  • 参考视频:腾讯云实现HTTPS

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2118003.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

有了室内外一体化人行导航,你还怕迷路吗?

在快节奏的现代生活中&#xff0c;无论是穿梭于繁华的都市丛林&#xff0c;还是漫步于错综复杂的购物中心&#xff0c;迷路似乎成了不少人的“小确丧”。然而&#xff0c;随着科技的飞速发展&#xff0c;一项革命性的创新——室内外一体化人行导航系统&#xff0c;正悄然改变着…

CAN总线(一)

CAN总线&#xff08;Controller Area Network Bus&#xff09;&#xff0c;从名字上可以知道&#xff0c;CAN总线构建的是一种局域网网路&#xff0c;每个挂载在CAN总线上的设备都可以利用这个局域网去发送自己的消息&#xff0c;也可以接收局域网的各种消息&#xff0c;每个设…

解锁高效驱动密码:SiLM8260A系列SiLM8260ABCS-DG 集成米勒钳位的双通道隔离驱动芯片

附上SiLM8260A同系列型号参考&#xff1a; SiLM8260ADCS-DG 12.5V/11.5V SiLM8260ABCS-DG 8.5V/7.5V SiLM8260AACS-DG 5.5V/5V SiLM8260AGCS-DG 3.5V/3V SiLM8260ABCS-DG是一款集成了米勒钳位功能的双通道隔离驱动芯片&#xff0c;它精准地满足了上述严苛条件。具备…

Excel排序错误原因之一

# Excel日常表格中的文字排序乱了&#xff0c;在系统语言更改成英语之后&#xff0c;不再按照首字母的顺序排列&#xff0c;且强制设置序列顺序也无济于事。 Excel表格内的排序尝试 表格基础选项就有“排序”&#xff0c;在其自定义选项中的“选项”设置处设置“拼音排序”&am…

2024年实体行业都在用的AI自动直播,有哪些可以发展的机会?

抖捧AI自动直播系统&#xff0c;是一款结合Ai和短视频营销的自动化直播辅助工具&#xff0c;针对企业和实体店有多重的应用场景&#xff0c;通过预设的直播脚本和智能回复逻辑&#xff0c;实现不需要真人参与&#xff0c;就可以实现全天候24小时直播&#xff0c;当用户进入直播…

【Java基础】ThreadLocal<LoginUser>:存储登录用户信息

&#x1f49d;&#x1f49d;&#x1f49d;欢迎来到我的博客&#xff0c;很高兴能够在这里和您见面&#xff01;希望您在这里可以感受到一份轻松愉快的氛围&#xff0c;不仅可以获得有趣的内容和知识&#xff0c;也可以畅所欲言、分享您的想法和见解。 推荐:kwan 的首页,持续学…

【一篇文章搞定】LVGL显示中文+解决keil下LVGL无法正常显示中文及error:#8:missing closing quote问题

LVGL显示中文 进入lvgl中文文档&#xff1a;显示中文 — LVGL 文档 (100ask.net) 下载字体zip文件&#xff0c;解压得到一个.otf文件 进入LVGL的字体转换网站 Font Converter — LVGL 1.点击Browse指定.otf文件即可 2.range不用管 3.为了节省内存在Symbols中输入自己要使用的…

【计算机网络】概述篇

目录 导学 计算机网络的发展简史 互联网的发展历史 中国互联网的发展历史 计算机网络的层次结构 层次结构设计的基本原则 OSI七层模型 TCP/IP四层模型 现代互联网的网络拓扑 计算机网络的性能指标 速率 时延 发送时延 ​传播时延 ​排队时延 处理时延 总时延 …

K8S - Emptydir - 取代ELK 使用fluentd 构建logging saidcar

由于k8s 的无状态service 通常部署在多个POD中&#xff0c; 实现多实例面向高并发。 但是k8s 本身并没有提供集中查询多个pod的日志的功能 其中1个常见方案就是ELK. 本文的方案是 利用fluentd sidecar 和 emptydir 把多个pod的日志导向到bigquery的table中。 Emptydir 的简介 …

STM32F407ZGT6单片机HAL库——DAC输出

一、输出直流电压 1.cubemax的配置&#xff08;通道1&#xff09; 2.直流电压大小计算 3.主函数加入初始化的程序 float DAC_voltage1.5;HAL_DAC_SetValue(&hdac, DAC_CHANNEL_1, DAC_ALIGN_12B_R, DAC_voltage*4095/3.3);//HAL_DAC_Start(&hdac,DAC_CHANNEL_1); 二、…

深度解析价值流:推动业务架构创新与效益提升的核心指南

数字化转型下的价值流管理与架构 在当今的数字化转型背景下&#xff0c;如何有效创造并交付价值&#xff0c;成为企业竞争力提升的关键课题。作为企业架构的重要组成部分&#xff0c;价值流的概念为业务决策者和技术人员提供了全面的工具&#xff0c;帮助优化业务能力&#xf…

java,php,go,nodejs,Python开发web项目优缺点对比

Java 优点:java 是一门广泛应用于企业级开发的语言,丰富且庞大的开发框架和库。有较高的性能和可伸缩性。生态系统庞大且成熟,拥有大量的开源框架和工具,可以加速开发过程。 内置对多线程的支持,适合处理高并发的 Web 项目。 缺点:相比其他语言,Java 的语法相对冗长繁琐…

Linux-Shell编程【看这一篇就够了!!!】

目录 前言 什么是Shell编程 Shell脚本的执行方式 脚本格式要求 运行一个Shell 一个Shell例子 Shell的变量 shell变量的定义 设置环境变量 简单示范 位置参数变量 预定义变量 基本语法 运算符 应用案例 条件判断 单流程判断 常用判断条件 应用案例 多流程判…

Type-C 接口 取电 PD快充协议取电电压5V、9V、15V、20V

随着Type-C接口的普及&#xff0c;快充技术融入进了各种电子设备中&#xff0c;然而快充技术里快充协议是必不可少的&#xff0c;目前市面上已经出现多种快充协议&#xff0c;最常见的便是Type-C PD协议&#xff0c;下面来以起了解以下PD协议。 PD协议的工作原理主要是基于电压…

【OpenCV】灰度化和二值化处理图像

文章目录 1. 图像灰度化处理对比2. 代码示例3. 二值化处理 1. 图像灰度化处理对比 2. 代码示例 #include <opencv2/opencv.hpp> using namespace cv;int main() {Mat currentImage imread("path_to_image.jpg"); // 读取彩色图像Mat grayImage;// 将彩色图像…

负债不再是障碍?银行信贷“白名单“揭秘

谈及银行信贷产品&#xff0c;常闻有言称存在无需考量负债与查询记录之奇品&#xff0c;此等说法十有八九为中介诱人上钩之辞。轻信之下&#xff0c;恐将步入连环陷阱。除非个人资质出类拔萃&#xff0c;如就职于国央企或事业单位&#xff0c;工龄逾年&#xff0c;五险一金完备…

计算机毕业设计选题推荐-土地承包管理系统-Java/Python项目实战(亮点:数据可视化分析、账号锁定、智能推荐)

✨作者主页&#xff1a;IT研究室✨ 个人简介&#xff1a;曾从事计算机专业培训教学&#xff0c;擅长Java、Python、微信小程序、Golang、安卓Android等项目实战。接项目定制开发、代码讲解、答辩教学、文档编写、降重等。 ☑文末获取源码☑ 精彩专栏推荐⬇⬇⬇ Java项目 Python…

Address localhost:1099 is already in use:tomcat频繁重启端口占用问题

错误提示 Unable to open debugger port (127.0.0.1:58198): java.net.SocketException "Socket closed" Address localhost:1099 is already in use 端口被占用 报错原因 由于短时间内频繁运行tomcat服务器。 为了避免出现这一错误。可以点击刷新uodate resourc…

寻找客户资源的软件

如果你正在寻找能够帮助你高效寻找客户资源的软件&#xff0c;以下几款工具可以为你提供支持&#xff1a; 1. 微拓客APP 微拓客APP是一款专为企业和销售人员设计的智能拓客工具。它通过最新的全国工商数据&#xff0c;帮助你精准搜索目标客户&#xff0c;特别适合需要快速找到…

软件开发人员从0到1实现物联网项目:项目架构的思考

文章目录 前言单体应用足矣摒弃传统的微信对接后期的维护投入上真正的“云”&#xff1a;云托管0服务器免运维免费的CDN和DDoS防护 技术架构小结 前言 因为种种原因&#xff0c;《软件开发人员从0到1实现物联网项目》这个项目的进度停滞了将近一个月。 鉴于该项目的前期开发和…