From 56322786887f99604450cde07c9cab6fb5ea5135 Mon Sep 17 00:00:00 2001
From: 疯狂的狮子Li <15040126243@163.com>
Date: 星期一, 12 七月 2021 11:39:43 +0800
Subject: [PATCH] !67 同步dev分支 Merge pull request !67 from 疯狂的狮子Li/dev

---
 ruoyi-ui/src/directive/permission/hasPermi.js                                                        |    4 
 ruoyi-ui/src/views/demo/demo/index.vue                                                               |    6 
 ruoyi-common/src/main/java/com/ruoyi/common/core/redis/RedisCache.java                               |    6 
 ruoyi-ui/src/views/system/user/index.vue                                                             |   39 
 ruoyi-ui/src/layout/components/InnerLink/index.vue                                                   |   27 
 ruoyi-common/src/main/java/com/ruoyi/common/utils/DictUtils.java                                     |    4 
 ruoyi-admin/src/main/resources/i18n/messages_en_US.properties                                        |   33 
 ruoyi-quartz/pom.xml                                                                                 |    2 
 ruoyi-ui/src/views/system/user/authRole.vue                                                          |  117 +
 ruoyi-common/src/main/java/com/ruoyi/common/core/mybatisplus/cache/MybatisPlusRedisCache.java        |    3 
 ruoyi-ui/.env.production                                                                             |    3 
 ruoyi-ui/.env.staging                                                                                |    3 
 ruoyi-ui/package.json                                                                                |    2 
 ruoyi-ui/src/main.js                                                                                 |   13 
 ruoyi-ui/src/views/demo/tree/index.vue                                                               |    6 
 ruoyi-common/src/main/java/com/ruoyi/common/core/domain/entity/SysUser.java                          |    4 
 ruoyi-system/src/main/java/com/ruoyi/system/mapper/SysUserMapper.java                                |   16 
 ruoyi-demo/src/main/java/com/ruoyi/demo/feign/fallback/FeignTestFallback.java                        |    3 
 ruoyi-ui/src/router/index.js                                                                         |   27 
 ruoyi-extend/ruoyi-monitor-admin/pom.xml                                                             |   73 
 ruoyi-ui/src/directive/permission/hasRole.js                                                         |    4 
 sql/ry_20210210.sql                                                                                  |    4 
 ruoyi-ui/src/views/system/role/authUser.vue                                                          |  213 ++
 ruoyi-demo/src/main/java/com/ruoyi/demo/controller/RedisCacheController.java                         |   70 
 ruoyi-framework/src/main/java/com/ruoyi/framework/config/FeignConfig.java                            |   36 
 ruoyi-ui/src/views/system/notice/index.vue                                                           |    4 
 ruoyi-generator/src/main/resources/vm/vue/index.vue.vm                                               |   48 
 ruoyi-ui/vue.config.js                                                                               |    2 
 ruoyi-admin/Dockerfile                                                                               |   14 
 ruoyi-generator/pom.xml                                                                              |    2 
 ruoyi-ui/src/directive/dialog/drag.js                                                                |   64 
 ruoyi-admin/src/main/resources/application.yml                                                       |   55 
 README.md                                                                                            |   21 
 ruoyi-framework/src/main/java/com/ruoyi/framework/mybatisplus/CreateAndUpdateMetaObjectHandler.java  |   41 
 ruoyi-extend/pom.xml                                                                                 |   18 
 ruoyi-ui/src/directive/index.js                                                                      |   18 
 ruoyi-admin/src/main/resources/application-prod.yml                                                  |   31 
 ruoyi-extend/ruoyi-monitor-admin/Dockerfile                                                          |   13 
 ruoyi-ui/.env.development                                                                            |    3 
 ruoyi-framework/src/main/java/com/ruoyi/framework/config/SecurityConfig.java                         |   12 
 ruoyi-common/src/main/java/com/ruoyi/common/constant/UserConstants.java                              |    5 
 ruoyi-extend/ruoyi-monitor-admin/src/main/java/com/ruoyi/monitor/admin/config/SecurityConfig.java    |   48 
 ruoyi-admin/src/main/resources/application-dev.yml                                                   |   27 
 docker/nginx/nginx.conf                                                                              |   77 
 ruoyi-system/src/main/resources/mapper/system/SysUserMapper.xml                                      |   45 
 ruoyi-ui/src/components/Editor/index.vue                                                             |   13 
 ruoyi-system/src/main/java/com/ruoyi/system/domain/vo/MetaVo.java                                    |   24 
 ruoyi-ui/src/views/index.vue                                                                         |   26 
 ruoyi-ui/src/views/system/role/index.vue                                                             |   38 
 ruoyi-ui/src/layout/components/AppMain.vue                                                           |    2 
 ruoyi-ui/src/views/monitor/admin/index.vue                                                           |   20 
 pom.xml                                                                                              |   21 
 ruoyi-admin/src/main/resources/i18n/messages_zh_CN.properties                                        |   36 
 ruoyi-ui/src/api/system/user.js                                                                      |   17 
 ruoyi-generator/src/main/java/com/ruoyi/generator/service/GenTableServiceImpl.java                   |   12 
 ruoyi-ui/src/components/TopNav/index.vue                                                             |    4 
 docker/docker-compose.yml                                                                            |  119 +
 ruoyi-common/src/main/java/com/ruoyi/common/utils/poi/ExcelUtil.java                                 | 2144 ++++++++++++------------
 ruoyi-system/src/main/java/com/ruoyi/system/service/ISysUserService.java                             |   30 
 ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysUserController.java                     |   27 
 ruoyi-system/src/main/java/com/ruoyi/system/service/impl/SysMenuServiceImpl.java                     |   38 
 ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysIndexController.java                    |   29 
 ruoyi-extend/ruoyi-monitor-admin/src/main/java/com/ruoyi/monitor/admin/config/AdminServerConfig.java |   31 
 ruoyi-ui/src/views/system/role/selectUser.vue                                                        |  142 +
 ruoyi-ui/src/store/modules/permission.js                                                             |    3 
 ruoyi-framework/pom.xml                                                                              |    2 
 ruoyi-admin/pom.xml                                                                                  |   25 
 ruoyi-system/pom.xml                                                                                 |    2 
 ruoyi-ui/src/api/system/role.js                                                                      |   48 
 ruoyi-ui/src/components/HeaderSearch/index.vue                                                       |    4 
 ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysMenuController.java                     |    9 
 ruoyi-ui/src/components/ImageUpload/index.vue                                                        |  198 +
 ruoyi-common/pom.xml                                                                                 |    6 
 ruoyi-ui/src/assets/styles/ruoyi.scss                                                                |   18 
 ruoyi-generator/src/main/resources/vm/java/mapper.java.vm                                            |    2 
 ruoyi-common/src/main/java/com/ruoyi/common/utils/StringUtils.java                                   |   28 
 /dev/null                                                                                            |   15 
 ruoyi-system/src/main/java/com/ruoyi/system/service/ISysRoleService.java                             |   38 
 ruoyi-system/src/main/java/com/ruoyi/system/service/impl/SysUserServiceImpl.java                     |   67 
 ruoyi-demo/pom.xml                                                                                   |    4 
 ruoyi-extend/ruoyi-monitor-admin/src/main/java/com/ruoyi/monitor/admin/MonitorAdminApplication.java  |   19 
 ruoyi-generator/src/main/resources/vm/vue/index-tree.vue.vm                                          |   46 
 ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysRoleController.java                     |   57 
 docker/deploy.sh                                                                                     |   92 +
 ruoyi-framework/src/main/java/com/ruoyi/framework/config/RedisConfig.java                            |    8 
 ruoyi-framework/src/main/java/com/ruoyi/framework/config/properties/RedissonProperties.java          |   34 
 ruoyi-ui/src/components/FileUpload/index.vue                                                         |   77 
 ruoyi-common/src/main/java/com/ruoyi/common/core/mybatisplus/methods/InsertAll.java                  |   31 
 ruoyi-extend/ruoyi-monitor-admin/src/main/resources/application.yml                                  |   11 
 ruoyi-system/src/main/java/com/ruoyi/system/service/impl/SysRoleServiceImpl.java                     |   68 
 90 files changed, 3,526 insertions(+), 1,425 deletions(-)

diff --git a/README.md b/README.md
index 5fc29ab..a56dc6c 100644
--- a/README.md
+++ b/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 绛夊垎甯冨紡鍦烘櫙瑙e喅鏂规
+
+闆嗘垚 Mybatis-Plus Lombok Hutool 绛変究鎹峰紑鍙戝伐鍏� 閫傞厤閲嶅啓鐩稿叧涓氬姟 渚夸簬寮�鍙� 
+
 * 鍓嶇寮�鍙戞鏋� Vue銆丒lement UI
 * 鍚庣寮�鍙戞鏋� Spring Boot銆丷edis
 * 瀹瑰櫒妗嗘灦 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 @@
 ### 鍥涖�佸姞缇�
 浠ヤ笂涓夌偣宸茬粡鑳借В鍐冲ぇ瀹剁粷澶ч儴鍒嗛棶棰樹簡锛屽鏋滆繕鏈夐棶棰樻病鑳介�氳繃杩欏嚑绉嶆柟寮忚В鍐筹紝閭d箞鍔犵兢锛屽ぇ瀹朵竴璧峰湪缇ら噷鎺㈣涓�涓�
 
+## 璐$尞浠g爜
+
+娆㈣繋鍚勮矾鑻遍泟璞澃 `PR` 浠g爜 璇锋彁浜ゅ埌 `dev` 寮�鍙戝垎鏀� 缁熶竴娴嬭瘯鍙戠増
+
+妗嗘灦瀹氫綅涓� `閫氱敤鍚庡彴绠$悊绯荤粺(鍒嗗竷寮忛泦缇ゅ己鍖�)` 鍘熷垯涓婁笉鎺ュ彈涓氬姟 `PR` 
+
 ## 淇敼RuoYi鍔熻兘
 ### 渚濊禆鏀瑰姩
 
@@ -74,6 +88,7 @@
 * 绉婚櫎 fastjson 缁熶竴浣跨敤 jackson 搴忓垪鍖�
 * 闆嗘垚 dynamic-datasource 澶氭暟鎹簮(榛樿鏀寔MySQL,鍏朵粬绉嶇被闇�鑷閫傞厤)
 * 闆嗘垚 Lock4j 瀹炵幇鍒嗗竷寮� 娉ㄨВ閿併�佸伐鍏烽攣 澶氱澶氭牱
+* 澧炲姞 Docker 瀹瑰櫒缂栨帓 鎵撳寘鎻掍欢涓庨儴缃茶剼鏈�
 
 ### 浠g爜鏀瑰姩
 
@@ -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/)
diff --git a/docker/deploy.sh b/docker/deploy.sh
new file mode 100644
index 0000000..3b6e696
--- /dev/null
+++ b/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
diff --git a/docker/docker-compose.yml b/docker/docker-compose.yml
new file mode 100644
index 0000000..d40ae29
--- /dev/null
+++ b/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:
+      # 灏唌ysql8.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
diff --git a/docker/nginx/nginx.conf b/docker/nginx/nginx.conf
new file mode 100644
index 0000000..66ac29e
--- /dev/null
+++ b/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/ 涓篸ocker鏄犲皠璺緞 涓嶅厑璁告洿鏀�
+        #ssl_certificate_key  /etc/nginx/cert/xxx.local.key; # /etc/nginx/cert/ 涓篸ocker鏄犲皠璺緞 涓嶅厑璁告洿鏀�
+        #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;
+        }
+    }
+}
diff --git a/pom.xml b/pom.xml
index 2a8521c..d56898b 100644
--- a/pom.xml
+++ b/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>
 
diff --git a/ruoyi-admin/Dockerfile b/ruoyi-admin/Dockerfile
new file mode 100644
index 0000000..88f4932
--- /dev/null
+++ b/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"]
diff --git a/ruoyi-admin/pom.xml b/ruoyi-admin/pom.xml
index 031427f..7450213 100644
--- a/ruoyi-admin/pom.xml
+++ b/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>
@@ -24,7 +24,7 @@
             <optional>true</optional> <!-- 琛ㄧず渚濊禆涓嶄細浼犻�� -->
         </dependency>
 
-         <!-- Mysql椹卞姩鍖� -->
+        <!-- Mysql椹卞姩鍖� -->
         <dependency>
             <groupId>mysql</groupId>
             <artifactId>mysql-connector-java</artifactId>
@@ -82,7 +82,26 @@
                     <failOnMissingWebXml>false</failOnMissingWebXml>
                     <warName>${project.artifactId}</warName>
                 </configuration>
-           </plugin>
+            </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>
 
diff --git a/ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysIndexController.java b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysIndexController.java
new file mode 100644
index 0000000..a29620c
--- /dev/null
+++ b/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());
+    }
+}
diff --git a/ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysMenuController.java b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysMenuController.java
index 00f1464..f418541 100644
--- a/ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysMenuController.java
+++ b/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() + "'澶辫触锛屽湴鍧�蹇呴』浠ttp(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() + "'澶辫触锛屽湴鍧�蹇呴』浠ttp(s)://寮�澶�");
         }
diff --git a/ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysRoleController.java b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysRoleController.java
index 70e640e..5701aa1 100644
--- a/ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysRoleController.java
+++ b/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;
@@ -25,7 +27,7 @@
 
 /**
  * 瑙掕壊淇℃伅
- * 
+ *
  * @author ruoyi
  */
 @RestController
@@ -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));
+    }
 }
diff --git a/ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysUserController.java b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysUserController.java
index 079c15a..a67615f 100644
--- a/ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysUserController.java
+++ b/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();
+    }
 }
diff --git a/ruoyi-admin/src/main/resources/application-dev.yml b/ruoyi-admin/src/main/resources/application-dev.yml
index 35901b5..62312f6 100644
--- a/ruoyi-admin/src/main/resources/application-dev.yml
+++ b/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
diff --git a/ruoyi-admin/src/main/resources/application-prod.yml b/ruoyi-admin/src/main/resources/application-prod.yml
index 35901b5..2c9d4e5 100644
--- a/ruoyi-admin/src/main/resources/application-prod.yml
+++ b/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
diff --git a/ruoyi-admin/src/main/resources/application.yml b/ruoyi-admin/src/main/resources/application.yml
index e153d19..b88929d 100644
--- a/ruoyi-admin/src/main/resources/application.yml
+++ b/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:
+  # 涓嶆敮鎸佸鍖�, 濡傛湁闇�瑕佸彲鍦ㄦ敞瑙i厤缃� 鎴� 鎻愬崌鎵寘绛夌骇
+  # 渚嬪 com.**.**.mapper
   mapperPackage: com.ruoyi.**.mapper
   # 瀵瑰簲鐨� XML 鏂囦欢浣嶇疆
   mapperLocations: classpath*:mapper/**/*Mapper.xml
@@ -156,7 +160,9 @@
     # STATEMENT 鍏抽棴涓�绾х紦瀛�
     localCacheScope: SESSION
     # 寮�鍚疢ybatis浜岀骇缂撳瓨锛岄粯璁や负 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: '鏍囬锛歊uoYi-Vue-Plus鍚庡彴绠$悊绯荤粺_鎺ュ彛鏂囨。'
+  title: '鏍囬锛�${ruoyi.name}鍚庡彴绠$悊绯荤粺_鎺ュ彛鏂囨。'
   # 鎻忚堪
   description: '鎻忚堪锛氱敤浜庣鐞嗛泦鍥㈡棗涓嬪叕鍙哥殑浜哄憳淇℃伅,鍏蜂綋鍖呮嫭XXX,XXX妯″潡...'
   # 鐗堟湰
@@ -244,6 +250,8 @@
 
 # feign 鐩稿叧閰嶇疆
 feign:
+  # 涓嶆敮鎸佸鍖�, 濡傛湁闇�瑕佸彲鍦ㄦ敞瑙i厤缃� 鎴� 鎻愬崌鎵寘绛夌骇
+  # 渚嬪 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
diff --git a/ruoyi-admin/src/main/resources/i18n/messages_en_US.properties b/ruoyi-admin/src/main/resources/i18n/messages_en_US.properties
new file mode 100644
index 0000000..4187065
--- /dev/null
+++ b/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=
diff --git a/ruoyi-admin/src/main/resources/i18n/messages_zh_CN.properties b/ruoyi-admin/src/main/resources/i18n/messages_zh_CN.properties
new file mode 100644
index 0000000..d63aa1f
--- /dev/null
+++ b/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}]
diff --git a/ruoyi-common/pom.xml b/ruoyi-common/pom.xml
index e2cbd39..28af304 100644
--- a/ruoyi-common/pom.xml
+++ b/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>
diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/constant/UserConstants.java b/ruoyi-common/src/main/java/com/ruoyi/common/constant/UserConstants.java
index 56b46ba..eda4ab6 100644
--- a/ruoyi-common/src/main/java/com/ruoyi/common/constant/UserConstants.java
+++ b/ruoyi-common/src/main/java/com/ruoyi/common/constant/UserConstants.java
@@ -2,7 +2,7 @@
 
 /**
  * 鐢ㄦ埛甯搁噺淇℃伅
- * 
+ *
  * @author ruoyi
  */
 public class UserConstants
