Top

JAVA Struts2 DAY03

  1. NetCTOSS登录,请求1
  2. NetCTOSS登录,请求2
  3. 登录验证码-1
  4. 登录验证码-2
  5. 资费删除

1 NetCTOSS登录,请求1

1.1 问题

实现NetCTOSS项目的登录功能,要求在登录页面上输入账号、密码后,点击登录则自动校验登录信息是否正确,若校验成功则跳转至系统首页index.jsp,否则跳转回登录页并给予错误的提示信息。

本案例中,只要求打开登录页面即可。

1.2 方案

由于打开登录页面不需要任何业务,只是把请求转发给JSP即可,因此无需增加新的Action,只需要在struts.xml中对请求加以配置即可。

1.3 步骤

步骤一:struts.xml中,配置访问登录页的请求action

由于登录功能与资费功能无关,是一个新的模块,因此在struts.xml中追加一个新的package用于封装登录模块的配置信息,该package以模块名login命名。在该package下,配置当前请求对应的action,此action由于没有业务逻辑,因此不必指定class属性,Struts2会自动实例化默认的Action类ActionSupport,该类中存在方法execute,该方法返回success。另外此action也不需要配置method,当不配置此属性时,Struts2会默认调用execute方法。代码如下:

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE struts PUBLIC
    "-//Apache Software Foundation//DTD Struts Configuration 2.1.7//EN"
    "/struts.apache.org/dtds/struts-2.1.7.dtd">
<struts>
	
	<!-- 
		资费模块配置信息:
		一般情况下,一个模块的配置单独封装在一个package下,
		并且以模块名来命名package的name和namespace。
	 -->
	<package name="cost" namespace="/cost" extends="struts-default">
		<!—此处略去其他Action的配置... -->
	</package>
	
#cold_bold	<!--登录模块 -->
#cold_bold	<package name="login" namespace="/login" extends="struts-default">
#cold_bold		<!-- 
#cold_bold			打开登录页面:
#cold_bold			1、action的class属性可以省略,省略时Struts2
#cold_bold			会自动实例化默认的Action类ActionSupport,
#cold_bold			该类中有默认业务方法execute,返回success。
#cold_bold			2、action的method属性可以省略,省略时Struts2
#cold_bold			会自动调用execute方法。
#cold_bold		-->
#cold_bold		<action name="toLogin">
#cold_bold			<result name="success">
#cold_bold				/WEB-INF/main/login.jsp
#cold_bold			</result>
#cold_bold		</action>
	</package>
	
</struts>

步骤二:增加登录页面

在WEB-INF/main/下创建登录页面login.jsp,先在该页面上指定其编码类型为utf-8,然后将静态页面中的代码复制到此页面中。再修改页面上引用的样式文件以及图片的路径,最后将页面默认的一些错误提示信息删除。代码如下:

<%@page pageEncoding="utf-8"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "/www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="/www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title></title>
<link type="text/css" rel="stylesheet" media="all" href="../styles/global.css" />
<link type="text/css" rel="stylesheet" media="all" href="../styles/global_color.css" />
</head>
<body class="index">
<div class="login_box">
<table>
<tr>
<td class="login_info">账号:</td>
<td colspan="2"><input name="" type="text" class="width150" /></td>
<td class="login_error_info"><span class="required">30长度的字母、数字和下划线</span></td>
</tr>
<tr>
<td class="login_info">密码:</td>
<td colspan="2"><input name="" type="password" class="width150" /></td>
<td><span class="required">30长度的字母、数字和下划线</span></td>
</tr>
<tr>
<td class="login_info">验证码:</td>
<td class="width70"><input name="" type="text" class="width70" /></td>
<td><img src="../images/valicode.jpg" alt="验证码" title="点击更换" /></td>
<td><span class="required"></span></td>
</tr>
<tr>
<td></td>
<td class="login_button" colspan="2">
<a href="index.html"><img src="../images/login_btn.png" /></a>
</td>
<td><span class="required"></span></td>
</tr>
</table>
</div>
</body>
</html>

步骤三:测试

重新部署项目NETCTOSS并启动tomcat,然后访问NETCTOSS,路径为/localhost:8088/NETCTOSS/login/toLogin,效果如下:

图-1

1.4 完整代码

本案例的完整代码如下。

struts.xml完整代码:

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE struts PUBLIC
    "-//Apache Software Foundation//DTD Struts Configuration 2.1.7//EN"
    "/struts.apache.org/dtds/struts-2.1.7.dtd">
<struts>
	
	<!-- 
		资费模块配置信息:
		一般情况下,一个模块的配置单独封装在一个package下,
		并且以模块名来命名package的name和namespace。
	 -->
	<package name="cost" namespace="/cost" extends="struts-default">
		<!-- 查询资费数据 -->
		<action name="findCost" class="com.netctoss.action.FindCostAction">
			<!-- 
				正常情况下跳转到资费列表页面。
				一般一个模块的页面要打包在一个文件夹下,并且文件夹以模块名命名。
			 -->
			<result name="success">
				/WEB-INF/cost/find_cost.jsp
			</result>
			<!-- 
				错误情况下,跳转到错误页面。
				错误页面可以被所有模块复用,因此放在main下,
				该文件夹用于存放公用的页面。
			 -->
			<result name="error">
				/WEB-INF/main/error.jsp
			</result>
		</action>
	</package>
	
	<!-- 登录模块 -->
	<package name="login" namespace="/login" extends="struts-default">
		<!-- 
			打开登录页面:
			1、action的class属性可以省略,省略时Struts2
			   会自动实例化默认的Action类ActionSupport,
			   该类中有默认业务方法execute,返回success。
			2、action的method属性可以省略,省略时Struts2
			   会自动调用execute方法。
		-->
		<action name="toLogin">
			<result name="success">
				/WEB-INF/main/login.jsp
			</result>
		</action>
	</package>
	
</struts>

login.jsp完整代码:

<%@page pageEncoding="utf-8"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "/www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="/www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title></title>
<link type="text/css" rel="stylesheet" media="all" href="../styles/global.css" />
<link type="text/css" rel="stylesheet" media="all" href="../styles/global_color.css" />
</head>
<body class="index">
<div class="login_box">
<table>
<tr>
<td class="login_info">账号:</td>
<td colspan="2"><input name="" type="text" class="width150" /></td>
<td class="login_error_info"><span class="required">30长度的字母、数字和下划线</span></td>
</tr>
<tr>
<td class="login_info">密码:</td>
<td colspan="2"><input name="" type="password" class="width150" /></td>
<td><span class="required">30长度的字母、数字和下划线</span></td>
</tr>
<tr>
<td class="login_info">验证码:</td>
<td class="width70"><input name="" type="text" class="width70" /></td>
<td><img src="../images/valicode.jpg" alt="验证码" title="点击更换" /></td>
<td><span class="required"></span></td>
</tr>
<tr>
<td></td>
<td class="login_button" colspan="2">
<a href="index.html"><img src="../images/login_btn.png" /></a>
</td>
<td><span class="required"></span></td>
</tr>
</table>
</div>
</body>
</html>

2 NetCTOSS登录,请求2

2.1 问题

完成NetCTOSS登录验证。

2.2 方案

登录页面上,输入完账号、密码后,点击登录按钮时触发登录验证,该请求的过程如下图:

图-2

图中红色字体为需要开发的代码,其中最重要的是要在Action中写校验逻辑,具体校验逻辑如下图:

图-3

2.3 步骤

实现此案例需要按照如下步骤进行。

步骤一:创建管理员实体类

在com.netctoss.entity包下,创建管理员实体类Admin,用于封装管理员信息表admin_info中的数据,代码如下:

package com.netctoss.entity;

import java.sql.Date;

/**
 * 管理员实体类
 */
public class Admin {

	private Integer id;// 主键
	private String adminCode;// 账号
	private String password;// 密码
	private String name;// 姓名
	private String telephone;// 电话
	private String email;// 邮件
	private Date enrollDate;// 创建日期

	public Integer getId() {
		return id;
	}

	public void setId(Integer id) {
		this.id = id;
	}

	public String getAdminCode() {
		return adminCode;
	}

	public void setAdminCode(String adminCode) {
		this.adminCode = adminCode;
	}

	public String getPassword() {
		return password;
	}

	public void setPassword(String password) {
		this.password = password;
	}

	public String getName() {
		return name;
	}

	public void setName(String name) {
		this.name = name;
	}

	public String getTelephone() {
		return telephone;
	}

	public void setTelephone(String telephone) {
		this.telephone = telephone;
	}

	public String getEmail() {
		return email;
	}

	public void setEmail(String email) {
		this.email = email;
	}

	public Date getEnrollDate() {
		return enrollDate;
	}

	public void setEnrollDate(Date enrollDate) {
		this.enrollDate = enrollDate;
	}

}

步骤二:创建登录模块DAO,追加根据账号查询管理员的方法

在com.netctoss.dao包下,创建登录DAO的接口ILoginDao,并增加根据账号查询管理员的方法,代码如下:

package com.netctoss.dao;

import com.netctoss.entity.Admin;

/**
 * 登录模块DAO
 */
public interface ILoginDao {

	/**
	 * 根据管理员账号查询管理员
	 * @param code 账号
	 * @return
	 */
	Admin findByCode(String code);

}

