Skip to content
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,11 @@

import com.xiaojusurvey.engine.common.entity.BaseEntity;
import lombok.Data;
import org.springframework.data.mongodb.core.index.Indexed;
import org.springframework.data.mongodb.core.mapping.Document;

import java.util.Date;

/**
* @Author: LYF
* @CreateTime: 2024-06-06
Expand All @@ -18,4 +21,8 @@ public class Captcha extends BaseEntity {
*/
private String text;

//1 小时过期(3600s)
@Indexed(expireAfterSeconds = 3600)
private Date expireAt = new Date();

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
package com.xiaojusurvey.engine.common.enums;

import lombok.Getter;

/**
* @Author: WYX
* @CreateTime: 2025/9/1
* @Description:
*/
@Getter
public enum QuestionTypeEnum {
// 单行输入框
TEXT("text"),

// 多行输入框
TEXTAREA("textarea"),

// 单项选择
RADIO("radio"),

// 多项选择
CHECKBOX("checkbox"),

// 多项选择
BINARY_CHOICE("binary-choice"),

// 评分
RADIO_STAR("radio-star"),

// nps评分
RADIO_NPS("radio-nps"),

// 投票
VOTE("vote"),

// 多级联动
CASCADER("cascader");

private final String type;

QuestionTypeEnum(String type) {
this.type = type;
}

/**
* 获取支持聚合统计的问题类型列表
*/
public static QuestionTypeEnum[] getAggerationSupportTypes() {
return new QuestionTypeEnum[]{
RADIO, CHECKBOX, BINARY_CHOICE, RADIO_STAR, RADIO_NPS, VOTE, CASCADER
};
}

/**
* 根据类型字符串获取枚举
*/
public static QuestionTypeEnum fromType(String type) {
for (QuestionTypeEnum questionType : values()) {
if (questionType.type.equals(type)) {
return questionType;
}
}
return null;
}

/**
* 判断是否为评分类题型
*/
public boolean isRatingType() {
return this == QuestionTypeEnum.RADIO_NPS || this == QuestionTypeEnum.RADIO_STAR;
}

/**
* 判断是否为选项类体现
*/
public boolean isOptionType() {
return this == RADIO || this == CHECKBOX || this == VOTE || this == BINARY_CHOICE;
}

}

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
package com.xiaojusurvey.engine.core.auth.captcha;

import com.xiaojusurvey.engine.common.entity.user.Captcha;
import org.springframework.stereotype.Service;

import java.util.concurrent.ThreadLocalRandom;
import java.util.stream.Collectors;
import java.util.stream.IntStream;

