做過項目的人都知道,在構(gòu)思完一個項目的功能之后,緊接著的事情就是考慮這些構(gòu)思的功能如何實現(xiàn),對于自己不熟悉的領(lǐng)域,要進(jìn)行技術(shù)穿刺。我的穿刺方法為先查找有無比較好的開源組件可用,如果沒有,就查找相關(guān)的文檔,自己編寫和測試代碼。
在這一篇,我主要解決三個問題。 1、解決字符串加密的問題,在前面一篇中,我們設(shè)計用戶模塊的時候,準(zhǔn)備將用戶的密碼字段以MD5加密的方式保存,因此,這里需要寫一個對字符串加密生成MD5字符串的方法; 2、解決生成圖像縮略圖和生成驗證碼的問題; 3、解決url重寫的問題,之所以要用到url重寫,主要是為了讓用戶在訪問自己的主頁時,可以使用http://www./username或者h(yuǎn)ttp://username.這樣的形式,而不是像http://www./index.jsp?username=xxx這樣難看的形式。
需要說明的是,要解決上面的三個問題,不是沒有開源的東西可用,而是我覺得每次都要整合不同的組件是在是太麻煩,而我們需要的功能也不是很復(fù)雜,我們不需要太通用的東西,只要能夠解決這里特定的問題就行了,因此不如自己動手實現(xiàn),同時還可以獲得技術(shù)上的提高。
首先來看看MD5加密的問題,JDK中本來提供有數(shù)據(jù)加密的支持,其中java.security.MessageDigest類就可以實現(xiàn)MD5的加密,但是,加密后生成的數(shù)據(jù)是byte[]類型的,這里只需要寫一個方法將它轉(zhuǎn)換為字符串就行,代碼如下:
package com.xkland.util;

import java.security.MessageDigest;
import java.lang.NullPointerException;
import java.security.NoSuchAlgorithmException;

 public class StringUtil {
 public static char [] num_chars = new char [] { ‘ 0 ‘ ,
‘ 1 ‘ , ‘ 2 ‘ , ‘ 3 ‘ , ‘ 4 ‘ , ‘ 5 ‘ , ‘ 6 ‘ , ‘ 7 ‘ , ‘ 8 ‘ ,
‘ 9 ‘ , ‘ A ‘ , ‘ B ‘ , ‘ C ‘ , ‘ D ‘ , ‘ E ‘ , ‘ F ‘ } ;
public static String toMD5String(String input)
 throws NullPointerException,NoSuchAlgorithmException {
if (input == null ) throw new NullPointerException();
char [] output = new char [ 32 ];
MessageDigest md = MessageDigest.getInstance( " MD5 " );
byte [] by = md.digest(input.getBytes());
 for ( int i = 0 ;i < by.length;i ++ ) {
output[ 2 * i] = num_chars[ (by[i] & 0xf0 ) >> 4 ];
output[ 2 * i + 1 ] = num_chars[ by[i] & 0xf ];
}
return new String(output);
}
}
下面是它的測試用例:
package com.xkland.util;

import junit.framework.TestCase;

 public class StringUtilTest extends TestCase {

 public void testToMD5String() {
 try {
System.out.println(StringUtil.toMD5String( " abc " ));
 } catch (Exception e) {
}
}
}
運(yùn)行測試用例,輸出結(jié)果為:
900150983CD24FB0D6963F7D28E17F72
再來說說關(guān)于圖像縮略圖生成的問題,我準(zhǔn)備將它設(shè)置為一個可以讓Spring管理的類,簡單的說,可以利用Spring的配置文件來設(shè)置該類的一些屬性,比如原圖像保存的目錄和目標(biāo)圖像保存的目錄,生成的縮略圖的大小,生成縮略圖的方式。這里特別需要說明的就是這個生成縮略圖的方式,我們即可以指定它只簡單的執(zhí)行縮放,也可以指定它進(jìn)行剪裁以后再縮放。為什么要這么設(shè)計,請大家看看如下的效果圖,對于下面這兩張美女圖:


如果我們只通過簡單的縮放來生成縮略圖,那么在網(wǎng)頁上的布局效果為:

如果我們通過先剪切后縮放的效果來生成縮略圖,那么在網(wǎng)頁上布局的效果為:

