现在的位置: 首页 > 综合 > 正文

对天乙社区bbscs8实现的详细分析三

2017年12月07日 ⁄ 综合 ⁄ 共 92223字 ⁄ 字号 评论关闭

经过前面的分析,我们已经理清楚了业务层,接下来的部分将是web层部分.首先我们从web.xml开始,我们知

道任何一个java web应用系统都是从WEB-INF/web.xml启动的,根据servlet2.4规范filter执行是按照

web.xml配置的filter-mapping先后顺序进行执行,这里用了UrlRewriteFilter和字符编码过滤器

CharacterEncodingFilter(UTF-8),还有配置延迟加载时使用OpenSessionInView,可参考资料

http://www.javaeye.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(" &copy; 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;
  this.sysConfig = sysConfig;
  try {
   des = new DES(DES._DESede);//DES算法
/**
DES 64位密钥, 有效密钥56位, 8位用来校验.
DES ede, 密钥应该是64*2=128位, 有效密钥56*2=112位  -->16字节
Blowfish 密钥40--448位.
*/
  } catch (Exception ex) {
   logger.error(ex);
  }
  getCookies();
 }
从request,response原处引入这样参数到UserCookie中!
 private HttpServletRequest request;
 private HttpServletResponse response;
 private SysConfig sysConfig;
 private DES des;
getCookies...将查找如下key相关的Cookie信息:
 private static final String PASS_USERNAME_KEY = "PASS_USERNAME";//用于单点登录
 private static final String PASS_USERNAME_DES_KEY = "PASS_USERNAME_DES";//用于单点登


 private static final String BBSCS_FORUMPERNUM_KEY = "FN";
 private static final String BBSCS_POSTPERNUM_KEY = "PN";
 private static final String BBSCS_TIMEZONE_KEY = "TZ";
 private static final String BBSCS_FORUMVIEWMODE_KEY = "VM";
 private static final String BBSCS_LASTSENDNOTETIME_KEY = "LN";
 private static final String BBSCS_LASTPOSTTIME_KEY = "LP";
 private static final String BBSCS_EDITTYPE = "ET";
 private static final String BBSCS_AUTHCODE = "AC";
 private static final String BBSCS_USERNAME = "U";//用于BBSCS
 private static final String BBSCS_PASSWD = "P";//用于BBSCS
当然,它们也有初始值:
 private int postPerNum = 10;
 private int forumPerNum = 20;
 private int forumViewMode = 0;
 private String timeZone = "GMT+08:00";
 private String pusername = "";
 private String pusernamedes = "";
 private long lastSendNoteTime = 0;
 private long lastPostTime = 0;
 private int editType = 0;
 private String authCode = "";
 private String userName = "";
 private String passwd = "";
这里有request的使用Cookie cookies[] = request.getCookies();
 if (this.sysConfig.isUsePass()) { /**数据库UsePass=0,usePass=1可能指的是用于多个系

统之间的登录问题(使用通行证)*/
      if (sCookie.getName().equals

(PASS_USERNAME_KEY)) {
       this.pusername = sCookie.getValue();
       // System.out.println("pass

username:" + username);
      }
      if (sCookie.getName().equals

(PASS_USERNAME_DES_KEY)) {
       if (StringUtils.isNotBlank

(sCookie.getValue())) {
        buf = Util.base64decodebyte

(sCookie.getValue());
        byte[] dec = des.decode(buf,

Util.base64decodebyte(this.sysConfig.getCookieKey()));//Enc-Base64位加密
        this.pusernamedes = new

String(dec);
        // System.out.println("pass

usernamedes:" +
        // usernamedes);
       }
      }
     }
我们看验证码的一段:     
if (sCookie.getName().equals(BBSCS_AUTHCODE)) {
      if (StringUtils.isNotBlank(sCookie.getValue

())) {
       buf = Util.base64decodebyte

(sCookie.getValue());
       byte[] dec = des.decode(buf,

Util.base64decodebyte(this.sysConfig.getCookieKey()));
       this.authCode = new String(dec);
      }
     }
而我们回到AuthImg:
UserCookie uc = new UserCookie(request, response, sysConfig);
  uc.addAuthCode(rand);//这里用了UserCookie中的addAuthCode方法:
public void addAuthCode(String authCode) {
  this.addDES(BBSCS_AUTHCODE, authCode, -1);
 }
//而对于authCode其实它用了DES算法:
public void addC(String name, String value, int maxage) { //普通加Cookie的方法
  Cookie cookies = new Cookie(name, value);
  cookies.setPath(this.sysConfig.getCookiePath());
  cookies.setMaxAge(maxage);
  // cookies.setMaxAge(30 * 60);
  if (StringUtils.isNotBlank(this.sysConfig.getCookieDomain())) {//域名,用于

单点登录
   cookies.setDomain(this.sysConfig.getCookieDomain());
  }
  this.response.addCookie(cookies);//这里用到了response!
 }

 public void addDES(String name, String value, int maxage) {
  try {
   // DES des = new DES(DES._DESede);
   des.setKey(Util.base64decodebyte(this.sysConfig.getCookieKey()));//

加入密钥!
/**数据库中CookieKey=nhNhwZ6X7xzgXnnZBxWFQLwCGQtJojL3*/
   byte[] enc = des.encode(value.getBytes());
   value = Util.base64Encode(enc);
/**
public static String base64Encode(byte[] txt) {
  String encodeTxt = "";
  if (txt != null && txt.length > 0) {
   encodeTxt = new sun.misc.BASE64Encoder().encode(txt);
  }
  return encodeTxt;
 }
*/
   Cookie cookies = new Cookie(name, value);
   cookies.setPath(this.sysConfig.getCookiePath());
   // cookies.setMaxAge(30 * 60);
   cookies.setMaxAge(maxage);
   if (StringUtils.isNotBlank(this.sysConfig.getCookieDomain())) {
    cookies.setDomain(this.sysConfig.getCookieDomain());
   }
   this.response.addCookie(cookies);
  } catch (Exception ex) {
   // ex.printStackTrace();
   logger.error("addDES(String name, String value)" + ex);
  }
 }
好,我们暂时回到Login.java:
 this.getUserCookie().removeAuthCode();
/**
 public void removeAuthCode() {
  this.addC(BBSCS_AUTHCODE, "", 0);
 }
*/
  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());
  }
这里将一些登录特性ui用addCookies加入了其UserCookie中!
 public void addCookies(UserInfo ui) {
  this.forumPerNum = ui.getForumPerNum();
  addC(BBSCS_FORUMPERNUM_KEY, String.valueOf(ui.getForumPerNum()), -1);
  this.postPerNum = ui.getPostPerNum();
  addC(BBSCS_POSTPERNUM_KEY, String.valueOf(ui.getPostPerNum()), -1);
  this.timeZone = ui.getTimeZone();
  addC(BBSCS_TIMEZONE_KEY, Util.base64Encode(ui.getTimeZone()), -1);
  this.forumViewMode = ui.getForumViewMode();
  addC(BBSCS_FORUMVIEWMODE_KEY, String.valueOf(ui.getForumViewMode()), -1);
  this.editType = ui.getEditType();
  addC(BBSCS_EDITTYPE, String.valueOf(ui.getEditType()), -1);

 }
OK!return SUCCESS;到<result name="success" type="redirect">${tourl}</result>
当然,我们先讲下cookieLogin方法先:它由check发现后转到这里...
public String check() {
  if (StringUtils.isNotBlank(this.getUserCookie().getUserName())
    && StringUtils.isNotBlank(this.getUserCookie().getPasswd()))

{
   return this.cookieLogin();
  } else {
   return this.index();
  }
 }
我们这里可以观察其不同点在于:
UserInfo ui = this.getUserService().findUserInfoByUserName(this.getUserCookie().getUserName

());
  if (ui == null) {
   this.addActionError(this.getText("error.user.notexist"));
   return INPUT;
  }
if (!this.getUserCookie().getPasswd().equals(ui.getRePasswd())) { 
   if (this.getSysConfig().isUseSafeLogin()) {
    try {
     this.getLoginErrorService().createLoginError

(ui.getId());
    } catch (BbscsException ex1) {
     logger.error(ex1);
    }
   }
   this.addActionError(this.getText("error.login.passwd"));
   return INPUT;
  }
以及后面的tourl的设置,因为check中并没来的急设置它:
if (Constant.USE_URL_REWRITE) {
   tourl = this.getBasePath() + "main.html";
  } else {
   tourl = this.getBasePath() +

BBSCSUtil.getActionMappingURLWithoutPrefix("main");
  }
哦,我们还少分析了loginPass:
 public String input() {
  if (this.getSysConfig().getUsePass() == 0) {
   return INPUT;
  } else {
   this.setActionUrl(this.getSysConfig().getPassUrl

());//http://www.laoer.com/login
   return "loginPass";//而其对应的passLogin.jsp却比login.jsp简单...不知

有什么用...
  }
 }
对了,我们还有
 if (this.getAction().equalsIgnoreCase("admin")) {
   this.setAction("login");
   this.setHiddenLogin(0);
   tourl = this.getBasePath() +

BBSCSUtil.getActionMappingURLWithoutPrefix("adminMain");//注意点
   return this.input();
  }
  if (this.getAction().equalsIgnoreCase("relogin")) {
   this.setAction("login");
   this.setHiddenLogin(0);

   if (Constant.USE_URL_REWRITE) {
    tourl = this.getBasePath() + "main.html";
   } else {
    tourl = this.getBasePath() +

BBSCSUtil.getActionMappingURLWithoutPrefix("main");
   }
   this.addActionError(this.getText("error.login.re"));//注意点
   return this.input();
  }
首先我们看Login SUCCESS指的是什么:
public interface Action
{    public abstract String execute()
        throws Exception;
    public static final String SUCCESS = "success";
    public static final String NONE = "none";
    public static final String ERROR = "error";
    public static final String INPUT = "input";
    public static final String LOGIN = "login";
}
OK,<result name="success" type="redirect">${tourl}</result>转到tourl去/main.bbscs(未用

UrlRewriter)
为了先把用户这块讲完,我们选择了注册流程来分析:/reg/input
<package name="reg" extends="bbscs-default" namespace="/reg"> //namespace哦!
  <global-results>
   <result name="input">/WEB-INF/jsp/reg.jsp</result>
   <result name="passreg" type="redirect">
    ${sysConfig.getPassRegUrl()}
   </result>
  </global-results>
  <action name="input" class="regAction" method="input"></action>//注意它没有

