验证码

This commit is contained in:
Jack 2025-08-15 08:52:52 +08:00
parent 2a764f1516
commit 14f4f50e18
3 changed files with 361 additions and 129 deletions

BIN
captcha_preview.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 527 B

View File

@ -0,0 +1,358 @@
/*
* Copyright (c) 2025. Lorem ipsum dolor sit amet, consectetur adipiscing elit.
* Morbi non lorem porttitor neque feugiat blandit. Ut vitae ipsum eget quam lacinia accumsan.
* Etiam sed turpis ac ipsum condimentum fringilla. Maecenas magna.
* Proin dapibus sapien vel ante. Aliquam erat volutpat. Pellentesque sagittis ligula eget metus.
* Vestibulum commodo. Ut rhoncus gravida arcu.
*/
package com.suisung.mall.common.utils;
import javax.imageio.ImageIO;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
public class CaptchaUtil {
public static void main(String[] args) throws IOException {
// 生成示例验证码图片
String captchaCode = generateSimpleCaptcha(4);
System.out.println("生成的验证码: " + captchaCode);
BufferedImage image = createSimpleCaptchaImage(captchaCode);
// 保存到文件以便预览
File output = new File("captcha_preview.png");
ImageIO.write(image, "png", output);
System.out.println("验证码图片已保存到: " + output.getAbsolutePath());
}
/**
* 生成简单的数字和字母验证码
*
* @param length 验证码长度
* @return 验证码字符串
*/
public static String generateSimpleCaptcha(int length) {
String chars = "0123456789";
StringBuilder captcha = new StringBuilder();
java.util.Random random = new java.util.Random();
for (int i = 0; i < length; i++) {
captcha.append(chars.charAt(random.nextInt(chars.length())));
}
return captcha.toString();
}
/**
* 创建简单的验证码图片不依赖系统字体
*
* @param captchaCode 验证码
* @return 验证码图片
*/
public static BufferedImage createSimpleCaptchaImage(String captchaCode) {
int width = 120;
int height = 40;
BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
java.awt.Graphics2D g = image.createGraphics();
// 设置背景色
g.setColor(java.awt.Color.WHITE);
g.fillRect(0, 0, width, height);
// 绘制干扰线
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(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);
}
// 绘制验证码字符
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;
}
/**
* 用点阵方式绘制字符避免使用字体
*
* @param g Graphics对象
* @param c 要绘制的字符
* @param x x坐标偏移
* @param y y坐标偏移
* @param random 随机数生成器
*/
private static void drawCharacter(java.awt.Graphics2D g, char c, int x, int y, java.util.Random random) {
// 使用5x7点阵绘制字符
int[][] pattern = getCharacterPattern(c);
if (pattern != null) {
// 设置画笔颜色
g.setColor(java.awt.Color.BLACK);
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);
}
}
}
} else {
// 如果字符图案未定义绘制一个简单的替代图形
g.setColor(java.awt.Color.GRAY);
g.fillOval(x + 5, y + 5, 6, 6);
}
}
/**
* 获取字符的点阵图案
*
* @param c 字符
* @return 点阵图案二维数组
*/
private static int[][] getCharacterPattern(char c) {
// 简化的5x7点阵字符图案
switch (c) {
case '0':
return new int[][]{
{0, 1, 1, 1, 0},
{1, 0, 0, 0, 1},
{1, 0, 0, 0, 1},
{1, 0, 0, 0, 1},
{1, 0, 0, 0, 1},
{1, 0, 0, 0, 1},
{0, 1, 1, 1, 0}
};
case '1':
return new int[][]{
{0, 0, 1, 0, 0},
{0, 1, 1, 0, 0},
{1, 0, 1, 0, 0},
{0, 0, 1, 0, 0},
{0, 0, 1, 0, 0},
{0, 0, 1, 0, 0},
{1, 1, 1, 1, 1}
};
case '2':
return new int[][]{
{0, 1, 1, 1, 0},
{1, 0, 0, 0, 1},
{0, 0, 0, 0, 1},
{0, 0, 0, 1, 0},
{0, 0, 1, 0, 0},
{0, 1, 0, 0, 0},
{1, 1, 1, 1, 1}
};
case '3':
return new int[][]{
{0, 1, 1, 1, 0},
{1, 0, 0, 0, 1},
{0, 0, 0, 0, 1},
{0, 0, 1, 1, 0},
{0, 0, 0, 0, 1},
{1, 0, 0, 0, 1},
{0, 1, 1, 1, 0}
};
case '4':
return new int[][]{
{0, 0, 0, 1, 0},
{0, 0, 1, 1, 0},
{0, 1, 0, 1, 0},
{1, 0, 0, 1, 0},
{1, 1, 1, 1, 1},
{0, 0, 0, 1, 0},
{0, 0, 0, 1, 0}
};
case '5':
return new int[][]{
{1, 1, 1, 1, 1},
{1, 0, 0, 0, 0},
{1, 0, 0, 0, 0},
{1, 1, 1, 1, 0},
{0, 0, 0, 0, 1},
{0, 0, 0, 0, 1},
{1, 1, 1, 1, 0}
};
case '6':
return new int[][]{
{0, 1, 1, 1, 0},
{1, 0, 0, 0, 0},
{1, 0, 0, 0, 0},
{1, 1, 1, 1, 0},
{1, 0, 0, 0, 1},
{1, 0, 0, 0, 1},
{0, 1, 1, 1, 0}
};
case '7':
return new int[][]{
{1, 1, 1, 1, 1},
{0, 0, 0, 0, 1},
{0, 0, 0, 1, 0},
{0, 0, 1, 0, 0},
{0, 1, 0, 0, 0},
{0, 1, 0, 0, 0},
{0, 1, 0, 0, 0}
};
case '8':
return new int[][]{
{0, 1, 1, 1, 0},
{1, 0, 0, 0, 1},
{1, 0, 0, 0, 1},
{0, 1, 1, 1, 0},
{1, 0, 0, 0, 1},
{1, 0, 0, 0, 1},
{0, 1, 1, 1, 0}
};
case '9':
return new int[][]{
{0, 1, 1, 1, 0},
{1, 0, 0, 0, 1},
{1, 0, 0, 0, 1},
{0, 1, 1, 1, 1},
{0, 0, 0, 0, 1},
{0, 0, 0, 0, 1},
{0, 1, 1, 1, 0}
};
case 'A':
case 'a':
return new int[][]{
{0, 0, 1, 0, 0},
{0, 1, 0, 1, 0},
{1, 0, 0, 0, 1},
{1, 0, 0, 0, 1},
{1, 1, 1, 1, 1},
{1, 0, 0, 0, 1},
{1, 0, 0, 0, 1}
};
case 'B':
case 'b':
return new int[][]{
{1, 1, 1, 1, 0},
{1, 0, 0, 0, 1},
{1, 0, 0, 0, 1},
{1, 1, 1, 1, 0},
{1, 0, 0, 0, 1},
{1, 0, 0, 0, 1},
{1, 1, 1, 1, 0}
};
case 'C':
case 'c':
return new int[][]{
{0, 1, 1, 1, 0},
{1, 0, 0, 0, 1},
{1, 0, 0, 0, 0},
{1, 0, 0, 0, 0},
{1, 0, 0, 0, 0},
{1, 0, 0, 0, 1},
{0, 1, 1, 1, 0}
};
case 'D':
case 'd':
return new int[][]{
{1, 1, 1, 1, 0},
{1, 0, 0, 0, 1},
{1, 0, 0, 0, 1},
{1, 0, 0, 0, 1},
{1, 0, 0, 0, 1},
{1, 0, 0, 0, 1},
{1, 1, 1, 1, 0}
};
case 'E':
case 'e':
return new int[][]{
{1, 1, 1, 1, 1},
{1, 0, 0, 0, 0},
{1, 0, 0, 0, 0},
{1, 1, 1, 1, 0},
{1, 0, 0, 0, 0},
{1, 0, 0, 0, 0},
{1, 1, 1, 1, 1}
};
case 'F':
case 'f':
return new int[][]{
{1, 1, 1, 1, 1},
{1, 0, 0, 0, 0},
{1, 0, 0, 0, 0},
{1, 1, 1, 1, 0},
{1, 0, 0, 0, 0},
{1, 0, 0, 0, 0},
{1, 0, 0, 0, 0}
};
case 'G':
case 'g':
return new int[][]{
{0, 1, 1, 1, 0},
{1, 0, 0, 0, 1},
{1, 0, 0, 0, 0},
{1, 0, 1, 1, 1},
{1, 0, 0, 0, 1},
{1, 0, 0, 0, 1},
{0, 1, 1, 1, 0}
};
case 'H':
case 'h':
return new int[][]{
{1, 0, 0, 0, 1},
{1, 0, 0, 0, 1},
{1, 0, 0, 0, 1},
{1, 1, 1, 1, 1},
{1, 0, 0, 0, 1},
{1, 0, 0, 0, 1},
{1, 0, 0, 0, 1}
};
case 'I':
case 'i':
return new int[][]{
{0, 1, 1, 1, 0},
{0, 0, 1, 0, 0},
{0, 0, 1, 0, 0},
{0, 0, 1, 0, 0},
{0, 0, 1, 0, 0},
{0, 0, 1, 0, 0},
{0, 1, 1, 1, 0}
};
default:
return null;
}
}
}