可以看到通過第二種方式生成的縮略圖布局要漂亮一些,但是會損失圖片的信息。因此,兩種方式各有優(yōu)劣。所以在設(shè)計的時候就設(shè)計為能夠讓用戶靈活配置。
對于有些網(wǎng)友反映的gif動畫經(jīng)過縮放以后就不能動了,這個問題的主要原因是因為Java SDK 1.4和1.5版本的ImageIO類只能讀gif格式的文件,而不能寫gif格式的文件,因此,對于gif格式的文件,生成的縮略圖只能用png格式代替,在我的設(shè)計中,我準(zhǔn)備讓bmp格式的文件也讓png格式代替,因為png格式生成的文件更小,而且也不損失圖片質(zhì)量。至于Java SDK 1.4和1.5版不支持寫gif格式的文件,可以查看Java文檔,下面是截圖:

最新推出的Java SDK 6是可以寫gif格式的文件的,因此如果要解決這個問題,可以使用最新的JDK,下面是文檔截圖:

下面是我寫的生成縮略圖和生成驗證碼的ImageUtil類的源代碼:
package com.xkland.util;

import javax.imageio.ImageIO;

import java.awt.image.BufferedImage;
import java.io.File;
import java.awt.Image;
import java.awt.Graphics2D;
import java.util.Random;
import java.awt.Font;

 public class ImageUtil {
private String sourceDir; // 圖片的存放路徑
private String destinationDir; // 縮略圖的存放路徑
private String mode; // 生成縮略圖的模式,可選ScaleOnly或ClipAndScale
private String width; // 縮略圖的寬度
private String height; // 縮略圖的高度
private String characterStorage; // 用來生成驗證碼的字符倉庫
// 以下代碼段是為了使用Spring注入屬性
public void setCharacterStorage(String characterStorage) {
this .characterStorage = characterStorage;
}
public void setDestinationDir(String destinationDir) {
this .destinationDir = destinationDir;
}
public void setHeight(String height) {
this .height = height;
}
public void setMode(String mode) {
this .mode = mode;
}
public void setSourceDir(String sourceDir) {
this .sourceDir = sourceDir;
}
public void setWidth(String width) {
this .width = width;
}
// 生成縮略圖的方法,默認(rèn)縮略圖的文件名和原圖相同,存放路徑不同
public void createMicroImage(String fileName)
 throws Exception {
// 判斷sourceDir的格式是否為以"\"結(jié)尾,并生成完整的路徑
String sourceFileName;
String destinationFileName;
 if (sourceDir.lastIndexOf( ‘ \\ ‘ ) != (sourceDir.length() - 1 )) {
sourceFileName = sourceDir + " \\ " + fileName;
destinationFileName = destinationDir + " \\ " + fileName;
 } else {
sourceFileName = sourceDir + fileName;
destinationFileName = destinationDir + fileName;
}
// 創(chuàng)建文件,并判斷原文件是否存在
File sourceFile = new File(sourceFileName);
 if ( ! sourceFile.exists()) {
throw new Exception();
}
// 根據(jù)擴(kuò)展名判斷原文件的格式
String extension = fileName.substring(fileName.lastIndexOf( ‘ . ‘ ) + 1 );
if ( ! extension.equalsIgnoreCase( " jpg " ) && ! extension.equalsIgnoreCase( " bmp " )
 && ! extension.equalsIgnoreCase( " gif " ) && ! extension.equalsIgnoreCase( " png " )) {
throw new Exception();
}
// 判斷縮略圖的寬度和高度是否正確,如果不能正確解析則拋出異常
int destinationWidth = Integer.parseInt(width);
int destinationHeight = Integer.parseInt(height);
// 判斷縮放模式是否正確,如果配置錯誤,則拋出異常
if ( ! mode.equalsIgnoreCase( " ScaleOnly " )
 && ! mode.equalsIgnoreCase( " ClipAndScale " )) {
throw new Exception();
}
// 讀取圖像文件,并創(chuàng)建BufferedImage對象,如果不能讀取,則拋出異常
BufferedImage image = null ;
image = ImageIO.read(sourceFile);
 if (image == null ) {
throw new Exception();
}
// 獲取原圖像文件的高度和寬度
int sourceWidth = image.getWidth();
int sourceHeight = image.getHeight();
// 生成縮略圖
if (mode.equalsIgnoreCase( " ScaleOnly " )) {
BufferedImage destinationImage;
 if (( float )sourceWidth / destinationWidth > ( float )sourceHeight / destinationHeight) {
Image tempImage = image.getScaledInstance(destinationWidth, ( int )(destinationWidth * (( float )sourceHeight / sourceWidth)), Image.SCALE_DEFAULT);
destinationImage = new BufferedImage(destinationWidth, ( int )(destinationWidth * (( float )sourceHeight / sourceWidth)),BufferedImage.TYPE_INT_RGB);
Graphics2D graphics = destinationImage.createGraphics();
graphics.drawImage(tempImage, 0 , 0 , null );
 } else {
Image tempImage = image.getScaledInstance(( int )(destinationHeight * (( float )sourceWidth / sourceHeight)), destinationHeight, Image.SCALE_DEFAULT);
destinationImage = new BufferedImage(( int )(destinationHeight * (( float )sourceWidth / sourceHeight)), destinationHeight,BufferedImage.TYPE_INT_RGB);
Graphics2D graphics = destinationImage.createGraphics();
graphics.drawImage(tempImage, 0 , 0 , null );
}
// 如果是bmp或者gif,則縮略圖為png格式
if (extension.equalsIgnoreCase( " bmp " ) || extension.equalsIgnoreCase( " gif " )) {
extension = " png " ;
destinationFileName = destinationFileName.substring( 0 , destinationFileName.lastIndexOf( ‘ . ‘ )) + " . " + extension;
}
File destinationFile = new File(destinationFileName);
ImageIO.write(destinationImage, extension, destinationFile);
 } else {
BufferedImage destinationImage;
 if (( float )sourceWidth / destinationWidth > ( float )sourceHeight / destinationHeight) {
// 先裁減
int x = sourceWidth - ( int )(sourceHeight * (( float )destinationWidth / destinationHeight));
Image clipedImage = image.getSubimage(( int )( 0.5 * x), 0 , ( int )(sourceHeight * (( float )destinationWidth / destinationHeight)), sourceHeight);
// 后縮放
Image scaledImage = clipedImage.getScaledInstance(destinationWidth, destinationHeight, Image.SCALE_DEFAULT);
destinationImage = new BufferedImage(destinationWidth, destinationHeight,BufferedImage.TYPE_INT_RGB);
Graphics2D graphics = destinationImage.createGraphics();
graphics.drawImage(scaledImage, 0 , 0 , null );
 } else {
// 先裁減
int y = sourceHeight - ( int )(sourceWidth * (( float )destinationHeight / destinationWidth));
Image clipedImage = image.getSubimage( 0 , ( int )( 0.5 * y), sourceWidth, ( int )(sourceWidth * (( float )destinationHeight / destinationWidth)));
// 后縮放
Image scaledImage = clipedImage.getScaledInstance(destinationWidth, destinationHeight, Image.SCALE_DEFAULT);
destinationImage = new BufferedImage(destinationWidth, destinationHeight,BufferedImage.TYPE_INT_RGB);
Graphics2D graphics = destinationImage.createGraphics();
graphics.drawImage(scaledImage, 0 , 0 , null );
}
// 如果是bmp或者gif,則縮略圖為png格式
if (extension.equalsIgnoreCase( " bmp " ) || extension.equalsIgnoreCase( " gif " )) {
extension = " png " ;
destinationFileName = destinationFileName.substring( 0 , destinationFileName.lastIndexOf( ‘ . ‘ )) + " . " + extension;
}
File destinationFile = new File(destinationFileName);
ImageIO.write(destinationImage, extension, destinationFile);
}
}
// 生成驗證碼的方法
public BufferedImage createValidateImage() {
BufferedImage validateImage = new BufferedImage( 80 , 20 ,BufferedImage.TYPE_INT_RGB);
Graphics2D graphics = validateImage.createGraphics();
// 從characterStorage中隨機(jī)抽取四個字符生成驗證碼
int length = characterStorage.length();
char [] chars = new char [ 4 ];
Random rand = new Random();
 for ( int i = 0 ; i < 4 ; i ++ ) {
int index = rand.nextInt(length);
chars[i] = characterStorage.charAt(index);
}
String str = new String(chars);
graphics.setFont( new Font( " 宋體 " ,Font.BOLD, 18 ));
System.out.println(graphics.getFont());
graphics.drawString(str, 2 , 16 );
// 隨機(jī)畫干擾直線
for ( int i = 0 ; i < 5 ; i ++ ) {
int x1 = rand.nextInt( 80 );
int y1 = rand.nextInt( 20 );
int x2 = rand.nextInt( 80 );
int y2 = rand.nextInt( 20 );
graphics.drawLine(x1, y1, x2, y2);
}
return validateImage;
}
}
寫得比較倉促,沒有進(jìn)行重構(gòu),所以比較難看一點(diǎn)。下面是測試用例的代碼:
package com.xkland.util;

