若依框架(RuoYi)是一套基于Java开发的快速开发框架,它提供了许多常用的功能模块和工具,包括用户管理、部门管理、角色管理、菜单管理、字典管理、系统监控、定时任务等。若依框架采用了MVC(Model-View-Controller)的架构模式,使用了Spring Boot、MyBatis等流行的开源框架,可以帮助开发者快速搭建企业级的后台管理系统。若依框架还提供了许多可视化的操作界面,使得开发者可以方便地进行系统配置和管理。
官网地址:若依框架
参考文章:
ruoyi架构那点事
若依框架(前后端分离版)
若依介绍
定义
若依(RuoYi)是一款基于Spring Boot和MyBatis的Java快速开发框架,主要用于企业级应用系统的开发。它提供了一套完整的后台管理系统解决方案,包括用户权限管理、菜单配置、数据监控等功能,同时支持代码生成器,显著提升开发效率。
核心特点
- 模块化设计:前后端分离架构,前端可选Vue2/Vue3版本,后端基于Spring Boot。
 
- 代码生成器:通过可视化配置自动生成CRUD代码,减少重复劳动。
 
- 权限控制:基于RBAC(角色基于访问控制)模型,精细化管理菜单、按钮权限。
 
- 多数据源支持:轻松配置动态数据源,适应复杂业务场景。
 
技术栈
- 后端:Spring Boot、MyBatis、Shiro(安全框架)、Redis(缓存)。
 
- 前端:Vue.js、Element UI(Vue2版本)或Ant Design Vue(Vue3版本)。
 
- 数据库:支持MySQL、Oracle、SQL Server等主流数据库
 
环境要求
JDK8 以上,mysql,Redis(Redis 配置_redis教程),Maven,Vue
提供的功能
系统管理
用户管理:用户是系统操作者,该功能主要完成系统用户配置。
部门管理:配置系统组织机构(公司、部门、小组),树结构展现支持数据权限。
岗位管理:配置系统用户所属担任职务。
菜单管理:配置系统菜单,操作权限,按钮权限标识等。
角色管理:角色菜单权限分配、设置角色按机构进行数据范围权限划分。
字典管理:对系统中经常使用的一些较为固定的数据进行维护。
参数管理:对系统动态配置常用参数。
通知公告:系统通知公告信息发布维护。
日志管理
操作日志:系统正常操作日志记录和查询;系统异常信息日志记录和查询。
登录日志:系统登录日志记录查询包含登录异常。
系统监控
在线用户:当前系统中活跃用户状态监控。
服务监控:监视当前系统CPU、内存、磁盘、堆栈等相关信息。
缓存监控:对系统的缓存查询,查看、清理等操作。
在线构建器:拖动表单元素生成相应的HTML代码。
连接池监视:监视当期系统数据库连接池状态,可进行分析SQL找出系统性能瓶颈。
系统工具
定时任务:在线(添加、修改、删除)任务调度包含执行结果日志。
代码生成:前后端代码的生成(java、html、xml、sql)支持CRUD下载 。
系统接口:根据业务代码自动生成相关的api接口文档。
Redis简介
Redis(Remote Dictionary Server)是一个开源的高性能键值存储系统,支持多种数据结构(如字符串、哈希、列表、集合等)。它以内存存储为主,兼具持久化功能,常用于缓存、消息队列、实时数据分析等场景。
核心特点
内存存储:数据主要存储在内存中,读写速度极快(可达10万次/秒以上)。
持久化支持:提供RDB(快照)和AOF(日志)两种方式将数据保存到磁盘。
数据结构丰富:支持字符串、哈希、列表、集合、有序集合等,适应不同业务需求。
高可用性:通过哨兵(Sentinel)和集群(Cluster)模式实现故障自动转移与横向扩展。
典型应用场景
- 缓存加速:减轻数据库压力,提升网站响应速度。
 
- 会话存储:分布式系统中共享用户会话信息。
 
- 实时排行榜:利用有序集合实现分数排序。
 
- 消息队列:通过列表或Stream结构实现异步任务处理。
 
与其他数据库对比
- Redis vs MySQL:Redis基于内存,适合高速读写;MySQL基于磁盘,适合复杂查询与事务。
 
