20180702JavaWeb基础知识


layout: post title: "JavaWeb基础知识" date: 2018-07-02 10:36 toc: true comments: true categories: 技术学习 tags: - web


Web相关概念
  1. 软件架构
    1. C/S:客户端/服务器端
    2. B/S:浏览器/服务器端
2. 资源分类
   1. 静态资源:所有用户访问后,得到的结果都是一样的,称为静态资源.静态资源可以直接被浏览器解析
      * 如: html,css,JavaScript
   2. 动态资源:每个用户访问相同资源后,得到的结果可能不一样。称为动态资源。动态资源被访问后,需要先转换为静态资源,在返回给浏览器
      * 如:servlet/jsp,php,asp....

3. 网络通信三要素
   1. IP:电子设备(计算机)在网络中的唯一标识。
   2. 端口:应用程序在计算机中的唯一标识。 0~65536
   3. 传输协议:规定了数据传输的规则
      1. TCP:安全协议,三次握手。 速度稍慢
      2. UDP:不安全协议。 速度快
web服务器软件
* 服务器:安装了服务器软件的计算机
* 服务器软件:接收用户的请求,处理请求,做出响应
* web服务器软件:web容器,接收用户的请求,处理请求,做出响应。
  * 在web服务器软件中,可以部署web项目,让用户通过浏览器来访问这些项目

* 常见的java相关的web服务器软件:
    webLogic:oracle公司,大型的JavaEE服务器,支持所有的JavaEE规范,收费的。
    webSphere:IBM公司,大型的JavaEE服务器,支持所有的JavaEE规范,收费的。
    JBOSS:JBOSS公司的,大型的JavaEE服务器,支持所有的JavaEE规范,收费的。
    Tomcat:Apache基金组织,中小型的JavaEE服务器,仅仅支持少量的JavaEE规范servlet/jsp。开源的,免费的。
Tomcat:web服务器软件
启动报错:
    1. 暴力:找到占用的端口号,并且找到对应的进程,杀死该进程
        * netstat -ano
    2. 温柔:修改自身的端口号
        * conf/server.xml
        * <Connector port="8888" protocol="HTTP/1.1"
        connectionTimeout="20000"
        redirectPort="8445" />
        * 一般会将tomcat的默认端口号修改为80。80端口号是http协议的默认端口号。好处:在访问时,就不用输入端口号
    * 部署项目的方式:
        1. 直接将项目放到webapps目录下即可。
            * /hello:项目的访问路径-->虚拟目录
            * 简化部署:将项目打成一个war包,再将war包放置到webapps目录下。
                * war包会自动解压缩

        2. 配置conf/server.xml文件
            在<Host>标签体中配置
            <Context docBase="D:\hello" path="/hehe" />
            * docBase:项目存放的路径
            * path:虚拟目录

        3. 在conf\Catalina\localhost创建任意名称的xml文件。在文件中编写
            <Context docBase="D:\hello" />
            * 虚拟目录:xml文件的名称

    * 静态项目和动态项目:
        * 目录结构
            * java动态项目的目录结构:
                -- 项目的根目录
                    -- WEB-INF目录:
                        -- web.xml:web项目的核心配置文件
                        -- classes目录:放置字节码文件的目录
                        -- lib目录:放置依赖的jar包
IDEA与tomcat的相关配置
1. IDEA会为每一个tomcat部署的项目单独建立一份配置文件
    * 查看控制台的log:Using CATALINA_BASE:       "C:\Users\fqy\.IntelliJIdea2018.1\system\tomcat\_itcast"

2. 工作空间项目    和     tomcat部署的web项目
    * tomcat真正访问的是“tomcat部署的web项目”,"tomcat部署的web项目"对应着"工作空间项目" 的web目录下的所有资源
    * WEB-INF目录下的资源不能被浏览器直接访问。
3. 断点调试:使用"小虫子"启动 dubug 启动
设置浏览器不cache

缓存分3部分,一个是页面内容,一个是css样式,一个是JS文件

方法一:在页面<head>标签里增加如下meta标签。

<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=8">
<meta http-equiv="Expires" content="0">
<meta http-equiv="Pragma" content="no-cache">
<meta http-equiv="Cache-control" content="no-cache">
<meta http-equiv="Cache" content="no-cache">

方法二:给请求的路径中加一个随机数。

