`
sgwood
  • 浏览: 120928 次
  • 性别: Icon_minigender_1
  • 来自: 广州
社区版块
存档分类
最新评论

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

阅读更多

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

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

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

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

http://www.iteye.com/topic/32001;另外,还有struts-clearup,以及Strut2的

org.apache.struts2.dispatcher.FilterDispatcher,注意它有两个dipatcher参数,  在这种情况下,如

果请求是以/*开头的,并且是通过request   dispatcher的forward方法传递过来或者直接从客户端传递

过来的,则必须经过这个过滤器,  Servlet 2.3 版新增了Filter的功能,不过它只能由客户端发出请求

来调用Filter,但若使用   RequestDispatcher.forward( )或RequestDispatcher.include( )的方法调

用Filter 时,Filter 却不会执行.这个功能应该都是主要用于UrlRewrite用的吧.而<context-param>元

素定义了一些应用级的参数,如:urlrewrite,cluster都为false,servletmapping为

*.bbscs,poststoragemode为1;接下来是listener有两个,一是

com.loaer.bbscs.web.servlet.SysListener就是对上面的变量进行操作的一个监听器,而另一个则是

spring的ContextLoaderListener,这里我们不讨论,接下来是几个servlet,提供一些常用的功能:authimg

生成验证码,rss.另外还有welcome-file及struts-tag.tld的taglib和一些error-page,如:error-

code:401--->location:-->/401.htm.对于具体的加载过程可见启动resin3等服务器后的控制台显示,我个

人觉得是加载了应用级参数,再是2个Linstener..,到spring-->加载所有bean到时空器-->各个bean的加载

(包括hibernate的配置等)--->ContextLoader启动完成-->接下来,对Filter按相反的顺序加载(struts-

>struts-clean-->characterEncoding)先是struts2的配置文件的加载,还有global messages...
注意这段时间内也会启动一些TimerTask任务...,这点可从日志中看到,最后是

ObjectTypeDeterminerFactory应该是用于struts-clean吧.还有

OpenSessionInViewFilter,CharacterEncodingFilter,UrlRewriteFilter...
这样,应用和resin服务才开始发挥作用,才能访问!
好的,我们先看在com.laoer.bbscs.web.servlet包中的几个源文件:
SysListener:它继承自HttpServlet,实现了ServletContextLinster接口.
String rootpath = sce.getServletContext().getRealPath("/");
   if (rootpath != null) {
   rootpath = rootpath.replaceAll("\\", "/");
  } else {
   rootpath = "/";
  }
  if (!rootpath.endsWith("/")) {
   rootpath = rootpath + "/";
  }
  Constant.ROOTPATH = rootpath; 记录在常量java文件中.public static String

ROOTPATH = "";
我们看一个代表性的示例源码:
String poststoragemodes = sce.getServletContext().getInitParameter("poststoragemode");
  if (poststoragemodes == null) {
   poststoragemodes = "0";
  }
  Constant.POST_STORAGE_MODE = NumberUtils.toInt(poststoragemodes, 0);//文章存

储格式(这里是文件)
-->
 <context-param>
    <param-name>poststoragemode</param-name>
    <param-value>1</param-value>
  </context-param>
接下来,我们分析AuthImg,主要用它的doGet方法:
public void doGet(HttpServletRequest request, HttpServletResponse response) throws

ServletException, IOException {
  response.setContentType("image/jpeg");
  ServletOutputStream out = response.getOutputStream();

  int width = 60, height = 20;
  BufferedImage image = new BufferedImage(width, height,

BufferedImage.TYPE_INT_RGB);
//这段代码创建了一个 BufferedImage 对象,它代表一个  60像素宽、20像素高的图像。为了应用这个

图像,我们需要有图形上下文,而 BufferedImage 对象的 createGraphics() 方法就返回一个与该图像

相关的 Graphics2D 对象:


  Graphics g = image.getGraphics();
  Random random = new Random();

  g.setColor(getRandColor(200, 250));
  g.fillRect(0, 0, width, height); //背景色

  g.setFont(mFont); //字体private Font mFont = new Font("Times New Roman",

Font.PLAIN, 18);

  g.setColor(getRandColor(160, 200));
  for (int i = 0; i < 155; i++) {
   int x = random.nextInt(width);
   int y = random.nextInt(height);
   int xl = random.nextInt(12);
   int yl = random.nextInt(12);
   g.drawLine(x, y, x + xl, y + yl);//画线155条
  }

  String rand = RandomStringUtils.randomNumeric(4);//产生四个随机数
  char c;
  for (int i = 0; i < 4; i++) {
   c = rand.charAt(i);
   g.setColor(new Color(20 + random.nextInt(110), 20 + random.nextInt

(110), 20 + random.nextInt(110))); //各个数的着色不一样
   g.drawString(String.valueOf(c), 13 * i + 6, 16);
  }
  WebApplicationContext wc =

WebApplicationContextUtils.getWebApplicationContext(this.getServletContext());//
webapplicationcontextutils.getwebapplicationcontext(servletcontext); 如果没有直接返回null
  SysConfig sysConfig = (SysConfig) wc.getBean("sysConfig");//web层得到

sysConfig
  UserCookie uc = new UserCookie(request, response, sysConfig);//写入Cookie中
  uc.addAuthCode(rand);

  JPEGImageEncoder encoder = JPEGCodec.createJPEGEncoder(out);//也可以用

ImageIO.write(bi,"jpg",out);
  encoder.encode(image);
  out.close();
 }
接下来,看RSS:它也是用在doGet,有两种,一种是首页,不带bid参数,一种是带bid.用于各个版区的...
  long bid;
  try {
   bid = Long.parseLong(request.getParameter("bid"));
  } catch (NumberFormatException e) {
   bid = 0L;
  }
 首先为了使用服务,这里用了spring的WebApplicationContext wc =

WebApplicationContextUtils.getWebApplicationContext(this.getServletContext());这样我们就可以

得到ForumService和SysConfig,BoardService..这里用了下SyndFeed feed=new SynFeedImpl();
 com.sun.syndication这个包,接着便是feed.setFeedType("rss_2.0");先设置好feed源的标题/链接(有

两种,分有bid和没bid)/描述,注意这些参数大多来自于sysConfig,接下来,我们设置entry(条目)及其

description
List<SyndEntry> entries = new ArrayList<SyndEntry>();
   SyndEntry entry;
   SyndContent description;
我们看后半部分的代码:
 Board board = boardService.getBoardByID(bid);//得到board
   if (board != null) {
    if (board.getBoardType() == 2 || board.getBoardType() == 3)

{
     String forumLink = "";
     try {
      forumLink = BBSCSUtil.absoluteActionURL

(request, "/forum.bbscs?action=index&bid=" + bid)
        .toString();//RSS源链接
     } catch (MalformedURLException ex2) {
      forumLink = "";
     }
     feed.setTitle(sysConfig.getForumName() + " - " +

board.getBoardName());
     feed.setLink(forumLink);
     feed.setDescription(sysConfig.getWebName() + " - " +

sysConfig.getForumName() + " - "
       + board.getBoardName());

     List<SyndEntry> entries = new ArrayList<SyndEntry>

();//所有条目
     SyndEntry entry;
     SyndContent description;

     Pages pages = new Pages();//构造一个Pages对象
     pages.setPage(1);
     pages.setPerPageNum(40);
     pages.setFileName("");

     PageList pl = forumService.findForumsMainWWW(bid,

pages);//重点的地方
     List flist = pl.getObjectList();

     for (int i = 0; i < flist.size(); i++) {
      Forum f = (Forum) flist.get(i);
      try {
       postLink =

BBSCSUtil.absoluteActionURL(request,
         "/main.bbscs?

action=read&bid=" + f.getBoardID() + "&postID=" + f.getMainID())
         .toString();
      } catch (MalformedURLException ex) {
       postLink = "";
      }

      entry = new SyndEntryImpl();
      entry.setTitle(f.getTitle());
      entry.setLink(postLink);
      entry.setPublishedDate(new Date

(f.getPostTime()));

      description = new SyndContentImpl();
      if (f.getEditType() == 0) {
       description.setType("text/plain");//

文本类型
      } else {
       description.setType("text/html");
      }
      description.setValue(BBSCSUtil
        .getSpeShortString

(forumService.getForumDetail(f, false), 400, ""));//格式化
      entry.setDescription(description);
      entries.add(entry);
     }

     feed.setEntries(entries);
     try {
      SyndFeedOutput output = new SyndFeedOutput

();
      
      Document messagesDocument =

output.outputJDom(feed);
      Format format = Format.getPrettyFormat();
      format.setOmitDeclaration(true);
      XMLOutputter xmlo = new XMLOutputter

(format);
      xmlo.output(messagesDocument, out);
     } catch (Exception ex) {
      logger.error(ex);
     }
    }
   }

其中:
public static URL absoluteActionURL(HttpServletRequest request, String action) throws

MalformedURLException {
  return new URL(RequestUtils.serverURL(request) + getActionMappingURL(action,

request));
 }
--->
public static String getActionMappingURL(String action, HttpServletRequest request) {

  StringBuffer value = new StringBuffer(request.getContextPath());//获得的当前

目录路径

  // Use our servlet mapping, if one is specified
  String servletMapping = Constant.SERVLET_MAPPING;//*.bbscs
  if (servletMapping != null) {
   String queryString = null;
   int question = action.indexOf("?");//action="/main.bbscs?

action=read&bid=" + f.getBoardID() + "&postID=" + f.getMainID()).toString();
   if (question >= 0) {
    queryString = action.substring(question);//?

action=read&bid=12&postID=123123
   }
   String actionMapping = getActionMappingName(action);// /main
   if (servletMapping.startsWith("*.")) {
    value.append(actionMapping);
    value.append(servletMapping.substring(1));

//value=/main.bbcs
   } else if (servletMapping.endsWith("/*")) {
    value.append(servletMapping.substring(0,

servletMapping.length() - 2));
    value.append(actionMapping);
   } else if (servletMapping.equals("/")) {
    value.append(actionMapping);
   }
   if (queryString != null) {
    value.append(queryString);
   }
  }
--->
public static String getActionMappingName(String action) {
  String value = action;
  int question = action.indexOf("?");
  if (question >= 0) {
   value = value.substring(0, question);// /main.bbscs
  }
  int slash = value.lastIndexOf("/");
  int period = value.lastIndexOf(".");
  if ((period >= 0) && (period > slash)) {
   value = value.substring(0, period);// /main
  }
  if (value.startsWith("/")) {
   return (value);
  } else {
   return ("/" + value);
  }
 }
OK!接下来,我们看看登录流程先开始分析吧!
在浏览器在输入http://localhost:8080/bbscs8启动:
 <welcome-file-list>
    <welcome-file>index.jsp</welcome-file> //相对当前目录哦!!!
  </welcome-file-list>
index.jsp触发了action login.bbscs?action=check:
<script language="javascript" type="text/javascript">
window.location.href="login.bbscs?action=check";
</script>
</head>
-->struts2发生作用...
我们看下struts.properties:
struts.devMode=false
struts.action.extension=bbscs  //后缀
struts.enable.DynamicMethodInvocation=true
struts.i18n.reload=true
struts.ui.theme=simple

struts.locale=zh_CN
struts.i18n.encoding=UTF-8
struts.objectFactory=spring   //由spring来代理bean
struts.objectFactory.spring.autoWire=name

struts.serve.static.browserCache=false
struts.url.includeParams=none

struts.custom.i18n.resources=com.laoer.bbscs.web.action.BaseAction //资源文件!
看后台的输出日志[com.opensymphony.xwork2.validator.ActionValidatorManagerFactory]-[INFO]

Detected AnnotationActionValidatorManager, initializing it... (注意:并不一定是这里触发哦!
接着我们看struts.xml,其中name为bbscs-default的package继承之struts-default,并引入了许多自定义

的interceptor和interceptor-stack.也设置了global-results...这些是为它们package使用的.
<package name="loginout" extends="bbscs-default" namespace="/">
  <action name="login" class="loginAction">
   <result name="success" type="redirect">${tourl}</result>
   <result name="input">/WEB-INF/jsp/login.jsp</result>
   <result name="loginPass">/WEB-INF/jsp/passLogin.jsp</result>
   <interceptor-ref name="defaultStack"></interceptor-ref>//一旦继承了

struts-default包(package),所有Action都会调用拦截器栈 ——defaultStack。当然,在Action配置

中加入“<interceptor-ref name="xx" />”可以覆盖defaultStack。
   <interceptor-ref name="remoteAddrInterceptor"></interceptor-ref>
   <interceptor-ref name="userCookieInterceptor"></interceptor-ref>
   <interceptor-ref name="requestBasePathInterceptor"></interceptor-

ref>
  </action>
 </package>
注意这里的login对应于spring配置文件action-servlet.xml中的loginAction:
<bean id="loginAction" class="com.laoer.bbscs.web.action.Login"  //所管理的action bean
  scope="prototype" autowire="byName"> //用了autowire就不用下面这段了
  <!--
   <property name="sysConfig">
   <ref bean="sysConfig" />
   </property>
  -->
 </bean>
好的,我们先看拦截器:<interceptor-ref name="defaultStack"></interceptor-ref>使用默认的拦截器

栈(在访问被拦截的方法或字段时,拦截器链中的拦截器就会按其之前定义的顺序被调用),我们可以打开

struts2.0core包找到struts-default.xml打开找到<interceptors>元素内容..请看资

料:http://www.blogjava.net/max/archive/2006/12/06/85925.html
 <interceptor-stack name="defaultStack">
                <interceptor-ref name="exception"/>
                <interceptor-ref name="alias"/>
                <interceptor-ref name="servletConfig"/>
                <interceptor-ref name="prepare"/>
                <interceptor-ref name="i18n"/>
                <interceptor-ref name="chain"/>
                <interceptor-ref name="debugging"/>
                <interceptor-ref name="profiling"/>
                <interceptor-ref name="scopedModelDriven"/>
                <interceptor-ref name="modelDriven"/>
                <interceptor-ref name="fileUpload"/>
                <interceptor-ref name="checkbox"/>
                <interceptor-ref name="staticParams"/>
                <interceptor-ref name="params">
                  <param name="excludeParams">dojo..*</param>
                </interceptor-ref>
                <interceptor-ref name="conversionError"/>
                <interceptor-ref name="validation">
                    <param name="excludeMethods">input,back,cancel,browse</param>
                </interceptor-ref>
                <interceptor-ref name="workflow">
                    <param name="excludeMethods">input,back,cancel,browse</param>
                </interceptor-ref>
            </interceptor-stack>
而自定义拦截器是要求拦截器是无状态的原因是Struts 2不能保证为每一个请求或者action创建一个实例

,所以如果拦截器带有状态,会引发并发问题。所有的Struts 2的拦截器都直接或间接实现接口

com.opensymphony.xwork2.interceptor.Interceptor。除此之外,大家可能更喜欢继承类

com.opensymphony.xwork2.interceptor.AbstractInterceptor。需实现其public String intercept

(ActionInvocation invocation) throws Exception .
而下面的remoteAddrInterceptor:
 <interceptor name="remoteAddrInterceptor"
   class="com.laoer.bbscs.web.interceptor.RemoteAddrInterceptor">
   </interceptor>
我们进入web.interceptor层:
 public String intercept(ActionInvocation invocation) throws Exception {
  ActionContext ac = invocation.getInvocationContext();
  Object action = invocation.getAction();
  HttpServletRequest request = (HttpServletRequest) ac.get

(ServletActionContext.HTTP_REQUEST);//得到request请求
  String userRemoteAddr = request.getRemoteAddr();
  if (action instanceof RemoteAddrAware) {  //action是RomoteAddrAware实例?

   ((RemoteAddrAware)action).setRemoteAddr(userRemoteAddr);
   //System.out.println(userRemoteAddr);
  }

  return invocation.invoke();
 }
}
我们可以看到RemoteAddrAware是如下这个接口,这是为了方便将远程地址放入action中:
public interface RemoteAddrAware {
 public void setRemoteAddr(String remoteAddr);
}

接下来是userCookieInterceptor:
<interceptor name="userCookieInterceptor"    
class="com.laoer.bbscs.web.interceptor.UserCookieInterceptor">
   </interceptor>
public String intercept(ActionInvocation invocation) throws Exception {
  ActionContext ac = invocation.getInvocationContext();
  Object action = invocation.getAction();

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

(ServletActionContext.HTTP_REQUEST); //用于UserCookie
   HttpServletResponse response = (HttpServletResponse) ac.get

(ServletActionContext.HTTP_RESPONSE);//用于UserCookie

   ServletContext servletContext = (ServletContext) ac.get

(ServletActionContext.SERVLET_CONTEXT);
   WebApplicationContext wc =

WebApplicationContextUtils.getWebApplicationContext(servletContext);//得到业务层服务!
   if (wc == null) {
    logger.error("ApplicationContext could not be found.");
   } else {
    SysConfig sysConfig = (SysConfig) wc.getBean("sysConfig");
    UserCookie userCookie = new UserCookie(request, response,

sysConfig);   //关键点!!!!
    //logger.debug("userCookie sid:" + userCookie.getSid());
    ((UserCookieAware) action).setUserCookie(userCookie);
   }
  }
  return invocation.invoke();
 }
而UserCookieAware:
public interface UserCookieAware {
 public void setUserCookie(UserCookie userCookie);
}
看最后一个interceptor:requestBasePathInterceptor
public String intercept(ActionInvocation invocation) throws Exception {
  ActionContext ac = invocation.getInvocationContext();
  Object action = invocation.getAction();

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

(ServletActionContext.HTTP_REQUEST);
   StringBuffer sb = new StringBuffer();
   sb.append(BBSCSUtil.getWebRealPath(request));//得到域名
/**
public static String getWebRealPath(HttpServletRequest request) {
  StringBuffer sb = new StringBuffer();
  sb.append("http://");
  sb.append(request.getServerName());
  if (request.getServerPort() != 80) {
   sb.append(":");
   sb.append(request.getServerPort());
  }
  return sb.toString(); //返回域名啊!
 }
*/
   sb.append(request.getContextPath());//request相对路径
   sb.append("/");
   ((RequestBasePathAware) action).setBasePath(sb.toString());//设置

BasePath
  }

  return invocation.invoke();
 }
其中,RequestBasePathAware:
public interface RequestBasePathAware {
 public void setBasePath(String basePath);
}

我们回到public class Login extends BaseAction implements RequestBasePathAware,

RemoteAddrAware, UserCookieAware, SessionAware,可见这个Login实现了我们的这些Aware..并且它继

承了BaseAction,而BaseAction继承了ActionSupport,它有几个通用的方

法:getAction,setAction,getAjax,setAjax,及page.total(本来私有的)的getter/setter方法,另外还有

以下方法:
protected String executeMethod(String method) throws Exception { //子类用!
  Class[] c = null;
  Method m = this.getClass().getMethod(method, c);
  Object[] o = null;
  String result = (String) m.invoke(this, o);
  return result;
 }

 public int boolean2int(boolean value) {
  if (value) {
   return 1;
  } else {
   return 0;
  }
 }

 public boolean int2boolean(int value) {
  if (value == 0) {
   return false;
  } else {
   return true;
  }
 }
有点类似C++了!true-->1  value!=0--->true
我们进入正题Login:
首先它需要一个静态的logger:private static final Log logger = LogFactory.getLog(Login.class);
还有private static final long serivalVeserionUID...
当然,它需要get/set一下上面的basePath,remoteAddr,userCookie.另外还有一个session
作为struts,它有与表单交互的字

段:actionUrl,tourl,passwd,username,hiddenLogin,authCode,urlRewrite,useAuthCode,cookieTime=-1

等及其getter/setter方法...注意:
public boolean isUseAuthCode() {
  return useAuthCode;
 }
另外,我们可以看到其构造方法中:
 public Login() {
  this.setRadioYesNoListValues();//隐身选择是或否
  this.setCookieTimeListValues();//Cookie时间选择一年/一月/一天/浏览器进程
 }
 private void setRadioYesNoListValues() { //private的注意哦!!
  radioYesNoList.add(new RadioInt(0, this.getText("bbscs.no")));//注意getText

从资源文件BaseAction中获得字符串值!
  radioYesNoList.add(new RadioInt(1, this.getText("bbscs.yes")));
 }
 private void setCookieTimeListValues() {
  cookieTimeList.add(new RadioInt(365 * 24 * 3600, this.getText

("login.cookietime0")));//一年以365计算
  cookieTimeList.add(new RadioInt(30 * 24 * 3600, this.getText

("login.cookietime1")));
  cookieTimeList.add(new RadioInt(24 * 3600, this.getText

("login.cookietime2")));
  cookieTimeList.add(new RadioInt(-1, this.getText("login.cookietime3")));
 }
我们来看RadioInt(com.laoer.bbscs.web.ui):它是一个简单的bean,封装了两个属性int的key和String类

型的value,而公开其getter/setter方法,和下面的构造方法:
public RadioInt(int key, String value) {
  this.key = key;
  this.value = value;
 }
当然,也有其List<RadioInt> radioYesNoList = new ArrayList<RadioInt>();

 public List<RadioInt> getRadioYesNoList() {
  return radioYesNoList;
 }
 public void setRadioYesNoList(List<RadioInt> radioYesNoList) {
  this.radioYesNoList = radioYesNoList;
 }
也于提供给界面用.而private只能用于类的构造之中.对于一个action,它将调用业务层来处理数据,完成

逻辑操作!这里用到了sysConfig,userService,loginErrorService,userOnlineService,在这个action类

中提供get/set,由spring的applicationContext.xml注入!我们先看
<bean id="sysConfig"
  class="com.laoer.bbscs.service.config.SysConfig">
  <constructor-arg>
   <ref bean="configService" />
  </constructor-arg>
  <property name="isLoad">
   <value>${bbscs.isloadconfig}</value> //bbscs.isloadconfig=false
  </property>
 </bean>
而我们看<bean id="configService" parent="txProxyTemplate"> //其它如userService都类似哦!!
  <property name="target">
   <ref bean="configTarget" />
  </property>
 </bean>
而txProxyTemplate是一个事务处理的TransactionProxyFactoryBean:
<bean id="txProxyTemplate" abstract="true"
  

class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean">
  <property name="transactionManager">
   <ref bean="myTransactionManager" />
  </property>
  <property name="transactionAttributes"> //对于如下的内容进行事务
   <props>
    <prop key="create*">
     PROPAGATION_REQUIRED,-

com.laoer.bbscs.exception.BbscsException
    </prop>
    <prop key="save*">
     PROPAGATION_REQUIRED,-

com.laoer.bbscs.exception.BbscsException
    </prop>
    <prop key="remove*">
     PROPAGATION_REQUIRED,-

com.laoer.bbscs.exception.BbscsException
    </prop>
    <prop key="update*">
     PROPAGATION_REQUIRED,-

com.laoer.bbscs.exception.BbscsException
    </prop>
    <prop key="del*">
     PROPAGATION_REQUIRED,-

com.laoer.bbscs.exception.BbscsException//出错,报BbscsException
    </prop>
    <prop key="*">PROPAGATION_REQUIRED,readOnly</prop>//只读
   </props>
  </property>
 </bean>
-->
<bean id="myTransactionManager"  //事务管理器
  class="org.springframework.orm.hibernate3.HibernateTransactionManager">
  <property name="sessionFactory">
   <ref local="sessionFactory" />
  </property>
 </bean>
-->
<bean id="sessionFactory"  //session工厂
  class="org.springframework.orm.hibernate3.LocalSessionFactoryBean">
  <property name="dataSource">
   <ref local="dataSource" /> //dataSource!!数据源bean.
  </property>
  <property name="mappingResources">
   <list>
    <value>com/laoer/bbscs/bean/UserInfo.hbm.xml</value>
     ........
    <value> com/laoer/bbscs/bean/Elite-{datasource.type}.hbm.xml
    </value>
   </list>
  </property>
  <property name="hibernateProperties">
   <props>
    <prop key="hibernate.dialect">
     ${hibernate.dialect}
    </prop>
    <prop key="hibernate.show_sql">
     ${hibernate.show_sql}
    </prop>
    <prop key="hibernate.jdbc.fetch_size">
     ${hibernate.jdbc.fetch_size}
    </prop>
    <prop key="hibernate.jdbc.batch_size">
     ${hibernate.jdbc.batch_size}
    </prop>
   </props>
  </property>
 </bean>
OK!我们回到login.bbscs?action=check,这将getAction()-->check!首先它将执行execute方法:
public String execute() {
  this.setUrlRewrite(Constant.USE_URL_REWRITE); //public static boolean

USE_URL_REWRITE = false;
  this.setUserAuthCodeValue();
  ....
接下来,根据if (this.getAction().equalsIgnoreCase("index")) {...
}if (this.getAction().equalsIgnoreCase("admin")) {..
}if (this.getAction().equalsIgnoreCase("login")) {
   return this.login();
  }

  if (this.getAction().equalsIgnoreCase("check")) {
   return this.check();
  }
来进行流程的选择(这就是所为的逻辑吧)!
public String check() { //对cookie的检测!
  if (StringUtils.isNotBlank(this.getUserCookie().getUserName())
    && StringUtils.isNotBlank(this.getUserCookie().getPasswd()))

{
   return this.cookieLogin();//有cookie
  } else {
   return this.index();
  }
 }
--->
 public String index() {
  this.setAction("login");
  this.setHiddenLogin(0);
  if (Constant.USE_URL_REWRITE) {
   tourl = this.getBasePath() + "main.html"; //注意,曾经有sb.append

("/");
  } else {
   tourl = this.getBasePath() +

BBSCSUtil.getActionMappingURLWithoutPrefix("main");//此工具方法加后缀main.bbscs
  }

  return this.input();
 }
-->
 public String input() {//是否要登录
  if (this.getSysConfig().getUsePass() == 0) {//usePass=1表示使用通行证登录
   return

INPUT;//action=login,hiddenLogin=0,tourl=XXXX:80/main.bbscs,urlRewrite=false,userAuthCodeVal

ue,注意到:
 private boolean urlRewrite = false;
 private boolean useAuthCode = true;
 private int cookieTime = -1;
  还有basePath,remoteAddress,UserCookie,以及两组List等等} else {
   this.setActionUrl(this.getSysConfig().getPassUrl

());//PassUrl=http://www.laoer.com/login.do
   return "loginPass";
  }
 }
我们返回struts.xml中可以找到它将result到哪里:
   <result name="success" type="redirect">${tourl}</result>
   <result name="input">/WEB-INF/jsp/login.jsp</result>
   <result name="loginPass">/WEB-INF/jsp/passLogin.jsp</result>
好,我们已经INPUT到/WEB-INF/jsp/login.jsp界面中了:
<%@page contentType="text/html; charset=UTF-8"%>
<%@taglib uri="/WEB-INF/struts-tags.tld" prefix="s"%>
<%@taglib uri="/WEB-INF/bbscs.tld" prefix="bbscs"%>
另外还有<%@ taglib uri="/WEB-INF/FCKeditor.tld" prefix="FCK" %>
其中的,s是struts2的,而bbscs是本系统的...
<%@page contentType="text/html; charset=UTF-8"%>
<%
  String path = request.getContextPath();
  String basePath = request.getScheme() + "://" + request.getServerName() + ":" +

request.getServerPort() + path + "/"; //basePath!
%>

其中,bbscs:webinfo是网站信息用的标签!<title><bbscs:webinfo type="forumname"/> - <s:text

name="login.title"/><bbscs:webinfo type="poweredby"/></title>
 <tag>
  <name>webinfo</name>
  <tag-class>com.laoer.bbscs.web.taglib.WebInfoTag</tag-class>//WebInfoTag
  <body-content>empty</body-content> //无内容的!
  <attribute>
   <name>type</name>
   <required>true</required>
   <rtexprvalue>true</rtexprvalue> //run time expression value运行时表

达式
  </attribute>
 </tag>
我们进入WebInfoTag一看:它有一个属性type及其get/set方法
 public Component getBean(ValueStack arg0, HttpServletRequest arg1,

HttpServletResponse arg2) {
  return new WebInfo(arg0, pageContext);  //构造一个Component
 }

 protected void populateParams() {
  super.populateParams();
  WebInfo tag = (WebInfo) component;
  tag.setType(type);
 }
而WebInfo实现了Component接口,它的构造方法:
public WebInfo(ValueStack stack, PageContext pageContext) {
  super(stack);
  this.pageContext = pageContext;
 }
这里关键的地方是:
public boolean start(Writer writer) {
  boolean result = super.start(writer);
  WebApplicationContext wc =

WebApplicationContextUtils.getWebApplicationContext(this.pageContext
    .getServletContext()); //调用服务层
  SysConfig sysConfig = (SysConfig) wc.getBean("sysConfig");//这里主要用了

sysConfg
  StringBuffer sb = new StringBuffer();
  if (this.getType().equalsIgnoreCase("forumname")) { //type="forunname"
   sb.append(sysConfig.getForumName());
  }

  if (this.getType().equalsIgnoreCase("poweredby")) {//type="poweredby"
   sb.append(" - ");
   sb.append("Powered By BBS-CS[天乙社区]");
  }

  if (this.getType().equalsIgnoreCase("meta")) {//type="meta"
   sb.append("<meta name="keywords" content="");
   sb.append(sysConfig.getMetaKeywords());
   sb.append(""/> ");
   sb.append("<meta name="description" content="");
   sb.append(sysConfig.getMetaDescription());
   sb.append(""/>");
  }
  if (this.getType().equalsIgnoreCase("pagefoot")) {//type="pagefoot"
   Locale locale = this.pageContext.getRequest().getLocale();
   ResourceBundleMessageSource messageSource =

(ResourceBundleMessageSource) wc.getBean("messageSource");//从消息资源文件获得
   if (StringUtils.isNotBlank(sysConfig.getWebName())) {
    if (StringUtils.isNotBlank(sysConfig.getWebUrl())) {
     sb.append("<a href="");
     sb.append(sysConfig.getWebUrl());
     sb.append("" target="_blank">");
     sb.append(sysConfig.getWebName());
     sb.append("</a>");
    } else {
     sb.append(sysConfig.getWebName());
    }
   }
   if (StringUtils.isNotBlank(sysConfig.getForumName())) {
    sb.append(" | ");
    if (StringUtils.isNotBlank(sysConfig.getForumUrl())) {
     sb.append("<a href="");
     sb.append(sysConfig.getForumUrl());
     sb.append("" target="_blank">");
     sb.append(sysConfig.getForumName());
     sb.append("</a>");
    } else {
     sb.append(sysConfig.getForumName());
    }
   }
   if (StringUtils.isNotBlank(sysConfig.getWebmasterEmail())) {
    sb.append(" | ");
    sb.append("<a href="mailto:");
    sb.append(sysConfig.getWebmasterEmail());
    sb.append("">");
    sb.append(messageSource.getMessage("bbscs.contactus", null,

locale));
    sb.append("</a>");
   }
   if (StringUtils.isNotBlank(sysConfig.getPrivacyUrl())) {
    sb.append(" | ");
    sb.append("<a href="");
    sb.append(sysConfig.getPrivacyUrl());
    sb.append("" target="_blank">");
    sb.append(messageSource.getMessage("bbscs.privacy", null,

locale));
    sb.append("</a>");
   }
   if (StringUtils.isNotBlank(sysConfig.getCopyRightMsg())) {
    sb.append("<BR/>");
    sb.append(sysConfig.getCopyRightMsg()); //加入版权信息
   }
   sb.append("<BR/>");
   sb.append("<strong><font face="Tahoma" size="1" color="#A0A0A4

">");
   sb.append("Powered By ");
   sb.append("<a href="http://www.laoer.com" target='_blank'>BBS-

CS</a>");
   sb.append(" V");
   sb.append(Constant.VSERION);
   sb.append(" &copy; 2007</font></strong>");
  }

  try {
   writer.write(sb.toString()); //输入
  } catch (IOException e) {
   e.printStackTrace();
  }

  return result;
 }
我们看login.jsp中,有许多struts2的标签,如s:form s:hidden s:text s:actionerror s:textfield

s:if s:else  s:radio s:submit s:submit s:url (样式用cssClass)对于具体的使用请

看:http://www.blogjava.net/max/archive/2006/10/18/75857.html
注意<img alt="<s:text name="login.authcode"/>" src="authimg" align="absmiddle" />是从authimg

得到图片的(注意这里是相对URL)
也注意到:s:actionerror也用到了template.bbscs0 <s:actionerror theme="bbscs0"/>
<#if (actionErrors?exists && actionErrors?size > 0)>
 <div class="errormsg">
 <#list actionErrors as error>
  <span class="errorMessage">${error}</span><br/>
 </#list>
 </div>
</#if>
注意到struts.properties文件中:
struts.ui.theme=simple
//struts.ui.templateDir=template 默认
//struts.ui.templateSuffix=ftl 默认
好的,我们提交表单,进入login.bbscs,还是最终达到Login.java
public String execute() {
  this.setUrlRewrite(Constant.USE_URL_REWRITE);
  this.setUserAuthCodeValue();
-->
private void setUserAuthCodeValue() {
  this.setUseAuthCode(this.getSysConfig().isUseAuthCode()); //=true
 }
if (this.getAction().equalsIgnoreCase("login")) {
   return this.login();
  }
--->
public String login() {
  if (StringUtils.isBlank(this.username) || StringUtils.isBlank(this.passwd))

{ //输入的帐号和密码是否为否
   this.addActionError(this.getText("error.nullerror"));
   return INPUT;
  }

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

());//查找有没有这个用户
  if (ui == null) {
   this.addActionError(this.getText("error.user.notexist"));
   return INPUT;
  }
  if (this.getSysConfig().isUseSafeLogin()) { //是否安全登录模式(例如3次登录机

会)
   if (this.getLoginErrorService().isCanNotLogin(ui.getId())) {
    this.addActionError(this.getText("error.login.times"));
    return INPUT;
   }
  }

  if (!Util.hash(this.getPasswd()).equals(ui.getRePasswd())) { // 密码错误
/**
public synchronized static final String hash(String data) {
  if (digest == null) {
   try {
    digest = MessageDigest.getInstance("MD5");
   } catch (NoSuchAlgorithmException nsae) {
    System.err
      .println("Failed to load the MD5

MessageDigest. " + "We will be unable to function normally.");
    nsae.printStackTrace();
   }
  }
  // Now, compute hash.
  digest.update(data.getBytes());
  return encodeHex(digest.digest());
 }

*/
   if (this.getSysConfig().isUseSafeLogin()) {
    try {
     this.getLoginErrorService

().createLoginErrorui.getId());//加入错误服务中!
    } catch (BbscsException ex1) {
     logger.error(ex1);
    }
   }
   this.addActionError(this.getText("error.login.passwd"));
   return INPUT;
  }

  if (this.getSysConfig().isUseAuthCode()) { //使用验证码
   String cauthCode = this.getUserCookie().getAuthCode();//Cookie中得到

AuthCode!

   if (StringUtils.isBlank(cauthCode) || !cauthCode.equals

(this.getAuthCode())) {
    this.addActionError(this.getText("error.login.authcode"));
    return INPUT;
   }
  }

  ui.setLastLoginIP(ui.getLoginIP());//上一次的
  ui.setLastLoginTime(ui.getLoginTime());//上一次

  ui.setLoginIP(this.getRemoteAddr());
  ui.setLoginTime(new Date()); //时间类型哦
  ui.setUserLocale(this.getLocale().toString());

  long nowTime = System.currentTimeMillis();
  UserOnline uo = new UserOnline();
  uo.setAtPlace("");
  uo.setBoardID(0);
  uo.setNickName(ui.getNickName());
  uo.setOnlineTime(nowTime);//long类型的时间
  uo.setUserGroupID(ui.getGroupID());
  uo.setUserID(ui.getId());
  uo.setUserName(ui.getUserName());
  uo.setValidateCode(ui.getId() + "_" + nowTime);//构造出来的,用于避免重复登录

吧!
  if (this.getHiddenLogin() == 1 || ui.getHiddenLogin() == 1) { // 用户隐身登


   uo.setHiddenUser(1);
  }

  try {
   ui = this.getUserService().saveAtLogin(ui); // 用户登录处理
/**
public UserInfo saveAtLogin(UserInfo userInfo) throws BbscsException {
  try {
   if ((System.currentTimeMillis() - userInfo.getLastLoginTime

().getTime()) > 30 * 60000) {
    userInfo.setLoginTimes(userInfo.getLoginTimes() + 1);//不一

样吧!
    userInfo.setExperience(userInfo.getExperience() + 1);
   }
   userInfo = this.getUserInfoDAO().saveUserInfo(userInfo);
   this.getUserInfoFileIO().writeUserFile(userInfo);
   return userInfo;
  } catch (Exception ex) {
   logger.error(ex);
   throw new BbscsException(ex);
  }
 }
*/
   uo = this.getUserOnlineService().createUserOnline(uo); // 加入在线用

户表
  } catch (BbscsException ex) {
   logger.error(ex);
   return INPUT;
  }

  UserSession us = userService.getUserSession(ui);
/**
 public UserSession getUserSession(UserInfo ui) {
  UserSession us = new UserSession();
  us.setEmail(ui.getEmail());
  us.setGroupID(ui.getGroupID());
  us.setId(ui.getId());
  us.setNickName(ui.getNickName());
  String[] signDetail = new String[3];
  signDetail[0] = ui.getSignDetail0() == null ? "" : ui.getSignDetail0();
  signDetail[1] = ui.getSignDetail1() == null ? "" : ui.getSignDetail1();
  signDetail[2] = ui.getSignDetail2() == null ? "" : ui.getSignDetail2();
  us.setSignDetail(signDetail);
  us.setUserName(ui.getUserName());
  us.setLastActiveTime(System.currentTimeMillis());

  Map[] permissionMap = this.getUserPermission(ui);

  us.setUserPermissionArray(permissionMap);
-->
/**
public Map[] getUserPermission(UserInfo userInfo) {
  return this.getUserPermission(userInfo.getGroupID());
 }
*/
  return us;
 }
*/

  us.setValidateCode(uo.getValidateCode());//Session的validateCode改变之
  
  this.getSession().put(Constant.USER_SESSION_KEY, us);
放入本Login本关的Session中!public static final String USER_SESSION_KEY = "user_session";这里

我们可以简单的看一下UserSession的处理,好象我们以前讲过吧,这里重新讲一次:
 private String userName = "";
 private String id = "";
 private String nickName = "";
 private String email = "";
 private long lastActiveTime = 0;
 private Map userPermission = new HashMap();
 private Map boardPermission = new HashMap();
 private Map specialPermission = new HashMap();
 private Map boardSpecialPermission = new HashMap();
 private long bid = 0;
 private int groupID = 0;
 private long addedOnlineTime = 0;
 private long addedOnlineHour = 0;
 private String validateCode = "";
 private String[] signDetail = { "", "", "" };
 private String boardPass = "";
 private int initStatus = 0;
这些是它的属性,当然也有get/set;上面的us.setValidateCode就是这样工作的..我们这里重点看下:
us.setUserPermissionArray(permissionMap);
public void setUserPermissionArray(Map[] permissionMap) {
  setSpecialPermission(permissionMap[1]); //特别的权力!
/**
public void setSpecialPermission(Map specialPermission) {
  this.specialPermission = specialPermission;
 }
而它是通过根据Permission的TypeID确定的:
 Permission permission = (Permission) permissionList.get(i);
     if (permission.getTypeID() == 0) {
       userPermission[0].put

(permission.getResource() + "," + permission.getAction(), permission);
      } else {
       userPermission[1].put

(permission.getId(), permission);
      }
*/
  Set pset = permissionMap[0].entrySet();//Map的遍历哦!
  Iterator it = pset.iterator();
  while (it.hasNext()) {
   Map.Entry p = (Map.Entry) it.next();
   Permission permission = (Permission) p.getValue();//getValue
   String[] actions = permission.getAction().split(",");
   for (int i = 0; i < actions.length; i++) {
    String[] resources = ((String) p.getKey()).split

(",");//getKey
    this.getUserPermission().put(resources[0] + "?action=" +

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

 }
  this.getUserCookie().removeAuthCode(); //Cookie的authCode改变
  this.getUserCookie().addCookies(ui);
  // this.getUserCookie().addValidateCode(uo.getValidateCode());
  if (this.getCookieTime() != -1) {
   this.getUserCookie().addC("U", this.getUsername(),

this.getCookieTime());
   this.getUserCookie().addDES("P", Util.hash(this.getPasswd()),

this.getCookieTime());//这里对UserSession和UserCookie都进行了改变...
  }

  return SUCCESS;
 }
我们知道在进入Login之前,已经对UserCookie进行了操作:
UserCookie userCookie = new UserCookie(request, response, sysConfig);
((UserCookieAware) action).setUserCookie(userCookie);
看下面授代码:
public UserCookie(HttpServletRequest request, HttpServletResponse response, SysConfig

sysConfig) {
  this.request = request;
  this.response = response;

分享到:
评论
1 楼 fengzi1 2008-12-04  
 

相关推荐

    对天乙社区bbscs8实现的详细分析一(附文档下载)

    【标题】"对天乙社区bbscs8实现的详细分析一(附文档下载)" 提供的是关于一个名为“天乙社区”的BBS系统——BBScs8的深入解析。这个系列的分析文档可能涵盖了该社区平台的架构设计、功能模块、代码结构以及可能涉及到...

    [论坛社区]天乙社区修改版_bbscs7.rar

    【描述】:“[论坛社区]天乙社区修改版_bbscs7.rar”的描述简洁,主要强调了这是天乙社区的一个修改版,可能包含了开发者或用户对原版论坛功能、界面或性能的改进。RAR是一种常见的文件压缩格式,通常用于打包多个...

    天乙社区开源代码(strut+strping+hibernate)

    下面将对这三个关键技术进行详细解析。 **1. Struts框架** Struts是Apache组织开发的一款MVC(Model-View-Controller)架构的Java Web框架。在天乙社区项目中,Struts负责处理HTTP请求,协调控制器、模型和视图...

    BBSCS_5_3_1.rar_bbs struts_bbs系统_jsp bbs down_天乙社区_虚拟社区

    【BBSCS_5_3_1.rar_bbs struts_bbs系统_jsp bbs down_天乙社区_虚拟社区】 本项目是基于BBS(Bulletin Board System,电子公告板)理念,采用Struts框架,结合JSP、JavaBean和Servlet技术构建的一款网络虚拟社区...

    天乙社区6.0(含源码)

    而"BBSCS_6_0"可能代表了天乙社区6.0的主要应用文件或数据库文件。这可能是一个可执行文件,用于启动社区平台,或者是包含所有社区数据的数据库文件。对于开发者而言,这个文件可以用于测试和调试,也可以作为构建...

    [论坛社区]天乙社区修改版_bbscs7.zip

    【标题】"天乙社区修改版_bbscs7.zip"是一个包含Java JSP应用源码的压缩包,专为学生毕业设计学习而准备。这个压缩文件可能是某个开发者或团队为了帮助初学者理解Web应用程序开发,特别是Java Web技术,如JSP(Java...

    天乙社区系统(Struts+Spring+Hibernate)源代码

    5. **BBSCS_6_0_4**:这个文件名可能是天乙社区系统的一个特定版本,其中可能包含了系统的核心模块,如用户管理、帖子发布、评论功能等。开发者可以通过分析源代码,学习SSH框架的实际应用,了解如何将这些技术组件...

    天乙社区6.0(Struts+hibernate+spring)源码

    《天乙社区6.0:Struts、Hibernate与Spring整合深度解析》 在软件开发领域,Struts、Hibernate和Spring是三个非常重要的框架,它们分别在MVC(模型-视图-控制器)架构、对象关系映射(ORM)以及依赖注入(DI)和...

    天乙社区论坛源码,适合初级SSH向中等水平的进阶学习资料--源代码

    本源码是在天乙社区论坛基础上的二次开发,实战型项目的源码,适合初级SSH开发的进阶学习。由于源码比较大且一次上传大小有限制,所以分作三个压缩文件。 bbscs7.rar源代码包 lib1.rar(天乙社区论坛源码,适合初级...

    天乙6.0论坛源码,采用s1sh框架

    《天乙6.0论坛源码解析:基于SSH框架的J2EE社区构建》 天乙6.0论坛是一款基于Java技术栈开发的社区软件,其核心架构采用了经典的Struts1、Spring和Hibernate(SSH)框架,这在当时是相当流行的企业级应用开发组合。...

    bbs-cs 天乙社区 v6.0.1(含源码)

    这三项,根据你的实际情况修改,但注意useUnicode=true&characterEncoding=UTF-8不能修改。 Oracle安装 用bbscs6_oracle.sql建表(Oracle9请用bbscs6_oracle9.sql建表) datasource.driverClassName=oracle.jdbc....

    天乙社区论坛源码,适合初级SSH向中等水平的进阶学习资料--lib1.rar

    本源码是在天乙社区论坛基础上的二次开发,实战型项目的源码,适合初级SSH开发的进阶...bbscs7.rar源代码包(天乙社区论坛源码,适合初级SSH向中等水平的进阶学习资料--源代码) lib1.rar和lib.rar是项目需要的jar包。

    BBSCS_5_3_1.rar_javaBean mysql_oracle

    一套Web式网络社区软件,天乙社区采用JSP+JavaBean构架,后台可以使用MYSQL、Oracle、SQL Server等多种数据库,适用于Linux/UNIX、Windows等多种操作系统,具有界面简洁、功能强大、操作方便等特点

    bbs论坛(SSH)

    从【压缩包子文件的文件名称列表】"天乙社区bbsI(ssh)"来看,这应该是一个名为“天乙社区”的BBS论坛的源代码包,包含了SSH框架的具体实现。用户在使用这个项目时,需要将提供的数据库脚本`bbscs7.sql`导入到MySQL...

    bbs 论坛程序

    天乙社区论坛可能是一个开源的论坛软件,开发者或团队对其原始代码进行了修改和优化,以满足特定需求或提供额外的功能。二次开发通常包括界面改进、性能优化、增加新特性以及修复已知问题等方面的工作。 文件...

Global site tag (gtag.js) - Google Analytics