JavaWeb

JavaWeb服务器端学习

Web相关概念回顾

软件架构

  • C/S:客户端/服务器端
  • B/S:浏览器/服务器端

资源分类

  • 静态资源,如html,css,JavaScript

  • 动态资源,如servlet/jsp,asp,php

    image-20210731161144760

网络通信三要素

  • IP:电子设备(计算机)在网络中的唯一标识
  • 端口:应用程序在计算机上的唯一标识(0~65535)
  • 传输协议:规定了数据传输的规则
    • TCP:三次握手的安全协议
    • UDP:面向无连接的不安全协议

Tomcat服务器软件

  • 服务器:安装了服务器软件的计算机

  • 服务器软件:用于接受用户的请求、处理请求、作出响应

  • Web服务器软件(Web容器):在Web服务器软件中,可以部署Web项目,让用户可以通过服务器来访问这些项目

  • 常见的Java相关的Web服务器软件:

    • WebLogic:Oracle公司的大型JavaEE服务器,收费的,支持所有的JavaEE规范

    • WebSphere:IBM公司的大型JavaEE服务器,收费的,支持所有的JavaEE规范

    • JBoss:JBoss公司的大型JavaEE服务器,收费的,支持所有的JavaEE规范

    • Tomcat:Apache基金组织的中小型JavaEE服务器,仅支持少量JavaEE规范,开源、免费

      JavaEE:Java语言在企业级开发中使用的技术规范的总和,一共规定了13项大的规范

Tomcat

下载:

  • 访问https://tomcat.apache.org下载对应的版本

安装:

  • 直接解压压缩包即可,安装目录不要有中文和空格
  • 目录结构: image-20210731163425498

卸载:

  • 直接删除安装目录即可

启动:

  • 在安装目录下的bin目录执行startup.bat批处理文件
    • 如果出现控制台乱码,修改安装目录下的conf目录下的logging.properties文件的encodingGBK
    • 如果控制台一闪而过:配置JAVA_HOMEJRE_HOME的环境变量
    • 启动报错:在安装目录下的logs目录查看日志文件
      • 端口占用错误:
        1. 找到占用端口的程序,杀死该进程:
          • cmd输入netstat -ano查看占用8080端口的进程PID
          • 任务管理器杀死该PID的进程
        2. 修改自身的端口号:
          • 编辑安装目录下的conf目录下的server.xml
          • 修改<Connerctor port=端口号/>中相应的端口号,使得不冲突
      • HTTP协议默认端口号默认为80,访问时可以省略端口号

访问:

  • 本机访问:浏览器访问http://127.0.0.1:8080http://localhost:8080
  • 访问其他:http://服务器ip:8080

关闭:

  • 正常关闭:执行安装目录下的bin目录的shutdown.bat批处理文件,也可以在窗口直接Ctrl+C
  • 强制关闭:直接关掉启动startup.bat的窗口

配置:

部署项目的方式:
1.直接将项目放到安装目录下的webapps目录下即可
  1. webapps目录下放置项目文件夹
  2. war包放置到webapps目录下,这种方式在Tomcat服务器启动时会自动解包成项目文件夹
2.配置conf目录下的server.xml文件,重启服务器生效

​ 在<Host></Host>标签体中配置<Context docBase="项目路径" path="/虚拟访问路径"/>

3.在conf/Catalina/localhost目录下创建任意的xml文件

​ 在创建的xml文件中配置<Context docBase="项目路径" />,此时的虚拟访问路径就是创建的xml文件名称

静态项目和动态项目
  • 目录结构:

    • Java动态项目的目录结构:

      ——项目根目录

      ————WEB-INF目录

      ——————web.xml :web项目的核心配置文件

      ——————classes目录:放置字节码文件的目录

      ——————lib目录:放置依赖的jar包

Tomcat与IDEA集成

IDEA集成Tomcat
  • 在IDEA的导航栏选择 image-20210731181519317
  • 运行二级菜单选择 image-20210731181608049
  • 编辑配置弹出的窗口中选择+号添加新配置 image-20210731181655875
  • 配置新服务器的来源 image-20210731181756724
  • 保存即可完成配置
IDEA启动Tomcat
  • 创建JavaEE项目或模块,勾选Web Application image-20210731182403416
    并勾选创建web.xml image-20210731182428481

  • 启动创建的JavaWeb项目:点击导航栏的 image-20210731182541089
    或右上角的 image-20210731182624541

  • 浏览器访问ip:端口以及Tomcat部署的war包,访问路径为Tomcat配置窗口的部署菜单下

    注意

    1. 新版IDEA下的部署菜单的最下面,找到Application Context即为**访问路径
      ** image-20210731183046584
    2. 新版IDEA的JavaEE项目创建稍有不同,如果想要原来的JavaEE项目创建界面,修改IDEA的软件注册表:
      • Ctrl Shift Alt /:打开维护界面
        ,选择注册表image-20210731183455885
      • 搜索并勾选javaee.legacy.project.wizardimage-20210731183551065
IDEA的Tomcat项目配置
  1. IDEA会为每一个Tomcat部署的项目单独建立一份配置文件:在启动项目时打印在Server控制台

    image-20210801015440946

    其中CATALINA_BASE后面的路径就是项目配置文件的路径

  2. IDEA项目的工作空间和Tomcat部署的web项目不是同一个概念

    *
    Tomcat真正访问的是Tomcat部署的web项目,对应着IDEA项目工作空间的 image-20210801020007942
    目录下的所有资源

    • WEB-INF目录下的资源不能被浏览器直接访问
  3. 断点调试:通过 image-20210801020356707 以debug方式启动项目

Servlet入门

Servlet = Server + Applet

  • 概念:运行在服务器端的小程序,Servlet是一个接口,定义了Java类被浏览器访问到(Tomcat识别)的规则

快速入门:

  1. 创建JavaEE项目

  2. 定义一个类,实现Servlet接口 image-20210731211508413

  3. 实现接口中的抽象方法

  4. 配置Servlet:在web.xml文件中配置Servlet

    image-20210731210732207

Servlet执行过程:

  1. 当服务器接收到浏览器的请求后,会解析请求的URL路径,获取访问的Servlet的资源路径
  2. 查找web.xml文件,是否有对应的<url-pattern>标签体内容
  3. 如果有,就通过<servlet-name>标签找到对应的<servlet-class>全类名
  4. Tomcat会将字节码文件加载进内存,并且创建其对象
  5. 调用其方法

Servlet生命周期

  • 被创建:执行init()方法,只执行一次

    • Servlet什么时候被创建?

      • 默认情况下,第一次被访问时,Servlet被创建

      • 可以配置指定Servlet的创建时机:

        在web.xml配置文件中,对每一个<servlet>标签配置<load-on-startup>标签的值

        1. 此标签值默认为-1,指定为负数会在第一次被访问时创建,指定为0或正数会在启动服务器时创建
        2. <load-on-startup>较小值的 servlet 在以较大值的 servlet 之前加载
    • 由于Servlet的init()方法只执行一次,说明一个Servlet在内存中只存在一个对象,Servlet是单例的,所以多个用户同时访问时,可能会存在安全问题,所以尽量不要再Servlet中定义成员变量

  • 提供服务:执行service()方法,会执行多次

    • 每次访问Servlet时都会调用一次service()方法
  • 被销毁:执行destroy()方法,只执行一次

    • 在服务器关闭之前执行

    对于每一个Servlet都会执行上述方法(初始化、销毁)

