README.md
@@ -4,12 +4,17 @@ [](https://gitee.com/JavaLionLi/RuoYi-Vue-Plus/blob/master/LICENSE) [](https://www.jetbrains.com/?from=RuoYi-Vue-Plus) <br> [](https://gitee.com/JavaLionLi/RuoYi-Vue-Plus) [](https://gitee.com/JavaLionLi/RuoYi-Vue-Plus) []() []() []() åºäº RuoYi-Vue éæ Mybatis-Plus Lombok Hutool ç便æ·å¼åå·¥å · éé éåç¸å ³ä¸å¡ 便äºå¼å å®æä¸ RuoYi-Vue 忥 RuoYi-Vue-Plus æ¯åºäº RuoYi-Vue é对 `åå¸å¼é群` åºæ¯å级 å®æä¸ RuoYi-Vue 忥 éæ Lock4j dynamic-datasource çåå¸å¼åºæ¯è§£å³æ¹æ¡ éæ Mybatis-Plus Lombok Hutool ç便æ·å¼åå·¥å · éé éåç¸å ³ä¸å¡ 便äºå¼å * å端å¼åæ¡æ¶ VueãElement UI * å端å¼åæ¡æ¶ Spring BootãRedis * 容卿¡æ¶ Undertow åºäº Netty ç髿§è½å®¹å¨ @@ -27,12 +32,15 @@ * 夿°æ®æºæ¡æ¶ dynamic-datasource æ¯æä¸»ä»ä¸å¤ç§ç±»æ°æ®åºå¼æ * Redis客æ·ç«¯ éç¨ Redisson æ§è½æ´å¼º * åå¸å¼é Lock4j 注解éãå·¥å ·é å¤ç§å¤æ · * é¨ç½²æ¹å¼ Docker 容å¨ç¼æ ä¸é®é¨ç½²ä¸å¡é群 ## åèææ¡£ ä½¿ç¨æ¡æ¶å请ä»ç»é è¯»ææ¡£éç¹æ³¨æäºé¡¹ <br> >[åå§åé¡¹ç® å¿ ç](https://gitee.com/JavaLionLi/RuoYi-Vue-Plus/wikis/å ³äºåå§å项ç®?sort_id=4164117) > >[é¨ç½²é¡¹ç® å¿ ç](https://gitee.com/JavaLionLi/RuoYi-Vue-Plus/wikis/å ³äºåºç¨é¨ç½²?sort_id=4219382) > >[åèææ¡£ Wiki](https://gitee.com/JavaLionLi/RuoYi-Vue-Plus/wikis/pages) @@ -58,6 +66,12 @@ ### åãå 群 以ä¸ä¸ç¹å·²ç»è½è§£å³å¤§å®¶ç»å¤§é¨åé®é¢äºï¼å¦æè¿æé®é¢æ²¡è½éè¿è¿å ç§æ¹å¼è§£å³ï¼é£ä¹å 群ï¼å¤§å®¶ä¸èµ·å¨ç¾¤éæ¢è®¨ä¸ä¸ ## è´¡ç®ä»£ç 欢è¿åè·¯è±éè±ªæ° `PR` 代ç 请æäº¤å° `dev` å¼å忝 ç»ä¸æµè¯åç æ¡æ¶å®ä½ä¸º `éç¨åå°ç®¡çç³»ç»(åå¸å¼é群强å)` ååä¸ä¸æ¥åä¸å¡ `PR` ## ä¿®æ¹RuoYiåè½ ### ä¾èµæ¹å¨ @@ -74,6 +88,7 @@ * ç§»é¤ fastjson ç»ä¸ä½¿ç¨ jackson åºåå * éæ dynamic-datasource 夿°æ®æº(é»è®¤æ¯æMySQL,å ¶ä»ç§ç±»éèªè¡éé ) * éæ Lock4j å®ç°åå¸å¼ 注解éãå·¥å ·é å¤ç§å¤æ · * å¢å Docker 容å¨ç¼æ æå æä»¶ä¸é¨ç½²èæ¬ ### ä»£ç æ¹å¨ @@ -90,7 +105,7 @@ ### å ¶ä» * 忥å级 RuoYi-Vue 3.5.0 * 忥å级 RuoYi-Vue * GitHub å°å [RuoYi-Vue-Plus-github](https://github.com/JavaLionLi/RuoYi-Vue-Plus) * 忍¡å fast 忝 [RuoYi-Vue-Plus-fast](https://gitee.com/JavaLionLi/RuoYi-Vue-Plus/tree/fast/) * Oracle 模å oracle 忝 [RuoYi-Vue-Plus-oracle](https://gitee.com/JavaLionLi/RuoYi-Vue-Plus/tree/oracle/) docker/deploy.sh
¶Ô±ÈÐÂÎļþ @@ -0,0 +1,92 @@ #!/bin/bash #使ç¨è¯´æï¼ç¨æ¥æç¤ºè¾å ¥åæ° usage() { echo "Usage: sh æ§è¡èæ¬.sh [port|mount|monitor|base|start|stop|stopall|rm|rmiNoneTag]" exit 1 } #å¼å¯æéç«¯å£ port(){ firewall-cmd --add-port=3306/tcp --permanent firewall-cmd --add-port=6379/tcp --permanent service firewalld restart } ##æ¾ç½®æè½½æä»¶ mount(){ #æè½½é ç½®æä»¶ if test ! -f "/docker/nginx/conf/nginx.conf" ;then mkdir -p /docker/nginx/conf cp nginx/nginx.conf /docker/nginx/conf/nginx.conf fi } #å¯å¨åºç¡æ¨¡å base(){ docker-compose up -d mysql nginx-web redis } #å¯å¨åºç¡æ¨¡å monitor(){ docker-compose up -d ruoyi-monitor-admin } #å¯å¨ç¨åºæ¨¡å start(){ docker-compose up -d ruoyi-server1 ruoyi-server2 } #忢ç¨åºæ¨¡å stop(){ docker-compose stop ruoyi-server1 ruoyi-server2 } #å ³éæææ¨¡å stopall(){ docker-compose stop } #å é¤æææ¨¡å rm(){ docker-compose rm } #å é¤Tag为空çéå rmiNoneTag(){ docker images|grep none|awk '{print $3}'|xargs docker rmi -f } #æ ¹æ®è¾å ¥åæ°ï¼éæ©æ§è¡å¯¹åºæ¹æ³ï¼ä¸è¾å ¥åæ§è¡ä½¿ç¨è¯´æ case "$1" in "port") port ;; "mount") mount ;; "base") base ;; "monitor") monitor ;; "start") start ;; "stop") stop ;; "stopall") stopall ;; "rm") rm ;; "rmiNoneTag") rmiNoneTag ;; *) usage ;; esac docker/docker-compose.yml
¶Ô±ÈÐÂÎļþ @@ -0,0 +1,119 @@ version: '3' services: mysql: image: mysql:8.0.24 container_name: mysql environment: # æ¶åºä¸æµ· TZ: Asia/Shanghai # root å¯ç MYSQL_ROOT_PASSWORD: root # åå§åæ°æ®åº(åç»çåå§åsqlä¼å¨è¿ä¸ªåºæ§è¡) MYSQL_DATABASE: ry-vue ports: - 3306:3306 volumes: # æ°æ®æè½½ - /docker/mysql/data/:/var/lib/mysql/ # é ç½®æè½½ - /docker/mysql/conf/:/etc/mysql/conf.d/ command: # å°mysql8.0é»è®¤å¯ç çç¥ ä¿®æ¹ä¸º åå çç¥ (mysql8.0å¯¹å ¶é»è®¤çç¥åäºæ´æ¹ ä¼å¯¼è´å¯ç æ æ³å¹é ) --default-authentication-plugin=mysql_native_password --character-set-server=utf8mb4 --collation-server=utf8mb4_general_ci --explicit_defaults_for_timestamp=true --lower_case_table_names=1 privileged: true restart: always networks: ruoyi_net: ipv4_address: 172.30.0.36 nginx-web: # 妿éè¦æå®çæ¬ å°±æ latest æ¢æçæ¬å· image: nginx:latest container_name: nginx-web ports: - 80:80 - 443:443 volumes: # è¯ä¹¦æ å° - /docker/nginx/cert:/etc/nginx/cert # é ç½®æä»¶æ å° - /docker/nginx/conf/nginx.conf:/etc/nginx/nginx.conf # 页é¢ç®å½ - /docker/nginx/html:/usr/share/nginx/html # æ¥å¿ç®å½ - /docker/nginx/log:/var/log/nginx # ä¸»æºæ¬æºæ¶é´æä»¶æ å° ä¸æ¬æºæ¶é´åæ¥ - /etc/localtime:/etc/localtime:ro privileged: true restart: always networks: - ruoyi_net redis: image: redis:6.2.1 container_name: redis ports: - 6379:6379 environment: # 设置ç¯å¢åé æ¶åºä¸æµ· ç¼ç UTF-8 TZ: Asia/Shanghai LANG: en_US.UTF-8 volumes: # é ç½®æä»¶ - /docker/redis/conf/redis.conf:/redis.conf:rw # æ°æ®æä»¶ - /docker/redis/data:/data:rw command: "redis-server --appendonly yes" privileged: true restart: always networks: ruoyi_net: ipv4_address: 172.30.0.48 ruoyi-server1: image: "ruoyi/ruoyi-server:2.5.0" environment: - TZ=Asia/Shanghai volumes: # é ç½®æä»¶ - /docker/server1/logs/:/ruoyi/server/logs/ privileged: true restart: always networks: ruoyi_net: ipv4_address: 172.30.0.60 ruoyi-server2: image: "ruoyi/ruoyi-server:2.5.0" environment: - TZ=Asia/Shanghai volumes: # é ç½®æä»¶ - /docker/server2/logs/:/ruoyi/server/logs/ privileged: true restart: always networks: ruoyi_net: ipv4_address: 172.30.0.61 ruoyi-monitor-admin: image: "ruoyi/ruoyi-monitor-admin:2.5.0" environment: - TZ=Asia/Shanghai privileged: true restart: always networks: ruoyi_net: ipv4_address: 172.30.0.90 networks: ruoyi_net: driver: bridge ipam: config: - subnet: 172.30.0.0/16 docker/nginx/nginx.conf
¶Ô±ÈÐÂÎļþ @@ -0,0 +1,77 @@ worker_processes 1; error_log /var/log/nginx/error.log warn; pid /var/run/nginx.pid; events { worker_connections 1024; } http { include mime.types; default_type application/octet-stream; sendfile on; keepalive_timeout 65; # éå¶bodyå¤§å° client_max_body_size 100m; log_format main '$remote_addr - $remote_user [$time_local] "$request" ' '$status $body_bytes_sent "$http_referer" ' '"$http_user_agent" "$http_x_forwarded_for"'; access_log /var/log/nginx/access.log main; upstream server { server 172.30.0.60:8080; server 172.30.0.61:8080; } upstream monitor-admin { server 172.30.0.90:9090; } server { listen 80; server_name localhost; # httpsé ç½®åè start #listen 443 ssl; # è¯ä¹¦ç´æ¥åæ¾ /docker/nginx/cert/ ç®å½ä¸å³å¯ æ´æ¹è¯ä¹¦åç§°å³å¯ æ éæ´æ¹è¯ä¹¦è·¯å¾ #ssl on; #ssl_certificate /etc/nginx/cert/xxx.local.crt; # /etc/nginx/cert/ 为dockeræ å°è·¯å¾ ä¸å è®¸æ´æ¹ #ssl_certificate_key /etc/nginx/cert/xxx.local.key; # /etc/nginx/cert/ 为dockeræ å°è·¯å¾ ä¸å è®¸æ´æ¹ #ssl_session_timeout 5m; #ssl_ciphers ECDHE-RSA-AES128-GCM-SHA256:ECDHE:ECDH:AES:HIGH:!NULL:!aNULL:!MD5:!ADH:!RC4; #ssl_protocols TLSv1 TLSv1.1 TLSv1.2; #ssl_prefer_server_ciphers on; # httpsé ç½®åè end location / { root /usr/share/nginx/html; try_files $uri $uri/ /index.html; index index.html index.htm; } location /prod-api/ { proxy_set_header Host $http_host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header REMOTE-HOST $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_pass http://server/; } location /admin/ { proxy_set_header Host $http_host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header REMOTE-HOST $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_pass http://monitor-admin/admin/; } error_page 500 502 503 504 /50x.html; location = /50x.html { root html; } } } pom.xml
@@ -6,32 +6,38 @@ <groupId>com.ruoyi</groupId> <artifactId>ruoyi-vue-plus</artifactId> <version>${ruoyi-vue-plus.version}</version> <version>2.5.0</version> <name>RuoYi-Vue-Plus</name> <url>https://gitee.com/JavaLionLi/RuoYi-Vue-Plus</url> <description>RuoYi-Vue-Plusåå°ç®¡çç³»ç»</description> <properties> <ruoyi-vue-plus.version>2.4.0</ruoyi-vue-plus.version> <spring-boot.version>2.4.7</spring-boot.version> <ruoyi-vue-plus.version>2.5.0</ruoyi-vue-plus.version> <spring-boot.version>2.4.8</spring-boot.version> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding> <java.version>1.8</java.version> <maven-jar-plugin.version>3.1.1</maven-jar-plugin.version> <druid.version>1.2.6</druid.version> <knife4j.version>3.0.2</knife4j.version> <knife4j.version>3.0.3</knife4j.version> <poi.version>4.1.2</poi.version> <velocity.version>1.7</velocity.version> <jwt.version>0.9.1</jwt.version> <mybatis-plus.version>3.4.3</mybatis-plus.version> <hutool.version>5.7.2</hutool.version> <hutool.version>5.7.4</hutool.version> <feign.version>3.0.3</feign.version> <feign-okhttp.version>11.0</feign-okhttp.version> <spring-boot-admin.version>2.4.1</spring-boot-admin.version> <redisson.version>3.15.2</redisson.version> <spring-boot-admin.version>2.4.3</spring-boot-admin.version> <redisson.version>3.16.0</redisson.version> <lock4j.version>2.2.1</lock4j.version> <datasource.version>3.4.0</datasource.version> <!-- docker é ç½® --> <docker.registry.url>localhost</docker.registry.url> <docker.registry.host>http://${docker.registry.url}:2375</docker.registry.host> <docker.namespace>ruoyi</docker.namespace> <docker.plugin.version>1.2.0</docker.plugin.version> </properties> <!-- ä¾èµå£°æ --> @@ -192,6 +198,7 @@ <module>ruoyi-generator</module> <module>ruoyi-common</module> <module>ruoyi-demo</module> <module>ruoyi-extend</module> </modules> <packaging>pom</packaging> ruoyi-admin/Dockerfile
¶Ô±ÈÐÂÎļþ @@ -0,0 +1,14 @@ FROM anapsix/alpine-java:8_server-jre_unlimited MAINTAINER Lion Li RUN mkdir -p /ruoyi/server RUN mkdir -p /ruoyi/server/logs WORKDIR /ruoyi/server EXPOSE 8080 ADD ./target/ruoyi-admin.jar ./app.jar ENTRYPOINT ["java", "-Djava.security.egd=file:/dev/./urandom", "-jar", "app.jar"] ruoyi-admin/pom.xml
@@ -5,7 +5,7 @@ <parent> <artifactId>ruoyi-vue-plus</artifactId> <groupId>com.ruoyi</groupId> <version>${ruoyi-vue-plus.version}</version> <version>2.5.0</version> </parent> <modelVersion>4.0.0</modelVersion> <packaging>jar</packaging> @@ -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> ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysIndexController.java
¶Ô±ÈÐÂÎļþ @@ -0,0 +1,29 @@ package com.ruoyi.web.controller.system; import cn.hutool.core.util.StrUtil; import com.ruoyi.common.config.RuoYiConfig; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; /** * é¦é¡µ * * @author ruoyi */ @RestController public class SysIndexController { /** ç³»ç»åºç¡é ç½® */ @Autowired private RuoYiConfig ruoyiConfig; /** * 访é®é¦é¡µï¼æç¤ºè¯ */ @RequestMapping("/") public String index() { return StrUtil.format("欢è¿ä½¿ç¨{}åå°ç®¡çæ¡æ¶ï¼å½åçæ¬ï¼v{}ï¼è¯·éè¿å端å°å访é®ã", ruoyiConfig.getName(), ruoyiConfig.getVersion()); } } ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysMenuController.java
@@ -1,8 +1,6 @@ package com.ruoyi.web.controller.system; import cn.hutool.core.util.StrUtil; import com.ruoyi.common.annotation.Log; import com.ruoyi.common.constant.Constants; import com.ruoyi.common.constant.UserConstants; import com.ruoyi.common.core.controller.BaseController; import com.ruoyi.common.core.domain.AjaxResult; @@ -11,6 +9,7 @@ import com.ruoyi.common.enums.BusinessType; import com.ruoyi.common.utils.SecurityUtils; import com.ruoyi.common.utils.ServletUtils; import com.ruoyi.common.utils.StringUtils; import com.ruoyi.framework.web.service.TokenService; import com.ruoyi.system.service.ISysMenuService; import org.springframework.beans.factory.annotation.Autowired; @@ -98,8 +97,7 @@ { return AjaxResult.error("æ°å¢èå'" + menu.getMenuName() + "'失败ï¼èååç§°å·²åå¨"); } else if (UserConstants.YES_FRAME.equals(menu.getIsFrame()) && !StrUtil.startWithAny(menu.getPath(), Constants.HTTP, Constants.HTTPS)) else if (UserConstants.YES_FRAME.equals(menu.getIsFrame()) && !StringUtils.ishttp(menu.getPath())) { return AjaxResult.error("æ°å¢èå'" + menu.getMenuName() + "'失败ï¼å°åå¿ é¡»ä»¥http(s)://å¼å¤´"); } @@ -119,8 +117,7 @@ { return AjaxResult.error("ä¿®æ¹èå'" + menu.getMenuName() + "'失败ï¼èååç§°å·²åå¨"); } else if (UserConstants.YES_FRAME.equals(menu.getIsFrame()) && !StrUtil.startWithAny(menu.getPath(), Constants.HTTP, Constants.HTTPS)) else if (UserConstants.YES_FRAME.equals(menu.getIsFrame()) && !StringUtils.ishttp(menu.getPath())) { return AjaxResult.error("ä¿®æ¹èå'" + menu.getMenuName() + "'失败ï¼å°åå¿ é¡»ä»¥http(s)://å¼å¤´"); } ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysRoleController.java
@@ -6,6 +6,7 @@ import com.ruoyi.common.core.controller.BaseController; import com.ruoyi.common.core.domain.AjaxResult; import com.ruoyi.common.core.domain.entity.SysRole; import com.ruoyi.common.core.domain.entity.SysUser; import com.ruoyi.common.core.domain.model.LoginUser; import com.ruoyi.common.core.page.TableDataInfo; import com.ruoyi.common.enums.BusinessType; @@ -14,6 +15,7 @@ import com.ruoyi.common.utils.poi.ExcelUtil; import com.ruoyi.framework.web.service.SysPermissionService; import com.ruoyi.framework.web.service.TokenService; import com.ruoyi.system.domain.SysUserRole; import com.ruoyi.system.service.ISysRoleService; import com.ruoyi.system.service.ISysUserService; import org.springframework.beans.factory.annotation.Autowired; @@ -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)); } } ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysUserController.java
@@ -196,4 +196,31 @@ user.setUpdateBy(SecurityUtils.getUsername()); return toAjax(userService.updateUserStatus(user)); } /** * æ ¹æ®ç¨æ·ç¼å·è·åææè§è² */ @PreAuthorize("@ss.hasPermi('system:user:query')") @GetMapping("/authRole/{userId}") public AjaxResult authRole(@PathVariable("userId") Long userId) { SysUser user = userService.selectUserById(userId); List<SysRole> roles = roleService.selectRolesByUserId(userId); Map<String, Object> ajax = new HashMap<>(); ajax.put("user", user); ajax.put("roles", SysUser.isAdmin(userId) ? roles : roles.stream().filter(r -> !r.isAdmin()).collect(Collectors.toList())); return AjaxResult.success(ajax); } /** * ç¨æ·ææè§è² */ @PreAuthorize("@ss.hasPermi('system:user:edit')") @Log(title = "ç¨æ·ç®¡ç", businessType = BusinessType.GRANT) @PutMapping("/authRole") public AjaxResult insertAuthRole(Long userId, Long[] roleIds) { userService.insertUserAuth(userId, roleIds); return success(); } } ruoyi-admin/src/main/resources/application-dev.yml
@@ -110,3 +110,30 @@ subscriptionsPerConnection: 5 # DNSçæµæ¶é´é´éï¼åä½ï¼æ¯«ç§ dnsMonitoringInterval: 5000 --- # çæ§é ç½® spring: boot: admin: # Spring Boot Admin Client 客æ·ç«¯çç¸å ³é ç½® client: # 设置 Spring Boot Admin Server å°å url: http://localhost:9090/admin instance: prefer-ip: true # 注åå®ä¾æ¶ï¼ä¼å ä½¿ç¨ IP username: ruoyi password: 123456 # Actuator çæ§ç«¯ç¹çé 置项 management: endpoints: web: # Actuator æä¾ç API æ¥å£çæ ¹ç®å½ãé»è®¤ä¸º /actuator base-path: /actuator exposure: # éè¦å¼æ¾ç端ç¹ãé»è®¤å¼åªæå¼ health å info 两个端ç¹ãéè¿è®¾ç½® * ï¼å¯ä»¥å¼æ¾ææç«¯ç¹ã # ç产ç¯å¢ä¸å»ºè®®æ¾å¼ææ æ ¹æ®é¡¹ç®éæ±æ¾å¼å³å¯ include: '*' endpoint: logfile: external-file: ./logs/sys-console.log ruoyi-admin/src/main/resources/application-prod.yml
@@ -12,7 +12,7 @@ # ä¸»åºæ°æ®æº master: driverClassName: com.mysql.cj.jdbc.Driver url: jdbc:mysql://localhost:3306/ry-vue?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8&autoReconnect=true url: jdbc:mysql://172.30.0.36:3306/ry-vue?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8&autoReconnect=true username: root password: root # ä»åºæ°æ®æº @@ -66,7 +66,7 @@ # redis é ç½® redis: # å°å host: localhost host: 172.30.0.48 # 端å£ï¼é»è®¤ä¸º6379 port: 6379 # æ°æ®åºç´¢å¼ @@ -110,3 +110,30 @@ subscriptionsPerConnection: 5 # DNSçæµæ¶é´é´éï¼åä½ï¼æ¯«ç§ dnsMonitoringInterval: 5000 --- # çæ§é ç½® spring: boot: admin: # Spring Boot Admin Client 客æ·ç«¯çç¸å ³é ç½® client: # 设置 Spring Boot Admin Server å°å url: http://172.30.0.90:9090/admin instance: prefer-ip: true # 注åå®ä¾æ¶ï¼ä¼å ä½¿ç¨ IP username: ruoyi password: 123456 # Actuator çæ§ç«¯ç¹çé 置项 management: endpoints: web: # Actuator æä¾ç API æ¥å£çæ ¹ç®å½ãé»è®¤ä¸º /actuator base-path: /actuator exposure: # éè¦å¼æ¾ç端ç¹ãé»è®¤å¼åªæå¼ health å info 两个端ç¹ãéè¿è®¾ç½® * ï¼å¯ä»¥å¼æ¾ææç«¯ç¹ã # ç产ç¯å¢ä¸å»ºè®®æ¾å¼ææ æ ¹æ®é¡¹ç®éæ±æ¾å¼å³å¯ include: health,info endpoint: logfile: external-file: ./logs/sys-console.log ruoyi-admin/src/main/resources/application.yml
@@ -64,6 +64,8 @@ # Springé ç½® spring: application: name: ${ruoyi.name} # èµæºä¿¡æ¯ messages: # å½é åèµæºæä»¶è·¯å¾ @@ -110,6 +112,8 @@ # MyBatisPlusé ç½® # https://baomidou.com/config/ mybatis-plus: # 䏿¯æå¤å , 妿éè¦å¯å¨æ³¨è§£é ç½® æ æåæ«å ç级 # ä¾å¦ com.**.**.mapper mapperPackage: com.ruoyi.**.mapper # 对åºç XML æä»¶ä½ç½® mapperLocations: classpath*:mapper/**/*Mapper.xml @@ -156,7 +160,9 @@ # STATEMENT å ³éä¸çº§ç¼å localCacheScope: SESSION # å¼å¯Mybatisäºçº§ç¼åï¼é»è®¤ä¸º true cacheEnabled: true cacheEnabled: false # æ´è¯¦ç»çæ¥å¿è¾åº 伿æ§è½æè # logImpl: org.apache.ibatis.logging.stdout.StdOutImpl global-config: # æ¯å¦æå° Logo banner banner: true @@ -203,7 +209,7 @@ # 请æ±åç¼ pathMapping: /dev-api # æ é¢ title: 'æ é¢ï¼RuoYi-Vue-Plusåå°ç®¡çç³»ç»_æ¥å£ææ¡£' title: 'æ é¢ï¼${ruoyi.name}åå°ç®¡çç³»ç»_æ¥å£ææ¡£' # æè¿° description: 'æè¿°ï¼ç¨äºç®¡çé墿ä¸å ¬å¸ç人åä¿¡æ¯,å ·ä½å æ¬XXX,XXX模å...' # çæ¬ @@ -244,6 +250,8 @@ # feign ç¸å ³é ç½® feign: # 䏿¯æå¤å , 妿éè¦å¯å¨æ³¨è§£é ç½® æ æåæ«å ç级 # ä¾å¦ com.**.**.feign package: com.ruoyi.**.feign # å¼å¯å缩 compression: @@ -255,6 +263,21 @@ enabled: true circuitbreaker: enabled: true --- # redisson ç¼åé ç½® redisson: cacheGroup: # ç¨ä¾: @Cacheable(cacheNames="groupId", key="#XXX") æ¹å¯ä½¿ç¨ç¼åç»é ç½® - groupId: redissonCacheMap # ç»è¿ææ¶é´(èæ¬çæ§) ttl: 60000 # ç»æå¤§ç©ºé²æ¶é´(èæ¬çæ§) maxIdleTime: 60000 # ç»æå¤§é¿åº¦ maxSize: 0 - groupId: testCache ttl: 1000 maxIdleTime: 500 --- # åå¸å¼é lock4j å ¨å±é ç½® lock4j: @@ -293,31 +316,3 @@ tablePrefix: QRTZ_ # sqlserver å¯ç¨ # selectWithLockSQL: SELECT * FROM {0}LOCKS UPDLOCK WHERE LOCK_NAME = ? --- # çæ§é ç½® spring: application: name: ruoyi-vue-plus boot: admin: # Spring Boot Admin Client 客æ·ç«¯çç¸å ³é ç½® client: # 设置 Spring Boot Admin Server å°å url: http://localhost:${server.port}${spring.boot.admin.context-path} instance: prefer-ip: true # 注åå®ä¾æ¶ï¼ä¼å ä½¿ç¨ IP # Spring Boot Admin Server æå¡ç«¯çç¸å ³é ç½® context-path: /admin # é ç½® Spring # Actuator çæ§ç«¯ç¹çé 置项 management: endpoints: web: # Actuator æä¾ç API æ¥å£çæ ¹ç®å½ãé»è®¤ä¸º /actuator base-path: /actuator exposure: # éè¦å¼æ¾ç端ç¹ãé»è®¤å¼åªæå¼ health å info 两个端ç¹ãéè¿è®¾ç½® * ï¼å¯ä»¥å¼æ¾ææç«¯ç¹ã include: '*' endpoint: logfile: external-file: ./logs/sys-console.log ruoyi-admin/src/main/resources/i18n/messages_en_US.properties
¶Ô±ÈÐÂÎļþ @@ -0,0 +1,33 @@ #éè¯¯æ¶æ¯ not.null= user.jcaptcha.error= user.jcaptcha.expire= user.not.exists= user.password.not.match= user.password.retry.limit.count= user.password.retry.limit.exceed= user.password.delete= user.blocked= role.blocked= user.logout.success= length.not.valid= user.username.not.valid= user.password.not.valid= user.email.not.valid= user.mobile.phone.number.not.valid= user.login.success= user.notfound= user.forcelogout= user.unknown.error= ##æä»¶ä¸ä¼ æ¶æ¯ upload.exceed.maxSize= upload.filename.exceed.length= ##æé no.permission= no.create.permission= no.update.permission= no.delete.permission= no.export.permission= no.view.permission= ruoyi-admin/src/main/resources/i18n/messages_zh_CN.properties
¶Ô±ÈÐÂÎļþ @@ -0,0 +1,36 @@ #éè¯¯æ¶æ¯ not.null=* å¿ é¡»å¡«å user.jcaptcha.error=éªè¯ç é误 user.jcaptcha.expire=éªè¯ç 已失æ user.not.exists=ç¨æ·ä¸åå¨/å¯ç é误 user.password.not.match=ç¨æ·ä¸åå¨/å¯ç é误 user.password.retry.limit.count=å¯ç è¾å ¥é误{0}次 user.password.retry.limit.exceed=å¯ç è¾å ¥é误{0}次ï¼å¸æ·éå®10åé user.password.delete=对ä¸èµ·ï¼æ¨çè´¦å·å·²è¢«å é¤ user.blocked=ç¨æ·å·²å°ç¦ï¼è¯·è系管çå role.blocked=è§è²å·²å°ç¦ï¼è¯·è系管çå user.logout.success=éåºæå length.not.valid=é¿åº¦å¿ é¡»å¨{min}å°{max}个å符ä¹é´ user.username.not.valid=* 2å°20个æ±åãåæ¯ãæ°åæä¸åçº¿ç»æï¼ä¸å¿ 须以鿰åå¼å¤´ user.password.not.valid=* 5-50个å符 user.email.not.valid=é®ç®±æ ¼å¼é误 user.mobile.phone.number.not.valid=ææºå·æ ¼å¼é误 user.login.success=ç»å½æå user.notfound=è¯·éæ°ç»å½ user.forcelogout=管çå强å¶éåºï¼è¯·éæ°ç»å½ user.unknown.error=æªç¥é误ï¼è¯·éæ°ç»å½ ##æä»¶ä¸ä¼ æ¶æ¯ upload.exceed.maxSize=ä¸ä¼ çæä»¶å¤§å°è¶ åºéå¶çæä»¶å¤§å°ï¼<br/>å 许çæä»¶æå¤§å¤§å°æ¯ï¼{0}MBï¼ upload.filename.exceed.length=ä¸ä¼ çæä»¶åæé¿{0}个å符 ##æé no.permission=æ¨æ²¡ææ°æ®çæéï¼è¯·è系管çåæ·»å æé [{0}] no.create.permission=æ¨æ²¡æåå»ºæ°æ®çæéï¼è¯·è系管çåæ·»å æé [{0}] no.update.permission=æ¨æ²¡æä¿®æ¹æ°æ®çæéï¼è¯·è系管çåæ·»å æé [{0}] no.delete.permission=æ¨æ²¡æå 餿°æ®çæéï¼è¯·è系管çåæ·»å æé [{0}] no.export.permission=æ¨æ²¡æå¯¼åºæ°æ®çæéï¼è¯·è系管çåæ·»å æé [{0}] no.view.permission=æ¨æ²¡ææ¥çæ°æ®çæéï¼è¯·è系管çåæ·»å æé [{0}] ruoyi-common/pom.xml
@@ -5,7 +5,7 @@ <parent> <artifactId>ruoyi-vue-plus</artifactId> <groupId>com.ruoyi</groupId> <version>${ruoyi-vue-plus.version}</version> <version>2.5.0</version> </parent> <modelVersion>4.0.0</modelVersion> @@ -116,10 +116,6 @@ <artifactId>feign-okhttp</artifactId> </dependency> <dependency> <groupId>de.codecentric</groupId> <artifactId>spring-boot-admin-starter-server</artifactId> </dependency> <dependency> <groupId>de.codecentric</groupId> <artifactId>spring-boot-admin-starter-client</artifactId> ruoyi-common/src/main/java/com/ruoyi/common/constant/UserConstants.java
@@ -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"; ruoyi-common/src/main/java/com/ruoyi/common/core/domain/entity/SysUser.java
@@ -148,6 +148,10 @@ @TableField(exist = false) private Long[] postIds; /** è§è²ID */ @TableField(exist = false) private Long roleId; public SysUser(Long userId) { this.userId = userId; ruoyi-common/src/main/java/com/ruoyi/common/core/mybatisplus/cache/MybatisPlusRedisCache.java
@@ -15,6 +15,9 @@ /** * mybatis-redis äºçº§ç¼å * * ä½¿ç¨æ¹æ³ é ç½®æä»¶å¼å¯ mybatis-plus äºçº§ç¼å * å¨ XxxMapper.java ç±»ä¸æ·»å 注解 @CacheNamespace(implementation = MybatisPlusRedisCache.class, eviction = MybatisPlusRedisCache.class) * * @author Lion Li */ @Slf4j ruoyi-common/src/main/java/com/ruoyi/common/core/mybatisplus/methods/InsertAll.java
@@ -1,12 +1,16 @@ package com.ruoyi.common.core.mybatisplus.methods; import cn.hutool.core.util.StrUtil; import com.baomidou.mybatisplus.annotation.IdType; import com.baomidou.mybatisplus.core.enums.SqlMethod; import com.baomidou.mybatisplus.core.injector.AbstractMethod; import com.baomidou.mybatisplus.core.metadata.TableInfo; import com.baomidou.mybatisplus.core.metadata.TableInfoHelper; import org.apache.ibatis.executor.keygen.Jdbc3KeyGenerator; import org.apache.ibatis.executor.keygen.KeyGenerator; import org.apache.ibatis.executor.keygen.NoKeyGenerator; import org.apache.ibatis.mapping.MappedStatement; import org.apache.ibatis.mapping.SqlSource; import com.baomidou.mybatisplus.core.injector.AbstractMethod; import com.baomidou.mybatisplus.core.metadata.TableInfo; /** * åsqlæ¹éæå ¥ @@ -20,9 +24,28 @@ final String sql = "<script>insert into %s %s values %s</script>"; final String fieldSql = prepareFieldSql(tableInfo); final String valueSql = prepareValuesSqlForMysqlBatch(tableInfo); KeyGenerator keyGenerator = new NoKeyGenerator(); SqlMethod sqlMethod = SqlMethod.INSERT_ONE; String keyProperty = null; String keyColumn = null; // 表å å«ä¸»é®å¤çé»è¾,妿ä¸å å«ä¸»é®å½æ®éåæ®µå¤ç if (StrUtil.isNotBlank(tableInfo.getKeyProperty())) { if (tableInfo.getIdType() == IdType.AUTO) { /** èªå¢ä¸»é® */ keyGenerator = new Jdbc3KeyGenerator(); keyProperty = tableInfo.getKeyProperty(); keyColumn = tableInfo.getKeyColumn(); } else { if (null != tableInfo.getKeySequence()) { keyGenerator = TableInfoHelper.genKeyGenerator(getMethod(sqlMethod), tableInfo, builderAssistant); keyProperty = tableInfo.getKeyProperty(); keyColumn = tableInfo.getKeyColumn(); } } } final String sqlResult = String.format(sql, tableInfo.getTableName(), fieldSql, valueSql); SqlSource sqlSource = languageDriver.createSqlSource(configuration, sqlResult, modelClass); return this.addInsertMappedStatement(mapperClass, modelClass, "insertAll", sqlSource, new NoKeyGenerator(), null, null); return this.addInsertMappedStatement(mapperClass, modelClass, "insertAll", sqlSource, keyGenerator, keyProperty, keyColumn); } private String prepareFieldSql(TableInfo tableInfo) { ruoyi-common/src/main/java/com/ruoyi/common/core/redis/RedisCache.java
@@ -205,9 +205,9 @@ * @param hKeys Hashé®éå * @return Hash对象éå */ public <T> List<T> getMultiCacheMapValue(final String key, final Collection<Object> hKeys) { RListMultimap rListMultimap = redissonClient.getListMultimap(key); return rListMultimap.getAll(hKeys); public <K,V> Map<K,V> getMultiCacheMapValue(final String key, final Set<K> hKeys) { RMap<K,V> rMap = redissonClient.getMap(key); return rMap.getAll(hKeys); } /** ruoyi-common/src/main/java/com/ruoyi/common/utils/DictUtils.java
@@ -88,7 +88,7 @@ StringBuilder propertyString = new StringBuilder(); List<SysDictData> datas = getDictCache(dictType); if (StrUtil.containsAny(separator, dictValue) && CollUtil.isNotEmpty(datas)) if (StrUtil.containsAny(dictValue, separator) && CollUtil.isNotEmpty(datas)) { for (SysDictData dict : datas) { @@ -128,7 +128,7 @@ StringBuilder propertyString = new StringBuilder(); List<SysDictData> datas = getDictCache(dictType); if (StrUtil.containsAny(separator, dictLabel) && CollUtil.isNotEmpty(datas)) if (StrUtil.containsAny(dictLabel, separator) && CollUtil.isNotEmpty(datas)) { for (SysDictData dict : datas) { ruoyi-common/src/main/java/com/ruoyi/common/utils/StringUtils.java
¶Ô±ÈÐÂÎļþ @@ -0,0 +1,28 @@ package com.ruoyi.common.utils; import cn.hutool.core.util.StrUtil; import com.ruoyi.common.constant.Constants; /** * åç¬¦ä¸²å·¥å ·ç±» * * @author ruoyi */ public class StringUtils extends org.apache.commons.lang3.StringUtils { /** 空å符串 */ private static final String NULLSTR = ""; /** ä¸å线 */ private static final char SEPARATOR = '_'; /** * æ¯å¦ä¸ºhttp(s)://å¼å¤´ * * @param link 龿¥ * @return ç»æ */ public static boolean ishttp(String link) { return StrUtil.startWithAny(link, Constants.HTTP, Constants.HTTPS); } } ruoyi-common/src/main/java/com/ruoyi/common/utils/poi/ExcelUtil.java
@@ -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; /** * 导åºç±»åï¼EXPORT:å¯¼åºæ°æ®ï¼IMPORTï¼å¯¼å ¥æ¨¡æ¿ï¼ */ 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(); } /** * 对excel表åé»è®¤ç¬¬ä¸ä¸ªç´¢å¼åè½¬æ¢ælist * * @param is è¾å ¥æµ * @return 转æ¢åéå */ public List<T> importExcel(InputStream is) throws Exception { return importExcel(StrUtil.EMPTY, is); } /** * 对excel表åæå®è¡¨æ ¼ç´¢å¼åè½¬æ¢æ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 { // å¦æä¼ å ¥çsheetåä¸åå¨åé»è®¤æå第1个sheet. sheet = wb.getSheetAt(0); } if (sheet == null) { throw new IOException("æä»¶sheetä¸åå¨"); } int rows = sheet.getPhysicalNumberOfRows(); if (rows > 0) { // å®ä¹ä¸ä¸ªmapç¨äºåæ¾excelåçåºå·åfield. 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); } } // ææ°æ®æ¶æå¤ç å¾å°ç±»çææfield. Field[] allFields = clazz.getDeclaredFields(); // å®ä¹ä¸ä¸ªmapç¨äºåæ¾åçåºå·åfield. 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); // ä»mapä¸å¾å°å¯¹åºåçfield. 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; } /** * 对listæ°æ®æºå°å ¶éé¢çæ°æ®å¯¼å ¥å°excel表å * * @param list å¯¼åºæ°æ®éå * @param sheetName å·¥ä½è¡¨çåç§° * @return ç»æ */ public AjaxResult exportExcel(List<T> list, String sheetName) { this.init(list, sheetName, Type.EXPORT); return exportExcel(); } /** * 对listæ°æ®æºå°å ¶éé¢çæ°æ®å¯¼å ¥å°excel表å * * @param sheetName å·¥ä½è¡¨çåç§° * @return ç»æ */ public AjaxResult importTemplateExcel(String sheetName) { this.init(null, sheetName, Type.IMPORT); return exportExcel(); } /** * 对listæ°æ®æºå°å ¶éé¢çæ°æ®å¯¼å ¥å°excel表å * * @return ç»æ */ public AjaxResult exportExcel() { OutputStream out = null; try { // ååºä¸å ±æå¤å°ä¸ªsheet. 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); } // å¦æè®¾ç½®äºcombo屿§åæ¬ååªè½éæ©ä¸è½è¾å ¥ 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); } /** * è§£æå¯¼åºå¼ 0=ç·,1=女,2=æªç¥ * * @param propertyValue åæ°å¼ * @param converterExp ç¿»è¯æ³¨è§£ * @param separator åé符 * @return è§£æåå¼ */ 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); } /** * ååè§£æå¼ ç·=0,女=1,æªç¥=2 * * @param propertyValue åæ°å¼ * @param converterExp ç¿»è¯æ³¨è§£ * @param separator åé符 * @return è§£æåå¼ */ 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); } /** * è§£æåå ¸å¼ * * @param dictValue åå ¸å¼ * @param dictType åå ¸ç±»å * @param separator åé符 * @return åå ¸æ ç¾ */ public static String convertDictByExp(String dictValue, String dictType, String separator) { return DictUtils.getDictLabel(dictType, dictValue, separator); } /** * ååè§£æå¼åå ¸å¼ * * @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; } } 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; /** * 导åºç±»åï¼EXPORT:å¯¼åºæ°æ®ï¼IMPORTï¼å¯¼å ¥æ¨¡æ¿ï¼ */ 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(); } /** * 对excel表åé»è®¤ç¬¬ä¸ä¸ªç´¢å¼åè½¬æ¢ælist * * @param is è¾å ¥æµ * @return 转æ¢åéå */ public List<T> importExcel(InputStream is) throws Exception { return importExcel(StrUtil.EMPTY, is); } /** * 对excel表åæå®è¡¨æ ¼ç´¢å¼åè½¬æ¢æ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 { // å¦æä¼ å ¥çsheetåä¸åå¨åé»è®¤æå第1个sheet. sheet = wb.getSheetAt(0); } if (sheet == null) { throw new IOException("æä»¶sheetä¸åå¨"); } int rows = sheet.getPhysicalNumberOfRows(); if (rows > 0) { // å®ä¹ä¸ä¸ªmapç¨äºåæ¾excelåçåºå·åfield. 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); } } // ææ°æ®æ¶æå¤ç å¾å°ç±»çææfield. Field[] allFields = clazz.getDeclaredFields(); // å®ä¹ä¸ä¸ªmapç¨äºåæ¾åçåºå·åfield. 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); // ä»mapä¸å¾å°å¯¹åºåçfield. 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; } /** * 对listæ°æ®æºå°å ¶éé¢çæ°æ®å¯¼å ¥å°excel表å * * @param list å¯¼åºæ°æ®éå * @param sheetName å·¥ä½è¡¨çåç§° * @return ç»æ */ public AjaxResult exportExcel(List<T> list, String sheetName) { this.init(list, sheetName, Type.EXPORT); return exportExcel(); } /** * 对listæ°æ®æºå°å ¶éé¢çæ°æ®å¯¼å ¥å°excel表å * * @param sheetName å·¥ä½è¡¨çåç§° * @return ç»æ */ public AjaxResult importTemplateExcel(String sheetName) { this.init(null, sheetName, Type.IMPORT); return exportExcel(); } /** * 对listæ°æ®æºå°å ¶éé¢çæ°æ®å¯¼å ¥å°excel表å * * @return ç»æ */ public AjaxResult exportExcel() { OutputStream out = null; try { // ååºä¸å ±æå¤å°ä¸ªsheet. 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); } // å¦æè®¾ç½®äºcombo屿§åæ¬ååªè½éæ©ä¸è½è¾å ¥ 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); } /** * è§£æå¯¼åºå¼ 0=ç·,1=女,2=æªç¥ * * @param propertyValue åæ°å¼ * @param converterExp ç¿»è¯æ³¨è§£ * @param separator åé符 * @return è§£æåå¼ */ 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); } /** * ååè§£æå¼ ç·=0,女=1,æªç¥=2 * * @param propertyValue åæ°å¼ * @param converterExp ç¿»è¯æ³¨è§£ * @param separator åé符 * @return è§£æåå¼ */ 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); } /** * è§£æåå ¸å¼ * * @param dictValue åå ¸å¼ * @param dictType åå ¸ç±»å * @param separator åé符 * @return åå ¸æ ç¾ */ public static String convertDictByExp(String dictValue, String dictType, String separator) { return DictUtils.getDictLabel(dictType, dictValue, separator); } /** * ååè§£æå¼åå ¸å¼ * * @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; } } 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> </project> ruoyi-demo/src/main/java/com/ruoyi/demo/controller/RedisCacheController.java
¶Ô±ÈÐÂÎļþ @@ -0,0 +1,70 @@ package com.ruoyi.demo.controller; import com.ruoyi.common.core.domain.AjaxResult; import lombok.RequiredArgsConstructor; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.cache.annotation.CacheEvict; import org.springframework.cache.annotation.CachePut; import org.springframework.cache.annotation.Cacheable; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; /** * spring-cache æ¼ç¤ºæ¡ä¾ * * @author Lion Li */ // ç±»çº§å« ç¼åç»ä¸é ç½® //@CacheConfig(cacheNames = "redissonCacheMap") @RequiredArgsConstructor(onConstructor_ = @Autowired) @RestController @RequestMapping("/demo/cache") public class RedisCacheController { /** * æµè¯ @Cacheable * * 表示è¿ä¸ªæ¹æ³æäºç¼åçåè½,æ¹æ³çè¿åå¼ä¼è¢«ç¼å䏿¥ * ä¸ä¸æ¬¡è°ç¨è¯¥æ¹æ³å,ä¼å»æ£æ¥æ¯å¦ç¼åä¸å·²ç»æå¼ * 妿æå°±ç´æ¥è¿å,ä¸è°ç¨æ¹æ³ * å¦ææ²¡æ,å°±è°ç¨æ¹æ³,ç¶åæç»æç¼åèµ·æ¥ * è¿ä¸ªæ³¨è§£ãä¸è¬ç¨å¨æ¥è¯¢æ¹æ³ä¸ã * * cacheNames 为é ç½®æä»¶å groupId */ @Cacheable(cacheNames = "redissonCacheMap", key = "#key", condition = "#key != null") @GetMapping("/test1") public AjaxResult<String> test1(String key, String value){ return AjaxResult.success("æä½æå", value); } /** * æµè¯ @CachePut * * å äº@CachePutæ³¨è§£çæ¹æ³,ä¼ææ¹æ³çè¿åå¼putå°ç¼åéé¢ç¼åèµ·æ¥,ä¾å ¶å®å°æ¹ä½¿ç¨ * å®ãé常ç¨å¨æ°å¢æ¹æ³ä¸ã * * cacheNames 为 é ç½®æä»¶å groupId */ @CachePut(cacheNames = "redissonCacheMap", key = "#key", condition = "#key != null") @GetMapping("/test2") public AjaxResult<String> test2(String key, String value){ return AjaxResult.success("æä½æå", value); } /** * æµè¯ @CacheEvict * * 使ç¨äºCacheEvictæ³¨è§£çæ¹æ³,伿¸ 空æå®ç¼å * ãä¸è¬ç¨å¨æ´æ°æè å é¤çæ¹æ³ä¸ã * * cacheNames 为 é ç½®æä»¶å groupId */ @CacheEvict(cacheNames = "redissonCacheMap", key = "#key", condition = "#key != null") @GetMapping("/test3") public AjaxResult<String> test3(String key, String value){ return AjaxResult.success("æä½æå", value); } } ruoyi-demo/src/main/java/com/ruoyi/demo/feign/fallback/FeignTestFallback.java
@@ -7,7 +7,10 @@ /** * feignæµè¯fallback * èªå®ä¹å°è£ ç»æä½çæ * ééåè§£ç å¨ æ ¹æ®èªå®ä¹å®ä½ èªè¡è§£æçæ * * @see {com.ruoyi.framework.config.FeignConfig#errorDecoder()} * @author Lion Li */ @Slf4j ruoyi-extend/pom.xml
¶Ô±ÈÐÂÎļþ @@ -0,0 +1,18 @@ <?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <parent> <artifactId>ruoyi-vue-plus</artifactId> <groupId>com.ruoyi</groupId> <version>2.5.0</version> </parent> <modelVersion>4.0.0</modelVersion> <artifactId>ruoyi-extend</artifactId> <packaging>pom</packaging> <modules> <module>ruoyi-monitor-admin</module> </modules> </project> ruoyi-extend/ruoyi-monitor-admin/Dockerfile
¶Ô±ÈÐÂÎļþ @@ -0,0 +1,13 @@ FROM anapsix/alpine-java:8_server-jre_unlimited MAINTAINER Lion Li RUN mkdir -p /ruoyi/monitor WORKDIR /ruoyi/monitor EXPOSE 9090 ADD ./target/ruoyi-monitor-admin.jar ./app.jar ENTRYPOINT ["java", "-Djava.security.egd=file:/dev/./urandom", "-jar", "app.jar"] ruoyi-extend/ruoyi-monitor-admin/pom.xml
¶Ô±ÈÐÂÎļþ @@ -0,0 +1,73 @@ <?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <parent> <artifactId>ruoyi-extend</artifactId> <groupId>com.ruoyi</groupId> <version>2.5.0</version> </parent> <modelVersion>4.0.0</modelVersion> <packaging>jar</packaging> <artifactId>ruoyi-monitor-admin</artifactId> <dependencies> <!-- SpringWeb模å --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <!-- spring security å®å ¨è®¤è¯ --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> </dependency> <dependency> <groupId>de.codecentric</groupId> <artifactId>spring-boot-admin-starter-server</artifactId> </dependency> </dependencies> <build> <finalName>${project.artifactId}</finalName> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> <version>${spring-boot.version}</version> <configuration> <fork>true</fork> <!-- å¦ææ²¡æè¯¥é ç½®ï¼devtoolsä¸ä¼çæ --> </configuration> <executions> <execution> <goals> <goal>repackage</goal> </goals> </execution> </executions> </plugin> <plugin> <groupId>com.spotify</groupId> <artifactId>docker-maven-plugin</artifactId> <version>${docker.plugin.version}</version> <configuration> <imageName>${docker.namespace}/${project.artifactId}:${project.version}</imageName> <dockerDirectory>${project.basedir}</dockerDirectory> <dockerHost>${docker.registry.host}</dockerHost> <registryUrl>${docker.registry.url}</registryUrl> <serverId>${docker.registry.url}</serverId> <resources> <resource> <targetPath>/</targetPath> <directory>${project.build.directory}</directory> <include>${project.build.finalName}.jar</include> </resource> </resources> </configuration> </plugin> </plugins> </build> </project> ruoyi-extend/ruoyi-monitor-admin/src/main/java/com/ruoyi/monitor/admin/MonitorAdminApplication.java
¶Ô±ÈÐÂÎļþ @@ -0,0 +1,19 @@ package com.ruoyi.monitor.admin; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; /** * Admin çæ§å¯å¨ç¨åº * * @author Lion Li */ @SpringBootApplication public class MonitorAdminApplication { public static void main(String[] args) { SpringApplication.run(MonitorAdminApplication.class, args); System.out.println("Admin çæ§å¯å¨æå" ); } } ruoyi-extend/ruoyi-monitor-admin/src/main/java/com/ruoyi/monitor/admin/config/AdminServerConfig.java
¶Ô±ÈÐÂÎļþ @@ -0,0 +1,31 @@ package com.ruoyi.monitor.admin.config; import de.codecentric.boot.admin.server.config.EnableAdminServer; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.autoconfigure.task.TaskExecutionAutoConfiguration; import org.springframework.boot.task.TaskExecutorBuilder; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Lazy; import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor; import java.util.concurrent.Executor; /** * springboot-admin serveré 置类 * * @author Lion Li */ @Configuration @EnableAdminServer public class AdminServerConfig { @Lazy @Bean(name = TaskExecutionAutoConfiguration.APPLICATION_TASK_EXECUTOR_BEAN_NAME) @ConditionalOnMissingBean(Executor.class) public ThreadPoolTaskExecutor applicationTaskExecutor(TaskExecutorBuilder builder) { return builder.build(); } } ruoyi-extend/ruoyi-monitor-admin/src/main/java/com/ruoyi/monitor/admin/config/SecurityConfig.java
¶Ô±ÈÐÂÎļþ @@ -0,0 +1,48 @@ package com.ruoyi.monitor.admin.config; import de.codecentric.boot.admin.server.config.AdminServerProperties; import org.springframework.context.annotation.Configuration; import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; import org.springframework.security.web.authentication.SavedRequestAwareAuthenticationSuccessHandler; /** * spring securityé ç½® * * @author ruoyi */ @Configuration @EnableWebSecurity @EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true, proxyTargetClass = true) public class SecurityConfig extends WebSecurityConfigurerAdapter { private final String adminContextPath; public SecurityConfig(AdminServerProperties adminServerProperties) { this.adminContextPath = adminServerProperties.getContextPath(); } @Override protected void configure(HttpSecurity httpSecurity) throws Exception { SavedRequestAwareAuthenticationSuccessHandler successHandler = new SavedRequestAwareAuthenticationSuccessHandler(); successHandler.setTargetUrlParameter("redirectTo"); successHandler.setDefaultTargetUrl(adminContextPath + "/"); httpSecurity.authorizeRequests() //æäºå¯¹ææéæèµäº§åç»å½é¡µé¢çå ¬å ±è®¿é®æéã .antMatchers(adminContextPath + "/assets/**").permitAll() .antMatchers(adminContextPath + "/login").permitAll() //å¿ é¡»å¯¹æ¯ä¸ªå ¶ä»è¯·æ±è¿è¡èº«ä»½éªè¯ .anyRequest().authenticated().and() //é ç½®ç»å½å注é .formLogin().loginPage(adminContextPath + "/login") .successHandler(successHandler).and() .logout().logoutUrl(adminContextPath + "/logout").and() //å¯ç¨HTTP-Basicæ¯æãè¿æ¯Spring Boot Admin Client注åæå¿ éç .httpBasic().and().csrf().disable() .headers().frameOptions().disable(); } } ruoyi-extend/ruoyi-monitor-admin/src/main/resources/application.yml
¶Ô±ÈÐÂÎļþ @@ -0,0 +1,11 @@ server: port: 9090 spring: security: user: name: ruoyi password: 123456 boot: admin: context-path: /admin ruoyi-framework/pom.xml
@@ -5,7 +5,7 @@ <parent> <artifactId>ruoyi-vue-plus</artifactId> <groupId>com.ruoyi</groupId> <version>${ruoyi-vue-plus.version}</version> <version>2.5.0</version> </parent> <modelVersion>4.0.0</modelVersion> ruoyi-framework/src/main/java/com/ruoyi/framework/config/AdminServerConfig.java
ÎļþÒÑɾ³ý ruoyi-framework/src/main/java/com/ruoyi/framework/config/FeignConfig.java
@@ -54,4 +54,40 @@ return new Retryer.Default(); } // /** // * èªå®ä¹å¼å¸¸è§£ç å¨ // * ç¨äºèªå®ä¹è¿åä½å¼å¸¸çæ // */ // @Bean // public ErrorDecoder errorDecoder() { // return new CustomErrorDecoder(); // } // // // /** // * èªå®ä¹è¿åä½è§£ç å¨ // */ // @Slf4j // public static class CustomErrorDecoder implements ErrorDecoder { // // @Override // public Exception decode(String methodKey, Response response) { // Exception exception = null; // try { // // è·ååå§çè¿åå 容 // String json = JsonUtils.toJsonString(response.body().asReader(StandardCharsets.UTF_8)); // exception = new RuntimeException(json); // // å°è¿åå 容ååºåå为Resultï¼è¿éåºæ ¹æ®èªèº«é¡¹ç®ä½ä¿®æ¹ // AjaxResult result = JsonUtils.parseObject(json, AjaxResult.class); // // ä¸å¡å¼å¸¸æåºç®åç RuntimeExceptionï¼ä¿ç忥éè¯¯ä¿¡æ¯ // if (result.getCode() != 200) { // exception = new RuntimeException(result.getMsg()); // } // } catch (IOException e) { // log.error(e.getMessage(), e); // } // return exception; // } // } } ruoyi-framework/src/main/java/com/ruoyi/framework/config/RedisConfig.java
@@ -19,6 +19,7 @@ import java.io.IOException; import java.util.HashMap; import java.util.List; import java.util.Map; /** @@ -78,8 +79,13 @@ */ @Bean public CacheManager cacheManager(RedissonClient redissonClient) { List<RedissonProperties.CacheGroup> cacheGroup = redissonProperties.getCacheGroup(); Map<String, CacheConfig> config = new HashMap<>(); config.put("redissonCacheMap", new CacheConfig(30*60*1000, 10*60*1000)); for (RedissonProperties.CacheGroup group : cacheGroup) { CacheConfig cacheConfig = new CacheConfig(group.getTtl(), group.getMaxIdleTime()); cacheConfig.setMaxSize(group.getMaxSize()); config.put(group.getGroupId(), cacheConfig); } return new RedissonSpringCacheManager(redissonClient, config, JsonJacksonCodec.INSTANCE); } ruoyi-framework/src/main/java/com/ruoyi/framework/config/SecurityConfig.java
@@ -3,7 +3,6 @@ import com.ruoyi.framework.security.filter.JwtAuthenticationTokenFilter; import com.ruoyi.framework.security.handle.AuthenticationEntryPointImpl; import com.ruoyi.framework.security.handle.LogoutSuccessHandlerImpl; import de.codecentric.boot.admin.server.config.AdminServerProperties; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; import org.springframework.http.HttpMethod; @@ -57,9 +56,6 @@ @Autowired private CorsFilter corsFilter; @Autowired private AdminServerProperties adminServerProperties; /** * è§£å³ æ æ³ç´æ¥æ³¨å ¥ AuthenticationManager * @@ -104,12 +100,13 @@ .antMatchers("/login", "/captchaImage").anonymous() .antMatchers( HttpMethod.GET, "/", "/*.html", "/**/*.html", "/**/*.css", "/**/*.js" "/**/*.js", "/profile/**" ).permitAll() .antMatchers("/profile/**").anonymous() .antMatchers("/common/download**").anonymous() .antMatchers("/common/download/resource**").anonymous() .antMatchers("/doc.html").anonymous() @@ -117,9 +114,6 @@ .antMatchers("/webjars/**").anonymous() .antMatchers("/*/api-docs").anonymous() .antMatchers("/druid/**").anonymous() // Spring Boot Admin Server çå®å ¨é ç½® .antMatchers(adminServerProperties.getContextPath()).anonymous() .antMatchers(adminServerProperties.getContextPath() + "/**").anonymous() // Spring Boot Actuator çå®å ¨é ç½® .antMatchers("/actuator").anonymous() .antMatchers("/actuator/**").anonymous() ruoyi-framework/src/main/java/com/ruoyi/framework/config/properties/RedissonProperties.java
@@ -2,10 +2,11 @@ import lombok.Data; import lombok.NoArgsConstructor; import org.redisson.client.codec.Codec; import org.redisson.config.TransportMode; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.stereotype.Component; import java.util.List; /** * Redisson é ç½®å±æ§ @@ -36,6 +37,11 @@ * åæºæå¡é ç½® */ private SingleServerConfig singleServerConfig; /** * ç¼åç» */ private List<CacheGroup> cacheGroup; @Data @NoArgsConstructor @@ -98,4 +104,30 @@ } @Data @NoArgsConstructor public static class CacheGroup { /** * ç»id */ private String groupId; /** * ç»è¿ææ¶é´ */ private long ttl; /** * ç»æå¤§ç©ºé²æ¶é´ */ private long maxIdleTime; /** * ç»æå¤§é¿åº¦ */ private int maxSize; } } ruoyi-framework/src/main/java/com/ruoyi/framework/mybatisplus/CreateAndUpdateMetaObjectHandler.java
@@ -1,6 +1,8 @@ package com.ruoyi.framework.mybatisplus; import cn.hutool.http.HttpStatus; import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler; import com.ruoyi.common.exception.CustomException; import com.ruoyi.common.utils.SecurityUtils; import org.apache.ibatis.reflection.MetaObject; @@ -8,6 +10,7 @@ /** * MPæ³¨å ¥å¤çå¨ * * @author Lion Li * @date 2021/4/25 */ @@ -15,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); } } ruoyi-generator/pom.xml
@@ -5,7 +5,7 @@ <parent> <artifactId>ruoyi-vue-plus</artifactId> <groupId>com.ruoyi</groupId> <version>${ruoyi-vue-plus.version}</version> <version>2.5.0</version> </parent> <modelVersion>4.0.0</modelVersion> ruoyi-generator/src/main/java/com/ruoyi/generator/service/GenTableServiceImpl.java
@@ -182,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)) { ruoyi-generator/src/main/resources/vm/java/mapper.java.vm
@@ -11,8 +11,6 @@ * @author ${author} * @date ${datetime} */ // å¦ä½¿éåæ¢æ°æ®æº 请å¿ä½¿ç¨ç¼å ä¼é ææ°æ®ä¸ä¸è´ç°è±¡ @CacheNamespace(implementation = MybatisPlusRedisCache.class, eviction = MybatisPlusRedisCache.class) public interface ${ClassName}Mapper extends BaseMapperPlus<${ClassName}> { } ruoyi-generator/src/main/resources/vm/vue/index-tree.vue.vm
@@ -256,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; }); } } 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; }); } } ruoyi-quartz/pom.xml
@@ -5,7 +5,7 @@ <parent> <artifactId>ruoyi-vue-plus</artifactId> <groupId>com.ruoyi</groupId> <version>${ruoyi-vue-plus.version}</version> <version>2.5.0</version> </parent> <modelVersion>4.0.0</modelVersion> ruoyi-system/pom.xml
@@ -5,7 +5,7 @@ <parent> <artifactId>ruoyi-vue-plus</artifactId> <groupId>com.ruoyi</groupId> <version>${ruoyi-vue-plus.version}</version> <version>2.5.0</version> </parent> <modelVersion>4.0.0</modelVersion> ruoyi-system/src/main/java/com/ruoyi/system/domain/vo/MetaVo.java
@@ -1,6 +1,8 @@ package com.ruoyi.system.domain.vo; import lombok.*; import com.ruoyi.common.utils.StringUtils; import lombok.Data; import lombok.NoArgsConstructor; import lombok.experimental.Accessors; /** @@ -28,6 +30,11 @@ */ private boolean noCache; /** * å é¾å°åï¼http(s)://å¼å¤´ï¼ */ private String link; public MetaVo(String title, String icon) { this.title = title; this.icon = icon; @@ -39,4 +46,19 @@ this.noCache = noCache; } public MetaVo(String title, String icon, String link) { this.title = title; this.icon = icon; this.link = link; } public MetaVo(String title, String icon, boolean noCache, String link) { this.title = title; this.icon = icon; this.noCache = noCache; if (StringUtils.ishttp(link)) { this.link = link; } } } ruoyi-system/src/main/java/com/ruoyi/system/mapper/SysUserMapper.java
@@ -25,6 +25,22 @@ public List<SysUser> selectUserList(SysUser sysUser); /** * æ ¹æ®æ¡ä»¶å页æ¥è¯¢æªå·²é ç¨æ·è§è²å表 * * @param user ç¨æ·ä¿¡æ¯ * @return ç¨æ·ä¿¡æ¯éåä¿¡æ¯ */ public Page<SysUser> selectAllocatedList(@Param("page") Page<SysUser> page, @Param("user") SysUser user); /** * æ ¹æ®æ¡ä»¶å页æ¥è¯¢æªåé ç¨æ·è§è²å表 * * @param user ç¨æ·ä¿¡æ¯ * @return ç¨æ·ä¿¡æ¯éåä¿¡æ¯ */ public Page<SysUser> selectUnallocatedList(@Param("page") Page<SysUser> page, @Param("user") SysUser user); /** * éè¿ç¨æ·åæ¥è¯¢ç¨æ· * * @param userName ç¨æ·å ruoyi-system/src/main/java/com/ruoyi/system/service/ISysRoleService.java
@@ -3,6 +3,8 @@ import com.ruoyi.common.core.domain.entity.SysRole; import com.ruoyi.common.core.mybatisplus.core.IServicePlus; import com.ruoyi.common.core.page.TableDataInfo; import com.ruoyi.common.core.domain.entity.SysRole; import com.ruoyi.system.domain.SysUserRole; import java.util.List; import java.util.Set; @@ -26,7 +28,15 @@ public List<SysRole> selectRoleList(SysRole role); /** * æ ¹æ®ç¨æ·IDæ¥è¯¢è§è² * æ ¹æ®ç¨æ·IDæ¥è¯¢è§è²å表 * * @param userId ç¨æ·ID * @return è§è²å表 */ public List<SysRole> selectRolesByUserId(Long userId); /** * æ ¹æ®ç¨æ·IDæ¥è¯¢è§è²æé * * @param userId ç¨æ·ID * @return æéå表 @@ -134,4 +144,30 @@ * @return ç»æ */ public int deleteRoleByIds(Long[] roleIds); /** * åæ¶ææç¨æ·è§è² * * @param userRole ç¨æ·åè§è²å ³èä¿¡æ¯ * @return ç»æ */ public int deleteAuthUser(SysUserRole userRole); /** * æ¹éåæ¶ææç¨æ·è§è² * * @param roleId è§è²ID * @param userIds éè¦åæ¶ææçç¨æ·æ°æ®ID * @return ç»æ */ public int deleteAuthUsers(Long roleId, Long[] userIds); /** * æ¹ééæ©ææç¨æ·è§è² * * @param roleId è§è²ID * @param userIds éè¦å é¤çç¨æ·æ°æ®ID * @return ç»æ */ public int insertAuthUsers(Long roleId, Long[] userIds); } ruoyi-system/src/main/java/com/ruoyi/system/service/ISysUserService.java
@@ -25,6 +25,22 @@ public List<SysUser> selectUserList(SysUser user); /** * æ ¹æ®æ¡ä»¶å页æ¥è¯¢å·²åé ç¨æ·è§è²å表 * * @param user ç¨æ·ä¿¡æ¯ * @return ç¨æ·ä¿¡æ¯éåä¿¡æ¯ */ public TableDataInfo<SysUser> selectAllocatedList(SysUser user); /** * æ ¹æ®æ¡ä»¶å页æ¥è¯¢æªåé ç¨æ·è§è²å表 * * @param user ç¨æ·ä¿¡æ¯ * @return ç¨æ·ä¿¡æ¯éåä¿¡æ¯ */ public TableDataInfo<SysUser> selectUnallocatedList(SysUser user); /** * éè¿ç¨æ·åæ¥è¯¢ç¨æ· * * @param userName ç¨æ·å @@ -104,6 +120,14 @@ public int updateUser(SysUser user); /** * ç¨æ·ææè§è² * * @param userId ç¨æ·ID * @param roleIds è§è²ç» */ public void insertUserAuth(Long userId, Long[] roleIds); /** * ä¿®æ¹ç¨æ·ç¶æ * * @param user ç¨æ·ä¿¡æ¯ @@ -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); 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()); } /** * æ¯å¦ä¸ºparent_viewç»ä»¶ * * @param menu èåä¿¡æ¯ @@ -357,7 +387,7 @@ /** * æ ¹æ®ç¶èç¹çIDè·åææåèç¹ * * @param list å类表 * @param list å类表 * @param parentId ä¼ å ¥çç¶èç¹ID * @return String */ ruoyi-system/src/main/java/com/ruoyi/system/service/impl/SysRoleServiceImpl.java
@@ -60,6 +60,27 @@ } /** * æ ¹æ®ç¨æ·IDæ¥è¯¢è§è² * * @param userId ç¨æ·ID * @return è§è²å表 */ @Override public List<SysRole> selectRolesByUserId(Long userId) { List<SysRole> userRoles = baseMapper.selectRolePermissionByUserId(userId); List<SysRole> roles = selectRoleAll(); for (SysRole role : roles) { for (SysRole userRole : userRoles) { if (role.getRoleId().longValue() == userRole.getRoleId().longValue()) { role.setFlag(true); break; } } } return roles; } /** * æ ¹æ®ç¨æ·IDæ¥è¯¢æé * * @param userId ç¨æ·ID @@ -305,4 +326,51 @@ roleDeptMapper.delete(new LambdaQueryWrapper<SysRoleDept>().in(SysRoleDept::getRoleId, ids)); return baseMapper.deleteBatchIds(ids); } /** * åæ¶ææç¨æ·è§è² * * @param userRole ç¨æ·åè§è²å ³èä¿¡æ¯ * @return ç»æ */ @Override public int deleteAuthUser(SysUserRole userRole) { return userRoleMapper.delete(new LambdaQueryWrapper<SysUserRole>() .eq(SysUserRole::getRoleId, userRole.getRoleId()) .eq(SysUserRole::getUserId, userRole.getUserId())); } /** * æ¹éåæ¶ææç¨æ·è§è² * * @param roleId è§è²ID * @param userIds éè¦åæ¶ææçç¨æ·æ°æ®ID * @return ç»æ */ @Override public int deleteAuthUsers(Long roleId, Long[] userIds) { return userRoleMapper.delete(new LambdaQueryWrapper<SysUserRole>() .eq(SysUserRole::getRoleId, roleId) .in(SysUserRole::getUserId, Arrays.asList(userIds))); } /** * æ¹ééæ©ææç¨æ·è§è² * * @param roleId è§è²ID * @param userIds éè¦å é¤çç¨æ·æ°æ®ID * @return ç»æ */ @Override public int insertAuthUsers(Long roleId, Long[] userIds) { // æ°å¢ç¨æ·ä¸è§è²ç®¡ç List<SysUserRole> list = new ArrayList<SysUserRole>(); for (Long userId : userIds) { SysUserRole ur = new SysUserRole(); ur.setUserId(userId); ur.setRoleId(roleId); list.add(ur); } return userRoleMapper.insertAll(list); } } ruoyi-system/src/main/java/com/ruoyi/system/service/impl/SysUserServiceImpl.java
@@ -70,6 +70,30 @@ } /** * æ ¹æ®æ¡ä»¶å页æ¥è¯¢å·²åé ç¨æ·è§è²å表 * * @param user ç¨æ·ä¿¡æ¯ * @return ç¨æ·ä¿¡æ¯éåä¿¡æ¯ */ @Override @DataScope(deptAlias = "d", userAlias = "u", isUser = true) public TableDataInfo<SysUser> selectAllocatedList(SysUser user) { return PageUtils.buildDataInfo(baseMapper.selectAllocatedList(PageUtils.buildPage(), user)); } /** * æ ¹æ®æ¡ä»¶å页æ¥è¯¢æªåé ç¨æ·è§è²å表 * * @param user ç¨æ·ä¿¡æ¯ * @return ç¨æ·ä¿¡æ¯éåä¿¡æ¯ */ @Override @DataScope(deptAlias = "d", userAlias = "u", isUser = true) public TableDataInfo<SysUser> selectUnallocatedList(SysUser user) { return PageUtils.buildDataInfo(baseMapper.selectUnallocatedList(PageUtils.buildPage(), user)); } /** * éè¿ç¨æ·åæ¥è¯¢ç¨æ· * * @param userName ç¨æ·å @@ -232,6 +256,21 @@ } /** * ç¨æ·ææè§è² * * @param userId ç¨æ·ID * @param roleIds è§è²ç» */ @Override @Transactional public void insertUserAuth(Long userId, Long[] roleIds) { userRoleMapper.delete(new LambdaQueryWrapper<SysUserRole>() .eq(SysUserRole::getUserId, userId)); insertUserRole(userId, roleIds); } /** * ä¿®æ¹ç¨æ·ç¶æ * * @param user ç¨æ·ä¿¡æ¯ @@ -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 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} ruoyi-ui/.env.development
@@ -7,5 +7,8 @@ # è¥ä¾ç®¡çç³»ç»/å¼åç¯å¢ VUE_APP_BASE_API = '/dev-api' # çæ§å°å VUE_APP_MONITRO_ADMIN = 'http://localhost:9090/admin/login' # è·¯ç±æå è½½ VUE_CLI_BABEL_TRANSPILE_MODULES = true ruoyi-ui/.env.production
@@ -4,5 +4,8 @@ # ç产ç¯å¢é ç½® ENV = 'production' # çæ§å°å VUE_APP_MONITRO_ADMIN = '/admin/login' # è¥ä¾ç®¡çç³»ç»/ç产ç¯å¢ VUE_APP_BASE_API = '/prod-api' ruoyi-ui/.env.staging
@@ -6,5 +6,8 @@ # æµè¯ç¯å¢é ç½® ENV = 'staging' # çæ§å°å VUE_APP_MONITRO_ADMIN = '/admin/login' # è¥ä¾ç®¡çç³»ç»/æµè¯ç¯å¢ VUE_APP_BASE_API = '/stage-api' ruoyi-ui/package.json
@@ -1,6 +1,6 @@ { "name": "ruoyi-vue-plus", "version": "2.4.0", "version": "2.5.0", "description": "RuoYi-Vue-Plusåå°ç®¡çç³»ç»", "author": "LionLi", "license": "MIT", ruoyi-ui/src/api/system/role.js
@@ -72,4 +72,50 @@ method: 'get', params: query }) } } // æ¥è¯¢è§è²å·²ææç¨æ·å表 export function allocatedUserList(query) { return request({ url: '/system/role/authUser/allocatedList', method: 'get', params: query }) } // æ¥è¯¢è§è²æªææç¨æ·å表 export function unallocatedUserList(query) { return request({ url: '/system/role/authUser/unallocatedList', method: 'get', params: query }) } // åæ¶ç¨æ·ææè§è² export function authUserCancel(data) { return request({ url: '/system/role/authUser/cancel', method: 'put', data: data }) } // æ¹éåæ¶ç¨æ·ææè§è² export function authUserCancelAll(data) { return request({ url: '/system/role/authUser/cancelAll', method: 'put', params: data }) } // ææç¨æ·éæ© export function authUserSelectAll(data) { return request({ url: '/system/role/authUser/selectAll', method: 'put', params: data }) } ruoyi-ui/src/api/system/user.js
@@ -125,3 +125,20 @@ method: 'get' }) } // æ¥è¯¢ææè§è² export function getAuthRole(userId) { return request({ url: '/system/user/authRole/' + userId, method: 'get' }) } // ä¿åææè§è² export function updateAuthRole(data) { return request({ url: '/system/user/authRole', method: 'put', params: data }) } ruoyi-ui/src/assets/styles/ruoyi.scss
@@ -53,6 +53,13 @@ margin-left: 20px; } .h1, .h2, .h3, .h4, .h5, .h6, h1, h2, h3, h4, h5, h6 { font-family: inherit; font-weight: 500; line-height: 1.1; color: inherit; } .el-dialog:not(.is-fullscreen){ margin-top: 6vh !important; } @@ -120,6 +127,17 @@ width: inherit; } /** è¡¨æ ¼æ´å¤æä½ä¸ææ ·å¼ */ .el-table .el-dropdown-link { cursor: pointer; color: #1890ff; margin-left: 5px; } .el-table .el-dropdown, .el-icon-arrow-down { font-size: 12px; } .el-tree-node__content > .el-checkbox { margin-right: 8px; } ruoyi-ui/src/components/Editor/index.vue
@@ -9,7 +9,7 @@ :headers="headers" style="display: none" ref="upload" v-if="this.uploadUrl" v-if="this.type == 'url'" > </el-upload> <div class="editor" ref="editor" :style="styles"></div> @@ -46,14 +46,15 @@ type: Boolean, default: false, }, /* ä¸ä¼ å°å */ uploadUrl: { /* ç±»åï¼base64æ ¼å¼ãurlæ ¼å¼ï¼ */ type: { type: String, default: "", default: "url", } }, data() { return { uploadUrl: process.env.VUE_APP_BASE_API + "/common/upload", // ä¸ä¼ çå¾çæå¡å¨å°å headers: { Authorization: "Bearer " + getToken() }, @@ -119,7 +120,7 @@ const editor = this.$refs.editor; this.Quill = new Quill(editor, this.options); // å¦æè®¾ç½®äºä¸ä¼ å°ååèªå®ä¹å¾çä¸ä¼ äºä»¶ if (this.uploadUrl) { if (this.type == 'url') { let toolbar = this.Quill.getModule("toolbar"); toolbar.addHandler("image", (value) => { this.uploadType = "image"; @@ -165,7 +166,7 @@ // è·åå æ æå¨ä½ç½® let length = quill.getSelection().index; // æå ¥å¾ç res.url为æå¡å¨è¿åçå¾çå°å quill.insertEmbed(length, "image", res.url); quill.insertEmbed(length, "image", process.env.VUE_APP_BASE_API + res.data.fileName); // è°æ´å æ å°æå quill.setSelection(length + 1); } else { ruoyi-ui/src/components/FileUpload/index.vue
@@ -4,7 +4,7 @@ :action="uploadFileUrl" :before-upload="handleBeforeUpload" :file-list="fileList" :limit="1" :limit="limit" :on-error="handleUploadError" :on-exceed="handleExceed" :on-success="handleUploadSuccess" @@ -26,8 +26,8 @@ <!-- æä»¶å表 --> <transition-group class="upload-file-list el-upload-list el-upload-list--text" name="el-fade-in-linear" tag="ul"> <li :key="file.uid" class="el-upload-list__item ele-upload-list__item-content" v-for="(file, index) in list"> <el-link :href="file.url" :underline="false" target="_blank"> <li :key="file.uid" class="el-upload-list__item ele-upload-list__item-content" v-for="(file, index) in fileList"> <el-link :href="`${baseUrl}${file.url}`" :underline="false" target="_blank"> <span class="el-icon-document"> {{ getFileName(file.name) }} </span> </el-link> <div class="ele-upload-list__item-content-action"> @@ -42,9 +42,15 @@ import { getToken } from "@/utils/auth"; export default { name: "FileUpload", props: { // å¼ value: [String, Object, Array], // æ°ééå¶ limit: { type: Number, default: 5, }, // 大å°éå¶(MB) fileSize: { type: Number, @@ -63,6 +69,7 @@ }, data() { return { baseUrl: process.env.VUE_APP_BASE_API, uploadFileUrl: process.env.VUE_APP_BASE_API + "/common/upload", // ä¸ä¼ çå¾çæå¡å¨å°å headers: { Authorization: "Bearer " + getToken(), @@ -70,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> ruoyi-ui/src/components/HeaderSearch/index.vue
@@ -70,9 +70,11 @@ this.show = false }, change(val) { const path = val.path; if(this.ishttp(val.path)) { // http(s):// è·¯å¾æ°çªå£æå¼ window.open(val.path, "_blank"); const pindex = path.indexOf("http"); window.open(path.substr(pindex, path.length), "_blank"); } else { this.$router.push(val.path) } ruoyi-ui/src/components/ImageUpload/index.vue
@@ -5,33 +5,38 @@ list-type="picture-card" :on-success="handleUploadSuccess" :before-upload="handleBeforeUpload" :limit="limit" :on-error="handleUploadError" :on-exceed="handleExceed" name="file" :show-file-list="false" :on-remove="handleRemove" :show-file-list="true" :headers="headers" style="display: inline-block; vertical-align: top" :file-list="fileList" :on-preview="handlePictureCardPreview" :class="{hide: this.fileList.length >= this.limit}" > <el-image v-if="!value" :src="value"> <div slot="error" class="image-slot"> <i class="el-icon-plus" /> </div> </el-image> <div v-else class="image"> <el-image :src="value" :style="`width:150px;height:150px;`" fit="fill"/> <div class="mask"> <div class="actions"> <span title="é¢è§" @click.stop="dialogVisible = true"> <i class="el-icon-zoom-in" /> </span> <span title="ç§»é¤" @click.stop="removeImage"> <i class="el-icon-delete" /> </span> </div> </div> </div> <i class="el-icon-plus"></i> </el-upload> <el-dialog :visible.sync="dialogVisible" title="é¢è§" width="800" append-to-body> <img :src="value" style="display: block; max-width: 100%; margin: 0 auto;"> <!-- ä¸ä¼ æç¤º --> <div class="el-upload__tip" slot="tip" v-if="showTip"> 请ä¸ä¼ <template v-if="fileSize"> 大å°ä¸è¶ è¿ <b style="color: #f56c6c">{{ fileSize }}MB</b> </template> <template v-if="fileType"> æ ¼å¼ä¸º <b style="color: #f56c6c">{{ fileType.join("/") }}</b> </template> çæä»¶ </div> <el-dialog :visible.sync="dialogVisible" title="é¢è§" width="800" append-to-body > <img :src="dialogImageUrl" style="display: block; max-width: 100%; margin: 0 auto" /> </el-dialog> </div> </template> @@ -40,36 +45,128 @@ import { getToken } from "@/utils/auth"; export default { props: { value: [String, Object, Array], // å¾çæ°ééå¶ limit: { type: Number, default: 5, }, // 大å°éå¶(MB) fileSize: { type: Number, default: 5, }, // æä»¶ç±»å, ä¾å¦['png', 'jpg', 'jpeg'] fileType: { type: Array, default: () => ["png", "jpg", "jpeg"], }, // æ¯å¦æ¾ç¤ºæç¤º isShowTip: { type: Boolean, default: true } }, data() { return { dialogImageUrl: "", dialogVisible: false, hideUpload: false, baseUrl: process.env.VUE_APP_BASE_API, uploadImgUrl: process.env.VUE_APP_BASE_API + "/common/upload", // ä¸ä¼ çå¾çæå¡å¨å°å headers: { Authorization: "Bearer " + getToken(), }, fileList: [] }; }, props: { watch: { value: { type: String, default: "", handler(val) { if (val) { // é¦å å°å¼è½¬ä¸ºæ°ç» const list = Array.isArray(val) ? val : this.value.split(','); // ç¶åå°æ°ç»è½¬ä¸ºå¯¹è±¡æ°ç» this.fileList = list.map(item => { if (typeof item === "string") { if (item.indexOf(this.baseUrl) === -1) { item = { name: this.baseUrl + item, url: this.baseUrl + item }; } else { item = { name: item, url: item }; } } return item; }); } else { this.fileList = []; return []; } }, deep: true, immediate: true } }, computed: { // æ¯å¦æ¾ç¤ºæç¤º showTip() { return this.isShowTip && (this.fileType || this.fileSize); }, }, methods: { removeImage() { this.$emit("input", ""); // å é¤å¾ç handleRemove(file, fileList) { const findex = this.fileList.indexOf(file.name); this.fileList.splice(findex, 1); this.$emit("input", this.listToString(this.fileList)); }, // ä¸ä¼ æååè° handleUploadSuccess(res) { this.$emit("input", res.data.url); this.fileList.push({ name: res.data.fileName, url: res.data.fileName }); this.$emit("input", this.listToString(this.fileList)); this.loading.close(); }, handleBeforeUpload() { // ä¸ä¼ åloadingå è½½ handleBeforeUpload(file) { let isImg = false; if (this.fileType.length) { let fileExtension = ""; if (file.name.lastIndexOf(".") > -1) { fileExtension = file.name.slice(file.name.lastIndexOf(".") + 1); } isImg = this.fileType.some(type => { if (file.type.indexOf(type) > -1) return true; if (fileExtension && fileExtension.indexOf(type) > -1) return true; return false; }); } else { isImg = file.type.indexOf("image") > -1; } if (!isImg) { this.$message.error( `æä»¶æ ¼å¼ä¸æ£ç¡®, 请ä¸ä¼ ${this.fileType.join("/")}å¾çæ ¼å¼æä»¶!` ); return false; } if (this.fileSize) { const isLt = file.size / 1024 / 1024 < this.fileSize; if (!isLt) { this.$message.error(`ä¸ä¼ 头åå¾ç大å°ä¸è½è¶ è¿ ${this.fileSize} MB!`); return false; } } this.loading = this.$loading({ lock: true, text: "ä¸ä¼ ä¸", background: "rgba(0, 0, 0, 0.7)", }); }, // æä»¶ä¸ªæ°è¶ åº handleExceed() { this.$message.error(`ä¸ä¼ æä»¶æ°éä¸è½è¶ è¿ ${this.limit} 个!`); }, // ä¸ä¼ 失败 handleUploadError() { this.$message({ type: "error", @@ -77,24 +174,37 @@ }); this.loading.close(); }, }, 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> 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; } ruoyi-ui/src/directive/dialog/drag.js
¶Ô±ÈÐÂÎļþ @@ -0,0 +1,64 @@ /** * v-dialogDrag å¼¹çªææ½ * Copyright (c) 2019 ruoyi */ export default { bind(el, binding, vnode, oldVnode) { const value = binding.value if (value == false) return // è·åææ½å å®¹å¤´é¨ const dialogHeaderEl = el.querySelector('.el-dialog__header'); const dragDom = el.querySelector('.el-dialog'); dialogHeaderEl.style.cursor = 'move'; // è·ååæå±æ§ ie domå ç´ .currentStyle ç«çè°·æ window.getComputedStyle(domå ç´ , null); const sty = dragDom.currentStyle || window.getComputedStyle(dragDom, null); dragDom.style.position = 'absolute'; dragDom.style.marginTop = 0; let width = dragDom.style.width; if (width.includes('%')) { width = +document.body.clientWidth * (+width.replace(/\%/g, '') / 100); } else { width = +width.replace(/\px/g, ''); } dragDom.style.left = `${(document.body.clientWidth - width) / 2}px`; // é¼ æ æä¸äºä»¶ dialogHeaderEl.onmousedown = (e) => { // é¼ æ æä¸ï¼è®¡ç®å½åå ç´ è·ç¦»å¯è§åºçè·ç¦» (é¼ æ ç¹å»ä½ç½®è·ç¦»å¯è§çªå£çè·ç¦») const disX = e.clientX - dialogHeaderEl.offsetLeft; const disY = e.clientY - dialogHeaderEl.offsetTop; // è·åå°çå¼å¸¦px æ£åå¹é æ¿æ¢ let styL, styT; // 注æå¨ieä¸ ç¬¬ä¸æ¬¡è·åå°çå¼ä¸ºç»ä»¶èªå¸¦50% ç§»å¨ä¹åèµå¼ä¸ºpx if (sty.left.includes('%')) { styL = +document.body.clientWidth * (+sty.left.replace(/\%/g, '') / 100); styT = +document.body.clientHeight * (+sty.top.replace(/\%/g, '') / 100); } else { styL = +sty.left.replace(/\px/g, ''); styT = +sty.top.replace(/\px/g, ''); }; // é¼ æ ææ½äºä»¶ document.onmousemove = function (e) { // éè¿äºä»¶å§æï¼è®¡ç®ç§»å¨çè·ç¦» ï¼å¼å§ææ½è³ç»æææ½çè·ç¦»ï¼ const l = e.clientX - disX; const t = e.clientY - disY; let finallyL = l + styL let finallyT = t + styT // ç§»å¨å½åå ç´ dragDom.style.left = `${finallyL}px`; dragDom.style.top = `${finallyT}px`; }; document.onmouseup = function (e) { document.onmousemove = null; document.onmouseup = null; }; } } }; ruoyi-ui/src/directive/index.js
¶Ô±ÈÐÂÎļþ @@ -0,0 +1,18 @@ import hasRole from './permission/hasRole' import hasPermi from './permission/hasPermi' import dialogDrag from './dialog/drag' const install = function(Vue) { Vue.directive('hasRole', hasRole) Vue.directive('hasPermi', hasPermi) Vue.directive('dialogDrag', dialogDrag) } if (window.Vue) { window['hasRole'] = hasRole window['hasPermi'] = hasPermi window['dialogDrag'] = dialogDrag Vue.use(install); // eslint-disable-line } export default install ruoyi-ui/src/directive/permission/hasPermi.js
@@ -1,8 +1,8 @@ /** * æä½æéå¤ç * v-hasPermi æä½æéå¤ç * Copyright (c) 2019 ruoyi */ import store from '@/store' export default { ruoyi-ui/src/directive/permission/hasRole.js
@@ -1,8 +1,8 @@ /** * è§è²æéå¤ç * v-hasRole è§è²æéå¤ç * Copyright (c) 2019 ruoyi */ import store from '@/store' export default { ruoyi-ui/src/directive/permission/index.js
ÎļþÒÑɾ³ý ruoyi-ui/src/layout/components/AppMain.vue
@@ -51,7 +51,7 @@ // fix css style bug in open el-dialog .el-popup-parent--hidden { .fixed-header { padding-right: 15px; padding-right: 17px; } } </style> ruoyi-ui/src/layout/components/InnerLink/index.vue
¶Ô±ÈÐÂÎļþ @@ -0,0 +1,27 @@ <script> export default { data() { return {}; }, render() { const { $route: { meta: { link } }, } = this; if ({ link }.link === "") { return "404"; } let url = { link }.link; const height = document.documentElement.clientHeight - 94.5 + "px"; const style = { height: height }; return ( <div style={style}> <iframe src={url} frameborder="no" style="width: 100%; height: 100%" scrolling="auto" ></iframe> </div> ); }, }; </script> ruoyi-ui/src/main.js
@@ -10,7 +10,7 @@ import App from './App' import store from './store' import router from './router' import permission from './directive/permission' import directive from './directive' //directive import './assets/icons' // icon import './permission' // permission control @@ -20,6 +20,12 @@ import Pagination from "@/components/Pagination"; // èªå®ä¹è¡¨æ ¼å·¥å ·ç»ä»¶ import RightToolbar from "@/components/RightToolbar" // 坿æ¬ç»ä»¶ import Editor from "@/components/Editor" // æä»¶ä¸ä¼ ç»ä»¶ import FileUpload from "@/components/FileUpload" // å¾çä¸ä¼ ç»ä»¶ import ImageUpload from "@/components/ImageUpload" // åå ¸æ ç¾ç»ä»¶ import DictTag from '@/components/DictTag' // 头鍿 ç¾ç»ä»¶ @@ -52,8 +58,11 @@ Vue.component('DictTag', DictTag) Vue.component('Pagination', Pagination) Vue.component('RightToolbar', RightToolbar) Vue.component('Editor', Editor) Vue.component('FileUpload', FileUpload) Vue.component('ImageUpload', ImageUpload) Vue.use(permission) Vue.use(directive) Vue.use(VueMeta) /** ruoyi-ui/src/router/index.js
@@ -6,6 +6,7 @@ /* Layout */ import Layout from '@/layout' import ParentView from '@/components/ParentView'; import InnerLink from '@/layout/components/InnerLink' /** * Note: è·¯ç±é 置项 @@ -81,6 +82,32 @@ ] }, { path: '/auth', component: Layout, hidden: true, children: [ { path: 'role/:userId(\\d+)', component: (resolve) => require(['@/views/system/user/authRole'], resolve), name: 'AuthRole', meta: { title: 'åé è§è²'} } ] }, { path: '/auth', component: Layout, hidden: true, children: [ { path: 'user/:roleId(\\d+)', component: (resolve) => require(['@/views/system/role/authUser'], resolve), name: 'AuthUser', meta: { title: 'åé ç¨æ·'} } ] }, { path: '/dict', component: Layout, hidden: true, ruoyi-ui/src/store/modules/permission.js
@@ -2,6 +2,7 @@ import { getRouters } from '@/api/menu' import Layout from '@/layout/index' import ParentView from '@/components/ParentView'; import InnerLink from '@/layout/components/InnerLink' const permission = { state: { @@ -65,6 +66,8 @@ route.component = Layout } else if (route.component === 'ParentView') { route.component = ParentView } else if (route.component === 'InnerLink') { route.component = InnerLink } else { route.component = loadView(route.component) } ruoyi-ui/src/views/demo/demo/index.vue
@@ -304,17 +304,19 @@ this.buttonLoading = true; if (this.form.id != null) { updateDemo(this.form).then(response => { this.buttonLoading = false; this.msgSuccess("ä¿®æ¹æå"); this.open = false; this.getList(); }).finally(() => { this.buttonLoading = false; }); } else { addDemo(this.form).then(response => { this.buttonLoading = false; this.msgSuccess("æ°å¢æå"); this.open = false; this.getList(); }).finally(() => { this.buttonLoading = false; }); } } ruoyi-ui/src/views/demo/tree/index.vue
@@ -255,17 +255,19 @@ this.buttonLoading = true; if (this.form.id != null) { updateTree(this.form).then(response => { this.buttonLoading = false; this.msgSuccess("ä¿®æ¹æå"); this.open = false; this.getList(); }).finally(() => { this.buttonLoading = false; }); } else { addTree(this.form).then(response => { this.buttonLoading = false; this.msgSuccess("æ°å¢æå"); this.open = false; this.getList(); }).finally(() => { this.buttonLoading = false; }); } } ruoyi-ui/src/views/index.vue
@@ -91,6 +91,32 @@ <span>æ´æ°æ¥å¿</span> </div> <el-collapse accordion> <el-collapse-item title="v2.5.0 - 2021-7-12"> <ol> <li>update springboot 2.4.7 => 2.4.8</li> <li>update knife4j 3.0.2 => 3.0.3</li> <li>update hutool 5.7.2 => 5.7.4</li> <li>update spring-boot-admin 2.4.1 => 2.4.3</li> <li>update redisson 3.15.2 => 3.16.0</li> <li>add å¢å docker ç¼æ ä¸ shell èæ¬</li> <li>add å¢å feign çæ èªå®ä¹ç»æä½è§£ææ¹æ³ ä¸ demo 注é</li> <li>add ç¨æ·ç®¡çæ°å¢åé è§è²åè½</li> <li>add è§è²ç®¡çæ°å¢åé ç¨æ·åè½</li> <li>add å¢å spring-cacheæ¼ç¤ºæ¡ä¾</li> <li>update ç¬ç« springboot-admin çæ§å°æ©å±æ¨¡å项ç®</li> <li>update springboot-admin çæ§ å¢å ç¨æ·ç»å½æé管ç</li> <li>update ä¼å代ç çæå¨ æ¹éå¯¼å ¥</li> <li>update ä¼å å¢å MPæ³¨å ¥å¼å¸¸æ¦æª</li> <li>update å ³éé»è®¤äºçº§ç¼å æ¨èä½¿ç¨ spring-cache 注解æå¨ç¼å</li> <li>update FileUpload ImageUploadç»ä»¶ æ¯æå¤å¾çä¸ä¼ </li> <li>update ä¼åä¸è±æè¯è¨é ç½®</li> <li>update è§èmavenåæ³</li> <li>fix redisè·åmap屿§bugä¿®å¤ã</li> <li>fix ä¿®å¤ æé®loading å端500塿»é®é¢</li> <li>fix ç¸å¯¹è·¯å¾ä¸è½½é®é¢</li> <li>fix ä¿®å¤ hutool å·¥å ·è¿åç»æä¸ä¸è´é®é¢</li> </ol> </el-collapse-item> <el-collapse-item title="v2.4.0 - 2021-6-24"> <ol> <li>update springboot 2.3.11 => 2.4.7</li> ruoyi-ui/src/views/monitor/admin/index.vue
@@ -1,26 +1,16 @@ <template> <div v-loading="loading" :style="'height:'+ height"> <iframe :src="src" frameborder="no" style="width: 100%;height: 100%" scrolling="auto" /> </div> <i-frame :src="url" /> </template> <script> import iFrame from "@/components/iFrame/index"; export default { name: "Admin", components: { iFrame }, data() { console.log(process.env) return { src: process.env.VUE_APP_BASE_API + "/admin", height: document.documentElement.clientHeight - 94.5 + "px;", loading: true url: process.env.VUE_APP_MONITRO_ADMIN }; }, mounted: function() { setTimeout(() => { this.loading = false; }, 230); const that = this; window.onresize = function temp() { that.height = document.documentElement.clientHeight - 94.5 + "px;"; }; } }; </script> ruoyi-ui/src/views/system/notice/index.vue
@@ -177,13 +177,9 @@ <script> import { listNotice, getNotice, delNotice, addNotice, updateNotice } from "@/api/system/notice"; import Editor from '@/components/Editor'; export default { name: "Notice", components: { Editor }, data() { return { // é®ç½©å± ruoyi-ui/src/views/system/role/authUser.vue
¶Ô±ÈÐÂÎļþ @@ -0,0 +1,213 @@ <template> <div class="app-container"> <el-form :model="queryParams" ref="queryForm" v-show="showSearch" :inline="true"> <el-form-item label="ç¨æ·åç§°" prop="userName"> <el-input v-model="queryParams.userName" placeholder="请è¾å ¥ç¨æ·åç§°" clearable size="small" style="width: 240px" @keyup.enter.native="handleQuery" /> </el-form-item> <el-form-item label="ææºå·ç " prop="phonenumber"> <el-input v-model="queryParams.phonenumber" placeholder="请è¾å ¥ææºå·ç " clearable size="small" style="width: 240px" @keyup.enter.native="handleQuery" /> </el-form-item> <el-form-item> <el-button type="primary" icon="el-icon-search" size="mini" @click="handleQuery">æç´¢</el-button> <el-button icon="el-icon-refresh" size="mini" @click="resetQuery">éç½®</el-button> </el-form-item> </el-form> <el-row :gutter="10" class="mb8"> <el-col :span="1.5"> <el-button type="primary" plain icon="el-icon-plus" size="mini" @click="openSelectUser" v-hasPermi="['system:role:add']" >æ·»å ç¨æ·</el-button> </el-col> <el-col :span="1.5"> <el-button type="danger" plain icon="el-icon-circle-close" size="mini" :disabled="multiple" @click="cancelAuthUserAll" v-hasPermi="['system:role:remove']" >æ¹éåæ¶ææ</el-button> </el-col> <el-col :span="1.5"> <el-button type="warning" plain icon="el-icon-close" size="mini" @click="handleClose" >å ³é</el-button> </el-col> <right-toolbar :showSearch.sync="showSearch" @queryTable="getList"></right-toolbar> </el-row> <el-table v-loading="loading" :data="userList" @selection-change="handleSelectionChange"> <el-table-column type="selection" width="55" align="center" /> <el-table-column label="ç¨æ·åç§°" prop="userName" :show-overflow-tooltip="true" /> <el-table-column label="ç¨æ·æµç§°" prop="nickName" :show-overflow-tooltip="true" /> <el-table-column label="é®ç®±" prop="email" :show-overflow-tooltip="true" /> <el-table-column label="ææº" prop="phonenumber" :show-overflow-tooltip="true" /> <el-table-column label="ç¶æ" align="center" prop="status"> <template slot-scope="scope"> <dict-tag :options="statusOptions" :value="scope.row.status"/> </template> </el-table-column> <el-table-column label="å建æ¶é´" align="center" prop="createTime" width="180"> <template slot-scope="scope"> <span>{{ parseTime(scope.row.createTime) }}</span> </template> </el-table-column> <el-table-column label="æä½" align="center" class-name="small-padding fixed-width"> <template slot-scope="scope"> <el-button size="mini" type="text" icon="el-icon-circle-close" @click="cancelAuthUser(scope.row)" v-hasPermi="['system:role:remove']" >åæ¶ææ</el-button> </template> </el-table-column> </el-table> <pagination v-show="total>0" :total="total" :page.sync="queryParams.pageNum" :limit.sync="queryParams.pageSize" @pagination="getList" /> <select-user ref="select" :roleId="queryParams.roleId" @ok="handleQuery" /> </div> </template> <script> import { allocatedUserList, authUserCancel, authUserCancelAll } from "@/api/system/role"; import selectUser from "./selectUser"; export default { name: "AuthUser", components: { selectUser }, data() { return { // é®ç½©å± loading: true, // éä¸ç¨æ·ç» userIds: [], // éå¤ä¸ªç¦ç¨ multiple: true, // æ¾ç¤ºæç´¢æ¡ä»¶ showSearch: true, // æ»æ¡æ° total: 0, // ç¨æ·è¡¨æ ¼æ°æ® userList: [], // ç¶ææ°æ®åå ¸ statusOptions: [], // æ¥è¯¢åæ° queryParams: { pageNum: 1, pageSize: 10, roleId: undefined, userName: undefined, phonenumber: undefined } }; }, created() { const roleId = this.$route.params && this.$route.params.roleId; if (roleId) { this.queryParams.roleId = roleId; this.getList(); this.getDicts("sys_normal_disable").then(response => { this.statusOptions = response.data; }); } }, methods: { /** æ¥è¯¢ææç¨æ·å表 */ getList() { this.loading = true; allocatedUserList(this.queryParams).then(response => { this.userList = response.rows; this.total = response.total; this.loading = false; } ); }, // è¿åæé® handleClose() { this.$store.dispatch("tagsView/delView", this.$route); this.$router.push({ path: "/system/role" }); }, /** æç´¢æé®æä½ */ handleQuery() { this.queryParams.pageNum = 1; this.getList(); }, /** éç½®æé®æä½ */ resetQuery() { this.resetForm("queryForm"); this.handleQuery(); }, // å¤éæ¡é䏿°æ® handleSelectionChange(selection) { this.userIds = selection.map(item => item.userId) this.multiple = !selection.length }, /** æå¼ææç¨æ·è¡¨å¼¹çª */ openSelectUser() { this.$refs.select.show(); }, /** åæ¶æææé®æä½ */ cancelAuthUser(row) { const roleId = this.queryParams.roleId; this.$confirm('确认è¦åæ¶è¯¥ç¨æ·"' + row.userName + '"è§è²åï¼', "è¦å", { confirmButtonText: "ç¡®å®", cancelButtonText: "åæ¶", type: "warning" }).then(function() { return authUserCancel({ userId: row.userId, roleId: roleId }); }).then(() => { this.getList(); this.msgSuccess("åæ¶æææå"); }).catch(() => {}); }, /** æ¹éåæ¶æææé®æä½ */ cancelAuthUserAll(row) { const roleId = this.queryParams.roleId; const userIds = this.userIds.join(","); this.$confirm('æ¯å¦åæ¶éä¸ç¨æ·æææ°æ®é¡¹?', "è¦å", { confirmButtonText: "ç¡®å®", cancelButtonText: "åæ¶", type: "warning" }).then(() => { return authUserCancelAll({ roleId: roleId, userIds: userIds }); }).then(() => { this.getList(); this.msgSuccess("åæ¶æææå"); }).catch(() => {}); } } }; </script> ruoyi-ui/src/views/system/role/index.vue
@@ -124,7 +124,7 @@ </template> </el-table-column> <el-table-column label="æä½" align="center" class-name="small-padding fixed-width"> <template slot-scope="scope"> <template slot-scope="scope" v-if="scope.row.roleId !== 1"> <el-button size="mini" type="text" @@ -135,17 +135,21 @@ <el-button size="mini" type="text" icon="el-icon-circle-check" @click="handleDataScope(scope.row)" v-hasPermi="['system:role:edit']" >æ°æ®æé</el-button> <el-button size="mini" type="text" icon="el-icon-delete" @click="handleDelete(scope.row)" v-hasPermi="['system:role:remove']" >å é¤</el-button> <el-dropdown size="mini" @command="(command) => handleCommand(command, scope.row)"> <span class="el-dropdown-link"> <i class="el-icon-d-arrow-right el-icon--right"></i>æ´å¤ </span> <el-dropdown-menu slot="dropdown"> <el-dropdown-item command="handleDataScope" icon="el-icon-circle-check" v-hasPermi="['system:role:edit']">æ°æ®æé</el-dropdown-item> <el-dropdown-item command="handleAuthUser" icon="el-icon-user" v-hasPermi="['system:role:edit']">åé ç¨æ·</el-dropdown-item> </el-dropdown-menu> </el-dropdown> </template> </el-table-column> </el-table> @@ -469,6 +473,19 @@ this.single = selection.length!=1 this.multiple = !selection.length }, // æ´å¤æä½è§¦å handleCommand(command, row) { switch (command) { case "handleDataScope": this.handleDataScope(row); break; case "handleAuthUser": this.handleAuthUser(row); break; default: break; } }, // æ æéï¼å±å¼/æå ï¼ handleCheckedTreeExpand(value, type) { if (type == 'menu') { @@ -548,6 +565,11 @@ this.title = "åé æ°æ®æé"; }); }, /** åé ç¨æ·æä½ */ handleAuthUser: function(row) { const roleId = row.roleId; this.$router.push("/auth/user/" + roleId); }, /** æäº¤æé® */ submitForm: function() { this.$refs["form"].validate(valid => { ruoyi-ui/src/views/system/role/selectUser.vue
¶Ô±ÈÐÂÎļþ @@ -0,0 +1,142 @@ <template> <!-- ææç¨æ· --> <el-dialog title="éæ©ç¨æ·" :visible.sync="visible" width="800px" top="5vh" append-to-body> <el-form :model="queryParams" ref="queryForm" :inline="true"> <el-form-item label="ç¨æ·åç§°" prop="userName"> <el-input v-model="queryParams.userName" placeholder="请è¾å ¥ç¨æ·åç§°" clearable size="small" @keyup.enter.native="handleQuery" /> </el-form-item> <el-form-item label="ææºå·ç " prop="phonenumber"> <el-input v-model="queryParams.phonenumber" placeholder="请è¾å ¥ææºå·ç " clearable size="small" @keyup.enter.native="handleQuery" /> </el-form-item> <el-form-item> <el-button type="primary" icon="el-icon-search" size="mini" @click="handleQuery">æç´¢</el-button> <el-button icon="el-icon-refresh" size="mini" @click="resetQuery">éç½®</el-button> </el-form-item> </el-form> <el-row> <el-table @row-click="clickRow" ref="table" :data="userList" @selection-change="handleSelectionChange" height="260px"> <el-table-column type="selection" width="55"></el-table-column> <el-table-column label="ç¨æ·åç§°" prop="userName" :show-overflow-tooltip="true" /> <el-table-column label="ç¨æ·æµç§°" prop="nickName" :show-overflow-tooltip="true" /> <el-table-column label="é®ç®±" prop="email" :show-overflow-tooltip="true" /> <el-table-column label="ææº" prop="phonenumber" :show-overflow-tooltip="true" /> <el-table-column label="ç¶æ" align="center" prop="status"> <template slot-scope="scope"> <dict-tag :options="statusOptions" :value="scope.row.status"/> </template> </el-table-column> <el-table-column label="å建æ¶é´" align="center" prop="createTime" width="180"> <template slot-scope="scope"> <span>{{ parseTime(scope.row.createTime) }}</span> </template> </el-table-column> </el-table> <pagination v-show="total>0" :total="total" :page.sync="queryParams.pageNum" :limit.sync="queryParams.pageSize" @pagination="getList" /> </el-row> <div slot="footer" class="dialog-footer"> <el-button type="primary" @click="handleSelectUser">ç¡® å®</el-button> <el-button @click="visible = false">å æ¶</el-button> </div> </el-dialog> </template> <script> import { unallocatedUserList, authUserSelectAll } from "@/api/system/role"; export default { props: { // è§è²ç¼å· roleId: { type: Number } }, data() { return { // é®ç½©å± visible: false, // é䏿°ç»å¼ userIds: [], // æ»æ¡æ° total: 0, // æªææç¨æ·æ°æ® userList: [], // ç¶ææ°æ®åå ¸ statusOptions: [], // æ¥è¯¢åæ° queryParams: { pageNum: 1, pageSize: 10, roleId: undefined, userName: undefined, phonenumber: undefined } }; }, created() { this.getDicts("sys_normal_disable").then(response => { this.statusOptions = response.data; }); }, methods: { // æ¾ç¤ºå¼¹æ¡ show() { this.queryParams.roleId = this.roleId; this.getList(); this.visible = true; }, clickRow(row) { this.$refs.table.toggleRowSelection(row); }, // å¤éæ¡é䏿°æ® handleSelectionChange(selection) { this.userIds = selection.map(item => item.userId); }, // æ¥è¯¢è¡¨æ°æ® getList() { unallocatedUserList(this.queryParams).then(res => { this.userList = res.rows; this.total = res.total; }); }, /** æç´¢æé®æä½ */ handleQuery() { this.queryParams.pageNum = 1; this.getList(); }, /** éç½®æé®æä½ */ resetQuery() { this.resetForm("queryForm"); this.handleQuery(); }, /** éæ©ææç¨æ·æä½ */ handleSelectUser() { const roleId = this.queryParams.roleId; const userIds = this.userIds.join(","); authUserSelectAll({ roleId: roleId, userIds: userIds }).then(res => { this.msgSuccess(res.msg); if (res.code === 200) { this.visible = false; this.$emit("ok"); } }); } } }; </script> ruoyi-ui/src/views/system/user/authRole.vue
¶Ô±ÈÐÂÎļþ @@ -0,0 +1,117 @@ <template> <div class="app-container"> <h4 class="form-header h4">åºæ¬ä¿¡æ¯</h4> <el-form ref="form" :model="form" label-width="80px"> <el-row> <el-col :span="8" :offset="2"> <el-form-item label="ç¨æ·æµç§°" prop="nickName"> <el-input v-model="form.nickName" disabled /> </el-form-item> </el-col> <el-col :span="8" :offset="2"> <el-form-item label="ç»å½è´¦å·" prop="phonenumber"> <el-input v-model="form.userName" disabled /> </el-form-item> </el-col> </el-row> </el-form> <h4 class="form-header h4">è§è²ä¿¡æ¯</h4> <el-table v-loading="loading" :row-key="getRowKey" @row-click="clickRow" ref="table" @selection-change="handleSelectionChange" :data="roles.slice((pageNum-1)*pageSize,pageNum*pageSize)"> <el-table-column label="åºå·" type="index" align="center"> <template slot-scope="scope"> <span>{{(pageNum - 1) * pageSize + scope.$index + 1}}</span> </template> </el-table-column> <el-table-column type="selection" :reserve-selection="true" width="55"></el-table-column> <el-table-column label="è§è²ç¼å·" align="center" prop="roleId" /> <el-table-column label="è§è²åç§°" align="center" prop="roleName" /> <el-table-column label="æéå符" align="center" prop="roleKey" /> <el-table-column label="å建æ¶é´" align="center" prop="createTime" width="180"> <template slot-scope="scope"> <span>{{ parseTime(scope.row.createTime) }}</span> </template> </el-table-column> </el-table> <pagination v-show="total>0" :total="total" :page.sync="pageNum" :limit.sync="pageSize" /> <el-form label-width="100px"> <el-form-item style="text-align: center;margin-left:-120px;margin-top:30px;"> <el-button type="primary" @click="submitForm()">æäº¤</el-button> <el-button @click="close()">è¿å</el-button> </el-form-item> </el-form> </div> </template> <script> import { getAuthRole, updateAuthRole } from "@/api/system/user"; export default { name: "AuthRole", data() { return { // é®ç½©å± loading: true, // åé¡µä¿¡æ¯ total: 0, pageNum: 1, pageSize: 10, // éä¸è§è²ç¼å· roleIds:[], // è§è²ä¿¡æ¯ roles: [], // ç¨æ·ä¿¡æ¯ form: {} }; }, created() { const userId = this.$route.params && this.$route.params.userId; if (userId) { this.loading = true; getAuthRole(userId).then((response) => { this.form = response.data.user; this.roles = response.data.roles; this.total = this.roles.length; this.$nextTick(() => { this.roles.forEach((row) => { if (row.flag) { this.$refs.table.toggleRowSelection(row); } }); }); this.loading = false; }); } }, methods: { /** åå»éä¸è¡æ°æ® */ clickRow(row) { this.$refs.table.toggleRowSelection(row); }, // å¤éæ¡é䏿°æ® handleSelectionChange(selection) { this.roleIds = selection.map((item) => item.roleId); }, // ä¿åéä¸çæ°æ®ç¼å· getRowKey(row) { return row.roleId; }, /** æäº¤æé® */ submitForm() { const userId = this.form.userId; const roleIds = this.roleIds.join(","); updateAuthRole({ userId: userId, roleIds: roleIds }).then((response) => { this.msgSuccess("æææå"); this.close(); }); }, /** å ³éæé® */ close() { this.$store.dispatch("tagsView/delView", this.$route); this.$router.push({ path: "/system/user" }); }, }, }; </script> ruoyi-ui/src/views/system/user/index.vue
@@ -167,7 +167,7 @@ width="160" class-name="small-padding fixed-width" > <template slot-scope="scope"> <template slot-scope="scope" v-if="scope.row.userId !== 1"> <el-button size="mini" type="text" @@ -176,20 +176,23 @@ v-hasPermi="['system:user:edit']" >ä¿®æ¹</el-button> <el-button v-if="scope.row.userId !== 1" size="mini" type="text" icon="el-icon-delete" @click="handleDelete(scope.row)" v-hasPermi="['system:user:remove']" >å é¤</el-button> <el-button size="mini" type="text" icon="el-icon-key" @click="handleResetPwd(scope.row)" v-hasPermi="['system:user:resetPwd']" >éç½®</el-button> <el-dropdown size="mini" @command="(command) => handleCommand(command, scope.row)"> <span class="el-dropdown-link"> <i class="el-icon-d-arrow-right el-icon--right"></i>æ´å¤ </span> <el-dropdown-menu slot="dropdown"> <el-dropdown-item command="handleResetPwd" icon="el-icon-key" v-hasPermi="['system:user:resetPwd']">éç½®å¯ç </el-dropdown-item> <el-dropdown-item command="handleAuthRole" icon="el-icon-circle-check" v-hasPermi="['system:user:edit']">åé è§è²</el-dropdown-item> </el-dropdown-menu> </el-dropdown> </template> </el-table-column> </el-table> @@ -561,6 +564,19 @@ this.single = selection.length != 1; this.multiple = !selection.length; }, // æ´å¤æä½è§¦å handleCommand(command, row) { switch (command) { case "handleResetPwd": this.handleResetPwd(row); break; case "handleAuthRole": this.handleAuthRole(row); break; default: break; } }, /** æ°å¢æé®æä½ */ handleAdd() { this.reset(); @@ -603,6 +619,11 @@ }); }).catch(() => {}); }, /** åé è§è²æä½ */ handleAuthRole: function(row) { const userId = row.userId; this.$router.push("/auth/role/" + userId); }, /** æäº¤æé® */ submitForm: function() { this.$refs["form"].validate(valid => { ruoyi-ui/vue.config.js
@@ -109,7 +109,7 @@ config.optimization.runtimeChunk('single'), { from: path.resolve(__dirname, './public/robots.txt'), //é²ç¬è«æä»¶ to: './', //å°æ ¹ç®å½ä¸ to: './' //å°æ ¹ç®å½ä¸ } } ) sql/ry_20210210.sql
@@ -159,7 +159,7 @@ insert into sys_menu values('1', 'ç³»ç»ç®¡ç', '0', '1', 'system', null, 1, 0, 'M', '0', '0', '', 'system', 'admin', sysdate(), '', null, 'ç³»ç»ç®¡çç®å½'); insert into sys_menu values('2', 'ç³»ç»çæ§', '0', '2', 'monitor', null, 1, 0, 'M', '0', '0', '', 'monitor', 'admin', sysdate(), '', null, 'ç³»ç»çæ§ç®å½'); insert into sys_menu values('3', 'ç³»ç»å·¥å ·', '0', '3', 'tool', null, 1, 0, 'M', '0', '0', '', 'tool', 'admin', sysdate(), '', null, 'ç³»ç»å·¥å ·ç®å½'); insert into sys_menu values('4', 'è¥ä¾å®ç½', '0', '4', 'http://ruoyi.vip', null , 0, 0, 'M', '0', '0', '', 'guide', 'admin', sysdate(), '', null, 'è¥ä¾å®ç½å°å'); insert into sys_menu values('4', 'PLUSå®ç½', '0', '4', 'https://gitee.com/JavaLionLi/RuoYi-Vue-Plus', null , 0, 0, 'M', '0', '0', '', 'guide', 'admin', sysdate(), '', null, 'RuoYi-Vue-Pluså®ç½å°å'); -- äºçº§èå insert into sys_menu values('100', 'ç¨æ·ç®¡ç', '1', '1', 'user', 'system/user/index', 1, 0, 'C', '0', '0', 'system:user:list', 'user', 'admin', sysdate(), '', null, 'ç¨æ·ç®¡çèå'); insert into sys_menu values('101', 'è§è²ç®¡ç', '1', '2', 'role', 'system/role/index', 1, 0, 'C', '0', '0', 'system:role:list', 'peoples', 'admin', sysdate(), '', null, 'è§è²ç®¡çèå'); @@ -685,4 +685,4 @@ update_by varchar(64) default '' comment 'æ´æ°è ', update_time datetime comment 'æ´æ°æ¶é´', primary key (column_id) ) engine=innodb auto_increment=1 comment = '代ç çæä¸å¡è¡¨å段'; ) engine=innodb auto_increment=1 comment = '代ç çæä¸å¡è¡¨å段';