- 本文对应项目:目录:G:\idea_java_project 下的travel项目
- 本文参考资料《综合案例笔记》笔记
1、准备工作
项目导入
将资料的空travel项目导入IDEA中。(注意导入项目的方法,选择的是pom.xml文件,参考视频1-4.00)。
IDEA中导入Maven项目的方法:文章1 、文章2 、文章3 (根据具体情况具体分析,IDEA中有多种导入项目的方法)
我们在导入项目的过程中,可能出现项目目录不全的情况,这种情况参考文章:添加链接描述 。在import module的时候选择import modules from external model ,选择下面的Maven,这样才会导入正确的目录结构。
IDEA中如何打开多个项目:添加链接描述 。也可以在“File”选择Open,直接选择在新的窗口打开项目。
- 导入项目的过程中总有这样那样的错误,注意多尝试多查询!!细节要注意!
项目启动
我们已经在pom.xml中安装了tomcat插件,并指定相应的端口以及项目访问的虚拟目录。我们下面介绍的是通过Maven中的tomcat来运行项目。当然,也可以向上面的文章1、文章2一样,使用我们计算机安装的Tomcat来运行项目(具体参考上面文章)。
<configuration>
<!-- 通过maven tomcat7:run运行项目时,访问项目的端口号 -->
<port>80</port>
<!-- 项目访问路径 本例:localhost:9090, 如果配置的aa, 则访问路径为localhost:9090/aa-->
<path>/travel</path>
</configuration>
方法1
方法2:配置,如下(参考视频2)



我们启动项目的时候出现编译异常,出现在MailUtils类,这是因为MailUtils类的编码是UTF-8 BOM,我们需要在NotePad++中将其编码修改为UTF-8,并在IDEA中刷新项目,这样就不会出错。参考文章:添加链接描述
这个黑马旅游网的首页我们已经做过,现在只需要完成登录、注册、安全退出、页面展示、旅游线路收藏等功能即可!
技术选型
本项目的技术选型分为三层架构的3个部分,如下:
4.1 Web层
a) Servlet:前端控制器
b) html:视图 不用JSP原因:我们做的系统是互联网系统,项目是要给网上的用户访问的,这就要求用户的访问速度要快,而且可以做到真正的前后端的分离(JSP作为前端语言还是可以嵌入后端语言)。如果我们要做后台的管理系统,如办公自动化系统,财务系统等给内部人员使用的系统,就会使用JSP作为视图。 (在pom.xml中不需要配置jar包的坐标)
c) Filter:过滤器 (在pom.xml中不需要配置jar包的坐标)
d) BeanUtils:数据封装 简化将表单提交的数据封装为对象的流程
e) Jackson:json序列化工具 我们将页面数据通过异步(Ajax)的方式提交到服务器(比如注册功能,需要将用户填写的数据提交到服务器并接收服务器的响应消息),否则HTML中很难接收服务器返回给客户端的数据,而异步请求响应的过程中,我们一般会将服务器响应回客户端的数据序列化为JSON返回。
4.2 Service层(注意,将数据存储到Redis缓存,是在service业务逻辑层;注册后发送邮件的Javamail也在service层)
f) Javamail:java发送邮件工具
g) Redis:nosql内存数据库
h) Jedis:java的redis客户端
4.3 Dao层
i) Mysql:数据库
j) Druid:数据库连接池
k) JdbcTemplate:jdbc的工具
此时,pom.xml文件中已经将各个技术所需的jar包的坐标配置好了。(参考项目pom.xml文件)
创建数据库
资料中已经提供了travel数据库的原文件,我们在SQLyog中创建即可。
CREATE DATABASE travel; -- 创建数据库
USE travel; -- 使用数据库
-- 下面就是创建表的语句
复制提供好的sql
下面是我们首先使用到的tab_user表,结构如下:

首先是用户管理相关的功能:用户的注册、登录、以及退出功能
1、注册功能
注册功能分析(视频5)
见视频5分析

