avatar


24.RuoYi(若依)快速上手

《基于Java的后端开发入门》的前几章,我们讨论了SpringBoot。SpringBoot,其实还是基于Spring的,但是封装了很多内容,可以 简化工程配置和依赖管理等

RuoYi的封装程度比SpringBoot的封装程度还要高,用户登录、权限控制、菜单管理等都已经有了。

我们可以在RuoYi的基础上进行二次开发。

概述

官网:http://www.ruoyi.vip/
官方文档:http://doc.ruoyi.vip

通过官网,我们可以看到RuoYi有三个版本:

  • 若依管理系统
    • 基于SpringBoot
    • 前后端不分离
  • Vue前端分离版
    • 基于SpringBoot
    • 前后端分离
  • Cloud微服务版
    • 基于SpringCloud的权限管理系统
    • 前后端分离

本文讨论的是Vue前端分离版

启动

步骤

  1. 通过RuoYi官网提供的地址下载
  2. 通过IDEA导入项目
  3. 创建数据库,ry-vue
  4. 执行sql文件夹中的sql文件
  5. 修改application-druid.yml的MySQL连接信息
  6. 修改application.yml的Redis连接信息
  7. 修改logback.xmllog.path
  8. 启动ruoyi-admin中的启动类RuoYiAppliacation
  9. 启动前端项目
    进入到ruoyi-ui的目录中执行npm install安装依赖
    执行npm run dev启动前端项目

application-druid.ymlapplication.ymllogback.xml位于目录RuoYi-Vue/ruoyi-admin/src/main/resources/

问题解决

npm install

执行npm install,可能会有如下报错:

1
2
3
4
5
6
7
8
npm ERR! code EEXIST
npm ERR! syscall mkdir
npm ERR! path /Users/kaka/.npm/_cacache/content-v2/sha512/42/6c
npm ERR! errno EEXIST
npm ERR! Invalid response body while trying to fetch https://registry.npmjs.org/lint-staged: EACCES: permission denied, mkdir '/Users/kaka/.npm/_cacache/content-v2/sha512/42/6c'
npm ERR! File exists: /Users/kaka/.npm/_cacache/content-v2/sha512/42/6c
npm ERR! Remove the existing file and try again, or run npm
npm ERR! with --force to overwrite files recklessly.

使用sudo可以解决,sudo npm install

npm run dev

执行npm run dev,可能会有如下报错:

1
2
3
4
5
6
7
8
9
10
95% emitting CompressionPlugin ERROR  Error: error:0308010C:digital envelope routines::unsupported
Error: error:0308010C:digital envelope routines::unsupported
at new Hash (node:internal/crypto/hash:69:19)
at Object.createHash (node:crypto:133:10)
at /Users/kaka/Desktop/RuoYi-Vue/ruoyi-ui/node_modules/compression-webpack-plugin/dist/index.js:243:42
at CompressionPlugin.compress (/Users/kaka/Desktop/RuoYi-Vue/ruoyi-ui/node_modules/compression-webpack-plugin/dist/index.js:284:9)
at /Users/kaka/Desktop/RuoYi-Vue/ruoyi-ui/node_modules/compression-webpack-plugin/dist/index.js:305:12
at _next1 (eval at create (/Users/kaka/Desktop/RuoYi-Vue/ruoyi-ui/node_modules/tapable/lib/HookCodeFactory.js:33:10), <anonymous>:14:17)
at eval (eval at create (/Users/kaka/Desktop/RuoYi-Vue/ruoyi-ui/node_modules/tapable/lib/HookCodeFactory.js:33:10), <anonymous>:33:1)
at /Users/kaka/Desktop/RuoYi-Vue/ruoyi-ui/node_modules/copy-webpack-plugin/dist/index.js:91:9

这是因为在Node的18版本中,默认使用了OpenSSL 3.0及以上的版本,而OpenSSL3.0对允许算法和密钥大小增加了严格的限制,我们可以通过设置NODE_OPTIONS环境变量来强制使用旧版本。

修改package.json的如下部分的"dev": "vue-cli-service serve",

1
2
3
4
5
6
7
"scripts": {
"dev": "vue-cli-service serve",
"build:prod": "vue-cli-service build",
"build:stage": "vue-cli-service build --mode staging",
"preview": "node build/index.js --preview",
"lint": "eslint --ext .js,.vue src"
},

对于MacOS和Linux,修改为:

1
"dev": "export NODE_OPTIONS=--openssl-legacy-provider && vue-cli-service serve",

对于Windows,修改为:

1
set NODE_OPTIONS=--openssl-legacy-provider

结构

代码结构

  • ruoyi-admin:web模块,存放controller
  • ruoyi—common:公共模块,存放工具类
  • ruoyi-framwork:框架模块,存放一些第三方框架代码和配置
  • ruoyi-generator:代码生成器模块
  • ruoyi-quartz:定时任务模块
  • ruoyi-system:系统模块,存放domain,mapper,service
  • ruoyi-ui:前端项目

表结构

  • gen_table:代码生成器,表信息
  • gen_table_column:代码生成器,列信息
  • sys_config:系统配置表
  • sys_dept:部门表
  • sys_dict_data:字典目录表
  • sys_dict_type:字典类型表
  • sys_job:定时任务表
  • sys_job_log:任务日志表
  • sys_logininfor:登录信息表
  • sys_menu:菜单表
  • sys_notice:系统通知表
  • sys_oper_log:执行日志表
  • sys_post:岗位表
  • sys_role:角色表
  • sys_role_dept:角色和部门关系表
  • sys_role_menu:角色和菜单关系表
  • sys_user:用户表
  • sys_user_post:用户和岗位关系表
  • sys_user_role:用户和角色关系表

配置文件

项目的配置文件都在ruoyi-adminresources中:

  • i18n:处理国际化。
  • META-INF:此文件包含有关JAR内容的元数据。
  • mybatis:mybatis配置信息。
  • application.yml:项目的配置信息。
  • application-druid.yml:数据库连接信息。
  • banner.txt:启动时候的banner图标信息。
  • logback.xml:日志配置信息。

岗位管理(部分源码解读)

本文以系统管理 -> 岗位管理为例,讨论和CRUD相关的部分源码。

分页

源码

获取岗位列表相关的代码如下:

1
2
3
4
5
6
7
8
9
10
11
/**
* 获取岗位列表
*/
@PreAuthorize("@ss.hasPermi('system:post:list')")
@GetMapping("/list")
public TableDataInfo list(SysPost post)
{
startPage();
List<SysPost> list = postService.selectPostList(post);
return getDataTable(list);
}
  • @PreAuthorize是SpringSecurity中的注解。

startPage

startPage(),这个处理分页。
我们点击startPage(),会发现最后调用了PageUtilsstartPage()方法。

示例代码:

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
package com.ruoyi.common.utils;

import com.github.pagehelper.PageHelper;
import com.ruoyi.common.core.page.PageDomain;
import com.ruoyi.common.core.page.TableSupport;
import com.ruoyi.common.utils.sql.SqlUtil;

/**
* 分页工具类
*
* @author ruoyi
*/
public class PageUtils extends PageHelper
{
/**
* 设置请求分页数据
*/
public static void startPage()
{
PageDomain pageDomain = TableSupport.buildPageRequest();
Integer pageNum = pageDomain.getPageNum();
Integer pageSize = pageDomain.getPageSize();
String orderBy = SqlUtil.escapeOrderBySql(pageDomain.getOrderBy());
Boolean reasonable = pageDomain.getReasonable();
PageHelper.startPage(pageNum, pageSize, orderBy).setReasonable(reasonable);
}

/**
* 清理分页的线程变量
*/
public static void clearPage()
{
PageHelper.clearPage();
}
}

分页方法通过PageHelper.startPage()实现,关于PageHelper,可以参考《12.MyBatis》的分页插件部分。

TableSupport.buildPageRequest

再点进PageDomain pageDomain = TableSupport.buildPageRequest();TableSupport.buildPageRequest();,示例代码:

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
package com.ruoyi.common.core.page;

import com.ruoyi.common.core.text.Convert;
import com.ruoyi.common.utils.ServletUtils;

/**
* 表格数据处理
*
* @author ruoyi
*/
public class TableSupport
{
【部分代码略】

/**
* 封装分页对象
*/
public static PageDomain getPageDomain()
{
PageDomain pageDomain = new PageDomain();
pageDomain.setPageNum(Convert.toInt(ServletUtils.getParameter(PAGE_NUM), 1));
pageDomain.setPageSize(Convert.toInt(ServletUtils.getParameter(PAGE_SIZE), 10));
pageDomain.setOrderByColumn(ServletUtils.getParameter(ORDER_BY_COLUMN));
pageDomain.setIsAsc(ServletUtils.getParameter(IS_ASC));
pageDomain.setReasonable(ServletUtils.getParameterToBool(REASONABLE));
return pageDomain;
}

public static PageDomain buildPageRequest()
{
return getPageDomain();
}
}