import junit.framework.TestCase;
import javax.imageio.ImageIO;
import java.awt.image.BufferedImage;
import java.io.File;

 public class ImageUtilTest extends TestCase {

 public void testCreateMicroImage() throws Exception {
ImageUtil util = new ImageUtil();
util.setSourceDir( " E:\\ " );
util.setDestinationDir( " F:\\ " );
util.setWidth( " 100 " );
util.setHeight( " 100 " );
// 以僅縮放的形式生成縮略圖
util.setMode( " ScaleOnly " );
// 橫圖像
util.createMicroImage( " 001.bmp " );
// 豎圖像
util.createMicroImage( " 002.jpg " );
// 以先裁減后縮放的形式生成縮略圖
util.setDestinationDir( " G:\\ " );
util.setMode( " ClipAndScale " );
// 橫圖像
util.createMicroImage( " 001.bmp " );
// 豎圖像
util.createMicroImage( " 002.jpg " );
}
 public void testCreateValidateImage() throws Exception {
ImageUtil util = new ImageUtil();
util.setCharacterStorage( " ABCDEFGHIJKLMNOPQRSTUVWXYZ北冥有魚其名為鯤鯤之大不知其幾千里也化而為鳥其名為鵬鵬之背不知其幾千里也怒而飛其翼若垂天之云是鳥也海運(yùn)則將徙于南冥南冥者天池也 " );
BufferedImage image = util.createValidateImage();
ImageIO.write(image, " jpg " , new File( " F:\\validateImage.jpg " ));
}
}
運(yùn)行該測試用例,可以成功的生成縮略圖,并且可以生成驗證碼,生成的驗證碼如下圖:
 把以上代碼再修改再完善,就可以創(chuàng)建更漂亮一點(diǎn)的圖形了。
