Skip to content

Commit 57bea19

Browse files
authored
feat: 新增验证码和获取回收数据功能 (#517)
* feat: 新增验证码生成器,丰富验证码样式 * feat: 优化验证码功能,比较时大小写不敏感。Captcha添加1小时过期 * test: 新增验证码功能单元测试 * feat: 新增获取回收数据功能 * test: 新增获取回收数据功能测试 * fix: 解决server-check问题
1 parent a604683 commit 57bea19

14 files changed

Lines changed: 1346 additions & 52 deletions

File tree

survey-common/src/main/java/com/xiaojusurvey/engine/common/entity/user/Captcha.java

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,11 @@
22

33
import com.xiaojusurvey.engine.common.entity.BaseEntity;
44
import lombok.Data;
5+
import org.springframework.data.mongodb.core.index.Indexed;
56
import org.springframework.data.mongodb.core.mapping.Document;
67

8+
import java.util.Date;
9+
710
/**
811
* @Author: LYF
912
* @CreateTime: 2024-06-06
@@ -18,4 +21,8 @@ public class Captcha extends BaseEntity {
1821
*/
1922
private String text;
2023

24+
//1 小时过期(3600s)
25+
@Indexed(expireAfterSeconds = 3600)
26+
private Date expireAt = new Date();
27+
2128
}

survey-core/src/main/java/com/xiaojusurvey/engine/core/auth/captcha/SimpleCaptchaGenerator.java

Lines changed: 0 additions & 45 deletions
This file was deleted.
Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
package com.xiaojusurvey.engine.core.auth.captcha;
2+
3+
import com.xiaojusurvey.engine.common.entity.user.Captcha;
4+
import org.springframework.stereotype.Service;
5+
6+
import java.util.concurrent.ThreadLocalRandom;
7+
import java.util.stream.Collectors;
8+
import java.util.stream.IntStream;
9+
10+
/**
11+
* @Author: WYX
12+
* @CreateTime: 2025/8/10
13+
* @Description: 彩色字符+噪声svg验证码生成器
14+
*/
15+
@Service("svgCaptchaGenerator")
16+
public class SvgCaptchaGenerator implements CaptchaGenerator {
17+
private static final int WIDTH = 150;
18+
private static final int HEIGHT = 50;
19+
private static final int SIZE = 4; // 对齐 size: 4
20+
private static final int NOISE = 3; // 对齐 noise: 3
21+
private static final String BG = "#f0f0f0"; // 对齐 background
22+
private static final String POOL = "23456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghjkmnpqrstuvwxyz"; // ignore 0o1i
23+
24+
@Override
25+
public Captcha generateRandomText(int length) {
26+
ThreadLocalRandom rnd = ThreadLocalRandom.current();
27+
String text = IntStream.range(0, length)
28+
.mapToObj(i -> String.valueOf(POOL.charAt(rnd.nextInt(POOL.length()))))
29+
.collect(Collectors.joining());
30+
Captcha c = new Captcha();
31+
c.setText(text);
32+
return c;
33+
}
34+
35+
@Override
36+
public String generateRandomSvg(String text) {
37+
ThreadLocalRandom rnd = ThreadLocalRandom.current();
38+
StringBuilder svg = new StringBuilder();
39+
svg.append("<svg xmlns='http://www.w3.org/2000/svg' width='")
40+
.append(WIDTH).append("' height='").append(HEIGHT)
41+
.append("' viewBox='0 0 ").append(WIDTH).append(" ").append(HEIGHT).append("'>");
42+
43+
// background
44+
svg.append("<rect width='100%' height='100%' fill='").append(BG).append("'/>");
45+
46+
// noise lines (cubic bezier), count = 3
47+
for (int i = 0; i < NOISE; i++) {
48+
svg.append("<path d='").append(randomCubicPath(rnd))
49+
.append("' stroke='").append(randomVivid(rnd))
50+
.append("' stroke-width='").append(rnd.nextInt(1, 3))
51+
.append("' fill='none' opacity='0.85'/>");
52+
}
53+
54+
// characters (color=true, each glyph random color)
55+
int fontSize = 28;
56+
int startX = 18;
57+
int gap = 30;
58+
for (int i = 0; i < text.length(); i++) {
59+
char ch = text.charAt(i);
60+
int x = startX + i * gap + rnd.nextInt(-2, 3);
61+
int y = HEIGHT / 2 + rnd.nextInt(-4, 5);
62+
int rotate = rnd.nextInt(-25, 26);
63+
int skew = rnd.nextInt(-10, 11);
64+
65+
svg.append("<g transform='translate(").append(x).append(",").append(y)
66+
.append(") rotate(").append(rotate).append(") skewX(").append(skew).append(")'>")
67+
.append("<text x='0' y='0' dominant-baseline='middle' text-anchor='middle'")
68+
.append(" font-family='Verdana,Arial' font-size='").append(fontSize).append("'")
69+
.append(" fill='").append(randomVivid(rnd)).append("'>")
70+
.append(ch)
71+
.append("</text></g>");
72+
}
73+
74+
svg.append("</svg>");
75+
return svg.toString();
76+
}
77+
78+
private static String randomCubicPath(ThreadLocalRandom rnd) {
79+
// 模拟 svg-captcha 的随机贝塞尔干扰线
80+
int x1 = -10, y1 = rnd.nextInt(0, HEIGHT);
81+
int x2 = rnd.nextInt(0, WIDTH / 2), y2 = rnd.nextInt(0, HEIGHT);
82+
int x3 = rnd.nextInt(WIDTH / 2, WIDTH), y3 = rnd.nextInt(0, HEIGHT);
83+
int x4 = WIDTH + 10, y4 = rnd.nextInt(0, HEIGHT);
84+
return String.format("M %d %d C %d %d, %d %d, %d %d", x1, y1, x2, y2, x3, y3, x4, y4);
85+
}
86+
87+
private static String randomVivid(ThreadLocalRandom rnd) {
88+
int h = rnd.nextInt(0, 360);
89+
int s = rnd.nextInt(70, 100);
90+
int l = rnd.nextInt(40, 65);
91+
return String.format("hsl(%d,%d%%,%d%%)", h, s, l);
92+
}
93+
94+
}