什么result
我们到action-servlet.xml找到<bean id="regAction" class="com.laoer.bbscs.web.action.Reg"
  scope="prototype" autowire="byName"></bean>
进入com.laoer.bbscs.web.action.Reg extends BaseAction implements RemoteAddrAware,

UserCookieAware:
它有

answer,email,nickName,passwd,question,rePasswd,userName,validateCode,authCode,useAuthCode=tr

ue,userRemoteAddr,userCookie,也注入了一此服务sysConfig,ipSeeker,templateMail,sysStatService
我们看下input:
public String input() throws Exception {
  if (this.getSysConfig().getUsePass() == 1) {//使用通行证
   return "passreg";
  }
  //usePass=0
  if (this.getSysConfig().getOpenUserReg() == 0) { // 关闭注册
   addActionError(this.getText("error.reg.notallowreg"));
   return ERROR;
  }
  this.setUseAuthCode(this.getSysConfig().isUseRegAuthCode());//=1
  this.setAction("add");
  return INPUT;
 }
好,我们看reg.jsp:
 <tr>
    <td width="170"><div align="right"><s:text name="reg.username"/>:<span

class="font2">*</span></div></td>
    <td width="180">
      <s:textfield name="userName" id="userName" cssClass="input1" size="30" maxlength="20"

onfocus="changeStyle('usernameMsg','msg2');" onblur="changeStyle

('usernameMsg','msg1');"></s:textfield>
    </td>
    <td width="370">
      <div class="msg1" id="usernameMsg"><s:text name="reg.username.notice"/></div>
      <s:fielderror theme="bbscs0"> //bbscs0的fielderror
      <s:param>userName</s:param>
      </s:fielderror>
    </td>
  </tr>

 <tr>
    <td class="t1">&nbsp;</td>
    <td valign="top"><div align="center">
      <input type="button" name="Check" value="<s:text name="reg.checkusename"/>"

class="button1" onclick="checkUserNameAction();"/> //触发js
    </div></td>
    <td>
      <div id="checkUserNameMsg">
      </div>
    </td>
  </tr>
<s:if test="%{useAuthCode}">
  <tr>
    <td><div align="right"><s:text name="login.authcode"/>:<span

class="font2">*</span></div></td>
    <td>
      <s:textfield name="authCode" id="authCode" cssClass="input1" onfocus="changeStyle

('authCodeMsg','msg2');" onblur="changeStyle('authCodeMsg','msg1');" size="5"

maxlength="50">
      </s:textfield>
      <img alt="<s:text name="login.authcode"/>" src="<%=basePath%>authimg"

align="absmiddle" />
    </td>
    <td><div class="msg1" id="authCodeMsg"><s:text name="reg.authcode.motice"/></div>
    <s:fielderror theme="bbscs0">
      <s:param>authCode</s:param>
      </s:fielderror>
    </td>
  </tr>
  </s:if>
对于这三段代码,我们一个一个来分析之:
function changeStyle(elementID,toStyle) {
  document.getElementById(elementID).className=toStyle;
}
注意到:
<link href="<%=basePath%>css/css1.css" rel="stylesheet" type="text/css">
<script type="text/javascript" src="<%=basePath%>js/jsMsg.jsp"></script>
<script type="text/javascript" src="<%=basePath%>js/prototype.js"></script>
<script type="text/javascript" src="<%=basePath%>js/comm.js"></script>
css1中有一段样式如下:
.msg1 {
 color: #999999;
 padding: 6px;
}
.msg2 {
 background-color: #EDF3FA;
 border: 1px solid #408FD0;
 padding: 5px 5px 5px 25px;
 background-image: url(../images/standard_msg_warning.gif);
 background-repeat: no-repeat;
 background-position: 5px 5px;
}
.msg3 {
 background-color: #F0FFF0;
 border: 1px solid #00EA00;
 padding: 5px 5px 5px 25px;
 background-image: url(../images/standard_msg_ok.gif);
 background-repeat: no-repeat;
 background-position: 5px 5px;
}

.errormsg {
 background-color: #FFEFEE;
 border: 1px solid #FF6860;
 padding: 5px 5px 5px 25px;
 background-image: url(../images/standard_msg_error.gif);
 background-repeat: no-repeat;
 background-position: 5px 5px;
 line-height: 16pt;
}
而第三个authcode是否要取决于useAuthCode:在Reg.java中private boolean useAuthCode = true;
对于s:fielderror用于theme bbscs0 且这个标签带param!请自行参看源代码! 
  <div class="errormsg">
    <#assign doneStartUlTag=true><#t/>
   </#if><#t/>
   <#list eValue as eEachValue><#t/>
    <span class="errorMessage">${eEachValue}</span><br/>
   </#list><#t/>
  </#if><#t/>
  </#list><#t/>
 </#list><#t/>
 <#if (haveMatchedErrorField && (!doneEndUlTag))><#t/>
 </div>
我们这样重点看下:checkUserNameAction,这个请求是基于Ajax的:
function checkUserNameAction() {
  if ($('userName').value == "" || $('userName').length == 0) {
    alert("<s:text name="reg.inputusername"/>");
    return;
  }
  $('checkUserNameMsg').className = "msg2";
  $('checkUserNameMsg').innerHTML = "<s:text name="bbscs.checking"/>";
  var url = getActionMappingURL("/reg/check");
/**在comm.js中:
function getActionMappingURL(action) {
  var value = contextPath;//变量大多在jsMsg中定义!
  var servletMapping = servletMappingStr;
  var queryString;
  var question = action.indexOf("?");
  if (question >= 0) {
    queryString = action.substring(question);
  }

  var actionMapping = getActionMappingName(action);
  if (startsWith(servletMapping,"*.")) {
    value += actionMapping;
    value += servletMapping.substring(1);
  }
  else if (endsWith(servletMapping,"/*")) {
    value += servletMapping.substring(0, servletMapping.length - 2);
    value += actionMapping;
  }
  else if (servletMapping == "/") {
    value += actionMapping;
  }
  if (queryString != undefined) {
    value += queryString;
  }
  return value;
}

*/
  var pars = "userName=" + $('userName').value;
  var myAjax = new Ajax.Request(url, {method: 'get', parameters: pars, onComplete:

showResult}); //prototype的Ajax.Request关键点
}
function showResult(res) {
  resText = res.responseText;
  var jsonMsgObj = new JsonMsgObj(resText);
/**
var JsonMsgObj = function(responseText) {
  this.json = eval('(' + responseText + ')');
}
JsonMsgObj.prototype.getCodeid = function() {
  return this.json.codeid;
}

JsonMsgObj.prototype.getMessage = function() {
  return this.json.message;
}

JsonMsgObj.prototype.getText = function() {
  return this.json.text;
}

*/
  //alert(jsonMsgObj.getCodeid());
  var codeid = jsonMsgObj.getCodeid();
  if (codeid == "0") {
    $('checkUserNameMsg').className = "msg3"; //OK
  }
  else {
    $('checkUserNameMsg').className = "errormsg";
  }
  $('checkUserNameMsg').innerHTML = jsonMsgObj.getMessage();
}
这里用了json和prototype,设置了传回来的Json为utf-8,而且把json数据直接扔进responseText回来后自

己eval的,OK!我们进入后端:/reg/check?userName=?
<action name="check" class="checkUserNameAction"></action>
-->
<bean id="checkUserNameAction"
  class="com.laoer.bbscs.web.action.CheckUserName" scope="prototype"
  autowire="byName">
  <!--
   <property name="userService">
   <ref bean="userService" />
   </property>
   <property name="sysConfig">
   <ref bean="sysConfig" />
   </property>
   <property name="ajaxMessagesJson">  //注意哦!
   <ref bean="ajaxMessagesJson" />
   </property>
  -->
 </bean>
由于它与Json有联系,因此写在一个专门的文件中..它首先引入了