- Redis vs Memcached:Redis支持数据持久化和更多数据结构,Memcached仅支持简单键值且无持久化。
 
Redis的灵活性和高性能使其成为现代分布式系统中的重要组件。
Nginx介绍
Nginx(发音为“engine-x”)是一款高性能的开源Web服务器、反向代理服务器、负载均衡器及HTTP缓存工具。最初由俄罗斯开发者Igor Sysoev设计,于2004年首次公开发布,现已成为全球最流行的Web服务器之一。
Nginx的核心功能
Web服务器:Nginx可以高效地处理静态内容(如HTML、CSS、图片),支持高并发连接,资源占用低,适合高流量场景。
反向代理:作为反向代理服务器,Nginx可将客户端请求转发至后端多台服务器,隐藏真实服务器信息,提升安全性和可扩展性。
负载均衡:支持多种负载均衡算法(如轮询、加权轮询、IP哈希),将流量分配到多台后端服务器,提高系统可用性和性能。
HTTP缓存:通过缓存静态或动态内容,减少后端服务器压力,加速响应速度。
Nginx的特点
事件驱动架构:基于异步非阻塞模型,支持高并发连接(单机可处理数万并发请求)。
 
模块化设计:通过模块扩展功能(如支持SSL、gzip压缩、HTTP/2)。
 
低资源消耗:内存和CPU占用率远低于传统服务器(如Apache)。
 
跨平台:支持Linux、Windows、macOS等操作系统。
 
登录验证码流程
首先通过后端生成一个表达式上传到前端,并且将验证码答案存放在Redis中,用户通过前端登录时将key值和输入答案一起传入数据库。
登录操作将内容保存在表中 sys_logininfor
进行增删该查操作

单应用在resources目录下的application.yml,多模块ruoyi-generator中的resources目录下的generator.yml,可以自己根据实际情况调整默认配置。
1 2 3 4 5 6 7 8 9 10
   | # 代码生成 gen:    # 开发者姓名,生成到类注释上   author: ruoyi   # 默认生成包路径 system 需改成自己的模块名称 如 system monitor tool   packageName: com.ruoyi.system   # 自动去除表前缀,默认是false   autoRemovePre: false   # 表前缀(生成类名不会包含表前缀,多个用逗号分隔)   tablePrefix: sys_
   | 
 
代码生成使用
1、登录系统(系统工具 -> 代码生成 -> 导入对应表)
2、代码生成列表中找到需要表(可预览、编辑、同步、删除生成配置)
3、点击生成代码会得到一个ruoyi.zip执行sql文件,按照包内目录结构复制到自己的项目中即可
代码生成支持编辑、预览、同步
预览:对生成的代码提前预览,防止出现一些不符合预期的情况。
同步:对原表的字段进行同步,包括新增、删除、修改的字段处理。
修改:对生成的代码基本信息、字段信息、生成信息做一系列的调整。
另外多模块所有代码生成的相关业务逻辑代码在ruoyi-generator模块,不需要可以自行删除模块。
部署
后端部署
打包工程文件:在工程文件下打开执行bin/package.bat文件进行打包,生成war/jar文件
前端部署
当项目开发完毕,只需要运行一行命令就可以打包应用
1 2 3 4
   |  npm run build:prod
  npm run build:stage
 
  | 
 
构建打包成功之后,会在根目录生成 dist 文件夹,里面就是构建打包好的文件,通常是 .js 、.css、index.html 等静态文件。
通常情况下 dist 文件夹的静态文件发布到你的 nginx 或者静态服务器即可,其中的 index.html 是后台服务的入口页面。
快速搭建若依
前端搭建
后端搭建
修改数据库和redis的配置。
代码介绍

ruoyi-quartz
使用quartz作为定时任务管理,这里不多说,老生常谈的问题。如果想了解可以参考文章:quartz实现定时任务
ruoyi-generator
该包为代码生成器,主要的流程如下。