注册功能-前台-表单校验+异步提交表单数据
包括表单的校验以及校验正确后表单数据的异步提交。具体见regist.html代码解析。
1、表单校验部分;
2、数据的异步提交部分;
为什么自己定义的方法不能写在window.onload()中:添加链接描述
为什么使用异步提交表单的方法:在此使用异步提交表单是为了获取服务器响应的数据。因为我们前台使用的是html作为视图层,不能够向JSP一样直接从servlet相关的域对象获取返回提示信息的值,只能通过ajax获取响应数据JSON。
注册功能-后台-Servlet的实现+service层+dao层实现
步骤
1、首先我们设置处理整个项目Request与Response编码问题的过滤器:CharchaterFilter;
2、其次是处理注册请求的Servlet:RegistUserServlet;
3、这里需要创建一个用户表user表的实体类:User,以及用于封装后端返回前端数据对象的类:ResultInfo;
4、编写service层UserService类的方法:regist();
5、编写dao层的UserDao类的方法:findByUsername(String username)方法以及save(User user),此处使用到JDBCTemplate,需要添加JDBC工具类JDBCUtils;
编写后我们在register.html填写数据并点击注册(异步提交,因此注册页面不会提交表单跳转页面,没有任何反应),发现数据库添加一行数据。我们多次使用同一数据提交注册,IDEA控制台没有反应,且数据库的数据不会增加。
注册功能-前台返回数据的处理+后台验证码
步骤
1、后台RegistUserServlet先对验证码进行判断,正确执行下面内容,不正确返回错误信息给register.html;
2、前台处理返回数据,首先在表单前面添加一个div用于显示注册失败的提示信息,处理异步请求返回的JSON数据data;
测试:注册时没有填写验证码,验证码错误;验证码正确其他信息错误,无法提交;全部填写正确,用户名重复,提交注册,页面显示“用户名重复!!!”;将用户名修改,再次提交,跳转到注册成功页面,且数据库更新一个用户数据;(注意,每次重新提交都要刷新验证码,否则验证码会出错!)
注册功能-邮件激活分析
为什么要进行邮件激活?为了保证用户填写的邮箱是正确的。将来可以推广一些宣传信息,到用户邮箱中。
//分为2个部分
1、给用户发送邮件;这个是复杂功能,我们在Service层进行操作;
2、用户点击邮件实现激活;
注册功能-邮件激活-发送邮件
发送邮件的功能一般都是由工具类来实现,我们不需要会编写工具类,只要看得懂拿过来修改即可!我们的工具类是:MailUtils (见视频14对其分析)。
注意,邮箱在使用的时候,如果是qq邮箱,MailUtils 的成员变量PASSWORD可以直接填写自己的qq邮箱密码(我们也给qq邮箱设置客户端授权码);如果是163邮箱,PASSWORD要填写授权码;
- 授权码:登录163邮箱,设置 -> POP3/SMTP/IMAP -> 客户端授权密码 ,选择开启客户端授权码。授权码是用于登录第三方邮件客户端的专用密码,开启这个授权码,我们的MailUtils才能登录邮箱。!
测试MailUtils能否发送邮件,我们在MailUtils中创建一个测试方法,我们直接在MailUtils中点击运行这个方法,我们的邮箱就会收到一封邮件。
public static void main(String[] args) throws Exception { // 做测试用
MailUtils.sendMail("收件人邮箱(使用自己的163邮箱)","你好,这是一封测试邮件,无需回复。","测试邮件");
System.out.println("发送成功");
}
注意,这里设置自己qq邮箱(195那个)以及163邮箱的客户端授权码,使用qq邮箱作为发送,使用163邮箱作为接收,最后163邮箱成功收到邮件。这里,MailUtils就是获取qq邮箱的授权作为发送方(需要使用qq邮箱的客户端授权码),然后将邮件发送到163邮箱(只要有邮箱即可,不需要授权码)。
我们下面在实现发送邮件给你的时候,使用自己的qq邮箱作为发送方(将qq邮箱与客户端授权码赋予MailUtils的USER与PASSWORD即可),然后获取填写的用户的邮箱,调用MailUtils的sendMail方法,将相应的数据发送到用户邮箱。
注册功能-邮件激活-点击激活用户分析
最重要的是要发送什么内容给用户才能激活,那么怎么样才算激活?我们使用User实体类中的一个成员变量status表示激活状态,Y代表激活,N代表未激活。我们注册的时候,将status设置为N,用户点击激活邮件的超链接后,将status修改为N,就代表用户激活。
另外,我们激活的时候需要区分哪一个用户的status要修改为Y。我们可以在service层查询到注册用户不存在,往数据库写数据的时候,给每一个用户生成一个随机的激活码code,携带status=N放入数据库。此时已经确认用户注册成功,我们将激活码存储到MailUtils的信息中,发送给用户邮箱,用户收到邮件后,点击携带激活码的超链接访问激活用户的Servlet,这样就可以根据激活码查询User对象,将对应User对象的Status修改为Y。(不用uid区分用户是因为uid很容易被猜到!)
需要注意,发送邮件的功能我们在service业务逻辑层处理,因为dao层只处理简单的逻辑,而web层的Servlet用来控制页面。我们在service的regist方法已经可以确定注册成功,就可以在这里发送邮件激活!
另外,我们使用UuidUtil根据类来获取随机的激活码(会用即可!)
步骤
1、在service层,发现用户名不重复,可以注册之后,我们需要设置status以及激活码code,将这两个存储到User对象。
2、在service层,可以注册并往数据库写入相应的信息后,就要发送激活邮件,激活邮件携带用户的唯一标识激活码,发送给用户;
测试:
1、将注册用户邮箱填写为自己的163邮箱,将发送邮件的官方邮箱填写为自己的qq邮箱;
2、访问:http://localhost/travel/register.html ,重新注册一个用户,邮箱填写为自己的163邮箱,点击注册,跳转到成功页面,然后我们的163邮箱收到一封邮件,数据库数据还没有更新(我们还没有修改DAO层的代码);
3、点击激活邮件的链接,路径为:http://localhost/travel/activeUserServlet?code=4ee7afd043534731a15e463c4f1bb568 。
注册功能-邮件激活-ActiveUSerServlet代码实现+service层、dao层的实现
步骤
1、编写激活的Servlet:ActiveUserServlet。调用service层的active方法激活用户;
2、编写service层的active方法,active方法调用dao层的findByCode方法,根据code查找用户,查找不到返回false,查找到返回true,并调用dao层的updateStatus修改状态码完成激活;
3、编写dao层的findByCode方法与updateStatus方法;
4、注意将前面的save方法添加status与code2个内容插入代码;
debug功能:将光标放到对象上面,可以看到对象内部变量的状态。
测试:
1、将注册用户邮箱填写为自己的163邮箱,将发送邮件的官方邮箱填写为自己的qq邮箱;
2、访问:http://localhost/travel/register.html ,重新注册一个用户,邮箱填写为自己的163邮箱,点击注册,跳转到成功页面,然后我们的163邮箱收到一封邮件,数据库数据更新,status=N;
3、点击激活邮件的链接,路径为:http://localhost/travel/activeUserServlet?code=4ee7afd043534731a15e463c4f1bb568 ,显示“激活成功,请登录”。我们点击登录,到login.html页面。与此同时,数据库对应用户的status变为Y。成功!
2、登录功能
登录功能分析
分析如下(具体哦见视频18分析)
登录功能实现-后台-Servlet部分
步骤
1、编写LoginServlet类,调用service层的login(User user)方法,返回一个数据比较全的User对象。这里同样使用ResultInfo封装结果数据,并在登录页面使用异步提交,这里ResultInfo转换为JSON数据返回login.html。判断用户是否存在,不存在提示“用户名或密码错误”,ResultInfo的flag=false;存在则判断是否激活,没激活提示“您尚未激活,请激活!”,ResultInfo的flag=false;激活了就将ResultInfo的flag=true。将来login.html接收ResultInfo的flag=true进行登录跳转。
2、编写service层的login()方法,调用dao层的findByUsernameAndPassword方法;
3、编写dao层的findByUsernameAndPassword方法,根据用户名与密码查询用户;
4、编写验证码部分功能;
登录功能实现-前台-login.html部分
分析
1、同样是表单验证部分,参考前面的register.html;
2、表单验证通过,发送Ajax异步请求,同时获取返回的数据,将数据进行处理;
测试:
1、首先测试表单验证部分;
2、测试验证码部分;
3、验证码正确,输入错误的数据库没有的用户或者密码,显示“用户名或密码错误”
4、验证码正确,表单内容正确,我们输入已经激活的用户,正确登录跳转到index.html;
5、验证码正确,表单内容正确,我们输入尚未激活的用户,显示“您尚未激活,请激活!”;
注意,HTML页面的调试需要在浏览器f12进入开发者状态调试,同样可以打断点调试。而java代码部分使用IDEA调试。
登录功能实现-前台-index.html姓名提示 部分
分析:我们无法在HTML页面通过EL表达式获取Session域存储的姓名内容,因此我们还是的通过异步请求的方式,在index.html页面发送异步请求到服务器查询用户的信息返回到index.html页面。
我们在index.html页面中添加如下区域
<!--引入头部-->
<div id="header"></div>
我们在上面引入了include.js
<!--导入布局js,共享header和footer-->
<script type="text/javascript" src="js/include.js"></script>
include.js的代码为
$(function () {
$.get("header.html",function (data) {
$("#header").html(data);
});
$.get("footer.html",function (data) {
$("#footer").html(data);
});
});
分析:index.html导入一个include.js的文件,这个文件中使用异步get请求请求header.html页面,并将响应回来的header.html页面放到index.html页面的id="header"的div中。(注意,这里请求的是整个header.html页面,我们之前向Servlet发送异步请求,请求的也是整个Servlet,而Servlet会将相应的数据封装成为JSON发送回浏览器。既Ajax请求的如果是静态页面,返回的就是整个页面;请求的若是Servlet,返回的就是Servlet通过Response写回浏览器页面的数据)
头部信息header.html如下;同样,尾部信息也是这样加载进来的!(注意这种加载页面的方法,便于页面的复用!)
由此可见,我们异步请求姓名应该在header中执行。
1、首先,在Header.html中发送异步请求到FindUserServlet来获取用户名;
2、在LoginServlet中,如果登录成功,将相应的用户User对象存储到Session中;
3、编写FindUserServlet,从Session取出登录的User对象,并将User对象发送异步请求的header.html。如果没有等登录或者没有Session(登录退出Session被销毁),或者Session中没有User对象(第一次登录),那么FindUserServlet获取不到User对象,返回的JSON也没有name的值,header.html中也不会显示用户的name。
测试:正常登录,inde.html页面有用户名的提示:
退出功能
什么叫做登录了?session中有user对象。我们成功登录之后,会往Session中存储登录用户对应的User对象。如果我们退出登录,那么登录用户存储在Session中的对象应该删除。
事实上,我们登录的时候,会通过过滤器进行过滤。对于登录页面register.html、首页index.html以及样式相关的文件,直接放行;而其他页面,会先获取Session获取并判断User对象是否为空,为空说明还没有登录,不放行;不为空说明已经登录,可以访问其他页面。因此,Session中有没有不为空的user对象,是有没有登录的标志。另外,在index.html中,如果没有登录,Session中也获取不到user,也没办法显示“欢迎回来 , name”。
实现步骤:
1. 首先,退出是个超链接,我们将超链接的跳转路径指向ExitServlet,将session销毁
<a href="javascript:location.href='exitServlet'">退出</a> location.href的值不需要加虚拟路径与"/";
当然,我们直接如下写也可以:
<a href="javascript:'exitServlet'">退出</a>
注意这里的写法!!!
2. 销毁Session,重定向跳转到登录页面
其次是Servlet部分的代码优化
3、优化Servlet——BaseServlet的抽取
减少Servlet的数量,现在是一个功能一个Servlet,将其优化为一个模块一个Servlet,相当于在数据库中一张表对应一个Servlet,在Servlet中提供不同的方法,完成用户的请求。(见视频24分析)
分析:我们以前为什么要一个功能对应一个Servlet?
原理:
因为我们之前写的Servlet继承了HttpServlet,HTTPServlet里面,service方法已经对浏览器请求中的请求方式作出判断,完成doPost()与doGet()方法的分发。而我们的Servlet如果继承HTTPServlet,就只需要复写HTTPServlet里面的doGet()与doPost()方法即可。
而Tomcat在接收到客户端的请求后,会先执行HTTPServlet的service方法,HTTPServlet的service方法会根据其判断的请求的方式是get还是post,自动调用我们在自己的类中定义的doGet()与doPost()方法。
说明:
如果我们想把所有的用户Servlet的功能写到一个UserServlet中,每一个功能写为UserServlet的一个方法,面临的问题是:用户的请求发送过来,怎么取找到对应的方法?比如注册请求就应该在UserServlet中找注册方法,登录请求就应该找登录方法....但是HttpServlet只分发了请求方法,并没有办法根据不同的请求调用相应的方法执行。而我们的Servlet为了能够被浏览器通过URL访问到,又必须继承HttpServlet。
解决:
HttpServlet没办法修改,我们可以自己定义一个BaseServlet,这个BaseServlet继承了HttpServlet,然后我们的模块Servlet,如UserServlet、CategoryServlet等继承BaseServlet,这就相当于间接继承HttpServlet,我们的模块Servlet也是真正的Servlet,可以被浏览器通过URL访问到(HttpServlet的子类才能被浏览器通过URL访问到)。
其次,我们在BaseServlet中根据请求功能的不同,分发这些请求去调用模块Servlet,如UserServlet的不同功能:比如注册请求就分发去找UserServlet的注册方法,登录请求就分发去找登录方法。这样,我们就可以将一个模块的所有功能写到一个Servlet中!