function OpenURL(){ var url="index.jsp"+"?a="+Math.random(); window.open(url);

方法三:在java代码中增加如下代码。

如果是简单的功能测试用的可以直接在serevlet上写,如果是一个完整的项目,可以考虑在过滤器上加

response.setHeader("Cache-Control","no-cache"); 
response.setHeader("Pragma","no-cache"); 
response.setDateHeader("Expires",0);
Servlet: server applet
概念:运行在服务器端的小程序
* Servlet就是一个接口,定义了Java类被浏览器访问到(tomcat识别)的规则。
* 将来我们自定义一个类,实现Servlet接口,复写方法。
Servlet执行原理

Servlet执行原理

Servlet中的生命周期方法

多个客户端并发访问同一个Servlet时,web服务器会为每一个客户端的访问请求创建一个线程,并在这个线程上调用service方法,因此,service方法内如果访问了同一个资源的话,就有可能引发线程安全问题。

image-20200224164330946

    1. 被创建:执行init方法,只执行一次
        * Servlet什么时候被创建?
            * 默认情况下,第一次被访问时,Servlet被创建
            * 可以配置执行Servlet的创建时机。
                * 在<servlet>标签下配置
                    1. 第一次被访问时,创建
                        * <load-on-startup>的值为负数
                    2. 在服务器启动时,创建
                        * <load-on-startup>的值为0或正整数
        * Servlet的init方法,只执行一次,说明一个Servlet在内存中只存在一个对象,Servlet是单例的
            * 多个用户同时访问时,可能存在线程安全问题。
            * 解决:尽量不要在Servlet中定义成员变量。即使定义了成员变量,也不要对修改值
    2. 提供服务:执行service方法,执行多次
        * 每次访问Servlet时,Service方法都会被调用一次。
    3. 被销毁:执行destroy方法,只执行一次
        * Servlet被销毁时执行。服务器关闭时,Servlet被销毁
        * 只有服务器正常关闭时,才会执行destroy方法。
        * destroy方法在Servlet被销毁之前执行,一般用于释放资源    

Servlet3.0:支持注解配置。可以不需要web.xml了
    * 在类上使用@WebServlet注解,进行配置;@WebServlet("资源路径")
Servlet的体系结构
Servlet -- 接口
    |
GenericServlet -- 抽象类
    |
HttpServlet  -- 抽象类

* GenericServlet:将Servlet接口中其他的方法做了默认空实现,只将service()方法作为抽象
  * 将来定义Servlet类时,可以继承GenericServlet,实现service()方法即可

* HttpServlet:对http协议的一种封装,简化操作
  1. 定义类继承HttpServlet
  2. 复写doGet/doPost方法

HttpServlet

Servlet相关配置
urlpartten:Servlet访问路径
1. 一个Servlet可以定义多个访问路径 : @WebServlet({"/d4","/dd4","/ddd4"})
2. 路径定义规则:
   1. /xxx:路径匹配
   2. /xxx/xxx:多层路径,目录结构
   3. *.do:扩展名匹配
HTTP
* 概念:Hyper Text Transfer Protocol 超文本传输协议
    * 传输协议:定义了,客户端和服务器端通信时,发送数据的格式
    * 特点:
        1. 基于TCP/IP的高级协议
        2. 默认端口号:80
        3. 基于请求/响应模型的:一次请求对应一次响应
        4. 无状态的:每次请求之间相互独立,不能交互数据

    * 历史版本:
        * 1.0:每一次请求响应都会建立新的连接
        * 1.1:复用连接

request&response对象原理

请求消息:客户端发送给服务器端的数据
    1. 请求行
        请求方式 请求url 请求协议/版本
        GET /login.html    HTTP/1.1

        * 请求方式:
            * HTTP协议有7中请求方式,常用的有2种
                * GET:
                    1. 请求参数在请求行中,在url后。
                    2. 请求的url长度有限制的
                    3. 不太安全
                * POST:
                    1. 请求参数在请求体中
                    2. 请求的url长度没有限制的
                    3. 相对安全
    2. 请求头:客户端浏览器告诉服务器一些信息
        请求头名称: 请求头值
        * 常见的请求头:
            1. User-Agent:浏览器告诉服务器,我访问你使用的浏览器版本信息
                * 可以在服务器端获取该头的信息,解决浏览器的兼容性问题

            2. Referer:http://localhost/login.html
                * 告诉服务器,我(当前请求)从哪里来?
                    * 作用:
                        1. 防盗链:
                        2. 统计工作:
    3. 请求空行
        空行,就是用于分割POST请求的请求头,和请求体的。
    4. 请求体(正文):
        * 封装POST请求消息的请求参数的

    * 字符串格式:
        POST /login.html    HTTP/1.1
        Host: localhost
        User-Agent: Mozilla/5.0 (Windows NT 6.1; Win64; x64; rv:60.0) Gecko/20100101 Firefox/60.0
        Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
        Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2
        Accept-Encoding: gzip, deflate
        Referer: http://localhost/login.html
        Connection: keep-alive
        Upgrade-Insecure-Requests: 1

        username=zhangsan    

get:请求的数据会附在URL之后(就是把数据放置在HTTP协议头中),以?分割URL和传输数据,多个参数用&连接;例如:login.action?name=hyddd&password=idontknow&verify=%E4%BD%A0 %E5%A5%BD。

如果数据是英文字母/数字,原样发送,如果是空格,转换为+,如果是中文/其他字符,则直接把字符串用BASE64加密,得出如: %E4%BD%A0%E5%A5%BD,其中%XX中的XX为该符号以16进制表示的ASCII。

响应消息:服务器端发送给客户端的数据
数据格式:
        1. 响应行
            1. 组成:协议/版本 响应状态码 状态码描述
            2. 响应状态码:服务器告诉客户端浏览器本次请求和响应的一个状态。
                1. 状态码都是3位数字 
                2. 分类:
                    1. 1xx:服务器就收客户端消息,但没有接受完成,等待一段时间后,发送1xx多状态码
                    2. 2xx:成功。代表:200
                    3. 3xx:重定向。代表:302(重定向),304(访问缓存)
                    4. 4xx:客户端错误。
                        * 代表:
                            * 404(请求路径没有对应的资源) 
                            * 405:请求方式没有对应的doXxx方法
                    5. 5xx:服务器端错误。代表:500(服务器内部出现异常)
        2. 响应头:
            1. 格式:头名称: 值
            2. 常见的响应头:
                1. Content-Type:服务器告诉客户端本次响应体数据格式以及编码格式
                2. Content-disposition:服务器告诉客户端以什么格式打开响应体数据
                    * 值:
                        * in-line:默认值,在当前页面内打开
                        * attachment;filename=xxx:以附件形式打开响应体。文件下载
        3. 响应空行
        4. 响应体:传输的数据

        * 响应字符串格式
        HTTP/1.1 200 OK
        Content-Type: text/html;charset=UTF-8
        Content-Length: 101
        Date: Wed, 06 Jun 2018 07:08:42 GMT

        <html>
          <head>
            <title>$Title$</title>
          </head>
          <body>
          hello , response
          </body>
        </html>                                            
Request对象
1. request对象和response对象的原理
    1. request和response对象是由服务器创建的。我们来使用它们
    2. request对象是来获取请求消息,response对象是来设置响应消息

2. request对象继承体系结构:    
    ServletRequest        --    接口
        |    继承
    HttpServletRequest    -- 接口
        |    实现
    org.apache.catalina.connector.RequestFacade 类(tomcat)

request&response对象原理

request功能
1. 获取请求消息数据
        1. 获取请求行数据
            * GET /day14/demo1?name=zhangsan HTTP/1.1
            * 方法:
                1. 获取请求方式 :GET
                    * String getMethod()  
                2. (*)获取虚拟目录:/day14
                    * String getContextPath()
                3. 获取Servlet路径: /demo1
                    * String getServletPath()
                4. 获取get方式请求参数:name=zhangsan
                    * String getQueryString()
                5. (*)获取请求URI:/day14/demo1
                    * String getRequestURI():        /day14/demo1
                    * StringBuffer getRequestURL()  :http://localhost/day14/demo1
                    * URL:统一资源定位符 : http://localhost/day14/demo1    中华人民共和国
                    * URI:统一资源标识符 : /day14/demo1                共和国

                6. 获取协议及版本:HTTP/1.1
                    * String getProtocol()

                7. 获取客户机的IP地址:
                    * String getRemoteAddr()

        2. 获取请求头数据
            * 方法:
                * (*)String getHeader(String name):通过请求头的名称获取请求头的值
                * Enumeration<String> getHeaderNames():获取所有的请求头名称

        3. 获取请求体数据:
            * 请求体:只有POST请求方式,才有请求体,在请求体中封装了POST请求的请求参数
            * 步骤:
                1. 获取流对象
                    *  BufferedReader getReader():获取字符输入流,只能操作字符数据
                    *  ServletInputStream getInputStream():获取字节输入流,可以操作所有类型数据
                        * 在文件上传知识点后讲解

                2. 再从流对象中拿数据

    2. 其他功能:(重点)
        1. 获取请求参数通用方式:不论get还是post请求方式都可以使用下列方法来获取请求参数
            1. String getParameter(String name):根据参数名称获取参数值                                           username=zs&password=123
            2. String[] getParameterValues(String name):根据参数名称获取参数值的数组                           hobby=xx&hobby=game
            3. Enumeration<String> getParameterNames():获取所有请求的参数名称
            4. Map<String,String[]> getParameterMap():获取所有参数的map集合

            * 中文乱码问题:
                * get方式:tomcat 8 已经将get方式乱码问题解决了
                * post方式:会乱码
                    在获取参数前,设置request的编码request.setCharacterEncoding("utf-8");

        2. 请求转发:一种在服务器内部的资源跳转方式
            1. 步骤:
                1. 通过request对象获取请求转发器对象
                    RequestDispatcher getRequestDispatcher(String path)
                2. 使用RequestDispatcher对象来进行转发
                    forward(ServletRequest request, ServletResponse response) 

            2. 特点:
                1. 浏览器地址栏路径不发生变化
                2. 只能转发到当前服务器内部资源中。
                3. 转发是一次请求
        3. 共享数据:
            * 域对象:一个有作用范围的对象,可以在范围内共享数据
            * request域:代表一次请求的范围,一般用于请求转发的多个资源中共享数据
            * 方法:
                1. void setAttribute(String name,Object obj):存储数据
                2. Object getAttitude(String name):通过键获取值
                3. void removeAttribute(String name):通过键移除键值对

        4. 获取ServletContext:
            * ServletContext getServletContext()
Request请求转发和域对象

Request请求转发&域对象

登录案例分析

登录案例分析

Response对象
功能:设置响应消息
    1. 设置响应行
        1. 格式:HTTP/1.1 200 ok
        2. 设置状态码:setStatus(int sc) 
    2. 设置响应头:setHeader(String name, String value) 

    3. 设置响应体:
        * 使用步骤:
            1. 获取输出流
                * 字符输出流:PrintWriter getWriter()
                * 字节输出流:ServletOutputStream getOutputStream()

            2. 使用输出流,将数据输出到客户端浏览器
response乱码

image-20200224154255379

response中文乱码
1.如果中文返回出现??字符,这表明没有加HttpServletResponse.setCharacterEncoding("UTF-8");这句话。可以加上来设置服务器端的编码
2.如果返回的中文是“烇湫”这种乱码,说明浏览器的解析问题,应该检查下是否忘加response.setHeader("Content-type", "text/html;charset=UTF-8");这句话来通知浏览器服务器发送的数据格式
3.如果还是有乱码,就指定html文件里内容的编码方式<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />

request中文乱码
地址栏直接输入URL访问浏览器默认将参数按照utf-8进行编码;点击页面中的超链接访问和提交表单访问浏览器将参数按照当前页面的显示编码进行编码。
解决方法如下:
1. 不管是get请求还是post请求,将中文参数进行url编码,用URLEncoder.encode()进行编码传到后台
2. 设置pageEncoding=“utf-8”
3. 后台接收编码也用 utf-8
4. web.xml设置编码
重定向

重定向

* 重定向:资源跳转的方式
* 代码实现:
//1. 设置状态码为302
    response.setStatus(302);
    //2.设置响应头location
    response.setHeader("location","/day15/responseDemo2");
    //简单的重定向方法
    response.sendRedirect("/day15/responseDemo2");
forward 和 redirect 区别
* 重定向的特点:redirect
    1. 地址栏发生变化
    2. 重定向可以访问其他站点(服务器)的资源
    3. 重定向是两次请求。不能使用request对象来共享数据
* 转发的特点:forward
    1. 转发地址栏路径不变
    2. 转发只能访问当前服务器下的资源
    3. 转发是一次请求,可以使用request对象来共享数据
web中的路径问题
1. 相对路径:通过相对路径不可以确定唯一资源
    * 如:./index.html
    * 不以/开头,以.开头路径

    * 规则:找到当前资源和目标资源之间的相对位置关系
        * ./:当前目录
        * ../:后退一级目录
2. 绝对路径:通过绝对路径可以确定唯一资源
    * 如:http://localhost/day15/responseDemo2        /day15/responseDemo2
    * 以/开头的路径

    * 规则:判断定义的路径是给谁用的?判断请求将来从哪儿发出  
    * 给客户端浏览器使用:需要加虚拟目录(项目的访问路径)
        * 建议虚拟目录动态获取:request.getContextPath()
        * <a> , <form> 重定向...
    * 给服务器使用:不需要加虚拟目录
        * 转发路径
ServletConfig对象

ServletConfig主要用于加载Servlet的初始化参数。在一个web应用可以存在多个ServletConfig对象。ServletConfig主要用于获得字符集编码,获取数据库连接信息,获得配置文件等操作。

String getServletName()  -- 获取当前Servlet在web.xml中配置的名字
String getInitParameter(String name) -- 获取当前Servlet指定名称的初始化参数的值
Enumeration getInitParameterNames()  -- 获取当前Servlet初始化参数名字组成枚举
ServletContext getServletContext()  -- 获取代表当前web应用的ServletContext对象
ServletContext对象

概念:代表整个web应用,可以和程序的容器(服务器)来通信

ServletContext用来存放全局变量,每个Java虚拟机每个Web项目只有一个ServletContext,这个ServletContext是由Web服务器创建的,来保证它的唯一性。由于一个WEB应用中的所有Servlet共享同一个ServletContext对象,因此Servlet对象之间可以通过ServletContext对象通讯。

2. 获取:
    1. 通过request对象获取
        request.getServletContext();
    2. 通过HttpServlet获取
        this.getServletContext();
3. 功能:
    1. 获取MIME类型:
        * MIME类型:在互联网通信过程中定义的一种文件数据类型
            * 格式: 大类型/小类型   text/html        image/jpeg

        * 获取:String getMimeType(String file)  
    2. 域对象:共享数据
        1. setAttribute(String name,Object value)
        2. getAttribute(String name)
        3. removeAttribute(String name)

        * ServletContext对象范围:所有用户所有请求的数据
    3. 获取文件的真实(服务器)路径
        1. 方法:String getRealPath(String path)  
            String b = context.getRealPath("/b.txt");//web目录下资源访问    
            String c = context.getRealPath("/WEB-INF/c.txt");//WEB-INF目录下的资源访问    
            String a = context.getRealPath("/WEB-INF/classes/a.txt");//src目录下的资源访问
案例:文件下载
* 步骤:
    1. 定义页面,编辑超链接href属性,指向Servlet,传递资源名称filename
    2. 定义Servlet
        1. 获取文件名称
        2. 使用字节输入流加载文件进内存
        3. 指定response的响应头: content-disposition:attachment;filename=xxx
        4. 将数据写出到response输出流
* 中文文件名问题解决思路:
    1. 获取客户端使用的浏览器版本信息
    2. 根据不同的版本信息,设置filename的编码方式不同
<a href="/day15/downloadServlet?filename=九尾.jpg">图片1</a>
@WebServlet("/downloadServlet")
public class DownloadServlet extends HttpServlet {
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        //1.获取请求参数,文件名称
        String filename = request.getParameter("filename");
        //2.使用字节输入流加载文件进内存
        //2.1找到文件服务器路径
        ServletContext servletContext = this.getServletContext();
        String realPath = servletContext.getRealPath("/img/" + filename);
        //2.2用字节流关联
        FileInputStream fis = new FileInputStream(realPath);

        //3.设置response的响应头
        //3.1设置响应头类型:content-type
        String mimeType = servletContext.getMimeType(filename);//获取文件的mime类型
        response.setHeader("content-type",mimeType);
        //3.2设置响应头打开方式:content-disposition

        //解决中文文件名问题
        //1.获取user-agent请求头、
        String agent = request.getHeader("user-agent");
        //2.使用工具类方法编码文件名即可
        filename = DownLoadUtils.getFileName(agent, filename);

        response.setHeader("content-disposition","attachment;filename="+filename);
        //4.将输入流的数据写出到输出流中
        ServletOutputStream sos = response.getOutputStream();
        byte[] buff = new byte[1024 * 8];
        int len = 0;
        while((len = fis.read(buff)) != -1){
            sos.write(buff,0,len);
        }
        fis.close();
    }

    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        this.doPost(request,response);
    }
}
Cookie:客户端会话技术
会话技术
1. 会话:一次会话中包含多次请求和响应。
    * 一次会话:浏览器第一次给服务器资源发送请求,会话建立,直到有一方断开为止