Servlet3.0注解开发

  • 好处:简化了web.xml的配置

  • 步骤:

    • 创建JavaEE项目,选择Servlet的版本为3.0以上,可以不勾选web.xml

      注意:选择的JavaEE版本为JavaEE6开始才支持3.0JavaEE73.1JavaEE84.0

    • 定义一个类,实现Servlet接口

    • 重写抽象方法

    • 在类上使用@WebServlet注解,指定映射的虚拟访问路径,例如:

    • 通过urlPatterns指定: image-20210801014531501

    • 或直接给value赋值(可省略): image-20210801014651107

Servlet体系结构

image-20210801134635415

  • GenericServletServlet接口中其他的方法做了默认空实现,只将service()方法作为抽象方法
  • HttpServletHTTP协议进行了封装,在service()方法中对每种请求方式(doXxx())做了分发

Servlet相关配置

  1. urlPattern:Servlet的访问路径,这是一个String[]数组
    • 可以为一个Servlet定义多个访问路径
    • 路径定义规则:
      1. /xxx
      2. /xxx/xxx 多层路径的目录型结构
      3. /xxx/* 匹配/xxx开头,后面内容随意的Servlet
      4. /* 匹配所有可能的虚拟请求路径
      5. *.do 匹配前面任意,后面以.do结尾的Servlet,注意前面没有/

HTTP

  • 概念:Hyper Text Transfer Protocol 超文本传输协议

    • 定义和客户端和服务器端的通信时发送数据的格式
    • 特点:
      1. 基于TCP/IP的高级协议
      2. 默认端口号:80,例如访问http://www.baidu.com相当于http://ip地址:80
      3. 基于请求/响应模型,一次请求对应一次响应
      4. 无状态的:每次请求之间相互独立,不能通信数据
    • 历史版本:
      • 1.0 :每次请求/响应都会建立新的连接
      • 1.1:可以复用连接

请求消息数据格式

image-20210801173507355

  • 请求行

    1. 格式:请求方式 请求url 请求协议/版本 image-20210801170942567

    2. 请求方式:HTTP协议有7种请求方式,常用的有2种

      • GET:
        1. 请求参数在请求行中,在url后 image-20210801172350702
        2. 请求的url长度有限制
        3. 不太安全
      • POST:
        1. 请求参数在请求体中
        2. 请求的url长度没有限制
        3. 相对安全
  • 请求头:包含若干个属性,格式为 请求头属性名:请求头属性值

    常见的请求头:

    • Host:请求的主机名
    • User-Agent:浏览器告诉服务器,访问服务器的浏览器的版本信息
    • Accept:可接收的格式
    • Accept-Language:可接收的语言
    • Referer:告诉服务器这个请求是哪个URL过来的,可以用于防盗链和统计工作
    • Connection:连接方式
  • 请求空行:最后一个请求头之后是一个空行,包括回车符和换行符,分隔POST请求头和体

  • 请求体(正文):在POST方法中含有,封装了POST请求消息的请求参数

Request

Request对象和Response对象都是由服务器创建的,我们来使用它们,Request对象用于获取请求消息,Response对象用于设置响应消息

Request对象继承体系

image-20210801184932632

Request功能

Request对象获取请求消息
  1. 获取请求行数据

    • public String getMethod()获取请求方式,例如 GET、POST 或 PUT。
    • public String getContextPath()获取请求上下文(虚拟目录),该路径以/
      字符开头但不以/字符结束,对于默认上下文(即/根),此方法返回""空字符串
    • public String getServletPath()获取请求调用Servlet的URL部分,此路径以/字符开头,包括 servlet 名称或到 servlet
      的路径
    • public String queryString()获取以GET请求查询的参数,如果没有参数则返回null
    • public String getRequestURI()获取请求的URL的一部分,相当于拼接ContextPath和ServletPath
      • URI:统一资源标识符
    • public String getRequestURL()获取请求的URL,返回的 URL 包含一个协议、服务器名称、端口号、服务器路径,但是*
      不包含queryString()的内容*
      • URL:统一资源定位符
    • public String getProtocol()继承于ServletRequest接口,返回请求使用的协议的名称和版本
    • public String getRemoteAddr()继承于ServletRequest接口,获取客户机的IP地址
  2. 获取请求头数据

    • String getHeader(String name)返回指定的请求头的值,如果该请求不包含指定name的头,则此方法返回 null
      。如果有多个具有相同名称的头,则此方法返回请求中的第一个头。头名称不区分大小写
    • Enumeration<E> getHearderNames() 返回此请求包含的所有头名称的枚举。如果该请求没有头,则此方法返回一个空枚举
  3. 获取请求体数据

    • 只有POST方式才有请求体,在请求体中封装了POST请求的请求参数

    • 步骤:

      1. 获取流对象

        BufferedReader getReader()获取字符输入流,只能操作字符数据,适合操作文本

        ServletInputStream getInputStream()获取字节输入流,可操作所有数据,适合文件上传场景

      2. 从流对象中拿数据

其他功能
  1. 获取请求参数的通用方式:不论GET还是POST方式都可以使用下列的方法

    • String getParameter(String name)根据参数名name获取参数值,如果该参数不存在则返回null
    • String[] getParameterValues(String name)根据参数名name
      获取参数值的数组,多用于复选框,如果该参数不存在则返回null
    • Enumeration<String> getParameterNames()获取所有请求的参数名称,没有参数则返回空枚举
    • Map<String,String[]> getParameterMap()获取所有请求的参数的集合

    中文乱码问题:

    :white_check_mark:GET方式在Tomcat 8下的乱码问题被解决了

    :x:POST方式会乱码,解决方式request.setCharacterEncoding("utf-8");设置字符编码方式

  2. 请求转发:一种在服务器内部的资源跳转方式

    image-20210801232407311

    • 步骤:
      1. 通过request对象获取请求转发器对象:RequestDispatcher getRequestDispatcher(String s)
      2. 使用请求转发器对象进行转发:void forward(ServletRequest var1, ServletResponse var2)
    • 特点:
      1. 转发后浏览器地址栏路径没有发生变化
      2. 只能转发到当前服务器的内部资源
      3. 转发是一次请求
  3. 共享数据

    • 域对象:一个有作用范围的对象,可以在范围内共享数据
    • request域:代表一次请求的范围,一般用于请求转发的多个资源中共享数据
    • 方法:继承于ServletRequest接口
      1. void setAttribute(String s,Object o):存储数据
      2. Object getAttribute(String s):通过键获取值
      3. void removeAttribute(String s):通过键移除键值对
  4. 获取ServletContext

    image-20210801235001351

    • ServletContext getServletContext():获取ServletContext对象

案例

  • 需求:

    1. 编写login.html登录页面。需要具有username和password两个输入框

    2. 使用Druid数据库连接池技术,操作mysql的day14数据库中的user表

      注意:使用这些框架时,要把jar包放到WEB-INF目录下才能被打包进项目!

    3. 使用jdbcTemplate技术封装JDBC

    4. 登录成功跳转到SuccessServlet展示:登录成功![用户名],欢迎您

      注意:页面乱码要设置request的setCharacterEncoding()和response的setContentType()

    5. 登录失败跳转到FailServlet展示:登录失败,用户名或密码错误

BeanUtils

  • 用于封装JavaBean

    • JavaBean:标准的Java类
      • 标准的Java类
        1. 类必须被public修饰
        2. 必须提供空参构造器
        3. 成员变量必须用private修饰
        4. 提供公共settergetter方法
    • 功能:封装数据
  • 概念:

    • 成员变量

    • 属性:settergetter方法截取替换后的产物

      例如:对于方法getUsername(),截取之后:Username,首字母替换为小写:username

    • 方法:

      1. void setProperty(Object bean,String name,Object value)

        此方法为bean的成员变量属性name赋值为value

      2. String getProperty(Object bean,String name)

        此方法返回bean的成员变量属性name的值,总会返回String字符串形式

      3. void populate(Object bean,Map properties):根据properties的属性封装到bean

        建议配合request.getParameterMap()返回所有的请求参数的Map键值对集合使用

响应消息数据格式

image-20210803154816147

  • 响应行

    1. 格式:协议/版本 响应状态码 状态码描述

    2. 响应状态码:服务器告诉浏览器本次请求和响应的状态

      分类:状态码都是3位数字

      • 1xx:服务器接收客户端消息但没有接收完成,等待一段时间后发送1xx状态码。这一类型的状态码,代表请求已被接受,需要继续处理。

      • 2xx:请求已成功,出现此状态码是表示正常状态。例如:200 OK

      • 3xx:重定向,后续的请求地址(重定向目标)在本次响应的 Location域中指明。

        例如:302::请求的资源临时从不同的 URI响应请求。由于这样的重定向是临时的,客户端应当继续向原有地址发送以后的请求。

        304:客户端发送了一个带条件的 GET 请求且该请求已被允许,而文档的内容(
        自上次访问以来或根据请求的条件)并没有改变则返回此状态码。响应禁止包含消息体,因此以消息头后空行结尾。

      • 4xx:客户端请求错误

        例如:404:代表请求的路径在服务器上没有对应的资源Not Found

        405:请求方式没有对应的方法(GET、POST等)

      • 5xx:服务器错误,例如500:服务器内部错误,一般是服务器后台源代码错误

  • 响应头

    常见的响应头:

    • Content-Type:服务器告诉客户端本次响应体的数据格式以及编码格式。

      浏览器会根据Content-Type自动适应编码格式

    • Content-disposition:服务器告诉客户端以什么格式打开响应体数据

      • in-line:在浏览器内打开,默认值
      • attachment:以附件形式打开响应体,需要指定filename=xxx,常用于文件下载
  • 响应空行:最后一个响应头之后是一个空行,包括回车符和换行符,分隔响应头和体

  • 响应体:服务器传送给客户端的数据

Response

Response对象继承体系

image-20210803163341005

Response功能

Response对象设置响应消息
  1. 设置响应行

    格式:HTTP/1.1 200 OK

    • void setStatus(int sc)设置此响应的状态码。此方法用于设置没有错误时的返回状态代码(例如状态代码 SC_OK 或
      SC_MOVED_TEMPORARILY)。如果有错误,并且调用者希望调用 Web 应用程序中定义的错误页面,则应改用 sendError 方法。
  2. 设置响应头:

    • void setHeader(String name, String value)设置响应头,如果已经设置了头,则新值将重写以前的值。containsHeader
      方法可用于测试在设置其值之前头是否存在。
  3. 设置响应体

    使用步骤:

    1. 获取输出流
      • PrintWriter getWriter()获取字符输出流
      • ServletOutputStream getOutputStream()获取字节输出流
    2. 使用输出流将数据输出到客户端浏览器

案例

完成重定向

image-20210803164915947

步骤:

在设置重定向时,往往分为两个步骤:

  1. 利用setStatus(302)告知浏览器需要重定向
  2. 利用setHeader("location","重定向到哪个虚拟访问路径")设置location

可以把上述两部简化为一步,调用void sendRedirect(String s)方法指定重定向的虚拟访问路径s即可

转发的特点:

  1. 转发后浏览器地址栏路径没有发生变化
  2. 只能转发到当前服务器的内部资源
  3. 转发是一次请求
  4. forward()可以使用request域对象共享数据
重定向的特点
  1. 重定向后浏览器的地址栏路径发生变化
  2. 重定向可以访问其他站点(服务器)的资源
  3. 重定向是两次请求
  4. sendRedirect()不能使用request域对象共享数据
路径写法:

分类:

  1. 相对路径:通过相对路径不可以确定唯一的资源,不以/开头

    规则:找到当前的资源和目标资源之间的相对位置关系

    • ./:当前目录,不写也默认
    • ../:上一级目录
  2. 绝对路径:通过绝对路径可以确定唯一的资源,以/开头

    例如:http://localhost/day15/responseDemo2,可以简写为/day15/responseDemo2

    规则:判断定义的路径是给谁用的(客户端/服务器)?判断请求是从哪里来的

    • 给客户端浏览器使用:需要加虚拟目录(项目的访问路径),建议使用getContextPath()来动态获取
    • 给服务器使用:不需要加虚拟目录
服务器输出字符数据到浏览器
步骤:
  1. 获取字符输出流:PrintWriter getWriter(),调用ServletResponse的此方法返回字符输出流对象,

    但获取到的流默认编码是ISO-8859-1(1个字节编码),如果要输出中文(2到3个字节)需要解决
    乱码问题

  2. 输出数据

注意:乱码问题

由于浏览器默认解码方式与平台有关,比如windows下默认GBK,所以需要在
获取流之前指定ContentType

  1. response.setHeader("content-type","text/html;charset=utf-8"),这种方式太麻烦
  2. response.setContentType("text/html;charset=utf-8"),这个方法直接设置ContentType
服务器输出字节数据到浏览器
步骤
  1. 获取字节输出流:ServletOutputStream getOutputStream(),调用此方法返回字节输出流对象
  2. 输出数据
注意
  1. 输出字节数据时,如果使用byte[] getBytes()方法,则按照平台的默认字符集(windows下为GBK)编码
  2. 需要按照指定的编码获得字节数组,则利用重载形式byte[] getBytes(String charsetName)设置编码集
验证码案例——Response
产生验证码图像
  1. 利用BufferedImage类的构造方法BufferedImage(int width,int height,int imageType)产生一个指定widthheight
    和图像类型imageType的初始图像,imageType可以为BufferedImage.TYPE_INT_RGB

  2. 获取此BufferedImage图像的Graphics画笔对象,用于对图像进行绘制

  3. 绘制:

    • 利用setColor(Color c)设置画笔的渲染颜色
    • 利用fillRect(int x,int y,int width,int height)填充矩形从(x,y)(x+width,y+height)
    • 利用drawRect(int x,int y,int width,int height)绘制边框,注意边框有1px的宽度
    • 利用 drawLine(int x1,int y1,int x2,int y2)绘制线条,线条从(x1,y1)(x2,y2)
  4. 产生随机验证码字符

    • 利用drawString(String str,int x,int y)绘制文本,str将会被绘制到(x,y)位置
    • 文本可以由随机数Random类产生,再由String valueOf(Object obj)包装为String对象
  5. 将图片输出到页面上

    • ImageIO的静态方法write(RenderedImage image,String formatName,OutputStream output)可以把实现了RenderedImage
      接口的指定image对象以formatName格式输出到output输出流

      在本案例中把上面绘制好的imagejpg格式输出到ServletResponse对象的字节输出流

      1
      ImageIO.write(image, "jpg", response.getOutputStream());

ServletContext

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

    image-20210801235001351

  • 获取:

    1. 通过ServletRequest对象获取:ServletContext getServletContext()
    2. 通过HttpServlet对象获取:this.getServletContext()
  • 功能:

    1. 获取MIME类型:String getMimeType(String s)返回文件名为s的MIME类型

      • MIME:在互联网通信过程中定义的一种文件数据类型

        格式: 大类型/小类型,例如text/htmlimage/jpeg

    2. 域对象:共享数据

      • 方法
        1. void setAttribute(String s,Object o)设置域对象s的值为o
        2. Object getAttribute(String s)返回域对象s的值为Object类型
        3. void removeAttribute(String s)删除域对象中指定的s对应的键值对
      • 作用范围:所有用户所有请求的数据,生命周期很长(伴随服务器)
    3. 获取文件的真实(服务器)路径

      • 方法:String getRealPath(String s)返回s资源的真实路径

        注意:真实路径:

        1. 对于项目
          web目录下的文件
          image-20210804031015234 ,均被打包到部署的真实根路径下

          真实根路径:Tomcat服务器此项目配置文件conf/Catalina/localhost/上下文名称.xml

          可以在Tomcat启动时控制台打印的Using CATALINA_BASE:此项目配置文件路径找到

          xml配置文件里的<Context />标签里的docBase="项目路径"即为项目真实路径

        2. 对于项目web目录下的WEB-INF文件夹,均被打包到部署的真实根路径下的WEB-INF
          文件夹

          WEB-INF文件夹还包含了编译之后的class字节码文件(存放于classes文件夹)

        3. 对于项目src目录下的文件,均被打包到部署的真实根路径下的WEB-INF
          下的classes文件夹

          相比之下ClassLoader类加载器对象的getResource(String name)方法只能获取src的文件

文件下载案例

  • 需求:

    1. 页面显示超链接
    2. 点击超链接后弹出下载提示框
    3. 完成图片文件下载
  • 分析:

    • 超链接指向的资源如果能被浏览器解析,则在浏览器中展示(例如图片),如果不能解析,则会弹出下载提示框(例如视频)

    • 任何资源都需要弹出下载提示框

    • 使用响应头可以指定资源的打开方式:

      • Content-disposition:服务器告诉客户端以什么格式打开响应体数据

        1. in-line:在浏览器内打开,默认值

        2. attachment:以附件形式打开响应体,需要指定filename=xxx,常用于文件下载

  • 步骤:

    1. 定义页面,编辑超链接href指向Servlet,可以利用请求参数携带需要下载的文件名

      image-20210804130306959

    2. 定义Servlet

      • 获取文件名称:位于请求参数
      • 使用字节输入流加载文件到内存
        • 加载文件进内存需要获取到文件的真实路径
      • 指定response的响应头:Content-disposition为以附件形式打开:attachment
      • 将数据输出到response的输出流
中文文件名问题
  • 解决思路:
    1. 获取客户端使用的浏览器版本信息
    2. 根据不同的版本信息,设置不同的filename编码方式(利用编码工具类)

会话技术

  • 会话:一次会话中包含多次请求和响应
    • 一次会话:浏览器第一次给服务器资源发送请求,会话建立,直到有一方断开为止,会话结束
  • 功能:在一次会话的范围内的多次请求间共享数据
  • 方式:
    1. 客户端会话技术:Cookie
    2. 服务器端会话技术:Session
概念
  • 客户端会话技术,将数据保存到客户端
快速入门
  • 使用步骤:

    1. 创建Cookie对象,绑定数据
      • Cookie(String name, String value),构造一个带指定名称和值的Cookie对象
    2. 发送Cookie对象,随着HttpServletResponse对象
      • void addCookie(Cookie cookie) 将指定Cookie添加到response,可多次调用设置多个Cookie
    3. 获取Cookie,拿到数据,通过HttpServletRequest对象
      • Cookie[] getCookies()返回包含客户端随此请求一起发送的所有 Cookie 对象的数组。如果没有发送任何Cookie
        ,则此方法返回 null
  • 实现原理:

    • 基于响应头:Set-Cookie和请求头Cookie实现
  • Cookie细节:

    1. 一次发送多个Cookie?

      可以创建多个Cookie并多次调用addCookie(Cookie cookie)发送

    2. Cookie在浏览器中保存的时间?

      • 默认情况下,浏览器关闭后,Cookie被销毁

      • 持久化存储Cookie:setMaxAge(int seconds)

        参数seconds的取值情况:

        1. 正数:将Cookie数据写到硬盘的文件中,持久化存储,存活时间为seconds秒,写入即开始计时
        2. 零:删除Cookie信息
        3. 负数:默认值,浏览器关闭后Cookie被销毁
    3. Cookie保存中文?

      • Tomcat8之前,Cookie不能直接存储中文数据

        需要将中文数据转码保存,一般采用URL编码(由%XX组成)

      • Tomcat8之后,Cookie可以直接存储中文数据,但是对于特殊字符(例如空格、:等)还是不支持,建议使用URL编码

    4. Cookie共享的范围?

      1. 假设在同一个Tomcat服务器中部署了多个web项目,能否共享Cookie?

        • 默认情况下,Cookie不能共享

        • 可以通过setPath(String path)设置Cookie的共享范围,默认为当前的虚拟上下文目录

          例如默认相当于setPath("/虚拟上下文目录")

          如果要共享到Tomcat服务器多个项目,可以设置为服务器根目录setPath("/")

      2. 假设在不同的Tomcat服务器中部署了多个web项目,能否共享Cookie?

        • setDomain(String path)如果设置的一级域名相同,那么多个服务器之间Cookie可以共享

          例如setDomain(".baidu.com"),那么tieba.baidu.comnews.baidu.com可以共享cookie

Cookie的特点
  1. Cookie存储数据在客户端浏览器
  2. 浏览器对于单个Cookie的大小有限制(一般4KB),以及对于同一个域名下的总Cookie数量也有限制(一般20个)
Cookie的作用
  1. Cookie一般用于存储少量的不太敏感的数据
  2. 在不登陆的情况下完成服务器对客户端的身份识别
案例
  • 记住上一次访问的时间

    • 需求:

      1. 访问一个Servlet,如果是第一次访问,则提示:您好,欢迎您首次登录
      2. 如果不是第一次访问,则提示:欢迎回来,上次您登录的时间是:[时间字符串]
    • 分析:

      • 可以采用Cookie来完成

      • 在Servlet中判断是否有一个名为lastTime的Cookie

        ?说明不是第一次访问

        1. 响应数据:欢迎回来,您上次访问的事件是:[lastTime的时间]
        2. 写回Cookie:lastTime=[当前时间]

        没有?说明是第一次访问

        1. 响应数据:您好,欢迎您首次登录
        2. 写回Cookie:lastTime=[当前时间]

Session

概念
  • 服务器端会话技术,在一次会话的多次请求间共享数据,将数据保存在服务器端的对象中:HttpSession
快速入门

获取HttpSession对象:HttpServletRequest实现类调用HttpSession getSession()返回此对象

使用HttpSession对象:3个方法:

  1. Object getAttribute(Stirng name)返回与此会话中的指定name绑定在一起的对象,没有则返回null
  2. void setAttribute(String name,Object value)使用指定namevalue对象绑定到此会话,有则替换
  3. void removeAttribute(String name) 从此会话中移除与指定name绑定在一起的对象
原理
  • Session的实现依赖于Cookie,其实就是一个Cookie请求头/响应头字段:JSESSIONID

image-20210805021115545

Session的细节
  1. 客户端关闭后,服务器不关闭,两次获取的Session是同一个吗?

    • 默认情况下不是同一个,客户端关闭后Session就失效了
    • 可以通过手动指定Cookie的字段:JSESSIONIDsession.getId()然后设置Cookie的存活时间
  2. 客户端不关闭,服务器关闭后,两次获取的Session是同一个吗?

    • 不是同一个,但是要确保数据不丢失:

      Tomcat会自动序列化/反序列化,但IDEA活化会删除work目录导致活化失败,建议部署到Tomcat

      • session的钝化:在服务器正常关闭之前,把session对象序列化到硬盘上
      • session的活化:在服务器启动后,将序列化的session文件反序列化到内存,还原JSESSIONID
  3. Session的失效时间?

    • 服务器关闭
    • session对象调用invalidate()
    • session的默认失效时间:30分钟,在Tomcat配置文件web.xml中配置<session-config>的超时时间
Session的特点
  1. session用于存储一次会话的多次请求的数据,存储在服务器端
  2. session可以存储任意类型、任意大小的数据

Session和Cookie的区别:

  1. session存储数据在服务器端,而Cookie在客户端
  2. session存储数据没有大小限制,而Cookie有
  3. session数据安全,Cookie相对不安全

验证码案例——Session

  • 需求:

    1. 访问带有验证码的登录界面login.jsp
    2. 用户输入用户名、密码以及验证码
      • 如果用户名和密码输入有误,则跳转到登录页面,提示:用户名或密码错误
      • 如果验证码输入有误,则跳转到登录页面,提示:验证码错误
      • 如果全部输入正确,则跳转到success.jsp,显示:[用户名],欢迎您
  • 分析:

    image-20210805024528927

JSP

JSP是 Java Server Pages (Java服务器端页面)的缩写

概念

  • 可以理解为一个特殊的页面,其中既可以直接定义HTML标签,又可以定义Java代码
  • 可以用于简化书写

原理:

JSP本质上就是一个Servlet

  • image-20210805000518670 * JSP编译之后的类继承于`HttpJspBase`,而`HttpJspBase`继承于`HttpServlet`
    • JSP的脚本:JSP定义Java代码的方式

      1. <% Java代码 %>:定义的Java代码在service()方法中,可以定义service()方法中的所有内容
      2. <%! Java代码 %>:只能定义成员变量和成员方法
      3. <%= Java代码%>:定义的Java代码会输出到页面上,相当于调用out.write(Java代码)

JSP的内置对象

  • 概念:在JSP页面中不需要创建,可以直接使用的对象

  • JSP一共有9个内置对象,都可以通过pageContext对象来获取: image-20210805144208603

    image-20210805144128175

    1. requestimage-20210805143123203

    2. responseimage-20210805143143363

    3. out:一个JspWriter
      字符输出流对象,可以将数据输出到页面上 image-20210805002755326

      JspWriterout对象和response.getWriter()对象的区别

      • JspWriterout对象定义在什么位置就输出内容到什么位置

      • response.getWriter()是一个PrintWriter对象(继承于Writer),它的输出总会在页面最前面

        原因:Tomcat服务器作出响应之前会先找response.getWriter()的缓冲区数据,再找out
        对象的缓冲区数据,所以导致response.getWriter()输出的内容永远在out对象之前

        总结:在JSP中,编写的普通的文本内容相当于调用了out.write("文本内容")
        ,这些文本内容会被写入到页面相应的位置,也可以被夹在JSP的两段<%Java代码%>之间

    4. pageContextimage-20210805143232859 一个PageContext对象,只在当前页面有效

    5. applicationimage-20210805143342462 一个ServletContext实现类对象

    6. page:一个 image-20210805143357398 对象

    7. config:一个 image-20210805143419831 实现类对象

    8. session:一个 image-20210805143450080 实现类对象

    9. exceptionimage-20210805143018353

      • 只有当JSP页面声明了isErrorPage="true"才有此内置对象

JSP指令

  • 作用:用于配置JSP页面,导入资源文件

  • 格式:<%@指令名称 属性名1=属性值1 属性名2=属性值2...%>

  • 分类:

    • page:用于配置JSP页面

      • contentType:等同于response.setContentType()。IDEA会自动读取JSP的此属性更改文件编码
        1. 设置响应体的MIME类型以及字符集
        2. 设置当前JSP页面的编码,IDEA会根据此属性自动修改文件编码,否则需手动指定pageEncoding
      • pageEncoding:指定当前JSP文件的编码,建议与contentTypecharset统一
      • import:导入包,和Java中的import效果一样
      • errorPage:当前页面发生异常后,会自动跳转到指定的错误页面
      • isErrorPage:标识当前页面是否是错误页面
        1. 如果为true,则可以调用exception对象输出错误信息
        2. 如果为false,则不可以调用exception对象(默认值)
    • include:用于导入页面的资源文件

      • 例如在一个JSP页面利用@include引入另一个页面,在访问此JSP页面时会先载入引入的页面在上方
    • taglib:用于导入资源,例如导入JSTL

      image-20210805141755489

JSP注释

  • HTML注释:<!-- -->只能注释HTML代码

  • JSP注释:<%-- --%>可以注释HTML代码也可以注释JSP代码,推荐使用

    区别:

    • 使用HTML注释,会把注释也响应给浏览器,只不过浏览器不显示出来
    • 使用JSP注释,在编译时就会忽略JSP注释及其内容

MVC开发模式

JSP演变历史:
  • 早期只有Servlet,只能使用response输出标签数据,非常麻烦
  • 后来有了JSP,简化了Servlet的开发,但是在JSP中既写Java代码,又写HTML标签等造成了难以维护
  • 再后来Java的Web开发借鉴了MVC开发模式,使得程序的设计更加合理
MVC的概念:
  • M:Model,模型:JavaBean
    • 完成具体的业务操作,如:查询数据库、封装对象
  • V:View,视图:JSP
    • 展示数据
  • C:Controller,控制器:Servlet
    • 获取用户的输入
    • 调用Model
    • 将Model返回的数据交给View进行展示

优缺点:

  • 优点
    1. 耦合性低,方便维护,利于分工协作
    2. 重用性高
  • 缺点:
    1. 使得项目架构变得复杂,对开发人员要求高

EL表达式

EL 是 Expression Language 表达式语言的缩写

  • 作用:替换和简化JSP页面中Java代码的编写

  • 语法:${表达式}

  • 注意:

    • JSP默认支持EL表达式
      • 忽略EL表达式
        1. 设置JSP指令@page的属性isELIgnored="true",忽略当前JSP页面所有的EL表达式
        2. \${表达式},忽略\之后的EL表达式
  • 使用:

    • 运算

      • 运算符:
        1. 算术运算符:+-*/(div)%(mod)
        2. 比较运算符:><>=<===!=
        3. 逻辑运算符:&&(and)||(or)!(not)
        4. 空运算符:empty
          • 空运算符用于判断字符串、集合、数组对象是否为null或长度是否为0
          • 可以搭配逻辑运算符使用:
            1. not empty 对象,对empty的判断结果取反
            2. empty 对象1 and empty 对象2,对两个empty判断结果进行逻辑与运算
    • 获取值

      • EL表达式只能从域对象中获取值

      • 语法:

        1. ${域名称.键名}:从指定的域名称中获取指定键的值

          域名称(相当于Map):

          • pageScope –> pageContext

          • requestScope –> request

          • sessionScope –>session

          • applicantScope –>application(ServletContext)

            例如在request域中存储了name=Jerry

            使用${requestScope.name}即可获取Jerry,这相当于${requestScope.get("name")}

            值得注意的是,如果获取的数据不存在域中,不会导致显示null,而是不显示

        2. ${键名}:依次从最小的域中查找是否有该键对应的值,直到查找到为止

          最小的域:按照上方域名称从上往下的顺序即从小到大

        3. 获取对象:

          • ${域名称.键名},返回键名对应的对象的toString()形式

          • ${域名称.键名.属性名},相当于调用get属性名()

            例如${域名称.user.birthday}相当于${域名称.user.getBirthDay()}

            也可以调用自定义的getXxx()方法:${域名称.user.getXxx()}相当于${域名称.user.xxx}

        4. 获取List集合:

          • ${域名称.键名},返回键名对应的List集合,相当于${域名称.get("键名")}
          • ${域名称.键名[索引]},相当于调用键名对应的List集合对象的get(索引),此方法不会越界
        5. 获取Map集合:

          • ${域名称.键名}返回键名对应的Map集合,相当于${域名称.get("键名")}

          • ${域名称.键名.Map的键名},相当于调用键名对应的Map集合对象的get("Map的键名")

            还可以写成${域名称.键名["Map的键名"]},此方法相当于Map集合对象的get("Map的键名")

  • 隐式对象

    • EL表达式中有11个隐式对象

      • pageContext:可以用于获取其他8个内置对象

        用法:${pageContext.request}:获取request内置对象

        案例:可以用于动态获取虚拟目录${pageContext.request.contextPath}

