Commit bbe71ec2c893abc4e8a4a983f48e416f9e3e94db

Authored by YunaiV
1 parent aa38c0f9

完成部分权限的认证操作的迁移

Showing 39 changed files with 370 additions and 394 deletions
http-client.env.json
1 1 {
2 2 "local": {
3 3 "baseUrl": "http://127.0.0.1:8080/api",
4   - "token": "yudaoyuanma1"
  4 + "token": "test1"
5 5 }
6 6 }
... ...
ruoyi-admin/src/main/java/com/ruoyi/web/controller/monitor/ServerController.java
... ... @@ -9,17 +9,15 @@ import com.ruoyi.framework.web.domain.Server;
9 9  
10 10 /**
11 11 * 服务器监控
12   - *
  12 + *
13 13 * @author ruoyi
14 14 */
15 15 @RestController
16 16 @RequestMapping("/monitor/server")
17   -public class ServerController
18   -{
  17 +public class ServerController {
19 18 @PreAuthorize("@ss.hasPermi('monitor:server:list')")
20 19 @GetMapping()
21   - public AjaxResult getInfo() throws Exception
22   - {
  20 + public AjaxResult getInfo() throws Exception {
23 21 Server server = new Server();
24 22 server.copyTo();
25 23 return AjaxResult.success(server);
... ...
ruoyi-admin/src/main/java/com/ruoyi/web/controller/tool/SwaggerController.java renamed to ruoyi-admin/src/main/java/com/ruoyi/web/controller/monitor/SwaggerController.java
1   -package com.ruoyi.web.controller.tool;
2   -
3   -import org.springframework.security.access.prepost.PreAuthorize;
4   -import org.springframework.stereotype.Controller;
5   -import org.springframework.web.bind.annotation.GetMapping;
6   -import org.springframework.web.bind.annotation.RequestMapping;
7   -import com.ruoyi.common.core.controller.BaseController;
8   -
9   -/**
10   - * swagger 接口
11   - *
12   - * @author ruoyi
13   - */
14   -@Controller
15   -@RequestMapping("/tool/swagger")
16   -public class SwaggerController extends BaseController
17   -{
18   - @PreAuthorize("@ss.hasPermi('tool:swagger:view')")
19   - @GetMapping()
20   - public String index()
21   - {
22   - return redirect("/swagger-ui.html");
23   - }
24   -}
  1 +package com.ruoyi.web.controller.tool;
  2 +
  3 +import org.springframework.security.access.prepost.PreAuthorize;
  4 +import org.springframework.stereotype.Controller;
  5 +import org.springframework.web.bind.annotation.GetMapping;
  6 +import org.springframework.web.bind.annotation.RequestMapping;
  7 +import com.ruoyi.common.core.controller.BaseController;
  8 +
  9 +/**
  10 + * swagger 接口
  11 + *
  12 + * @author ruoyi
  13 + */
  14 +@Controller
  15 +@RequestMapping("/tool/swagger")
  16 +public class SwaggerController extends BaseController
  17 +{
  18 + @PreAuthorize("@ss.hasPermi('tool:swagger:view')")
  19 + @GetMapping()
  20 + public String index()
  21 + {
  22 + return redirect("/swagger-ui.html");
  23 + }
  24 +}
... ...
ruoyi-admin/src/main/java/com/ruoyi/web/controller/monitor/SysUserOnline.java
... ... @@ -5,31 +5,45 @@ package com.ruoyi.system.domain;
5 5 *
6 6 * @author ruoyi
7 7 */
8   -public class SysUserOnline
9   -{
10   - /** 会话编号 */
  8 +public class SysUserOnline {
  9 + /**
  10 + * 会话编号
  11 + */
11 12 private String tokenId;
12 13  
13   - /** 部门名称 */
  14 + /**
  15 + * 部门名称
  16 + */
14 17 private String deptName;
15 18  
16   - /** 用户名称 */
  19 + /**
  20 + * 用户名称
  21 + */
17 22 private String userName;
18 23  
19   - /** 登录IP地址 */
  24 + /**
  25 + * 登录IP地址
  26 + */
20 27 private String ipaddr;
21 28  
22   - /** 登录地址 */
  29 + /**
  30 + * 登录地址
  31 + */
23 32 private String loginLocation;
24 33  
25   - /** 浏览器类型 */
  34 + /**
  35 + * 浏览器类型
  36 + */
26 37 private String browser;
27 38  
28   - /** 操作系统 */
  39 + /**
  40 + * 操作系统
  41 + */
29 42 private String os;
30 43  
31   - /** 登录时间 */
  44 + /**
  45 + * 登录时间
  46 + */
32 47 private Long loginTime;
33 48  
34   -
35 49 }
... ...
ruoyi-common/src/main/java/com/ruoyi/common/annotation/DataScope.java
... ... @@ -8,14 +8,13 @@ import java.lang.annotation.Target;
8 8  
9 9 /**
10 10 * 数据权限过滤注解
11   - *
  11 + *
12 12 * @author ruoyi
13 13 */
14 14 @Target(ElementType.METHOD)
15 15 @Retention(RetentionPolicy.RUNTIME)
16 16 @Documented
17   -public @interface DataScope
18   -{
  17 +public @interface DataScope {
19 18 /**
20 19 * 部门表的别名
21 20 */
... ...
ruoyi-common/src/main/java/com/ruoyi/common/annotation/DataSource.java
... ... @@ -6,21 +6,21 @@ import java.lang.annotation.Inherited;
6 6 import java.lang.annotation.Retention;
7 7 import java.lang.annotation.RetentionPolicy;
8 8 import java.lang.annotation.Target;
  9 +
9 10 import com.ruoyi.common.enums.DataSourceType;
10 11  
11 12 /**
12 13 * 自定义多数据源切换注解
13   - *
  14 + * <p>
14 15 * 优先级:先方法,后类,如果方法覆盖了类上的数据源类型,以方法的为准,否则以类上的为准
15 16 *
16 17 * @author ruoyi
17 18 */
18   -@Target({ ElementType.METHOD, ElementType.TYPE })
  19 +@Target({ElementType.METHOD, ElementType.TYPE})
19 20 @Retention(RetentionPolicy.RUNTIME)
20 21 @Documented
21 22 @Inherited
22   -public @interface DataSource
23   -{
  23 +public @interface DataSource {
24 24 /**
25 25 * 切换数据源名称
26 26 */
... ...
ruoyi-common/src/main/java/com/ruoyi/common/annotation/RepeatSubmit.java
... ... @@ -9,15 +9,13 @@ import java.lang.annotation.Target;
9 9  
10 10 /**
11 11 * 自定义注解防止表单重复提交
12   - *
13   - * @author ruoyi
14 12 *
  13 + * @author ruoyi
15 14 */
16 15 @Inherited
17 16 @Target(ElementType.METHOD)
18 17 @Retention(RetentionPolicy.RUNTIME)
19 18 @Documented
20   -public @interface RepeatSubmit
21   -{
  19 +public @interface RepeatSubmit {
22 20  
23 21 }
... ...
ruoyi-common/src/main/java/com/ruoyi/common/constant/GenConstants.java
... ... @@ -2,7 +2,7 @@ package com.ruoyi.common.constant;
2 2  
3 3 /**
4 4 * 代码生成通用常量
5   - *
  5 + *
6 6 * @author ruoyi
7 7 */
8 8 public class GenConstants
... ... @@ -13,6 +13,7 @@ public class GenConstants
13 13 /** 树表(增删改查) */
14 14 public static final String TPL_TREE = "tree";
15 15  
  16 +
16 17 /** 树编码字段 */
17 18 public static final String TREE_CODE = "treeCode";
18 19  
... ...
ruoyi-common/src/main/java/com/ruoyi/common/filter/RepeatableFilter.java
... ... @@ -8,45 +8,38 @@ import javax.servlet.ServletException;
8 8 import javax.servlet.ServletRequest;
9 9 import javax.servlet.ServletResponse;
10 10 import javax.servlet.http.HttpServletRequest;
  11 +
11 12 import org.springframework.http.MediaType;
12 13 import com.ruoyi.common.utils.StringUtils;
13 14  
14 15 /**
15 16 * Repeatable 过滤器
16   - *
  17 + *
17 18 * @author ruoyi
18 19 */
19   -public class RepeatableFilter implements Filter
20   -{
  20 +public class RepeatableFilter implements Filter {
21 21 @Override
22   - public void init(FilterConfig filterConfig) throws ServletException
23   - {
  22 + public void init(FilterConfig filterConfig) throws ServletException {
24 23  
25 24 }
26 25  
27 26 @Override
28 27 public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
29   - throws IOException, ServletException
30   - {
  28 + throws IOException, ServletException {
31 29 ServletRequest requestWrapper = null;
32 30 if (request instanceof HttpServletRequest
33   - && StringUtils.equalsAnyIgnoreCase(request.getContentType(), MediaType.APPLICATION_JSON_VALUE))
34   - {
  31 + && StringUtils.equalsAnyIgnoreCase(request.getContentType(), MediaType.APPLICATION_JSON_VALUE)) {
35 32 requestWrapper = new RepeatedlyRequestWrapper((HttpServletRequest) request, response);
36 33 }
37   - if (null == requestWrapper)
38   - {
  34 + if (null == requestWrapper) {
39 35 chain.doFilter(request, response);
40   - }
41   - else
42   - {
  36 + } else {
43 37 chain.doFilter(requestWrapper, response);
44 38 }
45 39 }
46 40  
47 41 @Override
48   - public void destroy()
49   - {
  42 + public void destroy() {
50 43  
51 44 }
52 45 }
... ...
ruoyi-common/src/main/java/com/ruoyi/common/filter/RepeatedlyRequestWrapper.java
... ... @@ -9,19 +9,18 @@ import javax.servlet.ServletInputStream;
9 9 import javax.servlet.ServletResponse;
10 10 import javax.servlet.http.HttpServletRequest;
11 11 import javax.servlet.http.HttpServletRequestWrapper;
  12 +
12 13 import com.ruoyi.common.utils.http.HttpHelper;
13 14  
14 15 /**
15 16 * 构建可重复读取inputStream的request
16   - *
  17 + *
17 18 * @author ruoyi
18 19 */
19   -public class RepeatedlyRequestWrapper extends HttpServletRequestWrapper
20   -{
  20 +public class RepeatedlyRequestWrapper extends HttpServletRequestWrapper {
21 21 private final byte[] body;
22 22  
23   - public RepeatedlyRequestWrapper(HttpServletRequest request, ServletResponse response) throws IOException
24   - {
  23 + public RepeatedlyRequestWrapper(HttpServletRequest request, ServletResponse response) throws IOException {
25 24 super(request);
26 25 request.setCharacterEncoding("UTF-8");
27 26 response.setCharacterEncoding("UTF-8");
... ... @@ -30,41 +29,34 @@ public class RepeatedlyRequestWrapper extends HttpServletRequestWrapper
30 29 }
31 30  
32 31 @Override
33   - public BufferedReader getReader() throws IOException
34   - {
  32 + public BufferedReader getReader() throws IOException {
35 33 return new BufferedReader(new InputStreamReader(getInputStream()));
36 34 }
37 35  
38 36 @Override
39   - public ServletInputStream getInputStream() throws IOException
40   - {
  37 + public ServletInputStream getInputStream() throws IOException {
41 38  
42 39 final ByteArrayInputStream bais = new ByteArrayInputStream(body);
43 40  
44   - return new ServletInputStream()
45   - {
  41 + return new ServletInputStream() {
46 42  
47 43 @Override
48   - public int read() throws IOException
49   - {
  44 + public int read() throws IOException {
50 45 return bais.read();
51 46 }
52 47  
53 48 @Override
54   - public boolean isFinished()
55   - {
  49 + public boolean isFinished() {
56 50 return false;
57 51 }
58 52  
59 53 @Override
60   - public boolean isReady()
61   - {
  54 + public boolean isReady() {
62 55 return false;
63 56 }
64 57  
65 58 @Override
66   - public void setReadListener(ReadListener readListener)
67   - {
  59 + public void setReadListener(ReadListener readListener) {
68 60  
69 61 }
70 62 };
... ...
ruoyi-framework/src/main/java/com/ruoyi/framework/web/domain/Server.java
... ... @@ -55,46 +55,6 @@ public class Server {
55 55 */
56 56 private List<SysFile> sysFiles = new LinkedList<SysFile>();
57 57  
58   - public Cpu getCpu() {
59   - return cpu;
60   - }
61   -
62   - public void setCpu(Cpu cpu) {
63   - this.cpu = cpu;
64   - }
65   -
66   - public Mem getMem() {
67   - return mem;
68   - }
69   -
70   - public void setMem(Mem mem) {
71   - this.mem = mem;
72   - }
73   -
74   - public Jvm getJvm() {
75   - return jvm;
76   - }
77   -
78   - public void setJvm(Jvm jvm) {
79   - this.jvm = jvm;
80   - }
81   -
82   - public Sys getSys() {
83   - return sys;
84   - }
85   -
86   - public void setSys(Sys sys) {
87   - this.sys = sys;
88   - }
89   -
90   - public List<SysFile> getSysFiles() {
91   - return sysFiles;
92   - }
93   -
94   - public void setSysFiles(List<SysFile> sysFiles) {
95   - this.sysFiles = sysFiles;
96   - }
97   -
98 58 public void copyTo() throws Exception {
99 59 SystemInfo si = new SystemInfo();
100 60 HardwareAbstractionLayer hal = si.getHardware();
... ...
ruoyi-framework/src/main/java/com/ruoyi/framework/web/domain/server/Cpu.java
... ... @@ -38,22 +38,10 @@ public class Cpu {
38 38 */
39 39 private double free;
40 40  
41   - public int getCpuNum() {
42   - return cpuNum;
43   - }
44   -
45   - public void setCpuNum(int cpuNum) {
46   - this.cpuNum = cpuNum;
47   - }
48   -
49 41 public double getTotal() {
50 42 return Arith.round(Arith.mul(total, 100), 2);
51 43 }
52 44  
53   - public void setTotal(double total) {
54   - this.total = total;
55   - }
56   -
57 45 public double getSys() {
58 46 return Arith.round(Arith.mul(sys / total, 100), 2);
59 47 }
... ...
ruoyi-framework/src/main/java/com/ruoyi/framework/web/domain/server/Jvm.java
... ... @@ -40,18 +40,10 @@ public class Jvm {
40 40 return Arith.div(total, (1024 * 1024), 2);
41 41 }
42 42  
43   - public void setTotal(double total) {
44   - this.total = total;
45   - }
46   -
47 43 public double getMax() {
48 44 return Arith.div(max, (1024 * 1024), 2);
49 45 }
50 46  
51   - public void setMax(double max) {
52   - this.max = max;
53   - }
54   -
55 47 public double getFree() {
56 48 return Arith.div(free, (1024 * 1024), 2);
57 49 }
... ... @@ -75,22 +67,6 @@ public class Jvm {
75 67 return ManagementFactory.getRuntimeMXBean().getVmName();
76 68 }
77 69  
78   - public String getVersion() {
79   - return version;
80   - }
81   -
82   - public void setVersion(String version) {
83   - this.version = version;
84   - }
85   -
86   - public String getHome() {
87   - return home;
88   - }
89   -
90   - public void setHome(String home) {
91   - this.home = home;
92   - }
93   -
94 70 /**
95 71 * JDK启动时间
96 72 */
... ...
ruoyi-framework/src/main/java/com/ruoyi/framework/web/domain/server/Mem.java
... ... @@ -27,27 +27,16 @@ public class Mem {
27 27 return Arith.div(total, (1024 * 1024 * 1024), 2);
28 28 }
29 29  
30   - public void setTotal(long total) {
31   - this.total = total;
32   - }
33   -
34 30 public double getUsed() {
35 31 return Arith.div(used, (1024 * 1024 * 1024), 2);
36 32 }
37 33  
38   - public void setUsed(long used) {
39   - this.used = used;
40   - }
41   -
42 34 public double getFree() {
43 35 return Arith.div(free, (1024 * 1024 * 1024), 2);
44 36 }
45 37  
46   - public void setFree(long free) {
47   - this.free = free;
48   - }
49   -
50 38 public double getUsage() {
51 39 return Arith.mul(Arith.div(used, total, 4), 100);
52 40 }
  41 +
53 42 }
... ...
ruoyi-framework/src/main/java/com/ruoyi/framework/web/domain/server/Sys.java
... ... @@ -31,43 +31,4 @@ public class Sys {
31 31 */
32 32 private String osArch;
33 33  
34   - public String getComputerName() {
35   - return computerName;
36   - }
37   -
38   - public void setComputerName(String computerName) {
39   - this.computerName = computerName;
40   - }
41   -
42   - public String getComputerIp() {
43   - return computerIp;
44   - }
45   -
46   - public void setComputerIp(String computerIp) {
47   - this.computerIp = computerIp;
48   - }
49   -
50   - public String getUserDir() {
51   - return userDir;
52   - }
53   -
54   - public void setUserDir(String userDir) {
55   - this.userDir = userDir;
56   - }
57   -
58   - public String getOsName() {
59   - return osName;
60   - }
61   -
62   - public void setOsName(String osName) {
63   - this.osName = osName;
64   - }
65   -
66   - public String getOsArch() {
67   - return osArch;
68   - }
69   -
70   - public void setOsArch(String osArch) {
71   - this.osArch = osArch;
72   - }
73 34 }
... ...
ruoyi-framework/src/main/java/com/ruoyi/framework/web/domain/server/SysFile.java
... ... @@ -41,59 +41,4 @@ public class SysFile {
41 41 */
42 42 private double usage;
43 43  
44   - public String getDirName() {
45   - return dirName;
46   - }
47   -
48   - public void setDirName(String dirName) {
49   - this.dirName = dirName;
50   - }
51   -
52   - public String getSysTypeName() {
53   - return sysTypeName;
54   - }
55   -
56   - public void setSysTypeName(String sysTypeName) {
57   - this.sysTypeName = sysTypeName;
58   - }
59   -
60   - public String getTypeName() {
61   - return typeName;
62   - }
63   -
64   - public void setTypeName(String typeName) {
65   - this.typeName = typeName;
66   - }
67   -
68   - public String getTotal() {
69   - return total;
70   - }
71   -
72   - public void setTotal(String total) {
73   - this.total = total;
74   - }
75   -
76   - public String getFree() {
77   - return free;
78   - }
79   -
80   - public void setFree(String free) {
81   - this.free = free;
82   - }
83   -
84   - public String getUsed() {
85   - return used;
86   - }
87   -
88   - public void setUsed(String used) {
89   - this.used = used;
90   - }
91   -
92   - public double getUsage() {
93   - return usage;
94   - }
95   -
96   - public void setUsage(double usage) {
97   - this.usage = usage;
98   - }
99 44 }
... ...
ruoyi-framework/src/main/java/com/ruoyi/framework/web/service/PermissionService.java
... ... @@ -17,68 +17,6 @@ import com.ruoyi.common.utils.StringUtils;
17 17 */
18 18 @Service("ss")
19 19 public class PermissionService {
20   - /**
21   - * 所有权限标识
22   - */
23   - private static final String ALL_PERMISSION = "*:*:*";
24   -
25   - /**
26   - * 管理员角色权限标识
27   - */
28   - private static final String SUPER_ADMIN = "admin";
29   -
30   - @Autowired
31   - private TokenService tokenService;
32   -
33   - /**
34   - * 验证用户是否具备某权限
35   - *
36   - * @param permission 权限字符串
37   - * @return 用户是否具备某权限
38   - */
39   - public boolean hasPermi(String permission) {
40   - if (StringUtils.isEmpty(permission)) {
41   - return false;
42   - }
43   - LoginUser loginUser = tokenService.getLoginUser(ServletUtils.getRequest());
44   - if (StringUtils.isNull(loginUser) || CollectionUtils.isEmpty(loginUser.getPermissions())) {
45   - return false;
46   - }
47   - return hasPermissions(loginUser.getPermissions(), permission);
48   - }
49   -
50   - /**
51   - * 验证用户是否不具备某权限,与 hasPermi逻辑相反
52   - *
53   - * @param permission 权限字符串
54   - * @return 用户是否不具备某权限
55   - */
56   - public boolean lacksPermi(String permission) {
57   - return hasPermi(permission) != true;
58   - }
59   -
60   - /**
61   - * 验证用户是否具有以下任意一个权限
62   - *
63   - * @param permissions 以 PERMISSION_NAMES_DELIMETER 为分隔符的权限列表
64   - * @return 用户是否具有以下任意一个权限
65   - */
66   - public boolean hasAnyPermi(String permissions) {
67   - if (StringUtils.isEmpty(permissions)) {
68   - return false;
69   - }
70   - LoginUser loginUser = tokenService.getLoginUser(ServletUtils.getRequest());
71   - if (StringUtils.isNull(loginUser) || CollectionUtils.isEmpty(loginUser.getPermissions())) {
72   - return false;
73   - }
74   - Set<String> authorities = loginUser.getPermissions();
75   - for (String permission : permissions.split(PERMISSION_DELIMETER)) {
76   - if (permission != null && hasPermissions(authorities, permission)) {
77   - return true;
78   - }
79   - }
80   - return false;
81   - }
82 20  
83 21 /**
84 22 * 判断用户是否拥有某个角色
... ... @@ -104,16 +42,6 @@ public class PermissionService {
104 42 }
105 43  
106 44 /**
107   - * 验证用户是否不具备某角色,与 isRole逻辑相反。
108   - *
109   - * @param role 角色名称
110   - * @return 用户是否不具备某角色
111   - */
112   - public boolean lacksRole(String role) {
113   - return hasRole(role) != true;
114   - }
115   -
116   - /**
117 45 * 验证用户是否具有以下任意一个角色
118 46 *
119 47 * @param roles 以 ROLE_NAMES_DELIMETER 为分隔符的角色列表
... ... @@ -135,14 +63,4 @@ public class PermissionService {
135 63 return false;
136 64 }
137 65  
138   - /**
139   - * 判断是否包含权限
140   - *
141   - * @param permissions 权限列表
142   - * @param permission 权限字符串
143   - * @return 用户是否具备某权限
144   - */
145   - private boolean hasPermissions(Set<String> permissions, String permission) {
146   - return permissions.contains(ALL_PERMISSION) || permissions.contains(StringUtils.trim(permission));
147   - }
148 66 }
... ...
ruoyi-ui/src/utils/request.js
... ... @@ -61,7 +61,9 @@ service.interceptors.response.use(res =&gt; {
61 61 }
62 62 ).then(() => {
63 63 store.dispatch('LogOut').then(() => {
64   - location.href = '/index';
  64 + if (location.pathname !== '/login') { // 避免重复跳转
  65 + location.href = '/index';
  66 + }
65 67 })
66 68 })
67 69 } else if (code === 500) {
... ...
ruoyi-ui/src/views/system/role/index.vue
... ... @@ -392,6 +392,7 @@ export default {
392 392 menuIds: [],
393 393 dataScope: undefined,
394 394 deptCheckStrictly: false,
  395 + menuCheckStrictly: true,
395 396 remark: undefined
396 397 };
397 398 this.resetForm("form");
... ... @@ -471,8 +472,12 @@ export default {
471 472 });
472 473 // 获得角色拥有的菜单集合
473 474 listRoleMenus(id).then(response => {
  475 + // 设置为严格,避免设置父节点自动选中子节点,解决半选中问题
  476 + this.form.menuCheckStrictly = true
474 477 // 设置选中
475   - this.$refs.menu.setCheckedKeys(response.data, true, false);
  478 + this.$refs.menu.setCheckedKeys(response.data);
  479 + // 设置为非严格,继续使用半选中
  480 + this.form.menuCheckStrictly = false
476 481 })
477 482 },
478 483 /** 分配数据权限操作 */
... ... @@ -523,7 +528,7 @@ export default {
523 528 roleId: this.form.id,
524 529 dataScope: this.form.dataScope,
525 530 dataScopeDeptIds: this.form.dataScope !== SysDataScopeEnum.DEPT_CUSTOM ? [] :
526   - this.$refs.dept.getCheckedKeys(false)
  531 + this.$refs.dept.getCheckedKeys()
527 532 }).then(response => {
528 533 this.msgSuccess("修改成功");
529 534 this.openDataScope = false;
... ... @@ -536,7 +541,7 @@ export default {
536 541 if (this.form.id !== undefined) {
537 542 assignRoleMenu({
538 543 roleId: this.form.id,
539   - menuIds: this.$refs.menu.getCheckedKeys(true)
  544 + menuIds: [...this.$refs.menu.getCheckedKeys(), ...this.$refs.menu.getHalfCheckedKeys()]
540 545 }).then(response => {
541 546 this.msgSuccess("修改成功");
542 547 this.openMenu = false;
... ...
src/main/java/cn/iocoder/dashboard/framework/logger/operatelog/core/aop/OperateLogAspect.java
... ... @@ -233,11 +233,6 @@ public class OperateLogAspect {
233 233 }
234 234 }
235 235  
236   - private static void fillContentFields(SysOperateLogCreateReqVO operateLogVO) {
237   - operateLogVO.setContent(CONTENT.get());
238   - operateLogVO.setExts(EXTS.get());
239   - }
240   -
241 236 private static boolean isLogEnable(ProceedingJoinPoint joinPoint, OperateLog operateLog) {
242 237 // 有 @OperateLog 注解的情况下
243 238 if (operateLog != null) {
... ...
src/main/java/cn/iocoder/dashboard/framework/security/core/handler/LogoutSuccessHandlerImpl.java
... ... @@ -2,7 +2,7 @@ package cn.iocoder.dashboard.framework.security.core.handler;
2 2  
3 3 import cn.hutool.core.util.StrUtil;
4 4 import cn.iocoder.dashboard.framework.security.config.SecurityProperties;
5   -import cn.iocoder.dashboard.framework.security.core.service.SecurityFrameworkService;
  5 +import cn.iocoder.dashboard.framework.security.core.service.SecurityAuthFrameworkService;
6 6 import cn.iocoder.dashboard.framework.security.core.util.SecurityUtils;
7 7 import cn.iocoder.dashboard.util.servlet.ServletUtils;
8 8 import org.springframework.security.core.Authentication;
... ... @@ -26,7 +26,7 @@ public class LogoutSuccessHandlerImpl implements LogoutSuccessHandler {
26 26 private SecurityProperties securityProperties;
27 27  
28 28 @Resource
29   - private SecurityFrameworkService securityFrameworkService;
  29 + private SecurityAuthFrameworkService securityFrameworkService;
30 30  
31 31 @Override
32 32 public void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) {
... ...
src/main/java/cn/iocoder/dashboard/framework/security/core/service/SecurityFrameworkService.java renamed to src/main/java/cn/iocoder/dashboard/framework/security/core/service/SecurityAuthFrameworkService.java
... ... @@ -4,16 +4,11 @@ import cn.iocoder.dashboard.framework.security.core.LoginUser;
4 4 import org.springframework.security.core.userdetails.UserDetailsService;
5 5  
6 6 /**
7   - * Security 框架 Service 接口,定义 security 组件需要的功能
  7 + * Security 框架 Auth Service 接口,定义 security 组件需要的功能
  8 + *
  9 + * @author 芋道源码
8 10 */
9   -public interface SecurityFrameworkService extends UserDetailsService {
10   -
11   - /**
12   - * 基于 token 退出登录
13   - *
14   - * @param token token
15   - */
16   - void logout(String token);
  11 +public interface SecurityAuthFrameworkService extends UserDetailsService {
17 12  
18 13 /**
19 14 * 校验 token 的有效性,并获取用户信息
... ... @@ -32,4 +27,11 @@ public interface SecurityFrameworkService extends UserDetailsService {
32 27 */
33 28 LoginUser mockLogin(Long userId);
34 29  
  30 + /**
  31 + * 基于 token 退出登录
  32 + *
  33 + * @param token token
  34 + */
  35 + void logout(String token);
  36 +
35 37 }
... ...
src/main/java/cn/iocoder/dashboard/framework/security/core/service/SecurityPermissionFrameworkService.java 0 → 100644
  1 +package cn.iocoder.dashboard.framework.security.core.service;
  2 +
  3 +/**
  4 + * Security 框架 Permission Service 接口,定义 security 组件需要的功能
  5 + *
  6 + * @author 芋道源码
  7 + */
  8 +public interface SecurityPermissionFrameworkService {
  9 +
  10 + /**
  11 + * 判断是否有权限
  12 + *
  13 + * @param permission 权限
  14 + * @return 是否
  15 + */
  16 + boolean hasPermission(String permission);
  17 +
  18 + /**
  19 + * 判断是否有权限,任一一个即可
  20 + *
  21 + * @param permissions 权限
  22 + * @return 是否
  23 + */
  24 + boolean hasAnyPermissions(String... permissions);
  25 +
  26 +}
... ...
src/main/java/cn/iocoder/dashboard/framework/web/core/handler/GlobalExceptionHandler.java
... ... @@ -3,7 +3,9 @@ package cn.iocoder.dashboard.framework.web.core.handler;
3 3 import cn.iocoder.dashboard.common.exception.GlobalException;
4 4 import cn.iocoder.dashboard.common.exception.ServiceException;
5 5 import cn.iocoder.dashboard.common.pojo.CommonResult;
  6 +import cn.iocoder.dashboard.framework.security.core.util.SecurityUtils;
6 7 import lombok.extern.slf4j.Slf4j;
  8 +import org.springframework.security.access.AccessDeniedException;
7 9 import org.springframework.validation.BindException;
8 10 import org.springframework.validation.FieldError;
9 11 import org.springframework.web.HttpRequestMethodNotSupportedException;
... ... @@ -64,6 +66,9 @@ public class GlobalExceptionHandler {
64 66 if (ex instanceof ServiceException) {
65 67 return serviceExceptionHandler((ServiceException) ex);
66 68 }
  69 + if (ex instanceof AccessDeniedException) {
  70 + return accessDeniedExceptionHandler(request, (AccessDeniedException) ex);
  71 + }
67 72 if (ex instanceof GlobalException) {
68 73 return globalExceptionHandler(request, (GlobalException) ex);
69 74 }
... ... @@ -131,7 +136,7 @@ public class GlobalExceptionHandler {
131 136 public CommonResult<?> validationException(ValidationException ex) {
132 137 log.warn("[constraintViolationExceptionHandler]", ex);
133 138 // 无法拼接明细的错误信息,因为 Dubbo Consumer 抛出 ValidationException 异常时,是直接的字符串信息,且人类不可读
134   - return CommonResult.error(BAD_REQUEST.getCode(), "请求参数不正确");
  139 + return CommonResult.error(BAD_REQUEST);
135 140 }
136 141  
137 142 /**
... ... @@ -159,6 +164,18 @@ public class GlobalExceptionHandler {
159 164 }
160 165  
161 166 /**
  167 + * 处理 Spring Security 权限不足的异常
  168 + *
  169 + * 来源是,使用 @PreAuthorize 注解,AOP 进行权限拦截
  170 + */
  171 + @ExceptionHandler(value = AccessDeniedException.class)
  172 + public CommonResult<?> accessDeniedExceptionHandler(HttpServletRequest req, AccessDeniedException ex) {
  173 + log.warn("[accessDeniedExceptionHandler][userId({}) 无法访问 url({})]", SecurityUtils.getLoginUserId(),
  174 + req.getRequestURL(), ex);
  175 + return CommonResult.error(FORBIDDEN);
  176 + }
  177 +
  178 + /**
162 179 * 处理业务异常 ServiceException
163 180 *
164 181 * 例如说,商品库存不足,用户手机号已存在。
... ...
src/main/java/cn/iocoder/dashboard/modules/system/controller/auth/vo/SysAuthLoginReqVO.java
... ... @@ -7,9 +7,7 @@ import lombok.Builder;
7 7 import lombok.Data;
8 8 import lombok.NoArgsConstructor;
9 9 import org.hibernate.validator.constraints.Length;
10   -import org.springframework.validation.annotation.Validated;
11 10  
12   -import javax.validation.Valid;
13 11 import javax.validation.constraints.NotEmpty;
14 12 import javax.validation.constraints.Pattern;
15 13  
... ... @@ -22,7 +20,7 @@ public class SysAuthLoginReqVO {
22 20  
23 21 @ApiModelProperty(value = "账号", required = true, example = "yudaoyuanma")
24 22 @NotEmpty(message = "登陆账号不能为空")
25   - @Length(min = 5, max = 16, message = "账号长度为 5-16 位")
  23 + @Length(min = 4, max = 16, message = "账号长度为 4-16 位")
26 24 @Pattern(regexp = "^[A-Za-z0-9]+$", message = "账号格式为数字以及字母")
27 25 private String username;
28 26  
... ...
src/main/java/cn/iocoder/dashboard/modules/system/controller/user/SysUserController.http 0 → 100644
  1 +### 请求 /system/user/page 接口 => 没有权限
  2 +GET {{baseUrl}}/system/user/page?pageNo=1&pageSize=10
  3 +Authorization: Bearer test104 # 使用测试账号
... ...
src/main/java/cn/iocoder/dashboard/modules/system/controller/user/SysUserController.java
... ... @@ -18,6 +18,7 @@ import io.swagger.annotations.Api;
18 18 import io.swagger.annotations.ApiImplicitParam;
19 19 import io.swagger.annotations.ApiImplicitParams;
20 20 import io.swagger.annotations.ApiOperation;
  21 +import org.springframework.security.access.prepost.PreAuthorize;
21 22 import org.springframework.validation.annotation.Validated;
22 23 import org.springframework.web.bind.annotation.*;
23 24 import org.springframework.web.multipart.MultipartFile;
... ... @@ -41,7 +42,7 @@ public class SysUserController {
41 42  
42 43 @ApiOperation("获得用户分页列表")
43 44 @GetMapping("/page")
44   -// @PreAuthorize("@ss.hasPermi('system:user:list')")
  45 + @PreAuthorize("@ss.hasPermission('system:user:list')")
45 46 public CommonResult<PageResult<SysUserPageItemRespVO>> pageUsers(@Validated SysUserPageReqVO reqVO) {
46 47 // 获得用户分页列表
47 48 PageResult<SysUserDO> pageResult = userService.pageUsers(reqVO);
... ...
src/main/java/cn/iocoder/dashboard/modules/system/dal/mysql/dao/permission/SysRoleMenuMapper.java
1 1 package cn.iocoder.dashboard.modules.system.dal.mysql.dao.permission;
2 2  
  3 +import cn.iocoder.dashboard.framework.mybatis.core.mapper.BaseMapperX;
3 4 import cn.iocoder.dashboard.modules.system.dal.mysql.dataobject.permission.SysRoleMenuDO;
4 5 import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
5   -import com.baomidou.mybatisplus.core.mapper.BaseMapper;
6 6 import org.apache.ibatis.annotations.Mapper;
7 7  
8 8 import java.util.Collection;
  9 +import java.util.Date;
9 10 import java.util.List;
10 11 import java.util.stream.Collectors;
11 12  
12 13 @Mapper
13   -public interface SysRoleMenuMapper extends BaseMapper<SysRoleMenuDO> {
  14 +public interface SysRoleMenuMapper extends BaseMapperX<SysRoleMenuDO> {
14 15  
15 16 default List<SysRoleMenuDO> selectListByRoleId(Long roleId) {
16 17 return selectList(new QueryWrapper<SysRoleMenuDO>().eq("role_id", roleId));
... ... @@ -32,4 +33,9 @@ public interface SysRoleMenuMapper extends BaseMapper&lt;SysRoleMenuDO&gt; {
32 33 .in("menu_id", menuIds));
33 34 }
34 35  
  36 + default boolean selectExistsByUpdateTimeAfter(Date maxUpdateTime) {
  37 + return selectOne(new QueryWrapper<SysRoleMenuDO>().select("id")
  38 + .gt("update_time", maxUpdateTime).last("LIMIT 1")) != null;
  39 + }
  40 +
35 41 }
... ...
src/main/java/cn/iocoder/dashboard/modules/system/mq/consumer/permission/SysRoleMenuRefreshConsumer.java 0 → 100644
  1 +package cn.iocoder.dashboard.modules.system.mq.consumer.permission;
  2 +
  3 +import cn.iocoder.dashboard.framework.redis.core.pubsub.AbstractChannelMessageListener;
  4 +import cn.iocoder.dashboard.modules.system.mq.message.permission.SysRoleMenuRefreshMessage;
  5 +import cn.iocoder.dashboard.modules.system.service.permission.SysPermissionService;
  6 +import lombok.extern.slf4j.Slf4j;
  7 +import org.springframework.stereotype.Component;
  8 +
  9 +import javax.annotation.Resource;
  10 +
  11 +/**
  12 + * 针对 {@link SysRoleMenuRefreshMessage} 的消费者
  13 + *
  14 + * @author 芋道源码
  15 + */
  16 +@Component
  17 +@Slf4j
  18 +public class SysRoleMenuRefreshConsumer extends AbstractChannelMessageListener<SysRoleMenuRefreshMessage> {
  19 +
  20 + @Resource
  21 + private SysPermissionService permissionService;
  22 +
  23 + @Override
  24 + public void onMessage(SysRoleMenuRefreshMessage message) {
  25 + log.info("[onMessage][收到 Role 与 Menu 的关联刷新消息]");
  26 + permissionService.initLocalCache();
  27 + }
  28 +
  29 +}
... ...
src/main/java/cn/iocoder/dashboard/modules/system/mq/message/permission/SysRoleMenuRefreshMessage.java 0 → 100644
  1 +package cn.iocoder.dashboard.modules.system.mq.message.permission;
  2 +
  3 +import cn.iocoder.dashboard.framework.redis.core.pubsub.ChannelMessage;
  4 +import lombok.Data;
  5 +
  6 +/**
  7 + * 角色与菜单数据刷新 Message
  8 + */
  9 +@Data
  10 +public class SysRoleMenuRefreshMessage implements ChannelMessage {
  11 +
  12 + @Override
  13 + public String getChannel() {
  14 + return "system.role-menu.refresh";
  15 + }
  16 +
  17 +}
... ...
src/main/java/cn/iocoder/dashboard/modules/system/mq/producer/permission/SysPermissionProducer.java 0 → 100644
  1 +package cn.iocoder.dashboard.modules.system.mq.producer.permission;
  2 +
  3 +import cn.iocoder.dashboard.framework.redis.core.util.RedisMessageUtils;
  4 +import cn.iocoder.dashboard.modules.system.mq.message.permission.SysRoleMenuRefreshMessage;
  5 +import org.springframework.data.redis.core.StringRedisTemplate;
  6 +import org.springframework.stereotype.Component;
  7 +
  8 +import javax.annotation.Resource;
  9 +
  10 +/**
  11 + * Permission 权限相关消息的 Producer
  12 + */
  13 +@Component
  14 +public class SysPermissionProducer {
  15 +
  16 + @Resource
  17 + private StringRedisTemplate stringRedisTemplate;
  18 +
  19 + /**
  20 + * 发送 {@link SysRoleMenuRefreshMessage} 消息
  21 + */
  22 + public void sendRoleMenuRefreshMessage() {
  23 + SysRoleMenuRefreshMessage message = new SysRoleMenuRefreshMessage();
  24 + RedisMessageUtils.sendChannelMessage(stringRedisTemplate, message);
  25 + }
  26 +
  27 +}
... ...
src/main/java/cn/iocoder/dashboard/modules/system/service/auth/SysAuthService.java
1 1 package cn.iocoder.dashboard.modules.system.service.auth;
2 2  
3   -import cn.iocoder.dashboard.framework.security.core.service.SecurityFrameworkService;
  3 +import cn.iocoder.dashboard.framework.security.core.service.SecurityAuthFrameworkService;
4 4  
5 5 /**
6 6 * 认证 Service 接口
... ... @@ -9,7 +9,7 @@ import cn.iocoder.dashboard.framework.security.core.service.SecurityFrameworkSer
9 9 *
10 10 * @author 芋道源码
11 11 */
12   -public interface SysAuthService extends SecurityFrameworkService {
  12 +public interface SysAuthService extends SecurityAuthFrameworkService {
13 13  
14 14 String login(String username, String password, String captchaUUID, String captchaCode);
15 15  
... ...
src/main/java/cn/iocoder/dashboard/modules/system/service/permission/SysMenuService.java
... ... @@ -59,6 +59,14 @@ public interface SysMenuService {
59 59 List<SysMenuDO> listMenusFromCache(Collection<Long> menuIds, Collection<Integer> menuTypes,
60 60 Collection<Integer> menusStatuses);
61 61  
  62 + /**
  63 + * 获得权限对应的菜单数组
  64 + *
  65 + * @param permission 权限标识
  66 + * @return 数组
  67 + */
  68 + List<SysMenuDO> getMenuListByPermissionFromCache(String permission);
  69 +
62 70 /*
63 71 * 创建菜单
64 72 *
... ...
src/main/java/cn/iocoder/dashboard/modules/system/service/permission/SysPermissionService.java
1 1 package cn.iocoder.dashboard.modules.system.service.permission;
2 2  
  3 +import cn.iocoder.dashboard.framework.security.core.service.SecurityPermissionFrameworkService;
3 4 import cn.iocoder.dashboard.modules.system.dal.mysql.dataobject.permission.SysMenuDO;
4 5 import org.springframework.lang.Nullable;
5 6  
... ... @@ -14,12 +15,12 @@ import java.util.Set;
14 15 *
15 16 * @author 芋道源码
16 17 */
17   -public interface SysPermissionService {
  18 +public interface SysPermissionService extends SecurityPermissionFrameworkService {
18 19  
19 20 /**
20 21 * 初始化
21 22 */
22   - void init();
  23 + void initLocalCache();
23 24  
24 25 /**
25 26 * 获得角色们拥有的菜单列表,从缓存中获取
... ...
src/main/java/cn/iocoder/dashboard/modules/system/service/permission/SysRoleService.java
... ... @@ -57,6 +57,16 @@ public interface SysRoleService {
57 57 boolean hasAnyAdmin(Collection<SysRoleDO> roleList);
58 58  
59 59 /**
  60 + * 判断角色编号数组中,是否有管理员
  61 + *
  62 + * @param ids 角色编号数组
  63 + * @return 是否有管理员
  64 + */
  65 + default boolean hasAnyAdmin(Set<Long> ids) {
  66 + return hasAnyAdmin(listRolesFromCache(ids));
  67 + }
  68 +
  69 + /**
60 70 * 创建角色
61 71 *
62 72 * @param reqVO 创建角色信息
... ...
src/main/java/cn/iocoder/dashboard/modules/system/service/permission/impl/SysMenuServiceImpl.java
... ... @@ -61,7 +61,7 @@ public class SysMenuServiceImpl implements SysMenuService {
61 61 *
62 62 * 这里声明 volatile 修饰的原因是,每次刷新时,直接修改指向
63 63 */
64   - private volatile Multimap<String, SysMenuDO> permMenuCache;
  64 + private volatile Multimap<String, SysMenuDO> permissionMenuCache;
65 65 /**
66 66 * 缓存菜单的最大更新时间,用于后续的增量轮询,判断是否有更新
67 67 */
... ... @@ -76,7 +76,7 @@ public class SysMenuServiceImpl implements SysMenuService {
76 76 private SysMenuProducer menuProducer;
77 77  
78 78 /**
79   - * 初始化 {@link #menuCache} 和 {@link #permMenuCache} 缓存
  79 + * 初始化 {@link #menuCache} 和 {@link #permissionMenuCache} 缓存
80 80 */
81 81 @Override
82 82 @PostConstruct
... ... @@ -95,7 +95,7 @@ public class SysMenuServiceImpl implements SysMenuService {
95 95 permMenuCacheBuilder.put(menuDO.getPermission(), menuDO);
96 96 });
97 97 menuCache = menuCacheBuilder.build();
98   - permMenuCache = permMenuCacheBuilder.build();
  98 + permissionMenuCache = permMenuCacheBuilder.build();
99 99 assert menuList.size() > 0; // 断言,避免告警
100 100 maxUpdateTime = menuList.stream().max(Comparator.comparing(BaseDO::getUpdateTime)).get().getUpdateTime();
101 101 log.info("[initLocalCache][缓存菜单,数量为:{}]", menuList.size());
... ... @@ -163,6 +163,11 @@ public class SysMenuServiceImpl implements SysMenuService {
163 163 }
164 164  
165 165 @Override
  166 + public List<SysMenuDO> getMenuListByPermissionFromCache(String permission) {
  167 + return new ArrayList<>(permissionMenuCache.get(permission));
  168 + }
  169 +
  170 + @Override
166 171 public Long createMenu(SysMenuCreateReqVO reqVO) {
167 172 // 校验父菜单存在
168 173 checkParentResource(reqVO.getParentId(), null);
... ...
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;
2 2  
3 3 import cn.hutool.core.collection.CollUtil;
4 4 import cn.hutool.core.collection.CollectionUtil;
  5 +import cn.hutool.core.util.ArrayUtil;
  6 +import cn.iocoder.dashboard.framework.mybatis.core.dataobject.BaseDO;
  7 +import cn.iocoder.dashboard.framework.security.core.util.SecurityUtils;
5 8 import cn.iocoder.dashboard.modules.system.dal.mysql.dao.permission.SysRoleMenuMapper;
6 9 import cn.iocoder.dashboard.modules.system.dal.mysql.dao.permission.SysUserRoleMapper;
7 10 import cn.iocoder.dashboard.modules.system.dal.mysql.dataobject.permission.SysMenuDO;
8 11 import cn.iocoder.dashboard.modules.system.dal.mysql.dataobject.permission.SysRoleDO;
9 12 import cn.iocoder.dashboard.modules.system.dal.mysql.dataobject.permission.SysRoleMenuDO;
10 13 import cn.iocoder.dashboard.modules.system.dal.mysql.dataobject.permission.SysUserRoleDO;
  14 +import cn.iocoder.dashboard.modules.system.mq.producer.permission.SysPermissionProducer;
11 15 import cn.iocoder.dashboard.modules.system.service.permission.SysMenuService;
12 16 import cn.iocoder.dashboard.modules.system.service.permission.SysPermissionService;
13 17 import cn.iocoder.dashboard.modules.system.service.permission.SysRoleService;
... ... @@ -18,24 +22,29 @@ import com.google.common.collect.Multimap;
18 22 import lombok.extern.slf4j.Slf4j;
19 23 import org.springframework.stereotype.Service;
20 24 import org.springframework.transaction.annotation.Transactional;
  25 +import org.springframework.transaction.support.TransactionSynchronization;
  26 +import org.springframework.transaction.support.TransactionSynchronizationManager;
21 27  
22 28 import javax.annotation.PostConstruct;
23 29 import javax.annotation.Resource;
24   -import java.util.Collection;
25   -import java.util.Collections;
26   -import java.util.List;
27   -import java.util.Set;
  30 +import java.util.*;
28 31  
29 32 /**
30 33 * 权限 Service 实现类
31 34 *
32 35 * @author 芋道源码
33 36 */
34   -@Service
  37 +@Service("ss") // 使用 Spring Security 的缩写,方便食用
35 38 @Slf4j
36 39 public class SysPermissionServiceImpl implements SysPermissionService {
37 40  
38 41 /**
  42 + * 定时执行 {@link #schedulePeriodicRefresh()} 的周期
  43 + * 因为已经通过 Redis Pub/Sub 机制,所以频率不需要高
  44 + */
  45 + private static final long SCHEDULER_PERIOD = 5 * 60 * 1000L;
  46 +
  47 + /**
39 48 * 角色编号与菜单编号的缓存映射
40 49 * key:角色编号
41 50 * value:菜单编号的数组
... ... @@ -51,6 +60,10 @@ public class SysPermissionServiceImpl implements SysPermissionService {
51 60 * 这里声明 volatile 修饰的原因是,每次刷新时,直接修改指向
52 61 */
53 62 private volatile Multimap<Long, Long> menuRoleCache;
  63 + /**
  64 + * 缓存菜单的最大更新时间,用于后续的增量轮询,判断是否有更新
  65 + */
  66 + private volatile Date maxUpdateTime;
54 67  
55 68 @Resource
56 69 private SysRoleMenuMapper roleMenuMapper;
... ... @@ -62,14 +75,22 @@ public class SysPermissionServiceImpl implements SysPermissionService {
62 75 @Resource
63 76 private SysMenuService menuService;
64 77  
  78 + @Resource
  79 + private SysPermissionProducer permissionProducer;
  80 +
65 81 /**
66 82 * 初始化 {@link #roleMenuCache} 和 {@link #menuRoleCache} 缓存
67 83 */
68 84 @Override
69 85 @PostConstruct
70   - public void init() {
  86 + public void initLocalCache() {
  87 + // 获取角色与菜单的关联列表,如果有更新
  88 + List<SysRoleMenuDO> roleMenuList = this.loadRoleMenuIfUpdate(maxUpdateTime);
  89 + if (CollUtil.isEmpty(roleMenuList)) {
  90 + return;
  91 + }
  92 +
71 93 // 初始化 roleMenuCache 和 menuRoleCache 缓存
72   - List<SysRoleMenuDO> roleMenuList = roleMenuMapper.selectList(null);
73 94 ImmutableMultimap.Builder<Long, Long> roleMenuCacheBuilder = ImmutableMultimap.builder();
74 95 ImmutableMultimap.Builder<Long, Long> menuRoleCacheBuilder = ImmutableMultimap.builder();
75 96 roleMenuList.forEach(roleMenuDO -> {
... ... @@ -78,9 +99,32 @@ public class SysPermissionServiceImpl implements SysPermissionService {
78 99 });
79 100 roleMenuCache = roleMenuCacheBuilder.build();
80 101 menuRoleCache = menuRoleCacheBuilder.build();
  102 + assert roleMenuList.size() > 0; // 断言,避免告警
  103 + maxUpdateTime = roleMenuList.stream().max(Comparator.comparing(BaseDO::getUpdateTime)).get().getUpdateTime();
81 104 log.info("[initLocalCache][初始化角色与菜单的关联数量为 {}]", roleMenuList.size());
82 105 }
83 106  
  107 + /**
  108 + * 如果角色与菜单的关联发生变化,从数据库中获取最新的全量角色与菜单的关联。
  109 + * 如果未发生变化,则返回空
  110 + *
  111 + * @param maxUpdateTime 当前角色与菜单的关联的最大更新时间
  112 + * @return 角色与菜单的关联列表
  113 + */
  114 + private List<SysRoleMenuDO> loadRoleMenuIfUpdate(Date maxUpdateTime) {
  115 + // 第一步,判断是否要更新。
  116 + if (maxUpdateTime == null) { // 如果更新时间为空,说明 DB 一定有新数据
  117 + log.info("[loadRoleMenuIfUpdate][首次加载全量角色与菜单的关联]");
  118 + } else { // 判断数据库中是否有更新的角色与菜单的关联
  119 + if (!roleMenuMapper.selectExistsByUpdateTimeAfter(maxUpdateTime)) {
  120 + return null;
  121 + }
  122 + log.info("[loadRoleMenuIfUpdate][增量加载全量角色与菜单的关联]");
  123 + }
  124 + // 第二步,如果有更新,则从数据库加载所有角色与菜单的关联
  125 + return roleMenuMapper.selectList();
  126 + }
  127 +
84 128 @Override
85 129 public List<SysMenuDO> listRoleMenusFromCache(Collection<Long> roleIds, Collection<Integer> menuTypes,
86 130 Collection<Integer> menusStatuses) {
... ... @@ -140,6 +184,15 @@ public class SysPermissionServiceImpl implements SysPermissionService {
140 184 if (!CollectionUtil.isEmpty(deleteMenuIds)) {
141 185 roleMenuMapper.deleteListByRoleIdAndMenuIds(roleId, deleteMenuIds);
142 186 }
  187 + // 发送刷新消息. 注意,需要事务提交后,在进行发送刷新消息。不然 db 还未提交,结果缓存先刷新了
  188 + TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronization() {
  189 +
  190 + @Override
  191 + public void afterCommit() {
  192 + permissionProducer.sendRoleMenuRefreshMessage();
  193 + }
  194 +
  195 + });
143 196 }
144 197  
145 198 @Override
... ... @@ -189,4 +242,39 @@ public class SysPermissionServiceImpl implements SysPermissionService {
189 242 // TODO 实现我
190 243 }
191 244  
  245 + @Override
  246 + public boolean hasPermission(String permission) {
  247 + return hasAnyPermissions(permission);
  248 + }
  249 +
  250 + @Override
  251 + public boolean hasAnyPermissions(String... permissions) {
  252 + // 如果为空,说明已经有权限
  253 + if (ArrayUtil.isEmpty(permissions)) {
  254 + return true;
  255 + }
  256 +
  257 + // 获得当前登陆的角色。如果为空,说明没有权限
  258 + Set<Long> roleIds = SecurityUtils.getLoginUserRoleIds();
  259 + if (CollUtil.isEmpty(roleIds)) {
  260 + return false;
  261 + }
  262 + // 判断是否是超管。如果是,当然符合条件
  263 + if (roleService.hasAnyAdmin(roleIds)) {
  264 + return true;
  265 + }
  266 +
  267 + // 遍历权限,判断是否有一个满足
  268 + return Arrays.stream(permissions).anyMatch(permission -> {
  269 + List<SysMenuDO> menuList = menuService.getMenuListByPermissionFromCache(permission);
  270 + // 采用严格模式,如果权限找不到对应的 Menu 的话,认为
  271 + if (CollUtil.isEmpty(menuList)) {
  272 + return false;
  273 + }
  274 + // 获得是否拥有该权限,任一一个
  275 + return menuList.stream().anyMatch(menu -> CollUtil.containsAny(roleIds,
  276 + menuRoleCache.get(menu.getId())));
  277 + });
  278 + }
  279 +
192 280 }
... ...
src/main/java/cn/iocoder/dashboard/modules/system/service/permission/impl/SysRoleServiceImpl.java
... ... @@ -217,6 +217,8 @@ public class SysRoleServiceImpl implements SysRoleService {
217 217 updateObject.setId(id);
218 218 updateObject.setStatus(status);
219 219 roleMapper.updateById(updateObject);
  220 + // 发送刷新消息
  221 + roleProducer.sendRoleRefreshMessage();
220 222 }
221 223  
222 224 @Override
... ... @@ -229,6 +231,8 @@ public class SysRoleServiceImpl implements SysRoleService {
229 231 updateObject.setDataScope(dataScope);
230 232 updateObject.setDataScopeDeptIds(dataScopeDeptIds);
231 233 roleMapper.updateById(updateObject);
  234 + // 发送刷新消息
  235 + roleProducer.sendRoleRefreshMessage();
232 236 }
233 237  
234 238 /**
... ...
src/main/resources/application.yaml
... ... @@ -34,7 +34,7 @@ yudao:
34 34 token-timeout: 1d
35 35 session-timeout: 30m
36 36 mock-enable: true
37   - mock-secret: yudaoyuanma
  37 + mock-secret: test
38 38 swagger:
39 39 title: 管理后台
40 40 description: 提供管理员管理的所有功能
... ...