- 添加CLAUDE.md项目配置指南 - 添加document目录包含详细的架构分析文档 - 添加doc目录包含环境使用手册 - 添加bin目录包含构建和清理脚本 - 添加.github配置文件
1095 lines
31 KiB
Markdown
1095 lines
31 KiB
Markdown
# RuoYi-Vue 技术栈详细分析报告
|
||
|
||
## 1. 技术栈概览
|
||
|
||
RuoYi-Vue 采用前后端分离架构,技术栈选型遵循**主流、稳定、高效**的原则,覆盖了从前端展示到后端服务、从数据存储到系统监控的完整技术体系。
|
||
|
||
### 1.1 技术栈全景图
|
||
|
||
```
|
||
┌─────────────────────────────────────────────────────────────────┐
|
||
│ 前端技术栈 │
|
||
│ Vue.js 2.6 + Vue Router + Vuex + Element UI + Axios │
|
||
└─────────────────────────────────────────────────────────────────┘
|
||
↕ HTTP/JSON
|
||
┌─────────────────────────────────────────────────────────────────┐
|
||
│ 后端技术栈 │
|
||
│ Spring Boot 2.5 + Spring Security + MyBatis + JWT │
|
||
└─────────────────────────────────────────────────────────────────┘
|
||
↕ JDBC/Redis Protocol
|
||
┌─────────────────────────────────────────────────────────────────┐
|
||
│ 数据存储技术栈 │
|
||
│ MySQL 5.7+ + Redis 6.x + Druid │
|
||
└─────────────────────────────────────────────────────────────────┘
|
||
```
|
||
|
||
## 2. 后端技术栈深度分析
|
||
|
||
### 2.1 Spring Boot 2.5.15
|
||
|
||
#### 选型理由
|
||
- **快速开发**:自动配置机制大大减少配置工作量
|
||
- **生态完善**:Spring生态系统成熟,组件丰富
|
||
- **微服务就绪**:为后续微服务改造提供基础
|
||
- **运维友好**:内嵌容器,支持健康检查和监控
|
||
|
||
#### 核心特性应用
|
||
|
||
**1. 自动配置(Auto Configuration)**
|
||
```java
|
||
@SpringBootApplication
|
||
@EnableConfigurationProperties({RuoYiConfig.class})
|
||
public class RuoYiApplication {
|
||
public static void main(String[] args) {
|
||
SpringApplication.run(RuoYiApplication.class, args);
|
||
}
|
||
}
|
||
```
|
||
|
||
**2. 配置属性绑定**
|
||
```yaml
|
||
# application.yml
|
||
ruoyi:
|
||
name: RuoYi
|
||
version: 3.9.0
|
||
copyrightYear: 2024
|
||
profile: /home/ruoyi/uploadPath
|
||
addressEnabled: false
|
||
```
|
||
|
||
**3. 条件化配置**
|
||
```java
|
||
@Configuration
|
||
@ConditionalOnProperty(prefix = "ruoyi", name = "redis", havingValue = "true")
|
||
public class RedisConfig {
|
||
// Redis配置类
|
||
}
|
||
```
|
||
|
||
#### 性能优化配置
|
||
|
||
**JVM参数优化**
|
||
```bash
|
||
java -Xms512m -Xmx2048m -XX:MetaspaceSize=128m -XX:MaxMetaspaceSize=512m \
|
||
-XX:+UseG1GC -XX:G1HeapRegionSize=16m -XX:+UseStringDeduplication \
|
||
-jar ruoyi-admin.jar
|
||
```
|
||
|
||
**连接池配置优化**
|
||
```yaml
|
||
server:
|
||
tomcat:
|
||
threads:
|
||
max: 800
|
||
min-spare: 100
|
||
max-connections: 10000
|
||
accept-count: 1000
|
||
connection-timeout: 20000
|
||
```
|
||
|
||
### 2.2 Spring Security 5.5.x
|
||
|
||
#### 选型理由
|
||
- **企业级安全**:提供完善的认证和授权机制
|
||
- **灵活配置**:支持多种认证方式和授权策略
|
||
- **防护全面**:内置CSRF、XSS等安全防护
|
||
- **标准兼容**:支持OAuth2、JWT等标准协议
|
||
|
||
#### 核心安全配置
|
||
|
||
**1. 安全配置类**
|
||
```java
|
||
@Configuration
|
||
@EnableWebSecurity
|
||
@EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true)
|
||
public class SecurityConfig extends WebSecurityConfigurerAdapter {
|
||
|
||
@Override
|
||
protected void configure(HttpSecurity http) throws Exception {
|
||
http
|
||
.csrf().disable()
|
||
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
|
||
.and()
|
||
.authorizeRequests()
|
||
.antMatchers("/login", "/register").permitAll()
|
||
.anyRequest().authenticated()
|
||
.and()
|
||
.addFilterBefore(jwtAuthenticationTokenFilter, UsernamePasswordAuthenticationFilter.class);
|
||
}
|
||
}
|
||
```
|
||
|
||
**2. JWT认证实现**
|
||
```java
|
||
@Component
|
||
public class JwtAuthenticationTokenFilter extends OncePerRequestFilter {
|
||
@Override
|
||
protected void doFilterInternal(HttpServletRequest request,
|
||
HttpServletResponse response,
|
||
FilterChain chain) throws ServletException, IOException {
|
||
String token = getToken(request);
|
||
if (StringUtils.isNotEmpty(token) && SecurityUtils.getAuthentication() == null) {
|
||
Claims claims = tokenService.parseToken(token);
|
||
// 验证token有效性并设置认证信息
|
||
UsernamePasswordAuthenticationToken authToken =
|
||
new UsernamePasswordAuthenticationToken(loginUser, null, authorities);
|
||
SecurityContextHolder.getContext().setAuthentication(authToken);
|
||
}
|
||
chain.doFilter(request, response);
|
||
}
|
||
}
|
||
```
|
||
|
||
**3. 权限控制注解**
|
||
```java
|
||
@PreAuthorize("@ss.hasPermi('system:user:add')")
|
||
@Log(title = "用户管理", businessType = BusinessType.INSERT)
|
||
@PostMapping
|
||
public AjaxResult add(@Validated @RequestBody SysUser user) {
|
||
return userService.insertUser(user);
|
||
}
|
||
```
|
||
|
||
#### 安全机制详解
|
||
|
||
| 安全特性 | 实现方式 | 应用场景 |
|
||
|----------|----------|----------|
|
||
| 身份认证 | JWT Token | 用户登录验证 |
|
||
| 权限控制 | RBAC + 注解 | 接口级权限控制 |
|
||
| 数据权限 | @DataScope | 行级数据权限 |
|
||
| CSRF防护 | Token验证 | 表单提交防护 |
|
||
| 密码加密 | BCrypt | 密码存储加密 |
|
||
| 登录限制 | 失败次数控制 | 暴力破解防护 |
|
||
|
||
### 2.3 MyBatis 3.5.x
|
||
|
||
#### 选型理由
|
||
- **SQL可控**:开发者可以完全控制SQL语句
|
||
- **性能优秀**:直接JDBC封装,性能接近原生
|
||
- **学习成本低**:相比JPA,学习曲线平缓
|
||
- **灵活性强**:支持复杂查询和动态SQL
|
||
|
||
#### 核心配置与应用
|
||
|
||
**1. MyBatis配置**
|
||
```yaml
|
||
mybatis:
|
||
mapper-locations: classpath*:mapper/**/*Mapper.xml
|
||
type-aliases-package: com.ruoyi.**.domain
|
||
configuration:
|
||
map-underscore-to-camel-case: true
|
||
cache-enabled: true
|
||
lazy-loading-enabled: true
|
||
multiple-result-sets-enabled: true
|
||
use-generated-keys: true
|
||
default-executor-type: reuse
|
||
default-statement-timeout: 600
|
||
```
|
||
|
||
**2. 分页插件配置**
|
||
```java
|
||
@Configuration
|
||
public class MybatisConfig {
|
||
@Bean
|
||
public PageHelper pageHelper() {
|
||
PageHelper pageHelper = new PageHelper();
|
||
Properties properties = new Properties();
|
||
properties.setProperty("offsetAsPageNum", "true");
|
||
properties.setProperty("rowBoundsWithCount", "true");
|
||
properties.setProperty("reasonable", "true");
|
||
properties.setProperty("dialect", "mysql");
|
||
pageHelper.setProperties(properties);
|
||
return pageHelper;
|
||
}
|
||
}
|
||
```
|
||
|
||
**3. 动态SQL示例**
|
||
```xml
|
||
<select id="selectUserList" parameterType="SysUser" resultMap="SysUserResult">
|
||
select distinct u.user_id, u.dept_id, u.user_name, u.nick_name
|
||
from sys_user u
|
||
left join sys_dept d on u.dept_id = d.dept_id
|
||
left join sys_user_role ur on u.user_id = ur.user_id
|
||
left join sys_role r on r.role_id = ur.role_id
|
||
where u.del_flag = '0'
|
||
<if test="userId != null and userId != 0">
|
||
AND u.user_id = #{userId}
|
||
</if>
|
||
<if test="userName != null and userName != ''">
|
||
AND u.user_name like concat('%', #{userName}, '%')
|
||
</if>
|
||
<if test="status != null and status != ''">
|
||
AND u.status = #{status}
|
||
</if>
|
||
<if test="phonenumber != null and phonenumber != ''">
|
||
AND u.phonenumber like concat('%', #{phonenumber}, '%')
|
||
</if>
|
||
<!-- 数据范围过滤 -->
|
||
${params.dataScope}
|
||
order by u.create_time desc
|
||
</select>
|
||
```
|
||
|
||
### 2.4 MySQL 5.7+ 数据库
|
||
|
||
#### 选型理由
|
||
- **成熟稳定**:经过多年生产环境验证
|
||
- **性能优秀**:查询优化器和存储引擎先进
|
||
- **生态完善**:工具链和社区支持完善
|
||
- **成本可控**:开源免费,运维成本低
|
||
|
||
#### 数据库设计特点
|
||
|
||
**1. 表结构设计**
|
||
```sql
|
||
-- 用户表设计示例
|
||
CREATE TABLE `sys_user` (
|
||
`user_id` bigint NOT NULL AUTO_INCREMENT COMMENT '用户ID',
|
||
`dept_id` bigint DEFAULT NULL COMMENT '部门ID',
|
||
`user_name` varchar(30) NOT NULL COMMENT '用户账号',
|
||
`nick_name` varchar(30) NOT NULL COMMENT '用户昵称',
|
||
`user_type` varchar(2) DEFAULT '00' COMMENT '用户类型',
|
||
`email` varchar(50) DEFAULT '' COMMENT '用户邮箱',
|
||
`phonenumber` varchar(11) DEFAULT '' COMMENT '手机号码',
|
||
`sex` char(1) DEFAULT '0' COMMENT '用户性别',
|
||
`avatar` varchar(100) DEFAULT '' COMMENT '头像地址',
|
||
`password` varchar(100) DEFAULT '' COMMENT '密码',
|
||
`status` char(1) DEFAULT '0' COMMENT '帐号状态(0正常 1停用)',
|
||
`del_flag` char(1) DEFAULT '0' COMMENT '删除标志(0代表存在 2代表删除)',
|
||
`login_ip` varchar(128) DEFAULT '' COMMENT '最后登录IP',
|
||
`login_date` datetime DEFAULT NULL COMMENT '最后登录时间',
|
||
`create_by` varchar(64) DEFAULT '' COMMENT '创建者',
|
||
`create_time` datetime DEFAULT NULL COMMENT '创建时间',
|
||
`update_by` varchar(64) DEFAULT '' COMMENT '更新者',
|
||
`update_time` datetime DEFAULT NULL COMMENT '更新时间',
|
||
`remark` varchar(500) DEFAULT NULL COMMENT '备注',
|
||
PRIMARY KEY (`user_id`)
|
||
) ENGINE=InnoDB COMMENT='用户信息表';
|
||
```
|
||
|
||
**2. 索引优化策略**
|
||
```sql
|
||
-- 常用查询索引
|
||
ALTER TABLE sys_user ADD INDEX idx_user_name (user_name);
|
||
ALTER TABLE sys_user ADD INDEX idx_dept_id (dept_id);
|
||
ALTER TABLE sys_user ADD INDEX idx_status (status);
|
||
ALTER TABLE sys_user ADD INDEX idx_create_time (create_time);
|
||
|
||
-- 复合索引
|
||
ALTER TABLE sys_user ADD INDEX idx_dept_status (dept_id, status);
|
||
```
|
||
|
||
### 2.5 Redis 6.x 缓存
|
||
|
||
#### 选型理由
|
||
- **高性能**:内存存储,响应时间微秒级
|
||
- **数据结构丰富**:支持字符串、哈希、列表等多种数据类型
|
||
- **持久化支持**:RDB和AOF双重保障
|
||
- **集群支持**:支持主从复制和集群模式
|
||
|
||
#### 应用场景分析
|
||
|
||
**1. 缓存配置**
|
||
```yaml
|
||
# Redis配置
|
||
spring:
|
||
redis:
|
||
host: 127.0.0.1
|
||
port: 6379
|
||
database: 0
|
||
password:
|
||
timeout: 10s
|
||
lettuce:
|
||
pool:
|
||
min-idle: 0
|
||
max-idle: 8
|
||
max-active: 8
|
||
max-wait: -1ms
|
||
```
|
||
|
||
**2. 缓存应用实例**
|
||
```java
|
||
@Service
|
||
public class SysUserServiceImpl implements ISysUserService {
|
||
|
||
@Autowired
|
||
private RedisCache redisCache;
|
||
|
||
@Override
|
||
@Cacheable(cacheNames = "user", key = "#userId")
|
||
public SysUser selectUserById(Long userId) {
|
||
return userMapper.selectUserById(userId);
|
||
}
|
||
|
||
@Override
|
||
@CacheEvict(cacheNames = "user", key = "#user.userId")
|
||
public int updateUser(SysUser user) {
|
||
return userMapper.updateUser(user);
|
||
}
|
||
}
|
||
```
|
||
|
||
**3. 分布式锁实现**
|
||
```java
|
||
@Component
|
||
public class RedisLock {
|
||
|
||
@Autowired
|
||
private StringRedisTemplate redisTemplate;
|
||
|
||
public boolean tryLock(String key, String value, long timeout, TimeUnit unit) {
|
||
Boolean success = redisTemplate.opsForValue()
|
||
.setIfAbsent(key, value, timeout, unit);
|
||
return success != null && success;
|
||
}
|
||
|
||
public void unlock(String key, String value) {
|
||
String script = "if redis.call('get', KEYS[1]) == ARGV[1] then " +
|
||
"return redis.call('del', KEYS[1]) else return 0 end";
|
||
redisTemplate.execute(new DefaultRedisScript<>(script, Long.class),
|
||
Arrays.asList(key), value);
|
||
}
|
||
}
|
||
```
|
||
|
||
### 2.6 Druid 1.2.x 连接池
|
||
|
||
#### 选型理由
|
||
- **性能优越**:高效的连接池实现
|
||
- **监控功能**:提供详细的SQL执行监控
|
||
- **安全防护**:内置SQL注入检测和防护
|
||
- **配置灵活**:丰富的配置选项和扩展点
|
||
|
||
#### 核心配置
|
||
|
||
```yaml
|
||
spring:
|
||
datasource:
|
||
type: com.alibaba.druid.pool.DruidDataSource
|
||
druid:
|
||
# 初始连接数
|
||
initial-size: 5
|
||
# 最小连接池数量
|
||
min-idle: 10
|
||
# 最大连接池数量
|
||
max-active: 20
|
||
# 配置获取连接等待超时的时间
|
||
max-wait: 60000
|
||
# 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒
|
||
time-between-eviction-runs-millis: 60000
|
||
# 配置一个连接在池中最小生存的时间,单位是毫秒
|
||
min-evictable-idle-time-millis: 300000
|
||
# 配置一个连接在池中最大生存的时间,单位是毫秒
|
||
max-evictable-idle-time-millis: 900000
|
||
# 配置检测连接是否有效
|
||
validation-query: SELECT 1 FROM DUAL
|
||
test-while-idle: true
|
||
test-on-borrow: false
|
||
test-on-return: false
|
||
# 打开PSCache,并且指定每个连接上PSCache的大小
|
||
pool-prepared-statements: true
|
||
max-pool-prepared-statement-per-connection-size: 20
|
||
# 配置监控统计拦截的filters
|
||
filters: stat,wall,config
|
||
# 通过connectProperties属性来打开mergeSql功能;慢SQL记录
|
||
connection-properties: druid.stat.mergeSql\\=true;druid.stat.slowSqlMillis\\=5000
|
||
# 配置DruidStatFilter
|
||
web-stat-filter:
|
||
enabled: true
|
||
url-pattern: "/*"
|
||
exclusions: "*.js,*.gif,*.jpg,*.bmp,*.png,*.css,*.ico,/druid/*"
|
||
# 配置DruidStatViewServlet
|
||
stat-view-servlet:
|
||
enabled: true
|
||
url-pattern: "/druid/*"
|
||
# 允许访问的IP白名单
|
||
allow: 127.0.0.1,192.168.163.1
|
||
# 禁止访问的IP黑名单
|
||
deny: 192.168.1.73
|
||
# 登录名
|
||
login-username: admin
|
||
# 登录密码
|
||
login-password: 123456
|
||
```
|
||
|
||
## 3. 前端技术栈深度分析
|
||
|
||
### 3.1 Vue.js 2.6.12
|
||
|
||
#### 选型理由
|
||
- **渐进式框架**:可以逐步采用,不需要全盘重构
|
||
- **学习曲线平缓**:相比React和Angular更容易上手
|
||
- **生态完善**:组件库、工具链和社区支持完善
|
||
- **性能优秀**:虚拟DOM和响应式系统性能优越
|
||
|
||
#### 核心特性应用
|
||
|
||
**1. 组件化开发**
|
||
```vue
|
||
<template>
|
||
<div class="user-list">
|
||
<el-table :data="userList" style="width: 100%">
|
||
<el-table-column prop="userName" label="用户名称" />
|
||
<el-table-column prop="nickName" label="用户昵称" />
|
||
<el-table-column prop="status" label="状态">
|
||
<template slot-scope="scope">
|
||
<el-tag :type="scope.row.status === '0' ? 'success' : 'danger'">
|
||
{{ scope.row.status === '0' ? '正常' : '停用' }}
|
||
</el-tag>
|
||
</template>
|
||
</el-table-column>
|
||
</el-table>
|
||
</div>
|
||
</template>
|
||
|
||
<script>
|
||
import { listUser } from "@/api/system/user";
|
||
|
||
export default {
|
||
name: "UserList",
|
||
data() {
|
||
return {
|
||
userList: []
|
||
};
|
||
},
|
||
created() {
|
||
this.getList();
|
||
},
|
||
methods: {
|
||
getList() {
|
||
listUser().then(response => {
|
||
this.userList = response.rows;
|
||
});
|
||
}
|
||
}
|
||
};
|
||
</script>
|
||
```
|
||
|
||
**2. 响应式数据绑定**
|
||
```javascript
|
||
// 数据响应式
|
||
data() {
|
||
return {
|
||
form: {
|
||
userName: '',
|
||
nickName: '',
|
||
status: '0'
|
||
},
|
||
rules: {
|
||
userName: [
|
||
{ required: true, message: "用户名不能为空", trigger: "blur" },
|
||
{ min: 2, max: 20, message: '用户名长度必须介于 2 和 20 之间', trigger: 'blur' }
|
||
]
|
||
}
|
||
};
|
||
},
|
||
computed: {
|
||
// 计算属性
|
||
isEdit() {
|
||
return this.form.userId != null;
|
||
}
|
||
}
|
||
```
|
||
|
||
### 3.2 Element UI 2.15.14
|
||
|
||
#### 选型理由
|
||
- **企业级UI**:专为后台管理系统设计
|
||
- **组件丰富**:提供60+高质量组件
|
||
- **设计统一**:遵循统一的设计语言
|
||
- **文档完善**:详细的API文档和示例
|
||
|
||
#### 核心组件应用
|
||
|
||
**1. 表格组件高级用法**
|
||
```vue
|
||
<template>
|
||
<el-table
|
||
:data="tableData"
|
||
style="width: 100%"
|
||
@selection-change="handleSelectionChange"
|
||
@sort-change="handleSortChange">
|
||
|
||
<el-table-column type="selection" width="55" />
|
||
<el-table-column prop="userName" label="用户名称" sortable="custom" />
|
||
<el-table-column prop="nickName" label="用户昵称" />
|
||
<el-table-column prop="dept.deptName" label="部门" />
|
||
<el-table-column prop="status" label="状态">
|
||
<template slot-scope="scope">
|
||
<el-switch
|
||
v-model="scope.row.status"
|
||
active-value="0"
|
||
inactive-value="1"
|
||
@change="handleStatusChange(scope.row)" />
|
||
</template>
|
||
</el-table-column>
|
||
<el-table-column label="操作" width="180">
|
||
<template slot-scope="scope">
|
||
<el-button size="mini" @click="handleUpdate(scope.row)">修改</el-button>
|
||
<el-button size="mini" type="danger" @click="handleDelete(scope.row)">删除</el-button>
|
||
</template>
|
||
</el-table-column>
|
||
</el-table>
|
||
</template>
|
||
```
|
||
|
||
**2. 表单验证**
|
||
```vue
|
||
<el-form ref="form" :model="form" :rules="rules" label-width="80px">
|
||
<el-form-item label="用户名称" prop="userName">
|
||
<el-input v-model="form.userName" placeholder="请输入用户名称" />
|
||
</el-form-item>
|
||
<el-form-item label="用户昵称" prop="nickName">
|
||
<el-input v-model="form.nickName" placeholder="请输入用户昵称" />
|
||
</el-form-item>
|
||
<el-form-item label="用户性别" prop="sex">
|
||
<el-select v-model="form.sex" placeholder="请选择性别">
|
||
<el-option label="男" value="0" />
|
||
<el-option label="女" value="1" />
|
||
<el-option label="未知" value="2" />
|
||
</el-select>
|
||
</el-form-item>
|
||
</el-form>
|
||
```
|
||
|
||
### 3.3 Vue Router 3.x
|
||
|
||
#### 路由配置策略
|
||
|
||
**1. 动态路由配置**
|
||
```javascript
|
||
// router/index.js
|
||
import Vue from 'vue'
|
||
import Router from 'vue-router'
|
||
import Layout from '@/layout'
|
||
|
||
Vue.use(Router)
|
||
|
||
// 静态路由
|
||
export const constantRoutes = [
|
||
{
|
||
path: '/login',
|
||
component: () => import('@/views/login/index'),
|
||
hidden: true
|
||
},
|
||
{
|
||
path: '/',
|
||
component: Layout,
|
||
redirect: '/dashboard',
|
||
children: [
|
||
{
|
||
path: 'dashboard',
|
||
component: () => import('@/views/dashboard/index'),
|
||
name: 'Dashboard',
|
||
meta: { title: '首页', icon: 'dashboard', affix: true }
|
||
}
|
||
]
|
||
}
|
||
]
|
||
|
||
// 动态路由
|
||
export const asyncRoutes = [
|
||
{
|
||
path: '/system',
|
||
component: Layout,
|
||
name: 'System',
|
||
meta: {
|
||
title: '系统管理',
|
||
icon: 'system'
|
||
},
|
||
children: [
|
||
{
|
||
path: 'user',
|
||
component: () => import('@/views/system/user/index'),
|
||
name: 'User',
|
||
meta: { title: '用户管理', icon: 'user' }
|
||
},
|
||
{
|
||
path: 'role',
|
||
component: () => import('@/views/system/role/index'),
|
||
name: 'Role',
|
||
meta: { title: '角色管理', icon: 'peoples' }
|
||
}
|
||
]
|
||
}
|
||
]
|
||
|
||
const createRouter = () => new Router({
|
||
mode: 'history',
|
||
scrollBehavior: () => ({ y: 0 }),
|
||
routes: constantRoutes
|
||
})
|
||
|
||
const router = createRouter()
|
||
|
||
export function resetRouter() {
|
||
const newRouter = createRouter()
|
||
router.matcher = newRouter.matcher
|
||
}
|
||
|
||
export default router
|
||
```
|
||
|
||
**2. 路由守卫**
|
||
```javascript
|
||
// permission.js
|
||
import router from './router'
|
||
import store from './store'
|
||
import { Message } from 'element-ui'
|
||
import NProgress from 'nprogress'
|
||
import 'nprogress/nprogress.css'
|
||
import { getToken } from '@/utils/auth'
|
||
|
||
NProgress.configure({ showSpinner: false })
|
||
|
||
const whiteList = ['/login', '/register']
|
||
|
||
router.beforeEach(async(to, from, next) => {
|
||
NProgress.start()
|
||
|
||
if (getToken()) {
|
||
if (to.path === '/login') {
|
||
next({ path: '/' })
|
||
NProgress.done()
|
||
} else {
|
||
if (store.getters.roles.length === 0) {
|
||
try {
|
||
await store.dispatch('GetInfo')
|
||
const accessRoutes = await store.dispatch('GenerateRoutes')
|
||
router.addRoutes(accessRoutes)
|
||
next({ ...to, replace: true })
|
||
} catch (error) {
|
||
await store.dispatch('LogOut')
|
||
Message.error(error || 'Has Error')
|
||
next(`/login?redirect=${to.path}`)
|
||
NProgress.done()
|
||
}
|
||
} else {
|
||
next()
|
||
}
|
||
}
|
||
} else {
|
||
if (whiteList.indexOf(to.path) !== -1) {
|
||
next()
|
||
} else {
|
||
next(`/login?redirect=${to.path}`)
|
||
NProgress.done()
|
||
}
|
||
}
|
||
})
|
||
|
||
router.afterEach(() => {
|
||
NProgress.done()
|
||
})
|
||
```
|
||
|
||
### 3.4 Vuex 3.x 状态管理
|
||
|
||
#### 状态管理架构
|
||
|
||
**1. Store结构**
|
||
```javascript
|
||
// store/index.js
|
||
import Vue from 'vue'
|
||
import Vuex from 'vuex'
|
||
import app from './modules/app'
|
||
import user from './modules/user'
|
||
import permission from './modules/permission'
|
||
|
||
Vue.use(Vuex)
|
||
|
||
const store = new Vuex.Store({
|
||
modules: {
|
||
app,
|
||
user,
|
||
permission
|
||
},
|
||
getters: {
|
||
sidebar: state => state.app.sidebar,
|
||
device: state => state.app.device,
|
||
token: state => state.user.token,
|
||
avatar: state => state.user.avatar,
|
||
name: state => state.user.name,
|
||
roles: state => state.user.roles,
|
||
permissions: state => state.user.permissions,
|
||
permission_routes: state => state.permission.routes
|
||
}
|
||
})
|
||
|
||
export default store
|
||
```
|
||
|
||
**2. 用户模块**
|
||
```javascript
|
||
// store/modules/user.js
|
||
import { login, logout, getInfo } from '@/api/login'
|
||
import { getToken, setToken, removeToken } from '@/utils/auth'
|
||
|
||
const user = {
|
||
state: {
|
||
token: getToken(),
|
||
name: '',
|
||
avatar: '',
|
||
roles: [],
|
||
permissions: []
|
||
},
|
||
|
||
mutations: {
|
||
SET_TOKEN: (state, token) => {
|
||
state.token = token
|
||
},
|
||
SET_NAME: (state, name) => {
|
||
state.name = name
|
||
},
|
||
SET_AVATAR: (state, avatar) => {
|
||
state.avatar = avatar
|
||
},
|
||
SET_ROLES: (state, roles) => {
|
||
state.roles = roles
|
||
},
|
||
SET_PERMISSIONS: (state, permissions) => {
|
||
state.permissions = permissions
|
||
}
|
||
},
|
||
|
||
actions: {
|
||
// 登录
|
||
Login({ commit }, userInfo) {
|
||
const username = userInfo.username.trim()
|
||
const password = userInfo.password
|
||
const code = userInfo.code
|
||
const uuid = userInfo.uuid
|
||
return new Promise((resolve, reject) => {
|
||
login(username, password, code, uuid).then(res => {
|
||
setToken(res.token)
|
||
commit('SET_TOKEN', res.token)
|
||
resolve()
|
||
}).catch(error => {
|
||
reject(error)
|
||
})
|
||
})
|
||
},
|
||
|
||
// 获取用户信息
|
||
GetInfo({ commit, state }) {
|
||
return new Promise((resolve, reject) => {
|
||
getInfo().then(res => {
|
||
const user = res.user
|
||
const avatar = user.avatar == null || user.avatar === "" ? require("@/assets/images/profile.jpg") : user.avatar
|
||
|
||
if (res.roles && res.roles.length > 0) {
|
||
commit('SET_ROLES', res.roles)
|
||
commit('SET_PERMISSIONS', res.permissions)
|
||
} else {
|
||
commit('SET_ROLES', ['ROLE_DEFAULT'])
|
||
}
|
||
commit('SET_NAME', user.userName)
|
||
commit('SET_AVATAR', avatar)
|
||
resolve(res)
|
||
}).catch(error => {
|
||
reject(error)
|
||
})
|
||
})
|
||
}
|
||
}
|
||
}
|
||
|
||
export default user
|
||
```
|
||
|
||
### 3.5 Axios HTTP客户端
|
||
|
||
#### 请求拦截器配置
|
||
|
||
```javascript
|
||
// utils/request.js
|
||
import axios from 'axios'
|
||
import { MessageBox, Message, Notification } from 'element-ui'
|
||
import store from '@/store'
|
||
import { getToken } from '@/utils/auth'
|
||
|
||
// 创建axios实例
|
||
const service = axios.create({
|
||
baseURL: process.env.VUE_APP_BASE_API,
|
||
timeout: 10000
|
||
})
|
||
|
||
// 请求拦截器
|
||
service.interceptors.request.use(
|
||
config => {
|
||
// 是否需要设置 token
|
||
const isToken = (config.headers || {}).isToken === false
|
||
if (getToken() && !isToken) {
|
||
config.headers['Authorization'] = 'Bearer ' + getToken()
|
||
}
|
||
|
||
// get请求映射params参数
|
||
if (config.method === 'get' && config.params) {
|
||
let url = config.url + '?' + tansParams(config.params)
|
||
url = url.slice(0, -1)
|
||
config.params = {}
|
||
config.url = url
|
||
}
|
||
|
||
return config
|
||
},
|
||
error => {
|
||
console.log(error)
|
||
Promise.reject(error)
|
||
}
|
||
)
|
||
|
||
// 响应拦截器
|
||
service.interceptors.response.use(
|
||
res => {
|
||
const code = res.data.code || 200
|
||
const msg = errorCode[code] || res.data.msg || errorCode['default']
|
||
|
||
if (code === 401) {
|
||
MessageBox.confirm('登录状态已过期,您可以继续留在该页面,或者重新登录', '系统提示', {
|
||
confirmButtonText: '重新登录',
|
||
cancelButtonText: '取消',
|
||
type: 'warning'
|
||
}).then(() => {
|
||
store.dispatch('LogOut').then(() => {
|
||
location.href = '/index'
|
||
})
|
||
})
|
||
return Promise.reject('无效的会话,或者会话已过期,请重新登录。')
|
||
} else if (code === 500) {
|
||
Message({
|
||
message: msg,
|
||
type: 'error'
|
||
})
|
||
return Promise.reject(new Error(msg))
|
||
} else if (code !== 200) {
|
||
Notification.error({
|
||
title: msg
|
||
})
|
||
return Promise.reject('error')
|
||
} else {
|
||
return res.data
|
||
}
|
||
},
|
||
error => {
|
||
console.log('err' + error)
|
||
let { message } = error
|
||
if (message === 'Network Error') {
|
||
message = '后端接口连接异常'
|
||
} else if (message.includes('timeout')) {
|
||
message = '系统接口请求超时'
|
||
} else if (message.includes('Request failed with status code')) {
|
||
message = '系统接口' + message.substr(message.length - 3) + '异常'
|
||
}
|
||
Message({
|
||
message: message,
|
||
type: 'error',
|
||
duration: 5 * 1000
|
||
})
|
||
return Promise.reject(error)
|
||
}
|
||
)
|
||
|
||
export default service
|
||
```
|
||
|
||
## 4. 构建工具链分析
|
||
|
||
### 4.1 Maven构建系统
|
||
|
||
#### 项目结构配置
|
||
```xml
|
||
<!-- pom.xml -->
|
||
<?xml version="1.0" encoding="UTF-8"?>
|
||
<project xmlns="http://maven.apache.org/POM/4.0.0">
|
||
<modelVersion>4.0.0</modelVersion>
|
||
|
||
<groupId>com.ruoyi</groupId>
|
||
<artifactId>ruoyi</artifactId>
|
||
<version>3.9.0</version>
|
||
<packaging>pom</packaging>
|
||
|
||
<name>ruoyi</name>
|
||
<description>若依管理系统</description>
|
||
|
||
<properties>
|
||
<ruoyi.version>3.9.0</ruoyi.version>
|
||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
|
||
<java.version>1.8</java.version>
|
||
<maven-jar-plugin.version>3.1.1</maven-jar-plugin.version>
|
||
<spring-boot.version>2.5.15</spring-boot.version>
|
||
<spring-framework.version>5.3.21</spring-framework.version>
|
||
</properties>
|
||
|
||
<modules>
|
||
<module>ruoyi-admin</module>
|
||
<module>ruoyi-framework</module>
|
||
<module>ruoyi-system</module>
|
||
<module>ruoyi-quartz</module>
|
||
<module>ruoyi-generator</module>
|
||
<module>ruoyi-common</module>
|
||
</modules>
|
||
</project>
|
||
```
|
||
|
||
### 4.2 Vue CLI构建配置
|
||
|
||
```javascript
|
||
// vue.config.js
|
||
'use strict'
|
||
const path = require('path')
|
||
|
||
function resolve(dir) {
|
||
return path.join(__dirname, dir)
|
||
}
|
||
|
||
const CompressionPlugin = require('compression-webpack-plugin')
|
||
|
||
const name = process.env.VUE_APP_TITLE || '若依管理系统'
|
||
const port = process.env.port || process.env.npm_config_port || 80
|
||
|
||
module.exports = {
|
||
publicPath: process.env.NODE_ENV === "production" ? "/" : "/",
|
||
outputDir: 'dist',
|
||
assetsDir: 'static',
|
||
lintOnSave: process.env.NODE_ENV === 'development',
|
||
productionSourceMap: false,
|
||
devServer: {
|
||
host: '0.0.0.0',
|
||
port: port,
|
||
open: true,
|
||
proxy: {
|
||
[process.env.VUE_APP_BASE_API]: {
|
||
target: `http://localhost:8080`,
|
||
changeOrigin: true,
|
||
pathRewrite: {
|
||
['^' + process.env.VUE_APP_BASE_API]: ''
|
||
}
|
||
}
|
||
},
|
||
disableHostCheck: true
|
||
},
|
||
configureWebpack: {
|
||
name: name,
|
||
resolve: {
|
||
alias: {
|
||
'@': resolve('src')
|
||
}
|
||
},
|
||
plugins: [
|
||
// 压缩插件
|
||
new CompressionPlugin({
|
||
cache: false,
|
||
test: /\.(js|css|html)?$/i,
|
||
filename: '[path].gz[query]',
|
||
algorithm: 'gzip',
|
||
threshold: 1024,
|
||
minRatio: 0.8
|
||
})
|
||
]
|
||
},
|
||
chainWebpack(config) {
|
||
config.plugins.delete('preload')
|
||
config.plugins.delete('prefetch')
|
||
|
||
// 设置 svg-sprite-loader
|
||
config.module
|
||
.rule('svg')
|
||
.exclude.add(resolve('src/assets/icons'))
|
||
.end()
|
||
config.module
|
||
.rule('icons')
|
||
.test(/\.svg$/)
|
||
.include.add(resolve('src/assets/icons'))
|
||
.end()
|
||
.use('svg-sprite-loader')
|
||
.loader('svg-sprite-loader')
|
||
.options({
|
||
symbolId: 'icon-[name]'
|
||
})
|
||
.end()
|
||
}
|
||
}
|
||
```
|
||
|
||
## 5. 技术栈优势与挑战
|
||
|
||
### 5.1 核心优势
|
||
|
||
| 维度 | 优势描述 | 具体体现 |
|
||
|------|----------|----------|
|
||
| **开发效率** | 代码生成器 + 脚手架 | 一键生成CRUD功能,快速搭建项目结构 |
|
||
| **学习成本** | 主流技术栈 | 技术栈成熟,文档丰富,学习资料完善 |
|
||
| **系统稳定性** | 企业级框架 | Spring生态 + MySQL稳定性保障 |
|
||
| **扩展性** | 模块化架构 | 支持功能模块独立开发和部署 |
|
||
| **安全性** | 完善防护机制 | 多层次安全防护,权限控制细粒度 |
|
||
| **性能** | 缓存 + 优化 | Redis缓存 + SQL优化 + 前端性能优化 |
|
||
|
||
### 5.2 潜在挑战
|
||
|
||
| 挑战 | 影响 | 解决方案 |
|
||
|------|------|----------|
|
||
| **技术债务** | 随着业务复杂度增加可能产生技术债务 | 定期重构,代码审查,测试覆盖 |
|
||
| **版本兼容** | 依赖库版本升级可能带来兼容性问题 | 渐进式升级,充分测试 |
|
||
| **性能瓶颈** | 大数据量情况下可能出现性能瓶颈 | 分库分表,读写分离,缓存优化 |
|
||
| **团队协作** | 多人协作可能出现代码冲突 | Git工作流规范,代码规范统一 |
|
||
|
||
## 6. 技术演进建议
|
||
|
||
### 6.1 短期优化(3-6个月)
|
||
|
||
1. **性能监控集成**
|
||
- 集成APM工具(如SkyWalking、Pinpoint)
|
||
- 建立性能基线和告警机制
|
||
|
||
2. **缓存策略优化**
|
||
- 实施多级缓存策略
|
||
- 优化缓存键设计和过期策略
|
||
|
||
3. **前端性能优化**
|
||
- 实施代码分割和懒加载
|
||
- 优化首屏加载时间
|
||
|
||
### 6.2 中期演进(6-12个月)
|
||
|
||
1. **微服务准备**
|
||
- 服务边界识别和拆分设计
|
||
- 服务通信机制设计
|
||
|
||
2. **DevOps完善**
|
||
- CI/CD流水线优化
|
||
- 自动化测试覆盖提升
|
||
|
||
3. **技术栈升级**
|
||
- Spring Boot升级到最新稳定版
|
||
- Vue升级到Vue 3.x
|
||
|
||
### 6.3 长期规划(12个月以上)
|
||
|
||
1. **云原生改造**
|
||
- 容器化部署
|
||
- Kubernetes编排
|
||
|
||
2. **架构现代化**
|
||
- 微服务架构演进
|
||
- 事件驱动架构引入
|
||
|
||
3. **智能化运维**
|
||
- 自动扩缩容
|
||
- 智能故障诊断
|
||
|
||
## 7. 总结
|
||
|
||
RuoYi-Vue的技术栈选型体现了**实用主义**的设计理念,在技术先进性和稳定性之间找到了很好的平衡点。整体技术架构具有以下特点:
|
||
|
||
✅ **成熟稳定**:采用业界验证的主流技术栈
|
||
✅ **学习友好**:技术选型考虑了开发者的学习成本
|
||
✅ **扩展灵活**:架构设计支持功能扩展和性能优化
|
||
✅ **安全可靠**:完善的安全机制和防护策略
|
||
✅ **开发高效**:代码生成器等工具大大提升开发效率
|
||
✅ **运维便利**:支持多环境部署和系统监控
|
||
|
||
该技术栈非常适合作为企业级后台管理系统的技术基础,能够满足大部分中小企业的业务需求,同时为后续的技术演进预留了充足的空间。 |