package com.tcm.training.admin.auth.service; import com.tcm.training.admin.auth.vo.CaptchaResponse; import org.springframework.stereotype.Service; import java.nio.charset.StandardCharsets; import java.util.Base64; import java.util.Map; import java.util.UUID; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ThreadLocalRandom; @Service public class CaptchaService { private static final long EXPIRE_MILLIS = 5 * 60 * 1000L; private static final char[] CAPTCHA_CHARS = "23456789ABCDEFGHJKLMNPQRSTUVWXYZ".toCharArray(); private final Map captchaStore = new ConcurrentHashMap(); public CaptchaResponse createCaptcha() { clearExpired(); String code = randomCode(4); String captchaId = UUID.randomUUID().toString().replace("-", ""); captchaStore.put(captchaId, new CaptchaEntry(code, System.currentTimeMillis() + EXPIRE_MILLIS)); String svg = buildSvg(code); String imageData = "data:image/svg+xml;base64," + Base64.getEncoder().encodeToString(svg.getBytes(StandardCharsets.UTF_8)); return new CaptchaResponse(captchaId, imageData); } public void validate(String captchaId, String captchaCode) { clearExpired(); CaptchaEntry entry = captchaStore.remove(captchaId); if (entry == null) { throw new IllegalArgumentException("验证码已失效,请刷新后重试"); } if (entry.expireAt < System.currentTimeMillis()) { throw new IllegalArgumentException("验证码已过期,请刷新后重试"); } if (!entry.code.equalsIgnoreCase(captchaCode)) { throw new IllegalArgumentException("验证码错误"); } } private void clearExpired() { long now = System.currentTimeMillis(); captchaStore.entrySet().removeIf(entry -> entry.getValue().expireAt < now); } private String randomCode(int length) { StringBuilder builder = new StringBuilder(length); ThreadLocalRandom random = ThreadLocalRandom.current(); for (int i = 0; i < length; i++) { builder.append(CAPTCHA_CHARS[random.nextInt(CAPTCHA_CHARS.length)]); } return builder.toString(); } private String buildSvg(String code) { ThreadLocalRandom random = ThreadLocalRandom.current(); StringBuilder builder = new StringBuilder(); builder.append(""); builder.append(""); builder.append(""); builder.append(""); builder.append(""); for (int i = 0; i < 6; i++) { builder.append(""); } for (int i = 0; i < 16; i++) { builder.append(""); } for (int i = 0; i < code.length(); i++) { int x = 18 + i * 24; int y = 29 + random.nextInt(6); int rotate = random.nextInt(-18, 19); String color = random.nextBoolean() ? "#1f2937" : "#2563eb"; builder.append("").append(code.charAt(i)).append(""); } builder.append(""); return builder.toString(); } private static class CaptchaEntry { private final String code; private final long expireAt; private CaptchaEntry(String code, long expireAt) { this.code = code; this.expireAt = expireAt; } } }