JSTL标签

JSTL 是 JavaServer Pages Standard Tag Library (JSP标准标签库)的缩写

  • 概念:是由Apache组织提供的开源免费JSP标签

  • 作用:用于简化和替换JSP页面上的Java代码

  • 使用步骤:

    1. 导入jar包:taglibs-standard-impl-1.2.5.jartaglibs-standard-spec-1.2.5.jar
    2. 引入标签库:<%@ tablib prefix="前缀名" uri="资源路径"%>
    3. 使用标签
  • 常用的JSTL标签

    • if,相当于Java代码的if语句

      • 格式:<前缀名:if test="boolean表达式的值"></前缀名:if>

      • 用法:必须指定test属性,如果为true,则显示if标签体的内容,否则不显示if标签体的内容

        通常在test属性搭配EL表达式使用

    • choose,相当于Java代码的switch语句

      • 格式:

        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        <c:choose>
        <c:when test="${requestScope.number == 1}">
        <h3>星期一</h3>
        </c:when>
        <c:when test="${requestScope.number == 2}">
        <h3>星期二</h3>
        </c:when>
        <c:otherwise>
        <h3>其他</h3>
        </c:otherwise>
        </c:choose>
      • 用法:在choose标签内的每一个when标签相当于switch语句的每一个caseotherwise相当于switch
        语句的default

        when标签:条件在test属性中指出,通常搭配EL语句

    • foreach,相当于Java代码的for语句

      • 格式:
      • 属性:
        1. begin:开始值,包括
        2. end:结束值,包括
        3. var:临时变量名
        4. step:步长
        5. varStatus:循环的状态对象
          • index:元素在容器中的索引,从0开始
          • count:循环计数器,从1开始
  • JSTL案例:

    • 需求:在request域中有一个存放User对象的List集合,使用JSTL和EL将List集合内容展示到JSP的表格中