2. 功能:在一次会话的范围内的多次请求间,共享数据
3. 方式:
    1. 客户端会话技术:Cookie
    2. 服务器端会话技术:Session
1. 概念:客户端会话技术,将数据保存到客户端

2. 快速入门:
    * 使用步骤:
        1. 创建Cookie对象,绑定数据
            * new Cookie(String name, String value) 
        2. 发送Cookie对象
            * response.addCookie(Cookie cookie) 
        3. 获取Cookie,拿到数据
            * Cookie[]  request.getCookies()  
Cookie原理

基于响应头set-cookie和请求头cookie实现

Cookie原理

cookie的细节
4. cookie的细节
    1. 一次可不可以发送多个cookie?
        * 可以创建多个Cookie对象,使用response调用多次addCookie方法发送cookie即可。
    2. cookie在浏览器中保存多长时间?
        1. 默认情况下,当浏览器关闭后,Cookie数据被销毁
        2. 持久化存储:
            * setMaxAge(int seconds)
                1. 正数:将Cookie数据写到硬盘的文件中。持久化存储。并指定cookie存活时间,时间到后,cookie文件自动失效
                2. 负数:默认值
                3. 零:删除cookie信息
    3. cookie能不能存中文?
        * 在tomcat 8 之前 cookie中不能直接存储中文数据。
            * 需要将中文数据转码---一般采用URL编码(%E3)
        * 在tomcat 8 之后,cookie支持中文数据。特殊字符还是不支持,建议使用URL编码存储,URL解码解析
    4. cookie共享问题?
        1. 假设在一个tomcat服务器中,部署了多个web项目,那么在这些web项目中cookie能不能共享?
            * 默认情况下cookie不能共享

            * setPath(String path):设置cookie的获取范围。默认情况下,设置当前的虚拟目录
                * 如果要共享,则可以将path设置为"/"
        2. 不同的tomcat服务器间cookie共享问题?
            * setDomain(String path):如果设置一级域名相同,那么多个服务器之间cookie可以共享
            * setDomain(".baidu.com"),那么tieba.baidu.com和news.baidu.com中cookie可以共享

5. Cookie的特点和作用
    1. cookie存储数据在客户端浏览器
    2. 浏览器对于单个cookie 的大小有限制(4kb) 以及 对同一个域名下的总cookie数量也有限制(20个)

    * 作用:
        1. cookie一般用于存出少量的不太敏感的数据
        2. 在不登录的情况下,完成服务器对客户端的身份识别
Cookie案例:记住上次访问时间
1. 访问一个Servlet,如果是第一次访问,则提示:您好,欢迎您首次访问。
2. 如果不是第一次访问,则提示:欢迎回来,您上次访问时间为:显示时间字符串

Cookie案例_记住上次访问时间

Session:服务器端会话技术
1. 概念:服务器端会话技术,在一次会话的多次请求间共享数据,将数据保存在服务器端的对象中。
2. 快速入门:
    1. 获取HttpSession对象:
        HttpSession session = request.getSession();
    2. 使用HttpSession对象:
        Object getAttribute(String name)  
        void setAttribute(String name, Object value)
        void removeAttribute(String name)  
Session原理

Session原理

Session细节
4. 细节:
    1. 当客户端关闭后,服务器不关闭,两次获取session是否为同一个?
        * 默认情况下。不是。
        * 如果需要相同,则可以创建Cookie,键为JSESSIONID,设置最大存活时间,让cookie持久化保存。
             Cookie c = new Cookie("JSESSIONID",session.getId());
             c.setMaxAge(60*60);
             response.addCookie(c);

    2. 客户端不关闭,服务器关闭后,两次获取的session是同一个吗?
        * 不是同一个,但是要确保数据不丢失。tomcat自动完成以下工作
            * session的钝化:
                * 在服务器正常关闭之前,将session对象系列化到硬盘上
            * session的活化:
                * 在服务器启动后,将session文件转化为内存中的session对象即可。

    3. session什么时候被销毁?
        1. 服务器关闭
        2. session对象调用invalidate() 。
        3. session默认失效时间 30分钟
            选择性配置修改    
            <session-config>
                <session-timeout>30</session-timeout>
            </session-config>

 5. session的特点
     1. session用于存储一次会话的多次请求的数据,存在服务器端
     2. session可以存储任意类型,任意大小的数据
     3. 如果客户端禁止Cookies了,服务器会将ID写到URL中。
session与Cookie的区别:
  1. session存储数据在服务器端,Cookie在客户端
  2. session没有数据大小限制,Cookie有
  3. session数据安全,Cookie相对于不安全
案例:验证码登录

登录案例

JSP
JSP原理:本质Servlet

JSP原理

1. 概念:
    * Java Server Pages: java服务器端页面
        * 可以理解为:一个特殊的页面,其中既可以指定定义html标签,又可以定义java代码
        * 用于简化书写!!!

3. JSP的脚本:JSP定义Java代码的方式
    1. <%  代码 %>:定义的java代码,在service方法中。service方法中定义什么,脚本中就可以定义什么。
    2. <%! 代码 %>:定义的java代码,在jsp转换后的java类的成员位置。
    3. <%= 代码 %>:定义的java代码,会输出到页面上。输出语句中可以定义什么,该脚本中就可以定义什么。

1. 指令
    * 作用:用于配置JSP页面,导入资源文件
    * 格式:
        <%@ 指令名称 属性名1=属性值1 属性名2=属性值2 ... %>

    * 分类:
        1. page        : 配置JSP页面的
            * contentType:等同于response.setContentType()
                1. 设置响应体的mime类型以及字符集
                2. 设置当前jsp页面的编码(高级的IDE生效,低级工具需要设置pageEncoding属性)
            * import:导包
            * errorPage:当前页面发生异常后,会自动跳转到指定的错误页面
            * isErrorPage:标识当前也是是否是错误页面。
                * true:是,可以使用内置对象exception
                * false:否。默认值。不可以使用内置对象exception

        2. include    : 页面包含的。导入页面的资源文件
            * <%@include file="top.jsp"%>
        3. taglib    : 导入资源
            * <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
                * prefix:前缀,自定义的
2. 注释:
    1. html注释:
        <!-- -->:只能注释html代码片段
    2. jsp注释:推荐使用
        <%-- --%>:可以注释所有    


4. JSP的内置对象:
    * 在jsp页面中不需要获取和创建,可以直接使用的对象
    * jsp一共有9个内置对象。
    变量名                    真实类型                        作用
* pageContext                PageContext                    当前页面共享数据,还可以获取其他八个内置
* request                    HttpServletRequest            一次请求访问的多个资源(转发)
* session                    HttpSession                    一次会话的多个请求间
* application                ServletContext                所有用户间共享数据
* response                    HttpServletResponse            响应对象
* page                        Object                        当前页面(Servlet)的对象  this
* out                        JspWriter                    输出对象,数据输出到页面上
* config                    ServletConfig                Servlet的配置对象
* exception                    Throwable                    异常对象

5. JSP的动作元素           
    jsp:include 包含静态和动态的文件
    jsp:useBean 创建一个Bean实例
    jsp:setProperty 设置已经实例化的Bean对象的属性
    jsp:getProperty输出属性值
    jsp:forward转发请求

6.* out:字符输出流对象。可以将数据输出到页面上。和response.getWriter()类似
    * response.getWriter()和out.write()的区别:
        * 在tomcat服务器真正给客户端做出响应之前,会先找response缓冲区数据,再找out缓冲区数据。
        * response.getWriter()数据输出永远在out.write()之前
    **out.wirte()与out.print()**

    * out.wirte()与out.print()区别
        * out.write()方法只能输出字符/字符数组/字符串等等字符相关类型的数据
        * out.print()方法将所有数据都转换为了字符串的形式输出.
        * 如果值为null,out.print()会打印出null;out.write()会直接报出空指针异常
MVC

MVC开发模式