在com.netctoss.dao包下,创建ILoginDao接口的实现类LoginDaoImpl,并实现根据账号查询管理员的方法,代码如下:

package com.netctoss.dao;

import com.netctoss.entity.Admin;

/**
 *	当前阶段学习重点是Struts2,对于DAO的实现就模拟实现了。
 *	同学们可以使用JDBC/MyBatis自行实现该DAO。
 */
public class LoginDaoImpl implements ILoginDao {

	@Override
	public Admin findByCode(String code) {
		// 模拟根据账号查询管理员信息
		Admin a = new Admin();
		a.setId(1);
		a.setAdminCode("tarena");
		a.setPassword("123");
		a.setName("呵呵");
		a.setTelephone("110");
		a.setEmail("tarena@tarena.com.cn");
		return a;
	}

}

为了方便后续Action访问此DAO,将该DAO的实例化封装在DAOFactory中代码如下:

package com.netctoss.dao;

/**
 * DAO工厂,负责统一的创建DAO示例。
 */
public class DAOFactory {

	/**
	 * 资费DAO实例
	 */
	private static ICostDao costDao = new CostDaoImpl();
	
#cold_bold	/**
#cold_bold	 * 登录DAO实例
#cold_bold	 */
#cold_bold	private static ILoginDao loginDao = new LoginDaoImpl();

	/**
	 * 返回资费DAO实例
	 */
	public static ICostDao getCostDAO() {
		return costDao;
	}
	
#cold_bold	/**
#cold_bold	 * 返回登录DAO实例
#cold_bold	 */
#cold_bold	public static ILoginDao getLoginDAO() {
#cold_bold		return loginDao;
#cold_bold	}

}

步骤四:创建登录校验Action

在com.netctoss.action包下,创建Action的父类BaseAction,用于封装Action公用的方法,当前要将获取session的方法封装在该类中,以便于复用。代码如下:

package com.netctoss.action;

import java.util.Map;

import org.apache.struts2.interceptor.SessionAware;

/**
 * Action基类,用于封装Action通用的方法。
 */
public class BaseAction implements SessionAware {

	protected Map<String, Object> session;

	/* 
	 * 采用接口注入的方式统一获取session
	 */
	@Override
	public void setSession(Map<String, Object> arg0) {
		session = arg0;
	}

}

在com.netctoss.action包下,创建登录校验Action类LoginAction,继承于BaseAction。在该Action的业务方法中,根据传入的账号及密码,调用登录DAO校验用户登录是否成功,成功时将登录信息记录到session中,并跳转至系统首页,否则跳转回登录页,并给予错误提示。代码如下:

package com.netctoss.action;

import com.netctoss.dao.DAOFactory;
import com.netctoss.dao.ILoginDao;
import com.netctoss.entity.Admin;

/**
 *	登录校验Action
 */
public class LoginAction extends BaseAction {

	// input
	private String adminCode;// 账号
	private String password;// 密码

	// output
	private String errorMsg;// 错误信息

	public String execute() {
		ILoginDao dao = DAOFactory.getLoginDAO();
		Admin admin = null;
		try {
			// 根据账号查询管理员
			admin = dao.findByCode(adminCode);
		} catch (Exception e) {
			e.printStackTrace();
			return "error";
		}

		if (admin == null) {
			// 如果管理员为空,则说明账号有误,校验失败
			errorMsg = "账号不存在.";
			return "fail";
		} else {
			// 如果管理员不为空,进一步校验密码
			if (password != null 
					&&password.equals(admin.getPassword())) {
				// 如果密码一致,校验成功
				session.put("admin", admin);
				return "success";
			} else {
				// 如果密码不一致,校验失败
				errorMsg = "密码有误.";
				return "fail";
			}
		}
	}

	public String getAdminCode() {
		return adminCode;
	}

	public void setAdminCode(String adminCode) {
		this.adminCode = adminCode;
	}

	public String getPassword() {
		return password;
	}

	public void setPassword(String password) {
		this.password = password;
	}

	public String getErrorMsg() {
		return errorMsg;
	}

	public void setErrorMsg(String errorMsg) {
		this.errorMsg = errorMsg;
	}

}

步骤五:在struts.xml中配置校验Action

在struts.xml中配置登录校验action,登录成功则转至系统首页,否则转至登录页,代码如下:

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE struts PUBLIC
    "-//Apache Software Foundation//DTD Struts Configuration 2.1.7//EN"
    "/struts.apache.org/dtds/struts-2.1.7.dtd">
<struts>
	
	<!-- 
		资费模块配置信息:
		一般情况下,一个模块的配置单独封装在一个package下,
		并且以模块名来命名package的name和namespace。
	 -->
	<package name="cost" namespace="/cost" extends="struts-default">
		<!--查询资费数据 -->
		<action name="findCost" class="com.netctoss.action.FindCostAction">
			<!—此处略去其他Action的配置... -->
		</action>
	</package>
	
	<!--登录模块 -->
	<package name="login" namespace="/login" extends="struts-default">
		<!—此处略去其他Action的配置... -->
#cold_bold		<!--登录校验 -->
#cold_bold		<action name="login" class="com.netctoss.action.LoginAction">
#cold_bold			<!--校验成功,跳转到系统首页 -->
#cold_bold			<result name="success">
#cold_bold				/WEB-INF/main/index.jsp
#cold_bold			</result>
#cold_bold			<!--登录失败,跳转回登录页面 -->
#cold_bold			<result name="fail">
#cold_bold				/WEB-INF/main/login.jsp
#cold_bold			</result>
#cold_bold			<!--报错,跳转到错误页面 -->
#cold_bold			<result name="error">
#cold_bold				/WEB-INF/main/error.jsp
#cold_bold			</result>
#cold_bold		</action>
	</package>
	
</struts>

步骤六:创建系统首页

在WEB-INF/main下创建系统首页index.jsp,首先设置其编码为utf-8,然后从静态页面中将HTML代码复制到此页面上,最后修改样式引用的路径。代码如下:

<%@page pageEncoding="utf-8"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "/www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="/www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title></title>
<link type="text/css" rel="stylesheet" media="all" href="../styles/global.css" />
<link type="text/css" rel="stylesheet" media="all" href="../styles/global_color.css" />
</head>
<body class="index">
<!--导航区域开始-->
<div id="index_navi">
<ul id="menu">
<li><a href="index.html" class="index_on"></a></li>
<li><a href="role/role_list.html" class="role_off"></a></li>
<li><a href="admin/admin_list.html" class="admin_off"></a></li>
<li><a href="fee/fee_list.html" class="fee_off"></a></li>
<li><a href="account/account_list.html" class="account_off"></a></li>
<li><a href="service/service_list.html" class="service_off"></a></li>
<li><a href="bill/bill_list.html" class="bill_off"></a></li>
<li><a href="report/report_list.html" class="report_off"></a></li>
<li><a href="user/user_info.html" class="information_off"></a></li>
<li><a href="user/user_modi_pwd.html" class="password_off"></a></li>
</ul>
</div>
</body>
</html>

步骤七:在登录页面上设置表单项

在login.jsp中,追加表单设置,将账号及密码放在表单内,并且表单提交路径设置为登录action。然后对账号、密码文本框的name进行设置,使用基本属性注入的方式将其值注入给登录Action,并给登录按钮图片设置提交事件。最后使用Struts2显示标签+OGNL表达式,在页面上输出错误信息。代码如下:

<%@page pageEncoding="utf-8"%>
<%@taglib uri="/struts-tags" prefix="s"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "/www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="/www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title></title>
<link type="text/css" rel="stylesheet" media="all" href="../styles/global.css" />
<link type="text/css" rel="stylesheet" media="all" href="../styles/global_color.css" />
</head>
<body class="index">
<div class="login_box">
#cold_bold	<form action="login" method="post">
	<table>
	<tr>
	<td class="login_info">账号:</td>
#cold_bold	<td colspan="2"><input name="adminCode" type="text" class="width150" /></td>
	<td class="login_error_info"><span class="required">30长度的字母、数字和下划线</span></td>
	</tr>
	<tr>
	<td class="login_info">密码:</td>
#cold_bold	<td colspan="2"><input name="password" type="password" class="width150" /></td>
	<td><span class="required">30长度的字母、数字和下划线</span></td>
	</tr>
	<tr>
	<td class="login_info">验证码:</td>
	<td class="width70"><input name="" type="text" class="width70" /></td>
	<td><img src="../images/valicode.jpg" alt="验证码" title="点击更换" /></td>
	<td><span class="required"></span></td>
	</tr>
	<tr>
	<td></td>
	<td class="login_button" colspan="2">
#cold_bold	<a href="javascript:document.forms[0].submit();"><img src="../images/login_btn.png" /></a>
	</td>
#cold_bold	<td><span class="required"><s:property value="errorMsg"/></span></td>
	</tr>
	</table>
</form>
</div>
</body>
</html>

步骤八:测试

部署项目NETCTOSS并重新启动tomcat,访问NETCTOSS登录页,在页面上输入正确的账号及密码,点击登录后,效果如下图:

图-4

后退,在页面上输入错误的账号或密码,点击登录后,效果如下图:

图-5

