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目录:存放各种业务controller
      • system:系统管理相关接口(用户、角色、菜单等)
      • 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 打包工具,编译和优化前端代码

数据库设计

若依框架的数据库设计遵循标准的权限管理模型,核心表结构包括:

  1. 用户表(sys_user):存储系统用户基本信息
  2. 角色表(sys_role):定义系统角色
  3. 菜单表(sys_menu):配置系统菜单和权限
  4. 部门表(sys_dept):组织机构管理
  5. 岗位表(sys_post):用户岗位管理
  6. 用户角色关联表(sys_user_role):用户与角色多对多关系
  7. 角色菜单关联表(sys_role_menu):角色与菜单多对多关系
  8. 角色部门关联表(sys_role_dept):角色数据权限配置

1.6 主要特性

  1. 完全响应式布局:支持电脑、平板、手机等所有主流设备
  2. 强大的一键生成功能:包括控制器、模型、视图、菜单等
  3. 支持多数据源:简单配置即可实现切换
  4. 支持按钮及数据权限:可自定义部门数据权限
  5. 完善的XSS防范及脚本过滤:彻底杜绝XSS攻击
  6. 支持CSRF跨站请求伪造防御:双重提交令牌验证
  7. Maven多项目依赖:模块及插件分项目,尽量松耦合
  8. 国际化支持:服务端及客户端支持
  9. 完善的日志记录体系:简单注解即可实现
  10. 支持服务监控、数据监控、缓存监控功能

2. 核心技术解析

2.1 SpringBoot框架

2.1.1 框架介绍

Spring Boot是一款开箱即用框架,提供各种默认配置来简化项目配置。遵循"约定优先于配置"的原则,使用SpringBoot只需很少的配置,大部分时候直接使用默认配置即可。

2.1.2 核心优势

  • 使编码变得简单:推荐使用注解
  • 使配置变得简单:自动配置、快速集成新技术能力,没有冗余代码生成和XML配置的要求
  • 使部署变得简单:内嵌Tomcat、Jetty、Undertow等web容器,无需以war包形式部署
  • 使监控变得简单:提供运行时的应用监控
  • 使集成变得简单:对主流开发框架的无配置集成
  • 使开发变得简单:极大地提高了开发快速构建项目、部署效率

2.1.3 在若依项目中的应用

  1. 自动配置:通过@SpringBootApplication注解实现组件扫描和自动配置
  2. 内嵌服务器:使用内嵌Tomcat,通过java -jar方式运行
  3. 配置管理:通过application.ymlapplication-druid.yml进行统一配置
  4. 依赖管理:通过spring-boot-starter简化依赖配置
  5. Actuator监控:提供应用监控和管理端点

2.2 Apache Shiro安全控制

2.2.1 框架介绍

Apache Shiro是Java的一个安全框架,可以帮助我们完成:认证、授权、加密、会话管理、与Web集成、缓存等。其不仅可以用在JavaSE环境,也可以用在JavaEE环境。

2.2.2 核心特性

  1. Authentication(身份认证):验证用户是不是拥有相应的身份
  2. Authorization(授权):验证某个已认证的用户是否拥有某个权限
  3. Session Management(会话管理):用户登录后就是一次会话,在没有退出之前,它的所有信息都在会话中
  4. Cryptography(加密):保护数据的安全性,如密码加密存储到数据库
  5. Caching(缓存):提高效率,如用户登录后,其用户信息、角色/权限不必每次去查
  6. Concurrency(并发):支持多线程应用的并发验证
  7. Testing(测试支持)
  8. Run As(允许一个用户假装为另一个用户)
  9. Remember Me(记住我):一次登录后,下次再来的话不用登录

2.2.3 核心架构

  1. Subject(主体):代表了当前的"用户",所有Subject都绑定到SecurityManager
  2. SecurityManager(安全管理器):所有与安全有关的操作都会与SecurityManager交互,是Shiro的核心
  3. Realm(域):Shiro从Realm获取安全数据(如用户、角色、权限)
  4. SessionManager:管理Session的生命周期
  5. SessionDAO:用于会话的CRUD操作
  6. CacheManager:缓存控制器,管理用户、角色、权限等的缓存

2.2.4 在若依项目中的应用

  1. 用户认证:通过自定义Realm实现用户名密码验证
  2. 权限控制:基于RBAC模型,实现菜单权限、按钮权限、数据权限
  3. 会话管理:集成Redis实现分布式会话管理
  4. 密码加密:使用MD5+盐值加密存储用户密码
  5. 记住我功能:通过Cookie实现记住登录状态
  6. 验证码:集成Kaptcha实现登录验证码

2.3 MyBatis持久层框架

2.3.1 框架介绍

Apache MyBatis是一款优秀的持久层框架,它支持定制化SQL、存储过程以及高级映射。MyBatis避免了几乎所有的JDBC代码和手动设置参数以及获取结果集的工作。

2.3.2 核心特性

  1. SQL映射:通过XML或注解方式配置SQL语句
  2. 动态SQL:支持动态SQL语句的拼接
  3. 关联映射:支持一对一、一对多、多对多关联
  4. 缓存机制:一级缓存和二级缓存
  5. 插件机制:支持分页插件、性能监控插件等

