Struts2 驗證碼圖片生成例項
Step 1.隨機驗證碼
一步一步來,要生成驗證碼圖片,首先要有驗證碼,然後才能在畫在圖片上。為了能夠靈活控制驗證碼,特別編寫了SecurityCode類,它向外提供隨機字串。並且可以控制字串的長度和難度。SecurityCode類中提供的驗證碼分三個難度,易(全數字)、中(數字+小寫英文)、難(數字+大小寫英文)。難度使用列舉SecurityCodeLevle表示,避免使用1、2、3這樣沒有明確意義的數字來區分。同時,還控制了能否出現重複的字元。為了能夠方便使用,方法設計為static。
SecurityCode類:
package com.syz.onego.action.util; import java.util.Arrays; /** * 工具類,生成隨機驗證碼字串 * @version 1.0 2012/12/01 * @author shiyz * */ public class SecurityCode { /** * 驗證碼難度級別,Simple只包含數字,Medium包含數字和小寫英文,Hard包含數字和大小寫英文 */ public enum SecurityCodeLevel {Simple,Medium,Hard}; /** * 產生預設驗證碼,4位中等難度 * @return String 驗證碼 */ public static String getSecurityCode(){ return getSecurityCode(4,SecurityCodeLevel.Medium,false); } /** * 產生長度和難度任意的驗證碼 * @param length 長度 * @param level 難度級別 * @param isCanRepeat 是否能夠出現重複的字元,如果為true,則可能出現 5578這樣包含兩個5,如果為false,則不可能出現這種情況 * @return String 驗證碼 */ public static String getSecurityCode(int length,SecurityCodeLevel level,boolean isCanRepeat){ //隨機抽取len個字元 int len=length; //字元集合(除去易混淆的數字0、數字1、字母l、字母o、字母O) char[] codes={'1','2','3','4','5','6','7','8','9', 'a','b','c','d','e','f','g','h','i','j','k','m','n','p','q','r','s','t','u','v','w','x','y','z', 'A','B','C','D','E','F','G','H','I','J','K','L','M','N','P','Q','R','S','T','U','V','W','X','Y','Z'}; //根據不同的難度擷取字元陣列 if(level==SecurityCodeLevel.Simple){ codes=Arrays.copyOfRange(codes, 0,9); }else if(level==SecurityCodeLevel.Medium){ codes=Arrays.copyOfRange(codes, 0,33); } //字元集合長度 int n=codes.length; //丟擲執行時異常 if(len>n&&isCanRepeat==false){ throw new RuntimeException( String.format("呼叫SecurityCode.getSecurityCode(%1$s,%2$s,%3$s)出現異常,當isCanRepeat為%3$s時,傳入引數%1$s不能大於%4$s", len,level,isCanRepeat,n)); } //存放抽取出來的字元 char[] result=new char[len]; //判斷能否出現重複的字元 if(isCanRepeat){ for(int i=0;i<result.length;i++){ //索引 0 and n-1 int r=(int)(Math.random()*n); //將result中的第i個元素設定為codes[r]存放的數值 result[i]=codes[r]; } }else{ for(int i=0;i<result.length;i++){ //索引 0 and n-1 int r=(int)(Math.random()*n); //將result中的第i個元素設定為codes[r]存放的數值 result[i]=codes[r]; //必須確保不會再次抽取到那個字元,因為所有抽取的字元必須不相同。 //因此,這裡用陣列中的最後一個字元改寫codes[r],並將n減1 codes[r]=codes[n-1]; n--; } } return String.valueOf(result); } }
Step 2.圖片
第一步已經完成,有了上面SecurityCode類提供的驗證碼,就應該考慮怎麼在圖片上寫字串了。在Java中操作圖片,需要使用BufferedImage類,它代表記憶體中的圖片。寫字串,就需要從圖片BufferedImage上得到繪圖圖面Graphics,然後在圖面上drawString。
為了使驗證碼有一定的干擾性,也繪製了一些噪點。呼叫Graphics類的drawRect繪製1*1大小的方塊就可以了。
特別說明一下,由於後面要與Strtus2結合使用,而在Struts2中向前臺返回圖片資料使用的是資料流的形式。所以提供了從圖片向流的轉換方法convertImageToStream。
SecurityImage類:
package com.syz.onego.action.util; import java.awt.Color; import java.awt.Font; import java.awt.Graphics; import java.awt.image.BufferedImage; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.util.Random; import com.sun.image.codec.jpeg.ImageFormatException; import com.sun.image.codec.jpeg.JPEGCodec; import com.sun.image.codec.jpeg.JPEGImageEncoder; /** * 驗證碼生成器類,可生成數字、大寫、小寫字母及三者混合型別的驗證碼。 * 支援自定義驗證碼字元數量; * 支援自定義驗證碼圖片的大小; * 支援自定義需排除的特殊字元; * 支援自定義干擾線的數量; * 支援自定義驗證碼圖文顏色 * @author shiyz * @version 1.0 */ public class SecurityImage { /** * 生成驗證碼圖片 * @param securityCode 驗證碼字元 * @return BufferedImage 圖片 */ public static BufferedImage createImage(String securityCode){ //驗證碼長度 int codeLength=securityCode.length(); //字型大小 int fSize = 15; int fWidth = fSize + 1; //圖片寬度 int width = codeLength * fWidth + 6 ; //圖片高度 int height = fSize * 2 + 1; //圖片 BufferedImage image=new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB); Graphics g=image.createGraphics(); //設定背景色 g.setColor(Color.WHITE); //填充背景 g.fillRect(0, 0, width, height); //設定邊框顏色 g.setColor(Color.LIGHT_GRAY); //邊框字型樣式 g.setFont(new Font("Arial", Font.BOLD, height - 2)); //繪製邊框 g.drawRect(0, 0, width - 1, height -1); //繪製噪點 Random rand = new Random(); //設定噪點顏色 g.setColor(Color.LIGHT_GRAY); for(int i = 0;i < codeLength * 6;i++){ int x = rand.nextInt(width); int y = rand.nextInt(height); //繪製1*1大小的矩形 g.drawRect(x, y, 1, 1); } //繪製驗證碼 int codeY = height - 10; //設定字型顏色和樣式 g.setColor(new Color(19,148,246)); g.setFont(new Font("Georgia", Font.BOLD, fSize)); for(int i = 0; i < codeLength;i++){ g.drawString(String.valueOf(securityCode.charAt(i)), i * 16 + 5, codeY); } //關閉資源 g.dispose(); return image; } /** * 返回驗證碼圖片的流格式 * @param securityCode 驗證碼 * @return ByteArrayInputStream 圖片流 */ public static ByteArrayInputStream getImageAsInputStream(String securityCode){ BufferedImage image = createImage(securityCode); return convertImageToStream(image); } /** * 將BufferedImage轉換成ByteArrayInputStream * @param image 圖片 * @return ByteArrayInputStream 流 */ private static ByteArrayInputStream convertImageToStream(BufferedImage image){ ByteArrayInputStream inputStream = null; ByteArrayOutputStream bos = new ByteArrayOutputStream(); JPEGImageEncoder jpeg = JPEGCodec.createJPEGEncoder(bos); try { jpeg.encode(image); byte[] bts = bos.toByteArray(); inputStream = new ByteArrayInputStream(bts); } catch (ImageFormatException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } return inputStream; } }
Step 3.驗證碼與Struts 2結合
1)Action
有了上面兩步操作作為鋪墊,含有驗證碼的圖片就不成問題了,下面就可以使用Struts2的Action向前臺返回圖片資料了。
SecurityCodeImageAction類:
package com.syz.onego.action.front.user;
import java.io.ByteArrayInputStream;
import java.util.Map;
import org.apache.struts2.convention.annotation.Action;
import org.apache.struts2.convention.annotation.Result;
import org.apache.struts2.convention.annotation.Results;
import org.apache.struts2.interceptor.SessionAware;
import com.opensymphony.xwork2.ActionSupport;
import com.syz.onego.action.util.SecurityCode;
import com.syz.onego.action.util.SecurityImage;
@Results({@Result(name = "success", type = "stream", params = {
"contentType", "image/jpeg",
"inputName", "imageStream",
"bufferSize",
"4096" })})
public class SecurityCodeImageAction extends ActionSupport implements SessionAware{
private static final long serialVersionUID = 1496691731440581303L;
//圖片流
private ByteArrayInputStream imageStream;
//session域
private Map<String, Object> session ;
public ByteArrayInputStream getImageStream() {
return imageStream;
}
public void setImageStream(ByteArrayInputStream imageStream) {
this.imageStream = imageStream;
}
public void setSession(Map<String, Object> session) {
this.session = session;
}
@Action("imagecode")
public String execute() throws Exception {
//如果開啟Hard模式,可以不區分大小寫
//String securityCode = SecurityCode.getSecurityCode(4,SecurityCodeLevel.Hard, false).toLowerCase();
//獲取預設難度和長度的驗證碼
String securityCode = SecurityCode.getSecurityCode();
imageStream = SecurityImage.getImageAsInputStream(securityCode);
//放入session中
session.put("securityCode", securityCode);
return SUCCESS;
}
}
如果是配置檔案的話:
在 Struts.xml配置檔案中,需要配置SecurityCodeImageAction,由於現在返回的是流,就不應該再使用普通的方式了,應該在result上加上type="stream"。
同時<param name="inputName">這一項的值,應該與SecurityCodeImageAction中的圖片流名稱一致。
Struts.xml:
<action name="SecurityCodeImageAction" class="securityCodeImageAction">
<result name="success" type="stream">
<param name="contentType">image/jpeg</param>
<param name="inputName">imageStream</param>
<param name="bufferSize">2048</param>
</result>
</action>
3)前臺JSP
定義一個img元素,將src指向SecurityCodeImageAction就可以了,瀏覽器向Action傳送請求,伺服器將圖片流返回,圖片就能夠顯示了。
<img src="${ctx}/front/user/imagecode.action" id="Verify" style="cursor:pointer;" alt="看不清,換一張"/>
4)JS
驗證碼一般都有點選重新整理的功能,這個也容易實現,點選圖片,重新給圖片的src賦值。但是這時,瀏覽器會有快取問題,如果瀏覽器發現src中的url不變,就認為圖片沒有改變,就會使用快取中的圖片,而不是重新向伺服器請求。解決辦法是在url後面加上一個時間戳,每次點選時,時間戳都不一樣,瀏覽器就認為是新的圖片,然後就傳送請求了。
jQuery:
$(function () {
//點選圖片更換驗證碼
$("#Verify").click(function(){
$(this).attr("src","${ctx}/front/user/imagecode.action?timestamp="+new Date().getTime());
});
});
5)效果
生成的驗證碼圖片如下所示,淺藍色的字型,淺灰色的噪點。