View File

@ -6,6 +6,7 @@ import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import com.suisung.mall.common.constant.RedisConstant;
import com.suisung.mall.common.modules.base.ShopBaseConfig;
import com.suisung.mall.common.utils.CaptchaUtil;
import com.suisung.mall.core.web.service.RedisService;
import com.suisung.mall.core.web.service.impl.BaseServiceImpl;
import com.suisung.mall.shop.base.mapper.ShopBaseConfigMapper;
@ -116,10 +117,10 @@ public class ShopBaseConfigServiceImpl extends BaseServiceImpl<ShopBaseConfigMap
}
// 创建一个不依赖字体的简单数字验证码
String captchaCode = generateSimpleCaptcha(4);
String captchaCode = CaptchaUtil.generateSimpleCaptcha(4);
// 手动创建简单的验证码图片
BufferedImage image = createSimpleCaptchaImage(captchaCode);
BufferedImage image = CaptchaUtil.createSimpleCaptchaImage(captchaCode);
// 保存验证码到Redis
String code = RedisConstant.Verifycode_NameSpace + verify_token + captchaCode;
@ -152,133 +153,6 @@ public class ShopBaseConfigServiceImpl extends BaseServiceImpl<ShopBaseConfigMap
}
}
/**
* 生成简单的数字验证码
*
* @param length 验证码长度
* @return 验证码字符串
*/
private String generateSimpleCaptcha(int length) {
String chars = "0123456789";
StringBuilder captcha = new StringBuilder();
java.util.Random random = new java.util.Random();
for (int i = 0; i < length; i++) {
captcha.append(chars.charAt(random.nextInt(chars.length())));
}
return captcha.toString();
}
/**
* 创建简单的验证码图片不依赖系统字体
*
* @param captchaCode 验证码
* @return 验证码图片
*/
private BufferedImage createSimpleCaptchaImage(String captchaCode) {
int width = 120;
int height = 40;
BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
java.awt.Graphics2D g = image.createGraphics();
// 设置背景色
g.setColor(java.awt.Color.WHITE);
g.fillRect(0, 0, width, height);
// 绘制干扰线
java.util.Random random = new java.util.Random();
g.setColor(java.awt.Color.LIGHT_GRAY);
for (int i = 0; i < 10; 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(java.awt.Color.BLACK);
int fontSize = 20;
for (int i = 0; i < captchaCode.length(); i++) {
char c = captchaCode.charAt(i);
// 使用drawString可能会触发字体问题所以我们用图形代替
int x = 20 + i * 20;
int y = 25;
// 绘制数字的简单表示这里仍然可能有问题所以我们用点阵方式
drawSimpleNumber(g, c, x, y);
}
g.dispose();
return image;
}
/**
* 用简单图形绘制数字避免使用字体
*
* @param g Graphics对象
* @param c 要绘制的字符
* @param x x坐标
* @param y y坐标
*/
private void drawSimpleNumber(java.awt.Graphics2D g, char c, int x, int y) {
// 简单的数字绘制避免使用字体
switch (c) {
case '0':
g.drawOval(x, y - 15, 15, 15);
break;
case '1':
g.drawLine(x + 7, y - 15, x + 7, y);
break;
case '2':
g.drawLine(x, y - 15, x + 15, y - 15); // 上横线
g.drawLine(x + 15, y - 15, x + 15, y - 7); // 右上竖线
g.drawLine(x, y - 7, x + 15, y - 7); // 中横线
g.drawLine(x, y - 7, x, y); // 左下竖线
g.drawLine(x, y, x + 15, y); // 下横线
break;
case '3':
g.drawLine(x, y - 15, x + 15, y - 15); // 上横线
g.drawLine(x + 15, y - 15, x + 15, y); // 右竖线
g.drawLine(x, y - 7, x + 15, y - 7); // 中横线
g.drawLine(x, y, x + 15, y); // 下横线
break;
case '4':
g.drawLine(x, y - 15, x, y - 7); // 左上竖线
g.drawLine(x + 15, y - 15, x + 15, y); // 右竖线
g.drawLine(x, y - 7, x + 15, y - 7); // 中横线
break;
case '5':
g.drawLine(x, y - 15, x + 15, y - 15); // 上横线
g.drawLine(x, y - 15, x, y - 7); // 左上竖线
g.drawLine(x, y - 7, x + 15, y - 7); // 中横线
g.drawLine(x + 15, y - 7, x + 15, y); // 右下竖线
g.drawLine(x, y, x + 15, y); // 下横线
break;
case '6':
g.drawOval(x, y - 15, 15, 15);
g.drawLine(x, y - 7, x, y); // 左下竖线
g.drawLine(x, y, x + 15, y); // 下横线
break;
case '7':
g.drawLine(x, y - 15, x + 15, y - 15); // 上横线
g.drawLine(x + 15, y - 15, x + 15, y); // 右竖线
break;
case '8':
g.drawOval(x, y - 15, 15, 15);
g.drawOval(x, y - 7, 15, 15);
break;
case '9':
g.drawOval(x, y - 15, 15, 15);
g.drawLine(x + 15, y - 7, x + 15, y); // 右下竖线
g.drawLine(x, y, x + 15, y); // 下横线
break;
default:
// 绘制一个简单的点作为默认
g.fillOval(x + 7, y - 7, 3, 3);
}
}
// /**
// * 获取验证码