admin

Kotlin + SpringBoot 验证码实现
前言许久时间没有学习Kotlin了,单独的从基础语法学起,着实让人感到非常乏味,因此在学习一门新东西的时候我总是喜...
扫描右侧二维码阅读全文
04
2018/04

Kotlin + SpringBoot 验证码实现

前言

许久时间没有学习Kotlin了,单独的从基础语法学起,着实让人感到非常乏味,因此在学习一门新东西的时候我总是喜欢通过一些常用实践来进行跳跃式地学习:即要实现某个功能的时候,需要用到哪些东西,就去查阅那些东西的使用,而不是按部就班地按照书本或者教程去做。这样的学习,我感觉不仅高效,而且有趣。于是,今天就用Kotlin + SpringBoot来实现一个简单的验证码功能。效果如下:
login-verifycode.jpg

实践

暂且把SpringBoot放到一边,单独地先把验证码的工具类写出来再说,由上面的图片可知,我实现的是一个计算式子的验证码,比较简单只包含 3 中运算符:加减乘。
那么首先,我们先用一个数组来存储运算符号:

val operators = arrayOf("+", "-", "×")

要写出上面的代码,自然而然的就得去学习kotlin的数组,所以在这里就 get 了arrayOf的用法,简单吧。
接下来我们需要随机生成计算表达式,并且返回结果是表达式字符串 + 表达式的值。
一个函数返回多个值,在java中是通过对象返回的,在kotlin中也不例外,但是kotlin对于这种对象有着专门的关键字声明,那就是data,新建数据类如下:

data class VerifyCode(var sCode: String, var result: Int)

这里又学习了data关键字的使用,以及数据类的声明:可以直接在类声明的同时将构造属性写在后面,是不是非常简洁?
下面来实现表达式的随机生成:

private fun getVerifyCode(): VerifyCode {
    var opt = operators[ThreadLocalRandom.current().nextInt(3)]

    var num1 = ThreadLocalRandom.current().nextInt(100)
    var num2 = ThreadLocalRandom.current().nextInt(100)

    var sCode = ""
    val result = when (opt) {
        "+" -> {
            sCode = "$num1 + $num2 = ?"
            num1 + num2
        }
        "-" -> {
            //避免产生负数
            sCode = if (num1 < num2) "$num2 - $num1 = ?" else "$num1 - $num2 = ?"
            Math.abs(num1 - num2)
        }
        else -> {
            //运算符为乘法,则只生成最多只有一个两位数的乘法
            num1 = ThreadLocalRandom.current().nextInt(100)
            num2 = ThreadLocalRandom.current().nextInt(if(num1 > 10) 10 else 100)
            sCode = "$num1 × $num2 = ?"
            num1 * num2
        }
    }
    return VerifyCode(sCode, result)
}

上面需要注意的就是一般表达式验证码是不会出现负数的,所以需要额外的处理一下,还有就是要考虑表达式计算的难度,一般两个两位数的整数相乘用户是不能接受的,所以也需要额外处理一下,整个构造思路就是:

  • 随机选取一个运算符
  • 随机生成两个整数
  • 判断运算符,拼接成字符串并且计算表达式结果(包括对负数以及乘法的额外处理)

在这里,又能学到关键字when的使用。
有了表达式字符串与计算结果,最后就是将表达式画进图片里了,主要包含以下几个步骤:

  • 填充图片背景
  • 绘制干扰线
  • 画噪点
  • 画计算表达式
  • 扭曲图片

代码如下:

fun getVerifyCodeImage(width: Int, height: Int, interLines: Int = 5): ImageCode {
    val image = BufferedImage(width, height, BufferedImage.TYPE_INT_RGB)
    val graphics = image.graphics

    //设置背景颜色
    //graphics.color = getRandomColor()
    //填充背景
    graphics.fillRect(0, 0, width, height)
    //绘制干扰线
    if (interLines > 0) {
        for (i in 1..interLines) {
            graphics.color = getRandomColor()
            val x1 = ThreadLocalRandom.current().nextInt(4)
            val x2 = ThreadLocalRandom.current().nextInt((width * 0.8).toInt(), width)
            val y1 = height - ThreadLocalRandom.current().nextInt(height)
            val y2 = ThreadLocalRandom.current().nextInt(height)
            graphics.drawLine(x1, y1, x2, y2)
        }
    }

    //画噪点
    val yawpRate = 0.05f// 噪声率
    val area = (yawpRate * width * height).toInt()//噪点数量
    for (i in 0 until area) {
        val xxx = ThreadLocalRandom.current().nextInt(width)
        val yyy = ThreadLocalRandom.current().nextInt(height)
        image.setRGB(xxx, yyy, getRandomColor().rgb)
    }

    //设置字体大小
    val fontSize = (height * 0.8).toInt() //字体大小为图片高度的80%
    val font = Font(Font.SANS_SERIF, Font.BOLD, fontSize)
    graphics.font = font

    //生成验证码字符串及验证码结果值
    var (sCode, result) = getVerifyCode()

    //获取验证码字符串像素宽度
    val fm = graphics.getFontMetrics(font)
    val sCodePx = fm.stringWidth(sCode)

    //画验证码字符串
    graphics.color = Color(0x000)
    graphics.drawString(sCode, (width - sCodePx) / 2, fontSize + (height - fontSize) / 2)

    //扭曲图像
    shearY(graphics, width, height, getRandomColor())

    graphics.dispose()
    return ImageCode(image, result)
}

