/**
* Copyright (c) 2011-2017, James Zhan 詹波 (jfinal@126.com).
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.jfinal.captcha;
import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Font;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.geom.QuadCurve2D;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.util.Random;
import javax.imageio.ImageIO;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.Cookie;
import com.jfinal.core.Controller;
import com.jfinal.kit.LogKit;
import com.jfinal.kit.StrKit;
import com.jfinal.render.Render;
import com.jfinal.render.RenderException;
/**
* CaptchaRender.
*/
public class CaptchaRender extends Render {
protected static String captchaName = "_jfinal_captcha";
protected static Random random = new Random(System.nanoTime());
// 默认的验证码大小
protected static final int WIDTH = 108, HEIGHT = 40;
// 验证码随机字符数组
protected static final char[] charArray = "3456789ABCDEFGHJKMNPQRSTUVWXY".toCharArray();
// 验证码字体
protected static final Font[] RANDOM_FONT = new Font[] {
new Font("nyala", Font.BOLD, 38),
new Font("Arial", Font.BOLD, 32),
new Font("Bell MT", Font.BOLD, 32),
new Font("Credit valley", Font.BOLD, 34),
new Font("Impact", Font.BOLD, 32),
new Font(Font.MONOSPACED, Font.BOLD, 40)
};
/**
* 设置 captchaName
*/
public static void setCaptchaName(String captchaName) {
if (StrKit.isBlank(captchaName)) {
throw new IllegalArgumentException("captchaName can not be blank.");
}
CaptchaRender.captchaName = captchaName;
}
/**
* 生成验证码
*/
public void render() {
Captcha captcha = createCaptcha();
CaptchaManager.me().getCaptchaCache().put(captcha);
Cookie cookie = new Cookie(captchaName, captcha.getKey());
cookie.setMaxAge(-1);
cookie.setPath("/");
response.addCookie(cookie);
response.setHeader("Pragma","no-cache");
response.setHeader("Cache-Control","no-cache");
response.setDateHeader("Expires", 0);
response.setContentType("image/jpeg");
ServletOutputStream sos = null;
try {
BufferedImage image = new BufferedImage(WIDTH, HEIGHT, BufferedImage.TYPE_INT_RGB);
drawGraphic(captcha.getValue(), image);
sos = response.getOutputStream();
ImageIO.write(image, "jpeg", sos);
} catch (IOException e) {
if (getDevMode()) {
throw new RenderException(e);
}
} catch (Exception e) {
throw new RenderException(e);
} finally {
if (sos != null) {
try {sos.close();} catch (IOException e) {LogKit.logNothing(e);}
}
}
}
protected Captcha createCaptcha() {
String captchaKey = getCaptchaKeyFromCookie();
if (StrKit.isBlank(captchaKey)) {
captchaKey = StrKit.getRandomUUID();
}
return new Captcha(captchaKey, getRandomString(), Captcha.DEFAULT_EXPIRE_TIME);
}
protected String getCaptchaKeyFromCookie() {
Cookie[] cookies = request.getCookies();
if (cookies != null) {
for (Cookie cookie : cookies) {
if (cookie.getName().equals(captchaName)) {
return cookie.getValue();
}
}
}
return null;
}
protected String getRandomString() {
char[] randomChars = new char[4];
for (int i=0; i<randomChars.length; i++) {
randomChars[i] = charArray[random.nextInt(charArray.length)];
}
return String.valueOf(randomChars);
}
protected void drawGraphic(String randomString, BufferedImage image){
// 获取图形上下文
Graphics2D g = image.createGraphics();
g.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_NEAREST_NEIGHBOR);
// 图形抗锯齿
g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
// 字体抗锯齿
g.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
// 设定背景色
g.setColor(getRandColor(210, 250));
g.fillRect(0, 0, WIDTH, HEIGHT);
//绘制小字符背景
Color color = null;
for(int i = 0; i < 20; i++){
color = getRandColor(120, 200);
g.setColor(color);
String rand = String.valueOf(charArray[random.nextInt(charArray.length)]);
g.drawString(rand, random.nextInt(WIDTH), random.nextInt(HEIGHT));
color = null;
}
//设定字体
g.setFont(RANDOM_FONT[random.nextInt(RANDOM_FONT.length)]);
// 绘制验证码
for (int i = 0; i < randomString.length(); i++){
//旋转度数 最好小于45度
int degree = random.nextInt(28);
if (i % 2 == 0) {
degree = degree * (-1);
}
//定义坐标
int x = 22 * i, y = 21;
//旋转区域
g.rotate(Math.toRadians(degree), x, y);
//设定字体颜色
color = getRandColor(20, 130);
g.setColor(color);
//将认证码显示到图象中
g.drawString(String.valueOf(randomString.charAt(i)), x + 8, y + 10);
//旋转之后,必须旋转回来
g.rotate(-Math.toRadians(degree), x, y);
}
//图片中间曲线,使用上面缓存的color
g.setColor(color);
//width是线宽,float型
BasicStroke bs = new BasicStroke(3);
g.setStroke(bs);
//画出曲线
QuadCurve2D.Double curve = new QuadCurve2D.Double(0d, random.nextInt(HEIGHT - 8) + 4, WIDTH / 2, HEIGHT / 2, WIDTH, random.nextInt(HEIGHT - 8) + 4);
g.draw(curve);
// 销毁图像
g.dispose();
}
/*
* 给定范围获得随机颜色
*/
protected Color getRandColor(int fc, int bc) {
Random random = new Random();
if (fc > 255)
fc = 255;
if (bc > 255)
bc = 255;
int r = fc + random.nextInt(bc - fc);
int g = fc + random.nextInt(bc - fc);
int b = fc + random.nextInt(bc - fc);
return new Color(r, g, b);
}
/**
* 校验用户输入的验证码是否正确
* @param controller 控制器
* @param userInputString 用户输入的字符串
* @return 验证通过返回 true, 否则返回 false
*/
public static boolean validate(Controller controller, String userInputString) {
String captchaKey = controller.getCookie(captchaName);
if (validate(captchaKey, userInputString)) {
controller.removeCookie(captchaName);
return true;
}
return false;
}
/**
* 校验用户输入的验证码是否正确
* @param captchaKey 验证码 key,在不支持 cookie 的情况下可通过传参给服务端
* @param userInputString 用户输入的字符串
* @return 验证通过返回 true, 否则返回 false
*/
public static boolean validate(String captchaKey, String userInputString) {
ICaptchaCache captchaCache = CaptchaManager.me().getCaptchaCache();
Captcha captcha = captchaCache.get(captchaKey);
if (captcha != null && captcha.notExpired() && captcha.getValue().equalsIgnoreCase(userInputString)) {
captchaCache.remove(captcha.getKey());
return true;
}
return false;
}
}