GenTableServiceImpl/generatorCode代码生成
核心代码为该类的方法。主要使用org.apache.velocity.app的API。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
   | @Override public void generatorCode(String tableName){          GenTable table = genTableMapper.selectGenTableByName(tableName);          setSubTable(table);          setPkColumn(table);     VelocityInitializer.initVelocity();     VelocityContext context = VelocityUtils.prepareContext(table);          List<String> templates = VelocityUtils.getTemplateList(table.getTplCategory());     for (String template : templates) {         if (!StringUtils.containsAny(template, "sql.vm", "api.js.vm", "index.vue.vm", "index-tree.vue.vm")){                          StringWriter sw = new StringWriter();             Template tpl = Velocity.getTemplate(template, Constants.UTF8);             tpl.merge(context, sw);             try {                 String path = getGenPath(table, template);                 FileUtils.writeStringToFile(new File(path), sw.toString(), CharsetKit.UTF_8);             } catch (IOException e) {                 throw new ServiceException("渲染模板失败,表名:" + table.getTableName());             }         }     } }
   | 
 
ruoyi-system
标准的增删改查方法。没有什么多说的。但是ruoyi项目将controller与service部分隔离开来。
ruoyi-common

annotation
自定义注解,注解的功能在下方。

config
获取application.yml中的配置信息,并注入项目bean中。

constant
为项目提供常量池。
core
1.controller:所有接口层的基类,提供了分页,排序等方法,其他接口类直接继承即可。架构的常用做法。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43
   | public class BaseController {     protected final Logger logger = LoggerFactory.getLogger(this.getClass());          @InitBinder     public void initBinder(WebDataBinder binder){                  binder.registerCustomEditor(Date.class, new PropertyEditorSupport(){             @Override             public void setAsText(String text){                 setValue(DateUtils.parseDate(text));             }         });     }          protected void startPage() {         PageDomain pageDomain = TableSupport.buildPageRequest();         Integer pageNum = pageDomain.getPageNum();         Integer pageSize = pageDomain.getPageSize();         if (StringUtils.isNotNull(pageNum) && StringUtils.isNotNull(pageSize)) {             String orderBy = SqlUtil.escapeOrderBySql(pageDomain.getOrderBy());             Boolean reasonable = pageDomain.getReasonable();             PageHelper.startPage(pageNum, pageSize, orderBy).setReasonable(reasonable);         }     }          protected void startOrderBy() {         PageDomain pageDomain = TableSupport.buildPageRequest();         if (StringUtils.isNotEmpty(pageDomain.getOrderBy())) {             String orderBy = SqlUtil.escapeOrderBySql(pageDomain.getOrderBy());             PageHelper.orderBy(orderBy);         }     }          @SuppressWarnings({ "rawtypes", "unchecked" })     protected TableDataInfo getDataTable(List<?> list) {         TableDataInfo rspData = new TableDataInfo();         rspData.setCode(HttpStatus.SUCCESS);         rspData.setMsg("查询成功");         rspData.setRows(list);         rspData.setTotal(new PageInfo(list).getTotal());         return rspData;     } }
  | 
 
domain:权限等架构查询使用的实体类集合,其中BaseEntity、AjaxResult为架构设计经常使用的。BaseEntity需要其他实体类继承,提供了页码、总数等通用字段。AjaxResult是统一返回的实体类,能与前台约定固定的返回格式。{code: message: data}格式。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43
   | 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";          public AjaxResult() {}          public AjaxResult(int code, String msg) {         super.put(CODE_TAG, code);         super.put(MSG_TAG, msg);     }          public AjaxResult(int code, String msg, Object data) {         super.put(CODE_TAG, code);         super.put(MSG_TAG, msg);         if (StringUtils.isNotNull(data)) {             super.put(DATA_TAG, data);         }     } } public class BaseEntity implements Serializable {     private static final long serialVersionUID = 1L;          private String searchValue;          private String createBy;          @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")     private Date createTime;          private String updateBy;          @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")     private Date updateTime;          private String remark;          private Map<String, Object> params; }
  | 
 
exception
封装了一系列的异常,在特定时期使用即可。

filter
过滤器,通用写法,如果有使用直接复制即可。

utils
提供了一大波工具类。如果需要可以直接复制使用。

ruoyi-framework

