效果:
一、思路
1、管理员登录时,将用户信息存储到Redis里,并且设置过期时间(注意过期时间跟token同步)
2、通过Key去Redis遍历获取用户信息,加于展示
二、设计
1、结构设计
Key: LOGIN_TOKEN_KEY
Value:
2、对象
@Data
public class OnlineAdmin {
/**
* 会话编号
*/
private String tokenId;
/**
* 用户Token
*/
private String token;
/**
* 管理员的UID
*/
private String adminUid;
/**
* 用户名称
*/
private String userName;
/**
* 登录IP地址
*/
private String ipaddr;
/**
* 登录地址
*/
private String loginLocation;
/**
* 浏览器类型
*/
private String browser;
/**
* 操作系统
*/
private String os;
/**
* 角色名称
*/
private String roleName;
/**
* 登录时间
*/
private String loginTime;
/**
* 过期时间
*/
private String expireTime;
}
三、实现
1、登录时将用户信息存储到Redis上
具体实现:
@Override
public void addOnlineAdmin(Admin admin, Long expirationSecond) {
// 1.获取请求信息
HttpServletRequest request = RequestHolder.getRequest();
// 2.获取操作系统,浏览器及浏览器版本信息
Map<String, String> map = IpUtils.getOsAndBrowserInfo(request);
String os = map.get(SysConf.OS);
String browser = map.get(SysConf.BROWSER);
String ip = IpUtils.getIpAddr(request);
// 3.组装用户信息
OnlineAdmin onlineAdmin = new OnlineAdmin();
onlineAdmin.setAdminUid(admin.getUid());
onlineAdmin.setTokenId(admin.getTokenUid());
onlineAdmin.setToken(admin.getValidCode());
onlineAdmin.setOs(os);
onlineAdmin.setBrowser(browser);
onlineAdmin.setIpaddr(ip);
onlineAdmin.setLoginTime(DateUtils.getNowTime());
onlineAdmin.setRoleName(admin.getRole().getRoleName());
onlineAdmin.setUserName(admin.getUserName());
onlineAdmin.setExpireTime(DateUtils.getDateStr(new Date(), expirationSecond));
// 4.从Redis中获取IP来源
String jsonResult = redisUtil.get(RedisConf.IP_SOURCE + Constants.SYMBOL_COLON + ip);
if (StringUtils.isEmpty(jsonResult)) {
String addresses = IpUtils.getAddresses(SysConf.IP + SysConf.EQUAL_TO + ip, SysConf.UTF_8);
if (StringUtils.isNotEmpty(addresses)) {
onlineAdmin.setLoginLocation(addresses);
redisUtil.setEx(RedisConf.IP_SOURCE + Constants.SYMBOL_COLON + ip, addresses, 24, TimeUnit.HOURS);
}
} else {
onlineAdmin.setLoginLocation(jsonResult);
}
// 5.将登录的管理员存储到在线用户表
redisUtil.setEx(RedisConf.LOGIN_TOKEN_KEY + RedisConf.SEGMENTATION + admin.getValidCode(), JsonUtils.objectToJson(onlineAdmin), expirationSecond, TimeUnit.SECONDS);
// 6.在维护一张表,用于 uuid - token 互相转换
redisUtil.setEx(RedisConf.LOGIN_UUID_KEY + RedisConf.SEGMENTATION + admin.getTokenUid(), admin.getValidCode(), expirationSecond, TimeUnit.SECONDS);
}
2、展示用户表
创建接口
@AuthorityVerify
@ApiOperation(value = "获取在线管理员列表", notes = "获取在线管理员列表", response = String.class)
@PostMapping(value = "/getOnlineAdminList")
public String getOnlineAdminList(@Validated({GetList.class}) @RequestBody AdminVO adminVO, BindingResult result) {
// 参数校验
ThrowableUtils.checkParamArgument(result);
return adminService.getOnlineAdminList(adminVO);
}
具体实现
@Override
public String getOnlineAdminList(AdminVO adminVO) {
// 拼装分页信息
int pageSize = adminVO.getPageSize().intValue();
int currentPage = adminVO.getCurrentPage().intValue();
AtomicReference<Integer> total = new AtomicReference<Integer>(0);
int startIndex = Math.max((currentPage - 1) * pageSize, 0);
// 获取Redis中匹配的key
Set<String> keys = redisUtil.getRedisTemplate().execute((RedisCallback<Set<String>>) connection -> {
Set<String> keySetTemp = new ConcurrentSkipListSet<>();
int index = 0;
try (Cursor<byte[]> cursor = connection.scan(ScanOptions.scanOptions()
.match(RedisConf.LOGIN_TOKEN_KEY + "*")
.count(100000)
.build())) {
// 获取登录用户的key
while (cursor.hasNext()) {
index ++;
// 先偏移起始位置个数据
if (index<startIndex){
cursor.next();
continue;
}
String key = new String(cursor.next(), CharsetUtil.UTF_8);
// 获取需要的key
if (keySetTemp.size() <= pageSize) {
keySetTemp.add(key);
}
}
total.set(index);
} catch (Exception e) {
log.error("Redis Scan get Exception:{}", ExceptionUtil.stacktraceToOneLineString(e), e);
return new ConcurrentSkipListSet<>();
}
return keySetTemp;
});
// 获取在线用户数据
List<String> onlineAdminJsonList = redisUtil.multiGet(keys);
List<OnlineAdmin> onlineAdminList = new ArrayList<>();
for (String item : onlineAdminJsonList) {
OnlineAdmin onlineAdmin = JsonUtils.jsonToPojo(item, OnlineAdmin.class);
// 数据脱敏【移除用户的token令牌】
onlineAdmin.setToken("");
onlineAdminList.add(onlineAdmin);
}
Page<OnlineAdmin> page = new Page<>();
page.setCurrent(currentPage);
page.setTotal(total.get());
page.setSize(pageSize);
page.setRecords(onlineAdminList);
return ResultUtil.successWithData(page);
}
3、踢出用户
创建接口
@AuthorityVerify
@OperationLogger(value = "强退用户")
@ApiOperation(value = "强退用户", notes = "强退用户", response = String.class)
@PostMapping(value = "/forceLogout")
public String forceLogout(@ApiParam(name = "tokenUidList", value = "tokenList", required = false) @RequestBody List<String> tokenUidList) {
return adminService.forceLogout(tokenUidList);
}
具体实现
@Override
public String forceLogout(List<String> tokenUidList) {
if (tokenUidList == null || tokenUidList.size() == 0) {
return ResultUtil.errorWithMessage(MessageConf.PARAM_INCORRECT);
}
// 从Redis中通过TokenUid获取到用户的真实token
List<String> tokenList = new ArrayList<>();
tokenUidList.forEach(item -> {
String token = redisUtil.get(RedisConf.LOGIN_UUID_KEY + RedisConf.SEGMENTATION + item);
if(StringUtils.isNotEmpty(token)) {
tokenList.add(token);
}
});
// 根据token删除Redis中的在线用户
List<String> keyList = new ArrayList<>();
String keyPrefix = RedisConf.LOGIN_TOKEN_KEY + RedisConf.SEGMENTATION;
for (String token : tokenList) {
keyList.add(keyPrefix + token);
}
redisUtil.delete(keyList);
return ResultUtil.successWithMessage(MessageConf.OPERATION_SUCCESS);
}