零基础学Struts
上QQ阅读APP看书,第一时间看更新

15.5 完成用户注册模块

15.5.1 整合Struts 2和Spring

整合Spring之前首先需要为Web应用添加Spring所需的JAR文件,然后修改“web.xml”文件。通过添加一个Listener,使得Web应用启动时会自动查找WEB-INF目录下的“applicationContext.xml”配置文件,并根据该配置文件来创建Spring容器。同时安装Struts 2的Spring插件,代码如下所示。

        <? xml version="1.0" encoding="UTF-8"? >
        <web-app version="2.5" xmlns="http://java.sun.com/xml/ns/javaee"
            xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
            xsi:schemaLocation="http://java.sun.com/xml/ns/javaee
            http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd">
            <! --定义核心Filter FilterDispatcher -->
            <filter>
                    <! -- 定义核心Filter的名称 -->
                    <filter-name>struts2</filter-name>
                    <! --定义核心Filter的实现类 -->
                    <filter-class>
                            org.apache.struts2.dispatcher.FilterDispatcher
                    </filter-class>
            </filter>
            <filter-mapping>
                    <! --核心Filter的名称 -->
                    <filter-name>struts2</filter-name>
                    <! --使用该核心Filter来接受所有的Web请求 -->
                    <url-pattern>/*</url-pattern>
            </filter-mapping>
            <listener>
                    <listener-class>
                            org.springframework.web.context.ContextLoaderListener
                    </listener-class>
            </listener>
        </web-app>

15.5.2 创建用户注册页

新建用户注册页,该页面中包含一个表单,用来输入注册用户信息,代码如下所示。

        <body>
          <! -- 注册表单 -->
          <center>
          <h2>注册页面</h2>
          <div id="result" style="color: red"></div>
          <s:actionerror/>
          <s:form action="register" validate="true">
                <s:textfield name="username" label="用户名" onblur="validateName(); "></s:textfield>
                <s:password name="password" label="密码"></s:password>
                <s:password name="repassword" label="确认密码"></s:password>
                <s:textfield name="age" label="年龄"></s:textfield>
                <s:textfield name="birth" label="出生日期"></s:textfield>
                <s:textfield name="email" label="邮箱地址"></s:textfield>
                <s:textfield name="yanzhengma" label="验证码"></s:textfield>
                <s:submit value="注册"></s:submit>
          </s:form>
          </center>
        </body>

15.5.3 创建用户注册控制器

新建业务控制器RegisterAction,该Action接受用户注册页传递的参数,并将接受的参数设置到User实例中,然后调用业务逻辑组件保存该用户,代码如下所示。

    package net.hncu.action;
    import java.util.Date;
    import java.util.Map;
    import net.hncu.po.User;
    import net.hncu.service.UserService;
    import com.opensymphony.xwork2.ActionContext;
    import com.opensymphony.xwork2.ActionSupport;
    public class RegisterAction extends ActionSupport{
          //用户名
          private String username;
          //密码
          private String password;
          //重复密码
          private String repassword;
          //年龄
          private int age;
          //生日
          private Date birth;
          //email地址
          private String email;
          private UserService userService;
          public void setUserService(UserService userService) {
                this.userService = userService;
          }
          //各属性的settergetter方法
          public String getYanzhengma() {
                return yanzhengma;
          }
          public void setYanzhengma(String yanzhengma) {
                this.yanzhengma = yanzhengma;
          }
          public String getUsername() {
                return username;
          }
          public void setUsername(String username) {
                this.username = username;
          }
          public String getPassword() {
                return password;
          }
          public void setPassword(String password) {
                this.password = password;
          }
          public String getRepassword() {
                return repassword;
          }
          public void setRepassword(String repassword) {
                this.repassword = repassword;
          }
          public int getAge() {
                return age;
          }
          public void setAge(int age) {
                this.age = age;
          }
          public Date getBirth() {
                return birth;
          }
          public void setBirth(Date birth) {
                this.birth = birth;
          }
          public String getEmail() {
                return email;
          }
          public void setEmail(String email) {
                this.email = email;
          }
          public String execute() throws Exception {
                //User实例
                User user = new User();
                //设置user属性值
                user.setUsername(username);
                user.setPassword(password);
                user.setAge(age);
                user.setBirth(birth);
                user.setEmail(email);
                //添加用户失败
                if(! userService.add(user)){
                        addActionError("注册失败");
                }else {
                        //添加用户成功
                        return SUCCESS;
                }
                return ERROR;
          }
    }

15.5.4 配置用户注册控制器

在Spring配置文件中配置业务控制器registerAction,并为其注入业务逻辑组件,代码如下所示。

    <! -- 创建RegisterAction实例 -->
    <bean id="registerAction" class="net.hncu.action.RegisterAction" scope="prototype">
        <property name="userService" ref="userService"></property>
    </bean>

在“struts.xml”文件中配置RegisterAction,并定义处理结果与视图资源之间的关系,代码如下所示。

    <action name="register" class="registerAction">
          <! -- 定义处理结果与视图资源之间的关系-->
          <result name="input">/register.jsp</result>
          <result name="success">/login.jsp</result>
          <result name="error">/register.jsp</result>
    </action>

15.5.5 创建校验规则文件

新建校验规则文件,其中包含如下输入校验规则。

(1)用户名、密码、确认密码必须输入。

(2)用户名只能是数字或者字母,长度在6~20之间。

(3)密码、确认密码必须是数字或者字母,长度在6~20之间。

(4)密码和确认密码必须相同。

(5)年龄必须为整数而且必须是有效的年龄值。

(6)出生日期必须为正确的日期格式,如1988-01-03,而且只能是1900-1-1~2010-1-1之间。

(7)邮箱地址必须为合法的邮箱地址。

通过如上校验规则,编写校验规则文件,代码如下所示。

    <? xml version="1.0" encoding="UTF-8"? >
    <! DOCTYPE validators PUBLIC
            "-//OpenSymphony Group//XWork Validator 1.0.2//EN"
            "http://www.opensymphony.com/xwork/xwork-validator-1.0.2.dtd">
    <validators>
          <validator type="requiredstring">
                <param name="fieldName">username</param>
                <param name="trim">true</param>
                <message>必须输入用户名</message>
          </validator>
          <validator type="regex">
                <param name="fieldName">username</param>
                <param name="expression"><! [CDATA[(\w{6,20})]]></param>
                <message>用户名长度必须在620之间</message>
          </validator>
          <validator type="requiredstring">
                <param name="fieldName">password</param>
                <param name="trim">true</param>
                <message>必须输入密码</message>
          </validator>
          <validator type="regex">
                <param name="fieldName">password</param>
                <param name="expression"><! [CDATA[(\w{6,20})]]></param>
                <message>密码长度必须在620之间</message>
          </validator>
          <validator type="requiredstring">
                <param name="fieldName">repassword</param>
                      <param name="trim">true</param>
                      <message>必须输入确认密码</message>
                </validator>
                <validator type="regex">
                      <param name="fieldName">repassword</param>
                      <param name="expression"><! [CDATA[(\w{6,20})]]></param>
                      <message>确认密码长度必须在620之间</message>
                </validator>
                <validator type="fieldexpression">
                      <param name="fieldName">repassword</param>
                      <param name="expression"><! [CDATA[(repassword==password)]]></param>
                      <message>密码和确认密码必须一致</message>
                </validator>
                <validator type="int">
                      <param name="fieldName">age</param>
                      <param name="min">1</param>
                      <param name="max">120</param>
                      <message>年龄必须在${min}${max}之间</message>
                </validator>
                <validator type="date">
                      <param name="fieldName">birth</param>
                      <param name="min">1900-01-01</param>
                      <param name="max">2010-01-01</param>
                      <message>出生日期必须在1900-01-012010-01-01之间</message>
                </validator>
                <validator type="email">
                      <param name="fieldName">email</param>
                      <message>请输入有效的电子邮箱地址</message>
                </validator>
          </validators>

15.5.6 创建图形验证码生成类

为了防止恶意用户的注册以及暴力破解,需要为注册系统添加图形验证码检测功能。用户只有输入正确的验证码才能进行注册,否则不能注册。

为了生成图形验证码,首先添加一个图形验证码生成类,通过调用该类中的方法即可生成图形验证码,代码如下所示。

        package net.hncu.util;
        import java.awt.Color;
        import java.awt.Font;
        import java.awt.Graphics2D;
        import java.awt.image.BufferedImage;
        import java.io.File;
        import java.util.LinkedList;
        import java.util.Random;
        import javax.imageio.ImageIO;
        import javax.servlet.http.HttpServletResponse;
        import com.sun.image.codec.jpeg.JPEGCodec;
    import com.sun.image.codec.jpeg.JPEGImageEncoder;
    public class Yanzhengma {
          public String sRand = "";
          public Yanzhengma() {
          }
          // 给定范围获得随机颜色
          public 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);
          }
          //创建图像格式为jpg类型,默认长和宽
          public BufferedImage getBufferedImage(String content) {
                int width = 60, height = 20;
                return getBufferedImage(content, width, height);
          }
          //创建图像格式为jpg类型,接受长和宽两个参数
          public BufferedImage getBufferedImage(String content, int width, int height) {
                // 实例化BufferedImage
                BufferedImage image = new BufferedImage(width, height,
                          BufferedImage.TYPE_INT_RGB);
                // 获取图形上下文
                Graphics2D g = image.createGraphics();
                // 生成随机类
                Random random = new Random();
                // 设定背景色
                g.setColor(getRandColor(200, 250));
                g.fillRect(0, 0, width, height);
                // 设定字体
                g.setFont(new Font("Times New Roman", Font.PLAIN, 18));
                // 设置字体颜色
                g.setColor(Color.black);
                g.drawString(content, 10, 15);
                g.dispose();
                //返回图片
                return image;
          }
              //返回一个4位的验证码
              public String getContent() throws InterruptedException {
                    String content = "";
                    for (int i = 0; i < 4; i++) {
                            content += getChar();
                            //利用休眠以控制字符的重复问题
                            Thread.sleep(new Random().nextInt(10) + 10);
                    }
                    return content;
              }
              //获取随机字符
              public char getChar() {
                    Random random = new Random();
                    char ch = '0' ;
                    LinkedList ls = new LinkedList();
                    for (int i = 0; i < 10; i++) {// 0-9
                            ls.add(String.valueOf(48 + i));
                    }
                    for (int i = 0; i < 26; i++) {// A-Z
                            ls.add(String.valueOf(65 + i));
                    }
                    for (int i = 0; i < 26; i++) {// a-z
                            ls.add(String.valueOf(97 + i));
                    }
                    int index = random.nextInt(ls.size());
                    if (index > (ls.size() -1)) {
                            index = ls.size() -1;
                    }
                    ch = (char) Integer.parseInt(String.valueOf(ls.get(index)));
                    return ch;
              }
        }

15.5.7 生成图形验证码

前面已经创建好了图形验证码生成类,现在只需新建一个Servlet,由该Servlet调用图形验证码生成类中的方法来生成图形验证码,代码如下所示。

        package net.hncu.servlet;
        import java.awt.Color;
        import java.awt.Font;
        import java.awt.image.BufferedImage;
        import java.io.IOException;
        import java.util.Random;
        import javax.imageio.ImageIO;
        import javax.servlet.ServletException;
        import javax.servlet.http.HttpServlet;
        import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;
    import javax.servlet.http.HttpSession;
    import net.hncu.util.Yanzhengma;
    public class YanZhengMaServlet extends HttpServlet{
          private Font mFont = new Font("Arial Black", Font.PLAIN, 16);
              public void init() throws ServletException
              {
                  super.init();
              }
              public void service(HttpServletRequest request, HttpServletResponse response)
    throws ServletException, IOException
              {
              //阻止页面内容被缓存
                  response.setHeader("Pragma", "No-cache");
                  response.setHeader("Cache-Control", "no-cache");
                  response.setDateHeader("Expires", 0);
                  //设置生成的图片的大小
                  int width=100, height=18;
                  Yanzhengma yanzhengma = new Yanzhengma();
                  BufferedImage image = null;
                  String text = "";
                  //找到随机数组成的字符内容
                  try {
                            text = yanzhengma.getContent();
                  } catch (InterruptedException e) {
                            e.printStackTrace();
                  }
                  image = yanzhengma.getBufferedImage(text,100,18);
                  //输出图片
                  ImageIO.write(image, "JPEG", response.getOutputStream());
                  //将验证码的值设置到session范围中
                  HttpSession session = request.getSession();
                  session.setAttribute("yanzhengma", text);
              }
    }

YanZhengMaServlet负责将随机生成的字符串写入图片中,同时将字符串设置到session范围变量中。在“web.xml”文件中配置YanZhengMaServlet,代码如下所示。

    <servlet>
          <servlet-name>img</servlet-name>
          <servlet-class>net.hncu.servlet.YanZhengMaServlet</servlet-class>
    </servlet>
        <servlet-mapping>
              <servlet-name>img</servlet-name>
              <url-pattern>/Yanzhengma</url-pattern>
        </servlet-mapping>

在注册页面中添加代码用来显示该图形验证码。同时提供刷新链接,这样用户可以随时随心刷新图形验证码,代码如下所示。

        验证码:<img src="Yanzhengma"/>
        <a href="#" onClick="history.go(0) ">单击刷新</a>

因为添加了图形验证码,所以必须在用户注册控制器中对其输入的验证码进行判断,如果输入错误则返回用户注册页,如果正确则执行注册,代码如下所示。

        package net.hncu.action;
        import java.util.Date;
        import java.util.Map;
        import net.hncu.po.User;
        import net.hncu.service.UserService;
        import com.opensymphony.xwork2.ActionContext;
        import com.opensymphony.xwork2.ActionSupport;
        public class RegisterAction extends ActionSupport{
              ...
              ...
              //验证码
              private String yanzhengma;
              public String getYanzhengma() {
                    return yanzhengma;
              }
              public void setYanzhengma(String yanzhengma) {
                    this.yanzhengma = yanzhengma;
              }
              ...
              public String execute() throws Exception {
                    Map session = ActionContext.getContext().getSession();
                    //校验验证码是否输入正确
                    if(yanzhengma.equalsIgnoreCase((String)session.get("yanzhengma"))) {
                          //User实例
                          User user = new User();
                            //设置user属性值
                            user.setUsername(username);
                            user.setPassword(password);
                            user.setAge(age);
                            user.setBirth(birth);
                            user.setEmail(email);
                            //添加用户失败
                              if(! userService.add(user)){
                                      addActionError("注册失败");
                              }else {
                                      //添加用户成功
                                      return SUCCESS;
                              }
                      } else {
                              this.addActionError("验证码输入有误!");
                      }
                      return ERROR;
                }
          }

15.5.8 使用Ajax验证用户名是否被占用

在传统的Web应用中,需要填写所有注册信息并提交后,才能知道该用户名是否存在,用户体验非常差。使用Ajax技术可以使得用户输入完用户名后就能够提示用户输入的用户名是否被占用。

新建业务控制器ValidateNameAction,设置同注册表单相对应的属性,并添加其setter和getter方法,代码如下所示。

        package net.hncu.action;
        import net.hncu.service.UserService;
        import com.opensymphony.xwork2.ActionSupport;
        public class ValidateNameAction extends ActionSupport {
              //用户名
              private String username;
              //提示信息
              private String tip;
              private UserService userService;
              public void setUserService(UserService userService) {
                    this.userService = userService;
              }
              public String getUsername() {
                    return username;
              }
              public void setUsername(String user) {
                    this.username = user;
              }
              public String getTip() {
                    return tip;
              }
              public void setTip(String tip) {
                    this.tip = tip;
              }
              public String execute() {
                    //调用业务逻辑组件进行判断
                    if (userService.isRegister(username)) {
                            setTip("该用户名可以使用");
                    } else {
                              setTip("用户名已被占用,请重新输入");
                      }
                      return SUCCESS;
                }
          }

在Spring配置文件中配置业务控制器ValidateNameAction,并为其注入业务逻辑组件,代码如下所示。

        <! -- 创建ValidateNameAction实例 -->
        <bean id="validateNameAction" class="net.hncu.action.ValidateNameAction" scope="prototype">
            <property name="userService" ref="userService"></property>
        </bean>

在“struts.xml”文件中配置该业务控制器ValidateNameAction,并配置其Result的type类型的json。为了能够配置json类型的Result,必须配置包继承json-default,代码如下所示。

        <? xml version="1.0" encoding="UTF-8" ? >
        <! DOCTYPE struts PUBLIC
            "-//Apache Software Foundation//DTD Struts Configuration 2.0//EN"
            "http://struts.apache.org/dtds/struts-2.0.dtd">
        <! -- struts为配置文件根元素-->
        <struts>
              <constant name="struts.i18n.encoding" value="gb2312"></constant>
              <! -- Action必须放在指定的包名空间中-->
              <package name="struts2" extends="json-default">
                  <action name="validateName" class="validateNameAction">
                        <result type="json" name="success"></result>
                  </action>
              </package>
        </struts>

修改用户注册页面,配置当焦点离开该文本框将触发JavaScript的validateName()事件代码。validateName()方法将以异步的方式和服务器进行通信,并将返回的结果显示出来,代码如下所示。

        <%@ page language="java" import="java.util.*" pageEncoding="gb2312"%>
        <%@ taglib prefix="s" uri="/struts-tags" %>
        <! DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
        <html>
          <head>
            <title>注册页面</title>
            <script type="text/javascript" src="js/prototype.js"></script>
            <script type="text/javascript" src="js/json.js"></script>
            <script language="JavaScript">
                  function validateName()
                  {
                  //请求的地址
                  var url = ' validateName.action' ;
              var params = Form.Element.serialize(' username' );
              //创建Ajax.Request对象,对应于发送请求
              var myAjax = new Ajax.Request(
              url,
              {
              //请求方式:POST
              method:' post' ,
              //请求参数
              parameters:params,
              //指定回调函数
              onComplete: processResponse,
              //是否异步发送请求
              asynchronous:true
              });
              }
              function processResponse(request)
              {
                var action = request.responseText.parseJSON();
        $("result").innerHTML = action.tip;
              }
        </script>
        </head>
        <body>
        <! -- 注册表单 -->
        <center>
        <h2>注册页面</h2>
        <div id="result" style="color: red"></div>
        <s:actionerror/>
        <s:form action="register" validate="true">
              <s:textfield name="username" label="用户名" onblur="validateName(); "></s:textfield>
              <s:password name="password" label="密码"></s:password>
              <s:password name="repassword" label="确认密码"></s:password>
              <s:textfield name="age" label="年龄"></s:textfield>
              <s:textfield name="birth" label="出生日期"></s:textfield>
              <s:textfield name="email" label="邮箱地址"></s:textfield>
              <s:textfield name="yanzhengma" label="验证码"></s:textfield>
              <s:submit value="注册"></s:submit>
        </s:form>
        验证码:<img src="Yanzhengma" id="authImg"/>
        <a href="#" onClick="history.go(0) ">单击刷新</a>
        </center>
        </body>
    </html>

15.5.9 测试注册新用户

打开浏览器,运行用户登录页,如图15.2所示。从页面中可以看到一个注册表单以及一个图形验证码。

当输入一个错误的验证码进行注册时,页面会跳转回注册页面,并显示验证码输入错误提示信息,如图15.3所示。

图15.2 注册页面

图15.3 验证码输入错误

输完用户名信息,并将光标移出该文本框时,页面显示该用户名是否被占用的提示信息。

图15.4 检测用户名是否被占用