對天乙社區bbscs8實現的詳細分析三

經過前面的分析,我們已經理清楚了業務層,接下來的部分將是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>

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章