疯狂的狮子Li
2021-07-12 56322786887f99604450cde07c9cab6fb5ea5135
!67 同步dev分支
Merge pull request !67 from 疯狂的狮子Li/dev
已修改67个文件
已添加22个文件
已删除2个文件
2766 ■■■■ 文件已修改
README.md 21 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
docker/deploy.sh 92 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
docker/docker-compose.yml 119 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
docker/nginx/nginx.conf 77 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
pom.xml 21 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-admin/Dockerfile 14 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-admin/pom.xml 21 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysIndexController.java 29 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysMenuController.java 9 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysRoleController.java 55 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysUserController.java 27 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-admin/src/main/resources/application-dev.yml 27 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-admin/src/main/resources/application-prod.yml 31 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-admin/src/main/resources/application.yml 55 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-admin/src/main/resources/i18n/messages_en_US.properties 33 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-admin/src/main/resources/i18n/messages_zh_CN.properties 36 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-common/pom.xml 6 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-common/src/main/java/com/ruoyi/common/constant/UserConstants.java 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-common/src/main/java/com/ruoyi/common/core/domain/entity/SysUser.java 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-common/src/main/java/com/ruoyi/common/core/mybatisplus/cache/MybatisPlusRedisCache.java 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-common/src/main/java/com/ruoyi/common/core/mybatisplus/methods/InsertAll.java 31 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-common/src/main/java/com/ruoyi/common/core/redis/RedisCache.java 6 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-common/src/main/java/com/ruoyi/common/utils/DictUtils.java 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-common/src/main/java/com/ruoyi/common/utils/StringUtils.java 28 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-common/src/main/java/com/ruoyi/common/utils/poi/ExcelUtil.java 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-demo/pom.xml 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-demo/src/main/java/com/ruoyi/demo/controller/RedisCacheController.java 70 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-demo/src/main/java/com/ruoyi/demo/feign/fallback/FeignTestFallback.java 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-extend/pom.xml 18 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-extend/ruoyi-monitor-admin/Dockerfile 13 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-extend/ruoyi-monitor-admin/pom.xml 73 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-extend/ruoyi-monitor-admin/src/main/java/com/ruoyi/monitor/admin/MonitorAdminApplication.java 19 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-extend/ruoyi-monitor-admin/src/main/java/com/ruoyi/monitor/admin/config/AdminServerConfig.java 31 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-extend/ruoyi-monitor-admin/src/main/java/com/ruoyi/monitor/admin/config/SecurityConfig.java 48 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-extend/ruoyi-monitor-admin/src/main/resources/application.yml 11 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-framework/pom.xml 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-framework/src/main/java/com/ruoyi/framework/config/AdminServerConfig.java 63 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-framework/src/main/java/com/ruoyi/framework/config/FeignConfig.java 36 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-framework/src/main/java/com/ruoyi/framework/config/RedisConfig.java 8 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-framework/src/main/java/com/ruoyi/framework/config/SecurityConfig.java 12 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-framework/src/main/java/com/ruoyi/framework/config/properties/RedissonProperties.java 34 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-framework/src/main/java/com/ruoyi/framework/mybatisplus/CreateAndUpdateMetaObjectHandler.java 11 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-generator/pom.xml 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-generator/src/main/java/com/ruoyi/generator/service/GenTableServiceImpl.java 6 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-generator/src/main/resources/vm/java/mapper.java.vm 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-generator/src/main/resources/vm/vue/index-tree.vue.vm 42 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-generator/src/main/resources/vm/vue/index.vue.vm 44 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-quartz/pom.xml 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-system/pom.xml 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-system/src/main/java/com/ruoyi/system/domain/vo/MetaVo.java 24 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-system/src/main/java/com/ruoyi/system/mapper/SysUserMapper.java 16 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-system/src/main/java/com/ruoyi/system/service/ISysRoleService.java 38 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-system/src/main/java/com/ruoyi/system/service/ISysUserService.java 24 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-system/src/main/java/com/ruoyi/system/service/impl/SysMenuServiceImpl.java 34 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-system/src/main/java/com/ruoyi/system/service/impl/SysRoleServiceImpl.java 68 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-system/src/main/java/com/ruoyi/system/service/impl/SysUserServiceImpl.java 61 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-system/src/main/resources/mapper/system/SysUserMapper.xml 39 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-ui/.env.development 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-ui/.env.production 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-ui/.env.staging 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-ui/package.json 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-ui/src/api/system/role.js 46 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-ui/src/api/system/user.js 17 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-ui/src/assets/styles/ruoyi.scss 18 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-ui/src/components/Editor/index.vue 13 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-ui/src/components/FileUpload/index.vue 59 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-ui/src/components/HeaderSearch/index.vue 4 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-ui/src/components/ImageUpload/index.vue 196 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-ui/src/components/TopNav/index.vue 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-ui/src/directive/dialog/drag.js 64 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-ui/src/directive/index.js 18 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-ui/src/directive/permission/hasPermi.js 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-ui/src/directive/permission/hasRole.js 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-ui/src/directive/permission/index.js 15 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-ui/src/layout/components/AppMain.vue 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-ui/src/layout/components/InnerLink/index.vue 27 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-ui/src/main.js 13 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-ui/src/router/index.js 27 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-ui/src/store/modules/permission.js 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-ui/src/views/demo/demo/index.vue 6 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-ui/src/views/demo/tree/index.vue 6 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-ui/src/views/index.vue 26 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-ui/src/views/monitor/admin/index.vue 20 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-ui/src/views/system/notice/index.vue 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-ui/src/views/system/role/authUser.vue 213 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-ui/src/views/system/role/index.vue 38 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-ui/src/views/system/role/selectUser.vue 142 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-ui/src/views/system/user/authRole.vue 117 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-ui/src/views/system/user/index.vue 39 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-ui/vue.config.js 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
sql/ry_20210210.sql 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
README.md
@@ -4,12 +4,17 @@
[![License](https://img.shields.io/badge/License-MIT-blue.svg)](https://gitee.com/JavaLionLi/RuoYi-Vue-Plus/blob/master/LICENSE)
[![使用IntelliJ IDEA开发维护](https://img.shields.io/badge/IntelliJ%20IDEA-提供支持-blue.svg)](https://www.jetbrains.com/?from=RuoYi-Vue-Plus)
<br>
[![RuoYi-Vue-Plus](https://img.shields.io/badge/RuoYi_Vue_Plus-2.4.0-success.svg)](https://gitee.com/JavaLionLi/RuoYi-Vue-Plus)
[![RuoYi-Vue-Plus](https://img.shields.io/badge/RuoYi_Vue_Plus-2.5.0-success.svg)](https://gitee.com/JavaLionLi/RuoYi-Vue-Plus)
[![Spring Boot](https://img.shields.io/badge/Spring%20Boot-2.4-blue.svg)]()
[![JDK-8+](https://img.shields.io/badge/JDK-8+-green.svg)]()
[![JDK-11](https://img.shields.io/badge/JDK-11-green.svg)]()
基于 RuoYi-Vue é›†æˆ Mybatis-Plus Lombok Hutool ç­‰ä¾¿æ·å¼€å‘工具 é€‚配重写相关业务 ä¾¿äºŽå¼€å‘ å®šæœŸä¸Ž RuoYi-Vue åŒæ­¥
RuoYi-Vue-Plus æ˜¯åŸºäºŽ RuoYi-Vue é’ˆå¯¹ `分布式集群` åœºæ™¯å‡çº§ å®šæœŸä¸Ž RuoYi-Vue åŒæ­¥
集成 Lock4j dynamic-datasource ç­‰åˆ†å¸ƒå¼åœºæ™¯è§£å†³æ–¹æ¡ˆ
集成 Mybatis-Plus Lombok Hutool ç­‰ä¾¿æ·å¼€å‘工具 é€‚配重写相关业务 ä¾¿äºŽå¼€å‘
* å‰ç«¯å¼€å‘框架 Vue、Element UI
* åŽç«¯å¼€å‘框架 Spring Boot、Redis
* å®¹å™¨æ¡†æž¶ Undertow åŸºäºŽ Netty çš„高性能容器
@@ -27,12 +32,15 @@
* å¤šæ•°æ®æºæ¡†æž¶ dynamic-datasource æ”¯æŒä¸»ä»Žä¸Žå¤šç§ç±»æ•°æ®åº“异构
* Redis客户端 é‡‡ç”¨ Redisson æ€§èƒ½æ›´å¼º
* åˆ†å¸ƒå¼é” Lock4j æ³¨è§£é”ã€å·¥å…·é” å¤šç§å¤šæ ·
* éƒ¨ç½²æ–¹å¼ Docker å®¹å™¨ç¼–排 ä¸€é”®éƒ¨ç½²ä¸šåŠ¡é›†ç¾¤
## å‚考文档
使用框架前请仔细阅读文档重点注意事项
<br>
>[初始化项目 å¿…看](https://gitee.com/JavaLionLi/RuoYi-Vue-Plus/wikis/关于初始化项目?sort_id=4164117)
>
>[部署项目 å¿…看](https://gitee.com/JavaLionLi/RuoYi-Vue-Plus/wikis/关于应用部署?sort_id=4219382)
>[参考文档 Wiki](https://gitee.com/JavaLionLi/RuoYi-Vue-Plus/wikis/pages)
@@ -58,6 +66,12 @@
### å››ã€åŠ ç¾¤
以上三点已经能解决大家绝大部分问题了,如果还有问题没能通过这几种方式解决,那么加群,大家一起在群里探讨一下
## è´¡çŒ®ä»£ç 
欢迎各路英雄豪杰 `PR` ä»£ç  è¯·æäº¤åˆ° `dev` å¼€å‘分支 ç»Ÿä¸€æµ‹è¯•发版
框架定位为 `通用后台管理系统(分布式集群强化)` åŽŸåˆ™ä¸Šä¸æŽ¥å—ä¸šåŠ¡ `PR`
## ä¿®æ”¹RuoYi功能
### ä¾èµ–改动
@@ -74,6 +88,7 @@
* ç§»é™¤ fastjson ç»Ÿä¸€ä½¿ç”¨ jackson åºåˆ—化
* é›†æˆ dynamic-datasource å¤šæ•°æ®æº(默认支持MySQL,其他种类需自行适配)
* é›†æˆ Lock4j å®žçŽ°åˆ†å¸ƒå¼ æ³¨è§£é”ã€å·¥å…·é” å¤šç§å¤šæ ·
* å¢žåŠ  Docker å®¹å™¨ç¼–排 æ‰“包插件与部署脚本
### ä»£ç æ”¹åЍ
@@ -90,7 +105,7 @@
### å…¶ä»–
* åŒæ­¥å‡çº§ RuoYi-Vue 3.5.0
* åŒæ­¥å‡çº§ RuoYi-Vue
* GitHub åœ°å€ [RuoYi-Vue-Plus-github](https://github.com/JavaLionLi/RuoYi-Vue-Plus)
* å•模块 fast åˆ†æ”¯ [RuoYi-Vue-Plus-fast](https://gitee.com/JavaLionLi/RuoYi-Vue-Plus/tree/fast/)
* Oracle æ¨¡å— oracle åˆ†æ”¯ [RuoYi-Vue-Plus-oracle](https://gitee.com/JavaLionLi/RuoYi-Vue-Plus/tree/oracle/)
docker/deploy.sh
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,92 @@
#!/bin/bash
#使用说明,用来提示输入参数
usage() {
    echo "Usage: sh æ‰§è¡Œè„šæœ¬.sh [port|mount|monitor|base|start|stop|stopall|rm|rmiNoneTag]"
    exit 1
}
#开启所需端口
port(){
    firewall-cmd --add-port=3306/tcp --permanent
    firewall-cmd --add-port=6379/tcp --permanent
    service firewalld restart
}
##放置挂载文件
mount(){
    #挂载配置文件
    if test ! -f "/docker/nginx/conf/nginx.conf" ;then
        mkdir -p /docker/nginx/conf
        cp nginx/nginx.conf /docker/nginx/conf/nginx.conf
    fi
}
#启动基础模块
base(){
    docker-compose up -d mysql nginx-web redis
}
#启动基础模块
monitor(){
    docker-compose up -d ruoyi-monitor-admin
}
#启动程序模块
start(){
    docker-compose up -d ruoyi-server1 ruoyi-server2
}
#停止程序模块
stop(){
    docker-compose stop ruoyi-server1 ruoyi-server2
}
#关闭所有模块
stopall(){
    docker-compose stop
}
#删除所有模块
rm(){
    docker-compose rm
}
#删除Tag为空的镜像
rmiNoneTag(){
    docker images|grep none|awk '{print $3}'|xargs docker rmi -f
}
#根据输入参数,选择执行对应方法,不输入则执行使用说明
case "$1" in
"port")
    port
;;
"mount")
    mount
;;
"base")
    base
;;
"monitor")
    monitor
;;
"start")
    start
;;
"stop")
    stop
;;
"stopall")
    stopall
;;
"rm")
    rm
;;
"rmiNoneTag")
    rmiNoneTag
;;
*)
    usage
;;
esac
docker/docker-compose.yml
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,119 @@
version: '3'
services:
  mysql:
    image: mysql:8.0.24
    container_name: mysql
    environment:
      # æ—¶åŒºä¸Šæµ·
      TZ: Asia/Shanghai
      # root å¯†ç 
      MYSQL_ROOT_PASSWORD: root
      # åˆå§‹åŒ–数据库(后续的初始化sql会在这个库执行)
      MYSQL_DATABASE: ry-vue
    ports:
      - 3306:3306
    volumes:
      # æ•°æ®æŒ‚è½½
      - /docker/mysql/data/:/var/lib/mysql/
      # é…ç½®æŒ‚è½½
      - /docker/mysql/conf/:/etc/mysql/conf.d/
    command:
      # å°†mysql8.0默认密码策略 ä¿®æ”¹ä¸º åŽŸå…ˆ ç­–ç•¥ (mysql8.0对其默认策略做了更改 ä¼šå¯¼è‡´å¯†ç æ— æ³•匹配)
      --default-authentication-plugin=mysql_native_password
      --character-set-server=utf8mb4
      --collation-server=utf8mb4_general_ci
      --explicit_defaults_for_timestamp=true
      --lower_case_table_names=1
    privileged: true
    restart: always
    networks:
      ruoyi_net:
        ipv4_address: 172.30.0.36
  nginx-web:
    # å¦‚果需要指定版本 å°±æŠŠ latest æ¢æˆç‰ˆæœ¬å·
    image: nginx:latest
    container_name: nginx-web
    ports:
      - 80:80
      - 443:443
    volumes:
      # è¯ä¹¦æ˜ å°„
      - /docker/nginx/cert:/etc/nginx/cert
      # é…ç½®æ–‡ä»¶æ˜ å°„
      - /docker/nginx/conf/nginx.conf:/etc/nginx/nginx.conf
      # é¡µé¢ç›®å½•
      - /docker/nginx/html:/usr/share/nginx/html
      # æ—¥å¿—目录
      - /docker/nginx/log:/var/log/nginx
      # ä¸»æœºæœ¬æœºæ—¶é—´æ–‡ä»¶æ˜ å°„ ä¸Žæœ¬æœºæ—¶é—´åŒæ­¥
      - /etc/localtime:/etc/localtime:ro
    privileged: true
    restart: always
    networks:
      - ruoyi_net
  redis:
    image: redis:6.2.1
    container_name: redis
    ports:
      - 6379:6379
    environment:
      # è®¾ç½®çŽ¯å¢ƒå˜é‡ æ—¶åŒºä¸Šæµ· ç¼–码UTF-8
      TZ: Asia/Shanghai
      LANG: en_US.UTF-8
    volumes:
      # é…ç½®æ–‡ä»¶
      - /docker/redis/conf/redis.conf:/redis.conf:rw
      # æ•°æ®æ–‡ä»¶
      - /docker/redis/data:/data:rw
    command: "redis-server --appendonly yes"
    privileged: true
    restart: always
    networks:
      ruoyi_net:
        ipv4_address: 172.30.0.48
  ruoyi-server1:
    image: "ruoyi/ruoyi-server:2.5.0"
    environment:
      - TZ=Asia/Shanghai
    volumes:
      # é…ç½®æ–‡ä»¶
      - /docker/server1/logs/:/ruoyi/server/logs/
    privileged: true
    restart: always
    networks:
      ruoyi_net:
        ipv4_address: 172.30.0.60
  ruoyi-server2:
    image: "ruoyi/ruoyi-server:2.5.0"
    environment:
      - TZ=Asia/Shanghai
    volumes:
      # é…ç½®æ–‡ä»¶
      - /docker/server2/logs/:/ruoyi/server/logs/
    privileged: true
    restart: always
    networks:
      ruoyi_net:
        ipv4_address: 172.30.0.61
  ruoyi-monitor-admin:
    image: "ruoyi/ruoyi-monitor-admin:2.5.0"
    environment:
      - TZ=Asia/Shanghai
    privileged: true
    restart: always
    networks:
      ruoyi_net:
        ipv4_address: 172.30.0.90
networks:
  ruoyi_net:
    driver: bridge
    ipam:
      config:
        - subnet: 172.30.0.0/16
docker/nginx/nginx.conf
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,77 @@
worker_processes  1;
error_log  /var/log/nginx/error.log warn;
pid        /var/run/nginx.pid;
events {
    worker_connections  1024;
}
http {
    include       mime.types;
    default_type  application/octet-stream;
    sendfile        on;
    keepalive_timeout  65;
    # é™åˆ¶body大小
    client_max_body_size 100m;
    log_format  main  '$remote_addr - $remote_user [$time_local] "$request" '
                          '$status $body_bytes_sent "$http_referer" '
                          '"$http_user_agent" "$http_x_forwarded_for"';
    access_log  /var/log/nginx/access.log  main;
    upstream server {
        server 172.30.0.60:8080;
        server 172.30.0.61:8080;
    }
    upstream monitor-admin {
        server 172.30.0.90:9090;
    }
    server {
        listen       80;
        server_name  localhost;
        # https配置参考 start
        #listen       443 ssl;
        # è¯ä¹¦ç›´æŽ¥å­˜æ”¾ /docker/nginx/cert/ ç›®å½•下即可 æ›´æ”¹è¯ä¹¦åç§°å³å¯ æ— éœ€æ›´æ”¹è¯ä¹¦è·¯å¾„
        #ssl on;
        #ssl_certificate      /etc/nginx/cert/xxx.local.crt; # /etc/nginx/cert/ ä¸ºdocker映射路径 ä¸å…è®¸æ›´æ”¹
        #ssl_certificate_key  /etc/nginx/cert/xxx.local.key; # /etc/nginx/cert/ ä¸ºdocker映射路径 ä¸å…è®¸æ›´æ”¹
        #ssl_session_timeout 5m;
        #ssl_ciphers ECDHE-RSA-AES128-GCM-SHA256:ECDHE:ECDH:AES:HIGH:!NULL:!aNULL:!MD5:!ADH:!RC4;
        #ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
        #ssl_prefer_server_ciphers on;
        # https配置参考 end
        location / {
            root   /usr/share/nginx/html;
            try_files $uri $uri/ /index.html;
            index  index.html index.htm;
        }
        location /prod-api/ {
            proxy_set_header Host $http_host;
            proxy_set_header X-Real-IP $remote_addr;
            proxy_set_header REMOTE-HOST $remote_addr;
            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
            proxy_pass http://server/;
        }
        location /admin/ {
            proxy_set_header Host $http_host;
            proxy_set_header X-Real-IP $remote_addr;
            proxy_set_header REMOTE-HOST $remote_addr;
            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
            proxy_pass http://monitor-admin/admin/;
        }
        error_page   500 502 503 504  /50x.html;
        location = /50x.html {
            root   html;
        }
    }
}
pom.xml
@@ -6,32 +6,38 @@
    <groupId>com.ruoyi</groupId>
    <artifactId>ruoyi-vue-plus</artifactId>
    <version>${ruoyi-vue-plus.version}</version>
    <version>2.5.0</version>
    <name>RuoYi-Vue-Plus</name>
    <url>https://gitee.com/JavaLionLi/RuoYi-Vue-Plus</url>
    <description>RuoYi-Vue-Plus后台管理系统</description>
    <properties>
        <ruoyi-vue-plus.version>2.4.0</ruoyi-vue-plus.version>
        <spring-boot.version>2.4.7</spring-boot.version>
        <ruoyi-vue-plus.version>2.5.0</ruoyi-vue-plus.version>
        <spring-boot.version>2.4.8</spring-boot.version>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
        <java.version>1.8</java.version>
        <maven-jar-plugin.version>3.1.1</maven-jar-plugin.version>
        <druid.version>1.2.6</druid.version>
        <knife4j.version>3.0.2</knife4j.version>
        <knife4j.version>3.0.3</knife4j.version>
        <poi.version>4.1.2</poi.version>
        <velocity.version>1.7</velocity.version>
        <jwt.version>0.9.1</jwt.version>
        <mybatis-plus.version>3.4.3</mybatis-plus.version>
        <hutool.version>5.7.2</hutool.version>
        <hutool.version>5.7.4</hutool.version>
        <feign.version>3.0.3</feign.version>
        <feign-okhttp.version>11.0</feign-okhttp.version>
        <spring-boot-admin.version>2.4.1</spring-boot-admin.version>
        <redisson.version>3.15.2</redisson.version>
        <spring-boot-admin.version>2.4.3</spring-boot-admin.version>
        <redisson.version>3.16.0</redisson.version>
        <lock4j.version>2.2.1</lock4j.version>
        <datasource.version>3.4.0</datasource.version>
        <!-- docker é…ç½® -->
        <docker.registry.url>localhost</docker.registry.url>
        <docker.registry.host>http://${docker.registry.url}:2375</docker.registry.host>
        <docker.namespace>ruoyi</docker.namespace>
        <docker.plugin.version>1.2.0</docker.plugin.version>
    </properties>
    <!-- ä¾èµ–声明 -->
@@ -192,6 +198,7 @@
        <module>ruoyi-generator</module>
        <module>ruoyi-common</module>
        <module>ruoyi-demo</module>
        <module>ruoyi-extend</module>
    </modules>
    <packaging>pom</packaging>
ruoyi-admin/Dockerfile
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,14 @@
FROM anapsix/alpine-java:8_server-jre_unlimited
MAINTAINER Lion Li
RUN mkdir -p /ruoyi/server
RUN mkdir -p /ruoyi/server/logs
WORKDIR /ruoyi/server
EXPOSE 8080
ADD ./target/ruoyi-admin.jar ./app.jar
ENTRYPOINT ["java", "-Djava.security.egd=file:/dev/./urandom", "-jar", "app.jar"]
ruoyi-admin/pom.xml
@@ -5,7 +5,7 @@
    <parent>
        <artifactId>ruoyi-vue-plus</artifactId>
        <groupId>com.ruoyi</groupId>
        <version>${ruoyi-vue-plus.version}</version>
        <version>2.5.0</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>
    <packaging>jar</packaging>
@@ -83,6 +83,25 @@
                    <warName>${project.artifactId}</warName>
                </configuration>
           </plugin>
            <plugin>
                <groupId>com.spotify</groupId>
                <artifactId>docker-maven-plugin</artifactId>
                <version>${docker.plugin.version}</version>
                <configuration>
                    <imageName>${docker.namespace}/ruoyi-server:${project.version}</imageName>
                    <dockerDirectory>${project.basedir}</dockerDirectory>
                    <dockerHost>${docker.registry.host}</dockerHost>
                    <registryUrl>${docker.registry.url}</registryUrl>
                    <serverId>${docker.registry.url}</serverId>
                    <resources>
                        <resource>
                            <targetPath>/</targetPath>
                            <directory>${project.build.directory}</directory>
                            <include>${project.build.finalName}.jar</include>
                        </resource>
                    </resources>
                </configuration>
            </plugin>
        </plugins>
    </build>
ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysIndexController.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,29 @@
package com.ruoyi.web.controller.system;
import cn.hutool.core.util.StrUtil;
import com.ruoyi.common.config.RuoYiConfig;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
 * é¦–页
 *
 * @author ruoyi
 */
@RestController
public class SysIndexController
{
    /** ç³»ç»ŸåŸºç¡€é…ç½® */
    @Autowired
    private RuoYiConfig ruoyiConfig;
    /**
     * è®¿é—®é¦–页,提示语
     */
    @RequestMapping("/")
    public String index()
    {
        return StrUtil.format("欢迎使用{}后台管理框架,当前版本:v{},请通过前端地址访问。", ruoyiConfig.getName(), ruoyiConfig.getVersion());
    }
}
ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysMenuController.java
@@ -1,8 +1,6 @@
package com.ruoyi.web.controller.system;
import cn.hutool.core.util.StrUtil;
import com.ruoyi.common.annotation.Log;
import com.ruoyi.common.constant.Constants;
import com.ruoyi.common.constant.UserConstants;
import com.ruoyi.common.core.controller.BaseController;
import com.ruoyi.common.core.domain.AjaxResult;
@@ -11,6 +9,7 @@
import com.ruoyi.common.enums.BusinessType;
import com.ruoyi.common.utils.SecurityUtils;
import com.ruoyi.common.utils.ServletUtils;
import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.framework.web.service.TokenService;
import com.ruoyi.system.service.ISysMenuService;
import org.springframework.beans.factory.annotation.Autowired;
@@ -98,8 +97,7 @@
        {
            return AjaxResult.error("新增菜单'" + menu.getMenuName() + "'失败,菜单名称已存在");
        }
        else if (UserConstants.YES_FRAME.equals(menu.getIsFrame())
                && !StrUtil.startWithAny(menu.getPath(), Constants.HTTP, Constants.HTTPS))
        else if (UserConstants.YES_FRAME.equals(menu.getIsFrame()) && !StringUtils.ishttp(menu.getPath()))
        {
            return AjaxResult.error("新增菜单'" + menu.getMenuName() + "'失败,地址必须以http(s)://开头");
        }
@@ -119,8 +117,7 @@
        {
            return AjaxResult.error("修改菜单'" + menu.getMenuName() + "'失败,菜单名称已存在");
        }
        else if (UserConstants.YES_FRAME.equals(menu.getIsFrame())
                && !StrUtil.startWithAny(menu.getPath(), Constants.HTTP, Constants.HTTPS))
        else if (UserConstants.YES_FRAME.equals(menu.getIsFrame()) && !StringUtils.ishttp(menu.getPath()))
        {
            return AjaxResult.error("修改菜单'" + menu.getMenuName() + "'失败,地址必须以http(s)://开头");
        }
ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysRoleController.java
@@ -6,6 +6,7 @@
import com.ruoyi.common.core.controller.BaseController;
import com.ruoyi.common.core.domain.AjaxResult;
import com.ruoyi.common.core.domain.entity.SysRole;
import com.ruoyi.common.core.domain.entity.SysUser;
import com.ruoyi.common.core.domain.model.LoginUser;
import com.ruoyi.common.core.page.TableDataInfo;
import com.ruoyi.common.enums.BusinessType;
@@ -14,6 +15,7 @@
import com.ruoyi.common.utils.poi.ExcelUtil;
import com.ruoyi.framework.web.service.SysPermissionService;
import com.ruoyi.framework.web.service.TokenService;
import com.ruoyi.system.domain.SysUserRole;
import com.ruoyi.system.service.ISysRoleService;
import com.ruoyi.system.service.ISysUserService;
import org.springframework.beans.factory.annotation.Autowired;
@@ -171,4 +173,57 @@
    {
        return AjaxResult.success(roleService.selectRoleAll());
    }
    /**
     * æŸ¥è¯¢å·²åˆ†é…ç”¨æˆ·è§’色列表
     */
    @PreAuthorize("@ss.hasPermi('system:role:list')")
    @GetMapping("/authUser/allocatedList")
    public TableDataInfo allocatedList(SysUser user)
    {
        return userService.selectAllocatedList(user);
    }
    /**
     * æŸ¥è¯¢æœªåˆ†é…ç”¨æˆ·è§’色列表
     */
    @PreAuthorize("@ss.hasPermi('system:role:list')")
    @GetMapping("/authUser/unallocatedList")
    public TableDataInfo unallocatedList(SysUser user)
    {
        return userService.selectUnallocatedList(user);
    }
    /**
     * å–消授权用户
     */
    @PreAuthorize("@ss.hasPermi('system:role:edit')")
    @Log(title = "角色管理", businessType = BusinessType.GRANT)
    @PutMapping("/authUser/cancel")
    public AjaxResult cancelAuthUser(@RequestBody SysUserRole userRole)
    {
        return toAjax(roleService.deleteAuthUser(userRole));
    }
    /**
     * æ‰¹é‡å–消授权用户
     */
    @PreAuthorize("@ss.hasPermi('system:role:edit')")
    @Log(title = "角色管理", businessType = BusinessType.GRANT)
    @PutMapping("/authUser/cancelAll")
    public AjaxResult cancelAuthUserAll(Long roleId, Long[] userIds)
    {
        return toAjax(roleService.deleteAuthUsers(roleId, userIds));
    }
    /**
     * æ‰¹é‡é€‰æ‹©ç”¨æˆ·æŽˆæƒ
     */
    @PreAuthorize("@ss.hasPermi('system:role:edit')")
    @Log(title = "角色管理", businessType = BusinessType.GRANT)
    @PutMapping("/authUser/selectAll")
    public AjaxResult selectAuthUserAll(Long roleId, Long[] userIds)
    {
        return toAjax(roleService.insertAuthUsers(roleId, userIds));
    }
}
ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysUserController.java
@@ -196,4 +196,31 @@
        user.setUpdateBy(SecurityUtils.getUsername());
        return toAjax(userService.updateUserStatus(user));
    }
    /**
     * æ ¹æ®ç”¨æˆ·ç¼–号获取授权角色
     */
    @PreAuthorize("@ss.hasPermi('system:user:query')")
    @GetMapping("/authRole/{userId}")
    public AjaxResult authRole(@PathVariable("userId") Long userId)
    {
        SysUser user = userService.selectUserById(userId);
        List<SysRole> roles = roleService.selectRolesByUserId(userId);
        Map<String, Object> ajax = new HashMap<>();
        ajax.put("user", user);
        ajax.put("roles", SysUser.isAdmin(userId) ? roles : roles.stream().filter(r -> !r.isAdmin()).collect(Collectors.toList()));
        return AjaxResult.success(ajax);
    }
    /**
     * ç”¨æˆ·æŽˆæƒè§’色
     */
    @PreAuthorize("@ss.hasPermi('system:user:edit')")
    @Log(title = "用户管理", businessType = BusinessType.GRANT)
    @PutMapping("/authRole")
    public AjaxResult insertAuthRole(Long userId, Long[] roleIds)
    {
        userService.insertUserAuth(userId, roleIds);
        return success();
    }
}
ruoyi-admin/src/main/resources/application-dev.yml
@@ -110,3 +110,30 @@
    subscriptionsPerConnection: 5
    # DNS监测时间间隔,单位:毫秒
    dnsMonitoringInterval: 5000
--- # ç›‘控配置
spring:
  boot:
    admin:
      # Spring Boot Admin Client å®¢æˆ·ç«¯çš„相关配置
      client:
        # è®¾ç½® Spring Boot Admin Server åœ°å€
        url: http://localhost:9090/admin
        instance:
          prefer-ip: true # æ³¨å†Œå®žä¾‹æ—¶ï¼Œä¼˜å…ˆä½¿ç”¨ IP
        username: ruoyi
        password: 123456
# Actuator ç›‘控端点的配置项
management:
  endpoints:
    web:
      # Actuator æä¾›çš„ API æŽ¥å£çš„æ ¹ç›®å½•。默认为 /actuator
      base-path: /actuator
      exposure:
        # éœ€è¦å¼€æ”¾çš„端点。默认值只打开 health å’Œ info ä¸¤ä¸ªç«¯ç‚¹ã€‚通过设置 * ï¼Œå¯ä»¥å¼€æ”¾æ‰€æœ‰ç«¯ç‚¹ã€‚
        # ç”Ÿäº§çŽ¯å¢ƒä¸å»ºè®®æ”¾å¼€æ‰€æœ‰ æ ¹æ®é¡¹ç›®éœ€æ±‚放开即可
        include: '*'
  endpoint:
    logfile:
      external-file: ./logs/sys-console.log
ruoyi-admin/src/main/resources/application-prod.yml
@@ -12,7 +12,7 @@
        # ä¸»åº“数据源
        master:
          driverClassName: com.mysql.cj.jdbc.Driver
          url: jdbc:mysql://localhost:3306/ry-vue?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8&autoReconnect=true
          url: jdbc:mysql://172.30.0.36:3306/ry-vue?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8&autoReconnect=true
          username: root
          password: root
        # ä»Žåº“数据源
@@ -66,7 +66,7 @@
  # redis é…ç½®
  redis:
    # åœ°å€
    host: localhost
    host: 172.30.0.48
    # ç«¯å£ï¼Œé»˜è®¤ä¸º6379
    port: 6379
    # æ•°æ®åº“索引
@@ -110,3 +110,30 @@
    subscriptionsPerConnection: 5
    # DNS监测时间间隔,单位:毫秒
    dnsMonitoringInterval: 5000
--- # ç›‘控配置
spring:
  boot:
    admin:
      # Spring Boot Admin Client å®¢æˆ·ç«¯çš„相关配置
      client:
        # è®¾ç½® Spring Boot Admin Server åœ°å€
        url: http://172.30.0.90:9090/admin
        instance:
          prefer-ip: true # æ³¨å†Œå®žä¾‹æ—¶ï¼Œä¼˜å…ˆä½¿ç”¨ IP
        username: ruoyi
        password: 123456
# Actuator ç›‘控端点的配置项
management:
  endpoints:
    web:
      # Actuator æä¾›çš„ API æŽ¥å£çš„æ ¹ç›®å½•。默认为 /actuator
      base-path: /actuator
      exposure:
        # éœ€è¦å¼€æ”¾çš„端点。默认值只打开 health å’Œ info ä¸¤ä¸ªç«¯ç‚¹ã€‚通过设置 * ï¼Œå¯ä»¥å¼€æ”¾æ‰€æœ‰ç«¯ç‚¹ã€‚
        # ç”Ÿäº§çŽ¯å¢ƒä¸å»ºè®®æ”¾å¼€æ‰€æœ‰ æ ¹æ®é¡¹ç›®éœ€æ±‚放开即可
        include: health,info
  endpoint:
    logfile:
      external-file: ./logs/sys-console.log
ruoyi-admin/src/main/resources/application.yml
@@ -64,6 +64,8 @@
# Spring配置
spring:
  application:
    name: ${ruoyi.name}
  # èµ„源信息
  messages:
    # å›½é™…化资源文件路径
@@ -110,6 +112,8 @@
# MyBatisPlus配置
# https://baomidou.com/config/
mybatis-plus:
  # ä¸æ”¯æŒå¤šåŒ…, å¦‚有需要可在注解配置 æˆ– æå‡æ‰«åŒ…等级
  # ä¾‹å¦‚ com.**.**.mapper
  mapperPackage: com.ruoyi.**.mapper
  # å¯¹åº”çš„ XML æ–‡ä»¶ä½ç½®
  mapperLocations: classpath*:mapper/**/*Mapper.xml
@@ -156,7 +160,9 @@
    # STATEMENT å…³é—­ä¸€çº§ç¼“å­˜
    localCacheScope: SESSION
    # å¼€å¯Mybatis二级缓存,默认为 true
    cacheEnabled: true
    cacheEnabled: false
    # æ›´è¯¦ç»†çš„æ—¥å¿—输出 ä¼šæœ‰æ€§èƒ½æŸè€—
    # logImpl: org.apache.ibatis.logging.stdout.StdOutImpl
  global-config:
    # æ˜¯å¦æ‰“印 Logo banner
    banner: true
@@ -203,7 +209,7 @@
  # è¯·æ±‚前缀
  pathMapping: /dev-api
  # æ ‡é¢˜
  title: '标题:RuoYi-Vue-Plus后台管理系统_接口文档'
  title: '标题:${ruoyi.name}后台管理系统_接口文档'
  # æè¿°
  description: '描述:用于管理集团旗下公司的人员信息,具体包括XXX,XXX模块...'
  # ç‰ˆæœ¬
@@ -244,6 +250,8 @@
# feign ç›¸å…³é…ç½®
feign:
  # ä¸æ”¯æŒå¤šåŒ…, å¦‚有需要可在注解配置 æˆ– æå‡æ‰«åŒ…等级
  # ä¾‹å¦‚ com.**.**.feign
  package: com.ruoyi.**.feign
  # å¼€å¯åŽ‹ç¼©
  compression:
@@ -255,6 +263,21 @@
    enabled: true
  circuitbreaker:
    enabled: true
--- # redisson ç¼“存配置
redisson:
  cacheGroup:
    # ç”¨ä¾‹: @Cacheable(cacheNames="groupId", key="#XXX") æ–¹å¯ä½¿ç”¨ç¼“存组配置
    - groupId: redissonCacheMap
      # ç»„过期时间(脚本监控)
      ttl: 60000
      # ç»„最大空闲时间(脚本监控)
      maxIdleTime: 60000
      # ç»„最大长度
      maxSize: 0
    - groupId: testCache
      ttl: 1000
      maxIdleTime: 500
--- # åˆ†å¸ƒå¼é” lock4j å…¨å±€é…ç½®
lock4j:
@@ -293,31 +316,3 @@
            tablePrefix: QRTZ_
            # sqlserver å¯ç”¨
            # selectWithLockSQL: SELECT * FROM {0}LOCKS UPDLOCK WHERE LOCK_NAME = ?
--- # ç›‘控配置
spring:
  application:
    name: ruoyi-vue-plus
  boot:
    admin:
      # Spring Boot Admin Client å®¢æˆ·ç«¯çš„相关配置
      client:
        # è®¾ç½® Spring Boot Admin Server åœ°å€
        url: http://localhost:${server.port}${spring.boot.admin.context-path}
        instance:
          prefer-ip: true # æ³¨å†Œå®žä¾‹æ—¶ï¼Œä¼˜å…ˆä½¿ç”¨ IP
      # Spring Boot Admin Server æœåŠ¡ç«¯çš„ç›¸å…³é…ç½®
      context-path: /admin # é…ç½® Spring
# Actuator ç›‘控端点的配置项
management:
  endpoints:
    web:
      # Actuator æä¾›çš„ API æŽ¥å£çš„æ ¹ç›®å½•。默认为 /actuator
      base-path: /actuator
      exposure:
        # éœ€è¦å¼€æ”¾çš„端点。默认值只打开 health å’Œ info ä¸¤ä¸ªç«¯ç‚¹ã€‚通过设置 * ï¼Œå¯ä»¥å¼€æ”¾æ‰€æœ‰ç«¯ç‚¹ã€‚
        include: '*'
  endpoint:
    logfile:
      external-file: ./logs/sys-console.log
ruoyi-admin/src/main/resources/i18n/messages_en_US.properties
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,33 @@
#错误消息
not.null=
user.jcaptcha.error=
user.jcaptcha.expire=
user.not.exists=
user.password.not.match=
user.password.retry.limit.count=
user.password.retry.limit.exceed=
user.password.delete=
user.blocked=
role.blocked=
user.logout.success=
length.not.valid=
user.username.not.valid=
user.password.not.valid=
user.email.not.valid=
user.mobile.phone.number.not.valid=
user.login.success=
user.notfound=
user.forcelogout=
user.unknown.error=
##文件上传消息
upload.exceed.maxSize=
upload.filename.exceed.length=
##权限
no.permission=
no.create.permission=
no.update.permission=
no.delete.permission=
no.export.permission=
no.view.permission=
ruoyi-admin/src/main/resources/i18n/messages_zh_CN.properties
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,36 @@
#错误消息
not.null=* å¿…须填写
user.jcaptcha.error=验证码错误
user.jcaptcha.expire=验证码已失效
user.not.exists=用户不存在/密码错误
user.password.not.match=用户不存在/密码错误
user.password.retry.limit.count=密码输入错误{0}次
user.password.retry.limit.exceed=密码输入错误{0}次,帐户锁定10分钟
user.password.delete=对不起,您的账号已被删除
user.blocked=用户已封禁,请联系管理员
role.blocked=角色已封禁,请联系管理员
user.logout.success=退出成功
length.not.valid=长度必须在{min}到{max}个字符之间
user.username.not.valid=* 2到20个汉字、字母、数字或下划线组成,且必须以非数字开头
user.password.not.valid=* 5-50个字符
user.email.not.valid=邮箱格式错误
user.mobile.phone.number.not.valid=手机号格式错误
user.login.success=登录成功
user.notfound=请重新登录
user.forcelogout=管理员强制退出,请重新登录
user.unknown.error=未知错误,请重新登录
##文件上传消息
upload.exceed.maxSize=上传的文件大小超出限制的文件大小!<br/>允许的文件最大大小是:{0}MB!
upload.filename.exceed.length=上传的文件名最长{0}个字符
##权限
no.permission=您没有数据的权限,请联系管理员添加权限 [{0}]
no.create.permission=您没有创建数据的权限,请联系管理员添加权限 [{0}]
no.update.permission=您没有修改数据的权限,请联系管理员添加权限 [{0}]
no.delete.permission=您没有删除数据的权限,请联系管理员添加权限 [{0}]
no.export.permission=您没有导出数据的权限,请联系管理员添加权限 [{0}]
no.view.permission=您没有查看数据的权限,请联系管理员添加权限 [{0}]
ruoyi-common/pom.xml
@@ -5,7 +5,7 @@
    <parent>
        <artifactId>ruoyi-vue-plus</artifactId>
        <groupId>com.ruoyi</groupId>
        <version>${ruoyi-vue-plus.version}</version>
        <version>2.5.0</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>
@@ -116,10 +116,6 @@
            <artifactId>feign-okhttp</artifactId>
        </dependency>
        <dependency>
            <groupId>de.codecentric</groupId>
            <artifactId>spring-boot-admin-starter-server</artifactId>
        </dependency>
        <dependency>
            <groupId>de.codecentric</groupId>
            <artifactId>spring-boot-admin-starter-client</artifactId>
ruoyi-common/src/main/java/com/ruoyi/common/constant/UserConstants.java
@@ -56,6 +56,9 @@
    /** ParentView组件标识 */
    public final static String PARENT_VIEW = "ParentView";
    /** InnerLink组件标识 */
    public final static String INNER_LINK = "InnerLink";
    /** æ ¡éªŒè¿”回结果码 */
    public final static String UNIQUE = "0";
ruoyi-common/src/main/java/com/ruoyi/common/core/domain/entity/SysUser.java
@@ -148,6 +148,10 @@
    @TableField(exist = false)
    private Long[] postIds;
    /** è§’色ID */
    @TableField(exist = false)
    private Long roleId;
    public SysUser(Long userId)
    {
        this.userId = userId;
ruoyi-common/src/main/java/com/ruoyi/common/core/mybatisplus/cache/MybatisPlusRedisCache.java
@@ -15,6 +15,9 @@
/**
 * mybatis-redis äºŒçº§ç¼“å­˜
 *
 * ä½¿ç”¨æ–¹æ³• é…ç½®æ–‡ä»¶å¼€å¯ mybatis-plus äºŒçº§ç¼“å­˜
 * åœ¨ XxxMapper.java ç±»ä¸Šæ·»åŠ æ³¨è§£ @CacheNamespace(implementation = MybatisPlusRedisCache.class, eviction = MybatisPlusRedisCache.class)
 *
 * @author Lion Li
 */
@Slf4j
ruoyi-common/src/main/java/com/ruoyi/common/core/mybatisplus/methods/InsertAll.java
@@ -1,12 +1,16 @@
package com.ruoyi.common.core.mybatisplus.methods;
import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.core.enums.SqlMethod;
import com.baomidou.mybatisplus.core.injector.AbstractMethod;
import com.baomidou.mybatisplus.core.metadata.TableInfo;
import com.baomidou.mybatisplus.core.metadata.TableInfoHelper;
import org.apache.ibatis.executor.keygen.Jdbc3KeyGenerator;
import org.apache.ibatis.executor.keygen.KeyGenerator;
import org.apache.ibatis.executor.keygen.NoKeyGenerator;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.mapping.SqlSource;
import com.baomidou.mybatisplus.core.injector.AbstractMethod;
import com.baomidou.mybatisplus.core.metadata.TableInfo;
/**
 * å•sql批量插入
@@ -20,9 +24,28 @@
        final String sql = "<script>insert into %s %s values %s</script>";
        final String fieldSql = prepareFieldSql(tableInfo);
        final String valueSql = prepareValuesSqlForMysqlBatch(tableInfo);
        KeyGenerator keyGenerator = new NoKeyGenerator();
        SqlMethod sqlMethod = SqlMethod.INSERT_ONE;
        String keyProperty = null;
        String keyColumn = null;
        // è¡¨åŒ…含主键处理逻辑,如果不包含主键当普通字段处理
        if (StrUtil.isNotBlank(tableInfo.getKeyProperty())) {
            if (tableInfo.getIdType() == IdType.AUTO) {
                /** è‡ªå¢žä¸»é”® */
                keyGenerator = new Jdbc3KeyGenerator();
                keyProperty = tableInfo.getKeyProperty();
                keyColumn = tableInfo.getKeyColumn();
            } else {
                if (null != tableInfo.getKeySequence()) {
                    keyGenerator = TableInfoHelper.genKeyGenerator(getMethod(sqlMethod), tableInfo, builderAssistant);
                    keyProperty = tableInfo.getKeyProperty();
                    keyColumn = tableInfo.getKeyColumn();
                }
            }
        }
        final String sqlResult = String.format(sql, tableInfo.getTableName(), fieldSql, valueSql);
        SqlSource sqlSource = languageDriver.createSqlSource(configuration, sqlResult, modelClass);
        return this.addInsertMappedStatement(mapperClass, modelClass, "insertAll", sqlSource, new NoKeyGenerator(), null, null);
        return this.addInsertMappedStatement(mapperClass, modelClass, "insertAll", sqlSource, keyGenerator, keyProperty, keyColumn);
    }
    private String prepareFieldSql(TableInfo tableInfo) {
ruoyi-common/src/main/java/com/ruoyi/common/core/redis/RedisCache.java
@@ -205,9 +205,9 @@
     * @param hKeys Hash键集合
     * @return Hash对象集合
     */
    public <T> List<T> getMultiCacheMapValue(final String key, final Collection<Object> hKeys) {
        RListMultimap rListMultimap = redissonClient.getListMultimap(key);
        return rListMultimap.getAll(hKeys);
    public <K,V> Map<K,V> getMultiCacheMapValue(final String key, final Set<K> hKeys) {
        RMap<K,V>  rMap = redissonClient.getMap(key);
        return rMap.getAll(hKeys);
    }
    /**
ruoyi-common/src/main/java/com/ruoyi/common/utils/DictUtils.java
@@ -88,7 +88,7 @@
        StringBuilder propertyString = new StringBuilder();
        List<SysDictData> datas = getDictCache(dictType);
        if (StrUtil.containsAny(separator, dictValue) && CollUtil.isNotEmpty(datas))
        if (StrUtil.containsAny(dictValue, separator) && CollUtil.isNotEmpty(datas))
        {
            for (SysDictData dict : datas)
            {
@@ -128,7 +128,7 @@
        StringBuilder propertyString = new StringBuilder();
        List<SysDictData> datas = getDictCache(dictType);
        if (StrUtil.containsAny(separator, dictLabel) && CollUtil.isNotEmpty(datas))
        if (StrUtil.containsAny(dictLabel, separator) && CollUtil.isNotEmpty(datas))
        {
            for (SysDictData dict : datas)
            {
ruoyi-common/src/main/java/com/ruoyi/common/utils/StringUtils.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,28 @@
package com.ruoyi.common.utils;
import cn.hutool.core.util.StrUtil;
import com.ruoyi.common.constant.Constants;
/**
 * å­—符串工具类
 *
 * @author ruoyi
 */
public class StringUtils extends org.apache.commons.lang3.StringUtils {
    /** ç©ºå­—符串 */
    private static final String NULLSTR = "";
    /** ä¸‹åˆ’线 */
    private static final char SEPARATOR = '_';
    /**
     * æ˜¯å¦ä¸ºhttp(s)://开头
     *
     * @param link é“¾æŽ¥
     * @return ç»“æžœ
     */
    public static boolean ishttp(String link) {
        return StrUtil.startWithAny(link, Constants.HTTP, Constants.HTTPS);
    }
}
ruoyi-common/src/main/java/com/ruoyi/common/utils/poi/ExcelUtil.java
@@ -716,7 +716,7 @@
        for (String item : convertSource)
        {
            String[] itemArray = item.split("=");
            if (StrUtil.containsAny(separator, propertyValue))
            if (StrUtil.containsAny(propertyValue, separator))
            {
                for (String value : propertyValue.split(separator))
                {
@@ -753,7 +753,7 @@
        for (String item : convertSource)
        {
            String[] itemArray = item.split("=");
            if (StrUtil.containsAny(separator, propertyValue))
            if (StrUtil.containsAny(propertyValue, separator))
            {
                for (String value : propertyValue.split(separator))
                {
ruoyi-demo/pom.xml
@@ -5,7 +5,7 @@
    <parent>
        <artifactId>ruoyi-vue-plus</artifactId>
        <groupId>com.ruoyi</groupId>
        <version>${ruoyi-vue-plus.version}</version>
        <version>2.5.0</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>
ruoyi-demo/src/main/java/com/ruoyi/demo/controller/RedisCacheController.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,70 @@
package com.ruoyi.demo.controller;
import com.ruoyi.common.core.domain.AjaxResult;
import lombok.RequiredArgsConstructor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.CachePut;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
 * spring-cache æ¼”示案例
 *
 * @author Lion Li
 */
// ç±»çº§åˆ« ç¼“存统一配置
//@CacheConfig(cacheNames = "redissonCacheMap")
@RequiredArgsConstructor(onConstructor_ = @Autowired)
@RestController
@RequestMapping("/demo/cache")
public class RedisCacheController {
    /**
     * æµ‹è¯• @Cacheable
     *
     * è¡¨ç¤ºè¿™ä¸ªæ–¹æ³•有了缓存的功能,方法的返回值会被缓存下来
     * ä¸‹ä¸€æ¬¡è°ƒç”¨è¯¥æ–¹æ³•前,会去检查是否缓存中已经有值
     * å¦‚果有就直接返回,不调用方法
     * å¦‚果没有,就调用方法,然后把结果缓存起来
     * è¿™ä¸ªæ³¨è§£ã€Œä¸€èˆ¬ç”¨åœ¨æŸ¥è¯¢æ–¹æ³•上」
     *
     * cacheNames ä¸ºé…ç½®æ–‡ä»¶å†… groupId
     */
    @Cacheable(cacheNames = "redissonCacheMap", key = "#key", condition = "#key != null")
    @GetMapping("/test1")
    public AjaxResult<String> test1(String key, String value){
        return AjaxResult.success("操作成功", value);
    }
    /**
     * æµ‹è¯• @CachePut
     *
     * åŠ äº†@CachePut注解的方法,会把方法的返回值put到缓存里面缓存起来,供其它地方使用
     * å®ƒã€Œé€šå¸¸ç”¨åœ¨æ–°å¢žæ–¹æ³•上」
     *
     * cacheNames ä¸º é…ç½®æ–‡ä»¶å†… groupId
     */
    @CachePut(cacheNames = "redissonCacheMap", key = "#key", condition = "#key != null")
    @GetMapping("/test2")
    public AjaxResult<String> test2(String key, String value){
        return AjaxResult.success("操作成功", value);
    }
    /**
     * æµ‹è¯• @CacheEvict
     *
     * ä½¿ç”¨äº†CacheEvict注解的方法,会清空指定缓存
     * ã€Œä¸€èˆ¬ç”¨åœ¨æ›´æ–°æˆ–者删除的方法上」
     *
     * cacheNames ä¸º é…ç½®æ–‡ä»¶å†… groupId
     */
    @CacheEvict(cacheNames = "redissonCacheMap", key = "#key", condition = "#key != null")
    @GetMapping("/test3")
    public AjaxResult<String> test3(String key, String value){
        return AjaxResult.success("操作成功", value);
    }
}
ruoyi-demo/src/main/java/com/ruoyi/demo/feign/fallback/FeignTestFallback.java
@@ -7,7 +7,10 @@
/**
 * feign测试fallback
 * è‡ªå®šä¹‰å°è£…结构体熔断
 * éœ€é‡å†™è§£ç å™¨ æ ¹æ®è‡ªå®šä¹‰å®žä½“ è‡ªè¡Œè§£æžç†”æ–­
 *
 * @see {com.ruoyi.framework.config.FeignConfig#errorDecoder()}
 * @author Lion Li
 */
@Slf4j
ruoyi-extend/pom.xml
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,18 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <artifactId>ruoyi-vue-plus</artifactId>
        <groupId>com.ruoyi</groupId>
        <version>2.5.0</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>
    <artifactId>ruoyi-extend</artifactId>
    <packaging>pom</packaging>
    <modules>
        <module>ruoyi-monitor-admin</module>
    </modules>
</project>
ruoyi-extend/ruoyi-monitor-admin/Dockerfile
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,13 @@
FROM anapsix/alpine-java:8_server-jre_unlimited
MAINTAINER Lion Li
RUN mkdir -p /ruoyi/monitor
WORKDIR /ruoyi/monitor
EXPOSE 9090
ADD ./target/ruoyi-monitor-admin.jar ./app.jar
ENTRYPOINT ["java", "-Djava.security.egd=file:/dev/./urandom", "-jar", "app.jar"]
ruoyi-extend/ruoyi-monitor-admin/pom.xml
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,73 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <artifactId>ruoyi-extend</artifactId>
        <groupId>com.ruoyi</groupId>
        <version>2.5.0</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>
    <packaging>jar</packaging>
    <artifactId>ruoyi-monitor-admin</artifactId>
    <dependencies>
        <!-- SpringWeb模块 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <!-- spring security å®‰å…¨è®¤è¯ -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-security</artifactId>
        </dependency>
        <dependency>
            <groupId>de.codecentric</groupId>
            <artifactId>spring-boot-admin-starter-server</artifactId>
        </dependency>
    </dependencies>
    <build>
        <finalName>${project.artifactId}</finalName>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <version>${spring-boot.version}</version>
                <configuration>
                    <fork>true</fork> <!-- å¦‚果没有该配置,devtools不会生效 -->
                </configuration>
                <executions>
                    <execution>
                        <goals>
                            <goal>repackage</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>
            <plugin>
                <groupId>com.spotify</groupId>
                <artifactId>docker-maven-plugin</artifactId>
                <version>${docker.plugin.version}</version>
                <configuration>
                    <imageName>${docker.namespace}/${project.artifactId}:${project.version}</imageName>
                    <dockerDirectory>${project.basedir}</dockerDirectory>
                    <dockerHost>${docker.registry.host}</dockerHost>
                    <registryUrl>${docker.registry.url}</registryUrl>
                    <serverId>${docker.registry.url}</serverId>
                    <resources>
                        <resource>
                            <targetPath>/</targetPath>
                            <directory>${project.build.directory}</directory>
                            <include>${project.build.finalName}.jar</include>
                        </resource>
                    </resources>
                </configuration>
            </plugin>
        </plugins>
    </build>
</project>
ruoyi-extend/ruoyi-monitor-admin/src/main/java/com/ruoyi/monitor/admin/MonitorAdminApplication.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,19 @@
package com.ruoyi.monitor.admin;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
/**
 * Admin ç›‘控启动程序
 *
 * @author Lion Li
 */
@SpringBootApplication
public class MonitorAdminApplication {
    public static void main(String[] args) {
        SpringApplication.run(MonitorAdminApplication.class, args);
        System.out.println("Admin ç›‘控启动成功" );
    }
}
ruoyi-extend/ruoyi-monitor-admin/src/main/java/com/ruoyi/monitor/admin/config/AdminServerConfig.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,31 @@
package com.ruoyi.monitor.admin.config;
import de.codecentric.boot.admin.server.config.EnableAdminServer;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.task.TaskExecutionAutoConfiguration;
import org.springframework.boot.task.TaskExecutorBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Lazy;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import java.util.concurrent.Executor;
/**
 * springboot-admin server配置类
 *
 * @author Lion Li
 */
@Configuration
@EnableAdminServer
public class AdminServerConfig {
    @Lazy
    @Bean(name = TaskExecutionAutoConfiguration.APPLICATION_TASK_EXECUTOR_BEAN_NAME)
    @ConditionalOnMissingBean(Executor.class)
    public ThreadPoolTaskExecutor applicationTaskExecutor(TaskExecutorBuilder builder) {
        return builder.build();
    }
}
ruoyi-extend/ruoyi-monitor-admin/src/main/java/com/ruoyi/monitor/admin/config/SecurityConfig.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,48 @@
package com.ruoyi.monitor.admin.config;
import de.codecentric.boot.admin.server.config.AdminServerProperties;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.web.authentication.SavedRequestAwareAuthenticationSuccessHandler;
/**
 * spring security配置
 *
 * @author ruoyi
 */
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true, proxyTargetClass = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
    private final String adminContextPath;
    public SecurityConfig(AdminServerProperties adminServerProperties) {
        this.adminContextPath = adminServerProperties.getContextPath();
    }
    @Override
    protected void configure(HttpSecurity httpSecurity) throws Exception {
        SavedRequestAwareAuthenticationSuccessHandler successHandler = new SavedRequestAwareAuthenticationSuccessHandler();
        successHandler.setTargetUrlParameter("redirectTo");
        successHandler.setDefaultTargetUrl(adminContextPath + "/");
        httpSecurity.authorizeRequests()
            //授予对所有静态资产和登录页面的公共访问权限。
            .antMatchers(adminContextPath + "/assets/**").permitAll()
            .antMatchers(adminContextPath + "/login").permitAll()
            //必须对每个其他请求进行身份验证
            .anyRequest().authenticated().and()
            //配置登录和注销
            .formLogin().loginPage(adminContextPath + "/login")
            .successHandler(successHandler).and()
            .logout().logoutUrl(adminContextPath + "/logout").and()
            //启用HTTP-Basic支持。这是Spring Boot Admin Client注册所必需的
            .httpBasic().and().csrf().disable()
            .headers().frameOptions().disable();
    }
}
ruoyi-extend/ruoyi-monitor-admin/src/main/resources/application.yml
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,11 @@
server:
  port: 9090
spring:
  security:
    user:
      name: ruoyi
      password: 123456
  boot:
    admin:
      context-path: /admin
ruoyi-framework/pom.xml
@@ -5,7 +5,7 @@
    <parent>
        <artifactId>ruoyi-vue-plus</artifactId>
        <groupId>com.ruoyi</groupId>
        <version>${ruoyi-vue-plus.version}</version>
        <version>2.5.0</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>
ruoyi-framework/src/main/java/com/ruoyi/framework/config/AdminServerConfig.java
ÎļþÒÑɾ³ý
ruoyi-framework/src/main/java/com/ruoyi/framework/config/FeignConfig.java
@@ -54,4 +54,40 @@
        return new Retryer.Default();
    }
//    /**
//     * è‡ªå®šä¹‰å¼‚常解码器
//     * ç”¨äºŽè‡ªå®šä¹‰è¿”回体异常熔断
//     */
//    @Bean
//    public ErrorDecoder errorDecoder() {
//        return new CustomErrorDecoder();
//    }
//
//
//    /**
//     * è‡ªå®šä¹‰è¿”回体解码器
//     */
//    @Slf4j
//    public static class CustomErrorDecoder implements ErrorDecoder {
//
//        @Override
//        public Exception decode(String methodKey, Response response) {
//            Exception exception = null;
//            try {
//                // èŽ·å–åŽŸå§‹çš„è¿”å›žå†…å®¹
//                String json = JsonUtils.toJsonString(response.body().asReader(StandardCharsets.UTF_8));
//                exception = new RuntimeException(json);
//                // å°†è¿”回内容反序列化为Result,这里应根据自身项目作修改
//                AjaxResult result = JsonUtils.parseObject(json, AjaxResult.class);
//                // ä¸šåŠ¡å¼‚å¸¸æŠ›å‡ºç®€å•çš„ RuntimeException,保留原来错误信息
//                if (result.getCode() != 200) {
//                    exception = new RuntimeException(result.getMsg());
//                }
//            } catch (IOException e) {
//                log.error(e.getMessage(), e);
//            }
//            return exception;
//        }
//    }
}
ruoyi-framework/src/main/java/com/ruoyi/framework/config/RedisConfig.java
@@ -19,6 +19,7 @@
import java.io.IOException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
@@ -78,8 +79,13 @@
     */
    @Bean
    public CacheManager cacheManager(RedissonClient redissonClient) {
        List<RedissonProperties.CacheGroup> cacheGroup = redissonProperties.getCacheGroup();
        Map<String, CacheConfig> config = new HashMap<>();
        config.put("redissonCacheMap", new CacheConfig(30*60*1000, 10*60*1000));
        for (RedissonProperties.CacheGroup group : cacheGroup) {
            CacheConfig cacheConfig = new CacheConfig(group.getTtl(), group.getMaxIdleTime());
            cacheConfig.setMaxSize(group.getMaxSize());
            config.put(group.getGroupId(), cacheConfig);
        }
        return new RedissonSpringCacheManager(redissonClient, config, JsonJacksonCodec.INSTANCE);
    }
ruoyi-framework/src/main/java/com/ruoyi/framework/config/SecurityConfig.java
@@ -3,7 +3,6 @@
import com.ruoyi.framework.security.filter.JwtAuthenticationTokenFilter;
import com.ruoyi.framework.security.handle.AuthenticationEntryPointImpl;
import com.ruoyi.framework.security.handle.LogoutSuccessHandlerImpl;
import de.codecentric.boot.admin.server.config.AdminServerProperties;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.http.HttpMethod;
@@ -57,9 +56,6 @@
    @Autowired
    private CorsFilter corsFilter;
    @Autowired
    private AdminServerProperties adminServerProperties;
    /**
     * è§£å†³ æ— æ³•直接注入 AuthenticationManager
     *
@@ -104,12 +100,13 @@
                .antMatchers("/login", "/captchaImage").anonymous()
                .antMatchers(
                        HttpMethod.GET,
                        "/",
                        "/*.html",
                        "/**/*.html",
                        "/**/*.css",
                        "/**/*.js"
                        "/**/*.js",
                        "/profile/**"
                ).permitAll()
                .antMatchers("/profile/**").anonymous()
                .antMatchers("/common/download**").anonymous()
                .antMatchers("/common/download/resource**").anonymous()
                .antMatchers("/doc.html").anonymous()
@@ -117,9 +114,6 @@
                .antMatchers("/webjars/**").anonymous()
                .antMatchers("/*/api-docs").anonymous()
                .antMatchers("/druid/**").anonymous()
                // Spring Boot Admin Server çš„安全配置
                .antMatchers(adminServerProperties.getContextPath()).anonymous()
                .antMatchers(adminServerProperties.getContextPath() + "/**").anonymous()
                // Spring Boot Actuator çš„安全配置
                .antMatchers("/actuator").anonymous()
                .antMatchers("/actuator/**").anonymous()
ruoyi-framework/src/main/java/com/ruoyi/framework/config/properties/RedissonProperties.java
@@ -2,10 +2,11 @@
import lombok.Data;
import lombok.NoArgsConstructor;
import org.redisson.client.codec.Codec;
import org.redisson.config.TransportMode;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
import java.util.List;
/**
 * Redisson é…ç½®å±žæ€§
@@ -36,6 +37,11 @@
     * å•机服务配置
     */
    private SingleServerConfig singleServerConfig;
    /**
     * ç¼“存组
     */
    private List<CacheGroup> cacheGroup;
    @Data
    @NoArgsConstructor
@@ -98,4 +104,30 @@
    }
    @Data
    @NoArgsConstructor
    public static class CacheGroup {
        /**
         * ç»„id
         */
        private String groupId;
        /**
         * ç»„过期时间
         */
        private long ttl;
        /**
         * ç»„最大空闲时间
         */
        private long maxIdleTime;
        /**
         * ç»„最大长度
         */
        private int maxSize;
    }
}
ruoyi-framework/src/main/java/com/ruoyi/framework/mybatisplus/CreateAndUpdateMetaObjectHandler.java
@@ -1,6 +1,8 @@
package com.ruoyi.framework.mybatisplus;
import cn.hutool.http.HttpStatus;
import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler;
import com.ruoyi.common.exception.CustomException;
import com.ruoyi.common.utils.SecurityUtils;
import org.apache.ibatis.reflection.MetaObject;
@@ -8,6 +10,7 @@
/**
 * MP注入处理器
 *
 * @author Lion Li
 * @date 2021/4/25
 */
@@ -15,6 +18,7 @@
    @Override
    public void insertFill(MetaObject metaObject) {
        try {
        //根据属性名字设置要填充的值
        if (metaObject.hasGetter("createTime")) {
            if (metaObject.getValue("createTime") == null) {
@@ -26,10 +30,14 @@
                this.setFieldValByName("createBy", SecurityUtils.getUsername(), metaObject);
            }
        }
        } catch (Exception e) {
            throw new CustomException("自动注入异常 => " + e.getMessage(), HttpStatus.HTTP_UNAUTHORIZED);
        }
    }
    @Override
    public void updateFill(MetaObject metaObject) {
        try {
        if (metaObject.hasGetter("updateBy")) {
            if (metaObject.getValue("updateBy") == null) {
                this.setFieldValByName("updateBy", SecurityUtils.getUsername(), metaObject);
@@ -40,6 +48,9 @@
                this.setFieldValByName("updateTime", new Date(), metaObject);
            }
        }
        } catch (Exception e) {
            throw new CustomException("自动注入异常 => " + e.getMessage(), HttpStatus.HTTP_UNAUTHORIZED);
        }
    }
}
ruoyi-generator/pom.xml
@@ -5,7 +5,7 @@
    <parent>
        <artifactId>ruoyi-vue-plus</artifactId>
        <groupId>com.ruoyi</groupId>
        <version>${ruoyi-vue-plus.version}</version>
        <version>2.5.0</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>
ruoyi-generator/src/main/java/com/ruoyi/generator/service/GenTableServiceImpl.java
@@ -182,8 +182,8 @@
                    List<GenTableColumn> genTableColumns = genTableColumnMapper.selectDbTableColumnsByName(tableName);
                    for (GenTableColumn column : genTableColumns) {
                        GenUtils.initColumnField(column, table);
                        genTableColumnMapper.insert(column);
                    }
                    genTableColumnMapper.insertAll(genTableColumns);
                }
            }
        } catch (Exception e) {
@@ -258,7 +258,7 @@
        // èŽ·å–æ¨¡æ¿åˆ—è¡¨
        List<String> templates = VelocityUtils.getTemplateList(table.getTplCategory());
        for (String template : templates) {
            if (!StrUtil.containsAny(template, "sql.vm", "api.js.vm", "index.vue.vm", "index-tree.vue.vm")) {
            if (!StrUtil.containsAny("sql.vm", "api.js.vm", "index.vue.vm", "index-tree.vue.vm", template)) {
                // æ¸²æŸ“模板
                StringWriter sw = new StringWriter();
                Template tpl = Velocity.getTemplate(template, Constants.UTF8);
@@ -290,9 +290,9 @@
        dbTableColumns.forEach(column -> {
            if (!tableColumnNames.contains(column.getColumnName())) {
                GenUtils.initColumnField(column, table);
                genTableColumnMapper.insert(column);
            }
        });
        genTableColumnMapper.insertAll(tableColumns);
        List<GenTableColumn> delColumns = tableColumns.stream().filter(column -> !dbTableColumnNames.contains(column.getColumnName())).collect(Collectors.toList());
        if (CollUtil.isNotEmpty(delColumns)) {
ruoyi-generator/src/main/resources/vm/java/mapper.java.vm
@@ -11,8 +11,6 @@
 * @author ${author}
 * @date ${datetime}
 */
// å¦‚使需切换数据源 è¯·å‹¿ä½¿ç”¨ç¼“å­˜ ä¼šé€ æˆæ•°æ®ä¸ä¸€è‡´çŽ°è±¡
@CacheNamespace(implementation = MybatisPlusRedisCache.class, eviction = MybatisPlusRedisCache.class)
public interface ${ClassName}Mapper extends BaseMapperPlus<${ClassName}> {
}
ruoyi-generator/src/main/resources/vm/vue/index-tree.vue.vm
@@ -256,46 +256,10 @@
import { list${BusinessName}, get${BusinessName}, del${BusinessName}, add${BusinessName}, update${BusinessName}, export${BusinessName} } from "@/api/${moduleName}/${businessName}";
import Treeselect from "@riophae/vue-treeselect";
import "@riophae/vue-treeselect/dist/vue-treeselect.css";
#foreach($column in $columns)
#if($column.insert && !$column.pk && $column.htmlType == "imageUpload")
import ImageUpload from '@/components/ImageUpload';
#break
#end
#end
#foreach($column in $columns)
#if($column.insert && !$column.pk && $column.htmlType == "fileUpload")
import FileUpload from '@/components/FileUpload';
#break
#end
#end
#foreach($column in $columns)
#if($column.insert && !$column.pk && $column.htmlType == "editor")
import Editor from '@/components/Editor';
#break
#end
#end
export default {
  name: "${BusinessName}",
  components: {
#foreach($column in $columns)
#if($column.insert && !$column.pk && $column.htmlType == "imageUpload")
    ImageUpload,
#break
#end
#end
#foreach($column in $columns)
#if($column.insert && !$column.pk && $column.htmlType == "fileUpload")
    FileUpload,
#break
#end
#end
#foreach($column in $columns)
#if($column.insert && !$column.pk && $column.htmlType == "editor")
    Editor,
#break
#end
#end
    Treeselect
  },
  data() {
@@ -510,17 +474,19 @@
#end
          if (this.form.${pkColumn.javaField} != null) {
            update${BusinessName}(this.form).then(response => {
              this.buttonLoading = false;
              this.msgSuccess("修改成功");
              this.open = false;
              this.getList();
            }).finally(() => {
              this.buttonLoading = false;
            });
          } else {
            add${BusinessName}(this.form).then(response => {
              this.buttonLoading = false;
              this.msgSuccess("新增成功");
              this.open = false;
              this.getList();
            }).finally(() => {
              this.buttonLoading = false;
            });
          }
        }
ruoyi-generator/src/main/resources/vm/vue/index.vue.vm
@@ -308,47 +308,9 @@
<script>
import { list${BusinessName}, get${BusinessName}, del${BusinessName}, add${BusinessName}, update${BusinessName}, export${BusinessName} } from "@/api/${moduleName}/${businessName}";
#foreach($column in $columns)
#if($column.insert && !$column.pk && $column.htmlType == "imageUpload")
import ImageUpload from '@/components/ImageUpload';
#break
#end
#end
#foreach($column in $columns)
#if($column.insert && !$column.pk && $column.htmlType == "fileUpload")
import FileUpload from '@/components/FileUpload';
#break
#end
#end
#foreach($column in $columns)
#if($column.insert && !$column.pk && $column.htmlType == "editor")
import Editor from '@/components/Editor';
#break
#end
#end
export default {
  name: "${BusinessName}",
  components: {
#foreach($column in $columns)
#if($column.insert && !$column.pk && $column.htmlType == "imageUpload")
    ImageUpload,
#break
#end
#end
#foreach($column in $columns)
#if($column.insert && !$column.pk && $column.htmlType == "fileUpload")
    FileUpload,
#break
#end
#end
#foreach($column in $columns)
#if($column.insert && !$column.pk && $column.htmlType == "editor")
    Editor,
#break
#end
#end
  },
  data() {
    return {
      //按钮loading
@@ -567,17 +529,19 @@
#end
          if (this.form.${pkColumn.javaField} != null) {
            update${BusinessName}(this.form).then(response => {
              this.buttonLoading = false;
              this.msgSuccess("修改成功");
              this.open = false;
              this.getList();
            }).finally(() => {
              this.buttonLoading = false;
            });
          } else {
            add${BusinessName}(this.form).then(response => {
              this.buttonLoading = false;
              this.msgSuccess("新增成功");
              this.open = false;
              this.getList();
            }).finally(() => {
              this.buttonLoading = false;
            });
          }
        }
ruoyi-quartz/pom.xml
@@ -5,7 +5,7 @@
    <parent>
        <artifactId>ruoyi-vue-plus</artifactId>
        <groupId>com.ruoyi</groupId>
        <version>${ruoyi-vue-plus.version}</version>
        <version>2.5.0</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>
ruoyi-system/pom.xml
@@ -5,7 +5,7 @@
    <parent>
        <artifactId>ruoyi-vue-plus</artifactId>
        <groupId>com.ruoyi</groupId>
        <version>${ruoyi-vue-plus.version}</version>
        <version>2.5.0</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>
ruoyi-system/src/main/java/com/ruoyi/system/domain/vo/MetaVo.java
@@ -1,6 +1,8 @@
package com.ruoyi.system.domain.vo;
import lombok.*;
import com.ruoyi.common.utils.StringUtils;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.experimental.Accessors;
/**
@@ -28,6 +30,11 @@
     */
    private boolean noCache;
    /**
     * å†…链地址(http(s)://开头)
     */
    private String link;
    public MetaVo(String title, String icon) {
        this.title = title;
        this.icon = icon;
@@ -39,4 +46,19 @@
        this.noCache = noCache;
    }
    public MetaVo(String title, String icon, String link) {
        this.title = title;
        this.icon = icon;
        this.link = link;
    }
    public MetaVo(String title, String icon, boolean noCache, String link) {
        this.title = title;
        this.icon = icon;
        this.noCache = noCache;
        if (StringUtils.ishttp(link)) {
            this.link = link;
        }
    }
}
ruoyi-system/src/main/java/com/ruoyi/system/mapper/SysUserMapper.java
@@ -25,6 +25,22 @@
    public List<SysUser> selectUserList(SysUser sysUser);
    /**
     * æ ¹æ®æ¡ä»¶åˆ†é¡µæŸ¥è¯¢æœªå·²é…ç”¨æˆ·è§’色列表
     *
     * @param user ç”¨æˆ·ä¿¡æ¯
     * @return ç”¨æˆ·ä¿¡æ¯é›†åˆä¿¡æ¯
     */
    public Page<SysUser> selectAllocatedList(@Param("page") Page<SysUser> page, @Param("user") SysUser user);
    /**
     * æ ¹æ®æ¡ä»¶åˆ†é¡µæŸ¥è¯¢æœªåˆ†é…ç”¨æˆ·è§’色列表
     *
     * @param user ç”¨æˆ·ä¿¡æ¯
     * @return ç”¨æˆ·ä¿¡æ¯é›†åˆä¿¡æ¯
     */
    public Page<SysUser> selectUnallocatedList(@Param("page") Page<SysUser> page, @Param("user") SysUser user);
    /**
     * é€šè¿‡ç”¨æˆ·åæŸ¥è¯¢ç”¨æˆ·
     *
     * @param userName ç”¨æˆ·å
ruoyi-system/src/main/java/com/ruoyi/system/service/ISysRoleService.java
@@ -3,6 +3,8 @@
import com.ruoyi.common.core.domain.entity.SysRole;
import com.ruoyi.common.core.mybatisplus.core.IServicePlus;
import com.ruoyi.common.core.page.TableDataInfo;
import com.ruoyi.common.core.domain.entity.SysRole;
import com.ruoyi.system.domain.SysUserRole;
import java.util.List;
import java.util.Set;
@@ -26,7 +28,15 @@
    public List<SysRole> selectRoleList(SysRole role);
    /**
     * æ ¹æ®ç”¨æˆ·ID查询角色
     * æ ¹æ®ç”¨æˆ·ID查询角色列表
     *
     * @param userId ç”¨æˆ·ID
     * @return è§’色列表
     */
    public List<SysRole> selectRolesByUserId(Long userId);
    /**
     * æ ¹æ®ç”¨æˆ·ID查询角色权限
     *
     * @param userId ç”¨æˆ·ID
     * @return æƒé™åˆ—表
@@ -134,4 +144,30 @@
     * @return ç»“æžœ
     */
    public int deleteRoleByIds(Long[] roleIds);
    /**
     * å–消授权用户角色
     *
     * @param userRole ç”¨æˆ·å’Œè§’色关联信息
     * @return ç»“æžœ
     */
    public int deleteAuthUser(SysUserRole userRole);
    /**
     * æ‰¹é‡å–消授权用户角色
     *
     * @param roleId è§’色ID
     * @param userIds éœ€è¦å–消授权的用户数据ID
     * @return ç»“æžœ
     */
    public int deleteAuthUsers(Long roleId, Long[] userIds);
    /**
     * æ‰¹é‡é€‰æ‹©æŽˆæƒç”¨æˆ·è§’色
     *
     * @param roleId è§’色ID
     * @param userIds éœ€è¦åˆ é™¤çš„用户数据ID
     * @return ç»“æžœ
     */
    public int insertAuthUsers(Long roleId, Long[] userIds);
}
ruoyi-system/src/main/java/com/ruoyi/system/service/ISysUserService.java
@@ -25,6 +25,22 @@
    public List<SysUser> selectUserList(SysUser user);
    /**
     * æ ¹æ®æ¡ä»¶åˆ†é¡µæŸ¥è¯¢å·²åˆ†é…ç”¨æˆ·è§’色列表
     *
     * @param user ç”¨æˆ·ä¿¡æ¯
     * @return ç”¨æˆ·ä¿¡æ¯é›†åˆä¿¡æ¯
     */
    public TableDataInfo<SysUser> selectAllocatedList(SysUser user);
    /**
     * æ ¹æ®æ¡ä»¶åˆ†é¡µæŸ¥è¯¢æœªåˆ†é…ç”¨æˆ·è§’色列表
     *
     * @param user ç”¨æˆ·ä¿¡æ¯
     * @return ç”¨æˆ·ä¿¡æ¯é›†åˆä¿¡æ¯
     */
    public TableDataInfo<SysUser> selectUnallocatedList(SysUser user);
    /**
     * é€šè¿‡ç”¨æˆ·åæŸ¥è¯¢ç”¨æˆ·
     *
     * @param userName ç”¨æˆ·å
@@ -104,6 +120,14 @@
    public int updateUser(SysUser user);
    /**
     * ç”¨æˆ·æŽˆæƒè§’色
     *
     * @param userId ç”¨æˆ·ID
     * @param roleIds è§’色组
     */
    public void insertUserAuth(Long userId, Long[] roleIds);
    /**
     * ä¿®æ”¹ç”¨æˆ·çŠ¶æ€
     *
     * @param user ç”¨æˆ·ä¿¡æ¯
ruoyi-system/src/main/java/com/ruoyi/system/service/impl/SysMenuServiceImpl.java
@@ -3,6 +3,7 @@
import cn.hutool.core.lang.Validator;
import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.ruoyi.common.constant.Constants;
import com.ruoyi.common.constant.UserConstants;
import com.ruoyi.common.core.domain.TreeSelect;
import com.ruoyi.common.core.domain.entity.SysMenu;
@@ -10,6 +11,7 @@
import com.ruoyi.common.core.domain.entity.SysUser;
import com.ruoyi.common.core.mybatisplus.core.ServicePlusImpl;
import com.ruoyi.common.utils.SecurityUtils;
import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.system.domain.SysRoleMenu;
import com.ruoyi.system.domain.vo.MetaVo;
import com.ruoyi.system.domain.vo.RouterVo;
@@ -135,7 +137,7 @@
            router.setName(getRouteName(menu));
            router.setPath(getRouterPath(menu));
            router.setComponent(getComponent(menu));
            router.setMeta(new MetaVo(menu.getMenuName(), menu.getIcon(), StrUtil.equals("1", menu.getIsCache())));
            router.setMeta(new MetaVo(menu.getMenuName(), menu.getIcon(), StrUtil.equals("1", menu.getIsCache()), menu.getPath()));
            List<SysMenu> cMenus = menu.getChildren();
            if (!cMenus.isEmpty() && UserConstants.TYPE_DIR.equals(menu.getMenuType())) {
                router.setAlwaysShow(true);
@@ -148,7 +150,19 @@
                children.setPath(menu.getPath());
                children.setComponent(menu.getComponent());
                children.setName(StrUtil.upperFirst(menu.getPath()));
                children.setMeta(new MetaVo(menu.getMenuName(), menu.getIcon(), StrUtil.equals("1", menu.getIsCache())));
                children.setMeta(new MetaVo(menu.getMenuName(), menu.getIcon(), StrUtil.equals("1", menu.getIsCache()), menu.getPath()));
                childrenList.add(children);
                router.setChildren(childrenList);
            } else if (menu.getParentId().intValue() == 0 && isInnerLink(menu)) {
                router.setMeta(null);
                router.setPath("/inner");
                List<RouterVo> childrenList = new ArrayList<RouterVo>();
                RouterVo children = new RouterVo();
                String routerPath = StringUtils.replaceEach(menu.getPath(), new String[] { Constants.HTTP, Constants.HTTPS }, new String[] { "", "" });
                children.setPath(routerPath);
                children.setComponent(UserConstants.INNER_LINK);
                children.setName(StringUtils.capitalize(routerPath));
                children.setMeta(new MetaVo(menu.getMenuName(), menu.getIcon(), menu.getPath()));
                childrenList.add(children);
                router.setChildren(childrenList);
            }
@@ -305,6 +319,10 @@
     */
    public String getRouterPath(SysMenu menu) {
        String routerPath = menu.getPath();
        // å†…链打开外网方式
        if (menu.getParentId().intValue() != 0 && isInnerLink(menu)) {
            routerPath = StringUtils.replaceEach(routerPath, new String[] { Constants.HTTP, Constants.HTTPS }, new String[] { "", "" });
        }
        // éžå¤–链并且是一级目录(类型为目录)
        if (0 == menu.getParentId().intValue() && UserConstants.TYPE_DIR.equals(menu.getMenuType())
                && UserConstants.NO_FRAME.equals(menu.getIsFrame())) {
@@ -327,6 +345,8 @@
        String component = UserConstants.LAYOUT;
        if (StrUtil.isNotEmpty(menu.getComponent()) && !isMenuFrame(menu)) {
            component = menu.getComponent();
        } else if (StrUtil.isEmpty(menu.getComponent()) && menu.getParentId().intValue() != 0 && isInnerLink(menu)) {
            component = UserConstants.INNER_LINK;
        } else if (StrUtil.isEmpty(menu.getComponent()) && isParentView(menu)) {
            component = UserConstants.PARENT_VIEW;
        }
@@ -345,6 +365,16 @@
    }
    /**
     * æ˜¯å¦ä¸ºå†…链组件
     *
     * @param menu èœå•信息
     * @return ç»“æžœ
     */
    public boolean isInnerLink(SysMenu menu) {
        return menu.getIsFrame().equals(UserConstants.NO_FRAME) && StringUtils.ishttp(menu.getPath());
    }
    /**
     * æ˜¯å¦ä¸ºparent_view组件
     *
     * @param menu èœå•信息
ruoyi-system/src/main/java/com/ruoyi/system/service/impl/SysRoleServiceImpl.java
@@ -60,6 +60,27 @@
    }
    /**
     * æ ¹æ®ç”¨æˆ·ID查询角色
     *
     * @param userId ç”¨æˆ·ID
     * @return è§’色列表
     */
    @Override
    public List<SysRole> selectRolesByUserId(Long userId) {
        List<SysRole> userRoles = baseMapper.selectRolePermissionByUserId(userId);
        List<SysRole> roles = selectRoleAll();
        for (SysRole role : roles) {
            for (SysRole userRole : userRoles) {
                if (role.getRoleId().longValue() == userRole.getRoleId().longValue()) {
                    role.setFlag(true);
                    break;
                }
            }
        }
        return roles;
    }
    /**
     * æ ¹æ®ç”¨æˆ·ID查询权限
     *
     * @param userId ç”¨æˆ·ID
@@ -305,4 +326,51 @@
        roleDeptMapper.delete(new LambdaQueryWrapper<SysRoleDept>().in(SysRoleDept::getRoleId, ids));
        return baseMapper.deleteBatchIds(ids);
    }
    /**
     * å–消授权用户角色
     *
     * @param userRole ç”¨æˆ·å’Œè§’色关联信息
     * @return ç»“æžœ
     */
    @Override
    public int deleteAuthUser(SysUserRole userRole) {
        return userRoleMapper.delete(new LambdaQueryWrapper<SysUserRole>()
            .eq(SysUserRole::getRoleId, userRole.getRoleId())
            .eq(SysUserRole::getUserId, userRole.getUserId()));
    }
    /**
     * æ‰¹é‡å–消授权用户角色
     *
     * @param roleId è§’色ID
     * @param userIds éœ€è¦å–消授权的用户数据ID
     * @return ç»“æžœ
     */
    @Override
    public int deleteAuthUsers(Long roleId, Long[] userIds) {
        return userRoleMapper.delete(new LambdaQueryWrapper<SysUserRole>()
            .eq(SysUserRole::getRoleId, roleId)
            .in(SysUserRole::getUserId, Arrays.asList(userIds)));
    }
    /**
     * æ‰¹é‡é€‰æ‹©æŽˆæƒç”¨æˆ·è§’色
     *
     * @param roleId è§’色ID
     * @param userIds éœ€è¦åˆ é™¤çš„用户数据ID
     * @return ç»“æžœ
     */
    @Override
    public int insertAuthUsers(Long roleId, Long[] userIds) {
        // æ–°å¢žç”¨æˆ·ä¸Žè§’色管理
        List<SysUserRole> list = new ArrayList<SysUserRole>();
        for (Long userId : userIds) {
            SysUserRole ur = new SysUserRole();
            ur.setUserId(userId);
            ur.setRoleId(roleId);
            list.add(ur);
        }
        return userRoleMapper.insertAll(list);
    }
}
ruoyi-system/src/main/java/com/ruoyi/system/service/impl/SysUserServiceImpl.java
@@ -70,6 +70,30 @@
    }
    /**
     * æ ¹æ®æ¡ä»¶åˆ†é¡µæŸ¥è¯¢å·²åˆ†é…ç”¨æˆ·è§’色列表
     *
     * @param user ç”¨æˆ·ä¿¡æ¯
     * @return ç”¨æˆ·ä¿¡æ¯é›†åˆä¿¡æ¯
     */
    @Override
    @DataScope(deptAlias = "d", userAlias = "u", isUser = true)
    public TableDataInfo<SysUser> selectAllocatedList(SysUser user) {
        return PageUtils.buildDataInfo(baseMapper.selectAllocatedList(PageUtils.buildPage(), user));
    }
    /**
     * æ ¹æ®æ¡ä»¶åˆ†é¡µæŸ¥è¯¢æœªåˆ†é…ç”¨æˆ·è§’色列表
     *
     * @param user ç”¨æˆ·ä¿¡æ¯
     * @return ç”¨æˆ·ä¿¡æ¯é›†åˆä¿¡æ¯
     */
    @Override
    @DataScope(deptAlias = "d", userAlias = "u", isUser = true)
    public TableDataInfo<SysUser> selectUnallocatedList(SysUser user) {
        return PageUtils.buildDataInfo(baseMapper.selectUnallocatedList(PageUtils.buildPage(), user));
    }
    /**
     * é€šè¿‡ç”¨æˆ·åæŸ¥è¯¢ç”¨æˆ·
     *
     * @param userName ç”¨æˆ·å
@@ -232,6 +256,21 @@
    }
    /**
     * ç”¨æˆ·æŽˆæƒè§’色
     *
     * @param userId ç”¨æˆ·ID
     * @param roleIds è§’色组
     */
    @Override
    @Transactional
    public void insertUserAuth(Long userId, Long[] roleIds)
    {
        userRoleMapper.delete(new LambdaQueryWrapper<SysUserRole>()
            .eq(SysUserRole::getUserId, userId));
        insertUserRole(userId, roleIds);
    }
    /**
     * ä¿®æ”¹ç”¨æˆ·çŠ¶æ€
     *
     * @param user ç”¨æˆ·ä¿¡æ¯
@@ -339,6 +378,28 @@
    }
    /**
     * æ–°å¢žç”¨æˆ·è§’色信息
     *
     * @param userId ç”¨æˆ·ID
     * @param roleIds è§’色组
     */
    public void insertUserRole(Long userId, Long[] roleIds) {
        if (Validator.isNotNull(roleIds)) {
            // æ–°å¢žç”¨æˆ·ä¸Žè§’色管理
            List<SysUserRole> list = new ArrayList<SysUserRole>();
            for (Long roleId : roleIds) {
                SysUserRole ur = new SysUserRole();
                ur.setUserId(userId);
                ur.setRoleId(roleId);
                list.add(ur);
            }
            if (list.size() > 0) {
                userRoleMapper.insertAll(list);
            }
        }
    }
    /**
     * é€šè¿‡ç”¨æˆ·ID删除用户
     *
     * @param userId ç”¨æˆ·ID
ruoyi-system/src/main/resources/mapper/system/SysUserMapper.xml
@@ -139,6 +139,45 @@
        <!-- æ•°æ®èŒƒå›´è¿‡æ»¤ -->
        <if test="params.dataScope != null and params.dataScope != ''">
            AND ( ${params.dataScope} )
        </if>
    </select>
    <select id="selectAllocatedList" parameterType="SysUser" resultMap="SysUserResult">
        select distinct u.user_id, u.dept_id, u.user_name, u.nick_name, u.email, u.phonenumber, u.status, u.create_time
        from sys_user u
             left join sys_dept d on u.dept_id = d.dept_id
             left join sys_user_role ur on u.user_id = ur.user_id
             left join sys_role r on r.role_id = ur.role_id
        where u.del_flag = '0' and r.role_id = #{user.roleId}
        <if test="user.userName != null and user.userName != ''">
            AND u.user_name like concat('%', #{user.userName}, '%')
        </if>
        <if test="user.phonenumber != null and user.phonenumber != ''">
            AND u.phonenumber like concat('%', #{user.phonenumber}, '%')
        </if>
        <!-- æ•°æ®èŒƒå›´è¿‡æ»¤ -->
        <if test="user.params.dataScope != null and user.params.dataScope != ''">
            AND ( ${user.params.dataScope} )
        </if>
    </select>
    <select id="selectUnallocatedList" parameterType="SysUser" resultMap="SysUserResult">
        select distinct u.user_id, u.dept_id, u.user_name, u.nick_name, u.email, u.phonenumber, u.status, u.create_time
        from sys_user u
             left join sys_dept d on u.dept_id = d.dept_id
             left join sys_user_role ur on u.user_id = ur.user_id
             left join sys_role r on r.role_id = ur.role_id
        where u.del_flag = '0' and (r.role_id != #{user.roleId} or r.role_id IS NULL)
        and u.user_id not in (select u.user_id from sys_user u inner join sys_user_role ur on u.user_id = ur.user_id and ur.role_id = #{user.roleId})
        <if test="user.userName != null and user.userName != ''">
            AND u.user_name like concat('%', #{user.userName}, '%')
        </if>
        <if test="user.phonenumber != null and user.phonenumber != ''">
            AND u.phonenumber like concat('%', #{user.phonenumber}, '%')
        </if>
        <!-- æ•°æ®èŒƒå›´è¿‡æ»¤ -->
        <if test="user.params.dataScope != null and user.params.dataScope != ''">
            AND ( ${user.params.dataScope} )
        </if>
    </select>
ruoyi-ui/.env.development
@@ -7,5 +7,8 @@
# è‹¥ä¾ç®¡ç†ç³»ç»Ÿ/开发环境
VUE_APP_BASE_API = '/dev-api'
# ç›‘控地址
VUE_APP_MONITRO_ADMIN = 'http://localhost:9090/admin/login'
# è·¯ç”±æ‡’加载
VUE_CLI_BABEL_TRANSPILE_MODULES = true
ruoyi-ui/.env.production
@@ -4,5 +4,8 @@
# ç”Ÿäº§çŽ¯å¢ƒé…ç½®
ENV = 'production'
# ç›‘控地址
VUE_APP_MONITRO_ADMIN = '/admin/login'
# è‹¥ä¾ç®¡ç†ç³»ç»Ÿ/生产环境
VUE_APP_BASE_API = '/prod-api'
ruoyi-ui/.env.staging
@@ -6,5 +6,8 @@
# æµ‹è¯•环境配置
ENV = 'staging'
# ç›‘控地址
VUE_APP_MONITRO_ADMIN = '/admin/login'
# è‹¥ä¾ç®¡ç†ç³»ç»Ÿ/测试环境
VUE_APP_BASE_API = '/stage-api'
ruoyi-ui/package.json
@@ -1,6 +1,6 @@
{
  "name": "ruoyi-vue-plus",
  "version": "2.4.0",
  "version": "2.5.0",
  "description": "RuoYi-Vue-Plus后台管理系统",
  "author": "LionLi",
  "license": "MIT",
ruoyi-ui/src/api/system/role.js
@@ -73,3 +73,49 @@
    params: query
  })
}
// æŸ¥è¯¢è§’色已授权用户列表
export function allocatedUserList(query) {
  return request({
    url: '/system/role/authUser/allocatedList',
    method: 'get',
    params: query
  })
}
// æŸ¥è¯¢è§’色未授权用户列表
export function unallocatedUserList(query) {
  return request({
    url: '/system/role/authUser/unallocatedList',
    method: 'get',
    params: query
  })
}
// å–消用户授权角色
export function authUserCancel(data) {
  return request({
    url: '/system/role/authUser/cancel',
    method: 'put',
    data: data
  })
}
// æ‰¹é‡å–消用户授权角色
export function authUserCancelAll(data) {
  return request({
    url: '/system/role/authUser/cancelAll',
    method: 'put',
    params: data
  })
}
// æŽˆæƒç”¨æˆ·é€‰æ‹©
export function authUserSelectAll(data) {
  return request({
    url: '/system/role/authUser/selectAll',
    method: 'put',
    params: data
  })
}
ruoyi-ui/src/api/system/user.js
@@ -125,3 +125,20 @@
    method: 'get'
  })
}
// æŸ¥è¯¢æŽˆæƒè§’色
export function getAuthRole(userId) {
  return request({
    url: '/system/user/authRole/' + userId,
    method: 'get'
  })
}
// ä¿å­˜æŽˆæƒè§’色
export function updateAuthRole(data) {
  return request({
    url: '/system/user/authRole',
    method: 'put',
    params: data
  })
}
ruoyi-ui/src/assets/styles/ruoyi.scss
@@ -53,6 +53,13 @@
    margin-left: 20px;
}
.h1, .h2, .h3, .h4, .h5, .h6, h1, h2, h3, h4, h5, h6 {
    font-family: inherit;
    font-weight: 500;
    line-height: 1.1;
    color: inherit;
}
.el-dialog:not(.is-fullscreen){
    margin-top: 6vh !important;
}
@@ -120,6 +127,17 @@
    width: inherit;
}
/** è¡¨æ ¼æ›´å¤šæ“ä½œä¸‹æ‹‰æ ·å¼ */
.el-table .el-dropdown-link {
    cursor: pointer;
    color: #1890ff;
    margin-left: 5px;
}
.el-table .el-dropdown, .el-icon-arrow-down {
    font-size: 12px;
}
.el-tree-node__content > .el-checkbox {
    margin-right: 8px;
}
ruoyi-ui/src/components/Editor/index.vue
@@ -9,7 +9,7 @@
      :headers="headers"
      style="display: none"
      ref="upload"
      v-if="this.uploadUrl"
      v-if="this.type == 'url'"
    >
    </el-upload>
    <div class="editor" ref="editor" :style="styles"></div>
@@ -46,14 +46,15 @@
      type: Boolean,
      default: false,
    },
    /* ä¸Šä¼ åœ°å€ */
    uploadUrl: {
    /* ç±»åž‹ï¼ˆbase64格式、url格式) */
    type: {
      type: String,
      default: "",
      default: "url",
    }
  },
  data() {
    return {
      uploadUrl: process.env.VUE_APP_BASE_API + "/common/upload", // ä¸Šä¼ çš„图片服务器地址
      headers: {
        Authorization: "Bearer " + getToken()
      },
@@ -119,7 +120,7 @@
      const editor = this.$refs.editor;
      this.Quill = new Quill(editor, this.options);
      // å¦‚果设置了上传地址则自定义图片上传事件
      if (this.uploadUrl) {
      if (this.type == 'url') {
        let toolbar = this.Quill.getModule("toolbar");
        toolbar.addHandler("image", (value) => {
          this.uploadType = "image";
@@ -165,7 +166,7 @@
        // èŽ·å–å…‰æ ‡æ‰€åœ¨ä½ç½®
        let length = quill.getSelection().index;
        // æ’入图片  res.url为服务器返回的图片地址
        quill.insertEmbed(length, "image", res.url);
        quill.insertEmbed(length, "image", process.env.VUE_APP_BASE_API + res.data.fileName);
        // è°ƒæ•´å…‰æ ‡åˆ°æœ€åŽ
        quill.setSelection(length + 1);
      } else {
ruoyi-ui/src/components/FileUpload/index.vue
@@ -4,7 +4,7 @@
      :action="uploadFileUrl"
      :before-upload="handleBeforeUpload"
      :file-list="fileList"
      :limit="1"
      :limit="limit"
      :on-error="handleUploadError"
      :on-exceed="handleExceed"
      :on-success="handleUploadSuccess"
@@ -26,8 +26,8 @@
    <!-- æ–‡ä»¶åˆ—表 -->
    <transition-group class="upload-file-list el-upload-list el-upload-list--text" name="el-fade-in-linear" tag="ul">
      <li :key="file.uid" class="el-upload-list__item ele-upload-list__item-content" v-for="(file, index) in list">
        <el-link :href="file.url" :underline="false" target="_blank">
      <li :key="file.uid" class="el-upload-list__item ele-upload-list__item-content" v-for="(file, index) in fileList">
        <el-link :href="`${baseUrl}${file.url}`" :underline="false" target="_blank">
          <span class="el-icon-document"> {{ getFileName(file.name) }} </span>
        </el-link>
        <div class="ele-upload-list__item-content-action">
@@ -42,9 +42,15 @@
import { getToken } from "@/utils/auth";
export default {
  name: "FileUpload",
  props: {
    // å€¼
    value: [String, Object, Array],
    // æ•°é‡é™åˆ¶
    limit: {
      type: Number,
      default: 5,
    },
    // å¤§å°é™åˆ¶(MB)
    fileSize: {
      type: Number,
@@ -63,6 +69,7 @@
  },
  data() {
    return {
      baseUrl: process.env.VUE_APP_BASE_API,
      uploadFileUrl: process.env.VUE_APP_BASE_API + "/common/upload", // ä¸Šä¼ çš„图片服务器地址
      headers: {
        Authorization: "Bearer " + getToken(),
@@ -70,19 +77,15 @@
      fileList: [],
    };
  },
  computed: {
    // æ˜¯å¦æ˜¾ç¤ºæç¤º
    showTip() {
      return this.isShowTip && (this.fileType || this.fileSize);
    },
    // åˆ—表
    list() {
  watch: {
    value: {
      handler(val) {
        if (val) {
      let temp = 1;
      if (this.value) {
        // é¦–先将值转为数组
        const list = Array.isArray(this.value) ? this.value : [this.value];
          const list = Array.isArray(val) ? val : this.value.split(',');
        // ç„¶åŽå°†æ•°ç»„转为对象数组
        return list.map((item) => {
          this.fileList = list.map(item => {
          if (typeof item === "string") {
            item = { name: item, url: item };
          }
@@ -93,6 +96,15 @@
        this.fileList = [];
        return [];
      }
      },
      deep: true,
      immediate: true
    }
  },
  computed: {
    // æ˜¯å¦æ˜¾ç¤ºæç¤º
    showTip() {
      return this.isShowTip && (this.fileType || this.fileSize);
    },
  },
  methods: {
@@ -126,7 +138,7 @@
    },
    // æ–‡ä»¶ä¸ªæ•°è¶…出
    handleExceed() {
      this.$message.error(`只允许上传单个文件`);
      this.$message.error(`上传文件数量不能超过 ${this.limit} ä¸ª!`);
    },
    // ä¸Šä¼ å¤±è´¥
    handleUploadError(err) {
@@ -135,12 +147,13 @@
    // ä¸Šä¼ æˆåŠŸå›žè°ƒ
    handleUploadSuccess(res, file) {
      this.$message.success("上传成功");
      this.$emit("input", res.data.url);
      this.fileList.push({ name: res.data.fileName, url: res.data.fileName });
      this.$emit("input", this.listToString(this.fileList));
    },
    // åˆ é™¤æ–‡ä»¶
    handleDelete(index) {
      this.fileList.splice(index, 1);
      this.$emit("input", '');
      this.$emit("input", this.listToString(this.fileList));
    },
    // èŽ·å–æ–‡ä»¶åç§°
    getFileName(name) {
@@ -149,11 +162,17 @@
      } else {
        return "";
      }
    },
    // å¯¹è±¡è½¬æˆæŒ‡å®šå­—符串分隔
    listToString(list, separator) {
      let strs = "";
      separator = separator || ",";
      for (let i in list) {
        strs += list[i].url + separator;
    }
  },
  created() {
    this.fileList = this.list;
  },
      return strs != '' ? strs.substr(0, strs.length - 1) : '';
    }
  }
};
</script>
ruoyi-ui/src/components/HeaderSearch/index.vue
@@ -70,9 +70,11 @@
      this.show = false
    },
    change(val) {
      const path = val.path;
      if(this.ishttp(val.path)) {
        // http(s):// è·¯å¾„新窗口打开
        window.open(val.path, "_blank");
        const pindex = path.indexOf("http");
        window.open(path.substr(pindex, path.length), "_blank");
      } else {
        this.$router.push(val.path)
      }
ruoyi-ui/src/components/ImageUpload/index.vue
@@ -5,33 +5,38 @@
      list-type="picture-card"
      :on-success="handleUploadSuccess"
      :before-upload="handleBeforeUpload"
      :limit="limit"
      :on-error="handleUploadError"
      :on-exceed="handleExceed"
      name="file"
      :show-file-list="false"
      :on-remove="handleRemove"
      :show-file-list="true"
      :headers="headers"
      style="display: inline-block; vertical-align: top"
      :file-list="fileList"
      :on-preview="handlePictureCardPreview"
      :class="{hide: this.fileList.length >= this.limit}"
    >
      <el-image v-if="!value" :src="value">
        <div slot="error" class="image-slot">
          <i class="el-icon-plus" />
        </div>
      </el-image>
      <div v-else class="image">
        <el-image :src="value" :style="`width:150px;height:150px;`" fit="fill"/>
        <div class="mask">
          <div class="actions">
            <span title="预览" @click.stop="dialogVisible = true">
              <i class="el-icon-zoom-in" />
            </span>
            <span title="移除" @click.stop="removeImage">
              <i class="el-icon-delete" />
            </span>
          </div>
        </div>
      </div>
      <i class="el-icon-plus"></i>
    </el-upload>
    <el-dialog :visible.sync="dialogVisible" title="预览" width="800" append-to-body>
      <img :src="value" style="display: block; max-width: 100%; margin: 0 auto;">
    <!-- ä¸Šä¼ æç¤º -->
    <div class="el-upload__tip" slot="tip" v-if="showTip">
      è¯·ä¸Šä¼ 
      <template v-if="fileSize"> å¤§å°ä¸è¶…过 <b style="color: #f56c6c">{{ fileSize }}MB</b> </template>
      <template v-if="fileType"> æ ¼å¼ä¸º <b style="color: #f56c6c">{{ fileType.join("/") }}</b> </template>
      çš„æ–‡ä»¶
    </div>
    <el-dialog
      :visible.sync="dialogVisible"
      title="预览"
      width="800"
      append-to-body
    >
      <img
        :src="dialogImageUrl"
        style="display: block; max-width: 100%; margin: 0 auto"
      />
    </el-dialog>
  </div>
</template>
@@ -40,36 +45,128 @@
import { getToken } from "@/utils/auth";
export default {
  props: {
    value: [String, Object, Array],
    // å›¾ç‰‡æ•°é‡é™åˆ¶
    limit: {
      type: Number,
      default: 5,
    },
    // å¤§å°é™åˆ¶(MB)
    fileSize: {
       type: Number,
      default: 5,
    },
    // æ–‡ä»¶ç±»åž‹, ä¾‹å¦‚['png', 'jpg', 'jpeg']
    fileType: {
      type: Array,
      default: () => ["png", "jpg", "jpeg"],
    },
    // æ˜¯å¦æ˜¾ç¤ºæç¤º
    isShowTip: {
      type: Boolean,
      default: true
    }
  },
  data() {
    return {
      dialogImageUrl: "",
      dialogVisible: false,
      hideUpload: false,
      baseUrl: process.env.VUE_APP_BASE_API,
      uploadImgUrl: process.env.VUE_APP_BASE_API + "/common/upload", // ä¸Šä¼ çš„图片服务器地址
      headers: {
        Authorization: "Bearer " + getToken(),
      },
      fileList: []
    };
  },
  props: {
  watch: {
    value: {
      type: String,
      default: "",
      handler(val) {
        if (val) {
          // é¦–先将值转为数组
          const list = Array.isArray(val) ? val : this.value.split(',');
          // ç„¶åŽå°†æ•°ç»„转为对象数组
          this.fileList = list.map(item => {
            if (typeof item === "string") {
              if (item.indexOf(this.baseUrl) === -1) {
                  item = { name: this.baseUrl + item, url: this.baseUrl + item };
              } else {
                  item = { name: item, url: item };
              }
            }
            return item;
          });
        } else {
          this.fileList = [];
          return [];
        }
      },
      deep: true,
      immediate: true
    }
  },
  computed: {
    // æ˜¯å¦æ˜¾ç¤ºæç¤º
    showTip() {
      return this.isShowTip && (this.fileType || this.fileSize);
    },
  },
  methods: {
    removeImage() {
      this.$emit("input", "");
    // åˆ é™¤å›¾ç‰‡
    handleRemove(file, fileList) {
      const findex = this.fileList.indexOf(file.name);
      this.fileList.splice(findex, 1);
      this.$emit("input", this.listToString(this.fileList));
    },
    // ä¸Šä¼ æˆåŠŸå›žè°ƒ
    handleUploadSuccess(res) {
      this.$emit("input", res.data.url);
      this.fileList.push({ name: res.data.fileName, url: res.data.fileName });
      this.$emit("input", this.listToString(this.fileList));
      this.loading.close();
    },
    handleBeforeUpload() {
    // ä¸Šä¼ å‰loading加载
    handleBeforeUpload(file) {
      let isImg = false;
      if (this.fileType.length) {
        let fileExtension = "";
        if (file.name.lastIndexOf(".") > -1) {
          fileExtension = file.name.slice(file.name.lastIndexOf(".") + 1);
        }
        isImg = this.fileType.some(type => {
          if (file.type.indexOf(type) > -1) return true;
          if (fileExtension && fileExtension.indexOf(type) > -1) return true;
          return false;
        });
      } else {
        isImg = file.type.indexOf("image") > -1;
      }
      if (!isImg) {
        this.$message.error(
          `文件格式不正确, è¯·ä¸Šä¼ ${this.fileType.join("/")}图片格式文件!`
        );
        return false;
      }
      if (this.fileSize) {
        const isLt = file.size / 1024 / 1024 < this.fileSize;
        if (!isLt) {
          this.$message.error(`上传头像图片大小不能超过 ${this.fileSize} MB!`);
          return false;
        }
      }
      this.loading = this.$loading({
        lock: true,
        text: "上传中",
        background: "rgba(0, 0, 0, 0.7)",
      });
    },
    // æ–‡ä»¶ä¸ªæ•°è¶…出
    handleExceed() {
      this.$message.error(`上传文件数量不能超过 ${this.limit} ä¸ª!`);
    },
    // ä¸Šä¼ å¤±è´¥
    handleUploadError() {
      this.$message({
        type: "error",
@@ -77,24 +174,37 @@
      });
      this.loading.close();
    },
    // é¢„览
    handlePictureCardPreview(file) {
      this.dialogImageUrl = file.url;
      this.dialogVisible = true;
  },
  watch: {},
    // å¯¹è±¡è½¬æˆæŒ‡å®šå­—符串分隔
    listToString(list, separator) {
      let strs = "";
      separator = separator || ",";
      for (let i in list) {
        strs += list[i].url + separator;
      }
      return strs != '' ? strs.substr(0, strs.length - 1) : '';
    }
  }
};
</script>
<style scoped lang="scss">
.image {
  position: relative;
  .mask {
// .el-upload--picture-card æŽ§åˆ¶åŠ å·éƒ¨åˆ†
::v-deep.hide .el-upload--picture-card {
    display: none;
}
// åŽ»æŽ‰åŠ¨ç”»æ•ˆæžœ
::v-deep .el-list-enter-active,
::v-deep .el-list-leave-active {
    transition: all 0s;
}
::v-deep .el-list-enter, .el-list-leave-active {
    opacity: 0;
    position: absolute;
    top: 0;
    width: 100%;
    background-color: rgba(0, 0, 0, 0.5);
    transition: all 0.3s;
  }
  &:hover .mask {
    opacity: 1;
  }
    transform: translateY(0);
}
</style>
ruoyi-ui/src/components/TopNav/index.vue
ruoyi-ui/src/directive/dialog/drag.js
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,64 @@
/**
* v-dialogDrag å¼¹çª—拖拽
* Copyright (c) 2019 ruoyi
*/
export default {
  bind(el, binding, vnode, oldVnode) {
    const value = binding.value
    if (value == false) return
    // èŽ·å–æ‹–æ‹½å†…å®¹å¤´éƒ¨
    const dialogHeaderEl = el.querySelector('.el-dialog__header');
    const dragDom = el.querySelector('.el-dialog');
    dialogHeaderEl.style.cursor = 'move';
    // èŽ·å–åŽŸæœ‰å±žæ€§ ie dom元素.currentStyle ç«ç‹è°·æ­Œ window.getComputedStyle(dom元素, null);
    const sty = dragDom.currentStyle || window.getComputedStyle(dragDom, null);
    dragDom.style.position = 'absolute';
    dragDom.style.marginTop = 0;
    let width = dragDom.style.width;
    if (width.includes('%')) {
      width = +document.body.clientWidth * (+width.replace(/\%/g, '') / 100);
    } else {
      width = +width.replace(/\px/g, '');
    }
    dragDom.style.left = `${(document.body.clientWidth - width) / 2}px`;
    // é¼ æ ‡æŒ‰ä¸‹äº‹ä»¶
    dialogHeaderEl.onmousedown = (e) => {
      // é¼ æ ‡æŒ‰ä¸‹ï¼Œè®¡ç®—当前元素距离可视区的距离 (鼠标点击位置距离可视窗口的距离)
      const disX = e.clientX - dialogHeaderEl.offsetLeft;
      const disY = e.clientY - dialogHeaderEl.offsetTop;
      // èŽ·å–åˆ°çš„å€¼å¸¦px æ­£åˆ™åŒ¹é…æ›¿æ¢
      let styL, styT;
      // æ³¨æ„åœ¨ie中 ç¬¬ä¸€æ¬¡èŽ·å–åˆ°çš„å€¼ä¸ºç»„ä»¶è‡ªå¸¦50% ç§»åŠ¨ä¹‹åŽèµ‹å€¼ä¸ºpx
      if (sty.left.includes('%')) {
        styL = +document.body.clientWidth * (+sty.left.replace(/\%/g, '') / 100);
        styT = +document.body.clientHeight * (+sty.top.replace(/\%/g, '') / 100);
      } else {
        styL = +sty.left.replace(/\px/g, '');
        styT = +sty.top.replace(/\px/g, '');
      };
      // é¼ æ ‡æ‹–拽事件
      document.onmousemove = function (e) {
        // é€šè¿‡äº‹ä»¶å§”托,计算移动的距离 ï¼ˆå¼€å§‹æ‹–拽至结束拖拽的距离)
        const l = e.clientX - disX;
        const t = e.clientY - disY;
        let finallyL = l + styL
        let finallyT = t + styT
        // ç§»åŠ¨å½“å‰å…ƒç´ 
        dragDom.style.left = `${finallyL}px`;
        dragDom.style.top = `${finallyT}px`;
      };
      document.onmouseup = function (e) {
        document.onmousemove = null;
        document.onmouseup = null;
      };
    }
  }
};
ruoyi-ui/src/directive/index.js
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,18 @@
import hasRole from './permission/hasRole'
import hasPermi from './permission/hasPermi'
import dialogDrag from './dialog/drag'
const install = function(Vue) {
  Vue.directive('hasRole', hasRole)
  Vue.directive('hasPermi', hasPermi)
  Vue.directive('dialogDrag', dialogDrag)
}
if (window.Vue) {
  window['hasRole'] = hasRole
  window['hasPermi'] = hasPermi
  window['dialogDrag'] = dialogDrag
  Vue.use(install); // eslint-disable-line
}
export default install
ruoyi-ui/src/directive/permission/hasPermi.js
@@ -1,5 +1,5 @@
 /**
 * æ“ä½œæƒé™å¤„理
 * v-hasPermi æ“ä½œæƒé™å¤„理
 * Copyright (c) 2019 ruoyi
 */
 
ruoyi-ui/src/directive/permission/hasRole.js
@@ -1,5 +1,5 @@
 /**
 * è§’色权限处理
 * v-hasRole è§’色权限处理
 * Copyright (c) 2019 ruoyi
 */
 
ruoyi-ui/src/directive/permission/index.js
ÎļþÒÑɾ³ý
ruoyi-ui/src/layout/components/AppMain.vue
@@ -51,7 +51,7 @@
// fix css style bug in open el-dialog
.el-popup-parent--hidden {
  .fixed-header {
    padding-right: 15px;
    padding-right: 17px;
  }
}
</style>
ruoyi-ui/src/layout/components/InnerLink/index.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,27 @@
<script>
export default {
  data() {
    return {};
  },
  render() {
    const { $route: { meta: { link } }, } = this;
    if ({ link }.link === "") {
      return "404";
    }
    let url = { link }.link;
    const height = document.documentElement.clientHeight - 94.5 + "px";
    const style = { height: height };
    return (
      <div style={style}>
        <iframe
          src={url}
          frameborder="no"
          style="width: 100%; height: 100%"
          scrolling="auto"
        ></iframe>
      </div>
    );
  },
};
</script>
ruoyi-ui/src/main.js
@@ -10,7 +10,7 @@
import App from './App'
import store from './store'
import router from './router'
import permission from './directive/permission'
import directive from './directive' //directive
import './assets/icons' // icon
import './permission' // permission control
@@ -20,6 +20,12 @@
import Pagination from "@/components/Pagination";
// è‡ªå®šä¹‰è¡¨æ ¼å·¥å…·ç»„ä»¶
import RightToolbar from "@/components/RightToolbar"
// å¯Œæ–‡æœ¬ç»„ä»¶
import Editor from "@/components/Editor"
// æ–‡ä»¶ä¸Šä¼ ç»„ä»¶
import FileUpload from "@/components/FileUpload"
// å›¾ç‰‡ä¸Šä¼ ç»„ä»¶
import ImageUpload from "@/components/ImageUpload"
// å­—典标签组件
import DictTag from '@/components/DictTag'
// å¤´éƒ¨æ ‡ç­¾ç»„ä»¶
@@ -52,8 +58,11 @@
Vue.component('DictTag', DictTag)
Vue.component('Pagination', Pagination)
Vue.component('RightToolbar', RightToolbar)
Vue.component('Editor', Editor)
Vue.component('FileUpload', FileUpload)
Vue.component('ImageUpload', ImageUpload)
Vue.use(permission)
Vue.use(directive)
Vue.use(VueMeta)
/**
ruoyi-ui/src/router/index.js
@@ -6,6 +6,7 @@
/* Layout */
import Layout from '@/layout'
import ParentView from '@/components/ParentView';
import InnerLink from '@/layout/components/InnerLink'
/**
 * Note: è·¯ç”±é…ç½®é¡¹
@@ -81,6 +82,32 @@
    ]
  },
  {
    path: '/auth',
    component: Layout,
    hidden: true,
    children: [
      {
        path: 'role/:userId(\\d+)',
        component: (resolve) => require(['@/views/system/user/authRole'], resolve),
        name: 'AuthRole',
        meta: { title: '分配角色'}
      }
    ]
  },
  {
    path: '/auth',
    component: Layout,
    hidden: true,
    children: [
      {
        path: 'user/:roleId(\\d+)',
        component: (resolve) => require(['@/views/system/role/authUser'], resolve),
        name: 'AuthUser',
        meta: { title: '分配用户'}
      }
    ]
  },
  {
    path: '/dict',
    component: Layout,
    hidden: true,
ruoyi-ui/src/store/modules/permission.js
@@ -2,6 +2,7 @@
import { getRouters } from '@/api/menu'
import Layout from '@/layout/index'
import ParentView from '@/components/ParentView';
import InnerLink from '@/layout/components/InnerLink'
const permission = {
  state: {
@@ -65,6 +66,8 @@
        route.component = Layout
      } else if (route.component === 'ParentView') {
        route.component = ParentView
      } else if (route.component === 'InnerLink') {
        route.component = InnerLink
      } else {
        route.component = loadView(route.component)
      }
ruoyi-ui/src/views/demo/demo/index.vue
@@ -304,17 +304,19 @@
          this.buttonLoading = true;
          if (this.form.id != null) {
            updateDemo(this.form).then(response => {
              this.buttonLoading = false;
              this.msgSuccess("修改成功");
              this.open = false;
              this.getList();
            }).finally(() => {
              this.buttonLoading = false;
            });
          } else {
            addDemo(this.form).then(response => {
              this.buttonLoading = false;
              this.msgSuccess("新增成功");
              this.open = false;
              this.getList();
            }).finally(() => {
              this.buttonLoading = false;
            });
          }
        }
ruoyi-ui/src/views/demo/tree/index.vue
@@ -255,17 +255,19 @@
          this.buttonLoading = true;
          if (this.form.id != null) {
            updateTree(this.form).then(response => {
              this.buttonLoading = false;
              this.msgSuccess("修改成功");
              this.open = false;
              this.getList();
            }).finally(() => {
              this.buttonLoading = false;
            });
          } else {
            addTree(this.form).then(response => {
              this.buttonLoading = false;
              this.msgSuccess("新增成功");
              this.open = false;
              this.getList();
            }).finally(() => {
              this.buttonLoading = false;
            });
          }
        }
ruoyi-ui/src/views/index.vue
@@ -91,6 +91,32 @@
            <span>更新日志</span>
          </div>
          <el-collapse accordion>
            <el-collapse-item title="v2.5.0 - 2021-7-12">
              <ol>
                <li>update springboot 2.4.7 => 2.4.8</li>
                <li>update knife4j 3.0.2 => 3.0.3</li>
                <li>update hutool 5.7.2 => 5.7.4</li>
                <li>update spring-boot-admin 2.4.1 => 2.4.3</li>
                <li>update redisson 3.15.2 => 3.16.0</li>
                <li>add å¢žåŠ  docker ç¼–排 ä¸Ž shell è„šæœ¬</li>
                <li>add å¢žåŠ  feign ç†”æ–­ è‡ªå®šä¹‰ç»“构体解析方法 ä¸Ž demo æ³¨é‡Š</li>
                <li>add ç”¨æˆ·ç®¡ç†æ–°å¢žåˆ†é…è§’色功能</li>
                <li>add è§’色管理新增分配用户功能</li>
                <li>add å¢žåŠ spring-cache演示案例</li>
                <li>update ç‹¬ç«‹ springboot-admin ç›‘控到扩展模块项目</li>
                <li>update springboot-admin ç›‘控 å¢žåŠ ç”¨æˆ·ç™»å½•æƒé™ç®¡ç†</li>
                <li>update ä¼˜åŒ–代码生成器 æ‰¹é‡å¯¼å…¥</li>
                <li>update ä¼˜åŒ– å¢žåŠ MP注入异常拦截</li>
                <li>update å…³é—­é»˜è®¤äºŒçº§ç¼“å­˜ æŽ¨èä½¿ç”¨ spring-cache æ³¨è§£æ‰‹åŠ¨ç¼“å­˜</li>
                <li>update FileUpload ImageUpload组件 æ”¯æŒå¤šå›¾ç‰‡ä¸Šä¼ </li>
                <li>update ä¼˜åŒ–中英文语言配置</li>
                <li>update è§„范maven写法</li>
                <li>fix redis获取map属性bug修复。</li>
                <li>fix ä¿®å¤ æŒ‰é’®loading åŽç«¯500卡死问题</li>
                <li>fix ç›¸å¯¹è·¯å¾„下载问题</li>
                <li>fix ä¿®å¤ hutool å·¥å…·è¿”回结果不一致问题</li>
              </ol>
            </el-collapse-item>
            <el-collapse-item title="v2.4.0 - 2021-6-24">
              <ol>
                <li>update springboot 2.3.11 => 2.4.7</li>
ruoyi-ui/src/views/monitor/admin/index.vue
@@ -1,26 +1,16 @@
<template>
  <div v-loading="loading" :style="'height:'+ height">
    <iframe :src="src" frameborder="no" style="width: 100%;height: 100%" scrolling="auto" />
  </div>
  <i-frame :src="url" />
</template>
<script>
import iFrame from "@/components/iFrame/index";
export default {
  name: "Admin",
  components: { iFrame },
  data() {
    console.log(process.env)
    return {
      src: process.env.VUE_APP_BASE_API + "/admin",
      height: document.documentElement.clientHeight - 94.5 + "px;",
      loading: true
      url: process.env.VUE_APP_MONITRO_ADMIN
    };
  },
  mounted: function() {
    setTimeout(() => {
      this.loading = false;
    }, 230);
    const that = this;
    window.onresize = function temp() {
      that.height = document.documentElement.clientHeight - 94.5 + "px;";
    };
  }
};
</script>
ruoyi-ui/src/views/system/notice/index.vue
@@ -177,13 +177,9 @@
<script>
import { listNotice, getNotice, delNotice, addNotice, updateNotice } from "@/api/system/notice";
import Editor from '@/components/Editor';
export default {
  name: "Notice",
  components: {
    Editor
  },
  data() {
    return {
      // é®ç½©å±‚
ruoyi-ui/src/views/system/role/authUser.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,213 @@
<template>
  <div class="app-container">
     <el-form :model="queryParams" ref="queryForm" v-show="showSearch" :inline="true">
      <el-form-item label="用户名称" prop="userName">
        <el-input
          v-model="queryParams.userName"
          placeholder="请输入用户名称"
          clearable
          size="small"
          style="width: 240px"
          @keyup.enter.native="handleQuery"
        />
      </el-form-item>
      <el-form-item label="手机号码" prop="phonenumber">
        <el-input
          v-model="queryParams.phonenumber"
          placeholder="请输入手机号码"
          clearable
          size="small"
          style="width: 240px"
          @keyup.enter.native="handleQuery"
        />
      </el-form-item>
      <el-form-item>
        <el-button type="primary" icon="el-icon-search" size="mini" @click="handleQuery">搜索</el-button>
        <el-button icon="el-icon-refresh" size="mini" @click="resetQuery">重置</el-button>
      </el-form-item>
    </el-form>
    <el-row :gutter="10" class="mb8">
      <el-col :span="1.5">
        <el-button
          type="primary"
          plain
          icon="el-icon-plus"
          size="mini"
          @click="openSelectUser"
          v-hasPermi="['system:role:add']"
        >添加用户</el-button>
      </el-col>
      <el-col :span="1.5">
        <el-button
          type="danger"
          plain
          icon="el-icon-circle-close"
          size="mini"
          :disabled="multiple"
          @click="cancelAuthUserAll"
          v-hasPermi="['system:role:remove']"
        >批量取消授权</el-button>
      </el-col>
      <el-col :span="1.5">
        <el-button
          type="warning"
          plain
          icon="el-icon-close"
          size="mini"
          @click="handleClose"
        >关闭</el-button>
      </el-col>
      <right-toolbar :showSearch.sync="showSearch" @queryTable="getList"></right-toolbar>
    </el-row>
    <el-table v-loading="loading" :data="userList" @selection-change="handleSelectionChange">
      <el-table-column type="selection" width="55" align="center" />
      <el-table-column label="用户名称" prop="userName" :show-overflow-tooltip="true" />
      <el-table-column label="用户昵称" prop="nickName" :show-overflow-tooltip="true" />
      <el-table-column label="邮箱" prop="email" :show-overflow-tooltip="true" />
      <el-table-column label="手机" prop="phonenumber" :show-overflow-tooltip="true" />
      <el-table-column label="状态" align="center" prop="status">
        <template slot-scope="scope">
          <dict-tag :options="statusOptions" :value="scope.row.status"/>
        </template>
      </el-table-column>
      <el-table-column label="创建时间" align="center" prop="createTime" width="180">
        <template slot-scope="scope">
          <span>{{ parseTime(scope.row.createTime) }}</span>
        </template>
      </el-table-column>
      <el-table-column label="操作" align="center" class-name="small-padding fixed-width">
        <template slot-scope="scope">
          <el-button
            size="mini"
            type="text"
            icon="el-icon-circle-close"
            @click="cancelAuthUser(scope.row)"
            v-hasPermi="['system:role:remove']"
          >取消授权</el-button>
        </template>
      </el-table-column>
    </el-table>
    <pagination
      v-show="total>0"
      :total="total"
      :page.sync="queryParams.pageNum"
      :limit.sync="queryParams.pageSize"
      @pagination="getList"
    />
    <select-user ref="select" :roleId="queryParams.roleId" @ok="handleQuery" />
  </div>
</template>
<script>
import { allocatedUserList, authUserCancel, authUserCancelAll } from "@/api/system/role";
import selectUser from "./selectUser";
export default {
  name: "AuthUser",
  components: { selectUser },
  data() {
    return {
      // é®ç½©å±‚
      loading: true,
      // é€‰ä¸­ç”¨æˆ·ç»„
      userIds: [],
      // éžå¤šä¸ªç¦ç”¨
      multiple: true,
      // æ˜¾ç¤ºæœç´¢æ¡ä»¶
      showSearch: true,
      // æ€»æ¡æ•°
      total: 0,
      // ç”¨æˆ·è¡¨æ ¼æ•°æ®
      userList: [],
      // çŠ¶æ€æ•°æ®å­—å…¸
      statusOptions: [],
      // æŸ¥è¯¢å‚æ•°
      queryParams: {
        pageNum: 1,
        pageSize: 10,
        roleId: undefined,
        userName: undefined,
        phonenumber: undefined
      }
    };
  },
  created() {
    const roleId = this.$route.params && this.$route.params.roleId;
    if (roleId) {
      this.queryParams.roleId = roleId;
      this.getList();
      this.getDicts("sys_normal_disable").then(response => {
        this.statusOptions = response.data;
      });
    }
  },
  methods: {
    /** æŸ¥è¯¢æŽˆæƒç”¨æˆ·åˆ—表 */
    getList() {
      this.loading = true;
      allocatedUserList(this.queryParams).then(response => {
          this.userList = response.rows;
          this.total = response.total;
          this.loading = false;
        }
      );
    },
    // è¿”回按钮
    handleClose() {
      this.$store.dispatch("tagsView/delView", this.$route);
      this.$router.push({ path: "/system/role" });
    },
    /** æœç´¢æŒ‰é’®æ“ä½œ */
    handleQuery() {
      this.queryParams.pageNum = 1;
      this.getList();
    },
    /** é‡ç½®æŒ‰é’®æ“ä½œ */
    resetQuery() {
      this.resetForm("queryForm");
      this.handleQuery();
    },
    // å¤šé€‰æ¡†é€‰ä¸­æ•°æ®
    handleSelectionChange(selection) {
      this.userIds = selection.map(item => item.userId)
      this.multiple = !selection.length
    },
    /** æ‰“开授权用户表弹窗 */
    openSelectUser() {
      this.$refs.select.show();
    },
    /** å–消授权按钮操作 */
    cancelAuthUser(row) {
      const roleId = this.queryParams.roleId;
      this.$confirm('确认要取消该用户"' + row.userName + '"角色吗?', "警告", {
        confirmButtonText: "确定",
        cancelButtonText: "取消",
        type: "warning"
      }).then(function() {
        return authUserCancel({ userId: row.userId, roleId: roleId });
      }).then(() => {
        this.getList();
        this.msgSuccess("取消授权成功");
      }).catch(() => {});
    },
    /** æ‰¹é‡å–消授权按钮操作 */
    cancelAuthUserAll(row) {
      const roleId = this.queryParams.roleId;
      const userIds = this.userIds.join(",");
      this.$confirm('是否取消选中用户授权数据项?', "警告", {
          confirmButtonText: "确定",
          cancelButtonText: "取消",
          type: "warning"
      }).then(() => {
          return authUserCancelAll({ roleId: roleId, userIds: userIds });
      }).then(() => {
        this.getList();
        this.msgSuccess("取消授权成功");
      }).catch(() => {});
    }
  }
};
</script>
ruoyi-ui/src/views/system/role/index.vue
@@ -124,7 +124,7 @@
        </template>
      </el-table-column>
      <el-table-column label="操作" align="center" class-name="small-padding fixed-width">
        <template slot-scope="scope">
        <template slot-scope="scope" v-if="scope.row.roleId !== 1">
          <el-button
            size="mini"
            type="text"
@@ -135,17 +135,21 @@
          <el-button
            size="mini"
            type="text"
            icon="el-icon-circle-check"
            @click="handleDataScope(scope.row)"
            v-hasPermi="['system:role:edit']"
          >数据权限</el-button>
          <el-button
            size="mini"
            type="text"
            icon="el-icon-delete"
            @click="handleDelete(scope.row)"
            v-hasPermi="['system:role:remove']"
          >删除</el-button>
          <el-dropdown size="mini" @command="(command) => handleCommand(command, scope.row)">
            <span class="el-dropdown-link">
              <i class="el-icon-d-arrow-right el-icon--right"></i>更多
            </span>
            <el-dropdown-menu slot="dropdown">
              <el-dropdown-item command="handleDataScope" icon="el-icon-circle-check"
                v-hasPermi="['system:role:edit']">数据权限</el-dropdown-item>
              <el-dropdown-item command="handleAuthUser" icon="el-icon-user"
                v-hasPermi="['system:role:edit']">分配用户</el-dropdown-item>
            </el-dropdown-menu>
          </el-dropdown>
        </template>
      </el-table-column>
    </el-table>
@@ -469,6 +473,19 @@
      this.single = selection.length!=1
      this.multiple = !selection.length
    },
    // æ›´å¤šæ“ä½œè§¦å‘
    handleCommand(command, row) {
      switch (command) {
        case "handleDataScope":
          this.handleDataScope(row);
          break;
        case "handleAuthUser":
          this.handleAuthUser(row);
          break;
        default:
          break;
      }
    },
    // æ ‘权限(展开/折叠)
    handleCheckedTreeExpand(value, type) {
      if (type == 'menu') {
@@ -548,6 +565,11 @@
        this.title = "分配数据权限";
      });
    },
    /** åˆ†é…ç”¨æˆ·æ“ä½œ */
    handleAuthUser: function(row) {
      const roleId = row.roleId;
      this.$router.push("/auth/user/" + roleId);
    },
    /** æäº¤æŒ‰é’® */
    submitForm: function() {
      this.$refs["form"].validate(valid => {
ruoyi-ui/src/views/system/role/selectUser.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,142 @@
<template>
  <!-- æŽˆæƒç”¨æˆ· -->
  <el-dialog title="选择用户" :visible.sync="visible" width="800px" top="5vh" append-to-body>
    <el-form :model="queryParams" ref="queryForm" :inline="true">
      <el-form-item label="用户名称" prop="userName">
        <el-input
          v-model="queryParams.userName"
          placeholder="请输入用户名称"
          clearable
          size="small"
          @keyup.enter.native="handleQuery"
        />
      </el-form-item>
      <el-form-item label="手机号码" prop="phonenumber">
        <el-input
          v-model="queryParams.phonenumber"
          placeholder="请输入手机号码"
          clearable
          size="small"
          @keyup.enter.native="handleQuery"
        />
      </el-form-item>
      <el-form-item>
        <el-button type="primary" icon="el-icon-search" size="mini" @click="handleQuery">搜索</el-button>
        <el-button icon="el-icon-refresh" size="mini" @click="resetQuery">重置</el-button>
      </el-form-item>
    </el-form>
    <el-row>
      <el-table @row-click="clickRow" ref="table" :data="userList" @selection-change="handleSelectionChange" height="260px">
        <el-table-column type="selection" width="55"></el-table-column>
        <el-table-column label="用户名称" prop="userName" :show-overflow-tooltip="true" />
        <el-table-column label="用户昵称" prop="nickName" :show-overflow-tooltip="true" />
        <el-table-column label="邮箱" prop="email" :show-overflow-tooltip="true" />
        <el-table-column label="手机" prop="phonenumber" :show-overflow-tooltip="true" />
        <el-table-column label="状态" align="center" prop="status">
          <template slot-scope="scope">
            <dict-tag :options="statusOptions" :value="scope.row.status"/>
          </template>
        </el-table-column>
        <el-table-column label="创建时间" align="center" prop="createTime" width="180">
          <template slot-scope="scope">
            <span>{{ parseTime(scope.row.createTime) }}</span>
          </template>
        </el-table-column>
      </el-table>
      <pagination
        v-show="total>0"
        :total="total"
        :page.sync="queryParams.pageNum"
        :limit.sync="queryParams.pageSize"
        @pagination="getList"
      />
    </el-row>
    <div slot="footer" class="dialog-footer">
      <el-button type="primary" @click="handleSelectUser">ç¡® å®š</el-button>
      <el-button @click="visible = false">取 æ¶ˆ</el-button>
    </div>
  </el-dialog>
</template>
<script>
import { unallocatedUserList, authUserSelectAll } from "@/api/system/role";
export default {
  props: {
    // è§’色编号
    roleId: {
      type: Number
    }
  },
  data() {
    return {
      // é®ç½©å±‚
      visible: false,
      // é€‰ä¸­æ•°ç»„值
      userIds: [],
      // æ€»æ¡æ•°
      total: 0,
      // æœªæŽˆæƒç”¨æˆ·æ•°æ®
      userList: [],
      // çŠ¶æ€æ•°æ®å­—å…¸
      statusOptions: [],
      // æŸ¥è¯¢å‚æ•°
      queryParams: {
        pageNum: 1,
        pageSize: 10,
        roleId: undefined,
        userName: undefined,
        phonenumber: undefined
      }
    };
  },
  created() {
    this.getDicts("sys_normal_disable").then(response => {
      this.statusOptions = response.data;
    });
  },
  methods: {
    // æ˜¾ç¤ºå¼¹æ¡†
    show() {
      this.queryParams.roleId = this.roleId;
      this.getList();
      this.visible = true;
    },
    clickRow(row) {
      this.$refs.table.toggleRowSelection(row);
    },
    // å¤šé€‰æ¡†é€‰ä¸­æ•°æ®
    handleSelectionChange(selection) {
      this.userIds = selection.map(item => item.userId);
    },
    // æŸ¥è¯¢è¡¨æ•°æ®
    getList() {
      unallocatedUserList(this.queryParams).then(res => {
        this.userList = res.rows;
        this.total = res.total;
      });
    },
    /** æœç´¢æŒ‰é’®æ“ä½œ */
    handleQuery() {
      this.queryParams.pageNum = 1;
      this.getList();
    },
    /** é‡ç½®æŒ‰é’®æ“ä½œ */
    resetQuery() {
      this.resetForm("queryForm");
      this.handleQuery();
    },
    /** é€‰æ‹©æŽˆæƒç”¨æˆ·æ“ä½œ */
    handleSelectUser() {
      const roleId = this.queryParams.roleId;
      const userIds = this.userIds.join(",");
      authUserSelectAll({ roleId: roleId, userIds: userIds }).then(res => {
        this.msgSuccess(res.msg);
        if (res.code === 200) {
          this.visible = false;
          this.$emit("ok");
        }
      });
    }
  }
};
</script>
ruoyi-ui/src/views/system/user/authRole.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,117 @@
<template>
  <div class="app-container">
    <h4 class="form-header h4">基本信息</h4>
    <el-form ref="form" :model="form" label-width="80px">
      <el-row>
        <el-col :span="8" :offset="2">
          <el-form-item label="用户昵称" prop="nickName">
            <el-input v-model="form.nickName" disabled />
          </el-form-item>
        </el-col>
        <el-col :span="8" :offset="2">
          <el-form-item label="登录账号" prop="phonenumber">
            <el-input  v-model="form.userName" disabled />
          </el-form-item>
        </el-col>
      </el-row>
    </el-form>
    <h4 class="form-header h4">角色信息</h4>
    <el-table v-loading="loading" :row-key="getRowKey" @row-click="clickRow" ref="table" @selection-change="handleSelectionChange" :data="roles.slice((pageNum-1)*pageSize,pageNum*pageSize)">
      <el-table-column label="序号" type="index" align="center">
        <template slot-scope="scope">
          <span>{{(pageNum - 1) * pageSize + scope.$index + 1}}</span>
        </template>
      </el-table-column>
      <el-table-column type="selection" :reserve-selection="true" width="55"></el-table-column>
      <el-table-column label="角色编号" align="center" prop="roleId" />
      <el-table-column label="角色名称" align="center" prop="roleName" />
      <el-table-column label="权限字符" align="center" prop="roleKey" />
      <el-table-column label="创建时间" align="center" prop="createTime" width="180">
        <template slot-scope="scope">
          <span>{{ parseTime(scope.row.createTime) }}</span>
        </template>
      </el-table-column>
    </el-table>
    <pagination v-show="total>0" :total="total" :page.sync="pageNum" :limit.sync="pageSize" />
    <el-form label-width="100px">
      <el-form-item style="text-align: center;margin-left:-120px;margin-top:30px;">
        <el-button type="primary" @click="submitForm()">提交</el-button>
        <el-button @click="close()">返回</el-button>
      </el-form-item>
    </el-form>
  </div>
</template>
<script>
import { getAuthRole, updateAuthRole } from "@/api/system/user";
export default {
  name: "AuthRole",
  data() {
    return {
       // é®ç½©å±‚
      loading: true,
      // åˆ†é¡µä¿¡æ¯
      total: 0,
      pageNum: 1,
      pageSize: 10,
      // é€‰ä¸­è§’色编号
      roleIds:[],
      // è§’色信息
      roles: [],
      // ç”¨æˆ·ä¿¡æ¯
      form: {}
    };
  },
  created() {
    const userId = this.$route.params && this.$route.params.userId;
    if (userId) {
      this.loading = true;
      getAuthRole(userId).then((response) => {
        this.form = response.data.user;
        this.roles = response.data.roles;
        this.total = this.roles.length;
        this.$nextTick(() => {
          this.roles.forEach((row) => {
            if (row.flag) {
              this.$refs.table.toggleRowSelection(row);
            }
          });
        });
        this.loading = false;
      });
    }
  },
  methods: {
    /** å•击选中行数据 */
    clickRow(row) {
      this.$refs.table.toggleRowSelection(row);
    },
    // å¤šé€‰æ¡†é€‰ä¸­æ•°æ®
    handleSelectionChange(selection) {
      this.roleIds = selection.map((item) => item.roleId);
    },
    // ä¿å­˜é€‰ä¸­çš„æ•°æ®ç¼–号
    getRowKey(row) {
      return row.roleId;
    },
    /** æäº¤æŒ‰é’® */
    submitForm() {
      const userId = this.form.userId;
      const roleIds = this.roleIds.join(",");
      updateAuthRole({ userId: userId, roleIds: roleIds }).then((response) => {
        this.msgSuccess("授权成功");
        this.close();
      });
    },
    /** å…³é—­æŒ‰é’® */
    close() {
      this.$store.dispatch("tagsView/delView", this.$route);
      this.$router.push({ path: "/system/user" });
    },
  },
};
</script>
ruoyi-ui/src/views/system/user/index.vue
@@ -167,7 +167,7 @@
            width="160"
            class-name="small-padding fixed-width"
          >
            <template slot-scope="scope">
            <template slot-scope="scope" v-if="scope.row.userId !== 1">
              <el-button
                size="mini"
                type="text"
@@ -176,20 +176,23 @@
                v-hasPermi="['system:user:edit']"
              >修改</el-button>
              <el-button
                v-if="scope.row.userId !== 1"
                size="mini"
                type="text"
                icon="el-icon-delete"
                @click="handleDelete(scope.row)"
                v-hasPermi="['system:user:remove']"
              >删除</el-button>
              <el-button
                size="mini"
                type="text"
                icon="el-icon-key"
                @click="handleResetPwd(scope.row)"
                v-hasPermi="['system:user:resetPwd']"
              >重置</el-button>
              <el-dropdown size="mini" @command="(command) => handleCommand(command, scope.row)">
                <span class="el-dropdown-link">
                  <i class="el-icon-d-arrow-right el-icon--right"></i>更多
                </span>
                <el-dropdown-menu slot="dropdown">
                  <el-dropdown-item command="handleResetPwd" icon="el-icon-key"
                    v-hasPermi="['system:user:resetPwd']">重置密码</el-dropdown-item>
                  <el-dropdown-item command="handleAuthRole" icon="el-icon-circle-check"
                    v-hasPermi="['system:user:edit']">分配角色</el-dropdown-item>
                </el-dropdown-menu>
              </el-dropdown>
            </template>
          </el-table-column>
        </el-table>
@@ -561,6 +564,19 @@
      this.single = selection.length != 1;
      this.multiple = !selection.length;
    },
    // æ›´å¤šæ“ä½œè§¦å‘
    handleCommand(command, row) {
      switch (command) {
        case "handleResetPwd":
          this.handleResetPwd(row);
          break;
        case "handleAuthRole":
          this.handleAuthRole(row);
          break;
        default:
          break;
      }
    },
    /** æ–°å¢žæŒ‰é’®æ“ä½œ */
    handleAdd() {
      this.reset();
@@ -603,6 +619,11 @@
          });
        }).catch(() => {});
    },
    /** åˆ†é…è§’色操作 */
    handleAuthRole: function(row) {
      const userId = row.userId;
      this.$router.push("/auth/role/" + userId);
    },
    /** æäº¤æŒ‰é’® */
    submitForm: function() {
      this.$refs["form"].validate(valid => {
ruoyi-ui/vue.config.js
@@ -109,7 +109,7 @@
          config.optimization.runtimeChunk('single'),
          {
             from: path.resolve(__dirname, './public/robots.txt'), //防爬虫文件
             to: './', //到根目录下
             to: './' //到根目录下
          }
        }
      )
sql/ry_20210210.sql
@@ -159,7 +159,7 @@
insert into sys_menu values('1', '系统管理', '0', '1', 'system',           null,   1, 0, 'M', '0', '0', '', 'system',   'admin', sysdate(), '', null, '系统管理目录');
insert into sys_menu values('2', '系统监控', '0', '2', 'monitor',          null,   1, 0, 'M', '0', '0', '', 'monitor',  'admin', sysdate(), '', null, '系统监控目录');
insert into sys_menu values('3', '系统工具', '0', '3', 'tool',             null,   1, 0, 'M', '0', '0', '', 'tool',     'admin', sysdate(), '', null, '系统工具目录');
insert into sys_menu values('4', '若依官网', '0', '4', 'http://ruoyi.vip', null ,  0, 0, 'M', '0', '0', '', 'guide',    'admin', sysdate(), '', null, '若依官网地址');
insert into sys_menu values('4', 'PLUS官网', '0', '4', 'https://gitee.com/JavaLionLi/RuoYi-Vue-Plus', null ,  0, 0, 'M', '0', '0', '', 'guide',    'admin', sysdate(), '', null, 'RuoYi-Vue-Plus官网地址');
-- äºŒçº§èœå•
insert into sys_menu values('100',  '用户管理', '1',   '1', 'user',       'system/user/index',        1, 0, 'C', '0', '0', 'system:user:list',        'user',          'admin', sysdate(), '', null, '用户管理菜单');
insert into sys_menu values('101',  '角色管理', '1',   '2', 'role',       'system/role/index',        1, 0, 'C', '0', '0', 'system:role:list',        'peoples',       'admin', sysdate(), '', null, '角色管理菜单');