第 10 章 综合电子留言板.docx

上传人:b****6 文档编号:7325280 上传时间:2023-01-23 格式:DOCX 页数:15 大小:93.14KB
下载 相关 举报
第 10 章 综合电子留言板.docx_第1页
第1页 / 共15页
第 10 章 综合电子留言板.docx_第2页
第2页 / 共15页
第 10 章 综合电子留言板.docx_第3页
第3页 / 共15页
第 10 章 综合电子留言板.docx_第4页
第4页 / 共15页
第 10 章 综合电子留言板.docx_第5页
第5页 / 共15页
点击查看更多>>
下载资源
资源描述

第 10 章 综合电子留言板.docx

《第 10 章 综合电子留言板.docx》由会员分享,可在线阅读,更多相关《第 10 章 综合电子留言板.docx(15页珍藏版)》请在冰豆网上搜索。

第 10 章 综合电子留言板.docx

第10章综合电子留言板

第 10 章 综合电子留言板

注意

将前九章的知识结合起来,实现一个电子留言板,包括注册登录,发帖回复功能。

如果你不满足以下任一条件,请继续阅读,否则请跳过此后的部分,进入下一章:

第 11 章文件上传。

1.对电子留言板不感兴趣。

10.1. 电子留言板用户指南

首页显示的是主题列表。

用户如果想发表新主题或者对主题进行回复,必须先注册为会员。

注册后进入登录页面进行登录。

登录后即出现在用户在线列表中。

点击标题可以看到主题的详细信息。

登录以后即可发布新主题。

10.2. 数据库设计

数据库er图

共定义了三张表:

1.user用户,保存注册用的信息。

2.thread主题,用户发起的主题帖子,外键关联user,对应发表主题的用户

3.comment回复,对主题帖子发起的回复,外键关联user和thread,对应发表回复的用户和回复的主题。

建表sql脚本放在10-01/WEB-INF/sql/import.sql。

--用户

createtableuser(

idbigint,--主键

usernamevarchar(100),--帐号

passwordvarchar(100),--密码

reg_timedatetime,--注册时间

last_logindatetime--上次登录时间

);

--主题

createtablethread(

idbigint,--主题

titlevarchar(200),--标题

contentvarchar(2000),--内容

create_timedatetime,--发帖时间

update_timedatetime,--更新时间

hitinteger,--点击数

userbigint--发帖用户

);

--回复

createtablecomment(

idbigint,--主题

contentvarchar(2000),--内容

create_timedatetime,--发布时间

userbigint,--回复用户

threadbigint--回复的主题

);

根据数据库表建模。

每张表对应三部分:

domain,dao和servlet。

domain是简单的javabean用来封装数据表中的数据,dao中进行对数据库的业务操作,servlet作为控制器处理请求调用dao和domain实现业务功能。

为了便于管理,将使用到的类分成四个包,domain,dao,utils和web。

domain,dao,web中分别包含domain,dao和servlet类,utils包中是数据库连接工具和过滤器。

这里的domain和dao都是按照理想状态编写的,将数据库表中的字段对应到domain类中,然后dao提供CRUD功能,不过dao中的有些功能并没有用到,比如update和remove。

10.3. 功能设计

整个在线留言板可分为两大功能部分:

用户管理与主题回复管理。

10.3.1. 用户管理

用户管理功能包括:

新用户注册,用户登录,用户注销。

用户登录的时候顺便带上一个用户在线列表。

这部分的页面主要在security目录下,操作代码都放在anni.web.UserServlet.java和对应的anni.domain.User,anni.dao.UserDao中。

1.新用户注册

这是CRUD中的create,向用户表中添加一条新信息,我们只在前台页面中使用javascript进行数据校验,要求用户输入用户名,密码,并且在两次密码输入相同的时候才能提交。

提交的请求交由UserServlet的register()方法处理。

/**

*注册新用户.

*/

