若依SpringBoot框架技术介绍与使用指南
1. 框架概述
1.1 项目简介
若依(RuoYi)是一款基于SpringBoot的权限管理系统快速开发框架,由经典技术组合(Spring Boot、Apache Shiro、MyBatis、Thymeleaf)构建,专注于让开发者能够专注业务逻辑开发,降低技术难度,从而节省人力成本,缩短项目周期,提高软件安全质量。
若依框架提供了一套业界公认的、标准化的后台管理系统解决方案,内置了用户管理、角色管理、菜单权限、数据权限、系统监控等企业级应用必备功能。开发者可以专注于业务逻辑的实现,而无需从零开始搭建项目基础架构。
1.2 版本体系
若依框架提供多个版本以满足不同需求:
| 版本 | 技术栈 | 适用场景 | 最新版本 |
|---|---|---|---|
| RuoYi | SpringBoot + Bootstrap | 传统单体应用,前后端不分离 | v4.8.2 |
| RuoYi-Vue | SpringBoot + Vue | 前后端分离,现代化Web应用 | v3.9.1 |
| RuoYi-Cloud | SpringCloud + Vue | 微服务架构,分布式系统 | v3.6.7 |
| RuoYi-App | Uniapp + Vue | 移动端应用 | 最新版 |
1.3 整体架构设计
若依采用经典的多模块Maven项目结构,遵循分层架构设计原则:
com.ruoyi
├── ruoyi-admin # 应用入口层
├── ruoyi-framework # 框架核心层
├── ruoyi-system # 业务实现层
├── ruoyi-common # 通用工具层
├── ruoyi-generator # 代码生成层
└── ruoyi-quartz # 定时任务层
1.4 核心模块划分
1.4.1 ruoyi-admin(应用入口层)
- 职责:整个应用的启动入口和web接口暴露
- 核心内容:
RuoYiApplication.java:SpringBoot启动类controller目录:存放各种业务controllersystem:系统管理相关接口(用户、角色、菜单等)monitor:系统监控接口(在线用户、日志等)tool:工具接口(代码生成、swagger等)
- 依赖关系:
- 依赖ruoyi-framework(获取安全配置)
- 依赖ruoyi-quartz(定时任务)
- 依赖ruoyi-generator(代码生成)
1.4.2 ruoyi-framework(框架核心层)
- 职责:提供安全认证、拦截器、配置等框架级功能
- 核心内容:
config/:配置类(Security、Redis、Druid、MyBatis等)security/:安全模块(过滤器、处理器、服务)aspectj/:切面编程(日志、数据权限、限流)web/:Web相关服务
- 关键设计思想:
- 过滤器链:请求 → CORS过滤器 → Token验证 → 权限校验 → Controller
- AOP切面编程:通过注解实现日志、权限、限流等功能
1.4.3 ruoyi-system(业务实现层)
- 职责:系统核心业务逻辑实现
- 标准分层结构(以用户管理为例):
controller/:接口层,接收HTTP请求service/:业务逻辑层,处理业务规则mapper/:数据访问接口domain/:实体类resources/mapper/:SQL映射文件
- 内置功能模块:
- 用户管理(sys_user)
- 角色管理(sys_role)
- 菜单管理(sys_menu)
- 部门管理(sys_dept)
- 岗位管理(sys_post)
- 字典管理(sys_dict)
- 参数配置(sys_config)
- 操作日志(sys_oper_log)
- 登录日志(sys_logininfor)
1.4.4 ruoyi-common(通用工具层)
- 职责:提供项目通用组件、工具类、常量、异常等
- 核心内容:
annotation/:自定义注解(日志、数据权限、防重复提交、限流、Excel导出)core/:核心类(Controller基类、统一返回结果、实体基类、登录用户信息)page/:分页数据redis/:Redis操作类constant/:常量类enums/:枚举类exception/:异常类utils/:工具类(字符串、日期、安全、Excel、文件等)filter/:过滤器(XSS过滤、可重复读取请求体)
1.4.5 ruoyi-generator(代码生成层)
- 职责:根据数据库表结构自动生成代码
- 生成内容:
- Domain实体类
- Mapper接口 + XML
- Service接口 + 实现类
- Controller接口
- 前端Vue页面(index.vue)
- 前端API(api.js)
- 菜单SQL
- 模板文件位置:
resources/vm/
1.4.6 ruoyi-quartz(定时任务层)
- 职责:实现定时任务的调度管理
- 核心功能:
- 动态添加/修改/删除定时任务
- 任务执行日志记录
- 任务并发控制
- Cron表达式配置
1.5 技术栈组成
后端技术栈
| 技术名称 | 版本 | 作用描述 |
|---|---|---|
| Spring Boot | 2.2.x / 3.x | 核心框架,快速构建独立运行的Spring应用 |
| Spring Framework | 5.2.x | 基础框架,提供依赖注入、AOP等功能 |
| Apache Shiro | 1.7 | 安全框架,提供认证、授权、加密等功能 |
| Apache MyBatis | 3.5.x | 持久层框架,SQL映射和数据库操作 |
| Hibernate Validation | 6.0.x | 参数校验组件 |
| Alibaba Druid | 1.2.x | 数据库连接池,提供监控功能 |
| Thymeleaf | 3.0.x | 模板引擎,服务端渲染 |
| PageHelper | 1.4.x | 分页插件 |
| Quartz | 2.3.x | 定时任务调度 |
| Swagger | 2.9.x | API文档生成工具 |
前端技术栈(不分离版本)
| 技术名称 | 版本 | 作用描述 |
|---|---|---|
| Bootstrap | 3.3.7 | 前端UI框架 |
| jQuery | 3.x | JavaScript库 |
| jQuery Validation | 1.19.x | 表单验证插件 |
| jQuery Bootstrap Table | 1.18.x | 表格插件 |
| Layer | 3.5.x | 弹层组件 |
| Bootstrap TreeView | 1.2.x | 树形结构组件 |
前端技术栈(Vue分离版本)
| 技术名称 | 版本 | 作用描述 |
|---|---|---|
| Vue.js | 2.6.x / 3.x | 前端框架,构建单页面应用 |
| Vue Router | 3.x / 4.x | 路由管理,实现前端页面导航 |
| Vuex | 3.x / 4.x | 状态管理,集中管理应用共享数据 |
| Element UI / Element Plus | 2.14.x / 2.x | UI组件库,提供丰富的基础组件 |
| Axios | 0.21.x | 数据请求库,向后端发送HTTP请求 |
| Webpack | 4.x / 5.x | 打包工具,编译和优化前端代码 |
数据库设计
若依框架的数据库设计遵循标准的权限管理模型,核心表结构包括:
- 用户表(sys_user):存储系统用户基本信息
- 角色表(sys_role):定义系统角色
- 菜单表(sys_menu):配置系统菜单和权限
- 部门表(sys_dept):组织机构管理
- 岗位表(sys_post):用户岗位管理
- 用户角色关联表(sys_user_role):用户与角色多对多关系
- 角色菜单关联表(sys_role_menu):角色与菜单多对多关系
- 角色部门关联表(sys_role_dept):角色数据权限配置
1.6 主要特性
- 完全响应式布局:支持电脑、平板、手机等所有主流设备
- 强大的一键生成功能:包括控制器、模型、视图、菜单等
- 支持多数据源:简单配置即可实现切换
- 支持按钮及数据权限:可自定义部门数据权限
- 完善的XSS防范及脚本过滤:彻底杜绝XSS攻击
- 支持CSRF跨站请求伪造防御:双重提交令牌验证
- Maven多项目依赖:模块及插件分项目,尽量松耦合
- 国际化支持:服务端及客户端支持
- 完善的日志记录体系:简单注解即可实现
- 支持服务监控、数据监控、缓存监控功能
2. 核心技术解析
2.1 SpringBoot框架
2.1.1 框架介绍
Spring Boot是一款开箱即用框架,提供各种默认配置来简化项目配置。遵循"约定优先于配置"的原则,使用SpringBoot只需很少的配置,大部分时候直接使用默认配置即可。
2.1.2 核心优势
- 使编码变得简单:推荐使用注解
- 使配置变得简单:自动配置、快速集成新技术能力,没有冗余代码生成和XML配置的要求
- 使部署变得简单:内嵌Tomcat、Jetty、Undertow等web容器,无需以war包形式部署
- 使监控变得简单:提供运行时的应用监控
- 使集成变得简单:对主流开发框架的无配置集成
- 使开发变得简单:极大地提高了开发快速构建项目、部署效率
2.1.3 在若依项目中的应用
- 自动配置:通过
@SpringBootApplication注解实现组件扫描和自动配置 - 内嵌服务器:使用内嵌Tomcat,通过
java -jar方式运行 - 配置管理:通过
application.yml和application-druid.yml进行统一配置 - 依赖管理:通过
spring-boot-starter简化依赖配置 - Actuator监控:提供应用监控和管理端点
2.2 Apache Shiro安全控制
2.2.1 框架介绍
Apache Shiro是Java的一个安全框架,可以帮助我们完成:认证、授权、加密、会话管理、与Web集成、缓存等。其不仅可以用在JavaSE环境,也可以用在JavaEE环境。
2.2.2 核心特性
- Authentication(身份认证):验证用户是不是拥有相应的身份
- Authorization(授权):验证某个已认证的用户是否拥有某个权限
- Session Management(会话管理):用户登录后就是一次会话,在没有退出之前,它的所有信息都在会话中
- Cryptography(加密):保护数据的安全性,如密码加密存储到数据库
- Caching(缓存):提高效率,如用户登录后,其用户信息、角色/权限不必每次去查
- Concurrency(并发):支持多线程应用的并发验证
- Testing(测试支持)
- Run As(允许一个用户假装为另一个用户)
- Remember Me(记住我):一次登录后,下次再来的话不用登录
2.2.3 核心架构
- Subject(主体):代表了当前的"用户",所有Subject都绑定到SecurityManager
- SecurityManager(安全管理器):所有与安全有关的操作都会与SecurityManager交互,是Shiro的核心
- Realm(域):Shiro从Realm获取安全数据(如用户、角色、权限)
- SessionManager:管理Session的生命周期
- SessionDAO:用于会话的CRUD操作
- CacheManager:缓存控制器,管理用户、角色、权限等的缓存
2.2.4 在若依项目中的应用
- 用户认证:通过自定义Realm实现用户名密码验证
- 权限控制:基于RBAC模型,实现菜单权限、按钮权限、数据权限
- 会话管理:集成Redis实现分布式会话管理
- 密码加密:使用MD5+盐值加密存储用户密码
- 记住我功能:通过Cookie实现记住登录状态
- 验证码:集成Kaptcha实现登录验证码
2.3 MyBatis持久层框架
2.3.1 框架介绍
Apache MyBatis是一款优秀的持久层框架,它支持定制化SQL、存储过程以及高级映射。MyBatis避免了几乎所有的JDBC代码和手动设置参数以及获取结果集的工作。
2.3.2 核心特性
- SQL映射:通过XML或注解方式配置SQL语句
- 动态SQL:支持动态SQL语句的拼接
- 关联映射:支持一对一、一对多、多对多关联
- 缓存机制:一级缓存和二级缓存
- 插件机制:支持分页插件、性能监控插件等
2.3.3 在若依项目中的应用
- 数据访问:通过Mapper接口和XML映射文件实现数据操作
- 分页查询:集成PageHelper插件实现分页
- 数据权限:通过AOP切面实现数据权限过滤
- 动态SQL:根据查询条件动态拼接SQL语句
- 批量操作:支持批量插入、批量更新、批量删除
2.4 MyBatis-Plus增强框架
2.4.1 框架介绍
MyBatis-Plus(简称MP)是一个MyBatis的增强工具,在MyBatis的基础上只做增强不做改变,为简化开发、提高效率而生。
2.4.2 核心特性
- 无侵入:只做增强不做改变,引入它不会对现有工程产生影响
- 损耗小:启动即会自动注入基本CRUD,性能基本无损耗,直接面向对象操作
- 强大的CRUD操作:内置通用Mapper、通用Service,仅仅通过少量配置即可实现单表大部分CRUD操作
- 支持Lambda形式调用:通过Lambda表达式,方便地编写各类查询条件
- 支持主键自动生成:支持多达4种主键策略(内含分布式唯一ID生成器-序列化)
- 支持ActiveRecord模式:支持ActiveRecord形式调用,实体类只需继承Model类即可进行强大的CRUD操作
- 支持自定义通用操作:支持全局通用方法注入
- 内置代码生成器:采用代码或者Maven插件可快速生成Mapper、Model、Service、Controller层代码
- 内置分页插件:基于MyBatis物理分页,开发者无需关心具体操作
- 内置性能分析插件:可输出Sql语句以及其执行时间
- 内置拦截器:提供全表delete、update操作智能分析阻断
2.4.3 在若依项目中的应用
在若依的新版本中,MyBatis-Plus逐步替代原生MyBatis:
- 基础CRUD:自动生成,无需编写XML
- 分页查询:自动分页插件
- 查询语法:Lambda表达式(类型安全)
- 乐观锁:注解即可实现
- 逻辑删除:注解即可实现
2.5 Spring Security安全框架
2.5.1 框架介绍
Spring Security是一个能够为基于Spring的企业应用系统提供声明式的安全访问控制解决方案的安全框架。
2.5.2 核心功能
- Authentication(认证):用户登录
- Authorization(授权):判断用户拥有什么权限,可以访问什么资源
- 安全防护:跨站脚本攻击、session攻击等
- 加密功能:密码加密存储
- 会话管理:管理用户会话
- 缓存支持:缓存用户权限信息
- RememberMe功能:记住登录状态
2.5.3 在若依项目中的应用
在若依-Vue等前后端分离版本中,Spring Security替代Shiro:
- JWT认证:基于JWT的无状态认证
- 过滤器链:配置自定义过滤器链
- 权限注解:支持
@PreAuthorize等注解 - 密码加密:使用BCryptPasswordEncoder加密
- CORS配置:处理跨域请求
2.6 Thymeleaf模板引擎
2.6.1 模板介绍
Thymeleaf是一个用于Web和独立Java环境的模板引擎,能够处理HTML、XML、JavaScript、CSS甚至纯文本。能轻易的与Spring MVC等Web框架进行集成作为Web应用的模板引擎。
2.6.2 核心特性
- 自然模板:模板文件能直接在浏览器中打开并正确显示
- 国际化支持:支持多语言
- 语法简单:功能强大,使用方便
- Spring集成:与Spring框架集成良好
- 性能优化:3.0版本重构,减少内存占用和提升性能
2.6.3 在若依项目中的应用
- 页面渲染:服务端渲染HTML页面
- 表单绑定:表单数据绑定和验证
- 国际化:多语言支持
- 静态资源处理:CSS、JS、图片等静态资源引用
2.7 数据库连接池Druid
2.7.1 连接池介绍
Druid是阿里巴巴开源的数据库连接池,为监控而生,是Java语言中最好的数据库连接池之一。
2.7.2 核心特性
- 监控统计:内置强大的监控功能
- 防SQL注入:内置防SQL注入功能
- 扩展性强:可扩展监控统计、过滤等
- 性能优异:经过阿里巴巴大规模生产环境验证
- 配置简单:配置简单,易于使用
2.7.3 在若依项目中的应用
- 连接池管理:管理数据库连接
- SQL监控:监控SQL执行情况
- 慢SQL记录:记录执行时间过长的SQL
- 连接泄露检测:检测连接泄露情况
3. 项目搭建与配置
3.1 环境准备
3.1.1 必需环境
# Java开发环境
JDK >= 1.8 (推荐1.8版本)
# 数据库环境
MySQL >= 5.7.0 (推荐5.7版本)
# 构建工具
Maven >= 3.0
3.1.2 可选环境
# 前端开发环境(Vue版本)
Node.js >= 10
npm >= 6.0
# 缓存服务
Redis >= 3.0
# 代码编辑器
IntelliJ IDEA / Eclipse / VS Code
3.2 项目下载与导入
3.2.1 下载项目
# 方式一:使用Git克隆(推荐)
git clone https://gitee.com/y_project/RuoYi.git
# 方式二:使用SpringBoot3,JDK17+版本
git clone https://gitee.com/y_project/RuoYi.git
cd RuoYi
git checkout springboot3
# 方式三:直接下载ZIP包
访问 https://gitee.com/y_project/RuoYi 下载最新版本
3.2.2 导入项目
Eclipse导入方式:
- 菜单
File→Import - 选择
Maven→Existing Maven Projects - 点击
Next按钮,选择工作目录 - 点击
Finish按钮,即可成功导入 - Eclipse会自动加载Maven依赖包,初次加载会比较慢
IntelliJ IDEA导入方式:
- 选择
File→Open - 选择项目根目录下的
pom.xml文件 - 选择
Open as Project - 等待Maven依赖下载完成
3.3 配置文件说明
3.3.1 通用配置 application.yml
# 项目相关配置
ruoyi:
# 名称
name: RuoYi
# 版本
version: 4.8.2
# 版权年份
copyrightYear: 2026
# 实例演示开关
demoEnabled: true
# 文件路径(示例:Windows配置D:/ruoyi/uploadPath,Linux配置/home/ruoyi/uploadPath)
profile: D:/ruoyi/uploadPath
# 获取ip地址开关
addressEnabled: false
# 开发环境配置
server:
# 服务器的HTTP端口,默认为80
port: 80
servlet:
# 应用的访问路径
context-path: /
tomcat:
# tomcat的URI编码
uri-encoding: UTF-8
# tomcat最大线程数,默认为200
max-threads: 800
# Tomcat启动初始化的线程数,默认值25
min-spare-threads: 30
# 日志配置
logging:
level:
com.ruoyi: debug
org.springframework: warn
# 用户配置
user:
password:
# 密码错误{maxRetryCount}次锁定10分钟
maxRetryCount: 5
# Spring配置
spring:
# 模板引擎
thymeleaf:
mode: HTML
encoding: utf-8
# 禁用缓存
cache: false
# 资源信息
messages:
# 国际化资源文件路径
basename: static/i18n/messages
jackson:
time-zone: GMT+8
date-format: yyyy-MM-dd HH:mm:ss
profiles:
active: druid
# 文件上传
servlet:
multipart:
# 单个文件大小
max-file-size: 10MB
# 设置总上传的文件大小
max-request-size: 20MB
# 服务模块
devtools:
restart:
# 热部署开关
enabled: true
# MyBatis
mybatis:
# 搜索指定包别名
typeAliasesPackage: com.ruoyi.**.domain
# 配置mapper的扫描,找到所有的mapper.xml映射文件
mapperLocations: classpath*:mapper/**/*Mapper.xml
# 加载全局的配置文件
configLocation: classpath:mybatis/mybatis-config.xml
# PageHelper分页插件
pagehelper:
helperDialect: mysql
reasonable: true
supportMethodsArguments: true
params: count=countSql
# Shiro
shiro:
user:
# 登录地址
loginUrl: /login
# 权限认证失败地址
unauthorizedUrl: /unauth
# 首页地址
indexUrl: /index
# 验证码开关
captchaEnabled: true
# 验证码类型 math 数组计算 char 字符
captchaType: math
cookie:
# 设置Cookie的域名 默认空,即当前访问的域名
domain:
# 设置cookie的有效访问路径
path: /
# 设置HttpOnly属性
httpOnly: true
# 设置Cookie的过期时间,天为单位
maxAge: 30
# 设置密钥,务必保持唯一性
cipherKey: zSyK5Kp6PZAAjlT+eeNMlg==
session:
# Session超时时间,-1代表永不过期(默认30分钟)
expireTime: 30
# 同步session到数据库的周期(默认1分钟)
dbSyncPeriod: 1
# 相隔多久检查一次session的有效性,默认就是10分钟
validationInterval: 10
# 同一个用户最大会话数,比如2的意思是同一个账号允许最多同时两个人登录(默认-1不限制)
maxSession: -1
# 踢出之前登录的/之后登录的用户,默认踢出之前登录的用户
kickoutAfter: false
# 防止XSS攻击
xss:
# 过滤开关
enabled: true
# 排除链接(多个用逗号分隔)
excludes: /system/notice/*
# 匹配链接
urlPatterns: /system/*,/monitor/*,/tool/*
# Swagger配置
swagger:
# 是否开启swagger
enabled: true
3.3.2 数据源配置 application-druid.yml
# 数据源配置
spring:
datasource:
type: com.alibaba.druid.pool.DruidDataSource
driverClassName: com.mysql.cj.jdbc.Driver
druid:
# 主库数据源
master:
url: jdbc:mysql://localhost:3306/ry?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8
username: root
password: password
# 从库数据源
slave:
# 从数据源开关/默认关闭
enabled: false
url:
username:
password:
# 初始连接数
initialSize: 5
# 最小连接池数量
minIdle: 10
# 最大连接池数量
maxActive: 20
# 配置获取连接等待超时的时间
maxWait: 60000
# 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒
timeBetweenEvictionRunsMillis: 60000
# 配置一个连接在池中最小生存的时间,单位是毫秒
minEvictableIdleTimeMillis: 300000
# 配置一个连接在池中最大生存的时间,单位是毫秒
maxEvictableIdleTimeMillis: 900000
# 配置检测连接是否有效
validationQuery: SELECT 1 FROM DUAL
testWhileIdle: true
testOnBorrow: false
testOnReturn: false
webStatFilter:
enabled: true
statViewServlet:
enabled: true
# 设置白名单,不填则允许所有访问
allow:
url-pattern: /druid/*
# 控制台管理用户名和密码
login-username:
login-password:
filter:
stat:
enabled: true
# 慢SQL记录
log-slow-sql: true
slow-sql-millis: 1000
merge-sql: true
wall:
config:
multi-statement-allow: true
3.3.3 代码生成配置 generator.yml
# 代码生成
gen:
# 作者
author: ruoyi
# 默认生成包路径 system 需改成自己的模块名称 如 system monitor tool
packageName: com.ruoyi.system
# 自动去除表前缀,默认是false
autoRemovePre: false
# 表前缀(生成类名不会包含表前缀,多个用逗号分隔)
tablePrefix: sys_
3.4 数据库初始化
3.4.1 创建数据库
-- 创建数据库
CREATE DATABASE `ry` DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;
-- 使用数据库
USE `ry`;
3.4.2 导入数据脚本
# 方式一:命令行导入
mysql -u root -p ry < ry_20240101.sql
mysql -u root -p ry < quartz.sql
# 方式二:MySQL客户端导入
# 使用Navicat、MySQL Workbench等工具连接数据库
# 分别执行 ry_20240101.sql 和 quartz.sql 文件
# 方式三:使用source命令
mysql -u root -p
source D:/ruoyi/sql/ry_20240101.sql
source D:/ruoyi/sql/quartz.sql
3.4.3 修改数据库配置
根据实际环境修改application-druid.yml中的数据库连接信息:
- 数据库地址(url)
- 数据库用户名(username)
- 数据库密码(password)
3.5 项目启动
3.5.1 启动后端服务
# 方式一:在IDE中启动
找到 com.ruoyi.RuoYiApplication.java 类
右键 → Run 'RuoYiApplication'
# 方式二:使用Maven命令启动
mvn spring-boot:run
# 方式三:打包后启动
mvn clean package
java -jar ruoyi-admin/target/ruoyi-admin.jar
3.5.2 启动成功标识
启动成功后,控制台会显示以下信息:
(♥◠‿◠)ノ゙ 若依启动成功 ლ(´ڡ`ლ)゙
.-------. ____ __
| _ _ \ \ \ / /
| ( ' ) | \ _. / '
|(_ o _) / _( )_ .'
| (_,_).' __ ___(_ o _)'
| |\ \ | || |(_,_)'
| | \ `' /| `-' /
| | \ / \ /
''-' `'-' `-..-'
3.5.3 访问系统
访问地址:http://localhost
默认账号:admin
默认密码:admin123
3.6 常见问题解决
3.6.1 Mac系统路径问题
如果使用Mac系统,需要修改application.yml文件中的profile路径。
3.6.2 Linux表不存在问题
如果Linux系统提示表不存在,设置MySQL大小写敏感配置:
# 编辑 /etc/my.cnf 文件
[mysqld]
lower_case_table_names=1
# 重启MySQL服务
systemctl restart mysql
3.6.3 文件权限问题
如果提示当前权限不足,无法写入文件,请检查:
application.yml中的profile路径是否有可读可写权限logback.xml中的log.path路径是否有可读可写权限
3.6.4 端口占用问题
如果80端口被占用,可以修改application.yml中的端口:
server:
port: 8080 # 修改为其他端口
4. 功能模块使用
4.1 用户管理
4.1.1 功能介绍
用户管理模块是系统的基础模块之一,负责用户信息的管理和维护。
4.1.2 核心功能
- 用户查询:按条件查询用户列表
- 新增用户:创建新用户
- 修改用户:编辑用户信息
- 删除用户:删除用户(支持单个删除和批量删除)
- 重置密码:重置用户密码
- 分配角色:为用户分配角色
- 状态控制:启用/停用用户
- 导入导出:批量导入导出用户数据
4.1.3 数据库表结构
CREATE TABLE `sys_user` (
`user_id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '用户ID',
`dept_id` bigint(20) 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 '用户类型(00系统用户)',
`email` varchar(50) DEFAULT '' COMMENT '用户邮箱',
`phonenumber` varchar(11) DEFAULT '' COMMENT '手机号码',
`sex` char(1) DEFAULT '0' COMMENT '用户性别(0男 1女 2未知)',
`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 AUTO_INCREMENT=100 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='用户信息表';
4.1.4 代码实现示例
Controller层:
@Controller
@RequestMapping("/system/user")
public class SysUserController extends BaseController {
private String prefix = "system/user";
@Autowired
private ISysUserService userService;
@RequiresPermissions("system:user:view")
@GetMapping()
public String user() {
return prefix + "/user";
}
@RequiresPermissions("system:user:list")
@PostMapping("/list")
@ResponseBody
public TableDataInfo list(SysUser user) {
startPage();
List<SysUser> list = userService.selectUserList(user);
return getDataTable(list);
}
@RequiresPermissions("system:user:add")
@Log(title = "用户管理", businessType = BusinessType.INSERT)
@GetMapping("/add")
public String add(ModelMap mmap) {
mmap.put("roles", userService.selectRoleAll());
return prefix + "/add";
}
@RequiresPermissions("system:user:add")
@Log(title = "用户管理", businessType = BusinessType.INSERT)
@PostMapping("/add")
@ResponseBody
public AjaxResult addSave(@Validated SysUser user) {
if (UserConstants.USER_NAME_NOT_UNIQUE.equals(userService.checkLoginNameUnique(user.getLoginName()))) {
return error("新增用户'" + user.getLoginName() + "'失败,登录账号已存在");
} else if (UserConstants.USER_PHONE_NOT_UNIQUE.equals(userService.checkPhoneUnique(user))) {
return error("新增用户'" + user.getLoginName() + "'失败,手机号码已存在");
} else if (UserConstants.USER_EMAIL_NOT_UNIQUE.equals(userService.checkEmailUnique(user))) {
return error("新增用户'" + user.getLoginName() + "'失败,邮箱账号已存在");
}
user.setSalt(ShiroUtils.randomSalt());
user.setPassword(passwordService.encryptPassword(user.getLoginName(), user.getPassword(), user.getSalt()));
user.setCreateBy(ShiroUtils.getLoginName());
return toAjax(userService.insertUser(user));
}
@RequiresPermissions("system:user:edit")
@Log(title = "用户管理", businessType = BusinessType.UPDATE)
@GetMapping("/edit/{userId}")
public String edit(@PathVariable("userId") Long userId, ModelMap mmap) {
mmap.put("user", userService.selectUserById(userId));
mmap.put("roles", userService.selectRolesByUserId(userId));
return prefix + "/edit";
}
@RequiresPermissions("system:user:edit")
@Log(title = "用户管理", businessType = BusinessType.UPDATE)
@PostMapping("/edit")
@ResponseBody
public AjaxResult editSave(@Validated SysUser user) {
userService.checkUserAllowed(user);
if (UserConstants.USER_PHONE_NOT_UNIQUE.equals(userService.checkPhoneUnique(user))) {
return error("修改用户'" + user.getLoginName() + "'失败,手机号码已存在");
} else if (UserConstants.USER_EMAIL_NOT_UNIQUE.equals(userService.checkEmailUnique(user))) {
return error("修改用户'" + user.getLoginName() + "'失败,邮箱账号已存在");
}
user.setUpdateBy(ShiroUtils.getLoginName());
return toAjax(userService.updateUser(user));
}
@RequiresPermissions("system:user:remove")
@Log(title = "用户管理", businessType = BusinessType.DELETE)
@PostMapping("/remove")
@ResponseBody
public AjaxResult remove(String ids) {
return toAjax(userService.deleteUserByIds(ids));
}
}
4.2 角色管理
4.2.1 功能介绍
角色管理模块用于管理用户角色,不同的角色具有不同的权限。
4.2.2 核心功能
- 角色查询:按条件查询角色列表
- 新增角色:创建新角色
- 修改角色:编辑角色信息
- 删除角色:删除角色
- 分配权限:为角色分配菜单权限
- 数据权限:设置角色的数据范围
- 状态控制:启用/停用角色
4.2.3 数据权限模型
若依支持丰富的数据权限控制:
- 全部数据权限:可以查看所有数据
- 自定义数据权限:可以查看指定部门的数据
- 本部门数据权限:只能查看本部门的数据
- 本部门及以下数据权限:可以查看本部门及子部门的数据
- 仅本人数据权限:只能查看自己的数据
4.2.4 代码实现示例
Controller层:
@Controller
@RequestMapping("/system/role")
public class SysRoleController extends BaseController {
private String prefix = "system/role";
@Autowired
private ISysRoleService roleService;
@RequiresPermissions("system:role:view")
@GetMapping()
public String role() {
return prefix + "/role";
}
@RequiresPermissions("system:role:list")
@PostMapping("/list")
@ResponseBody
public TableDataInfo list(SysRole role) {
startPage();
List<SysRole> list = roleService.selectRoleList(role);
return getDataTable(list);
}
@RequiresPermissions("system:role:add")
@Log(title = "角色管理", businessType = BusinessType.INSERT)
@GetMapping("/add")
public String add() {
return prefix + "/add";
}
@RequiresPermissions("system:role:add")
@Log(title = "角色管理", businessType = BusinessType.INSERT)
@PostMapping("/add")
@ResponseBody
public AjaxResult addSave(@Validated SysRole role) {
if (UserConstants.ROLE_NAME_NOT_UNIQUE.equals(roleService.checkRoleNameUnique(role))) {
return error("新增角色'" + role.getRoleName() + "'失败,角色名称已存在");
} else if (UserConstants.ROLE_KEY_NOT_UNIQUE.equals(roleService.checkRoleKeyUnique(role))) {
return error("新增角色'" + role.getRoleName() + "'失败,角色权限已存在");
}
role.setCreateBy(ShiroUtils.getLoginName());
ShiroUtils.clearCachedAuthorizationInfo();
return toAjax(roleService.insertRole(role));
}
@RequiresPermissions("system:role:edit")
@Log(title = "角色管理", businessType = BusinessType.UPDATE)
@GetMapping("/edit/{roleId}")
public String edit(@PathVariable("roleId") Long roleId, ModelMap mmap) {
mmap.put("role", roleService.selectRoleById(roleId));
return prefix + "/edit";
}
@RequiresPermissions("system:role:edit")
@Log(title = "角色管理", businessType = BusinessType.UPDATE)
@PostMapping("/edit")
@ResponseBody
public AjaxResult editSave(@Validated SysRole role) {
roleService.checkRoleAllowed(role);
if (UserConstants.ROLE_NAME_NOT_UNIQUE.equals(roleService.checkRoleNameUnique(role))) {
return error("修改角色'" + role.getRoleName() + "'失败,角色名称已存在");
} else if (UserConstants.ROLE_KEY_NOT_UNIQUE.equals(roleService.checkRoleKeyUnique(role))) {
return error("修改角色'" + role.getRoleName() + "'失败,角色权限已存在");
}
role.setUpdateBy(ShiroUtils.getLoginName());
ShiroUtils.clearCachedAuthorizationInfo();
return toAjax(roleService.updateRole(role));
}
@RequiresPermissions("system:role:remove")
@Log(title = "角色管理", businessType = BusinessType.DELETE)
@PostMapping("/remove")
@ResponseBody
public AjaxResult remove(String ids) {
return toAjax(roleService.deleteRoleByIds(ids));
}
}
4.3 菜单管理
4.3.1 功能介绍
菜单管理模块用于管理系统的菜单,用户通过菜单进行系统操作。
4.3.2 菜单类型
若依支持三种菜单类型:
- 目录(M):作为菜单的父级,不对应具体页面
- 菜单(C):对应具体的页面
- 按钮(F):页面中的操作按钮权限
4.3.3 核心功能
- 菜单查询:按条件查询菜单列表(树形结构)
- 新增菜单:创建新菜单
- 修改菜单:编辑菜单信息
- 删除菜单:删除菜单
- 图标选择:选择菜单图标
- 排序调整:调整菜单显示顺序
4.3.4 代码实现示例
Controller层:
@Controller
@RequestMapping("/system/menu")
public class SysMenuController extends BaseController {
private String prefix = "system/menu";
@Autowired
private ISysMenuService menuService;
@RequiresPermissions("system:menu:view")
@GetMapping()
public String menu() {
return prefix + "/menu";
}
@RequiresPermissions("system:menu:list")
@PostMapping("/list")
@ResponseBody
public List<SysMenu> list(SysMenu menu) {
List<SysMenu> menusList = menuService.selectMenuList(menu);
return menusList;
}
@RequiresPermissions("system:menu:add")
@GetMapping("/add/{parentId}")
public String add(@PathVariable("parentId") Long parentId, ModelMap mmap) {
SysMenu menu = null;
if (0L != parentId) {
menu = menuService.selectMenuById(parentId);
} else {
menu = new SysMenu();
menu.setMenuId(0L);
menu.setMenuName("主目录");
}
mmap.put("menu", menu);
return prefix + "/add";
}
@RequiresPermissions("system:menu:add")
@Log(title = "菜单管理", businessType = BusinessType.INSERT)
@PostMapping("/add")
@ResponseBody
public AjaxResult addSave(@Validated SysMenu menu) {
if (UserConstants.MENU_NAME_NOT_UNIQUE.equals(menuService.checkMenuNameUnique(menu))) {
return error("新增菜单'" + menu.getMenuName() + "'失败,菜单名称已存在");
}
menu.setCreateBy(ShiroUtils.getLoginName());
ShiroUtils.clearCachedAuthorizationInfo();
return toAjax(menuService.insertMenu(menu));
}
@RequiresPermissions("system:menu:edit")
@Log(title = "菜单管理", businessType = BusinessType.UPDATE)
@GetMapping("/edit/{menuId}")
public String edit(@PathVariable("menuId") Long menuId, ModelMap mmap) {
SysMenu menu = menuService.selectMenuById(menuId);
mmap.put("menu", menu);
return prefix + "/edit";
}
@RequiresPermissions("system:menu:edit")
@Log(title = "菜单管理", businessType = BusinessType.UPDATE)
@PostMapping("/edit")
@ResponseBody
public AjaxResult editSave(@Validated SysMenu menu) {
if (UserConstants.MENU_NAME_NOT_UNIQUE.equals(menuService.checkMenuNameUnique(menu))) {
return error("修改菜单'" + menu.getMenuName() + "'失败,菜单名称已存在");
}
menu.setUpdateBy(ShiroUtils.getLoginName());
ShiroUtils.clearCachedAuthorizationInfo();
return toAjax(menuService.updateMenu(menu));
}
@RequiresPermissions("system:menu:remove")
@Log(title = "菜单管理", businessType = BusinessType.DELETE)
@GetMapping("/remove/{menuId}")
@ResponseBody
public AjaxResult remove(@PathVariable("menuId") Long menuId) {
if (menuService.selectCountMenuByParentId(menuId) > 0) {
return AjaxResult.warn("存在子菜单,不允许删除");
}
if (menuService.selectCountRoleMenuByMenuId(menuId) > 0) {
return AjaxResult.warn("菜单已分配,不允许删除");
}
ShiroUtils.clearCachedAuthorizationInfo();
return toAjax(menuService.deleteMenuById(menuId));
}
}
4.4 数据字典
4.4.1 功能介绍
数据字典用于管理系统中经常使用的一些固定的数据,如用户状态、性别等。
4.4.2 核心功能
- 字典类型管理:管理字典的类型(如sys_user_sex)
- 字典数据管理:管理具体的字典数据(如0-男、1-女、2-未知)
- 缓存刷新:刷新字典缓存
4.4.3 代码实现示例
字典使用:
// Controller层返回字典数据
@GetMapping("/add")
public String add(ModelMap mmap) {
mmap.put("sexes", DictUtils.getDictCache("sys_user_sex"));
mmap.put("status", DictUtils.getDictCache("sys_normal_disable"));
return prefix + "/add";
}
// Service层查询字典数据
List<SysDictData> data = dictDataService.selectDictDataByType("sys_user_sex");
4.5 系统监控
4.5.1 在线用户监控
实时监控当前系统中的在线用户,支持强制退出用户。
4.5.2 操作日志
记录系统正常操作日志和查询系统异常信息日志。
4.5.3 登录日志
记录系统登录日志查询,包含登录异常。
4.5.4 服务监控
监视当前系统CPU、内存、磁盘、堆栈等相关信息。
4.5.5 缓存监控
对系统的缓存进行查询、查看、清理等操作。
4.5.6 连接池监视
监视当前系统数据库连接池状态,可分析SQL找出系统性能瓶颈。
4.6 定时任务
4.6.1 功能介绍
定时任务模块提供在线配置定时任务功能,支持动态添加、修改、删除任务调度。
4.6.2 核心功能
- 任务查询:查询定时任务列表
- 新增任务:创建新的定时任务
- 修改任务:编辑定时任务
- 删除任务:删除定时任务
- 任务执行:立即执行任务
- 任务暂停/恢复:控制任务的运行状态
- 执行日志:查看任务执行日志
4.6.3 Cron表达式
若依集成了Cron表达式在线生成工具,支持可视化配置定时规则。
4.6.4 代码实现示例
任务类示例:
@Component("ryTask")
public class RyTask {
public void ryMultipleParams(String s, Boolean b, Long l, Double d, Integer i) {
System.out.println(StringUtils.format("执行多参方法: 字符串类型{},布尔类型{},长整型{},浮点型{},整形{}", s, b, l, d, i));
}
public void ryParams(String params) {
System.out.println("执行有参方法:" + params);
}
public void ryNoParams() {
System.out.println("执行无参方法");
}
}
配置任务:
- 任务名称:自定义任务名称
- Cron表达式:定义任务执行时间
- 调用目标:如
ryTask.ryParams('ruoyi') - 执行策略:立即执行/等待执行
- 并发执行:是否允许并发执行
4.7 代码生成
4.7.1 功能介绍
代码生成模块是若依框架的亮点功能,可以根据数据库表结构自动生成前后端代码。
4.7.2 生成内容
-
后端代码:
- Domain实体类
- Mapper接口
- Mapper XML映射文件
- Service接口
- ServiceImpl实现类
- Controller控制器
-
前端代码:
- index.vue页面
- api.js接口文件
-
菜单SQL:生成菜单插入SQL语句
4.7.3 使用步骤
- 进入【代码生成】菜单
- 点击【导入】按钮,选择需要生成的表
- 编辑表信息(表名、描述等)
- 配置生成信息(包路径、模块名、作者等)
- 配置字段信息(Java类型、显示类型、查询方式等)
- 点击【生成代码】按钮
- 下载代码包并解压到项目
- 执行菜单SQL添加菜单
- 刷新页面,即可看到生成的功能模块
4.7.4 代码生成配置
# generator.yml
gen:
author: ruoyi # 作者
packageName: com.ruoyi.system # 默认生成包路径
autoRemovePre: false # 自动去除表前缀
tablePrefix: sys_ # 表前缀
5. 开发最佳实践
5.1 代码规范
5.1.1 命名规范
- 包命名:全部小写,使用反域名规则,如
com.ruoyi.system - 类命名:使用大驼峰命名法,如
SysUserController - 方法命名:使用小驼峰命名法,如
selectUserList - 变量命名:使用小驼峰命名法,如
userName - 常量命名:全部大写,单词间用下划线分隔,如
MAX_RETRY_COUNT - 数据库表命名:全部小写,单词间用下划线分隔,如
sys_user
5.1.2 注释规范
/**
* 用户信息表 sys_user
*
* @author ruoyi
*/
public class SysUser extends BaseEntity {
/** 用户ID */
private Long userId;
/** 用户账号 */
@Excel(name = "用户账号")
private String userName;
/**
* 根据用户ID查询用户
*
* @param userId 用户ID
* @return 用户信息
*/
public SysUser selectUserById(Long userId) {
// ...
}
}
5.1.3 分层规范
-
Controller层:
- 负责接收HTTP请求
- 参数校验
- 调用Service层
- 返回响应结果
-
Service层:
- 负责业务逻辑处理
- 事务控制
- 调用Mapper层
-
Mapper层:
- 负责数据访问
- SQL语句编写
- 数据库操作
5.2 接口设计
5.2.1 RESTful API设计
GET /system/user/list # 查询用户列表
GET /system/user/{userId} # 查询用户详情
POST /system/user # 新增用户
PUT /system/user # 修改用户
DELETE /system/user/{userId} # 删除用户
5.2.2 统一返回格式
public class AjaxResult extends HashMap<String, Object> {
private static final long serialVersionUID = 1L;
/** 状态码 */
public static final String CODE_TAG = "code";
/** 返回内容 */
public static final String MSG_TAG = "msg";
/** 数据对象 */
public static final String DATA_TAG = "data";
/**
* 初始化一个新创建的 AjaxResult 对象,使其表示一个空消息。
*/
public AjaxResult() {
}
/**
* 初始化一个新创建的 AjaxResult 对象
*
* @param code 状态码
* @param msg 返回内容
*/
public AjaxResult(int code, String msg) {
super.put(CODE_TAG, code);
super.put(MSG_TAG, msg);
}
/**
* 初始化一个新创建的 AjaxResult 对象
*
* @param code 状态码
* @param msg 返回内容
* @param data 数据对象
*/
public AjaxResult(int code, String msg, Object data) {
super.put(CODE_TAG, code);
super.put(MSG_TAG, msg);
super.put(DATA_TAG, data);
}
/**
* 返回成功消息
*
* @return 成功消息
*/
public static AjaxResult success() {
return AjaxResult.success("操作成功");
}
/**
* 返回成功数据
*
* @return 成功消息
*/
public static AjaxResult success(Object data) {
return AjaxResult.success("操作成功", data);
}
/**
* 返回成功消息
*
* @param msg 返回内容
* @return 成功消息
*/
public static AjaxResult success(String msg) {
return AjaxResult.success(msg, null);
}
/**
* 返回成功消息
*
* @param msg 返回内容
* @param data 数据对象
* @return 成功消息
*/
public static AjaxResult success(String msg, Object data) {
return new AjaxResult(HttpStatus.SUCCESS, msg, data);
}
/**
* 返回错误消息
*
* @return 错误消息
*/
public static AjaxResult error() {
return AjaxResult.error("操作失败");
}
/**
* 返回错误消息
*
* @param msg 返回内容
* @return 错误消息
*/
public static AjaxResult error(String msg) {
return AjaxResult.error(msg, null);
}
/**
* 返回错误消息
*
* @param msg 返回内容
* @param data 数据对象
* @return 错误消息
*/
public static AjaxResult error(String msg, Object data) {
return new AjaxResult(HttpStatus.ERROR, msg, data);
}
/**
* 返回错误消息
*
* @param code 状态码
* @param msg 返回内容
* @return 错误消息
*/
public static AjaxResult error(int code, String msg) {
return new AjaxResult(code, msg, null);
}
}
5.3 异常处理
5.3.1 全局异常处理
@RestControllerAdvice
public class GlobalExceptionHandler {
private static final Logger log = LoggerFactory.getLogger(GlobalExceptionHandler.class);
/**
* 权限校验异常
*/
@ExceptionHandler(AuthorizationException.class)
public AjaxResult handleAuthorizationException(AuthorizationException e) {
log.error("没有权限,{}", e.getMessage());
return AjaxResult.error(HttpStatus.FORBIDDEN, "没有权限,请联系管理员授权");
}
/**
* 请求方式不支持
*/
@ExceptionHandler(HttpRequestMethodNotSupportedException.class)
public AjaxResult handleHttpRequestMethodNotSupported(HttpRequestMethodNotSupportedException e) {
log.error("不支持' " + e.getMethod() + "'请求");
return AjaxResult.error("不支持' " + e.getMethod() + "'请求");
}
/**
* 业务异常
*/
@ExceptionHandler(ServiceException.class)
public AjaxResult handleServiceException(ServiceException e) {
log.error("业务异常:{}", e.getMessage());
Integer code = e.getCode();
return StringUtils.isNotNull(code) ? AjaxResult.error(code, e.getMessage()) : AjaxResult.error(e.getMessage());
}
/**
* 拦截未知的运行时异常
*/
@ExceptionHandler(RuntimeException.class)
public AjaxResult handleRuntimeException(RuntimeException e, HttpServletRequest request) {
String requestURI = request.getRequestURI();
log.error("请求地址'{}',发生未知异常.", requestURI, e);
return AjaxResult.error(e.getMessage());
}
/**
* 系统异常
*/
@ExceptionHandler(Exception.class)
public AjaxResult handleException(Exception e, HttpServletRequest request) {
String requestURI = request.getRequestURI();
log.error("请求地址'{}',发生系统异常.", requestURI, e);
return AjaxResult.error(e.getMessage());
}
}
5.3.2 自定义业务异常
public class ServiceException extends RuntimeException {
private static final long serialVersionUID = 1L;
/**
* 错误码
*/
private Integer code;
/**
* 错误提示
*/
private String message;
/**
* 错误明细
*/
private String detailMessage;
public ServiceException() {
}
public ServiceException(String message) {
this.message = message;
}
public ServiceException(String message, Integer code) {
this.message = message;
this.code = code;
}
public String getDetailMessage() {
return detailMessage;
}
public ServiceException setDetailMessage(String detailMessage) {
this.detailMessage = detailMessage;
return this;
}
public String getMessage() {
return message;
}
public ServiceException setMessage(String message) {
this.message = message;
return this;
}
public Integer getCode() {
return code;
}
public ServiceException setCode(Integer code) {
this.code = code;
return this;
}
}
5.4 性能优化
5.4.1 数据库优化
- 索引优化:为常用查询字段添加索引
- SQL优化:避免SELECT *,只查询需要的字段
- 分页查询:使用PageHelper进行分页
- 批量操作:尽量使用批量插入、批量更新
5.4.2 缓存优化
- 字典缓存:将字典数据缓存到Redis
- 用户缓存:缓存用户信息和权限
- 配置缓存:缓存系统配置参数
- 页面缓存:对不经常变化的页面进行缓存
// 使用缓存注解
@Cacheable(value = "user", key = "#userId")
public SysUser selectUserById(Long userId) {
return userMapper.selectUserById(userId);
}
@CacheEvict(value = "user", key = "#userId")
public int updateUser(SysUser user) {
return userMapper.updateUser(user);
}
5.4.3 前端优化
- 按需加载:使用路由懒加载
- 代码分割:使用Webpack进行代码分割
- 压缩资源:压缩JS、CSS、图片等静态资源
- CDN加速:使用CDN加速静态资源加载
5.4.4 并发优化
- 连接池配置:合理配置数据库连接池参数
- 线程池配置:配置合适的线程池大小
- 异步处理:使用
@Async进行异步处理 - 限流措施:使用注解进行接口限流
5.5 安全最佳实践
5.5.1 密码安全
// 使用MD5+盐值加密
public static String encryptPassword(String username, String password, String salt) {
return new Md5Hash(username + password + salt).toHex().toString();
}
// 密码强度校验
public static boolean checkPasswordStrength(String password) {
// 密码长度至少8位
if (password.length() < 8) {
return false;
}
// 包含大小写字母、数字、特殊字符
boolean hasUpper = false;
boolean hasLower = false;
boolean hasDigit = false;
boolean hasSpecial = false;
for (char c : password.toCharArray()) {
if (Character.isUpperCase(c)) {
hasUpper = true;
} else if (Character.isLowerCase(c)) {
hasLower = true;
} else if (Character.isDigit(c)) {
hasDigit = true;
} else {
hasSpecial = true;
}
}
return hasUpper && hasLower && hasDigit && hasSpecial;
}
5.5.2 SQL注入防护
// 使用MyBatis的#{}而不是${}
// 正确写法
@Select("SELECT * FROM sys_user WHERE user_name = #{userName}")
SysUser selectUserByName(@Param("userName") String userName);
// 错误写法(容易SQL注入)
@Select("SELECT * FROM sys_user WHERE user_name = '${userName}'")
SysUser selectUserByName(@Param("userName") String userName);
// SQL关键字过滤
public static String escapeOrderBySql(String value) {
if (StringUtils.isNotEmpty(value) && !isValidOrderBySql(value)) {
throw new BaseException("参数不符合规范,不能进行查询");
}
return value;
}
private static boolean isValidOrderBySql(String value) {
return value.matches(SQL_PATTERN);
}
5.5.3 XSS防护
// 自定义XSS过滤注解
@Target({ElementType.METHOD, ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
public @interface Xss {
public String message() default "不允许任何脚本运行";
}
// XSS过滤器
@Component
public class XssFilter implements Filter {
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) {
XssHttpServletRequestWrapper xssRequest = new XssHttpServletRequestWrapper((HttpServletRequest) request);
try {
chain.doFilter(xssRequest, response);
} catch (IOException | ServletException e) {
e.printStackTrace();
}
}
}
5.5.4 CSRF防护
// CSRF令牌验证
@PostMapping("/add")
@ResponseBody
public AjaxResult add(@Validated SysUser user, HttpServletRequest request) {
// 验证CSRF令牌
String token = request.getParameter("csrfToken");
if (!validateCsrfToken(token)) {
return AjaxResult.error("CSRF令牌验证失败");
}
// ...
}
5.6 测试最佳实践
5.6.1 单元测试
@SpringBootTest
public class UserServiceTest {
@Autowired
private ISysUserService userService;
@Test
public void testSelectUserById() {
SysUser user = userService.selectUserById(1L);
assertNotNull(user);
assertEquals("admin", user.getUserName());
}
@Test
public void testInsertUser() {
SysUser user = new SysUser();
user.setUserName("test");
user.setNickName("测试用户");
int result = userService.insertUser(user);
assertTrue(result > 0);
}
}
5.6.2 接口测试
@SpringBootTest
@AutoConfigureMockMvc
public class UserControllerTest {
@Autowired
private MockMvc mockMvc;
@Test
public void testList() throws Exception {
mockMvc.perform(get("/system/user/list")
.param("pageNum", "1")
.param("pageSize", "10"))
.andExpect(status().isOk())
.andExpect(jsonPath("$.code").value(0));
}
}
6. 扩展与定制
6.1 新模块添加
6.1.1 创建业务模块
在若依框架中添加新模块的步骤:
- 创建模块目录:
com.ruoyi
├── ruoyi-admin
├── ruoyi-framework
├── ruoyi-system
├── ruoyi-common
├── ruoyi-generator
├── ruoyi-quartz
└── ruoyi-business # 新增业务模块
- 配置Maven依赖:
<!-- ruoyi-admin/pom.xml -->
<dependency>
<groupId>com.ruoyi</groupId>
<artifactId>ruoyi-business</artifactId>
<version>${ruoyi.version}</version>
</dependency>
- 创建包结构:
com.ruoyi.business
├── controller
├── service
├── mapper
└── domain
6.1.2 代码生成
使用若依的代码生成功能快速生成新模块代码:
- 创建数据库表
- 导入表到代码生成模块
- 配置生成参数
- 生成代码
- 将生成的代码复制到新模块
6.2 第三方集成
6.2.1 Redis集成
若依框架已经集成了Redis,主要用于缓存和会话管理。
Redis配置:
# application.yml
spring:
redis:
host: 127.0.0.1
port: 6379
password:
database: 0
timeout: 10000ms
lettuce:
pool:
max-active: 8
max-wait: -1ms
max-idle: 8
min-idle: 0
Redis工具类使用:
@Autowired
private RedisCache redisCache;
// 设置缓存
redisCache.setCacheObject("user:1", user);
// 获取缓存
SysUser user = redisCache.getCacheObject("user:1");
// 删除缓存
redisCache.deleteObject("user:1");
6.2.2 阿里云OSS集成
集成阿里云对象存储服务:
- 添加依赖:
<dependency>
<groupId>com.aliyun.oss</groupId>
<artifactId>aliyun-sdk-oss</artifactId>
<version>3.15.0</version>
</dependency>
- 配置OSS参数:
# application.yml
aliyun:
oss:
endpoint: oss-cn-hangzhou.aliyuncs.com
accessKeyId: your-access-key-id
accessKeySecret: your-access-key-secret
bucketName: your-bucket-name
- 创建OSS配置类:
@Configuration
public class OssConfig {
@Value("${aliyun.oss.endpoint}")
private String endpoint;
@Value("${aliyun.oss.accessKeyId}")
private String accessKeyId;
@Value("${aliyun.oss.accessKeySecret}")
private String accessKeySecret;
@Bean
public OSS ossClient() {
return new OSSClientBuilder().build(endpoint, accessKeyId, accessKeySecret);
}
}
6.2.3 短信服务集成
集成阿里云短信服务:
- 添加依赖:
<dependency>
<groupId>com.aliyun</groupId>
<artifactId>aliyun-java-sdk-core</artifactId>
<version>4.6.3</version>
</dependency>
<dependency>
<groupId>com.aliyun</groupId>
<artifactId>aliyun-java-sdk-dysmsapi</artifactId>
<version>2.2.1</version>
</dependency>
- 创建短信服务类:
@Service
public class SmsService {
@Value("${aliyun.sms.accessKeyId}")
private String accessKeyId;
@Value("${aliyun.sms.accessKeySecret}")
private String accessKeySecret;
@Value("${aliyun.sms.signName}")
private String signName;
@Value("${aliyun.sms.templateCode}")
private String templateCode;
public void sendSms(String phone, String code) {
DefaultProfile profile = DefaultProfile.getProfile("cn-hangzhou", accessKeyId, accessKeySecret);
IAcsClient client = new DefaultAcsClient(profile);
SendSmsRequest request = new SendSmsRequest();
request.setPhoneNumbers(phone);
request.setSignName(signName);
request.setTemplateCode(templateCode);
request.setTemplateParam("{\"code\":\"" + code + "\"}");
try {
SendSmsResponse response = client.getAcsResponse(request);
if (!"OK".equals(response.getCode())) {
throw new ServiceException("短信发送失败:" + response.getMessage());
}
} catch (ServerException | ClientException e) {
throw new ServiceException("短信发送异常:" + e.getMessage());
}
}
}
6.3 自定义配置
6.3.1 自定义注解
// 操作日志注解
@Target({ ElementType.PARAMETER, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Log {
/** 模块标题 */
String title() default "";
/** 功能 */
BusinessType businessType() default BusinessType.OTHER;
/** 是否保存请求的参数 */
boolean isSaveRequestData() default true;
/** 是否保存响应的参数 */
boolean isSaveResponseData() default true;
}
// 数据权限注解
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface DataScope {
/** 部门表的别名 */
public String deptAlias() default "";
/** 用户表的别名 */
public String userAlias() default "";
}
// 防重复提交注解
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface RepeatSubmit {
/** 间隔时间(ms),小于此时间视为重复提交 */
public int interval() default 5000;
/** 提示消息 */
public String message() default "不允许重复提交,请稍后再试";
}
6.3.2 自定义拦截器
@Component
public class MyInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
// 前置处理
return true;
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
// 后置处理
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
// 完成处理
}
}
@Configuration
public class WebMvcConfig implements WebMvcConfigurer {
@Autowired
private MyInterceptor myInterceptor;
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(myInterceptor)
.addPathPatterns("/**")
.excludePathPatterns("/login", "/register");
}
}
6.3.3 自定义AOP切面
@Aspect
@Component
@Slf4j
public class MyAspect {
@Pointcut("@annotation(com.ruoyi.common.annotation.MyAnnotation)")
public void myPointCut() {
}
@Before("myPointCut()")
public void doBefore(JoinPoint joinPoint) {
log.info("前置通知");
}
@AfterReturning(pointcut = "myPointCut()", returning = "jsonResult")
public void doAfterReturning(JoinPoint joinPoint, Object jsonResult) {
log.info("后置返回通知");
}
@AfterThrowing(pointcut = "myPointCut()", throwing = "e")
public void doAfterThrowing(JoinPoint joinPoint, Throwable e) {
log.info("异常通知");
}
@After("myPointCut()")
public void doAfter(JoinPoint joinPoint) {
log.info("后置最终通知");
}
@Around("myPointCut()")
public Object doAround(ProceedingJoinPoint pjp) {
log.info("环绕通知");
try {
return pjp.proceed();
} catch (Throwable e) {
log.error("异常", e);
return null;
}
}
}
6.4 自定义验证器
6.4.1 自定义注解
@Target({ElementType.METHOD, ElementType.FIELD, ElementType.CONSTRUCTOR, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy = PhoneValidator.class)
public @interface Phone {
String message() default "手机号格式不正确";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
6.4.2 自定义验证器
public class PhoneValidator implements ConstraintValidator<Phone, String> {
private static final String PHONE_PATTERN = "^1[3-9]\\d{9}$";
@Override
public void initialize(Phone constraintAnnotation) {
}
@Override
public boolean isValid(String value, ConstraintValidatorContext context) {
if (StringUtils.isEmpty(value)) {
return true;
}
return value.matches(PHONE_PATTERN);
}
}
6.4.3 使用自定义验证器
@PostMapping("/add")
@ResponseBody
public AjaxResult addSave(@Validated @RequestBody SysUser user) {
// 使用@Phone注解验证手机号
return toAjax(userService.insertUser(user));
}
6.5 前端扩展
6.5.1 自定义组件
// src/components/MyComponent/index.vue
<template>
<div class="my-component">
<slot></slot>
</div>
</template>
<script>
export default {
name: 'MyComponent',
props: {
// 组件属性
},
data() {
return {
// 组件数据
}
},
methods: {
// 组件方法
}
}
</script>
<style scoped>
.my-component {
/* 组件样式 */
}
</style>
6.5.2 自定义指令
// src/directive/permission.js
import store from '@/store'
function checkPermission(el, binding) {
const { value } = binding
const permissions = store.getters && store.getters.permissions
if (value && value instanceof Array && value.length > 0) {
const permissionRoles = value
const hasPermission = permissions.some(permission => {
return permissionRoles.includes(permission)
})
if (!hasPermission) {
el.parentNode && el.parentNode.removeChild(el)
}
} else {
throw new Error('need permissions! Like v-permission="[\'system:user:add\',\'system:user:edit\']"')
}
}
export default {
inserted(el, binding) {
checkPermission(el, binding)
},
update(el, binding) {
checkPermission(el, binding)
}
}
// 注册指令
import permission from './directive/permission'
Vue.directive('permission', permission)
6.5.3 自定义过滤器
// src/filters/index.js
export function formatDate(time) {
if (!time) return ''
const date = new Date(time)
const year = date.getFullYear()
const month = date.getMonth() + 1
const day = date.getDate()
return `${year}-${month}-${day}`
}
export function formatStatus(status) {
const statusMap = {
0: '正常',
1: '停用'
}
return statusMap[status] || '未知'
}
export default {
formatDate,
formatStatus
}
// 注册过滤器
import * as filters from './filters'
Object.keys(filters).forEach(key => {
Vue.filter(key, filters[key])
})
7. 总结
若依框架是一个成熟、稳定、功能完善的企业级快速开发平台。它提供了完整的权限管理、代码生成、系统监控等功能,能够帮助开发者快速搭建企业级应用系统。
7.1 框架优势
- 开箱即用:内置丰富的功能模块,无需从零开始
- 代码生成:强大的代码生成功能,大幅提升开发效率
- 权限控制:完善的RBAC权限模型,支持菜单权限、按钮权限、数据权限
- 技术先进:基于主流技术栈,易于维护和扩展
- 社区活跃:拥有活跃的开发者社区,丰富的文档和教程
7.2 适用场景
- 企业内部管理系统
- 电商后台管理系统
- CMS内容管理系统
- OA办公系统
- CRM客户关系管理系统
- 其他需要权限管理的Web应用
7.3 学习建议
- 循序渐进:先学习框架的基础使用,再深入理解框架原理
- 实践为主:通过实际项目开发来掌握框架的使用
- 阅读源码:阅读框架源码,理解框架的设计思想和实现原理
- 关注社区:关注若依社区动态,及时了解框架更新和最佳实践
- 持续学习:技术栈不断更新,需要持续学习新特性和新技术
7.4 发展趋势
若依框架正在向以下方向发展:
- Spring Boot 3:升级到Spring Boot 3,拥抱Jakarta EE
- Vue 3:升级到Vue 3,使用Composition API
- 微服务化:提供更好的微服务支持
- 低代码:集成低代码开发平台
- AI集成:集成AI能力,提供智能辅助开发
若依框架将继续保持其简洁、高效、实用的特点,为开发者提供更好的开发体验,助力企业快速构建高质量的Web应用系统。
更多推荐



所有评论(0)