@@ -57,6 +57,9 @@
     /** ParentView缁勪欢鏍囪瘑 */
     public final static String PARENT_VIEW = "ParentView";
 
+    /** InnerLink缁勪欢鏍囪瘑 */
+    public final static String INNER_LINK = "InnerLink";
+
     /** 鏍¢獙杩斿洖缁撴灉鐮� */
     public final static String UNIQUE = "0";
     public final static String NOT_UNIQUE = "1";
diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/core/domain/entity/SysUser.java b/ruoyi-common/src/main/java/com/ruoyi/common/core/domain/entity/SysUser.java
index 110c548..898138b 100644
--- a/ruoyi-common/src/main/java/com/ruoyi/common/core/domain/entity/SysUser.java
+++ b/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;
diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/core/mybatisplus/cache/MybatisPlusRedisCache.java b/ruoyi-common/src/main/java/com/ruoyi/common/core/mybatisplus/cache/MybatisPlusRedisCache.java
index 0e21d70..633a0b5 100644
--- a/ruoyi-common/src/main/java/com/ruoyi/common/core/mybatisplus/cache/MybatisPlusRedisCache.java
+++ b/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
diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/core/mybatisplus/methods/InsertAll.java b/ruoyi-common/src/main/java/com/ruoyi/common/core/mybatisplus/methods/InsertAll.java
index 9c8c0f7..ec57621 100644
--- a/ruoyi-common/src/main/java/com/ruoyi/common/core/mybatisplus/methods/InsertAll.java
+++ b/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;
 
 /**
  * 鍗晄ql鎵归噺鎻掑叆
@@ -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) {
diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/core/redis/RedisCache.java b/ruoyi-common/src/main/java/com/ruoyi/common/core/redis/RedisCache.java
index 3c8bc92..f46a215 100644
--- a/ruoyi-common/src/main/java/com/ruoyi/common/core/redis/RedisCache.java
+++ b/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);
 	}
 
 	/**
diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/utils/DictUtils.java b/ruoyi-common/src/main/java/com/ruoyi/common/utils/DictUtils.java
index 7da0c66..5d1fef1 100644
--- a/ruoyi-common/src/main/java/com/ruoyi/common/utils/DictUtils.java
+++ b/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)
             {
diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/utils/StringUtils.java b/ruoyi-common/src/main/java/com/ruoyi/common/utils/StringUtils.java
new file mode 100644
index 0000000..f3f29f4
--- /dev/null
+++ b/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 = '_';
+
+	/**
+	 * 鏄惁涓篽ttp(s)://寮�澶�
+	 *
+	 * @param link 閾炬帴
+	 * @return 缁撴灉
+	 */
+	public static boolean ishttp(String link) {
+		return StrUtil.startWithAny(link, Constants.HTTP, Constants.HTTPS);
+	}
+
+}
diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/utils/poi/ExcelUtil.java b/ruoyi-common/src/main/java/com/ruoyi/common/utils/poi/ExcelUtil.java
index 619ec80..96843d1 100644
--- a/ruoyi-common/src/main/java/com/ruoyi/common/utils/poi/ExcelUtil.java
+++ b/ruoyi-common/src/main/java/com/ruoyi/common/utils/poi/ExcelUtil.java
@@ -1,1072 +1,1072 @@
-package com.ruoyi.common.utils.poi;
-
-import cn.hutool.core.convert.Convert;
-import cn.hutool.core.lang.Validator;
-import cn.hutool.core.util.StrUtil;
-import com.ruoyi.common.annotation.Excel;
-import com.ruoyi.common.annotation.Excel.ColumnType;
-import com.ruoyi.common.annotation.Excel.Type;
-import com.ruoyi.common.annotation.Excels;
-import com.ruoyi.common.config.RuoYiConfig;
-import com.ruoyi.common.core.domain.AjaxResult;
-import com.ruoyi.common.exception.CustomException;
-import com.ruoyi.common.utils.DateUtils;
-import com.ruoyi.common.utils.DictUtils;
-import com.ruoyi.common.utils.file.FileTypeUtils;
-import com.ruoyi.common.utils.file.ImageUtils;
-import com.ruoyi.common.utils.reflect.ReflectUtils;
-import org.apache.poi.ss.usermodel.*;
-import org.apache.poi.ss.util.CellRangeAddressList;
-import org.apache.poi.xssf.streaming.SXSSFWorkbook;
-import org.apache.poi.xssf.usermodel.XSSFClientAnchor;
-import org.apache.poi.xssf.usermodel.XSSFDataValidation;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import java.io.*;
-import java.lang.reflect.Field;
-import java.math.BigDecimal;
-import java.text.DecimalFormat;
-import java.util.*;
-import java.util.stream.Collectors;
-
-/**
- * Excel鐩稿叧澶勭悊
- * 
- * @author ruoyi
- */
-public class ExcelUtil<T>
-{
-    private static final Logger log = LoggerFactory.getLogger(ExcelUtil.class);
-
-    /**
-     * Excel sheet鏈�澶ц鏁帮紝榛樿65536
-     */
-    public static final int sheetSize = 65536;
-
-    /**
-     * 宸ヤ綔琛ㄥ悕绉�
-     */
-    private String sheetName;
-
-    /**
-     * 瀵煎嚭绫诲瀷锛圗XPORT:瀵煎嚭鏁版嵁锛汭MPORT锛氬鍏ユā鏉匡級
-     */
-    private Type type;
-
-    /**
-     * 宸ヤ綔钖勫璞�
-     */
-    private Workbook wb;
-
-    /**
-     * 宸ヤ綔琛ㄥ璞�
-     */
-    private Sheet sheet;
-
-    /**
-     * 鏍峰紡鍒楄〃
-     */
-    private Map<String, CellStyle> styles;
-
-    /**
-     * 瀵煎叆瀵煎嚭鏁版嵁鍒楄〃
-     */
-    private List<T> list;
-
-    /**
-     * 娉ㄨВ鍒楄〃
-     */
-    private List<Object[]> fields;
-
-    /**
-     * 鏈�澶ч珮搴�
-     */
-    private short maxHeight;
-
-    /**
-     * 缁熻鍒楄〃
-     */
-    private Map<Integer, Double> statistics = new HashMap<Integer, Double>();
-    
-    /**
-     * 鏁板瓧鏍煎紡
-     */
-    private static final DecimalFormat DOUBLE_FORMAT = new DecimalFormat("######0.00");
-    
-    /**
-     * 瀹炰綋瀵硅薄
-     */
-    public Class<T> clazz;
-
-    public ExcelUtil(Class<T> clazz)
-    {
-        this.clazz = clazz;
-    }
-
-    public void init(List<T> list, String sheetName, Type type)
-    {
-        if (list == null)
-        {
-            list = new ArrayList<T>();
-        }
-        this.list = list;
-        this.sheetName = sheetName;
-        this.type = type;
-        createExcelField();
-        createWorkbook();
-    }
-
-    /**
-     * 瀵筫xcel琛ㄥ崟榛樿绗竴涓储寮曞悕杞崲鎴恖ist
-     * 
-     * @param is 杈撳叆娴�
-     * @return 杞崲鍚庨泦鍚�
-     */
-    public List<T> importExcel(InputStream is) throws Exception
-    {
-        return importExcel(StrUtil.EMPTY, is);
-    }
-
-    /**
-     * 瀵筫xcel琛ㄥ崟鎸囧畾琛ㄦ牸绱㈠紩鍚嶈浆鎹㈡垚list
-     * 
-     * @param sheetName 琛ㄦ牸绱㈠紩鍚�
-     * @param is 杈撳叆娴�
-     * @return 杞崲鍚庨泦鍚�
-     */
-    public List<T> importExcel(String sheetName, InputStream is) throws Exception
-    {
-        this.type = Type.IMPORT;
-        this.wb = WorkbookFactory.create(is);
-        List<T> list = new ArrayList<T>();
-        Sheet sheet = null;
-        if (Validator.isNotEmpty(sheetName))
-        {
-            // 濡傛灉鎸囧畾sheet鍚�,鍒欏彇鎸囧畾sheet涓殑鍐呭.
-            sheet = wb.getSheet(sheetName);
-        }
-        else
-        {
-            // 濡傛灉浼犲叆鐨剆heet鍚嶄笉瀛樺湪鍒欓粯璁ゆ寚鍚戠1涓猻heet.
-            sheet = wb.getSheetAt(0);
-        }
-
-        if (sheet == null)
-        {
-            throw new IOException("鏂囦欢sheet涓嶅瓨鍦�");
-        }
-
-        int rows = sheet.getPhysicalNumberOfRows();
-
-        if (rows > 0)
-        {
-            // 瀹氫箟涓�涓猰ap鐢ㄤ簬瀛樻斁excel鍒楃殑搴忓彿鍜宖ield.
-            Map<String, Integer> cellMap = new HashMap<String, Integer>();
-            // 鑾峰彇琛ㄥご
-            Row heard = sheet.getRow(0);
-            for (int i = 0; i < heard.getPhysicalNumberOfCells(); i++)
-            {
-                Cell cell = heard.getCell(i);
-                if (Validator.isNotNull(cell))
-                {
-                    String value = this.getCellValue(heard, i).toString();
-                    cellMap.put(value, i);
-                }
-                else
-                {
-                    cellMap.put(null, i);
-                }
-            }
-            // 鏈夋暟鎹椂鎵嶅鐞� 寰楀埌绫荤殑鎵�鏈塮ield.
-            Field[] allFields = clazz.getDeclaredFields();
-            // 瀹氫箟涓�涓猰ap鐢ㄤ簬瀛樻斁鍒楃殑搴忓彿鍜宖ield.
-            Map<Integer, Field> fieldsMap = new HashMap<Integer, Field>();
-            for (int col = 0; col < allFields.length; col++)
-            {
-                Field field = allFields[col];
-                Excel attr = field.getAnnotation(Excel.class);
-                if (attr != null && (attr.type() == Type.ALL || attr.type() == type))
-                {
-                    // 璁剧疆绫荤殑绉佹湁瀛楁灞炴�у彲璁块棶.
-                    field.setAccessible(true);
-                    Integer column = cellMap.get(attr.name());
-                    if (column != null)
-                    {
-                        fieldsMap.put(column, field);
-                    }
-                }
-            }
-            for (int i = 1; i < rows; i++)
-            {
-                // 浠庣2琛屽紑濮嬪彇鏁版嵁,榛樿绗竴琛屾槸琛ㄥご.
-                Row row = sheet.getRow(i);
-                if(row == null)
-                {
-                    continue;
-                }
-                T entity = null;
-                for (Map.Entry<Integer, Field> entry : fieldsMap.entrySet())
-                {
-                    Object val = this.getCellValue(row, entry.getKey());
-
-                    // 濡傛灉涓嶅瓨鍦ㄥ疄渚嬪垯鏂板缓.
-                    entity = (entity == null ? clazz.newInstance() : entity);
-                    // 浠巑ap涓緱鍒板搴斿垪鐨刦ield.
-                    Field field = fieldsMap.get(entry.getKey());
-                    // 鍙栧緱绫诲瀷,骞舵牴鎹璞$被鍨嬭缃��.
-                    Class<?> fieldType = field.getType();
-                    if (String.class == fieldType)
-                    {
-                        String s = Convert.toStr(val);
-                        if (StrUtil.endWith(s, ".0"))
-                        {
-                            val = StrUtil.subBefore(s, ".0",false);
-                        }
-                        else
-                        {
-                            String dateFormat = field.getAnnotation(Excel.class).dateFormat();
-                            if (Validator.isNotEmpty(dateFormat))
-                            {
-                                val = DateUtils.parseDateToStr(dateFormat, (Date) val);
-                            }
-                            else
-                            {
-                                val = Convert.toStr(val);
-                            }
-                        }
-                    }
-                    else if ((Integer.TYPE == fieldType || Integer.class == fieldType) && Validator.isNumber(Convert.toStr(val)))
-                    {
-                        val = Convert.toInt(val);
-                    }
-                    else if (Long.TYPE == fieldType || Long.class == fieldType)
-                    {
-                        val = Convert.toLong(val);
-                    }
-                    else if (Double.TYPE == fieldType || Double.class == fieldType)
-                    {
-                        val = Convert.toDouble(val);
-                    }
-                    else if (Float.TYPE == fieldType || Float.class == fieldType)
-                    {
-                        val = Convert.toFloat(val);
-                    }
-                    else if (BigDecimal.class == fieldType)
-                    {
-                        val = Convert.toBigDecimal(val);
-                    }
-                    else if (Date.class == fieldType)
-                    {
-                        if (val instanceof String)
-                        {
-                            val = DateUtils.parseDate(val);
-                        }
-                        else if (val instanceof Double)
-                        {
-                            val = DateUtil.getJavaDate((Double) val);
-                        }
-                    }
-                    else if (Boolean.TYPE == fieldType || Boolean.class == fieldType)
-                    {
-                        val = Convert.toBool(val, false);
-                    }
-                    if (Validator.isNotNull(fieldType))
-                    {
-                        Excel attr = field.getAnnotation(Excel.class);
-                        String propertyName = field.getName();
-                        if (Validator.isNotEmpty(attr.targetAttr()))
-                        {
-                            propertyName = field.getName() + "." + attr.targetAttr();
-                        }
-                        else if (Validator.isNotEmpty(attr.readConverterExp()))
-                        {
-                            val = reverseByExp(Convert.toStr(val), attr.readConverterExp(), attr.separator());
-                        }
-                        else if (Validator.isNotEmpty(attr.dictType()))
-                        {
-                            val = reverseDictByExp(Convert.toStr(val), attr.dictType(), attr.separator());
-                        }
-                        ReflectUtils.invokeSetter(entity, propertyName, val);
-                    }
-                }
-                list.add(entity);
-            }
-        }
-        return list;
-    }
-
-    /**
-     * 瀵筶ist鏁版嵁婧愬皢鍏堕噷闈㈢殑鏁版嵁瀵煎叆鍒癳xcel琛ㄥ崟
-     * 
-     * @param list 瀵煎嚭鏁版嵁闆嗗悎
-     * @param sheetName 宸ヤ綔琛ㄧ殑鍚嶇О
-     * @return 缁撴灉
-     */
-    public AjaxResult exportExcel(List<T> list, String sheetName)
-    {
-        this.init(list, sheetName, Type.EXPORT);
-        return exportExcel();
-    }
-
-    /**
-     * 瀵筶ist鏁版嵁婧愬皢鍏堕噷闈㈢殑鏁版嵁瀵煎叆鍒癳xcel琛ㄥ崟
-     * 
-     * @param sheetName 宸ヤ綔琛ㄧ殑鍚嶇О
-     * @return 缁撴灉
-     */
-    public AjaxResult importTemplateExcel(String sheetName)
-    {
-        this.init(null, sheetName, Type.IMPORT);
-        return exportExcel();
-    }
-
-    /**
-     * 瀵筶ist鏁版嵁婧愬皢鍏堕噷闈㈢殑鏁版嵁瀵煎叆鍒癳xcel琛ㄥ崟
-     * 
-     * @return 缁撴灉
-     */
-    public AjaxResult exportExcel()
-    {
-        OutputStream out = null;
-        try
-        {
-            // 鍙栧嚭涓�鍏辨湁澶氬皯涓猻heet.
-            double sheetNo = Math.ceil(list.size() / sheetSize);
-            for (int index = 0; index <= sheetNo; index++)
-            {
-                createSheet(sheetNo, index);
-
-                // 浜х敓涓�琛�
-                Row row = sheet.createRow(0);
-                int column = 0;
-                // 鍐欏叆鍚勪釜瀛楁鐨勫垪澶村悕绉�
-                for (Object[] os : fields)
-                {
-                    Excel excel = (Excel) os[1];
-                    this.createCell(excel, row, column++);
-                }
-                if (Type.EXPORT.equals(type))
-                {
-                    fillExcelData(index, row);
-                    addStatisticsRow();
-                }
-            }
-            String filename = encodingFilename(sheetName);
-            out = new FileOutputStream(getAbsoluteFile(filename));
-            wb.write(out);
-            return AjaxResult.success(filename);
-        }
-        catch (Exception e)
-        {
-            log.error("瀵煎嚭Excel寮傚父{}", e.getMessage());
-            throw new CustomException("瀵煎嚭Excel澶辫触锛岃鑱旂郴缃戠珯绠$悊鍛橈紒");
-        }
-        finally
-        {
-            if (wb != null)
-            {
-                try
-                {
-                    wb.close();
-                }
-                catch (IOException e1)
-                {
-                    e1.printStackTrace();
-                }
-            }
-            if (out != null)
-            {
-                try
-                {
-                    out.close();
-                }
-                catch (IOException e1)
-                {
-                    e1.printStackTrace();
-                }
-            }
-        }
-    }
-
-    /**
-     * 濉厖excel鏁版嵁
-     * 
-     * @param index 搴忓彿
-     * @param row 鍗曞厓鏍艰
-     */
-    public void fillExcelData(int index, Row row)
-    {
-        int startNo = index * sheetSize;
-        int endNo = Math.min(startNo + sheetSize, list.size());
-        for (int i = startNo; i < endNo; i++)
-        {
-            row = sheet.createRow(i + 1 - startNo);
-            // 寰楀埌瀵煎嚭瀵硅薄.
-            T vo = (T) list.get(i);
-            int column = 0;
-            for (Object[] os : fields)
-            {
-                Field field = (Field) os[0];
-                Excel excel = (Excel) os[1];
-                // 璁剧疆瀹炰綋绫荤鏈夊睘鎬у彲璁块棶
-                field.setAccessible(true);
-                this.addCell(excel, row, vo, field, column++);
-            }
-        }
-    }
-
-    /**
-     * 鍒涘缓琛ㄦ牸鏍峰紡
-     * 
-     * @param wb 宸ヤ綔钖勫璞�
-     * @return 鏍峰紡鍒楄〃
-     */
-    private Map<String, CellStyle> createStyles(Workbook wb)
-    {
-        // 鍐欏叆鍚勬潯璁板綍,姣忔潯璁板綍瀵瑰簲excel琛ㄤ腑鐨勪竴琛�
-        Map<String, CellStyle> styles = new HashMap<String, CellStyle>();
-        CellStyle style = wb.createCellStyle();
-        style.setAlignment(HorizontalAlignment.CENTER);
-        style.setVerticalAlignment(VerticalAlignment.CENTER);
-        style.setBorderRight(BorderStyle.THIN);
-        style.setRightBorderColor(IndexedColors.GREY_50_PERCENT.getIndex());
-        style.setBorderLeft(BorderStyle.THIN);
-        style.setLeftBorderColor(IndexedColors.GREY_50_PERCENT.getIndex());
-        style.setBorderTop(BorderStyle.THIN);
-        style.setTopBorderColor(IndexedColors.GREY_50_PERCENT.getIndex());
-        style.setBorderBottom(BorderStyle.THIN);
-        style.setBottomBorderColor(IndexedColors.GREY_50_PERCENT.getIndex());
-        Font dataFont = wb.createFont();
-        dataFont.setFontName("Arial");
-        dataFont.setFontHeightInPoints((short) 10);
-        style.setFont(dataFont);
-        styles.put("data", style);
-
-        style = wb.createCellStyle();
-        style.cloneStyleFrom(styles.get("data"));
-        style.setAlignment(HorizontalAlignment.CENTER);
-        style.setVerticalAlignment(VerticalAlignment.CENTER);
-        style.setFillForegroundColor(IndexedColors.GREY_50_PERCENT.getIndex());
-        style.setFillPattern(FillPatternType.SOLID_FOREGROUND);
-        Font headerFont = wb.createFont();
-        headerFont.setFontName("Arial");
-        headerFont.setFontHeightInPoints((short) 10);
-        headerFont.setBold(true);
-        headerFont.setColor(IndexedColors.WHITE.getIndex());
-        style.setFont(headerFont);
-        styles.put("header", style);
-        
-        style = wb.createCellStyle();
-        style.setAlignment(HorizontalAlignment.CENTER);
-        style.setVerticalAlignment(VerticalAlignment.CENTER);
-        Font totalFont = wb.createFont();
-        totalFont.setFontName("Arial");
-        totalFont.setFontHeightInPoints((short) 10);
-        style.setFont(totalFont);
-        styles.put("total", style);
-
-        style = wb.createCellStyle();
-        style.cloneStyleFrom(styles.get("data"));
-        style.setAlignment(HorizontalAlignment.LEFT);
-        styles.put("data1", style);
-
-        style = wb.createCellStyle();
-        style.cloneStyleFrom(styles.get("data"));
-        style.setAlignment(HorizontalAlignment.CENTER);
-        styles.put("data2", style);
-
-        style = wb.createCellStyle();
-        style.cloneStyleFrom(styles.get("data"));
-        style.setAlignment(HorizontalAlignment.RIGHT);
-        styles.put("data3", style);
-
-        return styles;
-    }
-
-    /**
-     * 鍒涘缓鍗曞厓鏍�
-     */
-    public Cell createCell(Excel attr, Row row, int column)
-    {
-        // 鍒涘缓鍒�
-        Cell cell = row.createCell(column);
-        // 鍐欏叆鍒椾俊鎭�
-        cell.setCellValue(attr.name());
-        setDataValidation(attr, row, column);
-        cell.setCellStyle(styles.get("header"));
-        return cell;
-    }
-
-    /**
-     * 璁剧疆鍗曞厓鏍间俊鎭�
-     * 
-     * @param value 鍗曞厓鏍煎��
-     * @param attr 娉ㄨВ鐩稿叧
-     * @param cell 鍗曞厓鏍间俊鎭�
-     */
-    public void setCellVo(Object value, Excel attr, Cell cell)
-    {
-        if (ColumnType.STRING == attr.cellType())
-        {
-            cell.setCellValue(Validator.isNull(value) ? attr.defaultValue() : value + attr.suffix());
-        }
-        else if (ColumnType.NUMERIC == attr.cellType())
-        {
-            if (Validator.isNotNull(value))
-            {
-                cell.setCellValue(StrUtil.contains(Convert.toStr(value), ".") ? Convert.toDouble(value) : Convert.toInt(value));
-            }
-        }
-        else if (ColumnType.IMAGE == attr.cellType())
-        {
-            ClientAnchor anchor = new XSSFClientAnchor(0, 0, 0, 0, (short) cell.getColumnIndex(), cell.getRow().getRowNum(), (short) (cell.getColumnIndex() + 1),
-                    cell.getRow().getRowNum() + 1);
-            String imagePath = Convert.toStr(value);
-            if (Validator.isNotEmpty(imagePath))
-            {
-                byte[] data = ImageUtils.getImage(imagePath);
-                getDrawingPatriarch(cell.getSheet()).createPicture(anchor,
-                        cell.getSheet().getWorkbook().addPicture(data, getImageType(data)));
-            }
-        }
-    }
-    
-    /**
-     * 鑾峰彇鐢诲竷
-     */
-    public static Drawing<?> getDrawingPatriarch(Sheet sheet)
-    {
-        if (sheet.getDrawingPatriarch() == null)
-        {
-            sheet.createDrawingPatriarch();
-        }
-        return sheet.getDrawingPatriarch();
-    }
-
-    /**
-     * 鑾峰彇鍥剧墖绫诲瀷,璁剧疆鍥剧墖鎻掑叆绫诲瀷
-     */
-    public int getImageType(byte[] value)
-    {
-        String type = FileTypeUtils.getFileExtendName(value);
-        if ("JPG".equalsIgnoreCase(type))
-        {
-            return Workbook.PICTURE_TYPE_JPEG;
-        }
-        else if ("PNG".equalsIgnoreCase(type))
-        {
-            return Workbook.PICTURE_TYPE_PNG;
-        }
-        return Workbook.PICTURE_TYPE_JPEG;
-    }
-
-    /**
-     * 鍒涘缓琛ㄦ牸鏍峰紡
-     */
-    public void setDataValidation(Excel attr, Row row, int column)
-    {
-        if (attr.name().indexOf("娉細") >= 0)
-        {
-            sheet.setColumnWidth(column, 6000);
-        }
-        else
-        {
-            // 璁剧疆鍒楀
-            sheet.setColumnWidth(column, (int) ((attr.width() + 0.72) * 256));
-        }
-        // 濡傛灉璁剧疆浜嗘彁绀轰俊鎭垯榧犳爣鏀句笂鍘绘彁绀�.
-        if (Validator.isNotEmpty(attr.prompt()))
-        {
-            // 杩欓噷榛樿璁句簡2-101鍒楁彁绀�.
-            setXSSFPrompt(sheet, "", attr.prompt(), 1, 100, column, column);
-        }
-        // 濡傛灉璁剧疆浜哻ombo灞炴�у垯鏈垪鍙兘閫夋嫨涓嶈兘杈撳叆
-        if (attr.combo().length > 0)
-        {
-            // 杩欓噷榛樿璁句簡2-101鍒楀彧鑳介�夋嫨涓嶈兘杈撳叆.
-            setXSSFValidation(sheet, attr.combo(), 1, 100, column, column);
-        }
-    }
-
-    /**
-     * 娣诲姞鍗曞厓鏍�
-     */
-    public Cell addCell(Excel attr, Row row, T vo, Field field, int column)
-    {
-        Cell cell = null;
-        try
-        {
-            // 璁剧疆琛岄珮
-            row.setHeight(maxHeight);
-            // 鏍规嵁Excel涓缃儏鍐靛喅瀹氭槸鍚﹀鍑�,鏈変簺鎯呭喌闇�瑕佷繚鎸佷负绌�,甯屾湜鐢ㄦ埛濉啓杩欎竴鍒�.
-            if (attr.isExport())
-            {
-                // 鍒涘缓cell
-                cell = row.createCell(column);
-                int align = attr.align().value();
-                cell.setCellStyle(styles.get("data" + (align >= 1 && align <= 3 ? align : "")));
-
-                // 鐢ㄤ簬璇诲彇瀵硅薄涓殑灞炴��
-                Object value = getTargetValue(vo, field, attr);
-                String dateFormat = attr.dateFormat();
-                String readConverterExp = attr.readConverterExp();
-                String separator = attr.separator();
-                String dictType = attr.dictType();
-                if (Validator.isNotEmpty(dateFormat) && Validator.isNotNull(value))
-                {
-                    cell.setCellValue(DateUtils.parseDateToStr(dateFormat, (Date) value));
-                }
-                else if (Validator.isNotEmpty(readConverterExp) && Validator.isNotNull(value))
-                {
-                    cell.setCellValue(convertByExp(Convert.toStr(value), readConverterExp, separator));
-                }
-                else if (Validator.isNotEmpty(dictType) && Validator.isNotNull(value))
-                {
-                    cell.setCellValue(convertDictByExp(Convert.toStr(value), dictType, separator));
-                }
-                else if (value instanceof BigDecimal && -1 != attr.scale())
-                {
-                    cell.setCellValue((((BigDecimal) value).setScale(attr.scale(), attr.roundingMode())).toString());
-                }
-                else
-                {
-                    // 璁剧疆鍒楃被鍨�
-                    setCellVo(value, attr, cell);
-                }
-                addStatisticsData(column, Convert.toStr(value), attr);
-            }
-        }
-        catch (Exception e)
-        {
-            log.error("瀵煎嚭Excel澶辫触{}", e);
-        }
-        return cell;
-    }
-
-    /**
-     * 璁剧疆 POI XSSFSheet 鍗曞厓鏍兼彁绀�
-     * 
-     * @param sheet 琛ㄥ崟
-     * @param promptTitle 鎻愮ず鏍囬
-     * @param promptContent 鎻愮ず鍐呭
-     * @param firstRow 寮�濮嬭
-     * @param endRow 缁撴潫琛�
-     * @param firstCol 寮�濮嬪垪
-     * @param endCol 缁撴潫鍒�
-     */
-    public void setXSSFPrompt(Sheet sheet, String promptTitle, String promptContent, int firstRow, int endRow,
-            int firstCol, int endCol)
-    {
-        DataValidationHelper helper = sheet.getDataValidationHelper();
-        DataValidationConstraint constraint = helper.createCustomConstraint("DD1");
-        CellRangeAddressList regions = new CellRangeAddressList(firstRow, endRow, firstCol, endCol);
-        DataValidation dataValidation = helper.createValidation(constraint, regions);
-        dataValidation.createPromptBox(promptTitle, promptContent);
-        dataValidation.setShowPromptBox(true);
-        sheet.addValidationData(dataValidation);
-    }
-
-    /**
-     * 璁剧疆鏌愪簺鍒楃殑鍊煎彧鑳借緭鍏ラ鍒剁殑鏁版嵁,鏄剧ず涓嬫媺妗�.
-     * 
-     * @param sheet 瑕佽缃殑sheet.
-     * @param textlist 涓嬫媺妗嗘樉绀虹殑鍐呭
-     * @param firstRow 寮�濮嬭
-     * @param endRow 缁撴潫琛�
-     * @param firstCol 寮�濮嬪垪
-     * @param endCol 缁撴潫鍒�
-     * @return 璁剧疆濂界殑sheet.
-     */
-    public void setXSSFValidation(Sheet sheet, String[] textlist, int firstRow, int endRow, int firstCol, int endCol)
-    {
-        DataValidationHelper helper = sheet.getDataValidationHelper();
-        // 鍔犺浇涓嬫媺鍒楄〃鍐呭
-        DataValidationConstraint constraint = helper.createExplicitListConstraint(textlist);
-        // 璁剧疆鏁版嵁鏈夋晥鎬у姞杞藉湪鍝釜鍗曞厓鏍间笂,鍥涗釜鍙傛暟鍒嗗埆鏄細璧峰琛屻�佺粓姝㈣銆佽捣濮嬪垪銆佺粓姝㈠垪
-        CellRangeAddressList regions = new CellRangeAddressList(firstRow, endRow, firstCol, endCol);
-        // 鏁版嵁鏈夋晥鎬у璞�
-        DataValidation dataValidation = helper.createValidation(constraint, regions);
-        // 澶勭悊Excel鍏煎鎬ч棶棰�
-        if (dataValidation instanceof XSSFDataValidation)
-        {
-            dataValidation.setSuppressDropDownArrow(true);
-            dataValidation.setShowErrorBox(true);
-        }
-        else
-        {
-            dataValidation.setSuppressDropDownArrow(false);
-        }
-
-        sheet.addValidationData(dataValidation);
-    }
-
-    /**
-     * 瑙f瀽瀵煎嚭鍊� 0=鐢�,1=濂�,2=鏈煡
-     * 
-     * @param propertyValue 鍙傛暟鍊�
-     * @param converterExp 缈昏瘧娉ㄨВ
-     * @param separator 鍒嗛殧绗�
-     * @return 瑙f瀽鍚庡��
-     */
-    public static String convertByExp(String propertyValue, String converterExp, String separator)
-    {
-        StringBuilder propertyString = new StringBuilder();
-        String[] convertSource = converterExp.split(",");
-        for (String item : convertSource)
-        {
-            String[] itemArray = item.split("=");
-            if (StrUtil.containsAny(separator, propertyValue))
-            {
-                for (String value : propertyValue.split(separator))
-                {
-                    if (itemArray[0].equals(value))
-                    {
-                        propertyString.append(itemArray[1] + separator);
-                        break;
-                    }
-                }
-            }
-            else
-            {
-                if (itemArray[0].equals(propertyValue))
-                {
-                    return itemArray[1];
-                }
-            }
-        }
-        return StrUtil.strip(propertyString.toString(), null,separator);
-    }
-
-    /**
-     * 鍙嶅悜瑙f瀽鍊� 鐢�=0,濂�=1,鏈煡=2
-     * 
-     * @param propertyValue 鍙傛暟鍊�
-     * @param converterExp 缈昏瘧娉ㄨВ
-     * @param separator 鍒嗛殧绗�
-     * @return 瑙f瀽鍚庡��
-     */
-    public static String reverseByExp(String propertyValue, String converterExp, String separator)
-    {
-        StringBuilder propertyString = new StringBuilder();
-        String[] convertSource = converterExp.split(",");
-        for (String item : convertSource)
-        {
-            String[] itemArray = item.split("=");
-            if (StrUtil.containsAny(separator, propertyValue))
-            {
-                for (String value : propertyValue.split(separator))
-                {
-                    if (itemArray[1].equals(value))
-                    {
-                        propertyString.append(itemArray[0] + separator);
-                        break;
-                    }
-                }
-            }
-            else
-            {
-                if (itemArray[1].equals(propertyValue))
-                {
-                    return itemArray[0];
-                }
-            }
-        }
-        return StrUtil.strip(propertyString.toString(), null,separator);
-    }
-    
-    /**
-     * 瑙f瀽瀛楀吀鍊�
-     * 
-     * @param dictValue 瀛楀吀鍊�
-     * @param dictType 瀛楀吀绫诲瀷
-     * @param separator 鍒嗛殧绗�
-     * @return 瀛楀吀鏍囩
-     */
-    public static String convertDictByExp(String dictValue, String dictType, String separator)
-    {
-        return DictUtils.getDictLabel(dictType, dictValue, separator);
-    }
-
-    /**
-     * 鍙嶅悜瑙f瀽鍊煎瓧鍏稿��
-     * 
-     * @param dictLabel 瀛楀吀鏍囩
-     * @param dictType 瀛楀吀绫诲瀷
-     * @param separator 鍒嗛殧绗�
-     * @return 瀛楀吀鍊�
-     */
-    public static String reverseDictByExp(String dictLabel, String dictType, String separator)
-    {
-        return DictUtils.getDictValue(dictType, dictLabel, separator);
-    }
-    
-    /**
-     * 鍚堣缁熻淇℃伅
-     */
-    private void addStatisticsData(Integer index, String text, Excel entity)
-    {
-        if (entity != null && entity.isStatistics())
-        {
-            Double temp = 0D;
-            if (!statistics.containsKey(index))
-            {
-                statistics.put(index, temp);
-            }
-            try
-            {
-                temp = Double.valueOf(text);
-            }
-            catch (NumberFormatException e)
-            {
-            }
-            statistics.put(index, statistics.get(index) + temp);
-        }
-    }
-
-    /**
-     * 鍒涘缓缁熻琛�
-     */
-    public void addStatisticsRow()
-    {
-        if (statistics.size() > 0)
-        {
-            Cell cell = null;
-            Row row = sheet.createRow(sheet.getLastRowNum() + 1);
-            Set<Integer> keys = statistics.keySet();
-            cell = row.createCell(0);
-            cell.setCellStyle(styles.get("total"));
-            cell.setCellValue("鍚堣");
-            
-            for (Integer key : keys)
-            {
-                cell = row.createCell(key);
-                cell.setCellStyle(styles.get("total"));
-                cell.setCellValue(DOUBLE_FORMAT.format(statistics.get(key)));
-            }
-            statistics.clear();
-        }
-    }
-
-    /**
-     * 缂栫爜鏂囦欢鍚�
-     */
-    public String encodingFilename(String filename)
-    {
-        filename = UUID.randomUUID().toString() + "_" + filename + ".xlsx";
-        return filename;
-    }
-
-    /**
-     * 鑾峰彇涓嬭浇璺緞
-     * 
-     * @param filename 鏂囦欢鍚嶇О
-     */
-    public String getAbsoluteFile(String filename)
-    {
-        String downloadPath = RuoYiConfig.getDownloadPath() + filename;
-        File desc = new File(downloadPath);
-        if (!desc.getParentFile().exists())
-        {
-            desc.getParentFile().mkdirs();
-        }
-        return downloadPath;
-    }
-
-    /**
-     * 鑾峰彇bean涓殑灞炴�у��
-     * 
-     * @param vo 瀹炰綋瀵硅薄
-     * @param field 瀛楁
-     * @param excel 娉ㄨВ
-     * @return 鏈�缁堢殑灞炴�у��
-     * @throws Exception
-     */
-    private Object getTargetValue(T vo, Field field, Excel excel) throws Exception
-    {
-        Object o = field.get(vo);
-        if (Validator.isNotEmpty(excel.targetAttr()))
-        {
-            String target = excel.targetAttr();
-            if (target.contains("."))
-            {
-                String[] targets = target.split("[.]");
-                for (String name : targets)
-                {
-                    o = getValue(o, name);
-                }
-            }
-            else
-            {
-                o = getValue(o, target);
-            }
-        }
-        return o;
-    }
-
-    /**
-     * 浠ョ被鐨勫睘鎬х殑get鏂规硶鏂规硶褰㈠紡鑾峰彇鍊�
-     * 
-     * @param o
-     * @param name
-     * @return value
-     * @throws Exception
-     */
-    private Object getValue(Object o, String name) throws Exception
-    {
-        if (Validator.isNotNull(o) && Validator.isNotEmpty(name))
-        {
-            Class<?> clazz = o.getClass();
-            Field field = clazz.getDeclaredField(name);
-            field.setAccessible(true);
-            o = field.get(o);
-        }
-        return o;
-    }
-
-    /**
-     * 寰楀埌鎵�鏈夊畾涔夊瓧娈�
-     */
-    private void createExcelField()
-    {
-        this.fields = new ArrayList<Object[]>();
-        List<Field> tempFields = new ArrayList<>();
-        tempFields.addAll(Arrays.asList(clazz.getSuperclass().getDeclaredFields()));
-        tempFields.addAll(Arrays.asList(clazz.getDeclaredFields()));
-        for (Field field : tempFields)
-        {
-            // 鍗曟敞瑙�
-            if (field.isAnnotationPresent(Excel.class))
-            {
-                putToField(field, field.getAnnotation(Excel.class));
-            }
-
-            // 澶氭敞瑙�
-            if (field.isAnnotationPresent(Excels.class))
-            {
-                Excels attrs = field.getAnnotation(Excels.class);
-                Excel[] excels = attrs.value();
-                for (Excel excel : excels)
-                {
-                    putToField(field, excel);
-                }
-            }
-        }
-        this.fields = this.fields.stream().sorted(Comparator.comparing(objects -> ((Excel) objects[1]).sort())).collect(Collectors.toList());
-        this.maxHeight = getRowHeight();
-    }
-    
-    /**
-     * 鏍规嵁娉ㄨВ鑾峰彇鏈�澶ц楂�
-     */
-    public short getRowHeight()
-    {
-        double maxHeight = 0;
-        for (Object[] os : this.fields)
-        {
-            Excel excel = (Excel) os[1];
-            maxHeight = maxHeight > excel.height() ? maxHeight : excel.height();
-        }
-        return (short) (maxHeight * 20);
-    }
-
-    /**
-     * 鏀惧埌瀛楁闆嗗悎涓�
-     */
-    private void putToField(Field field, Excel attr)
-    {
-        if (attr != null && (attr.type() == Type.ALL || attr.type() == type))
-        {
-            this.fields.add(new Object[] { field, attr });
-        }
-    }
-
-    /**
-     * 鍒涘缓涓�涓伐浣滅翱
-     */
-    public void createWorkbook()
-    {
-        this.wb = new SXSSFWorkbook(500);
-    }
-
-    /**
-     * 鍒涘缓宸ヤ綔琛�
-     * 
-     * @param sheetNo sheet鏁伴噺
-     * @param index 搴忓彿
-     */
-    public void createSheet(double sheetNo, int index)
-    {
-        this.sheet = wb.createSheet();
-        this.styles = createStyles(wb);
-        // 璁剧疆宸ヤ綔琛ㄧ殑鍚嶇О.
-        if (sheetNo == 0)
-        {
-            wb.setSheetName(index, sheetName);
-        }
-        else
-        {
-            wb.setSheetName(index, sheetName + index);
-        }
-    }
-
-    /**
-     * 鑾峰彇鍗曞厓鏍煎��
-     * 
-     * @param row 鑾峰彇鐨勮
-     * @param column 鑾峰彇鍗曞厓鏍煎垪鍙�
-     * @return 鍗曞厓鏍煎��
-     */
-    public Object getCellValue(Row row, int column)
-    {
-        if (row == null)
-        {
-            return row;
-        }
-        Object val = "";
-        try
-        {
-            Cell cell = row.getCell(column);
-            if (Validator.isNotNull(cell))
-            {
-                if (cell.getCellType() == CellType.NUMERIC || cell.getCellType() == CellType.FORMULA)
-                {
-                    val = cell.getNumericCellValue();
-                    if (DateUtil.isCellDateFormatted(cell))
-                    {
-                        val = DateUtil.getJavaDate((Double) val); // POI Excel 鏃ユ湡鏍煎紡杞崲
-                    }
-                    else
-                    {
-                        if ((Double) val % 1 != 0)
-                        {
-                            val = new BigDecimal(val.toString());
-                        }
-                        else
-                        {
-                            val = new DecimalFormat("0").format(val);
-                        }
-                    }
-                }
-                else if (cell.getCellType() == CellType.STRING)
-                {
-                    val = cell.getStringCellValue();
-                }
-                else if (cell.getCellType() == CellType.BOOLEAN)
-                {
-                    val = cell.getBooleanCellValue();
-                }
-                else if (cell.getCellType() == CellType.ERROR)
-                {
-                    val = cell.getErrorCellValue();
-                }
-
-            }
-        }
-        catch (Exception e)
-        {
-            return val;
-        }
-        return val;
-    }
-}
\ No newline at end of file
+package com.ruoyi.common.utils.poi;
+
+import cn.hutool.core.convert.Convert;
+import cn.hutool.core.lang.Validator;
+import cn.hutool.core.util.StrUtil;
+import com.ruoyi.common.annotation.Excel;
+import com.ruoyi.common.annotation.Excel.ColumnType;
+import com.ruoyi.common.annotation.Excel.Type;
+import com.ruoyi.common.annotation.Excels;
+import com.ruoyi.common.config.RuoYiConfig;
+import com.ruoyi.common.core.domain.AjaxResult;
+import com.ruoyi.common.exception.CustomException;
+import com.ruoyi.common.utils.DateUtils;
+import com.ruoyi.common.utils.DictUtils;
+import com.ruoyi.common.utils.file.FileTypeUtils;
+import com.ruoyi.common.utils.file.ImageUtils;
+import com.ruoyi.common.utils.reflect.ReflectUtils;
+import org.apache.poi.ss.usermodel.*;
+import org.apache.poi.ss.util.CellRangeAddressList;
+import org.apache.poi.xssf.streaming.SXSSFWorkbook;
+import org.apache.poi.xssf.usermodel.XSSFClientAnchor;
+import org.apache.poi.xssf.usermodel.XSSFDataValidation;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.*;
+import java.lang.reflect.Field;
+import java.math.BigDecimal;
+import java.text.DecimalFormat;
+import java.util.*;
+import java.util.stream.Collectors;
+
+/**
+ * Excel鐩稿叧澶勭悊
+ *
+ * @author ruoyi
+ */
+public class ExcelUtil<T>
+{
+    private static final Logger log = LoggerFactory.getLogger(ExcelUtil.class);
+
+    /**
+     * Excel sheet鏈�澶ц鏁帮紝榛樿65536
+     */
+    public static final int sheetSize = 65536;
+
+    /**
+     * 宸ヤ綔琛ㄥ悕绉�
+     */
+    private String sheetName;
+
+    /**
+     * 瀵煎嚭绫诲瀷锛圗XPORT:瀵煎嚭鏁版嵁锛汭MPORT锛氬鍏ユā鏉匡級
+     */
+    private Type type;
+
+    /**
+     * 宸ヤ綔钖勫璞�
+     */
+    private Workbook wb;
+
+    /**
+     * 宸ヤ綔琛ㄥ璞�
+     */
+    private Sheet sheet;
+
+    /**
+     * 鏍峰紡鍒楄〃
+     */
+    private Map<String, CellStyle> styles;
+
+    /**
+     * 瀵煎叆瀵煎嚭鏁版嵁鍒楄〃
+     */
+    private List<T> list;
+
+    /**
+     * 娉ㄨВ鍒楄〃
+     */
+    private List<Object[]> fields;
+
+    /**
+     * 鏈�澶ч珮搴�
+     */
+    private short maxHeight;
+
+    /**
+     * 缁熻鍒楄〃
+     */
+    private Map<Integer, Double> statistics = new HashMap<Integer, Double>();
+
+    /**
+     * 鏁板瓧鏍煎紡
+     */
+    private static final DecimalFormat DOUBLE_FORMAT = new DecimalFormat("######0.00");
+
+    /**
+     * 瀹炰綋瀵硅薄
+     */
+    public Class<T> clazz;
+
+    public ExcelUtil(Class<T> clazz)
+    {
+        this.clazz = clazz;
+    }
+
+    public void init(List<T> list, String sheetName, Type type)
+    {
+        if (list == null)
+        {
+            list = new ArrayList<T>();
+        }
+        this.list = list;
+        this.sheetName = sheetName;
+        this.type = type;
+        createExcelField();
+        createWorkbook();
+    }
+
+    /**
+     * 瀵筫xcel琛ㄥ崟榛樿绗竴涓储寮曞悕杞崲鎴恖ist
+     *
+     * @param is 杈撳叆娴�
+     * @return 杞崲鍚庨泦鍚�
+     */
+    public List<T> importExcel(InputStream is) throws Exception
+    {
+        return importExcel(StrUtil.EMPTY, is);
+    }
+
+    /**
+     * 瀵筫xcel琛ㄥ崟鎸囧畾琛ㄦ牸绱㈠紩鍚嶈浆鎹㈡垚list
+     *
+     * @param sheetName 琛ㄦ牸绱㈠紩鍚�
+     * @param is 杈撳叆娴�
+     * @return 杞崲鍚庨泦鍚�
+     */
+    public List<T> importExcel(String sheetName, InputStream is) throws Exception
+    {
+        this.type = Type.IMPORT;
+        this.wb = WorkbookFactory.create(is);
+        List<T> list = new ArrayList<T>();
+        Sheet sheet = null;
+        if (Validator.isNotEmpty(sheetName))
+        {
+            // 濡傛灉鎸囧畾sheet鍚�,鍒欏彇鎸囧畾sheet涓殑鍐呭.
+            sheet = wb.getSheet(sheetName);
+        }
+        else
+        {
+            // 濡傛灉浼犲叆鐨剆heet鍚嶄笉瀛樺湪鍒欓粯璁ゆ寚鍚戠1涓猻heet.
+            sheet = wb.getSheetAt(0);
+        }
+
+        if (sheet == null)
+        {
+            throw new IOException("鏂囦欢sheet涓嶅瓨鍦�");
+        }
+
+        int rows = sheet.getPhysicalNumberOfRows();
+
+        if (rows > 0)
+        {
+            // 瀹氫箟涓�涓猰ap鐢ㄤ簬瀛樻斁excel鍒楃殑搴忓彿鍜宖ield.
+            Map<String, Integer> cellMap = new HashMap<String, Integer>();
+            // 鑾峰彇琛ㄥご
+            Row heard = sheet.getRow(0);
+            for (int i = 0; i < heard.getPhysicalNumberOfCells(); i++)
+            {
+                Cell cell = heard.getCell(i);
+                if (Validator.isNotNull(cell))
+                {
+                    String value = this.getCellValue(heard, i).toString();
+                    cellMap.put(value, i);
+                }
+                else
+                {
+                    cellMap.put(null, i);
+                }
+            }
+            // 鏈夋暟鎹椂鎵嶅鐞� 寰楀埌绫荤殑鎵�鏈塮ield.
+            Field[] allFields = clazz.getDeclaredFields();
+            // 瀹氫箟涓�涓猰ap鐢ㄤ簬瀛樻斁鍒楃殑搴忓彿鍜宖ield.
+            Map<Integer, Field> fieldsMap = new HashMap<Integer, Field>();
+            for (int col = 0; col < allFields.length; col++)
+            {
+                Field field = allFields[col];
+                Excel attr = field.getAnnotation(Excel.class);
+                if (attr != null && (attr.type() == Type.ALL || attr.type() == type))
+                {
+                    // 璁剧疆绫荤殑绉佹湁瀛楁灞炴�у彲璁块棶.
+                    field.setAccessible(true);
+                    Integer column = cellMap.get(attr.name());
+                    if (column != null)
+                    {
+                        fieldsMap.put(column, field);
+                    }
+                }
+            }
+            for (int i = 1; i < rows; i++)
+            {
+                // 浠庣2琛屽紑濮嬪彇鏁版嵁,榛樿绗竴琛屾槸琛ㄥご.
+                Row row = sheet.getRow(i);
+                if(row == null)
+                {
+                    continue;
+                }
+                T entity = null;
+                for (Map.Entry<Integer, Field> entry : fieldsMap.entrySet())
+                {
+                    Object val = this.getCellValue(row, entry.getKey());
+
+                    // 濡傛灉涓嶅瓨鍦ㄥ疄渚嬪垯鏂板缓.
+                    entity = (entity == null ? clazz.newInstance() : entity);
+                    // 浠巑ap涓緱鍒板搴斿垪鐨刦ield.
+                    Field field = fieldsMap.get(entry.getKey());
+                    // 鍙栧緱绫诲瀷,骞舵牴鎹璞$被鍨嬭缃��.
+                    Class<?> fieldType = field.getType();
+                    if (String.class == fieldType)
+                    {
+                        String s = Convert.toStr(val);
+                        if (StrUtil.endWith(s, ".0"))
+                        {
+                            val = StrUtil.subBefore(s, ".0",false);
+                        }
+                        else
+                        {
+                            String dateFormat = field.getAnnotation(Excel.class).dateFormat();
+                            if (Validator.isNotEmpty(dateFormat))
+                            {
+                                val = DateUtils.parseDateToStr(dateFormat, (Date) val);
+                            }
+                            else
+                            {
+                                val = Convert.toStr(val);
+                            }
+                        }
+                    }
+                    else if ((Integer.TYPE == fieldType || Integer.class == fieldType) && Validator.isNumber(Convert.toStr(val)))
+                    {
+                        val = Convert.toInt(val);
+                    }
+                    else if (Long.TYPE == fieldType || Long.class == fieldType)
+                    {
+                        val = Convert.toLong(val);
+                    }
+                    else if (Double.TYPE == fieldType || Double.class == fieldType)
+                    {
+                        val = Convert.toDouble(val);
+                    }
+                    else if (Float.TYPE == fieldType || Float.class == fieldType)
+                    {
+                        val = Convert.toFloat(val);
+                    }
+                    else if (BigDecimal.class == fieldType)
+                    {
+                        val = Convert.toBigDecimal(val);
+                    }
+                    else if (Date.class == fieldType)
+                    {
+                        if (val instanceof String)
+                        {
+                            val = DateUtils.parseDate(val);
+                        }
+                        else if (val instanceof Double)
+                        {
+                            val = DateUtil.getJavaDate((Double) val);
+                        }
+                    }
+                    else if (Boolean.TYPE == fieldType || Boolean.class == fieldType)
+                    {
+                        val = Convert.toBool(val, false);
+                    }
+                    if (Validator.isNotNull(fieldType))
+                    {
+                        Excel attr = field.getAnnotation(Excel.class);
+                        String propertyName = field.getName();
+                        if (Validator.isNotEmpty(attr.targetAttr()))
+                        {
+                            propertyName = field.getName() + "." + attr.targetAttr();
+                        }
+                        else if (Validator.isNotEmpty(attr.readConverterExp()))
+                        {
+                            val = reverseByExp(Convert.toStr(val), attr.readConverterExp(), attr.separator());
+                        }
+                        else if (Validator.isNotEmpty(attr.dictType()))
+                        {
+                            val = reverseDictByExp(Convert.toStr(val), attr.dictType(), attr.separator());
+                        }
+                        ReflectUtils.invokeSetter(entity, propertyName, val);
+                    }
+                }
+                list.add(entity);
+            }
+        }
+        return list;
+    }
+
+    /**
+     * 瀵筶ist鏁版嵁婧愬皢鍏堕噷闈㈢殑鏁版嵁瀵煎叆鍒癳xcel琛ㄥ崟
+     *
+     * @param list 瀵煎嚭鏁版嵁闆嗗悎
+     * @param sheetName 宸ヤ綔琛ㄧ殑鍚嶇О
+     * @return 缁撴灉
+     */
+    public AjaxResult exportExcel(List<T> list, String sheetName)
+    {
+        this.init(list, sheetName, Type.EXPORT);
+        return exportExcel();
+    }
+
+    /**
+     * 瀵筶ist鏁版嵁婧愬皢鍏堕噷闈㈢殑鏁版嵁瀵煎叆鍒癳xcel琛ㄥ崟
+     *
+     * @param sheetName 宸ヤ綔琛ㄧ殑鍚嶇О
+     * @return 缁撴灉
+     */
+    public AjaxResult importTemplateExcel(String sheetName)
+    {
+        this.init(null, sheetName, Type.IMPORT);
+        return exportExcel();
+    }
+
+    /**
+     * 瀵筶ist鏁版嵁婧愬皢鍏堕噷闈㈢殑鏁版嵁瀵煎叆鍒癳xcel琛ㄥ崟
+     *
+     * @return 缁撴灉
+     */
+    public AjaxResult exportExcel()
+    {
+        OutputStream out = null;
+        try
+        {
+            // 鍙栧嚭涓�鍏辨湁澶氬皯涓猻heet.
+            double sheetNo = Math.ceil(list.size() / sheetSize);
+            for (int index = 0; index <= sheetNo; index++)
+            {
+                createSheet(sheetNo, index);
+
+                // 浜х敓涓�琛�
+                Row row = sheet.createRow(0);
+                int column = 0;
+                // 鍐欏叆鍚勪釜瀛楁鐨勫垪澶村悕绉�
+                for (Object[] os : fields)
+                {
+                    Excel excel = (Excel) os[1];
+                    this.createCell(excel, row, column++);
+                }
+                if (Type.EXPORT.equals(type))
+                {
+                    fillExcelData(index, row);
+                    addStatisticsRow();
+                }
+            }
+            String filename = encodingFilename(sheetName);
+            out = new FileOutputStream(getAbsoluteFile(filename));
+            wb.write(out);
+            return AjaxResult.success(filename);
+        }
+        catch (Exception e)
+        {
+            log.error("瀵煎嚭Excel寮傚父{}", e.getMessage());
+            throw new CustomException("瀵煎嚭Excel澶辫触锛岃鑱旂郴缃戠珯绠$悊鍛橈紒");
+        }
+        finally
+        {
+            if (wb != null)
+            {
+                try
+                {
+                    wb.close();
+                }
+                catch (IOException e1)
+                {
+                    e1.printStackTrace();
+                }
+            }
+            if (out != null)
+            {
+                try
+                {
+                    out.close();
+                }
+                catch (IOException e1)
+                {
+                    e1.printStackTrace();
+                }
+            }
+        }
+    }
+
+    /**
+     * 濉厖excel鏁版嵁
+     *
+     * @param index 搴忓彿
+     * @param row 鍗曞厓鏍艰
+     */
+    public void fillExcelData(int index, Row row)
+    {
+        int startNo = index * sheetSize;
+        int endNo = Math.min(startNo + sheetSize, list.size());
+        for (int i = startNo; i < endNo; i++)
+        {
+            row = sheet.createRow(i + 1 - startNo);
+            // 寰楀埌瀵煎嚭瀵硅薄.
+            T vo = (T) list.get(i);
+            int column = 0;
+            for (Object[] os : fields)
+            {
+                Field field = (Field) os[0];
+                Excel excel = (Excel) os[1];
+                // 璁剧疆瀹炰綋绫荤鏈夊睘鎬у彲璁块棶
+                field.setAccessible(true);
+                this.addCell(excel, row, vo, field, column++);
+            }
+        }
+    }
+
+    /**
+     * 鍒涘缓琛ㄦ牸鏍峰紡
+     *
+     * @param wb 宸ヤ綔钖勫璞�
+     * @return 鏍峰紡鍒楄〃
+     */
+    private Map<String, CellStyle> createStyles(Workbook wb)
+    {
+        // 鍐欏叆鍚勬潯璁板綍,姣忔潯璁板綍瀵瑰簲excel琛ㄤ腑鐨勪竴琛�
+        Map<String, CellStyle> styles = new HashMap<String, CellStyle>();
+        CellStyle style = wb.createCellStyle();
+        style.setAlignment(HorizontalAlignment.CENTER);
+        style.setVerticalAlignment(VerticalAlignment.CENTER);
+        style.setBorderRight(BorderStyle.THIN);
+        style.setRightBorderColor(IndexedColors.GREY_50_PERCENT.getIndex());
+        style.setBorderLeft(BorderStyle.THIN);
+        style.setLeftBorderColor(IndexedColors.GREY_50_PERCENT.getIndex());
+        style.setBorderTop(BorderStyle.THIN);
+        style.setTopBorderColor(IndexedColors.GREY_50_PERCENT.getIndex());
+        style.setBorderBottom(BorderStyle.THIN);
+        style.setBottomBorderColor(IndexedColors.GREY_50_PERCENT.getIndex());
+        Font dataFont = wb.createFont();
+        dataFont.setFontName("Arial");
+        dataFont.setFontHeightInPoints((short) 10);
+        style.setFont(dataFont);
+        styles.put("data", style);
+
+        style = wb.createCellStyle();
+        style.cloneStyleFrom(styles.get("data"));
+        style.setAlignment(HorizontalAlignment.CENTER);
+        style.setVerticalAlignment(VerticalAlignment.CENTER);
+        style.setFillForegroundColor(IndexedColors.GREY_50_PERCENT.getIndex());
+        style.setFillPattern(FillPatternType.SOLID_FOREGROUND);
+        Font headerFont = wb.createFont();
+        headerFont.setFontName("Arial");
+        headerFont.setFontHeightInPoints((short) 10);
+        headerFont.setBold(true);
+        headerFont.setColor(IndexedColors.WHITE.getIndex());
+        style.setFont(headerFont);
+        styles.put("header", style);
+
+        style = wb.createCellStyle();
+        style.setAlignment(HorizontalAlignment.CENTER);
+        style.setVerticalAlignment(VerticalAlignment.CENTER);
+        Font totalFont = wb.createFont();
+        totalFont.setFontName("Arial");
+        totalFont.setFontHeightInPoints((short) 10);
+        style.setFont(totalFont);
+        styles.put("total", style);
+
+        style = wb.createCellStyle();
+        style.cloneStyleFrom(styles.get("data"));
+        style.setAlignment(HorizontalAlignment.LEFT);
+        styles.put("data1", style);
+
+        style = wb.createCellStyle();
+        style.cloneStyleFrom(styles.get("data"));
+        style.setAlignment(HorizontalAlignment.CENTER);
+        styles.put("data2", style);
+
+        style = wb.createCellStyle();
+        style.cloneStyleFrom(styles.get("data"));
+        style.setAlignment(HorizontalAlignment.RIGHT);
+        styles.put("data3", style);
+
+        return styles;
+    }
+
+    /**
+     * 鍒涘缓鍗曞厓鏍�
+     */
+    public Cell createCell(Excel attr, Row row, int column)
+    {
+        // 鍒涘缓鍒�
+        Cell cell = row.createCell(column);
+        // 鍐欏叆鍒椾俊鎭�
+        cell.setCellValue(attr.name());
+        setDataValidation(attr, row, column);
+        cell.setCellStyle(styles.get("header"));
+        return cell;
+    }
+
+    /**
+     * 璁剧疆鍗曞厓鏍间俊鎭�
+     *
+     * @param value 鍗曞厓鏍煎��
+     * @param attr 娉ㄨВ鐩稿叧
+     * @param cell 鍗曞厓鏍间俊鎭�
+     */
+    public void setCellVo(Object value, Excel attr, Cell cell)
+    {
+        if (ColumnType.STRING == attr.cellType())
+        {
+            cell.setCellValue(Validator.isNull(value) ? attr.defaultValue() : value + attr.suffix());
+        }
+        else if (ColumnType.NUMERIC == attr.cellType())
+        {
+            if (Validator.isNotNull(value))
+            {
+                cell.setCellValue(StrUtil.contains(Convert.toStr(value), ".") ? Convert.toDouble(value) : Convert.toInt(value));
+            }
+        }
+        else if (ColumnType.IMAGE == attr.cellType())
+        {
+            ClientAnchor anchor = new XSSFClientAnchor(0, 0, 0, 0, (short) cell.getColumnIndex(), cell.getRow().getRowNum(), (short) (cell.getColumnIndex() + 1),
+                    cell.getRow().getRowNum() + 1);
+            String imagePath = Convert.toStr(value);
+            if (Validator.isNotEmpty(imagePath))
+            {
+                byte[] data = ImageUtils.getImage(imagePath);
+                getDrawingPatriarch(cell.getSheet()).createPicture(anchor,
+                        cell.getSheet().getWorkbook().addPicture(data, getImageType(data)));
+            }
+        }
+    }
+
+    /**
+     * 鑾峰彇鐢诲竷
+     */
+    public static Drawing<?> getDrawingPatriarch(Sheet sheet)
+    {
+        if (sheet.getDrawingPatriarch() == null)
+        {
+            sheet.createDrawingPatriarch();
+        }
+        return sheet.getDrawingPatriarch();
+    }
+
+    /**
+     * 鑾峰彇鍥剧墖绫诲瀷,璁剧疆鍥剧墖鎻掑叆绫诲瀷
+     */
+    public int getImageType(byte[] value)
+    {
+        String type = FileTypeUtils.getFileExtendName(value);
+        if ("JPG".equalsIgnoreCase(type))
+        {
+            return Workbook.PICTURE_TYPE_JPEG;
+        }
+        else if ("PNG".equalsIgnoreCase(type))
+        {
+            return Workbook.PICTURE_TYPE_PNG;
+        }
+        return Workbook.PICTURE_TYPE_JPEG;
+    }
+
+    /**
+     * 鍒涘缓琛ㄦ牸鏍峰紡
+     */
+    public void setDataValidation(Excel attr, Row row, int column)
+    {
+        if (attr.name().indexOf("娉細") >= 0)
+        {
+            sheet.setColumnWidth(column, 6000);
+        }
+        else
+        {
+            // 璁剧疆鍒楀
+            sheet.setColumnWidth(column, (int) ((attr.width() + 0.72) * 256));
+        }
+        // 濡傛灉璁剧疆浜嗘彁绀轰俊鎭垯榧犳爣鏀句笂鍘绘彁绀�.
+        if (Validator.isNotEmpty(attr.prompt()))
+        {
+            // 杩欓噷榛樿璁句簡2-101鍒楁彁绀�.
+            setXSSFPrompt(sheet, "", attr.prompt(), 1, 100, column, column);
+        }
+        // 濡傛灉璁剧疆浜哻ombo灞炴�у垯鏈垪鍙兘閫夋嫨涓嶈兘杈撳叆
+        if (attr.combo().length > 0)
+        {
+            // 杩欓噷榛樿璁句簡2-101鍒楀彧鑳介�夋嫨涓嶈兘杈撳叆.
+            setXSSFValidation(sheet, attr.combo(), 1, 100, column, column);
+        }
+    }
+
+    /**
+     * 娣诲姞鍗曞厓鏍�
+     */
+    public Cell addCell(Excel attr, Row row, T vo, Field field, int column)
+    {
+        Cell cell = null;
+        try
+        {
+            // 璁剧疆琛岄珮
+            row.setHeight(maxHeight);
+            // 鏍规嵁Excel涓缃儏鍐靛喅瀹氭槸鍚﹀鍑�,鏈変簺鎯呭喌闇�瑕佷繚鎸佷负绌�,甯屾湜鐢ㄦ埛濉啓杩欎竴鍒�.
+            if (attr.isExport())
+            {
+                // 鍒涘缓cell
+                cell = row.createCell(column);
+                int align = attr.align().value();
+                cell.setCellStyle(styles.get("data" + (align >= 1 && align <= 3 ? align : "")));
+
+                // 鐢ㄤ簬璇诲彇瀵硅薄涓殑灞炴��
+                Object value = getTargetValue(vo, field, attr);
+                String dateFormat = attr.dateFormat();
+                String readConverterExp = attr.readConverterExp();
+                String separator = attr.separator();
+                String dictType = attr.dictType();
+                if (Validator.isNotEmpty(dateFormat) && Validator.isNotNull(value))
+                {
+                    cell.setCellValue(DateUtils.parseDateToStr(dateFormat, (Date) value));
+                }
+                else if (Validator.isNotEmpty(readConverterExp) && Validator.isNotNull(value))
+                {
+                    cell.setCellValue(convertByExp(Convert.toStr(value), readConverterExp, separator));
+                }
+                else if (Validator.isNotEmpty(dictType) && Validator.isNotNull(value))
+                {
+                    cell.setCellValue(convertDictByExp(Convert.toStr(value), dictType, separator));
+                }
+                else if (value instanceof BigDecimal && -1 != attr.scale())
+                {
+                    cell.setCellValue((((BigDecimal) value).setScale(attr.scale(), attr.roundingMode())).toString());
+                }
+                else
+                {
+                    // 璁剧疆鍒楃被鍨�
+                    setCellVo(value, attr, cell);
+                }
+                addStatisticsData(column, Convert.toStr(value), attr);
+            }
+        }
+        catch (Exception e)
+        {
+            log.error("瀵煎嚭Excel澶辫触{}", e);
+        }
+        return cell;
+    }
+
+    /**
+     * 璁剧疆 POI XSSFSheet 鍗曞厓鏍兼彁绀�
+     *
+     * @param sheet 琛ㄥ崟
+     * @param promptTitle 鎻愮ず鏍囬
+     * @param promptContent 鎻愮ず鍐呭
+     * @param firstRow 寮�濮嬭
+     * @param endRow 缁撴潫琛�
+     * @param firstCol 寮�濮嬪垪
+     * @param endCol 缁撴潫鍒�
+     */
+    public void setXSSFPrompt(Sheet sheet, String promptTitle, String promptContent, int firstRow, int endRow,
+            int firstCol, int endCol)
+    {
+        DataValidationHelper helper = sheet.getDataValidationHelper();
+        DataValidationConstraint constraint = helper.createCustomConstraint("DD1");
+        CellRangeAddressList regions = new CellRangeAddressList(firstRow, endRow, firstCol, endCol);
+        DataValidation dataValidation = helper.createValidation(constraint, regions);
+        dataValidation.createPromptBox(promptTitle, promptContent);
+        dataValidation.setShowPromptBox(true);
+        sheet.addValidationData(dataValidation);
+    }
+
+    /**
+     * 璁剧疆鏌愪簺鍒楃殑鍊煎彧鑳借緭鍏ラ鍒剁殑鏁版嵁,鏄剧ず涓嬫媺妗�.
+     *
+     * @param sheet 瑕佽缃殑sheet.
+     * @param textlist 涓嬫媺妗嗘樉绀虹殑鍐呭
+     * @param firstRow 寮�濮嬭
+     * @param endRow 缁撴潫琛�
+     * @param firstCol 寮�濮嬪垪
+     * @param endCol 缁撴潫鍒�
+     * @return 璁剧疆濂界殑sheet.
+     */
+    public void setXSSFValidation(Sheet sheet, String[] textlist, int firstRow, int endRow, int firstCol, int endCol)
+    {
+        DataValidationHelper helper = sheet.getDataValidationHelper();
+        // 鍔犺浇涓嬫媺鍒楄〃鍐呭
+        DataValidationConstraint constraint = helper.createExplicitListConstraint(textlist);
+        // 璁剧疆鏁版嵁鏈夋晥鎬у姞杞藉湪鍝釜鍗曞厓鏍间笂,鍥涗釜鍙傛暟鍒嗗埆鏄細璧峰琛屻�佺粓姝㈣銆佽捣濮嬪垪銆佺粓姝㈠垪
+        CellRangeAddressList regions = new CellRangeAddressList(firstRow, endRow, firstCol, endCol);
+        // 鏁版嵁鏈夋晥鎬у璞�
+        DataValidation dataValidation = helper.createValidation(constraint, regions);
+        // 澶勭悊Excel鍏煎鎬ч棶棰�
+        if (dataValidation instanceof XSSFDataValidation)
+        {
+            dataValidation.setSuppressDropDownArrow(true);
+            dataValidation.setShowErrorBox(true);
+        }
+        else
+        {
+            dataValidation.setSuppressDropDownArrow(false);
+        }
+
+        sheet.addValidationData(dataValidation);
+    }
+
+    /**
+     * 瑙f瀽瀵煎嚭鍊� 0=鐢�,1=濂�,2=鏈煡
+     *
+     * @param propertyValue 鍙傛暟鍊�
+     * @param converterExp 缈昏瘧娉ㄨВ
+     * @param separator 鍒嗛殧绗�
+     * @return 瑙f瀽鍚庡��
+     */
+    public static String convertByExp(String propertyValue, String converterExp, String separator)
+    {
+        StringBuilder propertyString = new StringBuilder();
+        String[] convertSource = converterExp.split(",");
+        for (String item : convertSource)
+        {
+            String[] itemArray = item.split("=");
+            if (StrUtil.containsAny(propertyValue, separator))
+            {
+                for (String value : propertyValue.split(separator))
+                {
+                    if (itemArray[0].equals(value))
+                    {
+                        propertyString.append(itemArray[1] + separator);
+                        break;
+                    }
+                }
+            }
+            else
+            {
+                if (itemArray[0].equals(propertyValue))
+                {
+                    return itemArray[1];
+                }
+            }
+        }
+        return StrUtil.strip(propertyString.toString(), null,separator);
+    }
+
+    /**
+     * 鍙嶅悜瑙f瀽鍊� 鐢�=0,濂�=1,鏈煡=2
+     *
+     * @param propertyValue 鍙傛暟鍊�
+     * @param converterExp 缈昏瘧娉ㄨВ
+     * @param separator 鍒嗛殧绗�
+     * @return 瑙f瀽鍚庡��
+     */
+    public static String reverseByExp(String propertyValue, String converterExp, String separator)
+    {
+        StringBuilder propertyString = new StringBuilder();
+        String[] convertSource = converterExp.split(",");
+        for (String item : convertSource)
+        {
+            String[] itemArray = item.split("=");
+            if (StrUtil.containsAny(propertyValue, separator))
+            {
+                for (String value : propertyValue.split(separator))
+                {
+                    if (itemArray[1].equals(value))
+                    {
+                        propertyString.append(itemArray[0] + separator);
+                        break;
+                    }
+                }
+            }
+            else
+            {
+                if (itemArray[1].equals(propertyValue))
+                {
+                    return itemArray[0];
+                }
+            }
+        }
+        return StrUtil.strip(propertyString.toString(), null,separator);
+    }
+
+    /**
+     * 瑙f瀽瀛楀吀鍊�
+     *
+     * @param dictValue 瀛楀吀鍊�
+     * @param dictType 瀛楀吀绫诲瀷
+     * @param separator 鍒嗛殧绗�
+     * @return 瀛楀吀鏍囩
+     */
+    public static String convertDictByExp(String dictValue, String dictType, String separator)
+    {
+        return DictUtils.getDictLabel(dictType, dictValue, separator);
+    }
+
+    /**
+     * 鍙嶅悜瑙f瀽鍊煎瓧鍏稿��
+     *
+     * @param dictLabel 瀛楀吀鏍囩
+     * @param dictType 瀛楀吀绫诲瀷
+     * @param separator 鍒嗛殧绗�
+     * @return 瀛楀吀鍊�
+     */
+    public static String reverseDictByExp(String dictLabel, String dictType, String separator)
+    {
+        return DictUtils.getDictValue(dictType, dictLabel, separator);
+    }
+
+    /**
+     * 鍚堣缁熻淇℃伅
+     */
+    private void addStatisticsData(Integer index, String text, Excel entity)
+    {
+        if (entity != null && entity.isStatistics())
+        {
+            Double temp = 0D;
+            if (!statistics.containsKey(index))
+            {
+                statistics.put(index, temp);
+            }
+            try
+            {
+                temp = Double.valueOf(text);
+            }
+            catch (NumberFormatException e)
+            {
+            }
+            statistics.put(index, statistics.get(index) + temp);
+        }
+    }
+
+    /**
+     * 鍒涘缓缁熻琛�
+     */
+    public void addStatisticsRow()
+    {
+        if (statistics.size() > 0)
+        {
+            Cell cell = null;
+            Row row = sheet.createRow(sheet.getLastRowNum() + 1);
+            Set<Integer> keys = statistics.keySet();
+            cell = row.createCell(0);
+            cell.setCellStyle(styles.get("total"));
+            cell.setCellValue("鍚堣");
+
+            for (Integer key : keys)
+            {
+                cell = row.createCell(key);
+                cell.setCellStyle(styles.get("total"));
+                cell.setCellValue(DOUBLE_FORMAT.format(statistics.get(key)));
+            }
+            statistics.clear();
+        }
+    }
+
+    /**
+     * 缂栫爜鏂囦欢鍚�
+     */
+    public String encodingFilename(String filename)
+    {
+        filename = UUID.randomUUID().toString() + "_" + filename + ".xlsx";
+        return filename;
+    }
+
+    /**
+     * 鑾峰彇涓嬭浇璺緞
+     *
+     * @param filename 鏂囦欢鍚嶇О
+     */
+    public String getAbsoluteFile(String filename)
+    {
+        String downloadPath = RuoYiConfig.getDownloadPath() + filename;
+        File desc = new File(downloadPath);
+        if (!desc.getParentFile().exists())
+        {
+            desc.getParentFile().mkdirs();
+        }
+        return downloadPath;
+    }
+
+    /**
+     * 鑾峰彇bean涓殑灞炴�у��
+     *
+     * @param vo 瀹炰綋瀵硅薄
+     * @param field 瀛楁
+     * @param excel 娉ㄨВ
+     * @return 鏈�缁堢殑灞炴�у��
+     * @throws Exception
+     */
+    private Object getTargetValue(T vo, Field field, Excel excel) throws Exception
+    {
+        Object o = field.get(vo);
+        if (Validator.isNotEmpty(excel.targetAttr()))
+        {
+            String target = excel.targetAttr();
+            if (target.contains("."))
+            {
+                String[] targets = target.split("[.]");
+                for (String name : targets)
+                {
+                    o = getValue(o, name);
+                }
+            }
+            else
+            {
+                o = getValue(o, target);
+            }
+        }
+        return o;
+    }
+
+    /**
+     * 浠ョ被鐨勫睘鎬х殑get鏂规硶鏂规硶褰㈠紡鑾峰彇鍊�
+     *
+     * @param o
+     * @param name
+     * @return value
+     * @throws Exception
+     */
+    private Object getValue(Object o, String name) throws Exception
+    {
+        if (Validator.isNotNull(o) && Validator.isNotEmpty(name))
+        {
+            Class<?> clazz = o.getClass();
+            Field field = clazz.getDeclaredField(name);
+            field.setAccessible(true);
+            o = field.get(o);
+        }
+        return o;
+    }
+
+    /**
+     * 寰楀埌鎵�鏈夊畾涔夊瓧娈�
+     */
+    private void createExcelField()
+    {
+        this.fields = new ArrayList<Object[]>();
+        List<Field> tempFields = new ArrayList<>();
+        tempFields.addAll(Arrays.asList(clazz.getSuperclass().getDeclaredFields()));
+        tempFields.addAll(Arrays.asList(clazz.getDeclaredFields()));
+        for (Field field : tempFields)
+        {
+            // 鍗曟敞瑙�
+            if (field.isAnnotationPresent(Excel.class))
+            {
+                putToField(field, field.getAnnotation(Excel.class));
+            }
+
+            // 澶氭敞瑙�
+            if (field.isAnnotationPresent(Excels.class))
+            {
+                Excels attrs = field.getAnnotation(Excels.class);
+                Excel[] excels = attrs.value();
+                for (Excel excel : excels)
+                {
+                    putToField(field, excel);
+                }
+            }
+        }
+        this.fields = this.fields.stream().sorted(Comparator.comparing(objects -> ((Excel) objects[1]).sort())).collect(Collectors.toList());
+        this.maxHeight = getRowHeight();
+    }
+
+    /**
+     * 鏍规嵁娉ㄨВ鑾峰彇鏈�澶ц楂�
+     */
+    public short getRowHeight()
+    {
+        double maxHeight = 0;
+        for (Object[] os : this.fields)
+        {
+            Excel excel = (Excel) os[1];
+            maxHeight = maxHeight > excel.height() ? maxHeight : excel.height();
+        }
+        return (short) (maxHeight * 20);
+    }
+
+    /**
+     * 鏀惧埌瀛楁闆嗗悎涓�
+     */
+    private void putToField(Field field, Excel attr)
+    {
+        if (attr != null && (attr.type() == Type.ALL || attr.type() == type))
+        {
+            this.fields.add(new Object[] { field, attr });
+        }
+    }
+
+    /**
+     * 鍒涘缓涓�涓伐浣滅翱
+     */
+    public void createWorkbook()
+    {
+        this.wb = new SXSSFWorkbook(500);
+    }
+
+    /**
+     * 鍒涘缓宸ヤ綔琛�
+     *
+     * @param sheetNo sheet鏁伴噺
+     * @param index 搴忓彿
+     */
+    public void createSheet(double sheetNo, int index)
+    {
+        this.sheet = wb.createSheet();
+        this.styles = createStyles(wb);
+        // 璁剧疆宸ヤ綔琛ㄧ殑鍚嶇О.
+        if (sheetNo == 0)
+        {
+            wb.setSheetName(index, sheetName);
+        }
+        else
+        {
+            wb.setSheetName(index, sheetName + index);
+        }
+    }
+
+    /**
+     * 鑾峰彇鍗曞厓鏍煎��
+     *
+     * @param row 鑾峰彇鐨勮
+     * @param column 鑾峰彇鍗曞厓鏍煎垪鍙�
+     * @return 鍗曞厓鏍煎��
+     */
+    public Object getCellValue(Row row, int column)
+    {
+        if (row == null)
+        {
+            return row;
+        }
+        Object val = "";
+        try
+        {
+            Cell cell = row.getCell(column);
+            if (Validator.isNotNull(cell))
+            {
+                if (cell.getCellType() == CellType.NUMERIC || cell.getCellType() == CellType.FORMULA)
+                {
+                    val = cell.getNumericCellValue();
+                    if (DateUtil.isCellDateFormatted(cell))
+                    {
+                        val = DateUtil.getJavaDate((Double) val); // POI Excel 鏃ユ湡鏍煎紡杞崲
+                    }
+                    else
+                    {
+                        if ((Double) val % 1 != 0)
+                        {
+                            val = new BigDecimal(val.toString());
+                        }
+                        else
+                        {
+                            val = new DecimalFormat("0").format(val);
+                        }
+                    }
+                }
+                else if (cell.getCellType() == CellType.STRING)
+                {
+                    val = cell.getStringCellValue();
+                }
+                else if (cell.getCellType() == CellType.BOOLEAN)
+                {
+                    val = cell.getBooleanCellValue();
+                }
+                else if (cell.getCellType() == CellType.ERROR)
+                {
+                    val = cell.getErrorCellValue();
+                }
+
+            }
+        }
+        catch (Exception e)
+        {
+            return val;
+        }
+        return val;
+    }
+}
diff --git a/ruoyi-demo/pom.xml b/ruoyi-demo/pom.xml
index 4904924..d3909a2 100644
--- a/ruoyi-demo/pom.xml
+++ b/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>
 