EL表达式
1. 概念:Expression Language 表达式语言
2. 作用:替换和简化jsp页面中java代码的编写
3. 语法:${表达式}
4. 注意:
    * jsp默认支持el表达式的。如果要忽略el表达式
        1. 设置jsp中page指令中:isELIgnored="true" 忽略当前jsp页面中所有的el表达式
        2. \${表达式} :忽略当前这个el表达式

5. 使用:
    1. 运算:
        * 运算符:
            1. 算数运算符: + - * /(div) %(mod)
            2. 比较运算符: > < >= <= == !=
            3. 逻辑运算符: &&(and) ||(or) !(not)
            4. 空运算符: empty
                * 功能:用于判断字符串、集合、数组对象是否为null或者长度是否为0
                * ${empty list}:判断字符串、集合、数组对象是否为null或者长度为0
                * ${not empty str}:表示判断字符串、集合、数组对象是否不为null 并且 长度>0
    2. 获取值
        1. el表达式只能从域对象中获取值
        2. 语法:
            1. ${域名称.键名}:从指定域中获取指定键的值
                * 域名称:
                    1. pageScope        --> pageContext
                    2. requestScope     --> request
                    3. sessionScope     --> session
                    4. applicationScope --> application(ServletContext)
                * 举例:在request域中存储了name=张三
                * 获取:${requestScope.name}

            2. ${键名}:表示依次从最小的域中查找是否有该键对应的值,直到找到为止。
            3. 获取对象、List集合、Map集合的值
                1. 对象:${域名称.键名.属性名}
                    * 本质上会去调用对象的getter方法

                2. List集合:${域名称.键名[索引]}

                3. Map集合:
                    * ${域名称.键名.key名称}
                    * ${域名称.键名["key名称"]}
    3. 隐式对象:
        * el表达式中有11个隐式对象
        * pageContext:
            * 获取jsp其他八个内置对象
                * ${pageContext.request.contextPath}:动态获取虚拟目录
JSTL
1. 概念:JavaServer Pages Tag Library  JSP标准标签库
    * 是由Apache组织提供的开源的免费的jsp标签        <标签>

2. 作用:用于简化和替换jsp页面上的java代码        

3. 使用步骤:
    1. 导入jstl相关jar包
    2. 引入标签库:taglib指令:  <%@ taglib %>
    3. 使用标签

4. 常用的JSTL标签
    1. if:相当于java代码的if语句
        1. 属性:
            * test 必须属性,接受boolean表达式
                * 如果表达式为true,则显示if标签体内容,如果为false,则不显示标签体内容
                * 一般情况下,test属性值会结合el表达式一起使用
            2. 注意:
                * c:if标签没有else情况,想要else情况,则可以在定义一个c:if标签
    2. choose:相当于java代码的switch语句
        1. 使用choose标签声明                  相当于switch声明
        2. 使用when标签做判断                     相当于case
        3. 使用otherwise标签做其他情况的声明     相当于default

    3. foreach:相当于java代码的for语句
案例:用户CRUD

列表查询分析

三层架构

Filter:过滤器
1. 概念:
    * 生活中的过滤器:净水器,空气净化器,土匪、
    * web中的过滤器:当访问服务器的资源时,过滤器可以将请求拦截下来,完成一些特殊的功能。
    * 过滤器的作用:
        * 一般用于完成通用的操作。如:登录验证、统一编码处理、敏感字符过滤...

2. 快速入门:
    1. 步骤:
        1. 定义一个类,实现接口Filter
        2. 复写方法
        3. 配置拦截路径
            1. web.xml
            2. 注解
    2. 代码:
        @WebFilter("/*")//访问所有资源之前,都会执行该过滤器
        public class FilterDemo1 implements Filter {
            @Override
            public void init(FilterConfig filterConfig) throws ServletException             {}        
            @Override
            public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
                System.out.println("filterDemo1被执行了....");
                //放行
                filterChain.doFilter(servletRequest,servletResponse);

            }        
            @Override
            public void destroy() {

            }
        }
过滤器细节:
3. 过滤器细节:
    1. web.xml配置    
        <filter>
            <filter-name>demo1</filter-name>
            <filter-class>cn.itcast.web.filter.FilterDemo1</filter-class>
        </filter>
        <filter-mapping>
            <filter-name>demo1</filter-name>
            <!-- 拦截路径 -->
            <url-pattern>/*</url-pattern>
        </filter-mapping>
    2. 过滤器执行流程
        1. 执行过滤器
        2. 执行放行后的资源
        3. 回来执行过滤器放行代码下边的代码
    3. 过滤器生命周期方法
        1. init:服务器启动后,会创建Filter对象,然后调用init方法。只执行一次。用于加载资源
        2. doFilter:每一次请求被拦截资源时,会执行。执行多次
        3. destroy:在服务器关闭后,Filter对象被销毁。如果服务器是正常关闭,则会执行destroy方法。只执行一次。用于释放资源
    4. 过滤器配置详解
        * 拦截路径配置:
            1. 具体资源路径: /index.jsp   只有访问index.jsp资源时,过滤器才会被执行
            2. 拦截目录: /user/*    访问/user下的所有资源时,过滤器都会被执行
            3. 后缀名拦截: *.jsp        访问所有后缀名为jsp资源时,过滤器都会被执行
            4. 拦截所有资源:/*        访问所有资源时,过滤器都会被执行
        * 拦截方式配置:资源被访问的方式
            * 注解配置:
                * 设置dispatcherTypes属性
                    1. REQUEST:默认值。浏览器直接请求资源
                    2. FORWARD:转发访问资源
                    3. INCLUDE:包含访问资源
                    4. ERROR:错误跳转资源
                    5. ASYNC:异步访问资源
            * web.xml配置
                * 设置<dispatcher></dispatcher>标签即可

    5. 过滤器链(配置多个过滤器)
        * 执行顺序:如果有两个过滤器:过滤器1和过滤器2
            1. 过滤器1
            2. 过滤器2
            3. 资源执行
            4. 过滤器2
            5. 过滤器1 

        * 过滤器先后顺序问题:
            1. 注解配置:按照类名的字符串比较规则比较,值小的先执行
                * 如: AFilter 和 BFilter,AFilter就先执行了。
            2. web.xml配置: <filter-mapping>谁定义在上边,谁先执行
4. 案例:
    1. 案例1_登录验证
        * 需求:
            1. 访问day17_case案例的资源。验证其是否登录
            2. 如果登录了,则直接放行。
            3. 如果没有登录,则跳转到登录页面,提示"您尚未登录,请先登录"。

    2. 案例2_敏感词汇过滤
        * 需求:
            1. 对day17_case案例录入的数据进行敏感词汇过滤
            2. 敏感词汇参考《敏感词汇.txt》
            3. 如果是敏感词汇,替换为 *** 

        * 分析:
            1. 对request对象进行增强。增强获取参数相关方法
            2. 放行。传递代理对象
        * 增强对象的功能:
            * 设计模式:一些通用的解决固定问题的方式
            1. 装饰模式
            2. 代理模式
                * 概念:
                    1. 真实对象:被代理的对象
                    2. 代理对象:
                    3. 代理模式:代理对象代理真实对象,达到增强真实对象功能的目的
                 * 实现方式:
                     1. 静态代理:有一个类文件描述代理模式
                     2. 动态代理:在内存中形成代理类
                        * 实现步骤:
                            1. 代理对象和真实对象实现相同的接口
                            2. 代理对象 = Proxy.newProxyInstance();
                            3. 使用代理对象调用方法。
                            4. 增强方法

                        * 增强方式:
                            1. 增强参数列表
                            2. 增强返回值类型
                            3. 增强方法体执行逻辑    

2.案例1_登录验证

2.案例1_登录验证3.过滤敏感词汇

3.过滤敏感词汇

Listener:监听器
Listener是Servlet的监听器,它可以监听客户端的请求、服务端的操作等。通过监听器,可以自动激发一些操作,比如监听在线的用户的数量。

* 概念:web的三大组件之一。
    * 事件监听机制
        * 事件    :一件事情
        * 事件源 :事件发生的地方
        * 监听器 :一个对象
        * 注册监听:将事件、事件源、监听器绑定在一起。当事件源上发生某个事件后,执行监听器代码

image-20200225164201073

* ServletContextListener:监听ServletContext对象的创建和销毁
    * 方法:
        * void contextDestroyed(ServletContextEvent sce) 
                ServletContext对象被销毁之前会调用该方法
        * void contextInitialized(ServletContextEvent sce)
                ServletContext对象创建后会调用该方法
    * 步骤:
        1. 定义一个类,实现ServletContextListener接口
        2. 复写方法
        3. 配置
            1. web.xml
            <listener>
                <listener-class>
                cn.itcast.web.listener.ContextLoaderListener
                </listener-class>
            </listener>
                    * 指定初始化参数<context-param>
            2. 注解:
                * @WebListener

综合案例:旅游网站

技术选型
Web层
    a)    Servlet:前端控制器
    b)    html:视图
    c)    Filter:过滤器
    d)    BeanUtils:数据封装
    e)    Jackson:json序列化工具
Service层
    f)    Javamail:java发送邮件工具
    g)    Redis:nosql内存数据库
    h)    Jedis:java的redis客户端
Dao层
    i)    Mysql:数据库
    j)    Druid:数据库连接池
    k)    JdbcTemplate:jdbc的工具
数据库

1

set names utf8;
drop table if exists tab_favorite;
drop table if exists tab_route_img;
drop table if exists tab_route;
drop table if exists tab_category;
drop table if exists tab_seller;
drop table if exists tab_user;

/*==============================================================*/
/* Table: tab_category                                          */
/*==============================================================*/
create table tab_category
(
   cid                  int not null auto_increment,
   cname                varchar(100) not null,
   primary key (cid),
   unique key AK_nq_categoryname (cname)
);

/*==============================================================*/
/* Table: tab_favorite                                          */
/*==============================================================*/
create table tab_favorite
(
   rid                  int not null,
   date                 date not null,
   uid                  int not null,
   primary key (rid, uid)
);