pageDomain中的很多属性来自ServletUtils.getParameter(XXX)。点进ServletUtils.getParameter,会发现最后还是通过HttpServletRequest获取参数。

关于HttpServletRequest,可以参考《13.Servlet、Filter和Listener》

getDataTable

点进getDataTable(),该部分再组装返回的报文,示例代码:

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
package com.ruoyi.common.core.controller;

【部分代码略】

/**
* web层通用数据处理
*
* @author ruoyi
*/
public class BaseController
{

【部分代码略】

/**
* 响应请求分页数据
*/
@SuppressWarnings({ "rawtypes", "unchecked" })
protected TableDataInfo getDataTable(List<?> list)
{
TableDataInfo rspData = new TableDataInfo();
rspData.setCode(HttpStatus.SUCCESS);
rspData.setMsg("查询成功");
rspData.setRows(list);
rspData.setTotal(new PageInfo(list).getTotal());
return rspData;
}

【部分代码略】

}

操作记录

导出,示例代码:

1
2
3
4
5
6
7
8
9
@Log(title = "岗位管理", businessType = BusinessType.EXPORT)
@PreAuthorize("@ss.hasPermi('system:post:export')")
@PostMapping("/export")
public void export(HttpServletResponse response, SysPost post)
{
List<SysPost> list = postService.selectPostList(post);
ExcelUtil<SysPost> util = new ExcelUtil<SysPost>(SysPost.class);
util.exportExcel(response, list, "岗位数据");
}

注意@Log(title = "岗位管理", businessType = BusinessType.EXPORT),我们找到com.ruoyi.framework.aspectj.LogAspect,示例代码:

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
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
package com.ruoyi.framework.aspectj;

【部分代码略】

/**
* 操作日志记录处理
*
* @author ruoyi
*/
@Aspect
@Component
public class LogAspect
{

【部分代码略】

/**
* 处理完请求后执行
*
* @param joinPoint 切点
*/
@AfterReturning(pointcut = "@annotation(controllerLog)", returning = "jsonResult")
public void doAfterReturning(JoinPoint joinPoint, Log controllerLog, Object jsonResult)
{
handleLog(joinPoint, controllerLog, null, jsonResult);
}

/**
* 拦截异常操作
*
* @param joinPoint 切点
* @param e 异常
*/
@AfterThrowing(value = "@annotation(controllerLog)", throwing = "e")
public void doAfterThrowing(JoinPoint joinPoint, Log controllerLog, Exception e)
{
handleLog(joinPoint, controllerLog, e, null);
}

protected void handleLog(final JoinPoint joinPoint, Log controllerLog, final Exception e, Object jsonResult)
{
try
{
// 获取当前的用户
LoginUser loginUser = SecurityUtils.getLoginUser();

// *========数据库日志=========*//
SysOperLog operLog = new SysOperLog();
operLog.setStatus(BusinessStatus.SUCCESS.ordinal());
// 请求的地址
String ip = IpUtils.getIpAddr();
operLog.setOperIp(ip);
operLog.setOperUrl(StringUtils.substring(ServletUtils.getRequest().getRequestURI(), 0, 255));
if (loginUser != null)
{
operLog.setOperName(loginUser.getUsername());
}

if (e != null)
{
operLog.setStatus(BusinessStatus.FAIL.ordinal());
operLog.setErrorMsg(StringUtils.substring(e.getMessage(), 0, 2000));
}
// 设置方法名称
String className = joinPoint.getTarget().getClass().getName();
String methodName = joinPoint.getSignature().getName();
operLog.setMethod(className + "." + methodName + "()");
// 设置请求方式
operLog.setRequestMethod(ServletUtils.getRequest().getMethod());
// 处理设置注解上的参数
getControllerMethodDescription(joinPoint, controllerLog, operLog, jsonResult);
// 设置消耗时间
operLog.setCostTime(System.currentTimeMillis() - TIME_THREADLOCAL.get());
// 保存数据库
AsyncManager.me().execute(AsyncFactory.recordOper(operLog));
}
catch (Exception exp)
{
// 记录本地异常日志
log.error("异常信息:{}", exp.getMessage());
exp.printStackTrace();
}
finally
{
TIME_THREADLOCAL.remove();
}
}

【部分代码略】

}