DataScopeAspect
1.DataScopeAspect数据权限:在执行接口时,将当前用户的组织机构等查询条件利用AOP拼接上,可以看到根据在菜单页面维护的数据权限类别进行动态的拼接sql语句。核心代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
   | public static void dataScopeFilter(JoinPoint joinPoint, SysUser user, String deptAlias, String userAlias){          StringBuilder sqlString = new StringBuilder();     for (SysRole role : user.getRoles()) {         String dataScope = role.getDataScope();         if (DATA_SCOPE_ALL.equals(dataScope)) {             sqlString = new StringBuilder();             break;         } else if (DATA_SCOPE_CUSTOM.equals(dataScope)) {              sqlString.append(StringUtils.format(                 " OR {}.dept_id IN ( SELECT dept_id FROM sys_role_dept WHERE role_id = {} ) ",                 deptAlias, role.getRoleId()));         } else if (DATA_SCOPE_DEPT.equals(dataScope)) {              sqlString.append(StringUtils.format(" OR {}.dept_id = {} ", deptAlias, user.getDeptId()));         } else if (DATA_SCOPE_DEPT_AND_CHILD.equals(dataScope)) {              sqlString.append(StringUtils.format( " OR {}.dept_id IN ( SELECT dept_id FROM sys_dept WHERE dept_id = {} or find_in_set( {} , ancestors ) )",                 deptAlias, user.getDeptId(), user.getDeptId()));         }     } }
  | 
 
2.DataSourceAspect多数据源:利用上文DataSource注解动态的切换数据源,如果有需求可以直接使用,固定写法。
3.LogAspect日志:全局日志收集,如用户姓名、接口方法、调用ip等,并插入数据库,比较通用功能。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38
   | protected void handleLog(final JoinPoint joinPoint, Log controllerLog,                           final Exception e, Object jsonResult){         try {                          LoginUser loginUser = SecurityUtils.getLoginUser();                          SysOperLog operLog = new SysOperLog();             operLog.setStatus(BusinessStatus.SUCCESS.ordinal());                          String ip = IpUtils.getIpAddr(ServletUtils.getRequest());             operLog.setOperIp(ip);             operLog.setOperUrl(ServletUtils.getRequest().getRequestURI());                          if (loginUser != null) {                 operLog.setOperName(loginUser.getUsername());             }             if (e != null) {                 operLog.setStatus(BusinessStatus.FAIL.ordinal());                 operLog.setErrorMsg(StringUtils.substring(e.getMessage(), 0, 2000));             }                          String className = joinPoint.getTarget().getClass().getName();             String methodName = joinPoint.getSignature().getName();             operLog.setMethod(className + "." + methodName + "()");                          operLog.setRequestMethod(ServletUtils.getRequest().getMethod());                          getControllerMethodDescription(joinPoint, controllerLog, operLog, jsonResult);                          AsyncManager.me().execute(AsyncFactory.recordOper(operLog));         } catch (Exception exp) {                          log.error("==前置通知异常==");             log.error("异常信息:{}", exp.getMessage());             exp.printStackTrace();         }     } }
   | 
 
RateLimiterAspect限流:将每一次的调用的ip存放在redis中,然后判断本次调用和上次调用的相隔时间。短时间调用会阻止调用。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
   | @Before("@annotation(rateLimiter)") public void doBefore(JoinPoint point, RateLimiter rateLimiter) throws Throwable {          String key = rateLimiter.key();     int time = rateLimiter.time();     int count = rateLimiter.count();          String combineKey = getCombineKey(rateLimiter, point);     List<Object> keys = Collections.singletonList(combineKey);     try {                  Long number = redisTemplate.execute(limitScript, keys, count, time);         if (StringUtils.isNull(number) || number.intValue() > count) {             throw new ServiceException("访问过于频繁,请稍候再试");         }         log.info("限制请求'{}',当前请求'{}',缓存key'{}'", count, number.intValue(), key);     } catch (ServiceException e) {         throw e;     } catch (Exception e) {         throw new RuntimeException("服务器限流异常,请稍候再试");     } }
  | 
 
config