从上图可以看出,校验的结果满足要求,但是由于页面的刷新,导致文本框中原先输入的内容丢失了,应该予以保留才对。实际上只需要在文本框中追加value属性,并将Action中的数据设置给它既可,因此需要对login.jsp进行完善。

步骤九:完善

在login.jsp上,给文本框设置默认值,以便登录失败回到登录页时,无法保留住刚刚输入的账号及密码,代码如下:

<%@page pageEncoding="utf-8"%>
<%@taglib uri="/struts-tags" prefix="s"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "/www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="/www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title></title>
<link type="text/css" rel="stylesheet" media="all" href="../styles/global.css" />
<link type="text/css" rel="stylesheet" media="all" href="../styles/global_color.css" />
</head>
<body class="index">
<div class="login_box">
	<form action="login" method="post">
	<table>
	<tr>
	<td class="login_info">账号:</td>
	<td colspan="2">
#cold_bold		<input name="adminCode" type="text" class="width150" 
#cold_bold			value="<s:property value="adminCode"/>"/>
	</td>
	<td class="login_error_info"><span class="required">30长度的字母、数字和下划线</span></td>
	</tr>
	<tr>
	<td class="login_info">密码:</td>
	<td colspan="2">
#cold_bold		<input name="password" type="password" class="width150" 
#cold_bold			value="<s:property value="password"/>"/>
	</td>
	<td><span class="required">30长度的字母、数字和下划线</span></td>
	</tr>
	<tr>
	<td class="login_info">验证码:</td>
	<td class="width70"><input name="" type="text" class="width70" /></td>
	<td><img src="../images/valicode.jpg" alt="验证码" title="点击更换" /></td>
	<td><span class="required"></span></td>
	</tr>
	<tr>
	<td></td>
	<td class="login_button" colspan="2">
	<a href="javascript:document.forms[0].submit();"><img src="../images/login_btn.png" /></a>
	</td>
	<td><span class="required"><s:property value="errorMsg"/></span></td>
	</tr>
	</table>
</form>
</div>
</body>
</html>

完善后,刷新地址栏,重新输入错误的账号或密码后,点击登录,效果如下图:

图-6

注意:实际上Struts2有更好的办法实现上述显示默认值(步骤九)的逻辑,即使用Struts2的UI标签。但目前还没有讲到这些内容,因此暂且这样写,以后可以进行重构。希望大家理解这一点,实际项目中不要写出这样设置默认值的代码。

2.4 完整代码

本案例的完整代码如下。

实体类Admin完整代码:

package com.netctoss.entity;

import java.sql.Date;

/**
 * 管理员实体类
 */
public class Admin {

	private Integer id;// 主键
	private String adminCode;// 账号
	private String password;// 密码
	private String name;// 姓名
	private String telephone;// 电话
	private String email;// 邮件
	private Date enrollDate;// 创建日期

	public Integer getId() {
		return id;
	}

	public void setId(Integer id) {
		this.id = id;
	}

	public String getAdminCode() {
		return adminCode;
	}

	public void setAdminCode(String adminCode) {
		this.adminCode = adminCode;
	}

	public String getPassword() {
		return password;
	}

	public void setPassword(String password) {
		this.password = password;
	}

	public String getName() {
		return name;
	}

	public void setName(String name) {
		this.name = name;
	}

	public String getTelephone() {
		return telephone;
	}

	public void setTelephone(String telephone) {
		this.telephone = telephone;
	}

	public String getEmail() {
		return email;
	}

	public void setEmail(String email) {
		this.email = email;
	}

	public Date getEnrollDate() {
		return enrollDate;
	}

	public void setEnrollDate(Date enrollDate) {
		this.enrollDate = enrollDate;
	}

}

ILoginDao完整代码:

package com.netctoss.dao;

import com.netctoss.entity.Admin;

/**
 * 登录模块DAO
 */
public interface ILoginDao {

	/**
	 * 根据管理员账号查询管理员
	 * @param code 账号
	 * @return
	 */
	Admin findByCode(String code);

}

LoginDaoImpl完整代码:

package com.netctoss.dao;

import com.netctoss.entity.Admin;

/**
 *	当前阶段学习重点是Struts2,对于DAO的实现就模拟实现了。
 *	同学们可以使用JDBC/MyBatis自行实现该DAO。
 */
public class LoginDaoImpl implements ILoginDao {

	@Override
	public Admin findByCode(String code) {
		// 模拟根据账号查询管理员信息
		Admin a = new Admin();
		a.setId(1);
		a.setAdminCode("tarena");
		a.setPassword("123");
		a.setName("呵呵");
		a.setTelephone("110");
		a.setEmail("tarena@tarena.com.cn");
		return a;
	}

}

DAOFactory完整代码:

package com.netctoss.dao;

/**
 * DAO工厂,负责统一的创建DAO示例。
 */
public class DAOFactory {

	/**
	 * 资费DAO实例
	 */
	private static ICostDao costDao = new CostDaoImpl();
	
	/**
	 * 登录DAO实例
	 */
	private static ILoginDao loginDao = new LoginDaoImpl();

	/**
	 * 返回资费DAO实例
	 */
	public static ICostDao getCostDAO() {
		return costDao;
	}
	
	/**
	 * 返回登录DAO实例
	 */
	public static ILoginDao getLoginDAO() {
		return loginDao;
	}

}

BaseAction完整代码:

package com.netctoss.action;

import java.util.Map;

import org.apache.struts2.interceptor.SessionAware;

/**
 * Action基类,用于封装Action通用的方法。
 */
public class BaseAction implements SessionAware {

	protected Map<String, Object> session;

	/* 
	 * 采用接口注入的方式统一获取session
	 */
	@Override
	public void setSession(Map<String, Object> arg0) {
		session = arg0;
	}

}

LoginAction完整代码:

package com.netctoss.action;

import com.netctoss.dao.DAOFactory;
import com.netctoss.dao.ILoginDao;
import com.netctoss.entity.Admin;

/**
 *	登录校验Action
 */
public class LoginAction extends BaseAction {

	// input
	private String adminCode;// 账号
	private String password;// 密码

	// output
	private String errorMsg;// 错误信息

	public String execute() {
		ILoginDao dao = DAOFactory.getLoginDAO();
		Admin admin = null;
		try {
			// 根据账号查询管理员
			admin = dao.findByCode(adminCode);
		} catch (Exception e) {
			e.printStackTrace();
			return "error";
		}

		if (admin == null) {
			// 如果管理员为空,则说明账号有误,校验失败
			errorMsg = "账号不存在.";
			return "fail";
		} else {
			// 如果管理员不为空,进一步校验密码
			if (password != null 
					&&password.equals(admin.getPassword())) {
				// 如果密码一致,校验成功
				session.put("admin", admin);
				return "success";
			} else {
				// 如果密码不一致,校验失败
				errorMsg = "密码有误.";
				return "fail";
			}
		}
	}

	public String getAdminCode() {
		return adminCode;
	}

	public void setAdminCode(String adminCode) {
		this.adminCode = adminCode;
	}

	public String getPassword() {
		return password;
	}

	public void setPassword(String password) {
		this.password = password;
	}

	public String getErrorMsg() {
		return errorMsg;
	}

	public void setErrorMsg(String errorMsg) {
		this.errorMsg = errorMsg;
	}

}

struts.xml完整代码:

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE struts PUBLIC
    "-//Apache Software Foundation//DTD Struts Configuration 2.1.7//EN"
    "/struts.apache.org/dtds/struts-2.1.7.dtd">
<struts>
	
	<!-- 
		资费模块配置信息:
		一般情况下,一个模块的配置单独封装在一个package下,
		并且以模块名来命名package的name和namespace。
	 -->
	<package name="cost" namespace="/cost" extends="struts-default">
		<!-- 查询资费数据 -->
		<action name="findCost" class="com.netctoss.action.FindCostAction">
			<!-- 
				正常情况下跳转到资费列表页面。
				一般一个模块的页面要打包在一个文件夹下,并且文件夹以模块名命名。
			 -->
			<result name="success">
				/WEB-INF/cost/find_cost.jsp
			</result>
			<!-- 
				错误情况下,跳转到错误页面。
				错误页面可以被所有模块复用,因此放在main下,
				该文件夹用于存放公用的页面。
			 -->
			<result name="error">
				/WEB-INF/main/error.jsp
			</result>
		</action>
	</package>
	
	<!-- 登录模块 -->
	<package name="login" namespace="/login" extends="struts-default">
		<!-- 
			打开登录页面:
			1、action的class属性可以省略,省略时Struts2
			   会自动实例化默认的Action类ActionSupport,
			   该类中有默认业务方法execute,返回success。
			2、action的method属性可以省略,省略时Struts2
			   会自动调用execute方法。
		-->
		<action name="toLogin">
			<result name="success">
				/WEB-INF/main/login.jsp
			</result>
		</action>
		<!-- 登录校验 -->
		<action name="login" class="com.netctoss.action.LoginAction">
			<!-- 校验成功,跳转到系统首页 -->
			<result name="success">
				/WEB-INF/main/index.jsp
			</result>
			<!-- 登录失败,跳转回登录页面 -->
			<result name="fail">
				/WEB-INF/main/login.jsp
			</result>
			<!-- 报错,跳转到错误页面 -->
			<result name="error">
				/WEB-INF/main/error.jsp
			</result>
		</action>
	</package>
	