@@ -25,4 +25,4 @@
 
     </dependencies>
 
-</project>
\ No newline at end of file
+</project>
diff --git a/ruoyi-demo/src/main/java/com/ruoyi/demo/controller/RedisCacheController.java b/ruoyi-demo/src/main/java/com/ruoyi/demo/controller/RedisCacheController.java
new file mode 100644
index 0000000..4588f13
--- /dev/null
+++ b/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娉ㄨВ鐨勬柟娉�,浼氭妸鏂规硶鐨勮繑鍥炲�紁ut鍒扮紦瀛橀噷闈㈢紦瀛樿捣鏉�,渚涘叾瀹冨湴鏂逛娇鐢�
+	 * 瀹冦�岄�氬父鐢ㄥ湪鏂板鏂规硶涓娿��
+	 *
+	 * 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
+	 *
+	 * 浣跨敤浜咰acheEvict娉ㄨВ鐨勬柟娉�,浼氭竻绌烘寚瀹氱紦瀛�
+	 * 銆屼竴鑸敤鍦ㄦ洿鏂版垨鑰呭垹闄ょ殑鏂规硶涓娿��
+	 *
+	 * cacheNames 涓� 閰嶇疆鏂囦欢鍐� groupId
+	 */
+	@CacheEvict(cacheNames = "redissonCacheMap", key = "#key", condition = "#key != null")
+	@GetMapping("/test3")
+	public AjaxResult<String> test3(String key, String value){
+		return AjaxResult.success("鎿嶄綔鎴愬姛", value);
+	}
+
+}
diff --git a/ruoyi-demo/src/main/java/com/ruoyi/demo/feign/fallback/FeignTestFallback.java b/ruoyi-demo/src/main/java/com/ruoyi/demo/feign/fallback/FeignTestFallback.java
index 3b4dfd0..0f9e400 100644
--- a/ruoyi-demo/src/main/java/com/ruoyi/demo/feign/fallback/FeignTestFallback.java
+++ b/ruoyi-demo/src/main/java/com/ruoyi/demo/feign/fallback/FeignTestFallback.java
@@ -7,7 +7,10 @@
 
 /**
  * feign娴嬭瘯fallback
+ * 鑷畾涔夊皝瑁呯粨鏋勪綋鐔旀柇
+ * 闇�閲嶅啓瑙g爜鍣� 鏍规嵁鑷畾涔夊疄浣� 鑷瑙f瀽鐔旀柇
  *
+ * @see {com.ruoyi.framework.config.FeignConfig#errorDecoder()}
  * @author Lion Li
  */
 @Slf4j
diff --git a/ruoyi-extend/pom.xml b/ruoyi-extend/pom.xml
new file mode 100644
index 0000000..e71a426
--- /dev/null
+++ b/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>
diff --git a/ruoyi-extend/ruoyi-monitor-admin/Dockerfile b/ruoyi-extend/ruoyi-monitor-admin/Dockerfile
new file mode 100644
index 0000000..ef551fe
--- /dev/null
+++ b/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"]
diff --git a/ruoyi-extend/ruoyi-monitor-admin/pom.xml b/ruoyi-extend/ruoyi-monitor-admin/pom.xml
new file mode 100644
index 0000000..e9d48d6
--- /dev/null
+++ b/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>
diff --git a/ruoyi-extend/ruoyi-monitor-admin/src/main/java/com/ruoyi/monitor/admin/MonitorAdminApplication.java b/ruoyi-extend/ruoyi-monitor-admin/src/main/java/com/ruoyi/monitor/admin/MonitorAdminApplication.java
new file mode 100644
index 0000000..1d1cbca
--- /dev/null
+++ b/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 鐩戞帶鍚姩鎴愬姛" );
+	}
+
+}
diff --git a/ruoyi-extend/ruoyi-monitor-admin/src/main/java/com/ruoyi/monitor/admin/config/AdminServerConfig.java b/ruoyi-extend/ruoyi-monitor-admin/src/main/java/com/ruoyi/monitor/admin/config/AdminServerConfig.java
new file mode 100644
index 0000000..e2a9c51
--- /dev/null
+++ b/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();
+    }
+
+
+}
diff --git a/ruoyi-extend/ruoyi-monitor-admin/src/main/java/com/ruoyi/monitor/admin/config/SecurityConfig.java b/ruoyi-extend/ruoyi-monitor-admin/src/main/java/com/ruoyi/monitor/admin/config/SecurityConfig.java
new file mode 100644
index 0000000..98834a9
--- /dev/null
+++ b/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鏀寔銆傝繖鏄疭pring Boot Admin Client娉ㄥ唽鎵�蹇呴渶鐨�
+			.httpBasic().and().csrf().disable()
+			.headers().frameOptions().disable();
+	}
+
+}
diff --git a/ruoyi-extend/ruoyi-monitor-admin/src/main/resources/application.yml b/ruoyi-extend/ruoyi-monitor-admin/src/main/resources/application.yml
new file mode 100644
index 0000000..631f3e7
--- /dev/null
+++ b/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
diff --git a/ruoyi-framework/pom.xml b/ruoyi-framework/pom.xml
index 4313ce5..e7ed991 100644
--- a/ruoyi-framework/pom.xml
+++ b/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>
 
