自主练习:BBS电子布告栏
工程地址:D:\BbsDemo;
技术参考:JSP+Servlet+MySQL;
开发工具:DBeaver客户端+Eclipse 2021
需求描述
编写一个留言界面,留言信息有标题和内容,标题不能为空。当往数据库中插入留言信息时,自动添加时间和留言者,时间为系统时间,留言者为当前登录用户,没有登录默认为游客
出处:《Java web程序设计》电子工业出版社 张磊丁香乾//第251页 练习3.E.2
准备工作
第一步:修改.classpath文件
修改正常情况下第三行为下图所示
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-UsF4pEiQ-1655454307304)(C:\Users\Friedrich Hsing\AppData\Roaming\Typora\typora-user-images\image-20220419215218715.png)]
第二步:导入数据库连接jar包
总体设计
数据库
userdetail记录用户信息,包含下列数据:id,name,pwd;
messdetail记录留言界面:tital,mainmess,username,time。key分别是id,tital。设计时注意mainmess可以为空,但是其他不能为空。
实体类
User和Mess类,分别对应数据库内容设计数据域和setting和getting方法。
数据库操作类
UserDAO:实现id与pwd写入数据库;判定id是否存在,pwd是否输入正确。
MessDAO:实现将信息写入数据库;在页面中输出信息。
具体界面和功能(附图是1.0软件结束后的效果图)
登录:logon.jsp+LogonServlet.java
从表userdetail获取信息,进行比对。
登录界面
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-YwhU2RE3-1655454307306)(C:\Users\Friedrich Hsing\AppData\Roaming\Typora\typora-user-images\image-20220423232049208.png)]
注册:regist.jsp+RegistServlet.java
将完整的user信息写入表userdetail中。
注册界面
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-BrQCAdPM-1655454307307)(C:\Users\Friedrich Hsing\AppData\Roaming\Typora\typora-user-images\image-20220423232117569.png)]
显示所有信息:main.jsp+MainServlet.java
将表messdetail信息以一定格式全部输出。
显示页面
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-XLbVZmx9-1655454307308)(C:\Users\Friedrich Hsing\AppData\Roaming\Typora\typora-user-images\image-20220423232221124.png)]
用户输入信息:input.jsp+InputServlet.java
将信息处理好后(加上user.getName()和time的信息)写入表messdetail中。
发布界面
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-pVAMFp2o-1655454307308)(C:\Users\Friedrich Hsing\AppData\Roaming\Typora\typora-user-images\image-20220424000510410.png)]
界面之间的镶嵌关系
web.xml设计中强调起点为登录界面logon.jsp,存在提交(button)------->跳转main.jsp
注册(超链接)--------->跳转regist.jsp
注册成功之后跳转main.jsp。
main.jsp存在发布(超链接),----------->跳转input.jsp。
文件地址:
“D:\Friedrich Hsing’s Documents\Typora文档\自主练习:BBS电子布告栏设计阶段.pdf”
实现必要信息的传递
前文提出了一个必要的需求,实现信息定位到是哪个用户写的,这意味着在登陆界面logon.jsp实现将注册用户的信息写入request表头中,注册界面也要实现相应的功能。然后利用
request.getRequestDispatcher(“/a.jsp”).forward(request.response);
url地址不变,只能跳转到本web应用中的页面上。可以用request.setAttibute方法
实现重定位功能跳转main.jsp
更改:尝试直接写入Context中,发现这样子就不需要像上一个方案一样,需要重复的将信息写入表头。
正式开发第一阶段:围绕userdetail设计的功能
第一步,建包和建立文件,将已有储备的数据库操作类移植过来,实现过滤器,完成xml设置
其中**EncodeFilter.java实现中文字符编码设置功能。**在xml
代码如下:
<filter>
<filter-name>EncodeFilter</filter-name>
<filter-class>com.filter.EncodeFilter</filter-class>
</filter>
实现核心代码的逻辑如下,在doFilter()
中实现设置请求和响应对应字符的代码:
request.setCharacterEncoding("utf-8");
response.setContentType("text/html; charset=utf-8");
注意设置初始页面为登陆界面:
<welcome-file-list>
<welcome-file>logon.jsp</welcome-file>
</welcome-file-list>
注意在建立新的数据库bbs后,在web.xml设置关于数据库的信息在ServletConfig上,时候使用ServletContext.getInitParameter()
实现数据库连接信息必要的获取。
<context-param>
<param-name>server</param-name>
<param-value>localhost</param-value>
</context-param>
<context-param>
<param-name>dbname</param-name>
<param-value>bbs</param-value>
</context-param>
<context-param>
<param-name>user</param-name>
<param-value>root</param-value>
</context-param>
<context-param>
<param-name>pwd</param-name>
<param-value>**********</param-value>
</context-param>
第二步,实现两个简单的前端JSP文件,登录界面和注册界面,并基于AJAX技术,实现相应简单功能
分别实现logon.jsp&®ist.jsp两个文件,AJAX实现检验提交前是否存在信息没有输入。
注册页面regist.jsp文件在判断是否有信息没输入外,使用AJAX实现判断前后两次密码输入是否一致。
开发完成这两个简单的前端文件之后,检验,正常运行。
第三步,实现数据库表userdetail开发。
再dbeaver数据库可视化开发客户端中,建立新的数据库bbs,库中建立新的表userdetail。
userdetail含有三个属性:id,name,password//账号,用户名,密码;
实现并保存之后,可以开始实现数据库连接类的个性化开发。
第四步,基于数据库数据,实现实体类User,并根据User实现数据库连接类。
User类很容易实现,主要需要注意的是UserDao类
继承dboperate类(我自己开发的总数据库操作类)之后,依据这个类的方法实现下述两个功能:
添加用户和查询用户,实现这些功能具体信息如下:
AddUser()
INSERT INTO userdetail(id,pwd,name)value(?,?,?)
new String[] {user.getId(),user.getPwd(),user.getName()}
getUserById()
SELECT * FROM userdetail WHERE id=?
new String[] {id}
第五步,实现登录和注册的Servlet
先分析情景,登录过程中需要实现那些功能?
-
通过输入ID查找用户
-
用户不存在,返回提示
-
用户存在密码错误,返回提示
-
用户存在,密码正确,跳转main.jsp
注册没有什么特别注意的,单纯的将信息写入数据库中就行了
PS: 如果不使用try{}结构,则无法连接数据
具体逻辑(注册登录相同逻辑):
- 从前端JSP获取信息
- 连接数据库
- 操作数据库(写入信息或者搜索信息)
- 将User的信息写入ServletContext中
- 跳转main.jsp页面
下图是查阅的资料:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-0fiFLfYi-1655454307309)(C:\Users\Friedrich Hsing\AppData\Roaming\Typora\typora-user-images\image-20220422104252971.png)]
写到一半追加增加设计:
regist.jsp实现相同邮箱不能重复注册,发生错误提示弹窗;
在实现注册功能的具体连接之前,使用自定义UserDao中getUserById方法获取数据库信息,如果用户存在,不予注册资格
原本使用方法:
try {
dao.getConn(server, dbname, owner, password);
User tempuser=dao.getUserById("id");
if(tempuser!=null) {//用户不存在
boolean flag=dao.addUser(user);//添加用户
if(flag) {
System.out.println("AddUser Success!");
request.setAttribute("userinformation", user);
request.getRequestDispatcher("/main.jsp").forward(request,response);//跳转主页面
}
}else {//如果用户ID已经存在
System.out.println("this id is in the System, AddUser failed!");//控制台输出bug
}
后来查阅资料:发现主键数据可以自主实现对重复数据的判断,不需要格外代码实现:
主键是一列或一组列,用于唯一标识表中的每一行。为表定义主键时,必须遵循以下规则:
- 主键必须包含唯一值。如果主键由多列组成,则这些列中的值组合必须是唯一的。
- 主键列不能包含
NULL
值。- 一表只有一个主键。
报出异常:java.sql.SQLIntegrityConstraintViolationException
能否将这种异常应用到程序中?使用代码:catch(SQLIntegrityConstraintViolationException e)
错误:Unreachable catch block for IOException. This exception is never thrown from the try statement body
意义:永远无法发生这类异常
这一段或许可以加上,不过一切都要等待一个工作的完成:servlet实现弹窗
正式开发第二阶段:messdetail实现信息发布和信息浏览
第一步,实现数据库表messdetail
由需求描述,表messdetail有tital,mainmess,username,sendtime
补充:sendtime有多种数据类型,最终选用timestamp
数据类型。格外注意后面数据库中时间类型和代码中数据类型的一一对应。
第二步,实现数据库对应的实体类Mess,和数据库连接类MessDao
如同上文一样,Mess.java的设计没有什么需要额外注意的地方,但是MessDao的要求有所升级;
再将一条Mess写入数据库中,除了前端提供的tital和mainmess信息之外,获取系统的时间,登录者的名称名写入数据库。
不过这几步操作并没有打算再数据库连接类中实现,数据库连接类还是只实现AddMess()
功能。
getAllMess()
的方法实现获取所有信息,返回一个List<Mess>
数据结构的数据。
注意:在应用executeUpdate
等方法时,String[]
中的数据数据类型不同的需要使用,“ ”+
隔开
将数据库操作后返回结果封装入一个实体对象,并将实体对象返回至代码
Date sendtime属性我暂时使用getDate()
方法,没有报错,看情况调整。
更改:Mess实体类对应的数据结构更改为:java.sql.Timestamp
,来替换原来的java.util.Date
修改使用:getTimestamp()
方法,可以正常运行。
第三步,先实现写入mess的操作,input.jsp及其对应的InputServlet.java
在设计input.jsp前,先恶补一下之前嫌麻烦没有学习的html设计文字框大小。
<input type="text" style="width:800px; height:20px;">
使用AJAX,第一次判断tital是否为空,弹窗提示。
用户信息在最顶栏显示。
//注意用户信息从登录界面logon.jsp之后,要再一次传送转送到main.jsp中
更改:改用context方案不需要这么麻烦,注意jsp镶嵌java代码<%%>中context对应application的JSP内置对象
在这里出现了bug
最初设计时犯了两个严重的错误。
第一个错误是:直接将User实体类的对象存放到了request中,其实并不需要那么多数据。但是长远来看,使用User实体类传输数据无疑更为安全可靠。之后的2.0版本可以将这个功能加上去。
第二个错误是不应该使用request,相比于request,context更为稳妥。request需要每一个页面都实现一次将信息写入request表头。
不过这也只是暂时的规划,之后参考一些已经投入应用的大型项目,看看他们用户信息都写在哪里。
最大的bug:时间数据结构bug
bug报错描述:Data truncation: Incorrect datetime value: ‘Fri Apr 22 21:16:46 CST 2022’ for column ‘sendtime’ at row 1
其实心里有数,指导是从数据库的设计起始,关于时间的存储标准一直很混乱。导致这种混乱一开始是源于java.util.Date和java.sql.Date两种数据结构傻傻分不清,原本打算是等到最后再统一标准,但现在看来要是不早准备好会导致连测试都做不了。
第一,确认数据库的数据类型,最后我选择了相对来说占存储空间小一点的timestamp
数据结构。对应在java代码中的数据结构是:java.sql.Timestamp
。
第二,在主要实现功能的Servlet上,要实现最最最关键的步骤。
因为java代码只能(又或者是我技术不强?)通过java.util.Date实现获取服务器的系统时间。所以将系统时间插入数据库需要经过两个步骤:1.将系统时间通过java.util.Date
获取 2.将这个时间转化成为数据库接受的对应的数据结构java.sql.Timestamp
具体代码如下:
Date utilDate = new Date();
Timestamp sqlDate = new Timestamp(utilDate.getTime());
第四步,实现展现所有数据库中所有信息,main.jsp与MainServlet.java的开发
小插曲:忽然tomcat报错404,清理tomcat工作日志无效之后,根据bug404出现时间初步判断是webapp与tomcat连接出错,将工程移除,重新添加,成功运行。
在main.jsp实现前端展现数据库所有信息时,我忽然想到在Mess.java中直接规定好Mess数据的信息输出模式,岂不是更加稳妥?
在Mess实体类中实现了一个返回String数据类型的toString()
方法,下面是核心代码,因为有需要注意的地方单列如下:
result="<br>_______________________________________________________________________________________________-<br>发布人:"+getUsername()+"<br>发布时间:"+getSendtime()+"<br>-----------------------------------------------------------<br>发布主题:"+getTital()+"<br>-----------------------------------------------------------<br>发布内容:"+getMainmess();
可以正常运行,不过要注意使用<br>
添加在字符串中,网页端才能显示换行,不要使用习惯上在控制台中的\n
,达不到理想效果
问题1:Timestamp能否正常输出?可以,格式正常
一个注意点:
main.jsp在开始循环查找数据库之前一定要运行有下述代码:
<jsp:forward page="MainServlet"></jsp:forward>
实现main.jsp与MainServlet.java之间的连接。
<%
List<Mess> messes=(List<Mess>)request.getAttribute("messes");
if(messes==null){
%>
<jsp:forward page="MainServlet"></jsp:forward>
<%}
for(int i=0;i<messes.size();i++)
{
Mess mess=messes.get(i);
//输出这个mess
%>
<%=mess%>
<%
}
%>
至此,我们可以说是完成了主要的结构主干了。接下来就是优化系统了。
现阶段存在问题和2.0版本预计更新内容
问题1:
main.jsp中Mess的顺序是按照时间顺序来的,就是说最上面的信息是最初始的那一条,这样使用很不方便。
问题2:
需求中提到未登录用户(现在看来只有可能是调试阶段,检验后重新提交表单后不能存在登录失败的情况)游客显示,数据库中Null转化成游客的功能有所欠缺
问题3:
用户信息现阶段只有name,这存在一个原因是数据库设计时仅考虑了需求书上的内容,要封装整个User类进去,现在看来应该是可行的。
调试阶段出现bug,不过后面就没有出现这个bug了。信息重复输入数据库,第二条测试代码输入数据库操作两次。且第二次登录失败。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-JIN8NFPY-1655454307310)(C:\Users\Friedrich Hsing\AppData\Roaming\Typora\typora-user-images\image-20220423230949377.png)]
总结之前的临时思考所写出的一些想法,2.0版本主要升级方面如下:
- AJAX实现main.jsp自主刷新;
- logon.jsp登录失败弹窗提示密码错误;
- regist.jsp实现相同邮箱不能重复注册,发生错误提示弹窗;
- ServletContext传递User本体,目的是增强系统整体的安全稳定性;//参考大型网站软件的代码
- main.jsp实现用户名超链接,实现进入个人主页;//构造全新的JSP和servlet
- 信息Mess实体类和数据库更新,实现封装id(方便确认发送对象),如果有可能实现封装User全部信息(数据库如何引入自定义类?)
- Messdetail数据库表,数据输出倒序输出。
预计版本3.0更新对应功能:
User数据库及对应实体类升级,增加个人简介和头像功能;
Mess数据库实现Userinformation项目封装可以确认账户对象的具体方法
出现的未知知识,需要及时的补充:
使用Servlet实现弹窗;
数据库自定义数据类型;
AJAX,js与html之间互相传值
系统优化
1.实现数据库中的信息,新的信息排在上面,旧的信息排在后面
如何实现?一开始也是考虑从数据库角度出发。由于我起始没有系统的学过MySQL,所以我的第一反应是使用新的指令实现这一功能。
但是后来我考虑了一下,直接从List循环输出的角度考虑不久行了?
循环方式从:
for(int i=0;i<messes.size();i++)
修改为
for(int i=messes.size()-1;i>=0;i--)
2.实现User完整封装到ServletContext中
修改LogonServlet.java 48rank
RegistServlet.java 53rank中的java代码
修改input.jsp&main.jsp的application的JSP内置对象获得数据。
经检验成功运行。
3.未登录游客发言显示为游客
计划直接从显示端入手,即直接在Mess中的toString()方法中实现检验,如果没有name的信息则将Guest填进去,但是失败了。
启动第二个方案
在main.jsp&input.jsp先检验context中信息是否为null,如果是,填入预备好的Guest信息;如果不是,则正常运行。
正常运行。
升级2.0版本
利用AJAX异位同步实时刷新main.jsp界面
主要应用到的技术是AJAX的异位同步
复习了相应的知识点,值得注意的地方是:
-
一个完整的AJAX同步异位系统包括下列三个核心JavaScript函数:
- 创建xmlHttpRequest对象的函数
- function createXMLHttpRequest()
- new ActiveXObject(“Microsoft.xmlHttp”)
- new XMLHttpRequest()
- 绑定状态触发器的函数,主要用于处理服务器返回的信息
- function processor()
- 判断正常条件下运行:xmlHttp.readyState4&&xmlHttp.status200
- var x=xmlHttp.responseText//获得服务器返回的响应文本信息
- document.getElementById(“id”).innerHTML=x//前端html显示内容
- 具体操作的函数
- function getX()
- 先调用创造createXMLHttpRequest()方法
- i++//区别化请求内容
- xmlHttp.open(“GET”,“Servlet?count=”+i);//请求发送至服务器
- xmlHttp.send(null)
- 创建xmlHttpRequest对象的函数
-
除了三个主要的函数,还包括其他主体部分
- var xmlHttp;
- var i=0;
- setInterval(“function3();” ms);
-
html区域需要注意:
<div id=""></div>
中展示AJAX中的内容
-
Servlet注意
- out.println(“”);里面放着需要输出刷新的内容
- out.flush();
- out.close();
升级input.jsp,使之可以查看往期自己发布的信息
第一步,实现数据库操作类MessDao升级,实现方法getMessByUserName||getMessByUserId;
返回一个List。
第二步,实现一个新的Servlet实现这个功能
命名GetPersonalMessServlet.java,接入input.jsp
出现了bug
bug 1:发送新的Mess永远无法确认发送人
bug已修复,原因是因为没有即使的更新inputServlet中获取Context信息的代码。
bug2:input.jsp只显示最初是那一条信息
bug解决,因为把上文中一个循环while写成if了。
3.升级main.jsp,使用JSTL技术加持,增强代码的可维护性
在源程序中,大量使用了JSP镶嵌java代码的形式,为了保证效果,决定大量使用JSTL技术
导入包rmpl&&spec之后
修改核心代码为:
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<h1>欢迎!
<%User user=(User)application.getAttribute("userinformation");%>
<c:choose>
<c:when test="${user==null}">GUEST</c:when>
<c:when test="${user!=null}"><%=user.getName() %></c:when>
<a href="input.jsp">发布</a>
<br>
<h1>Crystal Palace社区主页</h1>
<br>
<%List<Mess> messes=(List<Mess>)request.getAttribute("messes");%>
<c:if test="${messes==null}">
<jsp:forward page="MainServlet"></jsp:forward>
</c:if>
<c:forEach var="mess" items="${messes}" varStatus="status">
${mess}
</c:forEach>
查阅资料发现,<c:forEach>
无法实现逆序查找,所以这一部分放弃现方案,还是应用原方案。
3.0版本预计更新内容
在2.0版本中原计划实现的功能只实现了ServletContext封装User,input.jsp页面实现查看之前发送的信息(类似个人界面),数据库自定义排列方式三个需求。
除了2.0版本原定要实现但是没有实现的功能外,3.0版本预计升级:
- 头像自定义//涉及到userdetail数据库层面的大规模工程升级
- 自主选择消息展现在main.jsp或者input.jsp的模式,支持顺序展示和逆序展示。//实现难度较小,尽可能不涉及servlet层面的结构。
- Messdetail数据库升级,实现id封装,通过id提升效率。
- Guest状态下,没有发布按钮