</struts>

index.jsp完整代码:

<%@page pageEncoding="utf-8"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "/www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="/www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title></title>
<link type="text/css" rel="stylesheet" media="all" href="../styles/global.css" />
<link type="text/css" rel="stylesheet" media="all" href="../styles/global_color.css" />
</head>
<body class="index">
<!--导航区域开始-->
<div id="index_navi">
<ul id="menu">
<li><a href="index.html" class="index_on"></a></li>
<li><a href="role/role_list.html" class="role_off"></a></li>
<li><a href="admin/admin_list.html" class="admin_off"></a></li>
<li><a href="fee/fee_list.html" class="fee_off"></a></li>
<li><a href="account/account_list.html" class="account_off"></a></li>
<li><a href="service/service_list.html" class="service_off"></a></li>
<li><a href="bill/bill_list.html" class="bill_off"></a></li>
<li><a href="report/report_list.html" class="report_off"></a></li>
<li><a href="user/user_info.html" class="information_off"></a></li>
<li><a href="user/user_modi_pwd.html" class="password_off"></a></li>
</ul>
</div>
</body>
</html>

login.jsp完整代码:

<%@page pageEncoding="utf-8"%>
<%@taglib uri="/struts-tags" prefix="s"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "/www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="/www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title></title>
<link type="text/css" rel="stylesheet" media="all" href="../styles/global.css" />
<link type="text/css" rel="stylesheet" media="all" href="../styles/global_color.css" />
</head>
<body class="index">
<div class="login_box">
	<form action="login" method="post">
	<table>
	<tr>
	<td class="login_info">账号:</td>
	<td colspan="2">
		<input name="adminCode" type="text" class="width150" 
			value="<s:property value="adminCode"/>"/>
	</td>
	<td class="login_error_info"><span class="required">30长度的字母、数字和下划线</span></td>
	</tr>
	<tr>
	<td class="login_info">密码:</td>
	<td colspan="2">
		<input name="password" type="password" class="width150" 
			value="<s:property value="password"/>"/>
	</td>
	<td><span class="required">30长度的字母、数字和下划线</span></td>
	</tr>
	<tr>
	<td class="login_info">验证码:</td>
	<td class="width70"><input name="" type="text" class="width70" /></td>
	<td><img src="../images/valicode.jpg" alt="验证码" title="点击更换" /></td>
	<td><span class="required"></span></td>
	</tr>
	<tr>
	<td></td>
	<td class="login_button" colspan="2">
	<a href="javascript:document.forms[0].submit();"><img src="../images/login_btn.png" /></a>
	</td>
	<td><span class="required"><s:property value="errorMsg"/></span></td>
	</tr>
	</table>
</form>
</div>
</body>
</html>

3 登录验证码-1

3.1 问题

NetCTOSS登录的案例中,提交时只考虑了账号、密码,没有考虑验证码,那么本案例中,要求给登录功能追加生成验证码的功能,以及在登录时要对验证码进行校验。

本案例中,完成在登录页面上生成验证码图片即可。

3.2 方案

我们可以在服务端动态生成验证码图片,然后使用stream类型的result将图片输出给页面img元素。

在生成图片后,需要将图片中的文本记录到session,以便后续提交登录时,方便对验证码的校验。

3.3 步骤

实现此案例需要按照如下步骤进行。

步骤一:创建生成图片工具类

在com.netctoss.util包下,创建生成图片工具类ImageUtil,并提供创建图片的代码,以及将图片转换成输入流的代码,代码如下:

package com.netctoss.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.io.InputStream;
import java.util.HashMap;
import java.util.Map;
import java.util.Random;

import com.sun.image.codec.jpeg.JPEGCodec;
import com.sun.image.codec.jpeg.JPEGImageEncoder;

/**
 *	生成验证码图片工具类
 */
public final class ImageUtil {

	private static final char[] chars = { '0', '1', '2', '3', '4', '5', '6',
			'7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I' };
	private static final int SIZE = 4;
	private static final int LINES = 5;
	private static final int WIDTH = 80;
	private static final int HEIGHT = 40;
	private static final int FONT_SIZE = 30;

	/**
	 * 生成验证码图片,封装与Map中。
	 * 其中Map的key是验证码,Map的value是验证码图片。
	 */
	public static Map<String, BufferedImage> createImage() {
		StringBuffer sb = new StringBuffer();
		BufferedImage image = new BufferedImage(WIDTH, HEIGHT,
				BufferedImage.TYPE_INT_RGB);
		Graphics graphic = image.getGraphics();
		graphic.setColor(Color.LIGHT_GRAY);
		graphic.fillRect(0, 0, WIDTH, HEIGHT);
		Random ran = new Random();
		// 画随机字符
		for (int i = 1; i <= SIZE; i++) {
			int r = ran.nextInt(chars.length);
			graphic.setColor(getRandomColor());
			graphic.setFont(new Font(null, Font.BOLD + Font.ITALIC, FONT_SIZE));
			graphic.drawString(chars[r] + "", (i - 1) * WIDTH / SIZE,
					HEIGHT / 2);
			sb.append(chars[r]);// 将字符保存,存入Session
		}
		// 画干扰线
		for (int i = 1; i <= LINES; i++) {
			graphic.setColor(getRandomColor());
			graphic.drawLine(ran.nextInt(WIDTH), ran.nextInt(HEIGHT),
					ran.nextInt(WIDTH), ran.nextInt(HEIGHT));
		}
		Map<String, BufferedImage> map = new HashMap<String, BufferedImage>();
		map.put(sb.toString(), image);
		return map;
	}

	/**
	 * 将图片转换为输入流
	 */
	public static InputStream getInputStream(BufferedImage image)
			throws IOException {
		ByteArrayOutputStream bos = new ByteArrayOutputStream();
		JPEGImageEncoder encoder = JPEGCodec.createJPEGEncoder(bos);
		encoder.encode(image);
		byte[] imageBts = bos.toByteArray();
		InputStream in = new ByteArrayInputStream(imageBts);
		return in;
	}

	private static Color getRandomColor() {
		Random ran = new Random();
		Color color = new Color(ran.nextInt(256), ran.nextInt(256),
				ran.nextInt(256));
		return color;
	}

}

步骤二:在Action中生成图片

在com.netctoss.action包下,创建CreateImageAction类,用于处理生成图片的请求,代码如下:

package com.netctoss.action;

import java.awt.image.BufferedImage;
import java.io.IOException;
import java.io.InputStream;
import java.util.Map;

import com.netctoss.util.ImageUtil;

/**
 *	生成验证码图片Action
 */
public class CreateImageAction extends BaseAction {

	/**
	 * 验证码图片输入流
	 */
	private InputStream imageStream;

	public String execute() {
		// 创建验证码图片
		Map<String, BufferedImage> imageMap = ImageUtil.createImage();
		// 取出验证码,放入session
		String code = imageMap.keySet().iterator().next();
		session.put("imageCode", code);

		// 取出图片
		BufferedImage image = imageMap.get(code);
		try {
			// 将图片转变为输入流
			imageStream = ImageUtil.getInputStream(image);
		} catch (IOException e) {
			e.printStackTrace();
			return "error";
		}

		return "success";
	}

	public InputStream getImageStream() {
		return imageStream;
	}

	public void setImageStream(InputStream imageStream) {
		this.imageStream = imageStream;
	}

}

步骤三:在struts.xml中配置action

在struts.xml中对此action进行配置,利用stream类型的result,将CreateImageAction中的输入流的内容输出给页面,代码如下:

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE struts PUBLIC
    "-//Apache Software Foundation//DTD Struts Configuration 2.1.7//EN"
    "/struts.apache.org/dtds/struts-2.1.7.dtd">
<struts>
	
	<!-- 
		资费模块配置信息:
		一般情况下,一个模块的配置单独封装在一个package下,
		并且以模块名来命名package的name和namespace。
	 -->
	<package name="cost" namespace="/cost" extends="struts-default">
		<!—此处略去其他Action的配置... -->
	</package>
	
	<!--登录模块 -->
	<package name="login" namespace="/login" extends="struts-default">
		<!—此处略去其他Action的配置... -->
#cold_bold		<!--生成验证码 -->
#cold_bold		<action name="createImage" class="com.netctoss.action.CreateImageAction">
#cold_bold			<!--使用stream类型的result -->
#cold_bold			<result name="success" type="stream">
#cold_bold				<!--指定输出的内容 -->
#cold_bold				<param name="inputName">imageStream</param>
#cold_bold			</result>
#cold_bold		</action>
	</package>
	
</struts>

步骤四:登录页面上,修改图片路径

在登录页面login.jsp上,将img的src属性指定为生成验证码图片的action,即 createImage。并且为了每次点击该图片时都刷新为新验证码,给该img的单击事件绑定函数,在函数中刷新img的src属性,代码如下:

<%@page pageEncoding="utf-8"%>
<%@taglib uri="/struts-tags" prefix="s"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "/www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="/www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title></title>
<link type="text/css" rel="stylesheet" media="all" href="../styles/global.css" />
<link type="text/css" rel="stylesheet" media="all" href="../styles/global_color.css" />
<script type="text/javascript" language="javascript">
#cold_bold	//刷新验证码图片
#cold_bold			function change(image){
#cold_bold				//改变img的src即可,由于该URL并没有变化,因此追加动态参数伪装成变化的URL。
#cold_bold		image.src = "createImage?date=" + new Date().getTime();
#cold_bold	}
		</script>
