京山游侠

专注技术,拒绝扯淡
posts - 50, comments - 868, trackbacks - 0, articles - 0
  BlogJava :: 首页 :: 新随笔 :: 联系 :: 聚合  :: 管理

本篇讨论的问题是对项目中遇到的难题进行技术穿刺。

做过项目的人都知道,在构思完一个项目的功能之后,紧接着的事情就是考虑这些构思的功能如何实现,对于自己不熟悉的领域,要进行技术穿刺。我的穿刺方法为先查找有无比较好的开源组件可用,如果没有,就查找相关的文档,自己编写和测试代码。

在这一篇,我主要解决三个问题。
1、解决字符串加密的问题,在前面一篇中,我们设计用户模块的时候,准备将用户的密码字段以MD5加密的方式保存,因此,这里需要写一个对字符串加密生成MD5字符串的方法;
2、解决生成图像缩略图和生成验证码的问题;
3、解决url重写的问题,之所以要用到url重写,主要是为了让用户在访问自己的主页时,可以使用http://www.xkland.com/username或者http://username.xkland.com这样的形式,而不是像http://www.xkland.com/index.jsp?username=xxx这样难看的形式。

需要说明的是,要解决上面的三个问题,不是没有开源的东西可用,而是我觉得每次都要整合不同的组件是在是太麻烦,而我们需要的功能也不是很复杂,我们不需要太通用的东西,只要能够解决这里特定的问题就行了,因此不如自己动手实现,同时还可以获得技术上的提高。

首先来看看MD5加密的问题,JDK中本来提供有数据加密的支持,其中java.security.MessageDigest类就可以实现MD5的加密,但是,加密后生成的数据是byte[]类型的,这里只需要写一个方法将它转换为字符串就行,代码如下:

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) {
   
  }

 }


}



运行测试用例,输出结果为:

900150983CD24FB0D6963F7D28E17F72


再来说说关于图像缩略图生成的问题,我准备将它设置为一个可以让Spring管理的类,简单的说,可以利用Spring的配置文件来设置该类的一些属性,比如原图像保存的目录和目标图像保存的目录,生成的缩略图的大小,生成缩略图的方式。这里特别需要说明的就是这个生成缩略图的方式,我们即可以指定它只简单的执行缩放,也可以指定它进行剪裁以后再缩放。为什么要这么设计,请大家看看如下的效果图,对于下面这两张美女图:
32.jpg

33.jpg


如果我们只通过简单的缩放来生成缩略图,那么在网页上的布局效果为:
38.JPG

如果我们通过先剪切后缩放的效果来生成缩略图,那么在网页上布局的效果为:
39.JPG

可以看到通过第二种方式生成的缩略图布局要漂亮一些,但是会损失图片的信息。因此,两种方式各有优劣。所以在设计的时候就设计为能够让用户灵活配置。

对于有些网友反映的gif动画经过缩放以后就不能动了,这个问题的主要原因是因为Java SDK 1.4和1.5版本的ImageIO类只能读gif格式的文件,而不能写gif格式的文件,因此,对于gif格式的文件,生成的缩略图只能用png格式代替,在我的设计中,我准备让bmp格式的文件也让png格式代替,因为png格式生成的文件更小,而且也不损失图片质量。至于Java SDK 1.4和1.5版不支持写gif格式的文件,可以查看Java文档,下面是截图:
30.JPG

最新推出的Java SDK 6是可以写gif格式的文件的,因此如果要解决这个问题,可以使用最新的JDK,下面是文档截图:
31.JPG

下面是我写的生成缩略图和生成验证码的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;
import  javax.servlet.http.HttpSession;

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;
    }

    
    
// 生成缩略图的方法,默认缩略图的文件名和原图相同,存放路径不同
     public   void  createMicroImage(String fileName)
    
