From bbe71ec2c893abc4e8a4a983f48e416f9e3e94db Mon Sep 17 00:00:00 2001 From: YunaiV Date: Sat, 23 Jan 2021 22:03:06 +0800 Subject: [PATCH] =?UTF-8?q?=E5=AE=8C=E6=88=90=E9=83=A8=E5=88=86=E6=9D=83?= =?UTF-8?q?=E9=99=90=E7=9A=84=E8=AE=A4=E8=AF=81=E6=93=8D=E4=BD=9C=E7=9A=84?= =?UTF-8?q?=E8=BF=81=E7=A7=BB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- http-client.env.json | 2 +- .../controller/monitor/ServerController.java | 8 +- .../{tool => monitor}/SwaggerController.java | 48 ++++----- .../web/controller/monitor/SysUserOnline.java | 36 +++++-- .../ruoyi/common/annotation/DataScope.java | 5 +- .../ruoyi/common/annotation/DataSource.java | 8 +- .../ruoyi/common/annotation/RepeatSubmit.java | 6 +- .../ruoyi/common/constant/GenConstants.java | 3 +- .../ruoyi/common/filter/RepeatableFilter.java | 25 ++--- .../filter/RepeatedlyRequestWrapper.java | 30 ++---- .../ruoyi/framework/web/domain/Server.java | 40 ------- .../framework/web/domain/server/Cpu.java | 12 --- .../framework/web/domain/server/Jvm.java | 24 ----- .../framework/web/domain/server/Mem.java | 13 +-- .../framework/web/domain/server/Sys.java | 39 ------- .../framework/web/domain/server/SysFile.java | 55 ---------- .../web/service/PermissionService.java | 82 -------------- ruoyi-ui/src/utils/request.js | 4 +- ruoyi-ui/src/views/system/role/index.vue | 11 +- .../operatelog/core/aop/OperateLogAspect.java | 5 - .../handler/LogoutSuccessHandlerImpl.java | 4 +- ...java => SecurityAuthFrameworkService.java} | 20 ++-- .../SecurityPermissionFrameworkService.java | 26 +++++ .../core/handler/GlobalExceptionHandler.java | 19 +++- .../controller/auth/vo/SysAuthLoginReqVO.java | 4 +- .../controller/user/SysUserController.http | 3 + .../controller/user/SysUserController.java | 3 +- .../dao/permission/SysRoleMenuMapper.java | 10 +- .../SysRoleMenuRefreshConsumer.java | 29 +++++ .../permission/SysRoleMenuRefreshMessage.java | 17 +++ .../permission/SysPermissionProducer.java | 27 +++++ .../system/service/auth/SysAuthService.java | 4 +- .../service/permission/SysMenuService.java | 8 ++ .../permission/SysPermissionService.java | 5 +- .../service/permission/SysRoleService.java | 10 ++ .../permission/impl/SysMenuServiceImpl.java | 11 +- .../impl/SysPermissionServiceImpl.java | 102 ++++++++++++++++-- .../permission/impl/SysRoleServiceImpl.java | 4 + src/main/resources/application.yaml | 2 +- 39 files changed, 370 insertions(+), 394 deletions(-) rename ruoyi-admin/src/main/java/com/ruoyi/web/controller/{tool => monitor}/SwaggerController.java (96%) rename src/main/java/cn/iocoder/dashboard/framework/security/core/service/{SecurityFrameworkService.java => SecurityAuthFrameworkService.java} (80%) create mode 100644 src/main/java/cn/iocoder/dashboard/framework/security/core/service/SecurityPermissionFrameworkService.java create mode 100644 src/main/java/cn/iocoder/dashboard/modules/system/controller/user/SysUserController.http create mode 100644 src/main/java/cn/iocoder/dashboard/modules/system/mq/consumer/permission/SysRoleMenuRefreshConsumer.java create mode 100644 src/main/java/cn/iocoder/dashboard/modules/system/mq/message/permission/SysRoleMenuRefreshMessage.java create mode 100644 src/main/java/cn/iocoder/dashboard/modules/system/mq/producer/permission/SysPermissionProducer.java diff --git a/http-client.env.json b/http-client.env.json index 33c8482f6..761939bbe 100644 --- a/http-client.env.json +++ b/http-client.env.json @@ -1,6 +1,6 @@ { "local": { "baseUrl": "http://127.0.0.1:8080/api", - "token": "yudaoyuanma1" + "token": "test1" } } diff --git a/ruoyi-admin/src/main/java/com/ruoyi/web/controller/monitor/ServerController.java b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/monitor/ServerController.java index 082027beb..eaa41fdb5 100644 --- a/ruoyi-admin/src/main/java/com/ruoyi/web/controller/monitor/ServerController.java +++ b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/monitor/ServerController.java @@ -9,17 +9,15 @@ import com.ruoyi.framework.web.domain.Server; /** * 服务器监控 - * + * * @author ruoyi */ @RestController @RequestMapping("/monitor/server") -public class ServerController -{ +public class ServerController { @PreAuthorize("@ss.hasPermi('monitor:server:list')") @GetMapping() - public AjaxResult getInfo() throws Exception - { + public AjaxResult getInfo() throws Exception { Server server = new Server(); server.copyTo(); return AjaxResult.success(server); diff --git a/ruoyi-admin/src/main/java/com/ruoyi/web/controller/tool/SwaggerController.java b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/monitor/SwaggerController.java similarity index 96% rename from ruoyi-admin/src/main/java/com/ruoyi/web/controller/tool/SwaggerController.java rename to ruoyi-admin/src/main/java/com/ruoyi/web/controller/monitor/SwaggerController.java index e901fedd7..f66ca24ec 100644 --- a/ruoyi-admin/src/main/java/com/ruoyi/web/controller/tool/SwaggerController.java +++ b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/monitor/SwaggerController.java @@ -1,24 +1,24 @@ -package com.ruoyi.web.controller.tool; - -import org.springframework.security.access.prepost.PreAuthorize; -import org.springframework.stereotype.Controller; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.RequestMapping; -import com.ruoyi.common.core.controller.BaseController; - -/** - * swagger 接口 - * - * @author ruoyi - */ -@Controller -@RequestMapping("/tool/swagger") -public class SwaggerController extends BaseController -{ - @PreAuthorize("@ss.hasPermi('tool:swagger:view')") - @GetMapping() - public String index() - { - return redirect("/swagger-ui.html"); - } -} +package com.ruoyi.web.controller.tool; + +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.stereotype.Controller; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import com.ruoyi.common.core.controller.BaseController; + +/** + * swagger 接口 + * + * @author ruoyi + */ +@Controller +@RequestMapping("/tool/swagger") +public class SwaggerController extends BaseController +{ + @PreAuthorize("@ss.hasPermi('tool:swagger:view')") + @GetMapping() + public String index() + { + return redirect("/swagger-ui.html"); + } +} diff --git a/ruoyi-admin/src/main/java/com/ruoyi/web/controller/monitor/SysUserOnline.java b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/monitor/SysUserOnline.java index 63d43f6cd..86bbb2daf 100644 --- a/ruoyi-admin/src/main/java/com/ruoyi/web/controller/monitor/SysUserOnline.java +++ b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/monitor/SysUserOnline.java @@ -5,31 +5,45 @@ package com.ruoyi.system.domain; * * @author ruoyi */ -public class SysUserOnline -{ - /** 会话编号 */ +public class SysUserOnline { + /** + * 会话编号 + */ private String tokenId; - /** 部门名称 */ + /** + * 部门名称 + */ private String deptName; - /** 用户名称 */ + /** + * 用户名称 + */ private String userName; - /** 登录IP地址 */ + /** + * 登录IP地址 + */ private String ipaddr; - /** 登录地址 */ + /** + * 登录地址 + */ private String loginLocation; - /** 浏览器类型 */ + /** + * 浏览器类型 + */ private String browser; - /** 操作系统 */ + /** + * 操作系统 + */ private String os; - /** 登录时间 */ + /** + * 登录时间 + */ private Long loginTime; - } diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/annotation/DataScope.java b/ruoyi-common/src/main/java/com/ruoyi/common/annotation/DataScope.java index fe5a01f55..26d664f6e 100644 --- a/ruoyi-common/src/main/java/com/ruoyi/common/annotation/DataScope.java +++ b/ruoyi-common/src/main/java/com/ruoyi/common/annotation/DataScope.java @@ -8,14 +8,13 @@ import java.lang.annotation.Target; /** * 数据权限过滤注解 - * + * * @author ruoyi */ @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) @Documented -public @interface DataScope -{ +public @interface DataScope { /** * 部门表的别名 */ diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/annotation/DataSource.java b/ruoyi-common/src/main/java/com/ruoyi/common/annotation/DataSource.java index 6b41ee739..11b0d579c 100644 --- a/ruoyi-common/src/main/java/com/ruoyi/common/annotation/DataSource.java +++ b/ruoyi-common/src/main/java/com/ruoyi/common/annotation/DataSource.java @@ -6,21 +6,21 @@ import java.lang.annotation.Inherited; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; + import com.ruoyi.common.enums.DataSourceType; /** * 自定义多数据源切换注解 - * + *

* 优先级:先方法,后类,如果方法覆盖了类上的数据源类型,以方法的为准,否则以类上的为准 * * @author ruoyi */ -@Target({ ElementType.METHOD, ElementType.TYPE }) +@Target({ElementType.METHOD, ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) @Documented @Inherited -public @interface DataSource -{ +public @interface DataSource { /** * 切换数据源名称 */ diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/annotation/RepeatSubmit.java b/ruoyi-common/src/main/java/com/ruoyi/common/annotation/RepeatSubmit.java index 628eef1ef..5f81b40bb 100644 --- a/ruoyi-common/src/main/java/com/ruoyi/common/annotation/RepeatSubmit.java +++ b/ruoyi-common/src/main/java/com/ruoyi/common/annotation/RepeatSubmit.java @@ -9,15 +9,13 @@ import java.lang.annotation.Target; /** * 自定义注解防止表单重复提交 - * - * @author ruoyi * + * @author ruoyi */ @Inherited @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) @Documented -public @interface RepeatSubmit -{ +public @interface RepeatSubmit { } diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/constant/GenConstants.java b/ruoyi-common/src/main/java/com/ruoyi/common/constant/GenConstants.java index dbab26704..7284290ab 100644 --- a/ruoyi-common/src/main/java/com/ruoyi/common/constant/GenConstants.java +++ b/ruoyi-common/src/main/java/com/ruoyi/common/constant/GenConstants.java @@ -2,7 +2,7 @@ package com.ruoyi.common.constant; /** * 代码生成通用常量 - * + * * @author ruoyi */ public class GenConstants @@ -13,6 +13,7 @@ public class GenConstants /** 树表(增删改查) */ public static final String TPL_TREE = "tree"; + /** 树编码字段 */ public static final String TREE_CODE = "treeCode"; diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/filter/RepeatableFilter.java b/ruoyi-common/src/main/java/com/ruoyi/common/filter/RepeatableFilter.java index 3698c75c3..431a3cb62 100644 --- a/ruoyi-common/src/main/java/com/ruoyi/common/filter/RepeatableFilter.java +++ b/ruoyi-common/src/main/java/com/ruoyi/common/filter/RepeatableFilter.java @@ -8,45 +8,38 @@ import javax.servlet.ServletException; import javax.servlet.ServletRequest; import javax.servlet.ServletResponse; import javax.servlet.http.HttpServletRequest; + import org.springframework.http.MediaType; import com.ruoyi.common.utils.StringUtils; /** * Repeatable 过滤器 - * + * * @author ruoyi */ -public class RepeatableFilter implements Filter -{ +public class RepeatableFilter implements Filter { @Override - public void init(FilterConfig filterConfig) throws ServletException - { + public void init(FilterConfig filterConfig) throws ServletException { } @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) - throws IOException, ServletException - { + throws IOException, ServletException { ServletRequest requestWrapper = null; if (request instanceof HttpServletRequest - && StringUtils.equalsAnyIgnoreCase(request.getContentType(), MediaType.APPLICATION_JSON_VALUE)) - { + && StringUtils.equalsAnyIgnoreCase(request.getContentType(), MediaType.APPLICATION_JSON_VALUE)) { requestWrapper = new RepeatedlyRequestWrapper((HttpServletRequest) request, response); } - if (null == requestWrapper) - { + if (null == requestWrapper) { chain.doFilter(request, response); - } - else - { + } else { chain.doFilter(requestWrapper, response); } } @Override - public void destroy() - { + public void destroy() { } } diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/filter/RepeatedlyRequestWrapper.java b/ruoyi-common/src/main/java/com/ruoyi/common/filter/RepeatedlyRequestWrapper.java index 9ad6f4940..2bd16d382 100644 --- a/ruoyi-common/src/main/java/com/ruoyi/common/filter/RepeatedlyRequestWrapper.java +++ b/ruoyi-common/src/main/java/com/ruoyi/common/filter/RepeatedlyRequestWrapper.java @@ -9,19 +9,18 @@ import javax.servlet.ServletInputStream; import javax.servlet.ServletResponse; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletRequestWrapper; + import com.ruoyi.common.utils.http.HttpHelper; /** * 构建可重复读取inputStream的request - * + * * @author ruoyi */ -public class RepeatedlyRequestWrapper extends HttpServletRequestWrapper -{ +public class RepeatedlyRequestWrapper extends HttpServletRequestWrapper { private final byte[] body; - public RepeatedlyRequestWrapper(HttpServletRequest request, ServletResponse response) throws IOException - { + public RepeatedlyRequestWrapper(HttpServletRequest request, ServletResponse response) throws IOException { super(request); request.setCharacterEncoding("UTF-8"); response.setCharacterEncoding("UTF-8"); @@ -30,41 +29,34 @@ public class RepeatedlyRequestWrapper extends HttpServletRequestWrapper } @Override - public BufferedReader getReader() throws IOException - { + public BufferedReader getReader() throws IOException { return new BufferedReader(new InputStreamReader(getInputStream())); } @Override - public ServletInputStream getInputStream() throws IOException - { + public ServletInputStream getInputStream() throws IOException { final ByteArrayInputStream bais = new ByteArrayInputStream(body); - return new ServletInputStream() - { + return new ServletInputStream() { @Override - public int read() throws IOException - { + public int read() throws IOException { return bais.read(); } @Override - public boolean isFinished() - { + public boolean isFinished() { return false; } @Override - public boolean isReady() - { + public boolean isReady() { return false; } @Override - public void setReadListener(ReadListener readListener) - { + public void setReadListener(ReadListener readListener) { } }; diff --git a/ruoyi-framework/src/main/java/com/ruoyi/framework/web/domain/Server.java b/ruoyi-framework/src/main/java/com/ruoyi/framework/web/domain/Server.java index 734b3f82a..fd19d98cf 100644 --- a/ruoyi-framework/src/main/java/com/ruoyi/framework/web/domain/Server.java +++ b/ruoyi-framework/src/main/java/com/ruoyi/framework/web/domain/Server.java @@ -55,46 +55,6 @@ public class Server { */ private List sysFiles = new LinkedList(); - public Cpu getCpu() { - return cpu; - } - - public void setCpu(Cpu cpu) { - this.cpu = cpu; - } - - public Mem getMem() { - return mem; - } - - public void setMem(Mem mem) { - this.mem = mem; - } - - public Jvm getJvm() { - return jvm; - } - - public void setJvm(Jvm jvm) { - this.jvm = jvm; - } - - public Sys getSys() { - return sys; - } - - public void setSys(Sys sys) { - this.sys = sys; - } - - public List getSysFiles() { - return sysFiles; - } - - public void setSysFiles(List sysFiles) { - this.sysFiles = sysFiles; - } - public void copyTo() throws Exception { SystemInfo si = new SystemInfo(); HardwareAbstractionLayer hal = si.getHardware(); diff --git a/ruoyi-framework/src/main/java/com/ruoyi/framework/web/domain/server/Cpu.java b/ruoyi-framework/src/main/java/com/ruoyi/framework/web/domain/server/Cpu.java index 5e0d150a8..1591cac6c 100644 --- a/ruoyi-framework/src/main/java/com/ruoyi/framework/web/domain/server/Cpu.java +++ b/ruoyi-framework/src/main/java/com/ruoyi/framework/web/domain/server/Cpu.java @@ -38,22 +38,10 @@ public class Cpu { */ private double free; - public int getCpuNum() { - return cpuNum; - } - - public void setCpuNum(int cpuNum) { - this.cpuNum = cpuNum; - } - public double getTotal() { return Arith.round(Arith.mul(total, 100), 2); } - public void setTotal(double total) { - this.total = total; - } - public double getSys() { return Arith.round(Arith.mul(sys / total, 100), 2); } diff --git a/ruoyi-framework/src/main/java/com/ruoyi/framework/web/domain/server/Jvm.java b/ruoyi-framework/src/main/java/com/ruoyi/framework/web/domain/server/Jvm.java index 991ceea3e..e0eb23b1f 100644 --- a/ruoyi-framework/src/main/java/com/ruoyi/framework/web/domain/server/Jvm.java +++ b/ruoyi-framework/src/main/java/com/ruoyi/framework/web/domain/server/Jvm.java @@ -40,18 +40,10 @@ public class Jvm { return Arith.div(total, (1024 * 1024), 2); } - public void setTotal(double total) { - this.total = total; - } - public double getMax() { return Arith.div(max, (1024 * 1024), 2); } - public void setMax(double max) { - this.max = max; - } - public double getFree() { return Arith.div(free, (1024 * 1024), 2); } @@ -75,22 +67,6 @@ public class Jvm { return ManagementFactory.getRuntimeMXBean().getVmName(); } - public String getVersion() { - return version; - } - - public void setVersion(String version) { - this.version = version; - } - - public String getHome() { - return home; - } - - public void setHome(String home) { - this.home = home; - } - /** * JDK启动时间 */ diff --git a/ruoyi-framework/src/main/java/com/ruoyi/framework/web/domain/server/Mem.java b/ruoyi-framework/src/main/java/com/ruoyi/framework/web/domain/server/Mem.java index b4c642f1d..ac0f9b445 100644 --- a/ruoyi-framework/src/main/java/com/ruoyi/framework/web/domain/server/Mem.java +++ b/ruoyi-framework/src/main/java/com/ruoyi/framework/web/domain/server/Mem.java @@ -27,27 +27,16 @@ public class Mem { return Arith.div(total, (1024 * 1024 * 1024), 2); } - public void setTotal(long total) { - this.total = total; - } - public double getUsed() { return Arith.div(used, (1024 * 1024 * 1024), 2); } - public void setUsed(long used) { - this.used = used; - } - public double getFree() { return Arith.div(free, (1024 * 1024 * 1024), 2); } - public void setFree(long free) { - this.free = free; - } - public double getUsage() { return Arith.mul(Arith.div(used, total, 4), 100); } + } diff --git a/ruoyi-framework/src/main/java/com/ruoyi/framework/web/domain/server/Sys.java b/ruoyi-framework/src/main/java/com/ruoyi/framework/web/domain/server/Sys.java index e43385d1c..eae6f07c5 100644 --- a/ruoyi-framework/src/main/java/com/ruoyi/framework/web/domain/server/Sys.java +++ b/ruoyi-framework/src/main/java/com/ruoyi/framework/web/domain/server/Sys.java @@ -31,43 +31,4 @@ public class Sys { */ private String osArch; - public String getComputerName() { - return computerName; - } - - public void setComputerName(String computerName) { - this.computerName = computerName; - } - - public String getComputerIp() { - return computerIp; - } - - public void setComputerIp(String computerIp) { - this.computerIp = computerIp; - } - - public String getUserDir() { - return userDir; - } - - public void setUserDir(String userDir) { - this.userDir = userDir; - } - - public String getOsName() { - return osName; - } - - public void setOsName(String osName) { - this.osName = osName; - } - - public String getOsArch() { - return osArch; - } - - public void setOsArch(String osArch) { - this.osArch = osArch; - } } diff --git a/ruoyi-framework/src/main/java/com/ruoyi/framework/web/domain/server/SysFile.java b/ruoyi-framework/src/main/java/com/ruoyi/framework/web/domain/server/SysFile.java index 48e028024..8a8d89f1e 100644 --- a/ruoyi-framework/src/main/java/com/ruoyi/framework/web/domain/server/SysFile.java +++ b/ruoyi-framework/src/main/java/com/ruoyi/framework/web/domain/server/SysFile.java @@ -41,59 +41,4 @@ public class SysFile { */ private double usage; - public String getDirName() { - return dirName; - } - - public void setDirName(String dirName) { - this.dirName = dirName; - } - - public String getSysTypeName() { - return sysTypeName; - } - - public void setSysTypeName(String sysTypeName) { - this.sysTypeName = sysTypeName; - } - - public String getTypeName() { - return typeName; - } - - public void setTypeName(String typeName) { - this.typeName = typeName; - } - - public String getTotal() { - return total; - } - - public void setTotal(String total) { - this.total = total; - } - - public String getFree() { - return free; - } - - public void setFree(String free) { - this.free = free; - } - - public String getUsed() { - return used; - } - - public void setUsed(String used) { - this.used = used; - } - - public double getUsage() { - return usage; - } - - public void setUsage(double usage) { - this.usage = usage; - } } diff --git a/ruoyi-framework/src/main/java/com/ruoyi/framework/web/service/PermissionService.java b/ruoyi-framework/src/main/java/com/ruoyi/framework/web/service/PermissionService.java index fe9744de0..cb3c2987c 100644 --- a/ruoyi-framework/src/main/java/com/ruoyi/framework/web/service/PermissionService.java +++ b/ruoyi-framework/src/main/java/com/ruoyi/framework/web/service/PermissionService.java @@ -17,68 +17,6 @@ import com.ruoyi.common.utils.StringUtils; */ @Service("ss") public class PermissionService { - /** - * 所有权限标识 - */ - private static final String ALL_PERMISSION = "*:*:*"; - - /** - * 管理员角色权限标识 - */ - private static final String SUPER_ADMIN = "admin"; - - @Autowired - private TokenService tokenService; - - /** - * 验证用户是否具备某权限 - * - * @param permission 权限字符串 - * @return 用户是否具备某权限 - */ - public boolean hasPermi(String permission) { - if (StringUtils.isEmpty(permission)) { - return false; - } - LoginUser loginUser = tokenService.getLoginUser(ServletUtils.getRequest()); - if (StringUtils.isNull(loginUser) || CollectionUtils.isEmpty(loginUser.getPermissions())) { - return false; - } - return hasPermissions(loginUser.getPermissions(), permission); - } - - /** - * 验证用户是否不具备某权限,与 hasPermi逻辑相反 - * - * @param permission 权限字符串 - * @return 用户是否不具备某权限 - */ - public boolean lacksPermi(String permission) { - return hasPermi(permission) != true; - } - - /** - * 验证用户是否具有以下任意一个权限 - * - * @param permissions 以 PERMISSION_NAMES_DELIMETER 为分隔符的权限列表 - * @return 用户是否具有以下任意一个权限 - */ - public boolean hasAnyPermi(String permissions) { - if (StringUtils.isEmpty(permissions)) { - return false; - } - LoginUser loginUser = tokenService.getLoginUser(ServletUtils.getRequest()); - if (StringUtils.isNull(loginUser) || CollectionUtils.isEmpty(loginUser.getPermissions())) { - return false; - } - Set authorities = loginUser.getPermissions(); - for (String permission : permissions.split(PERMISSION_DELIMETER)) { - if (permission != null && hasPermissions(authorities, permission)) { - return true; - } - } - return false; - } /** * 判断用户是否拥有某个角色 @@ -103,16 +41,6 @@ public class PermissionService { return false; } - /** - * 验证用户是否不具备某角色,与 isRole逻辑相反。 - * - * @param role 角色名称 - * @return 用户是否不具备某角色 - */ - public boolean lacksRole(String role) { - return hasRole(role) != true; - } - /** * 验证用户是否具有以下任意一个角色 * @@ -135,14 +63,4 @@ public class PermissionService { return false; } - /** - * 判断是否包含权限 - * - * @param permissions 权限列表 - * @param permission 权限字符串 - * @return 用户是否具备某权限 - */ - private boolean hasPermissions(Set permissions, String permission) { - return permissions.contains(ALL_PERMISSION) || permissions.contains(StringUtils.trim(permission)); - } } diff --git a/ruoyi-ui/src/utils/request.js b/ruoyi-ui/src/utils/request.js index 4febc4c87..b9404d162 100644 --- a/ruoyi-ui/src/utils/request.js +++ b/ruoyi-ui/src/utils/request.js @@ -61,7 +61,9 @@ service.interceptors.response.use(res => { } ).then(() => { store.dispatch('LogOut').then(() => { - location.href = '/index'; + if (location.pathname !== '/login') { // 避免重复跳转 + location.href = '/index'; + } }) }) } else if (code === 500) { diff --git a/ruoyi-ui/src/views/system/role/index.vue b/ruoyi-ui/src/views/system/role/index.vue index 2cc1a9399..bea708f3b 100644 --- a/ruoyi-ui/src/views/system/role/index.vue +++ b/ruoyi-ui/src/views/system/role/index.vue @@ -392,6 +392,7 @@ export default { menuIds: [], dataScope: undefined, deptCheckStrictly: false, + menuCheckStrictly: true, remark: undefined }; this.resetForm("form"); @@ -471,8 +472,12 @@ export default { }); // 获得角色拥有的菜单集合 listRoleMenus(id).then(response => { + // 设置为严格,避免设置父节点自动选中子节点,解决半选中问题 + this.form.menuCheckStrictly = true // 设置选中 - this.$refs.menu.setCheckedKeys(response.data, true, false); + this.$refs.menu.setCheckedKeys(response.data); + // 设置为非严格,继续使用半选中 + this.form.menuCheckStrictly = false }) }, /** 分配数据权限操作 */ @@ -523,7 +528,7 @@ export default { roleId: this.form.id, dataScope: this.form.dataScope, dataScopeDeptIds: this.form.dataScope !== SysDataScopeEnum.DEPT_CUSTOM ? [] : - this.$refs.dept.getCheckedKeys(false) + this.$refs.dept.getCheckedKeys() }).then(response => { this.msgSuccess("修改成功"); this.openDataScope = false; @@ -536,7 +541,7 @@ export default { if (this.form.id !== undefined) { assignRoleMenu({ roleId: this.form.id, - menuIds: this.$refs.menu.getCheckedKeys(true) + menuIds: [...this.$refs.menu.getCheckedKeys(), ...this.$refs.menu.getHalfCheckedKeys()] }).then(response => { this.msgSuccess("修改成功"); this.openMenu = false; diff --git a/src/main/java/cn/iocoder/dashboard/framework/logger/operatelog/core/aop/OperateLogAspect.java b/src/main/java/cn/iocoder/dashboard/framework/logger/operatelog/core/aop/OperateLogAspect.java index cd7c0e22d..6465b3112 100644 --- a/src/main/java/cn/iocoder/dashboard/framework/logger/operatelog/core/aop/OperateLogAspect.java +++ b/src/main/java/cn/iocoder/dashboard/framework/logger/operatelog/core/aop/OperateLogAspect.java @@ -233,11 +233,6 @@ public class OperateLogAspect { } } - private static void fillContentFields(SysOperateLogCreateReqVO operateLogVO) { - operateLogVO.setContent(CONTENT.get()); - operateLogVO.setExts(EXTS.get()); - } - private static boolean isLogEnable(ProceedingJoinPoint joinPoint, OperateLog operateLog) { // 有 @OperateLog 注解的情况下 if (operateLog != null) { diff --git a/src/main/java/cn/iocoder/dashboard/framework/security/core/handler/LogoutSuccessHandlerImpl.java b/src/main/java/cn/iocoder/dashboard/framework/security/core/handler/LogoutSuccessHandlerImpl.java index 59a49207f..0212294ea 100644 --- a/src/main/java/cn/iocoder/dashboard/framework/security/core/handler/LogoutSuccessHandlerImpl.java +++ b/src/main/java/cn/iocoder/dashboard/framework/security/core/handler/LogoutSuccessHandlerImpl.java @@ -2,7 +2,7 @@ package cn.iocoder.dashboard.framework.security.core.handler; import cn.hutool.core.util.StrUtil; import cn.iocoder.dashboard.framework.security.config.SecurityProperties; -import cn.iocoder.dashboard.framework.security.core.service.SecurityFrameworkService; +import cn.iocoder.dashboard.framework.security.core.service.SecurityAuthFrameworkService; import cn.iocoder.dashboard.framework.security.core.util.SecurityUtils; import cn.iocoder.dashboard.util.servlet.ServletUtils; import org.springframework.security.core.Authentication; @@ -26,7 +26,7 @@ public class LogoutSuccessHandlerImpl implements LogoutSuccessHandler { private SecurityProperties securityProperties; @Resource - private SecurityFrameworkService securityFrameworkService; + private SecurityAuthFrameworkService securityFrameworkService; @Override public void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) { diff --git a/src/main/java/cn/iocoder/dashboard/framework/security/core/service/SecurityFrameworkService.java b/src/main/java/cn/iocoder/dashboard/framework/security/core/service/SecurityAuthFrameworkService.java similarity index 80% rename from src/main/java/cn/iocoder/dashboard/framework/security/core/service/SecurityFrameworkService.java rename to src/main/java/cn/iocoder/dashboard/framework/security/core/service/SecurityAuthFrameworkService.java index b77a5b4a0..e3879353a 100644 --- a/src/main/java/cn/iocoder/dashboard/framework/security/core/service/SecurityFrameworkService.java +++ b/src/main/java/cn/iocoder/dashboard/framework/security/core/service/SecurityAuthFrameworkService.java @@ -4,16 +4,11 @@ import cn.iocoder.dashboard.framework.security.core.LoginUser; import org.springframework.security.core.userdetails.UserDetailsService; /** - * Security 框架 Service 接口,定义 security 组件需要的功能 + * Security 框架 Auth Service 接口,定义 security 组件需要的功能 + * + * @author 芋道源码 */ -public interface SecurityFrameworkService extends UserDetailsService { - - /** - * 基于 token 退出登录 - * - * @param token token - */ - void logout(String token); +public interface SecurityAuthFrameworkService extends UserDetailsService { /** * 校验 token 的有效性,并获取用户信息 @@ -32,4 +27,11 @@ public interface SecurityFrameworkService extends UserDetailsService { */ LoginUser mockLogin(Long userId); + /** + * 基于 token 退出登录 + * + * @param token token + */ + void logout(String token); + } diff --git a/src/main/java/cn/iocoder/dashboard/framework/security/core/service/SecurityPermissionFrameworkService.java b/src/main/java/cn/iocoder/dashboard/framework/security/core/service/SecurityPermissionFrameworkService.java new file mode 100644 index 000000000..debf61039 --- /dev/null +++ b/src/main/java/cn/iocoder/dashboard/framework/security/core/service/SecurityPermissionFrameworkService.java @@ -0,0 +1,26 @@ +package cn.iocoder.dashboard.framework.security.core.service; + +/** + * Security 框架 Permission Service 接口,定义 security 组件需要的功能 + * + * @author 芋道源码 + */ +public interface SecurityPermissionFrameworkService { + + /** + * 判断是否有权限 + * + * @param permission 权限 + * @return 是否 + */ + boolean hasPermission(String permission); + + /** + * 判断是否有权限,任一一个即可 + * + * @param permissions 权限 + * @return 是否 + */ + boolean hasAnyPermissions(String... permissions); + +} diff --git a/src/main/java/cn/iocoder/dashboard/framework/web/core/handler/GlobalExceptionHandler.java b/src/main/java/cn/iocoder/dashboard/framework/web/core/handler/GlobalExceptionHandler.java index 0c97dfef1..c0974a0f0 100644 --- a/src/main/java/cn/iocoder/dashboard/framework/web/core/handler/GlobalExceptionHandler.java +++ b/src/main/java/cn/iocoder/dashboard/framework/web/core/handler/GlobalExceptionHandler.java @@ -3,7 +3,9 @@ package cn.iocoder.dashboard.framework.web.core.handler; import cn.iocoder.dashboard.common.exception.GlobalException; import cn.iocoder.dashboard.common.exception.ServiceException; import cn.iocoder.dashboard.common.pojo.CommonResult; +import cn.iocoder.dashboard.framework.security.core.util.SecurityUtils; import lombok.extern.slf4j.Slf4j; +import org.springframework.security.access.AccessDeniedException; import org.springframework.validation.BindException; import org.springframework.validation.FieldError; import org.springframework.web.HttpRequestMethodNotSupportedException; @@ -64,6 +66,9 @@ public class GlobalExceptionHandler { if (ex instanceof ServiceException) { return serviceExceptionHandler((ServiceException) ex); } + if (ex instanceof AccessDeniedException) { + return accessDeniedExceptionHandler(request, (AccessDeniedException) ex); + } if (ex instanceof GlobalException) { return globalExceptionHandler(request, (GlobalException) ex); } @@ -131,7 +136,7 @@ public class GlobalExceptionHandler { public CommonResult validationException(ValidationException ex) { log.warn("[constraintViolationExceptionHandler]", ex); // 无法拼接明细的错误信息,因为 Dubbo Consumer 抛出 ValidationException 异常时,是直接的字符串信息,且人类不可读 - return CommonResult.error(BAD_REQUEST.getCode(), "请求参数不正确"); + return CommonResult.error(BAD_REQUEST); } /** @@ -158,6 +163,18 @@ public class GlobalExceptionHandler { return CommonResult.error(METHOD_NOT_ALLOWED.getCode(), String.format("请求方法不正确:%s", ex.getMessage())); } + /** + * 处理 Spring Security 权限不足的异常 + * + * 来源是,使用 @PreAuthorize 注解,AOP 进行权限拦截 + */ + @ExceptionHandler(value = AccessDeniedException.class) + public CommonResult accessDeniedExceptionHandler(HttpServletRequest req, AccessDeniedException ex) { + log.warn("[accessDeniedExceptionHandler][userId({}) 无法访问 url({})]", SecurityUtils.getLoginUserId(), + req.getRequestURL(), ex); + return CommonResult.error(FORBIDDEN); + } + /** * 处理业务异常 ServiceException * diff --git a/src/main/java/cn/iocoder/dashboard/modules/system/controller/auth/vo/SysAuthLoginReqVO.java b/src/main/java/cn/iocoder/dashboard/modules/system/controller/auth/vo/SysAuthLoginReqVO.java index affbf3ced..af9753534 100644 --- a/src/main/java/cn/iocoder/dashboard/modules/system/controller/auth/vo/SysAuthLoginReqVO.java +++ b/src/main/java/cn/iocoder/dashboard/modules/system/controller/auth/vo/SysAuthLoginReqVO.java @@ -7,9 +7,7 @@ import lombok.Builder; import lombok.Data; import lombok.NoArgsConstructor; import org.hibernate.validator.constraints.Length; -import org.springframework.validation.annotation.Validated; -import javax.validation.Valid; import javax.validation.constraints.NotEmpty; import javax.validation.constraints.Pattern; @@ -22,7 +20,7 @@ public class SysAuthLoginReqVO { @ApiModelProperty(value = "账号", required = true, example = "yudaoyuanma") @NotEmpty(message = "登陆账号不能为空") - @Length(min = 5, max = 16, message = "账号长度为 5-16 位") + @Length(min = 4, max = 16, message = "账号长度为 4-16 位") @Pattern(regexp = "^[A-Za-z0-9]+$", message = "账号格式为数字以及字母") private String username; diff --git a/src/main/java/cn/iocoder/dashboard/modules/system/controller/user/SysUserController.http b/src/main/java/cn/iocoder/dashboard/modules/system/controller/user/SysUserController.http new file mode 100644 index 000000000..87283759d --- /dev/null +++ b/src/main/java/cn/iocoder/dashboard/modules/system/controller/user/SysUserController.http @@ -0,0 +1,3 @@ +### 请求 /system/user/page 接口 => 没有权限 +GET {{baseUrl}}/system/user/page?pageNo=1&pageSize=10 +Authorization: Bearer test104 # 使用测试账号 diff --git a/src/main/java/cn/iocoder/dashboard/modules/system/controller/user/SysUserController.java b/src/main/java/cn/iocoder/dashboard/modules/system/controller/user/SysUserController.java index a7c860f61..46daf8271 100644 --- a/src/main/java/cn/iocoder/dashboard/modules/system/controller/user/SysUserController.java +++ b/src/main/java/cn/iocoder/dashboard/modules/system/controller/user/SysUserController.java @@ -18,6 +18,7 @@ import io.swagger.annotations.Api; import io.swagger.annotations.ApiImplicitParam; import io.swagger.annotations.ApiImplicitParams; import io.swagger.annotations.ApiOperation; +import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.*; import org.springframework.web.multipart.MultipartFile; @@ -41,7 +42,7 @@ public class SysUserController { @ApiOperation("获得用户分页列表") @GetMapping("/page") -// @PreAuthorize("@ss.hasPermi('system:user:list')") + @PreAuthorize("@ss.hasPermission('system:user:list')") public CommonResult> pageUsers(@Validated SysUserPageReqVO reqVO) { // 获得用户分页列表 PageResult pageResult = userService.pageUsers(reqVO); diff --git a/src/main/java/cn/iocoder/dashboard/modules/system/dal/mysql/dao/permission/SysRoleMenuMapper.java b/src/main/java/cn/iocoder/dashboard/modules/system/dal/mysql/dao/permission/SysRoleMenuMapper.java index 914903bc1..4c331eeee 100644 --- a/src/main/java/cn/iocoder/dashboard/modules/system/dal/mysql/dao/permission/SysRoleMenuMapper.java +++ b/src/main/java/cn/iocoder/dashboard/modules/system/dal/mysql/dao/permission/SysRoleMenuMapper.java @@ -1,16 +1,17 @@ package cn.iocoder.dashboard.modules.system.dal.mysql.dao.permission; +import cn.iocoder.dashboard.framework.mybatis.core.mapper.BaseMapperX; import cn.iocoder.dashboard.modules.system.dal.mysql.dataobject.permission.SysRoleMenuDO; import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; -import com.baomidou.mybatisplus.core.mapper.BaseMapper; import org.apache.ibatis.annotations.Mapper; import java.util.Collection; +import java.util.Date; import java.util.List; import java.util.stream.Collectors; @Mapper -public interface SysRoleMenuMapper extends BaseMapper { +public interface SysRoleMenuMapper extends BaseMapperX { default List selectListByRoleId(Long roleId) { return selectList(new QueryWrapper().eq("role_id", roleId)); @@ -32,4 +33,9 @@ public interface SysRoleMenuMapper extends BaseMapper { .in("menu_id", menuIds)); } + default boolean selectExistsByUpdateTimeAfter(Date maxUpdateTime) { + return selectOne(new QueryWrapper().select("id") + .gt("update_time", maxUpdateTime).last("LIMIT 1")) != null; + } + } diff --git a/src/main/java/cn/iocoder/dashboard/modules/system/mq/consumer/permission/SysRoleMenuRefreshConsumer.java b/src/main/java/cn/iocoder/dashboard/modules/system/mq/consumer/permission/SysRoleMenuRefreshConsumer.java new file mode 100644 index 000000000..6927e1464 --- /dev/null +++ b/src/main/java/cn/iocoder/dashboard/modules/system/mq/consumer/permission/SysRoleMenuRefreshConsumer.java @@ -0,0 +1,29 @@ +package cn.iocoder.dashboard.modules.system.mq.consumer.permission; + +import cn.iocoder.dashboard.framework.redis.core.pubsub.AbstractChannelMessageListener; +import cn.iocoder.dashboard.modules.system.mq.message.permission.SysRoleMenuRefreshMessage; +import cn.iocoder.dashboard.modules.system.service.permission.SysPermissionService; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; + +import javax.annotation.Resource; + +/** + * 针对 {@link SysRoleMenuRefreshMessage} 的消费者 + * + * @author 芋道源码 + */ +@Component +@Slf4j +public class SysRoleMenuRefreshConsumer extends AbstractChannelMessageListener { + + @Resource + private SysPermissionService permissionService; + + @Override + public void onMessage(SysRoleMenuRefreshMessage message) { + log.info("[onMessage][收到 Role 与 Menu 的关联刷新消息]"); + permissionService.initLocalCache(); + } + +} diff --git a/src/main/java/cn/iocoder/dashboard/modules/system/mq/message/permission/SysRoleMenuRefreshMessage.java b/src/main/java/cn/iocoder/dashboard/modules/system/mq/message/permission/SysRoleMenuRefreshMessage.java new file mode 100644 index 000000000..491c9b0d2 --- /dev/null +++ b/src/main/java/cn/iocoder/dashboard/modules/system/mq/message/permission/SysRoleMenuRefreshMessage.java @@ -0,0 +1,17 @@ +package cn.iocoder.dashboard.modules.system.mq.message.permission; + +import cn.iocoder.dashboard.framework.redis.core.pubsub.ChannelMessage; +import lombok.Data; + +/** + * 角色与菜单数据刷新 Message + */ +@Data +public class SysRoleMenuRefreshMessage implements ChannelMessage { + + @Override + public String getChannel() { + return "system.role-menu.refresh"; + } + +} diff --git a/src/main/java/cn/iocoder/dashboard/modules/system/mq/producer/permission/SysPermissionProducer.java b/src/main/java/cn/iocoder/dashboard/modules/system/mq/producer/permission/SysPermissionProducer.java new file mode 100644 index 000000000..f9eded668 --- /dev/null +++ b/src/main/java/cn/iocoder/dashboard/modules/system/mq/producer/permission/SysPermissionProducer.java @@ -0,0 +1,27 @@ +package cn.iocoder.dashboard.modules.system.mq.producer.permission; + +import cn.iocoder.dashboard.framework.redis.core.util.RedisMessageUtils; +import cn.iocoder.dashboard.modules.system.mq.message.permission.SysRoleMenuRefreshMessage; +import org.springframework.data.redis.core.StringRedisTemplate; +import org.springframework.stereotype.Component; + +import javax.annotation.Resource; + +/** + * Permission 权限相关消息的 Producer + */ +@Component +public class SysPermissionProducer { + + @Resource + private StringRedisTemplate stringRedisTemplate; + + /** + * 发送 {@link SysRoleMenuRefreshMessage} 消息 + */ + public void sendRoleMenuRefreshMessage() { + SysRoleMenuRefreshMessage message = new SysRoleMenuRefreshMessage(); + RedisMessageUtils.sendChannelMessage(stringRedisTemplate, message); + } + +} diff --git a/src/main/java/cn/iocoder/dashboard/modules/system/service/auth/SysAuthService.java b/src/main/java/cn/iocoder/dashboard/modules/system/service/auth/SysAuthService.java index e212554a9..d1c0580e4 100644 --- a/src/main/java/cn/iocoder/dashboard/modules/system/service/auth/SysAuthService.java +++ b/src/main/java/cn/iocoder/dashboard/modules/system/service/auth/SysAuthService.java @@ -1,6 +1,6 @@ package cn.iocoder.dashboard.modules.system.service.auth; -import cn.iocoder.dashboard.framework.security.core.service.SecurityFrameworkService; +import cn.iocoder.dashboard.framework.security.core.service.SecurityAuthFrameworkService; /** * 认证 Service 接口 @@ -9,7 +9,7 @@ import cn.iocoder.dashboard.framework.security.core.service.SecurityFrameworkSer * * @author 芋道源码 */ -public interface SysAuthService extends SecurityFrameworkService { +public interface SysAuthService extends SecurityAuthFrameworkService { String login(String username, String password, String captchaUUID, String captchaCode); diff --git a/src/main/java/cn/iocoder/dashboard/modules/system/service/permission/SysMenuService.java b/src/main/java/cn/iocoder/dashboard/modules/system/service/permission/SysMenuService.java index 42b91c8d5..9a6c1df67 100644 --- a/src/main/java/cn/iocoder/dashboard/modules/system/service/permission/SysMenuService.java +++ b/src/main/java/cn/iocoder/dashboard/modules/system/service/permission/SysMenuService.java @@ -59,6 +59,14 @@ public interface SysMenuService { List listMenusFromCache(Collection menuIds, Collection menuTypes, Collection menusStatuses); + /** + * 获得权限对应的菜单数组 + * + * @param permission 权限标识 + * @return 数组 + */ + List getMenuListByPermissionFromCache(String permission); + /* * 创建菜单 * diff --git a/src/main/java/cn/iocoder/dashboard/modules/system/service/permission/SysPermissionService.java b/src/main/java/cn/iocoder/dashboard/modules/system/service/permission/SysPermissionService.java index 60936a414..1e30d7297 100644 --- a/src/main/java/cn/iocoder/dashboard/modules/system/service/permission/SysPermissionService.java +++ b/src/main/java/cn/iocoder/dashboard/modules/system/service/permission/SysPermissionService.java @@ -1,5 +1,6 @@ package cn.iocoder.dashboard.modules.system.service.permission; +import cn.iocoder.dashboard.framework.security.core.service.SecurityPermissionFrameworkService; import cn.iocoder.dashboard.modules.system.dal.mysql.dataobject.permission.SysMenuDO; import org.springframework.lang.Nullable; @@ -14,12 +15,12 @@ import java.util.Set; * * @author 芋道源码 */ -public interface SysPermissionService { +public interface SysPermissionService extends SecurityPermissionFrameworkService { /** * 初始化 */ - void init(); + void initLocalCache(); /** * 获得角色们拥有的菜单列表,从缓存中获取 diff --git a/src/main/java/cn/iocoder/dashboard/modules/system/service/permission/SysRoleService.java b/src/main/java/cn/iocoder/dashboard/modules/system/service/permission/SysRoleService.java index 1c9b2a817..dee985a29 100644 --- a/src/main/java/cn/iocoder/dashboard/modules/system/service/permission/SysRoleService.java +++ b/src/main/java/cn/iocoder/dashboard/modules/system/service/permission/SysRoleService.java @@ -56,6 +56,16 @@ public interface SysRoleService { */ boolean hasAnyAdmin(Collection roleList); + /** + * 判断角色编号数组中,是否有管理员 + * + * @param ids 角色编号数组 + * @return 是否有管理员 + */ + default boolean hasAnyAdmin(Set ids) { + return hasAnyAdmin(listRolesFromCache(ids)); + } + /** * 创建角色 * diff --git a/src/main/java/cn/iocoder/dashboard/modules/system/service/permission/impl/SysMenuServiceImpl.java b/src/main/java/cn/iocoder/dashboard/modules/system/service/permission/impl/SysMenuServiceImpl.java index 97c889d11..60a4015b5 100644 --- a/src/main/java/cn/iocoder/dashboard/modules/system/service/permission/impl/SysMenuServiceImpl.java +++ b/src/main/java/cn/iocoder/dashboard/modules/system/service/permission/impl/SysMenuServiceImpl.java @@ -61,7 +61,7 @@ public class SysMenuServiceImpl implements SysMenuService { * * 这里声明 volatile 修饰的原因是,每次刷新时,直接修改指向 */ - private volatile Multimap permMenuCache; + private volatile Multimap permissionMenuCache; /** * 缓存菜单的最大更新时间,用于后续的增量轮询,判断是否有更新 */ @@ -76,7 +76,7 @@ public class SysMenuServiceImpl implements SysMenuService { private SysMenuProducer menuProducer; /** - * 初始化 {@link #menuCache} 和 {@link #permMenuCache} 缓存 + * 初始化 {@link #menuCache} 和 {@link #permissionMenuCache} 缓存 */ @Override @PostConstruct @@ -95,7 +95,7 @@ public class SysMenuServiceImpl implements SysMenuService { permMenuCacheBuilder.put(menuDO.getPermission(), menuDO); }); menuCache = menuCacheBuilder.build(); - permMenuCache = permMenuCacheBuilder.build(); + permissionMenuCache = permMenuCacheBuilder.build(); assert menuList.size() > 0; // 断言,避免告警 maxUpdateTime = menuList.stream().max(Comparator.comparing(BaseDO::getUpdateTime)).get().getUpdateTime(); log.info("[initLocalCache][缓存菜单,数量为:{}]", menuList.size()); @@ -162,6 +162,11 @@ public class SysMenuServiceImpl implements SysMenuService { .collect(Collectors.toList()); } + @Override + public List getMenuListByPermissionFromCache(String permission) { + return new ArrayList<>(permissionMenuCache.get(permission)); + } + @Override public Long createMenu(SysMenuCreateReqVO reqVO) { // 校验父菜单存在 diff --git a/src/main/java/cn/iocoder/dashboard/modules/system/service/permission/impl/SysPermissionServiceImpl.java b/src/main/java/cn/iocoder/dashboard/modules/system/service/permission/impl/SysPermissionServiceImpl.java index 5a32be038..935efa3b4 100644 --- a/src/main/java/cn/iocoder/dashboard/modules/system/service/permission/impl/SysPermissionServiceImpl.java +++ b/src/main/java/cn/iocoder/dashboard/modules/system/service/permission/impl/SysPermissionServiceImpl.java @@ -2,12 +2,16 @@ package cn.iocoder.dashboard.modules.system.service.permission.impl; import cn.hutool.core.collection.CollUtil; import cn.hutool.core.collection.CollectionUtil; +import cn.hutool.core.util.ArrayUtil; +import cn.iocoder.dashboard.framework.mybatis.core.dataobject.BaseDO; +import cn.iocoder.dashboard.framework.security.core.util.SecurityUtils; import cn.iocoder.dashboard.modules.system.dal.mysql.dao.permission.SysRoleMenuMapper; import cn.iocoder.dashboard.modules.system.dal.mysql.dao.permission.SysUserRoleMapper; import cn.iocoder.dashboard.modules.system.dal.mysql.dataobject.permission.SysMenuDO; import cn.iocoder.dashboard.modules.system.dal.mysql.dataobject.permission.SysRoleDO; import cn.iocoder.dashboard.modules.system.dal.mysql.dataobject.permission.SysRoleMenuDO; import cn.iocoder.dashboard.modules.system.dal.mysql.dataobject.permission.SysUserRoleDO; +import cn.iocoder.dashboard.modules.system.mq.producer.permission.SysPermissionProducer; import cn.iocoder.dashboard.modules.system.service.permission.SysMenuService; import cn.iocoder.dashboard.modules.system.service.permission.SysPermissionService; import cn.iocoder.dashboard.modules.system.service.permission.SysRoleService; @@ -18,23 +22,28 @@ import com.google.common.collect.Multimap; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; +import org.springframework.transaction.support.TransactionSynchronization; +import org.springframework.transaction.support.TransactionSynchronizationManager; import javax.annotation.PostConstruct; import javax.annotation.Resource; -import java.util.Collection; -import java.util.Collections; -import java.util.List; -import java.util.Set; +import java.util.*; /** * 权限 Service 实现类 * * @author 芋道源码 */ -@Service +@Service("ss") // 使用 Spring Security 的缩写,方便食用 @Slf4j public class SysPermissionServiceImpl implements SysPermissionService { + /** + * 定时执行 {@link #schedulePeriodicRefresh()} 的周期 + * 因为已经通过 Redis Pub/Sub 机制,所以频率不需要高 + */ + private static final long SCHEDULER_PERIOD = 5 * 60 * 1000L; + /** * 角色编号与菜单编号的缓存映射 * key:角色编号 @@ -51,6 +60,10 @@ public class SysPermissionServiceImpl implements SysPermissionService { * 这里声明 volatile 修饰的原因是,每次刷新时,直接修改指向 */ private volatile Multimap menuRoleCache; + /** + * 缓存菜单的最大更新时间,用于后续的增量轮询,判断是否有更新 + */ + private volatile Date maxUpdateTime; @Resource private SysRoleMenuMapper roleMenuMapper; @@ -62,14 +75,22 @@ public class SysPermissionServiceImpl implements SysPermissionService { @Resource private SysMenuService menuService; + @Resource + private SysPermissionProducer permissionProducer; + /** * 初始化 {@link #roleMenuCache} 和 {@link #menuRoleCache} 缓存 */ @Override @PostConstruct - public void init() { + public void initLocalCache() { + // 获取角色与菜单的关联列表,如果有更新 + List roleMenuList = this.loadRoleMenuIfUpdate(maxUpdateTime); + if (CollUtil.isEmpty(roleMenuList)) { + return; + } + // 初始化 roleMenuCache 和 menuRoleCache 缓存 - List roleMenuList = roleMenuMapper.selectList(null); ImmutableMultimap.Builder roleMenuCacheBuilder = ImmutableMultimap.builder(); ImmutableMultimap.Builder menuRoleCacheBuilder = ImmutableMultimap.builder(); roleMenuList.forEach(roleMenuDO -> { @@ -78,9 +99,32 @@ public class SysPermissionServiceImpl implements SysPermissionService { }); roleMenuCache = roleMenuCacheBuilder.build(); menuRoleCache = menuRoleCacheBuilder.build(); + assert roleMenuList.size() > 0; // 断言,避免告警 + maxUpdateTime = roleMenuList.stream().max(Comparator.comparing(BaseDO::getUpdateTime)).get().getUpdateTime(); log.info("[initLocalCache][初始化角色与菜单的关联数量为 {}]", roleMenuList.size()); } + /** + * 如果角色与菜单的关联发生变化,从数据库中获取最新的全量角色与菜单的关联。 + * 如果未发生变化,则返回空 + * + * @param maxUpdateTime 当前角色与菜单的关联的最大更新时间 + * @return 角色与菜单的关联列表 + */ + private List loadRoleMenuIfUpdate(Date maxUpdateTime) { + // 第一步,判断是否要更新。 + if (maxUpdateTime == null) { // 如果更新时间为空,说明 DB 一定有新数据 + log.info("[loadRoleMenuIfUpdate][首次加载全量角色与菜单的关联]"); + } else { // 判断数据库中是否有更新的角色与菜单的关联 + if (!roleMenuMapper.selectExistsByUpdateTimeAfter(maxUpdateTime)) { + return null; + } + log.info("[loadRoleMenuIfUpdate][增量加载全量角色与菜单的关联]"); + } + // 第二步,如果有更新,则从数据库加载所有角色与菜单的关联 + return roleMenuMapper.selectList(); + } + @Override public List listRoleMenusFromCache(Collection roleIds, Collection menuTypes, Collection menusStatuses) { @@ -140,6 +184,15 @@ public class SysPermissionServiceImpl implements SysPermissionService { if (!CollectionUtil.isEmpty(deleteMenuIds)) { roleMenuMapper.deleteListByRoleIdAndMenuIds(roleId, deleteMenuIds); } + // 发送刷新消息. 注意,需要事务提交后,在进行发送刷新消息。不然 db 还未提交,结果缓存先刷新了 + TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronization() { + + @Override + public void afterCommit() { + permissionProducer.sendRoleMenuRefreshMessage(); + } + + }); } @Override @@ -189,4 +242,39 @@ public class SysPermissionServiceImpl implements SysPermissionService { // TODO 实现我 } + @Override + public boolean hasPermission(String permission) { + return hasAnyPermissions(permission); + } + + @Override + public boolean hasAnyPermissions(String... permissions) { + // 如果为空,说明已经有权限 + if (ArrayUtil.isEmpty(permissions)) { + return true; + } + + // 获得当前登陆的角色。如果为空,说明没有权限 + Set roleIds = SecurityUtils.getLoginUserRoleIds(); + if (CollUtil.isEmpty(roleIds)) { + return false; + } + // 判断是否是超管。如果是,当然符合条件 + if (roleService.hasAnyAdmin(roleIds)) { + return true; + } + + // 遍历权限,判断是否有一个满足 + return Arrays.stream(permissions).anyMatch(permission -> { + List menuList = menuService.getMenuListByPermissionFromCache(permission); + // 采用严格模式,如果权限找不到对应的 Menu 的话,认为 + if (CollUtil.isEmpty(menuList)) { + return false; + } + // 获得是否拥有该权限,任一一个 + return menuList.stream().anyMatch(menu -> CollUtil.containsAny(roleIds, + menuRoleCache.get(menu.getId()))); + }); + } + } diff --git a/src/main/java/cn/iocoder/dashboard/modules/system/service/permission/impl/SysRoleServiceImpl.java b/src/main/java/cn/iocoder/dashboard/modules/system/service/permission/impl/SysRoleServiceImpl.java index 6b2d74cc4..a40896b97 100644 --- a/src/main/java/cn/iocoder/dashboard/modules/system/service/permission/impl/SysRoleServiceImpl.java +++ b/src/main/java/cn/iocoder/dashboard/modules/system/service/permission/impl/SysRoleServiceImpl.java @@ -217,6 +217,8 @@ public class SysRoleServiceImpl implements SysRoleService { updateObject.setId(id); updateObject.setStatus(status); roleMapper.updateById(updateObject); + // 发送刷新消息 + roleProducer.sendRoleRefreshMessage(); } @Override @@ -229,6 +231,8 @@ public class SysRoleServiceImpl implements SysRoleService { updateObject.setDataScope(dataScope); updateObject.setDataScopeDeptIds(dataScopeDeptIds); roleMapper.updateById(updateObject); + // 发送刷新消息 + roleProducer.sendRoleRefreshMessage(); } /** diff --git a/src/main/resources/application.yaml b/src/main/resources/application.yaml index 8eb614f9b..5c0441571 100644 --- a/src/main/resources/application.yaml +++ b/src/main/resources/application.yaml @@ -34,7 +34,7 @@ yudao: token-timeout: 1d session-timeout: 30m mock-enable: true - mock-secret: yudaoyuanma + mock-secret: test swagger: title: 管理后台 description: 提供管理员管理的所有功能