部分代码转自其他 JAVA 验证码生成文章,关键在于上面代码我对验证码的位置做了处理,使其显示在图片的中间,主要就是计算出字符串的像素宽度,这个宽度和字符串的长度不同,通过 39、40 行的代码可以得出。
最后整个工具类贴出来:

package me._52xz.downloader.util

import java.awt.Color
import java.awt.Font
import java.awt.Graphics
import java.awt.image.BufferedImage
import java.util.concurrent.ThreadLocalRandom



/**
 * Created by Mr.Xu on 2018/4/4.

 */


object VerifyCodeUtil {

    //运算符,简单采用加减乘,除的话/与%是不同的,不是计算机专业的用户一般不懂,所以不采用
    val operators = arrayOf("+", "-", "×")

    data class VerifyCode(var sCode: String, var result: Int)
    data class ImageCode(var image: BufferedImage, var code: Int)

    /**
     * 生成验证码表达式字符串以及表达式的计算结果
     */
    private fun getVerifyCode(): VerifyCode {
        var opt = operators[ThreadLocalRandom.current().nextInt(3)]

        var num1 = ThreadLocalRandom.current().nextInt(100)
        var num2 = ThreadLocalRandom.current().nextInt(100)

        var sCode = ""
        val result = when (opt) {
            "+" -> {
                sCode = "$num1 + $num2 = ?"
                num1 + num2
            }
            "-" -> {
                //避免产生负数
                sCode = if (num1 < num2) "$num2 - $num1 = ?" else "$num1 - $num2 = ?"
                Math.abs(num1 - num2)
            }
            else -> {
                //运算符为乘法,则只生成最多只有一个两位数的乘法
                num1 = ThreadLocalRandom.current().nextInt(100)
                num2 = ThreadLocalRandom.current().nextInt(if(num1 > 10) 10 else 100)
                sCode = "$num1 × $num2 = ?"
                num1 * num2
            }
        }
        return VerifyCode(sCode, result)
    }

    /**
     * 获取随机颜色
     */
    private fun getRandomColor(): Color = Color(ThreadLocalRandom.current().nextInt(0xffffff))

    /**
     * 获取验证码图片以及验证码对应的计算结果
     * param width:图片宽度
     * param height:图片高度
     * param interLines:干扰线条数
     */
    fun getVerifyCodeImage(width: Int, height: Int, interLines: Int = 5): ImageCode {
        val image = BufferedImage(width, height, BufferedImage.TYPE_INT_RGB)
        val graphics = image.graphics

        //设置背景颜色
        //graphics.color = getRandomColor()
        //填充背景
        graphics.fillRect(0, 0, width, height)
        //绘制干扰线
        if (interLines > 0) {
            for (i in 1..interLines) {
                graphics.color = getRandomColor()
                val x1 = ThreadLocalRandom.current().nextInt(4)
                val x2 = ThreadLocalRandom.current().nextInt((width * 0.8).toInt(), width)
                val y1 = height - ThreadLocalRandom.current().nextInt(height)
                val y2 = ThreadLocalRandom.current().nextInt(height)
                graphics.drawLine(x1, y1, x2, y2)
            }
        }

        //画噪点
        val yawpRate = 0.05f// 噪声率
        val area = (yawpRate * width * height).toInt()//噪点数量
        for (i in 0 until area) {
            val xxx = ThreadLocalRandom.current().nextInt(width)
            val yyy = ThreadLocalRandom.current().nextInt(height)
            image.setRGB(xxx, yyy, getRandomColor().rgb)
        }

        //设置字体大小
        val fontSize = (height * 0.8).toInt() //字体大小为图片高度的80%
        val font = Font(Font.SANS_SERIF, Font.BOLD, fontSize)
        graphics.font = font

        //生成验证码字符串及验证码结果值
        var (sCode, result) = getVerifyCode()

        //获取验证码字符串像素宽度
        val fm = graphics.getFontMetrics(font)
        val sCodePx = fm.stringWidth(sCode)

        //画验证码字符串
        graphics.color = Color(0x000)
        graphics.drawString(sCode, (width - sCodePx) / 2, fontSize + (height - fontSize) / 2)

        //扭曲图像
        shearY(graphics, width, height, getRandomColor())

        graphics.dispose()
        return ImageCode(image, result)
    }


    private fun shearY(g: Graphics, w1: Int, h1: Int, color: Color) {

        val period = h1 / 4

        val frames = 20
        val phase = 7
        for (i in 0 until w1) {
            val d = (period shr 1).toDouble() * Math.sin(i.toDouble() / period.toDouble() + 6.2831853071795862 * phase.toDouble() / frames.toDouble())
            g.copyArea(i, 0, 1, h1, 0, d.toInt())
        }

    }
}

最后就是SpringBoot的 Controller 中使用了:

@GetMapping("/verifyCode")
fun verifyCode(session: HttpSession, response: HttpServletResponse): Unit {
    var (image, code) = VerifyCodeUtil.getVerifyCodeImage(120, 28)
    session.setAttribute("verifyCode", code)
    response.contentType = "image/jpg"
    ImageIO.write(image, "jpg", response.outputStream)
}

运行效果图就是最前面给出的。

总结

自从用了Kotlin腰不疼了、腿不酸了、对代码的兴趣也更高了。。说实话,Kotlin语法的简洁性远远超过了Java

Last modification:August 18th, 2018 at 03:18 pm
If you think my article is useful to you, please feel free to appreciate

Leave a Comment