• Ruoyi-vue项目讲解


    @[TOC]若依前后端调用接口解读
    若依github官方下载地址
    若依gitee官方下载地址

    1.验证码时候的前端调用接口

    调用前端登录界面的时候,调用的是login.vue这个文件中的created函数
    调用created函数
    这里我们查看getCode函数方法
    getCode函数方法
    可以看到,这里先调用了一个getCodeImg函数,然后接收到后端返回的值之后,再进行相应的处理,显示图片以及保存redis中的key,进入getCodeImg函数进行查看
    getCodeImg函数
    到api/login.js的文件下面查看getCodeImg函数
    getCodeImg函数
    可以看出,这里再次调用了一个request函数,查看request内容

    import request from '@/utils/request'
    
    • 1

    查看request.js的文件
    request.js中调用内容
    这里的process.env.VUE_APP_BASE_API我们通过.env.development查看定义
    在这里插入图片描述
    可以看出,这里每次前端发出api的时候都会添加固定的前缀/dev-api前缀。

    反向代理

    除此之外,这里的url还调用了一次反向代理,在vue.config.js之中配置
    反向代理配置
    可以看出来这里跨域发送到后端的时候将process.env.VUE_APP_BASE_API转换为空字符串,并且将发送端口转为8080端口,通过浏览器我们也可以观察到前端发送的url请求内容前端的url调用调用的接口内容

    2.验证码后端调用接口

    发送完成之后,跨域转化完成之后就是localhost:8080/captchaImage接口,找到后端对应的调用部分,首先从数据库中查询是否开启验证码
    查询是否开启验证码
    接下来保存验证码在redis中保存的信息并且生成验证码,生成的字符串类似于4+1=?@5这种形式
    生成验证码的部分
    这里将4+1=?@5这块字符串拆分开来,将4+1生成图片,5保存在redis中为答案
    最终redis的保存结果
    将后端的结果返回到前端,前端保存图片内容以及redis的值

    前端保存图片以及redis的值
    这里我们可以看到this.codeUrl对应图片显示的部分

    vue前端显示图片
    对应的验证码部分图片如图所示
    验证码部分图片
    前端接收到后端返回的图片之后,成功显示图片

    3.前端发起登录请求的调用

    点击登录之后,从浏览器中查看发起的请求
    浏览器中查看发起的请求
    从上面可以看出,login、getInfo和getRouters三个请求在点击登录按钮之后同时被发送出去,我们首先看登录按钮调用的方法
    登录按钮调用的方法
    从这里可以看出,登录按钮调用handleLogin方法,查看方法的实现
    登录方法的调用
    这里首先查看是否需要记住密码,需要的时候保存在cookie之中,接下来会发生跳转

    this.$store.dispatch("Login", this.loginForm).then(() => {...})
    
    • 1

    这里跳转到src/store/modules/user.js中的Login函数之中
    跳转方法
    这里又调用了login函数,查看login函数的部分
    login函数
    发出一个post请求,将username、password、code、uuid发送出去

    4.后端接收请求内容

    后端接收login请求
    后端接收请求为四个变量,这里后端将四个变量打包成一个结构体,然后进入到loginService.login函数中进行查看
    验证码判定部分
    首先进行验证码的验证,进入validateCaptcha函数中
    validateCaptcha方法
    这里通过从redis中取出验证码数据来与自己输入的数据进行判断,如果验证码过期或者不相同的情况下抛出各种异常,而如果验证码通过的时候继续通过用户名和密码进行验证
    调用用户名和密码进行验证
    这里的authenticationManager.authenticate(new UsernamePasswordAuthenticationToken(username,password));
    就调用了UserDetailsService的实现类UserDetailsServiceImpl中的loadUserByUsername这个函数从数据库中取出来用户名和密码并且封装到LoginUser类之中
    loadUserByUsername函数调用
    最终如果验证成功的情况下,从authentication中取出LoginUser类,然后记录用户的登录信息并且生成token内容
    在这里插入图片描述

    5.前端发送/getInfo请求的调用

    这两个调用确实比较难找,在permission.js文件之中调用beforeEach函数的时候出现
    调用beforeEach函数
    这里的beforeEach函数在跳转页面的时候进行调用
    我们首先查看GetInfo函数的调用,GetInfo在store/modules/user.js之中

    GetInfo函数调用
    可以看出这里调用了getInfo方法,查找此方法
    getInfo方法调用
    这里的getInfo方法调用通过get发送了一个url的请求,该请求获取当前用户的信息并使用全局存储一下,接下来我们查看后端对于/getInfo请求的响应
    后端getInfo接口的调用
    这里查询用户以及权限信息返回给前端部分,然后我们继续查看前端接收到返回值之后的调用
    GetInfo之后调用
    可以看到GetInfo拿到角色权限等相关信息之后,才能够查找到可访问的路由表

    6.前端发送/getRouter请求的调用

    查看GenerateRoutes的调用
    GenerateRoutes
    这里查看调用的GenerateRoutes发出的请求,在src/store/modules/permission.js之中(实在找不到就采用全局搜索的方式)。
    GenerateRoutes函数的调用
    查看getRouters方法的调用

    import { getRouters } from '@/api/menu'
    
    • 1

    调用getRouters请求
    可以看出这里通过get发送/getRouters的url请求,回到后端查看getRouters方法的调用

    7.后端接收getRouters请求

    getRouters路由
    这里采用的是一个不断延伸的树,进入到buildMenus函数之中,首先查看selectMenuTreeByUserId函数
    selectMenuTreeByUserId函数图片
    查看getChildPerms函数的调用,这一步是建树的关键
    根据数据表的结构进行建立树
    可以看出根节点为0,因此传入的parentId为0,进入到getChildPerms函数之中
    getChildPerms函数的调用
    这里可以看出,如果遍历节点的根节点为当前节点的时候,就继续深入遍历,然后将遍历的节点放入当前节点的list之中,这样就建立了一个不断深入的递归树。
    getChildPerms只是进行了第一层,将指定的父节点与孩子节点建立联系,接下来继续深入需要递归调用,进入recursionFn函数的调用环节

    private void recursionFn(List<SysMenu> list, SysMenu t)
    {
        // 当前节点的子节点放入list之中
        List<SysMenu> childList = getChildList(list, t);
        //找一级菜单的子菜单
        t.setChildren(childList);
        for (SysMenu tChild : childList)
        //继续遍历孩子节点,二级菜单里面找三级菜单
        {
            if (hasChild(list, tChild))
            {
                recursionFn(list, tChild);
            }
        }
    }
    
    /**
     * 得到子节点列表
     *
     *
     * 从23条数据中把系统的子菜单找到
     */
    private List<SysMenu> getChildList(List<SysMenu> list, SysMenu t)
    {
        List<SysMenu> tlist = new ArrayList<SysMenu>();
        Iterator<SysMenu> it = list.iterator();
        while (it.hasNext())
        {
            SysMenu n = (SysMenu) it.next();
            if (n.getParentId().longValue() == t.getMenuId().longValue())
            //找到当前节点的子节点并放入tlist中,n.getParentId()为当前的孩子节点,
            //t.getMenuId()为遍历的23个节点Id
            {
                tlist.add(n);
            }
        }
        return tlist;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38

    接下来,再使用getRouters方法将buildMenus方法打包

    @Override
    public List<RouterVo> buildMenus(List<SysMenu> menus)
        ......
    
    • 1
    • 2
    • 3

    将List< SysMenu >转换为List< RouterVo>的类型,然后返回给前端,前端接收到之后进行处理
    getRouters方法调用

    8.登录之后的跳转界面发送getList请求方法

    接下来我们需要看登录成功之后页面如何跳转的,回到login.vue之中
    Login.vue登录之后跳转
    可以看出登录成功之后往根路径去跳转

    this.$router.push({ path: this.redirect || "/" }).catch(()=>{});
    
    • 1

    此时我们需要知道根路径"/"指的是哪个界面,打开src/router/index.js之中去查看
    根路径对应的索引
    这里我们找到了跟路径对应的界面,进入到’'之后我们会自动地往index路径去跳转,打开@/views/index查看页面,发现这里就是首页的各种内容。
    并且这个首页还调用了Layout组件的布局
    这里我们查看主页侧边栏的调用
    在这里插入图片描述
    从这里可以看到调用侧边栏sidebar-container,具体的Sidebar调用查看import内容

    import {AppMain, Navbar, Settings, Sidebar, TagsView } from './components'
    
    • 1

    具体的内容在src/layout/components/Sidebar之中有index.vue,这里sidebar-item之中有v-for循环

     <sidebar-item
         v-for="(route, index) in sidebarRouters"
         :key="route.path  + index"
         :item="route"
         :base-path="route.path"
     />
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    这里遍历的是我们的router对象,可以把后台的数据迭代出来。对应路径在src/views/system/,这里跳转的方向在数据库中有所记录
    数据库中记录跳转方向
    比如第一个用户管理对应的路径在views之中的system/user/index.vue
    点击侧边栏进入用户管理,发现页面分为两块
    页面分界
    一块是左边的树状侧边栏,另外一块是中间的查询结果显示,中间的数据属于list,而侧边栏的数据属于tree,现在需要读取这两块的数据。在views/system/user/index中去找created方法,发现正好出现了这两部分的内容
    getList和getTreeselect方法调用
    进入到getList()函数中查询用户列表

    getList(){
      this.loading = true;
      //让前端页面有一个加速转圈圈的效果
      listUser(this.addDateRange(this.queryParams, this.dateRange)).then(response => {
          //查看listUser代码的入口
          //取到这两个变量之后view会自动加载相应的效果???
          this.userList = response.rows;
          this.total = response.total;
          //获取到了list和total数据
          this.loading = false;
       }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    查看listUser函数的调用

    // 查询用户列表
    // 这里找到了listUser调用的请求方法
    export function listUser(query) {
      return request({
        url: '/system/user/list',
        method: 'get',
        params: query
      })
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    9.后端处理/system/user/list方法

    这里由于/system/user/list后端采用的是分层url的处理方式,因此我们先搜索list
    /system/user/list接口的调用
    这里先查看@ss.hasPermi(‘system:dept:list’)")的权限调用方法
    权限调用方法
    这里判断用户是否具有某种权限
    判断是否具有某种权限
    这里取出LoginUser之后采用的是切分的方式判断是否具有权限,查看hasPermissions函数的调用

    private boolean hasPermissions(Set<String> permissions, String permission)
    {
        return permissions.contains(ALL_PERMISSION) || permissions.contains(StringUtils.trim(permission));
    }
    
    • 1
    • 2
    • 3
    • 4

    这里permissions = * : * : * ,而permission = “system:user:list”,判断权限的时候如果为全权限 或者permissions字符串中包含permission,则可以获取到权限。
    查看startPage函数调用,

    protected void startPage()
    {
        PageDomain pageDomain = TableSupport.buildPageRequest();
        //pageDomain一个新对象,专门用来获取分页信息的
        Integer pageNum = pageDomain.getPageNum();
        Integer pageSize = pageDomain.getPageSize();
        if (StringUtils.isNotNull(pageNum) && StringUtils.isNotNull(pageSize))
        {
            String orderBy = SqlUtil.escapeOrderBySql(pageDomain.getOrderBy());
            Boolean reasonable = pageDomain.getReasonable();
            PageHelper.startPage(pageNum, pageSize, orderBy).setReasonable(reasonable);
            //PageHelper可以单独设置页数,把分页参数设置到PageHelper提供的对象中即可
            //reasonable对参数进行逻辑处理,保证参数的正确性,比如pageNum=0或-1,将自动将pageNum设置为1
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    这里定义了PageDomain等参数都是为了从前端发回的请求取出pageNum和pageSize这两个参数的,orderBy用来定义排序的顺序,reasonable对参数进行逻辑处理,保证参数的正确性,最后调用PageHelper中startPage方法封装好参数,调用PageHelper中的startPage会保证下一条sql语句进行分页处理,并且将结果封装成Page类型对象。
    然后调用查询语句查询,并且将结果返回给前端。
    结果返回
    前端取回到数据之后,就会自动加载界面
    前端自动加载界面

    10.登录之后点击用户管理发出的第二次请求

    用户管理显示
    登录之后点击用户管理之后,在src/views/system/user/index.vue之中查看created函数的调用,在getList之后会发出第二次请求getTreeselect获取侧边栏的显示图
    获取侧边栏的显示图
    侧边栏的显示图
    查看treeselect函数的调用
    treeselect函数的调用
    找到treeselect函数发出的请求,这里为部门下拉树结构

    export function treeselect() {
      return request({
        url: '/system/dept/treeselect',
        method: 'get'
      })
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    找到获取部门下拉树的后端调用
    获取部门下拉树列表
    这里selectDeptList选择出部门信息的表格,deptService.buildDeptTreeSelect(depts)将depts建成一层一层的树,查看其中的代码

    @Override
    public List<TreeSelect> buildDeptTreeSelect(List<SysDept> depts)
    {
        List<SysDept> deptTrees = buildDeptTree(depts);
        //将10条记录组装成一个树状图
        return deptTrees.stream().map(TreeSelect::new).collect(Collectors.toList());
        //将deptTrees全部封装成TreeSelect,然后封装成集合,从SysDept封装成TreeSelect
        //需要转换的原因:SysDept中的字段过多,而TreeSelect中的字段较少,前端不需要SysDept
        //一样这么多的字段,本质上就是对字段进行复制
        //这里通过构造函数进行映射
        /***
         * public TreeSelect(SysDept dept)
         * {
         *    this.id = dept.getDeptId();
         *    this.label = dept.getDeptName();
         *    this.children = dept.getChildren().stream().map(TreeSelect::new).collect(Collectors.toList());
         * }
         */
     }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    这里buildDeptTree针对depts建立多层树,而由于SysDept类中包含了过多的内容,因此需要将SysDept转换成TreeSelect类,初始化类为

    public class SysDept extends BaseEntity
    {
        private static final long serialVersionUID = 1L;
    
        /** 部门ID */
        private Long deptId;
    
        /** 父部门ID */
        private Long parentId;
    
        /** 祖级列表 */
        private String ancestors;
    
        /** 部门名称 */
        private String deptName;
    
        /** 显示顺序 */
        private String orderNum;
    
        /** 负责人 */
        private String leader;
    
        /** 联系电话 */
        private String phone;
    
        /** 邮箱 */
        private String email;
    
        /** 部门状态:0正常,1停用 */
        private String status;
    
        /** 删除标志(0代表存在 2代表删除) */
        private String delFlag;
    
        /** 父部门名称 */
        private String parentName;
        ... 
     }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38

    接着查看TreeSelect类调用

    public class TreeSelect implements Serializable
    {
        private static final long serialVersionUID = 1L;
    
        /** 节点ID */
        private Long id;
    
        /** 节点名称 */
        private String label;
        ......
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    也就是说前端不需要这么多的信息,所以选择TreeSelect这么多的内容即可
    这里SysDept类的初始化方法为

    public TreeSelect(SysDept dept)
    {
        this.id = dept.getDeptId();
        this.label = dept.getDeptName();
        this.children = dept.getChildren().stream().map(TreeSelect::new).collect(Collectors.toList());
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    点击研发部门之后的内容

    点击研发部门之后的内容
    点击研发部门之后基于研发部门进行进一步的查询

    在src/views/system/user/index.vue之中查看el-tree标签内容
    el-tree标签内容
    查看handleNodeClick的方法
    节点单击调用方法
    这里查看getList方法调用

    getList() {
      this.loading = true;
      //让它有一个加载的效果,转圈圈
      listUser(this.addDateRange(this.queryParams, this.dateRange)).then(response => {
          //查看listUser代码的入口
          //取到这两个变量之后view会自动加载相应的效果???
          this.userList = response.rows;
          this.total = response.total;
          //获取到了list和total数据
          this.loading = false;
        }
      );
    },
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    this.loading = true让它加载有一个转圈圈的功能,而listUser调用相应的请求

    export function listUser(query) {
      return request({
        url: '/system/user/list',
        method: 'get',
        params: query
      })
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    这里调用/system/user/list与之前的区别在于这里的user加入了其他的参数,因此查找的时候是条件查找。

    文件的导入和导出

    文件的导入和导出界面
    这里有文件的导入和导出两个按钮,导入是将文件的数据写入到数据库中,而导出则是将数据库的数据写出到文件中

    前端点击新增方法

    点击新增方法

    提交按钮的方法

    提交按钮的方法

    点这个圈圈能把所有的断点都去掉。

    异步任务管理器

    异步任务管理器中的调用在ruoyi-framework中的src/main/java/com.ruoyi.framework.web.service.SysLoginService.java中的

    AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_FAIL, MessageUtils.message("user.jcaptcha.expire")));
    
    • 1

    1.调用异步管理器

    AsyncManager.me()
    
    • 1

    获取一个AsyncManager对象,AsyncManager是一个单例模式

    2.执行execute方法,执行任务,传入的是一个Task对象,实现了Runnable接口,表示它是一个任务,由线程Thread去执行。

    封装了登录信息,执行了添加操作,这里不会执行,而是交给线程对象来执行。
    这里首先调用了异步线程池

    /**
    * 异步操作任务调度线程池
    */
    private ScheduledExecutorService executor = SpringUtils.getBean("scheduledExecutorService");
    ......
    /**
     * 执行任务
     * 
     * @param task 任务
     */
    public void execute(TimerTask task)
    {
        executor.schedule(task, OPERATE_DELAY_TIME, TimeUnit.MILLISECONDS);
    }
    /***
    * 这里是创建线程池的操作,corePoolSize为核心池的大小
    * 异步任务管理器内部定义了一个线程池,然后根据业务创建
    * 添加日志的任务,交给线程池来处理,这样做到来日志和业务
    * 的抽象、解耦合,日志全部统一处理。
    * 同步:登录成功之后必须记录日志,如果没有记录下来登录成功
    * 这部分就需要等
    ***/
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
  • 相关阅读:
    从GPT入门,到R语言基础与作图、回归模型分析、混合效应模型、多元统计分析及结构方程模型、Meta分析、随机森林模型及贝叶斯回归分析综合应用等专题及实战案例
    VALSE2022天津线下参会个人总结8月22日-1
    springboot2.x版本集成redis说明(lettuce、redisson)
    【单片机基础】按键状态机实现短按、长按、双击、三击和N击
    STM32存储左右互搏 QSPI总线FATS文件读写FLASH W25QXX
    调度的艺术:Eureka在分布式任务调度中的妙用
    admin后台管理
    golang正则regexp包使用-04-使用正则替换(ReplaceAll(),ReplaceAllLiteral(),ReplaceAllFunc())
    【剪映专业版】11音频的全流程剪辑操作
    web前端期末大作业实例 (1500套) 集合
  • 原文地址:https://blog.csdn.net/znevegiveup1/article/details/132780614