16.2 登 录 界 面
登录界面是系统的入口,在登录界面中用户可以选择要登录的账套以及使用的账号,登录成功以后应用服务器会分配一个SessionId,必须将此SessionId保存到WebSession中。完成登录界面需要三个文件:作为视图的index.jsp用来显示登录界面,LoginForm.java作为用户提交表单的模型,LoginAction.java则执行实际的登录动作。
登录界面由三个表单项构成:账套列表、用户名、密码。在用户单击【登录】按钮的时候要在客户端校验用户名是否为空,如果校验通过则提示“正在登录请稍候……”。
【例16.3】登录界面的JSP代码(index.jsp)。
代码如下:
<%@ page contentType="text/html; charset=UTF-8"%>
<%@ taglib uri="/WEB-INF/struts-bean.tld" prefix="bean"%>
<%@ taglib uri="/WEB-INF/struts-html.tld" prefix="html"%>
<%@ taglib uri="/WEB-INF/struts-logic.tld" prefix="logic"%>
<%@page import="java.util.List"%>
<%@page import="java.util.ArrayList"%>
<%@page errorPage="/errorPage.jsp" %>
<html:html locale="true">
<head>
</head>
<html:base />
<title>登录</title>
<link rel="stylesheet" type="text/css" href="css/style.css">
<script>
function checkForm()
{
if(document.loginForm.userName.value=="")
{
alert("请输入用户名");
return false;
}
document.getElementById("loadingDiv").style.visibility = "visible";
return true;
}
</script>
</head>
<body bgColor=#ffffff>
<table width="379" height="228" border="0" align="center"
background="images/login.jpg">
<tr>
<td width="122"></td>
<td width="241">
<html:form
action="loginAction.do" method="POST" onsubmit="return
checkForm()">账 套:<html:select property="acName">
<html:optionsCollection property="ACInfos"
label="displayName" value="name" />
</html:select><br>
账户名:<html:text property="userName" styleClass="input1" value="admin1"></html:text><br>
密 码:<html:password property="password" styleClass="input1"></html:password><br>
<html:submit styleClass="button1">登录</html:submit><br/>
<html:errors />
<div
id="loadingDiv" style="visibility:hidden">正在登录请
稍候……</div>
</html:form>
</td>
</tr>
</table>
</body>
</html:html>
在表单上设置了onSubmit事件监听器,这样当用户单击“登录”按钮的时候会首先执行JavaScript代码checkForm,只有checkForm方法返回true,表单才被提交。在checkForm方法中校验用户名是否为空,如果为空则提示用户“请输入用户名”,否则将初始状态为隐藏的包含文字“正在登录请稍候……”的层loadingDiv显示出来,然后返回true。
得到账套列表的方法getACInfos定义在LoginForm中,当index.jsp初始化的时候会调用LoginForm的getACInfos方法得到账套列表,并使用<html:optionsCollection/>标记将列表显示到下拉列表中,label="displayName"、 value="name" 表示显示ACInfo的displayName属性,而内部保存的属性则为name。
图16.1是Web端的登录界面。