sysConfig,ajaxMessagJson,userService三个服务,也有一个属性userName;下面是其execute方法
 public String execute() {
  if (StringUtils.isBlank(this.getUserName())) {
   this.getAjaxMessagesJson().setMessage("E_USERNAME_001", "请填写用户

名!");
  }
  else if (!Util.validateUserName(this.getUserName())) {
/**
 public static boolean validateUserName(String username) {
  Pattern p = Pattern.compile("^/w+$");
  Matcher m = p.matcher(username);
  if (m.find()) {
   return true;
  }
  return false;
 }
*/
   this.getAjaxMessagesJson().setMessage("E_USERNAME_002", "用户名只能

由英文、数字和下划线组成!");
  }
  else if (this.getSysConfig().isCanNotRegUserName(this.getUserName())) {
   this.getAjaxMessagesJson().setMessage("E_USERNAME_004", "该用户不能

注册!");
  }
  else {
   UserInfo userInfo = this.getUserService().findUserInfoByUserName

(this.getUserName());
   if (userInfo != null) {
    this.getAjaxMessagesJson().setMessage("E_USERNAME_003", "用

户名已存在,请选择其他用户名!");
   }
   else {
    this.getAjaxMessagesJson().setMessage("0", "该用户名可以注册

!");//返回0可以注册哦!其它不行
   }
  }

  // System.out.println(this.getAjaxMessagesJson().getJsonString());
  return RESULT_AJAXJSON;//ajaxjson是个页面,输出json数据,这个我们可以看bbscs

-default package中的global-results中<result name="ajaxjson">/WEB-

INF/jsp/ajaxjson.jsp</result>
/**看BaseAction中的常量:
 public static final String RESULT_AJAXJSON = "ajaxjson";
 public static final String RESULT_HTMLERROR = "htmlError";
 public static final String RESULT_ERROR = "error";
 public static final String RESULT_JSONSTRING = "jsonstring";
而下面是ajaxjson.jsp的一部分:
<%
request.setAttribute("decorator", "none");
response.setHeader("Cache-Control","no-cache"); //HTTP 1.1
response.setHeader("Pragma","no-cache"); //HTTP 1.0
response.setDateHeader ("Expires", 0); //prevents caching at the proxy server
%>
<s:property value="ajaxMessagesJson.getJsonString()" escape="false"/>
*/
 }

我们看下AjaxMessagesJson,它在com.laoer.bbscs.web.ajax(也就只有它一个文件):
public class AjaxMessagesJson {
 private static final Log logger = LogFactory.getLog(AjaxMessagesJson.class);
 private JSONObject json = new JSONObject();//用了json-lib-1.1-jdk15.jar
 public void setMessage(String codeid, String message) {
  try {
   this.json.put("codeid", codeid);//codeid
   this.json.put("message", message);//message
   this.json.put("text", "");//空的text
  } catch (JSONException e) {
   logger.error(e);
  }
 }

 public void setMessage(String codeid, String message, String text) {
  try {
   this.json.put("codeid", codeid);
   this.json.put("message", message);
   this.json.put("text", text);//不为空的text
  } catch (JSONException e) {
   logger.error(e);
  }
 }

 public String getJsonString() {   //返回JsonString结果!
  return this.json.toString();
 }
}
好了,完成了用户名的检查和输入其它字段,后我们点击提交按钮,我们看生成的html代码:
<form id="add" name="add" onsubmit="return true;" action="/bbscs8/reg/add.bbscs"

method="post">
好,进入struts.xml:
<action name="add" class="regAction" method="add">
   <interceptor-ref name="remoteAddrInterceptorStack"></interceptor-

ref>
   <interceptor-ref name="userCookieInterceptor"></interceptor-ref>
   <result name="success" type="redirect">
    /regSucceed.jsp
   </result>
  </action>
由于remoteAddrInterceptorStack包括defaultStack中的验证validation!
<interceptor-stack name="remoteAddrInterceptorStack">
    <interceptor-ref name="defaultStack"></interceptor-ref>
    <interceptor-ref name="remoteAddrInterceptor"></interceptor

-ref>
   所以add请求会先执行Reg-validation.xml,再进入method=add中:
 <field name="userName">
  <field-validator type="requiredstring">//字段规则
   <param name="trim">true</param>
   <message key="error.reg.name.null"></message>
  </field-validator>
  <field-validator type="regex">
   <param name="expression">w+</param>//正则!
   <message key="error.reg.name0"></message>
  </field-validator>
  <field-validator type="stringlength">//长度
   <param name="minLength">3</param>
   <param name="maxLength">20</param>
   <param name="trim">true</param>
   <message key="error.reg.username.toolong"></message>
  </field-validator>
 </field>
<field name="rePasswd">
  <field-validator type="requiredstring">
   <param name="trim">true</param>
   <message key="error.reg.passwd.null"></message>
  </field-validator>
  <field-validator type="stringlength">
   <param name="minLength">6</param>
   <param name="maxLength">20</param>
   <param name="trim">true</param>
   <message key="error.reg.passwd.toolong"></message>
  </field-validator>
  <field-validator type="fieldexpression">//规则
   <param name="expression">(rePasswd == passwd)</param>//两字段相等
   <message key="error.reg.passwd.notsame"></message>
  </field-validator>
 </field>
若出错当然是到INPUT。检查通过后便进入到add方法中:
 public String add() {
  if (this.getSysConfig().getUsePass() == 1) {
   return "passreg"; //是否可通过
  }
  this.setUseAuthCode(this.getSysConfig().isUseRegAuthCode());//验证码是否使用
  if (this.getSysConfig().getOpenUserReg() == 0) { // 关闭注册
   addActionError(this.getText("error.reg.notallowreg"));
   return ERROR;
  }
  if (this.getSysConfig().isCanNotRegUserName(this.getUserName())) { // 不能注

册的用户名
   addFieldError("userName", this.getText("error.reg.badusername", new

String[] { this.getUserName() }));//在字段错误中加入业务逻辑错误!
  }
  if (this.getSysConfig().getUseForbid() == 1) {
   if (this.getSysConfig().isForbidIP(this.getUserRemoteAddr())) {
    this.addFieldError("userName", this.getText

("error.reg.ipforbid", new String[] { this
      .getUserRemoteAddr() }));//这里说明userName

的FieldError可能有多个
   }
   if (this.getSysConfig().isForbidEmail(this.getEmail())) {
    this.addFieldError("email", this.getText

("error.reg.emailforbid", new String[] { this.getEmail() }));
   }
  }
  if (this.getSysConfig().isUseRegAuthCode()) {
   if (!this.getUserCookie().getAuthCode().equals(this.getAuthCode()))

{
    this.addFieldError("authCode", this.getText

("error.reg.authcode.same"));//注意验证码与cookie的关系!
   }
  }

  if (this.hasFieldErrors()) {
   return INPUT; //有错误到INPUT
  }

  UserInfo ui = this.getUserService().findUserInfoByUserName(this.getUserName

());

  if (ui != null) {
   this.addFieldError("userName", this.getText("error.reg.name1"));//更

高的业务逻辑考虑!
   return INPUT;
  }

  ui = this.getUserService().findUserInfoByEmail(this.getEmail());
  if (ui != null) {
   this.addFieldError("email", this.getText("error.reg.emailerror"));
   return INPUT;
  }

  ui = new UserInfo();//注意setPasswd和setRePasswd的不同

  ui.setAcceptFriend(1);
  ui.setAnswer(this.getAnswer());
  ui.setArticleEliteNum(0);
  ui.setArticleNum(0);
  ui.setBirthDay(1);
  ui.setBirthMonth(1);
  ui.setBirthYear(1980);
  ui.setEmail(this.getEmail());
  ui.setExperience(0);
  ui.setForumPerNum(0);
  ui.setForumViewMode(0);
  ui.setHavePic(0);
  ui.setLastLoginIP("0.0.0.0");
  ui.setLastLoginTime(new Date());
  ui.setLifeForce(0);
  ui.setLiterary(0);
  ui.setLoginIP("0.0.0.0");
  ui.setLoginTime(new Date());
  ui.setLoginTimes(0);
  ui.setNickName(this.getSysConfig().bestrowScreenNickName(this.getNickName

())); // 屏蔽敏感字
/**public String bestrowScreenNickName(String txt) {
  if (StringUtils.isNotBlank(this.getCanNotUseNickName())) {
   String[] words = this.getCanNotUseNickName().split(";");
   for (int i = 0; i < words.length; i++) {
    txt = txt.replaceAll(words[i], this.getBestrowScreen

());//bestrowScreen是固定值:在数据库中为**
   }
  }
  return txt;
 }
*/
  ui.setPasswd(this.getPasswd());
  ui.setPicFileName("");
  ui.setPostPerNum(0);
  ui.setQuestion(this.getQuestion());
  ui.setReceiveNote(1);
  ui.setRegTime(new Date());
  ui.setRePasswd(Util.hash(this.getPasswd()));//其实hash方法是MD5加密!
  ui.setSignDetail0(this.getText("bbscs.userdefaultsign"));
  ui.setSignDetail1(this.getText("bbscs.userdefaultsign"));
  ui.setSignDetail2(this.getText("bbscs.userdefaultsign"));
  ui.setSignName0("A");
  ui.setSignName1("B");
  ui.setSignName2("C");
  ui.setStayTime(0);
  ui.setTimeZone("GMT+08:00");
  ui.setUserFrom(this.getIpSeeker().getCountry(this.getUserRemoteAddr()));
  ui.setUserKnow(0);
  ui.setUserName(this.getUserName());
  ui.setUserTitle(0);
  if (this.getSysConfig().isCheckRegUser() || this.getSysConfig

().isCheckRegUserEmail()) { //默认两者都为0,审核才能是会员!
   ui.setValidated(0);
   ui.setGroupID(Constant.USER_GROUP_UNVUSER);//未审核
  } else {
   ui.setValidated(1);
   ui.setGroupID(Constant.USER_GROUP_REGUSER);
  }
  ui.setEditType(-1);
  ui.setUserLocale(this.getLocale().toString());//getLocale来自ActionSupport!
  /**
public Locale getLocale()
    {
        return ActionContext.getContext().getLocale();
    }
*/
  ui.setValidateCode(RandomStringUtils.randomAlphanumeric(10));//用于审核用的

,不是重复登录用!随机10位数
  ui.setCoin(100);

  UserDetail ud = new UserDetail();
  ud.setBrief("");
  ud.setDreamJob("");
  ud.setDreamLover("");
  ud.setFavourArt("");
  ud.setFavourBook("");
  ud.setFavourChat("");
  ud.setFavourMovie("");
  ud.setFavourMusic("");
  ud.setFavourPeople("");
  ud.setFavourTeam("");
  ud.setGraduate("");
  ud.setHeight("");
  ud.setHomePage("");
  ud.setIcqNo("");
  ud.setInterest("");
  ud.setMsn("");
  ud.setOicqNo("");
  ud.setSex((short) 0);
  ud.setWeight("");
  ud.setYahoo("");

  ui.setUserDetail(ud);
  ud.setUserInfo(ui);

  try {
   ui = this.getUserService().saveUserInfo(ui);
   this.getSysStatService().saveAllUserNum(this.getUserService

().getAllUserNum(), this.getUserName());//重新从UserService得到最新的人数,还有最新的注册名

写入SysStatService!
   if (this.getSysConfig().isCheckRegUserEmail()) {//需要用邮件来审核之
    String subject = this.getText("reg.validate.email.title",

new String[] { this.getSysConfig()
      .getForumName() });
    Map<String, String> root = new HashMap<String, String>();
    root.put("website", this.getSysConfig().getForumName());
    root.put("forumurl", this.getSysConfig().getForumUrl());
    root.put("userName", ui.getUserName());
    root.put("validateCode", ui.getValidateCode());//关键点...
    this.getTemplateMail().sendMailFromTemplate(ui.getEmail(),

subject, "regValidate.ftl", root,
      this.getLocale());
   }//发信!
/**下面是模板文件的一段:
<table width="98%" border="0" align="center" cellpadding="5" cellspacing="0">
  <tr>
    <td><strong>非常感谢您成为${website}的用户</strong></td>
  </tr>
  <tr>
    <td>您的帐户尚处于未认证的状态,只要你点击下面的链接,即可通过认证</td>
  </tr>
  <tr>
    <td><a href="${forumurl}/reg/validateuser.bbscs?userName=${userName}

&validateCode=${validateCode}" target="_blank">${forumurl}/reg/validateuser.bbscs?

userName=${userName}&validateCode=${validateCode}</a></td>
  </tr>
</table>
*/
   return SUCCESS;
  } catch (BbscsException e) {
   this.addActionError(this.getText

("error.reg.createrror"));//ActionError!
   return ERROR;
  }
 }
我们这里有必要讲一下IPSeeker这个工具类,它在com.laoer.bbscs.comm包中。它在程序中通过了
ui.setUserFrom(this.getIpSeeker().getCountry(this.getUserRemoteAddr()));
由IP得到用户来自哪里的解决方案.(用来封装ip相关信息,目前只有两个字段,ip所在的国家country和

地区area),注意它读取的文件是WEB-INF/IPDate/QQwry.Dat,下面是其属性的定义:
 // 一些固定常量,比如记录长度等等
 private static final int IP_RECORD_LENGTH = 7;
 private static final byte REDIRECT_MODE_1 = 0x01;
 private static final byte REDIRECT_MODE_2 = 0x02;
 // Log对象
 private static Log log = LogFactory.getLog(IPSeeker.class);
 // 用来做为cache,查询一个ip时首先查看cache,以减少不必要的重复查找
 private Hashtable<String, IPLocation> ipCache;
 // 随机文件访问类
 private RandomAccessFile ipFile;
 // 内存映射文件
 private MappedByteBuffer mbb;
 // 起始地区的开始和结束的绝对偏移
 private long ipBegin, ipEnd;
 // 为提高效率而采用的临时变量
 private IPLocation loc;
 private byte[] buf;
 private byte[] b4;
 private byte[] b3;
-->
public IPSeeker() {
  ipCache = new Hashtable<String, IPLocation>();
  loc = new IPLocation();
  buf = new byte[100];
  b4 = new byte[4];
  b3 = new byte[3];
  try {
   // ClassPathResource cpr = new ClassPathResource("/" + IPDATE_FILE);
   // System.out.println(cpr.getFile());
   ipFile = new RandomAccessFile(IPDATE_FILE_PATH + IPDATE_FILE, "r");
   // ipFile = new RandomAccessFile(cpr.getFile(), "r");//随机读!
  } catch (FileNotFoundException e) {
   // 如果找不到这个文件,再尝试再当前目录下搜索,这次全部改用小写文件


   // 因为有些系统可能区分大小写导致找不到ip地址信息文件
   String filename = new File(IPDATE_FILE_PATH + IPDATE_FILE).getName

().toLowerCase();
   File[] files = new File(IPDATE_FILE_PATH).listFiles();
   for (int i = 0; i < files.length; i++) {
    if (files[i].isFile()) {
     if (files[i].getName().toLowerCase().equals

(filename)) {
      try {
       ipFile = new RandomAccessFile(files

[i], "r");
      } catch (FileNotFoundException e1) {
       log.error("IP地址信息文件没有找到,

IP显示功能将无法使用");
       ipFile = null;
      }
      break;
     }
    }
   }
  }
  // 如果打开文件成功,读取文件头信息
  if (ipFile != null) {
   try {
    ipBegin = readLong4(0);
    ipEnd = readLong4(4);
    if (ipBegin == -1 || ipEnd == -1) {
     ipFile.close();
     ipFile = null;
    }
   } catch (IOException e) {
    log.error("IP地址信息文件格式有错误,IP显示功能将无法使用");
    ipFile = null;
   }
  }
 }
好的,我们转到要用的getCountry()方法上:
public String getCountry(String ip) {
  return getCountry(getIpByteArrayFromString(ip));//String->Byte[]
 }
-->
public static byte[] getIpByteArrayFromString(String ip) {
  byte[] ret = new byte[4];
  StringTokenizer st = new StringTokenizer(ip, ".");
  try {
   ret[0] = (byte) (Integer.parseInt(st.nextToken()) & 0xFF);
   ret[1] = (byte) (Integer.parseInt(st.nextToken()) & 0xFF);
   ret[2] = (byte) (Integer.parseInt(st.nextToken()) & 0xFF);
   ret[3] = (byte) (Integer.parseInt(st.nextToken()) & 0xFF);
  } catch (Exception e) {
   log.error(e.getMessage());
  }
  return ret;
 }
进而到了public String getCountry(byte[] ip) {
  // 检查ip地址文件是否正常
  if (ipFile == null) {
   return "bad.ip.file";
  }
  // 保存ip,转换ip字节数组为字符串形式,注意现在是字节!!!而非10进制数了哦~
  String ipStr = getIpStringFromBytes(ip);
  // 先检查cache中是否已经包含有这个ip的结果,没有再搜索文件
  if (ipCache.containsKey(ipStr)) {
   IPLocation loc = (IPLocation) ipCache.get(ipStr);
   return loc.country;
  } else {
   IPLocation loc = getIPLocation(ip);
   ipCache.put(ipStr, loc.getCopy());
   return loc.country;
  }
 }
而IPLocation是个内部类!
private class IPLocation {
  public String country;

  public String area;

  public IPLocation() {
   country = area = "";
  }

  public IPLocation getCopy() {
   IPLocation ret = new IPLocation();
   ret.country = country;
   ret.area = area;
   return ret;
  }
 }
若缓存中没有则-->
private IPLocation getIPLocation(byte[] ip) {
  IPLocation info = null;
  long offset = locateIP(ip);
  if (offset != -1) {
   info = getIPLocation(offset);
  }
  if (info == null) {
   info = new IPLocation();
   info.country = "";
   info.area = "";
  }
  return info;
 }
好,关键的是一个private long locateIP(byte[] ip)这个方法将根据ip的内容,定位到包含这个ip国家

地区的记录处,返回一个绝对偏移 方法使用二分法查找。而private IPLocation getIPLocation(long

offset)返回IPLocation!OK!对于具体地代码深入分析有待研究!
OK!我们完成了用户的注册了。
好的,我们 <result name="success" type="redirect">
    /regSucceed.jsp //根目录下!
   </result>
而regSucceed.jsp向我们输出了提示成功字样。其中的路径问题有待小心哦~我们login进入主页面,继续

上次的main.bbscs分析之,我们先看struts.xml:
 <package name="main" extends="bbscs-default" namespace="/">
  <default-interceptor-ref name="userAuthInterceptorStack"></default-

interceptor-ref>
这里表明整个包都需要userAuthInterceptorStack:
   <interceptor-stack name="userAuthInterceptorStack">
    <interceptor-ref name="defaultStack"></interceptor-ref>
    <interceptor-ref name="userLoginInterceptor"></interceptor-

ref>
    <interceptor-ref

name="userPermissionInterceptor"></interceptor-ref>
   </interceptor-stack>
我们先看main:
  <action name="main" class="com.laoer.bbscs.web.action.Main">//不需要spring的

代理!
   <result name="success">/WEB-INF/jsp/main.jsp</result>
  </action>
它有四个属性:inUrl,bid(long),postID,nagUrl,应该也是与JSP界面交互的吧!我们看execute:
public String execute() {
  if (this.getAction().equalsIgnoreCase("read")) {
   if (this.bid == 0 && StringUtils.isBlank(this.postID)) {
    this.setInUrl(BBSCSUtil.getActionMappingURLWithoutPrefix

("in"));
   } else {
    this.setInUrl(BBSCSUtil.getActionMappingURLWithoutPrefix

("read?action=topic&bid=" + this.bid + "&id="
      + this.postID));
   }
  } else if (this.getAction().equalsIgnoreCase("forum")) {
   if (this.bid == 0) {
    this.setInUrl(BBSCSUtil.getActionMappingURLWithoutPrefix

("in"));
   } else {
    this.setInUrl(BBSCSUtil.getActionMappingURLWithoutPrefix

("forum?action=index&bid=" + this.bid));
   }
  } else {
   if (StringUtils.isBlank(this.getInUrl())) {
    this.setInUrl(BBSCSUtil.getActionMappingURLWithoutPrefix

("in"));
   }
  }
  if (Constant.USE_URL_REWRITE) {
   this.setNagUrl("nag.html");
  }
  else {
   this.setNagUrl(BBSCSUtil.getActionMappingURLWithoutPrefix("nag"));
  }
  return SUCCESS;
 }
这里主要根据bid和action(还有postID)决定inUrl和nagUrl,当然一开始是
 if (StringUtils.isBlank(this.getInUrl())) {
    this.setInUrl(BBSCSUtil.getActionMappingURLWithoutPrefix

("in"));
   }
this.setNagUrl(BBSCSUtil.getActionMappingURLWithoutPrefix("nag"));//系统默认设置
进入main.jsp我们发现,它其实是由一个大table中的一个tr有三个td:第一个是

frmTitle,iframe=nagFrame而它的src是一个action:
<td align="middle" noWrap vAlign="center" id="frmTitle" height="100%">
      <iframe id="nagFrame" name="nagFrame" frameBorder="0" scrolling="auto"

src="<s:property value="%{nagUrl}"/>" class="iframe1"></iframe>
    </td>
而第二个td里面加了个table,里面的tr->td <td onclick="switchSysBar()">
用于屏幕切换,第三个td为mainFrame:
<td style="width: 100%">
      <iframe frameBorder="0" id="mainFrame" name="mainFrame" scrolling="yes"

src="<s:property value="%{inUrl}"/>" class="iframe2"></iframe>
    </td>
我们再看看JavaScript:
<script language="JavaScript" type="text/javascript">
if(self!=top){
  top.location=self.location;
}
function switchSysBar(){
  if (switchPoint.innerHTML=='&lt;'){ //注意&lt;->;&gt;-<
    switchPoint.innerHTML='&gt;'
    document.getElementById("frmTitle").style.display="none";
  }
  else{
    switchPoint.innerHTML='&lt;'
    document.getElementById("frmTitle").style.display="block";
  }
}

function changeMainFrameSrc(url) {
  //alert(url);
  document.getElementById("mainFrame").src = url;
}
接下来,我们分别分析/nag和/in。先从struts.xml看起:
<action name="nag" class="nagAction">
      <interceptor-ref name="userAuthInterceptorStack"></interceptor-ref>
   <interceptor-ref name="userSessionInterceptor"></interceptor-ref>
   <result name="success">/WEB-INF/jsp/nag.jsp</result>
另外一个:
<action name="in" class="inAction">
   <interceptor-ref name="mainUserAuthInterceptorStack"></interceptor-

ref>
   <interceptor-ref name="requestBasePathInterceptor"></interceptor-

ref>
   <result name="success">/WEB-INF/jsp/in.jsp</result>
  </action>
在看action-servlet.xml,这里我们应该发现所有被spring代理的action的格式基本上都是:
<bean id="nagAction"
  class="com.laoer.bbscs.web.action.Nag" scope="prototype"
  autowire="byName">
 </bean>
<bean id="inAction"
  class="com.laoer.bbscs.web.action.In" scope="prototype"
  autowire="byName">
 </bean>
我们先看userAuthInterceptorStack:
<interceptor-stack name="userAuthInterceptorStack">
    <interceptor-ref name="defaultStack"></interceptor-ref>
    <interceptor-ref name="userLoginInterceptor"></interceptor-

ref>
    <interceptor-ref

name="userPermissionInterceptor"></interceptor-ref>
   </interceptor-stack>
它们的scope="prototype" autowire="byName"一个请求一个对象,且自动注入类型:byName,进入

userLoginInterceptor, 这里它完全用了一个通行证的做法,十分强大!我们看laoer的精彩说法:
天乙通行证是一个单点登录系统(Single sign on),用户登录一次通行证,即可以直接使用社区、Blog

等产品,无需再次注册或登录,保证几个产品用户的统一,但天乙通行证由于本身没有产品化,所以现在

没有开源,如果使用天乙社区需要做用户整合,只要实现通行证的类似系统就可以。 SSO的实现技术有多

种,也有一些开源产品,而天乙通行证使用的是比较简单,但非常有效的方案,即同域名下的Cookie方式

,实现用户的单点登录。 以天乙通行证为例,天乙社区(bbs.laoer.com)、天乙Blog(blog.laoer.com

)都运行在laoer.com域名下,用户在www.laoer.com上注册后登录,通行证就会在用户的客户端写入两个

Cookie,一个是用户名(Cookie名:PASS_USERNAME),一个是加密的用户名(Cookie名:

PASS_USERNAME_DES),加密的方式采用3DES,采用3DES加密是因为3DES可逆的加密算法,即对密文可以

还原,Cookie的Domain为.laoer.com,这样在laoer.com下的所有域名都可以访问到这个Cookie。
接下来我们说一下社区的认证过程,社区采用Struts2的MVC结构,在Action之前,都会执行一个拦截器(

UserLoginInterceptor),这个拦截器首先会读取通行证的两个Cookie,如果两个Cookie都存在,则对加

密的用户名进行解密,采用的密钥与通行证加密的密钥一致,然后解密后的用户名与另一个明文的用户名

比对,如果两个值一致,说明用户已经在通行证登录,则建立在社区的Session(如果Session已经存在,

则认为用户在社区也已经登录),如果通行证的Cookie不存在或是用户比对错误,则认为用户没有登录或

已经签退,社区会做游客登录,详细处理流程请阅读UserLoginInterceptor.java源码。
如果用户在现有系统上整合社区,只要顶层域写入两个Cookie,在社区后台中有设置通行证的项目,两个

系统采用的密钥一致就行了,同时3DES算法可以用多种语言实现,比如.NET、Ruby等等,所以可以在编程

语言异构的网络系统中无缝使用,当然用户也可以采用自己的加密解密算法。
这个方案要注意的问题:
1、只能使用在一个主域名下,如果要跨域名则不能实现。
2、需要用户支持Cookie。
3、密钥安全性,由于所有应用都使用统一的密钥,需要在加强管理,定期更换,或是采用RSA方法对密钥

加密,减少密钥泄漏的可能性。
4、对于非WEB系统,比如客户端程序,需要扩展其他方法实现。
原文见:http://bbs.laoer.com/main-read-15-ff80808113baa8140114123973985584.html
下面是我的理解:userLoginInterceptor首先引入request,response,servletContext,wc--

>sysConfig,us,uc-->一些服务!注意一点,通行证信息放在Cookie中....其判断流程是这样的:分两类,

由sysConfig.isUsePass()决定!
(1)若使用通行证,根据uc.isLoginPass再分为通行证是否登录,若登录即uc.isLoginPass为true,按

us=null分两类,有session和没session,当us=null时,根据uc获得用户信息:
if (us == null) {// 用户session没有在社区存在
      UserInfo ui =

userService.findUserInfoByUserName(uc.getPusername());
      if (ui != null) {//进而分为用户是否存在,存在

的话,做登录,且更新session和cookie

ui = userService.saveAtLogin(ui); // 用户登录处理
        uo =

userOnlineService.createUserOnline(uo); // 加入在线用户表

        us =

userService.getUserSession(ui);
        us.setLastActiveTime

(nowTime);
        us.setValidateCode

(uo.getValidateCode());

        ac.getSession().put

(Constant.USER_SESSION_KEY, us);

        
        uc.addCookies(ui);
} else { // 用户不存在,是新用户
       isNewUser = true;//等下一起创建之!
      }
这是没有session的情况,如果有session呢,也就是说用户已经在社区登录之中!
if (!us.getUserName().equals(uc.getPusername())) {// 用户在社区中的登录名和通行证中的用户名

不一致.这时仍然由us.getusername得到用户信息
UserInfo ui = userService.findUserInfoByUserName(uc.getPusername());
   if (ui != null) { // 用户存在,重新登录,原来的session删除之,以

cookie为准
uc.removeAllCookies();//去掉原来BBSokie信息
/**
public void removeAllCookies() {
  addC(BBSCS_FORUMPERNUM_KEY, "", 0);
  addC(BBSCS_POSTPERNUM_KEY, "", 0);
  addC(BBSCS_TIMEZONE_KEY, "", 0);
  addC(BBSCS_LASTSENDNOTETIME_KEY, "", 0);
  addC(BBSCS_LASTPOSTTIME_KEY, "", 0);
  addC(BBSCS_FORUMVIEWMODE_KEY, "", 0);
  addC(BBSCS_EDITTYPE, "-1", 0);
  addC(BBSCS_AUTHCODE, "", 0);
  addC(BBSCS_USERNAME, "", 0);
  addC(BBSCS_PASSWD, "", 0);
 }
*/
  }else{isNewUser=true}
if(isNewUser){// 创建社区用户,uc.getPusername()
}
这样并没有us.getUserNmae().equals(us.getPusername())的判断,说明us以及uc不需要改变!
 UserSession us = (UserSession) ac.getSession().get(Constant.USER_SESSION_KEY);
 UserCookie uc = new UserCookie(request, response, sysConfig);
若uc.isLoginPass()为假的话,通行证未登录,做游客登录
if (us == null) {// 用户没有登录过,直接做游客登录,有uo,us,uc.addGuestCookies();}
而用户在社区是登录状态us(存在的话),需要强制做游客登录
if (us.getGroupID() != Constant.USER_GROUP_GUEST) {// //如果原来用户不是游客,先清除原

Session,做游客登录
       // userSessionCache.remove

(uc.getUserName());
       ac.getSession().remove

(Constant.USER_SESSION_KEY);
       uc.removeAllCookies();
对于不使用通行证的话,根据us=null来决定,其子判断uc.isSaveLoginCookie()
: public boolean isSaveLoginCookie() {
  if (StringUtils.isNotBlank(this.userName) && StringUtils.isNotBlank

(this.passwd)) {
   return true;
  } else {
   return false;
  }
有的话,从cookie中取,没有的话,Guest!我们看其中的一段代码:
uo = userOnlineService.createUserOnline(uo);
       us = this.createGuestUserSession

(uo.getUserID(), uo.getUserName(), userService);
/**
private UserSession createGuestUserSession(String gUestID, String gUesrName, UserService

userService) {
  UserSession us = new UserSession();

  us.setGroupID(Constant.USER_GROUP_GUEST);
  us.setId(gUestID);
  us.setNickName("Guest");
  us.setUserName(gUesrName);
  Map[] permissionMap = userService.getUserPermission

(Constant.USER_GROUP_GUEST);//public static final int USER_GROUP_GUEST = 1;
  us.setUserPermissionArray(permissionMap);
  return us;
 }
*/
       us.setLastActiveTime

(System.currentTimeMillis());
       us.setValidateCode

(uo.getValidateCode());
       ac.getSession().put

(Constant.USER_SESSION_KEY, us);

       uc.removeAuthCode();//不用验证了!
       uc.addGuestCookies();

 }
好的,我们看另外一个interceptor:userPermissionInterceptor,这个是用户权限的拦截器:
这里有  String actionName = "/" + ac.getName();// ac.getName()=nag!-->/nag!
  String ajax = "html";
  String saction = "";
  Map map = ac.getParameters();
String[] _ajax = (String[]) map.get("ajax");//ajax参数!
  if (_ajax != null) {
   ajax = _ajax[0];
  }
  String[] _saction = (String[]) map.get("action");//action参数!
  if (_saction != null) {
   saction = _saction[0];
  }
我们重点看下面的代码:
UserSession us = (UserSession) ac.getSession().get(Constant.USER_SESSION_KEY);//获得session

   Permission permission = (Permission) us.getUserPermission().get

(actionName + "?action=*");//我们知道加入的时间用的是setUserPermissionArray
/**
private Map userPermission = new HashMap();
public void setUserPermissionArray(Map[] permissionMap) {
  setSpecialPermission(permissionMap[1]);
  Set pset = permissionMap[0].entrySet();
  Iterator it = pset.iterator();
  while (it.hasNext()) {
   Map.Entry p = (Map.Entry) it.next();
   Permission permission = (Permission) p.getValue();
   String[] actions = permission.getAction().split(",");
   for (int i = 0; i < actions.length; i++) {
    String[] resources = ((String) p.getKey()).split(",");
    this.getUserPermission().put(resources[0] + "?action=" +

actions[i], p.getValue());
   }
  }

 }

*/
   if (permission != null) {
    havePermission = true;
   } else {
    permission = (Permission) us.getUserPermission().get

(actionName + "?action=" + saction);//带有saction!
    if (permission != null) {
     havePermission = true;
    } else {
     havePermission = false;
    }
   }
   if (havePermission) {
    return invocation.invoke();
   } else {  //没有权限的话

    HttpServletRequest request = (HttpServletRequest) ac.get

(ServletActionContext.HTTP_REQUEST);
    StringBuffer sb = new StringBuffer();
    sb.append(BBSCSUtil.getWebRealPath(request));
    sb.append(request.getContextPath());
    sb.append("/");
    sb.append(BBSCSUtil.getActionMappingURLWithoutPrefix

(ac.getName())); sb生成一个完整的URL
    UrlHelper.buildParametersString(map, sb, "&");

    String curl = sb.toString();//含参数的完整的URL

    SysConfig sysConfig = (SysConfig) wc.getBean("sysConfig");

    ResourceBundleMessageSource messageSource =

(ResourceBundleMessageSource) wc.getBean("messageSource");

    if (ajax.equalsIgnoreCase("html")) {
     String errorMsg = messageSource.getMessage

("error.noPermission", null, ac.getLocale());
     ac.getValueStack().set("interceptError", errorMsg);
     ac.getValueStack().set("tourl", curl);//保存当前URL
     ac.getValueStack().set("useAuthCode",

sysConfig.isUseAuthCode());
     if (sysConfig.getUsePass() == 0) {
      return "intercepthtml";//不使用通行!
     } else {
      ac.getValueStack().set("actionUrl",

sysConfig.getPassUrl());//进入PASS的URL!
      return "intercepthtmlpass";
     }
    } else if (ajax.equalsIgnoreCase("shtml")) {
     String errorMsg = messageSource.getMessage

("error.noPermission", null, ac.getLocale());
     ac.getValueStack().set("interceptError", errorMsg);
     return "interceptshtml";
    } else {
     String errorMsg = messageSource.getMessage

("error.noPermission.ajax", null, ac.getLocale());
     AjaxMessagesJson ajaxMessagesJson =

(AjaxMessagesJson) wc.getBean("ajaxMessagesJson");
     ajaxMessagesJson.setMessage("E_NO_Permission",

errorMsg);
     ac.getValueStack().set("ajaxMessagesJson",

ajaxMessagesJson);
     return "ajaxjson";
    }
   }
/**
<global-results>
   <result name="error">/WEB-INF/jsp/error.jsp</result>
   <result name="htmlError">/WEB-INF/jsp/htmlError.jsp</result>
   <result name="ajaxjson">/WEB-INF/jsp/ajaxjson.jsp</result>
   <result name="jsonstring">/WEB-INF/jsp/jsonstring.jsp</result>
   <result name="intercepthtml">/WEB-

INF/jsp/intercepthtml.jsp</result>//用到
   <result name="intercepthtmlpass">/WEB-

INF/jsp/intercepthtmlpass.jsp</result>//用到
   <result name="interceptshtml">/WEB-

INF/jsp/interceptshtml.jsp</result>//用到
   <result name="relogin" type="redirect-action">login?

action=relogin</result>
   <result name="boardPasswd">/WEB-INF/jsp/boardPasswd.jsp</result>
  </global-results>
*/
我们可以看看具体的JSP页面,其中html是有登录的,而sthml是另外一种类型.它们都是ajax请求时,在

url上带的参数,用于区分返回的页面类型。
<%@page contentType="text/html; charset=UTF-8"%>
<%@taglib uri="/WEB-INF/struts-tags.tld" prefix="s"%>
<div id="error" class="errormsg">
  <s:property value="%{interceptError}"/>
</div>
我们看看资源文件中的内容:
error.noPermission=您没有操作此项功能的权限,如果您还没有登录,可能需要登录才能操作!<a

href="javascript:;" onclick="Element.toggle('loginform');"><strong>我要登录</strong></a>
error.noPermission.ajax=您没有操作此项功能的权限!
注意:Element.toggle是prototype.js中的函数,交替隐藏或显示,见资料:

http://blog.sina.com.cn/u/562f0574010009pk
好的,我们将两个复杂的interceptor分析完了!我们还是回到Nag.java中吧:
它负责导航菜单的显示,extends BaseAction implements UserSessionAware,我们直接看execute方法


public String execute() {
  int isHidden = 0;
  if (userSession.isHaveSpecialPermission

(Constant.SPERMISSION_CAN_SEE_HIDDEN_BOARD)) { // 如果用户有查看隐藏版区的权限public static

final long SPERMISSION_CAN_SEE_HIDDEN_BOARD = 901;
/**
public boolean isHaveSpecialPermission(long permissionID) {
  return this.specialPermission.containsKey(new Long(permissionID));
 }
*/
   isHidden = -1;
  }
  this.setUrlRewrite(Constant.USE_URL_REWRITE);
  this.boardList = this.getBoardService().findBoardsByParentID(0, 1, isHidden,

Constant.FIND_BOARDS_BY_ORDER);//顶层的!
  // System.out.println(boardList);

  for (int i = 0; i < this.boardList.size(); i++) {
   Board b = (Board) this.boardList.get(i);
   List bclist = this.getBoardService().findBoardsByParentID(b.getId(),

1, isHidden,
     Constant.FIND_BOARDS_BY_ORDER);
   this.boardMap.put(b.getId(), bclist);//2级的放入boardMap
  }

  List bsaveids = this.getBoardSaveService().findBoardSaveBidsByUid

(userSession.getId());userSession.getId()-->userId
  this.boardSaveList = this.getBoardService().findBoardsInIDs(bsaveids);//用户

保存的版区!

  this.setUsePass(this.getSysConfig().isUsePass());

  return SUCCESS;
 }
我们看jsp页面:
<!--
function loadChild(bid,btype) {
  var obj = document.getElementById("child" + bid);
  var imgObj = document.getElementById("img" + bid);
  if (obj.style.display == "none") {
    obj.style.display = "block";
    imgObj.src="images/collapse.gif";
  }
  else {
    obj.style.display = "none";
    imgObj.src="images/expand.gif";
  }
}

function loadBoardSave() {

  var obj = document.getElementById("boardSaveDiv");
  var imgObj = document.getElementById("boardSaveImg");
  if (obj.style.display == "none") {
    obj.style.display = "block";
    imgObj.src="images/collapse.gif";
  }
  else {
    obj.style.display = "none";
    imgObj.src="images/expand.gif";
  }
}

function loadUserCenter() {
  var obj = document.getElementById("userCenterDiv");
  var imgObj = document.getElementById("imgUserCenterSet");
  if (obj.style.display == "none") {
    obj.style.display = "block";
    imgObj.src="images/collapse.gif";
  }
  else {
    obj.style.display = "none";
    imgObj.src="images/expand.gif";
  }
}

function loadUserLogout() { //不用了
  var obj = document.getElementById("userLogoutDiv");
  var imgObj = document.getElementById("imgLogout");
  if (obj.style.display == "none") {
    obj.style.display = "block";
    imgObj.src="images/collapse.gif";
  }
  else {
    obj.style.display = "none";
    imgObj.src="images/expand.gif";
  }
}
//-->
</script>
<base target="mainFrame"/>
body区域分<div> <ul><li>第一项</li>下面是用户中心的li:
<li><a href="javascript:;" onclick="loadUserCenter();"><img id="imgUserCenterSet"

src="images/expand.gif" alt="" width="25" height="15" border="0" align="absmiddle"/><s:text

name="nag.usercenter"/></a></li>
而其下的div:
<div id="userCenterDiv" class="nag" style="display:none">
        <ul>
          <li>
          <s:url action="signSet" id="signSetUrl"></s:url>
          <a href="${signSetUrl}"><img id="imgSignSet" src="images/node.gif" alt=""

border="0" align="absmiddle"/><s:text name="signset.title"/></a>
          </li>
          <li>
          <s:url action="nickNameSet" id="nickNameSetUrl"></s:url>
          <a href="${nickNameSetUrl}"><img id="imgNickNameSet" src="images/node.gif" alt=""

border="0" align="absmiddle"/><s:text name="nickset.title"/></a>
          </li>
          <s:url action="userConfig" id="userConfigUrl"></s:url>
          <li><a href="${userConfigUrl}"><img id="imgUserConfig" src="images/node.gif"

alt="" border="0" align="absmiddle"/><s:text name="userconfig.title"/></a></li>
          <s:url action="friendSet" id="friendSetUrl"></s:url>
          <li><a href="${friendSetUrl}"><img id="imgFriendSet" src="images/node.gif" alt=""

border="0" align="absmiddle"/><s:text name="friend.title"/></a></li>
          <s:url action="note" id="noteUrl"></s:url>
          <li><a href="${noteUrl}"><img id="imgNote" src="images/node.gif" alt="" border="0"

align="absmiddle"/><s:text name="note.title"/></a></li>
          <s:url action="bookMark" id="bookMarkUrl"></s:url>
          <li><a href="${bookMarkUrl}"><img id="imgBookMark" src="images/node.gif" alt=""

border="0" align="absmiddle"/><s:text name="bookmark.title"/></a></li>
          <s:url action="userFace?action=index" id="userFaceUrl"></s:url>
          <li><a href="${userFaceUrl}"><img id="imgFace" src="images/node.gif" alt=""

border="0" align="absmiddle"/><s:text name="face.title"/></a></li>
          <s:url action="userDetailSet?action=index" id="userDetailSetUrl"></s:url>
          <li><a href="${userDetailSetUrl}"><img id="imgUserDetailSet" src="images/node.gif"

alt="" border="0" align="absmiddle"/><s:text name="userdetail.title"/></a></li>
          <s:url action="cpasswd?action=index" id="cpasswdUrl"></s:url>
          <li><a href="${cpasswdUrl}"><img id="imgCpasswd" src="images/node.gif" alt=""

border="0" align="absmiddle"/><s:text name="cpasswd.title"/></a></li>
          <s:url action="boardSaveManage" id="boardSaveManageUrl"></s:url>
          <li><a href="${boardSaveManageUrl}"><img id="imgboardSaveManage"

src="images/node.gif" alt="" border="0" align="absmiddle"/><s:text

name="boardsave.title"/></a></li>
        </ul>
      </div>
</url>
</div>//上面的部分完成显示!
加根Hr,其它类似,我们重点分析如下:
  <s:iterator id="b1" value="%{boardList}">
    <li>
    <s:if test="%{urlRewrite}">
    <a href="javascript:;" onclick="loadChild('<s:property value="#b1.id"/>','<s:property

value="#b1.boardType"/>')"><img id="img<s:property value="#b1.id"/>" src="images/expand.gif"

alt="展开" width="25" height="15" border="0" align="absmiddle"/></a><a href="forum-index-

<s:property value="#b1.id"/>.html"><s:property value="#b1.boardName"/></a>
    </s:if>
    <s:else>
    <s:url action="forum?action=index" id="furl">
    <s:param name="bid" value="#b1.id"/>
    </s:url>
    <a href="javascript:;" onclick="loadChild('<s:property value="#b1.id"/>','<s:property

value="#b1.boardType"/>')"><img id="img<s:property value="#b1.id"/>" src="images/expand.gif"

alt="展开" width="25" height="15" border="0" align="absmiddle"/></a><a

href="${furl}"><s:property value="#b1.boardName"/></a>
    </s:else>
    <div id="child<s:property value="#b1.id"/>" class="nag" style="display:none">
    <s:set name="bl2" value="%{boardMap.get(#b1.id)}"></s:set>
    <ul>
    <s:iterator id="b" value="#bl2">
    <s:url action="forum?action=index" id="burl">
    <s:param name="bid" value="#b.id"/>
    </s:url>
    <li>
    <s:if test="%{urlRewrite}">
    <img id="img<s:property value="#b.id"/>" src="images/node.gif" alt=""

align="absmiddle"/><a href="forum-index-<s:property value="#b.id"/>.html"><s:property

value="#b.boardName"/></a>
    </s:if>
    <s:else>
    <s:url action="forum?action=index" id="burl">
    <s:param name="bid" value="#b.id"/>
    </s:url>
    <img id="img<s:property value="#b.id"/>" src="images/node.gif" alt=""

align="absmiddle"/><a href="${burl}"><s:property value="#b.boardName"/></a>
    </s:else>
    </li>
    </s:iterator>
    </ul>
    </div>
    </li>
好,我们看in!首先看它的interceptor:
 <action name="in" class="inAction">
   <interceptor-ref name="mainUserAuthInterceptorStack"></interceptor-

ref>
   <interceptor-ref name="requestBasePathInterceptor"></interceptor-

ref>//讲过
   <result name="success">/WEB-INF/jsp/in.jsp</result>
  </action>
这里有一个mainUserAuthInterceptorStack:
<interceptor-stack name="mainUserAuthInterceptorStack">
    <interceptor-ref name="defaultStack"></interceptor-ref>
    <interceptor-ref name="userLoginInterceptor"></interceptor-

ref>//讲过
    <interceptor-ref

name="userSessionInterceptor"></interceptor-ref>
    <interceptor-ref name="userOnlineInterceptor"></interceptor

-ref>
    <interceptor-ref

name="userPermissionInterceptor"></interceptor-ref>//讲过!
   </interceptor-stack>
好,我们看看UserSessionInterceptor和userOnlineInterceptor吧:
public String intercept(ActionInvocation invocation) throws Exception {
  ActionContext ac = invocation.getInvocationContext();
  Object action = invocation.getAction();

  if (action instanceof UserSessionAware) {
   HttpServletRequest request = (HttpServletRequest) ac.get

(ServletActionContext.HTTP_REQUEST);
   HttpServletResponse response = (HttpServletResponse) ac.get

(ServletActionContext.HTTP_RESPONSE);

   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);//得到UserCookie!
    ((UserSessionAware) action).setUserCookie(userCookie);
    
    UserSession us = (UserSession) ac.getSession().get

(Constant.USER_SESSION_KEY);//得到UserSession!
        ((UserSessionAware)

action).setUserSession(us);
   }
  }

  return invocation.invoke();//将两个都放到UserSessionAware(它有两个公开实现的

方法),以便action类使用!
/**
public interface UserSessionAware {
 public void setUserCookie(UserCookie userCookie);
 public void setUserSession(UserSession userSession);
}
*/
 }
现在,我们要认真地分析下userOnlineInterceptor:
   long nowTime = System.currentTimeMillis();//现在的时间long
   long addedTime = nowTime - us.getLastActiveTime();//相隔的时间
   us.setAddedOnlineTime(us.getAddedOnlineTime() + addedTime);//累加在

线时间
   us.setAddedOnlineHour(us.getAddedOnlineHour() + addedTime);//累加在

线小时数
其实这个拦截器是在us.getAddedOnlineTime()>(sysConfig.getUserOnlineTime()*1000)这个条件去改变

相关的数据的!若不存在在线信息,添加之!若存在uo,其中比较了uo 和us的validateCode:
if (uo != null && !uo.getValidateCode().equals(us.getValidateCode())) { // 用户重复登录

      String ajax = "html";
      Map map = ac.getParameters();//action的参数


      String[] _ajax = (String[]) map.get("ajax");
      if (_ajax != null) {
       ajax = _ajax[0];//ajax="html"
      }

      ResourceBundleMessageSource messageSource =

(ResourceBundleMessageSource) wc
        .getBean("messageSource");
      String errorMsg = messageSource.getMessage

("error.login.re", null, ac.getLocale());
      if (ajax.equalsIgnoreCase("html") ||

ajax.equalsIgnoreCase("shtml")) {
       return "relogin";
      } else {
       AjaxMessagesJson ajaxMessagesJson =

(AjaxMessagesJson) wc.getBean("ajaxMessagesJson");
       ajaxMessagesJson.setMessage

("E_LOGIN", errorMsg);
       ac.getValueStack().set

("ajaxMessagesJson", ajaxMessagesJson);
       return "ajaxjson";
      }
这里的relogin:
<result name="relogin" type="redirect-action">login?action=relogin</result>注意你有个

actionerror!
接下来,是修改相应的userinfo信息及清理us!
UserInfo ui = userService.findUserInfoById(us.getId());

     if (ui != null) { // 取得正确用户信息

      ui.setStayTime(ui.getStayTime() +

(us.getAddedOnlineTime() / 1000)); // 增加用户在线时长
      us.setAddedOnlineTime(0);

      // 刷新权限
      if (Constant.USE_PERMISSION_CACHE) {
       logger.debug("刷新用户权限");
       us.setGroupID(ui.getGroupID());
       us.setBoardPermissionArray

(userService.getUserPermission(ui));
      }

      if (us.getAddedOnlineHour() >= 3600000) {
       logger.info("用户在线累计超过1个小时

,增加用户经验值");
       ui.setExperience(ui.getExperience()

+ (int) (us.getAddedOnlineHour() / 3600000));
       us.setAddedOnlineHour(0);

      }
最后,我们应该改变useronline的时间(用户在线存在时),以及us的活跃时间!
us.setLastActiveTime(nowTime);
ac.getSession().put(Constant.USER_SESSION_KEY, us);
好的,我们回到in:
<action name="in" class="inAction">
   <interceptor-ref name="mainUserAuthInterceptorStack"></interceptor-

ref>//它有默认

defaultStack,userLoginInterceptor,userSessionInterceptor,userOnlineInterceptor,userPermissio

nInterceptor(请注意联系与顺序)
   <interceptor-ref name="requestBasePathInterceptor"></interceptor-

ref>
   <result name="success">/WEB-INF/jsp/in.jsp</result>
  </action>
我们进入In.java不过它只实现了RequestBasePathAware:
其execute由index去完成!我们注意到extends BaseMainAction,而BaseMainAction:
public class BaseMainAction extends BaseAction implements UserSessionAware {
 private UserCookie userCookie;//get/set
 private UserSession userSession;
public String execute() {
  try {
   return this.executeMethod(this.getAction());
  } catch (Exception e) {
   logger.error(e);
   this.addActionError(this.getText("error.msg"));
   return ERROR;
  }
 }
-->
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;
 }
这个Action类注入有这些服务

userService,notService,friendService,userOnlineService,forumService,sysConfig,sysStatService

,boardService!而也于表单的或其它的属性(页面调用)有:

onlineHighest,sysinfo,lastLoginTime,titleValue,userTitle,newNoteNumInbox,noteAllNumInbox,fre

indNum,onlineGuestNum,friendOnlineNum,guest,newForums

(List<Forum>),boardList,boardMap,urlRewrite,usePass,actionUrl,toUrl,forumSite,useAuthCode.
我们先看onlineNum和onlineGuestNum的做法:
long atime = System.currentTimeMillis() - (this.getSysConfig().getUserOnlineTime() * 1000);

  long onlineNum = this.getUserOnlineService().getUserOnlineNum(atime, 0, 0,

Constant.NORMAL_USER_GROUPS);
  long onlineGuestNum = this.getUserOnlineService().getUserOnlineNum(atime, 0,

0, Constant.GUEST_USER_GROUPS);
  this.getSysStatService().saveOnline(onlineNum + onlineGuestNum);
this.setSysinfo(this.getText("bbscs.sysinfo", new String[] {
    String.valueOf(this.getSysStatService().getPostMainNum()),
    String.valueOf(this.getSysStatService().getPostNum()),
   String.valueOf(this.getSysStatService().getAllUserNum()), reguserurl

}));//bbscs.sysinfo=社区信息:主题数: {0} / 帖子总数: {1} / 社区人数: {2} / 欢迎新用户: {3}
其中:
long foNum = this.getUserOnlineService().getUserOnlineNumInIds(atime,
     this.getFriendService().findFriendIds

(this.getUserSession().getId(), 0), 0, 0,
     Constant.NORMAL_USER_GROUPS);
   this.setFriendOnlineNum(foNum);
其实struts2.0的Action我觉得比Struts1.2强在不仅能从表单得到数据,也能将数据传递过JSP界面,通

过标签或表达式语言show出来!
到in.jsp看看:
对于这个主页充分体现了strut2在界面上做的一些工作!请参考相关资料:

http://www.blogjava.net/max/archive/2006/10/18/75857.aspx
http://www.blogjava.net/max/archive/2007/04/28/114417.html
总体上对版区的显示是:
 <s:iterator id="board" value="%{boardList}">
<s:if test="#board.boardType==1">
 </s:if>
      <s:if test="#board.boardType==3">
</s:if>
   <s:set name="bl2" value="%{boardMap.get(#board.id)}"></s:set>
      <s:iterator id="b" value="#bl2">
</s:iterator>
      </s:iterator>
下面的最新贴由action传递过来的值:
 <table width="100%" border="0" cellpadding="3" cellspacing="0">
       <s:iterator id="newf" value="%{newForums}">
                        <tr>
                          <td>
                            <s:if test="%{urlRewrite}">
                              <a href="read-topic-<s:property value="#newf.boardID"/>-

<s:property value="#newf.mainID"/>-0-1-index-1.html"><s:property value="#newf.title"/></a>

                              [<a href="forum-index-<s:property

value="#newf.boardID"/>.html"><s:property value="#newf.boardName"/></a>]
                            </s:if>
       <s:else>
         <s:url action="read?action=topic"

id="posturl">
         <s:param name="bid"

value="#newf.boardID"/>
         <s:param name="id"

value="#newf.mainID"/>
         <s:param name="fcpage" value="1"/>
         <s:param name="fcaction"

value="index"/>
         </s:url>
         <a href="${posturl}"><s:property

value="#newf.title"/></a>
         <s:url action="forum?action=index"

id="forumurl">
         <s:param name="bid"

value="#newf.boardID"/>
         </s:url>
                              [<a href="${forumurl}"><s:property

value="#newf.boardName"/></a>]
                            </s:else>
                          </td>
                        </tr>
                      </s:iterator>
                    </table>
而其它的(如:班竹推荐,积分榜)大多由bbscs标签完成!我们来看看我
 <table width="100%" border="0" cellpadding="3" cellspacing="0">
                    <bbscs:in type="commend"/>
                </table>
这里我们重点分析一下bbscs在这页的一些tag:
<bbscs:face value="%{userSession.id}"/>
<bbscs:datetimes format="yyyy-MM-dd HH:mm:ss" datetype="date" value="%{lastLoginTime}"/>
<bbscs:boardmaster value="#board.boardMaster"/>
<bbscs:in type="commend"/>
<bbscs:in type="userexp"/>
<bbscs:in type="userlit"/>
<bbscs:in type="userknow"/>
我们一个一个来看:
<tag>
  <name>face</name>
  <tag-class>com.laoer.bbscs.web.taglib.UserFaceTag</tag-class>
  <body-content>empty</body-content>
  <attribute>
   <name>value</name>
   <required>true</required>
   <rtexprvalue>true</rtexprvalue>
  </attribute>
 </tag>
我们直接看UserFace.java:
public boolean start(Writer writer) {
  boolean result = super.start(writer);

  if (value == null) {
   value = "top";
  } else if (altSyntax()) {
   if (value.startsWith("%{") && value.endsWith("}")) {
    value = value.substring(2, value.length() -

1);//userSession.id
   }
  }
  String userId = "";
  Object idObj = this.getStack().findValue(value);//从栈中取值!
  if (idObj != null) {
   userId = (String) idObj;
  }
  StringBuffer sb = new StringBuffer();

  if (StringUtils.isBlank(userId)) {
   sb.append("<img src="");
   sb.append(facePicName);
   sb.append("" alt="Face" />");
   try {
    writer.write(sb.toString());
   } catch (IOException e) {
    e.printStackTrace();
   }
   return result;
  } else {
   if (userId.startsWith(Constant.GUEST_USERID)) { // 游客
    sb.append("<img src="");
    sb.append(facePicName);
    sb.append("" alt="Face" />");

   } else { // 正常用户
    WebApplicationContext wc =

WebApplicationContextUtils.getWebApplicationContext(this.pageContext
      .getServletContext());

    UserService us = (UserService) wc.getBean("userService");
    UserInfoSimple uis = us.getUserInfoSimple(userId);

    if (uis.getHavePic() == 1 && !uis.getPicFileName().equals

("-")) {
     sb.append("<a href="");
     sb.append(BBSCSUtil.getUserWebPath(uis.getId()));
     sb.append(uis.getPicFileName());//大图
     sb.append("" target="_blank">");
     sb.append("<img src="");
     sb.append(BBSCSUtil.getUserWebPath(uis.getId()));
     sb.append(uis.getPicFileName());
     sb.append(Constant.IMG_SMALL_FILEPREFIX);//小图
     sb.append("" alt="Face" border="0"

class="pic1"/>");
     sb.append("</a>");
    } else {
     sb.append("<img src="");
     sb.append(facePicName);
     sb.append("" alt="Face" />");
    }
   }
   try {
    writer.write(sb.toString());
   } catch (IOException e) {
    e.printStackTrace();
   }
  }

  return result;
 }
我们看第二个:
<bbscs:datetimes format="yyyy-MM-dd HH:mm:ss" datetype="date" value="%{lastLoginTime}"/>
 <tag>
  <name>datetimes</name>
  <tag-class>com.laoer.bbscs.web.taglib.DateTimesTag</tag-class>
  <body-content>empty</body-content>
  <attribute>
   <name>value</name>
   <required>true</required>
   <rtexprvalue>true</rtexprvalue>
  </attribute>
  <attribute>
   <name>datetype</name>
   <required>true</required>
   <rtexprvalue>true</rtexprvalue>
  </attribute>
  <attribute>
   <name>format</name>
   <required>false</required>
   <rtexprvalue>true</rtexprvalue>
  </attribute>
 </tag>
我们看com.laoer.bbscs.web.taglib.DateTimes关键的代码片断:
  Date date = null;
  if (this.datetype.equalsIgnoreCase("timestamp")) {
   long atime = (Long) this.getStack().findValue(value);
   date = new Date(atime);
  } else if (this.datetype.equalsIgnoreCase("date")) {
   date = (Date) this.getStack().findValue(value);
  } else {
   date = new Date();
  }
  String stime = Util.formatDate(date, format);//format有默认格式!
  try {
   writer.write(stime);
  } catch (IOException e) {
   logger.error(e);
  }
好,我们看第三个:<bbscs:boardmaster value="#board.boardMaster"/> #board是个标志!
 <tag>
  <name>boardmaster</name>
  <tag-class>com.laoer.bbscs.web.taglib.BoardMasterTag</tag-class>
  <body-content>empty</body-content>
  <attribute>
   <name>value</name>
   <required>true</required>
   <rtexprvalue>true</rtexprvalue>
  </attribute>
 </tag>
其主要的代码如下:
 Map boardMasterMap = (Map) this.getStack().findValue(value);//返回的是Map对象!

  if (boardMasterMap != null) {
   StringBuffer sb = new StringBuffer();
   // Set bmSet = boardMasterMap.entrySet();
   Iterator bmit = boardMasterMap.values().iterator();
   while (bmit.hasNext()) {
    com.laoer.bbscs.bean.BoardMaster bm =

(com.laoer.bbscs.bean.BoardMaster) bmit.next();
    if (bm.getIsHidden() == 0) {//不是隐身的!
     sb.append("<a href="");
     sb.append(BBSCSUtil.getActionMappingURL("/userInfo?

action=name&username=" + bm.getUserName(),
       request));
     sb.append("">");
     sb.append(bm.getUserName());
     sb.append("</a> ");
    }
   }
   try {
    writer.write(sb.toString());
   } catch (IOException e) {
    e.printStackTrace();
   }
  }
好,我们看最后一个了:<bbscs:in type="userknow"/> type有四种!
 <tag>
  <name>in</name>
  <tag-class>com.laoer.bbscs.web.taglib.InTag</tag-class>
  <body-content>empty</body-content>
  <attribute>
   <name>type</name>
   <required>true</required>
   <rtexprvalue>true</rtexprvalue>
  </attribute>
 </tag>
由于InTag中:
 public Component getBean(ValueStack arg0, HttpServletRequest arg1,

HttpServletResponse arg2) {
  return new InComponent(arg0);
 }

 protected void populateParams() {
  super.populateParams();

  InComponent tag = (InComponent)component;

  tag.setType(type);
 }
所示其执行者为InComponent,而非In!这可在InComponent.java里看到:
 if (this.getType().equalsIgnoreCase("commend")) {
   File commendFile = new File(BBSCSUtil.getIncludePath() +

"ForumCover_Commend_0.html");
   String commendlist = "";
   try {
    commendlist = FileUtils.readFileToString(commendFile,

Constant.CHARSET);
    if (commendlist == null) {
     commendlist = "";
    }
   } catch (IOException ex) {
    logger.error(ex);
    commendlist = "";
   }

   this.write(writer, commendlist);
   return result;
  }
-->
 private void write(Writer writer, String txt) {
  try {
   writer.write(txt);
  } catch (IOException e) {
   e.printStackTrace();
  }
 }
最后,我们应该对in.jsp中的一些东西有点感觉:
<s:property value="%{sysinfo}" escape="false"/>
其后面提供了google对本系统的搜索,也提供了搜索用户的表单:
 <s:form action="userInfo">
  <s:hidden name="action" value="name"></s:hidden>

 

抱歉!评论已关闭.