前言
Java登录验证码,支持多种背景干扰;同时支持动态Gif模式。
话不多说,先来个效果展示:
静态验证码:
GIF动态验证码:
步骤总览
- 需要一个能随机生成字符验证码的工具类-Randoms
- 定义一个验证码的抽象对象-Captcha
- PNG或者GIF输出随机生成的验证码图片
Randoms.java
能随机生成字符串的类
package ranpic;
import java.util.Random;
/**
*
* <p>随机生成指定长度的内容</p>
* @date 2019/01/08
* @author ffewi
* @version 1.0
*/
public class Randoms {
private static final Random RANDOM = new Random();
//定义验证码字符
public static final char ALPHA[] = {'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'G', 'K', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z'
, 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9'};
/**
* 产生两个数之间的随机数
*
* @param min 小数
* @param max 比min大的数
* @return int 随机数字
*/
public static int num(int min, int max) {
return min + RANDOM.nextInt(max - min);
}
/**
* 产生0--num的随机数,不包括num
*
* @param num 数字
* @return int 随机数字
*/
public static int num(int num) {
return RANDOM.nextInt(num);
}
public static char alpha() {
return ALPHA[num(0, ALPHA.length)];
}
}
Captcha.java
验证码抽象对象
package ranpic;
import java.awt.*;
import java.io.OutputStream;
import java.util.ArrayList;
import static ranpic.Randoms.alpha;
import static ranpic.Randoms.num;
/**
* <p>验证码抽象类,统一验证码的属性</p>
*
* @author ffewi
* @version 1.0
* @date 2019/01/08
*/
public abstract class Captcha {
/**
* 默认字体
*/
protected Font font = new Font("Verdana", Font.ITALIC | Font.BOLD, 28);
/**
* 默认验证码随机字符长度
*/
protected int len = 6;
/**
* 默认验证码显示跨度
*/
protected int width = 150;
/**
* 默认验证码显示高度
*/
protected int height = 40;
/**
* 随机字符串
*/
protected String chars = null;
/**
* 背景填充模式
* 1、圆形
* 2、空心距形
* 3、凸出立体距形
* 4、凹进去的立体距形
* 5、空心笛卡尔心形
* 6、完美心形
* 其他、默认无
*/
protected CaptchaBgFillMode mode = CaptchaBgFillMode.ROUND;
protected void drawBgByMode(Graphics2D g2d, CaptchaBgFillMode mode) {
if (CaptchaBgFillMode.ROUND.equals(mode)) {
g2d.drawOval(num(width), num(height), 5 + num(10), 5 + num(10));
} else if (CaptchaBgFillMode.HOLLOW_SHAPE.equals(mode)) {
g2d.drawRect(num(width), num(height), 5 + num(10), 5 + num(10));
} else if (CaptchaBgFillMode.PROTRUDING_STEREO_SHAPE.equals(mode)) {
g2d.draw3DRect(num(width), num(height), 5 + num(10), 5 + num(10), true);
} else if (CaptchaBgFillMode.CONCAVE_STEREOSCOPIC_SHAPE.equals(mode)) {
g2d.draw3DRect(num(width), num(height), 5 + num(10), 5 + num(10), false);
} else if (CaptchaBgFillMode.HOLLOW_DESCARTES_HEART_SHAPE.equals(mode)) {
drawHeart(g2d, 5 + num(width), 5 + num(height), CaptchaBgFillMode.HOLLOW_DESCARTES_HEART_SHAPE);
} else if (CaptchaBgFillMode.PERFECT_HEART_SHAPE.equals(mode)) {
drawHeart(g2d, 5 + num(width), 5 + num(height), CaptchaBgFillMode.PERFECT_HEART_SHAPE);
} else {
//
}
}
/**
* 生成随机字符数组
*
* @return 字符数组
*/
protected char[] alphas() {
char[] cs = new char[len];
for (int i = 0; i < len; i++) {
cs[i] = alpha();
}
chars = new String(cs);
return cs;
}
/**
* 给定范围获得随机颜色
*
* @return Color 随机颜色
*/
protected Color color(int fc, int bc) {
if (fc > 255)
fc = 255;
if (bc > 255)
bc = 255;
int r = fc + num(bc - fc);
int g = fc + num(bc - fc);
int b = fc + num(bc - fc);
return new Color(r, g, b);
}
private void drawHeart(Graphics2D g2d, int xn, int yn, CaptchaBgFillMode mode) {
// t 代表弧度
double t = 0;
// vt 代表 t 的增量
double vt = 0.01;
// maxt 代表 t 的最大值
double maxt = 2 * Math.PI;
// 需要循环的次数
double maxi = Math.ceil(maxt / vt);
// 保存所有点的坐标的数组
ArrayList<String> pointArr = new ArrayList();
// 控制心形大小
double size = 4/(1+num(4));
// x 用来暂时保存每次循环得到的 x 坐标
double x = 0;
// y 用来暂时保存每次循环得到的 y 坐标
double y = 0;
if (CaptchaBgFillMode.PERFECT_HEART_SHAPE.equals(mode)){
size /= 6;
}
for (double i = 0; i <= maxi; i++) {
if (CaptchaBgFillMode.HOLLOW_DESCARTES_HEART_SHAPE.equals(mode)) {
x = 2 * Math.sin(t) - Math.sin(2 * t);
y = 2 * Math.cos(t) - Math.cos(2 * t);
} else if (CaptchaBgFillMode.PERFECT_HEART_SHAPE.equals(mode)) {
x = 16 * Math.pow(Math.sin(t), 3);
y = 13 * Math.cos(t) - 5 * Math.cos(2 * t) - 2 * Math.cos(3 * t) - Math.cos(4 * t);
} else {
return;
}
t += vt;
pointArr.add(String.valueOf(x * size) + ',' + String.valueOf(-y * size));
}
for (String p : pointArr) {
String[] arr = p.split(",");
int x1 = (int) Double.parseDouble(arr[0]) + xn;
int y1 = (int) Double.parseDouble(arr[1]) + yn;
g2d.drawLine(x1, y1, x1, y1);
}
}
/**
* 验证码输出,抽象方法,由子类实现
*
* @param os 输出流
*/
public abstract void out(OutputStream os);
/**
* 获取随机字符串
*
* @return string
*/
public String text() {
return chars;
}
public Font getFont() {
return font;
}
public void setFont(Font font) {
this.font = font;
}
public int getLen() {
return len;
}
public void setLen(int len) {
this.len = len;
}
public int getWidth() {
return width;
}
public void setWidth(int width) {
this.width = width;
}
public int getHeight() {
return height;
}
public void setHeight(int height) {
this.height = height;
}
public CaptchaBgFillMode getMode() {
return mode;
}
public void setMode(CaptchaBgFillMode mode) {
this.mode = mode;
}
}
CaptchaBgFillMode.java
定义背景模式
package ranpic;
public enum CaptchaBgFillMode {
ROUND("1"),
HOLLOW_SHAPE("2"),
PROTRUDING_STEREO_SHAPE("3"),
CONCAVE_STEREOSCOPIC_SHAPE("4"),
HOLLOW_DESCARTES_HEART_SHAPE("5"),
PERFECT_HEART_SHAPE("6");
CaptchaBgFillMode(String name) {
}
}
PngCaptcha.java
PNG格式验证码实现
package ranpic;
import javax.imageio.ImageIO;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.io.OutputStream;
import static ranpic.Randoms.num;
/**
* <p>制作png静态图片</p>
*/
public class PngCaptcha extends Captcha {
{
}
public PngCaptcha(int width, int height) {
this.width = width;
this.height = height;
}
public PngCaptcha(int width, int height, int len) {
this(width, height);
this.len = len;
}
public PngCaptcha(int width, int height, int len, Font font) {
this(width, height, len);
this.font = font;
}
/**
* 生成验证码
*
* @throws java.io.IOException IO异常
*/
@Override
public void out(OutputStream out) {
graphicsImage(alphas(), out);
}
/**
* 画随机码图
*
* @param strs 文本
* @param out 输出流
*/
private boolean graphicsImage(char[] strs, OutputStream out) {
boolean ok = false;
try {
BufferedImage bi = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
Graphics2D g = (Graphics2D) bi.getGraphics();
AlphaComposite ac3;
Color color;
int len = strs.length;
g.setColor(Color.WHITE);
g.fillRect(0, 0, width, height);
// 随机画干扰的背景
for (int i = 0; i < 15; i++) {
color = color(150, 250);
g.setColor(color);
drawBgByMode(g, mode);
}
g.setFont(font);
int h = height - ((height - font.getSize()) >> 1),
w = width / len,
size = w - font.getSize() + 1;
/* 画字符串 */
for (int i = 0; i < len; i++) {
ac3 = AlphaComposite.getInstance(AlphaComposite.SRC_OVER, 0.7f);// 指定透明度
g.setComposite(ac3);
color = new Color(20 + num(110), 20 + num(110), 20 + num(110));// 对每个字符都用随机颜色
g.setColor(color);
g.drawString(strs[i] + "", (width - (len - i) * w) + size, h - 4);
}
ImageIO.write(bi, "png", out);
out.flush();
ok = true;
} catch (IOException e) {
ok = false;
} finally {
Streams.close(out);
}
return ok;
}
}
GifCaptcha.java
GIF格式验证码实现
package ranpic;
import tools.GifEncoder;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.OutputStream;
import static ranpic.Randoms.num;
public class GifCaptcha extends Captcha
{
public GifCaptcha()
{
}
public GifCaptcha(int width,int height){
this.width = width;
this.height = height;
}
public GifCaptcha(int width,int height,int len){
this(width,height);
this.len = len;
}
public GifCaptcha(int width, int height, int len, Font font)
{
this(width,height,len);
this.font = font;
}
@Override
public void out(OutputStream os)
{
try
{
GifEncoder gifEncoder = new GifEncoder();
gifEncoder.start(os);
gifEncoder.setQuality(180);
gifEncoder.setDelay(100);
gifEncoder.setRepeat(0);
BufferedImage frame;
char[] rands =alphas();
Color fontcolor[]=new Color[len];
for(int i=0;i<len;i++)
{
fontcolor[i]=new Color(20 + num(110), 20 + num(110), 20 + num(110));
}
for(int i=0;i<len;i++)
{
frame=graphicsImage(fontcolor, rands, i);
gifEncoder.addFrame(frame);
frame.flush();
}
gifEncoder.finish();
}finally
{
Streams.close(os);
}
}
/**
* 画随机码图
* @param fontcolor 随机字体颜色
* @param strs 字符数组
* @param flag 透明度使用
* @return BufferedImage
*/
private BufferedImage graphicsImage(Color[] fontcolor,char[] strs,int flag)
{
BufferedImage image = new BufferedImage(width, height,BufferedImage.TYPE_INT_RGB);
//或得图形上下文
//Graphics2D g2d=image.createGraphics();
Graphics2D g2d = (Graphics2D)image.getGraphics();
//利用指定颜色填充背景
g2d.setColor(Color.WHITE);
g2d.fillRect(0, 0, width, height);
AlphaComposite ac3;
int h = height - ((height - font.getSize()) >>1) ;
int w = width/len;
g2d.setFont(font);
for(int i=0;i<len;i++)
{
ac3 = AlphaComposite.getInstance(AlphaComposite.SRC_OVER, getAlpha(flag, i));
g2d.setComposite(ac3);
g2d.setColor(fontcolor[i]);
drawBgByMode(g2d, mode);
g2d.drawString(strs[i]+"", (width-(len-i)*w)+(w-font.getSize())+1, h-4);
}
g2d.dispose();
return image;
}
/**
* 获取透明度,从0到1,自动计算步长
* @return float 透明度
*/
private float getAlpha(int i,int j)
{
int num = i+j;
float r = (float)1/len,s = (len+1) * r;
return num > len ? (num *r - s) : num * r;
}
}
Streams.java
统一流关闭
package ranpic;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
public class Streams {
/**
* 关闭输入流
*
* @param in 输入流
*/
public static void close(InputStream in) {
if (in != null) {
try {
in.close();
} catch (IOException ioex) {
// ignore
}
}
}
/**
* 关闭输出流
*
* @param out 输出流
*/
public static void close(OutputStream out) {
if (out != null) {
try {
out.flush();
} catch (IOException ioex) {
// ignore
}
try {
out.close();
} catch (IOException ioex) {
// ignore
}
}
}
}
测试
package ranpic;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
public class Test {
public static void main(String[] args) throws FileNotFoundException {
Captcha captcha = new PngCaptcha(150, 40, 5);// png格式验证码
captcha.out(new FileOutputStream("E:/1_1.png"));
captcha.setMode(CaptchaBgFillMode.HOLLOW_SHAPE);
captcha.out(new FileOutputStream("E:/1_2.png"));
captcha.setMode(CaptchaBgFillMode.PROTRUDING_STEREO_SHAPE);
captcha.out(new FileOutputStream("E:/1_3.png"));
captcha.setMode(CaptchaBgFillMode.CONCAVE_STEREOSCOPIC_SHAPE);
captcha.out(new FileOutputStream("E:/1_4.png"));
captcha.setMode(CaptchaBgFillMode.HOLLOW_DESCARTES_HEART_SHAPE);
captcha.out(new FileOutputStream("E:/1_5.png"));
captcha.setMode(CaptchaBgFillMode.PERFECT_HEART_SHAPE);
captcha.out(new FileOutputStream("E:/1_6.png"));
//new Font("防宋", Font.PLAIN| Font.BOLD, 28)
Captcha captcha1 = new GifCaptcha(150, 40, 5);// gif格式动画验证码
captcha1.out(new FileOutputStream("E:/2_1.gif"));
captcha1.setMode(CaptchaBgFillMode.HOLLOW_SHAPE);
captcha1.out(new FileOutputStream("E:/2_2.gif"));
captcha1.setMode(CaptchaBgFillMode.PROTRUDING_STEREO_SHAPE);
captcha1.out(new FileOutputStream("E:/2_3.gif"));
captcha1.setMode(CaptchaBgFillMode.CONCAVE_STEREOSCOPIC_SHAPE);
captcha1.out(new FileOutputStream("E:/2_4.gif"));
captcha1.setMode(CaptchaBgFillMode.HOLLOW_DESCARTES_HEART_SHAPE);
captcha1.out(new FileOutputStream("E:/2_5.gif"));
captcha1.setMode(CaptchaBgFillMode.PERFECT_HEART_SHAPE);
captcha1.out(new FileOutputStream("E:/2_6.gif"));
}
}
最后附带GIF图片生成工具类
tools.rar