优化验证码性能

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;
import javax.imageio.ImageIO;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.io.OutputStream;
import java.util.Map;
import java.util.Random;
import java.util.concurrent.ConcurrentHashMap;
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 {
// 生成示例验证码图片
@ -29,17 +43,37 @@ public class CaptchaUtil {
}
/**
* 生成简单的数字和字母验证码
* 生成简单的数字验证码
*
* @param length 验证码长度
* @return 验证码字符串
*/
public static String generateSimpleCaptcha(int length) {
String chars = "0123456789";
StringBuilder captcha = new StringBuilder();
java.util.Random random = new java.util.Random();
return generateSimpleCaptcha(length, DIGITS);
}
/**
* 生成数字和字母验证码
*
* @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++) {
captcha.append(chars.charAt(random.nextInt(chars.length())));
captcha.append(chars[random.nextInt(chars.length)]);
}
return captcha.toString();
}
@ -55,56 +89,91 @@ public class CaptchaUtil {
int height = 40;
BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
java.awt.Graphics2D g = image.createGraphics();
Graphics2D g = image.createGraphics();
// 设置背景色
g.setColor(java.awt.Color.WHITE);
g.fillRect(0, 0, width, height);
try {
// 启用渲染提示以提高图像质量
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(new java.awt.Color(200, 200, 200));
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);
// 设置背景色
g.setColor(Color.WHITE);
g.fillRect(0, 0, width, height);
Random random = THREAD_LOCAL_RANDOM.get();
// 绘制干扰线
g.setColor(new Color(200, 200, 200));
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);
g.fillRect(x, y, 1, 1);
/**
* 创建错误提示图片
*/
public static void createErrorImage(OutputStream os) {
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 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点阵绘制字符
int[][] pattern = getCharacterPattern(c);
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 j = 0; j < pattern[i].length; j++) {
if (pattern[i][j] == 1) {
// 添加轻微随机偏移使字符不那么规整
int offsetX = random.nextInt(3) - 1;
int offsetY = random.nextInt(3) - 1;
// 使用更清晰的绘制方式增大像素点
g.fillRect(x + j * 3, y + i * 3, 2, 2);
// 使用更少的随机计算
int offsetX = (random.nextInt(3) - 1 + baseOffsetX) / 2;
int offsetY = (random.nextInt(3) - 1 + baseOffsetY) / 2;
g.fillRect(x + j * 3 + offsetX, y + i * 3 + offsetY, 2, 2);
}
}
}
} else {
// 如果字符图案未定义绘制一个简单的替代图形
g.setColor(java.awt.Color.GRAY);
g.setColor(Color.GRAY);
g.fillOval(x + 5, y + 5, 6, 6);
}
}
/**
* 获取字符的点阵图案
* 获取字符的点阵图案带缓存
*
* @param c 字符
* @return 点阵图案二维数组
*/
private static int[][] getCharacterPattern(char c) {
return CHARACTER_PATTERN_CACHE.getOrDefault(c, createCharacterPattern(c));
}
/**
* 创建字符的点阵图案
*
* @param c 字符
* @return 点阵图案二维数组
*/
private static int[][] createCharacterPattern(char c) {
// 简化的5x7点阵字符图案
switch (c) {
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 io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
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 javax.servlet.http.HttpServletResponse;
import java.io.IOException;
/**
* <p>
@ -22,6 +24,7 @@ import javax.servlet.http.HttpServletResponse;
* @author Xinze
* @since 2021-06-29
*/
@Slf4j
@Api(tags = "系统参数设置表")
@RestController("admin-shop-base-config")
@RequestMapping("/admin/shop/shop-base-config")
@ -53,7 +56,18 @@ public class ShopBaseConfigController {
@ApiOperation(value = "获取验证码", notes = "获取验证码")
@RequestMapping(value = "/image", method = RequestMethod.GET)
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
public void image(HttpServletResponse response) {
// 设置响应头确保浏览器正确处理图片
response.setContentType("image/png");
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.setHeader("Content-Disposition", "inline; filename=captcha.png");
// 添加防止代理缓存的头
response.setHeader("Proxy-Connection", "close");
response.setHeader("Connection", "close");
OutputStream os = null;
try {
@ -138,7 +144,9 @@ public class ShopBaseConfigServiceImpl extends BaseServiceImpl<ShopBaseConfigMap
} catch (Exception e) {
logger.error("获取验证码响应异常: " + e.getMessage(), e);
try {
response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
// 确保即使出错也返回一个错误图片或空白图片
response.setContentType("image/png");
CaptchaUtil.createErrorImage(response.getOutputStream());
} catch (Exception ex) {
logger.error("设置错误响应状态异常: " + ex.getMessage(), ex);
}

View File

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

View File

@ -6023,9 +6023,8 @@ public class ShopOrderBaseServiceImpl extends BaseServiceImpl<ShopOrderBaseMappe
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());
if (items.size() == 1) {
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"));
if (is_delivery == null) {
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<>());
if (CollUtil.isNotEmpty(giftbags)) {
for (Map giftbag : giftbags) {
//套餐库存判断
}
}
// List<Map> giftbags = (List<Map>) ObjectUtil.defaultIfNull(activitys.get("giftbag"), new ArrayList<>());
// if (CollUtil.isNotEmpty(giftbags)) {
// for (Map giftbag : giftbags) {
// //套餐库存判断
// }
// }
// 直接判断库存
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_code", "");
// 店铺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;
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);
if (packingFee.compareTo(new BigDecimal("10")) > 0) {
packingFee = new BigDecimal("10");
}
}
// 打包费
base_row.put("packing_fee", packingFee);
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);
@ -6390,7 +6394,6 @@ public class ShopOrderBaseServiceImpl extends BaseServiceImpl<ShopOrderBaseMappe
base_row.put("buyer_user_id", buyer_user_id);
base_row.put("buyer_user_name", buyer_user_name);
ShopOrderBase baseRow = Convert.convert(ShopOrderBase.class, base_row);
// 订单基础信息保存
boolean flag = saveOrUpdate(baseRow);
@ -6482,7 +6485,6 @@ public class ShopOrderBaseServiceImpl extends BaseServiceImpl<ShopOrderBaseMappe
info_row.setChain_id(Convert.toInt(user.getChain_id()));
}
// 订单基本info信息保存
if (!shopOrderInfoService.saveOrUpdate(info_row)) {
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_shipping_fee_amount(_freight); // 运费价格/运费金额
data_row.setOrder_shipping_fee(_freight); // 实际运费金额-卖家可修改
// 店铺统一设置的打包费
data_row.setPacking_fee(packingFee);