</head>
<body class="index">
<div class="login_box">
	<form action="login" method="post">
	<table>
	<tr>
	<td class="login_info">账号:</td>
	<td colspan="2">
		<input name="adminCode" type="text" class="width150" 
			value="<s:property value="adminCode"/>"/>
	</td>
	<td class="login_error_info"><span class="required">30长度的字母、数字和下划线</span></td>
	</tr>
	<tr>
	<td class="login_info">密码:</td>
	<td colspan="2">
		<input name="password" type="password" class="width150" 
			value="<s:property value="password"/>"/>
	</td>
	<td><span class="required">30长度的字母、数字和下划线</span></td>
	</tr>
	<tr>
	<td class="login_info">验证码:</td>
	<td class="width70"><input name="" type="text" class="width70" /></td>
#cold_bold	<td><img src="createImage" alt="验证码" title="点击更换" onclick="change(this);"/></td>
	<td><span class="required"></span></td>
	</tr>
	<tr>
	<td></td>
	<td class="login_button" colspan="2">
	<a href="javascript:document.forms[0].submit();"><img src="../images/login_btn.png" /></a>
	</td>
	<td><span class="required"><s:property value="errorMsg"/></span></td>
	</tr>
	</table>
</form>
</div>
</body>
</html>

步骤五:测试

重新部署项目NETCTOSS并重启tomcat,访问登录页面,效果如下图:

图-7

3.4 完整代码

本案例的完整代码如下。

ImageUtil完整代码:

package com.netctoss.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.io.InputStream;
import java.util.HashMap;
import java.util.Map;
import java.util.Random;

import com.sun.image.codec.jpeg.JPEGCodec;
import com.sun.image.codec.jpeg.JPEGImageEncoder;

/**
 *	生成验证码图片工具类
 */
public final class ImageUtil {

	private static final char[] chars = { '0', '1', '2', '3', '4', '5', '6',
			'7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I' };
	private static final int SIZE = 4;
	private static final int LINES = 5;
	private static final int WIDTH = 80;
	private static final int HEIGHT = 40;
	private static final int FONT_SIZE = 30;

	/**
	 * 生成验证码图片,封装与Map中。
	 * 其中Map的key是验证码,Map的value是验证码图片。
	 */
	public static Map<String, BufferedImage> createImage() {
		StringBuffer sb = new StringBuffer();
		BufferedImage image = new BufferedImage(WIDTH, HEIGHT,
				BufferedImage.TYPE_INT_RGB);
		Graphics graphic = image.getGraphics();
		graphic.setColor(Color.LIGHT_GRAY);
		graphic.fillRect(0, 0, WIDTH, HEIGHT);
		Random ran = new Random();
		// 画随机字符
		for (int i = 1; i <= SIZE; i++) {
			int r = ran.nextInt(chars.length);
			graphic.setColor(getRandomColor());
			graphic.setFont(new Font(null, Font.BOLD + Font.ITALIC, FONT_SIZE));
			graphic.drawString(chars[r] + "", (i - 1) * WIDTH / SIZE,
					HEIGHT / 2);
			sb.append(chars[r]);// 将字符保存,存入Session
		}
		// 画干扰线
		for (int i = 1; i <= LINES; i++) {
			graphic.setColor(getRandomColor());
			graphic.drawLine(ran.nextInt(WIDTH), ran.nextInt(HEIGHT),
					ran.nextInt(WIDTH), ran.nextInt(HEIGHT));
		}
		Map<String, BufferedImage> map = new HashMap<String, BufferedImage>();
		map.put(sb.toString(), image);
		return map;
	}

	/**
	 * 将图片转换为输入流
	 */
	public static InputStream getInputStream(BufferedImage image)
			throws IOException {
		ByteArrayOutputStream bos = new ByteArrayOutputStream();
		JPEGImageEncoder encoder = JPEGCodec.createJPEGEncoder(bos);
		encoder.encode(image);
		byte[] imageBts = bos.toByteArray();
		InputStream in = new ByteArrayInputStream(imageBts);
		return in;
	}

	private static Color getRandomColor() {
		Random ran = new Random();
		Color color = new Color(ran.nextInt(256), ran.nextInt(256),
				ran.nextInt(256));
		return color;
	}

}

CreateImageAction完整代码:

package com.netctoss.action;

import java.awt.image.BufferedImage;
import java.io.IOException;
import java.io.InputStream;
import java.util.Map;

import com.netctoss.util.ImageUtil;

/**
 *	生成验证码图片Action
 */
public class CreateImageAction extends BaseAction {

	/**
	 * 验证码图片输入流
	 */
	private InputStream imageStream;

	public String execute() {
		// 创建验证码图片
		Map<String, BufferedImage> imageMap = ImageUtil.createImage();
		// 取出验证码,放入session
		String code = imageMap.keySet().iterator().next();
		session.put("imageCode", code);

		// 取出图片
		BufferedImage image = imageMap.get(code);
		try {
			// 将图片转变为输入流
			imageStream = ImageUtil.getInputStream(image);
		} catch (IOException e) {
			e.printStackTrace();
			return "error";
		}

		return "success";
	}

	public InputStream getImageStream() {
		return imageStream;
	}

	public void setImageStream(InputStream imageStream) {
		this.imageStream = imageStream;
	}

}

struts.xml完整代码:

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE struts PUBLIC
    "-//Apache Software Foundation//DTD Struts Configuration 2.1.7//EN"
    "/struts.apache.org/dtds/struts-2.1.7.dtd">
<struts>
	
	<!-- 
		资费模块配置信息:
		一般情况下,一个模块的配置单独封装在一个package下,
		并且以模块名来命名package的name和namespace。
	 -->
	<package name="cost" namespace="/cost" extends="struts-default">
		<!-- 查询资费数据 -->
		<action name="findCost" class="com.netctoss.action.FindCostAction">
			<!-- 
				正常情况下跳转到资费列表页面。
				一般一个模块的页面要打包在一个文件夹下,并且文件夹以模块名命名。
			 -->
			<result name="success">
				/WEB-INF/cost/find_cost.jsp
			</result>
			<!-- 
				错误情况下,跳转到错误页面。
				错误页面可以被所有模块复用,因此放在main下,
				该文件夹用于存放公用的页面。
			 -->
			<result name="error">
				/WEB-INF/main/error.jsp
			</result>
		</action>
	</package>
	
	<!-- 登录模块 -->
	<package name="login" namespace="/login" extends="struts-default">
		<!-- 
			打开登录页面:
			1、action的class属性可以省略,省略时Struts2
			   会自动实例化默认的Action类ActionSupport,
			   该类中有默认业务方法execute,返回success。
			2、action的method属性可以省略,省略时Struts2
			   会自动调用execute方法。
		-->
		<action name="toLogin">
			<result name="success">
				/WEB-INF/main/login.jsp
			</result>
		</action>
		<!-- 登录校验 -->
		<action name="login" class="com.netctoss.action.LoginAction">
			<!-- 校验成功,跳转到系统首页 -->
			<result name="success">
				/WEB-INF/main/index.jsp
			</result>
			<!-- 登录失败,跳转回登录页面 -->
			<result name="fail">
				/WEB-INF/main/login.jsp
			</result>
			<!-- 报错,跳转到错误页面 -->
			<result name="error">
				/WEB-INF/main/error.jsp
			</result>
		</action>
		<!-- 生成验证码 -->
		<action name="createImage" class="com.netctoss.action.CreateImageAction">
			<!-- 使用stream类型的result -->
			<result name="success" type="stream">
				<!-- 指定输出的内容 -->
				<param name="inputName">imageStream</param>
			</result>
		</action>
	</package>
	
</struts>

login.jsp完整代码:

<%@page pageEncoding="utf-8"%>
<%@taglib uri="/struts-tags" prefix="s"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "/www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="/www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title></title>
<link type="text/css" rel="stylesheet" media="all" href="../styles/global.css" />
<link type="text/css" rel="stylesheet" media="all" href="../styles/global_color.css" />
<script type="text/javascript" language="javascript">
	//刷新验证码图片
			function change(image){
				//改变img的src即可,由于该URL并没有变化,因此追加动态参数伪装成变化的URL。
		image.src = "createImage?date=" + new Date().getTime();
	}
		</script>
</head>
<body class="index">
<div class="login_box">
	<form action="login" method="post">
	<table>
	<tr>
	<td class="login_info">账号:</td>
	<td colspan="2">
		<input name="adminCode" type="text" class="width150" 
			value="<s:property value="adminCode"/>"/>
	</td>
	<td class="login_error_info"><span class="required">30长度的字母、数字和下划线</span></td>
	</tr>
	<tr>
	<td class="login_info">密码:</td>
	<td colspan="2">
		<input name="password" type="password" class="width150" 
			value="<s:property value="password"/>"/>
	</td>
	<td><span class="required">30长度的字母、数字和下划线</span></td>
	</tr>
	<tr>
	<td class="login_info">验证码:</td>
	<td class="width70"><input name="" type="text" class="width70" /></td>
	<td><img src="createImage" alt="验证码" title="点击更换" onclick="change(this);"/></td>
	<td><span class="required"></span></td>
	</tr>
	<tr>
	<td></td>
	<td class="login_button" colspan="2">
	<a href="javascript:document.forms[0].submit();"><img src="../images/login_btn.png" /></a>
	</td>
	<td><span class="required"><s:property value="errorMsg"/></span></td>
	</tr>
	</table>
