验证码模块完善,增加配置项:

#验证码设置
  verification-code:
    #图形验证码有效时间 秒 包含滑块验证码有效时间, 以及验证通过之后,缓存中存储的验证结果有效时间
    effectiveTime: 300
    #水印
    watermark: LILI-SHOP
    #干扰项数量 最大2 默认0
    interfereNum: 1
    #允许误差像素
    faultTolerant: 3
This commit is contained in:
Chopper 2021-08-23 17:45:25 +08:00
parent f9308fa5bf
commit ef0eeda469
8 changed files with 178 additions and 32 deletions

View File

@ -1,8 +0,0 @@
{
"properties": [
{
"name": "spring.http.multipart.location",
"type": "java.lang.String",
"description": "Description for spring.http.multipart.location."
}
] }

View File

@ -205,6 +205,16 @@ jasypt:
password: lili
lili:
#验证码设置
verification-code:
#图形验证码有效时间 秒 包含滑块验证码有效时间, 以及验证通过之后,缓存中存储的验证结果有效时间
effectiveTime: 300
#水印
watermark: LILI-SHOP
#干扰项数量 最大2 默认0
interfereNum: 0
#允许误差像素
faultTolerant: 3
#短信模版配置
sms:
#登录

View File

@ -189,9 +189,9 @@ logging:
# 输出级别
level:
cn.lili: info
# org.hibernate: debug
# org.springframework: debug
# org.springframework.data.mongodb.core: debug
# org.hibernate: debug
# org.springframework: debug
# org.springframework.data.mongodb.core: debug
file:
# 指定路径
path: lili-logs
@ -204,7 +204,18 @@ jasypt:
encryptor:
password: lili
lili:
#验证码设置
verification-code:
#图形验证码有效时间 秒 包含滑块验证码有效时间, 以及验证通过之后,缓存中存储的验证结果有效时间
effectiveTime: 300
#水印
watermark: LILI-SHOP
#干扰项数量 最大2 默认0
interfereNum: 1
#允许误差像素
faultTolerant: 3
#短信模版配置
sms:
#登录

View File

@ -0,0 +1,50 @@
package cn.lili.common.properties;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Configuration;
/**
* 线程配置
*
* @author Chopper
*/
@Data
@Configuration
@ConfigurationProperties(prefix = "lili.verification-code")
public class VerificationCodeProperties {
/**
* 过期时间
* 包含滑块验证码有效时间 以及验证通过之后缓存中存储的验证结果有效时间
*/
private Long effectiveTime = 600L;
/**
* 水印
*/
private String watermark = "LILI-SHOP";
/**
* 干扰数量 最大数量
*/
private Integer interfereNum = 0;
/**
* 容错像素
*/
private Integer faultTolerant = 3;
public String getWatermark() {
return watermark;
}
public Integer getInterfereNum() {
if (interfereNum > 2) {
return 2;
}
return interfereNum;
}
}

View File