1.DruidProperties:从application.yml中获取数据源信息。固定写法不详细描述。
2.ApplicationConfig:配置时区信息这里不详细描述。
3.CaptchaConfig:验证码配置,配置文字文本框格式等,固定写法。
4.DruidConfig:多数据源配置,固定写法。
5.FastJson2JsonRedisSerializer:redis序列化配置,固定写法。
6.FilterConfig:过滤器配置,@ConditionalOnProperty(value = "xss.enabled", havingValue = "true")根据application.yml是否配置xss.enabled值决定是否加载该类,也就是是否开启xss拦截器。
7.KaptchaTextCreator:验证码验证的规则,这里是计算验证码,逻辑在此类中,不详细讲解。
8.MyBatisConfig:从application.yml动态获取mybatis包的地址。并重新封装SqlSessionFactory。实现mybatis路径的可配置化。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
   | @Bean public SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws Exception {          String typeAliasesPackage = env.getProperty("mybatis.typeAliasesPackage");     String mapperLocations = env.getProperty("mybatis.mapperLocations");     String configLocation = env.getProperty("mybatis.configLocation");          typeAliasesPackage = setTypeAliasesPackage(typeAliasesPackage);     VFS.addImplClass(SpringBootVFS.class);
      final SqlSessionFactoryBean sessionFactory = new SqlSessionFactoryBean();          sessionFactory.setDataSource(dataSource);          sessionFactory.setTypeAliasesPackage(typeAliasesPackage);          sessionFactory.setMapperLocations(resolveMapperLocations(StringUtils.split(mapperLocations, ",")));          sessionFactory.setConfigLocation(new DefaultResourceLoader().getResource(configLocation));     return sessionFactory.getObject(); }
   | 
 
9.RedisConfig:redis配置固定写法,如果整合redis可以参考。
10.ResourcesConfig:通用配置,其中包括拦截器生效配置,跨域配置等 可以直接使用。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37
   | public class ResourcesConfig implements WebMvcConfigurer {     @Autowired     private RepeatSubmitInterceptor repeatSubmitInterceptor;     @Override     public void addResourceHandlers(ResourceHandlerRegistry registry) {                  registry.addResourceHandler(Constants.RESOURCE_PREFIX + "/**")                 .addResourceLocations("file:" + RuoYiConfig.getProfile() + "/");                  registry.addResourceHandler("/swagger-ui/**")                 .addResourceLocations("classpath:/META-INF/resources/webjars/springfox-swagger-ui/");     }          @Override     public void addInterceptors(InterceptorRegistry registry){            registry.addInterceptor(repeatSubmitInterceptor).addPathPatterns("/**");     }          @Bean     public CorsFilter corsFilter() {         CorsConfiguration config = new CorsConfiguration();         config.setAllowCredentials(true);                  config.addAllowedOriginPattern("*");                  config.addAllowedHeader("*");                  config.addAllowedMethod("*");                  config.setMaxAge(1800L);                  UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();         source.registerCorsConfiguration("/**", config);                  return new CorsFilter(source);     } }
  | 
 
11.SecurityConfig:Security的配置,如果想详细了解,请访问博主专栏:Security入门到精通。
12.ServerConfig:获取请求信息,包括:域名,端口,上下文访问路径
13.ThreadPoolConfig:线程池配置,固定配置,其中下文的manager使用了异步线程池。
datasource
多数据源固定配置,与DataSourceAspect配合使用。
interceptor
主要功能为不允许重复点击,主要实现为将每一次的调用信息组装为key值存放到redis中,每一次调用从redis获取该key数据验证相隔时间,核心代码如下。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41
   | public boolean isRepeatSubmit(HttpServletRequest request, RepeatSubmit annotation){     String nowParams = "";     if (request instanceof RepeatedlyRequestWrapper){         RepeatedlyRequestWrapper repeatedlyRequest = (RepeatedlyRequestWrapper) request;         nowParams = HttpHelper.getBodyString(repeatedlyRequest);     }          if (StringUtils.isEmpty(nowParams)) {         nowParams = JSONObject.toJSONString(request.getParameterMap());     }     Map<String, Object> nowDataMap = new HashMap<String, Object>();     nowDataMap.put(REPEAT_PARAMS, nowParams);     nowDataMap.put(REPEAT_TIME, System.currentTimeMillis());          String url = request.getRequestURI();          String submitKey = request.getHeader(header);     if (StringUtils.isEmpty(submitKey)) {         submitKey = url;     }          String cacheRepeatKey = Constants.REPEAT_SUBMIT_KEY + submitKey;          Object sessionObj = redisCache.getCacheObject(cacheRepeatKey);          if (sessionObj != null) {         Map<String, Object> sessionMap = (Map<String, Object>) sessionObj;         if (sessionMap.containsKey(url)) {             Map<String, Object> preDataMap = (Map<String, Object>) sessionMap.get(url);                          if (compareParams(nowDataMap, preDataMap)                  && compareTime(nowDataMap, preDataMap, annotation.interval())) {                 return true;             }         }     }     Map<String, Object> cacheMap = new HashMap<String, Object>();     cacheMap.put(url, nowDataMap);     redisCache.setCacheObject(cacheRepeatKey, cacheMap, annotation.interval(), TimeUnit.MILLISECONDS);     return false; }
  | 
 