survey-core/src/main/java/com/xiaojusurvey/engine/core/auth/impl/AuthServiceImpl.java

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ public class AuthServiceImpl implements AuthService {
3232
@Resource
3333
private MongoRepository mongoRepository;
3434

35-
@Resource(name = "simpleCaptchaGenerator")
35+
@Resource(name = "svgCaptchaGenerator")
3636
private CaptchaGenerator captchaGenerator;
3737

3838
@Resource
@@ -45,8 +45,8 @@ public class AuthServiceImpl implements AuthService {
4545
@Override
4646
public CaptchaVo captcha() {
4747
Captcha captcha = captchaGenerator.generateRandomText(4);
48-
mongoRepository.save(captcha);
49-
return captchaGenerator.generateRandomSvg(captcha);
48+
Captcha saved = mongoRepository.save(captcha);
49+
return captchaGenerator.generateRandomSvg(saved);
5050
}
5151

5252
@Override
@@ -97,22 +97,20 @@ public UserVo login(UserParam userParam) {
9797

9898

9999
/**
100-
* 判断验证码是否正确
100+
* 判断验证码是否正确(大小写不敏感)
101101
*
102102
* @param captchaId 验证码id
103103
* @param captchaText 需要验证的文本
104-
* @return
105104
*/
106105
public void checkCaptchaIsCorrect(String captchaId, String captchaText) {
107106
if (ObjectUtils.isEmpty(captchaId) || ObjectUtils.isEmpty(captchaText)) {
108107
throw new ServiceException(RespErrorCode.CAPTCHA_INCORRECT.getMessage(), RespErrorCode.CAPTCHA_INCORRECT.getCode());
109108
}
110109
Captcha captcha = mongoRepository.findById(captchaId, Captcha.class);
111-
//非空判断
112110
if (ObjectUtils.isEmpty(captcha)) {
113111
throw new ServiceException(RespErrorCode.CAPTCHA_INCORRECT.getMessage(), RespErrorCode.CAPTCHA_INCORRECT.getCode());
114112
}
115-
if (!captchaText.equals(captcha.getText())) {
113+
if (!captchaText.equalsIgnoreCase(captcha.getText())) {
116114
throw new ServiceException(RespErrorCode.CAPTCHA_INCORRECT.getMessage(), RespErrorCode.CAPTCHA_INCORRECT.getCode());
117115
}
118116
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
package com.xiaojusurvey.engine.core.survey;
2+
3+
import com.xiaojusurvey.engine.core.survey.param.DataTableParam;
4+
import com.xiaojusurvey.engine.core.survey.vo.DataTableVO;
5+
6+
/**
7+
* @Author: WYX
8+
* @CreateTime: 2025/8/16
9+
* @Description: 获取问卷回收数据表格
10+
*/
11+
public interface DataStatisticService {
12+
DataTableVO getDataTable(DataTableParam param);
13+
}

survey-core/src/main/java/com/xiaojusurvey/engine/core/survey/dto/SurveyConfCode.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,8 @@ public static class DataItem {
8787
private Map<String, Object> rangeConfig;
8888
private String starStyle;
8989
private String innerType;
90+
// 多级联动配置数据
91+
private Map<String, Object> cascaderData;
9092

9193
}
9294

0 commit comments

Comments
 (0)