黑马就业班(02.JavaWeb+项目实战\16.JavaWeb综合项目实战手把手学习)旅游网站项目(发现案例有问题!已修正)+浏览器页面代码调试(视频45-16.00)

  • 本文对应项目:目录: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方法都是受保护的protectedgetMethod()方法只能获取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&currentPage=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则不需要),原因:添加链接描述

版权声明:本文为LKJgdut原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。