若依— — 快速入门【源码分析】

news2024/11/17 15:50:45

若依— — 快速入门

1 什么是若依

官网地址:http://www.ruoyi.vip/

  • 若依是一款优秀的开源项目,涉及到企业开发中大部分的管理系统,我们依此为模板进行二次开发,可以快速开发出符合大部分公司中的后台管理系统。

2 使用若依

使用开源项目流程:

  1. 下载并运行
  2. 看懂其中的业务流程
  3. 进行二次开发
  • 下载
    在这里插入图片描述
    在这里插入图片描述

  • 修改配置文件,配置数据源和Redis

  • 下载前端依赖,然后运行

同时别忘记启动本机redis

  • Redis官网地址:https://redis.io/
  • 如果没有redis的可以去官网下载一个

3 若依源码分析

3.1 登录流程分析

基本思路:
后端生成一个表达式,然后根据@分割,将表达式转为图片传给前端,将表达式结果存入Redis,最后用户通过表单提交,判断结果是否正确,以此来达到区分人机的效果。

详细流程:
①前端通过vue反向代理请求到后端接口http://localhost/dev-api/captchaImage【避免跨域问题】
②后端判断是否开启验证码开关captchaEnabled,同时根据captchaType验证码类型,生成对应的表达式,同时生成一个UUID作为存入验证码的key

在这里插入图片描述
根据@符号分割,然后将8+1通过IO操作生成图片传给前端,将结果9存入redis。redis的key为:用户每次请求时后端生成的UUID。

前缀+UUID作为redis的key:
在这里插入图片描述

将生成的UUID和验证码图片,传给前端。

 ajax.put("uuid", uuid);
 ajax.put("img", Base64.encode(os.toByteArray()));

③前端展示图片,同时保存后端返回的UUID
④用户输入验证码后,前端携带UUID+用户输入的验证码结果请求后端/login进行登录

在这里插入图片描述

  • 根据前端传入的UUID取对应redis的value,然后将value与前端传入的code做比较,如果正确,则进行用户名密码校验,校验成功则生成token,并存入redis
  // 验证码校验
  validateCaptcha(username, code, uuid);
  // 登录前置校验
  loginPreCheck(username, password);
  // 用户验证
  ...
  //最后生成token返回给前端
  return tokenService.createToken(loginUser);

在这里插入图片描述
⑤用户后面直接携带token访问后端不用重复登录

前端将后端返回的token存入浏览器的cookie中,并且让每次请求都携带上token

  • 浏览器的cookie中存入token:
    在这里插入图片描述
  • 后端每次请求携带上token:
    在这里插入图片描述

例如:

①后端生成一个表达式,1+1=2
1+1=?@2
②1+1=? 转成图片,传到前端进行展示
③表达式结果2存入redis

前端获取验证码的请求URL:http://localhost/dev-api/captchaImage

  • 若依的后端端口是8080,前端是80,为什么上面的URl直接通过80端口就能访问到后台呢?
  • 答案就是:反向代理

在这里插入图片描述

/dev-api 替换成'' 再映射到
http://localhost:8080

# 最后的URL被替换为:http://localhost:8080/captchaImage

3.2 登录具体流程

若依的安全框架之前使用的是shiro,后来转换为了SpringSecurity

①校验验证码

②校验用户名和密码

③生成Token

④异步任务管理器记录日志

使用异步任务管理器,结合线程池,实现了异步记录日志的操作,成功实现和业务逻辑异步解耦合。

⑤getInfo

获取当前用户的角色和权限信息,存储到Vuex中
权限匹配:*:*:*
请求后端/getInfo获取当前用户信息,包括拥有哪些权限等:
在这里插入图片描述
当前用户所拥有的权限:
在这里插入图片描述

⑥GetRouters

根据当前用户的权限获取动态路由

  • 因为若依是一个权限管理的系统,因此不同用户对应的左侧菜单栏是不同的

在这里插入图片描述

  • 若依底层维护了几张表:

包括:sys_menu,其中借助了parent_id来进行层级的管理在这里插入图片描述

3.3 用户管理

流程:加载Vue页面 - - 请求后台数据

例如:getList

  1. startPage():
    在这里插入图片描述
public class PageUtils extends PageHelper{
    /**
     * 设置请求分页数据
     */
    public static void startPage()
    {
    	//如果请求中没有携带请求分页参数,则自动设置
        PageDomain pageDomain = TableSupport.buildPageRequest();
        //默认为:1
        Integer pageNum = pageDomain.getPageNum();
        //默认为:10
        Integer pageSize = pageDomain.getPageSize();
        String orderBy = SqlUtil.escapeOrderBySql(pageDomain.getOrderBy());
        Boolean reasonable = pageDomain.getReasonable();
        PageHelper.startPage(pageNum, pageSize, orderBy).setReasonable(reasonable);
    }
    ...
}

PageHelper中的reasonable(合理的)对参数进行逻辑处理,保证参数的正确性。

  • 例如:pageNum= 0/ -1, 则直接将pageNum设置为1
  1. userService.selectUserList(user);

注解@DataScope(deptAlias="d", userAlias = "u")是给表设置别名的

  • sys_dept d, sys_user u
    /**
     * 根据条件分页查询用户列表
     * 
     * @param user 用户信息
     * @return 用户信息集合信息
     */
    @Override
    @DataScope(deptAlias = "d", userAlias = "u")
    public List<SysUser> selectUserList(SysUser user)
    {
        return userMapper.selectUserList(user);
    }
  1. treeselect
  1. 查出所有的部门数据
  2. 组装成树状结构
    buildDeptTreeSelect:将10条记录组装成一个树状图
    recursionFn(depts, dept);
    1、先找到顶级节点,再找到它的子节点
    2、遍历顶级节点的子节点,再找到它的子节点

后端:
在这里插入图片描述
前端:
在这里插入图片描述

封装VO:

  • 封装前:
    在这里插入图片描述
  • 封装后:
    在这里插入图片描述
    点击树状图中的数据:
    在这里插入图片描述

3.4 添加数据

在这里插入图片描述

  • reset:表单重置
  • getTreeselct:获取部门树状图
  • getUser:获取角色和部门信息

后端User业务:

在这里插入图片描述

3.5 修改数据

以修改用户信息为例
①先根据userId查询用户数据【请求的路径中有userId就是修改,没有就是查询】–GET请求
②根据userId进行修改【若依中的修改是:先删除再新增】–PUT请求

  1. 根据userId获取用户信息
  • 如果有id就是修改,如果没有就是查询操作
    在这里插入图片描述
  • 修改用户信息的前端操作:
    在这里插入图片描述

在这里插入图片描述

这里的getUser方法除了要获取所有岗位和角色信息之外,还要获取当前用户已经拥有的岗位和角色

  1. 根据userId发起PUT请求,修改用户信息
    在这里插入图片描述
  2. 后端进行修改逻辑处理
/**
 * 修改保存用户信息
 * 
 * @param user 用户信息
 * @return 结果
 */
@Override
@Transactional
public int updateUser(SysUser user)
{
    Long userId = user.getUserId();
    // 删除用户与角色关联
    userRoleMapper.deleteUserRoleByUserId(userId);
    // 新增用户与角色管理
    insertUserRole(user);
    // 删除用户与岗位关联
    userPostMapper.deleteUserPostByUserId(userId);
    // 新增用户与岗位管理
    insertUserPost(user);
    return userMapper.updateUser(user);
}

数据库:

  1. 修改user
  2. 重新维护user_post和user_role【用户-岗位,多对多关系,维护中间表】

根据是否有userId来区分是新增还是修改

  • 修改操作:直接先删除,然后再新增记录关系

3.6 删除数据

在这里插入图片描述

3.7 异步任务管理器

AsyncManager.me().execute(AsyncFactory.recordLogininfor(
username, Constants.LOGIN_FAIL,
MessageUtils.message("user.password.not.match")));

通过异步任务管理器记录登录日志:

  1. AsyncManager.me()获取一个AsyncManager对象
  2. 执行execute方法,执行任务,传入的是一个Task对象,实现了Runnable接口,是一个任务,由线程Thread去执行。

若依框架中维护了一个sys_oper_log表,用于记录操作日志:
在这里插入图片描述
①通过请求方法中的@Log日志,直接记录操作

/**
 * 删除用户
 */
