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: 提供管理员管理的所有功能