外观
RuoYi-Cloud
约 4093 字大约 14 分钟
2026-08-30
文档
环境准备
提示
以 RuoYi-Cloud 3.6.8 为例。
- 下载项目
# 后端
git clone https://gitcode.com/yangzongzhuan/RuoYi-Cloud.git
# 前端
git clone https://gitcode.com/yangzongzhuan/RuoYi-Cloud-Vue3.gitJDK-21
Node22、pnpm
- 修改
application.properties中 mysql 数据库使用ry-config,确认内部两个端口不和现有应用冲突 - 修改
ruoyi-system-dev.yml中的 mysql 数据源用户名及密码 - 进入Web后台 http://127.0.0.1:8080 (nacos/nacos),确认配置文件加载正确
- 修改
Navicat Premium 执行数据库文件
针对 RuoYi-Cloud 3.6.8,4 个 SQL 文件参考下面的对照表:
SQL 文件 是否必须执行 对应数据库 作用说明 ry_20260417.sql✅ 必须执行 ry-cloud核心业务库。包含系统管理、用户、权限等所有核心业务表。 ry_config_20260818.sql✅ 必须执行 ry-configNacos配置库。用于持久化存储微服务的配置信息,是微服务启动的关键。 quartz.sql❓ 按需执行 ry-cloud(通常)定时任务表。只有当你需要使用框架自带的定时任务功能( ruoyi-job模块)时才需要执行。ry_seata_20210128.sql❓ 按需执行 ry-seata分布式事务表。只有当你启用 Seata 分布式事务功能时才需要执行。默认是关闭的,可以跳过。
启动
后端
打开运行基础模块(启动没有先后顺序)
- RuoYiGatewayApplication (网关模块 必须)
- RuoYiAuthApplication (认证模块 必须)
- RuoYiSystemApplication (系统模块 必须)
- RuoYiMonitorApplication (监控中心 可选)
- RuoYiGenApplication (代码生成 可选)
- RuoYiJobApplication (定时任务 可选)
- RuoYiFileApplication (文件服务 可选)
前端
git switch typescript
git branch -vv
pnpm install
## 如果报错 可能是因为 pnpm install的时候不下载 sortablejs 这个包,补一下重新install就好了
pnpm dev提示
用户名:admin
密码:admin123
后端项目整体
树形图
RuoYi-Cloud/
+-- pom.xml # 父工程:统一版本管理 + dependencyManagement
+-- ruoyi-api/ # ★ Feign 远程接口模块(跨服务调用契约)
| `-- ruoyi-api-system/ # 系统服务接口:RemoteUserService / RemoteLogService / RemoteFileService
+-- ruoyi-common/ # ★ 公共能力模块(无业务,被各服务复用)
| +-- ruoyi-common-core/ # 最底层:BaseController、常量、工具类、登录用户模型
| +-- ruoyi-common-security/ # 安全认证:token 校验、权限注解
| +-- ruoyi-common-datasource/ # 多数据源(Druid dynamic-datasource)
| +-- ruoyi-common-datascope/ # 数据权限
| +-- ruoyi-common-log/ # 操作日志注解 @Log
| +-- ruoyi-common-redis/ # Redis 缓存
| +-- ruoyi-common-sensitive/ # 数据脱敏
| +-- ruoyi-common-swagger/ # 接口文档
| `-- ruoyi-common-seata/ # 分布式事务(依赖已建,未启用)
+-- ruoyi-auth/ # ★ 认证授权中心(登录、发 token,9200)
+-- ruoyi-gateway/ # ★ 网关(路由转发、token 鉴权、限流,8080)
+-- ruoyi-modules/ # ★★ 业务模块层 —— 你的新业务写在这里
| +-- ruoyi-system/ # 系统管理(用户/角色/菜单…,9201)
| +-- ruoyi-gen/ # 代码生成(9202)
| +-- ruoyi-job/ # 定时任务(9203)
| `-- ruoyi-file/ # 文件服务(9300)
`-- ruoyi-visual/
`-- ruoyi-monitor/ # SpringBoot Admin 监控(9100)新业务
推荐做法:新建一个业务模块 ruoyi-modules/ruoyi-xxx(业务隔离、独立部署),而不是塞进 ruoyi-system 。模块内部标准分层(参照 ruoyi-system 现有结构):
ruoyi-modules/ruoyi-xxx/
+-- pom.xml
`-- src/main/
+-- java/com/ruoyi/xxx/
| +-- RuoYiXxxApplication.java # 启动类
| +-- controller/ # 控制器(对外 REST 接口)
| +-- domain/ # 实体类(对应数据库表)
| +-- mapper/ # MyBatis Mapper 接口
| `-- service/ # 业务接口 ISysXxxService
| `-- impl/ # 业务实现 XxxServiceImpl
`-- resources/
+-- bootstrap.yml # 注册到 Nacos
`-- mapper/xxx/ # Mapper XML(XML 与 Java 同级包结构)如果只是加系统管理周边小功能(比如给 sys_user 加个字段),也可以直接加在 ruoyi-modules/ruoyi-system 对应包里。
跨服务调用(如 system 要调文件服务)时,把 Feign 接口放到 ruoyi-api/ 下新建的 ruoyi-api-xxx 模块(参照 ruoyi-api-system):
ruoyi-api/ruoyi-api-xxx/src/main/java/com/ruoyi/xxx/api/
+-- RemoteXxxService.java # @FeignClient 接口
+-- factory/ # 降级 FallbackFactory
`-- domain/ # 跨服务传输的 DTO模块依赖关系
依赖方向(自上而下引用):
| 模块 | 依赖 | 被谁依赖 |
|---|---|---|
| ruoyi-common-core | 无 ruoyi 内部依赖 | 所有模块 |
| ruoyi-common-security | core、redis | ruoyi-auth、各业务模块 |
| ruoyi-common-datasource/log/datascope/swagger/sensitive | core | ruoyi-modules-system 等业务模块 |
| ruoyi-api-system | core | 需要调 system 的模块(如 ruoyi-file) |
| ruoyi-modules-* | 对应 common-* + ruoyi-api-system | 网关路由转发 |
| ruoyi-auth | common-security + api-system | 被网关转发 |
| ruoyi-gateway | 只依赖 common-core/redis(不做业务) | 前端入口 |
运行时调用链(Feign 方向):
前端 --HTTP--> ruoyi-gateway(8080) --路由--> ruoyi-auth / ruoyi-system / ruoyi-gen / ruoyi-job / ruoyi-file
ruoyi-auth --Feign--> ruoyi-system (登录时校验用户、存登录日志)
ruoyi-system --Feign--> ruoyi-file (上传文件)
各服务 --注册/拉配置--> Nacos(8848)
各服务 --缓存--> Redis(6379) --数据--> MySQL ry-cloud(3306)要点:
- 网关不写业务代码,只做路由 + 鉴权;所有请求必须过网关(8080)。
- 服务间不直接 HTTP 调用,一律通过
ruoyi-api-*里的 Feign 接口。 - 数据库连接、Redis 等配置都在 Nacos 配置中心(ruoyi-xxx-dev.yml),不在本地 yml。
新业务模块实施计划
- 建模块骨架:新建
ruoyi-modules/ruoyi-xxx,复制ruoyi-system的 pom 结构(改artifactId、去掉不用的依赖);在根 pom.xml 的<modules>和ruoyi-modules/pom.xml中注册。 - 写业务代码:
controller/domain/mapper(+XML)/service/impl,mapper XML 放resources/mapper/xxx/。 - Nacos 配置:新增
ruoyi-xxx-dev.yml(数据源连 ry-cloud、Redis、端口),并更新sql/ry_config_*.sql。 - 网关路由 + 权限:
ruoyi-gateway-dev.yml加路由规则;菜单/按钮权限走sys_menu。 - 如需跨服务调用:新建
ruoyi-api-xxxFeign 接口模块,并在需要处引入。 - 验证:启动服务 > 注册到 Nacos > 通过网关 8080 访问接口 > 检查文档(swagger)。
前端项目整体
树形图
RuoYi-Cloud-Vue3/
+-- package.json # 依赖清单(pnpm,严格模式)
+-- vite.config.ts # 构建配置:@ 别名指向 src、代理 /prod-api > 网关 8080
+-- .env.development # VITE_APP_BASE_API=/prod-api
`-- src/ # ★★★ 前端全部代码都在这里
+-- main.ts # 入口:装配 Pinia / Router / 权限守卫
+-- App.vue
+-- permission.ts # 全局路由守卫(登录态校验 + 拉取动态路由)
+-- settings.ts # 系统全局配置
+-- api/ # ★ API 层:每个后端接口一个 TS 文件(写业务接口在这里)
| +-- login.ts # < 对应 ruoyi-auth(登录/登出/验证码)
| +-- menu.ts # < 对应 ruoyi-system 的菜单接口
| +-- system/ # < 对应后端 ruoyi-system 服务(9201)
| | +-- user.ts # 用户 CRUD
| | +-- role.ts / dept.ts / post.ts / menu.ts / config.ts
| | +-- notice.ts / dict/ / operlog.ts / logininfor.ts
| +-- monitor/ # < 对应后端 ruoyi-job(9203)等监控类
| | +-- job.ts / jobLog.ts / online.ts
| +-- tool/ # < 对应后端 ruoyi-gen 代码生成(9202)
| +-- gen.ts
+-- views/ # ★ 页面层:一个页面 = 一个目录,写页面在这里
| +-- index.vue / login.vue / register.vue / lock.vue
| +-- error/ # 401 / 404
| +-- system/ # < 对应后端 ruoyi-system
| | +-- user/ # 用户管理
| | | +-- index.vue # 列表页
| | +-- role/ dept/ menu/ dict/ config/ post/ notice/ operlog/ logininfor/
| +-- monitor/ # < 对应后端 ruoyi-job
| | +-- job/ index.vue # 定时任务
| | +-- online/
| +-- tool/
| +-- gen/ # < 对应后端 ruoyi-gen
| +-- index.vue + genInfoForm.vue + editTable.vue + importTable.vue
+-- types/ # ★ TS 类型层(本仓库是 TS 分支,与 api 一一对应)
| +-- api/
| +-- login.ts / menu.ts / common.ts
| +-- system/ ... / monitor/ ... / tool/ ...
+-- components/ # ★ 公共组件(跨页面复用才放这里)
| +-- FileUpload/ ImageUpload/ Pagination/ DictTag/ Editor/ ...
+-- store/modules/ # ★ Pinia 状态:user / permission / dict / app / tagsView ...
+-- router/index.ts # 只写"公共路由";业务路由不在这里手写(见下)
+-- directive/ # 自定义指令:hasPermi / hasRole / copyText
+-- utils/ # request.ts(axios 封装) / auth / dict / permission ...
+-- plugins/ # auth / cache / download / modal / tab
+-- layout/ # 框架布局(一般不用动)
`-- assets/ # 静态资源、svg 图标、样式后-前端对应关系
| 后端模块 | 后端端口 | 前端 API 层 | 前端页面层 | 前端类型层 |
|---|---|---|---|---|
| ruoyi-auth | 9200 | api/login.ts | views/login.vue | types/api/login.ts |
| ruoyi-system | 9201 | api/system/* | views/system/* | types/api/system/* |
| ruoyi-job | 9203 | api/monitor/job* | views/monitor/job* | types/api/monitor/* |
| ruoyi-gen | 9202 | api/tool/gen.ts | views/tool/gen/* | types/api/tool/* |
| ruoyi-file | 9300 | 无独立模块(走 components/FileUpload → /common/upload) | — | — |
| 你的新业务 | 新增服务 | 新建 src/api/xxx/ | 新建 src/views/xxx/ | 新建 src/types/api/xxx/ |
提示
注意两个"对不上"的映射:定时任务前端归在 monitor/ 下,代码生成前端归在 tool/ 下——这是若依前端的命名习惯,沿用即可。
模块依赖关系
+---------------------------- 一次请求的数据流 ----------------------------+
| |
| views/xxx/index.vue (页面) |
| | 调用 |
| ▼ |
| api/xxx.ts (接口封装) --> 依赖 utils/request.ts (axios 单例) |
| | baseURL = VITE_APP_BASE_API = /prod-api |
| ▼ |
| vite.config.ts 代理 /prod-api --> 网关 ruoyi-gateway :8080 |
| | 路由转发 + token 鉴权 |
| ▼ |
| 业务微服务 (ruoyi-system / 新服务) |
`-------------------------------------------------------------------------+
关键依赖链(代码层面):
main.ts --> App.vue --> router/index.ts (constantRoutes)
|
+-> permission.ts 路由守卫 --> store/modules/permission.ts
| | generateRoutes() 从后端拉菜单
| | getRouters() > api/menu.ts > 网关 > ruoyi-system
| |
| ▼
| 动态组件: import.meta.glob('./views/**/*.vue')
| 通过 loadView(后端菜单的 component 字段) 匹配 views 下的文件
|
`-> store/modules/user.ts --> api/login.ts (token 存取)
页面 views --依赖--> components / directives / utils / plugins / store
api/*.ts --依赖--> utils/request.ts + types/api/*.ts新业务模块
提示
(后端已有接口)前端要做的事
假设后端新增了 ruoyi-xxx 服务(如商品模块),前端只需 4 步:
写 API:新建
src/api/xxx/goods.ts,用request封装接口:import request from '@/utils/request' export function listGoods(query: any) { return request({ url: '/xxx/goods/list', method: 'get', params: query }) }写页面:新建
src/views/xxx/goods/index.vue(列表页)。配菜单(关键):在后端
sys_menu表(系统管理>菜单管理)新增菜单,component字段填xxx/goods/index——前端守卫会按这个字 符串在 views/ 下自动找到你的页面,不需要手改router/index.ts。(可选)写类型:新建
src/types/api/xxx/goods.ts定义 TS 类型;网关若没配该服务路由,需在ruoyi-gateway的配置文件里加路由 转发。
提示
补充:src/api 的 url 里 /xxx/goods/... 的 /xxx 是网关路由前缀,对应后端服务名,跟 vite.config.ts 的代理、后端 gateway 配 置保持一致即可。
RuoyiGenApplication
前端"代码生成"页面有两个列表,小心混淆:
| 界面 | 调用接口 | 查的是什么 | 为什么可能空 |
|---|---|---|---|
| 主页列表 | /tool/gen/list | 查 gen_table 表 = 已导入代码生成的表 | 从没导入过 → 天然为空 |
| "导入"按钮弹窗 | /tool/gen/db/list | 查 information_schema.tables = 数据库里还没导入的表 | 库里没有符合条件的表 / 连不上库 |
核心查询逻辑( GenTableMapper.xml 的 selectDbTableList ):
select
table_name, table_comment, create_time, update_time
from
information_schema.tables
where
table_schema = (select database()) -- 取"当前连接库"
AND table_name NOT LIKE 'qrtz\_%' -- 排除定时任务表
AND table_name NOT LIKE 'gen\_%' -- 排除代码生成自身表
AND table_name NOT IN (select table_name from gen_table) -- 排除已导入的导入流程:点"导入"勾选表 > /tool/gen/importTable > 读该表结构写入 gen_table + gen_table_column > 之后才出现在主页,可编辑配置、预览、生成代码。
身份认证
提示
项目版本:3.6.8 | 认证方案:JWT(无状态)+ Redis(有状态)双重校验,网关统一鉴权
一、整体架构
- 闭环一:首次登录 → 签发 Token
提示
登录链路:网关放行 → auth 验参数/黑名单 → Feign 内部调用 system 取用户+权限 → 验密码 → LoginUser 存 Redis → 签发 JWT 给前端。自此前端持有 JWT,Redis 持有登录态。
- 闭环二:请求身份验证(含服务间调用)
提示
验证链路:网关验 JWT 签名 + 查 Redis 登录态 → 用户身份写入请求头 → 下游服务恢复当前用户 → 注解权限校验 → 服务间 Feign 调用靠内部来源头隔离。
核心特点:JWT 只负责"身份声明",Redis 才是"登录态真身"。
- JWT 可被任何人解析(仅做签名校验),真正的会话状态存在 Redis;
- 网关每次请求都查 Redis 确认登录是否有效;
- 完整用户信息(含权限集合)只存在 Redis,不放进 JWT;
- 外部请求永远无法冒充服务间内部调用(
FROM_SOURCE头被网关移除,内部接口@InnerAuth强制校验)。
二、登录流程
重要
(ruoyi-auth,9200)
前端 POST /auth/login(TokenController.login):
- 入参校验(
SysLoginService.login)- 用户名 / 密码非空、长度范围校验;
- IP 黑名单校验
- 查 Redis 中的黑名单列表(
SYS_LOGIN_BLACKIPLIST),命中则拒绝;
- 查 Redis 中的黑名单列表(
- 远程查用户
- 通过 Feign
RemoteUserService.getUserInfo(username, INNER)调用 ruoyi-system; - 传入
SecurityConstants.INNER标记内部调用(走@InnerAuth白名单);
- 通过 Feign
- 状态校验
- 用户是否被删除(
del_flag)、是否停用(status);
- 用户是否被删除(
- 密码校验
passwordService.validate()(BCrypt 比对密文);
- 记录登录日志,返回
LoginUser(含 sysUser、roles、permissions 权限集合)。
三、签发 Token
重要
(TokenService.createToken)
String token = IdUtils.fastUUID(); // ① 生成 UUID 作为 userKey
loginUser.setToken(token);
refreshToken(loginUser); // ② LoginUser 整体存入 Redis(key: login_tokens:{userKey},30 分钟过期)
// ③ 用 JWT(HS512, secret) 封装 userKey/userId/userName 生成 access_token
rspMap.put("access_token", JwtUtils.createToken(claimsMap));
rspMap.put("expires_in", TOKEN_EXPIRE_TIME); // 返回给前端关键点:
- JWT 里只放
userKey(UUID) / userId / userName三个字段,不存密码、不存权限; - 完整的
LoginUser(含权限)存于 Redis,key 为login_tokens:{userKey}; - 签名算法 HS512,密钥
TokenConstants.SECRET。
四、请求鉴权
重要
(网关 AuthFilter,全局过滤器,order = -200)
每次请求必须经过网关,AuthFilter 做四件事:
- 白名单放行
- 路径匹配
IgnoreWhiteProperties(如/auth/login、验证码接口、/code等)直接放行;
- 路径匹配
- 取 token
- 从请求头
Authorization取,裁掉Bearer前缀(getToken);
- 从请求头
- 双重校验
JwtUtils.parseToken(token):验 JWT 签名和过期(签名错 / 过期 → 401 "令牌已过期或验证不正确");redisService.hasKey(login_tokens:{userKey}):查 Redis 登录态(不存在 → 401 "登录状态已过期");
- 透传用户信息
- 把
userKey / userId / userName写入请求头(USER_KEY、DETAILS_USER_ID、DETAILS_USERNAME); - 移除
FROM_SOURCE头(防止外部伪造内部调用来源); - 放行到下游服务。
- 把
五、下游服务取用户
重要
(HeaderInterceptor.preHandle)
每个业务模块都注册了该拦截器,收到网关转发的请求后:
- 从请求头取出
userId / userName / userKey放入SecurityContextHolder(ThreadLocal); - 若请求带 token,再按
login_tokens:{userKey}从 Redis 取完整LoginUser; - 校验是否快过期(
AuthUtil.verifyLoginUserExpire,将过期时自动续期); - 之后业务代码通过
SecurityUtils.getUserId() / getUsername() / getLoginUser()获取当前用户,无需再解 JWT。
六、权限校验
重要
(@RequiresPermissions + PreAuthorizeAspect)
- 登录时
getUserInfo返回的LoginUser中携带该用户的菜单权限集合 permissions(存于 Redis); - Controller 接口标注
@RequiresPermissions("system:user:list")等注解; PreAuthorizeAspect切面从 LoginUser 的 permissions 中校验,无权限返回 403。
七、服务间内部调用
重要
(@InnerAuth)
- 服务间 Feign 调用由
FeignRequestInterceptor自动带上FROM_SOURCE = INNER头; - 内部接口(如 system 的
getUserInfo)用@InnerAuth注解保护,InnerAuthAspect校验FROM_SOURCE头; - 网关
AuthFilter会移除外部请求伪造的FROM_SOURCE,形成闭环——外部永远调不到内部接口。
总结
提示
登录时 auth 校验密码 → 把用户身份存 Redis + 发一个只含身份的 JWT 给前端;之后前端每次带 JWT 请求,网关验 JWT 签名 + 查 Redis 登录态 → 通过后把用户信息塞进请求头转发;下游服务从请求头恢复当前用户,用 @RequiresPermissions 做权限控制;服务间调用靠 @InnerAuth + 内部请求头隔离。
常见问题排查
| 现象 | 可能原因 | 排查方向 |
|---|---|---|
| 网关报"令牌已过期或验证不正确" | JWT 签名密钥不一致 / JWT 过期 | 检查各服务 TokenConstants.SECRET 是否一致;JWT 过期时间 |
| 网关报"登录状态已过期" | Redis 中 login_tokens: 被清空 | 检查 Redis 是否重启、过期时间、login_tokens key 是否存在 |
| 接口报 403 | 用户权限集合中无对应权限 | 检查 sys_menu 权限标识、用户角色是否分配 |
| 外部请求能调到内部接口 | FROM_SOURCE 头未被移除 / @InnerAuth 缺失 | 确认网关 AuthFilter 顺序与移除逻辑、内部接口是否加注解 |
| 新服务登录态无效 | 新模块未注册 HeaderInterceptor | 确认模块是否引入 ruoyi-common-security 并启用拦截器 |
