经过前面的分析,我们已经理清楚了业务层,接下来的部分将是web层部分.首先我们从web.xml开始,我们知
道任何一个java web应用系统都是从WEB-INF/web.xml启动的,根据servlet2.4规范filter执行是按照
web.xml配置的filter-mapping先后顺序进行执行,这里用了UrlRewriteFilter和字符编码过滤器
CharacterEncodingFilter(UTF-8),还有配置延迟加载时使用OpenSessionInView,可参考资料
http://www.iteye.com/topic/32001;另外,还有struts-clearup,以及Strut2的
org.apache.struts2.dispatcher.FilterDispatcher,注意它有两个dipatcher参数, 在这种情况下,如
果请求是以/*开头的,并且是通过request dispatcher的forward方法传递过来或者直接从客户端传递
过来的,则必须经过这个过滤器, Servlet 2.3 版新增了Filter的功能,不过它只能由客户端发出请求
来调用Filter,但若使用 RequestDispatcher.forward( )或RequestDispatcher.include( )的方法调
用Filter 时,Filter 却不会执行.这个功能应该都是主要用于UrlRewrite用的吧.而<context-param>元
素定义了一些应用级的参数,如:urlrewrite,cluster都为false,servletmapping为
*.bbscs,poststoragemode为1;接下来是listener有两个,一是
com.loaer.bbscs.web.servlet.SysListener就是对上面的变量进行操作的一个监听器,而另一个则是
spring的ContextLoaderListener,这里我们不讨论,接下来是几个servlet,提供一些常用的功能:authimg
生成验证码,rss.另外还有welcome-file及struts-tag.tld的taglib和一些error-page,如:error-
code:401--->location:-->/401.htm.对于具体的加载过程可见启动resin3等服务器后的控制台显示,我个
人觉得是加载了应用级参数,再是2个Linstener..,到spring-->加载所有bean到时空器-->各个bean的加载
(包括hibernate的配置等)--->ContextLoader启动完成-->接下来,对Filter按相反的顺序加载(struts-
>struts-clean-->characterEncoding)先是struts2的配置文件的加载,还有global messages...
注意这段时间内也会启动一些TimerTask任务...,这点可从日志中看到,最后是
ObjectTypeDeterminerFactory应该是用于struts-clean吧.还有
OpenSessionInViewFilter,CharacterEncodingFilter,UrlRewriteFilter...
这样,应用和resin服务才开始发挥作用,才能访问!
好的,我们先看在com.laoer.bbscs.web.servlet包中的几个源文件:
SysListener:它继承自HttpServlet,实现了ServletContextLinster接口.
String rootpath = sce.getServletContext().getRealPath("/");
if (rootpath != null) {
rootpath = rootpath.replaceAll("\\", "/");
} else {
rootpath = "/";
}
if (!rootpath.endsWith("/")) {
rootpath = rootpath + "/";
}
Constant.ROOTPATH = rootpath; 记录在常量java文件中.public static String
ROOTPATH = "";
我们看一个代表性的示例源码:
String poststoragemodes = sce.getServletContext().getInitParameter("poststoragemode");
if (poststoragemodes == null) {
poststoragemodes = "0";
}
Constant.POST_STORAGE_MODE = NumberUtils.toInt(poststoragemodes, 0);//文章存
储格式(这里是文件)
-->
<context-param>
<param-name>poststoragemode</param-name>
<param-value>1</param-value>
</context-param>
接下来,我们分析AuthImg,主要用它的doGet方法:
public void doGet(HttpServletRequest request, HttpServletResponse response) throws
ServletException, IOException {
response.setContentType("image/jpeg");
ServletOutputStream out = response.getOutputStream();
int width = 60, height = 20;
BufferedImage image = new BufferedImage(width, height,
BufferedImage.TYPE_INT_RGB);
//这段代码创建了一个 BufferedImage 对象,它代表一个 60像素宽、20像素高的图像。为了应用这个
图像,我们需要有图形上下文,而 BufferedImage 对象的 createGraphics() 方法就返回一个与该图像
相关的 Graphics2D 对象:
Graphics g = image.getGraphics();
Random random = new Random();
g.setColor(getRandColor(200, 250));
g.fillRect(0, 0, width, height); //背景色
g.setFont(mFont); //字体private Font mFont = new Font("Times New Roman",
Font.PLAIN, 18);
g.setColor(getRandColor(160, 200));
for (int i = 0; i < 155; i++) {
int x = random.nextInt(width);
int y = random.nextInt(height);
int xl = random.nextInt(12);
int yl = random.nextInt(12);
g.drawLine(x, y, x + xl, y + yl);//画线155条
}
String rand = RandomStringUtils.randomNumeric(4);//产生四个随机数
char c;
for (int i = 0; i < 4; i++) {
c = rand.charAt(i);
g.setColor(new Color(20 + random.nextInt(110), 20 + random.nextInt
(110), 20 + random.nextInt(110))); //各个数的着色不一样
g.drawString(String.valueOf(c), 13 * i + 6, 16);
}
WebApplicationContext wc =
WebApplicationContextUtils.getWebApplicationContext(this.getServletContext());//
webapplicationcontextutils.getwebapplicationcontext(servletcontext); 如果没有直接返回null
SysConfig sysConfig = (SysConfig) wc.getBean("sysConfig");//web层得到
sysConfig
UserCookie uc = new UserCookie(request, response, sysConfig);//写入Cookie中
uc.addAuthCode(rand);
JPEGImageEncoder encoder = JPEGCodec.createJPEGEncoder(out);//也可以用
ImageIO.write(bi,"jpg",out);
encoder.encode(image);
out.close();
}
接下来,看RSS:它也是用在doGet,有两种,一种是首页,不带bid参数,一种是带bid.用于各个版区的...
long bid;
try {
bid = Long.parseLong(request.getParameter("bid"));
} catch (NumberFormatException e) {
bid = 0L;
}
首先为了使用服务,这里用了spring的WebApplicationContext wc =
WebApplicationContextUtils.getWebApplicationContext(this.getServletContext());这样我们就可以
得到ForumService和SysConfig,BoardService..这里用了下SyndFeed feed=new SynFeedImpl();
com.sun.syndication这个包,接着便是feed.setFeedType("rss_2.0");先设置好feed源的标题/链接(有
两种,分有bid和没bid)/描述,注意这些参数大多来自于sysConfig,接下来,我们设置entry(条目)及其
description
List<SyndEntry> entries = new ArrayList<SyndEntry>();
SyndEntry entry;
SyndContent description;
我们看后半部分的代码:
Board board = boardService.getBoardByID(bid);//得到board
if (board != null) {
if (board.getBoardType() == 2 || board.getBoardType() == 3)
{
String forumLink = "";
try {
forumLink = BBSCSUtil.absoluteActionURL
(request, "/forum.bbscs?action=index&bid=" + bid)
.toString();//RSS源链接
} catch (MalformedURLException ex2) {
forumLink = "";
}
feed.setTitle(sysConfig.getForumName() + " - " +
board.getBoardName());
feed.setLink(forumLink);
feed.setDescription(sysConfig.getWebName() + " - " +
sysConfig.getForumName() + " - "
+ board.getBoardName());
List<SyndEntry> entries = new ArrayList<SyndEntry>
();//所有条目
SyndEntry entry;
SyndContent description;
Pages pages = new Pages();//构造一个Pages对象
pages.setPage(1);
pages.setPerPageNum(40);
pages.setFileName("");
PageList pl = forumService.findForumsMainWWW(bid,
pages);//重点的地方
List flist = pl.getObjectList();
for (int i = 0; i < flist.size(); i++) {
Forum f = (Forum) flist.get(i);
try {
postLink =
BBSCSUtil.absoluteActionURL(request,
"/main.bbscs?
action=read&bid=" + f.getBoardID() + "&postID=" + f.getMainID())
.toString();
} catch (MalformedURLException ex) {
postLink = "";
}
entry = new SyndEntryImpl();
entry.setTitle(f.getTitle());
entry.setLink(postLink);
entry.setPublishedDate(new Date
(f.getPostTime()));
description = new SyndContentImpl();
if (f.getEditType() == 0) {
description.setType("text/plain");//
文本类型
} else {
description.setType("text/html");
}
description.setValue(BBSCSUtil
.getSpeShortString
(forumService.getForumDetail(f, false), 400, ""));//格式化
entry.setDescription(description);
entries.add(entry);
}
feed.setEntries(entries);
try {
SyndFeedOutput output = new SyndFeedOutput
();
Document messagesDocument =
output.outputJDom(feed);
Format format = Format.getPrettyFormat();
format.setOmitDeclaration(true);
XMLOutputter xmlo = new XMLOutputter
(format);
xmlo.output(messagesDocument, out);
} catch (Exception ex) {
logger.error(ex);
}
}
}
其中:
public static URL absoluteActionURL(HttpServletRequest request, String action) throws
MalformedURLException {
return new URL(RequestUtils.serverURL(request) + getActionMappingURL(action,
request));
}
--->
public static String getActionMappingURL(String action, HttpServletRequest request) {
StringBuffer value = new StringBuffer(request.getContextPath());//获得的当前
目录路径
// Use our servlet mapping, if one is specified
String servletMapping = Constant.SERVLET_MAPPING;//*.bbscs
if (servletMapping != null) {
String queryString = null;
int question = action.indexOf("?");//action="/main.bbscs?
action=read&bid=" + f.getBoardID() + "&postID=" + f.getMainID()).toString();
if (question >= 0) {
queryString = action.substring(question);//?
action=read&bid=12&postID=123123
}
String actionMapping = getActionMappingName(action);// /main
if (servletMapping.startsWith("*.")) {
value.append(actionMapping);
value.append(servletMapping.substring(1));
//value=/main.bbcs
} else if (servletMapping.endsWith("/*")) {
value.append(servletMapping.substring(0,
servletMapping.length() - 2));
value.append(actionMapping);
} else if (servletMapping.equals("/")) {
value.append(actionMapping);
}
if (queryString != null) {
value.append(queryString);
}
}
--->
public static String getActionMappingName(String action) {
String value = action;
int question = action.indexOf("?");
if (question >= 0) {
value = value.substring(0, question);// /main.bbscs
}
int slash = value.lastIndexOf("/");
int period = value.lastIndexOf(".");
if ((period >= 0) && (period > slash)) {
value = value.substring(0, period);// /main
}
if (value.startsWith("/")) {
return (value);
} else {
return ("/" + value);
}
}
OK!接下来,我们看看登录流程先开始分析吧!
在浏览器在输入http://localhost:8080/bbscs8启动:
<welcome-file-list>
<welcome-file>index.jsp</welcome-file> //相对当前目录哦!!!
</welcome-file-list>
index.jsp触发了action login.bbscs?action=check:
<script language="javascript" type="text/javascript">
window.location.href="login.bbscs?action=check";
</script>
</head>
-->struts2发生作用...
我们看下struts.properties:
struts.devMode=false
struts.action.extension=bbscs //后缀
struts.enable.DynamicMethodInvocation=true
struts.i18n.reload=true
struts.ui.theme=simple
struts.locale=zh_CN
struts.i18n.encoding=UTF-8
struts.objectFactory=spring //由spring来代理bean
struts.objectFactory.spring.autoWire=name
struts.serve.static.browserCache=false
struts.url.includeParams=none
struts.custom.i18n.resources=com.laoer.bbscs.web.action.BaseAction //资源文件!
看后台的输出日志[com.opensymphony.xwork2.validator.ActionValidatorManagerFactory]-[INFO]
Detected AnnotationActionValidatorManager, initializing it... (注意:并不一定是这里触发哦!
接着我们看struts.xml,其中name为bbscs-default的package继承之struts-default,并引入了许多自定义
的interceptor和interceptor-stack.也设置了global-results...这些是为它们package使用的.
<package name="loginout" extends="bbscs-default" namespace="/">
<action name="login" class="loginAction">
<result name="success" type="redirect">${tourl}</result>
<result name="input">/WEB-INF/jsp/login.jsp</result>
<result name="loginPass">/WEB-INF/jsp/passLogin.jsp</result>
<interceptor-ref name="defaultStack"></interceptor-ref>//一旦继承了
struts-default包(package),所有Action都会调用拦截器栈 ——defaultStack。当然,在Action配置
中加入“<interceptor-ref name="xx" />”可以覆盖defaultStack。
<interceptor-ref name="remoteAddrInterceptor"></interceptor-ref>
<interceptor-ref name="userCookieInterceptor"></interceptor-ref>
<interceptor-ref name="requestBasePathInterceptor"></interceptor-
ref>
</action>
</package>
注意这里的login对应于spring配置文件action-servlet.xml中的loginAction:
<bean id="loginAction" class="com.laoer.bbscs.web.action.Login" //所管理的action bean
scope="prototype" autowire="byName"> //用了autowire就不用下面这段了
<!--
<property name="sysConfig">
<ref bean="sysConfig" />
</property>
-->
</bean>
好的,我们先看拦截器:<interceptor-ref name="defaultStack"></interceptor-ref>使用默认的拦截器
栈(在访问被拦截的方法或字段时,拦截器链中的拦截器就会按其之前定义的顺序被调用),我们可以打开
struts2.0core包找到struts-default.xml打开找到<interceptors>元素内容..请看资
料:http://www.blogjava.net/max/archive/2006/12/06/85925.html
<interceptor-stack name="defaultStack">
<interceptor-ref name="exception"/>
<interceptor-ref name="alias"/>
<interceptor-ref name="servletConfig"/>
<interceptor-ref name="prepare"/>
<interceptor-ref name="i18n"/>
<interceptor-ref name="chain"/>
<interceptor-ref name="debugging"/>
<interceptor-ref name="profiling"/>
<interceptor-ref name="scopedModelDriven"/>
<interceptor-ref name="modelDriven"/>
<interceptor-ref name="fileUpload"/>
<interceptor-ref name="checkbox"/>
<interceptor-ref name="staticParams"/>
<interceptor-ref name="params">
<param name="excludeParams">dojo..*</param>
</interceptor-ref>
<interceptor-ref name="conversionError"/>
<interceptor-ref name="validation">
<param name="excludeMethods">input,back,cancel,browse</param>
</interceptor-ref>
<interceptor-ref name="workflow">
<param name="excludeMethods">input,back,cancel,browse</param>
</interceptor-ref>
</interceptor-stack>
而自定义拦截器是要求拦截器是无状态的原因是Struts 2不能保证为每一个请求或者action创建一个实例
,所以如果拦截器带有状态,会引发并发问题。所有的Struts 2的拦截器都直接或间接实现接口
com.opensymphony.xwork2.interceptor.Interceptor。除此之外,大家可能更喜欢继承类
com.opensymphony.xwork2.interceptor.AbstractInterceptor。需实现其public String intercept
(ActionInvocation invocation) throws Exception .
而下面的remoteAddrInterceptor:
<interceptor name="remoteAddrInterceptor"
class="com.laoer.bbscs.web.interceptor.RemoteAddrInterceptor">
</interceptor>
我们进入web.interceptor层:
public String intercept(ActionInvocation invocation) throws Exception {
ActionContext ac = invocation.getInvocationContext();
Object action = invocation.getAction();
HttpServletRequest request = (HttpServletRequest) ac.get
(ServletActionContext.HTTP_REQUEST);//得到request请求
String userRemoteAddr = request.getRemoteAddr();
if (action instanceof RemoteAddrAware) { //action是RomoteAddrAware实例?
((RemoteAddrAware)action).setRemoteAddr(userRemoteAddr);
//System.out.println(userRemoteAddr);
}
return invocation.invoke();
}
}
我们可以看到RemoteAddrAware是如下这个接口,这是为了方便将远程地址放入action中:
public interface RemoteAddrAware {
public void setRemoteAddr(String remoteAddr);
}
接下来是userCookieInterceptor:
<interceptor name="userCookieInterceptor"
class="com.laoer.bbscs.web.interceptor.UserCookieInterceptor">
</interceptor>
public String intercept(ActionInvocation invocation) throws Exception {
ActionContext ac = invocation.getInvocationContext();
Object action = invocation.getAction();
if (action instanceof UserCookieAware) {
HttpServletRequest request = (HttpServletRequest) ac.get
(ServletActionContext.HTTP_REQUEST); //用于UserCookie
HttpServletResponse response = (HttpServletResponse) ac.get
(ServletActionContext.HTTP_RESPONSE);//用于UserCookie
ServletContext servletContext = (ServletContext) ac.get
(ServletActionContext.SERVLET_CONTEXT);
WebApplicationContext wc =
WebApplicationContextUtils.getWebApplicationContext(servletContext);//得到业务层服务!
if (wc == null) {
logger.error("ApplicationContext could not be found.");
} else {
SysConfig sysConfig = (SysConfig) wc.getBean("sysConfig");
UserCookie userCookie = new UserCookie(request, response,
sysConfig); //关键点!!!!
//logger.debug("userCookie sid:" + userCookie.getSid());
((UserCookieAware) action).setUserCookie(userCookie);
}
}
return invocation.invoke();
}
而UserCookieAware:
public interface UserCookieAware {
public void setUserCookie(UserCookie userCookie);
}
看最后一个interceptor:requestBasePathInterceptor
public String intercept(ActionInvocation invocation) throws Exception {
ActionContext ac = invocation.getInvocationContext();
Object action = invocation.getAction();
if (action instanceof RequestBasePathAware) {
HttpServletRequest request = (HttpServletRequest) ac.get
(ServletActionContext.HTTP_REQUEST);
StringBuffer sb = new StringBuffer();
sb.append(BBSCSUtil.getWebRealPath(request));//得到域名
/**
public static String getWebRealPath(HttpServletRequest request) {
StringBuffer sb = new StringBuffer();
sb.append("http://");
sb.append(request.getServerName());
if (request.getServerPort() != 80) {
sb.append(":");
sb.append(request.getServerPort());
}
return sb.toString(); //返回域名啊!
}
*/
sb.append(request.getContextPath());//request相对路径
sb.append("/");
((RequestBasePathAware) action).setBasePath(sb.toString());//设置
BasePath
}
return invocation.invoke();
}
其中,RequestBasePathAware:
public interface RequestBasePathAware {
public void setBasePath(String basePath);
}
我们回到public class Login extends BaseAction implements RequestBasePathAware,
RemoteAddrAware, UserCookieAware, SessionAware,可见这个Login实现了我们的这些Aware..并且它继
承了BaseAction,而BaseAction继承了ActionSupport,它有几个通用的方
法:getAction,setAction,getAjax,setAjax,及page.total(本来私有的)的getter/setter方法,另外还有
以下方法:
protected String executeMethod(String method) throws Exception { //子类用!
Class[] c = null;
Method m = this.getClass().getMethod(method, c);
Object[] o = null;
String result = (String) m.invoke(this, o);
return result;
}
public int boolean2int(boolean value) {
if (value) {
return 1;
} else {
return 0;
}
}
public boolean int2boolean(int value) {
if (value == 0) {
return false;
} else {
return true;
}
}
有点类似C++了!true-->1 value!=0--->true
我们进入正题Login:
首先它需要一个静态的logger:private static final Log logger = LogFactory.getLog(Login.class);
还有private static final long serivalVeserionUID...
当然,它需要get/set一下上面的basePath,remoteAddr,userCookie.另外还有一个session
作为struts,它有与表单交互的字
段:actionUrl,tourl,passwd,username,hiddenLogin,authCode,urlRewrite,useAuthCode,cookieTime=-1
等及其getter/setter方法...注意:
public boolean isUseAuthCode() {
return useAuthCode;
}
另外,我们可以看到其构造方法中:
public Login() {
this.setRadioYesNoListValues();//隐身选择是或否
this.setCookieTimeListValues();//Cookie时间选择一年/一月/一天/浏览器进程
}
private void setRadioYesNoListValues() { //private的注意哦!!
radioYesNoList.add(new RadioInt(0, this.getText("bbscs.no")));//注意getText
从资源文件BaseAction中获得字符串值!
radioYesNoList.add(new RadioInt(1, this.getText("bbscs.yes")));
}
private void setCookieTimeListValues() {
cookieTimeList.add(new RadioInt(365 * 24 * 3600, this.getText
("login.cookietime0")));//一年以365计算
cookieTimeList.add(new RadioInt(30 * 24 * 3600, this.getText
("login.cookietime1")));
cookieTimeList.add(new RadioInt(24 * 3600, this.getText
("login.cookietime2")));
cookieTimeList.add(new RadioInt(-1, this.getText("login.cookietime3")));
}
我们来看RadioInt(com.laoer.bbscs.web.ui):它是一个简单的bean,封装了两个属性int的key和String类
型的value,而公开其getter/setter方法,和下面的构造方法:
public RadioInt(int key, String value) {
this.key = key;
this.value = value;
}
当然,也有其List<RadioInt> radioYesNoList = new ArrayList<RadioInt>();
public List<RadioInt> getRadioYesNoList() {
return radioYesNoList;
}
public void setRadioYesNoList(List<RadioInt> radioYesNoList) {
this.radioYesNoList = radioYesNoList;
}
也于提供给界面用.而private只能用于类的构造之中.对于一个action,它将调用业务层来处理数据,完成
逻辑操作!这里用到了sysConfig,userService,loginErrorService,userOnlineService,在这个action类
中提供get/set,由spring的applicationContext.xml注入!我们先看
<bean id="sysConfig"
class="com.laoer.bbscs.service.config.SysConfig">
<constructor-arg>
<ref bean="configService" />
</constructor-arg>
<property name="isLoad">
<value>${bbscs.isloadconfig}</value> //bbscs.isloadconfig=false
</property>
</bean>
而我们看<bean id="configService" parent="txProxyTemplate"> //其它如userService都类似哦!!
<property name="target">
<ref bean="configTarget" />
</property>
</bean>
而txProxyTemplate是一个事务处理的TransactionProxyFactoryBean:
<bean id="txProxyTemplate" abstract="true"
class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean">
<property name="transactionManager">
<ref bean="myTransactionManager" />
</property>
<property name="transactionAttributes"> //对于如下的内容进行事务
<props>
<prop key="create*">
PROPAGATION_REQUIRED,-
com.laoer.bbscs.exception.BbscsException
</prop>
<prop key="save*">
PROPAGATION_REQUIRED,-
com.laoer.bbscs.exception.BbscsException
</prop>
<prop key="remove*">
PROPAGATION_REQUIRED,-
com.laoer.bbscs.exception.BbscsException
</prop>
<prop key="update*">
PROPAGATION_REQUIRED,-
com.laoer.bbscs.exception.BbscsException
</prop>
<prop key="del*">
PROPAGATION_REQUIRED,-
com.laoer.bbscs.exception.BbscsException//出错,报BbscsException
</prop>
<prop key="*">PROPAGATION_REQUIRED,readOnly</prop>//只读
</props>
</property>
</bean>
-->
<bean id="myTransactionManager" //事务管理器
class="org.springframework.orm.hibernate3.HibernateTransactionManager">
<property name="sessionFactory">
<ref local="sessionFactory" />
</property>
</bean>
-->
<bean id="sessionFactory" //session工厂
class="org.springframework.orm.hibernate3.LocalSessionFactoryBean">
<property name="dataSource">
<ref local="dataSource" /> //dataSource!!数据源bean.
</property>
<property name="mappingResources">
<list>
<value>com/laoer/bbscs/bean/UserInfo.hbm.xml</value>
........
<value> com/laoer/bbscs/bean/Elite-{datasource.type}.hbm.xml
</value>
</list>
</property>
<property name="hibernateProperties">
<props>
<prop key="hibernate.dialect">
${hibernate.dialect}
</prop>
<prop key="hibernate.show_sql">
${hibernate.show_sql}
</prop>
<prop key="hibernate.jdbc.fetch_size">
${hibernate.jdbc.fetch_size}
</prop>
<prop key="hibernate.jdbc.batch_size">
${hibernate.jdbc.batch_size}
</prop>
</props>
</property>
</bean>
OK!我们回到login.bbscs?action=check,这将getAction()-->check!首先它将执行execute方法:
public String execute() {
this.setUrlRewrite(Constant.USE_URL_REWRITE); //public static boolean
USE_URL_REWRITE = false;
this.setUserAuthCodeValue();
....
接下来,根据if (this.getAction().equalsIgnoreCase("index")) {...
}if (this.getAction().equalsIgnoreCase("admin")) {..
}if (this.getAction().equalsIgnoreCase("login")) {
return this.login();
}
if (this.getAction().equalsIgnoreCase("check")) {
return this.check();
}
来进行流程的选择(这就是所为的逻辑吧)!
public String check() { //对cookie的检测!
if (StringUtils.isNotBlank(this.getUserCookie().getUserName())
&& StringUtils.isNotBlank(this.getUserCookie().getPasswd()))
{
return this.cookieLogin();//有cookie
} else {
return this.index();
}
}
--->
public String index() {
this.setAction("login");
this.setHiddenLogin(0);
if (Constant.USE_URL_REWRITE) {
tourl = this.getBasePath() + "main.html"; //注意,曾经有sb.append
("/");
} else {
tourl = this.getBasePath() +
BBSCSUtil.getActionMappingURLWithoutPrefix("main");//此工具方法加后缀main.bbscs
}
return this.input();
}
-->
public String input() {//是否要登录
if (this.getSysConfig().getUsePass() == 0) {//usePass=1表示使用通行证登录
return
INPUT;//action=login,hiddenLogin=0,tourl=XXXX:80/main.bbscs,urlRewrite=false,userAuthCodeVal
ue,注意到:
private boolean urlRewrite = false;
private boolean useAuthCode = true;
private int cookieTime = -1;
还有basePath,remoteAddress,UserCookie,以及两组List等等} else {
this.setActionUrl(this.getSysConfig().getPassUrl
());//PassUrl=http://www.laoer.com/login.do
return "loginPass";
}
}
我们返回struts.xml中可以找到它将result到哪里:
<result name="success" type="redirect">${tourl}</result>
<result name="input">/WEB-INF/jsp/login.jsp</result>
<result name="loginPass">/WEB-INF/jsp/passLogin.jsp</result>
好,我们已经INPUT到/WEB-INF/jsp/login.jsp界面中了:
<%@page contentType="text/html; charset=UTF-8"%>
<%@taglib uri="/WEB-INF/struts-tags.tld" prefix="s"%>
<%@taglib uri="/WEB-INF/bbscs.tld" prefix="bbscs"%>
另外还有<%@ taglib uri="/WEB-INF/FCKeditor.tld" prefix="FCK" %>
其中的,s是struts2的,而bbscs是本系统的...
<%@page contentType="text/html; charset=UTF-8"%>
<%
String path = request.getContextPath();
String basePath = request.getScheme() + "://" + request.getServerName() + ":" +
request.getServerPort() + path + "/"; //basePath!
%>
其中,bbscs:webinfo是网站信息用的标签!<title><bbscs:webinfo type="forumname"/> - <s:text
name="login.title"/><bbscs:webinfo type="poweredby"/></title>
<tag>
<name>webinfo</name>
<tag-class>com.laoer.bbscs.web.taglib.WebInfoTag</tag-class>//WebInfoTag
<body-content>empty</body-content> //无内容的!
<attribute>
<name>type</name>
<required>true</required>
<rtexprvalue>true</rtexprvalue> //run time expression value运行时表
达式
</attribute>
</tag>
我们进入WebInfoTag一看:它有一个属性type及其get/set方法
public Component getBean(ValueStack arg0, HttpServletRequest arg1,
HttpServletResponse arg2) {
return new WebInfo(arg0, pageContext); //构造一个Component
}
protected void populateParams() {
super.populateParams();
WebInfo tag = (WebInfo) component;
tag.setType(type);
}
而WebInfo实现了Component接口,它的构造方法:
public WebInfo(ValueStack stack, PageContext pageContext) {
super(stack);
this.pageContext = pageContext;
}
这里关键的地方是:
public boolean start(Writer writer) {
boolean result = super.start(writer);
WebApplicationContext wc =
WebApplicationContextUtils.getWebApplicationContext(this.pageContext
.getServletContext()); //调用服务层
SysConfig sysConfig = (SysConfig) wc.getBean("sysConfig");//这里主要用了
sysConfg
StringBuffer sb = new StringBuffer();
if (this.getType().equalsIgnoreCase("forumname")) { //type="forunname"
sb.append(sysConfig.getForumName());
}
if (this.getType().equalsIgnoreCase("poweredby")) {//type="poweredby"
sb.append(" - ");
sb.append("Powered By BBS-CS[天乙社区]");
}
if (this.getType().equalsIgnoreCase("meta")) {//type="meta"
sb.append("<meta name="keywords" content="");
sb.append(sysConfig.getMetaKeywords());
sb.append(""/> ");
sb.append("<meta name="description" content="");
sb.append(sysConfig.getMetaDescription());
sb.append(""/>");
}
if (this.getType().equalsIgnoreCase("pagefoot")) {//type="pagefoot"
Locale locale = this.pageContext.getRequest().getLocale();
ResourceBundleMessageSource messageSource =
(ResourceBundleMessageSource) wc.getBean("messageSource");//从消息资源文件获得
if (StringUtils.isNotBlank(sysConfig.getWebName())) {
if (StringUtils.isNotBlank(sysConfig.getWebUrl())) {
sb.append("<a href="");
sb.append(sysConfig.getWebUrl());
sb.append("" target="_blank">");
sb.append(sysConfig.getWebName());
sb.append("</a>");
} else {
sb.append(sysConfig.getWebName());
}
}
if (StringUtils.isNotBlank(sysConfig.getForumName())) {
sb.append(" | ");
if (StringUtils.isNotBlank(sysConfig.getForumUrl())) {
sb.append("<a href="");
sb.append(sysConfig.getForumUrl());
sb.append("" target="_blank">");
sb.append(sysConfig.getForumName());
sb.append("</a>");
} else {
sb.append(sysConfig.getForumName());
}
}
if (StringUtils.isNotBlank(sysConfig.getWebmasterEmail())) {
sb.append(" | ");
sb.append("<a href="mailto:");
sb.append(sysConfig.getWebmasterEmail());
sb.append("">");
sb.append(messageSource.getMessage("bbscs.contactus", null,
locale));
sb.append("</a>");
}
if (StringUtils.isNotBlank(sysConfig.getPrivacyUrl())) {
sb.append(" | ");
sb.append("<a href="");
sb.append(sysConfig.getPrivacyUrl());
sb.append("" target="_blank">");
sb.append(messageSource.getMessage("bbscs.privacy", null,
locale));
sb.append("</a>");
}
if (StringUtils.isNotBlank(sysConfig.getCopyRightMsg())) {
sb.append("<BR/>");
sb.append(sysConfig.getCopyRightMsg()); //加入版权信息
}
sb.append("<BR/>");
sb.append("<strong><font face="Tahoma" size="1" color="#A0A0A4
">");
sb.append("Powered By ");
sb.append("<a href="http://www.laoer.com" target='_blank'>BBS-
CS</a>");
sb.append(" V");
sb.append(Constant.VSERION);
sb.append(" © 2007</font></strong>");
}
try {
writer.write(sb.toString()); //输入
} catch (IOException e) {
e.printStackTrace();
}
return result;
}
我们看login.jsp中,有许多struts2的标签,如s:form s:hidden s:text s:actionerror s:textfield
s:if s:else s:radio s:submit s:submit s:url (样式用cssClass)对于具体的使用请
看:http://www.blogjava.net/max/archive/2006/10/18/75857.html
注意<img alt="<s:text name="login.authcode"/>" src="authimg" align="absmiddle" />是从authimg
得到图片的(注意这里是相对URL)
也注意到:s:actionerror也用到了template.bbscs0 <s:actionerror theme="bbscs0"/>
<#if (actionErrors?exists && actionErrors?size > 0)>
<div class="errormsg">
<#list actionErrors as error>
<span class="errorMessage">${error}</span><br/>
</#list>
</div>
</#if>
注意到struts.properties文件中:
struts.ui.theme=simple
//struts.ui.templateDir=template 默认
//struts.ui.templateSuffix=ftl 默认
好的,我们提交表单,进入login.bbscs,还是最终达到Login.java
public String execute() {
this.setUrlRewrite(Constant.USE_URL_REWRITE);
this.setUserAuthCodeValue();
-->
private void setUserAuthCodeValue() {
this.setUseAuthCode(this.getSysConfig().isUseAuthCode()); //=true
}
if (this.getAction().equalsIgnoreCase("login")) {
return this.login();
}
--->
public String login() {
if (StringUtils.isBlank(this.username) || StringUtils.isBlank(this.passwd))
{ //输入的帐号和密码是否为否
this.addActionError(this.getText("error.nullerror"));
return INPUT;
}
UserInfo ui = this.getUserService().findUserInfoByUserName(this.getUsername
());//查找有没有这个用户
if (ui == null) {
this.addActionError(this.getText("error.user.notexist"));
return INPUT;
}
if (this.getSysConfig().isUseSafeLogin()) { //是否安全登录模式(例如3次登录机
会)
if (this.getLoginErrorService().isCanNotLogin(ui.getId())) {
this.addActionError(this.getText("error.login.times"));
return INPUT;
}
}
if (!Util.hash(this.getPasswd()).equals(ui.getRePasswd())) { // 密码错误
/**
public synchronized static final String hash(String data) {
if (digest == null) {
try {
digest = MessageDigest.getInstance("MD5");
} catch (NoSuchAlgorithmException nsae) {
System.err
.println("Failed to load the MD5
MessageDigest. " + "We will be unable to function normally.");
nsae.printStackTrace();
}
}
// Now, compute hash.
digest.update(data.getBytes());
return encodeHex(digest.digest());
}
*/
if (this.getSysConfig().isUseSafeLogin()) {
try {
this.getLoginErrorService
().createLoginErrorui.getId());//加入错误服务中!
} catch (BbscsException ex1) {
logger.error(ex1);
}
}
this.addActionError(this.getText("error.login.passwd"));
return INPUT;
}
if (this.getSysConfig().isUseAuthCode()) { //使用验证码
String cauthCode = this.getUserCookie().getAuthCode();//Cookie中得到
AuthCode!
if (StringUtils.isBlank(cauthCode) || !cauthCode.equals
(this.getAuthCode())) {
this.addActionError(this.getText("error.login.authcode"));
return INPUT;
}
}
ui.setLastLoginIP(ui.getLoginIP());//上一次的
ui.setLastLoginTime(ui.getLoginTime());//上一次
ui.setLoginIP(this.getRemoteAddr());
ui.setLoginTime(new Date()); //时间类型哦
ui.setUserLocale(this.getLocale().toString());
long nowTime = System.currentTimeMillis();
UserOnline uo = new UserOnline();
uo.setAtPlace("");
uo.setBoardID(0);
uo.setNickName(ui.getNickName());
uo.setOnlineTime(nowTime);//long类型的时间
uo.setUserGroupID(ui.getGroupID());
uo.setUserID(ui.getId());
uo.setUserName(ui.getUserName());
uo.setValidateCode(ui.getId() + "_" + nowTime);//构造出来的,用于避免重复登录
吧!
if (this.getHiddenLogin() == 1 || ui.getHiddenLogin() == 1) { // 用户隐身登
录
uo.setHiddenUser(1);
}
try {
ui = this.getUserService().saveAtLogin(ui); // 用户登录处理
/**
public UserInfo saveAtLogin(UserInfo userInfo) throws BbscsException {
try {
if ((System.currentTimeMillis() - userInfo.getLastLoginTime
().getTime()) > 30 * 60000) {
userInfo.setLoginTimes(userInfo.getLoginTimes() + 1);//不一
样吧!
userInfo.setExperience(userInfo.getExperience() + 1);
}
userInfo = this.getUserInfoDAO().saveUserInfo(userInfo);
this.getUserInfoFileIO().writeUserFile(userInfo);
return userInfo;
} catch (Exception ex) {
logger.error(ex);
throw new BbscsException(ex);
}
}
*/
uo = this.getUserOnlineService().createUserOnline(uo); // 加入在线用
户表
} catch (BbscsException ex) {
logger.error(ex);
return INPUT;
}
UserSession us = userService.getUserSession(ui);
/**
public UserSession getUserSession(UserInfo ui) {
UserSession us = new UserSession();
us.setEmail(ui.getEmail());
us.setGroupID(ui.getGroupID());
us.setId(ui.getId());
us.setNickName(ui.getNickName());
String[] signDetail = new String[3];
signDetail[0] = ui.getSignDetail0() == null ? "" : ui.getSignDetail0();
signDetail[1] = ui.getSignDetail1() == null ? "" : ui.getSignDetail1();
signDetail[2] = ui.getSignDetail2() == null ? "" : ui.getSignDetail2();
us.setSignDetail(signDetail);
us.setUserName(ui.getUserName());
us.setLastActiveTime(System.currentTimeMillis());
Map[] permissionMap = this.getUserPermission(ui);
us.setUserPermissionArray(permissionMap);
-->
/**
public Map[] getUserPermission(UserInfo userInfo) {
return this.getUserPermission(userInfo.getGroupID());
}
*/
return us;
}
*/
us.setValidateCode(uo.getValidateCode());//Session的validateCode改变之
this.getSession().put(Constant.USER_SESSION_KEY, us);
放入本Login本关的Session中!public static final String USER_SESSION_KEY = "user_session";这里
我们可以简单的看一下UserSession的处理,好象我们以前讲过吧,这里重新讲一次:
private String userName = "";
private String id = "";
private String nickName = "";
private String email = "";
private long lastActiveTime = 0;
private Map userPermission = new HashMap();
private Map boardPermission = new HashMap();
private Map specialPermission = new HashMap();
private Map boardSpecialPermission = new HashMap();
private long bid = 0;
private int groupID = 0;
private long addedOnlineTime = 0;
private long addedOnlineHour = 0;
private String validateCode = "";
private String[] signDetail = { "", "", "" };
private String boardPass = "";
private int initStatus = 0;
这些是它的属性,当然也有get/set;上面的us.setValidateCode就是这样工作的..我们这里重点看下:
us.setUserPermissionArray(permissionMap);
public void setUserPermissionArray(Map[] permissionMap) {
setSpecialPermission(permissionMap[1]); //特别的权力!
/**
public void setSpecialPermission(Map specialPermission) {
this.specialPermission = specialPermission;
}
而它是通过根据Permission的TypeID确定的:
Permission permission = (Permission) permissionList.get(i);
if (permission.getTypeID() == 0) {
userPermission[0].put
(permission.getResource() + "," + permission.getAction(), permission);
} else {
userPermission[1].put
(permission.getId(), permission);
}
*/
Set pset = permissionMap[0].entrySet();//Map的遍历哦!
Iterator it = pset.iterator();
while (it.hasNext()) {
Map.Entry p = (Map.Entry) it.next();
Permission permission = (Permission) p.getValue();//getValue
String[] actions = permission.getAction().split(",");
for (int i = 0; i < actions.length; i++) {
String[] resources = ((String) p.getKey()).split
(",");//getKey
this.getUserPermission().put(resources[0] + "?action=" +
actions[i], p.getValue());
}
}
}
this.getUserCookie().removeAuthCode(); //Cookie的authCode改变
this.getUserCookie().addCookies(ui);
// this.getUserCookie().addValidateCode(uo.getValidateCode());
if (this.getCookieTime() != -1) {
this.getUserCookie().addC("U", this.getUsername(),
this.getCookieTime());
this.getUserCookie().addDES("P", Util.hash(this.getPasswd()),
this.getCookieTime());//这里对UserSession和UserCookie都进行了改变...
}
return SUCCESS;
}
我们知道在进入Login之前,已经对UserCookie进行了操作:
UserCookie userCookie = new UserCookie(request, response, sysConfig);
((UserCookieAware) action).setUserCookie(userCookie);
看下面授代码:
public UserCookie(HttpServletRequest request, HttpServletResponse response, SysConfig
sysConfig) {
this.request = request;
this.response = response;