点选随机汉字验证码实现的几个关键点
- 随机生成汉字
- 随机背景图片(只使用一张背景图略显尴尬)
- 字的布局
- 要允许一定的误差
随机生成汉字
/**
* 生成随机汉字
* @return
*/
private String getRandomChineseChar(){
String str;
int heightPos;//定义高低
int lowPos;
heightPos=(176+Math.abs(random.nextInt(39)));//获取最高位
lowPos=(161+Math.abs(random.nextInt(93)));//获取低位值
byte[] bytes=new byte[2];
bytes[0]=new Integer(heightPos).byteValue();
bytes[1]=new Integer(lowPos).byteValue();
try {
str=new String(bytes,"GBK");
} catch (UnsupportedEncodingException e) {
// e.printStackTrace();
str="䵵";//如有异常 我们返回这个字
}
return str;
}
生成随机颜色
/**
* 生成随机原色
* @return
*/
private Color getRandomColor(){
return new Color(random.nextInt(255),random.nextInt(255),random.nextInt(255));
}
生成汉字随机坐标
在这里我固定了几个字的顺序和位置,如果有需要可以对汉字进行顺序打乱后再定位
/**
* 生成个汉字和随机坐标
* @return
*/
public Map<String,int[]> createCode(){
int x=0;
int y=0;
Map<String,int[]> map=new HashMap<>();
for(int i=0;i<chars;i++){
String str=getRandomChineseChar();
while (map.containsKey(str)){
str=getRandomChineseChar();
}
switch (i){
case 0:
x=random.nextInt(30)+40;
y=random.nextInt(200)+60;
break;
case 1:
x=random.nextInt(40)+120;
y=random.nextInt(200)+70;
break;
case 2:
x=random.nextInt(30)+200;
y=random.nextInt(180)+100;
break;
case 3:
x=random.nextInt(30)+80;
y=random.nextInt(180)+90;
break;
default:
}
map.put(str,new int[]{x,y});
}
return map;
}
生成验证码图片
/**
* 生成验证码图片
* @param credential
* @return
*/
public BufferedImage createCodeImg(String credential){
Map<String,int[]> codeMap=this.createCode();
//test 打印坐标
String xy="";
for(String key:codeMap.keySet()){
xy=xy+codeMap.get(key)[0]+":"+codeMap.get(key)[1]+";";
}
Logs.error("坐标:{},\n{}",xy, JSONObject.toJSONString(codeMap));
redis.set(Constants.MSG_IMG_VERIFY_CODE + credential, codeMap, imgVerifyCodeExpireTime);
BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
Graphics2D g2 = (Graphics2D) image.getGraphics();
// 读取本地图片,做背景图片
String imgPath=checkImgPath + (random.nextInt(4) + 1) + ".jpg";
try {
//读取背景图片
g2.drawImage(ImageIO.read(new File(imgPath)),0,footSize,width,height,null);
} catch (IOException e) {
Logs.error(e.getMessage(),e);
}
g2.setColor(Color.WHITE);//设置颜色
g2.drawRect(0,0,width-1,height-1);//画边框
g2.setFont(new Font("宋体",Font.ITALIC,footSize));//设置字体
StringBuffer keysStr=new StringBuffer();
for(String key:codeMap.keySet()){
keysStr.append(key);
g2.setColor(getRandomColor());
g2.drawString(key,codeMap.get(key)[0],codeMap.get(key)[1]);
}
//画提示语
g2.setColor(Color.WHITE);
g2.setFont(new Font("宋体",Font.ITALIC,15));//设置字体
g2.drawString("请依次点击:"+keysStr,0,15);
//释放资源
g2.dispose();
return image;
}
输出验证码图片
response.setHeader("Pragma", "No-cache");
response.setHeader("Cache-Control", "no-cache");
response.setDateHeader("Expires", 0);
response.setContentType("image/jpeg");
ImageIO.write(clickVerifyUtil.createCodeImg(credential),"JPG",response.getOutputStream());
效果图
去博客设置页面,选择一款你喜欢的代码片高亮样式,下面展示同样高亮的 代码片
.
// An highlighted block
var foo = 'bar';
验证核心代码
只粘贴了部分核心代码,一些空值之类的检验需要自己完成
JSONObject obj=redis.get(Constants.MSG_IMG_VERIFY_CODE + credential,JSONObject.class);
String[] xyArr=xyStr.split(";");
/**
* 取出后直接消除,一个图片验证码只能使用一次
*/
redis.delete(Constants.MSG_IMG_VERIFY_CODE + credential);
//TODO 比对验证码的坐标在允许误差范围内是否一致
Object[] keys=obj.keySet().toArray();
for(int i=0;i<keys.length;i++){
String key= (String) keys[i];
JSONArray xyS=obj.getJSONArray(key);
String[] xyQ=xyArr[i].split(":");
int x=xyS.getInteger(0);//图片x坐标
int y=xyS.getInteger(1);//图片y坐标
int xq=Integer.valueOf(xyQ[0]);//请求的x坐标
int yq=Integer.valueOf(xyQ[1]);//请求的y坐标
if(offset(x,xq)>ERROR_ALLOW || offset(y,yq)>ERROR_ALLOW){
//验证不通过
return false;
}
}
return true;
前端demo
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<meta name="viewport" content="width=device-width,initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0, user-scalable=no"/>
<script src="https://code.jquery.com/jquery-3.1.1.min.js"></script>
<title>验证码</title>
</head>
<body>
<div style="text-align:center;position:relative;">
<!--<h2>这是点击的验证码</h2> -->
<!-- 这是点击的验证码 -->
<img id="codeT3" src="http://127.0.0.1:8080/click?credential=12345678&flag="+Math.random()"/>
</br>
<!--
<input type="button" value="刷新" οnclick="getCodeTree();" />
<input type="button" value="校验" οnclick="cheakOutTree();" />
-->
<select id="codeSelect" style="display: none;"></select>
<img src="/api/refresh.png" style="position:absolute;right:0;top:0; width:20px; height: 20px;" οnclick="getCodeTree();" />
</div>
<script type="text/javascript">
//点击次数
var number=0;
//获取验证码3
function getCodeTree() {
number = 0;
$(".zhezhao").remove();
document.getElementById("codeSelect").options.length = 0;
$("#codeT3").attr("src","http://127.0.0.1:8080/click?credential=12345678&flag="+Math.random());
}
$(function() {
$("#codeT3").bind("click", function(ev) {
var oEvent = ev || event;
//var number = $("#codeSelect option").length;
number++;
if (number > 4) {
return;
}
var x = oEvent.pageX;
var y = oEvent.pageY;
var img = document.getElementById('codeT3'); //获取图片的原点
var nodex = getNodePosition(img)[0];//原点x 与原点y
var nodey = getNodePosition(img)[1];
var xserver = parseInt(x) - parseInt(nodex);
var yserver = parseInt(y) - parseInt(nodey);
$("#codeSelect").append(
"<option value='"+ (parseInt(number)+1) +"'>" + xserver + ":" + yserver
+ "</option>");
var oDiv = document.createElement('img');
oDiv.style.left = (parseInt(x)-10) + 'px'; // 指定创建的DIV在文档中距离左侧的位置 图片大小30 左右移动5
oDiv.style.top = (parseInt(y) -15) + 'px'; // 指定创建的DIV在文档中距离顶部的位置
oDiv.style.border = '1px solid #FF0000'; // 设置边框
oDiv.style.position = 'absolute'; // 为新创建的DIV指定绝对定位
oDiv.style.width = '30px'; // 指定宽度
oDiv.style.height = '30px'; // 指定高度
//oDiv.src = 'select.png';
oDiv.style.opacity = '0.5'; //透明度
oDiv.className = 'zhezhao';//加class 点刷新后删除遮罩
document.body.appendChild(oDiv);
//第四次点击后自动提交
if (number == 4) {
cheakOutTree();
}
});
})
//校验验证码
function cheakOutTree() {
var txt = "";
$("#codeSelect option").each(function (){
var text = $(this).text();
if(txt == ""){
txt = text;
}else{
txt = txt + ";" + text;
}
});
$.ajax({
type:"post",
url:"http://localhost:8080/check",
data : {"xy" : txt,"credential":"12345678"},
cache : true,
success : function(data) {
alert(data.data);
if (!data.result) {
getCodeTree();
}
}
});
}
function getNodePosition(node) {
var top = left = 0;
while (node) {
if (node.tagName) {
top = top + node.offsetTop;
left = left + node.offsetLeft;
node = node.offsetParent;
}
else {
node = node.parentNode;
}
}
return [left, top];
}
</script>
</body>
</html>