优化验证码性能
This commit is contained in:
parent
8856aefd93
commit
42180e53bc
BIN
captcha_preview.png
Normal file
BIN
captcha_preview.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 2.4 KiB |
@ -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':
|
||||
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
@ -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);
|
||||
}
|
||||
|
||||
@ -159,7 +159,6 @@ public class OrderPayedListener {
|
||||
} catch (Exception e) {
|
||||
log.error("发送推送消息失败:{}", e.getMessage());
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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);
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user