throws  Exception {
        
// 判断sourceDir的格式是否为以"\"结尾,并生成完整的路径
        String sourceFileName;
        String destinationFileName;
        
if (sourceDir.lastIndexOf( ' \\ ' !=  (sourceDir.length() - 1 )) {
            sourceFileName 
=  sourceDir  +   " \\ "   +  fileName;
            destinationFileName 
=  destinationDir  +   " \\ "   +  fileName;
        }
else {
            sourceFileName 
=  sourceDir  +  fileName;
            destinationFileName 
=  destinationDir  +  fileName;
        }

        
        
// 创建文件,并判断原文件是否存在
        File sourceFile  =   new  File(sourceFileName);
        
if ( ! sourceFile.exists()) {
            
throw   new  Exception();
        }

        
// 根据扩展名判断原文件的格式
        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();
        }

        
        
// 读取图像文件,并创建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(HttpSession session) {
        BufferedImage validateImage 
=   new  BufferedImage( 80 , 20 ,BufferedImage.TYPE_INT_RGB);
        Graphics2D graphics 
=  validateImage.createGraphics();
        
        
// 从characterStorage中随机抽取四个字符生成验证码
         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);
        
// 将字符串保存到Session中,以便于验证
        session.setAttribute( " validateString " , str);
        
// 画字符串到图片中
        graphics.setFont( new  Font( " 宋体 " ,Font.BOLD, 18 ));
        graphics.drawString(str, 
2 16 );
        
// 随机画干扰直线
         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;
    }

}


写得比较仓促,没有进行重构,所以比较难看一点。下面是测试用例的代码:

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北冥有鱼其名为鲲鲲之大不知其几千里也化而为鸟其名为鹏鹏之背不知其几千里也怒而飞其翼若垂天之云是鸟也海运则将徙于南冥南冥者天池也 " );
        BufferedImage image 
=  util.createValidateImage();
        ImageIO.write(image, 
" jpg " new  File( " F:\\validateImage.jpg " ));
    }


}


运行该测试用例,可以成功的生成缩略图,并且可以生成验证码,生成的验证码如下图:
40.jpg
把以上代码再修改再完善,就可以创建更漂亮一点的图形了。

为了把上面这个ImageUtil类让SpringSide管理起来,并进行灵活的配置,可以在src\main\resources\spring目录下建立beans.xml文件,并如下配置:

<? xml version="1.0" encoding="UTF-8" ?>
<! DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN 2.0//EN" "http://www.springframework.org/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北冥有鱼其名为鲲鲲之大不知其几千里也化而为鸟其名为鹏鹏之背不知其几千里也怒而飞其翼若垂天之云是鸟也海运则将徙于南冥南冥者天池也 </ value >
        
</ property >
    
</ bean >
</ beans >


最后,我们再来看看url重写的问题。俗话说得好:“会者不难,难者不会”,刚开始我为了实现文章开头所说的url重写功能,尝试采用的是配置Servlet映射的方法,但是怎么都不成功,后来才想到使用Filter来实现。有时候开源的东西会直接影响人的思路,比如Struts 1.x采用的就是配置Servlet映射的方法,而到了2.0,也改成Filter了。

在Filter中实现url重写比较简单,无非就是分析字符串和替换字符串,这里我就不列代码了。只有想不到,没有做不到,想办法实现我们设计的功能,这便是技术穿刺的作用。


评论

# zsvsdxcw  回复  更多评论   