diff --git a/ruoyi-framework/src/main/java/com/ruoyi/framework/config/AdminServerConfig.java b/ruoyi-framework/src/main/java/com/ruoyi/framework/config/AdminServerConfig.java
deleted file mode 100644
index 59fed50..0000000
--- a/ruoyi-framework/src/main/java/com/ruoyi/framework/config/AdminServerConfig.java
+++ /dev/null
@@ -1,63 +0,0 @@
-package com.ruoyi.framework.config;
-
-import de.codecentric.boot.admin.server.config.EnableAdminServer;
-import org.springframework.beans.factory.ObjectProvider;
-import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
-import org.springframework.boot.autoconfigure.task.TaskExecutionAutoConfiguration;
-import org.springframework.boot.autoconfigure.thymeleaf.ThymeleafProperties;
-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 org.thymeleaf.dialect.IDialect;
-import org.thymeleaf.spring5.ISpringTemplateEngine;
-import org.thymeleaf.spring5.SpringTemplateEngine;
-import org.thymeleaf.templateresolver.ITemplateResolver;
-
-import java.util.Comparator;
-import java.util.LinkedHashSet;
-import java.util.Set;
-import java.util.concurrent.Executor;
-import java.util.stream.Collectors;
-
-/**
- * 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();
-    }
-
-    /**
-     * 瑙e喅 admin 涓� 椤圭洰 椤甸潰鐨勪氦鍙夊紩鐢� 灏� admin 鐨勮矾鐢辨斁鍒版渶鍚�
-     * @param properties
-     * @param templateResolvers
-     * @param dialects
-     * @return
-     */
-    @Bean
-    @ConditionalOnMissingBean(ISpringTemplateEngine.class)
-    SpringTemplateEngine templateEngine(ThymeleafProperties properties,
-                                        ObjectProvider<ITemplateResolver> templateResolvers, ObjectProvider<IDialect> dialects) {
-        SpringTemplateEngine engine = new SpringTemplateEngine();
-        engine.setEnableSpringELCompiler(properties.isEnableSpringElCompiler());
-        engine.setRenderHiddenMarkersBeforeCheckboxes(properties.isRenderHiddenMarkersBeforeCheckboxes());
-        templateResolvers.orderedStream().forEach(engine::addTemplateResolver);
-        dialects.orderedStream().forEach(engine::addDialect);
-        Set<ITemplateResolver> templateResolvers1 = engine.getTemplateResolvers();
-        templateResolvers1 = templateResolvers1.stream()
-                .sorted(Comparator.comparing(ITemplateResolver::getOrder))
-                .collect(Collectors.toCollection(LinkedHashSet::new));
-        engine.setTemplateResolvers(templateResolvers1);
-        return engine;
-    }
-}
diff --git a/ruoyi-framework/src/main/java/com/ruoyi/framework/config/FeignConfig.java b/ruoyi-framework/src/main/java/com/ruoyi/framework/config/FeignConfig.java
index f10769b..14db1c9 100644
--- a/ruoyi-framework/src/main/java/com/ruoyi/framework/config/FeignConfig.java
+++ b/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();
+//	}
+//
+//
+//	/**
+//	 * 鑷畾涔夎繑鍥炰綋瑙g爜鍣�
+//	 */
+//	@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;
+//		}
+//	}
+
 }