/*==============================================================*/
/* Table: tab_route                                             */
/*==============================================================*/
create table tab_route
(
   rid                  int not null auto_increment,
   rname                varchar(500) not null,
   price                double not null,
   routeIntroduce       varchar(1000),
   rflag                char(1) not null,
   rdate                varchar(19),
   isThemeTour          char(1) not null,
   count                int default 0,
   cid                  int not null,
   rimage               varchar(200),
   sid                  int,
   sourceId             varchar(50),
   primary key (rid),
   unique key AK_nq_sourceId (sourceId)
);

/*==============================================================*/
/* Table: tab_route_img                                         */
/*==============================================================*/
create table tab_route_img
(
   rgid                 int not null auto_increment,
   rid                  int not null,
   bigPic               varchar(200) not null,
   smallPic             varchar(200),
   primary key (rgid)
);

/*==============================================================*/
/* Table: tab_seller                                            */
/*==============================================================*/
create table tab_seller
(
   sid                  int not null auto_increment,
   sname                varchar(200) not null,
   consphone            varchar(20) not null,
   address              varchar(200),
   primary key (sid),
   unique key AK_Key_2 (sname)
);

/*==============================================================*/
/* Table: tab_user                                              */
/*==============================================================*/
create table tab_user
(
   uid                  int not null auto_increment,
   username             varchar(100) not null,
   password             varchar(32) not null,
   name                 varchar(100),
   birthday             date,
   sex                  char(1),
   telephone            varchar(11),
   email                varchar(100),
   status               char(1) ,
   code                    varchar(50),

   primary key (uid),
   unique key AK_nq_username (username),
   unique key AK_nq_code (code)
);

alter table tab_favorite add constraint FK_route_favorite foreign key (rid)
      references tab_route (rid) on delete restrict on update restrict;

alter table tab_favorite add constraint FK_user_favorite foreign key (uid)
      references tab_user (uid) on delete restrict on update restrict;

alter table tab_route add constraint FK_category_route foreign key (cid)
      references tab_category (cid) on delete restrict on update restrict;

alter table tab_route add constraint FK_seller_route foreign key (sid)
      references tab_seller (sid) on delete restrict on update restrict;

alter table tab_route_img add constraint FK_route_routeimg foreign key (rid)
      references tab_route (rid) on delete restrict on update restrict;


insert  into `tab_category`(`cid`,`cname`) values (8,'全球自由行'),(5,'国内游'),(4,'处境游'),(7,'抱团定制'),(6,'港澳游'),(2,'酒店'),(1,'门票'),(3,'香港车票');
注册登录退出

注册功能分析

1.注册功能分析

登录功能分析

2.登录功能

表单校验和发送异步请求

提升用户体验,并减轻服务器压力。使用异步提交表单是为了获取服务器响应的数据。因为我们前台使用的是html作为视图层,不能够直接从servlet相关的域对象获取值,只能通过ajax获取响应数据

<script>
    //校验用户名
    function checkUsername() {
    var username = $("#username").val();
    //2.定义正则
    var reg_username = /^\w{2,20}$/;
    var flag = reg_username.test(username);
    if (flag) {
        //用户名合法
        $("#username").css("border", "");
        $("#usernamemsg").html("");
    } else {
        //用户名非法,加一个红色边框
        $("#username").css("border", "1px solid red");
        $("#usernamemsg").html("单词字符,长度2到20位");
    }
    return flag;
}

//校验邮箱
function checkEmail() {
    //1.获取邮箱
    var email = $("#email").val();
    //2.定义正则        itcast@163.com
    var reg_email = /^\w+@\w+\.\w+$/;

    //3.判断
    var flag = reg_email.test(email);
    if (flag) {
        $("#email").css("border", "");
    } else {
        $("#email").css("border", "1px solid red");
    }
    return flag;
}

//入口函数
$(function () {
    //当表单提交时,调用所有的校验方法
    //如果这个方法没有返回值,或者返回为true,则表单提交,如果返回为false,则表单不提交
    $("#registerForm").submit(function(){
        //1.发送数据到服务器
        if(checkUsername() && checkEmail()){
            //校验通过,发送ajax请求,提交表单的数据   username=zhangsan&password=123
            //$(this)==$("#registerForm") serialize()将form转为key=value形式
            $.post("user/regist",$(this).serialize(),function(data){
                //处理服务器响应的数据  data  {flag:true,errorMsg:"注册失败"}
                if(data.flag){
                    //注册成功,跳转成功页面
                    location.href="register_ok.html";
                }else{
                    //注册失败,给errorMsg添加提示信息
                    $("#errorMsg").html(data.errorMsg);
                }
            });
        }
        //2.不让页面跳转
        return false;
    });

    //当某一个组件失去焦点是,调用对应的校验方法
    $("#username").blur(checkUsername);
    $("#email").blur(checkEmail);
});
</script>
RegistUserServlet

接受前端异步请求,调用service模块,响应结果返回

@WebServlet("/registUserServlet")
public class RegistUserServlet extends HttpServlet {
    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        String check = req.getParameter("check");
        HttpSession session = req.getSession();
        String checkcode_server = (String)session.getAttribute("CHECKCODE_SERVER");
        session.removeAttribute("CHECKCODE_SERVER");//只用一次
        if(!check.equalsIgnoreCase(checkcode_server)){
            ResultInfo resultInfo=new ResultInfo();
            resultInfo.setFlag(false);
            resultInfo.setErrorMsg("验证码错误");
            //结果序列化并返回
            ObjectMapper mapper=new ObjectMapper();
            String json = mapper.writeValueAsString(resultInfo);
            resp.setContentType("application/json;charset=utf-8");
            resp.getWriter().write(json);
            return;
        }

        //获取数据
        Map<String, String[]> parameterMap = req.getParameterMap();

        //封装对象
        User u=new User();
        try {
            BeanUtils.populate(u,parameterMap);
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        }
        System.out.println(u);
        //调用Service
        UserService userService=new UserServiceImpl();
        boolean flag = userService.regist(u);
        //返回结果对象
        ResultInfo resultInfo=new ResultInfo();
        if(flag){
            resultInfo.setFlag(true);
        }else {
            resultInfo.setFlag(false);
            resultInfo.setErrorMsg("注册失败");
        }

        //结果序列化并返回
        ObjectMapper mapper=new ObjectMapper();
        String json = mapper.writeValueAsString(resultInfo);
        resp.setContentType("application/json;charset=utf-8");
        resp.getWriter().write(json);
    }

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        doPost(req,resp);
    }
}
UserServiceImpl
public class UserServiceImpl implements UserService {
    UserDao userDao=new UserDaoImpl();
    @Override
    public boolean regist(User user) {
        User u = userDao.findByUsername(user.getUsername());
        if(u!=null){
            return false;
        }
        //设置激活状态和激活码
        user.setStatus("N");
        user.setCode(UuidUtil.getUuid());
        userDao.save(user);

        //发送激活邮件
        String content="<a href='http://localhost:8080/day10Travel_war_exploded/user/active?code="+user.getCode()+"'>点击激活黑马旅游网</a>";
        MailUtils.sendMail(user.getEmail(),content,"激活邮件");
        return true;
    }

    @Override
    public boolean active(String code) {
        //1.根据激活码查询用户对象
        User user = userDao.findByCode(code);
        if(user != null){
            //2.调用dao的修改激活状态的方法
            userDao.updateStatus(user);
            return true;
        }else{
            return false;
        }
    }

    @Override
    public User login(User user) {
       return  userDao.findUsernameAndPassword(user);
    }
}
UserDaoImpl
public class UserDaoImpl implements UserDao {
    private JdbcTemplate template = new JdbcTemplate(JDBCUtils.getDataSource());

    @Override
    public void save(User user) {
        //1.定义sql
        String sql = "insert into tab_user(username,password,name,birthday,sex,telephone,email,status,code) values(?,?,?,?,?,?,?,?,?)";
        //2.执行sql
        template.update(sql,user.getUsername(),
                user.getPassword(),
                user.getName(),
                user.getBirthday(),
                user.getSex(),
                user.getTelephone(),
                user.getEmail(),
                user.getStatus(),
                user.getCode()
        );
    }

    @Override
    public User findByUsername(String username) {
        String sql = "select * from tab_user where username=?";
        User user=null;
        try{
            user = template.queryForObject(sql, new BeanPropertyRowMapper<User>(User.class), username);
        }catch (Exception e){
        }
        return user;
    }

    @Override
    public User findByCode(String code) {
        String sql = "select * from tab_user where code=?";
        User user=null;
        try{
            user = template.queryForObject(sql, new BeanPropertyRowMapper<User>(User.class), code);
        }catch (Exception e){
        }
        return user;
    }

    @Override
    public void updateStatus(User user) {
        String sql="update tab_user set status= 'Y' where uid =?";
        template.update(sql,user.getUid());
    }

    @Override
    public User findUsernameAndPassword(User user) {
        String sql="select * from tab_user where username=? and password=?";
        User u=null;
        try{
            u = template.queryForObject(sql, new BeanPropertyRowMapper<>(User.class), user.getUsername(), user.getPassword());
        }catch (Exception e){
        }
        return u;
    }
}
User
public class User implements Serializable {
    private int uid;//用户id
    private String username;//用户名,账号
    private String password;//密码
    private String name;//真实姓名
    private String birthday;//出生日期
    private String sex;//男或女
    private String telephone;//手机号
    private String email;//邮箱
    private String status;//激活状态,Y代表激活,N代表未激活
    private String code;//激活码(要求唯一)
}
ResultInfo

封装后端返回前端数据对象

public class ResultInfo implements Serializable {
    private boolean flag;//后端返回结果正常为true,发生异常返回false
    private Object data;//后端返回结果数据对象
    private String errorMsg;//发生异常的错误消息
    }