2007-01-11 23:46 by zsvsdxcw
[URL=http://umclazts.com]vuxovteu[/URL] ezyquqvk http://vngphjrs.com zsjhmgtc kewcuwpw <a href="http://kdwsxrkk.com">quixkxnw</a>

# re: SpringSide开发实战(五):兵马未动,粮草先行  回复  更多评论   

2007-01-12 00:18 by zz[匿名]
你这跑题可跑的也太远了....这节跟springside没啥关系

# re: SpringSide开发实战(五):兵马未动,粮草先行  回复  更多评论   

2007-01-12 00:31 by billbai
看到你说到的:将它设置为一个可以让Spring管理的类,简单的说,可以利用Spring的配置文件来设置该类的一些属性,比如原图像保存的目录和目标图像保存的目录,生成的缩略图的大小,生成缩略图的方式。这里特别需要说明的就是这个生成缩略图的方式,我们即可以指定它只简单的执行缩放,也可以指定它进行剪裁以后再缩放。

功能上我都做到了,失真的问题我也解决了。就是更改下你说的让Sping来管理,然后还可以配置。可以交流下 QQ:285853807
可以的话交换个链接 我已经把你加了。

# eexlkmwn  回复  更多评论   

2007-01-12 00:47 by eexlkmwn
egqhsqbd http://uricvuds.com nbbwhpcs mdfiyxlr <a href="http://sdldxhfm.com">llbctmzr</a> [URL=http://ucuflorh.com]xoihbumf[/URL]

# re: SpringSide开发实战(五):兵马未动,粮草先行  回复  更多评论   

2007-01-12 10:42 by 搂住sb
temp = (java.lang.Integer.toHexString(b[n] & 0XFF));

# re: SpringSide开发实战(五):兵马未动,粮草先行  回复  更多评论   

2007-01-12 13:32 by 海边沫沫
@billbai
好的,我已经加你为好友了。

# re: SpringSide开发实战(五):兵马未动,粮草先行  回复  更多评论   

2007-01-12 13:39 by 海边沫沫
@搂住sb
Integer.toHexString虽好,但是存在以下问题:
1、生成的字符串不一定都是两个字符,比如15,转换为HexString后为"F",而不是"0F",这样会影响我们生成的MD5String的长度,使其不是32个字符
2、Integer.toHexString只能接受int参数,所以需要把byte类型转化为int类型,影响效率
3、你使用Integer.toHexString后,生成了16个字符串,然后把这16个字符串连接成为一个字符串,更加影响效率;我上面的方法先生成的是32个字符,然后构造字符串,比你的方法快多了

# re: SpringSide开发实战(五):兵马未动,粮草先行  回复  更多评论   

2007-01-12 20:59 by 海边沫沫
@zz[匿名]
怎么会没有关系呢,在SpringSide的论坛上可是有人提这样的问题哦。
框架不是万能的嘛,我们平时使用开源框架而受益,当然也应该发挥自己的力量也做一点贡献啦。说不定我上面的代码有一天被SpringSide吸收了呢?当然,它们专业的团队肯定会做的更好。

# re: SpringSide开发实战(五):兵马未动,粮草先行  回复  更多评论   

2007-01-17 00:59 by suwu
非常好,获益良多

# re: SpringSide开发实战(五):兵马未动,粮草先行  回复  更多评论   

2007-02-02 11:25 by raptor
太棒了,作者的文章太棒了,解决了我的大问题,实在是太感谢了,感谢

# re: SpringSide开发实战(五):兵马未动,粮草先行  回复  更多评论   

2007-02-27 10:40 by zj
写得很好!

# re: SpringSide开发实战(五):兵马未动,粮草先行  回复  更多评论   

2007-07-30 16:20 by 小白之家
好东西,不能不回帖

# re: SpringSide开发实战(五):兵马未动,粮草先行  回复  更多评论   

2007-08-14 14:46 by 同声翻译公司
您好,我们公司是一家中国境内的专业翻译公司,从事各专业翻译服务,包括笔译、口译、同声传译和同声传译设备租赁等。我们需要招聘兼职翻译、同传译员和外籍英文校对人员,不知道是否有时间。

希望有机会合作.
郭先生

北京华译网翻译公司
中国专家翻译网
地址:北京海淀区太阳园17号楼405室(北三环西路大钟寺东侧)邮编:100098
电话:010-82115891 82115892 传真010-82130386
上海:上海漕溪北路38号20G (东方商厦后面实业公寓南楼)
电话:021-34240860 34240925 传真:021-34240925

同传租赁 同声传译翻译公司 同声传译设备租赁 同声传译 同声传译设备租赁 同传租赁 北京同传设备租赁 上海同声传译设备租赁 同声传译

只有注册用户登录后才能发表评论。


网站导航: