package com.ruoyi.web.controller.miniapp;
|
|
import java.awt.image.BufferedImage;
|
import java.util.concurrent.TimeUnit;
|
import javax.imageio.ImageIO;
|
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.stereotype.Service;
|
import org.springframework.util.FastByteArrayOutputStream;
|
import com.google.code.kaptcha.Producer;
|
import com.ruoyi.common.config.RuoYiConfig;
|
import com.ruoyi.common.constant.Constants;
|
import com.ruoyi.common.core.domain.AjaxResult;
|
import com.ruoyi.common.core.redis.RedisCache;
|
import com.ruoyi.common.utils.sign.Base64;
|
import com.ruoyi.common.utils.uuid.IdUtils;
|
import com.ruoyi.system.service.ISysConfigService;
|
import jakarta.annotation.Resource;
|
|
/**
|
* 学生端图形验证码:生成 + 一次性消费校验。
|
* 供 MiniappAccountController(登录/注册/找回)与 MiniappController(绑定/开放登记)共用,
|
* 堵死"无验证码枚举花名册"的匿名爆破面。
|
*/
|
@Service
|
public class MiniappCaptchaService
|
{
|
/** 学生端独立验证码 key 前缀(不与管理端 captcha_codes: 互串) */
|
public static final String CAPTCHA_KEY = "miniapp_captcha:";
|
|
@Resource(name = "captchaProducer")
|
private Producer captchaProducer;
|
|
@Resource(name = "captchaProducerMath")
|
private Producer captchaProducerMath;
|
|
@Autowired private RedisCache redisCache;
|
@Autowired private ISysConfigService configService;
|
|
/** 生成验证码,返回 { captchaEnabled, uuid, img };开关与管理端一致 */
|
public AjaxResult generate()
|
{
|
AjaxResult ajax = AjaxResult.success();
|
boolean captchaEnabled = configService.selectCaptchaEnabled();
|
ajax.put("captchaEnabled", captchaEnabled);
|
if (!captchaEnabled)
|
{
|
return ajax;
|
}
|
String uuid = IdUtils.simpleUUID();
|
String capStr;
|
String code;
|
BufferedImage image;
|
if ("math".equals(RuoYiConfig.getCaptchaType()))
|
{
|
String capText = captchaProducerMath.createText();
|
capStr = capText.substring(0, capText.lastIndexOf("@"));
|
code = capText.substring(capText.lastIndexOf("@") + 1);
|
image = captchaProducerMath.createImage(capStr);
|
}
|
else
|
{
|
capStr = code = captchaProducer.createText();
|
image = captchaProducer.createImage(capStr);
|
}
|
redisCache.setCacheObject(CAPTCHA_KEY + uuid, code, Constants.CAPTCHA_EXPIRATION, TimeUnit.MINUTES);
|
try (FastByteArrayOutputStream os = new FastByteArrayOutputStream())
|
{
|
ImageIO.write(image, "jpg", os);
|
ajax.put("uuid", uuid);
|
ajax.put("img", Base64.encode(os.toByteArray()));
|
}
|
catch (Exception e)
|
{
|
return AjaxResult.error("验证码生成失败");
|
}
|
return ajax;
|
}
|
|
/** 一次性校验:通过返回 null,否则返回错误消息(先删 key 防重放) */
|
public String verify(String code, String uuid)
|
{
|
if (!configService.selectCaptchaEnabled()) return null;
|
if (code == null || code.trim().isEmpty() || uuid == null || uuid.trim().isEmpty()) return "请输入图形验证码";
|
String key = CAPTCHA_KEY + uuid;
|
Object expected = redisCache.getCacheObject(key);
|
if (expected == null) return "验证码已失效,请刷新后重试";
|
redisCache.deleteObject(key);
|
if (!code.trim().equalsIgnoreCase(expected.toString())) return "验证码错误";
|
return null;
|
}
|
|
/** 失败计数:固定 10 分钟窗口内累计(首次失败设定窗口,后续只增量,不滚存续命) */
|
public int incrFail(String key, int max)
|
{
|
long cnt = redisCache.redisTemplate.opsForValue().increment(key);
|
if (cnt == 1L)
|
{
|
redisCache.redisTemplate.expire(key, 10, TimeUnit.MINUTES);
|
}
|
return (int) cnt;
|
}
|
|
public int failCount(String key)
|
{
|
Object v = redisCache.getCacheObject(key);
|
return v instanceof Number ? ((Number) v).intValue() : 0;
|
}
|
|
public void clearFail(String key)
|
{
|
redisCache.deleteObject(key);
|
}
|
}
|