三层架构

  1. 界面层(表示层):用户看得到的界面,用户可以通过界面上的组件和服务器交互
  2. 业务逻辑层:处理业务逻辑
  3. 数据访问层:操作数据存储文件

image-20210806001159331

案例:用户信息的列表展示,代码详见day17_case
  • 需求:用户信息的增删改查操作

  • 设计:

    • 技术选型:Servlet + JSP + MySQL + JDBCTemplate + Druid + BeanUtils + Tomcat

    • 数据库设计:user表,包括用户的编号、姓名、性别、年龄、籍贯、QQ、邮箱

      1
      2
      3
      4
      5
      6
      7
      8
      9
      create table user(
      id int primary key auto_increment,
      name varchar(20) not null,
      gender varchar(5),
      age int,
      address varchar(32),
      qq varchar(20),
      email varchar(50)
      )
  • 开发:

    • 环境搭建:
      1. 创建数据库环境
      2. 创建项目,导入需要的jar包
    • coding:

image-20210806011642699

image-20210806155853142

image-20210806162531202

image-20210806165132525

image-20210806184821807

image-20210806211454118

image-20210806213921964

  • 测试
  • 部署运维
基础功能:
  1. 列表查询
  2. 登录
  3. 添加
  4. 删除
  5. 修改
复杂功能:
  1. 删除选中
  2. 分页查询
  3. 复杂条件查询