</form>
</div>
</body>
</html>

4 登录验证码-2

4.1 问题

NetCTOSS登录时,追加对验证码的校验。

4.2 方案

点击登录按钮提交表单时,在Action中先对验证码进行校验。

4.3 步骤

实现此案例需要按照如下步骤进行。

步骤一:Action中对验证码进行校验

在LoginAction中,追加输入属性验证码,并在业务方法中校验账号密码前对其进行校验,代码如下:

package com.netctoss.action;

import com.netctoss.dao.DAOFactory;
import com.netctoss.dao.ILoginDao;
import com.netctoss.entity.Admin;

/**
 *	登录校验Action
 */
public class LoginAction extends BaseAction {

	// input
	private String adminCode;// 账号
	private String password;// 密码
#cold_bold	private String verifyCode;// 验证码

	// output
	private String errorMsg;// 错误信息

	public String execute() {
#cold_bold		// 从session中取出生成的验证码
#cold_bold		String imageCode = (String) session.get("imageCode");
#cold_bold		// 验证用户输入的验证码是否与生成验证码一致
#cold_bold		if(imageCode == null || !imageCode.equalsIgnoreCase(verifyCode)) {
#cold_bold			//如果不一致,提示错误
#cold_bold			errorMsg = "验证码有误.";
#cold_bold			return "fail";
#cold_bold		}
		
		// 此处略去登录验证逻辑...
	}

	public String getAdminCode() {
		return adminCode;
	}

	public void setAdminCode(String adminCode) {
		this.adminCode = adminCode;
	}

	public String getPassword() {
		return password;
	}

	public void setPassword(String password) {
		this.password = password;
	}

	public String getErrorMsg() {
		return errorMsg;
	}

	public void setErrorMsg(String errorMsg) {
		this.errorMsg = errorMsg;
	}

#cold_bold	public String getVerifyCode() {
#cold_bold		return verifyCode;
#cold_bold	}
#cold_bold
#cold_bold	public void setVerifyCode(String verifyCode) {
#cold_bold		this.verifyCode = verifyCode;
#cold_bold	}

}

步骤二:JSP上设置表单项

在login.jsp上,设置验证码文本框的name属性值,使其与LoginAction中追加的属性名相同,代码如下:

<%@page pageEncoding="utf-8"%>
<%@taglib uri="/struts-tags" prefix="s"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "/www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="/www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title></title>
<link type="text/css" rel="stylesheet" media="all" href="../styles/global.css" />
<link type="text/css" rel="stylesheet" media="all" href="../styles/global_color.css" />
<script type="text/javascript" language="javascript">
	//刷新验证码图片
			function change(image){
				//改变img的src即可,由于该URL并没有变化,因此追加动态参数伪装成变化的URL。
		image.src = "createImage?date=" + new Date().getTime();
	}
		</script>
</head>
<body class="index">
<div class="login_box">
	<form action="login" method="post">
	<table>
	<tr>
	<td class="login_info">账号:</td>
	<td colspan="2">
		<input name="adminCode" type="text" class="width150" 
			value="<s:property value="adminCode"/>"/>
	</td>
	<td class="login_error_info"><span class="required">30长度的字母、数字和下划线</span></td>
	</tr>
	<tr>
	<td class="login_info">密码:</td>
	<td colspan="2">
		<input name="password" type="password" class="width150" 
			value="<s:property value="password"/>"/>
	</td>
	<td><span class="required">30长度的字母、数字和下划线</span></td>
	</tr>
	<tr>
	<td class="login_info">验证码:</td>
#cold_bold	<td class="width70"><input name="verifyCode" type="text" class="width70" /></td>
	<td><img src="createImage" alt="验证码" title="点击更换" onclick="change(this);"/></td>
	<td><span class="required"></span></td>
	</tr>
	<tr>
	<td></td>
	<td class="login_button" colspan="2">
	<a href="javascript:document.forms[0].submit();"><img src="../images/login_btn.png" /></a>
	</td>
	<td><span class="required"><s:property value="errorMsg"/></span></td>
	</tr>
	</table>
</form>
</div>
</body>
</html>

步骤三:测试

重新部署项目,然后访问登录页,输入错误的验证码后点击登录,效果如下图:

图-8

4.4 完整代码

本案例的完整代码如下所示:

LoginAction完整代码
package com.netctoss.action;

import com.netctoss.dao.DAOFactory;
import com.netctoss.dao.ILoginDao;
import com.netctoss.entity.Admin;

/**
 *	登录校验Action
 */
public class LoginAction extends BaseAction {

	// input
	private String adminCode;// 账号
	private String password;// 密码
	private String verifyCode;// 验证码

	// output
	private String errorMsg;// 错误信息

	public String execute() {
		// 从session中取出生成的验证码
		String imageCode = (String) session.get("imageCode");
		// 验证用户输入的验证码是否与生成验证码一致
		if(imageCode == null || !imageCode.equalsIgnoreCase(verifyCode)) {
			//如果不一致,提示错误
			errorMsg = "验证码有误.";
			return "fail";
		}
		
		ILoginDao dao = DAOFactory.getLoginDAO();
		Admin admin = null;
		try {
			// 根据账号查询管理员
			admin = dao.findByCode(adminCode);
		} catch (Exception e) {
			e.printStackTrace();
			return "error";
		}

		if (admin == null) {
			// 如果管理员为空,则说明账号有误,校验失败
			errorMsg = "账号不存在.";
			return "fail";
		} else {
			// 如果管理员不为空,进一步校验密码
			if (password != null 
					&&password.equals(admin.getPassword())) {
				// 如果密码一致,校验成功
				session.put("admin", admin);
				return "success";
			} else {
				// 如果密码不一致,校验失败
				errorMsg = "密码有误.";
				return "fail";
			}
		}
	}

	public String getAdminCode() {
		return adminCode;
	}

	public void setAdminCode(String adminCode) {
		this.adminCode = adminCode;
	}

	public String getPassword() {
		return password;
	}

	public void setPassword(String password) {
		this.password = password;
	}

	public String getErrorMsg() {
		return errorMsg;
	}

	public void setErrorMsg(String errorMsg) {
		this.errorMsg = errorMsg;
	}

	public String getVerifyCode() {
		return verifyCode;
	}

	public void setVerifyCode(String verifyCode) {
		this.verifyCode = verifyCode;
	}

}

login.jsp完整代码

<%@page pageEncoding="utf-8"%>
<%@taglib uri="/struts-tags" prefix="s"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "/www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="/www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title></title>
<link type="text/css" rel="stylesheet" media="all" href="../styles/global.css" />
<link type="text/css" rel="stylesheet" media="all" href="../styles/global_color.css" />
<script type="text/javascript" language="javascript">
	//刷新验证码图片
			function change(image){
				//改变img的src即可,由于该URL并没有变化,因此追加动态参数伪装成变化的URL。
		image.src = "createImage?date=" + new Date().getTime();
	}
		</script>
</head>
<body class="index">
<div class="login_box">
	<form action="login" method="post">
	<table>
	<tr>
	<td class="login_info">账号:</td>
	<td colspan="2">
		<input name="adminCode" type="text" class="width150" 
			value="<s:property value="adminCode"/>"/>
	</td>
	<td class="login_error_info"><span class="required">30长度的字母、数字和下划线</span></td>
	</tr>
	<tr>
	<td class="login_info">密码:</td>
	<td colspan="2">
		<input name="password" type="password" class="width150" 
			value="<s:property value="password"/>"/>
	</td>
	<td><span class="required">30长度的字母、数字和下划线</span></td>
	</tr>
	<tr>
	<td class="login_info">验证码:</td>
	<td class="width70"><input name="verifyCode" type="text" class="width70" /></td>
	<td><img src="createImage" alt="验证码" title="点击更换" onclick="change(this);"/></td>
	<td><span class="required"></span></td>
	</tr>
	<tr>
	<td></td>
	<td class="login_button" colspan="2">
	<a href="javascript:document.forms[0].submit();"><img src="../images/login_btn.png" /></a>
	</td>
	<td><span class="required"><s:property value="errorMsg"/></span></td>
	</tr>
	</table>
</form>
</div>
</body>
</html>

5 资费删除

5.1 问题

增加资费的删除功能,用于删除一条资费数据。

5.2 方案

点击删除按钮时,发起一次删除的请求,并传入资费ID。本次请求的Action负责调用DAO将数据删除,然后再将请求重定向给查询Action,从而实现列表页面的刷新。

5.3 步骤

步骤一:DAO中追加删除方法

在ICostDao中追加删除方法,参数为资费ID,代码如下:

package com.netctoss.dao;

import java.util.List;
import com.netctoss.entity.Cost;

/**
 * 资费DAO接口
 */
public interface ICostDao {