publicvoidregister(HttpServletRequestrequest,HttpServletResponseresponse)throwsException{

Stringusername=request.getParameter("username");

Stringpassword=request.getParameter("password");

StringconfirmPassword=request.getParameter("confirmPassword");

booleanuserExists=userDao.checkExists(username);

if(userExists){

request.setAttribute("error","用户名:

"+username+"已被使用了,请更换其他用户名注册。

");

request.getRequestDispatcher("/security/register.jsp").forward(request,response);

}else{

Useruser=newUser();

user.setUsername(username);

user.setPassword(password);

userDao.save(user);

response.sendRedirect(request.getContextPath()+"/security/registerSuccess.jsp");

}

}

获得用户名和密码后,先通过userDao.checkExists()检测数据库中是否已经有了同名的用户,如果用户名重复,就跳转到/security/register.jsp显示错误信息。

如果用户名没有重复,则将此用户信息添加入库,然后页面重定向到/security/registerSuccess.jsp显示注册成功信息。

保存信息之后使用redirect是个避免重复提交的简易方法,如果使用forward,浏览器上的url不会改变,用户刷新页面就会导致重复提交信息。

2.用户登录与注销

登录与注销的流程与之前介绍的大体相同。

第 4.2 节“例子:

在线列表”

/**

*登录.

*/

publicvoidlogin(HttpServletRequestrequest,HttpServletResponseresponse)throwsException{

Stringusername=request.getParameter("username");

Stringpassword=request.getParameter("password");

Useruser=userDao.login(username,password);

if(user!

=null){

user.setLastLogin(newDate());

userDao.update(user);

HttpSessionsession=request.getSession();

session.setAttribute("user",user);

//加入在线列表

session.setAttribute("onlineUserBindingListener",newOnlineUserBindingListener(username));

response.sendRedirect(request.getContextPath()+"/security/loginSuccess.jsp");

}else{

request.setAttribute("error","用户名或密码错误!

");

request.getRequestDispatcher("/security/login.jsp").forward(request,response);

}

}

/**

*注销.

*/

publicvoidlogout(HttpServletRequestrequest,HttpServletResponseresponse)throwsException{

request.getSession().invalidate();

response.sendRedirect(request.getContextPath()+"/security/logoutSuccess.jsp");

}

我们先根据请求中的用户名和密码去数据库搜索用户信息。

如果能找到,说明用户输入无误可以登录,这时更新用户最后登录时间,并将user保存到session中,同时使用listener操作在线列表。

如果用户名或密码错误,则将请求转发至/security/login.jsp页面,显示错误信息。

3.控制用户访问权限

与用户操作相关的还有anni.utils.SecurityFilter,我们使用它来控制用户的访问权限。

可以参考之前的讨论:

第 7.2 节“用filter控制用户访问权限”。

web.xml中对SecurityFilter的配置如下:

SecurityFilter

anni.utils.SecurityFilter

SecurityFilter

/*

因为filter-mapping太不灵活,我们让SecurityFilter过滤所有的请求,在代码里判断哪些请求需要保护。

publicvoiddoFilter(ServletRequestrequest,

ServletResponseresponse,

FilterChainchain)

throwsIOException,ServletException{

HttpServletRequestreq=(HttpServletRequest)request;

HttpServletResponseres=(HttpServletResponse)response;

Stringurl=req.getServletPath();

Stringmethod=req.getParameter("method");

if("/create.jsp".equals(url)||

("/thread.do".equals(url)&&"post".equals(method))||

("/comment.do".equals(url)&&"post".equals(method))){

HttpSessionsession=req.getSession();

if(session.getAttribute("user")==null){

res.sendRedirect(req.getContextPath()+"/security/securityFailure.jsp");

return;

}

}

chain.doFilter(request,response);

}

在此我们只保护三个请求:

/create.jsp(进入发布新主题的页面),/thread.do?

method=post(发布新主题),/comment.do?

method=post(发布回复)。

这三个操作只有在用户登录之后才能访问,如果用户还没有的登录就会页面重定向到/security/securityFailure.jsp,显示权限不足无法访问的提示信息。

10.3.2. 主题回复管理

主题回复管理功能包括:

查看所有主题,查看某一主题的详细信息和对应回复,发表新主题,发表回复。

点击主题时还会计算点击数。

1.查看所有主题信息

进入应用,index.jsp会立即跳转到/forum.do?

method=list,并在list.jsp中显示所有主题,包括主题标题,回复数,作者,点击数,最后回复时间,最后回复人。

这些信息按照“最后回复时间”进行逆序排列。

实现代码在anni.web.ForumServlet的list()方法内。

/**

*显示所有帖子.

*/

privatevoidlist(HttpServletRequestrequest,HttpServletResponseresponse)throwsException{

Listlist=forumDao.getAll();

request.setAttribute("list",list);

request.getRequestDispatcher("/list.jsp").forward(request,response);

}

调用anni.dao.ForumDao的pagedQuery()方法返回我们需要的信息,这里只用domain中定义的类已经无法满足我们了(显示的信息包含了三个表的信息),为了方便起见我们直接使用了Map来传递数据。

publicListgetAll()throwsException{

Connectionconn=null;

Statementstate=null;

Listlist=newArrayList();

try{

conn=DbUtils.getConn();

state=conn.createStatement();

Stringsql="select"+

"t.id,"+

"t.title,"+

"(selectcount(id)fromcommentwherethread=t.id)asreply,"+

"(selectusernamefromuserwhereid=t.user)asauthor,"+

"t.hit,"+

"(selecttop1create_timefromcommentwherethread=t.idorderbycreate_timedesc)ascreate_time,"+

"(selecttop1u.usernamefromcommentc,useruwherec.thread=t.idandc.user=u.id"+

"orderbycreate_timedesc)asuser"+

"fromthreadt"+

"orderbyuserdesc";

ResultSetrs=state.executeQuery(sql);

while(rs.next()){

Mapmap=newHashMap();

map.put("id",rs.getLong

(1));//主键

map.put("title",rs.getString

(2));//标题

map.put("reply",rs.getInt(3));//回复数

map.put("author",rs.getString(4));//作者

map.put("hit",rs.getInt(5));//点击数

map.put("updateDate",rs.getTimestamp(6));//最后发言时间

map.put("user",rs.getString(7));//最后发言人

list.add(map);

}

}finally{

DbUtils.close(null,state,conn);

}

returnlist;

}

或许有人会奇怪为什么不直接使用ResultSet。

这其实是一种理念问题,如果你返回ResultSet到jsp页面,的确免去了封装成Map的步骤,但是同时产生了两个问题。

第一,数据库操作对应的代码蔓延到前台页面,有违我们分层设计的初衷。

如果觉得我们这是过度设计的话,那么第二个问题则是更严重的,将ResultSet放到jsp上很难控制何时关闭数据库连接,如果发生了异常可能来不及关闭数据连接,用不了多长时间就会耗尽资源了。

ForumDao中,勉强拼凑出三个表连接查询的sql,还不清楚性能是否有保证。

2.显示主题详细信息

点击主题标题/forum.do?

method=view&id=1,会进入显示对应详细信息的页面/view.jsp。

顶部显示的是主题帖子的标题,发布时间,作者和内容。

主题内容下面列出所有的回复内容,页面底部是回复使用的表单,只有登录之后才能使用。

ForumServlet中的view()方法用来获得我们需要的主题信息和对应的回复信息。

/**

*显示帖子内容.

*/

privatevoidview(HttpServletRequestrequest,HttpServletResponseresponse)throwsException{

longid=Long.parseLong(request.getParameter("id"));

Mapthread=forumDao.viewThread(id);

Listlist=forumDao.getCommentsByThread(id);

request.setAttribute("thread",thread);

request.setAttribute("list",list);

request.getRequestDispatcher("/view.jsp").forward(request,response);

}

我们从请求中获得主题的id,获得主题详细信息和对应的回复信息列表,这两项都是使用Map传递数据传递到view.jsp页面中再使用el和jstl显示出来。

在显示主题详细信息时,顺便讲主题的点击数加一。

publicMapviewThread(longid)throwsException{

Connectionconn=null;

PreparedStatementstate=null;

Mapmap=newHashMap();

try{

conn=DbUtils.getConn();

state=conn.prepareStatement("selectt.id,t.title,t.content,t.create_time,u.username"+

"fromthreadt,useruwheret.user=u.idandt.id=?

");

state.setLong(1,id);

ResultSetrs=state.executeQuery();

if(rs.next()){

map.put("id",rs.getLong

(1));//主键

map.put("title",rs.getString

(2));//标题

map.put("content",rs.getString(3));//内容

map.put("createTime",rs.getTimestamp(4));//发布时间

map.put("username",rs.getString(5));//作者名

}

//增加点击数

state=conn.prepareStatement("updatethreadsethit=hit+1whereid=?

");

state.setLong(1,id);

state.executeUpdate();

}finally{

DbUtils.close(null,state,conn);

}

returnmap;

}

我们把这个更新操作放到查询之后,使用update将hit字段加一,也是为了避免在异常情况下找不到对应主题时,不必出现更新异常。

3.发布新主题和发布回复

这两项对应了anni.web.ThreadServlet和anni.web.CommentServlet中的post()方法。

为了简易起见,我们仅仅在页面上使用javascript检验输入的数据不能为空。

提交之后会调用对应dao中的save()方法将数据保存进数据库。

最后页面重定向到/forum.do?

method=list或/forum.do?

method=view&id=1。

实际上它们都是单纯的create操作(CRUD中的C)。

10.3.3. 显示在线用户列表

我们使用了HttpSessionBindingListener来实现在线用户列表。

详细介绍见第 8.2 节“使用HttpSessionBindingListener”。

/list.jsp和/view.jsp两个页面上的在线用户列表显示效果完全一样,如果有可能的话,我们希望将这些重复的部分从原来的页面中剥离出来,集中在一起让其他页面调用,这样更容易管理和维护。

为了实现这一功能,我们需要借用另一个jsp指令(directive):

include。

<%@includefile="/include/onlineUser.jsp"%>

这里的file可以使用相对路径,也可以使用绝对路径。

这里的绝对路径与使用forward时一致,都是以应用目录为根目录,参考这里的讨论第 3.4.1.2 节“绝对路径”。

我们顺便再看一下/include/onlineUser.jsp的内容:

<%@pagecontentType="text/html;charset=gb2312"%>

在线用户

forEachvar="item"items="${onlineUserList}">

 ${item}

forEach>

这就是一个单独的jsp页面,可以在里边使用jsp指令(directive),el,甚至是taglib。

不过taglib还是要在使用前定义的,因为每个页面都使用了相同的taglib定义和其他一些相同的html配置(编码,css等),我们也把这部分提取成一个jsp页面,让其他页面引用。

这个页面也放在include目录下,meta.jsp的内容如下。

<%@taglibprefix="c"uri="

setvar="ctx"value="${pageContext.requ

展开阅读全文
相关资源
猜你喜欢
相关搜索

当前位置:首页 > 小学教育 > 语文

copyright@ 2008-2022 冰豆网网站版权所有

经营许可证编号:鄂ICP备2022015515号-1