图16.1 登录界面
LoginForm有三个作用:一是保存index.jsp提交过来的表单中的账套名acName、用户名userName及密码password三个字段;二是提供得到账套列表的getACInfos方法;三是对提交的表单进行校验。
【例16.4】登录界面的Form类。
代码如下:
// LoginForm.java
public class LoginForm extends ActionForm
{
private String acName;
private String userName;
private String password;
public String getAcName()
{
return acName;
}
public void setAcName(String acName)
{
this.acName = acName;
}
public String getPassword()
{
return password;
}
public void setPassword(String password)
{
this.password = password;
}
public String getUserName()
{
return userName;
}
public void setUserName(String userName)
{
this.userName = userName;
}
/**
* 得到账套列表
*/
public ACInfo[] getACInfos()
{
ILoginService loginService = (ILoginService) WebEndServiceLocator
.getService((String)null,ILoginService.class);
return loginService.getAllACInfo();
}
public ActionErrors validate(ActionMapping mapping,
HttpServletRequest request)
{
ActionErrors errors = new ActionErrors();
if(StringUtils.isEmpty(acName))
{
errors.add("acName",new ActionMessage("请选择账套",false));
}
if(StringUtils.isEmpty(userName))
{
errors.add("userName",new ActionMessage(
"用户名不能为空",false));
}
return errors;
}
}
getACInfos方法直接将ILoginService接口的getAllACInfo方法的返回值作为自身的返回值。在登录之前还没有SessionId,所以在此处将SessionId参数设定为null。
在validate方法中校验用户提交的表单中账套名和用户名是否为空,虽然在客户端的JavaScript代码中校验用户名不为空了,但是客户端永远是不可信的,必须在服务端重新校验。案例系统没有多语言支持的要求,因此对于校验信息简化处理,即不定义在配置文件中,而是直接写在代码中,ActionMessage构造函数的第二个参数表示第一个参数是配置文件的键还是一个单纯的消息字符串,此处设置为false,"用户名不能为空"这样的字符串表示单纯的消息字符串,无须到配置文件中去进行消息的转换。
【例16.5】进行实际登录处理的Action类。
代码如下:
// LoginAction.java
public class LoginAction extends Action
{
public ActionForward execute(ActionMapping mapping, ActionForm form,
HttpServletRequest request, HttpServletResponse response)
throws Exception
{
LoginForm loginForm = (LoginForm) form;
String userName = loginForm.getUserName();
String password = loginForm.getPassword();
String acName = loginForm.getAcName();
ILoginService loginService = (ILoginService) WebEndServiceLocator
.getService((String)null,ILoginService.class);
try
{
String sessionId = loginService.login(userName, password, acName);
HttpSession session = request.getSession();
// 将应用sessionId放入WebSession
session.setAttribute(WebConstant.SESSIONID, sessionId);
ICommonService cs = (ICommonService) WebEndServiceLocator
.getService(sessionId, ICommonService.class);
// 将当前用户值对象放入WebSession
session.setAttribute(WebConstant.CURUSERINFO,
cs.getCurrentUser());
return mapping.findForward("mainPage");
} catch (LoginServiceException lse)
{
saveErrors(request, WebExceptionUtils.toActionMessages(lse));
return mapping.getInputForward();
}
}
}
这里调用ILoginService 接口的login实现登录操作,如果登录正确则将应用服务器分配的SessionId保存到WebSession中,并调用ICommonService 的getCurrentUser方法将当前用户的信息也保存到WebSession中,最后跳转到主页面。
如果出现密码错误、用户已被冻结等问题,则login方法中会抛出LoginServiceException 异常,所以在这里捕捉LoginServiceException 异常,并将异常消息显示给用户。WebExceptionUtils中的toActionMessages方法用来将异常对象中的异常消息转换成ActionMessages对象,其实现如下:
// Web端异常工具类
package com.cownew.PIS.framework.web.helper;
import org.apache.struts.action.ActionErrors;
import org.apache.struts.action.ActionMessage;
import org.apache.struts.action.ActionMessages;
public class WebExceptionUtils
{
public static ActionMessages toActionMessages(Exception e)
{
ActionMessages errors = new ActionMessages();
errors.add(ActionErrors.GLOBAL_MESSAGE, new ActionMessage(e
.getMessage(), false));
return errors;
}
}
index.jsp、LoginForm.java和LoginActon.java 这3个文件编写完毕后,在struts-config.xml中将三者的对应关系配置起来即可。
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE struts-config PUBLIC "-//Apache Software Foundation//DTD Struts Configuration 1.1//EN" "http://jakarta.apache.org/struts/dtds/struts-config_1_1.dtd">
<struts-config>
<form-beans>
<form-bean name="loginForm"
type="com.cownew.PIS.framework.web.LoginForm" />
</form-beans>
<global-forwards>
<forward name="errorPage" path="/errorPage.jsp" />
<forward name="loginPage" path="/index.jsp" />
<forward name="mainPage" path="/mainPage.jsp" />
</global-forwards>
<action-mappings>
<action input="/index.jsp" name="loginForm" path="/loginAction"
scope="request" type="com.cownew.PIS.framework.web.LoginAction"
validate="true" />
</action-mappings>
<message-resources parameter="ApplicationResources" />
</struts-config>
16.2.1 登出系统
当用户退出系统的时候如果执行退出操作就能够及早释放服务器端资源。因为登录操作涉及不到表单等问题,所以不需要编写JSP页面作为视图,也不需要Form作为模型,只要开发一个登出Action即可。Struts支持没有JSP和Form的Action,直接调用Action即可执行Action的execute方法,这时候的Action相当于一个Servlet,不过与Servlet相比能更方便地使用Struts提供的一些功能。
【例16.6】登出操作对应的Action。
代码如下:
// LogoutAction.java
public class LogoutAction extends Action
{
public ActionForward execute(ActionMapping mapping, ActionForm form,
HttpServletRequest request, HttpServletResponse response)
throws Exception
{
String sessionid = WebContextHelper.getSessionId(request);
ILoginService loginService = (ILoginService) WebEndServiceLocator
.getService(request, ILoginService.class);
loginService.logout(sessionid);
request.getSession().invalidate();
return mapping.findForward("loginPage");
}
}
在execute方法中首先调用ILoginService接口的logout方法登出应用服务器,然后调用HttpSession的invalidate方法将WebSession销毁,最后转向登录界面。
16.2.2 心跳页面
Web客户端同样存在Swing客户端的长时间无操作造成的Session失效问题,必须定时调用ICommonService接口提供的心跳操作。
可以使用HTML的元信息头实现刷新,HTML提供了一种要求浏览器定时刷新页面的方式,也就是在HTML的head标记内加入<meta http-equiv="refresh" content="60">,这样浏览器就可以每分钟刷新本页面了。
【例16.7】心跳页面(heartBeat.jsp)。
代码如下:
<%@ page language="java" contentType="text/html; charset=GB18030"
pageEncoding="GB18030"%>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<%@page import="com.cownew.PIS.framework.common.services.ICommonService"%>
<%@page import="com.cownew.PIS.framework.web.helper.WebEndServiceLocator"%>
<%@page import="com.cownew.ctk.common.StringUtils"%>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=GB18030">
<!-- 每隔60秒刷新一次 -->
<meta http-equiv="refresh" content="60">
<title>HeartBeat</title>
</head>
<body>
<%
ICommonService cs = (ICommonService)WebEndServiceLocator
.getService(request,ICommonService.class);
try
{
cs.nop();
}
catch(Throwable t)
{
//心跳操作不能停
out.println(StringUtils.stackToString(t));
}
%>
</body>
</html>
为了防止nop方法调用出现异常造成“心跳停止”,这里将所有可能的异常全部截获并且打印出来。
这个心跳页面必须放到用户永远都会打开的页面上,后边将会讲到的主菜单页面是用户在运行期间一直打开的页面,所以将心跳页面嵌入到主菜单页面中是比较好的选择。