diff --git a/ruoyi-framework/src/main/java/com/ruoyi/framework/config/RedisConfig.java b/ruoyi-framework/src/main/java/com/ruoyi/framework/config/RedisConfig.java
index 95c7572..bc28657 100644
--- a/ruoyi-framework/src/main/java/com/ruoyi/framework/config/RedisConfig.java
+++ b/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);
 	}
 
diff --git a/ruoyi-framework/src/main/java/com/ruoyi/framework/config/SecurityConfig.java b/ruoyi-framework/src/main/java/com/ruoyi/framework/config/SecurityConfig.java
index 79ade78..0a8337e 100644
--- a/ruoyi-framework/src/main/java/com/ruoyi/framework/config/SecurityConfig.java
+++ b/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;
-
     /**
      * 瑙e喅 鏃犳硶鐩存帴娉ㄥ叆 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()
diff --git a/ruoyi-framework/src/main/java/com/ruoyi/framework/config/properties/RedissonProperties.java b/ruoyi-framework/src/main/java/com/ruoyi/framework/config/properties/RedissonProperties.java
index 99db89e..b6e289d 100644
--- a/ruoyi-framework/src/main/java/com/ruoyi/framework/config/properties/RedissonProperties.java
+++ b/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 {
+
+		/**
+		 * 缁刬d
+		 */
+		private String groupId;
+
+		/**
+		 * 缁勮繃鏈熸椂闂�
+		 */
+		private long ttl;
+
+		/**
+		 * 缁勬渶澶х┖闂叉椂闂�
+		 */
+		private long maxIdleTime;
+
+		/**
+		 * 缁勬渶澶ч暱搴�
+		 */
+		private int maxSize;
+
+	}
+
 }
