Java实现验证码,算式验证码,附源码,开箱即用

在登陆或注册的时候,为了防止程序恶意请求,网站一般都会要求输入验证码。之前也写了一篇关于验证码功能的文章 java springboot 使用Kaptcha组件实现验证码功能,但是感觉这个组件只有字符串验证码,功能不太强。于是准备自己写一个验证码,主要是实现算式验证码。所有功能已实现并上传只github,链接:https://github.com/wsJava/verification-code

首先是获取算式

因为使用 除 会产生小数问题,所以只有+-* 运算符。下面这个方法是生成一个指定运算符数量的算式。然后构建一个Equation 对象,其中包含算式和结果。

/**
* 获取算式
 *
 * @param operatorCount 运算符数量
 * @return Equation 算式验证码对象
 */
public static Equation getEquation(int operatorCount) {
    int[] nums = new int[operatorCount + 1];
    char[] operates = new char[operatorCount];
    char[] expression = new char[operatorCount * 2 + 1];

    for (int i = 0; i < operatorCount + 1; i++) {
        nums[i] = random.nextInt(10);
        expression[i * 2] = (char) (nums[i] + 48);
    }
    for (int i = 0; i < operatorCount; i++) {
        operates[i] = OPERATOR_CHARS.charAt(random.nextInt(OPERATOR_CHARS.length()));
        expression[i * 2 + 1] = operates[i];
    }
    return new Equation(expression, getResult(nums, operates));
}

计算算式结果

主要使用堆栈的结构进行计算,将数字和运算符分别放入对应堆栈中,然后,对比前后两个运算符的优先级,如前相同或前一个优先级高,则将对应的数字和运算符取出来进行计算。再将结果存入数字栈中。代码如下:


/**
 * 算术运算符优先级
 */
private static final Map<Character, Integer> operatorMap = new HashMap<>(5);

static {
    operatorMap.put('+', 1);
    operatorMap.put('-', 1);
    operatorMap.put('x', 2);
}

/**
 * 计算算式结果
 *
 * @param nums      数字数组
 * @param operators 运算符数组
 * @return 计算结果
 */
private static int getResult(int[] nums, char[] operators) {
    LinkedList<Integer> numStack = new LinkedList<>();
    LinkedList<Character> operatorStack = new LinkedList<>();
    numStack.push(nums[0]);
    numStack.push(nums[1]);
    operatorStack.push(operators[0]);
    for (int i = 1; i < operators.length; i++) {
        if (operatorMap.get(operators[i]) <= operatorMap.get(operatorStack.peek())) {
            int num2 = numStack.pop();
            int num1 = numStack.pop();
            numStack.push(computerResult(num1, num2, operatorStack.pop()));
        }
        numStack.push(nums[i + 1]);
        operatorStack.push(operators[i]);
    }
    while (!operatorStack.isEmpty()) {
        int num2 = numStack.pop();
        int num1 = numStack.pop();
        numStack.push(computerResult(num1, num2, operatorStack.pop()));
    }
    return numStack.pop();
}

private static int computerResult(Integer num1, Integer num2, Character operator) {
	int result = 0;
    switch (operator) {
        case '+':
            result = num1 + num2;
            break;
        case '-':
            result = num1 - num2;
            break;
        case '*':
            result = num1 * num2;
            break;
        default:
    }
    return result;
}

绘制验证码

主要是绘制干扰线条和验证码,这里算式验证码没有使用干扰线条。注意获取随机数使用的是 线程安全的ThreadLocalRandom。这里代码不全,详细请参考 github 中的。

/**
 * 获取验证码对象
 *
 * @return VerificationImage 验证码对象
 */
public VerificationImage getVerificationImage() {
	BufferedImage bufferedImage = new BufferedImage(width, height, BufferedImage.TYPE_INT_BGR);
    char[] codes = null;
    String rightCode = null;
    Graphics graphics = bufferedImage.getGraphics();
    graphics.fillRect(0, 0, width, height);
    graphics.setFont(new Font("Times New Roman", Font.ROMAN_BASELINE, fontBasisSize));
    if (codeType == CodeTypeEnum.CHAR) {      //如果是字符类型验证码,则画干扰线
        graphics.setFont(new Font("Times New Roman", Font.ROMAN_BASELINE, fontBasisSize));
        for (int i = 0; i < lineCount; i++) {
            graphics.setColor(getRandColor());
            drawLine(graphics);
        }
        codes = getCharCode(codeSize);
        rightCode = String.valueOf(codes);
    } else if (codeType == CodeTypeEnum.EQUATION) {  //如果是算式类型验证码
        Equation equation = getEquation(operatorCount);
        codes = equation.getExpression();
        rightCode = String.valueOf(equation.getResult());
    }
    drawString(graphics, codes);
    graphics.dispose();
    return new VerificationImage(bufferedImage, rightCode);
}

/**
* 获取干扰线随机颜色
 *
 * @return 在 COLOR_BOUND 边界范围内随机生成的颜色
 */
private Color getRandColor() {
    int r = random.nextInt(COLOR_BOUND);
    int g = random.nextInt(COLOR_BOUND);
    int b = random.nextInt(COLOR_BOUND);
    return new Color(r, g, b);
}

/**
 * 绘制干扰线
 *
 * @param graphics 图形
 */
private void drawLine(Graphics graphics) {
    int x = random.nextInt(width);
    int y = random.nextInt(height);
    int x1 = random.nextInt(x + random.nextInt(width));
    int y1 = random.nextInt(y + random.nextInt(height));
    graphics.drawLine(x, y, x1, y1);
}

在response中设置验证码图片

这个地方可以改进,因为存储验证码结构的方式可以采用其他的。默认存到session中,但提供一个重载方法,提供Consumer接口,接收验证码结果自行存储,后续改进。

/**
 * 在 http 中设置验证图片和验证码
 *
 * @param request  http 请求, 在其中设置验证码
 * @param response http 响应, 设置返回验证码图片
 */
public void setHttpVerificationCode(HttpServletRequest request, HttpServletResponse response) throws IOException {
    response.setContentType("image/jpeg");//设置相应类型,输出的内容为图片
    response.setHeader("Pragma", "no-cache");//设置响应头信息,不要缓存此内容
    response.setHeader("Cache-Control", "no-store");
    response.setContentType("image/jpeg");
    response.setDateHeader("Expires", 0);

    HttpSession session = request.getSession();
    session.removeAttribute(SESSION_CODE_KEY);
    VerificationImage verificationImage = getVerificationImage();
    session.setAttribute(SESSION_CODE_KEY, verificationImage.getRightCode());
    ImageIO.write(verificationImage.getBufferedImage(), "JPEG", response.getOutputStream());
}

  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值