优化验证码性能

This commit is contained in:
Jack 2025-08-15 15:46:55 +08:00
parent 8856aefd93
commit 42180e53bc
6 changed files with 180 additions and 74 deletions

BIN
captcha_preview.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

View File

@ -9,11 +9,25 @@
package com.suisung.mall.common.utils; package com.suisung.mall.common.utils;
import javax.imageio.ImageIO; import javax.imageio.ImageIO;
import java.awt.*;
import java.awt.image.BufferedImage; import java.awt.image.BufferedImage;
import java.io.File; import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.io.OutputStream;
import java.util.Map;
import java.util.Random;
import java.util.concurrent.ConcurrentHashMap;
public class CaptchaUtil { public class CaptchaUtil {
// 字符点阵图案缓存
private static final Map<Character, int[][]> CHARACTER_PATTERN_CACHE = new ConcurrentHashMap<>();
// 预定义字符集
private static final char[] DIGITS = "0123456789".toCharArray();
private static final char[] ALPHANUMERIC = "23456789ABCDEFGHJKLMNPQRSTUVWXYZ".toCharArray();
// ThreadLocal Random 避免线程竞争
private static final ThreadLocal<Random> THREAD_LOCAL_RANDOM = ThreadLocal.withInitial(Random::new);
public static void main(String[] args) throws IOException { public static void main(String[] args) throws IOException {
// 生成示例验证码图片 // 生成示例验证码图片
@ -29,17 +43,37 @@ public class CaptchaUtil {
} }
/** /**
* 生成简单的数字和字母验证码 * 生成简单的数字验证码
* *
* @param length 验证码长度 * @param length 验证码长度
* @return 验证码字符串 * @return 验证码字符串
*/ */
public static String generateSimpleCaptcha(int length) { public static String generateSimpleCaptcha(int length) {
String chars = "0123456789"; return generateSimpleCaptcha(length, DIGITS);
StringBuilder captcha = new StringBuilder(); }
java.util.Random random = new java.util.Random();
/**
* 生成数字和字母验证码
*
* @param length 验证码长度
* @return 验证码字符串
*/
public static String generateAlphanumericCaptcha(int length) {
return generateSimpleCaptcha(length, ALPHANUMERIC);
}
/**
* 生成验证码
*
* @param length 验证码长度
* @param chars 字符集
* @return 验证码字符串
*/
private static String generateSimpleCaptcha(int length, char[] chars) {
StringBuilder captcha = new StringBuilder(length);
Random random = THREAD_LOCAL_RANDOM.get();
for (int i = 0; i < length; i++) { for (int i = 0; i < length; i++) {
captcha.append(chars.charAt(random.nextInt(chars.length()))); captcha.append(chars[random.nextInt(chars.length)]);
} }
return captcha.toString(); return captcha.toString();
} }
@ -55,56 +89,91 @@ public class CaptchaUtil {
int height = 40; int height = 40;
BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB); BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
java.awt.Graphics2D g = image.createGraphics(); Graphics2D g = image.createGraphics();
// 设置背景色 try {
g.setColor(java.awt.Color.WHITE); // 启用渲染提示以提高图像质量
g.fillRect(0, 0, width, height); g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
g.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
// 绘制干扰线 // 设置背景色
java.util.Random random = new java.util.Random(); g.setColor(Color.WHITE);
g.setColor(new java.awt.Color(200, 200, 200)); g.fillRect(0, 0, width, height);
for (int i = 0; i < 8; i++) {
int x1 = random.nextInt(width); Random random = THREAD_LOCAL_RANDOM.get();
int y1 = random.nextInt(height);
int x2 = random.nextInt(width); // 绘制干扰线
int y2 = random.nextInt(height); g.setColor(new Color(200, 200, 200));
g.drawLine(x1, y1, x2, y2); for (int i = 0; i < 8; i++) {
int x1 = random.nextInt(width);
int y1 = random.nextInt(height);
int x2 = random.nextInt(width);
int y2 = random.nextInt(height);
g.drawLine(x1, y1, x2, y2);
}
// 绘制干扰点
for (int i = 0; i < 30; i++) {
int x = random.nextInt(width);
int y = random.nextInt(height);
g.fillRect(x, y, 1, 1);
}
// 绘制验证码字符
int charWidth = width / (captchaCode.length() + 1);
for (int i = 0; i < captchaCode.length(); i++) {
char c = captchaCode.charAt(i);
int x = (i + 1) * charWidth - 8;
int y = height / 2 - 10;
// 添加随机旋转和位置偏移
java.awt.geom.AffineTransform original = g.getTransform();
java.awt.geom.AffineTransform transform = new java.awt.geom.AffineTransform();
transform.translate(x, y);
// 减小旋转角度避免字符变形过大
transform.rotate((random.nextDouble() - 0.5) * 0.3);
g.setTransform(transform);
// 使用点阵方式绘制字符
drawCharacter(g, c, 0, 0, random);
// 恢复原始变换
g.setTransform(original);
}
return image;
} finally {
g.dispose();
} }
}
// 绘制干扰点
g.setColor(new java.awt.Color(200, 200, 200)); /**
for (int i = 0; i < 30; i++) { * 创建错误提示图片
int x = random.nextInt(width); */
int y = random.nextInt(height); public static void createErrorImage(OutputStream os) {
g.fillRect(x, y, 1, 1); try {
int width = 120;
int height = 40;
BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
Graphics2D g = image.createGraphics();
try {
g.setColor(Color.WHITE);
g.fillRect(0, 0, width, height);
g.setColor(Color.RED);
g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
g.drawString("Error", 10, 25);
ImageIO.write(image, "png", os);
} finally {
g.dispose();
}
} catch (Exception e) {
System.out.println("创建错误图片失败: " + e.getMessage());
} }
// 绘制验证码字符
int charWidth = width / (captchaCode.length() + 1);
for (int i = 0; i < captchaCode.length(); i++) {
char c = captchaCode.charAt(i);
int x = (i + 1) * charWidth - 8;
int y = height / 2 - 10;
// 添加随机旋转和位置偏移
java.awt.geom.AffineTransform original = g.getTransform();
java.awt.geom.AffineTransform transform = new java.awt.geom.AffineTransform();
transform.translate(x, y);
// 减小旋转角度避免字符变形过大
transform.rotate((random.nextDouble() - 0.5) * 0.3);
g.setTransform(transform);
// 使用点阵方式绘制字符
drawCharacter(g, c, 0, 0, random);
// 恢复原始变换
g.setTransform(original);
}
g.dispose();
return image;
} }
/** /**
@ -116,39 +185,52 @@ public class CaptchaUtil {
* @param y y坐标偏移 * @param y y坐标偏移
* @param random 随机数生成器 * @param random 随机数生成器
*/ */
private static void drawCharacter(java.awt.Graphics2D g, char c, int x, int y, java.util.Random random) { private static void drawCharacter(Graphics2D g, char c, int x, int y, Random random) {
// 使用5x7点阵绘制字符 // 使用5x7点阵绘制字符
int[][] pattern = getCharacterPattern(c); int[][] pattern = getCharacterPattern(c);
if (pattern != null) { if (pattern != null) {
// 设置画笔颜色 // 设置画笔颜色
g.setColor(java.awt.Color.BLACK); g.setColor(Color.BLACK);
// 预计算偏移量减少重复计算
int baseOffsetX = random.nextInt(3) - 1;
int baseOffsetY = random.nextInt(3) - 1;
for (int i = 0; i < pattern.length; i++) { for (int i = 0; i < pattern.length; i++) {
for (int j = 0; j < pattern[i].length; j++) { for (int j = 0; j < pattern[i].length; j++) {
if (pattern[i][j] == 1) { if (pattern[i][j] == 1) {
// 添加轻微随机偏移使字符不那么规整 // 使用更少的随机计算
int offsetX = random.nextInt(3) - 1; int offsetX = (random.nextInt(3) - 1 + baseOffsetX) / 2;
int offsetY = random.nextInt(3) - 1; int offsetY = (random.nextInt(3) - 1 + baseOffsetY) / 2;
// 使用更清晰的绘制方式增大像素点 g.fillRect(x + j * 3 + offsetX, y + i * 3 + offsetY, 2, 2);
g.fillRect(x + j * 3, y + i * 3, 2, 2);
} }
} }
} }
} else { } else {
// 如果字符图案未定义绘制一个简单的替代图形 // 如果字符图案未定义绘制一个简单的替代图形
g.setColor(java.awt.Color.GRAY); g.setColor(Color.GRAY);
g.fillOval(x + 5, y + 5, 6, 6); g.fillOval(x + 5, y + 5, 6, 6);
} }
} }
/** /**
* 获取字符的点阵图案 * 获取字符的点阵图案带缓存
* *
* @param c 字符 * @param c 字符
* @return 点阵图案二维数组 * @return 点阵图案二维数组
*/ */
private static int[][] getCharacterPattern(char c) { private static int[][] getCharacterPattern(char c) {
return CHARACTER_PATTERN_CACHE.getOrDefault(c, createCharacterPattern(c));
}
/**
* 创建字符的点阵图案
*
* @param c 字符
* @return 点阵图案二维数组
*/
private static int[][] createCharacterPattern(char c) {
// 简化的5x7点阵字符图案 // 简化的5x7点阵字符图案
switch (c) { switch (c) {
case '0': case '0':

View File

@ -6,6 +6,7 @@ import com.suisung.mall.common.modules.base.ShopBaseConfig;
import com.suisung.mall.shop.base.service.ShopBaseConfigService; import com.suisung.mall.shop.base.service.ShopBaseConfigService;
import io.swagger.annotations.Api; import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation; import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.RequestMethod;
@ -13,6 +14,7 @@ import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController; import org.springframework.web.bind.annotation.RestController;
import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
/** /**
* <p> * <p>
@ -22,6 +24,7 @@ import javax.servlet.http.HttpServletResponse;
* @author Xinze * @author Xinze
* @since 2021-06-29 * @since 2021-06-29
*/ */
@Slf4j
@Api(tags = "系统参数设置表") @Api(tags = "系统参数设置表")
@RestController("admin-shop-base-config") @RestController("admin-shop-base-config")
@RequestMapping("/admin/shop/shop-base-config") @RequestMapping("/admin/shop/shop-base-config")
@ -53,7 +56,18 @@ public class ShopBaseConfigController {
@ApiOperation(value = "获取验证码", notes = "获取验证码") @ApiOperation(value = "获取验证码", notes = "获取验证码")
@RequestMapping(value = "/image", method = RequestMethod.GET) @RequestMapping(value = "/image", method = RequestMethod.GET)
public void image(HttpServletResponse response) { public void image(HttpServletResponse response) {
shopBaseConfigService.image(response); try {
shopBaseConfigService.image(response);
} catch (Exception e) {
log.error("验证码生成异常: ", e);
try {
response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
response.setContentType("text/plain;charset=utf-8");
response.getWriter().write("验证码生成失败");
} catch (IOException ioException) {
log.error("验证码响应写入异常: ", ioException);
}
}
} }

View File

@ -101,10 +101,16 @@ public class ShopBaseConfigServiceImpl extends BaseServiceImpl<ShopBaseConfigMap
*/ */
@Override @Override
public void image(HttpServletResponse response) { public void image(HttpServletResponse response) {
// 设置响应头确保浏览器正确处理图片
response.setContentType("image/png"); response.setContentType("image/png");
response.setHeader("Pragma", "no-cache"); response.setHeader("Pragma", "no-cache");
response.setHeader("Cache-Control", "no-cache"); response.setHeader("Cache-Control", "no-cache, no-store, must-revalidate");
response.setDateHeader("Expires", 0); response.setDateHeader("Expires", 0);
response.setHeader("Content-Disposition", "inline; filename=captcha.png");
// 添加防止代理缓存的头
response.setHeader("Proxy-Connection", "close");
response.setHeader("Connection", "close");
OutputStream os = null; OutputStream os = null;
try { try {
@ -138,7 +144,9 @@ public class ShopBaseConfigServiceImpl extends BaseServiceImpl<ShopBaseConfigMap
} catch (Exception e) { } catch (Exception e) {
logger.error("获取验证码响应异常: " + e.getMessage(), e); logger.error("获取验证码响应异常: " + e.getMessage(), e);
try { try {
response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR); // 确保即使出错也返回一个错误图片或空白图片
response.setContentType("image/png");
CaptchaUtil.createErrorImage(response.getOutputStream());
} catch (Exception ex) { } catch (Exception ex) {
logger.error("设置错误响应状态异常: " + ex.getMessage(), ex); logger.error("设置错误响应状态异常: " + ex.getMessage(), ex);
} }

View File

@ -159,7 +159,6 @@ public class OrderPayedListener {
} catch (Exception e) { } catch (Exception e) {
log.error("发送推送消息失败:{}", e.getMessage()); log.error("发送推送消息失败:{}", e.getMessage());
} }
} }
} }
} }

View File

@ -6023,9 +6023,8 @@ public class ShopOrderBaseServiceImpl extends BaseServiceImpl<ShopOrderBaseMappe
shopUserCartService.calUnUsedVoucher(cart_data, buyer_user_id); shopUserCartService.calUnUsedVoucher(cart_data, buyer_user_id);
} }
boolean is_virtual = false;
// 判断是否为虚拟订单 // 判断是否为虚拟订单
boolean is_virtual = false;
List<Map> items = (List<Map>) ObjectUtil.defaultIfNull(cart_data.get("items"), new ArrayList()); List<Map> items = (List<Map>) ObjectUtil.defaultIfNull(cart_data.get("items"), new ArrayList());
if (items.size() == 1) { if (items.size() == 1) {
Map items_item = ObjectUtil.defaultIfNull(items.get(0), new HashMap()); Map items_item = ObjectUtil.defaultIfNull(items.get(0), new HashMap());
@ -6043,6 +6042,7 @@ public class ShopOrderBaseServiceImpl extends BaseServiceImpl<ShopOrderBaseMappe
} }
} }
// 是否需要配送
Boolean is_delivery = Convert.toBool(cart_data.get("is_delivery")); Boolean is_delivery = Convert.toBool(cart_data.get("is_delivery"));
if (is_delivery == null) { if (is_delivery == null) {
is_delivery = true; is_delivery = true;
@ -6129,12 +6129,12 @@ public class ShopOrderBaseServiceImpl extends BaseServiceImpl<ShopOrderBaseMappe
} }
} }
List<Map> giftbags = (List<Map>) ObjectUtil.defaultIfNull(activitys.get("giftbag"), new ArrayList<>()); // List<Map> giftbags = (List<Map>) ObjectUtil.defaultIfNull(activitys.get("giftbag"), new ArrayList<>());
if (CollUtil.isNotEmpty(giftbags)) { // if (CollUtil.isNotEmpty(giftbags)) {
for (Map giftbag : giftbags) { // for (Map giftbag : giftbags) {
//套餐库存判断 // //套餐库存判断
} // }
} // }
// 直接判断库存 // 直接判断库存
Integer item_quantity = Convert.toInt(item_tmp_row.get("item_quantity")); Integer item_quantity = Convert.toInt(item_tmp_row.get("item_quantity"));
@ -6248,6 +6248,7 @@ public class ShopOrderBaseServiceImpl extends BaseServiceImpl<ShopOrderBaseMappe
order_voucher_row.put("voucher_price", BigDecimal.ZERO); order_voucher_row.put("voucher_price", BigDecimal.ZERO);
order_voucher_row.put("voucher_code", ""); order_voucher_row.put("voucher_code", "");
// 店铺Id
Integer _store_id = Convert.toInt(store_item.get("store_id")); Integer _store_id = Convert.toInt(store_item.get("store_id"));
// 优惠券使用标记更新 // 优惠券使用标记更新
@ -6338,13 +6339,16 @@ public class ShopOrderBaseServiceImpl extends BaseServiceImpl<ShopOrderBaseMappe
// 店铺统一设置的打包费 // 店铺统一设置的打包费
BigDecimal packingFee = BigDecimal.ZERO; BigDecimal packingFee = BigDecimal.ZERO;
if (!isVirtualGoods && !is_virtual && delivery_type_id.equals(StateCode.DELIVERY_TYPE_SAME_CITY)) { if (is_delivery && !is_virtual && delivery_type_id.equals(StateCode.DELIVERY_TYPE_SAME_CITY)) {
packingFee = shopStoreBaseService.getStorePackingFee(_store_id); packingFee = shopStoreBaseService.getStorePackingFee(_store_id);
if (packingFee.compareTo(new BigDecimal("10")) > 0) { if (packingFee.compareTo(new BigDecimal("10")) > 0) {
packingFee = new BigDecimal("10"); packingFee = new BigDecimal("10");
} }
} }
// 打包费
base_row.put("packing_fee", packingFee);
BigDecimal voucher_price = Convert.toBigDecimal(order_voucher_row.get("voucher_price")); BigDecimal voucher_price = Convert.toBigDecimal(order_voucher_row.get("voucher_price"));
// 重要应付款金额计算 // 重要应付款金额计算
BigDecimal order_payment_amount = NumberUtil.sub(NumberUtil.add(order_money_select_items, freight, packingFee), voucher_price); BigDecimal order_payment_amount = NumberUtil.sub(NumberUtil.add(order_money_select_items, freight, packingFee), voucher_price);
@ -6390,7 +6394,6 @@ public class ShopOrderBaseServiceImpl extends BaseServiceImpl<ShopOrderBaseMappe
base_row.put("buyer_user_id", buyer_user_id); base_row.put("buyer_user_id", buyer_user_id);
base_row.put("buyer_user_name", buyer_user_name); base_row.put("buyer_user_name", buyer_user_name);
ShopOrderBase baseRow = Convert.convert(ShopOrderBase.class, base_row); ShopOrderBase baseRow = Convert.convert(ShopOrderBase.class, base_row);
// 订单基础信息保存 // 订单基础信息保存
boolean flag = saveOrUpdate(baseRow); boolean flag = saveOrUpdate(baseRow);
@ -6482,7 +6485,6 @@ public class ShopOrderBaseServiceImpl extends BaseServiceImpl<ShopOrderBaseMappe
info_row.setChain_id(Convert.toInt(user.getChain_id())); info_row.setChain_id(Convert.toInt(user.getChain_id()));
} }
// 订单基本info信息保存 // 订单基本info信息保存
if (!shopOrderInfoService.saveOrUpdate(info_row)) { if (!shopOrderInfoService.saveOrUpdate(info_row)) {
throw new ApiException(I18nUtil._("保存订单基础数据失败!")); throw new ApiException(I18nUtil._("保存订单基础数据失败!"));
@ -7535,6 +7537,7 @@ public class ShopOrderBaseServiceImpl extends BaseServiceImpl<ShopOrderBaseMappe
data_row.setOrder_discount_amount(order_discount_amount); // 折扣价格/优惠总金额 data_row.setOrder_discount_amount(order_discount_amount); // 折扣价格/优惠总金额
data_row.setOrder_shipping_fee_amount(_freight); // 运费价格/运费金额 data_row.setOrder_shipping_fee_amount(_freight); // 运费价格/运费金额
data_row.setOrder_shipping_fee(_freight); // 实际运费金额-卖家可修改 data_row.setOrder_shipping_fee(_freight); // 实际运费金额-卖家可修改
// 店铺统一设置的打包费 // 店铺统一设置的打包费
data_row.setPacking_fee(packingFee); data_row.setPacking_fee(packingFee);