Filter:过滤器

web中的过滤器:

  • 当访问服务器资源时,过滤器可以将请求拦截下来,完成一些特殊的功能

过滤器的作用:

  • 一般用于完成通用的操作,如:登录验证、统一编码处理、敏感字符过滤

快速入门:

步骤:
  1. 定义一个类,实现接口Filter
  2. 重写接口的方法
    • init()
    • doFilter()
      • 放行请求:filterChain.doFilter(ServletRequest servletRequest,ServletResponse servletResponse)
    • destroy()
  3. 配置拦截路径
过滤器细节:
web.xml配置

类似于Servlet的配置,指定某个filter-nameurl-pattern

image-20210807133551501

过滤器执行流程
  1. 执行过滤器
  2. 执行放行后的资源:chain.doFilter(request,response)
  3. 执行放行之后的代码
过滤器生命周期方法
  • init():服务器开启后即执行,不需要访问都会执行,一般用于加载资源
  • doFilter()每一次请求被拦截的资源时会执行
  • destroy():服务器正常关闭后销毁执行,一般用于释放资源
过滤器配置详解
  • 拦截路径设置:

    1. 具体资源的拦截:/index.jsp,只有访问index.jsp资源时,过滤器才会被执行
    2. 具体目录拦截:/user/*,访问/user下的所有资源时都会被过滤器执行拦截
    3. 后缀名拦截:*.jsp,访问所有JSP资源时都会被过滤器执行拦截
    4. 拦截所有:/*,访问所有资源都会被过滤器执行拦截
  • 拦截方式设置:资源被访问的方式

    1. 注解配置:设定dispatcherTypes属性的值

      可选的属性值

      • REQUEST:浏览器直接请求资源时才会执行此过滤器,默认值
      • FORWARD转发访问资源时才会执行此过滤器
      • INCLUDE:包含访问资源时才会执行此过滤器
      • ERROR:错误跳转资源时才会执行此过滤器
      • ASYNC:异步访问资源时才会执行此过滤器
    2. web.xml配置:在filter-mapping标签里设定dispatcher标签的内容为可选的属性值属性值与注解方式取值一样

过滤器链(配置多个过滤器)
  • 拦截顺序:对于两个过滤器A和B,被拦截和返回是对称的

    1. A执行了
    2. B执行了
    3. 资源被访问到了
    4. B回来了
    5. A回来了
  • 先后顺序

    • 注解配置:按照类名的字符串进行比较,值小的先执行

      例如:AFilterBFilter,显然AFilter小于BFilter,所以AFilter先执行;

      同样的:Filter12小于Filter2,所以Filter12先执行

    • web.xml配置:看<filter-mapping>的位置,定义在web.xml越靠前的越先执行

    • 如果注解配置和web.xml同时配置了过滤器,那么会优先执行web.xml中配置的过滤器

案例1:登录验证(权限控制)

image-20210807174933312

  • 需求:
    1. 访问day17_case案例的资源,验证其是否登录
      • 验证用户是否登录,可以通过Session域中存放的User对象判断,如果有直接放行,如果没有就跳转到登录页面
      • 排除登录页面本身,只关心是否是登录相关的资源才走过滤器,否则直接放行
    2. 如果登录了,则直接放行
    3. 如果没有登录,则跳转到登录页面,提示”您尚未登录,请先登录”
案例2:敏感词汇过滤

image-20210807174853954

  • 需求:

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

    • 需要对request对象进行增强
  • 增强对象的功能:利用设计模式

    • Decorator装饰模式

    • 代理模式

      • 概念:

        1. 真实对象:被代理的对象
        2. 代理对象:
        3. 代理模式:代理对象去代理真实对象,达到增强真实对象功能的目的
      • 实现方式:

        1. 静态代理:在一个类文件描述代理模式
        2. 动态代理:在内存中形成代理类
      • 实现步骤:

        1. 代理对象和真实对象实现相同的接口
        2. 利用java.lang.reflect.Proxy
          类的静态方法:newProxyInstance(ClassLoader loader,Class<?>[] interfaces,InvocationHandler h)
          获取一个代理对象,其中参数:
          • loader是真实对象的ClassLoader类加载器
          • interfaces是真实对象实现的接口的数组,提供ClassLoadergetInterfaces()获取
          • h是代理对象调用任意方法时都会执行的函数式接口,重写invoke(Object proxy,Method method,Object[] args)
            方法即可,此方法中proxy是代理对象,method是代理对象的所有方法,args是执行method所需的参数列表。
        3. 使用代理对象调用方法
        4. 增强方法,增强的方式有:
          • 增强参数列表
          • 增强返回值
          • 增强方法体的执行逻辑

Listener:监听器

web中的监听器:

  • web的三大组件之一

事件监听机制:

  • 事件:一件事情
  • 事件源:事件发生的地方
  • 监听器:一个对象
  • 注册监听:将事件、事件源、监听器绑定在一起。当事件源上发生某个事件之后,执行监听器代码

监听器对象:

  • ServletContextListener:监听ServletContext对象的创建和销毁。此接口的实现接收有关其所属 Web
    应用程序的ServletContext上下文更改的通知。要接收通知事件,必须在 Web 应用程序的部署描述符中配置实现类。

    • 方法:

      1. public void contextInitialized(ServletContextEvent sce)

        ServletContext对象被创建后会调用此方法,在初始化 Web 应用程序中的所有过滤器或 servlet 之前,应该通知所有
        ServletContextListener 关于上下文初始化的信息。

      2. public void contextDestroyed(ServletContextEvent sce)

        ServletContext对象被销毁之前会调用此方法,通知即将关闭ServletContext,在通知所有 ServletContextListener
        上下文销毁之前,所有 servlet 和过滤器都已销毁。

快速入门:

步骤:
  1. 定义一个类,实现ServletContextListener接口

  2. 重写接口的方法

  3. 配置:

    • web.xml配置

      image-20210807215817460

      可以用于读取<context-param>标签内的初始化参数,通过<param-name>来获取<param-value>
      标签内的值,调用ServletContext对象的String getInitParameter(String s)方法传入param-name即可

    • 注解配置: image-20210807215853195
      被此注解标记的类是一个监听器类,等同于web.xml<listener-class>

综合案例:黑马旅游网

需求:

  • 技术选型:

    1. Web层:
      • Servlet:前端控制器
      • HTML:视图层
      • Filter:过滤器
      • BeanUtils:数据封装成JavaBean
      • Jackson:序列化JSON
    2. Service层
      • JavaMail:发送邮件的工具
      • Redis:NOSQL内存数据库做缓存
      • Jedis:操作Redis
    3. DAO层
      • MySQL:数据库持久层
      • Druid:数据库连接池
      • JDBCTemplate:操作数据库的模板工具
  • 数据库:

image-20210812213208092

实现:

1.用户注册:

image-20210812225952027

  • 前端表单提交之前在浏览器利用正则表达式验证:

    • 1.用户名:6到20位的单词字符
    • 2.密码:6到20位的单词字符
    • 3.email:例如123@qq.com
    • 4.姓名:非空
    • 5.手机号:手机号正则
    • 6.出生日期:非空
    • 7.验证码:非空
  • 所有表单项验证通过后提交表单:使用AJAX异步提交到registerUserServlet,参数可以调用表单的serialize()

    • 原因:前端使用的是HTML,不像JSP可以直接从Servlet获取值,只能通过AJAX响应数据
    • 踩坑1:点击提交按钮提交表单数据,但表单提交会导致页面刷新,在submit事件绑定的回调函数内部做逻辑处理时就要
      防止表单提交刷新页面,利用AJAX发送请求完毕后,要使submit回调函数返回false
    • 踩坑2:AJAX返回的数据默认是字符串,要么在AJAX发送请求指定参数为’json’,要么在后端指定响应头
    • 踩坑踩大坑!!疯狂乱码,跟过滤器设置响应头有关,响应头全部当做text/html处理会乱码
  • 邮件激活:

    • 发送邮件
      • 开启发件邮箱的POP3/SMTP服务,发送一条带有激活链接的邮件,链接到activeUserServlet
        • 踩坑:MailUtil工具类默认是qq邮箱作为发件箱,需要自己灵活修改
      • 激活邮件中为了区分是哪个用户点击的,需要给激活链接一个唯一校验码,用UuidUtil工具类生成
      • 激活链接需要把UuidUtil工具类生成的校验码设置为激活链接的请求参数,以供服务器比对数据库
    • 用户点击邮件激活
      • 用户点击激活链接之后发送请求到activeCodeServlet进行校验,请求参数为code校验码
        • 如果存在此校验码的用户,则修改tab_user表的status字段为Y,并返回激活成功提示
        • 如果不存在此校验码的用户,则返回一个错误提示

2.用户登录

image-20210813145133785

  • 前端:
    • 给表单绑定submit事件,点击登录按钮发送AJAX请求到loginServlet,根据返回的JSON判断是否成功登录
  • 后端:
    • 接收前端的请求参数,封装为User对象,传递给service层查询
    • 根据查询的结果,判断是否存在此User对象在数据库中
      • 如果存在此User,判断User对象的status是否激活
        • 如果没有激活,则封装ResultInfo一个错误提示
        • 如果已经激活,则封装ResultInfo一个成功信息,并且把登录的User对象存入Session中
      • 如果不存在此User,封装ResultInfo对象一个错误提示
    • 把ResultInfo对象转为JSON字符串并且返回给前端,记得设置响应头为JSON格式

登录成功之后的用户名显示:

  • 登录成功之后页面加载完成就发送AJAX请求,取出Seesion中的User对象,显示到前端对应的文本中

3.用户退出登录

  • 前端:
    • 点击退出链接访问exitServlet
  • 后端:
    • 销毁存放了User对象的Session,可以removeAttribute()也可以invalidate()使所有的session失效
    • 跳转到登录页面

优化Servlet

  • 背景原因:对于前端请求的每一个功能都需要对应的Servlet进行接收和响应,造成大量Servlet的编写
  • 优化方式:把对同一个数据库的操作的Servlet抽象为一个Servlet,前端请求的每一个功能封装为每一个方法
image-20210813173209744
  • 在封装的BaseServlet中获取request请求对象的URI,提取出访问的具体方法method
  • 获取调用此service()方法的具体的BaseServlet子类对象,调用this.getClass()利用反射执行这个method
    • 因为

4.旅游分类信息导航栏

image-20210813230628575

  • 前端:
    • 发送AJAX请求,加载分类导航栏的数据
    • 请求到后端的所有分类导航栏JSON数据后,遍历数据显示到页面上
  • 后端
    • 查询数据库中分类导航栏tab_category表中所有的数据,可以利用order by子句按照cid排序查询结果
    • 封装为List集合,转为JSON返回给前端

5.对分类信息导航栏的查询进行缓存优化

  • 原因:由于分类信息导航栏不会经常产生变化,每一次header.html
    加载完毕都会加载分类信息导航栏,大量查询数据库,适合使用Redis进行缓存
  • 实现:在Servlet调用的ServiceImpl实现类中对findAll()方法进行优化
    • 判断分类信息导航栏集合是否为空
      • 如果为空,说明是第一次查询,则查询数据库,然后写入Redis缓存
      • 如果不为空,说明不是第一次查询,直接返回Redis的结果
  • 踩坑
    • Jedis死活连接不上,配置文件也没有问题,结果是忘了开启Redis服务器端了…
    • UTF-8编码存入Redis的内容,在windows控制台下默认GBK查看会乱码,先利用chcp 65001切换编码

6.旅游线路的分类展示

  • 首先要保证前端可以区分每一个分类导航栏中的按钮链接:也就是每一个分类按钮指向的链接的请求参数可以为这个分类的cid
    ,在第4步拼接这些分类标签时的循环中就要处理好每一个a标签的href中的参数cid

  • 怎么让每一个分类的HTML静态页面能发送自己的cid到后端查询内容?

    • 由于点击不同分类的链接,跳转到统一的route_list.html页面,唯一能区分的就是请求参数,所以:
      • 通过此route_list.html页面的location对象的search属性返回此页面URL?及之后的请求参数
      • 然后AJAX发送这个?之后的请求参数(也就是此页面的cid)查询数据
  • 前端:

    • AJAX发送请求,携带参数:
      • 要查询的路线cidcid的值
      • 要查询的分页的页码currentPage
      • 每一页显示的条数pageSize
  • 后端:

    • 接收前端请求的参数:cid、当前页码currentPage、每一页的条数pageSize
    • 调用service层查询此cid对应的数据,以及按照当前页码和每一页的条数limit查询
    • 查询的结果封装为PageBean<T>对象,序列化为JSON返回给前端

7.旅游线路分类的分页

  • 前端:根据JSON的数据动态拼接HTML标签,主要是ul标签中的li标签数量和内容要根据查询出的此路线分类的分页数量动态拼接
    • 拼接标签要注意,为了能保证标签中的链接也是异步请求,不能直接href发送请求,要把href
      的超链接设置为javascript:function的形式
      • 例如<a href="javascript:load(要请求的cid,要请求的页码)"></a>
        ,这里的cid就是此页面的分类,请求的页码就是此标签要链接到的请求的currentPage,此load(cid,currentPage)是异步的
    • 可以把load(cid,currentPage)方法抽取出来,方便不同的链接传入参数异步请求不同的分类及其分页
  • 后端:接收前端的请求参数:cidcurrentPage(和pageSize),查询数据库并返回结果
    • 包装成PageBean对象,并序列化为JSON返回

8.分页按钮优化显示

  • 由于一个分类可能包含若干个分页,那么导致分页按钮过多,需要根据目前所在的页码动态调整分页按钮数量

  • 前端:分页按钮的起始索引和结束索引都保持他么之间最多相差10个按钮

    • 定义两个变量当做索引:起始的begin和一个结束的end

    • 如果此分类的总分页数量不超过10页,那么就直接显示这些分页按钮:begin=1end=10

    • 如果此分类的总分页数量超过10页,那么就按照前面5个按钮,后面4个按钮,当前分页按钮位于第6个

      • begin = currentPage -5end = currentPage + 4

      • 处理最前面和最后面的情况:

        • 如果currentPage < 5就显示前10页的结果:begin =1end =10

        • 如果currentPage > 总分页数量 - 4就显示最后10页的结果:

          begin = 总分页数量 - 9end = 总分页数量

    • 经过以上处理就可以保证当前页面按钮在第6个的位置,除非总记录数不足10条或者:

      • 当前页码不足6,则只显示前10个分页
      • 当前页码在最后4条以内,则只显示最后10个分页
    • 另外,如果每一次分页之后都想让页面回到顶端,可以在此函数内执行:window.scrollTo(0,0)

9.旅游线路模糊搜索

  • 前端:

    • 为搜索按钮绑定单击事件,如果单击就获取搜索输入框的值作为参数跳转到相应的搜索结果页面
      • 跳转:修改location对象的href属性:location.href = "要跳转的url?传递的参数列表"
    • 搜索结果页面也就是route_list.html,只不过这次查询的参数除了cid之外还要限制为rname
      • 在这个页面接收location地址栏的参数,获取cidrname
    • 前端对load(cid,currentPage)方法添加一个参数:load(cid,currentPage,rname)
    • 获取当前页面实际的rname,利用window.decodeURI解码,然后修改所有按钮的load
      方法为新的load方法,即传入cidcurrentPagername
  • 后端:接收前端传递的参数cidrname,编写SQL进行模糊查询,并返回结果

    • 注意判断rname是否为空或是否为""空字符串

      • 踩坑1

        1. rname还有可能为”null“这个字符串,因为JavaScript中:这个拼接操作会导致
        image-20210814213435643

        被解码的currentRname可能为null对象的情况,被第二条语句和''拼接就成了字符串的'null'

      • 踩坑2cid也有可能为"null"这个字符串的值,没有选择分类时直接搜索就会导致cid
        "null

    • 可以在dao层利用条件查询的where 1 = 1拼接and 字段名 = 字段值
      来拼接SQL,然后把参数按实际是否存在的情况装入集合调用toArray()进行JDBCTemplate的查询

    • 最后把查询到的结果封装为PageBean对象,序列化为JSON返回给前端

10.旅游路线的查看详情

image-20210815013209415

image-20210815013246676

  • 前端:点击每一个路线的<a>查看详情</a>标签,会被链接到此旅游路线的详情页面:route_detail.html
    • 区分点击的是哪个详情页面:为链接的URL传递参数:rid=每条路线的rid
    • route_detail.html页面加载完成后发送AJAX请求,参数为rid,查询此旅游路线的数据
    • 接收后端返回的此旅游路线的JSON数据,完成展示:
      • 遍历JSON数据,拼接相应的数据为HTML标签,填入页面
      • 对于小图预览图,由于已经有写好的JavaScript控制样式,只需要注意超过4张小图就隐藏后面的
        • 隐藏:即设置此图片所在的<a></a>标签的styledisplay:none
  • 后端:接收前端发送的rid,查询数据库中此路线的数据
    • 首先要查出请求的rid旅游路线在tab_route中的路线详情,封装为Route对象
    • 再从tab_img表查询出此rid对应的图片数据,封装为List<RouteImg>集合,设置为Route对象的成员
    • 最后从tab_seller表查询出此rid的路线对应的商家详情,封装为Seller对象,同样的设置为成员
    • 可以再查询出此Route对象所属的旅游路线分类,但是此页面不要求做展示,没有必要
    • 最后把封装好的Route对象序列化为JSON返回给前端

11.点击收藏按钮或取消收藏按钮的判断

image-20210815160048822

  • 前端:
    • 发送AJAX请求,参数传递此旅游路线的rid
      • 发送AJAX的时机要等到这个页面加载完毕并且此商品的详细信息加载完毕,否则修改按钮样式可能还没有经过第[10]
        步的查看详情遍历显示完,自然也就获取不到标签对象修改不了值了
      • 所以要编写一个AJAX方法,调用方法的时机在loadImg()方法里的最后,也就是上一步加载完毕
    • 接收后端返回的结果,根据结果显示按钮 点击收藏取消收藏
      • 返回的结果为true就给$(#favorite)标签添加已收藏样式和不可点击,否则false显示默认样式
  • 后端:
    • 接收前端的旅游路线rid参数,并且获取session中的User用户对象,去tab_favorite表中查询路线与用户是否存在此对应记录判断是否收藏,返回一个是否收藏的结果的布尔值

12.收藏次数

  • 前端:不需要做调整,只需要对返回的Route旅游路线对象的count属性进行展示就行了

后端:在查询路线详情service层封装Route对象的时候额外再去调用dao层利用聚合函数统计一下此旅游路线被收藏的数量,然后设置Route对象的count
属性就行了

13.点击收藏功能的实现

image-20210815224049640

  • 前端:发送AJAX请求,判断用户是否登录
    • 已经登录:
      • 发送AJAX请求,传递要收藏的旅游路线的rid即可,User对象不需要传递,因为session对象里有
      • 收藏写入数据库后不需要返回值,但是前端此时应该刷新一下页面改变”点击收藏”为”取消收藏”
    • 没有登录:弹框提示用户尚未登录,然后跳转到登录页面
  • 后端:
    • 接收前端的参数,要收藏的路线的rid、当前登录的用户的uid
    • 调用service层添加收藏记录