@ -330,7 +330,7 @@ public class CartServiceImpl implements CartService {
double totalPrice = tradeDTO.getSkuList().stream().mapToDouble(i -> i.getPurchasePrice() * i.getNum()).sum();
if (tradeDTO.getSkuList() != null && !tradeDTO.getSkuList().isEmpty()) {
List<String> ids = tradeDTO.getSkuList().parallelStream().filter(i -> Boolean.TRUE.equals(i.getChecked())).map(i -> i.getGoodsSku().getId()).collect(Collectors.toList());
List<String> storeIds = new ArrayList<>();
List<EsGoodsIndex> esGoodsList = esGoodsSearchService.getEsGoodsBySkuIds(ids);
for (EsGoodsIndex esGoodsIndex : esGoodsList) {
if (esGoodsIndex != null) {
@ -341,9 +341,17 @@ public class CartServiceImpl implements CartService {
count = currentGoodsCanUse.size();
}
}
storeIds.add(esGoodsIndex.getStoreId());
}
}
List<String> storeIds = new ArrayList<>();
for (CartSkuVO cartSkuVO : tradeDTO.getSkuList()) {
if (!storeIds.contains(cartSkuVO.getStoreId())) {
storeIds.add(cartSkuVO.getStoreId());
}
}
//获取可操作的优惠券集合
List<MemberCoupon> allScopeMemberCoupon = memberCouponService.getAllScopeMemberCoupon(tradeDTO.getMemberId(), storeIds);
if (allScopeMemberCoupon != null && !allScopeMemberCoupon.isEmpty()) {
//过滤满足消费门槛

View File

@ -35,6 +35,50 @@ public class ImageUtil {
}
/**
* 干扰图
*
* @param oriImage 原图
* @param templateImage 模板图
* @param x 随机扣取坐标X
* @param y 随机扣取坐标y
*/
public static void interfereTemplate(BufferedImage oriImage, BufferedImage templateImage,
int x, int y) {
//临时数组遍历用于高斯模糊存周边像素值
int[][] matrix = new int[3][3];
int[] values = new int[9];
int xLength = templateImage.getWidth();
int yLength = templateImage.getHeight();
//模板图像宽度
for (int i = 0; i < xLength; i++) {
//模板图片高度
for (int j = 0; j < yLength; j++) {
//如果模板图像当前像素点不是透明色 copy源文件信息到目标图片中
int rgb = templateImage.getRGB(i, j);
if (rgb < 0) {
//抠图区域高斯模糊
readPixel(oriImage, x + i, y + j, values);
fillMatrix(matrix, values);
oriImage.setRGB(x + i, y + j, avgMatrix(matrix));
}
//防止数组越界判断
if (i == (xLength - 1) || j == (yLength - 1)) {
continue;
}
int rightRgb = templateImage.getRGB(i + 1, j);
int downRgb = templateImage.getRGB(i, j + 1);
//描边处理,取带像素和无像素的界点判断该点是不是临界轮廓点,如果是设置该坐标像素是白色
boolean rgbImage = ((rgb >= 0 && rightRgb < 0)
|| (rgb < 0 && rightRgb >= 0)
|| (rgb >= 0 && downRgb < 0)
|| (rgb < 0 && downRgb >= 0));
}
}
}
/**
* @param oriImage 原图
* @param templateImage 模板图

View File

@ -3,7 +3,6 @@ package cn.lili.modules.verification;
import cn.lili.common.utils.Base64DecodeMultipartFile;
import cn.lili.common.vo.SerializableStream;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import org.springframework.util.Base64Utils;
import javax.imageio.ImageIO;
@ -21,10 +20,10 @@ import java.util.Random;
* @version v4.0
* @since 2020/11/17 14:34
*/
@Component
@Slf4j
public class SliderImageUtil {
private static final int BOLD = 5;
private static final String IMG_FILE_TYPE = "jpg";
@ -34,12 +33,18 @@ public class SliderImageUtil {
/**
* 根据模板切图
*
* @param sliderFile
* @param originalFile
* @return
* @param sliderFile 滑块
* @param originalFile 原图
* @param watermark 水印
* @param interfereNum 干扰选项
* @return 滑块参数
* @throws Exception sliderFile, originalFile
*/
public static Map<String, Object> pictureTemplatesCut(SerializableStream sliderFile, SerializableStream originalFile) throws Exception {
public static Map<String, Object> pictureTemplatesCut(
SerializableStream sliderFile,
SerializableStream interfereSliderFile,
SerializableStream originalFile,
String watermark, Integer interfereNum) throws Exception {
Random random = new Random();
Map<String, Object> pictureMap = new HashMap<>(16);
@ -70,6 +75,18 @@ public class SliderImageUtil {
//新建的图像根据模板颜色赋值,源图生成遮罩
ImageUtil.cutByTemplate(originalImage, sliderImage, newImage, randomX, randomY);
//干扰项
if (interfereNum > 0) {
BufferedImage interfereSliderImage = ImageIO.read(Base64DecodeMultipartFile.base64ToInputStream(interfereSliderFile.getBase64()));
for (int i = 0; i < interfereNum; i++) {
int interfereX = random.nextInt(originalWidth - 3 * sliderWidth) + 2 * sliderWidth;
int interfereY = random.nextInt(originalHeight - sliderHeight);
ImageUtil.interfereTemplate(originalImage, interfereSliderImage, interfereX, interfereY);
}
}
//设置抗锯齿的属性
graphics.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
graphics.setStroke(new BasicStroke(BOLD, BasicStroke.CAP_BUTT, BasicStroke.JOIN_BEVEL));
@ -77,7 +94,7 @@ public class SliderImageUtil {
graphics.dispose();
//添加水印
ImageUtil.addWatermark(originalImage, "LILI-SHOP");
ImageUtil.addWatermark(originalImage, watermark);
//新建流
ByteArrayOutputStream newImageOs = new ByteArrayOutputStream();
//利用ImageIO类提供的write方法将bi以png图片的数据模式写入流

View File

@ -4,14 +4,15 @@ import cn.lili.cache.Cache;
import cn.lili.cache.CachePrefix;
import cn.lili.common.enums.ResultCode;
import cn.lili.common.exception.ServiceException;
import cn.lili.common.properties.VerificationCodeProperties;
import cn.lili.common.utils.StringUtils;
import cn.lili.modules.verification.SliderImageUtil;
import cn.lili.modules.verification.enums.VerificationEnums;
import cn.lili.modules.verification.service.VerificationService;
import cn.lili.common.vo.SerializableStream;
import cn.lili.modules.system.entity.dos.VerificationSource;
import cn.lili.modules.system.entity.vo.VerificationDTO;
import cn.lili.modules.system.service.VerificationSourceService;
import cn.lili.modules.verification.SliderImageUtil;
import cn.lili.modules.verification.enums.VerificationEnums;
import cn.lili.modules.verification.service.VerificationService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
@ -36,6 +37,11 @@ public class VerificationServiceImpl implements VerificationService {
@Autowired
private VerificationSourceService verificationSourceService;
@Autowired
private VerificationCodeProperties verificationCodeProperties;
@Autowired
private Cache cache;
@ -68,22 +74,30 @@ public class VerificationServiceImpl implements VerificationService {
String originalResource = verificationResources.get(resourceNum).getResource();
//随机选择剪切模版图片地址
String sliderResource = verificationSlider.get(sliderNum).getResource();
// 干扰块
String interfereResource = verificationSlider.get(sliderNum == verificationSlider.size() - 1 ?
sliderNum - 1 : sliderNum + 1).getResource();
try {
//获取缓存中的资源
SerializableStream originalFile = getInputStream(originalResource);
SerializableStream sliderFile = getInputStream(sliderResource);
Map<String, Object> resultMap = SliderImageUtil.pictureTemplatesCut(sliderFile, originalFile);
//生成验证参数 120可以验证 无需手动清除120秒有效时间自动清除
cache.put(cacheKey(verificationEnums, uuid), resultMap.get("randomX"), 120L);
SerializableStream interfereSliderFile = verificationCodeProperties.getInterfereNum() > 0 ? getInputStream(interfereResource) : null;
//生成数据
Map<String, Object> resultMap = SliderImageUtil.pictureTemplatesCut(
sliderFile, interfereSliderFile, originalFile,
verificationCodeProperties.getWatermark(), verificationCodeProperties.getInterfereNum());
//生成验证参数 有效时间 默认600秒可以自行配置
cache.put(cacheKey(verificationEnums, uuid), resultMap.get("randomX"), verificationCodeProperties.getEffectiveTime());
resultMap.put("key", cacheKey(verificationEnums, uuid));
resultMap.put("effectiveTime", verificationCodeProperties.getEffectiveTime());
//移除横坐标移动距离
resultMap.remove("randomX");
return resultMap;
} catch (ServiceException e) {
throw e;
} catch (Exception e) {
log.error("创建校验错误",e);
log.error("创建校验错误", e);
return null;
}
}
@ -91,6 +105,7 @@ public class VerificationServiceImpl implements VerificationService {
/**
* 根据网络地址获取源文件
* 这里简单说一下这里是将不可序列化的inputstream序列化对象存入redis缓存
*
* @param originalResource
* @return
*/
@ -125,11 +140,10 @@ public class VerificationServiceImpl implements VerificationService {
}
log.debug("{}{}", randomX, xPos);
//验证结果
boolean result = Math.abs(randomX - xPos) < 3;
if (result) {
//验证成功则记录验证结果 验证有效时间120秒
cache.put(cacheResult(verificationEnums, uuid), true, 120L);
return result;
if (Math.abs(randomX - xPos) < verificationCodeProperties.getFaultTolerant()) {
//验证成功则记录验证结果 验证有效时间与验证码创建有效时间一致
cache.put(cacheResult(verificationEnums, uuid), true, verificationCodeProperties.getEffectiveTime());
return true;
}
return false;
}