Skip to content

Commit cf3655a

Browse files
committed
用户登录优化,踢出用户性能优化,在线用户查询性能优化
close elunez#802
1 parent f0ed88c commit cf3655a

File tree

10 files changed

+73
-90
lines changed

10 files changed

+73
-90
lines changed

eladmin-common/src/main/java/me/zhengjie/utils/RedisUtils.java

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -19,13 +19,11 @@
1919
import com.google.common.collect.Sets;
2020
import org.slf4j.Logger;
2121
import org.slf4j.LoggerFactory;
22-
import org.springframework.beans.factory.annotation.Value;
2322
import org.springframework.data.redis.connection.RedisConnection;
2423
import org.springframework.data.redis.connection.RedisConnectionFactory;
2524
import org.springframework.data.redis.core.*;
2625
import org.springframework.data.redis.serializer.StringRedisSerializer;
2726
import org.springframework.stereotype.Component;
28-
2927
import java.util.*;
3028
import java.util.concurrent.TimeUnit;
3129

@@ -36,9 +34,8 @@
3634
@SuppressWarnings({"unchecked", "all"})
3735
public class RedisUtils {
3836
private static final Logger log = LoggerFactory.getLogger(RedisUtils.class);
37+
3938
private RedisTemplate<Object, Object> redisTemplate;
40-
@Value("${jwt.online-key}")
41-
private String onlineKey;
4239

4340
public RedisUtils(RedisTemplate<Object, Object> redisTemplate) {
4441
this.redisTemplate = redisTemplate;
@@ -197,6 +194,21 @@ public void del(String... keys) {
197194
}
198195
}
199196

197+
/**
198+
* 批量模糊删除key
199+
* @param pattern
200+
*/
201+
public void scanDel(String pattern){
202+
ScanOptions options = ScanOptions.scanOptions().match(pattern).build();
203+
try (Cursor<byte[]> cursor = redisTemplate.executeWithStickyConnection(
204+
(RedisCallback<Cursor<byte[]>>) connection -> (Cursor<byte[]>) new ConvertingCursor<>(
205+
connection.scan(options), redisTemplate.getKeySerializer()::deserialize))) {
206+
while (cursor.hasNext()) {
207+
redisTemplate.delete(cursor.next());
208+
}
209+
}
210+
}
211+
200212
// ============================String=============================
201213

202214
/**

eladmin-system/src/main/java/me/zhengjie/modules/security/config/bean/LoginProperties.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ public class LoginProperties {
3939

4040
private LoginCode loginCode;
4141

42-
public static final String cacheKey = "USER-LOGIN-DATA";
42+
public static final String cacheKey = "user-login-cache:";
4343

4444
public boolean isSingleLogin() {
4545
return singleLogin;

eladmin-system/src/main/java/me/zhengjie/modules/security/rest/AuthorizationController.java

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -98,17 +98,18 @@ public ResponseEntity<Object> login(@Validated @RequestBody AuthUserDto authUser
9898
// SecurityContextHolder.getContext().setAuthentication(authentication);
9999
String token = tokenProvider.createToken(authentication);
100100
final JwtUserDto jwtUserDto = (JwtUserDto) authentication.getPrincipal();
101-
// 保存在线信息
102-
onlineUserService.save(jwtUserDto, token, request);
103101
// 返回 token 与 用户信息
104102
Map<String, Object> authInfo = new HashMap<String, Object>(2) {{
105103
put("token", properties.getTokenStartWith() + token);
106104
put("user", jwtUserDto);
107105
}};
108106
if (loginProperties.isSingleLogin()) {
109-
//踢掉之前已经登录的token
110-
onlineUserService.checkLoginOnUser(authUser.getUsername(), token);
107+
// 踢掉之前已经登录的token
108+
onlineUserService.kickOutForUsername(authUser.getUsername());
111109
}
110+
// 保存在线信息
111+
onlineUserService.save(jwtUserDto, token, request);
112+
// 返回登录信息
112113
return ResponseEntity.ok(authInfo);
113114
}
114115

eladmin-system/src/main/java/me/zhengjie/modules/security/rest/OnlineController.java

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -45,25 +45,25 @@ public class OnlineController {
4545
@ApiOperation("查询在线用户")
4646
@GetMapping
4747
@PreAuthorize("@el.check()")
48-
public ResponseEntity<PageResult<OnlineUserDto>> queryOnlineUser(String filter, Pageable pageable){
49-
return new ResponseEntity<>(onlineUserService.getAll(filter, pageable),HttpStatus.OK);
48+
public ResponseEntity<PageResult<OnlineUserDto>> queryOnlineUser(String username, Pageable pageable){
49+
return new ResponseEntity<>(onlineUserService.getAll(username, pageable),HttpStatus.OK);
5050
}
5151

5252
@ApiOperation("导出数据")
5353
@GetMapping(value = "/download")
5454
@PreAuthorize("@el.check()")
55-
public void exportOnlineUser(HttpServletResponse response, String filter) throws IOException {
56-
onlineUserService.download(onlineUserService.getAll(filter), response);
55+
public void exportOnlineUser(HttpServletResponse response, String username) throws IOException {
56+
onlineUserService.download(onlineUserService.getAll(username), response);
5757
}
5858

5959
@ApiOperation("踢出用户")
6060
@DeleteMapping
6161
@PreAuthorize("@el.check()")
6262
public ResponseEntity<Object> deleteOnlineUser(@RequestBody Set<String> keys) throws Exception {
63-
for (String key : keys) {
63+
for (String token : keys) {
6464
// 解密Key
65-
key = EncryptUtils.desDecrypt(key);
66-
onlineUserService.kickOut(key);
65+
token = EncryptUtils.desDecrypt(token);
66+
onlineUserService.logout(token);
6767
}
6868
return new ResponseEntity<>(HttpStatus.OK);
6969
}

eladmin-system/src/main/java/me/zhengjie/modules/security/security/TokenFilter.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,8 @@ public void doFilter(ServletRequest servletRequest, ServletResponse servletRespo
7070
OnlineUserDto onlineUserDto = null;
7171
boolean cleanUserCache = false;
7272
try {
73-
onlineUserDto = onlineUserService.getOne(properties.getOnlineKey() + token);
73+
String loginKey = tokenProvider.loginKey(token);
74+
onlineUserDto = onlineUserService.getOne(loginKey);
7475
} catch (ExpiredJwtException e) {
7576
log.error(e.getMessage());
7677
cleanUserCache = true;

eladmin-system/src/main/java/me/zhengjie/modules/security/security/TokenProvider.java

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
import cn.hutool.core.date.DateField;
1919
import cn.hutool.core.date.DateUtil;
2020
import cn.hutool.core.util.IdUtil;
21+
import cn.hutool.crypto.digest.DigestUtil;
2122
import io.jsonwebtoken.*;
2223
import io.jsonwebtoken.io.Decoders;
2324
import io.jsonwebtoken.security.Keys;
@@ -120,4 +121,15 @@ public String getToken(HttpServletRequest request) {
120121
}
121122
return null;
122123
}
124+
125+
/**
126+
* 获取登录用户RedisKey
127+
* @param token /
128+
* @return key
129+
*/
130+
public String loginKey(String token) {
131+
Claims claims = getClaims(token);
132+
String md5Token = DigestUtil.md5Hex(token);
133+
return properties.getOnlineKey() + claims.getSubject() + "-" + md5Token;
134+
}
123135
}