diff --git a/ruoyi-framework/src/main/java/com/ruoyi/framework/mybatisplus/CreateAndUpdateMetaObjectHandler.java b/ruoyi-framework/src/main/java/com/ruoyi/framework/mybatisplus/CreateAndUpdateMetaObjectHandler.java
index 4797a64..2479293 100644
--- a/ruoyi-framework/src/main/java/com/ruoyi/framework/mybatisplus/CreateAndUpdateMetaObjectHandler.java
+++ b/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,30 +18,38 @@
 
 	@Override
 	public void insertFill(MetaObject metaObject) {
-		//鏍规嵁灞炴�у悕瀛楄缃濉厖鐨勫��
-		if (metaObject.hasGetter("createTime")) {
-			if (metaObject.getValue("createTime") == null) {
-				this.setFieldValByName("createTime", new Date(), metaObject);
+		try {
+			//鏍规嵁灞炴�у悕瀛楄缃濉厖鐨勫��
+			if (metaObject.hasGetter("createTime")) {
+				if (metaObject.getValue("createTime") == null) {
+					this.setFieldValByName("createTime", new Date(), metaObject);
+				}
 			}
-		}
-		if (metaObject.hasGetter("createBy")) {
-			if (metaObject.getValue("createBy") == null) {
-				this.setFieldValByName("createBy", SecurityUtils.getUsername(), metaObject);
+			if (metaObject.hasGetter("createBy")) {
+				if (metaObject.getValue("createBy") == null) {
+					this.setFieldValByName("createBy", SecurityUtils.getUsername(), metaObject);
+				}
 			}
+		} catch (Exception e) {
+			throw new CustomException("鑷姩娉ㄥ叆寮傚父 => " + e.getMessage(), HttpStatus.HTTP_UNAUTHORIZED);
 		}
 	}
 
 	@Override
 	public void updateFill(MetaObject metaObject) {
-		if (metaObject.hasGetter("updateBy")) {
-			if (metaObject.getValue("updateBy") == null) {
-				this.setFieldValByName("updateBy", SecurityUtils.getUsername(), metaObject);
+		try {
+			if (metaObject.hasGetter("updateBy")) {
+				if (metaObject.getValue("updateBy") == null) {
+					this.setFieldValByName("updateBy", SecurityUtils.getUsername(), metaObject);
+				}
 			}
-		}
-		if (metaObject.hasGetter("updateTime")) {
-			if (metaObject.getValue("updateTime") == null) {
-				this.setFieldValByName("updateTime", new Date(), metaObject);
+			if (metaObject.hasGetter("updateTime")) {
+				if (metaObject.getValue("updateTime") == null) {
+					this.setFieldValByName("updateTime", new Date(), metaObject);
+				}
 			}
+		} catch (Exception e) {
+			throw new CustomException("鑷姩娉ㄥ叆寮傚父 => " + e.getMessage(), HttpStatus.HTTP_UNAUTHORIZED);
 		}
 	}
 
diff --git a/ruoyi-generator/pom.xml b/ruoyi-generator/pom.xml
index 8073706..642dfec 100644
--- a/ruoyi-generator/pom.xml
+++ b/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>
 
diff --git a/ruoyi-generator/src/main/java/com/ruoyi/generator/service/GenTableServiceImpl.java b/ruoyi-generator/src/main/java/com/ruoyi/generator/service/GenTableServiceImpl.java
index 4da27e8..5fcdd3b 100644
--- a/ruoyi-generator/src/main/java/com/ruoyi/generator/service/GenTableServiceImpl.java
+++ b/ruoyi-generator/src/main/java/com/ruoyi/generator/service/GenTableServiceImpl.java
@@ -182,9 +182,9 @@
                     List<GenTableColumn> genTableColumns = genTableColumnMapper.selectDbTableColumnsByName(tableName);
                     for (GenTableColumn column : genTableColumns) {
                         GenUtils.initColumnField(column, table);
-                        genTableColumnMapper.insert(column);
                     }
-                }
+					genTableColumnMapper.insertAll(genTableColumns);
+				}
             }
         } catch (Exception e) {
             throw new CustomException("瀵煎叆澶辫触锛�" + e.getMessage());
@@ -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)) {
diff --git a/ruoyi-generator/src/main/resources/vm/java/mapper.java.vm b/ruoyi-generator/src/main/resources/vm/java/mapper.java.vm
index 6d4f40f..6f8ff7b 100644
--- a/ruoyi-generator/src/main/resources/vm/java/mapper.java.vm
+++ b/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}> {
 
 }
diff --git a/ruoyi-generator/src/main/resources/vm/vue/index-tree.vue.vm b/ruoyi-generator/src/main/resources/vm/vue/index-tree.vue.vm
index d43c2ce..cedab30 100644
--- a/ruoyi-generator/src/main/resources/vm/vue/index-tree.vue.vm
+++ b/ruoyi-generator/src/main/resources/vm/vue/index-tree.vue.vm
@@ -256,52 +256,16 @@
 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() {
     return {
-	  //鎸夐挳loading
-	  buttonLoading: false,
+      // 鎸夐挳loading
+      buttonLoading: false,
       // 閬僵灞�
       loading: true,
       // 鏄剧ず鎼滅储鏉′欢
@@ -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;
             });
           }
         }
diff --git a/ruoyi-generator/src/main/resources/vm/vue/index.vue.vm b/ruoyi-generator/src/main/resources/vm/vue/index.vue.vm
index abc799e..12a9a63 100644
--- a/ruoyi-generator/src/main/resources/vm/vue/index.vue.vm
+++ b/ruoyi-generator/src/main/resources/vm/vue/index.vue.vm
@@ -308,51 +308,13 @@
 
 <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
-	  buttonLoading: false,
+      // 鎸夐挳loading
+      buttonLoading: false,
       // 閬僵灞�
       loading: true,
       // 瀵煎嚭閬僵灞�
@@ -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;
             });
           }
         }
diff --git a/ruoyi-quartz/pom.xml b/ruoyi-quartz/pom.xml
index cfcbffc..522a84e 100644
--- a/ruoyi-quartz/pom.xml
+++ b/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>
 
diff --git a/ruoyi-system/pom.xml b/ruoyi-system/pom.xml
index 2f5579f..85576db 100644
--- a/ruoyi-system/pom.xml
+++ b/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>
 
diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/domain/vo/MetaVo.java b/ruoyi-system/src/main/java/com/ruoyi/system/domain/vo/MetaVo.java
index e389f5d..495db54 100644
--- a/ruoyi-system/src/main/java/com/ruoyi/system/domain/vo/MetaVo.java
+++ b/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;
 
+	/**
+	 * 鍐呴摼鍦板潃锛坔ttp(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;
+		}
+	}
+
 }
diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/mapper/SysUserMapper.java b/ruoyi-system/src/main/java/com/ruoyi/system/mapper/SysUserMapper.java
index 17b17b0..cba0668 100644
--- a/ruoyi-system/src/main/java/com/ruoyi/system/mapper/SysUserMapper.java
+++ b/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 鐢ㄦ埛鍚�
diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/service/ISysRoleService.java b/ruoyi-system/src/main/java/com/ruoyi/system/service/ISysRoleService.java
index 2f46822..9d02ceb 100644
--- a/ruoyi-system/src/main/java/com/ruoyi/system/service/ISysRoleService.java
+++ b/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);
 }
diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/service/ISysUserService.java b/ruoyi-system/src/main/java/com/ruoyi/system/service/ISysUserService.java
index 4c67091..e277d10 100644
--- a/ruoyi-system/src/main/java/com/ruoyi/system/service/ISysUserService.java
+++ b/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 鐢ㄦ埛淇℃伅
@@ -123,7 +147,7 @@
      * 淇敼鐢ㄦ埛澶村儚
      *
      * @param userName 鐢ㄦ埛鍚�
-     * @param avatar   澶村儚鍦板潃
+     * @param avatar 澶村儚鍦板潃
      * @return 缁撴灉
      */
     public boolean updateUserAvatar(String userName, String avatar);
@@ -164,9 +188,9 @@
     /**
      * 瀵煎叆鐢ㄦ埛鏁版嵁
      *
-     * @param userList        鐢ㄦ埛鏁版嵁鍒楄〃
+     * @param userList 鐢ㄦ埛鏁版嵁鍒楄〃
      * @param isUpdateSupport 鏄惁鏇存柊鏀寔锛屽鏋滃凡瀛樺湪锛屽垯杩涜鏇存柊鏁版嵁
-     * @param operName        鎿嶄綔鐢ㄦ埛
+     * @param operName 鎿嶄綔鐢ㄦ埛
      * @return 缁撴灉
      */
     public String importUser(List<SysUser> userList, Boolean isUpdateSupport, String operName);
diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/SysMenuServiceImpl.java b/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/SysMenuServiceImpl.java
index f8b4706..f23e661 100644
--- a/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/SysMenuServiceImpl.java
+++ b/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,7 +345,9 @@
         String component = UserConstants.LAYOUT;
         if (StrUtil.isNotEmpty(menu.getComponent()) && !isMenuFrame(menu)) {
             component = menu.getComponent();
-        } else if (StrUtil.isEmpty(menu.getComponent()) && isParentView(menu)) {
+		} 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;
         }
         return component;
@@ -345,6 +365,16 @@
     }
 
     /**
+     * 鏄惁涓哄唴閾剧粍浠�
+     *
+     * @param menu 鑿滃崟淇℃伅
+     * @return 缁撴灉
+     */
+    public boolean isInnerLink(SysMenu menu) {
+        return menu.getIsFrame().equals(UserConstants.NO_FRAME) && StringUtils.ishttp(menu.getPath());
+    }
+
+    /**
      * 鏄惁涓簆arent_view缁勪欢
      *
      * @param menu 鑿滃崟淇℃伅
@@ -357,7 +387,7 @@
     /**
      * 鏍规嵁鐖惰妭鐐圭殑ID鑾峰彇鎵�鏈夊瓙鑺傜偣
      *
-     * @param list     鍒嗙被琛�
+     * @param list 鍒嗙被琛�
      * @param parentId 浼犲叆鐨勭埗鑺傜偣ID
      * @return String
      */
diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/SysRoleServiceImpl.java b/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/SysRoleServiceImpl.java
index 1bac225..df024ae 100644
--- a/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/SysRoleServiceImpl.java
+++ b/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);
+    }
 }
diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/SysUserServiceImpl.java b/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/SysUserServiceImpl.java
index 7363040..b5bcb70 100644
--- a/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/SysUserServiceImpl.java
+++ b/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 鐢ㄦ埛淇℃伅
@@ -257,7 +296,7 @@
      * 淇敼鐢ㄦ埛澶村儚
      *
      * @param userName 鐢ㄦ埛鍚�
-     * @param avatar   澶村儚鍦板潃
+     * @param avatar 澶村儚鍦板潃
      * @return 缁撴灉
      */
     @Override
@@ -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
@@ -377,9 +438,9 @@
     /**
      * 瀵煎叆鐢ㄦ埛鏁版嵁
      *
-     * @param userList        鐢ㄦ埛鏁版嵁鍒楄〃
+     * @param userList 鐢ㄦ埛鏁版嵁鍒楄〃
      * @param isUpdateSupport 鏄惁鏇存柊鏀寔锛屽鏋滃凡瀛樺湪锛屽垯杩涜鏇存柊鏁版嵁
-     * @param operName        鎿嶄綔鐢ㄦ埛
+     * @param operName 鎿嶄綔鐢ㄦ埛
      * @return 缁撴灉
      */
     @Override
diff --git a/ruoyi-system/src/main/resources/mapper/system/SysUserMapper.xml b/ruoyi-system/src/main/resources/mapper/system/SysUserMapper.xml
index df59332..5e11278 100644
--- a/ruoyi-system/src/main/resources/mapper/system/SysUserMapper.xml
+++ b/ruoyi-system/src/main/resources/mapper/system/SysUserMapper.xml
@@ -75,9 +75,9 @@
                r.data_scope,
                r.status as role_status
         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
+		    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
     </sql>
 
     <select id="selectPageUserList" parameterType="SysUser" resultMap="SysUserResult">
@@ -142,6 +142,45 @@
         </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>
+
     <select id="selectUserByUserName" parameterType="String" resultMap="SysUserResult">
         <include refid="selectUserVo"/>
         where u.user_name = #{userName}
diff --git a/ruoyi-ui/.env.development b/ruoyi-ui/.env.development
index abb97d4..a1a508d 100644
--- a/ruoyi-ui/.env.development
+++ b/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
diff --git a/ruoyi-ui/.env.production b/ruoyi-ui/.env.production
index 7179b32..b6eec53 100644
--- a/ruoyi-ui/.env.production
+++ b/ruoyi-ui/.env.production
@@ -4,5 +4,8 @@
 # 鐢熶骇鐜閰嶇疆
 ENV = 'production'
 
+# 鐩戞帶鍦板潃
+VUE_APP_MONITRO_ADMIN = '/admin/login'
+
 # 鑻ヤ緷绠$悊绯荤粺/鐢熶骇鐜
 VUE_APP_BASE_API = '/prod-api'
diff --git a/ruoyi-ui/.env.staging b/ruoyi-ui/.env.staging
index b5723d7..e74ce6c 100644
--- a/ruoyi-ui/.env.staging
+++ b/ruoyi-ui/.env.staging
@@ -6,5 +6,8 @@
 # 娴嬭瘯鐜閰嶇疆
 ENV = 'staging'
 
+# 鐩戞帶鍦板潃
+VUE_APP_MONITRO_ADMIN = '/admin/login'
+
 # 鑻ヤ緷绠$悊绯荤粺/娴嬭瘯鐜
 VUE_APP_BASE_API = '/stage-api'
diff --git a/ruoyi-ui/package.json b/ruoyi-ui/package.json
index af8db7e..648b172 100644
--- a/ruoyi-ui/package.json
+++ b/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",
diff --git a/ruoyi-ui/src/api/system/role.js b/ruoyi-ui/src/api/system/role.js
index 463501c..c669ac4 100644
--- a/ruoyi-ui/src/api/system/role.js
+++ b/ruoyi-ui/src/api/system/role.js
@@ -72,4 +72,50 @@
     method: 'get',
     params: query
   })
-}
\ No newline at end of file
+}
+
+
+// 鏌ヨ瑙掕壊宸叉巿鏉冪敤鎴峰垪琛�
+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
+  })
+}
diff --git a/ruoyi-ui/src/api/system/user.js b/ruoyi-ui/src/api/system/user.js
index 3b9a776..37f4eb3 100644
--- a/ruoyi-ui/src/api/system/user.js
+++ b/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
+  })
+}
diff --git a/ruoyi-ui/src/assets/styles/ruoyi.scss b/ruoyi-ui/src/assets/styles/ruoyi.scss
index dee6d09..c4b4365 100644
--- a/ruoyi-ui/src/assets/styles/ruoyi.scss
+++ b/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;
 }
diff --git a/ruoyi-ui/src/components/Editor/index.vue b/ruoyi-ui/src/components/Editor/index.vue
index d63a48d..98b9fa7 100644
--- a/ruoyi-ui/src/components/Editor/index.vue
+++ b/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: {
+    /* 绫诲瀷锛坆ase64鏍煎紡銆乽rl鏍煎紡锛� */
+    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 {
diff --git a/ruoyi-ui/src/components/FileUpload/index.vue b/ruoyi-ui/src/components/FileUpload/index.vue
index 81e2b3a..d47fb98 100644
--- a/ruoyi-ui/src/components/FileUpload/index.vue
+++ b/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,29 +77,34 @@
       fileList: [],
     };
   },
+  watch: {
+    value: {
+      handler(val) {
+        if (val) {
+          let temp = 1;
+          // 棣栧厛灏嗗�艰浆涓烘暟缁�
+          const list = Array.isArray(val) ? val : this.value.split(',');
+          // 鐒跺悗灏嗘暟缁勮浆涓哄璞℃暟缁�
+          this.fileList = list.map(item => {
+            if (typeof item === "string") {
+              item = { name: item, url: item };
+            }
+            item.uid = item.uid || new Date().getTime() + temp++;
+            return item;
+          });
+        } else {
+          this.fileList = [];
+          return [];
+        }
+      },
+      deep: true,
+      immediate: true
+    }
+  },
   computed: {
     // 鏄惁鏄剧ず鎻愮ず
     showTip() {
       return this.isShowTip && (this.fileType || this.fileSize);
-    },
-    // 鍒楄〃
-    list() {
-      let temp = 1;
-      if (this.value) {
-        // 棣栧厛灏嗗�艰浆涓烘暟缁�
-        const list = Array.isArray(this.value) ? this.value : [this.value];
-        // 鐒跺悗灏嗘暟缁勮浆涓哄璞℃暟缁�
-        return list.map((item) => {
-          if (typeof item === "string") {
-            item = { name: item, url: item };
-          }
-          item.uid = item.uid || new Date().getTime() + temp++;
-          return item;
-        });
-      } else {
-        this.fileList = [];
-        return [];
-      }
     },
   },
   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;
+      }
+      return strs != '' ? strs.substr(0, strs.length - 1) : '';
     }
-  },
-  created() {
-    this.fileList = this.list;
-  },
+  }
 };
 </script>
 
diff --git a/ruoyi-ui/src/components/HeaderSearch/index.vue b/ruoyi-ui/src/components/HeaderSearch/index.vue
index ce9f305..c44eff5 100644
--- a/ruoyi-ui/src/components/HeaderSearch/index.vue
+++ b/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):// 璺緞鏂扮獥鍙f墦寮�
-        window.open(val.path, "_blank");
+        const pindex = path.indexOf("http");
+        window.open(path.substr(pindex, path.length), "_blank");
       } else {
         this.$router.push(val.path)
       }
diff --git a/ruoyi-ui/src/components/ImageUpload/index.vue b/ruoyi-ui/src/components/ImageUpload/index.vue
index 17d30d8..f2a7402 100644
--- a/ruoyi-ui/src/components/ImageUpload/index.vue
+++ b/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() {
+    // 涓婁紶鍓峫oading鍔犺浇
+    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();
     },
-  },
-  watch: {},
+    // 棰勮
+    handlePictureCardPreview(file) {
+      this.dialogImageUrl = file.url;
+      this.dialogVisible = true;
+    },
+    // 瀵硅薄杞垚鎸囧畾瀛楃涓插垎闅�
+    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>
+
diff --git a/ruoyi-ui/src/components/TopNav/index.vue b/ruoyi-ui/src/components/TopNav/index.vue
index d89930a..c8837f2 100644
--- a/ruoyi-ui/src/components/TopNav/index.vue
+++ b/ruoyi-ui/src/components/TopNav/index.vue
@@ -73,9 +73,9 @@
             if(router.path === "/") {
               router.children[item].path = "/redirect/" + router.children[item].path;
             } else {
-			  if(!this.ishttp(router.children[item].path)) {
+              if(!this.ishttp(router.children[item].path)) {
                 router.children[item].path = router.path + "/" + router.children[item].path;
-			  }
+              }
             }
             router.children[item].parentPath = router.path;
           }
diff --git a/ruoyi-ui/src/directive/dialog/drag.js b/ruoyi-ui/src/directive/dialog/drag.js
new file mode 100644
index 0000000..728f49d
--- /dev/null
+++ b/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 姝e垯鍖归厤鏇挎崲
+      let styL, styT;
+
+      // 娉ㄦ剰鍦╥e涓� 绗竴娆¤幏鍙栧埌鐨勫�间负缁勪欢鑷甫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;
+      };
+    }
+  }
+};
\ No newline at end of file
diff --git a/ruoyi-ui/src/directive/index.js b/ruoyi-ui/src/directive/index.js
new file mode 100644
index 0000000..550109b
--- /dev/null
+++ b/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
diff --git a/ruoyi-ui/src/directive/permission/hasPermi.js b/ruoyi-ui/src/directive/permission/hasPermi.js
index d7107ce..7101e3e 100644
--- a/ruoyi-ui/src/directive/permission/hasPermi.js
+++ b/ruoyi-ui/src/directive/permission/hasPermi.js
@@ -1,8 +1,8 @@
  /**
- * 鎿嶄綔鏉冮檺澶勭悊
+ * v-hasPermi 鎿嶄綔鏉冮檺澶勭悊
  * Copyright (c) 2019 ruoyi
  */
- 
+
 import store from '@/store'
 
 export default {
diff --git a/ruoyi-ui/src/directive/permission/hasRole.js b/ruoyi-ui/src/directive/permission/hasRole.js
index 1303809..ad9d4d7 100644
--- a/ruoyi-ui/src/directive/permission/hasRole.js
+++ b/ruoyi-ui/src/directive/permission/hasRole.js
@@ -1,8 +1,8 @@
  /**
- * 瑙掕壊鏉冮檺澶勭悊
+ * v-hasRole 瑙掕壊鏉冮檺澶勭悊
  * Copyright (c) 2019 ruoyi
  */
- 
+
 import store from '@/store'
 
 export default {
diff --git a/ruoyi-ui/src/directive/permission/index.js b/ruoyi-ui/src/directive/permission/index.js
deleted file mode 100644
index e3d76d3..0000000
--- a/ruoyi-ui/src/directive/permission/index.js
+++ /dev/null
@@ -1,15 +0,0 @@
-import hasRole from './hasRole'
-import hasPermi from './hasPermi'
-
-const install = function(Vue) {
-  Vue.directive('hasRole', hasRole)
-  Vue.directive('hasPermi', hasPermi)
-}
-
-if (window.Vue) {
-  window['hasRole'] = hasRole
-  window['hasPermi'] = hasPermi
-  Vue.use(install); // eslint-disable-line
-}
-
-export default install
diff --git a/ruoyi-ui/src/layout/components/AppMain.vue b/ruoyi-ui/src/layout/components/AppMain.vue
index a897638..0c6f4b7 100644
--- a/ruoyi-ui/src/layout/components/AppMain.vue
+++ b/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>
diff --git a/ruoyi-ui/src/layout/components/InnerLink/index.vue b/ruoyi-ui/src/layout/components/InnerLink/index.vue
new file mode 100644
index 0000000..227ff2a
--- /dev/null
+++ b/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>
diff --git a/ruoyi-ui/src/main.js b/ruoyi-ui/src/main.js
index d1f8973..d07dead 100644
--- a/ruoyi-ui/src/main.js
+++ b/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)
 
 /**
diff --git a/ruoyi-ui/src/router/index.js b/ruoyi-ui/src/router/index.js
index ffb12e7..c7b9371 100644
--- a/ruoyi-ui/src/router/index.js
+++ b/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,
diff --git a/ruoyi-ui/src/store/modules/permission.js b/ruoyi-ui/src/store/modules/permission.js
index aacfc8c..81026f3 100644
--- a/ruoyi-ui/src/store/modules/permission.js
+++ b/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)
       }
diff --git a/ruoyi-ui/src/views/demo/demo/index.vue b/ruoyi-ui/src/views/demo/demo/index.vue
index 279c469..a6679f3 100644
--- a/ruoyi-ui/src/views/demo/demo/index.vue
+++ b/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;
             });
           }
         }
diff --git a/ruoyi-ui/src/views/demo/tree/index.vue b/ruoyi-ui/src/views/demo/tree/index.vue
index afe96c4..57b152f 100644
--- a/ruoyi-ui/src/views/demo/tree/index.vue
+++ b/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;
             });
           }
         }
diff --git a/ruoyi-ui/src/views/index.vue b/ruoyi-ui/src/views/index.vue
index dba6dfe..671dcde 100644
--- a/ruoyi-ui/src/views/index.vue
+++ b/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 鐔旀柇 鑷畾涔夌粨鏋勪綋瑙f瀽鏂规硶 涓� 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 浼樺寲浠g爜鐢熸垚鍣� 鎵归噺瀵煎叆</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灞炴�ug淇銆�</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>
diff --git a/ruoyi-ui/src/views/monitor/admin/index.vue b/ruoyi-ui/src/views/monitor/admin/index.vue
index f1d48b0..ad35dc4 100644
--- a/ruoyi-ui/src/views/monitor/admin/index.vue
+++ b/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>
diff --git a/ruoyi-ui/src/views/system/notice/index.vue b/ruoyi-ui/src/views/system/notice/index.vue
index 3115543..0ffd9d6 100644
--- a/ruoyi-ui/src/views/system/notice/index.vue
+++ b/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 {
       // 閬僵灞�
diff --git a/ruoyi-ui/src/views/system/role/authUser.vue b/ruoyi-ui/src/views/system/role/authUser.vue
new file mode 100644
index 0000000..a65ccbf
--- /dev/null
+++ b/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>
\ No newline at end of file
diff --git a/ruoyi-ui/src/views/system/role/index.vue b/ruoyi-ui/src/views/system/role/index.vue
index 081ea81..5bedd49 100644
--- a/ruoyi-ui/src/views/system/role/index.vue
+++ b/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 => {
diff --git a/ruoyi-ui/src/views/system/role/selectUser.vue b/ruoyi-ui/src/views/system/role/selectUser.vue
new file mode 100644
index 0000000..14ae0bb
--- /dev/null
+++ b/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>
diff --git a/ruoyi-ui/src/views/system/user/authRole.vue b/ruoyi-ui/src/views/system/user/authRole.vue
new file mode 100644
index 0000000..8e7f82a
--- /dev/null
+++ b/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>
diff --git a/ruoyi-ui/src/views/system/user/index.vue b/ruoyi-ui/src/views/system/user/index.vue
index 471e66f..605cf6e 100644
--- a/ruoyi-ui/src/views/system/user/index.vue
+++ b/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 => {
diff --git a/ruoyi-ui/vue.config.js b/ruoyi-ui/vue.config.js
index c749c8c..ee6353c 100644
--- a/ruoyi-ui/vue.config.js
+++ b/ruoyi-ui/vue.config.js
@@ -109,7 +109,7 @@
           config.optimization.runtimeChunk('single'),
           {
              from: path.resolve(__dirname, './public/robots.txt'), //闃茬埇铏枃浠�
-             to: './', //鍒版牴鐩綍涓�
+             to: './' //鍒版牴鐩綍涓�
           }
         }
       )
diff --git a/sql/ry_20210210.sql b/sql/ry_20210210.sql
index 1d8ec36..59a55f1 100644
--- a/sql/ry_20210210.sql
+++ b/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, '瑙掕壊绠$悊鑿滃崟');
@@ -685,4 +685,4 @@
   update_by         varchar(64)     default ''                 comment '鏇存柊鑰�',
   update_time       datetime                                   comment '鏇存柊鏃堕棿',
   primary key (column_id)
-) engine=innodb auto_increment=1 comment = '浠g爜鐢熸垚涓氬姟琛ㄥ瓧娈�';
\ No newline at end of file
+) engine=innodb auto_increment=1 comment = '浠g爜鐢熸垚涓氬姟琛ㄥ瓧娈�';

--
Gitblit v1.9.3