2.3.3 在若依项目中的应用

  1. 数据访问:通过Mapper接口和XML映射文件实现数据操作
  2. 分页查询:集成PageHelper插件实现分页
  3. 数据权限:通过AOP切面实现数据权限过滤
  4. 动态SQL:根据查询条件动态拼接SQL语句
  5. 批量操作:支持批量插入、批量更新、批量删除

2.4 MyBatis-Plus增强框架

2.4.1 框架介绍

MyBatis-Plus(简称MP)是一个MyBatis的增强工具,在MyBatis的基础上只做增强不做改变,为简化开发、提高效率而生。

2.4.2 核心特性

  1. 无侵入:只做增强不做改变,引入它不会对现有工程产生影响
  2. 损耗小:启动即会自动注入基本CRUD,性能基本无损耗,直接面向对象操作
  3. 强大的CRUD操作:内置通用Mapper、通用Service,仅仅通过少量配置即可实现单表大部分CRUD操作
  4. 支持Lambda形式调用:通过Lambda表达式,方便地编写各类查询条件
  5. 支持主键自动生成:支持多达4种主键策略(内含分布式唯一ID生成器-序列化)
  6. 支持ActiveRecord模式:支持ActiveRecord形式调用,实体类只需继承Model类即可进行强大的CRUD操作
  7. 支持自定义通用操作:支持全局通用方法注入
  8. 内置代码生成器:采用代码或者Maven插件可快速生成Mapper、Model、Service、Controller层代码
  9. 内置分页插件:基于MyBatis物理分页,开发者无需关心具体操作
  10. 内置性能分析插件:可输出Sql语句以及其执行时间
  11. 内置拦截器:提供全表delete、update操作智能分析阻断

2.4.3 在若依项目中的应用

在若依的新版本中,MyBatis-Plus逐步替代原生MyBatis:

  1. 基础CRUD:自动生成,无需编写XML
  2. 分页查询:自动分页插件
  3. 查询语法:Lambda表达式(类型安全)
  4. 乐观锁:注解即可实现
  5. 逻辑删除:注解即可实现

2.5 Spring Security安全框架

2.5.1 框架介绍

Spring Security是一个能够为基于Spring的企业应用系统提供声明式的安全访问控制解决方案的安全框架。

2.5.2 核心功能

  1. Authentication(认证):用户登录
  2. Authorization(授权):判断用户拥有什么权限,可以访问什么资源
  3. 安全防护:跨站脚本攻击、session攻击等
  4. 加密功能:密码加密存储
  5. 会话管理:管理用户会话
  6. 缓存支持:缓存用户权限信息
  7. RememberMe功能:记住登录状态

2.5.3 在若依项目中的应用

在若依-Vue等前后端分离版本中,Spring Security替代Shiro:

  1. JWT认证:基于JWT的无状态认证
  2. 过滤器链:配置自定义过滤器链
  3. 权限注解:支持@PreAuthorize等注解
  4. 密码加密:使用BCryptPasswordEncoder加密
  5. CORS配置:处理跨域请求

2.6 Thymeleaf模板引擎

2.6.1 模板介绍

Thymeleaf是一个用于Web和独立Java环境的模板引擎,能够处理HTML、XML、JavaScript、CSS甚至纯文本。能轻易的与Spring MVC等Web框架进行集成作为Web应用的模板引擎。

2.6.2 核心特性

  1. 自然模板:模板文件能直接在浏览器中打开并正确显示
  2. 国际化支持:支持多语言
  3. 语法简单:功能强大,使用方便
  4. Spring集成:与Spring框架集成良好
  5. 性能优化:3.0版本重构,减少内存占用和提升性能

2.6.3 在若依项目中的应用

  1. 页面渲染:服务端渲染HTML页面
  2. 表单绑定:表单数据绑定和验证
  3. 国际化:多语言支持
  4. 静态资源处理:CSS、JS、图片等静态资源引用

2.7 数据库连接池Druid

2.7.1 连接池介绍

Druid是阿里巴巴开源的数据库连接池,为监控而生,是Java语言中最好的数据库连接池之一。

2.7.2 核心特性

  1. 监控统计:内置强大的监控功能
  2. 防SQL注入:内置防SQL注入功能
  3. 扩展性强:可扩展监控统计、过滤等
  4. 性能优异:经过阿里巴巴大规模生产环境验证
  5. 配置简单:配置简单,易于使用

2.7.3 在若依项目中的应用

  1. 连接池管理:管理数据库连接
  2. SQL监控:监控SQL执行情况
  3. 慢SQL记录:记录执行时间过长的SQL
  4. 连接泄露检测:检测连接泄露情况

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导入方式:

  1. 菜单 FileImport
  2. 选择 MavenExisting Maven Projects
  3. 点击 Next 按钮,选择工作目录
  4. 点击 Finish 按钮,即可成功导入
  5. Eclipse会自动加载Maven依赖包,初次加载会比较慢

