RuoYi前后端分离改造:Vue+ElementUI集成方案
2026-02-04 04:11:14作者:咎岭娴Homer
一、改造背景与痛点分析
在传统的Web开发模式中,RuoYi框架采用SpringBoot+Thymeleaf的服务端渲染架构,这种架构在开发效率和用户体验上存在诸多痛点:
- 开发效率低下:前后端代码耦合紧密,前端开发依赖后端环境,无法实现并行开发
- 用户体验受限:页面刷新频繁,交互体验差,无法满足现代Web应用的响应式需求
- 扩展性不足:服务端渲染难以支持多端适配,移动端开发需要额外适配
- 维护成本高:前后端代码混杂,后期维护和功能迭代困难
本文将详细介绍如何将RuoYi框架改造为前后端分离架构,采用Vue+ElementUI作为前端技术栈,实现前后端解耦,提升开发效率和用户体验。
二、技术栈选型
2.1 后端技术栈
| 技术 | 版本 | 说明 |
|---|---|---|
| SpringBoot | 2.5.15 | 应用开发框架 |
| Shiro | 1.13.0 | 安全框架 |
| MyBatis | 3.5.9 | ORM框架 |
| Swagger | 3.0.0 | API文档生成工具 |
| JWT | 0.11.5 | 身份认证令牌 |
| Maven | 3.6.3 | 项目构建工具 |
2.2 前端技术栈
| 技术 | 版本 | 说明 |
|---|---|---|
| Vue | 3.2.37 | JavaScript框架 |
| Vue Router | 4.0.16 | 路由管理 |
| Vuex | 4.0.2 | 状态管理 |
| Element Plus | 2.2.17 | UI组件库 |
| Axios | 0.27.2 | HTTP客户端 |
| Vite | 3.0.7 | 前端构建工具 |
三、改造总体架构
flowchart TD
A[客户端] -->|HTTP/HTTPS| B[负载均衡]
B --> C[前端应用(Vue+ElementUI)]
C -->|RESTful API| D[后端服务(SpringBoot)]
D --> E[数据库]
D --> F[缓存]
D --> G[消息队列]
3.1 架构说明
- 客户端层:用户使用的浏览器或移动设备
- 前端应用层:基于Vue+ElementUI构建的单页应用(SPA)
- 后端服务层:提供RESTful API的SpringBoot应用
- 数据持久层:数据库、缓存等存储服务
四、后端改造步骤
4.1 添加依赖
修改pom.xml文件,添加JWT和CORS支持相关依赖:
<!-- JWT支持 -->
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-api</artifactId>
<version>0.11.5</version>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-impl</artifactId>
<version>0.11.5</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-jackson</artifactId>
<version>0.11.5</version>
<scope>runtime</scope>
</dependency>
<!-- CORS支持 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
4.2 配置CORS支持
创建CORS配置类,解决跨域问题:
package com.ruoyi.framework.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
import org.springframework.web.filter.CorsFilter;
@Configuration
public class CorsConfig {
@Bean
public CorsFilter corsFilter() {
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
CorsConfiguration config = new CorsConfiguration();
// 允许的源
config.addAllowedOriginPattern("*");
// 允许的请求头
config.addAllowedHeader("*");
// 允许的请求方法
config.addAllowedMethod("*");
// 允许携带Cookie
config.setAllowCredentials(true);
// 有效时长
config.setMaxAge(3600L);
// 配置路径
source.registerCorsConfiguration("/**", config);
return new CorsFilter(source);
}
}
4.3 集成JWT认证
4.3.1 创建JWT工具类
package com.ruoyi.common.utils.security;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import io.jsonwebtoken.security.Keys;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import java.security.Key;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
@Component
public class JwtUtils {
@Value("${jwt.secret}")
private String secret;
@Value("${jwt.expire}")
private long expire;
private Key getSigningKey() {
return Keys.hmacShaKeyFor(secret.getBytes());
}
/**
* 生成JWT令牌
*/
public String generateToken(String username) {
Date now = new Date();
Date expiration = new Date(now.getTime() + expire * 1000);
Map<String, Object> claims = new HashMap<>();
claims.put("username", username);
return Jwts.builder()
.setClaims(claims)
.setIssuedAt(now)
.setExpiration(expiration)
.signWith(getSigningKey(), SignatureAlgorithm.HS256)
.compact();
}
/**
* 解析JWT令牌
*/
public Claims parseToken(String token) {
return Jwts.parserBuilder()
.setSigningKey(getSigningKey())
.build()
.parseClaimsJws(token)
.getBody();
}
/**
* 从令牌中获取用户名
*/
public String getUsernameFromToken(String token) {
return parseToken(token).get("username", String.class);
}
/**
* 验证令牌是否过期
*/
public boolean isTokenExpired(String token) {
Date expiration = parseToken(token).getExpiration();
return expiration.before(new Date());
}
}
4.3.2 修改Shiro配置
修改ShiroConfig.java,替换传统的Session认证为JWT认证:
// 省略其他代码...
/**
* 安全管理器
*/
@Bean
public SecurityManager securityManager(UserRealm userRealm) {
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
// 设置realm
securityManager.setRealm(userRealm);
// 禁用记住我功能
securityManager.setRememberMeManager(null);
// 注入缓存管理器
securityManager.setCacheManager(getEhCacheManager());
// 禁用默认Session管理
securityManager.setSessionManager(sessionManager());
return securityManager;
}
/**
* JWT过滤器
*/
@Bean
public JwtFilter jwtFilter() {
return new JwtFilter();
}
/**
* Shiro过滤器配置
*/
@Bean
public ShiroFilterFactoryBean shiroFilterFactoryBean(SecurityManager securityManager) {
CustomShiroFilterFactoryBean shiroFilterFactoryBean = new CustomShiroFilterFactoryBean();
shiroFilterFactoryBean.setSecurityManager(securityManager);
// 匿名访问不鉴权注解列表
List<String> permitAllUrl = SpringUtils.getBean(PermitAllUrlProperties.class).getUrls();
if (StringUtils.isNotEmpty(permitAllUrl)) {
permitAllUrl.forEach(url -> filterChainDefinitionMap.put(url, "anon"));
}
// 添加JWT过滤器
Map<String, Filter> filters = new LinkedHashMap<>();
filters.put("jwt", jwtFilter());
// ... 其他过滤器
// 所有请求需要认证
filterChainDefinitionMap.put("/**", "jwt,kickout,onlineSession,syncOnlineSession,csrfValidateFilter");
shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
return shiroFilterFactoryBean;
}
4.4 改造控制器为RESTful风格
将传统的@Controller改造为@RestController,统一返回AjaxResult对象:
@RestController
@RequestMapping("/system/user")
public class SysUserController extends BaseController {
@Autowired
private ISysUserService sysUserService;
/**
* 获取用户列表
*/
@RequiresPermissions("system:user:list")
@PostMapping("/list")
public TableDataInfo list(SysUser user) {
startPage();
List<SysUser> list = sysUserService.selectSysUserList(user);
return getDataTable(list);
}
/**
* 获取用户详情
*/
@RequiresPermissions("system:user:query")
@GetMapping(value = "/{userId}")
public AjaxResult getInfo(@PathVariable Long userId) {
return success(sysUserService.selectSysUserById(userId));
}
/**
* 新增用户
*/
@RequiresPermissions("system:user:add")
@Log(title = "用户管理", businessType = BusinessType.INSERT)
@PostMapping
public AjaxResult add(@Validated @RequestBody SysUser user) {
if (UserConstants.NOT_UNIQUE.equals(sysUserService.checkUserNameUnique(user.getUserName()))) {
return error("新增用户'" + user.getUserName() + "'失败,登录账号已存在");
} else if (StringUtils.isNotEmpty(user.getPhonenumber())
&& UserConstants.NOT_UNIQUE.equals(sysUserService.checkPhoneUnique(user))) {
return error("新增用户'" + user.getUserName() + "'失败,手机号码已存在");
} else if (StringUtils.isNotEmpty(user.getEmail())
&& UserConstants.NOT_UNIQUE.equals(sysUserService.checkEmailUnique(user))) {
return error("新增用户'" + user.getUserName() + "'失败,邮箱账号已存在");
}
user.setCreateBy(getUsername());
return toAjax(sysUserService.insertSysUser(user));
}
// 其他方法...
}
五、前端项目搭建
5.1 创建Vue项目
# 使用npm创建Vue项目
npm create vite@latest ruoyi-ui -- --template vue
cd ruoyi-ui
npm install
# 安装Element Plus
npm install element-plus --save
# 安装Axios
npm install axios --save
# 安装Vue Router
npm install vue-router@4 --save
# 安装Vuex
npm install vuex@4 --save
5.2 项目目录结构
ruoyi-ui/
├── public/
├── src/
│ ├── api/ # API请求
│ ├── assets/ # 静态资源
│ ├── components/ # 公共组件
│ ├── router/ # 路由配置
│ ├── store/ # 状态管理
│ ├── styles/ # 全局样式
│ ├── utils/ # 工具函数
│ ├── views/ # 页面组件
│ ├── App.vue # 应用入口
│ └── main.js # 入口文件
├── .env.development # 开发环境配置
├── .env.production # 生产环境配置
├── package.json # 项目依赖
└── vite.config.js # Vite配置
5.3 配置Axios
创建src/utils/request.js:
import axios from 'axios';
import { ElMessage, ElMessageBox } from 'element-plus';
import store from '@/store';
import { getToken } from '@/utils/auth';
// 创建axios实例
const service = axios.create({
baseURL: import.meta.env.VITE_APP_BASE_API,
timeout: 5000
});
// 请求拦截器
service.interceptors.request.use(
config => {
if (store.getters.token) {
config.headers['Authorization'] = 'Bearer ' + getToken();
}
return config;
},
error => {
console.error('request error:', error);
return Promise.reject(error);
}
);
// 响应拦截器
service.interceptors.response.use(
response => {
const res = response.data;
// 状态码不是20000的情况
if (res.code !== 200) {
ElMessage({
message: res.msg || 'Error',
type: 'error',
duration: 5 * 1000
});
// token过期或没有权限
if (res.code === 401) {
ElMessageBox.confirm('您的登录已过期,请重新登录', '确认', {
confirmButtonText: '重新登录',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
store.dispatch('user/resetToken').then(() => {
location.reload();
});
});
}
return Promise.reject(new Error(res.msg || 'Error'));
} else {
return res;
}
},
error => {
console.error('response error:', error);
ElMessage({
message: error.message,
type: 'error',
duration: 5 * 1000
});
return Promise.reject(error);
}
);
export default service;
5.4 配置路由
创建src/router/index.js:
import { createRouter, createWebHistory } from 'vue-router';
import store from '@/store';
import { ElMessage } from 'element-plus';
// 白名单路由
const whiteList = ['/login', '/register'];
const router = createRouter({
history: createWebHistory(import.meta.env.BASE_URL),
routes: [
{
path: '/login',
name: 'login',
component: () => import('@/views/login/index.vue')
},
{
path: '/',
name: 'layout',
component: () => import('@/layout/index.vue'),
redirect: '/dashboard',
children: [
{
path: 'dashboard',
name: 'dashboard',
component: () => import('@/views/dashboard/index.vue'),
meta: { title: '首页', icon: 'dashboard' }
}
]
}
]
});
// 路由守卫
router.beforeEach(async (to, from, next) => {
// 存在token
if (store.getters.token) {
if (to.path === '/login') {
next({ path: '/' });
} else {
// 检查是否已加载用户信息
if (store.getters.name) {
next();
} else {
try {
// 获取用户信息
await store.dispatch('user/getInfo');
// 动态生成路由
const accessRoutes = await store.dispatch('permission/generateRoutes', store.getters.roles);
accessRoutes.forEach(route => {
router.addRoute(route);
});
next({ ...to, replace: true });
} catch (error) {
// 清除token并重新登录
await store.dispatch('user/resetToken');
ElMessage.error(error || 'Has Error');
next(`/login?redirect=${to.path}`);
}
}
}
} else {
// 不存在token
if (whiteList.indexOf(to.path) !== -1) {
next();
} else {
next(`/login?redirect=${to.path}`);
}
}
});
export default router;
5.5 实现登录功能
创建src/views/login/index.vue:
<template>
<div class="login-container">
<el-form ref="loginForm" :model="loginForm" :rules="loginRules" class="login-form">
<div class="title-container">
<h3 class="title">RuoYi管理系统</h3>
</div>
<el-form-item prop="username">
<el-input
v-model="loginForm.username"
placeholder="请输入用户名"
prefix-icon="User"
autocomplete="off"
/>
</el-form-item>
<el-form-item prop="password">
<el-input
v-model="loginForm.password"
type="password"
placeholder="请输入密码"
prefix-icon="Lock"
autocomplete="off"
show-password
/>
</el-form-item>
<el-form-item prop="code" v-if="captchaEnabled">
<el-input
v-model="loginForm.code"
placeholder="请输入验证码"
prefix-icon="Verification"
autocomplete="off"
style="width: 63%"
/>
<div class="login-code">
<img :src="codeUrl" @click="getCode" class="login-code-img" />
</div>
</el-form-item>
<el-checkbox v-model="loginForm.rememberMe" class="remember-me">记住我</el-checkbox>
<el-form-item>
<el-button
:loading="loading"
size="default"
type="primary"
style="width: 100%"
@click="handleLogin"
>
登录
</el-button>
</el-form-item>
</el-form>
</div>
</template>
<script setup>
import { ref, reactive, onMounted } from 'vue';
import { useStore } from 'vuex';
import { useRouter } from 'vue-router';
import { getCodeImg } from '@/api/login';
import { ElMessage } from 'element-plus';
const store = useStore();
const router = useRouter();
const loginForm = reactive({
username: '',
password: '',
code: '',
uuid: '',
rememberMe: false
});
const loginRules = reactive({
username: [{ required: true, trigger: 'blur', message: '请输入用户名' }],
password: [{ required: true, trigger: 'blur', message: '请输入密码' }],
code: [{ required:
登录后查看全文
热门项目推荐
相关项目推荐
Kimi-K2.5Kimi K2.5 是一款开源的原生多模态智能体模型,它在 Kimi-K2-Base 的基础上,通过对约 15 万亿混合视觉和文本 tokens 进行持续预训练构建而成。该模型将视觉与语言理解、高级智能体能力、即时模式与思考模式,以及对话式与智能体范式无缝融合。Python00- QQwen3-Coder-Next2026年2月4日,正式发布的Qwen3-Coder-Next,一款专为编码智能体和本地开发场景设计的开源语言模型。Python00
xw-cli实现国产算力大模型零门槛部署,一键跑通 Qwen、GLM-4.7、Minimax-2.1、DeepSeek-OCR 等模型Go06
PaddleOCR-VL-1.5PaddleOCR-VL-1.5 是 PaddleOCR-VL 的新一代进阶模型,在 OmniDocBench v1.5 上实现了 94.5% 的全新 state-of-the-art 准确率。 为了严格评估模型在真实物理畸变下的鲁棒性——包括扫描伪影、倾斜、扭曲、屏幕拍摄和光照变化——我们提出了 Real5-OmniDocBench 基准测试集。实验结果表明,该增强模型在新构建的基准测试集上达到了 SOTA 性能。此外,我们通过整合印章识别和文本检测识别(text spotting)任务扩展了模型的能力,同时保持 0.9B 的超紧凑 VLM 规模,具备高效率特性。Python00
KuiklyUI基于KMP技术的高性能、全平台开发框架,具备统一代码库、极致易用性和动态灵活性。 Provide a high-performance, full-platform development framework with unified codebase, ultimate ease of use, and dynamic flexibility. 注意:本仓库为Github仓库镜像,PR或Issue请移步至Github发起,感谢支持!Kotlin07
VLOOKVLOOK™ 是优雅好用的 Typora/Markdown 主题包和增强插件。 VLOOK™ is an elegant and practical THEME PACKAGE × ENHANCEMENT PLUGIN for Typora/Markdown.Less00
最新内容推荐
终极Emoji表情配置指南:从config.yaml到一键部署全流程如何用Aider AI助手快速开发游戏:从Pong到2048的完整指南从崩溃到重生:Anki参数重置功能深度优化方案 RuoYi-Cloud-Plus 微服务通用权限管理系统技术文档 GoldenLayout 布局配置完全指南 Tencent Cloud IM Server SDK Java 技术文档 解决JumpServer v4.10.1版本Windows发布机部署失败问题 最完整2025版!SeedVR2模型家族(3B/7B)选型与性能优化指南2025微信机器人新范式:从消息自动回复到智能助理的进化之路3分钟搞定!团子翻译器接入Gemini模型超详细指南
项目优选
收起
deepin linux kernel
C
27
11
OpenHarmony documentation | OpenHarmony开发者文档
Dockerfile
525
3.72 K
Ascend Extension for PyTorch
Python
331
395
本项目是CANN提供的数学类基础计算算子库,实现网络在NPU上加速计算。
C++
878
586
openEuler内核是openEuler操作系统的核心,既是系统性能与稳定性的基石,也是连接处理器、设备与服务的桥梁。
C
336
165
暂无简介
Dart
766
189
Nop Platform 2.0是基于可逆计算理论实现的采用面向语言编程范式的新一代低代码开发平台,包含基于全新原理从零开始研发的GraphQL引擎、ORM引擎、工作流引擎、报表引擎、规则引擎、批处理引引擎等完整设计。nop-entropy是它的后端部分,采用java语言实现,可选择集成Spring框架或者Quarkus框架。中小企业可以免费商用
Java
12
1
🎉 (RuoYi)官方仓库 基于SpringBoot,Spring Security,JWT,Vue3 & Vite、Element Plus 的前后端分离权限管理系统
Vue
1.33 K
747
🔥LeetCode solutions in any programming language | 多种编程语言实现 LeetCode、《剑指 Offer(第 2 版)》、《程序员面试金典(第 6 版)》题解
Java
67
20
React Native鸿蒙化仓库
JavaScript
302
352