diff --git a/README.md b/README.md index 688da088b..a56dc6c4a 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)
-[![RuoYi-Vue-Plus](https://img.shields.io/badge/RuoYi_Vue_Plus-2.4.0-success.svg)](https://gitee.com/JavaLionLi/RuoYi-Vue-Plus) +[![RuoYi-Vue-Plus](https://img.shields.io/badge/RuoYi_Vue_Plus-2.5.0-success.svg)](https://gitee.com/JavaLionLi/RuoYi-Vue-Plus) [![Spring Boot](https://img.shields.io/badge/Spring%20Boot-2.4-blue.svg)]() [![JDK-8+](https://img.shields.io/badge/JDK-8+-green.svg)]() [![JDK-11](https://img.shields.io/badge/JDK-11-green.svg)]() -基于 RuoYi-Vue 集成 Mybatis-Plus Lombok Hutool 等便捷开发工具 适配重写相关业务 便于开发 定期与 RuoYi-Vue 同步 +RuoYi-Vue-Plus 是基于 RuoYi-Vue 针对 `分布式集群` 场景升级 定期与 RuoYi-Vue 同步 + +集成 Lock4j dynamic-datasource 等分布式场景解决方案 + +集成 Mybatis-Plus Lombok Hutool 等便捷开发工具 适配重写相关业务 便于开发 + * 前端开发框架 Vue、Element UI * 后端开发框架 Spring Boot、Redis * 容器框架 Undertow 基于 Netty 的高性能容器 @@ -27,6 +32,7 @@ * 多数据源框架 dynamic-datasource 支持主从与多种类数据库异构 * Redis客户端 采用 Redisson 性能更强 * 分布式锁 Lock4j 注解锁、工具锁 多种多样 +* 部署方式 Docker 容器编排 一键部署业务集群 ## 参考文档 @@ -34,6 +40,8 @@
>[初始化项目 必看](https://gitee.com/JavaLionLi/RuoYi-Vue-Plus/wikis/关于初始化项目?sort_id=4164117) > +>[部署项目 必看](https://gitee.com/JavaLionLi/RuoYi-Vue-Plus/wikis/关于应用部署?sort_id=4219382) +> >[参考文档 Wiki](https://gitee.com/JavaLionLi/RuoYi-Vue-Plus/wikis/pages) ## 提问四部曲 @@ -58,6 +66,12 @@ ### 四、加群 以上三点已经能解决大家绝大部分问题了,如果还有问题没能通过这几种方式解决,那么加群,大家一起在群里探讨一下 +## 贡献代码 + +欢迎各路英雄豪杰 `PR` 代码 请提交到 `dev` 开发分支 统一测试发版 + +框架定位为 `通用后台管理系统(分布式集群强化)` 原则上不接受业务 `PR` + ## 修改RuoYi功能 ### 依赖改动 @@ -74,6 +88,7 @@ * 移除 fastjson 统一使用 jackson 序列化 * 集成 dynamic-datasource 多数据源(默认支持MySQL,其他种类需自行适配) * 集成 Lock4j 实现分布式 注解锁、工具锁 多种多样 +* 增加 Docker 容器编排 打包插件与部署脚本 ### 代码改动 @@ -90,14 +105,13 @@ ### 其他 -* 同步升级 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/) -## 关注作者(扫码请备注: "加群") - -![作者图片](https://images.gitee.com/uploads/images/2021/0508/235345_5503356a_1766278.jpeg) +## 扫码加群 一起交流 +![输入图片说明](https://images.gitee.com/uploads/images/2021/0625/160026_11d949aa_1766278.jpeg "07f7121fab14e57e03e5f6a35eff6ce.jpg") ## 捐献作者 作者为兼职做开源,平时还需要工作,如果帮到了您可以请作者吃个盒饭 diff --git a/docker/deploy.sh b/docker/deploy.sh new file mode 100644 index 000000000..3b6e69654 --- /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 000000000..d40ae29e7 --- /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: + # 将mysql8.0默认密码策略 修改为 原先 策略 (mysql8.0对其默认策略做了更改 会导致密码无法匹配) + --default-authentication-plugin=mysql_native_password + --character-set-server=utf8mb4 + --collation-server=utf8mb4_general_ci + --explicit_defaults_for_timestamp=true + --lower_case_table_names=1 + privileged: true + restart: always + networks: + ruoyi_net: + ipv4_address: 172.30.0.36 + + nginx-web: + # 如果需要指定版本 就把 latest 换成版本号 + image: nginx:latest + container_name: nginx-web + ports: + - 80:80 + - 443:443 + volumes: + # 证书映射 + - /docker/nginx/cert:/etc/nginx/cert + # 配置文件映射 + - /docker/nginx/conf/nginx.conf:/etc/nginx/nginx.conf + # 页面目录 + - /docker/nginx/html:/usr/share/nginx/html + # 日志目录 + - /docker/nginx/log:/var/log/nginx + # 主机本机时间文件映射 与本机时间同步 + - /etc/localtime:/etc/localtime:ro + privileged: true + restart: always + networks: + - ruoyi_net + + redis: + image: redis:6.2.1 + container_name: redis + ports: + - 6379:6379 + environment: + # 设置环境变量 时区上海 编码UTF-8 + TZ: Asia/Shanghai + LANG: en_US.UTF-8 + volumes: + # 配置文件 + - /docker/redis/conf/redis.conf:/redis.conf:rw + # 数据文件 + - /docker/redis/data:/data:rw + command: "redis-server --appendonly yes" + privileged: true + restart: always + networks: + ruoyi_net: + ipv4_address: 172.30.0.48 + + ruoyi-server1: + image: "ruoyi/ruoyi-server:2.5.0" + environment: + - TZ=Asia/Shanghai + volumes: + # 配置文件 + - /docker/server1/logs/:/ruoyi/server/logs/ + privileged: true + restart: always + networks: + ruoyi_net: + ipv4_address: 172.30.0.60 + + ruoyi-server2: + image: "ruoyi/ruoyi-server:2.5.0" + environment: + - TZ=Asia/Shanghai + volumes: + # 配置文件 + - /docker/server2/logs/:/ruoyi/server/logs/ + privileged: true + restart: always + networks: + ruoyi_net: + ipv4_address: 172.30.0.61 + + ruoyi-monitor-admin: + image: "ruoyi/ruoyi-monitor-admin:2.5.0" + environment: + - TZ=Asia/Shanghai + privileged: true + restart: always + networks: + ruoyi_net: + ipv4_address: 172.30.0.90 + +networks: + ruoyi_net: + driver: bridge + ipam: + config: + - subnet: 172.30.0.0/16 diff --git a/docker/nginx/nginx.conf b/docker/nginx/nginx.conf new file mode 100644 index 000000000..66ac29e3e --- /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/ 为docker映射路径 不允许更改 + #ssl_certificate_key /etc/nginx/cert/xxx.local.key; # /etc/nginx/cert/ 为docker映射路径 不允许更改 + #ssl_session_timeout 5m; + #ssl_ciphers ECDHE-RSA-AES128-GCM-SHA256:ECDHE:ECDH:AES:HIGH:!NULL:!aNULL:!MD5:!ADH:!RC4; + #ssl_protocols TLSv1 TLSv1.1 TLSv1.2; + #ssl_prefer_server_ciphers on; + # https配置参考 end + + location / { + root /usr/share/nginx/html; + try_files $uri $uri/ /index.html; + index index.html index.htm; + } + + location /prod-api/ { + proxy_set_header Host $http_host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header REMOTE-HOST $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_pass http://server/; + } + + location /admin/ { + proxy_set_header Host $http_host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header REMOTE-HOST $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_pass http://monitor-admin/admin/; + } + + error_page 500 502 503 504 /50x.html; + location = /50x.html { + root html; + } + } +} diff --git a/pom.xml b/pom.xml new file mode 100644 index 000000000..2029dc442 --- /dev/null +++ b/pom.xml @@ -0,0 +1,280 @@ + + + 4.0.0 + + com.ruoyi + ruoyi-vue-plus + 2.5.0 + + RuoYi-Vue-Plus + https://gitee.com/JavaLionLi/RuoYi-Vue-Plus + RuoYi-Vue-Plus后台管理系统 + + + 2.5.0 + 2.4.8 + UTF-8 + UTF-8 + 1.8 + 3.1.1 + 1.2.6 + 3.0.3 + 4.1.2 + 1.7 + 0.9.1 + 3.4.3 + 5.7.4 + 3.0.3 + 11.0 + 2.4.3 + 3.16.0 + 2.2.1 + 3.4.0 + + + localhost + http://${docker.registry.url}:2375 + ruoyi + 1.2.0 + + + + + + + + + org.springframework.boot + spring-boot-dependencies + ${spring-boot.version} + pom + import + + + + + com.alibaba + druid-spring-boot-starter + ${druid.version} + + + + com.github.xiaoymin + knife4j-spring-boot-starter + ${knife4j.version} + + + + + org.apache.poi + poi-ooxml + ${poi.version} + + + + + org.apache.velocity + velocity + ${velocity.version} + + + + + io.jsonwebtoken + jjwt + ${jwt.version} + + + + + com.baomidou + dynamic-datasource-spring-boot-starter + ${datasource.version} + + + + com.baomidou + mybatis-plus-boot-starter + ${mybatis-plus.version} + + + com.baomidou + mybatis-plus-extension + ${mybatis-plus.version} + + + + cn.hutool + hutool-all + ${hutool.version} + + + + org.springframework.cloud + spring-cloud-starter-openfeign + ${feign.version} + + + + io.github.openfeign + feign-okhttp + ${feign-okhttp.version} + + + + de.codecentric + spring-boot-admin-starter-server + ${spring-boot-admin.version} + + + de.codecentric + spring-boot-admin-starter-client + ${spring-boot-admin.version} + + + + + org.redisson + redisson-spring-boot-starter + ${redisson.version} + + + com.baomidou + lock4j-redisson-spring-boot-starter + ${lock4j.version} + + + + + + + ruoyi + ruoyi-extend + + pom + + + + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.1 + + ${java.version} + ${java.version} + ${project.build.sourceEncoding} + + + + + + + src/main/resources + + true + + + + + + + public + aliyun nexus + http://maven.aliyun.com/nexus/content/groups/public/ + + true + + + + + + + public + aliyun nexus + http://maven.aliyun.com/nexus/content/groups/public/ + + true + + + false + + + + + + + local + + + local + debug + + + + dev + + + dev + debug + + + + true + + + + prod + + prod + warn + + + + + + jdk8 + + true + 1.8 + + + 1.8 + + + + jdk11 + + 11 + + + 11 + 3.0.1 + + + + + + com.sun.xml.bind + jaxb-impl + ${jaxb.version} + + + + + + + com.sun.xml.bind + jaxb-impl + + + + + + diff --git a/ruoyi-extend/pom.xml b/ruoyi-extend/pom.xml new file mode 100644 index 000000000..e71a426cc --- /dev/null +++ b/ruoyi-extend/pom.xml @@ -0,0 +1,18 @@ + + + + ruoyi-vue-plus + com.ruoyi + 2.5.0 + + 4.0.0 + ruoyi-extend + pom + + + ruoyi-monitor-admin + + + diff --git a/ruoyi-extend/ruoyi-monitor-admin/Dockerfile b/ruoyi-extend/ruoyi-monitor-admin/Dockerfile new file mode 100644 index 000000000..ef551fec3 --- /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 000000000..e9d48d665 --- /dev/null +++ b/ruoyi-extend/ruoyi-monitor-admin/pom.xml @@ -0,0 +1,73 @@ + + + + ruoyi-extend + com.ruoyi + 2.5.0 + + 4.0.0 + jar + ruoyi-monitor-admin + + + + + org.springframework.boot + spring-boot-starter-web + + + + + org.springframework.boot + spring-boot-starter-security + + + + de.codecentric + spring-boot-admin-starter-server + + + + + ${project.artifactId} + + + org.springframework.boot + spring-boot-maven-plugin + ${spring-boot.version} + + true + + + + + repackage + + + + + + com.spotify + docker-maven-plugin + ${docker.plugin.version} + + ${docker.namespace}/${project.artifactId}:${project.version} + ${project.basedir} + ${docker.registry.host} + ${docker.registry.url} + ${docker.registry.url} + + + / + ${project.build.directory} + ${project.build.finalName}.jar + + + + + + + + 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 000000000..1d1cbca58 --- /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 000000000..e2a9c51de --- /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 000000000..98834a9bc --- /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支持。这是Spring 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 000000000..631f3e772 --- /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-ui/.env.development b/ruoyi-ui/.env.development index abb97d464..a1a508de4 100644 --- a/ruoyi-ui/.env.development +++ b/ruoyi-ui/.env.development @@ -7,5 +7,8 @@ ENV = 'development' # 若依管理系统/开发环境 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 7179b3291..b6eec53c6 100644 --- a/ruoyi-ui/.env.production +++ b/ruoyi-ui/.env.production @@ -4,5 +4,8 @@ VUE_APP_TITLE = RuoYi-Vue-Plus后台管理系统 # 生产环境配置 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 b5723d7b8..e74ce6ce2 100644 --- a/ruoyi-ui/.env.staging +++ b/ruoyi-ui/.env.staging @@ -6,5 +6,8 @@ NODE_ENV = production # 测试环境配置 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 af8db7e87..648b17292 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 463501ce8..c669ac43a 100644 --- a/ruoyi-ui/src/api/system/role.js +++ b/ruoyi-ui/src/api/system/role.js @@ -72,4 +72,50 @@ export function exportRole(query) { 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 3b9a776a7..37f4eb338 100644 --- a/ruoyi-ui/src/api/system/user.js +++ b/ruoyi-ui/src/api/system/user.js @@ -125,3 +125,20 @@ export function importTemplate() { 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 dee6d091c..c4b43658f 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 d63a48d12..98b9fa79b 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'" >
@@ -46,14 +46,15 @@ export default { type: Boolean, default: false, }, - /* 上传地址 */ - uploadUrl: { + /* 类型(base64格式、url格式) */ + type: { type: String, - default: "", + default: "url", } }, data() { return { + uploadUrl: process.env.VUE_APP_BASE_API + "/common/upload", // 上传的图片服务器地址 headers: { Authorization: "Bearer " + getToken() }, @@ -119,7 +120,7 @@ export default { 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 @@ export default { // 获取光标所在位置 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 3c3d17bf0..d47fb98f3 100644 --- a/ruoyi-ui/src/components/FileUpload/index.vue +++ b/ruoyi-ui/src/components/FileUpload/index.vue @@ -1,179 +1,198 @@ - - - - - + + + + + diff --git a/ruoyi-ui/src/components/HeaderSearch/index.vue b/ruoyi-ui/src/components/HeaderSearch/index.vue index ce9f305f9..c44eff56e 100644 --- a/ruoyi-ui/src/components/HeaderSearch/index.vue +++ b/ruoyi-ui/src/components/HeaderSearch/index.vue @@ -70,9 +70,11 @@ export default { this.show = false }, change(val) { + const path = val.path; if(this.ishttp(val.path)) { // http(s):// 路径新窗口打开 - window.open(val.path, "_blank"); + const pindex = path.indexOf("http"); + window.open(path.substr(pindex, path.length), "_blank"); } else { this.$router.push(val.path) } diff --git a/ruoyi-ui/src/components/ImageUpload/index.vue b/ruoyi-ui/src/components/ImageUpload/index.vue index 995b27794..f2a7402b3 100644 --- a/ruoyi-ui/src/components/ImageUpload/index.vue +++ b/ruoyi-ui/src/components/ImageUpload/index.vue @@ -1,100 +1,210 @@ - - - - - + + + + + diff --git a/ruoyi-ui/src/components/TopNav/index.vue b/ruoyi-ui/src/components/TopNav/index.vue index d89930a88..c8837f2a0 100644 --- a/ruoyi-ui/src/components/TopNav/index.vue +++ b/ruoyi-ui/src/components/TopNav/index.vue @@ -73,9 +73,9 @@ export default { 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 000000000..2e823465e --- /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 正则匹配替换 + let styL, styT; + + // 注意在ie中 第一次获取到的值为组件自带50% 移动之后赋值为px + if (sty.left.includes('%')) { + styL = +document.body.clientWidth * (+sty.left.replace(/\%/g, '') / 100); + styT = +document.body.clientHeight * (+sty.top.replace(/\%/g, '') / 100); + } else { + styL = +sty.left.replace(/\px/g, ''); + styT = +sty.top.replace(/\px/g, ''); + }; + + // 鼠标拖拽事件 + document.onmousemove = function (e) { + // 通过事件委托,计算移动的距离 (开始拖拽至结束拖拽的距离) + const l = e.clientX - disX; + const t = e.clientY - disY; + + let finallyL = l + styL + let finallyT = t + styT + + // 移动当前元素 + dragDom.style.left = `${finallyL}px`; + dragDom.style.top = `${finallyT}px`; + + }; + + document.onmouseup = function (e) { + document.onmousemove = null; + document.onmouseup = null; + }; + } + } +}; \ 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 000000000..550109b4d --- /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 d7107cec0..7101e3e83 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 13038099d..ad9d4d79d 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/layout/components/AppMain.vue b/ruoyi-ui/src/layout/components/AppMain.vue index a89763806..0c6f4b784 100644 --- a/ruoyi-ui/src/layout/components/AppMain.vue +++ b/ruoyi-ui/src/layout/components/AppMain.vue @@ -51,7 +51,7 @@ export default { // fix css style bug in open el-dialog .el-popup-parent--hidden { .fixed-header { - padding-right: 15px; + padding-right: 17px; } } 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 000000000..a45d1a4a4 --- /dev/null +++ b/ruoyi-ui/src/layout/components/InnerLink/index.vue @@ -0,0 +1,27 @@ + diff --git a/ruoyi-ui/src/main.js b/ruoyi-ui/src/main.js index d1f8973b7..d07dead41 100644 --- a/ruoyi-ui/src/main.js +++ b/ruoyi-ui/src/main.js @@ -10,7 +10,7 @@ import '@/assets/styles/ruoyi.scss' // ruoyi css 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 { parseTime, resetForm, addDateRange, selectDictLabel, selectDictLabels, 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.prototype.msgInfo = function (msg) { 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 ffb12e7d5..c7b9371b5 100644 --- a/ruoyi-ui/src/router/index.js +++ b/ruoyi-ui/src/router/index.js @@ -6,6 +6,7 @@ Vue.use(Router) /* Layout */ import Layout from '@/layout' import ParentView from '@/components/ParentView'; +import InnerLink from '@/layout/components/InnerLink' /** * Note: 路由配置项 @@ -80,6 +81,32 @@ export const constantRoutes = [ } ] }, + { + 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, diff --git a/ruoyi-ui/src/store/modules/permission.js b/ruoyi-ui/src/store/modules/permission.js index aacfc8c93..81026f367 100644 --- a/ruoyi-ui/src/store/modules/permission.js +++ b/ruoyi-ui/src/store/modules/permission.js @@ -2,6 +2,7 @@ import { constantRoutes } from '@/router' 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 @@ function filterAsyncRouter(asyncRouterMap, lastRouter = false, type = false) { 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 279c4693c..a6679f390 100644 --- a/ruoyi-ui/src/views/demo/demo/index.vue +++ b/ruoyi-ui/src/views/demo/demo/index.vue @@ -304,17 +304,19 @@ export default { 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 afe96c4ae..57b152fc9 100644 --- a/ruoyi-ui/src/views/demo/tree/index.vue +++ b/ruoyi-ui/src/views/demo/tree/index.vue @@ -255,17 +255,19 @@ export default { 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 dba6dfe8b..671dcde2b 100644 --- a/ruoyi-ui/src/views/index.vue +++ b/ruoyi-ui/src/views/index.vue @@ -91,6 +91,32 @@ 更新日志 + +
    +
  1. update springboot 2.4.7 => 2.4.8
  2. +
  3. update knife4j 3.0.2 => 3.0.3
  4. +
  5. update hutool 5.7.2 => 5.7.4
  6. +
  7. update spring-boot-admin 2.4.1 => 2.4.3
  8. +
  9. update redisson 3.15.2 => 3.16.0
  10. +
  11. add 增加 docker 编排 与 shell 脚本
  12. +
  13. add 增加 feign 熔断 自定义结构体解析方法 与 demo 注释
  14. +
  15. add 用户管理新增分配角色功能
  16. +
  17. add 角色管理新增分配用户功能
  18. +
  19. add 增加spring-cache演示案例
  20. +
  21. update 独立 springboot-admin 监控到扩展模块项目
  22. +
  23. update springboot-admin 监控 增加用户登录权限管理
  24. +
  25. update 优化代码生成器 批量导入
  26. +
  27. update 优化 增加MP注入异常拦截
  28. +
  29. update 关闭默认二级缓存 推荐使用 spring-cache 注解手动缓存
  30. +
  31. update FileUpload ImageUpload组件 支持多图片上传
  32. +
  33. update 优化中英文语言配置
  34. +
  35. update 规范maven写法
  36. +
  37. fix redis获取map属性bug修复。
  38. +
  39. fix 修复 按钮loading 后端500卡死问题
  40. +
  41. fix 相对路径下载问题
  42. +
  43. fix 修复 hutool 工具返回结果不一致问题
  44. +
+
  1. update springboot 2.3.11 => 2.4.7
  2. diff --git a/ruoyi-ui/src/views/monitor/admin/index.vue b/ruoyi-ui/src/views/monitor/admin/index.vue index f1d48b0ac..ad35dc463 100644 --- a/ruoyi-ui/src/views/monitor/admin/index.vue +++ b/ruoyi-ui/src/views/monitor/admin/index.vue @@ -1,26 +1,16 @@