經過前面的分析,我們已經理清楚了業務層,接下來的部分將是web層部分.首先我們從web.xml開始,我們知
道任何一個java web應用系統都是從WEB-INF/web.xml啓動的,根據servlet2.4規範filter執行是按照
web.xml配置的filter-mapping先後順序進行執行,這裏用了UrlRewriteFilter和字符編碼過濾器
CharacterEncodingFilter(UTF-8),還有配置延遲加載時使用OpenSessionInView,可參考資料
http://www.javaeye.com/topic/32001;另外,還有struts-clearup,以及Strut2的
org.apache.struts2.dispatcher.FilterDispatcher,注意它有兩個dipatcher參數, 在這種情況下,如
果請求是以/*開頭的,並且是通過request dispatcher的forward方法傳遞過來或者直接從客戶端傳遞
過來的,則必須經過這個過濾器, Servlet 2.3 版新增了Filter的功能,不過它只能由客戶端發出請求
來調用Filter,但若使用 RequestDispatcher.forward( )或RequestDispatcher.include( )的方法調
用Filter 時,Filter 卻不會執行.這個功能應該都是主要用於UrlRewrite用的吧.而<context-param>元
素定義了一些應用級的參數,如:urlrewrite,cluster都爲false,servletmapping爲
*.bbscs,poststoragemode爲1;接下來是listener有兩個,一是
com.loaer.bbscs.web.servlet.SysListener就是對上面的變量進行操作的一個監聽器,而另一個則是
spring的ContextLoaderListener,這裏我們不討論,接下來是幾個servlet,提供一些常用的功能:authimg
生成驗證碼,rss.另外還有welcome-file及struts-tag.tld的taglib和一些error-page,如:error-
code:401--->location:-->/401.htm.對於具體的加載過程可見啓動resin3等服務器後的控制檯顯示,我個
人覺得是加載了應用級參數,再是2個Linstener..,到spring-->加載所有bean到時空器-->各個bean的加載
(包括hibernate的配置等)--->ContextLoader啓動完成-->接下來,對Filter按相反的順序加載(struts-
>struts-clean-->characterEncoding)先是struts2的配置文件的加載,還有global messages...
注意這段時間內也會啓動一些TimerTask任務...,這點可從日誌中看到,最後是
ObjectTypeDeterminerFactory應該是用於struts-clean吧.還有
OpenSessionInViewFilter,CharacterEncodingFilter,UrlRewriteFilter...
這樣,應用和resin服務纔開始發揮作用,才能訪問!
好的,我們先看在com.laoer.bbscs.web.servlet包中的幾個源文件:
SysListener:它繼承自HttpServlet,實現了ServletContextLinster接口.
String rootpath = sce.getServletContext().getRealPath("/");
if (rootpath != null) {
rootpath = rootpath.replaceAll("//", "/");
} else {
rootpath = "/";
}
if (!rootpath.endsWith("/")) {
rootpath = rootpath + "/";
}
Constant.ROOTPATH = rootpath; 記錄在常量java文件中.public static String
ROOTPATH = "";
我們看一個代表性的示例源碼:
String poststoragemodes = sce.getServletContext().getInitParameter("poststoragemode");
if (poststoragemodes == null) {
poststoragemodes = "0";
}
Constant.POST_STORAGE_MODE = NumberUtils.toInt(poststoragemodes, 0);//文章存
儲格式(這裏是文件)
-->
<context-param>
<param-name>poststoragemode</param-name>
<param-value>1</param-value>
</context-param>
接下來,我們分析AuthImg,主要用它的doGet方法:
public void doGet(HttpServletRequest request, HttpServletResponse response) throws
ServletException, IOException {
response.setContentType("image/jpeg");
ServletOutputStream out = response.getOutputStream();
int width = 60, height = 20;
BufferedImage image = new BufferedImage(width, height,
BufferedImage.TYPE_INT_RGB);
//這段代碼創建了一個 BufferedImage 對象,它代表一個 60像素寬、20像素高的圖像。爲了應用這個
圖像,我們需要有圖形上下文,而 BufferedImage 對象的 createGraphics() 方法就返回一個與該圖像
相關的 Graphics2D 對象:
Graphics g = image.getGraphics();
Random random = new Random();
g.setColor(getRandColor(200, 250));
g.fillRect(0, 0, width, height); //背景色
g.setFont(mFont); //字體private Font mFont = new Font("Times New Roman",
Font.PLAIN, 18);
g.setColor(getRandColor(160, 200));
for (int i = 0; i < 155; i++) {
int x = random.nextInt(width);
int y = random.nextInt(height);
int xl = random.nextInt(12);
int yl = random.nextInt(12);
g.drawLine(x, y, x + xl, y + yl);//畫線155條
}
String rand = RandomStringUtils.randomNumeric(4);//產生四個隨機數
char c;
for (int i = 0; i < 4; i++) {
c = rand.charAt(i);
g.setColor(new Color(20 + random.nextInt(110), 20 + random.nextInt
(110), 20 + random.nextInt(110))); //各個數的着色不一樣
g.drawString(String.valueOf(c), 13 * i + 6, 16);
}
WebApplicationContext wc =
WebApplicationContextUtils.getWebApplicationContext(this.getServletContext());//
webapplicationcontextutils.getwebapplicationcontext(servletcontext); 如果沒有直接返回null
SysConfig sysConfig = (SysConfig) wc.getBean("sysConfig");//web層得到
sysConfig
UserCookie uc = new UserCookie(request, response, sysConfig);//寫入Cookie中
uc.addAuthCode(rand);
JPEGImageEncoder encoder = JPEGCodec.createJPEGEncoder(out);//也可以用
ImageIO.write(bi,"jpg",out);
encoder.encode(image);
out.close();
}
接下來,看RSS:它也是用在doGet,有兩種,一種是首頁,不帶bid參數,一種是帶bid.用於各個版區的...
long bid;
try {
bid = Long.parseLong(request.getParameter("bid"));
} catch (NumberFormatException e) {
bid = 0L;
}
首先爲了使用服務,這裏用了spring的WebApplicationContext wc =
WebApplicationContextUtils.getWebApplicationContext(this.getServletContext());這樣我們就可以
得到ForumService和SysConfig,BoardService..這裏用了下SyndFeed feed=new SynFeedImpl();
com.sun.syndication這個包,接着便是feed.setFeedType("rss_2.0");先設置好feed源的標題/鏈接(有
兩種,分有bid和沒bid)/描述,注意這些參數大多來自於sysConfig,接下來,我們設置entry(條目)及其
description
List<SyndEntry> entries = new ArrayList<SyndEntry>();
SyndEntry entry;
SyndContent description;
我們看後半部分的代碼:
Board board = boardService.getBoardByID(bid);//得到board
if (board != null) {
if (board.getBoardType() == 2 || board.getBoardType() == 3)
{
String forumLink = "";
try {
forumLink = BBSCSUtil.absoluteActionURL
(request, "/forum.bbscs?action=index&bid=" + bid)
.toString();//RSS源鏈接
} catch (MalformedURLException ex2) {
forumLink = "";
}
feed.setTitle(sysConfig.getForumName() + " - " +
board.getBoardName());
feed.setLink(forumLink);
feed.setDescription(sysConfig.getWebName() + " - " +
sysConfig.getForumName() + " - "
+ board.getBoardName());
List<SyndEntry> entries = new ArrayList<SyndEntry>
();//所有條目
SyndEntry entry;
SyndContent description;
Pages pages = new Pages();//構造一個Pages對象
pages.setPage(1);
pages.setPerPageNum(40);
pages.setFileName("");
PageList pl = forumService.findForumsMainWWW(bid,
pages);//重點的地方
List flist = pl.getObjectList();
for (int i = 0; i < flist.size(); i++) {
Forum f = (Forum) flist.get(i);
try {
postLink =
BBSCSUtil.absoluteActionURL(request,
"/main.bbscs?
action=read&bid=" + f.getBoardID() + "&postID=" + f.getMainID())
.toString();
} catch (MalformedURLException ex) {
postLink = "";
}
entry = new SyndEntryImpl();
entry.setTitle(f.getTitle());
entry.setLink(postLink);
entry.setPublishedDate(new Date
(f.getPostTime()));
description = new SyndContentImpl();
if (f.getEditType() == 0) {
description.setType("text/plain");//
文本類型
} else {
description.setType("text/html");
}
description.setValue(BBSCSUtil
.getSpeShortString
(forumService.getForumDetail(f, false), 400, ""));//格式化
entry.setDescription(description);
entries.add(entry);
}
feed.setEntries(entries);
try {
SyndFeedOutput output = new SyndFeedOutput
();
Document messagesDocument =
output.outputJDom(feed);
Format format = Format.getPrettyFormat();
format.setOmitDeclaration(true);
XMLOutputter xmlo = new XMLOutputter
(format);
xmlo.output(messagesDocument, out);
} catch (Exception ex) {
logger.error(ex);
}
}
}
其中:
public static URL absoluteActionURL(HttpServletRequest request, String action) throws
MalformedURLException {
return new URL(RequestUtils.serverURL(request) + getActionMappingURL(action,
request));
}
--->
public static String getActionMappingURL(String action, HttpServletRequest request) {
StringBuffer value = new StringBuffer(request.getContextPath());//獲得的當前
目錄路徑
// Use our servlet mapping, if one is specified
String servletMapping = Constant.SERVLET_MAPPING;//*.bbscs
if (servletMapping != null) {
String queryString = null;
int question = action.indexOf("?");//action="/main.bbscs?
action=read&bid=" + f.getBoardID() + "&postID=" + f.getMainID()).toString();
if (question >= 0) {
queryString = action.substring(question);//?
action=read&bid=12&postID=123123
}
String actionMapping = getActionMappingName(action);// /main
if (servletMapping.startsWith("*.")) {
value.append(actionMapping);
value.append(servletMapping.substring(1));
//value=/main.bbcs
} else if (servletMapping.endsWith("/*")) {
value.append(servletMapping.substring(0,
servletMapping.length() - 2));
value.append(actionMapping);
} else if (servletMapping.equals("/")) {
value.append(actionMapping);
}
if (queryString != null) {
value.append(queryString);
}
}
--->
public static String getActionMappingName(String action) {
String value = action;
int question = action.indexOf("?");
if (question >= 0) {
value = value.substring(0, question);// /main.bbscs
}
int slash = value.lastIndexOf("/");
int period = value.lastIndexOf(".");
if ((period >= 0) && (period > slash)) {
value = value.substring(0, period);// /main
}
if (value.startsWith("/")) {
return (value);
} else {
return ("/" + value);
}
}
OK!接下來,我們看看登錄流程先開始分析吧!
在瀏覽器在輸入http://localhost:8080/bbscs8啓動:
<welcome-file-list>
<welcome-file>index.jsp</welcome-file> //相對當前目錄哦!!!
</welcome-file-list>
index.jsp觸發了action login.bbscs?action=check:
<script language="javascript" type="text/javascript">
window.location.href="login.bbscs?action=check";
</script>
</head>
-->struts2發生作用...
我們看下struts.properties:
struts.devMode=false
struts.action.extension=bbscs //後綴
struts.enable.DynamicMethodInvocation=true
struts.i18n.reload=true
struts.ui.theme=simple
struts.locale=zh_CN
struts.i18n.encoding=UTF-8
struts.objectFactory=spring //由spring來代理bean
struts.objectFactory.spring.autoWire=name
struts.serve.static.browserCache=false
struts.url.includeParams=none
struts.custom.i18n.resources=com.laoer.bbscs.web.action.BaseAction //資源文件!
看後臺的輸出日誌[com.opensymphony.xwork2.validator.ActionValidatorManagerFactory]-[INFO]
Detected AnnotationActionValidatorManager, initializing it... (注意:並不一定是這裏觸發哦!
接着我們看struts.xml,其中name爲bbscs-default的package繼承之struts-default,並引入了許多自定義
的interceptor和interceptor-stack.也設置了global-results...這些是爲它們package使用的.
<package name="loginout" extends="bbscs-default" namespace="/">
<action name="login" class="loginAction">
<result name="success" type="redirect">${tourl}</result>
<result name="input">/WEB-INF/jsp/login.jsp</result>
<result name="loginPass">/WEB-INF/jsp/passLogin.jsp</result>
<interceptor-ref name="defaultStack"></interceptor-ref>//一旦繼承了
struts-default包(package),所有Action都會調用攔截器棧 ——defaultStack。當然,在Action配置
中加入“<interceptor-ref name="xx" />”可以覆蓋defaultStack。
<interceptor-ref name="remoteAddrInterceptor"></interceptor-ref>
<interceptor-ref name="userCookieInterceptor"></interceptor-ref>
<interceptor-ref name="requestBasePathInterceptor"></interceptor-
ref>
</action>
</package>
注意這裏的login對應於spring配置文件action-servlet.xml中的loginAction:
<bean id="loginAction" class="com.laoer.bbscs.web.action.Login" //所管理的action bean
scope="prototype" autowire="byName"> //用了autowire就不用下面這段了
<!--
<property name="sysConfig">
<ref bean="sysConfig" />
</property>
-->
</bean>
好的,我們先看攔截器:<interceptor-ref name="defaultStack"></interceptor-ref>使用默認的攔截器
棧(在訪問被攔截的方法或字段時,攔截器鏈中的攔截器就會按其之前定義的順序被調用),我們可以打開
struts2.0core包找到struts-default.xml打開找到<interceptors>元素內容..請看資
料:http://www.blogjava.net/max/archive/2006/12/06/85925.html
<interceptor-stack name="defaultStack">
<interceptor-ref name="exception"/>
<interceptor-ref name="alias"/>
<interceptor-ref name="servletConfig"/>
<interceptor-ref name="prepare"/>
<interceptor-ref name="i18n"/>
<interceptor-ref name="chain"/>
<interceptor-ref name="debugging"/>
<interceptor-ref name="profiling"/>
<interceptor-ref name="scopedModelDriven"/>
<interceptor-ref name="modelDriven"/>
<interceptor-ref name="fileUpload"/>
<interceptor-ref name="checkbox"/>
<interceptor-ref name="staticParams"/>
<interceptor-ref name="params">
<param name="excludeParams">dojo..*</param>
</interceptor-ref>
<interceptor-ref name="conversionError"/>
<interceptor-ref name="validation">
<param name="excludeMethods">input,back,cancel,browse</param>
</interceptor-ref>
<interceptor-ref name="workflow">
<param name="excludeMethods">input,back,cancel,browse</param>
</interceptor-ref>
</interceptor-stack>
而自定義攔截器是要求攔截器是無狀態的原因是Struts 2不能保證爲每一個請求或者action創建一個實例
,所以如果攔截器帶有狀態,會引發併發問題。所有的Struts 2的攔截器都直接或間接實現接口
com.opensymphony.xwork2.interceptor.Interceptor。除此之外,大家可能更喜歡繼承類
com.opensymphony.xwork2.interceptor.AbstractInterceptor。需實現其public String intercept
(ActionInvocation invocation) throws Exception .
而下面的remoteAddrInterceptor:
<interceptor name="remoteAddrInterceptor"
class="com.laoer.bbscs.web.interceptor.RemoteAddrInterceptor">
</interceptor>
我們進入web.interceptor層:
public String intercept(ActionInvocation invocation) throws Exception {
ActionContext ac = invocation.getInvocationContext();
Object action = invocation.getAction();
HttpServletRequest request = (HttpServletRequest) ac.get
(ServletActionContext.HTTP_REQUEST);//得到request請求
String userRemoteAddr = request.getRemoteAddr();
if (action instanceof RemoteAddrAware) { //action是RomoteAddrAware實例?
((RemoteAddrAware)action).setRemoteAddr(userRemoteAddr);
//System.out.println(userRemoteAddr);
}
return invocation.invoke();
}
}
我們可以看到RemoteAddrAware是如下這個接口,這是爲了方便將遠程地址放入action中:
public interface RemoteAddrAware {
public void setRemoteAddr(String remoteAddr);
}
接下來是userCookieInterceptor:
<interceptor name="userCookieInterceptor"
class="com.laoer.bbscs.web.interceptor.UserCookieInterceptor">
</interceptor>
public String intercept(ActionInvocation invocation) throws Exception {
ActionContext ac = invocation.getInvocationContext();
Object action = invocation.getAction();
if (action instanceof UserCookieAware) {
HttpServletRequest request = (HttpServletRequest) ac.get
(ServletActionContext.HTTP_REQUEST); //用於UserCookie
HttpServletResponse response = (HttpServletResponse) ac.get
(ServletActionContext.HTTP_RESPONSE);//用於UserCookie
ServletContext servletContext = (ServletContext) ac.get
(ServletActionContext.SERVLET_CONTEXT);
WebApplicationContext wc =
WebApplicationContextUtils.getWebApplicationContext(servletContext);//得到業務層服務!
if (wc == null) {
logger.error("ApplicationContext could not be found.");
} else {
SysConfig sysConfig = (SysConfig) wc.getBean("sysConfig");
UserCookie userCookie = new UserCookie(request, response,
sysConfig); //關鍵點!!!!
//logger.debug("userCookie sid:" + userCookie.getSid());
((UserCookieAware) action).setUserCookie(userCookie);
}
}
return invocation.invoke();
}
而UserCookieAware:
public interface UserCookieAware {
public void setUserCookie(UserCookie userCookie);
}
看最後一個interceptor:requestBasePathInterceptor
public String intercept(ActionInvocation invocation) throws Exception {
ActionContext ac = invocation.getInvocationContext();
Object action = invocation.getAction();
if (action instanceof RequestBasePathAware) {
HttpServletRequest request = (HttpServletRequest) ac.get
(ServletActionContext.HTTP_REQUEST);
StringBuffer sb = new StringBuffer();
sb.append(BBSCSUtil.getWebRealPath(request));//得到域名
/**
public static String getWebRealPath(HttpServletRequest request) {
StringBuffer sb = new StringBuffer();
sb.append("http://");
sb.append(request.getServerName());
if (request.getServerPort() != 80) {
sb.append(":");
sb.append(request.getServerPort());
}
return sb.toString(); //返回域名啊!
}
*/
sb.append(request.getContextPath());//request相對路徑
sb.append("/");
((RequestBasePathAware) action).setBasePath(sb.toString());//設置
BasePath
}
return invocation.invoke();
}
其中,RequestBasePathAware:
public interface RequestBasePathAware {
public void setBasePath(String basePath);
}
我們回到public class Login extends BaseAction implements RequestBasePathAware,
RemoteAddrAware, UserCookieAware, SessionAware,可見這個Login實現了我們的這些Aware..並且它繼
承了BaseAction,而BaseAction繼承了ActionSupport,它有幾個通用的方
法:getAction,setAction,getAjax,setAjax,及page.total(本來私有的)的getter/setter方法,另外還有
以下方法:
protected String executeMethod(String method) throws Exception { //子類用!
Class[] c = null;
Method m = this.getClass().getMethod(method, c);
Object[] o = null;
String result = (String) m.invoke(this, o);
return result;
}
public int boolean2int(boolean value) {
if (value) {
return 1;
} else {
return 0;
}
}
public boolean int2boolean(int value) {
if (value == 0) {
return false;
} else {
return true;
}
}
有點類似C++了!true-->1 value!=0--->true
我們進入正題Login:
首先它需要一個靜態的logger:private static final Log logger = LogFactory.getLog(Login.class);
還有private static final long serivalVeserionUID...
當然,它需要get/set一下上面的basePath,remoteAddr,userCookie.另外還有一個session
作爲struts,它有與表單交互的字
段:actionUrl,tourl,passwd,username,hiddenLogin,authCode,urlRewrite,useAuthCode,cookieTime=-1
等及其getter/setter方法...注意:
public boolean isUseAuthCode() {
return useAuthCode;
}
另外,我們可以看到其構造方法中:
public Login() {
this.setRadioYesNoListValues();//隱身選擇是或否
this.setCookieTimeListValues();//Cookie時間選擇一年/一月/一天/瀏覽器進程
}
private void setRadioYesNoListValues() { //private的注意哦!!
radioYesNoList.add(new RadioInt(0, this.getText("bbscs.no")));//注意getText
從資源文件BaseAction中獲得字符串值!
radioYesNoList.add(new RadioInt(1, this.getText("bbscs.yes")));
}
private void setCookieTimeListValues() {
cookieTimeList.add(new RadioInt(365 * 24 * 3600, this.getText
("login.cookietime0")));//一年以365計算
cookieTimeList.add(new RadioInt(30 * 24 * 3600, this.getText
("login.cookietime1")));
cookieTimeList.add(new RadioInt(24 * 3600, this.getText
("login.cookietime2")));
cookieTimeList.add(new RadioInt(-1, this.getText("login.cookietime3")));
}
我們來看RadioInt(com.laoer.bbscs.web.ui):它是一個簡單的bean,封裝了兩個屬性int的key和String類
型的value,而公開其getter/setter方法,和下面的構造方法:
public RadioInt(int key, String value) {
this.key = key;
this.value = value;
}
當然,也有其List<RadioInt> radioYesNoList = new ArrayList<RadioInt>();
public List<RadioInt> getRadioYesNoList() {
return radioYesNoList;
}
public void setRadioYesNoList(List<RadioInt> radioYesNoList) {
this.radioYesNoList = radioYesNoList;
}
也於提供給界面用.而private只能用於類的構造之中.對於一個action,它將調用業務層來處理數據,完成
邏輯操作!這裏用到了sysConfig,userService,loginErrorService,userOnlineService,在這個action類
中提供get/set,由spring的applicationContext.xml注入!我們先看
<bean id="sysConfig"
class="com.laoer.bbscs.service.config.SysConfig">
<constructor-arg>
<ref bean="configService" />
</constructor-arg>
<property name="isLoad">
<value>${bbscs.isloadconfig}</value> //bbscs.isloadconfig=false
</property>
</bean>
而我們看<bean id="configService" parent="txProxyTemplate"> //其它如userService都類似哦!!
<property name="target">
<ref bean="configTarget" />
</property>
</bean>
而txProxyTemplate是一個事務處理的TransactionProxyFactoryBean:
<bean id="txProxyTemplate" abstract="true"
class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean">
<property name="transactionManager">
<ref bean="myTransactionManager" />
</property>
<property name="transactionAttributes"> //對於如下的內容進行事務
<props>
<prop key="create*">
PROPAGATION_REQUIRED,-
com.laoer.bbscs.exception.BbscsException
</prop>
<prop key="save*">
PROPAGATION_REQUIRED,-
com.laoer.bbscs.exception.BbscsException
</prop>
<prop key="remove*">
PROPAGATION_REQUIRED,-
com.laoer.bbscs.exception.BbscsException
</prop>
<prop key="update*">
PROPAGATION_REQUIRED,-
com.laoer.bbscs.exception.BbscsException
</prop>
<prop key="del*">
PROPAGATION_REQUIRED,-
com.laoer.bbscs.exception.BbscsException//出錯,報BbscsException
</prop>
<prop key="*">PROPAGATION_REQUIRED,readOnly</prop>//只讀
</props>
</property>
</bean>
-->
<bean id="myTransactionManager" //事務管理器
class="org.springframework.orm.hibernate3.HibernateTransactionManager">
<property name="sessionFactory">
<ref local="sessionFactory" />
</property>
</bean>
-->
<bean id="sessionFactory" //session工廠
class="org.springframework.orm.hibernate3.LocalSessionFactoryBean">
<property name="dataSource">
<ref local="dataSource" /> //dataSource!!數據源bean.
</property>
<property name="mappingResources">
<list>
<value>com/laoer/bbscs/bean/UserInfo.hbm.xml</value>
........
<value> com/laoer/bbscs/bean/Elite-{datasource.type}.hbm.xml
</value>
</list>
</property>
<property name="hibernateProperties">
<props>
<prop key="hibernate.dialect">
${hibernate.dialect}
</prop>
<prop key="hibernate.show_sql">
${hibernate.show_sql}
</prop>
<prop key="hibernate.jdbc.fetch_size">
${hibernate.jdbc.fetch_size}
</prop>
<prop key="hibernate.jdbc.batch_size">
${hibernate.jdbc.batch_size}
</prop>
</props>
</property>
</bean>
OK!我們回到login.bbscs?action=check,這將getAction()-->check!首先它將執行execute方法:
public String execute() {
this.setUrlRewrite(Constant.USE_URL_REWRITE); //public static boolean
USE_URL_REWRITE = false;
this.setUserAuthCodeValue();
....
接下來,根據if (this.getAction().equalsIgnoreCase("index")) {...
}if (this.getAction().equalsIgnoreCase("admin")) {..
}if (this.getAction().equalsIgnoreCase("login")) {
return this.login();
}
if (this.getAction().equalsIgnoreCase("check")) {
return this.check();
}
來進行流程的選擇(這就是所爲的邏輯吧)!
public String check() { //對cookie的檢測!
if (StringUtils.isNotBlank(this.getUserCookie().getUserName())
&& StringUtils.isNotBlank(this.getUserCookie().getPasswd()))
{
return this.cookieLogin();//有cookie
} else {
return this.index();
}
}
--->
public String index() {
this.setAction("login");
this.setHiddenLogin(0);
if (Constant.USE_URL_REWRITE) {
tourl = this.getBasePath() + "main.html"; //注意,曾經有sb.append
("/");
} else {
tourl = this.getBasePath() +
BBSCSUtil.getActionMappingURLWithoutPrefix("main");//此工具方法加後綴main.bbscs
}
return this.input();
}
-->
public String input() {//是否要登錄
if (this.getSysConfig().getUsePass() == 0) {//usePass=1表示使用通行證登錄
return
INPUT;//action=login,hiddenLogin=0,tourl=XXXX:80/main.bbscs,urlRewrite=false,userAuthCodeVal
ue,注意到:
private boolean urlRewrite = false;
private boolean useAuthCode = true;
private int cookieTime = -1;
還有basePath,remoteAddress,UserCookie,以及兩組List等等} else {
this.setActionUrl(this.getSysConfig().getPassUrl
());//PassUrl=http://www.laoer.com/login.do
return "loginPass";
}
}
我們返回struts.xml中可以找到它將result到哪裏:
<result name="success" type="redirect">${tourl}</result>
<result name="input">/WEB-INF/jsp/login.jsp</result>
<result name="loginPass">/WEB-INF/jsp/passLogin.jsp</result>
好,我們已經INPUT到/WEB-INF/jsp/login.jsp界面中了:
<%@page contentType="text/html; charset=UTF-8"%>
<%@taglib uri="/WEB-INF/struts-tags.tld" prefix="s"%>
<%@taglib uri="/WEB-INF/bbscs.tld" prefix="bbscs"%>
另外還有<%@ taglib uri="/WEB-INF/FCKeditor.tld" prefix="FCK" %>
其中的,s是struts2的,而bbscs是本系統的...
<%@page contentType="text/html; charset=UTF-8"%>
<%
String path = request.getContextPath();
String basePath = request.getScheme() + "://" + request.getServerName() + ":" +
request.getServerPort() + path + "/"; //basePath!
%>
其中,bbscs:webinfo是網站信息用的標籤!<title><bbscs:webinfo type="forumname"/> - <s:text
name="login.title"/><bbscs:webinfo type="poweredby"/></title>
<tag>
<name>webinfo</name>
<tag-class>com.laoer.bbscs.web.taglib.WebInfoTag</tag-class>//WebInfoTag
<body-content>empty</body-content> //無內容的!
<attribute>
<name>type</name>
<required>true</required>
<rtexprvalue>true</rtexprvalue> //run time expression value運行時表
達式
</attribute>
</tag>
我們進入WebInfoTag一看:它有一個屬性type及其get/set方法
public Component getBean(ValueStack arg0, HttpServletRequest arg1,
HttpServletResponse arg2) {
return new WebInfo(arg0, pageContext); //構造一個Component
}
protected void populateParams() {
super.populateParams();
WebInfo tag = (WebInfo) component;
tag.setType(type);
}
而WebInfo實現了Component接口,它的構造方法:
public WebInfo(ValueStack stack, PageContext pageContext) {
super(stack);
this.pageContext = pageContext;
}
這裏關鍵的地方是:
public boolean start(Writer writer) {
boolean result = super.start(writer);
WebApplicationContext wc =
WebApplicationContextUtils.getWebApplicationContext(this.pageContext
.getServletContext()); //調用服務層
SysConfig sysConfig = (SysConfig) wc.getBean("sysConfig");//這裏主要用了
sysConfg
StringBuffer sb = new StringBuffer();
if (this.getType().equalsIgnoreCase("forumname")) { //type="forunname"
sb.append(sysConfig.getForumName());
}
if (this.getType().equalsIgnoreCase("poweredby")) {//type="poweredby"
sb.append(" - ");
sb.append("Powered By BBS-CS[天乙社區]");
}
if (this.getType().equalsIgnoreCase("meta")) {//type="meta"
sb.append("<meta name="keywords" content="");
sb.append(sysConfig.getMetaKeywords());
sb.append(""/> ");
sb.append("<meta name="description" content="");
sb.append(sysConfig.getMetaDescription());
sb.append(""/>");
}
if (this.getType().equalsIgnoreCase("pagefoot")) {//type="pagefoot"
Locale locale = this.pageContext.getRequest().getLocale();
ResourceBundleMessageSource messageSource =
(ResourceBundleMessageSource) wc.getBean("messageSource");//從消息資源文件獲得
if (StringUtils.isNotBlank(sysConfig.getWebName())) {
if (StringUtils.isNotBlank(sysConfig.getWebUrl())) {
sb.append("<a href="");
sb.append(sysConfig.getWebUrl());
sb.append("" target="_blank">");
sb.append(sysConfig.getWebName());
sb.append("</a>");
} else {
sb.append(sysConfig.getWebName());
}
}
if (StringUtils.isNotBlank(sysConfig.getForumName())) {
sb.append(" | ");
if (StringUtils.isNotBlank(sysConfig.getForumUrl())) {
sb.append("<a href="");
sb.append(sysConfig.getForumUrl());
sb.append("" target="_blank">");
sb.append(sysConfig.getForumName());
sb.append("</a>");
} else {
sb.append(sysConfig.getForumName());
}
}
if (StringUtils.isNotBlank(sysConfig.getWebmasterEmail())) {
sb.append(" | ");
sb.append("<a href="mailto:");
sb.append(sysConfig.getWebmasterEmail());
sb.append("">");
sb.append(messageSource.getMessage("bbscs.contactus", null,
locale));
sb.append("</a>");
}
if (StringUtils.isNotBlank(sysConfig.getPrivacyUrl())) {
sb.append(" | ");
sb.append("<a href="");
sb.append(sysConfig.getPrivacyUrl());
sb.append("" target="_blank">");
sb.append(messageSource.getMessage("bbscs.privacy", null,
locale));
sb.append("</a>");
}
if (StringUtils.isNotBlank(sysConfig.getCopyRightMsg())) {
sb.append("<BR/>");
sb.append(sysConfig.getCopyRightMsg()); //加入版權信息
}
sb.append("<BR/>");
sb.append("<strong><font face="Tahoma" size="1" color="#A0A0A4
">");
sb.append("Powered By ");
sb.append("<a href="http://www.laoer.com" target='_blank'>BBS-
CS</a>");
sb.append(" V");
sb.append(Constant.VSERION);
sb.append(" © 2007</font></strong>");
}
try {
writer.write(sb.toString()); //輸入
} catch (IOException e) {
e.printStackTrace();
}
return result;
}
我們看login.jsp中,有許多struts2的標籤,如s:form s:hidden s:text s:actionerror s:textfield
s:if s:else s:radio s:submit s:submit s:url (樣式用cssClass)對於具體的使用請
看:http://www.blogjava.net/max/archive/2006/10/18/75857.html
注意<img alt="<s:text name="login.authcode"/>" src="authimg" align="absmiddle" />是從authimg
得到圖片的(注意這裏是相對URL)
也注意到:s:actionerror也用到了template.bbscs0 <s:actionerror theme="bbscs0"/>
<#if (actionErrors?exists && actionErrors?size > 0)>
<div class="errormsg">
<#list actionErrors as error>
<span class="errorMessage">${error}</span><br/>
</#list>
</div>
</#if>
注意到struts.properties文件中:
struts.ui.theme=simple
//struts.ui.templateDir=template 默認
//struts.ui.templateSuffix=ftl 默認
好的,我們提交表單,進入login.bbscs,還是最終達到Login.java
public String execute() {
this.setUrlRewrite(Constant.USE_URL_REWRITE);
this.setUserAuthCodeValue();
-->
private void setUserAuthCodeValue() {
this.setUseAuthCode(this.getSysConfig().isUseAuthCode()); //=true
}
if (this.getAction().equalsIgnoreCase("login")) {
return this.login();
}
--->
public String login() {
if (StringUtils.isBlank(this.username) || StringUtils.isBlank(this.passwd))
{ //輸入的帳號和密碼是否爲否
this.addActionError(this.getText("error.nullerror"));
return INPUT;
}
UserInfo ui = this.getUserService().findUserInfoByUserName(this.getUsername
());//查找有沒有這個用戶
if (ui == null) {
this.addActionError(this.getText("error.user.notexist"));
return INPUT;
}
if (this.getSysConfig().isUseSafeLogin()) { //是否安全登錄模式(例如3次登錄機
會)
if (this.getLoginErrorService().isCanNotLogin(ui.getId())) {
this.addActionError(this.getText("error.login.times"));
return INPUT;
}
}
if (!Util.hash(this.getPasswd()).equals(ui.getRePasswd())) { // 密碼錯誤
/**
public synchronized static final String hash(String data) {
if (digest == null) {
try {
digest = MessageDigest.getInstance("MD5");
} catch (NoSuchAlgorithmException nsae) {
System.err
.println("Failed to load the MD5
MessageDigest. " + "We will be unable to function normally.");
nsae.printStackTrace();
}
}
// Now, compute hash.
digest.update(data.getBytes());
return encodeHex(digest.digest());
}
*/
if (this.getSysConfig().isUseSafeLogin()) {
try {
this.getLoginErrorService
().createLoginErrorui.getId());//加入錯誤服務中!
} catch (BbscsException ex1) {
logger.error(ex1);
}
}
this.addActionError(this.getText("error.login.passwd"));
return INPUT;
}
if (this.getSysConfig().isUseAuthCode()) { //使用驗證碼
String cauthCode = this.getUserCookie().getAuthCode();//Cookie中得到
AuthCode!
if (StringUtils.isBlank(cauthCode) || !cauthCode.equals
(this.getAuthCode())) {
this.addActionError(this.getText("error.login.authcode"));
return INPUT;
}
}
ui.setLastLoginIP(ui.getLoginIP());//上一次的
ui.setLastLoginTime(ui.getLoginTime());//上一次
ui.setLoginIP(this.getRemoteAddr());
ui.setLoginTime(new Date()); //時間類型哦
ui.setUserLocale(this.getLocale().toString());
long nowTime = System.currentTimeMillis();
UserOnline uo = new UserOnline();
uo.setAtPlace("");
uo.setBoardID(0);
uo.setNickName(ui.getNickName());
uo.setOnlineTime(nowTime);//long類型的時間
uo.setUserGroupID(ui.getGroupID());
uo.setUserID(ui.getId());
uo.setUserName(ui.getUserName());
uo.setValidateCode(ui.getId() + "_" + nowTime);//構造出來的,用於避免重複登錄
吧!
if (this.getHiddenLogin() == 1 || ui.getHiddenLogin() == 1) { // 用戶隱身登
錄
uo.setHiddenUser(1);
}
try {
ui = this.getUserService().saveAtLogin(ui); // 用戶登錄處理
/**
public UserInfo saveAtLogin(UserInfo userInfo) throws BbscsException {
try {
if ((System.currentTimeMillis() - userInfo.getLastLoginTime
().getTime()) > 30 * 60000) {
userInfo.setLoginTimes(userInfo.getLoginTimes() + 1);//不一
樣吧!
userInfo.setExperience(userInfo.getExperience() + 1);
}
userInfo = this.getUserInfoDAO().saveUserInfo(userInfo);
this.getUserInfoFileIO().writeUserFile(userInfo);
return userInfo;
} catch (Exception ex) {
logger.error(ex);
throw new BbscsException(ex);
}
}
*/
uo = this.getUserOnlineService().createUserOnline(uo); // 加入在線用
戶表
} catch (BbscsException ex) {
logger.error(ex);
return INPUT;
}
UserSession us = userService.getUserSession(ui);
/**
public UserSession getUserSession(UserInfo ui) {
UserSession us = new UserSession();
us.setEmail(ui.getEmail());
us.setGroupID(ui.getGroupID());
us.setId(ui.getId());
us.setNickName(ui.getNickName());
String[] signDetail = new String[3];
signDetail[0] = ui.getSignDetail0() == null ? "" : ui.getSignDetail0();
signDetail[1] = ui.getSignDetail1() == null ? "" : ui.getSignDetail1();
signDetail[2] = ui.getSignDetail2() == null ? "" : ui.getSignDetail2();
us.setSignDetail(signDetail);
us.setUserName(ui.getUserName());
us.setLastActiveTime(System.currentTimeMillis());
Map[] permissionMap = this.getUserPermission(ui);
us.setUserPermissionArray(permissionMap);
-->
/**
public Map[] getUserPermission(UserInfo userInfo) {
return this.getUserPermission(userInfo.getGroupID());
}
*/
return us;
}
*/
us.setValidateCode(uo.getValidateCode());//Session的validateCode改變之
this.getSession().put(Constant.USER_SESSION_KEY, us);
放入本Login本關的Session中!public static final String USER_SESSION_KEY = "user_session";這裏
我們可以簡單的看一下UserSession的處理,好象我們以前講過吧,這裏重新講一次:
private String userName = "";
private String id = "";
private String nickName = "";
private String email = "";
private long lastActiveTime = 0;
private Map userPermission = new HashMap();
private Map boardPermission = new HashMap();
private Map specialPermission = new HashMap();
private Map boardSpecialPermission = new HashMap();
private long bid = 0;
private int groupID = 0;
private long addedOnlineTime = 0;
private long addedOnlineHour = 0;
private String validateCode = "";
private String[] signDetail = { "", "", "" };
private String boardPass = "";
private int initStatus = 0;
這些是它的屬性,當然也有get/set;上面的us.setValidateCode就是這樣工作的..我們這裏重點看下:
us.setUserPermissionArray(permissionMap);
public void setUserPermissionArray(Map[] permissionMap) {
setSpecialPermission(permissionMap[1]); //特別的權力!
/**
public void setSpecialPermission(Map specialPermission) {
this.specialPermission = specialPermission;
}
而它是通過根據Permission的TypeID確定的:
Permission permission = (Permission) permissionList.get(i);
if (permission.getTypeID() == 0) {
userPermission[0].put
(permission.getResource() + "," + permission.getAction(), permission);
} else {
userPermission[1].put
(permission.getId(), permission);
}
*/
Set pset = permissionMap[0].entrySet();//Map的遍歷哦!
Iterator it = pset.iterator();
while (it.hasNext()) {
Map.Entry p = (Map.Entry) it.next();
Permission permission = (Permission) p.getValue();//getValue
String[] actions = permission.getAction().split(",");
for (int i = 0; i < actions.length; i++) {
String[] resources = ((String) p.getKey()).split
(",");//getKey
this.getUserPermission().put(resources[0] + "?action=" +
actions[i], p.getValue());
}
}
}
this.getUserCookie().removeAuthCode(); //Cookie的authCode改變
this.getUserCookie().addCookies(ui);
// this.getUserCookie().addValidateCode(uo.getValidateCode());
if (this.getCookieTime() != -1) {
this.getUserCookie().addC("U", this.getUsername(),
this.getCookieTime());
this.getUserCookie().addDES("P", Util.hash(this.getPasswd()),
this.getCookieTime());//這裏對UserSession和UserCookie都進行了改變...
}
return SUCCESS;
}
我們知道在進入Login之前,已經對UserCookie進行了操作:
UserCookie userCookie = new UserCookie(request, response, sysConfig);
((UserCookieAware) action).setUserCookie(userCookie);
看下面授代碼:
public UserCookie(HttpServletRequest request, HttpServletResponse response, SysConfig
sysConfig) {
this.request = request;
this.response = response;
this.sysConfig = sysConfig;
try {
des = new DES(DES._DESede);//DES算法
/**
DES 64位密鑰, 有效密鑰56位, 8位用來校驗.
DES ede, 密鑰應該是64*2=128位, 有效密鑰56*2=112位 -->16字節
Blowfish 密鑰40--448位.
*/
} catch (Exception ex) {
logger.error(ex);
}
getCookies();
}
從request,response原處引入這樣參數到UserCookie中!
private HttpServletRequest request;
private HttpServletResponse response;
private SysConfig sysConfig;
private DES des;
getCookies...將查找如下key相關的Cookie信息:
private static final String PASS_USERNAME_KEY = "PASS_USERNAME";//用於單點登錄
private static final String PASS_USERNAME_DES_KEY = "PASS_USERNAME_DES";//用於單點登
錄
private static final String BBSCS_FORUMPERNUM_KEY = "FN";
private static final String BBSCS_POSTPERNUM_KEY = "PN";
private static final String BBSCS_TIMEZONE_KEY = "TZ";
private static final String BBSCS_FORUMVIEWMODE_KEY = "VM";
private static final String BBSCS_LASTSENDNOTETIME_KEY = "LN";
private static final String BBSCS_LASTPOSTTIME_KEY = "LP";
private static final String BBSCS_EDITTYPE = "ET";
private static final String BBSCS_AUTHCODE = "AC";
private static final String BBSCS_USERNAME = "U";//用於BBSCS
private static final String BBSCS_PASSWD = "P";//用於BBSCS
當然,它們也有初始值:
private int postPerNum = 10;
private int forumPerNum = 20;
private int forumViewMode = 0;
private String timeZone = "GMT+08:00";
private String pusername = "";
private String pusernamedes = "";
private long lastSendNoteTime = 0;
private long lastPostTime = 0;
private int editType = 0;
private String authCode = "";
private String userName = "";
private String passwd = "";
這裏有request的使用Cookie cookies[] = request.getCookies();
if (this.sysConfig.isUsePass()) { /**數據庫UsePass=0,usePass=1可能指的是用於多個系
統之間的登錄問題(使用通行證)*/
if (sCookie.getName().equals
(PASS_USERNAME_KEY)) {
this.pusername = sCookie.getValue();
// System.out.println("pass
username:" + username);
}
if (sCookie.getName().equals
(PASS_USERNAME_DES_KEY)) {
if (StringUtils.isNotBlank
(sCookie.getValue())) {
buf = Util.base64decodebyte
(sCookie.getValue());
byte[] dec = des.decode(buf,
Util.base64decodebyte(this.sysConfig.getCookieKey()));//Enc-Base64位加密
this.pusernamedes = new
String(dec);
// System.out.println("pass
usernamedes:" +
// usernamedes);
}
}
}
我們看驗證碼的一段:
if (sCookie.getName().equals(BBSCS_AUTHCODE)) {
if (StringUtils.isNotBlank(sCookie.getValue
())) {
buf = Util.base64decodebyte
(sCookie.getValue());
byte[] dec = des.decode(buf,
Util.base64decodebyte(this.sysConfig.getCookieKey()));
this.authCode = new String(dec);
}
}
而我們回到AuthImg:
UserCookie uc = new UserCookie(request, response, sysConfig);
uc.addAuthCode(rand);//這裏用了UserCookie中的addAuthCode方法:
public void addAuthCode(String authCode) {
this.addDES(BBSCS_AUTHCODE, authCode, -1);
}
//而對於authCode其實它用了DES算法:
public void addC(String name, String value, int maxage) { //普通加Cookie的方法
Cookie cookies = new Cookie(name, value);
cookies.setPath(this.sysConfig.getCookiePath());
cookies.setMaxAge(maxage);
// cookies.setMaxAge(30 * 60);
if (StringUtils.isNotBlank(this.sysConfig.getCookieDomain())) {//域名,用於
單點登錄
cookies.setDomain(this.sysConfig.getCookieDomain());
}
this.response.addCookie(cookies);//這裏用到了response!
}
public void addDES(String name, String value, int maxage) {
try {
// DES des = new DES(DES._DESede);
des.setKey(Util.base64decodebyte(this.sysConfig.getCookieKey()));//
加入密鑰!
/**數據庫中CookieKey=nhNhwZ6X7xzgXnnZBxWFQLwCGQtJojL3*/
byte[] enc = des.encode(value.getBytes());
value = Util.base64Encode(enc);
/**
public static String base64Encode(byte[] txt) {
String encodeTxt = "";
if (txt != null && txt.length > 0) {
encodeTxt = new sun.misc.BASE64Encoder().encode(txt);
}
return encodeTxt;
}
*/
Cookie cookies = new Cookie(name, value);
cookies.setPath(this.sysConfig.getCookiePath());
// cookies.setMaxAge(30 * 60);
cookies.setMaxAge(maxage);
if (StringUtils.isNotBlank(this.sysConfig.getCookieDomain())) {
cookies.setDomain(this.sysConfig.getCookieDomain());
}
this.response.addCookie(cookies);
} catch (Exception ex) {
// ex.printStackTrace();
logger.error("addDES(String name, String value)" + ex);
}
}
好,我們暫時回到Login.java:
this.getUserCookie().removeAuthCode();
/**
public void removeAuthCode() {
this.addC(BBSCS_AUTHCODE, "", 0);
}
*/
this.getUserCookie().addCookies(ui);
// this.getUserCookie().addValidateCode(uo.getValidateCode());
if (this.getCookieTime() != -1) {
this.getUserCookie().addC("U", this.getUsername(),
this.getCookieTime());
this.getUserCookie().addDES("P", Util.hash(this.getPasswd()),
this.getCookieTime());
}
這裏將一些登錄特性ui用addCookies加入了其UserCookie中!
public void addCookies(UserInfo ui) {
this.forumPerNum = ui.getForumPerNum();
addC(BBSCS_FORUMPERNUM_KEY, String.valueOf(ui.getForumPerNum()), -1);
this.postPerNum = ui.getPostPerNum();
addC(BBSCS_POSTPERNUM_KEY, String.valueOf(ui.getPostPerNum()), -1);
this.timeZone = ui.getTimeZone();
addC(BBSCS_TIMEZONE_KEY, Util.base64Encode(ui.getTimeZone()), -1);
this.forumViewMode = ui.getForumViewMode();
addC(BBSCS_FORUMVIEWMODE_KEY, String.valueOf(ui.getForumViewMode()), -1);
this.editType = ui.getEditType();
addC(BBSCS_EDITTYPE, String.valueOf(ui.getEditType()), -1);
}
OK!return SUCCESS;到<result name="success" type="redirect">${tourl}</result>
當然,我們先講下cookieLogin方法先:它由check發現後轉到這裏...
public String check() {
if (StringUtils.isNotBlank(this.getUserCookie().getUserName())
&& StringUtils.isNotBlank(this.getUserCookie().getPasswd()))
{
return this.cookieLogin();
} else {
return this.index();
}
}
我們這裏可以觀察其不同點在於:
UserInfo ui = this.getUserService().findUserInfoByUserName(this.getUserCookie().getUserName
());
if (ui == null) {
this.addActionError(this.getText("error.user.notexist"));
return INPUT;
}
if (!this.getUserCookie().getPasswd().equals(ui.getRePasswd())) {
if (this.getSysConfig().isUseSafeLogin()) {
try {
this.getLoginErrorService().createLoginError
(ui.getId());
} catch (BbscsException ex1) {
logger.error(ex1);
}
}
this.addActionError(this.getText("error.login.passwd"));
return INPUT;
}
以及後面的tourl的設置,因爲check中並沒來的急設置它:
if (Constant.USE_URL_REWRITE) {
tourl = this.getBasePath() + "main.html";
} else {
tourl = this.getBasePath() +
BBSCSUtil.getActionMappingURLWithoutPrefix("main");
}
哦,我們還少分析了loginPass:
public String input() {
if (this.getSysConfig().getUsePass() == 0) {
return INPUT;
} else {
this.setActionUrl(this.getSysConfig().getPassUrl
());//http://www.laoer.com/login
return "loginPass";//而其對應的passLogin.jsp卻比login.jsp簡單...不知
有什麼用...
}
}
對了,我們還有
if (this.getAction().equalsIgnoreCase("admin")) {
this.setAction("login");
this.setHiddenLogin(0);
tourl = this.getBasePath() +
BBSCSUtil.getActionMappingURLWithoutPrefix("adminMain");//注意點
return this.input();
}
if (this.getAction().equalsIgnoreCase("relogin")) {
this.setAction("login");
this.setHiddenLogin(0);
if (Constant.USE_URL_REWRITE) {
tourl = this.getBasePath() + "main.html";
} else {
tourl = this.getBasePath() +
BBSCSUtil.getActionMappingURLWithoutPrefix("main");
}
this.addActionError(this.getText("error.login.re"));//注意點
return this.input();
}
首先我們看Login SUCCESS指的是什麼:
public interface Action
{ public abstract String execute()
throws Exception;
public static final String SUCCESS = "success";
public static final String NONE = "none";
public static final String ERROR = "error";
public static final String INPUT = "input";
public static final String LOGIN = "login";
}
OK,<result name="success" type="redirect">${tourl}</result>轉到tourl去/main.bbscs(未用
UrlRewriter)
爲了先把用戶這塊講完,我們選擇了註冊流程來分析:/reg/input
<package name="reg" extends="bbscs-default" namespace="/reg"> //namespace哦!
<global-results>
<result name="input">/WEB-INF/jsp/reg.jsp</result>
<result name="passreg" type="redirect">
${sysConfig.getPassRegUrl()}
</result>
</global-results>
<action name="input" class="regAction" method="input"></action>//注意它沒有
什麼result
我們到action-servlet.xml找到<bean id="regAction" class="com.laoer.bbscs.web.action.Reg"
scope="prototype" autowire="byName"></bean>
進入com.laoer.bbscs.web.action.Reg extends BaseAction implements RemoteAddrAware,
UserCookieAware:
它有
answer,email,nickName,passwd,question,rePasswd,userName,validateCode,authCode,useAuthCode=tr
ue,userRemoteAddr,userCookie,也注入了一此服務sysConfig,ipSeeker,templateMail,sysStatService
我們看下input:
public String input() throws Exception {
if (this.getSysConfig().getUsePass() == 1) {//使用通行證
return "passreg";
}
//usePass=0
if (this.getSysConfig().getOpenUserReg() == 0) { // 關閉註冊
addActionError(this.getText("error.reg.notallowreg"));
return ERROR;
}
this.setUseAuthCode(this.getSysConfig().isUseRegAuthCode());//=1
this.setAction("add");
return INPUT;
}
好,我們看reg.jsp:
<tr>
<td width="170"><div align="right"><s:text name="reg.username"/>:<span
class="font2">*</span></div></td>
<td width="180">
<s:textfield name="userName" id="userName" cssClass="input1" size="30" maxlength="20"
onfocus="changeStyle('usernameMsg','msg2');" onblur="changeStyle
('usernameMsg','msg1');"></s:textfield>
</td>
<td width="370">
<div class="msg1" id="usernameMsg"><s:text name="reg.username.notice"/></div>
<s:fielderror theme="bbscs0"> //bbscs0的fielderror
<s:param>userName</s:param>
</s:fielderror>
</td>
</tr>
<tr>
<td class="t1"> </td>
<td valign="top"><div align="center">
<input type="button" name="Check" value="<s:text name="reg.checkusename"/>"
class="button1" onclick="checkUserNameAction();"/> //觸發js
</div></td>
<td>
<div id="checkUserNameMsg">
</div>
</td>
</tr>
<s:if test="%{useAuthCode}">
<tr>
<td><div align="right"><s:text name="login.authcode"/>:<span
class="font2">*</span></div></td>
<td>
<s:textfield name="authCode" id="authCode" cssClass="input1" onfocus="changeStyle
('authCodeMsg','msg2');" onblur="changeStyle('authCodeMsg','msg1');" size="5"
maxlength="50">
</s:textfield>
<img alt="<s:text name="login.authcode"/>" src="<%=basePath%>authimg"
align="absmiddle" />
</td>
<td><div class="msg1" id="authCodeMsg"><s:text name="reg.authcode.motice"/></div>
<s:fielderror theme="bbscs0">
<s:param>authCode</s:param>
</s:fielderror>
</td>
</tr>
</s:if>
對於這三段代碼,我們一個一個來分析之:
function changeStyle(elementID,toStyle) {
document.getElementById(elementID).className=toStyle;
}
注意到:
<link href="<%=basePath%>css/css1.css" rel="stylesheet" type="text/css">
<script type="text/javascript" src="<%=basePath%>js/jsMsg.jsp"></script>
<script type="text/javascript" src="<%=basePath%>js/prototype.js"></script>
<script type="text/javascript" src="<%=basePath%>js/comm.js"></script>
css1中有一段樣式如下:
.msg1 {
color: #999999;
padding: 6px;
}
.msg2 {
background-color: #EDF3FA;
border: 1px solid #408FD0;
padding: 5px 5px 5px 25px;
background-image: url(../images/standard_msg_warning.gif);
background-repeat: no-repeat;
background-position: 5px 5px;
}
.msg3 {
background-color: #F0FFF0;
border: 1px solid #00EA00;
padding: 5px 5px 5px 25px;
background-image: url(../images/standard_msg_ok.gif);
background-repeat: no-repeat;
background-position: 5px 5px;
}
.errormsg {
background-color: #FFEFEE;
border: 1px solid #FF6860;
padding: 5px 5px 5px 25px;
background-image: url(../images/standard_msg_error.gif);
background-repeat: no-repeat;
background-position: 5px 5px;
line-height: 16pt;
}
而第三個authcode是否要取決於useAuthCode:在Reg.java中private boolean useAuthCode = true;
對於s:fielderror用於theme bbscs0 且這個標籤帶param!請自行參看源代碼!
<div class="errormsg">
<#assign doneStartUlTag=true><#t/>
</#if><#t/>
<#list eValue as eEachValue><#t/>
<span class="errorMessage">${eEachValue}</span><br/>
</#list><#t/>
</#if><#t/>
</#list><#t/>
</#list><#t/>
<#if (haveMatchedErrorField && (!doneEndUlTag))><#t/>
</div>
我們這樣重點看下:checkUserNameAction,這個請求是基於Ajax的:
function checkUserNameAction() {
if ($('userName').value == "" || $('userName').length == 0) {
alert("<s:text name="reg.inputusername"/>");
return;
}
$('checkUserNameMsg').className = "msg2";
$('checkUserNameMsg').innerHTML = "<s:text name="bbscs.checking"/>";
var url = getActionMappingURL("/reg/check");
/**在comm.js中:
function getActionMappingURL(action) {
var value = contextPath;//變量大多在jsMsg中定義!
var servletMapping = servletMappingStr;
var queryString;
var question = action.indexOf("?");
if (question >= 0) {
queryString = action.substring(question);
}
var actionMapping = getActionMappingName(action);
if (startsWith(servletMapping,"*.")) {
value += actionMapping;
value += servletMapping.substring(1);
}
else if (endsWith(servletMapping,"/*")) {
value += servletMapping.substring(0, servletMapping.length - 2);
value += actionMapping;
}
else if (servletMapping == "/") {
value += actionMapping;
}
if (queryString != undefined) {
value += queryString;
}
return value;
}
*/
var pars = "userName=" + $('userName').value;
var myAjax = new Ajax.Request(url, {method: 'get', parameters: pars, onComplete:
showResult}); //prototype的Ajax.Request關鍵點
}
function showResult(res) {
resText = res.responseText;
var jsonMsgObj = new JsonMsgObj(resText);
/**
var JsonMsgObj = function(responseText) {
this.json = eval('(' + responseText + ')');
}
JsonMsgObj.prototype.getCodeid = function() {
return this.json.codeid;
}
JsonMsgObj.prototype.getMessage = function() {
return this.json.message;
}
JsonMsgObj.prototype.getText = function() {
return this.json.text;
}
*/
//alert(jsonMsgObj.getCodeid());
var codeid = jsonMsgObj.getCodeid();
if (codeid == "0") {
$('checkUserNameMsg').className = "msg3"; //OK
}
else {
$('checkUserNameMsg').className = "errormsg";
}
$('checkUserNameMsg').innerHTML = jsonMsgObj.getMessage();
}
這裏用了json和prototype,設置了傳回來的Json爲utf-8,而且把json數據直接扔進responseText回來後自
己eval的,OK!我們進入後端:/reg/check?userName=?
<action name="check" class="checkUserNameAction"></action>
-->
<bean id="checkUserNameAction"
class="com.laoer.bbscs.web.action.CheckUserName" scope="prototype"
autowire="byName">
<!--
<property name="userService">
<ref bean="userService" />
</property>
<property name="sysConfig">
<ref bean="sysConfig" />
</property>
<property name="ajaxMessagesJson"> //注意哦!
<ref bean="ajaxMessagesJson" />
</property>
-->
</bean>
由於它與Json有聯繫,因此寫在一個專門的文件中..它首先引入了
sysConfig,ajaxMessagJson,userService三個服務,也有一個屬性userName;下面是其execute方法
public String execute() {
if (StringUtils.isBlank(this.getUserName())) {
this.getAjaxMessagesJson().setMessage("E_USERNAME_001", "請填寫用戶
名!");
}
else if (!Util.validateUserName(this.getUserName())) {
/**
public static boolean validateUserName(String username) {
Pattern p = Pattern.compile("^/w+$");
Matcher m = p.matcher(username);
if (m.find()) {
return true;
}
return false;
}
*/
this.getAjaxMessagesJson().setMessage("E_USERNAME_002", "用戶名只能
由英文、數字和下劃線組成!");
}
else if (this.getSysConfig().isCanNotRegUserName(this.getUserName())) {
this.getAjaxMessagesJson().setMessage("E_USERNAME_004", "該用戶不能
註冊!");
}
else {
UserInfo userInfo = this.getUserService().findUserInfoByUserName
(this.getUserName());
if (userInfo != null) {
this.getAjaxMessagesJson().setMessage("E_USERNAME_003", "用
戶名已存在,請選擇其他用戶名!");
}
else {
this.getAjaxMessagesJson().setMessage("0", "該用戶名可以註冊
!");//返回0可以註冊哦!其它不行
}
}
// System.out.println(this.getAjaxMessagesJson().getJsonString());
return RESULT_AJAXJSON;//ajaxjson是個頁面,輸出json數據,這個我們可以看bbscs
-default package中的global-results中<result name="ajaxjson">/WEB-
INF/jsp/ajaxjson.jsp</result>
/**看BaseAction中的常量:
public static final String RESULT_AJAXJSON = "ajaxjson";
public static final String RESULT_HTMLERROR = "htmlError";
public static final String RESULT_ERROR = "error";
public static final String RESULT_JSONSTRING = "jsonstring";
而下面是ajaxjson.jsp的一部分:
<%
request.setAttribute("decorator", "none");
response.setHeader("Cache-Control","no-cache"); //HTTP 1.1
response.setHeader("Pragma","no-cache"); //HTTP 1.0
response.setDateHeader ("Expires", 0); //prevents caching at the proxy server
%>
<s:property value="ajaxMessagesJson.getJsonString()" escape="false"/>
*/
}
我們看下AjaxMessagesJson,它在com.laoer.bbscs.web.ajax(也就只有它一個文件):
public class AjaxMessagesJson {
private static final Log logger = LogFactory.getLog(AjaxMessagesJson.class);
private JSONObject json = new JSONObject();//用了json-lib-1.1-jdk15.jar
public void setMessage(String codeid, String message) {
try {
this.json.put("codeid", codeid);//codeid
this.json.put("message", message);//message
this.json.put("text", "");//空的text
} catch (JSONException e) {
logger.error(e);
}
}
public void setMessage(String codeid, String message, String text) {
try {
this.json.put("codeid", codeid);
this.json.put("message", message);
this.json.put("text", text);//不爲空的text
} catch (JSONException e) {
logger.error(e);
}
}
public String getJsonString() { //返回JsonString結果!
return this.json.toString();
}
}
好了,完成了用戶名的檢查和輸入其它字段,後我們點擊提交按鈕,我們看生成的html代碼:
<form id="add" name="add" onsubmit="return true;" action="/bbscs8/reg/add.bbscs"
method="post">
好,進入struts.xml:
<action name="add" class="regAction" method="add">
<interceptor-ref name="remoteAddrInterceptorStack"></interceptor-
ref>
<interceptor-ref name="userCookieInterceptor"></interceptor-ref>
<result name="success" type="redirect">
/regSucceed.jsp
</result>
</action>
由於remoteAddrInterceptorStack包括defaultStack中的驗證validation!
<interceptor-stack name="remoteAddrInterceptorStack">
<interceptor-ref name="defaultStack"></interceptor-ref>
<interceptor-ref name="remoteAddrInterceptor"></interceptor
-ref>
所以add請求會先執行Reg-validation.xml,再進入method=add中:
<field name="userName">
<field-validator type="requiredstring">//字段規則
<param name="trim">true</param>
<message key="error.reg.name.null"></message>
</field-validator>
<field-validator type="regex">
<param name="expression">w+</param>//正則!
<message key="error.reg.name0"></message>
</field-validator>
<field-validator type="stringlength">//長度
<param name="minLength">3</param>
<param name="maxLength">20</param>
<param name="trim">true</param>
<message key="error.reg.username.toolong"></message>
</field-validator>
</field>
<field name="rePasswd">
<field-validator type="requiredstring">
<param name="trim">true</param>
<message key="error.reg.passwd.null"></message>
</field-validator>
<field-validator type="stringlength">
<param name="minLength">6</param>
<param name="maxLength">20</param>
<param name="trim">true</param>
<message key="error.reg.passwd.toolong"></message>
</field-validator>
<field-validator type="fieldexpression">//規則
<param name="expression">(rePasswd == passwd)</param>//兩字段相等
<message key="error.reg.passwd.notsame"></message>
</field-validator>
</field>
若出錯當然是到INPUT。檢查通過後便進入到add方法中:
public String add() {
if (this.getSysConfig().getUsePass() == 1) {
return "passreg"; //是否可通過
}
this.setUseAuthCode(this.getSysConfig().isUseRegAuthCode());//驗證碼是否使用
if (this.getSysConfig().getOpenUserReg() == 0) { // 關閉註冊
addActionError(this.getText("error.reg.notallowreg"));
return ERROR;
}
if (this.getSysConfig().isCanNotRegUserName(this.getUserName())) { // 不能注
冊的用戶名
addFieldError("userName", this.getText("error.reg.badusername", new
String[] { this.getUserName() }));//在字段錯誤中加入業務邏輯錯誤!
}
if (this.getSysConfig().getUseForbid() == 1) {
if (this.getSysConfig().isForbidIP(this.getUserRemoteAddr())) {
this.addFieldError("userName", this.getText
("error.reg.ipforbid", new String[] { this
.getUserRemoteAddr() }));//這裏說明userName
的FieldError可能有多個
}
if (this.getSysConfig().isForbidEmail(this.getEmail())) {
this.addFieldError("email", this.getText
("error.reg.emailforbid", new String[] { this.getEmail() }));
}
}
if (this.getSysConfig().isUseRegAuthCode()) {
if (!this.getUserCookie().getAuthCode().equals(this.getAuthCode()))
{
this.addFieldError("authCode", this.getText
("error.reg.authcode.same"));//注意驗證碼與cookie的關係!
}
}
if (this.hasFieldErrors()) {
return INPUT; //有錯誤到INPUT
}
UserInfo ui = this.getUserService().findUserInfoByUserName(this.getUserName
());
if (ui != null) {
this.addFieldError("userName", this.getText("error.reg.name1"));//更
高的業務邏輯考慮!
return INPUT;
}
ui = this.getUserService().findUserInfoByEmail(this.getEmail());
if (ui != null) {
this.addFieldError("email", this.getText("error.reg.emailerror"));
return INPUT;
}
ui = new UserInfo();//注意setPasswd和setRePasswd的不同
ui.setAcceptFriend(1);
ui.setAnswer(this.getAnswer());
ui.setArticleEliteNum(0);
ui.setArticleNum(0);
ui.setBirthDay(1);
ui.setBirthMonth(1);
ui.setBirthYear(1980);
ui.setEmail(this.getEmail());
ui.setExperience(0);
ui.setForumPerNum(0);
ui.setForumViewMode(0);
ui.setHavePic(0);
ui.setLastLoginIP("0.0.0.0");
ui.setLastLoginTime(new Date());
ui.setLifeForce(0);
ui.setLiterary(0);
ui.setLoginIP("0.0.0.0");
ui.setLoginTime(new Date());
ui.setLoginTimes(0);
ui.setNickName(this.getSysConfig().bestrowScreenNickName(this.getNickName
())); // 屏蔽敏感字
/**public String bestrowScreenNickName(String txt) {
if (StringUtils.isNotBlank(this.getCanNotUseNickName())) {
String[] words = this.getCanNotUseNickName().split(";");
for (int i = 0; i < words.length; i++) {
txt = txt.replaceAll(words[i], this.getBestrowScreen
());//bestrowScreen是固定值:在數據庫中爲**
}
}
return txt;
}
*/
ui.setPasswd(this.getPasswd());
ui.setPicFileName("");
ui.setPostPerNum(0);
ui.setQuestion(this.getQuestion());
ui.setReceiveNote(1);
ui.setRegTime(new Date());
ui.setRePasswd(Util.hash(this.getPasswd()));//其實hash方法是MD5加密!
ui.setSignDetail0(this.getText("bbscs.userdefaultsign"));
ui.setSignDetail1(this.getText("bbscs.userdefaultsign"));
ui.setSignDetail2(this.getText("bbscs.userdefaultsign"));
ui.setSignName0("A");
ui.setSignName1("B");
ui.setSignName2("C");
ui.setStayTime(0);
ui.setTimeZone("GMT+08:00");
ui.setUserFrom(this.getIpSeeker().getCountry(this.getUserRemoteAddr()));
ui.setUserKnow(0);
ui.setUserName(this.getUserName());
ui.setUserTitle(0);
if (this.getSysConfig().isCheckRegUser() || this.getSysConfig
().isCheckRegUserEmail()) { //默認兩者都爲0,審覈才能是會員!
ui.setValidated(0);
ui.setGroupID(Constant.USER_GROUP_UNVUSER);//未審覈
} else {
ui.setValidated(1);
ui.setGroupID(Constant.USER_GROUP_REGUSER);
}
ui.setEditType(-1);
ui.setUserLocale(this.getLocale().toString());//getLocale來自ActionSupport!
/**
public Locale getLocale()
{
return ActionContext.getContext().getLocale();
}
*/
ui.setValidateCode(RandomStringUtils.randomAlphanumeric(10));//用於審覈用的
,不是重複登錄用!隨機10位數
ui.setCoin(100);
UserDetail ud = new UserDetail();
ud.setBrief("");
ud.setDreamJob("");
ud.setDreamLover("");
ud.setFavourArt("");
ud.setFavourBook("");
ud.setFavourChat("");
ud.setFavourMovie("");
ud.setFavourMusic("");
ud.setFavourPeople("");
ud.setFavourTeam("");
ud.setGraduate("");
ud.setHeight("");
ud.setHomePage("");
ud.setIcqNo("");
ud.setInterest("");
ud.setMsn("");
ud.setOicqNo("");
ud.setSex((short) 0);
ud.setWeight("");
ud.setYahoo("");
ui.setUserDetail(ud);
ud.setUserInfo(ui);
try {
ui = this.getUserService().saveUserInfo(ui);
this.getSysStatService().saveAllUserNum(this.getUserService
().getAllUserNum(), this.getUserName());//重新從UserService得到最新的人數,還有最新的註冊名
寫入SysStatService!
if (this.getSysConfig().isCheckRegUserEmail()) {//需要用郵件來審覈之
String subject = this.getText("reg.validate.email.title",
new String[] { this.getSysConfig()
.getForumName() });
Map<String, String> root = new HashMap<String, String>();
root.put("website", this.getSysConfig().getForumName());
root.put("forumurl", this.getSysConfig().getForumUrl());
root.put("userName", ui.getUserName());
root.put("validateCode", ui.getValidateCode());//關鍵點...
this.getTemplateMail().sendMailFromTemplate(ui.getEmail(),
subject, "regValidate.ftl", root,
this.getLocale());
}//發信!
/**下面是模板文件的一段:
<table width="98%" border="0" align="center" cellpadding="5" cellspacing="0">
<tr>
<td><strong>非常感謝您成爲${website}的用戶</strong></td>
</tr>
<tr>
<td>您的帳戶尚處於未認證的狀態,只要你點擊下面的鏈接,即可通過認證</td>
</tr>
<tr>
<td><a href="${forumurl}/reg/validateuser.bbscs?userName=${userName}
&validateCode=${validateCode}" target="_blank">${forumurl}/reg/validateuser.bbscs?
userName=${userName}&validateCode=${validateCode}</a></td>
</tr>
</table>
*/
return SUCCESS;
} catch (BbscsException e) {
this.addActionError(this.getText
("error.reg.createrror"));//ActionError!
return ERROR;
}
}
我們這裏有必要講一下IPSeeker這個工具類,它在com.laoer.bbscs.comm包中。它在程序中通過了
ui.setUserFrom(this.getIpSeeker().getCountry(this.getUserRemoteAddr()));
由IP得到用戶來自哪裏的解決方案.(用來封裝ip相關信息,目前只有兩個字段,ip所在的國家country和
地區area),注意它讀取的文件是WEB-INF/IPDate/QQwry.Dat,下面是其屬性的定義:
// 一些固定常量,比如記錄長度等等
private static final int IP_RECORD_LENGTH = 7;
private static final byte REDIRECT_MODE_1 = 0x01;
private static final byte REDIRECT_MODE_2 = 0x02;
// Log對象
private static Log log = LogFactory.getLog(IPSeeker.class);
// 用來做爲cache,查詢一個ip時首先查看cache,以減少不必要的重複查找
private Hashtable<String, IPLocation> ipCache;
// 隨機文件訪問類
private RandomAccessFile ipFile;
// 內存映射文件
private MappedByteBuffer mbb;
// 起始地區的開始和結束的絕對偏移
private long ipBegin, ipEnd;
// 爲提高效率而採用的臨時變量
private IPLocation loc;
private byte[] buf;
private byte[] b4;
private byte[] b3;
-->
public IPSeeker() {
ipCache = new Hashtable<String, IPLocation>();
loc = new IPLocation();
buf = new byte[100];
b4 = new byte[4];
b3 = new byte[3];
try {
// ClassPathResource cpr = new ClassPathResource("/" + IPDATE_FILE);
// System.out.println(cpr.getFile());
ipFile = new RandomAccessFile(IPDATE_FILE_PATH + IPDATE_FILE, "r");
// ipFile = new RandomAccessFile(cpr.getFile(), "r");//隨機讀!
} catch (FileNotFoundException e) {
// 如果找不到這個文件,再嘗試再當前目錄下搜索,這次全部改用小寫文件
名
// 因爲有些系統可能區分大小寫導致找不到ip地址信息文件
String filename = new File(IPDATE_FILE_PATH + IPDATE_FILE).getName
().toLowerCase();
File[] files = new File(IPDATE_FILE_PATH).listFiles();
for (int i = 0; i < files.length; i++) {
if (files[i].isFile()) {
if (files[i].getName().toLowerCase().equals
(filename)) {
try {
ipFile = new RandomAccessFile(files
[i], "r");
} catch (FileNotFoundException e1) {
log.error("IP地址信息文件沒有找到,
IP顯示功能將無法使用");
ipFile = null;
}
break;
}
}
}
}
// 如果打開文件成功,讀取文件頭信息
if (ipFile != null) {
try {
ipBegin = readLong4(0);
ipEnd = readLong4(4);
if (ipBegin == -1 || ipEnd == -1) {
ipFile.close();
ipFile = null;
}
} catch (IOException e) {
log.error("IP地址信息文件格式有錯誤,IP顯示功能將無法使用");
ipFile = null;
}
}
}
好的,我們轉到要用的getCountry()方法上:
public String getCountry(String ip) {
return getCountry(getIpByteArrayFromString(ip));//String->Byte[]
}
-->
public static byte[] getIpByteArrayFromString(String ip) {
byte[] ret = new byte[4];
StringTokenizer st = new StringTokenizer(ip, ".");
try {
ret[0] = (byte) (Integer.parseInt(st.nextToken()) & 0xFF);
ret[1] = (byte) (Integer.parseInt(st.nextToken()) & 0xFF);
ret[2] = (byte) (Integer.parseInt(st.nextToken()) & 0xFF);
ret[3] = (byte) (Integer.parseInt(st.nextToken()) & 0xFF);
} catch (Exception e) {
log.error(e.getMessage());
}
return ret;
}
進而到了public String getCountry(byte[] ip) {
// 檢查ip地址文件是否正常
if (ipFile == null) {
return "bad.ip.file";
}
// 保存ip,轉換ip字節數組爲字符串形式,注意現在是字節!!!而非10進制數了哦~
String ipStr = getIpStringFromBytes(ip);
// 先檢查cache中是否已經包含有這個ip的結果,沒有再搜索文件
if (ipCache.containsKey(ipStr)) {
IPLocation loc = (IPLocation) ipCache.get(ipStr);
return loc.country;
} else {
IPLocation loc = getIPLocation(ip);
ipCache.put(ipStr, loc.getCopy());
return loc.country;
}
}
而IPLocation是個內部類!
private class IPLocation {
public String country;
public String area;
public IPLocation() {
country = area = "";
}
public IPLocation getCopy() {
IPLocation ret = new IPLocation();
ret.country = country;
ret.area = area;
return ret;
}
}
若緩存中沒有則-->
private IPLocation getIPLocation(byte[] ip) {
IPLocation info = null;
long offset = locateIP(ip);
if (offset != -1) {
info = getIPLocation(offset);
}
if (info == null) {
info = new IPLocation();
info.country = "";
info.area = "";
}
return info;
}
好,關鍵的是一個private long locateIP(byte[] ip)這個方法將根據ip的內容,定位到包含這個ip國家
地區的記錄處,返回一個絕對偏移 方法使用二分法查找。而private IPLocation getIPLocation(long
offset)返回IPLocation!OK!對於具體地代碼深入分析有待研究!
OK!我們完成了用戶的註冊了。
好的,我們 <result name="success" type="redirect">
/regSucceed.jsp //根目錄下!
</result>
而regSucceed.jsp向我們輸出了提示成功字樣。其中的路徑問題有待小心哦~我們login進入主頁面,繼續
上次的main.bbscs分析之,我們先看struts.xml:
<package name="main" extends="bbscs-default" namespace="/">
<default-interceptor-ref name="userAuthInterceptorStack"></default-
interceptor-ref>
這裏表明整個包都需要userAuthInterceptorStack:
<interceptor-stack name="userAuthInterceptorStack">
<interceptor-ref name="defaultStack"></interceptor-ref>
<interceptor-ref name="userLoginInterceptor"></interceptor-
ref>
<interceptor-ref
name="userPermissionInterceptor"></interceptor-ref>
</interceptor-stack>
我們先看main:
<action name="main" class="com.laoer.bbscs.web.action.Main">//不需要spring的
代理!
<result name="success">/WEB-INF/jsp/main.jsp</result>
</action>
它有四個屬性:inUrl,bid(long),postID,nagUrl,應該也是與JSP界面交互的吧!我們看execute:
public String execute() {
if (this.getAction().equalsIgnoreCase("read")) {
if (this.bid == 0 && StringUtils.isBlank(this.postID)) {
this.setInUrl(BBSCSUtil.getActionMappingURLWithoutPrefix
("in"));
} else {
this.setInUrl(BBSCSUtil.getActionMappingURLWithoutPrefix
("read?action=topic&bid=" + this.bid + "&id="
+ this.postID));
}
} else if (this.getAction().equalsIgnoreCase("forum")) {
if (this.bid == 0) {
this.setInUrl(BBSCSUtil.getActionMappingURLWithoutPrefix
("in"));
} else {
this.setInUrl(BBSCSUtil.getActionMappingURLWithoutPrefix
("forum?action=index&bid=" + this.bid));
}
} else {
if (StringUtils.isBlank(this.getInUrl())) {
this.setInUrl(BBSCSUtil.getActionMappingURLWithoutPrefix
("in"));
}
}
if (Constant.USE_URL_REWRITE) {
this.setNagUrl("nag.html");
}
else {
this.setNagUrl(BBSCSUtil.getActionMappingURLWithoutPrefix("nag"));
}
return SUCCESS;
}
這裏主要根據bid和action(還有postID)決定inUrl和nagUrl,當然一開始是
if (StringUtils.isBlank(this.getInUrl())) {
this.setInUrl(BBSCSUtil.getActionMappingURLWithoutPrefix
("in"));
}
this.setNagUrl(BBSCSUtil.getActionMappingURLWithoutPrefix("nag"));//系統默認設置
進入main.jsp我們發現,它其實是由一個大table中的一個tr有三個td:第一個是
frmTitle,iframe=nagFrame而它的src是一個action:
<td align="middle" noWrap vAlign="center" id="frmTitle" height="100%">
<iframe id="nagFrame" name="nagFrame" frameBorder="0" scrolling="auto"
src="<s:property value="%{nagUrl}"/>" class="iframe1"></iframe>
</td>
而第二個td裏面加了個table,裏面的tr->td <td onclick="switchSysBar()">
用於屏幕切換,第三個td爲mainFrame:
<td style="width: 100%">
<iframe frameBorder="0" id="mainFrame" name="mainFrame" scrolling="yes"
src="<s:property value="%{inUrl}"/>" class="iframe2"></iframe>
</td>
我們再看看JavaScript:
<script language="JavaScript" type="text/javascript">
if(self!=top){
top.location=self.location;
}
function switchSysBar(){
if (switchPoint.innerHTML=='<'){ //注意<->;>-<
switchPoint.innerHTML='>'
document.getElementById("frmTitle").style.display="none";
}
else{
switchPoint.innerHTML='<'
document.getElementById("frmTitle").style.display="block";
}
}
function changeMainFrameSrc(url) {
//alert(url);
document.getElementById("mainFrame").src = url;
}
接下來,我們分別分析/nag和/in。先從struts.xml看起:
<action name="nag" class="nagAction">
<interceptor-ref name="userAuthInterceptorStack"></interceptor-ref>
<interceptor-ref name="userSessionInterceptor"></interceptor-ref>
<result name="success">/WEB-INF/jsp/nag.jsp</result>
另外一個:
<action name="in" class="inAction">
<interceptor-ref name="mainUserAuthInterceptorStack"></interceptor-
ref>
<interceptor-ref name="requestBasePathInterceptor"></interceptor-
ref>
<result name="success">/WEB-INF/jsp/in.jsp</result>
</action>
在看action-servlet.xml,這裏我們應該發現所有被spring代理的action的格式基本上都是:
<bean id="nagAction"
class="com.laoer.bbscs.web.action.Nag" scope="prototype"
autowire="byName">
</bean>
<bean id="inAction"
class="com.laoer.bbscs.web.action.In" scope="prototype"
autowire="byName">
</bean>
我們先看userAuthInterceptorStack:
<interceptor-stack name="userAuthInterceptorStack">
<interceptor-ref name="defaultStack"></interceptor-ref>
<interceptor-ref name="userLoginInterceptor"></interceptor-
ref>
<interceptor-ref
name="userPermissionInterceptor"></interceptor-ref>
</interceptor-stack>
它們的scope="prototype" autowire="byName"一個請求一個對象,且自動注入類型:byName,進入
userLoginInterceptor, 這裏它完全用了一個通行證的做法,十分強大!我們看laoer的精彩說法:
天乙通行證是一個單點登錄系統(Single sign on),用戶登錄一次通行證,即可以直接使用社區、Blog
等產品,無需再次註冊或登錄,保證幾個產品用戶的統一,但天乙通行證由於本身沒有產品化,所以現在
沒有開源,如果使用天乙社區需要做用戶整合,只要實現通行證的類似系統就可以。 SSO的實現技術有多
種,也有一些開源產品,而天乙通行證使用的是比較簡單,但非常有效的方案,即同域名下的Cookie方式
,實現用戶的單點登錄。 以天乙通行證爲例,天乙社區(bbs.laoer.com)、天乙Blog(blog.laoer.com
)都運行在laoer.com域名下,用戶在www.laoer.com上註冊後登錄,通行證就會在用戶的客戶端寫入兩個
Cookie,一個是用戶名(Cookie名:PASS_USERNAME),一個是加密的用戶名(Cookie名:
PASS_USERNAME_DES),加密的方式採用3DES,採用3DES加密是因爲3DES可逆的加密算法,即對密文可以
還原,Cookie的Domain爲.laoer.com,這樣在laoer.com下的所有域名都可以訪問到這個Cookie。
接下來我們說一下社區的認證過程,社區採用Struts2的MVC結構,在Action之前,都會執行一個攔截器(
UserLoginInterceptor),這個攔截器首先會讀取通行證的兩個Cookie,如果兩個Cookie都存在,則對加
密的用戶名進行解密,採用的密鑰與通行證加密的密鑰一致,然後解密後的用戶名與另一個明文的用戶名
比對,如果兩個值一致,說明用戶已經在通行證登錄,則建立在社區的Session(如果Session已經存在,
則認爲用戶在社區也已經登錄),如果通行證的Cookie不存在或是用戶比對錯誤,則認爲用戶沒有登錄或
已經簽退,社區會做遊客登錄,詳細處理流程請閱讀UserLoginInterceptor.java源碼。
如果用戶在現有系統上整合社區,只要頂層域寫入兩個Cookie,在社區後臺中有設置通行證的項目,兩個
系統採用的密鑰一致就行了,同時3DES算法可以用多種語言實現,比如.NET、Ruby等等,所以可以在編程
語言異構的網絡系統中無縫使用,當然用戶也可以採用自己的加密解密算法。
這個方案要注意的問題:
1、只能使用在一個主域名下,如果要跨域名則不能實現。
2、需要用戶支持Cookie。
3、密鑰安全性,由於所有應用都使用統一的密鑰,需要在加強管理,定期更換,或是採用RSA方法對密鑰
加密,減少密鑰泄漏的可能性。
4、對於非WEB系統,比如客戶端程序,需要擴展其他方法實現。
原文見:http://bbs.laoer.com/main-read-15-ff80808113baa8140114123973985584.html
下面是我的理解:userLoginInterceptor首先引入request,response,servletContext,wc--
>sysConfig,us,uc-->一些服務!注意一點,通行證信息放在Cookie中....其判斷流程是這樣的:分兩類,
由sysConfig.isUsePass()決定!
(1)若使用通行證,根據uc.isLoginPass再分爲通行證是否登錄,若登錄即uc.isLoginPass爲true,按
us=null分兩類,有session和沒session,當us=null時,根據uc獲得用戶信息:
if (us == null) {// 用戶session沒有在社區存在
UserInfo ui =
userService.findUserInfoByUserName(uc.getPusername());
if (ui != null) {//進而分爲用戶是否存在,存在
的話,做登錄,且更新session和cookie
ui = userService.saveAtLogin(ui); // 用戶登錄處理
uo =
userOnlineService.createUserOnline(uo); // 加入在線用戶表
us =
userService.getUserSession(ui);
us.setLastActiveTime
(nowTime);
us.setValidateCode
(uo.getValidateCode());
ac.getSession().put
(Constant.USER_SESSION_KEY, us);
uc.addCookies(ui);
} else { // 用戶不存在,是新用戶
isNewUser = true;//等下一起創建之!
}
這是沒有session的情況,如果有session呢,也就是說用戶已經在社區登錄之中!
if (!us.getUserName().equals(uc.getPusername())) {// 用戶在社區中的登錄名和通行證中的用戶名
不一致.這時仍然由us.getusername得到用戶信息
UserInfo ui = userService.findUserInfoByUserName(uc.getPusername());
if (ui != null) { // 用戶存在,重新登錄,原來的session刪除之,以
cookie爲準
uc.removeAllCookies();//去掉原來BBSokie信息
/**
public void removeAllCookies() {
addC(BBSCS_FORUMPERNUM_KEY, "", 0);
addC(BBSCS_POSTPERNUM_KEY, "", 0);
addC(BBSCS_TIMEZONE_KEY, "", 0);
addC(BBSCS_LASTSENDNOTETIME_KEY, "", 0);
addC(BBSCS_LASTPOSTTIME_KEY, "", 0);
addC(BBSCS_FORUMVIEWMODE_KEY, "", 0);
addC(BBSCS_EDITTYPE, "-1", 0);
addC(BBSCS_AUTHCODE, "", 0);
addC(BBSCS_USERNAME, "", 0);
addC(BBSCS_PASSWD, "", 0);
}
*/
}else{isNewUser=true}
if(isNewUser){// 創建社區用戶,uc.getPusername()
}
這樣並沒有us.getUserNmae().equals(us.getPusername())的判斷,說明us以及uc不需要改變!
UserSession us = (UserSession) ac.getSession().get(Constant.USER_SESSION_KEY);
UserCookie uc = new UserCookie(request, response, sysConfig);
若uc.isLoginPass()爲假的話,通行證未登錄,做遊客登錄
if (us == null) {// 用戶沒有登錄過,直接做遊客登錄,有uo,us,uc.addGuestCookies();}
而用戶在社區是登錄狀態us(存在的話),需要強制做遊客登錄
if (us.getGroupID() != Constant.USER_GROUP_GUEST) {// //如果原來用戶不是遊客,先清除原
Session,做遊客登錄
// userSessionCache.remove
(uc.getUserName());
ac.getSession().remove
(Constant.USER_SESSION_KEY);
uc.removeAllCookies();
對於不使用通行證的話,根據us=null來決定,其子判斷uc.isSaveLoginCookie()
: public boolean isSaveLoginCookie() {
if (StringUtils.isNotBlank(this.userName) && StringUtils.isNotBlank
(this.passwd)) {
return true;
} else {
return false;
}
有的話,從cookie中取,沒有的話,Guest!我們看其中的一段代碼:
uo = userOnlineService.createUserOnline(uo);
us = this.createGuestUserSession
(uo.getUserID(), uo.getUserName(), userService);
/**
private UserSession createGuestUserSession(String gUestID, String gUesrName, UserService
userService) {
UserSession us = new UserSession();
us.setGroupID(Constant.USER_GROUP_GUEST);
us.setId(gUestID);
us.setNickName("Guest");
us.setUserName(gUesrName);
Map[] permissionMap = userService.getUserPermission
(Constant.USER_GROUP_GUEST);//public static final int USER_GROUP_GUEST = 1;
us.setUserPermissionArray(permissionMap);
return us;
}
*/
us.setLastActiveTime
(System.currentTimeMillis());
us.setValidateCode
(uo.getValidateCode());
ac.getSession().put
(Constant.USER_SESSION_KEY, us);
uc.removeAuthCode();//不用驗證了!
uc.addGuestCookies();
}
好的,我們看另外一個interceptor:userPermissionInterceptor,這個是用戶權限的攔截器:
這裏有 String actionName = "/" + ac.getName();// ac.getName()=nag!-->/nag!
String ajax = "html";
String saction = "";
Map map = ac.getParameters();
String[] _ajax = (String[]) map.get("ajax");//ajax參數!
if (_ajax != null) {
ajax = _ajax[0];
}
String[] _saction = (String[]) map.get("action");//action參數!
if (_saction != null) {
saction = _saction[0];
}
我們重點看下面的代碼:
UserSession us = (UserSession) ac.getSession().get(Constant.USER_SESSION_KEY);//獲得session
Permission permission = (Permission) us.getUserPermission().get
(actionName + "?action=*");//我們知道加入的時間用的是setUserPermissionArray
/**
private Map userPermission = new HashMap();
public void setUserPermissionArray(Map[] permissionMap) {
setSpecialPermission(permissionMap[1]);
Set pset = permissionMap[0].entrySet();
Iterator it = pset.iterator();
while (it.hasNext()) {
Map.Entry p = (Map.Entry) it.next();
Permission permission = (Permission) p.getValue();
String[] actions = permission.getAction().split(",");
for (int i = 0; i < actions.length; i++) {
String[] resources = ((String) p.getKey()).split(",");
this.getUserPermission().put(resources[0] + "?action=" +
actions[i], p.getValue());
}
}
}
*/
if (permission != null) {
havePermission = true;
} else {
permission = (Permission) us.getUserPermission().get
(actionName + "?action=" + saction);//帶有saction!
if (permission != null) {
havePermission = true;
} else {
havePermission = false;
}
}
if (havePermission) {
return invocation.invoke();
} else { //沒有權限的話
HttpServletRequest request = (HttpServletRequest) ac.get
(ServletActionContext.HTTP_REQUEST);
StringBuffer sb = new StringBuffer();
sb.append(BBSCSUtil.getWebRealPath(request));
sb.append(request.getContextPath());
sb.append("/");
sb.append(BBSCSUtil.getActionMappingURLWithoutPrefix
(ac.getName())); sb生成一個完整的URL
UrlHelper.buildParametersString(map, sb, "&");
String curl = sb.toString();//含參數的完整的URL
SysConfig sysConfig = (SysConfig) wc.getBean("sysConfig");
ResourceBundleMessageSource messageSource =
(ResourceBundleMessageSource) wc.getBean("messageSource");
if (ajax.equalsIgnoreCase("html")) {
String errorMsg = messageSource.getMessage
("error.noPermission", null, ac.getLocale());
ac.getValueStack().set("interceptError", errorMsg);
ac.getValueStack().set("tourl", curl);//保存當前URL
ac.getValueStack().set("useAuthCode",
sysConfig.isUseAuthCode());
if (sysConfig.getUsePass() == 0) {
return "intercepthtml";//不使用通行!
} else {
ac.getValueStack().set("actionUrl",
sysConfig.getPassUrl());//進入PASS的URL!
return "intercepthtmlpass";
}
} else if (ajax.equalsIgnoreCase("shtml")) {
String errorMsg = messageSource.getMessage
("error.noPermission", null, ac.getLocale());
ac.getValueStack().set("interceptError", errorMsg);
return "interceptshtml";
} else {
String errorMsg = messageSource.getMessage
("error.noPermission.ajax", null, ac.getLocale());
AjaxMessagesJson ajaxMessagesJson =
(AjaxMessagesJson) wc.getBean("ajaxMessagesJson");
ajaxMessagesJson.setMessage("E_NO_Permission",
errorMsg);
ac.getValueStack().set("ajaxMessagesJson",
ajaxMessagesJson);
return "ajaxjson";
}
}
/**
<global-results>
<result name="error">/WEB-INF/jsp/error.jsp</result>
<result name="htmlError">/WEB-INF/jsp/htmlError.jsp</result>
<result name="ajaxjson">/WEB-INF/jsp/ajaxjson.jsp</result>
<result name="jsonstring">/WEB-INF/jsp/jsonstring.jsp</result>
<result name="intercepthtml">/WEB-
INF/jsp/intercepthtml.jsp</result>//用到
<result name="intercepthtmlpass">/WEB-
INF/jsp/intercepthtmlpass.jsp</result>//用到
<result name="interceptshtml">/WEB-
INF/jsp/interceptshtml.jsp</result>//用到
<result name="relogin" type="redirect-action">login?
action=relogin</result>
<result name="boardPasswd">/WEB-INF/jsp/boardPasswd.jsp</result>
</global-results>
*/
我們可以看看具體的JSP頁面,其中html是有登錄的,而sthml是另外一種類型.它們都是ajax請求時,在
url上帶的參數,用於區分返回的頁面類型。
<%@page contentType="text/html; charset=UTF-8"%>
<%@taglib uri="/WEB-INF/struts-tags.tld" prefix="s"%>
<div id="error" class="errormsg">
<s:property value="%{interceptError}"/>
</div>
我們看看資源文件中的內容:
error.noPermission=您沒有操作此項功能的權限,如果您還沒有登錄,可能需要登錄才能操作!<a
href="javascript:;" onclick="Element.toggle('loginform');"><strong>我要登錄</strong></a>
error.noPermission.ajax=您沒有操作此項功能的權限!
注意:Element.toggle是prototype.js中的函數,交替隱藏或顯示,見資料:
http://blog.sina.com.cn/u/562f0574010009pk
好的,我們將兩個複雜的interceptor分析完了!我們還是回到Nag.java中吧:
它負責導航菜單的顯示,extends BaseAction implements UserSessionAware,我們直接看execute方法
:
public String execute() {
int isHidden = 0;
if (userSession.isHaveSpecialPermission
(Constant.SPERMISSION_CAN_SEE_HIDDEN_BOARD)) { // 如果用戶有查看隱藏版區的權限public static
final long SPERMISSION_CAN_SEE_HIDDEN_BOARD = 901;
/**
public boolean isHaveSpecialPermission(long permissionID) {
return this.specialPermission.containsKey(new Long(permissionID));
}
*/
isHidden = -1;
}
this.setUrlRewrite(Constant.USE_URL_REWRITE);
this.boardList = this.getBoardService().findBoardsByParentID(0, 1, isHidden,
Constant.FIND_BOARDS_BY_ORDER);//頂層的!
// System.out.println(boardList);
for (int i = 0; i < this.boardList.size(); i++) {
Board b = (Board) this.boardList.get(i);
List bclist = this.getBoardService().findBoardsByParentID(b.getId(),
1, isHidden,
Constant.FIND_BOARDS_BY_ORDER);
this.boardMap.put(b.getId(), bclist);//2級的放入boardMap
}
List bsaveids = this.getBoardSaveService().findBoardSaveBidsByUid
(userSession.getId());userSession.getId()-->userId
this.boardSaveList = this.getBoardService().findBoardsInIDs(bsaveids);//用戶
保存的版區!
this.setUsePass(this.getSysConfig().isUsePass());
return SUCCESS;
}
我們看jsp頁面:
<!--
function loadChild(bid,btype) {
var obj = document.getElementById("child" + bid);
var imgObj = document.getElementById("img" + bid);
if (obj.style.display == "none") {
obj.style.display = "block";
imgObj.src="images/collapse.gif";
}
else {
obj.style.display = "none";
imgObj.src="images/expand.gif";
}
}
function loadBoardSave() {
var obj = document.getElementById("boardSaveDiv");
var imgObj = document.getElementById("boardSaveImg");
if (obj.style.display == "none") {
obj.style.display = "block";
imgObj.src="images/collapse.gif";
}
else {
obj.style.display = "none";
imgObj.src="images/expand.gif";
}
}
function loadUserCenter() {
var obj = document.getElementById("userCenterDiv");
var imgObj = document.getElementById("imgUserCenterSet");
if (obj.style.display == "none") {
obj.style.display = "block";
imgObj.src="images/collapse.gif";
}
else {
obj.style.display = "none";
imgObj.src="images/expand.gif";
}
}
function loadUserLogout() { //不用了
var obj = document.getElementById("userLogoutDiv");
var imgObj = document.getElementById("imgLogout");
if (obj.style.display == "none") {
obj.style.display = "block";
imgObj.src="images/collapse.gif";
}
else {
obj.style.display = "none";
imgObj.src="images/expand.gif";
}
}
//-->
</script>
<base target="mainFrame"/>
body區域分<div> <ul><li>第一項</li>下面是用戶中心的li:
<li><a href="javascript:;" onclick="loadUserCenter();"><img id="imgUserCenterSet"
src="images/expand.gif" alt="" width="25" height="15" border="0" align="absmiddle"/><s:text
name="nag.usercenter"/></a></li>
而其下的div:
<div id="userCenterDiv" class="nag" style="display:none">
<ul>
<li>
<s:url action="signSet" id="signSetUrl"></s:url>
<a href="${signSetUrl}"><img id="imgSignSet" src="images/node.gif" alt=""
border="0" align="absmiddle"/><s:text name="signset.title"/></a>
</li>
<li>
<s:url action="nickNameSet" id="nickNameSetUrl"></s:url>
<a href="${nickNameSetUrl}"><img id="imgNickNameSet" src="images/node.gif" alt=""
border="0" align="absmiddle"/><s:text name="nickset.title"/></a>
</li>
<s:url action="userConfig" id="userConfigUrl"></s:url>
<li><a href="${userConfigUrl}"><img id="imgUserConfig" src="images/node.gif"
alt="" border="0" align="absmiddle"/><s:text name="userconfig.title"/></a></li>
<s:url action="friendSet" id="friendSetUrl"></s:url>
<li><a href="${friendSetUrl}"><img id="imgFriendSet" src="images/node.gif" alt=""
border="0" align="absmiddle"/><s:text name="friend.title"/></a></li>
<s:url action="note" id="noteUrl"></s:url>
<li><a href="${noteUrl}"><img id="imgNote" src="images/node.gif" alt="" border="0"
align="absmiddle"/><s:text name="note.title"/></a></li>
<s:url action="bookMark" id="bookMarkUrl"></s:url>
<li><a href="${bookMarkUrl}"><img id="imgBookMark" src="images/node.gif" alt=""
border="0" align="absmiddle"/><s:text name="bookmark.title"/></a></li>
<s:url action="userFace?action=index" id="userFaceUrl"></s:url>
<li><a href="${userFaceUrl}"><img id="imgFace" src="images/node.gif" alt=""
border="0" align="absmiddle"/><s:text name="face.title"/></a></li>
<s:url action="userDetailSet?action=index" id="userDetailSetUrl"></s:url>
<li><a href="${userDetailSetUrl}"><img id="imgUserDetailSet" src="images/node.gif"
alt="" border="0" align="absmiddle"/><s:text name="userdetail.title"/></a></li>
<s:url action="cpasswd?action=index" id="cpasswdUrl"></s:url>
<li><a href="${cpasswdUrl}"><img id="imgCpasswd" src="images/node.gif" alt=""
border="0" align="absmiddle"/><s:text name="cpasswd.title"/></a></li>
<s:url action="boardSaveManage" id="boardSaveManageUrl"></s:url>
<li><a href="${boardSaveManageUrl}"><img id="imgboardSaveManage"
src="images/node.gif" alt="" border="0" align="absmiddle"/><s:text
name="boardsave.title"/></a></li>
</ul>
</div>
</url>
</div>//上面的部分完成顯示!
加根Hr,其它類似,我們重點分析如下:
<s:iterator id="b1" value="%{boardList}">
<li>
<s:if test="%{urlRewrite}">
<a href="javascript:;" onclick="loadChild('<s:property value="#b1.id"/>','<s:property
value="#b1.boardType"/>')"><img id="img<s:property value="#b1.id"/>" src="images/expand.gif"
alt="展開" width="25" height="15" border="0" align="absmiddle"/></a><a href="forum-index-
<s:property value="#b1.id"/>.html"><s:property value="#b1.boardName"/></a>
</s:if>
<s:else>
<s:url action="forum?action=index" id="furl">
<s:param name="bid" value="#b1.id"/>
</s:url>
<a href="javascript:;" onclick="loadChild('<s:property value="#b1.id"/>','<s:property
value="#b1.boardType"/>')"><img id="img<s:property value="#b1.id"/>" src="images/expand.gif"
alt="展開" width="25" height="15" border="0" align="absmiddle"/></a><a
href="${furl}"><s:property value="#b1.boardName"/></a>
</s:else>
<div id="child<s:property value="#b1.id"/>" class="nag" style="display:none">
<s:set name="bl2" value="%{boardMap.get(#b1.id)}"></s:set>
<ul>
<s:iterator id="b" value="#bl2">
<s:url action="forum?action=index" id="burl">
<s:param name="bid" value="#b.id"/>
</s:url>
<li>
<s:if test="%{urlRewrite}">
<img id="img<s:property value="#b.id"/>" src="images/node.gif" alt=""
align="absmiddle"/><a href="forum-index-<s:property value="#b.id"/>.html"><s:property
value="#b.boardName"/></a>
</s:if>
<s:else>
<s:url action="forum?action=index" id="burl">
<s:param name="bid" value="#b.id"/>
</s:url>
<img id="img<s:property value="#b.id"/>" src="images/node.gif" alt=""
align="absmiddle"/><a href="${burl}"><s:property value="#b.boardName"/></a>
</s:else>
</li>
</s:iterator>
</ul>
</div>
</li>
好,我們看in!首先看它的interceptor:
<action name="in" class="inAction">
<interceptor-ref name="mainUserAuthInterceptorStack"></interceptor-
ref>
<interceptor-ref name="requestBasePathInterceptor"></interceptor-
ref>//講過
<result name="success">/WEB-INF/jsp/in.jsp</result>
</action>
這裏有一個mainUserAuthInterceptorStack:
<interceptor-stack name="mainUserAuthInterceptorStack">
<interceptor-ref name="defaultStack"></interceptor-ref>
<interceptor-ref name="userLoginInterceptor"></interceptor-
ref>//講過
<interceptor-ref
name="userSessionInterceptor"></interceptor-ref>
<interceptor-ref name="userOnlineInterceptor"></interceptor
-ref>
<interceptor-ref
name="userPermissionInterceptor"></interceptor-ref>//講過!
</interceptor-stack>
好,我們看看UserSessionInterceptor和userOnlineInterceptor吧:
public String intercept(ActionInvocation invocation) throws Exception {
ActionContext ac = invocation.getInvocationContext();
Object action = invocation.getAction();
if (action instanceof UserSessionAware) {
HttpServletRequest request = (HttpServletRequest) ac.get
(ServletActionContext.HTTP_REQUEST);
HttpServletResponse response = (HttpServletResponse) ac.get
(ServletActionContext.HTTP_RESPONSE);
ServletContext servletContext = (ServletContext) ac.get
(ServletActionContext.SERVLET_CONTEXT);
WebApplicationContext wc =
WebApplicationContextUtils.getWebApplicationContext(servletContext);
if (wc == null) {
logger.error("ApplicationContext could not be found.");
} else {
SysConfig sysConfig = (SysConfig) wc.getBean("sysConfig");
UserCookie userCookie = new UserCookie(request, response,
sysConfig);//得到UserCookie!
((UserSessionAware) action).setUserCookie(userCookie);
UserSession us = (UserSession) ac.getSession().get
(Constant.USER_SESSION_KEY);//得到UserSession!
((UserSessionAware)
action).setUserSession(us);
}
}
return invocation.invoke();//將兩個都放到UserSessionAware(它有兩個公開實現的
方法),以便action類使用!
/**
public interface UserSessionAware {
public void setUserCookie(UserCookie userCookie);
public void setUserSession(UserSession userSession);
}
*/
}
現在,我們要認真地分析下userOnlineInterceptor:
long nowTime = System.currentTimeMillis();//現在的時間long
long addedTime = nowTime - us.getLastActiveTime();//相隔的時間
us.setAddedOnlineTime(us.getAddedOnlineTime() + addedTime);//累加在
線時間
us.setAddedOnlineHour(us.getAddedOnlineHour() + addedTime);//累加在
線小時數
其實這個攔截器是在us.getAddedOnlineTime()>(sysConfig.getUserOnlineTime()*1000)這個條件去改變
相關的數據的!若不存在在線信息,添加之!若存在uo,其中比較了uo 和us的validateCode:
if (uo != null && !uo.getValidateCode().equals(us.getValidateCode())) { // 用戶重複登錄
String ajax = "html";
Map map = ac.getParameters();//action的參數
!
String[] _ajax = (String[]) map.get("ajax");
if (_ajax != null) {
ajax = _ajax[0];//ajax="html"
}
ResourceBundleMessageSource messageSource =
(ResourceBundleMessageSource) wc
.getBean("messageSource");
String errorMsg = messageSource.getMessage
("error.login.re", null, ac.getLocale());
if (ajax.equalsIgnoreCase("html") ||
ajax.equalsIgnoreCase("shtml")) {
return "relogin";
} else {
AjaxMessagesJson ajaxMessagesJson =
(AjaxMessagesJson) wc.getBean("ajaxMessagesJson");
ajaxMessagesJson.setMessage
("E_LOGIN", errorMsg);
ac.getValueStack().set
("ajaxMessagesJson", ajaxMessagesJson);
return "ajaxjson";
}
這裏的relogin:
<result name="relogin" type="redirect-action">login?action=relogin</result>注意你有個
actionerror!
接下來,是修改相應的userinfo信息及清理us!
UserInfo ui = userService.findUserInfoById(us.getId());
if (ui != null) { // 取得正確用戶信息
ui.setStayTime(ui.getStayTime() +
(us.getAddedOnlineTime() / 1000)); // 增加用戶在線時長
us.setAddedOnlineTime(0);
// 刷新權限
if (Constant.USE_PERMISSION_CACHE) {
logger.debug("刷新用戶權限");
us.setGroupID(ui.getGroupID());
us.setBoardPermissionArray
(userService.getUserPermission(ui));
}
if (us.getAddedOnlineHour() >= 3600000) {
logger.info("用戶在線累計超過1個小時
,增加用戶經驗值");
ui.setExperience(ui.getExperience()
+ (int) (us.getAddedOnlineHour() / 3600000));
us.setAddedOnlineHour(0);
}
最後,我們應該改變useronline的時間(用戶在線存在時),以及us的活躍時間!
us.setLastActiveTime(nowTime);
ac.getSession().put(Constant.USER_SESSION_KEY, us);
好的,我們回到in:
<action name="in" class="inAction">
<interceptor-ref name="mainUserAuthInterceptorStack"></interceptor-
ref>//它有默認
defaultStack,userLoginInterceptor,userSessionInterceptor,userOnlineInterceptor,userPermissio
nInterceptor(請注意聯繫與順序)
<interceptor-ref name="requestBasePathInterceptor"></interceptor-
ref>
<result name="success">/WEB-INF/jsp/in.jsp</result>
</action>
我們進入In.java不過它只實現了RequestBasePathAware:
其execute由index去完成!我們注意到extends BaseMainAction,而BaseMainAction:
public class BaseMainAction extends BaseAction implements UserSessionAware {
private UserCookie userCookie;//get/set
private UserSession userSession;
public String execute() {
try {
return this.executeMethod(this.getAction());
} catch (Exception e) {
logger.error(e);
this.addActionError(this.getText("error.msg"));
return ERROR;
}
}
-->
protected String executeMethod(String method) throws Exception {
Class[] c = null;
Method m = this.getClass().getMethod(method, c);
Object[] o = null;
String result = (String) m.invoke(this, o);
return result;
}
這個Action類注入有這些服務
userService,notService,friendService,userOnlineService,forumService,sysConfig,sysStatService
,boardService!而也於表單的或其它的屬性(頁面調用)有:
onlineHighest,sysinfo,lastLoginTime,titleValue,userTitle,newNoteNumInbox,noteAllNumInbox,fre
indNum,onlineGuestNum,friendOnlineNum,guest,newForums
(List<Forum>),boardList,boardMap,urlRewrite,usePass,actionUrl,toUrl,forumSite,useAuthCode.
我們先看onlineNum和onlineGuestNum的做法:
long atime = System.currentTimeMillis() - (this.getSysConfig().getUserOnlineTime() * 1000);
long onlineNum = this.getUserOnlineService().getUserOnlineNum(atime, 0, 0,
Constant.NORMAL_USER_GROUPS);
long onlineGuestNum = this.getUserOnlineService().getUserOnlineNum(atime, 0,
0, Constant.GUEST_USER_GROUPS);
this.getSysStatService().saveOnline(onlineNum + onlineGuestNum);
this.setSysinfo(this.getText("bbscs.sysinfo", new String[] {
String.valueOf(this.getSysStatService().getPostMainNum()),
String.valueOf(this.getSysStatService().getPostNum()),
String.valueOf(this.getSysStatService().getAllUserNum()), reguserurl
}));//bbscs.sysinfo=社區信息:主題數: {0} / 帖子總數: {1} / 社區人數: {2} / 歡迎新用戶: {3}
其中:
long foNum = this.getUserOnlineService().getUserOnlineNumInIds(atime,
this.getFriendService().findFriendIds
(this.getUserSession().getId(), 0), 0, 0,
Constant.NORMAL_USER_GROUPS);
this.setFriendOnlineNum(foNum);
其實struts2.0的Action我覺得比Struts1.2強在不僅能從表單得到數據,也能將數據傳遞過JSP界面,通
過標籤或表達式語言show出來!
到in.jsp看看:
對於這個主頁充分體現了strut2在界面上做的一些工作!請參考相關資料:
http://www.blogjava.net/max/archive/2006/10/18/75857.aspx
http://www.blogjava.net/max/archive/2007/04/28/114417.html
總體上對版區的顯示是:
<s:iterator id="board" value="%{boardList}">
<s:if test="#board.boardType==1">
</s:if>
<s:if test="#board.boardType==3">
</s:if>
<s:set name="bl2" value="%{boardMap.get(#board.id)}"></s:set>
<s:iterator id="b" value="#bl2">
</s:iterator>
</s:iterator>
下面的最新貼由action傳遞過來的值:
<table width="100%" border="0" cellpadding="3" cellspacing="0">
<s:iterator id="newf" value="%{newForums}">
<tr>
<td>
<s:if test="%{urlRewrite}">
<a href="read-topic-<s:property value="#newf.boardID"/>-
<s:property value="#newf.mainID"/>-0-1-index-1.html"><s:property value="#newf.title"/></a>
[<a href="forum-index-<s:property
value="#newf.boardID"/>.html"><s:property value="#newf.boardName"/></a>]
</s:if>
<s:else>
<s:url action="read?action=topic"
id="posturl">
<s:param name="bid"
value="#newf.boardID"/>
<s:param name="id"
value="#newf.mainID"/>
<s:param name="fcpage" value="1"/>
<s:param name="fcaction"
value="index"/>
</s:url>
<a href="${posturl}"><s:property
value="#newf.title"/></a>
<s:url action="forum?action=index"
id="forumurl">
<s:param name="bid"
value="#newf.boardID"/>
</s:url>
[<a href="${forumurl}"><s:property
value="#newf.boardName"/></a>]
</s:else>
</td>
</tr>
</s:iterator>
</table>
而其它的(如:班竹推薦,積分榜)大多由bbscs標籤完成!我們來看看我
<table width="100%" border="0" cellpadding="3" cellspacing="0">
<bbscs:in type="commend"/>
</table>
這裏我們重點分析一下bbscs在這頁的一些tag:
<bbscs:face value="%{userSession.id}"/>
<bbscs:datetimes format="yyyy-MM-dd HH:mm:ss" datetype="date" value="%{lastLoginTime}"/>
<bbscs:boardmaster value="#board.boardMaster"/>
<bbscs:in type="commend"/>
<bbscs:in type="userexp"/>
<bbscs:in type="userlit"/>
<bbscs:in type="userknow"/>
我們一個一個來看:
<tag>
<name>face</name>
<tag-class>com.laoer.bbscs.web.taglib.UserFaceTag</tag-class>
<body-content>empty</body-content>
<attribute>
<name>value</name>
<required>true</required>
<rtexprvalue>true</rtexprvalue>
</attribute>
</tag>
我們直接看UserFace.java:
public boolean start(Writer writer) {
boolean result = super.start(writer);
if (value == null) {
value = "top";
} else if (altSyntax()) {
if (value.startsWith("%{") && value.endsWith("}")) {
value = value.substring(2, value.length() -
1);//userSession.id
}
}
String userId = "";
Object idObj = this.getStack().findValue(value);//從棧中取值!
if (idObj != null) {
userId = (String) idObj;
}
StringBuffer sb = new StringBuffer();
if (StringUtils.isBlank(userId)) {
sb.append("<img src="");
sb.append(facePicName);
sb.append("" alt="Face" />");
try {
writer.write(sb.toString());
} catch (IOException e) {
e.printStackTrace();
}
return result;
} else {
if (userId.startsWith(Constant.GUEST_USERID)) { // 遊客
sb.append("<img src="");
sb.append(facePicName);
sb.append("" alt="Face" />");
} else { // 正常用戶
WebApplicationContext wc =
WebApplicationContextUtils.getWebApplicationContext(this.pageContext
.getServletContext());
UserService us = (UserService) wc.getBean("userService");
UserInfoSimple uis = us.getUserInfoSimple(userId);
if (uis.getHavePic() == 1 && !uis.getPicFileName().equals
("-")) {
sb.append("<a href="");
sb.append(BBSCSUtil.getUserWebPath(uis.getId()));
sb.append(uis.getPicFileName());//大圖
sb.append("" target="_blank">");
sb.append("<img src="");
sb.append(BBSCSUtil.getUserWebPath(uis.getId()));
sb.append(uis.getPicFileName());
sb.append(Constant.IMG_SMALL_FILEPREFIX);//小圖
sb.append("" alt="Face" border="0"
class="pic1"/>");
sb.append("</a>");
} else {
sb.append("<img src="");
sb.append(facePicName);
sb.append("" alt="Face" />");
}
}
try {
writer.write(sb.toString());
} catch (IOException e) {
e.printStackTrace();
}
}
return result;
}
我們看第二個:
<bbscs:datetimes format="yyyy-MM-dd HH:mm:ss" datetype="date" value="%{lastLoginTime}"/>
<tag>
<name>datetimes</name>
<tag-class>com.laoer.bbscs.web.taglib.DateTimesTag</tag-class>
<body-content>empty</body-content>
<attribute>
<name>value</name>
<required>true</required>
<rtexprvalue>true</rtexprvalue>
</attribute>
<attribute>
<name>datetype</name>
<required>true</required>
<rtexprvalue>true</rtexprvalue>
</attribute>
<attribute>
<name>format</name>
<required>false</required>
<rtexprvalue>true</rtexprvalue>
</attribute>
</tag>
我們看com.laoer.bbscs.web.taglib.DateTimes關鍵的代碼片斷:
Date date = null;
if (this.datetype.equalsIgnoreCase("timestamp")) {
long atime = (Long) this.getStack().findValue(value);
date = new Date(atime);
} else if (this.datetype.equalsIgnoreCase("date")) {
date = (Date) this.getStack().findValue(value);
} else {
date = new Date();
}
String stime = Util.formatDate(date, format);//format有默認格式!
try {
writer.write(stime);
} catch (IOException e) {
logger.error(e);
}
好,我們看第三個:<bbscs:boardmaster value="#board.boardMaster"/> #board是個標誌!
<tag>
<name>boardmaster</name>
<tag-class>com.laoer.bbscs.web.taglib.BoardMasterTag</tag-class>
<body-content>empty</body-content>
<attribute>
<name>value</name>
<required>true</required>
<rtexprvalue>true</rtexprvalue>
</attribute>
</tag>
其主要的代碼如下:
Map boardMasterMap = (Map) this.getStack().findValue(value);//返回的是Map對象!
if (boardMasterMap != null) {
StringBuffer sb = new StringBuffer();
// Set bmSet = boardMasterMap.entrySet();
Iterator bmit = boardMasterMap.values().iterator();
while (bmit.hasNext()) {
com.laoer.bbscs.bean.BoardMaster bm =
(com.laoer.bbscs.bean.BoardMaster) bmit.next();
if (bm.getIsHidden() == 0) {//不是隱身的!
sb.append("<a href="");
sb.append(BBSCSUtil.getActionMappingURL("/userInfo?
action=name&username=" + bm.getUserName(),
request));
sb.append("">");
sb.append(bm.getUserName());
sb.append("</a> ");
}
}
try {
writer.write(sb.toString());
} catch (IOException e) {
e.printStackTrace();
}
}
好,我們看最後一個了:<bbscs:in type="userknow"/> type有四種!
<tag>
<name>in</name>
<tag-class>com.laoer.bbscs.web.taglib.InTag</tag-class>
<body-content>empty</body-content>
<attribute>
<name>type</name>
<required>true</required>
<rtexprvalue>true</rtexprvalue>
</attribute>
</tag>
由於InTag中:
public Component getBean(ValueStack arg0, HttpServletRequest arg1,
HttpServletResponse arg2) {
return new InComponent(arg0);
}
protected void populateParams() {
super.populateParams();
InComponent tag = (InComponent)component;
tag.setType(type);
}
所示其執行者爲InComponent,而非In!這可在InComponent.java裏看到:
if (this.getType().equalsIgnoreCase("commend")) {
File commendFile = new File(BBSCSUtil.getIncludePath() +
"ForumCover_Commend_0.html");
String commendlist = "";
try {
commendlist = FileUtils.readFileToString(commendFile,
Constant.CHARSET);
if (commendlist == null) {
commendlist = "";
}
} catch (IOException ex) {
logger.error(ex);
commendlist = "";
}
this.write(writer, commendlist);
return result;
}
-->
private void write(Writer writer, String txt) {
try {
writer.write(txt);
} catch (IOException e) {
e.printStackTrace();
}
}
最後,我們應該對in.jsp中的一些東西有點感覺:
<s:property value="%{sysinfo}" escape="false"/>
其後面提供了google對本系統的搜索,也提供了搜索用戶的表單:
<s:form action="userInfo">
<s:hidden name="action" value="name"></s:hidden>