handleLog方法即记录日志的方法,其中绝大部分代码都在组装operLog实例,我们关注最后一行,以异步的方式记录操作日志到数据库。

1
AsyncManager.me().execute(AsyncFactory.recordOper(operLog));

客户管理(代码生成器)

我们以客户管理为例,讨论代码生成器的用法。

假设存在一张表如下:

1
2
3
4
5
6
7
CREATE TABLE `customer` (
`id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT 'id',
`name` varchar(255) NOT NULL COMMENT '名称',
`phone` varchar(255) DEFAULT NULL COMMENT '手机号',
`age` int(11) DEFAULT NULL COMMENT '年龄',
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8 COMMENT='客户表';

生成代码

依次点击系统工具代码生成,再点击导入

生成代码-1

在弹出框中选择我们需要的表,点击确定

生成代码-2

点击编辑,可以修改我们需要生成的功能的一些信息。

生成代码-3

设置功能名为"客户管理",隶属于"系统管理"。

生成代码-4

然后可以点击预览进行查看,点击生成代码下载文件。

生成代码-5

复制

解压后的代码如下:

复制-1

我们把文件复制到对应的位置

  • ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/CustomerController.java
  • ruoyi-system/src/main/java/com/ruoyi/system/domain/Customer.java
  • ruoyi-system/src/main/java/com/ruoyi/system/mapper/CustomerMapper.java
  • ruoyi-system/src/main/java/com/ruoyi/system/service/impl/CustomerServiceImpl.java
  • ruoyi-system/src/main/java/com/ruoyi/system/service/ICustomerService.java
  • ruoyi-system/src/main/resources/mapper/system/CustomerMapper.xml
  • ruoyi-ui/src/api/system/customer.js
  • ruoyi-ui/src/views/system/customer/index.vue

执行customerMenu.sql

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
-- 菜单 SQL
insert into sys_menu (menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark)
values('客户管理', '1', '1', 'customer', 'system/customer/index', 1, 0, 'C', '0', '0', 'system:customer:list', '#', 'admin', sysdate(), '', null, '客户管理菜单');

-- 按钮父菜单ID
SELECT @parentId := LAST_INSERT_ID();

-- 按钮 SQL
insert into sys_menu (menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark)
values('客户管理查询', @parentId, '1', '#', '', 1, 0, 'F', '0', '0', 'system:customer:query', '#', 'admin', sysdate(), '', null, '');

insert into sys_menu (menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark)
values('客户管理新增', @parentId, '2', '#', '', 1, 0, 'F', '0', '0', 'system:customer:add', '#', 'admin', sysdate(), '', null, '');

insert into sys_menu (menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark)
values('客户管理修改', @parentId, '3', '#', '', 1, 0, 'F', '0', '0', 'system:customer:edit', '#', 'admin', sysdate(), '', null, '');

insert into sys_menu (menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark)
values('客户管理删除', @parentId, '4', '#', '', 1, 0, 'F', '0', '0', 'system:customer:remove', '#', 'admin', sysdate(), '', null, '');

insert into sys_menu (menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark)
values('客户管理导出', @parentId, '5', '#', '', 1, 0, 'F', '0', '0', 'system:customer:export', '#', 'admin', sysdate(), '', null, '');

最后,我们重启项目,功能已经实现。

界面修改

我们一般是某一个管理系统需要用到RuoYi,而且不希望在界面上有太多的RuoYi的标志,在这里讨论一下如何修改界面。

首页

首页位于src/views/index.vue,修改该文件即可修改首页内容。

若依官网

在主页菜单栏,最后一项有一个"若依官网"。
可以通过菜单管理删除该功能,在删除之前,需要先在角色管理中删除若依官网的权限。

右上角

在顶部右上角,有几个导航栏。

找到src/layout/components/Navbar.vue,按需注释即可。

示例代码:

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
<template>
<div class="navbar">
<hamburger id="hamburger-container" :is-active="sidebar.opened" class="hamburger-container" @toggleClick="toggleSideBar" />

<breadcrumb id="breadcrumb-container" class="breadcrumb-container" v-if="!topNav"/>
<top-nav id="topmenu-container" class="topmenu-container" v-if="topNav"/>

<div class="right-menu">
<template v-if="device!=='mobile'">
<search id="header-search" class="right-menu-item" />

<el-tooltip content="源码地址" effect="dark" placement="bottom">
<ruo-yi-git id="ruoyi-git" class="right-menu-item hover-effect" />
</el-tooltip>

<el-tooltip content="文档地址" effect="dark" placement="bottom">
<ruo-yi-doc id="ruoyi-doc" class="right-menu-item hover-effect" />
</el-tooltip>

<screenfull id="screenfull" class="right-menu-item hover-effect" />

<el-tooltip content="布局大小" effect="dark" placement="bottom">
<size-select id="size-select" class="right-menu-item hover-effect" />
</el-tooltip>

</template>

【部分代码略】

</div>
</div>
</template>

【部分代码略】

背景和头像

修改如下的两个文件:

  • src/assets/images/login-background.jpg
  • src/assets/images/profile.jpg

标题

全局搜索若依管理系统若依后台管理系统ruoyi.vip,都替换。
替换完成后,需要重新执行npm run dev才能生效。

Logo

找到src/layout/components/Sidebar/Logo.vue,讲如下代码的logo: logoImg修改成logo: false去除Logo,或者替换assets/logo/logo.png,替换Logo。

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
import logoImg from '@/assets/logo/logo.png'
import variables from '@/assets/styles/variables.scss'

export default {
name: 'SidebarLogo',
props: {
collapse: {
type: Boolean,
required: true
}
},
computed: {
variables() {
return variables;
},
sideTheme() {
return this.$store.state.settings.sideTheme
}
},
data() {
return {
title: process.env.VUE_APP_TITLE,
logo: logoImg
}
}
}

部署

后端

修改配置

  • logback.xml文件中的路径建议改为./logs
  • application.yml中的profile需要改为服务器存在的真实路径。

打包:
后端打包

部署步骤:

  1. 将打包后的jar文件放在任意一个位置;
  2. 在服务器上和jar同一个目录下新建一个config目录,将项目里的application-druid.ymlapplication.yml或者其他yml配置文件复制出来,放入config目录。
    (在《21.SpringBoot [1/3]》的"多环境"的"外部配置文件"部分,我们讨论过配置文件的优先级,这种方法的优先级最高。)
  3. 执行命令启动:nohup java -jar ruoyi-admin.jar &
    也可以利用ry.sh脚本,将ry.sh放在jar同级目录下
    启动:./ry.sh start
    停止:./ry.sh stop
    重启:./ry.sh restart
    状态:./ry.sh status

最后我们执行curl http://127.0.0.1:8080,会收到如下返回:

1
欢迎使用RuoYi后台管理框架,当前版本:v3.8.6,请通过前端地址访问。

有些资料会讨论通过War包部署,RuoYi(前后端分离)本身就是基于SpringBoot的,通过War包部署,绝对不是行业主流,我们不讨论。

前端

前端打包命令:

1
npm run build:prod

如果又出现了如下的错误:

1
ERROR  Error: error:0308010C:digital envelope routines::unsupported

参数上文,将"build:prod": "vue-cli-service build",修改为:

1
"build:prod": "export NODE_OPTIONS=--openssl-legacy-provider && vue-cli-service build",

打包后会得到一个目录dist,将其传输到服务器上。

nginx.conf进行如下的配置:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
server {
listen 80;
server_name localhost;

location / {
root /root/dist;
try_files $uri $uri/ /index.html;
index index.html index.htm;
}

location /prod-api/{
proxy_set_header Host $http_host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header REMOTE-HOST $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_pass http://localhost:8080/;
}

error_page 500 502 503 504 /50x.html;
location = /50x.html {
root html;
}
}
  • root /root/dist;,配置的前端资源的地址
  • proxy_pass http://localhost:8080/;,配置的是后端服务的地址

如果报类似如下的错误:

1
2
3
4
5
6
7
[crit] 17192#0: *3 stat() "/root/dist/index.html" failed (13: Permission denied), client: 127.0.0.1, server: localhost, request: "GET / HTTP/1.1", host: "127.0.0.1"
[crit] 17192#0: *3 stat() "/root/dist/index.html" failed (13: Permission denied), client: 127.0.0.1, server: localhost, request: "GET / HTTP/1.1", host: "127.0.0.1"
[crit] 17192#0: *3 stat() "/root/dist/index.html" failed (13: Permission denied), client: 127.0.0.1, server: localhost, request: "GET / HTTP/1.1", host: "127.0.0.1"
[crit] 17192#0: *3 stat() "/root/dist/index.html" failed (13: Permission denied), client: 127.0.0.1, server: localhost, request: "GET / HTTP/1.1", host: "127.0.0.1"
[crit] 17192#0: *3 stat() "/root/dist/index.html" failed (13: Permission denied), client: 127.0.0.1, server: localhost, request: "GET / HTTP/1.1", host: "127.0.0.1"
[crit] 17192#0: *3 stat() "/root/dist/index.html" failed (13: Permission denied), client: 127.0.0.1, server: localhost, request: "GET / HTTP/1.1", host: "127.0.0.1"
[crit] 17192#0: *3 stat() "/root/dist/index.html" failed (13: Permission denied), client: 127.0.0.1, server: localhost, request: "GET / HTTP/1.1", host: "127.0.0.1"

可以在nginx.conf的增加如下的配置:

1
user root;

nginx默认通过nobody用户启动,user root;的含义是通过root用户启动。

去除Redis

建议不去除

建议不去除,哪怕是部署一个本地的Redis,只给RuoYi用也好。

如果要去除,参考如下操作。

去除Redis的操作

去除redis配置

去除application.yml中,和Redis相关的配置。

# redis 配置
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
redis:
# 地址
host: localhost
# 端口,默认为6379
port: 6379
# 数据库索引
database: 0
# 密码
password:
# 连接超时时间
timeout: 10s
lettuce:
pool:
# 连接池中的最小空闲连接
min-idle: 0
# 连接池中的最大空闲连接
max-idle: 8
# 连接池的最大数据库连接数
max-active: 8
# #连接池最大阻塞等待时间(使用负值表示没有限制)
max-wait: -1ms

去除ruoyi-framework下RedisConfig的配置

修改ruoyi-frameworkcom.ruoyi.framework.config.RedisConfig

可以考虑直接注释@Bean@Configuration@EnableCaching等注解,不删除RedisConfig

在ruoyi-common的core-redis下新建Cache类

使用ConcurrentHashMap替换Redis:

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
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
package com.ruoyi.common.core.redis;

import org.springframework.cache.Cache;
import org.springframework.cache.support.SimpleValueWrapper;
import org.springframework.stereotype.Component;

import java.util.Collection;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.Callable;
import java.util.concurrent.ConcurrentHashMap;

@Component
public class MyCache implements Cache {

// 使用ConcurrentHashMap作为数据的存储
private Map<String, Object> storage = new ConcurrentHashMap<>();

// getName获取cache的名称,存取数据的时候用来区分是针对哪个cache操作
@Override
public String getName() {
return null;
}

@Override
public Object getNativeCache() {
return null;
}

public boolean hasKey(String key){
return storage.containsKey(key);
}

@Override
public ValueWrapper get(Object key) {
String k = key.toString();
Object value = storage.get(k);

// 注意返回的数据,要和存放时接收到数据保持一致,要将数据反序列化回来。
return Objects.isNull(value) ? null : new SimpleValueWrapper(value);
}

@Override
public <T> T get(Object key, Class<T> type) {
return null;
}

@Override
public <T> T get(Object key, Callable<T> valueLoader) {
return null;
}

// put方法,就是执行将数据进行缓存
@Override
public void put(Object key, Object value) {
if (Objects.isNull(value)) {
return;
}
//存值
storage.put(key.toString(), value);
}

// evict方法,是用来清除某个缓存项
@Override
public void evict(Object key) {
storage.remove(key.toString());
}

// 删除集合
public boolean deleteObject(final Collection collection){
collection.forEach(o -> {
storage.remove(o.toString());
} );
return true;
}

// 获取所有的keys
public Collection<String> keys(final String pattern){
return storage.keySet();
}

@Override
public void clear() {

}
}

修改RedisCache类

修改修改RedisCache类,替换为使用MyCache

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
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
package com.ruoyi.common.core.redis;

import java.util.Collection;
import java.util.concurrent.TimeUnit;
import org.springframework.cache.Cache;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;

/**
* spring redis 工具类
*
* @author ruoyi
**/
@SuppressWarnings(value = { "unchecked", "rawtypes" })
@Component
public class RedisCache
{
// @Autowired
// public RedisTemplate redisTemplate;

@Resource
public MyCache myCache;

/**
* 缓存基本的对象,Integer、String、实体类等
*
* @param key 缓存的键值
* @param value 缓存的值
*/
public <T> void setCacheObject(final String key, final T value)
{
// redisTemplate.opsForValue().set(key, value);
myCache.put(key,value);
}

/**
* 缓存基本的对象,Integer、String、实体类等
*
* @param key 缓存的键值
* @param value 缓存的值
* @param timeout 时间
* @param timeUnit 时间颗粒度
*/
public <T> void setCacheObject(final String key, final T value, final Integer timeout, final TimeUnit timeUnit)
{
// redisTemplate.opsForValue().set(key, value, timeout, timeUnit);
// 所以 缓存 永不失效
// 所以 和 缓存时间 相关的方法要改
myCache.put(key,value);
}

/**
* 设置有效时间
*
* @param key Redis键
* @param timeout 超时时间
* @return true=设置成功;false=设置失败
*/
public boolean expire(final String key, final long timeout)
{
return expire(key, timeout, TimeUnit.SECONDS);
}

/**
* 设置有效时间
*
* @param key Redis键
* @param timeout 超时时间
* @param unit 时间单位
* @return true=设置成功;false=设置失败
*/
public boolean expire(final String key, final long timeout, final TimeUnit unit)
{
// return redisTemplate.expire(key, timeout, unit);
return true;
}

/**
* 获取有效时间
*
* @param key Redis键
* @return 有效时间
*/
public long getExpire(final String key)
{
// return redisTemplate.getExpire(key);
// 返回LONG的最大值
return Long.MAX_VALUE;
}

/**
* 判断 key是否存在
*
* @param key 键
* @return true 存在 false不存在
*/
public Boolean hasKey(String key)
{
// return redisTemplate.hasKey(key);
return myCache.hasKey(key);
}

/**
* 获得缓存的基本对象。
*
* @param key 缓存键值
* @return 缓存键值对应的数据
*/
public <T> T getCacheObject(final String key)
{
// ValueOperations<String, T> operation = redisTemplate.opsForValue();
// return operation.get(key);
Cache.ValueWrapper valueWrapper = myCache.get(key);
if (valueWrapper == null){
return null;
}else {
return (T) valueWrapper.get();
}
}

/**
* 删除单个对象
*
* @param key
*/
public boolean deleteObject(final String key)
{
// return redisTemplate.delete(key);
myCache.evict(key);
return true;
}

/**
* 删除集合对象
*
* @param collection 多个对象
* @return
*/
public boolean deleteObject(final Collection collection)
{
// return redisTemplate.delete(collection) > 0;
return myCache.deleteObject(collection);
}

/**
* 获得缓存的基本对象列表
*
* @param pattern 字符串前缀
* @return 对象列表
*/
public Collection<String> keys(final String pattern)
{
// return redisTemplate.keys(pattern);
return myCache.keys(pattern);
}
}
  • 对于和缓存时间相关的方法,可以根据实际需求修改。

修改ruoyi-common下utils.DictUtils

修改com.ruoyi.common.utils.DictUtilsgetDictCache方法,需要将getCacheObject的返回值,显式转换成JSONArray的对象。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
/**
* 获取字典缓存
*
* @param key 参数键
* @return dictDatas 字典数据列表
*/
public static List<SysDictData> getDictCache(String key)
{
// JSONArray arrayCache = SpringUtils.getBean(RedisCache.class).getCacheObject(getCacheKey(key));
JSONArray arrayCache = JSONArray.parseArray(JSON.toJSONString(SpringUtils.getBean(RedisCache.class).getCacheObject(getCacheKey(key))));
if (StringUtils.isNotNull(arrayCache))
{
return arrayCache.toList(SysDictData.class);
}
return null;
}

修改基于Redis的限流

修改ruoyi-frameworkcom.ruoyi.framework.aspectj.RateLimiterAspect

可以考虑直接注释相关的注解,不删除RateLimiterAspect

关闭菜单

登录系统后,考虑关闭如下菜单:

  1. 缓存监控
  2. 缓存列表
  3. 在线用户
文章作者: Kaka Wan Yifan
文章链接: https://kakawanyifan.com/10824
版权声明: 本博客所有文章版权为文章作者所有,未经书面许可,任何机构和个人不得以任何形式转载、摘编或复制。

留言板