IntelliJ IDEA导入方式:

  1. 选择 FileOpen
  2. 选择项目根目录下的pom.xml文件
  3. 选择Open as Project
  4. 等待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 生成内容

  1. 后端代码

    • Domain实体类
    • Mapper接口
    • Mapper XML映射文件
    • Service接口
    • ServiceImpl实现类
    • Controller控制器
  2. 前端代码

    • index.vue页面
    • api.js接口文件
  3. 菜单SQL:生成菜单插入SQL语句

4.7.3 使用步骤

  1. 进入【代码生成】菜单
  2. 点击【导入】按钮,选择需要生成的表
  3. 编辑表信息(表名、描述等)
  4. 配置生成信息(包路径、模块名、作者等)
  5. 配置字段信息(Java类型、显示类型、查询方式等)
  6. 点击【生成代码】按钮
  7. 下载代码包并解压到项目
  8. 执行菜单SQL添加菜单
  9. 刷新页面,即可看到生成的功能模块

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 分层规范

  1. Controller层

    • 负责接收HTTP请求
    • 参数校验
    • 调用Service层
    • 返回响应结果
  2. Service层

    • 负责业务逻辑处理
    • 事务控制
    • 调用Mapper层
  3. 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 数据库优化

  1. 索引优化:为常用查询字段添加索引
  2. SQL优化:避免SELECT *,只查询需要的字段
  3. 分页查询:使用PageHelper进行分页
  4. 批量操作:尽量使用批量插入、批量更新

5.4.2 缓存优化

  1. 字典缓存:将字典数据缓存到Redis
  2. 用户缓存:缓存用户信息和权限
  3. 配置缓存:缓存系统配置参数
  4. 页面缓存:对不经常变化的页面进行缓存
// 使用缓存注解
@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 前端优化

  1. 按需加载:使用路由懒加载
  2. 代码分割:使用Webpack进行代码分割
  3. 压缩资源:压缩JS、CSS、图片等静态资源
  4. CDN加速:使用CDN加速静态资源加载

5.4.4 并发优化

  1. 连接池配置:合理配置数据库连接池参数
  2. 线程池配置:配置合适的线程池大小
  3. 异步处理:使用@Async进行异步处理
  4. 限流措施:使用注解进行接口限流

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 创建业务模块

在若依框架中添加新模块的步骤:

  1. 创建模块目录
com.ruoyi
├── ruoyi-admin
├── ruoyi-framework
├── ruoyi-system
├── ruoyi-common
├── ruoyi-generator
├── ruoyi-quartz
└── ruoyi-business        # 新增业务模块
  1. 配置Maven依赖
<!-- ruoyi-admin/pom.xml -->
<dependency>
    <groupId>com.ruoyi</groupId>
    <artifactId>ruoyi-business</artifactId>
    <version>${ruoyi.version}</version>
</dependency>
  1. 创建包结构
com.ruoyi.business
├── controller
├── service
├── mapper
└── domain

6.1.2 代码生成

使用若依的代码生成功能快速生成新模块代码:

  1. 创建数据库表
  2. 导入表到代码生成模块
  3. 配置生成参数
  4. 生成代码
  5. 将生成的代码复制到新模块

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集成

集成阿里云对象存储服务:

  1. 添加依赖
<dependency>
    <groupId>com.aliyun.oss</groupId>
    <artifactId>aliyun-sdk-oss</artifactId>
    <version>3.15.0</version>
</dependency>
  1. 配置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
  1. 创建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 短信服务集成

集成阿里云短信服务:

  1. 添加依赖
<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>
  1. 创建短信服务类
@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 框架优势

  1. 开箱即用:内置丰富的功能模块,无需从零开始
  2. 代码生成:强大的代码生成功能,大幅提升开发效率
  3. 权限控制:完善的RBAC权限模型,支持菜单权限、按钮权限、数据权限
  4. 技术先进:基于主流技术栈,易于维护和扩展
  5. 社区活跃:拥有活跃的开发者社区,丰富的文档和教程

7.2 适用场景

  • 企业内部管理系统
  • 电商后台管理系统
  • CMS内容管理系统
  • OA办公系统
  • CRM客户关系管理系统
  • 其他需要权限管理的Web应用

7.3 学习建议

  1. 循序渐进:先学习框架的基础使用,再深入理解框架原理
  2. 实践为主:通过实际项目开发来掌握框架的使用
  3. 阅读源码:阅读框架源码,理解框架的设计思想和实现原理
  4. 关注社区:关注若依社区动态,及时了解框架更新和最佳实践
  5. 持续学习:技术栈不断更新,需要持续学习新特性和新技术

7.4 发展趋势

若依框架正在向以下方向发展:

  1. Spring Boot 3:升级到Spring Boot 3,拥抱Jakarta EE
  2. Vue 3:升级到Vue 3,使用Composition API
  3. 微服务化:提供更好的微服务支持
  4. 低代码:集成低代码开发平台
  5. AI集成:集成AI能力,提供智能辅助开发

若依框架将继续保持其简洁、高效、实用的特点,为开发者提供更好的开发体验,助力企业快速构建高质量的Web应用系统。

Logo

汇聚全球AI编程工具,助力开发者即刻编程。

更多推荐