验证码
@WebServlet("/checkCode")
public class CheckCodeServlet extends HttpServlet {
    public void doGet(HttpServletRequest request, HttpServletResponse response)throws ServletException, IOException {

        //服务器通知浏览器不要缓存
        response.setHeader("pragma","no-cache");
        response.setHeader("cache-control","no-cache");
        response.setHeader("expires","0");

        //在内存中创建一个长80,宽30的图片,默认黑色背景
        //参数一:长
        //参数二:宽
        //参数三:颜色
        int width = 80;
        int height = 30;
        BufferedImage image = new BufferedImage(width,height,BufferedImage.TYPE_INT_RGB);

        //获取画笔
        Graphics g = image.getGraphics();
        //设置画笔颜色为灰色
        g.setColor(Color.GRAY);
        //填充图片
        g.fillRect(0,0, width,height);

        //产生4个随机验证码,12Ey
        String checkCode = getCheckCode();
        //将验证码放入HttpSession中
        request.getSession().setAttribute("CHECKCODE_SERVER",checkCode);

        //设置画笔颜色为黄色
        g.setColor(Color.YELLOW);
        //设置字体的小大
        g.setFont(new Font("黑体",Font.BOLD,24));
        //向图片上写入验证码
        g.drawString(checkCode,15,25);

        //将内存中的图片输出到浏览器
        //参数一:图片对象
        //参数二:图片的格式,如PNG,JPG,GIF
        //参数三:图片输出到哪里去
        ImageIO.write(image,"PNG",response.getOutputStream());
    }
    /**
     * 产生4位随机字符串 
     */
    private String getCheckCode() {
        String base = "0123456789ABCDEFGabcdefg";
        int size = base.length();
        Random r = new Random();
        StringBuffer sb = new StringBuffer();
        for(int i=1;i<=4;i++){
            //产生0到size-1的随机值
            int index = r.nextInt(size);
            //在base字符串中获取下标为index的字符
            char c = base.charAt(index);
            //将c放入到StringBuffer中去
            sb.append(c);
        }
        return sb.toString();
    }
    public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        this.doGet(request,response);
    }
}
JDBCUtils
public class JDBCUtils {
    // 1.    声明静态数据源成员变量
    private static DataSource ds;

    // 2. 创建连接池对象
    static {
        // 加载配置文件中的数据
        InputStream is = JDBCUtils.class.getClassLoader().getResourceAsStream("/druid.properties");
        //druid.properties
        //    url=jdbc:mysql:///travel
        //    username=root
        //    password=123456
        //    initialSize=5
        //    maxActive=10
        //    maxWait=3000
        driverClassName=com.mysql.jdbc.Driver
        Properties pp = new Properties();
        try {
            pp.load(is);
            // 创建连接池,使用配置文件中的参数
            ds = DruidDataSourceFactory.createDataSource(pp);
        } catch (IOException e) {
            e.printStackTrace();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    // 3. 定义公有的得到数据源的方法
    public static DataSource getDataSource() {
        return ds;
    }

    // 4. 定义得到连接对象的方法
    public static Connection getConnection() throws SQLException {
        return ds.getConnection();
    }

    // 5.定义关闭资源的方法
    public static void close(Connection conn, Statement stmt, ResultSet rs) {
        if (rs != null) {
            try {
                rs.close();
            } catch (SQLException e) {}
        }

        if (stmt != null) {
            try {
                stmt.close();
            } catch (SQLException e) {}
        }

        if (conn != null) {
            try {
                conn.close();
            } catch (SQLException e) {}
        }
    }

    // 6.重载关闭方法
    public static void close(Connection conn, Statement stmt) {
        close(conn, stmt, null);
    }
}
邮件激活

image-20200307131259399

为什么要进行邮件激活?为了保证用户填写的邮箱是正确的。将来可以推广一些宣传信息,到用户邮箱中。

用户激活其实就是修改用户表中的status为‘Y’,具体实现看UserDao的findByCode updateStatus以及UserService的active

ActiveUserServlet

protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    //1.获取激活码
    String code = request.getParameter("code");
    if(code != null){
        //2.调用service完成激活
        UserService service = new UserServiceImpl();
        boolean flag = service.active(code);

        //3.判断标记
        String msg = null;
        if(flag){
            //激活成功
            msg = "激活成功,请<a href='login.html'>登录</a>";
        }else{
            //激活失败
            msg = "激活失败,请联系管理员!";
        }
        response.setContentType("text/html;charset=utf-8");
        response.getWriter().write(msg);
    }
}

邮件工具类MailUtils

public final class MailUtils {
    private static final String USER = "1210980644@qq.com"; // 发件人称号,同邮箱地址
    private static final String PASSWORD = "mzlnjllmvsfvbaci"; // 如果是qq邮箱可以使户端授权码,或者登录密码

    /**
     *
     * @param to 收件人邮箱
     * @param text 邮件正文
     * @param title 标题
     */
    /* 发送验证信息的邮件 */
    public static boolean sendMail(String to, String text, String title){
        try {
            final Properties props = new Properties();
            props.put("mail.smtp.auth", "true");
            props.put("mail.smtp.host", "smtp.qq.com");

            // 发件人的账号
            props.put("mail.user", USER);
            //发件人的密码
            props.put("mail.password", PASSWORD);

            // 构建授权信息,用于进行SMTP进行身份验证
            Authenticator authenticator = new Authenticator() {
                @Override
                protected PasswordAuthentication getPasswordAuthentication() {
                    // 用户名、密码
                    String userName = props.getProperty("mail.user");
                    String password = props.getProperty("mail.password");
                    return new PasswordAuthentication(userName, password);
                }
            };
            // 使用环境属性和授权信息,创建邮件会话
            Session mailSession = Session.getInstance(props, authenticator);
            // 创建邮件消息
            MimeMessage message = new MimeMessage(mailSession);
            // 设置发件人
            String username = props.getProperty("mail.user");
            InternetAddress form = new InternetAddress(username);
            message.setFrom(form);

            // 设置收件人
            InternetAddress toAddress = new InternetAddress(to);
            message.setRecipient(Message.RecipientType.TO, toAddress);

            // 设置邮件标题
            message.setSubject(title);

            // 设置邮件的内容体
            message.setContent(text, "text/html;charset=UTF-8");
            // 发送邮件
            Transport.send(message);
            return true;
        }catch (Exception e){
            e.printStackTrace();
        }
        return false;
    }

    public static void main(String[] args) throws Exception { // 做测试用
        MailUtils.sendMail("itcast_xian@163.com","你好,这是一封测试邮件,无需回复。","测试邮件");
        System.out.println("发送成功");
    }
}
登录表单校验和发送异步请求
<script>
    function checkUsername() {
    var username = $("#username").val();
    //2.定义正则
    var reg_username = /^\w{2,20}$/;
    var flag = reg_username.test(username);
    if (flag) {
        //用户名合法
        $("#username").css("border", "");
        $("#usernamemsg").html("");
    } else {
        //用户名非法,加一个红色边框
        $("#username").css("border", "1px solid red");
        $("#usernamemsg").html("单词字符,长度2到20位");
    }
    return flag;
}

function checkPassword() {
    var password = $("#password").val();
    //2.定义正则
    var reg_password = /^\w{2,20}$/;
    var flag = reg_password.test(password);
    if (flag) {
        //用户名合法
        $("#password").css("border", "");
    } else {
        //用户名非法,加一个红色边框
        $("#password").css("border", "1px solid red");
    }
    return flag;
}
$(function(){
    $("#login").click(function () {
        if(checkUsername() && checkPassword()){
            //校验通过,发送ajax请求,提交表单的数据   username=zhangsan&password=123
            //$(this)==$("#registerForm") serialize()将form转为key=value形式
            $.post("user/login",$("#loginForm").serialize(),function(data){
                //处理服务器响应的数据  data  {flag:true,errorMsg:"注册失败"}
                if(data.flag){
                    //注册成功,跳转成功页面
                    location.href="index.html";
                }else{
                    //注册失败,给errorMsg添加提示信息
                    $("#errorMsg").html(data.errorMsg);
                }
            });
        }
        //2.不让页面跳转
        return false;
    });
    //当某一个组件失去焦点是,调用对应的校验方法
    $("#username").blur(checkUsername);
    $("#password").blur(checkPassword);
});
</script>
LoginServlet
@WebServlet("/loginServlet")
public class LoginServlet extends HttpServlet {
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        //获取数据
        Map<String, String[]> parameterMap = request.getParameterMap();
        //封装对象
        User u=new User();
        try {
            BeanUtils.populate(u,parameterMap);
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        }
        UserService userService=new UserServiceImpl();
        User user = userService.login(u);
        ResultInfo resultInfo=new ResultInfo();
        HttpSession session=request.getSession();
        if(user==null){
            resultInfo.setFlag(false);
            resultInfo.setErrorMsg("用户名密码错误!");
        }else {
            if(user.getStatus().equals("N")){
                resultInfo.setFlag(false);
                resultInfo.setErrorMsg("您未完成邮件激活!");
            }else{
                session.setAttribute("user",user);
                resultInfo.setFlag(true);
            }
        }

        ObjectMapper mapper=new ObjectMapper();
        String json = mapper.writeValueAsString(resultInfo);
        response.setContentType("application/json;charset=utf-8");
        response.getWriter().write(json);
    }
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        doPost(request,response);
    }
}
退出

什么叫做登录了?session中有user对象。

实现步骤:

  1. 访问servlet,将session销毁

  2. 跳转到登录页面

<a href="javascript:location.href='exitServlet'">退出</a>
@WebServlet("/exitServlet")
public class ExitServlet extends HttpServlet {
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        request.getSession().invalidate();
        response.sendRedirect(request.getContextPath()+"/login.html");
    }
}
优化Servlet

减少Servlet的数量,现在是一个功能一个Servlet,将其优化为一个模块一个Servlet,相当于在数据库中一张表对应一个Servlet,在Servlet中提供不同的方法,完成用户的请求。

image-20200307132805458