manager
AsyncManager、ShutdownManager为异步工厂提供方法,AsyncFactory为如何使用异步,如果需要使用可以直接在类中参考编写。
security
1.JwtAuthenticationTokenFilter:主要为验证token是否正确,
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
   | public class JwtAuthenticationTokenFilter extends OncePerRequestFilter {     @Autowired     private TokenService tokenService;     @Override     protected void doFilterInternal(HttpServletRequest request,                                      HttpServletResponse response, FilterChain chain)                                     throws ServletException, IOException {                  LoginUser loginUser = tokenService.getLoginUser(request);         if (StringUtils.isNotNull(loginUser) && StringUtils.isNull(SecurityUtils.getAuthentication()))         {                          tokenService.verifyToken(loginUser);                          UsernamePasswordAuthenticationToken authenticationToken = new                  UsernamePasswordAuthenticationToken(loginUser, null, loginUser.getAuthorities());             authenticationToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));                          SecurityContextHolder.getContext().setAuthentication(authenticationToken);         }         chain.doFilter(request, response);     } }
  | 
 
2.AuthenticationEntryPointImpl、LogoutSuccessHandlerImpl:将调用信息转换为json返回的固定写法。如果有需要可以参考。
web
1.server:获取服务器信息,如cpu等,都是固定写法,如需参考直接复制即可。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
   | private void setCpuInfo(CentralProcessor processor) {          long[] prevTicks = processor.getSystemCpuLoadTicks();     Util.sleep(OSHI_WAIT_SECOND);     long[] ticks = processor.getSystemCpuLoadTicks();     long nice = ticks[TickType.NICE.getIndex()] - prevTicks[TickType.NICE.getIndex()];     long irq = ticks[TickType.IRQ.getIndex()] - prevTicks[TickType.IRQ.getIndex()];     long softirq = ticks[TickType.SOFTIRQ.getIndex()] - prevTicks[TickType.SOFTIRQ.getIndex()];     long steal = ticks[TickType.STEAL.getIndex()] - prevTicks[TickType.STEAL.getIndex()];     long cSys = ticks[TickType.SYSTEM.getIndex()] - prevTicks[TickType.SYSTEM.getIndex()];     long user = ticks[TickType.USER.getIndex()] - prevTicks[TickType.USER.getIndex()];     long iowait = ticks[TickType.IOWAIT.getIndex()] - prevTicks[TickType.IOWAIT.getIndex()];     long idle = ticks[TickType.IDLE.getIndex()] - prevTicks[TickType.IDLE.getIndex()];     long totalCpu = user + nice + cSys + idle + iowait + irq + softirq + steal;     cpu.setCpuNum(processor.getLogicalProcessorCount());     cpu.setTotal(totalCpu);     cpu.setSys(cSys);     cpu.setUsed(user);     cpu.setWait(iowait);     cpu.setFree(idle); }
  private void setMemInfo(GlobalMemory memory) {     mem.setTotal(memory.getTotal());     mem.setUsed(memory.getTotal() - memory.getAvailable());     mem.setFree(memory.getAvailable()); }
  | 
 