eladmin-system/src/main/java/me/zhengjie/modules/security/service/OnlineUserService.java

Lines changed: 21 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,9 @@
1515
*/
1616
package me.zhengjie.modules.security.service;
1717

18+
import lombok.AllArgsConstructor;
1819
import lombok.extern.slf4j.Slf4j;
20+
import me.zhengjie.modules.security.security.TokenProvider;
1921
import me.zhengjie.utils.PageResult;
2022
import me.zhengjie.modules.security.config.bean.SecurityProperties;
2123
import me.zhengjie.modules.security.service.dto.JwtUserDto;
@@ -28,23 +30,21 @@
2830
import javax.servlet.http.HttpServletResponse;
2931
import java.io.IOException;
3032
import java.util.*;
33+
import java.util.concurrent.TimeUnit;
3134

3235
/**
3336
* @author Zheng Jie
3437
* @date 2019年10月26日21:56:27
3538
*/
3639
@Service
3740
@Slf4j
41+
@AllArgsConstructor
3842
public class OnlineUserService {
3943

4044
private final SecurityProperties properties;
45+
private final TokenProvider tokenProvider;
4146
private final RedisUtils redisUtils;
4247

43-
public OnlineUserService(SecurityProperties properties, RedisUtils redisUtils) {
44-
this.properties = properties;
45-
this.redisUtils = redisUtils;
46-
}
47-
4848
/**
4949
* 保存在线用户信息
5050
* @param jwtUserDto /
@@ -62,17 +62,18 @@ public void save(JwtUserDto jwtUserDto, String token, HttpServletRequest request
6262
} catch (Exception e) {
6363
log.error(e.getMessage(),e);
6464
}
65-
redisUtils.set(properties.getOnlineKey() + token, onlineUserDto, properties.getTokenValidityInSeconds()/1000);
65+
String loginKey = tokenProvider.loginKey(token);
66+
redisUtils.set(loginKey, onlineUserDto, properties.getTokenValidityInSeconds(), TimeUnit.MILLISECONDS);
6667
}
6768

6869
/**
6970
* 查询全部数据
70-
* @param filter /
71+
* @param username /
7172
* @param pageable /
7273
* @return /
7374
*/
74-
public PageResult<OnlineUserDto> getAll(String filter, Pageable pageable){
75-
List<OnlineUserDto> onlineUserDtos = getAll(filter);
75+
public PageResult<OnlineUserDto> getAll(String username, Pageable pageable){
76+
List<OnlineUserDto> onlineUserDtos = getAll(username);
7677
return PageUtil.toPage(
7778
PageUtil.paging(pageable.getPageNumber(),pageable.getPageSize(), onlineUserDtos),
7879
onlineUserDtos.size()
@@ -81,43 +82,29 @@ public PageResult<OnlineUserDto> getAll(String filter, Pageable pageable){
8182

8283
/**
8384
* 查询全部数据,不分页
84-
* @param filter /
85+
* @param username /
8586
* @return /
8687
*/
87-
public List<OnlineUserDto> getAll(String filter){
88-
List<String> keys = redisUtils.scan(properties.getOnlineKey() + "*");
88+
public List<OnlineUserDto> getAll(String username){
89+
String loginKey = properties.getOnlineKey() +
90+
(StringUtils.isBlank(username) ? "" : "*" + username);
91+
List<String> keys = redisUtils.scan(loginKey + "*");
8992
Collections.reverse(keys);
9093
List<OnlineUserDto> onlineUserDtos = new ArrayList<>();
9194
for (String key : keys) {
92-
OnlineUserDto onlineUserDto = (OnlineUserDto) redisUtils.get(key);
93-
if(StringUtils.isNotBlank(filter)){
94-
if(onlineUserDto.toString().contains(filter)){
95-
onlineUserDtos.add(onlineUserDto);
96-
}
97-
} else {
98-
onlineUserDtos.add(onlineUserDto);
99-
}
95+
onlineUserDtos.add((OnlineUserDto) redisUtils.get(key));
10096
}
10197
onlineUserDtos.sort((o1, o2) -> o2.getLoginTime().compareTo(o1.getLoginTime()));
10298
return onlineUserDtos;
10399
}
104100

105-
/**
106-
* 踢出用户
107-
* @param key /
108-
*/
109-
public void kickOut(String key){
110-
key = properties.getOnlineKey() + key;
111-
redisUtils.del(key);
112-
}
113-
114101
/**
115102
* 退出登录
116103
* @param token /
117104
*/
118105
public void logout(String token) {
119-
String key = properties.getOnlineKey() + token;
120-
redisUtils.del(key);
106+
String loginKey = tokenProvider.loginKey(token);
107+
redisUtils.del(loginKey);
121108
}
122109

123110
/**
@@ -150,43 +137,13 @@ public OnlineUserDto getOne(String key) {
150137
return (OnlineUserDto)redisUtils.get(key);
151138
}
152139

153-
/**
154-
* 检测用户是否在之前已经登录,已经登录踢下线
155-
* @param userName 用户名
156-
*/
157-
public void checkLoginOnUser(String userName, String igoreToken){
158-
List<OnlineUserDto> onlineUserDtos = getAll(userName);
159-
if(onlineUserDtos ==null || onlineUserDtos.isEmpty()){
160-
return;
161-
}
162-
for(OnlineUserDto onlineUserDto : onlineUserDtos){
163-
if(onlineUserDto.getUserName().equals(userName)){
164-
try {
165-
String token =EncryptUtils.desDecrypt(onlineUserDto.getKey());
166-
if(StringUtils.isNotBlank(igoreToken)&&!igoreToken.equals(token)){
167-
this.kickOut(token);
168-
}else if(StringUtils.isBlank(igoreToken)){
169-
this.kickOut(token);
170-
}
171-
} catch (Exception e) {
172-
log.error("checkUser is error",e);
173-
}
174-
}
175-
}
176-
}
177-
178140
/**
179141
* 根据用户名强退用户
180142
* @param username /
181143
*/
182144
@Async
183-
public void kickOutForUsername(String username) throws Exception {
184-
List<OnlineUserDto> onlineUsers = getAll(username);
185-
for (OnlineUserDto onlineUser : onlineUsers) {
186-
if (onlineUser.getUserName().equals(username)) {
187-
String token =EncryptUtils.desDecrypt(onlineUser.getKey());
188-
kickOut(token);
189-
}
190-
}
145+
public void kickOutForUsername(String username) {
146+
String loginKey = properties.getOnlineKey() + username + "*";
147+
redisUtils.scanDel(loginKey);
191148
}
192149
}

eladmin-system/src/main/java/me/zhengjie/modules/security/service/UserCacheManager.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ public class UserCacheManager {
4646
public JwtUserDto getUserCache(String userName) {
4747
if (StringUtils.isNotEmpty(userName)) {
4848
// 获取数据
49-
Object obj = redisUtils.hget(LoginProperties.cacheKey, userName);
49+
Object obj = redisUtils.get(LoginProperties.cacheKey + userName);
5050
if(obj != null){
5151
return (JwtUserDto)obj;
5252
}
@@ -63,7 +63,7 @@ public void addUserCache(String userName, JwtUserDto user) {
6363
if (StringUtils.isNotEmpty(userName)) {
6464
// 添加数据, 避免数据同时过期
6565
long time = idleTime + RandomUtil.randomInt(900, 1800);
66-
redisUtils.hset(LoginProperties.cacheKey, userName, user, time);
66+
redisUtils.set(LoginProperties.cacheKey + userName, user, time);
6767
}
6868
}
6969

@@ -76,7 +76,7 @@ public void addUserCache(String userName, JwtUserDto user) {
7676
public void cleanUserCache(String userName) {
7777
if (StringUtils.isNotEmpty(userName)) {
7878
// 清除数据
79-
redisUtils.hdel(LoginProperties.cacheKey, userName);
79+
redisUtils.del(LoginProperties.cacheKey + userName);
8080
}
8181
}
8282
}

eladmin-system/src/main/resources/config/application-dev.yml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ login:
5656
# Redis用户登录缓存配置
5757
user-cache:
5858
# 存活时间/秒
59-
idle-time: 7200
59+
idle-time: 21600
6060
# 验证码
6161
login-code:
6262
# 验证码类型配置 查看 LoginProperties 类
@@ -84,9 +84,9 @@ jwt:
8484
# 令牌过期时间 此处单位/毫秒 ,默认4小时,可在此网站生成 https://www.convertworld.com/zh-hans/time/milliseconds.html
8585
token-validity-in-seconds: 14400000
8686
# 在线用户key
87-
online-key: online-token-
87+
online-key: "online-token:"
8888
# 验证码
89-
code-key: code-key-
89+
code-key: "captcha-code:"
9090
# token 续期检查时间范围(默认30分钟,单位毫秒),在token即将过期的一段时间内用户操作了,则给用户的token续期
9191
detect: 1800000
9292
# 续期时间范围,默认1小时,单位毫秒

eladmin-system/src/main/resources/config/application-prod.yml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ login:
5858
# Redis用户登录缓存配置
5959
user-cache:
6060
# 存活时间/秒
61-
idle-time: 7200
61+
idle-time: 21600
6262
# 验证码
6363
login-code:
6464
# 验证码类型配置 查看 LoginProperties 类
@@ -86,9 +86,9 @@ jwt:
8686
# 令牌过期时间 此处单位/毫秒 ,默认2小时,可在此网站生成 https://www.convertworld.com/zh-hans/time/milliseconds.html
8787
token-validity-in-seconds: 7200000
8888
# 在线用户key
89-
online-key: online-token-
89+
online-key: "online-token:"
9090
# 验证码
91-
code-key: code-key-
91+
code-key: "captcha-code:"
9292
# token 续期检查时间范围(默认30分钟,单位默认毫秒),在token即将过期的一段时间内用户操作了,则给用户的token续期
9393
detect: 1800000
9494
# 续期时间范围,默认 1小时,这里单位毫秒

0 commit comments

Comments
 (0)