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

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

2018年02月11日 ⁄ 综合 ⁄ 共 144012字 ⁄ 字号 评论关闭

在分析三,我们已经分析出jsp页面如何通过struts2的标签与action配合,将数据在表示层传递(set/get),并且把主要的开始流程给分析完了。这里我们将前台的主要请求大致分析一下:从导航部分开始,对于社区首页in.bbscs我们已经讲过,它这里只不过是将框架的target=mainForm以便从任何位置转到首页!进入个人中心,点击修改签名,触发了signSet.bbscs:
<action name="signSet" class="signSetAction">
   <interceptor-ref name="mainUserAuthInterceptorStack"></interceptor-ref>//用户权限!
   <result name="success">/WEB-INF/jsp/signSet.jsp</result>
   <result name="input">/WEB-INF/jsp/signEdit.jsp</result>
  </action>
-->
注意到BaseMainAction:
 public String execute() {
  try {
   return this.executeMethod(this.getAction());
  } catch (Exception e) {
   logger.error(e);
   this.addActionError(this.getText("error.msg"));
   return ERROR;
  }
 }
而在BaseAction:private String action = "index";
 public String index() {
  String[] userSign = new String[3];
  userSign[0] = this.getUserSession().getSignDetail()[0];//从UserSession的SignDetail数组中把三个对象拿出来!
  userSign[1] = this.getUserSession().getSignDetail()[1];
  userSign[2] = this.getUserSession().getSignDetail()[2];
  userSign = BBSCSUtil.filterUserSign(userSign, this.getSysConfig().isSignUseHtml(), this.getSysConfig()
    .isSignUseUBB(), this.getSysConfig().isSignUseSmile());
  this.setUserSign0(userSign[0]);
  this.setUserSign1(userSign[1]);
  this.setUserSign2(userSign[2]);
  return SUCCESS;
 }
我们看一下signSet:
 <strong><a href="javascript:;" onclick="loadSignEditPage(Ɔ');"><s:text name="signset.sign"/>A</a></strong>
 <div id="signDiv0" class="signDivOff" onclick="loadSignEditPage(Ɔ');" onmouseover="over(this);" onmouseout="out(this);"><s:property value="%{userSign0}" escape="false"/></div>
需要注意到底部有个div:
  <tr>
          <td colspan="2">
            <div id="signDetailChange"></div>
          </td>
        </tr> 
我们来看loadSignEditPage(signID):
function loadSignEditPage(signID) {
  Element.show("signDetailChange");
  $('signDetailChange').innerHTML = pageLoading;//在jsMsg.jsp中,var pageLoading = "<s:text name="js.pageLoading"/>";
  var url = getActionMappingURL("/signSet");
  var pars = "action=edit&ajax=shtml&signID=" + signID;

  var myAjax = new Ajax.Updater("signDetailChange", url, {method: 'get', parameters: pars});
}
我们看看这个/signSet?action=edit&ajax=shtml&singnID=0;
public String edit() {
  this.setAction("editdo"); //设置action!
  this.setSignDetail(this.getUserSession().getSignDetail()[this.getSignID()]);
  return INPUT;
 }
进入signEdit.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
%>
这里将显示出的内容到signDetailChange这个DIV内,而这里的内部有一个显示Smile的:
loadSmilePage('signDetail')
-->
function loadSmilePage(inputName) {
  Element.show("smileDiv");
  $('smileDiv').innerHTML = pageLoading;
  var url = "smile.jsp";//根路径中!
  var pars = "inputName="+inputName;
  var myAjax = new Ajax.Updater("smileDiv", url, {method: 'get', parameters: pars});
}
这里有用到了smile.jsp,下面是显示表情图片的一个jsp代码:
<%
    int counter = 0;
    for (int i = 0; i < 85; i++) {
      if (counter == 0) {
        counter = 8;
        out.println("<tr>");
      }
  %>
    <td>
      <div align="center">
        <img id="smile<%=i%>" src="images/smile/<%=i%>.gif" alt="smile" onmousemove="this.style.cursor='hand'this.style.cursor='pointer'" onclick="insertSmile('<%=inputName%>','{<%=i%>}');"/>
      </div>
    </td>
  <%
    if (counter == 1) {
      counter = 0;
      out.println("</tr>");
    }
    else {
      counter = counter - 1;
    }
    }
  %>
当我们选择一个后,点击后将触发insertSmile('<%=inputName%>','{<%=i%>}');注意inputName为需要写入到哪个元素,这里当然是textarea的signDetail,所以才用loadSmilePage('signDetail').下面的js改变文本域内容:
function insertSmile(inputName,smlieTag) {
  $(inputName).value = $(inputName).value + smlieTag; //smileTag类似于{1}
  $(inputName).focus();
}
对于关闭:
function closeSmilePage() {
  $("smileDiv").innerHTML = "";
  Element.hide("smileDiv");
}
好,我们单击保存标签按钮:function signEditDo() {
  var signID = $('signID').value;
  var oSignEditAjax = new SignEditAjax(signID);//用到ajax请求
  oSignEditAjax.edit();
}
下面是其代码:
var SignEditAjax = Class.create();
SignEditAjax.prototype = {
  initialize: function(signID) { //构造
    this.signID = signID;
  },

  edit: function() {
    showExeMsg();
    var url = getActionMappingURL("/signSet");
    var pars = "action=editdo&ajax=xml&signID="+this.signID+"&signDetail="+encodeURIComponent($('signDetail').value);//将文本字符串编码为一个统一资源标识符 (URI) 的一个有效组件
    var myAjax = new Ajax.Request(url, {method: 'post', parameters: pars, onComplete: this.editCompleted.bind(this)});
  },

  editCompleted: function(res) {
    resText = res.responseText;
   var jsonMsgObj = new JsonMsgObj(resText);
   var codeid = jsonMsgObj.getCodeid();
    hiddenExeMsg();
    alert(jsonMsgObj.getMessage());//提示信息
    if (codeid == "0") {
      $('signDiv'+this.signID).innerHTML = jsonMsgObj.getText();//写入内容
      closeSignEditPage();//关闭编辑框!
    }
  }
};
这里又用致函一个函数:
function hiddenExeMsg() {
  var loade = document.getElementById("exeingdiv");
  if (loade != null) {
    loade.style.display = "none";
  }
}
OK!我们进入SignSet.java的editdo方法中:
public String editdo() {
  if (BBSCSUtil.getSysCharsetStrLength(this.getSignDetail()) > this.getSysConfig().getSignMaxLen()) { // 签名超过指定长度
/**
 public static int getSysCharsetStrLength(String txt) {
  try {
   return txt.getBytes(Constant.CHARSET).length;
  } catch (UnsupportedEncodingException ex) {
   return txt.length();
  }
 }
*/
   this.getAjaxMessagesJson().setMessage(
     "E_USER_SIGN_TOOLONG",
     this.getText("error.sign.toolong", new String[] { String.valueOf(this.getSysConfig()
       .getSignMaxLen()) }));
   return RESULT_AJAXJSON;
  }
  UserInfo ui = this.getUserService().findUserInfoById(this.getUserSession().getId());
  if (ui != null) {
   String signDetail = "";
   if (StringUtils.isBlank(this.getSignDetail())) { // 签名为空,设为默认签名
    signDetail = this.getText("bbscs.userdefaultsign");//系统用的资源
    switch (this.getSignID()) {
    case 0:
     ui.setSignDetail0(signDetail);
     break;
    case 1:
     ui.setSignDetail1(signDetail);
     break;
    case 2:
     ui.setSignDetail2(signDetail);
     break;
    }
   } else {
    signDetail = this.getSysConfig().bestrowScreen(this.getSignDetail()); // 过滤敏感词

    switch (this.getSignID()) {
    case 0:
     ui.setSignDetail0(signDetail);
     break;
    case 1:
     ui.setSignDetail1(signDetail);
     break;
    case 2:
     ui.setSignDetail2(signDetail);
     break;
    }
   }
   try {
    ui = this.getUserService().saveUserInfo(ui);
    this.getUserSession().getSignDetail()[this.getSignID()] = signDetail;
    signDetail = BBSCSUtil.filterText(signDetail, this.getSysConfig().isSignUseHtml(), this.getSysConfig()
      .isSignUseUBB(), this.getSysConfig().isSignUseSmile());
    this.getAjaxMessagesJson().setMessage("0", this.getText("sign.edit.ok"), signDetail);//设置message提示信息!
   } catch (BbscsException ex) {
    logger.error(ex);
    this.getAjaxMessagesJson().setMessage("E_USER_SIGN_ERROR", this.getText("error.sign.edit"));
   }
  }
  return RESULT_AJAXJSON;
 }
这里的关键是对文本信息的过滤(BBSCSUtil):
public String bestrowScreen(String txt) {  //将系统不允许出现的字词换成**
  if (StringUtils.isNotBlank(this.getScreenWord())) {
   String[] words = this.getScreenWord().split(";");
   for (int i = 0; i < words.length; i++) {
    txt = txt.replaceAll(words[i], this.getBestrowScreen());
   }
  }
  return txt;
 }
public static String filterText(String sign, boolean useHTML, boolean useUBB, boolean useSmile) {
  if (!useHTML) { //默认1
   sign = TextUtils.htmlEncode(sign);//转意字符!这里用的是com.opensymphony.xwork2.util工具类!
  }
  if (useUBB) {//0
   sign = getUBB2HTML(sign);
  }
  if (useSmile) {//1
   sign = replaceSmile(sign);
  }
  sign = sign.replaceAll(" ", "<BR/>");
  sign = filterScript(sign);
  return sign;
 }
-->
 public static String replaceSmile(String txt) {
  if (txt != null) {
   return txt.replaceAll("/{(/d{1,2})/}", "<img src="images/smile/$1.gif" alt="smile"/>");//正则表达式!
  } else {
   return "";
  }
 }
 public static String filterScript(String txt) {
  return txt.replaceAll("[Ss][Cc][Rr][Ii][Pp][Tt]", "s.c.r.i.p.t");
 }
 public static String getUBB2HTML(String txt) {//UBB实现
  if (txt != null) {
   AutoFilter af = new AutoFilter(txt);
   txt = af.getFilteredStr();
  }
  return txt;
 }
AutoFilter继承了RegexFilter...而RegexFilter实现了Filter接口:
public interface Filter {
  public abstract String getFilteredStr();//我们便通过这个方法得到返回的结果的!
}
在AutoFilter的构造方法中有许多regex的reStr属性(在RegexFilter定义为protected)也有this.doFiltration();方法,我们看super(txt)方法:
 protected RegexFilter(String source) {
    this.source = source;
    this.tempSource = source;
  }
而每次代换doFiltration():
 protected void doFiltration() {
    this.applyFilter();
    this.tempSource = filter.getFilteredStr();
  }
用了 protected void applyFilter() {
    FilterBuilder builder = new RegFilterBuilder(regex, rpStr, tempSource);//注意这里用的是tempSource!
    FilterDirector direct = new FilterDirector(builder);//导演FilterDirector生成FilterBuilder的各种实现
    direct.construct();

    this.filter = builder.getFilter();
  }
我们看真正的执行者:RegFilterBuilder,实现了FilterBuilder中的所有方法:
public interface FilterBuilder {
  public abstract void buildFilter();
  public abstract Filter getFilter();
}
它使用的是JDK中的正则类:
import java.util.regex.Matcher;
import java.util.regex.Pattern;
  public RegFilterBuilder(String regex, String rpStr, String source) {
    super();
    this.regex = regex;
    this.rpStr = rpStr;
    this.source = source;
  }
 public void buildFilter() {  //关键的方法!需重点理解之!请参考资料:http://wcjok.bokee.com/4293762.html
    if (this.regex == null) {
      return;
    }
    Pattern p = Pattern.compile(regex, 2);
    Matcher matcher = p.matcher(this.source);
    StringBuffer sb = new StringBuffer();
    String tempString = rpStr;
    int rpL = rpStr.split("/$[0-9]+").length;
    while (matcher.find()) {
      for (int i = 0; (i < rpL) && (i < matcher.groupCount()); i++) {
        tempString = tempString.replaceAll("/$" + i, matcher.group(i));
      }
      matcher.appendReplacement(sb, tempString);
    }
    matcher.appendTail(sb);
    this.result = sb.toString();
  }