	/**
	 * 查询全部资费数据
	 */
	List<Cost>findAll();
	
#cold_bold	/**
#cold_bold	 * 删除一条资费数据
#cold_bold	 * @param id 主键
#cold_bold	 */
#cold_bold	void delete(int id);

}

在CostDaoImpl中,实现删除方法,代码如下:

package com.netctoss.dao;

import java.util.ArrayList;
import java.util.List;

import com.netctoss.entity.Cost;

/**
 *	当前阶段学习重点是Struts2,对于DAO的实现就模拟实现了。
 *	同学们可以使用JDBC/MyBatis自行实现该DAO。
 */
public class CostDaoImpl implements ICostDao {

	// 此处略去其他方法的实现...

	@Override
	public void delete(int id) {
		// 模拟根据id删除资费数据
		System.out.println("删除ID为[" + id + "]的资费数据.");
	}

步骤二:Action中处理删除请求

在com.netctoss.action包下,创建删除资费Action类DeleteCostAction,并增加输入属性id,在业务方法中调用DAO的删除方法来删除该资费数据即可,代码如下:

package com.netctoss.action;

import com.netctoss.dao.DAOFactory;
import com.netctoss.dao.ICostDao;

/**
 * 删除资费Action
 */
public class DeleteCostAction {

	// input
	private int id;

	public String execute() {
		ICostDao dao = DAOFactory.getCostDAO();
		try {
			// 删除资费
			dao.delete(id);
		} catch (Exception e) {
			e.printStackTrace();
			return "error";
		}
		return "success";
	}

	public int getId() {
		return id;
	}

	public void setId(int id) {
		this.id = id;
	}

}

步骤三:struts.xml中配置删除action

在struts.xml中,资费package下配置删除资费action,删除成功时用 redirectAction类型的result将请求重定向给资费查询Action,代码如下:

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE struts PUBLIC
    "-//Apache Software Foundation//DTD Struts Configuration 2.1.7//EN"
    "/struts.apache.org/dtds/struts-2.1.7.dtd">
<struts>
	
	<!-- 
		资费模块配置信息:
		一般情况下,一个模块的配置单独封装在一个package下,
		并且以模块名来命名package的name和namespace。
	 -->
	<package name="cost" namespace="/cost" extends="struts-default">
		<!—此处略去其他Action的配置... -->
#cold_bold		<!--删除资费 -->
#cold_bold		<action name="deleteCost" 
#cold_bold			class="com.netctoss.action.DeleteCostAction">
#cold_bold			<!--删除完之后,重定向到查询action -->
#cold_bold			<result name="success" type="redirectAction">
#cold_bold				findCost
#cold_bold			</result>
#cold_bold			<result name="error">
#cold_bold				/WEB-INF/main/error.jsp
#cold_bold			</result>
#cold_bold		</action>
	</package>
	
	<!--登录模块 -->
	<package name="login" namespace="/login" extends="struts-default">
		<!—此处略去其他Action的配置... -->
	</package>
	
</struts>

步骤四:JSP中发起删除请求

在find_cost.jsp中,给删除按钮的事件绑定函数,并传入id值,在该函数中请求资费删除action,代码如下:

<%@page pageEncoding="utf-8" isELIgnored="false"%>
<%@taglib uri="/struts-tags" prefix="s"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "/www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="/www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title></title>
<link type="text/css" rel="stylesheet" media="all" href="../styles/global.css" />
<link type="text/css" rel="stylesheet" media="all" href="../styles/global_color.css" />
<script language="javascript" type="text/javascript">
            //排序按钮的点击事件
function sort(btnObj) {
if (btnObj.className == "sort_desc")
                    btnObj.className = "sort_asc";
else
                    btnObj.className = "sort_desc";
            }

            //启用
function startFee() {
                var r = window.confirm("确定要启用此资费吗?资费启用后将不能修改和删除。");
document.getElementById("operate_result_info").style.display = "block";
            }
            //删除
function deleteFee(id) {
                var r = window.confirm("确定要删除此资费吗?");
if(r) {
	// 如果用户确认,则调用删除资费action
	window.location.href = "deleteCost?id="+id;
                }
            }
</script>
</head>
<body>
<!--Logo区域开始-->
<div id="header">
<img src="../images/logo.png" alt="logo" class="left"/>
<a href="#">[退出]</a>
</div>
<!--Logo区域结束-->
<!--导航区域开始-->
<div id="navi">
<ul id="menu">
<li><a href="../index.html" class="index_off"></a></li>
<li><a href="../role/role_list.html" class="role_off"></a></li>
<li><a href="../admin/admin_list.html" class="admin_off"></a></li>
<li><a href="../fee/fee_list.html" class="fee_on"></a></li>
<li><a href="../account/account_list.html" class="account_off"></a></li>
<li><a href="../service/service_list.html" class="service_off"></a></li>
<li><a href="../bill/bill_list.html" class="bill_off"></a></li>
<li><a href="../report/report_list.html" class="report_off"></a></li>
<li><a href="../user/user_info.html" class="information_off"></a></li>
<li><a href="../user/user_modi_pwd.html" class="password_off"></a></li>
</ul>
</div>
<!--导航区域结束-->
<!--主要区域开始-->
<div id="main">
<form action="" method="">
<!--排序-->
<div class="search_add">
<div>
<!--<input type="button" value="月租" class="sort_asc" onclick="sort(this);" />-->
<input type="button" value="基费" class="sort_asc" onclick="sort(this);" />
<input type="button" value="时长" class="sort_asc" onclick="sort(this);" />
</div>
<input type="button" value="增加" class="btn_add" onclick="location.href='fee_add.html';" />
</div>
<!--启用操作的操作提示-->
<div id="operate_result_info" class="operate_success">
<img src="../images/close.png" onclick="this.parentNode.style.display='none';" />
删除成功!
</div>
<!--数据区域:用表格展示数据-->
<div id="data">
<table id="datalist">
<tr>
<th>资费ID</th>
<th class="width100">资费名称</th>
<th>基本时长</th>
<th>基本费用</th>
<th>单位费用</th>
<th>创建时间</th>
<th>开通时间</th>
<th class="width50">状态</th>
<th class="width200"></th>
</tr>

<!--使用Struts2标签遍历集合,使用OGNL表达式输出内容。 -->
<s:iterator value="costs">
	<tr>
	<td><s:property value="id"/></td>
	<td><a href="fee_detail.html"><s:property value="name"/></a></td>
	<td><s:property value="baseDuration"/></td>
	<td><s:property value="baseCost"/></td>
	<td><s:property value="unitCost"/></td>
	<td><s:property value="createTime"/></td>
	<td><s:property value="startTime"/></td>
	<td>
		<s:if test="status==0">开通</s:if>
		<s:else>暂停</s:else>
	</td>
	<td>
	<input type="button" value="启用" class="btn_start" onclick="startFee();" />
	<input type="button" value="修改" class="btn_modify" onclick="location.href='fee_modi.html';" />
#cold_bold	<input type="button" value="删除" class="btn_delete" onclick="deleteFee(<s:property value="id"/>);" />
	</td>
	</tr>
</s:iterator>

</table>
<p>业务说明:<br />
                    1、创建资费时,状态为暂停,记载创建时间;<br />
                    2、暂停状态下,可修改,可删除;<br />
                    3、开通后,记载开通时间,且开通后不能修改、不能再停用、也不能删除;<br />
                    4、业务账号修改资费时,在下月底统一触发,修改其关联的资费ID(此触发动作由程序处理)
</p>
</div>
<!--分页-->
<div id="pages">
	<a href="#">上一页</a>
<a href="#" class="current_page">1</a>
<a href="#">2</a>
<a href="#">3</a>
<a href="#">4</a>
<a href="#">5</a>
<a href="#">下一页</a>
</div>
</form>
</div>
<!--主要区域结束-->
<div id="footer">
<p>[源自北美的技术,最优秀的师资,最真实的企业环境,最适用的实战项目]</p>
<p>版权所有(C)</p>
</div>
</body>
</html>

步骤五:测试

重新部署项目并重启tomcat,打开资费列表页面,点选任意资费数据的删除按钮,效果如下图:

图-9

点击确定后,该数据被删除,页面被刷新,效果如下图:

图-10

5.4 完整代码

本案例的完整代码如下。

ICostDao完整代码:

package com.netctoss.dao;

import java.util.List;
import com.netctoss.entity.Cost;

/**
 * 资费DAO接口
 */
public interface ICostDao {

	/**
	 * 查询全部资费数据
	 */
	List<Cost>findAll();
	
	/**
	 * 删除一条资费数据
	 * @param id 主键
	 */
	void delete(int id);

}

CostDaoImpl完整代码:

package com.netctoss.dao;

import java.util.ArrayList;
import java.util.List;

import com.netctoss.entity.Cost;

/**
 *	当前阶段学习重点是Struts2,对于DAO的实现就模拟实现了。
 *	同学们可以使用JDBC/MyBatis自行实现该DAO。
 */
public class CostDaoImpl implements ICostDao {

