Skip to content

Commit c4a9a88

Browse files
author
heavyrian2012
committed
滑动验证验证数据放到数据库中,解决多节点部署问题
1 parent 08a445a commit c4a9a88

4 files changed

Lines changed: 121 additions & 40 deletions

File tree

src/main/java/cn/wildfirechat/app/Application.java

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package cn.wildfirechat.app;
22

33
import cn.wildfirechat.app.jpa.PCSessionRepository;
4+
import cn.wildfirechat.app.jpa.SlideVerifyRepository;
45
import org.springframework.beans.factory.annotation.Autowired;
56
import org.springframework.boot.SpringApplication;
67
import org.springframework.boot.autoconfigure.SpringBootApplication;
@@ -20,6 +21,9 @@ public class Application {
2021
@Autowired
2122
private PCSessionRepository pcSessionRepository;
2223

24+
@Autowired
25+
private SlideVerifyRepository slideVerifyRepository;
26+
2327
public static void main(String[] args) {
2428
SpringApplication.run(Application.class, args);
2529
}
@@ -44,4 +48,9 @@ public void clearPCSession(){
4448
pcSessionRepository.deleteByCreateDtBefore(System.currentTimeMillis() - 60 * 60 * 1000);
4549
}
4650

51+
@Scheduled(fixedRate = 60 * 60 * 1000)
52+
public void cleanExpiredSlideVerify(){
53+
slideVerifyRepository.deleteExpired(java.time.Instant.now().minusSeconds(300));
54+
}
55+
4756
}
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
package cn.wildfirechat.app.jpa;
2+
3+
import javax.persistence.*;
4+
import java.sql.Timestamp;
5+
6+
@Entity
7+
@Table(name = "slide_verify")
8+
public class SlideVerify {
9+
@Id
10+
@Column(length = 64)
11+
private String token;
12+
13+
@Column
14+
private int x;
15+
16+
@Column
17+
private long timestamp;
18+
19+
@Column
20+
private boolean verified;
21+
22+
public SlideVerify() {
23+
}
24+
25+
public SlideVerify(String token, int x, long timestamp) {
26+
this.token = token;
27+
this.x = x;
28+
this.timestamp = timestamp;
29+
this.verified = false;
30+
}
31+
32+
public String getToken() {
33+
return token;
34+
}
35+
36+
public void setToken(String token) {
37+
this.token = token;
38+
}
39+
40+
public int getX() {
41+
return x;
42+
}
43+
44+
public void setX(int x) {
45+
this.x = x;
46+
}
47+
48+
public long getTimestamp() {
49+
return timestamp;
50+
}
51+
52+
public void setTimestamp(long timestamp) {
53+
this.timestamp = timestamp;
54+
}
55+
56+
public boolean isVerified() {
57+
return verified;
58+
}
59+
60+
public void setVerified(boolean verified) {
61+
this.verified = verified;
62+
}
63+
64+
public boolean isExpired(int timeoutSeconds) {
65+
return System.currentTimeMillis() - timestamp > timeoutSeconds * 1000L;
66+
}
67+
}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
package cn.wildfirechat.app.jpa;
2+
3+
import org.springframework.data.jpa.repository.Modifying;
4+
import org.springframework.data.jpa.repository.Query;
5+
import org.springframework.data.repository.CrudRepository;
6+
import org.springframework.data.repository.query.Param;
7+
import org.springframework.stereotype.Repository;
8+
9+
import java.time.Instant;
10+
import java.util.Optional;
11+
12+
@Repository
13+
public interface SlideVerifyRepository extends CrudRepository<SlideVerify, String> {
14+
15+
Optional<SlideVerify> findByToken(String token);
16+
17+
@Modifying
18+
@Query("DELETE FROM SlideVerify s WHERE s.timestamp < : cutoff")
19+
int deleteExpired(@Param("cutoff") Instant cutoff);
20+
}

src/main/java/cn/wildfirechat/app/slide/SlideVerifyService.java

Lines changed: 25 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,14 @@
11
package cn.wildfirechat.app.slide;
22

3+
import cn.wildfirechat.app.jpa.SlideVerify;
4+
import cn.wildfirechat.app.jpa.SlideVerifyRepository;
35
import org.slf4j.Logger;
46
import org.slf4j.LoggerFactory;
7+
import org.springframework.beans.factory.annotation.Autowired;
58
import org.springframework.stereotype.Service;
69

710
import javax.imageio.ImageIO;
811
import java.awt.*;
9-
import java.awt.geom.AffineTransform;
10-
import java.awt.geom.PathIterator;
11-
import java.awt.geom.Rectangle2D;
1212
import java.awt.geom.RoundRectangle2D;
1313
import java.awt.image.BufferedImage;
1414
import java.io.ByteArrayOutputStream;
@@ -17,14 +17,13 @@
1717
import java.util.HashMap;
1818
import java.util.Map;
1919
import java.util.UUID;
20-
import java.util.concurrent.ConcurrentHashMap;
2120