2.exception:@RestControllerAdvice、@ExceptionHandler全局的拦截异常,当系统中有异常时,该类会直接获取异常并输出,很多类就不用特意try catch了。架构的常用写法。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
   | @RestControllerAdvice public class GlobalExceptionHandler {     private static final Logger log = LoggerFactory.getLogger(GlobalExceptionHandler.class);          @ExceptionHandler(AccessDeniedException.class)     public AjaxResult handleAccessDeniedException(AccessDeniedException e, HttpServletRequest request) {         String requestURI = request.getRequestURI();         log.error("请求地址'{}',权限校验失败'{}'", requestURI, e.getMessage());         return AjaxResult.error(HttpStatus.FORBIDDEN, "没有权限,请联系管理员授权");     }          @ExceptionHandler(HttpRequestMethodNotSupportedException.class)     public AjaxResult handleHttpRequestMethodNotSupported(HttpRequestMethodNotSupportedException e,             HttpServletRequest request) {         String requestURI = request.getRequestURI();         log.error("请求地址'{}',不支持'{}'请求", requestURI, e.getMethod());         return AjaxResult.error(e.getMessage());     } }
   | 
 
3.service:权限相关的service。大部分都是curd的业务,这里详细说TokenService。TokenService使用jwt生成token、从token中获取数据等。大部分为jwt固定写法。
1 2 3 4 5 6 7 8 9 10 11 12 13 14
   |  private String createToken(Map<String, Object> claims) {     String token = Jwts.builder()             .setClaims(claims)             .signWith(SignatureAlgorithm.HS512, secret).compact();     return token; }
  private Claims parseToken(String token) {     return Jwts.parser()             .setSigningKey(secret)             .parseClaimsJws(token)             .getBody(); }
 
  | 
 
ruoyi-admin

common通用方法
CaptchaController:获取验证码通过API生成验证码。详细代码不进行讲解。
CommonController:通用的上传下载接口,如果需要直接使用即可。
monitor监控
1.CacheController监控redis
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
   | public class CacheController {     @Autowired     private RedisTemplate<String, String> redisTemplate;     @PreAuthorize("@ss.hasPermi('monitor:cache:list')")     @GetMapping()     public AjaxResult getInfo() throws Exception {                  Properties info = (Properties) redisTemplate.execute(             (RedisCallback<Object>) connection -> connection.info()         );         Properties commandStats = (Properties) redisTemplate.execute(             (RedisCallback<Object>) connection -> connection.info("commandstats")         );                  Object dbSize = redisTemplate.execute((RedisCallback<Object>) connection -> connection.dbSize());         Map<String, Object> result = new HashMap<>(3);         result.put("info", info);         result.put("dbSize", dbSize);                  List<Map<String, String>> pieList = new ArrayList<>();         commandStats.stringPropertyNames().forEach(key -> {             Map<String, String> data = new HashMap<>(2);             String property = commandStats.getProperty(key);             data.put("name", StringUtils.removeStart(key, "cmdstat_"));             data.put("value", StringUtils.substringBetween(property, "calls=", ",usec"));             pieList.add(data);         });         result.put("commandStats", pieList);         return AjaxResult.success(result);     } }
  | 
 