/**
* @Author: WYX
* @CreateTime: 2025/8/10
* @Description: 彩色字符+噪声svg验证码生成器
*/
@Service("svgCaptchaGenerator")
public class SvgCaptchaGenerator implements CaptchaGenerator {
private static final int WIDTH = 150;
private static final int HEIGHT = 50;
private static final int SIZE = 4; // 对齐 size: 4
private static final int NOISE = 3; // 对齐 noise: 3
private static final String BG = "#f0f0f0"; // 对齐 background
private static final String POOL = "23456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghjkmnpqrstuvwxyz"; // ignore 0o1i

@Override
public Captcha generateRandomText(int length) {
ThreadLocalRandom rnd = ThreadLocalRandom.current();
String text = IntStream.range(0, length)
.mapToObj(i -> String.valueOf(POOL.charAt(rnd.nextInt(POOL.length()))))
.collect(Collectors.joining());
Captcha c = new Captcha();
c.setText(text);
return c;
}

@Override
public String generateRandomSvg(String text) {
ThreadLocalRandom rnd = ThreadLocalRandom.current();
StringBuilder svg = new StringBuilder();
svg.append("<svg xmlns='http://www.w3.org/2000/svg' width='")
.append(WIDTH).append("' height='").append(HEIGHT)
.append("' viewBox='0 0 ").append(WIDTH).append(" ").append(HEIGHT).append("'>");

// background
svg.append("<rect width='100%' height='100%' fill='").append(BG).append("'/>");

// noise lines (cubic bezier), count = 3
for (int i = 0; i < NOISE; i++) {
svg.append("<path d='").append(randomCubicPath(rnd))
.append("' stroke='").append(randomVivid(rnd))
.append("' stroke-width='").append(rnd.nextInt(1, 3))
.append("' fill='none' opacity='0.85'/>");
}

// characters (color=true, each glyph random color)
int fontSize = 28;
int startX = 18;
int gap = 30;
for (int i = 0; i < text.length(); i++) {
char ch = text.charAt(i);
int x = startX + i * gap + rnd.nextInt(-2, 3);
int y = HEIGHT / 2 + rnd.nextInt(-4, 5);
int rotate = rnd.nextInt(-25, 26);
int skew = rnd.nextInt(-10, 11);

svg.append("<g transform='translate(").append(x).append(",").append(y)
.append(") rotate(").append(rotate).append(") skewX(").append(skew).append(")'>")
.append("<text x='0' y='0' dominant-baseline='middle' text-anchor='middle'")
.append(" font-family='Verdana,Arial' font-size='").append(fontSize).append("'")
.append(" fill='").append(randomVivid(rnd)).append("'>")
.append(ch)
.append("</text></g>");
}

svg.append("</svg>");
return svg.toString();
}

private static String randomCubicPath(ThreadLocalRandom rnd) {
// 模拟 svg-captcha 的随机贝塞尔干扰线
int x1 = -10, y1 = rnd.nextInt(0, HEIGHT);
int x2 = rnd.nextInt(0, WIDTH / 2), y2 = rnd.nextInt(0, HEIGHT);
int x3 = rnd.nextInt(WIDTH / 2, WIDTH), y3 = rnd.nextInt(0, HEIGHT);
int x4 = WIDTH + 10, y4 = rnd.nextInt(0, HEIGHT);
return String.format("M %d %d C %d %d, %d %d, %d %d", x1, y1, x2, y2, x3, y3, x4, y4);
}

private static String randomVivid(ThreadLocalRandom rnd) {
int h = rnd.nextInt(0, 360);
int s = rnd.nextInt(70, 100);
int l = rnd.nextInt(40, 65);
return String.format("hsl(%d,%d%%,%d%%)", h, s, l);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ public class AuthServiceImpl implements AuthService {
@Resource
private MongoRepository mongoRepository;

@Resource(name = "simpleCaptchaGenerator")
@Resource(name = "svgCaptchaGenerator")
private CaptchaGenerator captchaGenerator;

@Resource
Expand All @@ -45,8 +45,8 @@ public class AuthServiceImpl implements AuthService {
@Override
public CaptchaVo captcha() {
Captcha captcha = captchaGenerator.generateRandomText(4);
mongoRepository.save(captcha);
return captchaGenerator.generateRandomSvg(captcha);
Captcha saved = mongoRepository.save(captcha);
return captchaGenerator.generateRandomSvg(saved);
}

@Override
Expand Down Expand Up @@ -97,22 +97,20 @@ public UserVo login(UserParam userParam) {


/**
* 判断验证码是否正确
* 判断验证码是否正确(大小写不敏感)
*
* @param captchaId 验证码id
* @param captchaText 需要验证的文本
* @return
*/
public void checkCaptchaIsCorrect(String captchaId, String captchaText) {
if (ObjectUtils.isEmpty(captchaId) || ObjectUtils.isEmpty(captchaText)) {
throw new ServiceException(RespErrorCode.CAPTCHA_INCORRECT.getMessage(), RespErrorCode.CAPTCHA_INCORRECT.getCode());
}
Captcha captcha = mongoRepository.findById(captchaId, Captcha.class);
//非空判断
if (ObjectUtils.isEmpty(captcha)) {
throw new ServiceException(RespErrorCode.CAPTCHA_INCORRECT.getMessage(), RespErrorCode.CAPTCHA_INCORRECT.getCode());
}
if (!captchaText.equals(captcha.getText())) {
if (!captchaText.equalsIgnoreCase(captcha.getText())) {
throw new ServiceException(RespErrorCode.CAPTCHA_INCORRECT.getMessage(), RespErrorCode.CAPTCHA_INCORRECT.getCode());
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package com.xiaojusurvey.engine.core.survey;

import com.xiaojusurvey.engine.core.survey.param.AggregationStatisParam;
import com.xiaojusurvey.engine.core.survey.param.DataTableParam;
import com.xiaojusurvey.engine.core.survey.vo.AggregationStatisVO;
import com.xiaojusurvey.engine.core.survey.vo.DataTableVO;

import java.util.List;

/**
* @Author: WYX
* @CreateTime: 2025/8/16
* @Description: 获取问卷回收数据表格
*/
public interface DataStatisticService {
DataTableVO getDataTable(DataTableParam param);

List<AggregationStatisVO> getAggregationStatis(AggregationStatisParam param);
}
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,8 @@ public static class DataItem {
private Map<String, Object> rangeConfig;
private String starStyle;
private String innerType;
// 多级联动配置数据
private Map<String, Object> cascaderData;

}

Expand Down
Loading