BaseServlet
public class BaseServlet extends HttpServlet {
    @Override
    public void service(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException {
        //请求路径URI
        String requestURI = req.getRequestURI();
        System.out.println(requestURI);

        //获取方法并调用
        String method = requestURI.substring(requestURI.lastIndexOf("/") + 1);
        try {
            //this谁调用表示谁,getDeclaredMethod忽略访问修饰符
            Method method1 = this.getClass().getMethod(method, HttpServletRequest.class, HttpServletResponse.class);
//            method1.setAccessible(true);
            method1.invoke(this,req,res);
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        }
    }

    //将对象序列化为json 并返回客户端
    public void writeValue(Object o,HttpServletResponse resp) throws IOException {
        String json= new ObjectMapper().writeValueAsString(o);
        resp.setContentType("application/json;charset=utf-8");
        resp.getWriter().write(json);
    }

    //将对象序列化为json
    public String writeAsString(Object o) throws JsonProcessingException {
        return new ObjectMapper().writeValueAsString(o);
    }
}
UserServlet改写

将之前的Servlet实现的功能,抽取到UserServlet中的不同方法中实现,并且将UserService创建抽取到成员变量位置

@WebServlet("/user/*")
public class UserServlet extends BaseServlet {
    UserService userService = new UserServiceImpl();

    /**
     * 登录
     * @param req
     * @param resp
     * @throws IOException
     */
    public void login(HttpServletRequest req, HttpServletResponse resp) throws IOException {
        //获取数据
        Map<String, String[]> parameterMap = req.getParameterMap();
        //封装对象
        User u = new User();
        try {
            BeanUtils.populate(u, parameterMap);
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        }
        //UserService userService = new UserServiceImpl();
        User user = userService.login(u);
        ResultInfo resultInfo = new ResultInfo();
        HttpSession session = req.getSession();
        if (user == null) {
            resultInfo.setFlag(false);
            resultInfo.setErrorMsg("用户名密码错误!");
        } else {
            if (user.getStatus().equals("N")) {
                resultInfo.setFlag(false);
                resultInfo.setErrorMsg("您未完成邮件激活!");
            } else {
                session.setAttribute("user", user);
                resultInfo.setFlag(true);
            }
        }
        ObjectMapper mapper = new ObjectMapper();
        String json = mapper.writeValueAsString(resultInfo);
        resp.setContentType("application/json;charset=utf-8");
        resp.getWriter().write(json);
    }

    /**
     * 注册
     * @param req
     * @param resp
     * @throws IOException
     */
    public void regist(HttpServletRequest req, HttpServletResponse resp) throws IOException {
        String check = req.getParameter("check");
        HttpSession session = req.getSession();
        String checkcode_server = (String) session.getAttribute("CHECKCODE_SERVER");
        session.removeAttribute("CHECKCODE_SERVER");//只用一次
        if (!check.equalsIgnoreCase(checkcode_server)) {
            ResultInfo resultInfo = new ResultInfo();
            resultInfo.setFlag(false);
            resultInfo.setErrorMsg("验证码错误");
            //结果序列化并返回
            ObjectMapper mapper = new ObjectMapper();
            String json = mapper.writeValueAsString(resultInfo);
            resp.setContentType("application/json;charset=utf-8");
            resp.getWriter().write(json);
            return;
        }

        //获取数据
        Map<String, String[]> parameterMap = req.getParameterMap();

        //封装对象
        User u = new User();
        try {
            BeanUtils.populate(u, parameterMap);
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        }
        System.out.println(u);
        //调用Service
        //UserService userService = new UserServiceImpl();
        boolean flag = userService.regist(u);
        //返回结果对象
        ResultInfo resultInfo = new ResultInfo();
        if (flag) {
            resultInfo.setFlag(true);
        } else {
            resultInfo.setFlag(false);
            resultInfo.setErrorMsg("注册失败");
        }

        //结果序列化并返回
        ObjectMapper mapper = new ObjectMapper();
        String json = mapper.writeValueAsString(resultInfo);
        resp.setContentType("application/json;charset=utf-8");
        resp.getWriter().write(json);
    }

    /**
     * 激活
     * @param req
     * @param resp
     * @throws IOException
     */
    public void active(HttpServletRequest req, HttpServletResponse resp) throws IOException {
        //1.获取激活码
        String code = req.getParameter("code");
        if (code != null) {
            //2.调用service完成激活
            //UserService service = new UserServiceImpl();
            boolean flag = userService.active(code);
            //3.判断标记
            String msg = null;
            if (flag) {
                //激活成功
                msg = "激活成功,请<a href='../login.html'>登录</a>";
            } else {
                //激活失败
                msg = "激活失败,请联系管理员!";
            }
            resp.setContentType("text/html;charset=utf-8");
            resp.getWriter().write(msg);
        }
    }

    /**
     * 查询用户名
     * @param req
     * @param resp
     * @throws IOException
     */
    public void finduser(HttpServletRequest req, HttpServletResponse resp) throws IOException {
        HttpSession session=req.getSession();
        User user=(User)session.getAttribute("user");
        String json= new ObjectMapper().writeValueAsString(user);
        resp.setContentType("application/json;charset=utf-8");
        resp.getWriter().write(json);
    }