2.ServerController服务器监控:主要监控正在运行服务器的信息,核心代码如下,如果需要使用直接复制即可。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75
   | public void copyTo() throws Exception {     SystemInfo si = new SystemInfo();     HardwareAbstractionLayer hal = si.getHardware();     setCpuInfo(hal.getProcessor());     setMemInfo(hal.getMemory());     setSysInfo();     setJvmInfo();     setSysFiles(si.getOperatingSystem()); }
  private void setCpuInfo(CentralProcessor processor) {          long[] prevTicks = processor.getSystemCpuLoadTicks();     Util.sleep(OSHI_WAIT_SECOND);     long[] ticks = processor.getSystemCpuLoadTicks();     long nice = ticks[TickType.NICE.getIndex()] - prevTicks[TickType.NICE.getIndex()];     long irq = ticks[TickType.IRQ.getIndex()] - prevTicks[TickType.IRQ.getIndex()];     long softirq = ticks[TickType.SOFTIRQ.getIndex()] - prevTicks[TickType.SOFTIRQ.getIndex()];     long steal = ticks[TickType.STEAL.getIndex()] - prevTicks[TickType.STEAL.getIndex()];     long cSys = ticks[TickType.SYSTEM.getIndex()] - prevTicks[TickType.SYSTEM.getIndex()];     long user = ticks[TickType.USER.getIndex()] - prevTicks[TickType.USER.getIndex()];     long iowait = ticks[TickType.IOWAIT.getIndex()] - prevTicks[TickType.IOWAIT.getIndex()];     long idle = ticks[TickType.IDLE.getIndex()] - prevTicks[TickType.IDLE.getIndex()];     long totalCpu = user + nice + cSys + idle + iowait + irq + softirq + steal;     cpu.setCpuNum(processor.getLogicalProcessorCount());     cpu.setTotal(totalCpu);     cpu.setSys(cSys);     cpu.setUsed(user);     cpu.setWait(iowait);     cpu.setFree(idle); }
  private void setMemInfo(GlobalMemory memory) {     mem.setTotal(memory.getTotal());     mem.setUsed(memory.getTotal() - memory.getAvailable());     mem.setFree(memory.getAvailable()); }
  private void setSysInfo() {     Properties props = System.getProperties();     sys.setComputerName(IpUtils.getHostName());     sys.setComputerIp(IpUtils.getHostIp());     sys.setOsName(props.getProperty("os.name"));     sys.setOsArch(props.getProperty("os.arch"));     sys.setUserDir(props.getProperty("user.dir")); }
  private void setJvmInfo() throws UnknownHostException {     Properties props = System.getProperties();     jvm.setTotal(Runtime.getRuntime().totalMemory());     jvm.setMax(Runtime.getRuntime().maxMemory());     jvm.setFree(Runtime.getRuntime().freeMemory());     jvm.setVersion(props.getProperty("java.version"));     jvm.setHome(props.getProperty("java.home")); }
  private void setSysFiles(OperatingSystem os) {     FileSystem fileSystem = os.getFileSystem();     List<OSFileStore> fsArray = fileSystem.getFileStores();     for (OSFileStore fs : fsArray)     {         long free = fs.getUsableSpace();         long total = fs.getTotalSpace();         long used = total - free;         SysFile sysFile = new SysFile();         sysFile.setDirName(fs.getMount());         sysFile.setSysTypeName(fs.getType());         sysFile.setTypeName(fs.getName());         sysFile.setTotal(convertFileSize(total));         sysFile.setFree(convertFileSize(free));         sysFile.setUsed(convertFileSize(used));         sysFile.setUsage(Arith.mul(Arith.div(used, total, 4), 100));         sysFiles.add(sysFile);     } }
  | 
 
3.SysLogininforController,SysOperlogController 登录日志、操作日志:主要查询前文AOP生成的日志表,普通的增删改查。
4.SysUserOnlineController在线用户管理:主要功能为在线用户监控与强踢下线。通过查询和删除redis缓存即可实现。
system业务代码
这里都是系统管理业务代码,写法比较统一,但是在编写过程中有部分架构的规定,下面一一说明。
**1.@PreAuthorize(“@ss.hasPermi(‘system:dict:list’)”)**:权限注解,上文已经详解
2.AjaxResult:提供了 结果编码/调用信息/数据的返回格式,为前台提供了统一的返回格式。架构的基础组成部分。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
   | 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";          public AjaxResult() { }          public AjaxResult(int code, String msg) {         super.put(CODE_TAG, code);         super.put(MSG_TAG, msg);     }          public AjaxResult(int code, String msg, Object data) {         super.put(CODE_TAG, code);         super.put(MSG_TAG, msg);         if (StringUtils.isNotNull(data)) {             super.put(DATA_TAG, data);         }     }          public static AjaxResult success() {         return AjaxResult.success("操作成功");     } }
  | 
 
3.extends BaseController:上文介绍了BaseController,在这里使用就可以直接调用分页、排序等方法了。不用每个类都编写。
总结
ruoyi框架搭建方便,依赖组件非常少。同时提供了基本的业务功能,如用户管理、部门管理、代码生成器等,但是对于技术的深度还是不太到位,如mq的使用,安全框架等技术都没有特多的涉及。