zoukankan
html css js c++ java
ASP.NET在线用户列表精确版——解决用户意外退出在线列表无法及时更新问题
【原创作者】:丛兴滋(cncxz)[E
-
mail:cncxz@
126
.com]
【关 键 词】:xmlhttp ASP.NET在线用户列表 关闭浏览器 精确统计 单人登陆
【代码下载】:http:
//
www.thisky.cn/down/onlineuser.rar
最近所做的一个项目需要用到的在线用户列表,上网搜索了一下发现现有的解决方案对用户意外退出的处理均不是太理想。一般来说,用户离开系统的方式有三种:主动注销、会话超时、直接关闭浏览器,对于前两种,我们很容易便可将该用户从在线列表中清除,关键是第三种(很多用户都是直接关闭窗口的
~~
郁闷ing),程序无法捕获窗口关闭的精确时间,只能等到会话超时后在能将该用户清除出在线列表,假设我们设置会话超时时间为60分钟,而用户登陆系统随便浏览一个页面就以关闭浏览器的方式退出的话,我们要在将近1小时后才能从在线列表中将该用户清除出去(想象一下,系统显示n多人在线,可能除了你之外其他的n
-
1人都关机走人了,汗一个先```),而本文将尝试寻找一个解决方案把这种尴尬降至最低。
我的大概思路是,给每在线用户增加一个RefreshTime属性,建立一个负责将当前用户的RefreshTime属性设置为当前时间的单独页面(Refresh.aspx),然后在系统的主要页面(也可以是所有页面)中通过xmlhttp不断地请求Refresh.aspx页面,一旦用户关闭了与本系统相关的所有窗口,即以直接关闭浏览器的方式退出系统,那么该用户的RefreshTime属性便不会自动更新了,我们再设置一个自动刷新的超时时间(这个要比会话超时短很多_refreshTimeout),当发现某用户超过_refreshTimeout的时间没有自动刷新,就能判定该用户已经以直接关闭浏览器的方式退出了。
假设我们设置会话超时时间为60分钟,自动刷新超时时间为1分钟,在客户端通过xmlhttp每隔25秒(之所以不设1分钟,是防止网速慢的时候访问Refresh.aspx超时,个人感觉,不一定正确)访问一次Refresh.aspx页面,在用户登陆、用户注销、检测用户是否在线的时候都执行清理超时用户(包括会话超时和自动刷新超时)操作,这样一来,在线用户列表的统计误差就由60分钟降至1分钟了。
==========================================
具体实现如下:
1
、 新建一个名为ActiveUser的类,存储单个活动用户数据。
/**/
///
<summary>
///
单个在线用户数据,无法继承此类。
///
</summary>
public
sealed
class
ActiveUser
{
private
readonly
string
_ticket;
//
票据名称
private
readonly
string
_username;
//
登陆用户名
private
readonly
string
_truename;
//
登陆用户名
private
readonly
string
_roleid;
//
角色
private
readonly
DateTime _refreshtime;
//
最新刷新时间
private
readonly
DateTime _activetime;
//
最新活动时间
private
readonly
string
_clientip;
//
登陆IP
public
ActiveUser(
string
Ticket,
string
UserName,
string
TrueName,
string
RoleID,
string
ClientIP)
{
this
._ticket
=
Ticket;
this
._username
=
UserName;
this
._truename
=
TrueName;
this
._roleid
=
RoleID;
this
._refreshtime
=
DateTime.Now;
this
._activetime
=
DateTime.Now;
this
._clientip
=
ClientIP;
}
public
ActiveUser(
string
Ticket,
string
UserName,
string
TrueName,
string
RoleID,DateTime RefreshTime,DateTime ActiveTime,
string
ClientIP)
{
this
._ticket
=
Ticket;
this
._username
=
UserName;
this
._truename
=
TrueName;
this
._roleid
=
RoleID;
this
._refreshtime
=
RefreshTime;
this
._activetime
=
ActiveTime;
this
._clientip
=
ClientIP;
}
public
string
Ticket
{
get
{
return
_ticket;}
}
public
string
UserName
{
get
{
return
_username;}
}
public
string
TrueName
{
get
{
return
_truename;}
}
public
string
RoleID
{
get
{
return
_roleid;}
}
public
DateTime RefreshTime
{
get
{
return
_refreshtime;}
}
public
DateTime ActiveTime
{
get
{
return
_activetime;}
}
public
string
ClientIP
{
get
{
return
_clientip;}
}
}
2
、 新建一个名为PassPort的类,存储在线用户列表。
/**/
///
<summary>
///
PassPort 存储在线用户列表。
///
</summary>
public
class
PassPort
{
private
static
DataTable _activeusers;
private
int
_activeTimeout;
private
int
_refreshTimeout;
/**/
///
<summary>
///
初始化在线用户表。
///
</summary>
private
void
userstableFormat()
{
if
(_activeusers
==
null
)
{
_activeusers
=
new
DataTable(
"
ActiveUsers
"
);
DataColumn myDataColumn;
System.Type mystringtype;
mystringtype
=
System.Type.GetType(
"
System.String
"
);
System.Type mytimetype;
mytimetype
=
System.Type.GetType(
"
System.DateTime
"
);
myDataColumn
=
new
DataColumn(
"
Ticket
"
,mystringtype);
_activeusers.Columns.Add(myDataColumn);
myDataColumn
=
new
DataColumn(
"
UserName
"
,mystringtype);
_activeusers.Columns.Add(myDataColumn);
myDataColumn
=
new
DataColumn(
"
TrueName
"
,mystringtype);
_activeusers.Columns.Add(myDataColumn);
myDataColumn
=
new
DataColumn(
"
RoleID
"
,mystringtype);
_activeusers.Columns.Add(myDataColumn);
myDataColumn
=
new
DataColumn(
"
RefreshTime
"
,mytimetype);
_activeusers.Columns.Add(myDataColumn);
myDataColumn
=
new
DataColumn(
"
ActiveTime
"
,mytimetype);
_activeusers.Columns.Add(myDataColumn);
myDataColumn
=
new
DataColumn(
"
ClientIP
"
,mystringtype);
_activeusers.Columns.Add(myDataColumn);
}
}
public
PassPort()
{
userstableFormat();
//
初始化在线用户表
//
活动超时时间初始化 单位:分钟
try
{ _activeTimeout
=
int
.Parse(ConfigurationSettings.AppSettings[
"
ActiveTimeout
"
]); }
catch
{ _activeTimeout
=
60
; }
//
自动刷新超时时间初始化 单位:分钟
try
{ _refreshTimeout
=
int
.Parse(ConfigurationSettings.AppSettings[
"
RefreshTimeout
"
]); }
catch
{ _refreshTimeout
=
1
; }
}
//
全部用户列表
public
DataTable ActiveUsers
{
get
{
return
_activeusers.Copy();}
}
/**/
///
<summary>
///
新用户登陆。
///
</summary>
public
void
Login(ActiveUser user,
bool
SingleLogin)
{
DelTimeOut();
//
清除超时用户
if
(SingleLogin)
{
//
若是单人登陆则注销原来登陆的用户
this
.Logout(user.UserName,
false
);
}
DataRow myRow;
try
{
myRow
=
_activeusers.NewRow();
myRow[
"
Ticket
"
]
=
user.Ticket.Trim();
myRow[
"
UserName
"
]
=
user.UserName.Trim();
myRow[
"
TrueName
"
]
=
""
+
user.TrueName.Trim();
myRow[
"
RoleID
"
]
=
""
+
user.RoleID.Trim();
myRow[
"
ActiveTime
"
]
=
DateTime.Now;
myRow[
"
RefreshTime
"
]
=
DateTime.Now;
myRow[
"
ClientIP
"
]
=
user.ClientIP.Trim();
_activeusers.Rows.Add(myRow);
}
catch
(Exception e)
{
throw
(
new
Exception(e.Message));
}
_activeusers.AcceptChanges();
}
/**/
///
<summary>
///
用户注销,根据Ticket或UserName。
///
</summary>
private
void
Logout(
string
strUserKey,
bool
byTicket)
{
DelTimeOut();
//
清除超时用户
strUserKey
=
strUserKey.Trim();
string
strExpr;
strExpr
=
byTicket
?
"
Ticket='
"
+
strUserKey
+
"
'
"
:
"
UserName='
"
+
strUserKey
+
"
'
"
;
DataRow[] curUser;
curUser
=
_activeusers.Select(strExpr);
if
(curUser.Length
>
0
)
{
for
(
int
i
=
0
; i
<
curUser.Length; i
++
)
{
curUser[i].Delete();
}
}
_activeusers.AcceptChanges();
}
/**/
///
<summary>
///
用户注销,根据Ticket。
///
</summary>
///
<param name="strTicket">
要注销的用户Ticket
</param>
public
void
Logout(
string
strTicket)
{
this
.Logout(strTicket,
true
);
}
/**/
///
<summary>
///
清除超时用户。
///
</summary>
private
bool
DelTimeOut()
{
string
strExpr;
strExpr
=
"
ActiveTime < '
"
+
DateTime.Now.AddMinutes(
0
-
_activeTimeout)
+
"
'or RefreshTime < '
"
+
DateTime.Now.AddMinutes(
0
-
_refreshTimeout)
+
"
'
"
;
DataRow[] curUser;
curUser
=
_activeusers.Select(strExpr);
if
(curUser.Length
>
0
)
{
for
(
int
i
=
0
; i
<
curUser.Length; i
++
)
{
curUser[i].Delete();
}
}
_activeusers.AcceptChanges();
return
true
;
}
/**/
///
<summary>
///
更新用户活动时间。
///
</summary>
public
void
ActiveTime(
string
strTicket)
{
DelTimeOut();
string
strExpr;
strExpr
=
"
Ticket='
"
+
strTicket
+
"
'
"
;
DataRow[] curUser;
curUser
=
_activeusers.Select(strExpr);
if
(curUser.Length
>
0
)
{
for
(
int
i
=
0
; i
<
curUser.Length; i
++
)
{
curUser[i][
"
ActiveTime
"
]
=
DateTime.Now;
curUser[i][
"
RefreshTime
"
]
=
DateTime.Now;
}
}
_activeusers.AcceptChanges();
}
/**/
///
<summary>
///
更新系统自动刷新时间。
///
</summary>
public
void
RefreshTime(
string
strTicket)
{
DelTimeOut();
string
strExpr;
strExpr
=
"
Ticket='
"
+
strTicket
+
"
'
"
;
DataRow[] curUser;
curUser
=
_activeusers.Select(strExpr);
if
(curUser.Length
>
0
)
{
for
(
int
i
=
0
; i
<
curUser.Length; i
++
)
{
curUser[i][
"
RefreshTime
"
]
=
DateTime.Now;
}
}
_activeusers.AcceptChanges();
}
private
ActiveUser SingleUser(
string
strUserKey,
bool
byTicket)
{
strUserKey
=
strUserKey.Trim();
string
strExpr;
ActiveUser myuser;
strExpr
=
byTicket
?
"
Ticket='
"
+
strUserKey
+
"
'
"
:
"
UserName='
"
+
strUserKey
+
"
'
"
;
DataRow[] curUser;
curUser
=
_activeusers.Select(strExpr);
if
(curUser.Length
>
0
)
{
string
myTicket
=
(
string
)curUser[
0
][
"
Ticket
"
];
string
myUser
=
(
string
)curUser[
0
][
"
UserName
"
];
string
myName
=
(
string
)curUser[
0
][
"
TrueName
"
];
string
myRoleID
=
(
string
)curUser[
0
][
"
RoleID
"
];
DateTime myActiveTime
=
(DateTime)curUser[
0
][
"
ActiveTime
"
];
DateTime myRefreshtime
=
(DateTime)curUser[
0
][
"
RefreshTime
"
];
string
myClientIP
=
(
string
)curUser[
0
][
"
ClientIP
"
];
myuser
=
new
ActiveUser(myTicket,myUser,myName,myRoleID,myActiveTime,myRefreshtime,myClientIP);
}
else
{
myuser
=
new
ActiveUser(
""
,
""
,
""
,
""
,
""
);
}
return
myuser;
}
/**/
///
<summary>
///
按Ticket获取活动用户。
///
</summary>
public
ActiveUser SingleUser_byTicket(
string
strTicket)
{
return
this
.SingleUser(strTicket,
true
);
}
/**/
///
<summary>
///
按UserName获取活动用户。
///
</summary>
public
ActiveUser SingleUser_byUserName(
string
strUserName)
{
return
this
.SingleUser(strUserName,
false
);
}
/**/
///
<summary>
///
按Ticket判断用户是否在线。
///
</summary>
public
bool
IsOnline_byTicket(
string
strTicket)
{
return
(
bool
)(
this
.SingleUser(strTicket,
true
).UserName
!=
""
);
}
/**/
///
<summary>
///
按UserName判断用户是否在线。
///
</summary>
public
bool
IsOnline_byUserName(
string
strUserName)
{
return
(
bool
)(
this
.SingleUser(strUserName,
false
).UserName
!=
""
);
}
}
3
、 新建一个继承自PlaceHolder名为Refresh的类,执行更新自动刷新时间操作。
/**/
///
<summary>
///
Refresh 执行更新自动刷新时间操作。
///
</summary>
public
class
Refresh: PlaceHolder
{
/**/
///
<summary>
///
设置存储Ticket的Session名称,默认为Ticket。
///
</summary>
public
virtual
string
SessionName
{
get
{
object
obj1
=
this
.ViewState[
"
SessionName
"
];
if
(obj1
!=
null
)
{
return
((
string
) obj1).Trim(); }
return
"
Ticket
"
;
}
set
{
this
.ViewState[
"
SessionName
"
]
=
value;
}
}
protected
override
void
Render(HtmlTextWriter writer)
{
string
myTicket
=
(
string
)
this
.Page.Session[
this
.SessionName];
if
(myTicket
!=
null
)
{
PassPort myPass
=
new
PassPort();
myPass.RefreshTime(myTicket);
writer.Write(
"
OK:
"
+
DateTime.Now.ToString());
}
else
{
writer.Write(
"
Sorry:
"
+
DateTime.Now.ToString());
}
base
.Render(writer);
}
}
4
、 新建一个继承自PlaceHolder名为Script的类,生成执行xmlhttp的js脚本。。
/**/
///
<summary>
///
Script 生成执行xmlhttp的js脚本。
///
</summary>
public
class
Script: PlaceHolder
{
/**/
///
<summary>
///
设置js自动刷新的间隔时间,默认为25秒。
///
</summary>
public
virtual
int
RefreshTime
{
get
{
object
obj1
=
this
.ViewState[
"
RefreshTime
"
];
if
(obj1
!=
null
)
{
return
int
.Parse(((
string
) obj1).Trim());}
return
25
;
}
set
{
this
.ViewState[
"
RefreshTime
"
]
=
value;
}
}
protected
override
void
Render(HtmlTextWriter writer)
{
//
从web.config中读取xmlhttp的访问地址
string
refreshUrl
=
(
string
)ConfigurationSettings.AppSettings[
"
refreshUrl
"
];
string
scriptString
=
@"
<script language=""JavaScript"">
"
+
writer.NewLine;
scriptString
+=
@"
window.attachEvent(""onload"",
"
+
this
.ClientID
+
@"
_postRefresh);
"
+
writer.NewLine;
scriptString
+=
@"
var
"
+
this
.ClientID
+
@"
_xmlhttp=null;
"
+
writer.NewLine;
scriptString
+=
@"
function
"
+
this
.ClientID
+
@"
_postRefresh(){
"
+
writer.NewLine;
scriptString
+=
@"
var
"
+
this
.ClientID
+
@"
_xmlhttp = new ActiveXObject(""Msxml2.XMLHTTP"");
"
+
writer.NewLine;
scriptString
+=
@"
"
+
this
.ClientID
+
@"
_xmlhttp.Open(""POST"", ""
"
+
refreshUrl
+
@"
"", false);
"
+
writer.NewLine;
scriptString
+=
@"
"
+
this
.ClientID
+
@"
_xmlhttp.Send();
"
+
writer.NewLine;
scriptString
+=
@"
var refreshStr=
"
+
this
.ClientID
+
@"
_xmlhttp.responseText;
"
+
writer.NewLine;
scriptString
+=
@"
try {
"
+
writer.NewLine;
scriptString
+=
@"
var refreshStr2=refreshStr;
"
+
writer.NewLine;
//
scriptString += @" alert(refreshStr2);"+writer.NewLine;
scriptString
+=
@"
}
"
+
writer.NewLine;
scriptString
+=
@"
catch(e) {}
"
+
writer.NewLine;
scriptString
+=
@"
setTimeout(""
"
+
this
.ClientID
+
@"
_postRefresh()"",
"
+
this
.RefreshTime.ToString()
+
@"
000);
"
+
writer.NewLine;
scriptString
+=
@"
}
"
+
writer.NewLine;
scriptString
+=
@"
<
"
;
scriptString
+=
@"
/
"
;
scriptString
+=
@"
script>
"
+
writer.NewLine;
writer.Write(writer.NewLine);
writer.Write(scriptString);
writer.Write(writer.NewLine);
base
.Render(writer);
}
}
注意以上四个类同属于一个名为OnlineUser的工程,他们的命名空间为OnlineUser,编译生成一个dll。
===============================================
下面我简单介绍一下调用方法:
1
、 新建一个名为OnlineUserDemo的asp.net web应用程序
2
、 在vs的工具箱选项卡上右击,选择[添加
/
移除项],浏览定位到OnlineUser.dll,确定即可把Refresh 和Script添加到工具箱。
3
、 把自动生成的WebForm1.aspx删除,并设置web.config
<
appSettings
>
<
add key
=
"
ActiveTimeout
"
value
=
"
30
"
/>
<
add key
=
"
RefreshTimeout
"
value
=
"
1
"
/>
<
add key
=
"
refreshUrl
"
value
=
"
refresh.aspx
"
/>
</
appSettings
>
4
、 添加一个名为Online.aspx的web窗体,给该窗体添加一个Script控件,一个DataGrid控件(id为DataGrid1),两个HyperLink控件(分别链接到login.aspx和logout.aspx,text属性分别设置为“登陆”和“注销”),调整好四个控件的位置,转到codebehind,在Page_Load中加入如下代码:
string
myTicket
=
(
string
)
this
.Page.Session[
"
Ticket
"
];
if
(myTicket
!=
null
)
{
OnlineUser.PassPort myPassPort
=
new
OnlineUser.PassPort();
if
(myPassPort.IsOnline_byTicket(
this
.Session[
"
Ticket
"
].ToString()))
{
myPassPort.ActiveTime(
this
.Session[
"
Ticket
"
].ToString());
DataGrid1.DataSource
=
myPassPort.ActiveUsers;
DataGrid1.DataBind();
}
else
{
//
若在线用户列表中找不到当前用户,则定向到注销页面
Response.Redirect(
"
Logout.aspx
"
);
}
}
else
{
Response.Redirect(
"
Login.aspx
"
);
}
5
、 添加一个名为login.aspx的web窗体,给该窗体添加一个label控件(id为Label1),设置text属性为“输入一个用户名”,再添加一个textbox控件(id为TextBox1)和一个button控件(id为Button1),调整好他们的位置,双击Button1控件转到codebehind,为Button1的Click事件加入如下代码:
if
(TextBox1.Text.Trim()
==
""
)
{
//
不能为空
String scriptString
=
@"
<script language=JavaScript>
"
;
scriptString
+=
@"
alert(""输入一个用户名\n"");
"
;
scriptString
+=
@"
history.go(-1);
"
;
scriptString
+=
@"
<
"
;
scriptString
+=
@"
/
"
;
scriptString
+=
@"
script>
"
;
if
(
!
this
.Page.IsStartupScriptRegistered(
"
Startup
"
))
this
.Page.RegisterStartupScript(
"
Startup
"
, scriptString);
}
else
{
OnlineUser.PassPort myPassPort
=
new
OnlineUser.PassPort();
string
myTicket
=
DateTime.Now.ToString(
"
yyyyMMddHHmmss
"
);
string
myUser
=
TextBox1.Text.Trim();
string
myClintIP
=
this
.Request.UserHostAddress;
this
.Session[
"
Ticket
"
]
=
myTicket;
OnlineUser.ActiveUser myActiveUser
=
new
OnlineUser.ActiveUser(myTicket,myUser,myUser,
"
test
"
,myClintIP);
myPassPort.Login(myActiveUser,
true
);
Response.Redirect(
"
Online.aspx
"
);
}
6
、 添加一个名为logout.aspx的web窗体,给该窗体添加一个HyperLink控件,指向login.aspx,text属性设置为“重登陆”转到codebehind,在Page_Load中加入如下代码:
OnlineUser.PassPort myPassPort
=
new
OnlineUser.PassPort();
myPassPort.Logout(
this
.Session[
"
Ticket
"
].ToString());
this
.Session[
"
Ticket
"
]
=
""
;
7
、 添加一个名为Refresh.txt的文本文件,设置其内容为:
<%
@ Register TagPrefix
=
"
cc2
"
Namespace
=
"
OnlineUser
"
Assembly
=
"
OnlineUser
"
%>
<%
@ Page
%>
<
cc2:Refresh id
=
"
myRefresh
"
runat
=
"
server
"
></
cc2:Refresh
>
把Refresh.txt改名为Refresh.aspx
8
、 编译生成工程。
===============================================
下面进行功能测试:
1
、 打开浏览器,在地址栏输入
http:
//
你机器的IP地址/onlineuserdemo/Login.aspx
2
、 输入一个用户名(假设是test1)登陆,自动转到online.aspx页面
3
、 找同网段的另外一台机器(设你的机器为a,这台机器为b),重复执行第一步。
4
、 输入一个用户名(假设是test2)登陆,自动转到online.aspx页面
5
、 在b机器不断刷新online.aspx,若发现test1用户RefreshTime每过25秒自动更新一次而ActiveTime不变(这个时候a机器不要刷新页面啊),则证明a机器的自动刷新生效。
6
、 在a机器不断刷新online.aspx,若发现test2用户RefreshTime每过25秒自动更新一次而ActiveTime不变(这个时候b机器不要刷新页面啊),则证明b机器的自动刷新生效。
7
、 直接关闭一台机器(假设是a)上的online.aspx浏览窗口,在另一台机器(就是b啦)上刷新online.aspx,若发现1分钟后test1掉线在线用户只剩下test2,证明通过_refreshTimeout清除在线用户成功。
8
、 若5、
6
、7三步正常,则大功告成,否则就再调试调试
~~
==========================================================
附:网上现有的一些在线人数统计的文章
ASP.NET 在线用户列表
http:
//
www.mscenter.edu.cn/blog/jeffrey/archive/2005/03/25/1024.html
如何显示在线人数和所在位置
http:
//
www.7880.com/Info/Article-40c2aca0.html
不用Golobal.asa和session实现在线人数统计
http:
//
www.7880.com/Info/Article-31034d60.html
利用文件属性结合Session实现在线人数统计
http:
//
www.7880.com/Info/Article-2b369f40.html
网站当前的在线人数
http:
//
www.7880.com/Info/Article-2a6d34c0.html
基于数据库的在线人数,日访问量等统计
http:
//
www.7880.com/Info/Article-29f01580.html
网页在线人数统计的做法
http:
//
www.7880.com/Info/Article-1d4ebf20.html
不用Global.asa实现在线人数统计
http:
//
www.7880.com/Info/Article-1c09bc00.html
如何实现网页在线人数统计
http:
//
www.7880.com/Info/Article-12bd5d00.html
两种统计当前在线人数的方法
http:
//
www.7880.com/Info/Article-af79e0.html
查看全文
相关阅读:
Struts2(十六)Json
Struts2(十五)实现文件上传
Struts2(十四)拦截器实现权限管理
Eclipse下link方式安装插件
Struts2(十三)国际化-internationalization
Struts2(十二)使用验证框架验证数据较验
Struts2(十一)OGNL标签三与Struts2标签
Struts2(十)OGNL标签二与Struts2标签
Struts2(九)OGNL标签一与Struts2标签
Elasticsearch 分词器
原文地址:https://www.cnblogs.com/kokoliu/p/618144.html
最新文章
【BZOJ5471】[FJOI2018]邮递员问题(动态规划)
【BZOJ5470】[FJOI2018]所罗门王的宝藏()
【BZOJ5469】[FJOI2018]领导集团问题(动态规划,线段树合并)
【BZOJ5324】[JXOI2018]守卫(动态规划)
【BZOJ5323】[JXOI2018]游戏(组合计数,线性筛)
【BZOJ5322】[JXOI2018]排序问题(模拟)
【BZOJ5318】[JSOI2018]扫地机器人(动态规划)
【BZOJ5316】[JSOI2018]绝地反击(网络流,计算几何,二分)
【BZOJ5314】[JSOI2018]潜入行动(动态规划)
Spring(五)AOP简述
热门文章
CSS ID选择器(三)
Spring(四)Bean注入方试
Spring(三)Bean继续入门
Spring(二)Bean入门
Spring(一)简述
Java从零开始学四十三(DOM4j解析XML)
CSS标签选择器(二)
Struts2(十七)验证框架二
Java从零开始学四十二(DOM解析XML)
CSS(一)
Copyright © 2011-2022 走看看