    /**
     * 退出
     * @param req
     * @param resp
     * @throws IOException
     */
    public void exit(HttpServletRequest req, HttpServletResponse resp) throws IOException {
        req.getSession().invalidate();
        resp.sendRedirect(req.getContextPath()+"/login.html");
    }
}
页面路径改写

image-20200307133310538

分类导航展示

分类的数据在每一次页面加载后都会重新请求数据库来加载,对数据库的压力比较大,而且分类的数据不会经常产生变化,可以使用redis缓存这个数据。

image-20200307133412117

异步请求导航栏分类
//请求导航栏分类
$.get("category/findall",{},function (data) {
    //[{cid:1,cname:国内游},{},{}]
    var sy='<li class="nav-active"><a href="index.html">首页</a>';
    for(var i=0;i<data.length;i++){
        var li='<li><a href="route_list.html?cid='+data[i].cid+'">'+data[i].cname+'</a></li>';
        sy+=li;
    }
    var sc='<li><a href="favoriterank.html">收藏排行榜</a></li>';
    $("#category").html(sy+sc);
});
CategoryServlet
@WebServlet("/category/*")
public class CategoryServlet extends BaseServlet {
    CategoryService service=new CategoryServiceImpl();
    public void findall(HttpServletRequest res, HttpServletResponse resp) throws IOException {
        List<Category> category=service.findALl();
        writeValue(category,resp);
    }
}
CategoryServiceImpl
public class CategoryServiceImpl implements CategoryService {
    CategoryDao categoryDao=new CategoryDaoImpl();
    @Override
    public List<Category> findALl() {
        //从Redis中查找
        Jedis jedis= JedisUtil.getJedis();
        //Set<String> category = jedis.zrange("category", 0, -1);
        Set<Tuple> category = jedis.zrangeWithScores("category", 0, -1);
        List<Category> cs=new ArrayList<>();
        if(category==null||category.size()==0){
            System.out.println("从数据库查询,redis中无category");
            cs = categoryDao.findAll();
            for(int i=0;i<cs.size();i++){
                jedis.zadd("category",cs.get(i).getCid(),cs.get(i).getCname());
            }
            return cs;
        }else {
            System.out.println("从Redis查询,redis中有category");
            for(Tuple  tuple :category){
                Category category1=new Category();
                category1.setCname(tuple.getElement());
                category1.setCid((int) tuple.getScore());
                cs.add(category1);
            }
            return cs;
        }
    }
}
CategoryDaoImpl
public class CategoryDaoImpl implements CategoryDao {
    JdbcTemplate template=new JdbcTemplate(JDBCUtils.getDataSource());
    @Override
    public List<Category> findAll() {
        String sql="select * from tab_category";
        List<Category> categories = template.query(sql, new BeanPropertyRowMapper<>(Category.class));
        return categories;
    }
}
Category
public class Category implements Serializable {
    private int cid;//分类id
    private String cname;//分类名称
}
旅游线路分页及搜索

image-20200307134335593

客户端前端代码
<script>
    //根据传递过来的参数name获取对应的值
    function getParameter(name) {
    var reg = new RegExp("(^|&)" + name + "=([^&]*)(&|$)", "i");
    var r = location.search.substr(1).match(reg);
    if (r != null) return (r[2]);
    return null;
}
$(function () {
    var search = location.search;
    //alert(search);//?id=5
    // 切割字符串,拿到第二个值
    var cid = getParameter("cid");
    var rname=getParameter("rname");
    rname=window.decodeURI(rname);
    //alert(cid);
    // alert(rname);
    load(cid,1,rname);
});
//加载每一页的线路列表
function load(cid, currentPage,rname) {
    //发送Ajax请求,传递cid,请求route/pageQuery
    $.get("route/pageQuery", {cid: cid,currentPage:currentPage,rname:rname}, function (pb) {
        //解析pageBean
        //分页工具条
        $("#totalPage").html(pb.totalPage);
        $("#totalCount").html(pb.totalCount);

        //展示分页码
        var lis = "";
        var firstPage = '<li οnclick="javascript:load('+cid+','+1+',\''+rname+'\');"><a href="javascript:void(0)">首页</a></li>';
        var prePage = '<li class="threeword"><a href="javascript:load('+cid+','+ (currentPage-1<1 ? 1:currentPage-1) +',\''+rname+'\');">上一页</a></li>';
        lis = lis + firstPage + prePage;
        var li;
        //展示10页,前5后4
        var begin;
        var end;
        if(pb.totalPage<10){
            begin=1;
            end=pb.totalPage;
        }else {
            begin=currentPage-5;
            end=currentPage+4;
            if(begin<1){
                begin=1;
                end=begin+9;
            }
            if(end>pb.totalPage){
                end=pb.totalPage;
                begin=end-9;
            }
        }
        for (var i = begin ; i <= end; i++) {
            if(currentPage==i){
                li='<li class="curPage" οnclick="javascript:load('+cid+','+i+',\''+rname+'\');"><a href="javascript:void(0);">' + i + '</a></li>';
            }else {
                li='<li οnclick="javascript:load('+cid+','+i+',\''+rname+'\');"><a href="javascript:void(0);">' + i + '</a></li>';
            }
            lis+=li;
        }
        var nextPage = '<li class="threeword"><a href="javascript:load('+cid+','+((currentPage+1>pb.totalPage)?pb.totalPage:currentPage+1)+',\''+rname+'\');">下一页</a></li>';
        var lastPage = '<li class="threeword"><a href="javascript:load('+cid+','+pb.totalPage+',\''+rname+'\');">末页</a></li>';
        lis = lis + nextPage + lastPage;
        $("#pageNum").html(lis);

        //具体线路展示
        var route_lis = "";
        var li = "";
        for (var i = 0; i < pb.list.length; i++) {
            var route = pb.list[i];
            li = '                        <li>\n' +
                '                            <div class="img"><img src="' + route.rimage + '" style="width: 299px"></div>\n' +
                '                            <div class="text1">\n' +
                '                                <p>' + route.rname + '</p>\n' +
                '                                <br/>\n' +
                '                                <p>' + route.routeIntroduce + '</p>\n' +
                '                            </div>\n' +
                '                            <div class="price">\n' +
                '                                <p class="price_num">\n' +
                '                                    <span>&yen;</span>\n' +
                '                                    <span>' + route.price + '</span>\n' +
                '                                    <span>起</span>\n' +
                '                                </p>\n' +
                '                                <p><a href="route_detail.html?rid='+route.rid+'">查看详情</a></p>\n' +
                '                            </div>\n' +
                '                        </li>';
            route_lis += li;
        }
        $("#routes").html(route_lis);
        window.scrollTo(0,0);//返回页首
    })
}
</script>
RouteServlet
@WebServlet("/route/*")
public class RouteServlet extends BaseServlet {
    private RouteService routeService = new RouteServiceImpl();
    //分页查询
    public void pageQuery(HttpServletRequest res, HttpServletResponse resp) throws UnsupportedEncodingException {
        String currentPagestr = res.getParameter("currentPage");
        String pageSizestr = res.getParameter("pageSize");
        String cidstr = res.getParameter("cid");
        String rname=res.getParameter("rname");
        System.out.println(cidstr+rname);
        int cid=0;
        if(cidstr!=null&&cidstr.length()>0&&!"null".equals(cidstr)){
            cid=Integer.parseInt(cidstr);
        }
        int currentPage=0;
        if(currentPagestr!=null&&currentPagestr.length()>0){
            currentPage=Integer.parseInt(currentPagestr);
        }else {
            currentPage=1;
        }
        int pageSize=0;
        if(pageSizestr!=null&&pageSizestr.length()>0){
            pageSize=Integer.parseInt(pageSizestr);
        }else {
            pageSize=5;
        }
        if(rname!=null&&"null".equals(rname)){
            rname=null;
        }

        //调用Sercive
        PageBean<Route> pb=routeService.pageQuery(cid,pageSize,currentPage,rname);
        //4. 将pageBean对象序列化为json,返回
        try {
            writeValue(pb,resp);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}
RouteServiceImpl
public class RouteServiceImpl implements RouteService {
    RouteDao routeDao=new RouteDaoImpl();
    @Override
    public PageBean<Route> pageQuery(int cid, int pageSize, int currentPage,String rname) {
        PageBean<Route> pb=new PageBean<Route>();
        pb.setCurrentPage(currentPage);
        pb.setPageSize(pageSize);
        int totalCount=routeDao.findTotalCount(cid,rname);
        pb.setTotalCount(totalCount);
        int start=(currentPage-1)*pageSize;
        List<Route> list=routeDao.findByPage(cid,start,pageSize,rname);
        pb.setList(list);
        int totalPage=totalCount%pageSize==0?totalCount/pageSize:totalCount/pageSize+1;
        pb.setTotalPage(totalPage);
        return pb;
    }
}
RouteDaoImpl

采用多条件组合查询

public class RouteDaoImpl implements RouteDao {
    JdbcTemplate template = new JdbcTemplate(JDBCUtils.getDataSource());

    @Override
    public int findTotalCount(int cid, String rname) {
        //多条件组合查询
        String sql = "select count(*) from tab_route where 1=1 ";
        //判断参数是否有值
        List param = new ArrayList();

        StringBuilder sb = new StringBuilder(sql);
        if (cid != 0) {
            sb.append(" and cid= ? ");
            param.add(cid);
        }
        if (rname != null && rname.length() > 0) {
            sb.append(" and rname like ? ");
            param.add("%" + rname + "%");
        }
        sql = sb.toString();
        return template.queryForObject(sql, Integer.class, param.toArray());
    }

    @Override
    public List<Route> findByPage(int cid, int start, int pageSize, String rname) {
        String sql = "select * from tab_route where 1=1 ";
        //判断参数是否有值
        List param = new ArrayList();

        StringBuilder sb = new StringBuilder(sql);
        if (cid != 0) {
            sb.append(" and cid= ? ");
            param.add(cid);
        }
        if (rname != null && rname.length() > 0) {
            sb.append(" and rname like ? ");
            param.add("%" + rname + "%");
        }
        sb.append(" limit ?,? ");
        sql = sb.toString();
        System.out.println(sql);
        param.add(start);
        param.add(pageSize);
        return template.query(sql, new BeanPropertyRowMapper<Route>(Route.class), param.toArray());
    }
}
Route
public class Route implements Serializable {
    private int rid;//线路id,必输
    private String rname;//线路名称,必输
    private double price;//价格,必输
    private String routeIntroduce;//线路介绍
    private String rflag;   //是否上架,必输,0代表没有上架,1代表是上架
    private String rdate;   //上架时间
    private String isThemeTour;//是否主题旅游,必输,0代表不是,1代表是
    private int count;//收藏数量
    private int cid;//所属分类,必输
    private String rimage;//缩略图
    private int sid;//所属商家
    private String sourceId;//抓取数据的来源id

    private Category category;//所属分类
    private Seller seller;//所属商家
    private List<RouteImg> routeImgList;//商品详情图片列表
}
PageBean
public class PageBean<T> {
    private int totalCount;//总记录数
    private int totalPage;//总页数
    private int currentPage;//当前页码
    private int pageSize;//每页显示的条数
    private List<T> list;//每页显示的数据集合
}
旅游线路详情展示

image-20200307141501649

image-20200307141319255

前端异步请求
<a href="route_detail.html?rid='+route.rid+'">查看详情</a>
$(function () {
    var rid = getParameter("rid");
    $.get("route/findOne", {rid: rid}, function (route) {
        $("#rname").html(route.rname);
        $("#price").html("¥"+route.price);
        $("#routeIntroduce").html(route.routeIntroduce);
        $("#sname").html(route.seller.sname);
        $("#consphone").html(route.seller.consphone);
        $("#address").html(route.seller.address);
        $("#rimage").prop("src",route.rimage);

        //图片展示
        var lis='<a class="up_img up_img_disable"></a>';
        var li;
        for (var i = 0; i <route.routeImgList.length ; i++) {
            if(i<4){
                li='                <a title="" class="little_img"\n' +
                    '                   data-bigpic="'+route.routeImgList[i].bigPic+'"\n' +
                    '                   >\n' +
                    '                    <img src="'+route.routeImgList[i].smallPic+'">\n' +
                    '                </a>'
            }else {
                li='                <a title="" class="little_img"\n' +
                    '                   data-bigpic="'+route.routeImgList[i].bigPic+'"\n' +
                    '                   style="display:none;">\n' +
                    '                    <img src="'+route.routeImgList[i].smallPic+'">\n' +
                    '                </a>'
            }
            lis+=li;
        }
        lis+='<a class="down_img down_img_disable" style="margin-bottom: 0;"></a>';
        $("#dd").html(lis);
        //切换图片
        goImg();
    });
});
RouteServlet
//查询具体旅游线路
public void findOne(HttpServletRequest res, HttpServletResponse resp) throws IOException {
    String ridstr = res.getParameter("rid");
    int rid=0;
    if(ridstr!=null&&ridstr.length()>0&&!"null".equals(ridstr)){
        rid=Integer.parseInt(ridstr);
    }
    Route route = routeService.findOne(rid);
    writeValue(route,resp);
}
RouteServiceImpl
@Override
public Route findOne(int rid) {
    Route route = routeDao.findOne(rid);
    //根据route的rid查找routeImg
    List<RouteImg> routeImgs = new RouteImgImpl().findByRid(rid);
    route.setRouteImgList(routeImgs);
    //根据route的sid查找seller
    Seller seller = new SellerDaoImpl().findBySid(route.getSid());
    route.setSeller(seller);
    return route;
}
Dao
//RouteImgImpl 根据route的rid查找routeImg
@Override
public List<RouteImg> findByRid(int rid) {
    String sql = "select * from tab_route_img where rid=? ";
    List<RouteImg> routeImgs = null;
    try {
        routeImgs = template.query(sql, new BeanPropertyRowMapper<>(RouteImg.class), rid);
    } catch (Exception e) {
    }
    return routeImgs;
}

//SellerDaoImpl 根据route的sid查找seller
@Override
public Seller findBySid(int sid) {
    String sql = "select * from tab_seller where sid=? ";
    Seller seller = null;
    try {
        seller = template.queryForObject(sql, new BeanPropertyRowMapper<>(Seller.class), sid);
    } catch (Exception e) {
    }
    return seller;
}

//RouteDaoImpl  根据前端传递的rid查找Route
@Override
public Route findOne(int rid) {
    String sql = "select * from tab_route where rid=? ";
    Route route = null;
    try {
        route = template.queryForObject(sql, new BeanPropertyRowMapper<>(Route.class), rid);
    } catch (Exception e) {
    }
    return route;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值