為了把上面這個ImageUtil類讓SpringSide管理起來,并進(jìn)行靈活的配置,可以在src\main\resources\spring目錄下建立beans.xml文件,并如下配置:
<? xml version="1.0" encoding="UTF-8" ?>
<! DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN 2.0//EN" "http://www./dtd/spring-beans-2.0.dtd" >
< beans >
< bean id ="imageUtil" class ="com.xkland.util.ImageUtil" >
< property name ="sourceDir" >
< value > E:\ </ value >
</ property >
< property name ="destinationDir" >
< value > F:\ </ value >
</ property >
< property name ="width" >
< value > 100 </ value >
</ property >
< property name ="height" >
< value > 100 </ value >
</ property >
< property name ="mode" >
< value > ScaleOnly </ value >
</ property >
< property name ="characterStorage" >
< value > ABCDEFGHIJKLMNOPQRSTUVWXYZ北冥有魚其名為鯤鯤之大不知其幾千里也化而為鳥其名為鵬鵬之背不知其幾千里也怒而飛其翼若垂天之云是鳥也海運(yùn)則將徙于南冥南冥者天池也 </ value >
</ property >
</ bean >
</ beans >
最后,我們再來看看url重寫的問題。俗話說得好:“會者不難,難者不會”,剛開始我為了實現(xiàn)文章開頭所說的url重寫功能,嘗試采用的是配置Servlet映射的方法,但是怎么都不成功,后來才想到使用Filter來實現(xiàn)。有時候開源的東西會直接影響人的思路,比如Struts 1.x采用的就是配置Servlet映射的方法,而到了2.0,也改成Filter了。
在Filter中實現(xiàn)url重寫比較簡單,無非就是分析字符串和替換字符串,這里我就不列代碼了。只有想不到,沒有做不到,想辦法實現(xiàn)我們設(shè)計的功能,這便是技術(shù)穿刺的作用。
|