@PreAuthorize("@ss.hasPermi('system:user:remove')")
@Log(title = "用户管理", businessType = BusinessType.DELETE)
@DeleteMapping("/{userIds}")
public AjaxResult remove(@PathVariable Long[] userIds){
	...
}

②直接异步调用日志记录

public String login(String username, String password, String code, String uuid)
    {
        // 验证码校验
        validateCaptcha(username, code, uuid);
        // 登录前置校验
        loginPreCheck(username, password);
        // 用户验证
        Authentication authentication = null;
        try
        {
            UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(username, password);
            AuthenticationContextHolder.setContext(authenticationToken);
            // 该方法会去调用UserDetailsServiceImpl.loadUserByUsername
            authentication = authenticationManager.authenticate(authenticationToken);
        }
        catch (Exception e)
        {
            if (e instanceof BadCredentialsException)
            {
            	//异步记录日志打印
                AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_FAIL, MessageUtils.message("user.password.not.match")));
                throw new UserPasswordNotMatchException();
            }
        }
        finally
        {
            AuthenticationContextHolder.clearContext();
        }
        AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_SUCCESS, MessageUtils.message("user.login.success")));
        LoginUser loginUser = (LoginUser) authentication.getPrincipal();
        recordLoginInfo(loginUser.getUserId());
        // 生成token
        return tokenService.createToken(loginUser);
    }

打印日志实现:

  1. 创建线程池,配置核心参数等
  2. 创建任务
    在这里插入图片描述
  3. 记录日志
    在这里插入图片描述
/**
 * 记录登录信息
 * 
 * @param username 用户名
 * @param status 状态
 * @param message 消息
 * @param args 列表
 * @return 任务task
 */