	@Override
	public List<Cost> findAll() {
		// 模拟查询全部资费数据
		List<Cost> list = new ArrayList<Cost>();
		
		Cost c1 = new Cost();
		c1.setId(95);
		c1.setName("6元套餐");
		c1.setBaseDuration(66);
		c1.setBaseCost(6.6);
		c1.setUnitCost(0.6);
		c1.setDescr("6元套餐");
		c1.setStatus("0");
		c1.setCostType("2");
		list.add(c1);
		
		Cost c2 = new Cost();
		c2.setId(96);
		c2.setName("8元套餐");
		c2.setBaseDuration(88);
		c2.setBaseCost(8.8);
		c2.setUnitCost(0.8);
		c2.setDescr("8元套餐");
		c2.setStatus("0");
		c2.setCostType("2");
		list.add(c2);
		
		Cost c3 = new Cost();
		c3.setId(97);
		c3.setName("tarena");
		c3.setBaseDuration(99);
		c3.setBaseCost(9.9);
		c3.setUnitCost(0.9);
		c3.setDescr("tarena套餐");
		c3.setStatus("0");
		c3.setCostType("2");
		list.add(c3);
		
		return list;
	}

	@Override
	public void delete(int id) {
		// 模拟根据id删除资费数据
		System.out.println("删除ID为[" + id + "]的资费数据.");
	}

DeleteCostAction完整代码:

package com.netctoss.action;

import com.netctoss.dao.DAOFactory;
import com.netctoss.dao.ICostDao;

/**
 * 删除资费Action
 */
public class DeleteCostAction {

	// input
	private int id;

	public String execute() {
		ICostDao dao = DAOFactory.getCostDAO();
		try {
			// 删除资费
			dao.delete(id);
		} catch (Exception e) {
			e.printStackTrace();
			return "error";
		}
		return "success";
	}

	public int getId() {
		return id;
	}

	public void setId(int id) {
		this.id = id;
	}

}

struts.xml完整代码:

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE struts PUBLIC
    "-//Apache Software Foundation//DTD Struts Configuration 2.1.7//EN"
    "/struts.apache.org/dtds/struts-2.1.7.dtd">
<struts>
	
	<!-- 
		资费模块配置信息:
		一般情况下,一个模块的配置单独封装在一个package下,
		并且以模块名来命名package的name和namespace。
	 -->
	<package name="cost" namespace="/cost" extends="struts-default">
		<!-- 查询资费数据 -->
		<action name="findCost" class="com.netctoss.action.FindCostAction">
			<!-- 
				正常情况下跳转到资费列表页面。
				一般一个模块的页面要打包在一个文件夹下,并且文件夹以模块名命名。
			 -->
			<result name="success">
				/WEB-INF/cost/find_cost.jsp
			</result>
			<!-- 
				错误情况下,跳转到错误页面。
				错误页面可以被所有模块复用,因此放在main下,
				该文件夹用于存放公用的页面。
			 -->
			<result name="error">
				/WEB-INF/main/error.jsp
			</result>
		</action>
		<!-- 删除资费 -->
		<action name="deleteCost" 
			class="com.netctoss.action.DeleteCostAction">
			<!-- 删除完之后,重定向到查询action -->
			<result name="success" type="redirectAction">
				findCost
			</result>
			<result name="error">
				/WEB-INF/main/error.jsp
			</result>
		</action>
	</package>
	
	<!-- 登录模块 -->
	<package name="login" namespace="/login" extends="struts-default">
		<!-- 
			打开登录页面:
			1、action的class属性可以省略,省略时Struts2
			   会自动实例化默认的Action类ActionSupport,
			   该类中有默认业务方法execute,返回success。
			2、action的method属性可以省略,省略时Struts2
			   会自动调用execute方法。
		-->
		<action name="toLogin">
			<result name="success">
				/WEB-INF/main/login.jsp
			</result>
		</action>
		<!-- 登录校验 -->
		<action name="login" class="com.netctoss.action.LoginAction">
			<!-- 校验成功,跳转到系统首页 -->
			<result name="success">
				/WEB-INF/main/index.jsp
			</result>
			<!-- 登录失败,跳转回登录页面 -->
			<result name="fail">
				/WEB-INF/main/login.jsp
			</result>
			<!-- 报错,跳转到错误页面 -->
			<result name="error">
				/WEB-INF/main/error.jsp
			</result>
		</action>
		<!-- 生成验证码 -->
		<action name="createImage" class="com.netctoss.action.CreateImageAction">
			<!-- 使用stream类型的result -->
			<result name="success" type="stream">
				<!-- 指定输出的内容 -->
				<param name="inputName">imageStream</param>
			</result>
		</action>
	</package>
	
</struts>

find_cost.jsp完整代码:

<%@page pageEncoding="utf-8" isELIgnored="false"%>
<%@taglib uri="/struts-tags" prefix="s"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "/www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="/www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title></title>
<link type="text/css" rel="stylesheet" media="all" href="../styles/global.css" />
<link type="text/css" rel="stylesheet" media="all" href="../styles/global_color.css" />
<script language="javascript" type="text/javascript">
            //排序按钮的点击事件
function sort(btnObj) {
if (btnObj.className == "sort_desc")
                    btnObj.className = "sort_asc";
else
                    btnObj.className = "sort_desc";
            }

            //启用
function startFee() {
                var r = window.confirm("确定要启用此资费吗?资费启用后将不能修改和删除。");
document.getElementById("operate_result_info").style.display = "block";
            }
            //删除
function deleteFee(id) {
                var r = window.confirm("确定要删除此资费吗?");
if(r) {
	// 如果用户确认,则调用删除资费action
	window.location.href = "deleteCost?id="+id;
                }
            }
</script>
</head>
<body>
<!--Logo区域开始-->
<div id="header">
<img src="../images/logo.png" alt="logo" class="left"/>
<a href="#">[退出]</a>
</div>
<!--Logo区域结束-->
<!--导航区域开始-->
<div id="navi">
<ul id="menu">
<li><a href="../index.html" class="index_off"></a></li>
<li><a href="../role/role_list.html" class="role_off"></a></li>
<li><a href="../admin/admin_list.html" class="admin_off"></a></li>
<li><a href="../fee/fee_list.html" class="fee_on"></a></li>
<li><a href="../account/account_list.html" class="account_off"></a></li>
<li><a href="../service/service_list.html" class="service_off"></a></li>
<li><a href="../bill/bill_list.html" class="bill_off"></a></li>
<li><a href="../report/report_list.html" class="report_off"></a></li>
<li><a href="../user/user_info.html" class="information_off"></a></li>
<li><a href="../user/user_modi_pwd.html" class="password_off"></a></li>
</ul>
</div>
<!--导航区域结束-->
<!--主要区域开始-->
<div id="main">
<form action="" method="">
<!--排序-->
<div class="search_add">
<div>
<!--<input type="button" value="月租" class="sort_asc" onclick="sort(this);" />-->
<input type="button" value="基费" class="sort_asc" onclick="sort(this);" />
<input type="button" value="时长" class="sort_asc" onclick="sort(this);" />
</div>
<input type="button" value="增加" class="btn_add" onclick="location.href='fee_add.html';" />
</div>
<!--启用操作的操作提示-->
<div id="operate_result_info" class="operate_success">
<img src="../images/close.png" onclick="this.parentNode.style.display='none';" />
                    删除成功!
</div>
<!--数据区域:用表格展示数据-->
<div id="data">
<table id="datalist">
<tr>
<th>资费ID</th>
<th class="width100">资费名称</th>
<th>基本时长</th>
<th>基本费用</th>
<th>单位费用</th>
<th>创建时间</th>
<th>开通时间</th>
<th class="width50">状态</th>
<th class="width200"></th>
</tr>

<!-- 使用Struts2标签遍历集合,使用OGNL表达式输出内容。 -->
<s:iterator value="costs">
	<tr>
	<td><s:property value="id"/></td>
	<td><a href="fee_detail.html"><s:property value="name"/></a></td>
	<td><s:property value="baseDuration"/></td>
	<td><s:property value="baseCost"/></td>
	<td><s:property value="unitCost"/></td>
	<td><s:property value="createTime"/></td>
	<td><s:property value="startTime"/></td>
	<td>
		<s:if test="status==0">开通</s:if>
		<s:else>暂停</s:else>
	</td>
	<td>
	<input type="button" value="启用" class="btn_start" onclick="startFee();" />
	<input type="button" value="修改" class="btn_modify" onclick="location.href='fee_modi.html';" />
	<input type="button" value="删除" class="btn_delete" onclick="deleteFee(<s:property value="id"/>);" />
	</td>
	</tr>
</s:iterator>

</table>
<p>业务说明:<br />
                    1、创建资费时,状态为暂停,记载创建时间;<br />
                    2、暂停状态下,可修改,可删除;<br />
                    3、开通后,记载开通时间,且开通后不能修改、不能再停用、也不能删除;<br />
                    4、业务账号修改资费时,在下月底统一触发,修改其关联的资费ID(此触发动作由程序处理)
</p>
</div>
<!--分页-->
<div id="pages">
	<a href="#">上一页</a>
<a href="#" class="current_page">1</a>
<a href="#">2</a>
<a href="#">3</a>
<a href="#">4</a>
<a href="#">5</a>
<a href="#">下一页</a>
</div>
</form>
</div>
<!--主要区域结束-->
<div id="footer">
<p>[源自北美的技术,最优秀的师资,最真实的企业环境,最适用的实战项目]</p>
<p>版权所有(C) </p>
</div>
</body>
</html>