public Filter getFilter() { //回调一个刚过滤的中间结果!
    return (new RegexFilter() {
      public String getFilteredStr() {
        return result;
      }
    });
当然我们还有一个FilterDirector!由它管理FilterBuilder对象和它的buildFilter方法!
public FilterDirector(FilterBuilder builder) {
    this.builder = builder;
  }
  public void construct() {
    builder.buildFilter();
  }
而getFilter则仍由Builder自己来返回this.filter = builder.getFilter();
接下来,我们分析nickNameSet.bbscs,它很简单:
 <action name="nickNameSet" class="nickNameSetAction">
   <interceptor-ref name="mainUserAuthInterceptorStack"></interceptor-ref>
   <result name="input">/WEB-INF/jsp/nickNameSet.jsp</result>
  </action>
 public String index() {
  this.setAction("edit");
  this.setNickName(this.getUserSession().getNickName());
  return INPUT;
 }
<s:form action="nickNameSet">
      <s:hidden name="action"></s:hidden>
      <tr>
        <td><s:text name="nickset.title"/></td>
        <td>
          <s:textfield id="nickName" name="nickName" cssClass="input2" size="40" maxlength="20" onkeypress="return handleEnter(this, event);"></s:textfield>//已经被填充!
        </td>
....
我们提交editNickName():
function editNickName() {
  var url = getActionMappingURL("/nickNameSet");
  var pars = "action=edit&ajax=xml&nickName="+encodeURIComponent($('nickName').value);
  var myAjax = new Ajax.Request(url, {method: 'post', parameters: pars, onComplete: editNickNameOK});
}

function editNickNameOK(res) {
  resText = res.responseText;
  var jsonMsgObj = new JsonMsgObj(resText);
  var codeid = jsonMsgObj.getCodeid();
  alert(jsonMsgObj.getMessage());
  if (codeid == "0") {
    $('nickNameDiv').innerHTML = jsonMsgObj.getText();
  }
}
对于public String edit()方法我们不在分析了,哦,注意edit()的返回类型!String!
OK!我们已经将接下来分析userConfig.bbscs!
  <action name="userConfig" class="userConfigSetAction">
   <interceptor-ref name="mainUserAuthInterceptorStack"></interceptor-ref>
   <result name="input">/WEB-INF/jsp/userConfig.jsp</result>
  </action>
先看userConfigSet.java:有acceptFriend,forumPerNum,forumViewMode,hiddenLogin,postPerNum,receiveNote,editType,timeZone等交互字段!也有sysOptionsValues(服务),userForumNumPerPageValues(List<OptionsInt>类型),userPostNumPerPageValues,userTimeZoneValues(List<OptionsString>),forumViewModeValues,radioEditInterfaceList等填充用的!
 public String index() {
  this.setUserForumNumPerPageValuesInit();//初始化用户文章列表每页显示数
  this.setUserPostNumPerPageValuesInit();
  this.setForumViewModeValuesInit();
  this.setRadioEditInterfaceValues();
  this.setAction("edit");
  UserInfo ui = this.getUserService().findUserInfoById(this.getUserSession().getId());
  if (ui != null) {
   this.setAcceptFriend(this.int2boolean(ui.getAcceptFriend()));//字段,这里的int2boolean是BaseAction中的方法!现在用的是int2boolean!
   this.setForumPerNum(ui.getForumPerNum());
   this.setForumViewMode(ui.getForumViewMode());
   this.setHiddenLogin(this.int2boolean(ui.getHiddenLogin()));
   this.setPostPerNum(ui.getPostPerNum());
   this.setReceiveNote(this.int2boolean(ui.getReceiveNote()));
   this.setTimeZone(ui.getTimeZone());
   this.setEditType(ui.getEditType());
  }
  return INPUT;
 }
-->
private void setUserForumNumPerPageValuesInit() {
  this.setUserForumNumPerPageValues(this.getSysOptionsValues().getUserForumNumPerPageValues(this.getLocale()));
 }
private void setUserPostNumPerPageValuesInit() {
  this.setUserPostNumPerPageValues(this.getSysOptionsValues().getUserPostNumPerPageValues(this.getLocale(),
    this.getSysConfig().getUserPostPerPageNum()));
 }
private void setForumViewModeValuesInit() {
  this.setForumViewModeValues(this.getSysOptionsValues().getForumViewModeValues(this.getLocale()));
 }
private void setRadioEditInterfaceValues() {
  radioEditInterfaceList.add(new RadioInt(-1, this.getText("bbscs.editInterface"))); //值对!
  radioEditInterfaceList.add(new RadioInt(0, this.getText("bbscs.editInterface0")));
  radioEditInterfaceList.add(new RadioInt(1, this.getText("bbscs.editInterface1")));
  radioEditInterfaceList.add(new RadioInt(2, this.getText("bbscs.editInterface2")));
 }
注意到SysOptionsValues提供了系统的一些Option值,它在com.laoer.bbscs.comm包中! 我们看它是怎么被注入到spring中的,在applicationContext.xml:
<bean id="sysOptionsValues"
  class="com.laoer.bbscs.comm.SysOptionsValues">
  <property name="messageSource">
   <ref bean="messageSource" />
  </property>
 </bean>
OK!
public List<OptionsInt> getUserForumNumPerPageValues(Locale locale) {//local用于本地化!
  List<OptionsInt> l = new ArrayList<OptionsInt>();
  l.add(new OptionsInt(0, this.getMessageSource().getMessage("bbscs.usesystem", null, locale)));
  l.add(new OptionsInt(20, "20"));
  l.add(new OptionsInt(30, "30"));
  l.add(new OptionsInt(40, "40"));
  return l;
 }
 public List<OptionsInt> getUserPostNumPerPageValues(Locale locale, String[] ppns) {
  List<OptionsInt> l = new ArrayList<OptionsInt>();
  l.add(new OptionsInt(0, this.getMessageSource().getMessage("bbscs.usesystem", null, locale)));
  for (int i = 0; i < ppns.length; i++) {
   l.add(new OptionsInt(NumberUtils.toInt(ppns[i], 10), ppns[i]));
  }
  return l;
 }
 public List<OptionsInt> getForumViewModeValues(Locale locale) {
  List<OptionsInt> l = new ArrayList<OptionsInt>();
  l.add(new OptionsInt(0, this.getMessageSource().getMessage("bbscs.viewmode0", null, locale)));
  l.add(new OptionsInt(1, this.getMessageSource().getMessage("bbscs.viewmode1", null, locale)));
  l.add(new OptionsInt(2, this.getMessageSource().getMessage("bbscs.viewmode2", null, locale)));
  return l;
 }
这里有一个OptionsInt,它在com.laoer.bbscs.web.ui包中!它也是
 public OptionsInt(int key, String value) {
  this.key = key;
  this.value = value;
  
 }
在UserconfigSet中还有一个List<OptionsString>:
 private List<OptionsString> userTimeZoneValues = Constant.USERTIMEZONE;
 public List<OptionsString> getUserTimeZoneValues() { //提供给JSP页面显示!
  return userTimeZoneValues;
 }
我们可以看看Constant中的初始化,在其static代码段内:
for (int i = 0; i < TIMEZONEVALUES.length; i++) {
   String[] values = TIMEZONEVALUES[i];
   TIMEZONE.add(new OptionsInt(i, values[0]));
   USERTIMEZONE.add(new OptionsString(values[1], values[0]));//返回值!
  }
-->
public OptionsString(String key, String value) {
  this.key = key;
  this.value = value;
 }
好,我们看显示的JSP页面,userConfig.jsp:
<s:select list="forumViewModeValues" name="forumViewMode" id="forumViewMode" cssClass="select1" listKey="key" listValue="value"></s:select>//OptionInt中有key和value两个属性!
   <s:radio list="radioEditInterfaceList" name="editType" listKey="key" listValue="value" theme="bbscs0"></s:radio>//theme=bbscs0!,下面是radiomap.ftl的代码:
<@s.iterator value="parameters.list">
    <#if parameters.listKey?exists>
        <#assign itemKey = stack.findValue(parameters.listKey)/>
    <#else>
        <#assign itemKey = stack.findValue('top')/>
    </#if>
    <#assign itemKeyStr = itemKey.toString() />
    <#if parameters.listValue?exists>
        <#assign itemValue = stack.findString(parameters.listValue)/>
    <#else>
        <#assign itemValue = stack.findString('top')/>
    </#if>
<input type="radio" name="${parameters.name?html}" id="${parameters.id?html}${itemKeyStr?html}"<#rt/>
<#if tag.contains(parameters.nameValue, itemKey)>  //关键点!!!
 checked="checked"<#rt/>
</#if>
<#if itemKey?exists>
 value="${itemKeyStr?html}"<#rt/>
</#if>
<#if parameters.disabled?default(false)>
 disabled="disabled"<#rt/>
</#if>
<#if parameters.tabindex?exists>
 tabindex="${parameters.tabindex?html}"<#rt/>
</#if>
<#if parameters.cssClass?exists>
 class="${parameters.cssClass?html}"<#rt/>
</#if>
<#if parameters.cssStyle?exists>
 style="${parameters.cssStyle?html}"<#rt/>
</#if>
<#if parameters.title?exists>
 title="${parameters.title?html}"<#rt/>
</#if>
<#include "/${parameters.templateDir}/simple/scripting-events.ftl" />
<#include "/${parameters.templateDir}/simple/common-attributes.ftl" />
/><#rt/>
<label for="${parameters.id?html}${itemKeyStr?html}"><#rt/>
    ${itemValue}<#t/>
</label><br/>
</@s.iterator>
我们看下生成的代码html:
   <input type="radio" name="editType" id="userConfig_editType-1" value="-1"/><label for="userConfig_editType-1">使用系统默认设置</label><br/>
<input type="radio" name="editType" id="userConfig_editType0" value="0"/><label for="userConfig_editType0">禁用控件</label><br/>
<input type="radio" name="editType" id="userConfig_editType1" value="1"/><label for="userConfig_editType1">启用标准控件</label><br/>
<input type="radio" name="editType" id="userConfig_editType2" checked="checked" value="2"/><label for="userConfig_editType2">启用标准和所见即所得控件</label><br/>
好,我们修改后提交触发 <input type="button" name="ClosePage" value="<s:text name="bbscs.botton.save"/>" class="button2" onclick="editUserConfig();"/>
下面是ajax处理的js:
function editUserConfig() {
  showExeMsg();//处理框出现!红色的哦~
  var url = getActionMappingURL("/userConfig");
  var pars = "action=edit&ajax=xml&hiddenLogin=" + getCheckBoxValue("hiddenLogin") + "&receiveNote="
  + getCheckBoxValue("receiveNote") + "&acceptFriend=" + getCheckBoxValue("acceptFriend") + "&forumViewMode="
  + $('forumViewMode').value + "&forumPerNum=" + $('forumPerNum').value + "&postPerNum="
  + $('postPerNum').value + "&timeZone=" + encodeURIComponent($('timeZone').value)
  + "&editType=" + getRadioValueByName("editType"); //好多的参数啊!!!!
  //alert(pars);
  var myAjax = new Ajax.Request(url, {method: 'post', parameters: pars, onComplete: editUserConfigOK});
}

function editUserConfigOK(res) {
  resText = res.responseText;
  var jsonMsgObj = new JsonMsgObj(resText);
  var codeid = jsonMsgObj.getCodeid();
  hiddenExeMsg();
  alert(jsonMsgObj.getMessage());
}
好的,我们回到UserConfigSet.java:
public String edit() {
  UserInfo ui = this.getUserService().findUserInfoById(this.getUserSession().getId());
  if (ui != null) {
   ui.setAcceptFriend(this.boolean2int(this.getAcceptFriend()));//action自动获得值!注意现在是boolean2int!
   ui.setForumPerNum(this.getForumPerNum());
   ui.setForumViewMode(this.getForumViewMode());
   ui.setHiddenLogin(this.boolean2int(this.getHiddenLogin()));
   ui.setPostPerNum(this.getPostPerNum());
   ui.setReceiveNote(this.boolean2int(this.getReceiveNote()));
   ui.setTimeZone(this.getTimeZone());
   ui.setEditType(this.getEditType());

   try {
    ui = this.getUserService().saveUserInfo(ui);
    this.getUserCookie().addCookies(ui);//加入cookie中!
    this.getAjaxMessagesJson().setMessage("0", this.getText("userconfig.set.ok"));
   } catch (BbscsException ex) {
    logger.error(ex);
    this.getAjaxMessagesJson().setMessage("E_USERCONFIG_EDITFAILED",
      this.getText("error.userconfig.seterror"));
   }
   return RESULT_AJAXJSON;
  } else {
   this.getAjaxMessagesJson().setMessage("E_USER_NOEXIST", this.getText("error.user.noexist"));
   return RESULT_AJAXJSON;
  }
 }
我们看friendSet.bbscs!
<action name="friendSet" class="friendSetAction">
   <interceptor-ref name="mainUserAuthInterceptorStack"></interceptor-ref>
   <result name="success">/WEB-INF/jsp/friendSet.jsp</result>
   <result name="flist">/WEB-INF/jsp/friendList.jsp</result>
   <result name="input">/WEB-INF/jsp/friendAdd.jsp</result>
  </action>
它有friendList,freindName,id,isBlack,friendComment等属性.
public String index() {
  return SUCCESS;
 }
直接进入friendSet.jsp:
<body onload="loadFriendList();"> //<script type="text/javascript" src="js/friend.js"></script>
<div id="f_bg">
  <div id="f_tabs">
    <ul>
      <li id="tab1" class="f_tabClass1"><a href="javascript:;" onclick="loadFriendList();"><s:text name="friend.fuser"/></a></li>
      <li id="tab2" class="f_tabClass2"><a href="javascript:;" onclick="loadBlackUserList();"><s:text name="friend.blackuser"/></a></li>
    </ul>
  </div>
</div>
<div id="f_main">
  <div id="friendlist"></div>//一个div
  <div id="addfriend"></div>//另一个div,用于ajax
</div>
</body>
我们在js/friend.js中找到相应的js:
function loadFriendList() { //含一些初始化工作!
  hiddenElement("addfriend");//隐藏addfriend这个div
  $('tab1').className = "f_tabClass1";//着色!
  $('tab2').className = "f_tabClass2";
  $('friendlist').innerHTML = pageLoading;//加载中!
  var url = getActionMappingURL("/friendSet");
  var pars = "action=flist&ajax=shtml&isBlack=0";
  var myAjax = new Ajax.Updater("friendlist", url, {method: 'get', parameters: pars});
}
-->
public String flist() {
  this.setFriendList(this.getFriendService().findFriends(this.getUserSession().getId(), this.getIsBlack()));//找到他们!
  return "flist";
 }
这里首先用了:
<%
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:iterator id="f" value="%{friendList}"> //下面是用于显示的遍历!
    <tr>
      <td>
        <s:property value="#f.friendName"/>
      </td>
      <td>
        <s:property value="#f.friendComment"/>
      </td>
      <td><a href="javascript:;" onclick="friendDel('<s:property value="#f.id"/>','<s:property value="#f.isBlack"/>');"><s:text name="bbscs.del"/></a></td>
    </tr>
  </s:iterator>
下面的是这个增加按钮的显示:
 <s:if test="%{isBlack==0}">
      <a href="javascript:;" onclick="friendNew(Ɔ');"><s:text name="friend.add"/></a>
      </s:if>
      <s:if test="%{isBlack==1}">
      <a href="javascript:;" onclick="friendNew(Ƈ');"><s:text name="friend.addblack"/></a>
      </s:if>
当我们点击黑名单时,调用JS:
function loadBlackUserList() {
  hiddenElement("addfriend");
  $('tab1').className = "f_tabClass2";
  $('tab2').className = "f_tabClass1";
  $('friendlist').innerHTML = pageLoading;
  var url = getActionMappingURL("/friendSet");
  var pars = "action=flist&ajax=shtml&isBlack=1";
  var myAjax = new Ajax.Updater("friendlist", url, {method: 'get', parameters: pars});
}
如果有好友,我们可以删除之..
function friendDel(id,isBlack) {
  var del = confirm(confirm_del); //需要确认一下!
  if (del) {
    var oFriendDelAjax = new FriendDelAjax(id,isBlack);
    oFriendDelAjax.delFriend();
  }
  else {
    return false;
  }
}
看下面的代码:
var FriendDelAjax = Class.create();
FriendDelAjax.prototype = {
  initialize: function(id,isBlack) {
    this.id = id;
    this.isBlack = isBlack;
  },

  delFriend: function() {
    showExeMsg();
    var url = getActionMappingURL("/friendSet");
    var pars = "action=del&ajax=xml&id=" + this.id;
    var myAjax = new Ajax.Request(url, {method: 'get', parameters: pars, onComplete: this.delFriendCompleted.bind(this)});
  },
  delFriendCompleted: function(res) {
    resText = res.responseText;
   var jsonMsgObj = new JsonMsgObj(resText);
   var codeid = jsonMsgObj.getCodeid();
    hiddenExeMsg();
    alert(jsonMsgObj.getMessage());
    if (codeid == "0") {
      if (this.isBlack == "0") {
        loadFriendList(); //根据isBlack重新加载好友列表!
      }
      if (this.isBlack == "1") {
        loadBlackUserList();
      }
    }
  }
};
我们看JAVA代码片断:
Friend f = this.getFriendService().findFriendByID(this.getId(), this.getUserSession().getId());//找到friend!
  if (f != null) {
   UserInfo ui = this.getUserService().findUserInfoById(f.getFriendID());
   int isBlack = f.getIsBlack();
   try {
    this.getFriendService().removeFriend(f);//去之
    if (ui != null) {
     if (isBlack == 0) {//数据库的记录是好友!
      ui.setUserKnow(ui.getUserKnow() - 1); // 减少用户人缘系数
      this.getUserService().saveUserInfo(ui);
     } else {
      ui.setUserKnow(ui.getUserKnow() + 1); // 增加用户人缘系数
      this.getUserService().saveUserInfo(ui);
     }
    }
当我们点击增加时:出现div为addfriend的内容显示,下面是一个特别之处:
   <td width="82%">
        <s:textfield id="friendName" name="friendName" cssClass="input2" size="40" maxlength="20" onkeypress="return handleEnter(this, event);"></s:textfield>
      </td>
-->
function handleEnter (field, event) {
  var keyCode = event.keyCode ? event.keyCode : event.which ? event.which : event.charCode;//firefox2.0中不支持 window.event.keyCode!
  if (keyCode == 13) { //回车键(也就相当于<br>没有)
    return false;
  }
  return true;
}
当提交时:
function friendAdd() {
  var isBlack = $('isBlack').value;
  var oFriendAddAjax = new FriendAddAjax(isBlack);
  oFriendAddAjax.addFriend();
}
ar FriendAddAjax = Class.create();

FriendAddAjax.prototype = {
  initialize: function(isBlack) {
    this.isBlack = isBlack;
  },

  addFriend: function() {
    showExeMsg();
    var url = getActionMappingURL("/friendSet");
    var pars = "action=addsave&ajax=xml&friendName="+$('friendName').value+"&friendComment="
    + encodeURIComponent($('friendComment').value) + "&isBlack="+this.isBlack //带全参数哦!
    var myAjax = new Ajax.Request(url, {method: 'post', parameters: pars, onComplete: this.addFriendCompleted.bind(this)});
  },

  addFriendCompleted: function(res) {

    resText = res.responseText;
   var jsonMsgObj = new JsonMsgObj(resText);
   var codeid = jsonMsgObj.getCodeid();

    hiddenExeMsg();
    alert(jsonMsgObj.getMessage());
    if (codeid == "0") {
      closeFriendNewPage();
      if (this.isBlack == "0") {
        loadFriendList();
      }
      if (this.isBlack == "1") {
        loadBlackUserList();
      }
    }
  }
};
对于FriendSet.java的addsave方法:
f = this.getFriendFactory().getInstance(this.getUserSession().getId());
产生一个freind实例用于填充之.
f = this.getFriendService().saveFriend(f); // 保存用户
   this.getFriendService().friendIDsToFile(this.getUserSession().getId()); // 将用户列表写入文件
   if (this.getIsBlack() == 0) { // 添加好友情况下
    ui.setUserKnow(ui.getUserKnow() + 1); // 增加用户人缘系数
    this.getUserService().saveUserInfo(ui);
   } else {
    ui.setUserKnow(ui.getUserKnow() - 1); // 减少用户人缘系数
    this.getUserService().saveUserInfo(ui);
   }
好的,我们看note.bbscs:
<action name="note" class="noteAction">
   <interceptor-ref name="mainUserAuthInterceptorStack"></interceptor-ref>
   <interceptor-ref name="requestBasePathInterceptor"></interceptor-ref>
   <result name="success">/WEB-INF/jsp/note.jsp</result>
   <result name="noteInbox">/WEB-INF/jsp/noteInbox.jsp</result>
   <result name="noteOutbox">/WEB-INF/jsp/noteOutbox.jsp</result>
   <result name="input">/WEB-INF/jsp/noteSend.jsp</result>
   <result name="noteReadInbox">/WEB-INF/jsp/noteReadInbox.jsp</result>
  </action>
不过这里用到了requestBasePathInterceptor,注意他对应的是NoteAction.java:有fromID,id,ids,noteContext,noteTitle,toID,toUserName,needRe,pageList等属性.
public String index() {
  return SUCCESS;
 }
看note.jsp:<body onload="loadNoteInbox();">它有了js/note.js中的一些函数:
function loadNoteInbox() {
  $('noteListDiv').innerHTML = pageLoadingCenter;//noteListDiv是一个空的div,var pageLoadingCenter = "<div align="center"><s:text name="js.pageLoading"/></div>";
  var urls = getActionMappingURL("/note");
  var pars = "action=inbox&ajax=shtml";
  var myAjax = new Ajax.Updater("noteListDiv", urls, {method: 'get', parameters: pars});
  showInboxNum();
  showOutboxNum();
}
-->
function showInboxNum() {

  var url = getActionMappingURL("/note");
  var pars = "action=innum&ajax=xml";

  var myAjax = new Ajax.Request(url, {method: 'get', parameters: pars, onComplete: showInboxNumComplete});

}

function showInboxNumComplete(res) {
 var resText = res.responseText;
   var jsonMsgObj = new JsonMsgObj(resText);

   $('inboxNumDiv').innerHTML = jsonMsgObj.getText();
}

function showOutboxNum() {

  var url = getActionMappingURL("/note");
  var pars = "action=outnum&ajax=xml";

  var myAjax = new Ajax.Request(url, {method: 'get', parameters: pars, onComplete: showOutboxNumComplete});
}

function showOutboxNumComplete(res) {
 resText = res.responseText;
   var jsonMsgObj = new JsonMsgObj(resText);
   $('outboxNumDiv').innerHTML = jsonMsgObj.getText();
}
这里有三个ajax请求,对应的java代码:
public String innum() {
  long inBoxNum = this.getNoteService().getNoteAllNumInBox(this.getUserSession().getId());
  this.getAjaxMessagesJson().setMessage("0", "", String.valueOf(inBoxNum));
  return RESULT_AJAXJSON;
 }
 public String outnum() {
  long outBoxNum = this.getNoteService().getNoteAllNumOutBox(this.getUserSession().getId());
  this.getAjaxMessagesJson().setMessage("0", "", String.valueOf(outBoxNum));
  return RESULT_AJAXJSON;
 }
 public String inbox() { //这个方法带分页哦!~
  Pages pages = new Pages();
  pages.setPage(this.getPage());
  pages.setPerPageNum(this.getSysConfig().getPmPerPage());
  //pages.setPerPageNum(2);
  pages.setFileName(this.getBasePath()
    + BBSCSUtil.getActionMappingURLWithoutPrefix("note?action=inbox&ajax=shtml"));
  this.setPageList(this.getNoteService().findNotesInBox(this.getUserSession().getId(), pages));//可以用PageList!
  return "noteInbox"; //好,接下来我们看看它对应的页面!
 }
我们看看noteInbox.jsp,我们得到的pageList封装了所有的东东。
 <span class="font1"><strong><s:text name="note.title"/>:<s:property value="%{pageList.pages.totalNum}"/></strong></span> //总数的显示!
                    <input id="checkall" type="checkbox" name="checkall" value="checkall" onclick="checkAll();"/>
-->
function checkAll() {
  var ca = document.getElementById("checkall");
  var ids = document.getElementsByName("ids");
  for (var i = 0; i < ids.length; i++) {
      ids[i].checked = ca.checked;   //所有的ids checkbox与你选择的一致!
  }
}
下面是主要的遍历整个收件箱的其中关于note显示部分的过程:
 <td colspan="2">
                  <div id="noteDiv<s:property value="#note.id"/>" class="noteClass1" style="display:none">
                    <div id="noteDetail<s:property value="#note.id"/>"></div>
                    <div id="noteSend<s:property value="#note.id"/>" style="display:none">
                      <form action="<%=BBSCSUtil.getActionMappingURL("/note",request)%>" name="noteSendForm<s:property value="#note.id"/>">
                      <INPUT TYPE="hidden" name="id" value="<s:property value="#note.id"/>">
                      <table width="100%" border="0" cellpadding="5" cellspacing="0">

                        <tr>
                          <td width="15%"><s:text name="note.tousername"/>:</td>
                          <td width="85%"><input name="toUserName" type="text" value="<s:property value="#note.fromUserName"/>" readonly="readonly" class="input2" size="40" /></td>
                        </tr>
                        <tr>
                          <td width="15%"><s:text name="note.msg.title"/>:</td>
                          <td width="85%"><input name="noteTitle" type="text" class="input2" size="40" /></td>
                        </tr>
                        <tr>
                          <td valign="top"><s:text name="note.content"/>:</td>
                          <td><textarea name="noteContext" cols="40" rows="5" class="textarea1"></textarea></td>
                        </tr>
                        <tr>
                          <td><s:text name="note.needre"/>:</td>
                          <td><input type="checkbox" name="needRe" value="1" />
                            <s:text name="note.needre.notice"/></td>
                        </tr>
                        <tr>
                          <td>&nbsp;</td>
                          <td>
                            <input name="Submit2" type="button" class="button1" onclick="noteRe('<s:property value="#note.id"/>');" value="<s:text name="bbscs.re"/>" />
                            <input type="button" name="closeSendInButton" class="button1" onclick="closeNoteSendInNote('<s:property value="#note.id"/>');" value="<s:text name="bbscs.close"/>"/>
                          </td>
                        </tr>

                      </table>
                      </form>
                    </div>
                    <div id="needRe<s:property value="#note.id"/>" style="display:none"><s:property value="#note.needRe"/></div>
                  </div>
                </td>
我们先看loadNoteReadInbox()函数:
function loadNoteReadInbox(noteId,page) {

  needRe_span = document.getElementById("needRe"+noteId);//是否需要回复!
  var needRe_num_val = needRe_span.innerHTML;
  var needRe_int_val = parseInt(needRe_num_val);
  if (needRe_int_val == 1) {
    autoRe(noteId);//调用回复!见下
  }
  displayElement("noteDiv"+noteId);//显示note在noteDiv+noteId处!
  var urls = getActionMappingURL("/note");
  var pars = "action=readinbox&ajax=shtml&id=" + noteId + "&page=" + page;
  var myAjax = new Ajax.Updater("noteDetail"+noteId, urls, {method: 'get', parameters: pars});//更新的是noteDetail处div,注意另外一个noteSend的div还没显示出来!
  $('noteIsNew'+noteId).innerHTML = "<img src="images/note_old.gif"/>";
}
-->
function autoRe(noteId) {
  var cRe = confirm(confirmNoteRe); //var confirmNoteRe = "<s:text name="js.confirmNoteRe"/>";
  if (cRe) {
    var oNoteAutoReOjbAjax = new NoteAutoReOjbAjax(noteId);
    oNoteAutoReOjbAjax.autore();
  }
  else {
    return false;
  }
}
-->
ar NoteAutoReOjbAjax = Class.create();

NoteAutoReOjbAjax.prototype = {
  initialize: function(noteId) {
   this.noteId = noteId;
  },

  autore: function() {

    var url = getActionMappingURL("/note");
    var pars = "action=autore&ajax=xml&id=" + this.noteId;
    var myAjax = new Ajax.Request(url, {method: 'get', parameters: pars, onComplete: this.autoreCompleted.bind(this)});
  },

  autoreCompleted: function(res) {
   resText = res.responseText;
   var jsonMsgObj = new JsonMsgObj(resText);
   var codeid = jsonMsgObj.getCodeid();
   alert(jsonMsgObj.getMessage());
   if (codeid == "0") {
    refreshBoxNum("outbox",1);//增加发件箱note数
/**
function refreshBoxNum(boxName,num) {
  var num_span;
  if (boxName == "inbox") {
    num_span = document.getElementById("inboxNumDiv");
  }
  else {
    num_span = document.getElementById("outboxNumDiv");
  }
  var num_val = num_span.innerHTML;
  var int_val = parseInt(num_val);
  var new_int_val = int_val + num;
  num_span.innerHTML = new_int_val;
}
*/
    document.getElementById("needRe"+this.noteId).innerHTML = "0";//不需要回复了!
   }
  }
};
好,我们看noteInbox.jsp的底部相关JSP代码:
  <tr>
          <td colspan="3" class="bgColor3">
            <table width="100%" border="0" cellpadding="0" cellspacing="0">
              <tr>
                <td width="67%"><s:text name="bbscs.pagebreak"/>: <bbscs:pages value="%{pageList.pages}" javaScript="loadNoteInboxUrl"/> <a href="javascript:;" onclick="delAllInBox();"><s:text name="note.delall"/></a></td>
                <td width="33%">
                  <div align="right"><s:text name="note.selected"/>:
                    <select name="noteOp" class="select1">
                      <option value="1" selected="selected"><s:text name="bbscs.del"/></option>
                      <option value="2"><s:text name="note.getout"/></option>
                    </select>
                    <input name="Submit" type="submit" class="button1" onclick="noteOpInBox();" value="<s:text name="bbscs.exe"/>"/>
                  </div>
                  <div id="cpage" style="display:none"><s:property value="%{pageList.pages.page}"/></div> //当前页
                </td>
              </tr>
我们看js:
function delAllInBox() {
  var del = confirm(confirm_del);
  if (del) {
    var url = getActionMappingURL("/note");
    var pars = "action=delallinbox";
    var myAjax = new Ajax.Request(url, {method: 'get', parameters: pars, onComplete: delAllInBoxComplete});
  }
  else {
    return false;
  }
}
function noteOpInBox() {
  var noteOpSelectObj = document.getElementById("noteOp");

  if (noteOpSelectObj.options[noteOpSelectObj.selectedIndex].value == "1") {
  deleteIdsInboxNote(); //删除选择ids的note
  }
  //if (noteOpSelectObj.options[noteOpSelectObj.selectedIndex].value == "2") {
 
  //} //晕,还没有实现呢~~~
}
-->
function deleteIdsInboxNote() {
  var del = confirm(confirm_del);
  if (del) {
    var pageNum = document.getElementById("cpage").innerHTML;//获得当前页信息
    var ids = document.getElementsByName("ids");
    var noteNum = 0;

    var data = "";
    for (var i = 0; i < ids.length; i++) {
      if (ids[i].checked) {
        data += "&ids=";
        data += ids[i].value;//data=&ids=XX&ids=XX
        noteNum++;//note删除数!
      }
    }
    if (noteNum > 0) {
     var oNoteDelIdsInboxAjax = new NoteDelIdsInboxAjax(pageNum,noteNum,data);
     oNoteDelIdsInboxAjax.dels();
    }
    else {
    return false;
    }
  }
  else {
    return false;
  }
我们看看NoteDelIdsInboxAjax(pageNum,noteNum,data);
var NoteDelIdsInboxAjax = Class.create();
NoteDelIdsInboxAjax.prototype = {
  initialize: function(pageNum,noteNum,data) {
   this.pageNum = pageNum;
   this.noteNum = noteNum;
   this.data = data;
  },
  dels: function() {

    var url = getActionMappingURL("/note");
    var pars = "action=delidsinbox&ajax=xml" + this.data;
    var myAjax = new Ajax.Request(url, {method: 'post', parameters: pars, onComplete: this.delsCompleted.bind(this)});
  },
  delsCompleted: function(res) {
   resText = res.responseText;
   var jsonMsgObj = new JsonMsgObj(resText);
   var codeid = jsonMsgObj.getCodeid();
   alert(jsonMsgObj.getMessage());
   if (codeid == "0") {
    refreshBoxNum("inbox",-this.noteNum);//注意-代表是在原来的 num_span = document.getElementById("inboxNumDiv");中减去this.noteNum数量!
    var url = getActionMappingURL("/note?action=inbox&ajax=shtml&page=" + this.pageNum);//还是这页!
    loadNoteInboxUrl(url);
   }
  }
};
-->
function loadNoteInboxUrl(url) {
  $('noteListDiv').innerHTML = pageLoadingCenter;
  var urls = getActionName(url);
  var pars = getActionPars(url);
  var myAjax = new Ajax.Updater("noteListDiv", urls, {method: 'get', parameters: pars});
  showInboxNum();
  showOutboxNum();
}
而getActionName和getActionPars两个函数在comm.js中:
function getActionName(url) {
 var question = url.indexOf("?");
 if (question > 0) {
  return url.substring(0, question);
 }
 else {
  return url;
 }
}
function getActionPars(url) {
 var question = url.indexOf("?");
 if (question > 0) {
  return url.substring(question+1, url.length);
 }
 else {
  var d = new Date();
  var t = d.getTime();
  return "timestamp="+t;
 }
}
这里用了个bbscs:pages <bbscs:pages value="%{pageList.pages}" javaScript="loadNoteInboxUrl"/>我们分析下,这个子tag的属性可真多,不过只有value必须有,其它可不用:
<tag>
  <name>pages</name>
  <tag-class>com.laoer.bbscs.web.taglib.PageTag</tag-class>
  <body-content>empty</body-content>
  <attribute>
   <name>value</name>
   <required>true</required>
   <rtexprvalue>true</rtexprvalue>
  </attribute>
  <attribute>
   <name>styleClass</name>
   <required>false</required>
   <rtexprvalue>true</rtexprvalue>
  </attribute>
  <attribute>
   <name>argPage</name>
   <required>false</required>
   <rtexprvalue>true</rtexprvalue>
  </attribute>
  <attribute>
   <name>argTotal</name>
   <required>false</required>
   <rtexprvalue>true</rtexprvalue>
  </attribute>
  <attribute>
   <name>pageSep</name>
   <required>false</required>
   <rtexprvalue>true</rtexprvalue>
  </attribute>
  <attribute>
   <name>javaScript</name>
   <required>false</required>
   <rtexprvalue>true</rtexprvalue>
  </attribute>
 </tag>
我们看PageTag.java:
protected void populateParams() {
  super.populateParams();
  Page tag = (Page) component;
  tag.setArgPage(argPage);
  tag.setArgTotal(argTotal);
  tag.setJavaScript(javaScript);//OK!
  tag.setPageSep(pageSep);
  tag.setStyleClass(styleClass);
  tag.setValue(value);//OK!
 }
我们进入com.laoer.bbscs.web.taglib包里面的Page.java:
 protected String styleClass = ""; //显示格式
 protected String argPage = "page";//页参数名称
 protected String argTotal = "total";//总页参数名称
 protected int pageSep = 10;//一页显示的数量
 protected String javaScript = "";//用的javascript
 private String value;//%{pageList.pages} -->Pages pages = (Pages) this.getStack().findValue(value);
// 一行显示页数
  int pagesep = this.pageSep;
  // 总行数
  int allpagesep = (int) Math.ceil((pages.getAllPage() + pagesep - 1) / pagesep);
  // 当前行数
  int cpagesep = (int) Math.ceil((pages.getCpage() + pagesep - 1) / pagesep);
  // 当前一行显示页数
  int cnum;
  if (pages.getAllPage() > 0) {
   // 在页中
   if (cpagesep != allpagesep) {
    cnum = pagesep;
   }
   // 页末
   else {
    cnum = pages.getAllPage() % pagesep;
    // 正好整除
    if (cnum == 0) {
     cnum = pagesep;
    }
   }
  } else {
   cnum = 0;
  }
 String fileName = pages.getFileName();

 StringBuffer sb = new StringBuffer();

其实这个public boolean start(Writer writer)方法中主要根据pages.isUseUrlRewrite()和this.javaScript分四种情况来讨论这个分页的写法的。下面是本次调用所使用的部分:
sb.append("<span");

    if (StringUtils.isNotBlank(this.styleClass)) {//有没有样式!
     sb.append(" class="");
     sb.append(this.styleClass);
     sb.append(""");
    }
    sb.append(">");
    if (cpagesep > 1) {//一页显示数大于1
     sb.append("<a href="javascript:;" onclick="");
     sb.append(this.javaScript);
     sb.append("('");
     sb.append(fileName);
     sb.append(this.getArgPage());
     sb.append("=1&");
     sb.append(this.argTotal);
     sb.append("=");
     sb.append(pages.getTotalNum());
     sb.append("');">");
     // sb.append("|&lt;");
     sb.append("&laquo;");
     sb.append("</a>");

     // sb.append(" <a href="");
     sb.append(" <a href="javascript:;" onclick="");
     sb.append(this.javaScript);
     sb.append("('");

     sb.append(fileName);
     sb.append(this.getArgPage());
     sb.append("=");
     // 往前一行
     int previous;
     if (pages.getCpage() <= 1) {
      previous = 1;
     } else {
      previous = pages.getCpage() - 1;
     }
     sb.append(previous);
     // sb.append("&total=");
     sb.append("&");
     sb.append(this.argTotal);
     sb.append("=");
     sb.append(pages.getTotalNum());
     // sb.append("">");
     sb.append("');">");
     // sb.append("&lt;");
     sb.append("&#8249;");
     sb.append("</a>");

    }

    /**
     * middle butten ,append to <a href='xxx.lt?page=x&t=x'><img></a>
     */
    for (int i = 0; i < cnum; i++) {
     // sb.append(" <a href="");
     sb.append(" <a href="javascript:;" onclick="");
     sb.append(this.javaScript);
     sb.append("('");
     sb.append(fileName);
     sb.append(this.getArgPage());
     sb.append("=");

     sb.append(((i + 1) + ((cpagesep - 1) * pagesep)));
     sb.append("&");
     sb.append(this.argTotal);
     sb.append("=");
     sb.append(pages.getTotalNum());
     // sb.append("">");
     sb.append("');">");
     if (pages.getCpage() == (i + 1) + ((cpagesep - 1) * pagesep)) {
      sb.append("<strong>");
      sb.append((i + 1) + ((cpagesep - 1) * pagesep));
      sb.append("</strong>");
     } else {
      sb.append(((i + 1) + ((cpagesep - 1) * pagesep)));
     }
     sb.append("</a>");
    }
    /**
     * next butten ,append to <a href='xxx.lt?page=x&t=x'><img></a>
     */

    if (cpagesep < allpagesep) {

     // sb.append(" <a href="");
     // sb.append(pages.getFileName());
     sb.append(" <a href="javascript:;" onclick="");
     sb.append(this.javaScript);
     sb.append("('");

     sb.append(fileName);
     sb.append(this.getArgPage());
     sb.append("=");
     // sb.append("page=");
     int next;
     if (pages.getCpage() >= pages.getAllPage()) {
      next = (int) pages.getAllPage();
     } else {
      next = pages.getCpage() + 1;
     }
     sb.append(next);
     sb.append("&");
     sb.append(this.argTotal);
     sb.append("=");
     // sb.append("&t=");
     sb.append(pages.getTotalNum());
     // sb.append("">");
     sb.append("');">");

     // sb.append("&gt;");
     sb.append("&#8250;");
     sb.append("</a> ");

     // sb.append("<a href="");

     // sb.append(pages.getFileName());
     sb.append(" <a href="javascript:;" onclick="");
     sb.append(this.javaScript);
     sb.append("('");

     sb.append(fileName);
     sb.append(this.getArgPage());
     sb.append("=");
     // sb.append("page=");
     sb.append(pages.getAllPage());
     sb.append("&");
     sb.append(this.argTotal);
     sb.append("=");
     // sb.append("&t=");
     sb.append(pages.getTotalNum());
     // sb.append("">");
     sb.append("');">");
     // sb.append("&gt;|");
     sb.append("&raquo;");
     sb.append("</a>");
    }
    sb.append("</span>");
注意到这里用了两个replace方法,可将tempfilename中的一些值{page}{total}改为实际值:
public String replacePage(String txt, int page) {
  return txt.replaceAll("/{page/}", String.valueOf(page));
 }

 public String replaceTotal(String txt, long total) {
  return txt.replaceAll("/{total/}", String.valueOf(total));
 }
这里我们再回头看看javaScript:
function loadNoteInboxUrl(url) {
  $('noteListDiv').innerHTML = pageLoadingCenter;
  var urls = getActionName(url);
  var pars = getActionPars(url);
  var myAjax = new Ajax.Updater("noteListDiv", urls, {method: 'get', parameters: pars});
  showInboxNum();
  showOutboxNum();
}
我们看看这些是怎么与.java后端服务action联系在一起的:
public String readinbox() {
  note = this.getNoteService().findNoteByIDToID(this.getId(), this.getUserSession().getId());
  if (note == null) {
   this.addActionError(this.getText("error.note.notexist"));
   return RESULT_HTMLERROR;
  }
  if (note.getIsNew() == 1) {//note未读
   note.setIsNew(0); //设置为note已读
   try {
    note = this.getNoteService().saveNote(note);
   } catch (BbscsException ex2) {
    logger.error(ex2);
    this.addActionError(this.getText("error.note.read.error"));
    return RESULT_HTMLERROR;
   }
  }
  if (note.getSysMsg() == 0) {//不是系统消息!
   note.setNoteContext(BBSCSUtil.filterText(note.getNoteContext(), this.getSysConfig().isPmAllowHTML(), this
     .getSysConfig().isPmAllowUBB(), this.getSysConfig().isPmAllowSmilies()));
  }

  return "noteReadInbox";
 }
我们看noteReadInbox 对应的noteReadInbox.jsp:
 <s:property value="%{note.noteContext}" escape="false"/>
...
   <div align="right">
      <s:if test="%{note.noteType==1}">//收件箱!
      <span id="noteReImg<s:property value="%{note.id}"/>" align="right"></span>
      <s:if test="%{note.isRe==1}">
      <img src="images/note_replied.gif" alt=""/>
      </s:if>
        [<a href="javascript:;" onclick="loadNoteSendInNote('<s:property value="%{note.id}"/>');"><s:text name="bbscs.re"/></a>]
        [<a href="javascript:;" onclick="deleteInboxNote('<s:property value="%{note.id}"/>','<s:property value="%{page}"/>');"><s:text name="bbscs.del"/></a>]
      </s:if>
      <s:else>//发件箱
        [<a href="javascript:;" onclick="deleteOutboxNote('<s:property value="%{note.id}"/>','<s:property value="%{page}"/>');"><s:text name="bbscs.del"/></a>]
      </s:else>
      [<a href="javascript:;" onclick="closeNoteDiv('<s:property value="%{note.id}"/>');"><s:text name="bbscs.close"/></a>]//加入关闭按钮!
      </div>
我们看看这些js:
function deleteInboxNote(noteId,pageNum) {
  var del = confirm(confirm_del);
  if (del) {
    var oNoteDelInboxOjbAjax = new NoteDelInboxOjbAjax(noteId,pageNum);
    oNoteDelInboxOjbAjax.del();
  }
  else {
    return false;
  }
}

var NoteDelInboxOjbAjax = Class.create();

NoteDelInboxOjbAjax.prototype = {
  initialize: function(noteId,pageNum) {
   this.noteId = noteId;
    this.pageNum = pageNum;
  },

  del: function() {

    var url = getActionMappingURL("/note");
    var pars = "action=delinbox&ajax=xml&id=" + this.noteId;
    var myAjax = new Ajax.Request(url, {method: 'get', parameters: pars, onComplete: this.delCompleted.bind(this)});
  },

  delCompleted: function(res) {
   resText = res.responseText;
   var jsonMsgObj = new JsonMsgObj(resText);
   var codeid = jsonMsgObj.getCodeid();
   alert(jsonMsgObj.getMessage());
   if (codeid == "0") {
    refreshBoxNum("inbox",-1);
    var urls = getActionMappingURL("/note?action=inbox&ajax=shtml&page=" + this.pageNum);
    loadNoteInboxUrl(urls);
   }
  }
};

//===
function deleteOutboxNote(noteId,pageNum) {
  //alert(noteId+"/" +pageNum);
  var del = confirm(confirm_del);
  if (del) {
    var oNoteDelOutboxOjbAjax = new NoteDelOutboxOjbAjax(noteId,pageNum);
    oNoteDelOutboxOjbAjax.del();
  }
  else {
    return false;
  }
}

var NoteDelOutboxOjbAjax = Class.create();

NoteDelOutboxOjbAjax.prototype = {
  initialize: function(noteId,pageNum) {
   this.noteId = noteId;
    this.pageNum = pageNum;
  },

  del: function() {

    var url = getActionMappingURL("/note");
    var pars = "action=deloutbox&ajax=xml&id=" + this.noteId;
    var myAjax = new Ajax.Request(url, {method: 'get', parameters: pars, onComplete: this.delCompleted.bind(this)});
  },

  delCompleted: function(res) {
   resText = res.responseText;
   var jsonMsgObj = new JsonMsgObj(resText);
   var codeid = jsonMsgObj.getCodeid();
   alert(jsonMsgObj.getMessage());
   if (codeid == "0") {
    refreshBoxNum("outbox",-1);
    var urls = getActionMappingURL("/note?action=outbox&ajax=shtml&page=" + this.pageNum);
    loadNoteOutboxUrl(urls);
   }
  }
};

function loadNoteSendInNote(noteId) {
   Element.show('noteSend' + noteId);
 }
function closeNoteDiv(noteId) {
      hiddenElement("noteDiv"+noteId);
}
关于loadNoteSendInNote,它其实是一个内部的div执行回复功能用的!其关键的代码:
  <input name="Submit2" type="button" class="button1" onclick="noteRe('<s:property value="#note.id"/>');" value="<s:text name="bbscs.re"/>" />
<input type="button" name="closeSendInButton" class="button1" onclick="closeNoteSendInNote('<s:property value="#note.id"/>');" value="<s:text name="bbscs.close"/>"/>
这里我们看noteRe:
function noteRe(noteId) {
 var oNoteReAjax = new NoteReAjax(noteId);
 oNoteReAjax.re();
}

var NoteReAjax = Class.create();
NoteReAjax.prototype = {
  initialize: function(noteId) {
   this.noteId = noteId;
  },
  re: function() {
   var url = getActionMappingURL("/note");
   var noteForm = eval("document.noteSendForm" + this.noteId);
   var needRe = 0;
   if (noteForm.needRe.checked) {
    needRe = 1;
   }
   var pars = "action=re&ajax=xml&toUserName=" + noteForm.toUserName.value + "&noteTitle="
   + encodeURIComponent(noteForm.noteTitle.value) + "&noteContext=" + encodeURIComponent(noteForm.noteContext.value)
   + "&needRe=" + needRe + "&id="+noteForm.id.value;

    var myAjax = new Ajax.Request(url, {method: 'post', parameters: pars, onComplete: this.reCompleted.bind(this)});
  },
  reCompleted: function(res) {
   resText = res.responseText;
   var jsonMsgObj = new JsonMsgObj(resText);
   var codeid = jsonMsgObj.getCodeid();
   alert(jsonMsgObj.getMessage());
   if (codeid == "0") {
    refreshBoxNum("outbox",1);
    var noteForm = eval("document.noteSendForm" + this.noteId);
    noteForm.noteTitle.value = "";
    noteForm.noteContext.value = "";
    closeNoteSendInNote(this.noteId);
    $('noteReImg'+this.noteId).innerHTML = "<img src="images/note_replied.gif" alt=""/>";
   }
  }
};
而它在java文件中是这样完成任务的:
public String re() {
  if (this.getSysConfig().isInPmFloodTime(this.getUserCookie().getLastSendNoteTime())) {
   this.getAjaxMessagesJson().setMessage(
     "E_NOTE_INFLOODTIME",
     this.getText("error.note.isinfloodtime", new String[] { String.valueOf(this.getSysConfig()
       .getPmFloodTime()) }));
   return RESULT_AJAXJSON;
  }

  Note note = this.getNoteService().findNoteByIDToID(this.getId(), this.getUserSession().getId());
  if (note == null) {
   this.getAjaxMessagesJson().setMessage("E_NOTE_NOTEXIST", this.getText("error.note.notexist"));
   return RESULT_AJAXJSON;
  }
  note.setNeedRe(0);
  note.setIsRe(1);
  try {
   note = this.getNoteService().saveNote(note);
  } catch (BbscsException ex3) {
   logger.error(ex3);
  }

  if (StringUtils.isBlank(this.getToUserName()) || StringUtils.isBlank(this.getNoteTitle())
    || StringUtils.isBlank(this.getNoteContext())) {
   this.getAjaxMessagesJson().setMessage("E_NULL", this.getText("error.nullerror"));
   return RESULT_AJAXJSON;
  }

  if (BBSCSUtil.getSysCharsetStrLength(this.getNoteContext()) > this.getSysConfig().getPmMaxLength()) {
   this.getAjaxMessagesJson().setMessage(
     "E_NOTE_TOOLONG",
     this.getText("error.note.context.toolong", new String[] { String.valueOf(this.getSysConfig()
       .getPmMaxLength()) }));
   return RESULT_AJAXJSON;
  }

  if (this.getToUserName().equalsIgnoreCase(this.getUserSession().getUserName())) {
   this.getAjaxMessagesJson().setMessage("E_NOTE_USERSAME", this.getText("error.note.usersame"));
   return RESULT_AJAXJSON;
  }

  UserInfo ui = this.getUserService().findUserInfoByUserName(this.getToUserName());
  if (ui == null) {
   this.getAjaxMessagesJson().setMessage("E_USER_NOEXIST", this.getText("error.note.touser.noexist"));
   return RESULT_AJAXJSON;
  }

  // todo 不接收悄悄话
  if (ui.getReceiveNote() != 1) {
   this.getAjaxMessagesJson().setMessage("E_NOTE_USER_NOT_RECEIVE", this.getText("error.note.usernotreceive"));
   return RESULT_AJAXJSON;
  }

  // todo 黑名单
  Friend f = this.getFriendService().findFriendByName(this.getUserSession().getUserName(), ui.getId());
  if (f != null && f.getIsBlack() == 1) {
   this.getAjaxMessagesJson().setMessage("E_NOTE_USER_IS_BLACK", this.getText("error.note.userisinblack"));
   return RESULT_AJAXJSON;
  }

  Note inboxNote = this.getNoteFactory().getInstance(note.getFromID()); // 对方收件箱对象
  Note outboxNote = this.getNoteFactory().getInstance(this.getUserSession().getId()); // 自己发件箱对象

  Date sdate = new Date();

  inboxNote.setCreateTime(sdate);
  inboxNote.setFromID(this.getUserSession().getId());
  inboxNote.setFromNickName(this.getUserSession().getNickName());
  inboxNote.setFromUserName(this.getUserSession().getUserName());
  inboxNote.setIsNew(1);
  inboxNote.setIsRe(0);
  inboxNote.setNeedRe(this.getNeedRe());
  inboxNote.setNoteContext(this.getNoteContext());
  inboxNote.setNoteTitle(this.getNoteTitle());
  inboxNote.setNoteType(1);
  inboxNote.setToID(ui.getId());
  inboxNote.setToNickName(ui.getNickName());
  inboxNote.setToUserName(ui.getUserName());
  inboxNote.setSysMsg(0);

  outboxNote.setCreateTime(sdate);
  outboxNote.setFromID(this.getUserSession().getId());
  outboxNote.setFromNickName(this.getUserSession().getNickName());
  outboxNote.setFromUserName(this.getUserSession().getUserName());
  outboxNote.setIsNew(0);
  outboxNote.setIsRe(0);
  outboxNote.setNeedRe(0);
  outboxNote.setNoteContext(this.getNoteContext());
  outboxNote.setNoteTitle(this.getNoteTitle());
  outboxNote.setNoteType(0);
  outboxNote.setToID(ui.getId());
  outboxNote.setToNickName(ui.getNickName());
  outboxNote.setToUserName(ui.getUserName());
  outboxNote.setSysMsg(0);

  try {
   this.getNoteService().createNote(inboxNote, outboxNote);
   this.getUserCookie().addLastNoteSendTime();
   this.getAjaxMessagesJson().setMessage("0", this.getText("note.re.ok"));
  } catch (BbscsException ex) {
   logger.error(ex);
   this.getAjaxMessagesJson().setMessage("E_NOTE_ADDFAILED", this.getText("error.note.add.error"));
  }
  return RESULT_AJAXJSON;
 }
回到NoteAction.java:注意到autore是个回执用的!它发的短信加prefix!
this.getNoteService().createNote(inboxNote, outboxNote);
public String delallinbox() {
  try {
   this.getNoteService().removeAllInBox(this.getUserSession().getId());
   this.getAjaxMessagesJson().setMessage("0", this.getText("note.del.ok"));
  } catch (BbscsException ex4) {
   logger.error(ex4);
   this.getAjaxMessagesJson().setMessage("E_NOTE_DEL", this.getText("error.note.del.error"));
  }
  return RESULT_AJAXJSON;
 }
public String delinbox() {
  try {
   this.getNoteService().removeByIDToID(this.getId(), this.getUserSession().getId());//由id和toid
   this.getAjaxMessagesJson().setMessage("0", this.getText("note.del.ok"));
  } catch (BbscsException ex1) {
   logger.error(ex1);
   this.getAjaxMessagesJson().setMessage("E_NOTE_DEL", this.getText("error.note.del.error"));
  }
  return RESULT_AJAXJSON;
 }
public String delidsinbox() {  //根据ids删除相关toid用户的note!
  if (this.getIds() == null || this.getIds().isEmpty()) {
   this.getAjaxMessagesJson().setMessage("E_NOTE_IDSISNULL", this.getText("error.note.ids.null"));
   return RESULT_AJAXJSON;
  }
  try {
   this.getNoteService().removeInIDsToID(this.getIds(), this.getUserSession().getId());
   this.getAjaxMessagesJson().setMessage("0", this.getText("note.del.ok"));
  } catch (BbscsException ex5) {
   logger.error(ex5);
   this.getAjaxMessagesJson().setMessage("E_NOTE_DEL", this.getText("error.note.del.error"));
  }
  return RESULT_AJAXJSON;
 }

好,我们回到note.jsp,我们看changeBox:
function changeBox() {
  var boxSelectObj = document.getElementById("boxSelect");

  if (boxSelectObj.options[boxSelectObj.selectedIndex].value == "1") {
    loadNoteInbox();
  }
  if (boxSelectObj.options[boxSelectObj.selectedIndex].value == "2") {
    loadNoteOutbox();
  }
}
这里,有loadNoteOutbox我们应该可以发现它与收件箱十分的类似!我们也看看noteOutbox对应的noteOutbox.jsp,我们发现也大致相同,因此我们不对其专门分析。我们在回到note.jsp,它有一个发送的功能!
 <strong><a href="javascript:;" onclick="loadNoteSend();"><s:text name="note.sendnew"/></a></strong>
-->
function loadNoteSend() {
  displayElement("noteSendDiv");
  $('noteSendDiv').innerHTML = pageLoadingCenter;
  var urls = getActionMappingURL("/note");
  var pars = "action=add&ajax=shtml"; //发送
/**
 public String add() {
  this.setAction("addsave");
  return INPUT;//<result name="input">/WEB-INF/jsp/noteSend.jsp</result>
 }
*/
  var myAjax = new Ajax.Updater("noteSendDiv", urls, {method: 'get', parameters: pars});
}
我们提交可触发它的 onclick="noteAdd();":
function noteAdd() {
  var url = getActionMappingURL("/note");
  var needRe = 0;
  if ($('needRe').checked) {
    needRe = 1;
  }
  //alert(needRe);
  var pars = "action=addsave&ajax=xml&toUserName=" + $('toUserName').value + "&noteTitle="
  + encodeURIComponent($('noteTitle').value) + "&noteContext=" + encodeURIComponent($('noteContext').value)
  + "&needRe=" + needRe;
  //alert(data);
  var myAjax = new Ajax.Request(url, {method: 'post', parameters: pars, onComplete: noteAddComplete});
}
function noteAddComplete(res) {
 resText = res.responseText;
   var jsonMsgObj = new JsonMsgObj(resText);
  var codeid = jsonMsgObj.getCodeid();
  alert(jsonMsgObj.getMessage());
  if (codeid == "0") {
    closeNoteSend();
    refreshBoxNum("outbox",1);//更新发的!
  }
}
对于这个外部发送和内部回复差不多,我们就省略之。
OK!我们看下一个:书签管理bookMark.bbscs
<action name="bookMark" class="bookMarkAction">
   <interceptor-ref name="mainUserAuthInterceptorStack"></interceptor-ref>
   <interceptor-ref name="requestBasePathInterceptor"></interceptor-ref>
   <result name="success">/WEB-INF/jsp/bookMark.jsp</result>
   <result name="list">/WEB-INF/jsp/bookMarkList.jsp</result>
   <result name="input">/WEB-INF/jsp/bookMarkAddEdit.jsp</result>
  </action>
我们直接看BookMarkAction.java: 
public String index() {
  return SUCCESS;
 }
而在bookMark.jsp与note.jsp类似,也用了bookMark.js,且body部分代码:
<body onload="loadBookMarkListPage();">
<div id="bookMarkListDiv"></div>
<div id="addEditBookMarkDiv"></div>
</body>
function loadBookMarkListPage() {
  $('bookMarkListDiv').innerHTML = pageLoadingCenter;
  var urls = getActionMappingURL("/bookMark");
  var pars = "action=list&ajax=shtml";
  var myAjax = new Ajax.Updater("bookMarkListDiv", urls, {method: 'get', parameters: pars});
}
我们进入java代码一看:
 public String list() {
  Pages pages = new Pages();
  pages.setPage(this.getPage()); //page=1
  pages.setPerPageNum(10);
  pages.setFileName(this.getBasePath()
    + BBSCSUtil.getActionMappingURLWithoutPrefix("bookMark?action=" + this.getAction() + "&ajax=shtml"));//filename
  this.setPageList(this.getBookMarkService().findBookMarks(this.getUserSession().getId(), pages));
  return "list";
 }
在bookMarkList.jsp中我们注意需要注意的是loadBookMarkEditPage和bookMarkDel两个js及下面的共享js:
   <s:if test="#bookMark.isShare==1">
            <a href="javascript:;" onclick="bookMarkShare('<s:property value="#bookMark.id"/>',Ɔ','<s:property value="%{pageList.pages.page}"/>');"><s:text name="bookmark.unshare"/></a>
            </s:if>
            <s:else>
            <a href="javascript:;" onclick="bookMarkShare('<s:property value="#bookMark.id"/>',Ƈ','<s:property value="%{pageList.pages.page}"/>');"><s:text name="bookmark.share"/></a>
            </s:else>
好,我们看看新增(或修改)书签:loadBookMarkAddPage():
function loadBookMarkAddPage() {
  displayElement("addEditBookMarkDiv");
  $('addEditBookMarkDiv').innerHTML = pageLoadingCenter;
  var urls = getActionMappingURL("/bookMark");
  var pars = "action=add&ajax=shtml";
  var myAjax = new Ajax.Updater("addEditBookMarkDiv", urls, {method: 'get', parameters: pars});
}
-->
 public String add() {
  this.setAction("addsave");
  this.setIsShare(1);
  return INPUT;
 }
我们看提交后的js:
function bookMarkAdd() {
  var url = getActionMappingURL("/bookMark");
  var isShare = 0;
  if ($('isShare').checked) {
    isShare = 1;
  }
  var pars = "action=addsave&ajax=xml&bookMarkName=" + encodeURIComponent($('bookMarkName').value) + "&alt="
  + encodeURIComponent($('alt').value) + "&url=" + encodeURIComponent($('url').value) + "&isShare=" + isShare;

  var myAjax = new Ajax.Request(url, {method: 'post', parameters: pars, onComplete: bookMarkAddComplete});
}
function bookMarkAddComplete(res) {
 resText = res.responseText;
   var jsonMsgObj = new JsonMsgObj(resText);
   var codeid = jsonMsgObj.getCodeid();
   alert(jsonMsgObj.getMessage());
   if (codeid == "0") {
    closeAddEditBookMark();
    loadBookMarkListPage();
   }
}
-->
public String addsave() {
  if (StringUtils.isBlank(this.getBookMarkName())) {
   this.getAjaxMessagesJson().setMessage("E_BOOKMARK_NAME_NULL", this.getText("error.bookmark.name.null"));
   return RESULT_AJAXJSON;
  }
  BookMark bm = this.getBookMarkFactory().getInstance(this.getUserSession().getId());//实例一个BookMark对象
  bm.setAlt(StringUtils.trimToEmpty(this.getAlt()));//从表单中来!
  bm.setBookMarkName(StringUtils.trimToEmpty(this.getBookMarkName()));
  bm.setCreateTime(new Date());
  bm.setIsShare(this.getIsShare());
  bm.setUrl(StringUtils.trimToEmpty(this.getUrl()));
  bm.setUserID(this.getUserSession().getId());

  try {
   bm = this.getBookMarkService().saveBookMark(bm);
   this.getAjaxMessagesJson().setMessage("0", this.getText("bookmark.add.ok"));
  } catch (BbscsException ex) {
   logger.error(ex);
   this.getAjaxMessagesJson().setMessage("E_NOTE_ADDFAILED", this.getText("error.bookmark.add.error"));
  }
  return RESULT_AJAXJSON;
 }
对于其它的方法我们不再分析了,我们继续看下一个:userFace.bbscs?action=index
<action name="userFace" class="com.laoer.bbscs.web.action.UserFace">
   <interceptor-ref name="mainUserAuthInterceptorStack"></interceptor-ref>
   <result name="success">/WEB-INF/jsp/userFaceEdit.jsp</result>
   <result name="showface">/WEB-INF/jsp/showface.jsp</result>
   <result name="userFaceUpPage">/WEB-INF/jsp/userFaceUpPage.jsp</result>
   <result name="userFaceUpComponent">/WEB-INF/jsp/userFaceUpComponent.jsp</result>
  </action>
进入java代码:
 public String index() {
  this.setUseFace(this.getSysConfig().getUseFace());//要不要useFace,默认为1
  return SUCCESS;
 }
 这里有用到了<div id="userFaceDiv" align="center"><bbscs:face value="%{userSession.id}"/></div>注意这个div!
<s:if test="%{useFace==1}"> //action传递过来的值
        <iframe id="upfileIframe" src="<%=BBSCSUtil.getActionMappingURL("/userFace?action=uppage",request)%>" height="120" width="98%" scrolling="no" frameborder="0"></iframe>
        </s:if>
-->public String uppage() {
  this.setAction("up");
  return "userFaceUpPage";
 }
我们看看userFaceUpPage.jsp:
 <s:form action="userFace" method="POST" enctype="multipart/form-data">
  <s:hidden name="action"></s:hidden>//action=up
  <tr>
    <td>
      <strong><s:text name="face.upload"/></strong>
    </td>
  </tr>
  <tr>
    <td>
      <s:file name="upload" cssClass="input2" size="40" onchange="previewPic('upload');" id="upload"></s:file>//s:file
    </td>
  </tr>
  <tr>
    <td>
      <span id="photoview"></span>//这个很关键哦~
    </td>
  </tr>
  <tr>
    <td>
      <s:submit value="%{getText('face.upload')}" cssClass="button2"></s:submit>
    </td>
  </tr>
  </s:form>
我们选择一幅图片,就触发了previewPic('upload'),它在photoview处显示了图片:
function previewPic(upfilename){              //过滤功能!!!
  window.parent.upIframeSize();
  var upfile = document.getElementById(upfilename);
  if (upfile != "") {
    var extname = getExtName(upfile.value);
    if(extname != "jpg" && extname != "gif" && extname != "jpeg"){
      upfile.value = "";
      alert("<s:text name="face.jpggif"/>"); //不是图片
    }else{
      preViewPic('photoview', null, upfile.value, 0, null, 0, 120);
    }
  }
}

//图片预览
function preViewPic(picDivName,imgName,fn,iBorder,iBorderColor,iWidth,iHeight){
    //var picdiv=document.all(picDivName);
    var picdiv = document.getElementById(picDivName);
    if(picdiv != null && fn != null && fn!=''){
      if(imgName ==null) imgName = "picname";
      if(iBorder == null) iBorder=0;
      if(iBorderColor == null) iBorderColor="#CCCCCC";
      var strw,strh
      if(iWidth == null || iWidth == 0) strw = "";
      else strw = " width="+iWidth+" ";
      if(iHeight == null || iHeight == 0) strh = "";
      else strh = " height=" + iHeight + " ";
      picdiv.innerHTML="<img name='"+imgName+"' alt='预览状态...' "+strw+strh+" border="+iBorder+
            " src='"+fn+"' style='border-color:"+iBorderColor+"'>";
      document.focus();
    }
}
当我们点击上传头像时,触发了/useFace?action=up~~~~当然不是get,而是post方法了哦~
public String up() {
  // System.out.println(this.getUpload());用于测试
  // System.out.println(this.getUploadFileName());
  if (this.getUpload() == null || StringUtils.isBlank(this.getUploadFileName())) {
   this.setAjaxCodeid("1");
   this.setAjaxMsg(this.getText("error.userupimg.null"));
   return "userFaceUpComponent";
  }
  if (!BBSCSUtil.isAllowPicFile(this.getUploadFileName()) || this.getUpload().length() == 0
    || this.getUpload().length() > this.getSysConfig().getFaceSize() * 1024) {//大于50K
   this.setAjaxCodeid("2");
   this.setAjaxMsg(this.getText("error.userupimg.upnotice", new String[] { String.valueOf(this.getSysConfig()
     .getFaceSize()) }));
   return "userFaceUpComponent";
  }
  UserInfo ui = this.getUserService().findUserInfoById(this.getUserSession().getId());
  if (ui == null) {//用于不存在
   this.setAjaxCodeid("3");
   this.setAjaxMsg(this.getText("error.userupimg.uperror"));
  }
  String distFileName = ui.getId() + System.currentTimeMillis() + "."
    + FilenameUtils.getExtension(this.getUploadFileName());
  try {
   this.getUserService().createUserFacePic(ui, distFileName, new FileInputStream(this.getUpload()));//注意this.getUpload...
/**
 private File upload;
 public File getUpload() {
  return upload;
 }
 public void setUpload(File upload) {
  this.upload = upload;
 }
*/
   this.setAjaxCodeid("0");
   this.setAjaxMsg(this.getText("userupimg.up.ok"));
  } catch (FileNotFoundException ex) {
   this.setAjaxCodeid("3");
   this.setAjaxMsg(this.getText("error.userupimg.uperror"));
  } catch (BbscsException ex) {
   this.setAjaxCodeid("4");
   this.setAjaxMsg(this.getText("error.userupimg.uperror"));
  }
  return "userFaceUpComponent";
 }
我们看我们在iframe中的提交为什么能更新上面框架的内容,我们看userFaceUpComponent:
<%@page contentType="text/html; charset=UTF-8"%>
<%@taglib uri="/WEB-INF/struts-tags.tld" prefix="s"%>
<script language="JavaScript" type="text/javascript">
window.parent.OnUploadCompleted("<s:property value="%{ajaxCodeid}"/>","<s:property value="%{ajaxMsg}"/>");
</script>
哦,这里触发了上层的js:
function OnUploadCompleted(codeid,msg){
  var objif = document.getElementById('upfileIframe');
  objif.height = 120;
  objif.src= getActionMappingURL("/userFace?action=uppage");  //还原为原来的样子!
  if (codeid == "0") {
    //alert(msg);
    userFaceShow();
  }
  if (codeid == "1") {
    alert(msg); //提示错误信息
  }
  if (codeid == "2") {
    alert(msg);
  }
  if (codeid == "3") {
    alert(msg);
  }
  if (codeid == "4") {
    alert(msg);
  }
}
function userFaceShow() { //显示出来!
  $('userFaceDiv').innerHTML = pageLoadingCenter;
  var userId = "<s:property value="%{userSession.id}"/>";
  var urls = getActionMappingURL("/userFace");
  var pars = "action=showface&ajax=shtml&userId=" + userId;
  var myAjax = new Ajax.Updater("userFaceDiv", urls, {method: 'get', parameters: pars});
}
-->
 public String showface() {
  this.setUserId(this.getUserSession().getId());
  return "showface";
 }
看下showFace.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
%>
<bbscs:face value="%{userId}"/>
对于删除功能我们不再加以分析!
我们看userDetailSet.bbscs?action=index:
<action name="userDetailSet" class="userDetailSetAction">
   <interceptor-ref name="mainUserAuthInterceptorStack"></interceptor-ref>
   <interceptor-ref name="remoteAddrInterceptor"></interceptor-ref>
   <result name="input">/WEB-INF/jsp/userDetailSet.jsp</result>//注意这里是input
  </action>
十分简单哦我们看index方法中关键的代码:
UserInfo ui = this.getUserService().findUserInfoById(this.getUserSession().getId());
  if (ui == null) {
   this.addActionError(this.getText("error.user.noexist"));
   return ERROR;
  }
  setRadioSexListValues();
/**
List<RadioInt> radioSexList = new ArrayList<RadioInt>();

 private void setRadioSexListValues() {
  radioSexList.add(new RadioInt(1, this.getText("bbscs.man")));
  radioSexList.add(new RadioInt(2, this.getText("bbscs.woman")));
 }
*/
  this.setAction("edit");//action!
  this.setBirthDay(ui.getBirthDay());
  this.setBirthMonth(ui.getBirthMonth());
  this.setBirthYear(String.valueOf(ui.getBirthYear()));
另外,值得注意的是这个action类中还有一些yearValues,monthValues,dayValues:
private List<OptionsString> yearValues = Constant.YEARS;
private List<OptionsInt> monthValues = Constant.MONTH;
private List<OptionsInt> dayValues = Constant.DAY;
-->
public static List<OptionsString> YEARS = new ArrayList<OptionsString>();//OptionString! 
public static List<OptionsInt> MONTH = new ArrayList<OptionsInt>();
public static List<OptionsInt> DAY = new ArrayList<OptionsInt>();
我们看userDetailSet.jsp:
<table width="95%" border="0" align="center" cellpadding="0" cellspacing="0">
  <tr>
    <td>
      <s:actionerror theme="bbscs0"/>
    </td>
  </tr>
  <tr>
    <td>
      <s:actionmessage theme="bbscs0"/>  //显示数据更新成功的提示!
    </td>
  </tr>
</table>
我们在template.bbscs0中找到actionmessage.ftl:
<#if (actionMessages?exists && actionMessages?size > 0)>
 <div class="msg3">
  <#list actionMessages as message>
   <span class="actionMessage">${message}</span><br/>
  </#list>
 </div>
</#if>

  <td class="bgColor2">
          <s:select list="yearValues" name="birthYear" id="birthYear" cssClass="select1" listKey="key" listValue="value"></s:select>
          <s:text name="bbscs.year"/>
          <s:select list="monthValues" name="birthMonth" id="birthMonth" cssClass="select1" listKey="key" listValue="value"></s:select>
          <s:text name="bbscs.mon"/>
          <s:select list="dayValues" name="birthDay" id="birthDay" cssClass="select1" listKey="key" listValue="value"></s:select>
          <s:text name="bbscs.day"/>
        </td>
对应年月日的显示!注意这个页面的修改提交没用ajax!
好,我们看最后一个个人中心的设置了:boardSaveManage.bbscs
<action name="boardSaveManage" class="boardSaveManageAction">
   <interceptor-ref name="mainUserAuthInterceptorStack"></interceptor-ref>
   <result name="success">/WEB-INF/jsp/boardSaveManage.jsp</result>
   <result name="list" type="redirect-action">boardSaveManage?action=index</result>
  </action>
进入index方法中:
 public String index() {
  this.setAction("dels");
  List bslist = this.getBoardSaveService().findBoardSavesByUid(this.getUserSession().getId());
  this.setBoardList(this.getBoardService().findBoardsInIDs(this.getBoardIds(bslist)));
  return SUCCESS;
 }
==>private List getBoardIds(List bslist) {
  List<Long> blist = new ArrayList<Long>();
  for (int i = 0; i < bslist.size(); i++) {
   BoardSave bs = (BoardSave) bslist.get(i);
   blist.add(new Long(bs.getBoardID()));//将BoardID写入到blist中!
  }
  return blist;
 }
而boardSaveManage.jsp遍历结果加以显示出来!
<td valign="middle" class="bgColor4">
          <div align="center">
            <input type="checkbox" name="ids" value="<s:property value="#b.id"/>"/>
          </div>
        </td>
 <td colspan="4" class="bgColor4">
          <s:submit cssClass="button1" value="%{getText('bbscs.del')}"></s:submit>
        </td>
这个是删除功能:/boardSaveManage?action=dels:
public String dels() {
  if (this.getIds() == null || this.getIds().isEmpty()) {
   this.addActionError(this.getText("error.parametererror"));
   return ERROR;
  }
  try {
   this.getBoardSaveService().removeBoardSaveByBidsUid(this.getUserSession().getId(), this.getIds());
   return "list";
  } catch (BbscsException e) {
   logger.error(e);
   this.addActionError(this.getText("error.boardsave.del"));
   return ERROR;
  }
 }
OK!十分简单!我们看logout.bbscs先!
 <action name="logout" class="logoutAction">
      <interceptor-ref name="defaultStack"></interceptor-ref>
   <interceptor-ref name="userLoginInterceptor"></interceptor-ref>
   <interceptor-ref name="userSessionInterceptor"></interceptor-ref>
   <interceptor-ref name="requestBasePathInterceptor"></interceptor-ref>
   <result name="success" type="redirect">${logoutUrl}</result>
  </action>
它是在main package中的,但它继承了BaseAction,实现了UserSessionAware, RequestBasePathAware, SessionAware,好我们看其execute方法:
public String execute() {
  UserOnline uo = this.getUserOnlineService().findUserOnlineByUserID(userSession.getId());
  if (uo != null) {
   try {
    this.getUserOnlineService().removeUserOnline(uo);
   } catch (BbscsException e) {
    logger.error(e);
   }
  }
  this.getSession().remove(Constant.USER_SESSION_KEY);
  userCookie.removeAllCookies();

  if (StringUtils.isNotBlank(this.getAction()) && this.getAction().equalsIgnoreCase("pass")) {
   userCookie.removePassCookies();
  }

  if (StringUtils.isBlank(this.getSysConfig().getLogoutUrl())
    || this.getSysConfig().getLogoutUrl().startsWith("/")) {
   String url = this.getSysConfig().getLogoutUrl().substring(1, this.getSysConfig().getLogoutUrl().length());
   url = BBSCSUtil.getActionMappingURLWithoutPrefix(url);

   this.setLogoutUrl(this.basePath + url);//logout url!
  } else {
   this.setLogoutUrl(this.getSysConfig().getLogoutUrl());
  }
  return SUCCESS;
 }
在首页中,我们先分析在线online.bbscs?action=user及online.bbscs?action=friend:
  <action name="online" class="onlineAction">
   <interceptor-ref name="mainUserAuthInterceptorStack"></interceptor-ref>
   <result name="success">/WEB-INF/jsp/onlineList.jsp</result>
  </action>
在Online.java中:
 public String user() {
  long atime = this.getTime();
/**
 private long getTime() {
  return System.currentTimeMillis() - (this.getSysConfig().getUserOnlineTime() * 1000);
 }
*/
  this.setOnlineList(this.getUserOnlineService().findUserOnlines(atime, 0, 0, Constant.NORMAL_USER_GROUPS));//普通用户组"from UserOnline where onlineTime >= ?";
/**Criteria c = s.createCriteria(UserOnline.class);
        c.add(Restrictions.ge("onlineTime", new Long(atime)));
        if (boardID != 0) {
          c.add(Restrictions.eq("boardID", new Long(boardID)));
        }
        if (hiddenUser != -1) {
          c.add(Restrictions.eq("hiddenUser", new Integer(hiddenUser)));
        }
        if (groups != null && !groups.isEmpty()) {
          c.add(Restrictions.in("userGroupID", groups));
        }
        return c.list();
*/
  return SUCCESS;
 }
另外一个:action=friend
 public String friend() {
  long atime = this.getTime();
  this.setOnlineList(this.getUserOnlineService().findUserOnlinesInIds(atime,
    this.getFriendService().fileToFriendIDs(this.getUserSession().getId()), 0, 0,
    Constant.NORMAL_USER_GROUPS));
/**
public List fileToFriendIDs(String ownId) {
    List<String> l = new ArrayList<String>();
    File fromFile = new File(this.getUserConfig().getUserFilePath(ownId) + Constant.USER_FRIEND_FILE);//public static final String USER_FRIEND_FILE = "UserFriendFile.txt";
    try {
  String fids = FileUtils.readFileToString(fromFile, Constant.CHARSET);
  String[] ids = fids.split(",");
  if (ids != null) {
    for (int i = 0; i < ids.length; i++) {
      //System.out.println(ids[i]);
      l.add(ids[i]);
    }
  }
 } catch (IOException e) {
  logger.error(e);
 }
    return l;
  }
*/
  return SUCCESS;
 }
OK!对于用户信息的显示也有两类:我们先看URL:http://bbs.laoer.com/userInfo.bbscs?action=name&username=laoer以及http://bbs.laoer.com/userInfo.bbscs?action=id&id=4028818208ed006b0108ed020bd50001
进入userInfo:
  <action name="userInfo" class="userInfoAction">
   <interceptor-ref name="mainUserAuthInterceptorStack"></interceptor-ref>
   <result name="success">/WEB-INF/jsp/showUserInfo.jsp</result>
   <result name="bookMarkInUserInfo">/WEB-INF/jsp/bookMarkInUserInfo.jsp</result>
  </action>
我们直接看代码:
public String name() {
  this.ui = this.getUserService().findUserInfoByUserName(this.getUsername());
  if (this.ui == null) {
   this.addActionError(this.getText("error.user.noexist"));
   return ERROR;
  }

  this.setUserDetail(ui.getUserDetail());

  Pages pages = new Pages();
  pages.setPage(1);
  pages.setPerPageNum(10);
  pages.setTotalNum(10);

  PageList pl = this.getForumService().findForumsOwner(ui.getId(), 1, pages);//isNew=1代表主贴
  this.setOwnMainList(pl.getObjectList());

  pages = new Pages();
  pages.setPage(1);
  pages.setPerPageNum(10);
  pages.setTotalNum(10);

  pl = this.getForumService().findForumsOwner(ui.getId(), 0, pages);//0代表回复
  this.setOwnReList(pl.getObjectList());

  return SUCCESS;
 }
id方法类似,注意其中的一段:
ui.getUserDetail().setBrief(BBSCSUtil.filterText(ui.getUserDetail().getBrief(), false, false, true));
另外,有个bookMark方法,它是用于找到用户的共享bookMark!
 public String bookmark() {
  Pages pages = new Pages();
  pages.setPage(this.getPage());
  pages.setPerPageNum(10);
  pages.setFileName(BBSCSUtil.getActionMappingURLWithoutPrefix("userInfo?action=" + this.getAction() + "&id="
    + this.getId() + "&ajax=shtml"));
  this.setPageList(this.getBookMarkService().findBookMarksByUserIDShare(this.getId(), 1, pages));//PageList设置了哦~~
  return "bookMarkInUserInfo";
 }
我们先看bookMarkInUserInfo.jsp:
<s:iterator value="%{pageList.objectList}" id="bookMark">

      <tr>
        <td class="bgColor4">
          <span class="font1">
            <a href="<s:property value="#bookMark.url"/>" title="<s:property value="#bookMark.alt"/>" target="_blank"><s:property value="#bookMark.bookMarkName"/></a>
          </span>
        </td>
      </tr>
      </s:iterator>
      <tr>
        <td class="bgColor3"><s:text name="bbscs.pagebreak"/>:<bbscs:pages value="%{pageList.pages}" javaScript="loadBookMarkListPageUrl"/></td>
      </tr>
-->
function loadBookMarkListPageUrl(url) {   //注意这个函数是在showUserInfo.jsp中的
  $('bookMarkListDiv').innerHTML = pageLoadingCenter;
  var urls = getActionName(url);
  var pars = getActionPars(url);
  var myAjax = new Ajax.Updater("bookMarkListDiv", urls, {method: 'get', parameters: pars});
}
好,我们进而分析showUserInfo.jsp它的body onload="loadBookMarkListPage();"
var userid = "<s:property value="%{ui.id}"/>";
function loadBookMarkListPage() {
  $('bookMarkListDiv').innerHTML = pageLoadingCenter;
  var urls = getActionMappingURL("/userInfo");
  var pars = "action=bookmark&ajax=shtml&id="+userid;
  var myAjax = new Ajax.Updater("bookMarkListDiv", urls, {method: 'get', parameters: pars});
}
我们注意到,在用户信息页面上还有一个给该用户留言功能,这个功能点击后将显示一个div,它与前面的note类似!因此这个文件<script type="text/javascript" src="js/note.js"></script>还有<div id="noteSendDiv"></div>用户提供界面的显示哦~
OK!我们将分析下关键的forum-index-15.index(或froum?action=index&bid=15),我们还是从struts.xml文件中看起吧。在struts.xml中,专门定义了用于整个forum package!
<package name="forum" extends="bbscs-default" namespace="/">
  <default-interceptor-ref name="boardInterceptorStack"></default-interceptor-ref>
  <action name="forum" class="forumAction">
   <interceptor-ref name="boardInterceptorStack"></interceptor-ref>
   <interceptor-ref name="requestBasePathInterceptor"></interceptor-ref>
   <result name="forumBoard">/WEB-INF/jsp/forumBoard.jsp</result>
   <result name="forum">/WEB-INF/jsp/forum.jsp</result>
   <result name="forumHistory">/WEB-INF/jsp/forumHistory.jsp</result>
  </action>
....
我们先看boardInterceptorStack:
 <interceptor-stack name="boardInterceptorStack">
    <interceptor-ref name="defaultStack"></interceptor-ref>
    <interceptor-ref name="userLoginInterceptor"></interceptor-ref>
    <interceptor-ref name="userOnlineInterceptor"></interceptor-ref>
    <interceptor-ref name="boardInterceptor"></interceptor-ref>
   </interceptor-stack>
注意到它没有了userSessionInterceptor以及userPermissionInterceptor这两个权限检查的功能了!而增加了一个boardInterceptor,我们可以到com.laoer.bbscs.web.interceptor中找到这个拦截器,这个拦截器在进入版区之前就会进行调用,首先根据action提供的ajax,action,bid等值进入判断参数是否充分!如bid==0是不正确的,还有就是根据bid找到版区,没有的话,也报错!我们可在url分别输入http://bbs.laoer.com/forum.bbscs?action=index&bid=100&ajax=shtml 没有格式输出
http://bbs.laoer.com/forum.bbscs?action=index&bid=100&ajax=html 有html格式
http://bbs.laoer.com/forum.bbscs?action=index&bid=100 //用默认的String ajax = "html";
进行对比,就知道显示效果了!
   if (action instanceof BoardAware) {
    ((BoardAware) action).setBoard(board);

   }
接下来:
 if (us.getBid() != bid) {
    us.getBoardPermission().clear();
    us.getBoardSpecialPermission().clear();

    us.setBid(bid); //将bid写入us
    us.setBoardPass("");
    Map[] maps = boardService.getBoardPermission(bid, us.getGroupID()); // 取得版区用户组权限

    us.setBoardPermissionArray(maps);//写入权限!

    BoardMaster bm = (BoardMaster) board.getBoardMaster().get(us.getUserName());
    if (bm != null) {// 是斑竹
     Map[] bmpMap = boardService.getBoardMasterPermission(bm.getRoleID()); // 取得斑竹权限
     us.setBoardPermissionArray(bmpMap);
    }

    for (int i = 0; i < board.getParentIDs().size(); i++) {
     Board pboard = boardService.getBoardByID(((Long) (board.getParentIDs().get(i))).longValue());
     BoardMaster pbm = (BoardMaster) pboard.getBoardMaster().get(us.getUserName());//上级的权限!
     if (pbm != null && pbm.getOverChildPurview() == 1) {
      Map[] bmpMap = boardService.getBoardMasterPermission(pbm.getRoleID()); // 取得斑竹权限
      us.setBoardPermissionArray(bmpMap);
     }
    }
 ac.getSession().put(Constant.USER_SESSION_KEY, us);
 UserOnlineService userOnlineService = (UserOnlineService) wc.getBean("userOnlineService");

    UserOnline uo = userOnlineService.findUserOnlineByUserID(us.getId()); // 取得用户在线信息
    if (uo != null) {
     uo.setAtPlace(board.getBoardName());
     uo.setBoardID(bid);
     try {
      userOnlineService.saveUserOnline(uo);
     } catch (BbscsException ex) {
      logger.error(ex);
     }
    }

   }
if (board.getNeedPasswd() == 1 && StringUtils.isNotBlank(board.getPasswd())) {// 版区需要密码访问
    if (!us.isHaveBoardSpecialPermission(Constant.SPERMISSION_INBOARD_NOT_NEEDPASSWD)) {
     if (StringUtils.isBlank(us.getBoardPass())) {
      return "boardPasswd";
     } else if (!(bid + ":" + board.getPasswd()).equals(us.getBoardPass())) {//访问密码不对的话!
      // 版区需要密码,需要跳转
      ((ActionSupport) action).addActionError(messageSource.getMessage("error.board.passwd", null, ac
        .getLocale()));
      return "boardPasswd";
     }
    }
   }
这里有个<result name="boardPasswd">/WEB-INF/jsp/boardPasswd.jsp</result>
<s:form action="boardPasswd">
<s:hidden name="action" value="pass"></s:hidden>
<s:hidden name="bid" value="%{bid}"></s:hidden>
....
注意到struts.xml中boardPasswd是个special package中的一个action:
 <package name="special" extends="bbscs-default" namespace="/">
     <action name="boardPasswd" class="boardPasswdAction">
   <interceptor-ref name="userSessionInterceptorStack"></interceptor-ref>
   <interceptor-ref name="requestBasePathInterceptor"></interceptor-ref>
   <result name="success" type="redirect">${forwardUrl}</result>
  </action>
 </package>
我们看boardPasswd这个action中的pass方法:
public String pass() {
  if (StringUtils.isBlank(this.getPasswd())) {
   this.addActionError(this.getText("error.nullerror"));
   return "boardPasswd";
  }
  this.getUserSession().setBoardPass(this.getBid() + ":" + this.getPasswd());
  this.setForwardUrl(this.getBasePath()
    + BBSCSUtil.getActionMappingURLWithoutPrefix("forum?action=index&bid=" + this.getBid()));
  return SUCCESS;
 }
回过头来!
if (board.getIsAuth() == 1) {// 版区需要授权访问
    BoardMaster bm = (BoardMaster) board.getBoardMaster().get(us.getUserName());
    if (bm == null && !us.isHaveBoardSpecialPermission(Constant.SPERMISSION_INBOARD_NOT_NEEDAUTH)) { // 不是斑竹,并且没有不需要授权就可以进入的权限
     // 版区需要授权
     BoardAuthUserService boardAuthUserService = (BoardAuthUserService) wc
       .getBean("boardAuthUserService");
     if (boardAuthUserService.findBoardAuthUserByBidUid(bid, us.getId()) == null) {
      // 不是授权用户,需要跳转
      String errorMsg = messageSource.getMessage("error.user.not.auth", null, ac.getLocale());
      if (ajax.equalsIgnoreCase("html")) {
       ac.getValueStack().set("interceptError", errorMsg);
       return "intercepthtml";
      } else if (ajax.equalsIgnoreCase("shtml")) {
       ac.getValueStack().set("interceptError", errorMsg);
       return "interceptshtml";
      } else {
       AjaxMessagesJson ajaxMessagesJson = (AjaxMessagesJson) wc.getBean("ajaxMessagesJson");
       ajaxMessagesJson.setMessage("E_BOARDID_AUTH", errorMsg);
       ac.getValueStack().set("ajaxMessagesJson", ajaxMessagesJson);
       return "ajaxjson";
      }
     }
    }
   }

   boolean havePermission = false;
   Permission permission = (Permission) us.getBoardPermission().get(actionName + "?action=*");//具体地权限对比一下!
   if (permission != null) {
    havePermission = true;
   } else {
    permission = (Permission) us.getBoardPermission().get(actionName + "?action=" + saction);
    if (permission != null) {
     havePermission = true;
    } else {
     havePermission = false;
    }
   }
   if (havePermission) {
    if (action instanceof BoardAware) {
     ((BoardAware) action).setUserCookie(userCookie);
     ((BoardAware) action).setUserSession(us);
    }
    return invocation.invoke();
最后我们来看看BoardAware:
public interface BoardAware {
 public void setBoard(Board board);
 public void setUserCookie(UserCookie userCookie);
 public void setUserSession(UserSession userSession);//其实也和session cookie有关系了!
}
好,我们看forum.bbscs,注意它是从BaseBoardAction继承过来的,也实现了RequestBasePathAware方法,我们先看BaseBoardAction:public class BaseBoardAction extends BaseAction implements BoardAware,也要注意到它有以下属性和execute方法(这个和BaseMainAction一样):
 private Board board;
 private UserCookie userCookie;
 private UserSession userSession;
 private long bid = 0;
 private String tagId = "0";
 public String execute() {
  try {
   return this.executeMethod(this.getAction());
  } catch (Exception e) {
   logger.error(e);
   return ERROR;
  }
 }
进入com.laoer.bbscs.web.action包中的ForumAction.java中的excute:
 public String execute() { //重写方法
  if (this.getAction().equalsIgnoreCase("index")) {
   return this.index();
  } else if (this.getAction().equalsIgnoreCase("hot")) {
   return this.hot();
  } else if (this.getAction().equalsIgnoreCase("commend")) {
   return this.commend();
  } else if (this.getAction().equalsIgnoreCase("history")) {
   return this.history();
  } else {
   return this.index();
  }
 }
接下来,我们看看index()方法:
 public String index() {
  if (StringUtils.isNotBlank(this.getViewMode())) {//veiwMode一开始是无
   if (this.getUserCookie().getForumViewMode() != this.getViewModeToInt(this.getViewMode())) {
    this.getUserCookie().addForumViewMode(this.getViewModeToInt(this.getViewMode()));
   } else {
    setViewModeValue(this.getUserCookie().getForumViewMode());
   }
  } else {
   setViewModeValue(this.getUserCookie().getForumViewMode());//0
  }

  int isHidden = 0;
  if (this.getUserSession().isHaveSpecialPermission(Constant.SPERMISSION_CAN_SEE_HIDDEN_BOARD)) { // 如果用户有查看隐藏版区的权限
   isHidden = -1;
  }

  this.setUrlRewriteValue();//设置urlRewrite
  if (this.getBoard().getBoardType() == 1) {
   this.setBoardList(this.getBoardService().findBoardsByParentID(this.getBid(), 1, isHidden,
     Constant.FIND_BOARDS_BY_ORDER));
   return "forumBoard";//显示版区而已
  } else {
   this.setBids(String.valueOf(this.getBid()));//设置bid
   this.setBoardSelectValues(isHidden);//构造一些optionString对象,由new OptionsString(String.valueOf(b.getId()), BBSCSUtil.getBoardPrefixLine(b.getLevel(), "-")+ b.getBoardName())组成
/**
 public static String getBoardPrefixLine(int level, String txt) {
  StringBuffer sb = new StringBuffer();
  for (int i = 0; i < level * 2; i++) {
   sb.append(txt);
  }
  return sb.toString();
 }
*/
   Pages pages = new Pages();
   pages.setPage(this.getPage());
   pages.setPerPageNum(this.getUserForumPerNum(this.getUserCookie().getForumPerNum(), this.getSysConfig()
     .getForumPrePage()));//后者是默认值
   if (this.getTotal() > 0) {//在BaseAction中没有设置其值
    pages.setTotalNum(this.getTotal());
   }
   // pages.setPerPageNum(1);
   if (Constant.USE_URL_REWRITE) {
    pages.setUseUrlRewrite(Constant.USE_URL_REWRITE);
    pages.setFileName("forum-" + this.getAction() + "-" + this.getBid() + "-0-{page}-{total}.html");

   } else {
    pages.setFileName(this.getBasePath()
      + BBSCSUtil.getActionMappingURLWithoutPrefix("forum?action=" + this.getAction() + "&bid="
        + this.getBid()));
   }
   if (this.getTagId().equals("0")) {//没有tagid
    if (this.getUserCookie().getForumViewMode() == 1) {
     this.setPageList(this.getForumService().findForumsAll(this.getBid(), pages));
    } else if (this.getUserCookie().getForumViewMode() == 2) {
     this.setPageList(this.getForumService().findForumsMainLastRe(this.getBid(), pages));
    } else {
     this.setPageList(this.getForumService().findForumsMainWWW(this.getBid(), pages));
    }
   } else {//有的话
    if (this.getUserCookie().getForumViewMode() == 1) {
     this.setPageList(this.getForumService().findForumsAll(this.getBid(), this.getTagId(), pages));
    } else if (this.getUserCookie().getForumViewMode() == 2) {
     this
       .setPageList(this.getForumService().findForumsMainLastRe(this.getBid(), this.getTagId(),
         pages));
    } else {
     this.setPageList(this.getForumService().findForumsMainWWW(this.getBid(), this.getTagId(), pages));
    }
   }
   this.setParentBoards();//设置ParentBoard!
/**
 private void setParentBoards() {
  this.setParentBoards(this.getBoardService().findBoardsInIDs(this.getBoard().getParentIDs()));
 }
*/
   this.setForumSiteInit();
   return "forum";
  }
 }
OK!我们先看return的boardforum,再看forum!在boardforum中主要是遍历显示而已,不过对于
                <s:url action="forum?action=index" id="forumUrl">
                <s:param name="bid" value="#b.id"/>
                </s:url>
                <a href="${forumUrl}"><s:property value="#b.boardName"/></a>
好,我们看forum.jsp,我们分几部分来分析之:
第一个table用于显示导航条及tag:社区首页 ? 天乙社区 ? 社区技术
关键点是父board的遍历,其代码:<s:iterator id="pboard" value="%{parentBoards}">
      &raquo;
   <s:if test="%{urlRewrite}">
   <a href="forum-index-<s:property value="#pboard.id"/>.html"><s:property value="#pboard.boardName"/></a>
   </s:if>
   <s:else>
   <s:url action="forum?action=index" id="forumUrl">
   <s:param name="bid" value="#pboard.id"/>
   </s:url>
   <a href="${forumUrl}"><s:property value="#pboard.boardName"/></a>
   </s:else>
   </s:iterator>
而 <td>
        <s:text name="forum.all.num">
        <s:param value="%{pageList.pages.totalNum}"></s:param>//版区内帖子数!
        </s:text>
        </td>
marquee区域:
<td><marquee direction="left" scrollamount="3" onMouseOver="this.stop();" onMouseOut="this.start();"><s:text name="board.bulletin"/>:<s:property value="%{board.bulletin}" escape="false"/></marquee></td>
接下来是一个空的table用于一空行吧,下一个table用于显示一些常见的操作!注意发贴和发投票帖需action=index!next table是用于显示讨论版/精华帖/热门帖/推荐帖/历史帖 以及浏览方式(viewmode)和分页信息的. 注意
 <tr class="bgColor1">
     <td height="3" colspan="2"></td>
   </tr>
用于空行的格式化!接下来的这个table是显示帖子列表的!第一行为标题,用了<thead>,我们重点看下面的代码:
<tbody>
   <s:iterator id="f" value="%{pageList.objectList}">
      <tr>
        <td class="bgColor4">
          <div align="left">
            <bbscs:forum type="face" forumValue="#f"/>//图标
   <s:if test="#f.canNotDel==1">
   M
   </s:if>
          </div>
        </td>
        <td class="bgColor2">
          <div id="t<s:property value="#f.id"/>">
            <span class="font1"><bbscs:forum forumValue="#f" type="title" currentPageValue="%{page}"/></span>//标题(含摘要显示图标吧)
            <bbscs:forum forumValue="#f" type="titleitem" currentPageValue="%{page}"/>//有什么特色(如:精华、置顶)
          </div>
          <div id="u<s:property value="#f.id"/>">
            <span class="link1">
     <s:url action="userInfo?action=id" id="userInfoUrl">
     <s:param name="id" value="#f.userID"/>
     </s:url>
     <a href="${userInfoUrl}"><s:property value="#f.nickName"/>[<em><s:property value="#f.userName"/></em>]</a>//作者
            </span>
            <span class="font4">(<bbscs:forum forumValue="#f" type="posttime"/>)</span>//时间
          </div>
          <div class="summary1" id="s<s:property value="#f.id"/>" style="display:none">//摘要的显示div
          </div>
        </td>
        <td class="bgColor4"><div align="center"><bbscs:forum forumValue="#f" type="click"/></div></td>//点击数
        <td class="bgColor2"><div align="center"><bbscs:forum forumValue="#f" type="renum"/></div></td>//回复数
        <td class="bgColor4">
          <div align="right" class="font4">
            <span class="font4"><bbscs:forum forumValue="#f" type="lasttime"/></span>//最后回复时间
            <br/>
   <s:if test="#f.lastPostNickName.equals("---")">
   <s:property value="#f.lastPostNickName"/>
   </s:if>
   <s:else>
   <s:url action="userInfo?action=name" id="userInfoUrl">
   <s:param name="username" value="#f.lastPostUserName"/>
   </s:url>
   <span class="link1"><a href="${userInfoUrl}"><s:property value="#f.lastPostNickName"/></a></span>
   </s:else>
          </div>
        </td>
      </tr>
      </s:iterator>
      </tbody>
这里充分利用了forum这个tag,我们看看bbscs.tld是怎么定义它的!
 <tag>
  <name>forum</name>
  <tag-class>com.laoer.bbscs.web.taglib.ForumTag</tag-class>
  <body-content>empty</body-content>
  <attribute>
   <name>forumValue</name>
   <required>true</required>
   <rtexprvalue>true</rtexprvalue>
  </attribute>
  <attribute>
   <name>boardValue</name>
   <required>false</required>
   <rtexprvalue>true</rtexprvalue>
  </attribute>
  <attribute>
   <name>itemClass</name>
   <required>false</required>
   <rtexprvalue>true</rtexprvalue>
  </attribute>
  <attribute>
   <name>type</name>
   <required>true</required>
   <rtexprvalue>true</rtexprvalue>
  </attribute>
  <attribute>
   <name>currentPageValue</name>
   <required>false</required>
   <rtexprvalue>true</rtexprvalue>
  </attribute>
  <attribute>
   <name>currentActionValue</name>
   <required>false</required>
   <rtexprvalue>true</rtexprvalue>
  </attribute>
  <attribute>
   <name>inPagesValue</name>
   <required>false</required>
   <rtexprvalue>true</rtexprvalue>
  </attribute>
  <attribute>
   <name>totalnumValue</name>
   <required>false</required>
   <rtexprvalue>true</rtexprvalue>
  </attribute>
  <attribute>
   <name>tagIdValue</name>
   <required>false</required>
   <rtexprvalue>true</rtexprvalue>
  </attribute>
  <attribute>
   <name>indexValue</name>
   <required>false</required>
   <rtexprvalue>true</rtexprvalue>
  </attribute>
 </tag>
我们先在ForumTag.java中看到其属性,这些初始值对后面有十分重要的作用:
 private String forumValue = "";
 private String boardValue = "%{board}";//没有就用它
 private String itemClass = "font2";
 private String inPagesValue = "%{inpages}";//没有就用它
 private String currentPageValue = "%{page}";//没有就用它
 private String currentActionValue = "%{action}";//没有就用它
 private String tagIdValue = "%{tagId}";//没有就用它
 private String type = "";
 private String totalnumValue = "%{totalnum}";//没有就用它
 protected String indexValue = "%{rowstatus}";//没有就用它
我们按forum.jsp中的顺序来一一解决之,首先是<bbscs:forum type="face" forumValue="#f"/>:
-->  if (type.equalsIgnoreCase("face")) {
   if (f.getFace() == 0) {
    sb.append("<img src="");
    sb.append(sysConfig.getPostDefFaceImg());
    sb.append("" align="absmiddle"/>");
   } else {
    sb.append("<img src="images/");
    sb.append(f.getFace());
    sb.append(".gif" align="absmiddle"/>");
   }
   this.write(writer, sb.toString());
   return result;
  }
<bbscs:forum forumValue="#f" type="title" currentPageValue="%{page}"/>这个主要是显示出标题部分的一些信息:
  if (type.equalsIgnoreCase("title")) {
   Object boardObj = this.getStack().findValue(this.boardValue);
   int boardType = 3;
   Board board = (Board) boardObj;
   boardType = board.getBoardType();
   Object forumCurrentPageObj = this.getStack().findValue(this.currentPageValue);//从stack中取page值!
   int fcpage = 1;
   if (forumCurrentPageObj != null) {
    fcpage = ((Integer) forumCurrentPageObj).intValue();
   }

   String fcaction = "index";
   Object fcactionObj = this.getStack().findValue(this.currentActionValue);//action值
   if (fcactionObj != null) {
    fcaction = (String) fcactionObj;
   }

   String tagId = "0";
   Object tagIdObj = this.getStack().findValue(this.tagIdValue);//tag值
   if (tagIdObj != null) {
    tagId = (String) tagIdObj;
   }

   if (boardType == 2) {
    if (board.getId().longValue() != f.getBoardID()) {
     sb.append("[");
     sb.append(f.getBoardName());
     sb.append("] ");
    }
   }
   if (!f.getTagID().equals("0") && tagId.equals("0")) {
    sb.append("[");
    sb.append(f.getTagName());
    sb.append("] ");
   }
   if (f.getPostType() != 0) {
    if (f.getPostType() == 1) {
     sb.append(messageSource.getMessage("post.firstpus", null, this.request.getLocale()));//加上标志!
     sb.append(" ");
     // sb.append("[原创] ");
    }
    if (f.getPostType() == 2) {
     sb.append(messageSource.getMessage("post.fw", null, this.request.getLocale()));
     sb.append(" ");
     // sb.append("[转载] ");
    }
   }
   if (f.getIsVote() == 1) {
    // sb.append("[投票] ");
    sb.append(messageSource.getMessage("post.vote.title", null, this.request.getLocale()));
    sb.append(" ");
   }
   if (f.getTitleColor() != 0) {//标题color!
    sb.append("<a href="");
    if (Constant.USE_URL_REWRITE) {
     sb.append("read-topic-" + f.getBoardID() + "-" + f.getMainID() + "-" + tagId + "-" + fcpage + "-"
       + fcaction + "-1" + ".html");
    } else {
     sb.append(BBSCSUtil.getActionMappingURL("/read?action=topic&id=" + f.getMainID() + "&bid="
       + f.getBoardID() + "&fcpage=" + fcpage + "&fcaction=" + fcaction + "&tagId=" + tagId,
       request));
    }
    sb.append("">");
    sb.append("<font color="");
    sb.append(Constant.TITLECOLOR[f.getTitleColor()]);
    sb.append(""><strong>");
    sb.append(TextUtils.htmlEncode(f.getTitle()));
    sb.append("</strong></font>");
    sb.append("</a>");
   } else {
    sb.append("<a href="");
    if (Constant.USE_URL_REWRITE) {
     sb.append("read-topic-" + f.getBoardID() + "-" + f.getMainID() + "-" + tagId + "-" + fcpage + "-"
       + fcaction + "-1" + ".html");
    } else {
     sb.append(BBSCSUtil.getActionMappingURL("/read?action=topic&id=" + f.getMainID() + "&bid="
       + f.getBoardID() + "&fcpage=" + fcpage + "&fcaction=" + fcaction + "&tagId=" + tagId,
       request));
    }

    sb.append("">");
    sb.append(TextUtils.htmlEncode(f.getTitle()));
    sb.append("</a>");
   }
   this.write(writer, sb.toString());
   return result;
  }
接下来是<bbscs:forum forumValue="#f" type="titleitem" currentPageValue="%{page}"/>:显示的是
[ 1 2] (摘要图标) [(类型图标) 附件] [置顶] [精华] [推荐] 分别根据sysConfig.getUseLinkToPages() == 1/f.getHaveAttachFile() != 0/f.getIsTop() != 0/f.getIsLock() != 0/f.getElite() != 0/f.getCommend() != 0来确定之!下面一个是<bbscs:forum forumValue="#f" type="posttime"/>:
  if (type.equalsIgnoreCase("posttime")) {
   UserCookie uc = new UserCookie(request, response, sysConfig);
   if (sysConfig.getDateShowType() == 0) {
    sb.append(BBSCSUtil.formatDateTime(new Date(f.getPostTime()), sysConfig.getForumDateTimeFormat(), uc
      .getTimeZone()));
   } else {
    if (BBSCSUtil.isTodayTime(f.getPostTime(), uc.getTimeZone())) {
     sb.append(messageSource.getMessage("bbscs.today", null, request.getLocale()));
     sb.append(" ");
     sb.append(BBSCSUtil.formatDateTime(new Date(f.getPostTime()), sysConfig.getTimeFormat(), uc
       .getTimeZone()));
    } else if (BBSCSUtil.isYesterdayTime(f.getPostTime(), uc.getTimeZone())) {
     sb.append(messageSource.getMessage("bbscs.yesterday", null, request.getLocale()));
     sb.append(" ");
     sb.append(BBSCSUtil.formatDateTime(new Date(f.getPostTime()), sysConfig.getTimeFormat(), uc
       .getTimeZone()));
    } else {
     sb.append(BBSCSUtil.formatDateTime(new Date(f.getPostTime()), sysConfig.getForumDateTimeFormat(),
       uc.getTimeZone()));
    }
   }
   this.write(writer, sb.toString());
   return result;
  }
我们看样式先:(昨天 13:23)另外一种是(08-03 19:01) 注意这样用了BBSCSUtil类的一些方法:
 public static boolean isTodayTime(Date atime, String timeZone) {
  Calendar cld = Calendar.getInstance(TimeZone.getTimeZone(timeZone));
  cld.set(Calendar.MILLISECOND, 0);
  int nowyear = cld.get(Calendar.YEAR);
  int nowmonth = cld.get(Calendar.MONTH);
  int nowday = cld.get(Calendar.DAY_OF_MONTH);

  Calendar c = Calendar.getInstance(TimeZone.getTimeZone(timeZone));
  c.setTime(atime);
  c.set(Calendar.MILLISECOND, 0);
  int year = c.get(Calendar.YEAR);
  int month = c.get(Calendar.MONTH);
  int day = c.get(Calendar.DAY_OF_MONTH);
  if (year == nowyear && month == nowmonth && day == nowday) {//与时区一致!
   return true;
  } else {
   return false;
  }
 }
 public static boolean isYesterdayTime(Date atime, String timeZone) {
  Calendar cld = Calendar.getInstance(TimeZone.getTimeZone(timeZone));
  cld.set(Calendar.MILLISECOND, 0);
  cld.add(Calendar.DAY_OF_MONTH, -1);//现在的时间减一即为昨天!
  int yyear = cld.get(Calendar.YEAR);
  int ymonth = cld.get(Calendar.MONTH);
  int yday = cld.get(Calendar.DAY_OF_MONTH);

  Calendar c = Calendar.getInstance(TimeZone.getTimeZone(timeZone));
  c.setTime(atime);
  int year = c.get(Calendar.YEAR);
  int month = c.get(Calendar.MONTH);
  int day = c.get(Calendar.DAY_OF_MONTH);
  // int second = c.get(Calendar.SECOND);

  if (year == yyear && month == ymonth && day == yday) {
   return true;
  } else {
   return false;
  }
 }
 public static String formatDateTime(Date date, String format, String timeZone) {
  SimpleDateFormat outFormat = new SimpleDateFormat(format);
  Calendar c = Calendar.getInstance();
  c.setTimeInMillis(date.getTime());
  c.set(Calendar.MILLISECOND, 0);
  c.setTimeZone(TimeZone.getTimeZone(timeZone));
  return outFormat.format(c.getTime());
 }
 public static String getFileTypeIcon(String fileExt) {      //图标的显示!
  String fileTypeIcon = (String) Constant.ICON_MAP.get(fileExt);
  if (fileTypeIcon == null) {
   fileTypeIcon = "default.icon.gif";
  }
  return fileTypeIcon;
 }
OK,接下来<bbscs:forum forumValue="#f" type="click"/>和<bbscs:forum forumValue="#f" type="renum"/>两个:
  if (type.equalsIgnoreCase("click")) {
   if (f.getClick() >= sysConfig.getForumHotViews()) {//热
    sb.append("<span class="");
    sb.append(this.getItemClass());//private String itemClass = "font2";
    sb.append("">");
    sb.append(f.getClick());
    sb.append("</span>");
   } else {
    sb.append(f.getClick());
   }
   this.write(writer, sb.toString());
   return result;
  }
  if (type.equalsIgnoreCase("renum")) {
   sb.append("[");
   if (f.getReNum() > 0) {
    if (f.getReNum() >= sysConfig.getForumHotRes()) {
     sb.append("<span class="");
     sb.append(this.getItemClass());
     sb.append("">");
     sb.append("+");
     sb.append(f.getReNum());
     sb.append("</span>");
    } else {
     sb.append("+");
     sb.append(f.getReNum());
    }
   } else {
    sb.append(f.getReNum());
   }
   sb.append("]");
   this.write(writer, sb.toString());
   return result;
  }
OK,next!<bbscs:forum forumValue="#f" type="lasttime"/>它与posttime差不多!
  if (type.equalsIgnoreCase("lasttime")) {
   UserCookie uc = new UserCookie(request, response, sysConfig);
   if (sysConfig.getDateShowType() == 0) {//不用DateShow!而使用系统config的!
    sb.append(BBSCSUtil.formatDateTime(new Date(f.getLastTime()), sysConfig.getForumDateTimeFormat(), uc
      .getTimeZone()));
   } else {
    if (BBSCSUtil.isTodayTime(f.getLastTime(), uc.getTimeZone())) {
     sb.append(messageSource.getMessage("bbscs.today", null, request.getLocale()));
     sb.append(" ");
     sb.append(BBSCSUtil.formatDateTime(new Date(f.getLastTime()), sysConfig.getTimeFormat(), uc
       .getTimeZone()));
    } else if (BBSCSUtil.isYesterdayTime(f.getLastTime(), uc.getTimeZone())) {
     sb.append(messageSource.getMessage("bbscs.yesterday", null, request.getLocale()));
     sb.append(" ");
     sb.append(BBSCSUtil.formatDateTime(new Date(f.getLastTime()), sysConfig.getTimeFormat(), uc
       .getTimeZone()));
    } else {
     sb.append(BBSCSUtil.formatDateTime(new Date(f.getLastTime()), sysConfig.getForumDateTimeFormat(),
       uc.getTimeZone()));
    }

   }
   this.write(writer, sb.toString());
   return result;
  }
我们来看这个页面的几个JS:其中一个是摘要的显示,我们在讲titleitem时知道他的js函数是viewSummary带f.getBoardID()和f.getId()两个参数,这里用了Ajax:
var ViewSummaryOjb = function(bid,id){
  this.bid = bid;
  this.id = id;
}
ViewSummaryOjb.prototype.execute = function(resText) {
  $('s' + this.id).innerHTML = resText;
}

function viewSummary(bid,id) {
  Element.show("s" + id);
  $('s' + id).innerHTML = pageLoading;

  var url = getActionMappingURL("/read");
  var pars = "action=summary&ajax=shtml&id=" + id + "&bid=" + bid;
  var myAjax = new Ajax.Updater('s' + id, url, {method: 'get', parameters: pars});
}
好,我们在看刷新功能javascript:load():
function load() {
  var name=navigator.appName
  var vers=navigator.appVersion
  if(name=='Netscape'){
    window.location.reload(true)
  }
  else{
    history.go(0)
  }
}
加进珍藏功能boardsave('<s:property value="%{bid}"/>'):
function boardsave(bid) {
  showExeMsg();
  var url = getActionMappingURL("/boardSave");
  var pars = "action=save&ajax=xml&bid="+bid;

  var myAjax = new Ajax.Request(url, {method: 'get', parameters: pars, onComplete: boardsaveComplete});
}
function boardsaveComplete(res) {
  resText = res.responseText;
  var jsonMsgObj = new JsonMsgObj(resText);
  var codeid = jsonMsgObj.getCodeid();
  hiddenExeMsg();
  alert(jsonMsgObj.getMessage());
}
还有底部的选择版区所触发的onchange事件:
function changeBoard() {
  var boardSelectObj = document.getElementById("boardSelect");
  var boardId = boardSelectObj.options[boardSelectObj.selectedIndex].value;
  var url = "";
  if (useUrlRewrite == "true") {
    url = "forum-index-" + boardId + ".html";
  }
  else {
    url = getActionMappingURL("/forum?action=index&bid=" + boardId);
  }
  window.location.href = url;//GO!
}
我们再次看分页处的链接和viewMode处的链接再一次到index去理解一次,我们就可以获得最好的认识了!我们点击热门帖http://bbs.laoer.com/forum.bbscs?action=hot&bid=6还有推荐http://bbs.laoer.com/forum.bbscs?action=commend&bid=6以及历史帖http://bbs.laoer.com/forum.bbscs?action=history&bid=6都将由forumAction中的其它三个方法进行执行!注意他们都没有viewMode了,其中前两者用到的是forum.jsp,我们看历史帖的显示:
public String history() {
  int isHidden = 0;
  if (this.getUserSession().isHaveSpecialPermission(Constant.SPERMISSION_CAN_SEE_HIDDEN_BOARD)) { // 如果用户有查看隐藏版区的权限
   isHidden = -1;
  }
  this.setBids(String.valueOf(this.getBid()));
  this.setBoardSelectValues(isHidden);
  this.setUrlRewriteValue();
  if (this.getBoard().getBoardType() != 3) {
   return ERROR;
  }
  Pages pages = new Pages();
  pages.setPage(this.getPage());//可能有page
  pages.setPerPageNum(this.getUserForumPerNum(this.getUserCookie().getForumPerNum(), this.getSysConfig()
    .getForumPrePage()));
  if (this.getTotal() > 0) {
   pages.setTotalNum(this.getTotal());
  }
  if (Constant.USE_URL_REWRITE) {
   pages.setUseUrlRewrite(Constant.USE_URL_REWRITE);
   pages.setFileName("forum-" + this.getAction() + "-" + this.getBid() + "-0-{page}-{total}.html");//page是当前页,total是帖子数!

  } else {
   pages.setFileName(this.getBasePath()
     + BBSCSUtil.getActionMappingURLWithoutPrefix("forum?action=" + this.getAction() + "&bid="
       + this.getBid()));
  }
  this.setPageList(this.getForumHistoryService().findForumsMainWWW(this.getBid(), pages));//WWW方式浏览
  this.setParentBoards();
  this.setArchivesMonth(BBSCSUtil.getArchivesMonths(this.getBid()));
/**有一个文件专门存储了bid的存档月份用的!
 public static List<String> getArchivesMonths(long bid) {
  List<String> l = new ArrayList<String>();
  File archivesMonthsFile = new File(Constant.ROOTPATH + "archives/archivesmonths-" + bid + ".txt");
  String month = "";
  try {
   month = FileUtils.readFileToString(archivesMonthsFile, Constant.CHARSET);
  } catch (IOException e) {
   return l;
  }
  String[] months = month.split(",");
  if (months.length > 0) {
   for (int i = (months.length - 1); i >= 0; i--) {
    if (StringUtils.isNotBlank(months[i])) {
     l.add(months[i]);
    }
   }
  }
  return l;
 }
*/
  return "forumHistory";
 }
我们在forumHistory.jsp中发现其与forum.jsp十分类似,不同之处:
          <div id="t<s:property value="#f.id"/>">
            <span class="font1"><bbscs:forum forumValue="#f" type="titlehistory" currentPageValue="%{page}"/></span>
            <bbscs:forum forumValue="#f" type="titleitemhistory" currentPageValue="%{page}"/>
          </div>
我们到forumComponent.java中找到其相关的tag,可发现其与title 十分相似,只是其URL不同而已!
sb.append(BBSCSUtil.getActionMappingURL("/read?action=history&id=" + f.getMainID() + "&bid="
       + f.getBoardID() + "&fcpage=" + fcpage + "&fcaction=" + fcaction + "&tagId=" + tagId,
       request));
同样,我们也知道最后需要sb.append(TextUtils.htmlEncode(f.getTitle()));对字符串的格式化!在forumHistory.jsp中有个存档项,上前列出了存档的时间,点击将触发相应的历史记录列表显示:http://bbs.laoer.com/archives/15/2007-01/1.html,其代码如下:
      <s:iterator id="month" value="%{archivesMonth}">
      [<a href="archives/<s:property value="%{bid}"/>/<s:property value="#month"/>/1.html" target="_blank"><s:property value="#month"/></a>]
      </s:iterator>
好的,我们点击后链接到静态页面上,在列表中选择又将打开其静态化的帖子内容!archives是skin目录下的.好的,我们看精华帖http://bbs.laoer.com/refine.bbscs?action=index&pid=0&bid=6,我们看refine在struts.xml中的定义:
  <action name="refine" class="refineAction">
   <interceptor-ref name="boardInterceptorStack"></interceptor-ref>
   <result name="refine">/WEB-INF/jsp/refine.jsp</result>
   <result name="refineManage">/WEB-INF/jsp/refineManage.jsp</result>
   <result name="success" type="redirect">${forwardUrl}</result>
  </action>
先从index方法看起: 
public String index() {
  Elite elite = this.getEliteService().findEliteByID(this.getPid());//pid找起
  if (elite != null) {
   List pes = elite.getParentIDs();
   for (int i = 0; i < pes.size(); i++) {
    Elite pe = this.getEliteService().findEliteByID(((Long) pes.get(i)).longValue());//上上pid!
    eliteDirs.add(pe);//上上的精华帖
   }
   eliteDirs.add(elite);//最后才加入自己
  }
  return "refine";
 }
注意在refine.jsp中eliteDirs是用于显示精华目录并有链接用的!(如:社区技术是当前的上当,而eliteDirs是它的父及组的目录),下面的代码是关键的显示:
<table width="100%" border="0" cellpadding="0" cellspacing="0">
  <tr>
    <td class="bgColor3"><table width="100%" border="0" cellpadding="5" cellspacing="1">
      <tr>
        <td width="5%"><div align="center"><s:text name="refine.type"/></div></td>
        <td width="55%"><div align="center"><s:text name="forum.art.title"/></div></td>
        <td width="12%"><div align="center"><s:text name="forum.author"/></div></td>
        <td width="12%"><div align="center"><s:text name="read.e.doename"/></div></td>
        <td width="16%"><div align="center"><s:text name="read.e.doetime"/></div></td>
      </tr>
      <tbody id="eliteList"></tbody>
      <tbody id="forumList"></tbody>
    </table></td>
  </tr>
</table>
注意到script:
<script language="JavaScript" type="text/javascript">
<!--
var bid = "<s:property value="%{bid}"/>";
var pid = "<s:property value="%{pid}"/>";
//-->
</script>
<script type="text/javascript" src="js/jsMsg.jsp"></script>
<script type="text/javascript" src="js/prototype.js"></script>
<script type="text/javascript" src="js/comm.js"></script>
<script type="text/javascript" src="js/refine.js"></script>
</head>
<body onload="loadPage();">//启动!
function loadPage() {
  loadElitePage();
  //loadFlistPage(); ForumList!不用了
}

function loadElitePage() {
  var url = getActionMappingURL("/refine");
  var pars = "action=showelite&ajax=xml&bid="+bid+"&pid="+pid;

  var myAjax = new Ajax.Request(url, {method: 'get', parameters: pars, onComplete: showEliteListJson});
}

function showEliteListJson(res) {
 var nowelites = $('eliteList').childNodes;
 for (var i = 0; i < nowelites.length; i++) {
  $('eliteList').removeChild(nowelites[i]);
 }
 var resText = res.responseText;
 var jsonObj = eval('(' + resText + ')');
 //alert(jsonObj);
 //alert(jsonObj.forum);
 //jsonObj.forum.each(function(f){
 // alert(f.title);
 //})
 var dirs = jsonObj.dir;            //dir部分!
 for (var i = 0; i < dirs.length; i++) {
  var row = document.createElement("tr");
  row.setAttribute("id", "tre_"+dirs[i].id);

  var cell = document.createElement("td");
  var msgDiv = document.createElement("div");
  msgDiv.innerHTML = "<img src="images/dir.gif" />";
  cell.appendChild(msgDiv);
  cell.className = "bgColor4";
  row.appendChild(cell);

  cell = document.createElement("td");
  msgDiv = document.createElement("div");
  var eliteId = dirs[i].id;
  var eliteName = dirs[i].eliteName;
  msgDiv.innerHTML = "<a href=""+getActionMappingURL("/refine?action=index&bid="+bid+"&pid="+eliteId)+"">" + eliteName + "</a>";
  msgDiv.className = "font1";
  cell.appendChild(msgDiv);
  cell.className = "bgColor2";
  row.appendChild(cell);

  cell = document.createElement("td");
  msgDiv = document.createElement("div");
  msgDiv.setAttribute("align","center");
  msgDiv.innerHTML = "";
  cell.appendChild(msgDiv);
  cell.className = "bgColor4";
  row.appendChild(cell);

  cell = document.createElement("td");
  msgDiv = document.createElement("div");
  msgDiv.setAttribute("align","center");
  msgDiv.innerHTML = dirs[i].createUser;
  cell.appendChild(msgDiv);
  cell.className = "bgColor2";
  row.appendChild(cell);

  var d = new Date();
  d.setTime(dirs[i].eliteTime);

  cell = document.createElement("td");
  msgDiv = document.createElement("div");
  msgDiv.setAttribute("align","center");
  msgDiv.innerHTML = d.toLocaleString();
  cell.appendChild(msgDiv);
  cell.className = "bgColor4";
  row.appendChild(cell);

  $('eliteList').appendChild(row);
 }
 var forums = jsonObj.forum;               //内容部分!!
 //alert(forums);
 for (var i = 0; i < forums.length; i++) {
  var row = document.createElement("tr");
  row.setAttribute("id", "trf_"+forums[i].id);

  var cell = document.createElement("td");
  var msgDiv = document.createElement("div");
  msgDiv.innerHTML = "<img src="images/file.gif" />";
  cell.appendChild(msgDiv);
  cell.className = "bgColor4";
  row.appendChild(cell);

  cell = document.createElement("td");
  msgDiv = document.createElement("div");
  var mainId = forums[i].mainID;
  var title = forums[i].title;
  msgDiv.innerHTML = "<a href=""+getActionMappingURL("/read?action=elite&id=" + mainId + "&bid=" + bid + "&eliteId=" + pid + "&fcpage=1&fcaction=index")+"">" + title+"</a>";
  msgDiv.className = "font1";
  cell.appendChild(msgDiv);
  cell.className = "bgColor2";
  row.appendChild(cell);

  cell = document.createElement("td");
  msgDiv = document.createElement("div");
  msgDiv.setAttribute("align","center");
  msgDiv.innerHTML = forums[i].userName;
  cell.appendChild(msgDiv);
  cell.className = "bgColor4";
  row.appendChild(cell);

  cell = document.createElement("td");
  msgDiv = document.createElement("div");
  msgDiv.setAttribute("align","center");
  msgDiv.innerHTML = forums[i].doEliteName;
  cell.appendChild(msgDiv);
  cell.className = "bgColor2";
  row.appendChild(cell);

  var d = new Date();
  d.setTime(forums[i].doEliteTime);

  cell = document.createElement("td");
  msgDiv = document.createElement("div");
  msgDiv.setAttribute("align","center");
  msgDiv.innerHTML = d.toLocaleString();
  cell.appendChild(msgDiv);
  cell.className = "bgColor4";
  row.appendChild(cell);

  $('eliteList').appendChild(row);
 }

}
我们接下来看forumSearch.bbscs它支持按主题和作者进行简单查询! 
  <action name="forumSearch" class="forumSearchAction">
   <interceptor-ref name="boardInterceptorStack"></interceptor-ref>
   <result name="success">/WEB-INF/jsp/forumSearch.jsp</result>
  </action>
 public String index() {
  if (StringUtils.isBlank(this.getText())) {
   this.addActionError(this.getText("error.nullerror"));
   return ERROR;
  }
  Pages pages = new Pages();
  pages.setPage(this.getPage());
  pages.setPerPageNum(40);//分页数!
  String text = "";
  try {
   text = java.net.URLEncoder.encode(this.getText(), Constant.CHARSET);
  } catch (UnsupportedEncodingException ex) {
   text = "";
  }
  pages.setFileName(BBSCSUtil.getActionMappingURLWithoutPrefix("/forumSearch?bid=" + this.getBid() + "&con="
    + this.getCon() + "&text=" + text));//构造一个分页显示的URL
  if (this.getTotal() > 0) {
   pages.setTotalNum(this.getTotal());
  }
  this.setPageList(this.getForumService().getSearchList(this.getBid(), this.getCon(), this.getText(), pages));//con是条件!title是主题,userName是作者名
  return SUCCESS;
 }
我们看看forumSearch.jsp,其实也就是对pageList.objectList的遍历而已!这里值得注意的是它的返回按钮!接下来是[已订阅主题]subs.bbscs?action=index&bid=15:
  <action name="subs" class="subsAction">
   <interceptor-ref name="boardInterceptorStack"></interceptor-ref>
   <interceptor-ref name="requestBasePathInterceptor"></interceptor-ref>
   <result name="subsmy">/WEB-INF/jsp/subsmy.jsp</result>
   <result name="list">/WEB-INF/jsp/subsList.jsp</result>
  </action>
 public String index() {
  return "subsmy";
 }
在index方法中直接定位到subsmy.jsp中,而这个文件中只有一个<body onload="loadSubsList('<s:property value="%{bid}"/>');">和js/subs.js以及一个空的div用于显示列表:
function loadSubsList(bid) {
  $('subsList').innerHTML = pageLoading;

  var url = getActionMappingURL("/subs");
  var pars = "action=list&ajax=shtml&bid="+bid;

  var myAjax = new Ajax.Updater("subsList", url, {method: 'get', parameters: pars});
}--> public String list() {
  Pages pages = new Pages();
  pages.setPage(this.getPage());
  pages.setPerPageNum(20);//20个一页
  pages.setFileName(this.getBasePath()
    + BBSCSUtil.getActionMappingURLWithoutPrefix("/subs?ajax=shtml&action=" + this.getAction() + "&bid="
      + this.getBid()));
  this.setPageList(this.getSubscibeService().findSubscibesByUserID(this.getUserSession().getId(), this.getBid(),
    pages));
  return "list";
 }
我们看list所指的jsp:subsList.jsp,它其实也是遍历一次,不过这里有些特殊的功能:
<s:iterator id="subs" value="%{pageList.objectList}">
<tr>
  <td class="bgColor2">
    <s:url action="read?action=topic" id="postUrl">
    <s:param name="bid" value="#subs.boardID"/>
    <s:param name="id" value="#subs.postID"/>
    <s:param name="fcpage" value="1"/>
    <s:param name="fcaction" value="index"/>
    </s:url>
    <a href="${postUrl}"><span class="font1"><s:property value="#subs.postTitle"/></span></a>
  </td>
  <td width="15%" class="bgColor4">
    <div id="email<s:property value="#subs.id"/>" align="center">
      <s:if test="#subs.emailinform==0">
      --
      </s:if>
      <s:if test="#subs.emailinform==1">//email发送功能
      Email [<a href="javascript:;" onclick="cancleSubs('delemail','<s:property value="#subs.boardID"/>','<s:property value="#subs.id"/>','<s:property value="%{page}"/>');"><s:text name="bbscs.cancel"/></a>]
      </s:if>
    </div>
  </td>
  <td width="15%" class="bgColor4">
    <div id="msg<s:property value="#subs.id"/>" align="center">
      <s:if test="#subs.msginform==0">
      --
      </s:if>
      <s:if test="#subs.msginform==1">//msg 发送功能
      <s:text name="pot.sendnote"/> [<a href="javascript:;" onclick="cancleSubs('delmsg','<s:property value="#subs.boardID"/>','<s:property value="#subs.id"/>','<s:property value="%{page}"/>');"><s:text name="bbscs.cancel"/></a>]
      </s:if>
    </div>
  </td>
  <td class="bgColor2">
    <div align="center"><a href="javascript:;" onclick="cancleSubs('del','<s:property value="#subs.boardID"/>','<s:property value="#subs.id"/>','<s:property value="%{page}"/>');"><s:text name="bbscs.del"/></a></div>//取消订阅
  </td>
</tr>
</s:iterator>
注意到它们都是通过canclesubs进行操作的!注意其中的action参数!
var CancleSubsAjax = Class.create();

CancleSubsAjax.prototype = {
  initialize: function(action,bid,id,cpage) {
   this.action = action;
   this.bid = bid;
   this.id = id;
   this.cpage = cpage;
  },

  cancle: function() {

    var url = getActionMappingURL("/subs");
    var pars = "ajax=xml&action=" + this.action + "&id=" + this.id + "&bid=" + this.bid;
    var myAjax = new Ajax.Request(url, {method: 'get', parameters: pars, onComplete: this.cancleCompleted.bind(this)});
  },

  cancleCompleted: function(res) {
   resText = res.responseText;
   var jsonMsgObj = new JsonMsgObj(resText);
   var codeid = jsonMsgObj.getCodeid();
   alert(jsonMsgObj.getMessage());
   if (codeid == "0") {
    if (this.action == "delemail") {
     $('email' + this.id).innerHTML = "--";
    }
    if (this.action == "delmsg") {
     $('msg' + this.id).innerHTML = "--";
    }
    if (this.action == "del") {
     var url = getActionMappingURL("/subs?action=list&ajax=shtml&bid=" + this.bid + "&page=" + this.cpage);
     loadSubsListUrl(url);
    }
   }
   if (codeid == "1") {
    var url = getActionMappingURL("/subs?action=list&ajax=shtml&bid=" + this.bid + "&page=" + this.cpage);
    loadSubsListUrl(url);
    }
   }
};

function cancleSubs(action,bid,id,cpage) {
  var canc = confirm("你确认要取消订阅吗?");
  if (canc) {
    var oCancleSubsAjax = new CancleSubsAjax(action,bid,id,cpage);
    oCancleSubsAjax.cancle();
  }
  else {
    return false;
  }
}
而实际执行的java代码分别由如下所示:
 public String delemail() {
  return this.dela("delemail");
 }

 public String delmsg() {
  return this.dela("delmsg");
 }

 private String dela(String deltype) {
  Subscibe subs = this.getSubscibeService().findSubscibeByID(this.getId(), this.getUserSession().getId(),
    this.getBid());
  if (subs != null) {
   if (deltype.equalsIgnoreCase("delemail")) {
    subs.setEmailinform(0);
   }
   if (deltype.equalsIgnoreCase("delmsg")) {
    subs.setMsginform(0);
   }
   try {
    subs = this.getSubscibeService().saveSubscibe(subs);
    if (subs.getEmailinform() == 0 && subs.getMsginform() == 0) {
     this.getSubscibeService().removeSubscibe(subs);//两个都取消相当于remove!

     this.getAjaxMessagesJson().setMessage("1", this.getText("subs.cancle.ok"));
    } else {

     this.getAjaxMessagesJson().setMessage("0", this.getText("subs.cancle.ok"));
    }
   } catch (BbscsException ex) {
    this.getAjaxMessagesJson().setMessage("E_SUBS_CANCLE_ERROR", this.getText("error.subs.cancle.error"));
   }
  } else {
   this.getAjaxMessagesJson().setMessage("E_SUBS_CANCLE_ERROR", this.getText("error.subs.cancle.error"));
  }

  return RESULT_AJAXJSON;
 }

 public String del() {//直接删除
  try {
   this.getSubscibeService().removeSubscibe(this.getId(), this.getUserSession().getId(), this.getBid());
   this.getAjaxMessagesJson().setMessage("0", this.getText("subs.cancle.ok"));
  } catch (BbscsException ex1) {
   this.getAjaxMessagesJson().setMessage("E_SUBS_CANCLE_ERROR", this.getText("error.subs.cancle.error"));
  }
  return RESULT_AJAXJSON;
 }
我们点击发表新帖有类似URL:http://bbs.laoer.com/post.bbscs?action=add&bid=15&tagId=0!
  <action name="post" class="postAction">
   <interceptor-ref name="boardInterceptorStack"></interceptor-ref>
   <interceptor-ref name="remoteAddrInterceptor"></interceptor-ref>
   <interceptor-ref name="requestBasePathInterceptor"></interceptor-ref>
   <result name="input">/WEB-INF/jsp/post.jsp</result>
   <result name="success" type="redirect">${forwardUrl}</result>
   <result name="upfileInPost">/WEB-INF/jsp/upfileInPost.jsp</result>
   <result name="upfileInput">/WEB-INF/jsp/upfileInput.jsp</result>
   <result name="forumUpComponent">/WEB-INF/jsp/forumUpComponent.jsp</result>
   <result name="attachFiles">/WEB-INF/jsp/attachFiles.jsp</result>
  </action>
我们看add方法:
 public String add() {
  if (this.checkBeforePost().equals(ERROR)) {
/**
 private String checkBeforePost() {
  if (this.getSysConfig().getPostCheckTime() >0
    && !this.getUserSession().isHaveBoardSpecialPermission(Constant.SPERMISSION_NOT_POSTCHECKTIME)) {//config中的postchecktime>0还有用户没特殊权力!
   if ((System.currentTimeMillis() - this.getUserCookie().getLastPostTime()) <= (this.getSysConfig()
     .getPostCheckTime() * 1000)) {//发帖发的太快了!
    this.addActionError(this.getText("error.post.checktime", new String[] { String.valueOf(this
      .getSysConfig().getPostCheckTime()) }));
    return ERROR;
   }
  }
  if (this.getSysConfig().getUseForbid() == 1) {//用户不能发!
   if (this.getSysConfig().isForbidIP(this.getUserRemoteAddr())) {
    this.addActionError(this.getText("error.reg.ipforbid", new String[] { this.getUserRemoteAddr() }));
    return ERROR;
   }
  }
  if (this.getSysConfig().isUsePostPeriodOfTime()) {//不在适当的年份!
   long onwtime = System.currentTimeMillis();
   if (!this.getSysConfig().isInPostPeriodOfTime(onwtime)) {
/**
public boolean isInPostPeriodOfTime(long nowtime) {
  long starttime = 0;
  long endtime = 0;
  Calendar cld = Calendar.getInstance();
  int year = cld.get(Calendar.YEAR);
  int month = cld.get(Calendar.MONTH);
  int day = cld.get(Calendar.DAY_OF_MONTH);
  Calendar todaycld = Calendar.getInstance();
  todaycld.set(year, month, day, this.getPostPeriodOfTimeStart(), 0, 0);
  starttime = todaycld.getTime().getTime();
  Calendar cldend = Calendar.getInstance();
  cldend.set(year, month, day, this.getPostPeriodOfTimeEnd(), 0, 0);
  endtime = cldend.getTime().getTime();

  if (nowtime >= starttime && nowtime <= endtime) {
   return true;
  } else {
   return false;
  }
 }
*/
    this.addActionError(this.getText("error.post.isnotinperiodoftime", new String[] {
      String.valueOf(this.getSysConfig().getPostPeriodOfTimeStart()),
      String.valueOf(this.getSysConfig().getPostPeriodOfTimeEnd()) }));
    return ERROR;
   }
  }
  return SUCCESS;
 }
*/
   return ERROR;
  }
  this.setAction("addsave");
  if (this.getUserCookie().getEditType() == -1) {
   if (this.getSysConfig().getEditInterface() == 0) {
    this.setEditType(0);
   } else if (this.getSysConfig().getEditInterface() == 1) {
    this.setEditType(1);
   } else {
    this.setEditType(2);
   }
  } else {
   this.setEditType(this.getUserCookie().getEditType());
  }
  this.setTitleColor(0);//标题color
  this.setPostType(0);
  this.setSign(-1);
  this.setUserBlog(0);
  this.setPreviewAttach(true);
  this.setNeedsAttribute(false);
/**
 private void setNeedsAttribute(boolean isRe) {
  this.setPostHiddenTypeRe(this.getSysConfig().getPostHiddenTypeRe());//隐藏帖选项回复的默认壮态
  this.setPostHiddenTypeMoney(this.getSysConfig().getPostHiddenTypeMoney());//money
  this.setPostHiddenTypeArtNum(this.getSysConfig().getPostHiddenTypeArtNum());/资历帖

  int canUseTitleColor = 0;//不能用标题color
  if (!isRe) {
   if (this.getUserSession().isHaveBoardSpecialPermission(Constant.SPERMISSION_CAN_USE_TITLECOLOR)) {
    canUseTitleColor = 1;
   }
  }

  this.setTitleColorOptions(this.getSysOptionsValues().getTitleColorValues(canUseTitleColor, this.getLocale()));
  this.setPostPriceValues(this.getSysOptionsValues().getPostPriceValues(this.getSysConfig().getPostPriceLists()));
/**public String getTitleColorValues(int haveTitleColorp, Locale locale) {
  StringBuffer sb = new StringBuffer();
  sb.append("<option value="0">");
  sb.append(this.getMessageSource().getMessage("bbscs.default", null, locale));
  sb.append("</option>");
  if (haveTitleColorp == 1) {
   for (int i = 1; i < Constant.TITLECOLOR.length; i++) {
    sb.append("<option value="");
    sb.append(i);
    sb.append("" class="titleColor");
    sb.append(i);
    sb.append("">");
    sb.append(Constant.TITLECOLOR[i]);
    sb.append("</option>");
   }
  }
  return sb.toString();
 }

 public List<OptionsInt> getPostPriceValues(String[] prices) {
  List<OptionsInt> l = new ArrayList<OptionsInt>();
  for (int i = 0; i < prices.length; i++) {
   l.add(new OptionsInt(Integer.parseInt(prices[i]), prices[i]));
  }
  return l;
 }

*/
  this.tagValues.add(new OptionsString("0", this.getBoard().getBoardName()));
  Iterator it = this.getBoard().getBoardTag().iterator();
  BoardTag bt = null;
  while (it.hasNext()) {
   bt = (BoardTag) it.next();
   this.tagValues.add(new OptionsString(bt.getId(), bt.getTagName()));
  }
 }
*/
  return INPUT;
 }
好的,我们看jsp文件:post.jsp,对于这个文件,我们要注意的是有些值加入了判断是不是 <s:if test="%{action=='editdo'}">或者是<s:if test="%{action=='addsave'}">可见用户在修改自己的帖子时也用的是这个文件.当用户编辑的时候,用户签名之后的选项都被disabled="true"了或者去掉了(如隐藏帖选项),当我们提交后,<s:form action="post">在js之后进行触发:
<script type="text/javascript">
function postSubmit() {
  document.getElementById("postButton").disabled = true;
  document.post.submit();
}
</script>
另外,这个文件中的:
<s:actionerror theme="bbscs0"/> //错误提示信息
<table width="100%" border="0" cellpadding="5" cellspacing="0">
  <tr>
    <td class="bgColor3">
    <bbscs:post type="postat"/>
/**在bbscs.tld中<tag>
  <name>post</name>
  <tag-class>com.laoer.bbscs.web.taglib.PostTag</tag-class>
  <body-content>empty</body-content>
  <attribute>
   <name>type</name>
   <required>true</required>
   <rtexprvalue>true</rtexprvalue>
  </attribute>
  <attribute>
   <name>boardValue</name>
   <required>false</required>
   <rtexprvalue>true</rtexprvalue>
  </attribute>
  <attribute>
   <name>tagIdValue</name>
   <required>false</required>
   <rtexprvalue>true</rtexprvalue>
  </attribute>
 </tag>
-->postTag.java中:
 protected String type = "";
 protected String boardValue = "%{board}";
 protected String tagIdValue = "%{tagId}";
 实际上是if (type.equalsIgnoreCase("postat")) {
     this.write(writer, messageSource.getMessage("post.youatboards", new String[] { sb.toString() },
       request.getLocale()));
    }/您目前在 社区技术 区发表信息
*/
    </td>
  </tr>
</table>
OK!我们看addsave方法,注意它充当了验证的功能:
try {              //上传文件的部分!
    uploadFile = new UploadFile();
    uploadFile.setFileName(this.getUploadFileName().toLowerCase());
    uploadFile.setInputStream(new FileInputStream(this.getUpload()));
   } catch (FileNotFoundException e) {
    logger.error(e);
    uploadFile = null;
   }
  }
..
Forum f = new ForumMain();
f.setPreviewAttach(this.boolean2int(this.getPreviewAttach()));        //写入用户部分!
  try {
   UserInfo ui = this.getUserService().findUserInfoById(this.getUserSession().getId());
   if (ui == null) {
    this.addActionError(this.getText("error.post.guest"));
    this.setNeedsAttribute(false);
    return INPUT;
   }

   f = this.getForumService().createForum(f, this.getBoard(), ui, uploadFile);
   this.getUserCookie().addLastPostTime();
   if (this.getBoard().getAuditPost() == 0) {
    if (Constant.USE_URL_REWRITE) {
     this.setForwardUrl(this.getBasePath() + "forum-index-" + this.getBid() + "-" + this.getTagId()
       + "-1-0.html");
    } else {
     this.setForwardUrl(BBSCSUtil.getActionMappingURLWithoutPrefix("forum?action=index&bid="
       + this.getBid() + "&tagId=" + this.getTagId()));//forwardUrl用于发帖后成功的Href!
    }
   } else {
    this.setForwardUrl(BBSCSUtil.getActionMappingURLWithoutPrefix("postWaitAudit?bid=" + this.getBid()));
   }
   return SUCCESS;
  }
好的,我们先转看新投票功能http://bbs.laoer.com/votePost.bbscs?action=add&bid=15&tagId=0
  <action name="votePost" class="votePostAction">
   <interceptor-ref name="boardInterceptorStack"></interceptor-ref>
   <interceptor-ref name="remoteAddrInterceptor"></interceptor-ref>
   <interceptor-ref name="requestBasePathInterceptor"></interceptor-ref>
   <result name="input">/WEB-INF/jsp/votePost.jsp</result>
   <result name="success" type="redirect">${forwardUrl}</result>
  </action>
我们直接看VotePost:
 public String add() {
  this.setAction("addsave");
  this.setDeadLineYear(String.valueOf(Util.getYear()));
/**
 public static int getYear() {
  Calendar cld = Calendar.getInstance();
  cld.setTime(new java.util.Date());
  return cld.get(Calendar.YEAR);
 }
 public static int getMonth() {
  Calendar cld = Calendar.getInstance();
  cld.setTime(new java.util.Date());
  return cld.get(Calendar.MONTH) + 1;
 }

*/
  this.setDeadLineMon(Util.getMonth());
  this.setTagsAttribute();
  return INPUT;
 }
另外,关于tagValues:
        <td width="85%" class="bgColor2">
    <s:if test="%{action=='addsave'}">
    <s:select list="tagValues" id="tagId" name="tagId" cssClass="select1" listKey="key" listValue="value"></s:select>
    </s:if>
    <s:textfield id="title" name="title" cssClass="input2" size="60" maxlength="60"></s:textfield>
        </td>
当点击提交按钮运行postSubmit()这个函数:
function postSubmit() {
  document.getElementById("postButton").disabled = true;
  document.votePost.submit();
}
在addSave()方法中:
  f.setIsNew(1);
  f.setIsTop(0);
  f.setIsVote(1);
  f.setLastPostNickName("---");
  f.setLastPostTitle("");
  f.setLastPostUserName("---");
  f.setLastTime(nowtime);
  f.setMainID("");
  f.setNickName(this.getUserSession().getNickName());
  f.setParentID("");
  f.setPostTime(nowtime);
  f.setPostType(0);
  f.setQuoteText("");
  f.setReNum(0);
  f.setSign("");
  f.setTitle(stitle);
  f.setTitleColor(0);
  f.setUserID(this.getUserSession().getId());
  f.setUserName(this.getUserSession().getUserName());
  f.setEmailInform(0);
  f.setMsgInform(0);
...
  Vote vote = new Vote();
  vote.setDeadLine(Util.Date2Long(Integer.parseInt(this.getDeadLineYear()), this.getDeadLineMon(), this
    .getDeadLineDay()));
  vote.setIsM(this.getIsM());
  vote.setTitle(this.getTitle());
...
this.getForumService().createVoteForum(f, this.getBoard(), vote, ui, svoteItem);
this.getVoteService().saveVote(vote);
   this.setForwardUrl(this.getBasePath()
     + BBSCSUtil.getActionMappingURLWithoutPrefix("forum?action=index&bid=" + this.getBid()));//forwardUrl!!!
   return SUCCESS;
这里用到了tagValues:
 private void setTagsAttribute() {
  this.tagValues.add(new OptionsString("0", this.getBoard().getBoardName()));
  Iterator it = this.getBoard().getBoardTag().iterator();//这个Board的所有tag!
  BoardTag bt = null;
  while (it.hasNext()) {
   bt = (BoardTag) it.next();
   this.tagValues.add(new OptionsString(bt.getId(), bt.getTagName()));
  }
 }
OK!我们进入读取帖子这个关键的业务,我们选择一个喜欢的帖子进行分析(http://bbs.laoer.com/read-topic-15-ff80808113baa8140113d201333e5274-0-1-index-1.html),我们先在struts.xml中察看read这个action:
  <action name="read" class="readAction">
   <interceptor-ref name="boardInterceptorStack"></interceptor-ref>
   <interceptor-ref name="remoteAddrInterceptor"></interceptor-ref>
   <interceptor-ref name="requestBasePathInterceptor"></interceptor-ref>
   <result name="read">/WEB-INF/jsp/read.jsp</result>
   <result name="readHistory">/WEB-INF/jsp/readHistory.jsp</result>
   <result name="postSummary">/WEB-INF/jsp/postSummary.jsp</result>
   <result name="postShowIpInfo">/WEB-INF/jsp/postShowIpInfo.jsp</result>
   <result name="showUpFileInPost">/WEB-INF/jsp/showUpFileInPost.jsp</result>
   <result name="showAttach">/WEB-INF/jsp/showAttach.jsp</result>
   <result name="showVoteInPost">/WEB-INF/jsp/showVoteInPost.jsp</result>
   <result name="readWaste">/WEB-INF/jsp/readWaste.jsp</result>
   <result name="readAuditing">/WEB-INF/jsp/readAuditing.jsp</result>
   <result name="readElite">/WEB-INF/jsp/readElite.jsp</result>
  </action>
我们根据topic直接进入Read.java:
 public String execute() { //可见其内容之丰富!
  if (this.getAction().equalsIgnoreCase("topic")) {
   return this.topic();
  } else if (this.getAction().equalsIgnoreCase("history")) {
   return this.history();
  } else if (this.getAction().equalsIgnoreCase("own")) {
   return this.own();
  } else if (this.getAction().equalsIgnoreCase("summary")) {
   return this.summary();
  } else if (this.getAction().equalsIgnoreCase("showip")) {
   return this.showip();
  } else if (this.getAction().equalsIgnoreCase("summaryhistory")) {
   return this.summaryhistory();
  } else if (this.getAction().equalsIgnoreCase("showiphistory")) {
   return this.showiphistory();
  } else if (this.getAction().equalsIgnoreCase("showupfile")) {
   return this.showupfile();
  } else if (this.getAction().equalsIgnoreCase("attach")) {
   return this.attach();
  } else if (this.getAction().equalsIgnoreCase("showvote")) {
   return this.showvote();
  } else if (this.getAction().equalsIgnoreCase("waste")) {
   return this.waste();
  } else if (this.getAction().equalsIgnoreCase("auditing")) {
   return this.auditing();
  } else if (this.getAction().equalsIgnoreCase("auditingAttach")) {
   return this.auditingAttach();
  } else if (this.getAction().equalsIgnoreCase("elite")) {
   return this.elite();
  } else {
   return ERROR;
  }
 }
--->
 public String topic() {
  setUrlRewriteValue();//UrlRewrite!
  Pages pages = new Pages();
  pages.setPage(this.getInpages());
  pages.setPerPageNum(this.getUserPostPerNum(this.getUserCookie().getPostPerNum(), this.getSysConfig().getPostPerPage()));//设置PrePageNum!
  if (Constant.USE_URL_REWRITE) {
   pages.setUseUrlRewrite(Constant.USE_URL_REWRITE);
   pages.setFileName("read-" + this.getAction() + "-" + this.getBid() + "-" + this.getId() + "-"
     + this.getTagId() + "-" + this.getFcpage() + "-" + this.getFcaction() + "-{page}.html");
  } else {
   pages.setFileName(this.getBasePath()
     + BBSCSUtil.getActionMappingURLWithoutPrefix("read?action=" + this.getAction() + "&bid="
       + this.getBid() + "&id=" + this.getId() + "&fcpage=" + this.getFcpage() + "&fcaction="
       + this.getFcaction() + "&tagId=" + this.getTagId()));
  }
  this.setPageList(this.getForumService().findForumsTopic(this.getBid(), this.getId(), pages));

  if (this.getPageList().getObjectList().isEmpty()) {//没有~~
   return this.history();
  }

  this.pageTitle = "";

  Forum f = (Forum) this.getPageList().getObjectList().get(0);//主贴相关!
  if (f.getIsNew() == 1) {
   f.setClick(f.getClick() + 1);
   try {
    f = this.getForumService().updateForum(f);
    if (f.getClick() == this.getSysConfig().getForumHotViews() && f.getIsHidden() == 0) {
     UserInfo ui = this.getUserService().findUserInfoById(f.getUserID());
     if (ui != null) {
      ui.setLiterary(ui.getLiterary() + 1); // 增加用户文采值
      this.getUserService().saveUserInfo(ui);
     }
    }
   } catch (BbscsException ex) {
    this.addActionError(this.getText("error.post.getpost"));
    return ERROR;

   }
  } else {
   if (this.getInpages() == 1) {//private int inpages = 1;

    this.addActionError(this.getText("error.post.getpost"));
    return ERROR;
   }
  }
  this.pageTitle = f.getTitle();
  this.setTotalnum(this.getPageList().getPages().getTotalNum());
  this.setParentBoards();
  this.setForumSiteInit();
  return "read";
 }
我们注意最后的几个set!
 private int getUserPostPerNum(int userNum, int sysNum) {
  if (userNum == 0) {
   return sysNum;
  } else {
   return userNum;
  }
 }
 private void setParentBoards() {
  this.setParentBoards(this.getBoardService().findBoardsInIDs(this.getBoard().getParentIDs()));
 }
 private void setForumSiteInit() {
  String url = this.getSysConfig().getForumUrl();
  if (url == null) {
   url = "bbs.laoer.com";
  } else if (url.startsWith("http://")) {
   url = url.substring("http://".length(), url.length());
  }
  this.setForumSite(url);
 }
我们进入read.jsp:
<div id="head1right">   //头部的right部分:[分页: 1] [查看全部] [同主题打包] [订阅] [返回]
      <div id="head1rcontent">

      [<s:text name="bbscs.pagebreak"/>: <bbscs:pages value="%{pageList.pages}" argPage="inpages" argTotal="topicTotal"/>]
  <s:if test="%{action=='topic'}">
  [<bbscs:topic type="own"/>]
  </s:if>
  <s:if test="%{action=='own'}">
  [<bbscs:topic type="topic"/>]
  </s:if>
        [<bbscs:topic type="mailsendtopic"/>]
        [<bbscs:topic type="subs" />]
        [<bbscs:topic type="returnforum"/>]

      </div>
    </div>
我们进入topic这个tag的java实现代码处:
 protected String mainIdName = "%{id}";
 protected String forumCurrentPageName = "%{fcpage}";
 protected String inPagesName = "%{inpages}";
 protected String topicTotalNumName = "%{totalnum}";
 protected String type = "";
 protected String boardObjName = "%{board}";
 protected String forumCurrentActionName = "%{fcaction}";
 protected String tagIdProperty = "%{tagId}";
if (type.equalsIgnoreCase("returnforum")) {//返回版区
   sb.append("<a href="");
   if (Constant.USE_URL_REWRITE) {
    sb.append("forum-" + fcaction + "-" + board.getId() + "-" + tagId + "-" + fcpage + "-0.html");
   } else {
    sb.append(BBSCSUtil.getActionMappingURL("/forum?action=" + fcaction + "&bid=" + board.getId()
      + "&page=" + fcpage, request));
   }
   sb.append("">");

   sb.append(messageSource.getMessage("bbscs.back", null, request.getLocale()));
   sb.append("</a>");
   this.write(writer, sb.toString());
   return result;
  }
  if (type.equalsIgnoreCase("subs")) {//订阅帖子
   sb.append("<a href="javascript:;" onclick="subsTopic('");
   sb.append(board.getId());
   sb.append("','");
   sb.append(mainId);
   sb.append("');">");
   sb.append(messageSource.getMessage("post.subs.title", null, request.getLocale()));
   sb.append("</a>");
   this.write(writer, sb.toString());
   return result;
  }
  if (type.equalsIgnoreCase("mailsendtopic")) {//发信!
   sb.append("<a href="javascript:;" onclick="mailSendTopicAll('");
   sb.append(board.getId());
   sb.append("','");
   sb.append(mainId);
   sb.append("');">");
   sb.append(messageSource.getMessage("post.mailsendtopic.title", null, request.getLocale()));
   sb.append("</a>");
   this.write(writer, sb.toString());
   return result;
  }

  if (type.equalsIgnoreCase("own") || type.equalsIgnoreCase("topic")) {
   sb.append("<a href="");
   sb.append(BBSCSUtil.getActionMappingURL("/read?action=" + type + "&bid=" + board.getId() + "&id=" + mainId
     + "&fcpage=" + fcpage + "&fcaction=" + fcaction + "&tagId=" + tagId, request));
   sb.append("">");
   if (type.equalsIgnoreCase("own")) {//显示的字不一样
    sb.append(messageSource.getMessage("post.read.own", null, request.getLocale()));
   }
   if (type.equalsIgnoreCase("topic")) {
    sb.append(messageSource.getMessage("post.read.all", null, request.getLocale()));
   }
   sb.append("</a>");
   this.write(writer, sb.toString());
   return result;
  }

OK,我们继续分析read.jsp:
  <div id="head2">
    <div id="head2content">
    <s:text name="read.thisposturl"/>:<span id="posturl" class="font6"><bbscs:topic type="posturl"/></span>//main的URL!
    </div>
  </div>
--->
  if (type.equalsIgnoreCase("posturl")) {
   try {
    if (Constant.USE_URL_REWRITE) {
     sb.append(BBSCSUtil.absoluteActionURLHtml(request, "/main-read-" + board.getId() + "-" + mainId
       + ".html"));
    } else {
     sb.append(BBSCSUtil.absoluteActionURL(request, "/main?action=read&bid=" + board.getId()
       + "&postID=" + mainId));
    }
   } catch (MalformedURLException ex1) {
    sb.append("");
   }
   this.write(writer, sb.toString());
   return result;
  }
Go ON!   <span class="font2">[<bbscs:forum forumValue="f" type="floor"/>]</span>
                  <bbscs:forum forumValue="f" type="face"/>
 <span class="font1">
                    <strong><s:text name="forum.art.title"/>:<s:property value="#f.title"/></strong>//显示标题!
                    <span id="cndt<s:property value="#f.id"/>">
       <s:if test="#f.canNotDel==1">M</s:if>
                    </span>
                  </span>
这里用到两个tag,我们看floor:
if (type.equalsIgnoreCase("floor")) {//第几楼的显示!
   int index = 0;
   Object statusObj = this.getStack().findValue(indexValue);
   if (statusObj != null) {
    index = ((IteratorStatus) statusObj).getIndex();
   }
   int inpages = 1;
   Object inpagesObj = this.getStack().findValue(inPagesValue);
   if (inpagesObj != null) {
    inpages = ((Integer) inpagesObj).intValue();
   }
   UserCookie uc = new UserCookie(request, response, sysConfig);
   int perNum = this.getUserPostPerNum(uc.getPostPerNum(), sysConfig.getPostPerPage());
   int floor = (inpages - 1) * perNum + index;
   if (floor == 0) {
    sb.append(messageSource.getMessage("forum.floor0", null, request.getLocale()));//楼主
   } else {
    sb.append(messageSource.getMessage("forum.floor1", new String[] { String.valueOf(floor) }, request
      .getLocale()));//[9楼]
   }
   this.write(writer, sb.toString());
   return result;
  }
                 <div align="right">
     <s:if test="%{action!='own'}">//不是只看楼主的话
     <s:if test="%{fcaction=='index'}">
                    [<bbscs:forum forumValue="f" type="sendnote"/>]//留言
                    [<bbscs:forum forumValue="f" type="del" currentPageValue="%{fcpage}"/>]//删除
                    [<bbscs:forum forumValue="f" type="delattachpage"/>]//删除附件
     <s:if test="#f.isNew==1">//是主帖的话
                      [<bbscs:forum forumValue="f" type="movepage"/>]//移动!
     </s:if>
                    [<bbscs:forum forumValue="f" type="edit" currentPageValue="%{fcpage}"/>]//修改
                    [<bbscs:forum forumValue="f" type="re" currentPageValue="%{fcpage}" />]//回复
                    [<bbscs:forum forumValue="f" type="requote" currentPageValue="%{fcpage}" />]//引用回复
                    [<bbscs:forum forumValue="f" type="upfilepage"/>]//上传附件
                    </s:if>
                    </s:if>//下面是公有的
                    [<a href="javascript:;" onclick="mailSendTopic('<s:property value="%{bid}"/>','<s:property value="#f.id"/>');"><s:text name="post.mailsend.title" /></a>]//打包
                    [<a href="javascript:;" onclick="reportTopic('<s:property value="%{bid}"/>','<s:property value="#f.id"/>');"><s:text name="post.report.title" /></a>]//举报
                  </div>
我们再到ForumComponent.java中看相关的tag:
  if (type.equalsIgnoreCase("sendnote")) {
   sb.append("<a href="javascript:;" onclick="loadNoteSend('");//把下面的notesend这个div显示出来
   sb.append(f.getId());
   sb.append("');">");
   sb.append(messageSource.getMessage("pot.sendnote", null, request.getLocale()));
   sb.append("</a>");
   this.write(writer, sb.toString());
   return result;
  }
   if (type.equalsIgnoreCase("edit")) {
   Object forumCurrentPageObj = this.getStack().findValue(this.currentPageValue);
   int fcpage = 1;
   if (forumCurrentPageObj != null) {
    fcpage = ((Integer) forumCurrentPageObj).intValue();
   }

   int inpages = 1;
   Object inpagesObj = this.getStack().findValue(inPagesValue);
   if (inpagesObj != null) {
    inpages = ((Integer) inpagesObj).intValue();
   }

   String tagId = "0";
   Object tagIdObj = this.getStack().findValue(this.tagIdValue);
   if (tagIdObj != null) {
    tagId = (String) tagIdObj;
   }

   StringBuffer linksb = new StringBuffer();
   if (f.getIsVote() == 0) {
    linksb.append("/post?action=edit&");
   } else {
    linksb.append("/votePost?action=edit&");
   }
   linksb.append("id=");
   linksb.append(f.getId());
   linksb.append("&bid=");
   linksb.append(f.getBoardID());
   linksb.append("&fcpage=");
   linksb.append(fcpage);
   linksb.append("&inpages=");
   linksb.append(inpages);
   linksb.append("&mainID=");
   linksb.append(f.getMainID());
   linksb.append("&tagId=");
   linksb.append(tagId);
   sb.append("<a href="");
   sb.append(BBSCSUtil.getActionMappingURL(linksb.toString(), request));
   sb.append("">");
   sb.append(messageSource.getMessage("bbscs.change", null, request.getLocale()));
   sb.append("</a>");
   this.write(writer, sb.toString());//产生一个url!
   return result;
  }
  if (type.equalsIgnoreCase("del")) {
   Object forumCurrentPageObj = this.getStack().findValue(this.currentPageValue);
   int fcpage = 1;
   if (forumCurrentPageObj != null) {
    fcpage = ((Integer) forumCurrentPageObj).intValue();
   }
   sb.append("<a href="javascript:;" onclick="delapost('");
   sb.append(f.getBoardID());
   sb.append("','");
   sb.append(f.getId());
   sb.append("','");
   sb.append(f.getIsNew());
   sb.append("','");
   sb.append(fcpage);
   sb.append("');">");
   sb.append(messageSource.getMessage("bbscs.del", null, request.getLocale()));
   sb.append("</a>");
   this.write(writer, sb.toString());
   return result;
  }
其它省略之!我们继续看sidebar:
  <div class="siderbarcontent" id="commenddiv">
  <bbscs:topic type="commend"/>
  </div>
-->
  if (type.equalsIgnoreCase("commend")) {
   File commendFile = new File(BBSCSUtil.getIncludePath() + "Commend_" + board.getTopBid() + ".html");
   String commendText = "";
   try {
    commendText = FileUtils.readFileToString(commendFile, Constant.CHARSET);//读取文件!
    if (commendText == null) {
     commendText = "";
    }
   } catch (IOException ex) {
    commendText = "";
   }
   sb.append(commendText);
   this.write(writer, sb.toString());
   return result;
  }
接下来,我们分析下回复,有快速回复和普通回复两种,快速回复只需标题和内容两项就可以ctrl+enter进行回复了!
function ctrlEnter(e){
    var ie =navigator.appName=="Microsoft Internet Explorer"?true:false;
    if(ie){
        if(event.ctrlKey && window.event.keyCode==13){doSomething();}
    }else{
        if(isKeyTrigger(e,13,true)){doSomething();}
    }
}
 当我们回复时,form的action=post而action为addqre!
 <input type="hidden" name="action" value="addqre" />
        <input type="hidden" name="bid" value="<s:property value="%{bid}"/>" />
        <input type="hidden" name="parentID" value="<s:property value="%{id}"/>" />
        <input type="hidden" name="editType" value="0" />
        <input type="hidden" name="mainID" value="<s:property value="%{id}"/>" />
        <input type="hidden" name="totalnum" value="<s:property value="%{pageList.pages.totalNum}"/>" />
        <input type="hidden" name="sign" value="-1" />
        <input type="hidden" name="fcpage" value="<s:property value="%{fcpage}"/>" />
        <input type="hidden" name="tagId" value="<s:property value="%{tagId}"/>" />
我们进入Post.java的addqre()方法,它仍要checkBeforePost,title的bestrowScreen还有detail的长度和bestrowScreen:
  Forum mainForum = this.getForumService().findForumByID(this.getMainID(), this.getBid());
  if (mainForum == null) {
   this.addActionError(this.getText("error.post.getpost"));
   return ERROR;
  }
  if (mainForum.getIsLock() == 1) {
   this.addActionError(this.getText("error.post.islock"));
   return ERROR;
  }
其它的类似与addsave!最后,我们在read.jsp中看一下[打包]是怎么实现的:
<a href="javascript:;" onclick="mailSendTopic('<s:property value="%{bid}"/>','<s:property value="#f.id"/>');"><s:text name="post.mailsend.title" /></a>
在read.js中:
var SendMailTopicAjax = Class.create();

SendMailTopicAjax.prototype = {
  initialize: function(bid,id) {
    this.bid = bid;
    this.id = id;
  },

  sendMail: function() {
    showExeMsg();
    var url = getActionMappingURL("/postOpt");
    var pars = "ajax=xml&action=mailsend&id=" + this.id + "&bid=" + this.bid;
    var myAjax = new Ajax.Request(url, {method: 'get', parameters: pars, onComplete: this.sendMailCompleted.bind(this)});
  },

  sendMailCompleted: function(res) {
    var jsonMsgObj = new JsonMsgObj(res.responseText);
   var codeid = jsonMsgObj.getCodeid();
    hiddenExeMsg();
    Element.hide("postOpt" + this.id);
    alert(jsonMsgObj.getMessage());//提示成功!
  }
};

function mailSendTopic(bid,id) {
  var mset = confirm(sendtopicconfirm);
  if (mset) {
    Element.show("postOpt" + id);// <div id="postOpt<s:property value="#f.id"/>" class="summary1" style="display:none"></div>
    $('postOpt' + id).innerHTML = sendMailMsg;
    var oSendMailTopicAjax = new SendMailTopicAjax(bid,id);
    oSendMailTopicAjax.sendMail();
  }
  else {
    return false;
  }
}
我们看postOpt这个action:
  <action name="postOpt" class="postOptAction">
   <interceptor-ref name="boardInterceptorStack"></interceptor-ref>
   <interceptor-ref name="remoteAddrInterceptor"></interceptor-ref>
   <interceptor-ref name="requestBasePathInterceptor"></interceptor-ref>
  </action>
 public String mailsend() {
  Forum f = this.getForumService().findForumByID(this.getId(), this.getBid());
  if (f == null) {
   this.getAjaxMessagesJson().setMessage("E_POST_NOT_EXIST", this.getText("error.post.getpost"));
   return RESULT_AJAXJSON;
  }

  if (StringUtils.isNotBlank(this.getUserSession().getEmail()) && this.getSysConfig().getUseEmail() == 1) {
   try {

    String detail = "";
    if (f.getIsHidden() != 0) {
     detail = this.getText("post.hidden.notmailsend");
    } else {
     detail = this.getForumService().getForumDetail(f, false);//找到内容!
     if (f.getEditType() == 0) {
      detail = BBSCSUtil.filterText(detail, (this.getBoard().getAllowHTML() == 1), (this.getBoard()
        .getAllowUBB() == 1), true);
     } // else {detail = f.getDetail();}
    }

    String url = this.getBasePath()
      + BBSCSUtil.getActionMappingURLWithoutPrefix("main?action=read&bid=" + this.getBoard().getId()
        + "&postID=" + f.getMainID());

    Map root = new HashMap();
    root.put("website", this.getSysConfig().getForumName());
    root.put("title", f.getTitle());
    root.put("detail", detail);
    root.put("url", url);

    this.getTemplateMail().sendMailFromTemplate(this.getUserSession().getEmail(), f.getTitle(),
      "mailSend.ftl", root, this.getLocale());//mainSend!

    this.getAjaxMessagesJson().setMessage("0", this.getText("post.mailsend.ok"));
   } catch (Exception ex7) {
    ex7.printStackTrace();
    this.getAjaxMessagesJson().setMessage("E_POST_MAILSEND_ERROR", this.getText("post.mailsend.error"));
   }
   return RESULT_AJAXJSON;
  } else {
   this.getAjaxMessagesJson().setMessage("E_POST_MAILSEND_ERROR", this.getText("post.mailsend.error"));
   return RESULT_AJAXJSON;
  }
 }
而下面的方法是实现同主题打包的功能的:
 public String mailsendtopic() {
  OrderObj[] oo = { new OrderObj("postTime", Constant.ORDER_ASC) };
  List l = this.getForumService().findForumsTopicAll(this.getBid(), this.getMainid(), 0, 0, oo);
  if (l == null || l.isEmpty()) {
   this.getAjaxMessagesJson().setMessage("E_POST_NOT_EXIST", this.getText("error.post.getpost"));
   return RESULT_AJAXJSON;
  }
  if (StringUtils.isNotBlank(this.getUserSession().getEmail()) && this.getSysConfig().getUseEmail() == 1) {
   try {
    String detail = "";
    Forum mainForum = (Forum) l.get(0);
    List<Forum> nl = new ArrayList<Forum>();
    for (int i = 0; i < l.size(); i++) {
     Forum f = (Forum) l.get(i);
     if (f.getIsHidden() != 0) {
      detail = this.getText("post.hidden.notmailsend");
     } else {
      detail = this.getForumService().getForumDetail(f, false);
      if (f.getEditType() == 0) {
       f.setDetail(BBSCSUtil.filterText(detail, (this.getBoard().getAllowHTML() == 1), (this
         .getBoard().getAllowUBB() == 1), true));
      } else {
       f.setDetail(detail);
      }
     }
     nl.add(f);
    }

    String url = this.getBasePath()
      + BBSCSUtil.getActionMappingURLWithoutPrefix("main?action=read&bid=" + this.getBoard().getId()
        + "&postID=" + this.getMainid());

    Map root = new HashMap();
    root.put("website", this.getSysConfig().getForumName());
    root.put("nl", nl);
    root.put("url", url);

    this.getTemplateMail().sendMailFromTemplate(this.getUserSession().getEmail(), mainForum.getTitle(),
      "mailSendTopic.ftl", root, this.getLocale());//mailSendTopic!

    this.getAjaxMessagesJson().setMessage("0", this.getText("post.mailsend.ok"));
   } catch (Exception ex8) {
    ex8.printStackTrace();
    this.getAjaxMessagesJson().setMessage("E_POST_MAILSEND_ERROR", this.getText("post.mailsend.error"));
   }
   return RESULT_AJAXJSON;
  } else {
   this.getAjaxMessagesJson().setMessage("E_POST_MAILSEND_ERROR", this.getText("post.mailsend.error"));
   return RESULT_AJAXJSON;
  }
 }
对于举报功能,其实它也是发E-mail给webmaster的,下面是其实现的部分代码:
    Map root = new HashMap();
    root.put("userName", this.getUserSession().getUserName());
    root.put("title", f.getTitle());
    root.put("detail", detail);
    root.put("url", url);

    this.getTemplateMail().sendMailFromTemplate(this.getSysConfig().getWebmasterEmail(), title,
      "report.ftl", root, this.getLocale());

    this.getAjaxMessagesJson().setMessage("0", this.getText("post.report.ok"));
好的,关于其它的部分我们不再进行分析了.最后,我们看一下read的其它方法action="own":关键的是this.setPageList(this.getForumService().findForumsOwner(this.getBid(), f.getUserID(), this.getId(), pages));而action="elite":
 public String elite() {
  this.setEliteDirs(new ArrayList<Elite>());
  Elite elite = this.getEliteService().findEliteByID(this.getEliteId());
  if (elite != null) {
   List pes = elite.getParentIDs();
   for (int i = 0; i < pes.size(); i++) {
    Elite pe = this.getEliteService().findEliteByID(((Long) pes.get(i)).longValue());
    this.eliteDirs.add(pe);
   }
   this.eliteDirs.add(elite);
  }
  Forum f = this.getForumService().findForumByID(this.getId(), this.getBid());
  if (f == null) {
   this.addActionError(this.getText("error.post.getpost"));
   return ERROR;
  }
  this.setForum(f);
  return "readElite";
 }
OK!我们的分析已经大部分给分析完成了。除了对管理部分的action和jsp外,在read.jsp等界面元素上还有些功能仍需要进行详细地分析,这将在以后的时间里进行阐述!

 

抱歉!评论已关闭.