public static TimerTask recordLogininfor(final String username, final String status, final String message,
        final Object... args)
{
    final UserAgent userAgent = UserAgent.parseUserAgentString(ServletUtils.getRequest().getHeader("User-Agent"));
    final String ip = IpUtils.getIpAddr();
    return new TimerTask()
    {
        @Override
        public void run()
        {
            String address = AddressUtils.getRealAddressByIP(ip);
            StringBuilder s = new StringBuilder();
            s.append(LogUtils.getBlock(ip));
            s.append(address);
            s.append(LogUtils.getBlock(username));
            s.append(LogUtils.getBlock(status));
            s.append(LogUtils.getBlock(message));
            // 打印信息到日志
            sys_user_logger.info(s.toString(), args);
            // 获取客户端操作系统
            String os = userAgent.getOperatingSystem().getName();
            // 获取客户端浏览器
            String browser = userAgent.getBrowser().getName();
            // 封装对象
            SysLogininfor logininfor = new SysLogininfor();
            logininfor.setUserName(username);
            logininfor.setIpaddr(ip);
            logininfor.setLoginLocation(address);
            logininfor.setBrowser(browser);
            logininfor.setOs(os);
            logininfor.setMsg(message);
            // 日志状态
            if (StringUtils.equalsAny(status, Constants.LOGIN_SUCCESS, Constants.LOGOUT, Constants.REGISTER))
            {
                logininfor.setStatus(Constants.SUCCESS);
            }
            else if (Constants.LOGIN_FAIL.equals(status))
            {
                logininfor.setStatus(Constants.FAIL);
            }
            // 插入数据
            SpringUtils.getBean(ISysLogininforService.class).insertLogininfor(logininfor);
        }
    };

封装了登录用户的信息,执行添加操作,这里不会执行,而是将任务交给线程对象来执行。

  • 异步任务管理器,内部定义了一个线程池,然后根据业务创建添加日志的任务,交给线程池来处理,这样做到日志和业务的抽象,解耦合,日志全部统一处理。

3.8 代码自动生成(menu菜单)

  1. 创建数据表
use ruoyi_vue;
create table test_user(
 id int primary key auto_increment,
 name varchar(11),
  password varchar(11)
);
  1. 系统工具 - 代码生成
    完整步骤:
    ①在数据库中创建一个表
create table test_user(
    id int primary key ,
    name varchar(11),
    age int
)

②导入表

系统工具-代码生成

在这里插入图片描述
③导入之后,编辑我们导入的test_user表

需要新增表的字段描述和表的生成信息、表的描述等,否则无法成功生成正确的代码

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
④点击生成代码
在这里插入图片描述
3. 解压,使用代码
在这里插入图片描述

  • main(Java后端代码)
  • Vue(Vue前端代码)
  • SQL(菜单SQL)

对应复制或直接执行使用即可。

注意:

如果后端抛出404异常,点击Idea的rebuild project,重新启动即可

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

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

相关文章

Spring Security --- authorizeRequests配置

目录 自定义配置类之访问权限 匹配顺序规则 访问控制包含 访问控制url匹配 访问控制方法 角色、权限判断 使用注解进行角色权限控制 自定义配置类之访问权限 http.authorizeRequests()主要是对url进行访问权限控制通过这个方法来实现url授权操作支持链式写法 匹配顺序…

【react全家桶学习】react简介

react是什么&#xff1f; react是用于构建用户界面的JS库&#xff0c;是一个将数据渲染为HTML视图的开源JS库 谁开发的&#xff1f; 由Facebook开发&#xff0c;且开源 为什么要学&#xff1f; 原生JavaScript操作DOM繁琐、效率低 ( DOM-API操作 UI)使用JavaScript直接操作…

Attention注意力机制

加粗样式通俗理解&#xff1a;你会注意什么&#xff1f; 对于一个模型而言&#xff08;CNN&#xff0c;LSTM&#xff09;&#xff0c;模型本身很难决定什么重要什么不重要&#xff0c;因此注意力机制诞生了。 注意力机制&#xff1a;我们会把焦点聚焦在比较重要的事务上 怎么…

详细聊一聊Android Apk的四代签名

简介 大部分开发者对apk签名还停留在APK v2&#xff0c;对APK v3和APK v4了解很少&#xff0c;而且网上大部分文章讲解的含糊不清&#xff0c;所以根据官网文档重新整理一份。 apk签名从APK v1到APK v2改动很大&#xff0c;是颠覆性的&#xff0c;而APK v3只是对APK v2的一次…

RocketMQ是是如何管理消费进度的?又是如何保证消息成功消费的?

RocketMQ消费者保障 作者: 博学谷狂野架构师GitHub&#xff1a;GitHub地址 &#xff08;有我精心准备的130本电子书PDF&#xff09; 只分享干货、不吹水&#xff0c;让我们一起加油&#xff01;&#x1f604; 消息确认机制 consumer的每个实例是靠队列分配来决定如何消费消息的…

业务异步离线任务平台思考

目录 一、离线任务平台定义 二、实际开发那种的实现方式分析 三、企业应用与链接分享 &#xff08;一&#xff09;具体企业应用举例 &#xff08;二&#xff09;离线任务平台相关文章和论文链接 四、开源代码库参考 一、离线任务平台定义 离线任务平台通常是指一种基于云…

基于 Verilog HDL 设计真彩图的灰度处理模块

引言 FPGA比较擅长的是作定点数整数运算&#xff0c;那么对于带有小数部分的乘加运算。一般都选择先扩大若干倍&#xff0c;而后将运算结果缩小若干倍实现。 应用案例&#xff0c;真彩图转灰度图的心理学计算公式&#xff1a; Gray 0.299R 0.587G 0.114B 本文给出具体的…

Spring boot基础学习之(十八):通过shiro框架使用Mybatis实现用户的认证完整的认证流程

在上几篇文章的基础上&#xff0c;实现本次案例 注意&#xff1a;本篇文章的实现代码在几篇文章都已经详细的讲过了&#xff0c;所以在此篇文章&#xff0c;将不再有理论知识的陈述&#xff0c;更过的流程&#xff0c;如何通过代码实现连接数据库进行认证 添加本次案例所需要的…

【并发编程】ConcurrentHashMap源码分析(二)

addCount 统计元素个数 private transient volatile long baseCount; //初始化大小为2,如果竞争激烈,会扩容 2->4 private transient volatile CounterCell[] counterCells;如果竞争不激烈的情况下&#xff0c;直接用cas (baseCount1)如果竞争激烈的情况下&#xff0c;采用…

项目管理的三要素:时间、成本和质量

项目管理的三要素&#xff1a;时间、成本和质量&#xff0c;他们作为衡量一个项目的成功失败的指标&#xff0c;贯穿项目整个过程。 时间&#xff1a; 项目时间管理包括使项目按时完成必须实施的各项过程。 项目计划按照逻辑关系安排计划活动顺序时&#xff0c;需要考虑进度…

C#,码海拾贝(16)——求行列式值的全选主元高斯消去法,《C#数值计算算法编程》源代码升级改进版

1 高斯消去法 数学上&#xff0c;高斯消元法&#xff08;或译&#xff1a;高斯消去法&#xff09;&#xff0c;是线性代数规划中的一个算法&#xff0c;可用来为线性方程组求解。但其算法十分复杂&#xff0c;不常用于加减消元法&#xff0c;求出矩阵的秩&#xff0c;以及求出…

利好消息不断原油价格大幅走高

​几个OPEC成员国将在年底前将全球产量再削减116万桶/天&#xff0c;这将进一步给央行遏制全球通胀的努力带来负担&#xff0c;但关键是保护该联盟更广泛的产量策略免受政治压力的影响。 华盛顿介入批评了上周日的声明&#xff0c;8个OPEC生产国&#xff08;包括组织的领导国沙…

Java中jar包的创建和使用

Java中jar包的创建和使用 jar包的基本概念 jar包的全称是java archive。jar包本质就是一种压缩包。在Java开发中一般是用来压缩类的一个包。类似C/C中的静态库和动态库&#xff0c;但是又不完全是。 C/C中的静态库和动态库是对中间文件&#xff08;*.o&#xff09;打包成一个…

【电路原理】电路元件基本知识详解

博主简介&#xff1a;努力学习的22级计科生一枚~博主主页&#xff1a; 是瑶瑶子啦所属专栏: 电路理论 前言1.电阻元件2.电容元件3.电感元件4.独立电源4.1&#xff1a;电压源4.2&#xff1a;电流源5.受控电源6.符号补充&#xff1a;7.总结本专栏文章主要总结、归纳电路原理、电路…

数据结构-排序(2)

前言&#xff1a; 上一章节介绍了 排序中的插入排序和选择排序&#xff0c; 分别复盘了插入排序中的直接插入排序和希尔排序以及选择排序中的选择排序和堆排序。今天继续复盘交换排序。 目录 2.3交换排序 2.3.1冒泡排序 2.3.2快速排序 2.3.2快速排序非递归 2.3交换排序 基…

HTML5 <figure> 标签、HTML5 <footer> 标签

HTML5 <figure> 标签 实例 使用 <figure> 元素标记文档中的一个图像&#xff1a; <figure><img src"img_pulpit.jpg" alt"The Pulpit Rock" width"304" height"228"> </figure>尝试一下 浏览器支持 …

在proteus中仿真arduino实现矩阵键盘程序

矩阵键盘是可以解决我们端口缺乏的问题&#xff0c;当然&#xff0c;如果我们使用芯片来实现矩阵键盘的输入端口缺乏的问题将更加划算了&#xff0c;本文暂时不使用芯片来解决问题&#xff0c;而使用纯朴的8根线来实现矩阵键盘&#xff0c;目的是使初学者掌握原理。想了解使用芯…

Lua脚本

目录说明什么是Lua脚本为什么要使用Lua脚本Lua脚本的安装Lua脚本的使用Lua的变量Lua脚本的算术运算符Lua脚本的关系运算符Lua脚本的逻辑运算符Lua脚本不同的操作Lua脚本的函数和标准库Redis整合Lua脚本&#xff08;重点&#xff09;在Java集成Lua在SpringBoot项目中使用Redis集…

前端PC端适配,网页端适配

问题背景 由于我司是使用的大屏&#xff0c;且设计稿尺寸为19201080。但是需要适配各种分辨率&#xff0c; 比如12801024(5:4)、1366768(16&#xff1a;10)、16801050&#xff08;16&#xff1a;10&#xff09;。在尝试了多种方法之后&#xff0c;最终确定主要的适配方法为rem…

【vue3】04-vue基础语法补充及阶段案例

文章目录vue基础语法补充vue的computedvue的watch侦听书籍购物车案例vue基础语法补充 vue的computed computed&#xff1a;用于声明要在组件实例上暴露的计算属性。&#xff08;官方文档描述&#xff09; 我们已经知道&#xff0c;在模板中可以直接通过插值语法显示一些data中…