其实关键是BaseServlet中如何对方法的功能进行分发?
1、我们创建一个UserServlet,里面创建了多个方法(假设有login、regist等方法),分别完成用户的注册、登录、退出...等功能,然后将这个UserServlet的项目虚拟目录修改为:@WebServlet("/user/*"):适配user目录下面所有资源的访问请求 ;
2、我们将浏览器端页面访问UserServlet的路径修改为:.../user/login ,.../user/regist;
3、然后,这些页面访问UserServlet之前,会先访问BaseServlet,我们在BaseServlet中,获取请求的URL,根据请求的URL获取请求要访问的资源,或者说获知请求的功能,然后分发这些请求去UserServlet中相应功能对应的方法。比如我们BaseServlet获取请求为.../user/login,可以分发这个请求去UserServlet的login方法。
具体实现:
- PART1
1、创建BaseServlet继承HttpServlet,创建UserServlet继承BaseServlet,创建CategoryServlet继承BaseServlet。
我们在BaseServlet中重写HttpServlet的service方法(HttpServlet的service方法不再执行),客户端发送请求访问UserServlet或CategoryServlet中的方法的时候,就会去执行BaseServlet的service方法。
测试:我们在BaseServlet的service方法中打印一句话,启动服务器访问UserServlet:(匹配/user/*的请求都可以访问到UserServlet)http://localhost/travel/user/add ,发现控制台打印乱码

IDEA控制台中文乱码解决:-Dfile.encoding=gb2312 ,设置一下Maven中虚拟机的编码为GB2312。

- PART2
2、确定访问UserServlet时BaseServlet的service方法会先执行,我们就可以在BaseServlet中对方法进行分发。(具体分发方法见BaseServlet与UserServlet的代码)
这里使用反射的方式,通过UserServlet的Class对象的getMethod()方法获取想执行方法的Method对象,再通过Method的invoke方法就可以执行我们想执行的UserServlet中的方法
测试:
此时我们有2个Servlet:UserServlet、CategoryServlet。其中每个Servlet都有一个add()方法与find方法。我们访问:http://localhost/travel/user/add,报异常:
NoSuchMethodException: cn.itcast.travel.web.servlet.UserServlet.add:没有这个add方法。
分析:这是因为我们的add方法与find方法都是受保护的protected,getMethod()方法只能获取public的方法。因此我们需要使用getDeclaredMethod方法,才能获取add方法与find方法的Method对象。在执行这个protected方法之前,还需要method.setAccessible(true);,对protected方法的访问取消权限检查,暴力访问。
下面分别访问:
http://localhost/travel/user/add :UserServlet的add方法
http://localhost/travel/user/find :UserServlet的find方法
http://localhost/travel/category/add :CategoryServlet的add方法
http://localhost/travel/category/find :CategoryServlet的find方法
模块Servlet:CategoryServlet与UserServlet里面的功能方法本类就是想被访问到的,我们直接将他们的修饰符修改为public,这样就不需要暴力反射。(这样模块Servlet中private私有的方法也不会因为暴力反射被获取执行)
- PART3
3、完成BaseServlet的抽取之后,我们接下来编写UserServlet,就是将之前的各类Servlet的功能封装成为方法放到UserServlet中:RegistUserServlet——>regist()方法、LoginServlet——>login()方法、FindUserServlet——>findOne()方法、ExitServlet——>exit()方法、ActiveUserServlet——>active()方法。(验证码的CheckCodeServlet我们不抽取)
4、接下来对UserServlet各个方法的代码进行抽取:
将:UserService service = new UserServiceImpl(); 代码放到成员位置进行声明。方法中不需要再获取这个对象。
5、接下来,我们需要到前面的各类页面中修改访问的路径,因为我们所有User的Servlet功能已经抽取到UserServlet:
login.html页面:异步访问loginServlet,修改为:user/login (异步请求路径前面不需要加/,这是格式!)
header.html页面:异步访问findUserServlet,修改为:user/findOne ;同样是在header.html页面,退出功能,exitServlet修改为:user/exit
<a href="javascript:location.href='user/exit'">退出</a>
register.html页面:异步访问registUserServlet,修改为:user/regist
在UserServiceImpl中,设置邮件内容为访问ActiveUserServlet,修改为:
href='http://localhost/travel/user/active?code="+user.getCode()+"'
测试(测试之前注意清除浏览器缓存):
登录功能:正确登录时,抓包访问路径是:http://localhost/travel/user/findOne、错误无法登录时,访问路径是:http://localhost/travel/user/login。
退出功能:退出访问:http://localhost/travel/user/exit
注册和激活功能我们不再演示。效果类似。
下面是旅游页面相关的模块:分类信息展示、商品信息的列表展示等功能
4、分类数据展示功能
分类数据展示的效果如下:
现在门票、酒店等数据是假的数据,我们要从数据库中读取数据填充进去。分析见视频26.
分类数据展示功能-实现-后台代码
步骤
//我们从后往前写(以后开发大部分时候是从后向前写)
1、根据数据库中的category表,创建一个对应的Category类;
2、CategoryDaoImpl中创建一个返回List<Category>类型的findAll()方法,该方法查询数据库,返回数据封装成为List集合;
3、CategoryServiceImpl中创建一个返回List<Category>类型的findAll()方法,调用Dao层的findAll()方法。同样返回List<Category>;
4、编写CategoryServlet中的findAll()方法。注意将该方法的资源路径修改为:/category/*
我们发现各类模块Servlet中序列化JSON的动作很多,我们可以将这个动作的代码封装到所有模块Servlet的父类:BaseServlet中。在BaseServlet中创建2个域序列化JSON相关的方法:一个是直接将List数据序列化为JSON并写入Response的写出流,另一个是将List数据序列化为JSON返回。
5、将之前使用到JSON序列化的UserServlet代码全部替换为方法。
分类数据展示功能-实现-前台台代码
步骤
1、分类数据展示在index.html页面所加载的header.html部分,header.html加载后,向CategoryServlet的findAll()方法发送ajax请求,查询分类数据的信息,那么请求url为:category/findAll
2、在Ajax请求接收到返回的JSON数据后,在header.html数据展示部分对这些JSON进行遍历展示。
测试:访问index.html页面:http://localhost/travel/index.html,会显示数据库中取出的真实数据。
分类数据展示功能-缓存优化
分析发现,分类的数据在每一次页面加载后都会重新请求数据库来加载,对数据库的压力比较大,而且分类的数据不会经常产生变化,所以可以使用redis来缓存这个数据。
这个功能较为复杂,我们可以在service层对其进行业务逻辑的操作。
为什么放在service层?
复杂的业务逻辑一般都是在service层进行,dao层只有简单的逻辑功能,而web层的Servlet负责与浏览器交互,将数据展示到页面。我们都是查询,可以在service层完成redis缓存查询,这样web层拿到数据就不需要再考虑从哪里拿数据,而是直接处理显示。(记住这些功能一般是在service完成的即可)
步骤
1、先从Redis中查询,查询到说明Redis已经有数据sortedset,我们将sortedset中的数据取出,存储到List<Category>中返回;
2、如果Redis中没有查询到,先从MYSQL中查询(dao的findAll方法),然后将查询到的List<Category>中的数据存储到Redis的sortedset中。
测试:maven中pom.xml使用坐标导入了远程仓库的Redis相关包,我们使用的时候,不需要导包,只需要将Redis数据库打开即可。本机的Redis在:E:\redis\redis-2.8.9,点击启动服务端与客户端。
第一次访问:http://localhost/travel/index.html,发现打印:MYSQL数据库查询;刷新这个页面,打印:从redis中查询… 。后面都是从redis中查询。(而且从第二次开始我们发现刷新加载的速度比较快,因为从redis中读取比从MYSQL中读取快)
5、旅游线路的分页展示功能
点击了不同的分类后,将来看到的旅游线路不一样的。通过分析数据库表结构,发现,旅游线路表和分类表时一个多对一的关系。(见视频31分析)这里在tab_route设置一个外键cid,它对应tab_category表的cid属性。
将来我们点击分类展示的条目,就会去访问route.html,这时应该将这个条目对应的cid同时携带到route.html,这样就可以在tab_route表中,根据cid查询数据:Select * from tab_route where cid = ?; 这样就可以查询出条目对应的旅游线路。
分类分页展示功能-类别id的传递
步骤
1、我们在这里修改了CategoryServiceImpl的findAll方法代码,使其返回的List<Category>集合中的Category对象的cid有值,既实现了在Redis中查询SortedSet的score(cid);
2、在header.html页面,每一个条目的href的路径应该传递条目对应的cid,每一个条目都是跳转到route_list.html页面进行展示。
3、对于route_list.html页面,我们在index.html(header.html)页面点击的时候已经将cid传递过来,接下来我们通过DOM对象location的search 属性来获取cid,search 属性是一个可读可写的字符串,可设置或返回当前 URL 的查询部分(问号 ? 之后的部分)
- 视频31后面部分缺失,我在b站上找到相应的视频:https://www.bilibili.com/video/av93013349?p=31
分类分页展示功能-分页展示数据功能
分页功能的分析(见视频32)。我们之前使用的是同步请求分页,可以将数据放入Request域中,在JSP页面中使用EL表达式以及JSTL标签来遍历数据展示,比较方便。我们这里使用html作为页面,只能使用异步请求的方式完成分页。我们此处使用一个PageBean对象来封装在客户端与服务器端之间传递的数据。
步骤
1、服务器端后台代码的编写:
1) 首先创建封装页面显示数据的PageBean类、保存旅游线路信息的表tab_route对应的类Route;
2) 编写web层的RouteServlet,因为线路是一个单独的模块,我们将其写为一个RouteServlet,注意Servlet路径写为:"/route/*",且RouteServlet要继承BaseServlet;
这个Servlet中接收处理页面的分页显示的请求,并根据请求调用service层的pageQuery方法,返回一个封装了当前页面数据的PageBean<Route>对象。
3) 编写service层的RouteServiceImpl中pageQuery方法,该方法需要根据页面发送过来的当前页面相关信息,调用Dao层的相关方法,封装一个PageBean<Route>对象并返回。
这个PageBean<Route>对象中封装了5个属性,其中总记录数totalCount、当前页面数据集合List<Route>要通过数据库查询,当前页数currentPage与每页条目数pageSize是我们客户端提交的,totalPage总页数是可以计算的。
那么我们需要调用DAO层的findTotalCount()方法查询总记录数totalCount,调用findByPage()方法分页查询当前页面的数据。
4) 编写dao层的findTotalCount()方法:查询总记录数totalCount,findByPage()方法:分页查询当前页面的数据。
注意这个方法中三层架构各个部分处理的逻辑:
web:处理页面发送过来的分页显示的请求,调用service层的方法返回一个封装当前页面数据的PageBean<Route>对象;
service层:完成封装一个PageBean<Route>对象方法的逻辑操作,需要查询数据库的数据有总记录数totalCount、当前页面数据集合List<Route>,这些调用dao层的方法完成;
dao层:在数据库中查询总记录数totalCount、当前页面数据集合List<Route>
知识点:
使用JDBCTemplate:
1)如果要查询返回一个基本数据类型的数据,使用queryForObject(sql , 获取值包装类.class , 参数);
2)如果要查询返回某一个类的对象,使用使用queryForObject(sql , BeanPropertyRowMapper , 参数);
3)如果要查询返回一个List集合,使用query(sql , BeanPropertyRowMapper , 参数);
查询结果是多个值使用query,查询结果是一个值使用queryForObject。
测试:通过浏览器直接访问RouteServlet类的pageQuery方法:http://localhost/travel/route/pageQuery?cid=5,页面打印pageQuery返回的JSON数据。我们可以使用JSON的在线解析工具来看。以后如果获取的JSON数据比较复杂,我们可以使用在线解析器来看,这样比较方便操作。(注意我们项目插入的数据只有cid=5的部分!)
2、客户端前台代码的编写:
我们点击header.html条目栏的条目,就会跳转到route.html页面来分页显示数据。我们需要在route.html发送数据的异步请求,并接收数据进行展示。
1) 发送ajax请求,请求route/pageQuery,传递cid。返回封装当前页面数据的PageBean对象所转换成的JSON(我们使用JSON的在线解析器可以看JSON的结构,方便操作);
2) 解析pagebean数据,展示到页面上。展示到页面上的数据包括2部分:分页页码的展示、列表数据展示。(我们在JSP页面中可以使用EL表达式和JSTL标签来遍历显示分页条,在HTML页面中,就要使用JS来循环遍历了,注意区分);
3) 点击分页页码,可以实现翻页的功能;
我们不能在每一个分页页码的<li>中使用同步请求的方式,直接跳转到某一个位置,如下:
var li='<li><a href="route/pageQuery?cid=5¤tPage=3">'+i+'</a></1i>';
因为这样点击这个页码,页面会跳转,而且页面返回的JSON数据并不会装载到route_list.html页面,无法被我们解析,会显示上图的JSON数据。因此这里我们还是需要在页码的<li>标签中使用异步请求的方式获取数据。
4) 完成翻页功能,接下来完成如下几个小功能:
实现点击框框(<li>部分)也可以跳转,之前点击数字(<li>中的超链接)才能跳转;
给当前页加上特殊样式;
首页、上一页、下一页、末页的点击功能;
5) 太多页面看起来很难看,我们一个页面只显示一定数目的页码,其他的不显示。(达成如下图的效果)。
另外,我们完成点击某一页会跳转到这一页的顶部的效果。

6、根据关键字查询旅游线路的功能
功能:在下面的搜索框中输入搜索的内容,在数据库中完成查询,并将数据展示到页面上(其实设置MYSQL的模糊查询功能),注意,这里查询的是数据库中rname中的内容。另外,注意这个搜索的输入框在header.html页面。
步骤
//-----------客户端前台代码header.html页面
1、首先,我们需要header.html在点击“搜索”的时候,获取搜索输入框的内容rname,同时获取当前页面路径的cid,然后携带这2个参数跳转访问route_list.html。(cid代表在分页栏的哪一个条目下进行搜索,rname代表搜索的内容)
index.html与route_list.html都有header.html页面的搜索框,如果在index.html搜索,cid=null,要搜索所有cid中满足rname的数据;如果在route_list.html页面搜索,有对应这个页面的cid,要搜索这个cid下面满足rname的数据。
2、我们在route_list.html获取到cid与rname,根据这两个参数,发送异步请求去Servlet查询数据库对应的值,然后将返回的数据展示到页面。
//-----------服务器端后台代码
3、修改后台的代码
第一步:首先是RouteServlet,接收搜索参数“rname”,调用service层的pageQuery方法的时候,将rname也传递进去。那么我们就要修改service层的pageQuery方法的参数。另一方面,service在调用dao层的findTotalCount方法与findByPage方法的时候,也需要根据rname查询,传递进去一个rname。那么我们也要修改dao层的findTotalCount方法与findByPage方法的参数。
第二步:我们需要修改DAO查询数据库的代码,要根据rname与cid进行查询。
这里需要考虑多种情况:
1)我们有可能在首页点击搜索,此时没有cid(cid=null),有rname;
2)我们有可能直接点击分类条目搜索,此时没有rname(rname=null),有cid;
3)我们可能在点击分类后再分类中搜索,此时rname与cid都要
因此,在dao中我们需要根据cid与rname的值是否存在来分别进行不同的查询。(这里需要使用定义SQL模板的方法,具体见代码,参考之前的day09Case项目中模糊查询的代码,之前可能有多个模糊查询的值,这里只有一个——rname)
测试如下:测试1
//------------前台代码route_list.html
4、我们要在前台load方法中传递rname参数,并对数据进行展示,
1)首先load方法需要传递rname参数,同时对使用load方法的位置进行改造(修改的时候要拼接字符串,这里要格外注意!!!容易出错,先用常数替代,再替换为真实数据),参考视频42-3.00处解析,拼接字符串一定要小心。
完成,我们做一次总的测试,如下测试2
测试1:
我们先通过浏览器直接访问的方式,而不是通过页面跳转的方式来测试代码:
访问:http://localhost/travel/route/pageQuery (无cid与rname),查询所有的数据
访问:http://localhost/travel/route/pageQuery?cid=5 (只有cid=5),查询出cid=5的数据。(其实表里面也只有cid=5的数据)
访问:http://localhost/travel/route/pageQuery?rname=西安(只有rname),查询出来首页cid对应的满足rname的数据。(其实只有cid=5有数据)
访问:http://localhost/travel/route/pageQuery?cid=5&rname=西安,查询出id=5的条目下面的所有包含“西安”的条目。(这里注意tomcat7的解码问题,中文字符rname需要我们解决一下编码问题)
测试2:
//-------有rname,没有cid的情况(在首页点击搜索)
首先在首页index.html搜索“西安”,控制台报错:java.lang.reflect.InvocationTargetException,因为我们index.html路径中没有cid,那么获取的时候就会获取一个null值,我们将这个null值设置到路径中:
"http://localhost/travel/route_list.html?cid="+cid+"&rname="+rname;
我们在route_list.html获取cid,就获取到一个字符串,字符串的值是"null"(当一个null的值放入字符串中,再次获取出来,就变成值为"null"的字符串),再发送异步请求到RouteServlet,RouteServlet获取cidStr,同样获取一个字符串,值是"null",那么如下:
if(cidStr != null && cidStr.length() > 0)//cidStr="null",不是null,且length>0
{//那么在这里转换的时候便会出错
cid = Integer.parseInt(cidStr);//将字符串的cid转换为int的cid
}
那么我们加一个判断cidStr!=null,这样在首页点击,也不会报错。
if(cidStr != null && cidStr.length() > 0 && !"null".equals(cidStr))//判断cidStr!=null
{
cid = Integer.parseInt(cidStr);//将字符串的cid转换为int的cid
}
我们再在首页点击搜索“西安”,此时没有报错,而且此时cid=0,rname=西安,而在RouteDaoImpl中如果cid=0,那么不会去查询cid,而是直接查询tab_route表中rname=%西安%的数据。(此时查询出来的数据是满足cid=tab_category中的任意cid值,rname=%西安%的数据。当然这里我们只有cid=5有数据,如果其他cid有数据,那么也会查询出来)。
//-------有cid,没有rname的情况(在首页点击条目)
在首页点击条目“国内游”,查询出所有cid=5的数据;
//-------有cid,有rname的情况(在首页点击条目,在条目中搜索)
在首页点击条目“国内游”,查询出所有cid=5的数据;然后搜索“西安”,正确展示数据。
发现一个问题并解决
我们访问index.html,点击分类条目的“国内游”,跳转到route_list.html页面展示数据,此时访问的路径为:(我们没有输入搜索相关的内容rname)
http://localhost/travel/route_list.html?cid=5
我们发现路径中没有rname,那么我们在route_list.html中通过下面的语句获取route_list.html页面路径中的rname,得到的rname=null
var rname = getParameter("rname");
那么加载的时候的方法:load(cid,null,rname); 中的rname也为null,既load(5,null,null),发送到RouteServlet的pageQuery方法中的rname是一个空的字符串,如下图:


如上图分析,此时可以根据cid=5查询数据,rname在查询的时候被置为空字符串"",不会添加rname的查询语句。此时数据正常显示。
需要注意的是,我们查询的数据返回route_list.html页面的进行展示的时候,我们显示条目的
- 标签如下(此时的rname被添加到字符串li中,这样rname就变成"null"):
li = '<li οnclick="load('+cid+','+i+',\''+rname+'\')"><a href="javascript:void(0)">'+i+'</a></li>';如果我们没有在header.html页面添加rname数据点击搜索,此处的rname就是我们点击“国内游”的时候加载进来的rname=null,它被拼接到字符串li中,此时rname变成了rname=“null”,一个值为null的字符串。那么我们点击这个
- (表示跳转到其他页),同样会调用load(5,2,“null”)(注意此处rname从前面的null变成了"null"),如下图:



根据上面的分析,我们的SQL语句为 select count(*) from tab route where 1=1 and cid=5 and rname like "null"这样查询出来的数据为空。
解决:在dao查询中添加一句“!“null”.equals(rname)”,使得rname="null"的时候不会添加rname like “null” 的SQL模糊查询。这样便不会出错。

成功排除!!!
说明:我们在index.html页面输入搜索后,即使我们没有修改也可以正常显示,这是因为rname始终不为null,不会出错。
说明:
1)在RouteDaoImpl添加 “null”.equals(rname) 判断语句,是为了避免我们在搜索框没有内容(rname=null)的时候,点击其他页,此时rname被拼接到字符串中,再取出变成rname=“null”,会造成错误搜索
2)在RouteServlet添加 “null”.equals(cidStr) 判断语句,是为了避免我们在index.html首页点击搜索的时候,cid=null,并且被拼接到路径的字符串中,再次被获取出来的时候,cid=“null”,这样在类型转换的时候会出错,即使这里不错,后面查询的时候也会因为查询 cid=“null” 而查不出数据。7、查询旅游线路详情的功能
如下图

分析:1、我们点击查看详情,会跳转到route_detail.html页面展示数据,此时为了区分各个线路,应该将线路在tab_route表中对应的rid传递过去。(rid是某一条线路的标记) 我们在route_list.html页面的展示数据的<li>中,有一个“查看详情” <p><a href="route_detail.html">查看详情</a></p> 我们在这里传递线路对应的rid: <p><a href="route_detail.html?rid='+route.rid+'"> 需要注意的是,这里的数据在单引号的包围之中,这里route.rid仍然要使用单引号来拼接字符串,不能直接写成为如下:(容易犯错,注意!) <p><a href="route_detail.html?rid="+route.rid>查看详情</a></p> 2、我们点击“查看详情”访问route_detail.html的时候,会携带一个rid过来,当route_detail.html加载完成之后,我们发送Ajax请求(携带rid)给RouteServlet的findOne方法,这个方法根据rid调用service层的方法,查询当前页面的详细数据,然后将数据封装成为JSON,返回route_detail.html页面。对route_detail.html页面详细信息的分析:如下图,我们的route_detail.html页面的各部分信息如下,根据分析,我们需要查询的数据库表有3张。当然,查询完3张表后,这些所有的信息还是要封装到Route对象中,然后转换成为JSON返回route_detail.html页面。
1、tab_route(详细的旅游信息表):rname(线路名称)、price(价格)。这里面的rimage不是我们需要的,因为一个页面对应多张图片,Route类中有一个List<RouteImg> routeImgList集合,这个集合就是用来存储rid对应的图片类RouteImg的对象的,这个RouteImg类对应tab_route_img表。 我们查询数据库表知道,表tab_route_img的rid属性(多)对应表tab_route的rid属性(一),既可以把表tab_route_img的rid属性当作表tab_route的rid属性的外键。那么我们可以根据tab_route的rid去查询表tab_route_img对应rid的图片,这些图片就是我们对应rid页面的图片。 2、tab_route_img(图片信息表):对应rid属性的bigPic(页面图片); 我们又发现,商家对应的表tab_seller(一)的sid属性对应tab_route的sid属性(多),既可以把表tab_route的sid属性当作表tab_seller的sid属性的外键。既一个商家可能对应多个旅游线路。我们页面要显示商家的信息,还需要根据页面指定的rid,查询该页面的sid,根据sid查询tab_seller对应的商家信息。Route类中有一个Seller seller属性,就是用来存储对应的商家信息的,Seller 类对应tab_seller表。 3、tab_seller(商家信息表):对应sid的sname(商家信息)


具体实现://-----------后台代码 1、首先,创建tab_seller表对应的实体类:Seller(对应卖家信息),tab_route_img表对应的实体类:RouteImg(对应页面的图片信息); 2、其次编写web层的RouteServlet的findOne()方法,这个方法根据rid查询一个旅游线路的详细信息并序列化为JSON返回。调用service层的RouteService的findOne()方法,返回一个封装详细信息的Route对象。 3、再编写service层RouteServletImpl中的findOne()方法,这个方法完成一个Route对象的信息的封装。这个方法查询tab_route表,封装的Route对象并不完整。既Route对象的全部信息要从tab_route表、tab_seller表、tab_route_img表中查询; 通过rid,调用routeDao的findOne,返回一个Route对象。再调用routeImgDao的findByRid(rid)方法,返回一个封装线路图片对象的List<RouteImg>,并将该List设置到Route对象中(查询tab_route_img表); 调用sellerDao的findById(sid)方法,查询Seller对象并返回,并将Seller对象设置到Route对象中(查询tab_seller表)。 4、完成dao层的编写: routeDao:findOne(rid) 查询tab_route表,返回rid对应的Route对象(信息不完整); routeImgDao:findByRid(rid) 查询tab_route_img表,返回封装rid对应的图片对象RouteImg的List集合 sellerDao:findById(sid) 查询tab_seller表,返回Route对象的sid对应的商家信息封装成为的Seller对象测试:直接通过浏览器访问RouteServlet的findOne方法:http://localhost/travel/route/findOne?rid=1


//-----------前台代码 route_detail.html加载后完成以下功能(route_detail.html中已经定义方法实现了页码的一些功能): 1. 获取rid 2. 发送ajax请求,获取route对象 3. 解析对象的数据 这一部分需要对页面进行分析,我们如果不确定代码属于那一部分,直接将这部分代码删除刷新页面,那一部分不见这代码就属于那一部分我们在“f12-source-相应页面访问路径的文件”,可以查询访问页面的代码,在里面可以设置断点进行调试。(视频45-18.00)
浏览器页面相应的断点调试如下:
我们在浏览器页面右键检查元素,可以看到当前页面加载完毕之后的代码(f12是进入查看页面的源码,而审查元素是查看页面执行完毕后的标签的效果)。
8、旅游线路收藏功能
功能描述:如下,用户如果收藏过这个页面的旅游线路,则收藏按钮变为透明色,无法点击收藏;如果没有收藏过,收藏按钮为红色,可以点击收藏。
首先,判断用户是否收藏,并根据结果再页面显示收藏按钮。(先不设置点击可以收藏的功能)分析:1、在route_detail.html页面加载后,判断当前的登录用户是否收藏过该线路。 具体操作:当页面加载完成后,发送ajax请求,获取用户是否收藏的标记。根据标记,展示不同的按钮样式。 那么应该如何判断某一个用户是否收藏过某一条线路呢? 首先,一个用户可以收藏多条线路,一条线路可以被多个用户收藏,既用户和线路是多对多的关系。那么我们创建一个tab_favorite表,这个表的uid(多)对应用户表tab_user的uid(一),tab_favorite表的uid相当于tab_user表uid的外键。同样,tab_favorite表的rid(多)相当于tab_route表rid(一)的外键。 当我们想知道某一个用户有没有收藏某一条线路的时候,使用这个线路的rid去与对应用户的uid,查询tab_favorite表,如果有对应的rid与uid的对,说明用户已经收藏该线路,反之则没有收藏。

判断用户是否收藏的代码编写0、首先,编写tab_favorite表对应的java实体类Favorite类。 注意:数据库中rid和uid在java实体favorite中并没有单单定义为两个普通字段,而是分别对应route和user实体,为什么要这样?实际应用中我发现一个好处就是方便、更容易切合我要实现的功能(比如数据展示和提取)。 不理解,反正记住就算Favorite类中的属性是Route对象和User对象,而tab_favorite类中的属性是rid与uid,这样同样可以查询数据库并将数据封装为Favorite对象。 //-----------------后台代码 1、在RouteServlet中编写isFavorite()方法,获取rid与uid,调用FavoriteService的isFavorite方法查询是否收藏。 2、在service层的FavoriteServiceImpl中,编写isFavorite(rid ,uid)方法,调用dao层的findByRidAndUid(rid ,uid)方法方法,查询返回一个Boolean的结果。 3、在dao层的FavoriteDaoImpl中编写findByRidAndUid(uid , rid)方法,查询返回一个Favorite的对象。 //------------前台代码 在跳转到route_detail.html页面后,页面的路径有一个rid属性,获取这个rid属性,发送Ajax异步请求到RouteServlet去查询有没有这个rid对应的用户,根据查询结果设置页面的样式。测试:先登录,然后我们在tab_favorite中手动加一条记录,如下图。这样相当于“zhangsan”用户收藏了线路1(zhangsan已经登录)。然后我们进入rid=1的页面,发现已经收藏(结果如下图)。进入rid=其他值的页面,没有收藏。
(此处测试的时候我们需要将Redis打开,因为展示index.html的分页条目的时候,会先去查询Redis数据库,不开启会报错)

接下来,完成页面上“已收藏”多少次的功能,既页面被收藏的总次数的动态展示功能。
在线路表tab_route中有一个“count”属性,表示该线路被收藏的次数。 但是需要注意的是,这里不能直接去查询tab_route线路表的count,因为我们每次添加收藏的时候,会将收藏用户的uid与收藏线路的rid数据存储到tab_favorite表中,并不会更新tan_route表中的数据。 在前一个功能——“旅游线路详情展示”的功能中,我们在service层的RouteServiceImpl的findOne方法中,我们根据route_detail.html页面的rid去查询tab_route表、tab_seller表、tab_route_img表,并将查询的数据封装到一个Route对象中,此处我们应该根据rid去查询tab_favorite表中的rid对应的uid的数据,并将这个数目封装到查询出来的Route对象的count属性中。然后这个对象返回到route_detail.html页面,我们在这个页面取出Route对象count的值,放到页面“已收藏”的位置。 我们在前一个功能步骤
//-------后台 在service层的RouteServiceImpl中调用DAO层中FavoriteDaoImpl的findCountByRid方法(此处我们要查询的是tab_favorite表,因此我们必须将findCountByRid方法设置在Favorite类对应的DAO:FavoriteDaoImpl中 ),findCountByRid方法根据rid查询tab_favorite表中rid对应的uid的数量count并返回,将count设置到Route方法中。 //------前台 在route_detail.html页面,会通过异步请求获取一个封装了count的Route对象的JSON,我们将数据取出来放在“已收藏”处。测试:访问rid=1的页面:http://localhost/travel/route_detail.html?rid=1 ,这个页面各个被用户“zhangsan”收藏过一次,显示“已收藏”1次,如下图

最后,完成点击按钮可以收藏的功能。分析:
我们最终的目的是根据当前登录用户uid与线路的rid,添加一条数据到tab_favorite表中。 -----后台代码 web层:在RouteServlet中,添加一个addFavorite方法,判断用户是否登录,没有登录则结束方法,登录则获取用户的uid与线路的rid调用FavoriteService的add(rid,uid)方法添加数据。 service层:add(rid,uid)方法直接调用FavoriteDao的add(rid,uid)添加数据; dao层:add(rid,uid)方法直接操作tab_favorite表添加数据。 (其实应该从dao层开始编写) ------前台代码编写 在route_detail.html页面,给“收藏”按钮添加一个点击动作,点击执行addFavorite()方法。 在这个方法中,要判断用户是否登录。我们没办法像在JSP页面上,直接在html页面获取Session中有没有user。我们明确判断是否登录是要获取Session中的user,判断这个user是否为空。 我们之前为了在header.html实现“欢迎登录XXX”功能的时候已经在UserServlet中有一个findOne方法,这个方法用来获取已经登录的用户的对象User。我们可以在route_detail.html页面通过异步请求访问这个UserServlet的findOne()方法,根据查询回来的数据就可以知道Session有没有User对象,继而判断有没有登录。
我们还需处理几个小细节。
1)点击“收藏”后,由于我们是发送的异步请求,页面不会刷新,红色的收藏按钮不会变成灰色。通过点击后刷新页面(体验不好)的方法,或者发送异步请求的方式进行操作。
此时页面不刷新,总的收藏次数也无法更新,此时的异步请求发送到RouteServlet的findOne方法,查询回来一个Route对象(包含已经更新的当前页面被收藏的总次数,其实就是我们当前的用户多加了一次),根据Route对象中封装的线路被收藏的次数count,更新收藏数目。其次,此时我们明确当前页面被当前用户收藏,更新按钮的样式。2)我们点击“收藏”按钮之后,虽然变成灰色,但是还可以被点击,此时虽然无法向数据库tab_favorite添加同样的数据,但是也不太好。那么我们在按钮被置灰色后,将按钮的点击事件删除,这样就无法点击。
这里有个问题,如果我们将按钮的onclick动作删除,那么下一个登录的用户进来的时候,由于这个按钮的点击动作被删除,会不会不能点击?
其实是不会的,因为我们一个用户进入到此旅游线路的页面,页面会重新刷新加载这个按钮,当这个用户点击收藏后,会刷新页面,同时发Ajax请求到服务器去查询这个用户有没有收藏,如果收藏了,改变这个按钮的样式,并将这个点击的按钮删除。以后就算页面重新加载,这个按钮又重新被加载进来,但是同样还是会发生Ajax请求去查看当前用户有没有收藏,如果收藏了,改变这个按钮的样式,并将这个点击的按钮删除。
也就是说,只要是同一个登录的是用户,就会查询出这个用户已经收藏了这个线路,就会修改这个按钮的样式,并将这个重新加载的按钮的点击功能删除。如果登录的是不同用户,查询出来没有收藏,就不会删除这个按钮的点击功能,直到这个用户收藏,刷新页面就会改变这个按钮的样式并删除按钮的点击功能- 关于web层、service层与dao层的说明:
1)我们的页面要操作哪一的模块(如User、Route模块等),我们就要将相应的方法设置到这个模块对应的Servlet中。比如我们要在route_detail.html要查询当前页面是否被用户收藏,操作的是线路Route,因此我们应该将相应的方法isFavorite设置到RouteServlet中;
2)在Servlet中,我们的方法要返回/操作的是哪一类的数据,就去调用这类数据对应的service方法。如我们在RouteServlet中,findOne方法是要根据rid查询一条线路返回一个Route对象,我们就应该去查询RouteSerivceImpl的findOne(rid)方法;而在RouteServlet中,isFavorite方法是要根据rid与uid查询tab_favorite表有没有对应的数据,就应该调用FavoriteServiceImpl的isFavorite(rid,uid)方法;
3)**我们在service层中调用的方法操作哪一个表的数据,就将这个方法设置在这个表对应的DAO中。**比如在RouteServiceImpl的findOne方法中,要查询tab_route表返回一个Route对象,就调用RouteDao的方法;要查询tab_route_img表返回一个List集合,就调用RouteImgDao的方法;要查询tab_seller表返回一个Seller 对象,就调用SellerDao的方法;
4)要根据具体情形设置合适的web、service、dao层的方法!
- 项目中的实体类(domain)都必须继承Serializable(PageBean则不需要),原因:添加链接描述
- 关于web层、service层与dao层的说明: