diff --git a/spring-boot-demo-rbac-security/pom.xml b/spring-boot-demo-rbac-security/pom.xml index 91d894c..56c1f99 100644 --- a/spring-boot-demo-rbac-security/pom.xml +++ b/spring-boot-demo-rbac-security/pom.xml @@ -78,6 +78,11 @@ hutool-all + + com.google.guava + guava + + org.projectlombok lombok diff --git a/spring-boot-demo-rbac-security/sql/security.sql b/spring-boot-demo-rbac-security/sql/security.sql index c94ebbc..244a1b0 100644 --- a/spring-boot-demo-rbac-security/sql/security.sql +++ b/spring-boot-demo-rbac-security/sql/security.sql @@ -11,7 +11,7 @@ Target Server Version : 50718 File Encoding : 65001 - Date: 10/12/2018 14:46:38 + Date: 12/12/2018 01:52:06 */ SET NAMES utf8mb4; @@ -40,11 +40,16 @@ CREATE TABLE `sec_permission` -- ---------------------------- BEGIN; INSERT INTO `sec_permission` -VALUES (1072019441543417856, '测试页面', '/test', 1, 'page:test', NULL, 1, 0); +VALUES (1072548676754345984, '测试页面', '/test', 1, 'page:test', NULL, 1, 0); INSERT INTO `sec_permission` -VALUES (1072019441564389376, '测试页面-查询', '/**/test', 2, 'btn:test:query', 'GET', 1, 1072019441543417856); +VALUES (1072548676771123200, '测试页面-查询', '/**/test', 2, 'btn:test:query', 'GET', 1, 1072548676754345984); INSERT INTO `sec_permission` -VALUES (1072019441576972288, '测试页面-添加', '/**/test', 2, 'btn:test:insert', 'POST', 2, 1072019441543417856); +VALUES (1072548676783706112, '测试页面-添加', '/**/test', 2, 'btn:test:insert', 'POST', 2, 1072548676754345984); +INSERT INTO `sec_permission` +VALUES (1072548676792094720, '监控在线用户页面', '/monitor', 1, 'page:monitor:online', NULL, 2, 0); +INSERT INTO `sec_permission` +VALUES (1072548676800483328, '在线用户页面-查询', '/**/api/monitor/online/user/**', 2, 'btn:monitor:online:query', 'POST', 1, + 1072548676792094720); COMMIT; -- ---------------------------- @@ -68,9 +73,9 @@ CREATE TABLE `sec_role` -- ---------------------------- BEGIN; INSERT INTO `sec_role` -VALUES (1072019441480503296, '管理员', '超级管理员', 1544424326664, 1544424326664); +VALUES (1072548676695625728, '管理员', '超级管理员', 1544550506172, 1544550506172); INSERT INTO `sec_role` -VALUES (1072019441501474816, '普通用户', '普通用户', 1544424326669, 1544424326669); +VALUES (1072548676716597248, '普通用户', '普通用户', 1544550506177, 1544550506177); COMMIT; -- ---------------------------- @@ -90,15 +95,19 @@ CREATE TABLE `sec_role_permission` -- ---------------------------- BEGIN; INSERT INTO `sec_role_permission` -VALUES (1072019441480503296, 1072019441543417856); +VALUES (1072548676695625728, 1072548676754345984); +INSERT INTO `sec_role_permission` +VALUES (1072548676695625728, 1072548676771123200); +INSERT INTO `sec_role_permission` +VALUES (1072548676695625728, 1072548676783706112); INSERT INTO `sec_role_permission` -VALUES (1072019441480503296, 1072019441564389376); +VALUES (1072548676695625728, 1072548676792094720); INSERT INTO `sec_role_permission` -VALUES (1072019441480503296, 1072019441576972288); +VALUES (1072548676695625728, 1072548676800483328); INSERT INTO `sec_role_permission` -VALUES (1072019441501474816, 1072019441543417856); +VALUES (1072548676716597248, 1072548676754345984); INSERT INTO `sec_role_permission` -VALUES (1072019441501474816, 1072019441564389376); +VALUES (1072548676716597248, 1072548676771123200); COMMIT; -- ---------------------------- @@ -130,11 +139,11 @@ CREATE TABLE `sec_user` -- ---------------------------- BEGIN; INSERT INTO `sec_user` -VALUES (1072019440205434880, 'admin', '$2a$10$TwdumjYKUYRQvc3VC8dleOlWr4Q2TysQtfmCMplOWygOzyfrKQee2', '管理员', - '17300000000', 'admin@xkcoding.com', 785433600000, 1, 1, 1544424326483, 1544424326483); +VALUES (1072548675445723136, 'admin', '$2a$10$YNfKodirWWpUOQruzXEq9.sy3G9tATDlonLsPx2si7oVj6InP2KX2', '管理员', + '17300000000', 'admin@xkcoding.com', 785433600000, 1, 1, 1544550505995, 1544550505995); INSERT INTO `sec_user` -VALUES (1072019441035907072, 'user', '$2a$10$8hL7INOxQCzDzI08GGYNCOFKV6mjDcOqhJ/7c1VeF9agO.wBS3ylq', '普通用户', - '17300001111', 'user@xkcoding.com', 785433600000, 1, 1, 1544424326659, 1544424326659); +VALUES (1072548676288778240, 'user', '$2a$10$xAZBKSftV9/ZkkR2TEZUUeYPkn1yTXElmPE2.K/DmU1FrR4AR.Ggu', '普通用户', + '17300001111', 'user@xkcoding.com', 785433600000, 1, 1, 1544550506168, 1544550506168); COMMIT; -- ---------------------------- @@ -154,9 +163,9 @@ CREATE TABLE `sec_user_role` -- ---------------------------- BEGIN; INSERT INTO `sec_user_role` -VALUES (1072019440205434880, 1072019441480503296); +VALUES (1072548675445723136, 1072548676695625728); INSERT INTO `sec_user_role` -VALUES (1072019441035907072, 1072019441501474816); +VALUES (1072548676288778240, 1072548676716597248); COMMIT; -SET FOREIGN_KEY_CHECKS = 1; \ No newline at end of file +SET FOREIGN_KEY_CHECKS = 1; diff --git a/spring-boot-demo-rbac-security/src/main/java/com/xkcoding/rbac/security/common/Consts.java b/spring-boot-demo-rbac-security/src/main/java/com/xkcoding/rbac/security/common/Consts.java index 6b5efd9..ca6437e 100644 --- a/spring-boot-demo-rbac-security/src/main/java/com/xkcoding/rbac/security/common/Consts.java +++ b/spring-boot-demo-rbac-security/src/main/java/com/xkcoding/rbac/security/common/Consts.java @@ -37,4 +37,14 @@ public interface Consts { * JWT 在 Redis 中保存的key前缀 */ String REDIS_JWT_KEY_PREFIX = "security:jwt:"; + + /** + * 星号 + */ + String SYMBOL_STAR = "*"; + + /** + * 邮箱符号 + */ + String SYMBOL_EMAIL = "@"; } diff --git a/spring-boot-demo-rbac-security/src/main/java/com/xkcoding/rbac/security/common/PageResult.java b/spring-boot-demo-rbac-security/src/main/java/com/xkcoding/rbac/security/common/PageResult.java new file mode 100644 index 0000000..4a2307e --- /dev/null +++ b/spring-boot-demo-rbac-security/src/main/java/com/xkcoding/rbac/security/common/PageResult.java @@ -0,0 +1,42 @@ +package com.xkcoding.rbac.security.common; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.io.Serializable; +import java.util.List; + +/** + *

+ * 通用分页参数返回 + *

+ * + * @package: com.xkcoding.rbac.security.common + * @description: 通用分页参数返回 + * @author: yangkai.shen + * @date: Created in 2018-12-11 20:26 + * @copyright: Copyright (c) 2018 + * @version: V1.0 + * @modified: yangkai.shen + */ +@Data +@NoArgsConstructor +@AllArgsConstructor +public class PageResult implements Serializable { + private static final long serialVersionUID = 3420391142991247367L; + + /** + * 当前页数据 + */ + private List rows; + + /** + * 总条数 + */ + private Long total; + + public static PageResult of(List rows, Long total) { + return new PageResult<>(rows, total); + } +} diff --git a/spring-boot-demo-rbac-security/src/main/java/com/xkcoding/rbac/security/controller/MonitorController.java b/spring-boot-demo-rbac-security/src/main/java/com/xkcoding/rbac/security/controller/MonitorController.java new file mode 100644 index 0000000..0bca3ac --- /dev/null +++ b/spring-boot-demo-rbac-security/src/main/java/com/xkcoding/rbac/security/controller/MonitorController.java @@ -0,0 +1,46 @@ +package com.xkcoding.rbac.security.controller; + +import com.xkcoding.rbac.security.common.ApiResponse; +import com.xkcoding.rbac.security.common.PageResult; +import com.xkcoding.rbac.security.service.MonitorService; +import com.xkcoding.rbac.security.vo.OnlineUser; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +/** + *

+ * 监控 Controller,在线用户,手动踢出用户等功能 + *

+ * + * @package: com.xkcoding.rbac.security.controller + * @description: 监控 Controller,在线用户,手动踢出用户等功能 + * @author: yangkai.shen + * @date: Created in 2018-12-11 20:55 + * @copyright: Copyright (c) 2018 + * @version: V1.0 + * @modified: yangkai.shen + */ +@Slf4j +@RestController +@RequestMapping("/api/monitor") +public class MonitorController { + @Autowired + private MonitorService monitorService; + + /** + * 在线用户列表 + * + * @param page 当前页码 + * @param size 每页条数 + */ + @PostMapping("/online/user/{page}/{size}") + public ApiResponse onlineUser(@PathVariable Integer page, @PathVariable Integer size) { + PageResult pageResult = monitorService.onlineUser(page, size); + return ApiResponse.ofSuccess(pageResult); + } + +} diff --git a/spring-boot-demo-rbac-security/src/main/java/com/xkcoding/rbac/security/repository/UserDao.java b/spring-boot-demo-rbac-security/src/main/java/com/xkcoding/rbac/security/repository/UserDao.java index 4750dbb..45b30e9 100644 --- a/spring-boot-demo-rbac-security/src/main/java/com/xkcoding/rbac/security/repository/UserDao.java +++ b/spring-boot-demo-rbac-security/src/main/java/com/xkcoding/rbac/security/repository/UserDao.java @@ -1,10 +1,10 @@ package com.xkcoding.rbac.security.repository; -import com.xkcoding.rbac.security.model.Permission; import com.xkcoding.rbac.security.model.User; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.JpaSpecificationExecutor; +import java.util.List; import java.util.Optional; /** @@ -30,4 +30,12 @@ public interface UserDao extends JpaRepository, JpaSpecificationExec * @return 用户信息 */ Optional findByUsernameOrEmailOrPhone(String username, String email, String phone); + + /** + * 根据用户名列表查询用户列表 + * + * @param usernameList 用户名列表 + * @return 用户列表 + */ + List findByUsernameIn(List usernameList); } diff --git a/spring-boot-demo-rbac-security/src/main/java/com/xkcoding/rbac/security/service/MonitorService.java b/spring-boot-demo-rbac-security/src/main/java/com/xkcoding/rbac/security/service/MonitorService.java new file mode 100644 index 0000000..38a4f27 --- /dev/null +++ b/spring-boot-demo-rbac-security/src/main/java/com/xkcoding/rbac/security/service/MonitorService.java @@ -0,0 +1,63 @@ +package com.xkcoding.rbac.security.service; + +import cn.hutool.core.util.StrUtil; +import com.google.common.collect.Lists; +import com.xkcoding.rbac.security.common.Consts; +import com.xkcoding.rbac.security.common.PageResult; +import com.xkcoding.rbac.security.model.User; +import com.xkcoding.rbac.security.repository.UserDao; +import com.xkcoding.rbac.security.util.RedisUtil; +import com.xkcoding.rbac.security.vo.OnlineUser; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import java.util.List; +import java.util.stream.Collectors; + +/** + *

+ * 监控 Service + *

+ * + * @package: com.xkcoding.rbac.security.service + * @description: 监控 Service + * @author: yangkai.shen + * @date: Created in 2018-12-12 00:55 + * @copyright: Copyright (c) 2018 + * @version: V1.0 + * @modified: yangkai.shen + */ +@Service +public class MonitorService { + @Autowired + private RedisUtil redisUtil; + + @Autowired + private UserDao userDao; + + /** + * 在线用户分页列表 + * + * @param page 当前页 + * @param size 每页条数 + * @return 在线用户分页列表 + */ + public PageResult onlineUser(Integer page, Integer size) { + PageResult keys = redisUtil.findKeysForPage(Consts.REDIS_JWT_KEY_PREFIX + Consts.SYMBOL_STAR, page, size); + List rows = keys.getRows(); + Long total = keys.getTotal(); + + // 根据 redis 中键获取用户名列表 + List usernameList = rows.stream() + .map(s -> StrUtil.subAfter(s, Consts.REDIS_JWT_KEY_PREFIX, true)) + .collect(Collectors.toList()); + // 根据用户名查询用户信息 + List userList = userDao.findByUsernameIn(usernameList); + + // 封装在线用户信息 + List onlineUserList = Lists.newArrayList(); + userList.forEach(user -> onlineUserList.add(OnlineUser.create(user))); + + return new PageResult<>(onlineUserList, total); + } +} diff --git a/spring-boot-demo-rbac-security/src/main/java/com/xkcoding/rbac/security/util/RedisUtil.java b/spring-boot-demo-rbac-security/src/main/java/com/xkcoding/rbac/security/util/RedisUtil.java new file mode 100644 index 0000000..a4acd0f --- /dev/null +++ b/spring-boot-demo-rbac-security/src/main/java/com/xkcoding/rbac/security/util/RedisUtil.java @@ -0,0 +1,74 @@ +package com.xkcoding.rbac.security.util; + +import com.google.common.collect.Lists; +import com.xkcoding.rbac.security.common.PageResult; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.redis.connection.RedisConnection; +import org.springframework.data.redis.connection.RedisConnectionFactory; +import org.springframework.data.redis.core.Cursor; +import org.springframework.data.redis.core.RedisConnectionUtils; +import org.springframework.data.redis.core.ScanOptions; +import org.springframework.data.redis.core.StringRedisTemplate; +import org.springframework.stereotype.Component; + +import java.util.List; + +/** + *

+ * Redis工具类 + *

+ * + * @package: com.xkcoding.rbac.security.util + * @description: Redis工具类 + * @author: yangkai.shen + * @date: Created in 2018-12-11 20:24 + * @copyright: Copyright (c) 2018 + * @version: V1.0 + * @modified: yangkai.shen + */ +@Component +@Slf4j +public class RedisUtil { + @Autowired + private StringRedisTemplate stringRedisTemplate; + + /** + * 分页获取指定格式key,使用 scan 命令代替 keys 命令,在大数据量的情况下可以提高查询效率 + * + * @param patternKey key格式 + * @param currentPage 当前页码 + * @param pageSize 每页条数 + * @return 分页获取指定格式key + */ + public PageResult findKeysForPage(String patternKey, int currentPage, int pageSize) { + ScanOptions options = ScanOptions.scanOptions() + .match(patternKey) + .build(); + RedisConnectionFactory factory = stringRedisTemplate.getConnectionFactory(); + RedisConnection rc = factory.getConnection(); + Cursor cursor = rc.scan(options); + + List result = Lists.newArrayList(); + + long tmpIndex = 0; + int startIndex = (currentPage - 1) * pageSize; + int end = currentPage * pageSize; + while (cursor.hasNext()) { + String key = new String(cursor.next()); + if (tmpIndex >= startIndex && tmpIndex < end) { + result.add(key); + } + tmpIndex++; + } + + try { + cursor.close(); + RedisConnectionUtils.releaseConnection(rc, factory); + } catch (Exception e) { + log.warn("Redis连接关闭异常,", e); + } + + return new PageResult<>(result, tmpIndex); + } +} diff --git a/spring-boot-demo-rbac-security/src/main/java/com/xkcoding/rbac/security/vo/OnlineUser.java b/spring-boot-demo-rbac-security/src/main/java/com/xkcoding/rbac/security/vo/OnlineUser.java new file mode 100644 index 0000000..2d0268b --- /dev/null +++ b/spring-boot-demo-rbac-security/src/main/java/com/xkcoding/rbac/security/vo/OnlineUser.java @@ -0,0 +1,68 @@ +package com.xkcoding.rbac.security.vo; + +import cn.hutool.core.bean.BeanUtil; +import cn.hutool.core.util.StrUtil; +import com.xkcoding.rbac.security.common.Consts; +import com.xkcoding.rbac.security.model.User; +import lombok.Data; + +/** + *

+ * 在线用户 VO + *

+ * + * @package: com.xkcoding.rbac.security.vo + * @description: 在线用户 VO + * @author: yangkai.shen + * @date: Created in 2018-12-12 00:58 + * @copyright: Copyright (c) 2018 + * @version: V1.0 + * @modified: yangkai.shen + */ +@Data +public class OnlineUser { + + /** + * 主键 + */ + private Long id; + + /** + * 用户名 + */ + private String username; + + /** + * 昵称 + */ + private String nickname; + + /** + * 手机 + */ + private String phone; + + /** + * 邮箱 + */ + private String email; + + /** + * 生日 + */ + private Long birthday; + + /** + * 性别,男-1,女-2 + */ + private Integer sex; + + public static OnlineUser create(User user) { + OnlineUser onlineUser = new OnlineUser(); + BeanUtil.copyProperties(user, onlineUser); + // 脱敏 + onlineUser.setPhone(StrUtil.hide(user.getPhone(), 3, 7)); + onlineUser.setEmail(StrUtil.hide(user.getEmail(), 1, StrUtil.indexOfIgnoreCase(user.getEmail(), Consts.SYMBOL_EMAIL))); + return onlineUser; + } +} diff --git a/spring-boot-demo-rbac-security/src/test/java/com/xkcoding/rbac/security/repository/DataInitTest.java b/spring-boot-demo-rbac-security/src/test/java/com/xkcoding/rbac/security/repository/DataInitTest.java index 74d1d94..8068414 100644 --- a/spring-boot-demo-rbac-security/src/test/java/com/xkcoding/rbac/security/repository/DataInitTest.java +++ b/spring-boot-demo-rbac-security/src/test/java/com/xkcoding/rbac/security/repository/DataInitTest.java @@ -62,16 +62,21 @@ public class DataInitTest extends SpringBootDemoRbacSecurityApplicationTests { createUserRoleRelation(user.getId(), roleUser.getId()); // 页面权限 - Permission pagePerm = createPermission("/test", "测试页面", 1, "page:test", null, 1, 0L); + Permission testPagePerm = createPermission("/test", "测试页面", 1, "page:test", null, 1, 0L); // 按钮权限 - Permission btnQueryPerm = createPermission("/**/test", "测试页面-查询", 2, "btn:test:query", "GET", 1, pagePerm.getId()); - Permission btnPermInsert = createPermission("/**/test", "测试页面-添加", 2, "btn:test:insert", "POST", 2, pagePerm.getId()); - - createRolePermissionRelation(roleAdmin.getId(), pagePerm.getId()); - createRolePermissionRelation(roleUser.getId(), pagePerm.getId()); - createRolePermissionRelation(roleAdmin.getId(), btnQueryPerm.getId()); - createRolePermissionRelation(roleUser.getId(), btnQueryPerm.getId()); - createRolePermissionRelation(roleAdmin.getId(), btnPermInsert.getId()); + Permission testBtnQueryPerm = createPermission("/**/test", "测试页面-查询", 2, "btn:test:query", "GET", 1, testPagePerm.getId()); + Permission testBtnPermInsert = createPermission("/**/test", "测试页面-添加", 2, "btn:test:insert", "POST", 2, testPagePerm.getId()); + + Permission monitorOnlinePagePerm = createPermission("/monitor", "监控在线用户页面", 1, "page:monitor:online", null, 2, 0L); + Permission monitorOnlineBtnQueryPerm = createPermission("/**/api/monitor/online/user/**", "在线用户页面-查询", 2, "btn:monitor:online:query", "POST", 1, monitorOnlinePagePerm.getId()); + + createRolePermissionRelation(roleAdmin.getId(), testPagePerm.getId()); + createRolePermissionRelation(roleUser.getId(), testPagePerm.getId()); + createRolePermissionRelation(roleAdmin.getId(), testBtnQueryPerm.getId()); + createRolePermissionRelation(roleUser.getId(), testBtnQueryPerm.getId()); + createRolePermissionRelation(roleAdmin.getId(), testBtnPermInsert.getId()); + createRolePermissionRelation(roleAdmin.getId(), monitorOnlinePagePerm.getId()); + createRolePermissionRelation(roleAdmin.getId(), monitorOnlineBtnQueryPerm.getId()); } private void createRolePermissionRelation(Long roleId, Long permissionId) { diff --git a/spring-boot-demo-rbac-security/src/test/java/com/xkcoding/rbac/security/repository/UserDaoTest.java b/spring-boot-demo-rbac-security/src/test/java/com/xkcoding/rbac/security/repository/UserDaoTest.java new file mode 100644 index 0000000..1c9bdb1 --- /dev/null +++ b/spring-boot-demo-rbac-security/src/test/java/com/xkcoding/rbac/security/repository/UserDaoTest.java @@ -0,0 +1,39 @@ +package com.xkcoding.rbac.security.repository; + +import com.xkcoding.rbac.security.SpringBootDemoRbacSecurityApplicationTests; +import com.xkcoding.rbac.security.model.User; +import lombok.extern.slf4j.Slf4j; +import org.assertj.core.util.Lists; +import org.junit.Assert; +import org.junit.Test; +import org.springframework.beans.factory.annotation.Autowired; + +import java.util.List; +import java.util.Optional; + +/** + *

+ * UserDao 测试 + *

+ * + * @package: com.xkcoding.rbac.security.repository + * @description: UserDao 测试 + * @author: yangkai.shen + * @date: Created in 2018-12-12 01:10 + * @copyright: Copyright (c) 2018 + * @version: V1.0 + * @modified: yangkai.shen + */ +@Slf4j +public class UserDaoTest extends SpringBootDemoRbacSecurityApplicationTests { + @Autowired + private UserDao userDao; + + @Test + public void findByUsernameIn() { + List usernameList = Lists.newArrayList("admin", "user"); + List userList = userDao.findByUsernameIn(usernameList); + Assert.assertEquals(2, userList.size()); + log.info("【userList】= {}", userList); + } +} \ No newline at end of file diff --git a/spring-boot-demo-rbac-security/src/test/java/com/xkcoding/rbac/security/util/RedisUtilTest.java b/spring-boot-demo-rbac-security/src/test/java/com/xkcoding/rbac/security/util/RedisUtilTest.java new file mode 100644 index 0000000..cbe51ab --- /dev/null +++ b/spring-boot-demo-rbac-security/src/test/java/com/xkcoding/rbac/security/util/RedisUtilTest.java @@ -0,0 +1,34 @@ +package com.xkcoding.rbac.security.util; + +import cn.hutool.json.JSONUtil; +import com.xkcoding.rbac.security.SpringBootDemoRbacSecurityApplicationTests; +import com.xkcoding.rbac.security.common.Consts; +import com.xkcoding.rbac.security.common.PageResult; +import lombok.extern.slf4j.Slf4j; +import org.junit.Test; +import org.springframework.beans.factory.annotation.Autowired; + +/** + *

+ * 测试RedisUtil + *

+ * + * @package: com.xkcoding.rbac.security.util + * @description: 测试RedisUtil + * @author: yangkai.shen + * @date: Created in 2018-12-11 20:44 + * @copyright: Copyright (c) 2018 + * @version: V1.0 + * @modified: yangkai.shen + */ +@Slf4j +public class RedisUtilTest extends SpringBootDemoRbacSecurityApplicationTests { + @Autowired + private RedisUtil redisUtil; + + @Test + public void findKeysForPage() { + PageResult pageResult = redisUtil.findKeysForPage(Consts.REDIS_JWT_KEY_PREFIX + Consts.SYMBOL_STAR, 2, 1); + log.info("【pageResult】= {}", JSONUtil.toJsonStr(pageResult)); + } +} \ No newline at end of file