2221
@Service
2322
public class SlideVerifyService {
2423
private static final Logger LOG = LoggerFactory.getLogger(SlideVerifyService.class);
2524

26-
// 存储验证码信息:token -> 验证数据
27-
private final ConcurrentHashMap<String, VerifyData> verifyCache = new ConcurrentHashMap<>();
25+
@Autowired
26+
private SlideVerifyRepository slideVerifyRepository;
2827

2928
// 验证码有效时间(秒)
3029
private static final int VERIFY_TIMEOUT = 300; // 5分钟
@@ -38,23 +37,6 @@ public class SlideVerifyService {
3837
private static final int SLIDER_WIDTH = 50;
3938
private static final int SLIDER_HEIGHT = 50;
4039

41-
// 验证数据类
42-
private static class VerifyData {
43-
int x; // 正确的x坐标
44-
long timestamp; // 生成时间戳
45-
boolean verified; // 是否已验证
46-
47-
VerifyData(int x) {
48-
this.x = x;
49-
this.timestamp = System.currentTimeMillis();
50-
this.verified = false;
51-
}
52-
53-
boolean isExpired() {
54-
return System.currentTimeMillis() - timestamp > VERIFY_TIMEOUT * 1000;
55-
}
56-
}
57-
5840
/**
5941
* 生成滑动验证码
6042
*/
@@ -68,8 +50,9 @@ public Map<String, Object> generateSlideVerify() {
6850

6951
LOG.info("生成滑动验证码: token={}, x={}, y={}", token, x, y);
7052

71-
// 存储验证数据
72-
verifyCache.put(token, new VerifyData(x));
53+
// 保存验证数据到数据库
54+
SlideVerify slideVerify = new SlideVerify(token, x, System.currentTimeMillis());
55+
slideVerifyRepository.save(slideVerify);
7356

7457
try {
7558
// 生成背景图(带缺口)
@@ -89,7 +72,7 @@ public Map<String, Object> generateSlideVerify() {
8972
return result;
9073
} catch (Exception e) {
9174
LOG.error("生成滑动验证码失败", e);
92-
verifyCache.remove(token);
75+
slideVerifyRepository.delete(slideVerify);
9376
throw new RuntimeException("生成滑动验证码失败");
9477
}
9578
}
@@ -98,34 +81,35 @@ public Map<String, Object> generateSlideVerify() {
9881
* 验证滑动位置
9982
*/
10083
public boolean verifySlide(String token, int userX) {
101-
VerifyData data = verifyCache.get(token);
84+
SlideVerify data = slideVerifyRepository.findByToken(token).orElse(null);
10285

10386
if (data == null) {
10487
LOG.warn("验证token不存在: {}", token);
10588
return false;
10689
}
10790

108-
if (data.isExpired()) {
91+
if (data.isExpired(VERIFY_TIMEOUT)) {
10992
LOG.warn("验证token已过期: {}", token);
110-
verifyCache.remove(token);
93+
slideVerifyRepository.delete(data);
11194
return false;
11295
}
11396

114-
if (data.verified) {
97+
if (data.isVerified()) {
11598
LOG.warn("验证token已使用: {}", token);
11699
return false;
117100
}
118101

119102
// 验证位置是否在误差范围内
120-
int difference = Math.abs(data.x - userX);
103+
int difference = Math.abs(data.getX() - userX);
121104
boolean success = difference <= TOLERANCE;
122105

123106
if (success) {
124-
data.verified = true;
125-
LOG.info("滑动验证成功,token: {}, 正确位置: {}, 用户位置: {}, 差值: {}", token, data.x, userX, difference);
107+
data.setVerified(true);
108+
slideVerifyRepository.save(data);
109+
LOG.info("滑动验证成功,token: {}, 正确位置: {}, 用户位置: {}, 差值: {}", token, data.getX(), userX, difference);
126110
} else {
127-
LOG.warn("滑动验证失败,token: {}, 正确位置: {}, 用户位置: {}, 差值: {}, 容差: {}", token, data.x, userX, difference, TOLERANCE);
128-
verifyCache.remove(token); // 验证失败则移除token
111+
LOG.warn("滑动验证失败,token: {}, 正确位置: {}, 用户位置: {}, 差值: {}, 容差: {}", token, data.getX(), userX, difference, TOLERANCE);
112+
slideVerifyRepository.delete(data); // 验证失败则删除记录
129113
}
130114

131115
return success;
@@ -135,15 +119,15 @@ public boolean verifySlide(String token, int userX) {
135119
* 检查token是否已验证(一次性使用)
136120
*/
137121
public boolean isVerified(String token) {
138-
VerifyData data = verifyCache.get(token);
139-
if (data == null || data.isExpired()) {
122+
SlideVerify data = slideVerifyRepository.findByToken(token).orElse(null);
123+
if (data == null || data.isExpired(VERIFY_TIMEOUT)) {
140124
return false;
141125
}
142126

143127
// token已验证通过,立即删除,确保只能使用一次
144-
if (data.verified) {
128+
if (data.isVerified()) {
145129
LOG.info("验证token已使用,删除token: {}", token);
146-
verifyCache.remove(token);
130+
slideVerifyRepository.delete(data);
147131
return true;
148132
}
149133

@@ -154,7 +138,8 @@ public boolean isVerified(String token) {
154138
* 清理过期的验证数据
155139
*/
156140
public void cleanExpiredData() {
157-
verifyCache.entrySet().removeIf(entry -> entry.getValue().isExpired());
141+
java.time.Instant cutoff = java.time.Instant.now().minusSeconds(VERIFY_TIMEOUT);
142+
slideVerifyRepository.deleteExpired(cutoff);
158143
}
159144

160145
/**

0 commit comments

Comments
 (0)