JavaWeb 深入3

360 阅读18分钟

文件的下载和上传

文件下载时,中文名乱码问题

  • 通过请求头中的 User-Agent属性获取浏览器的类型
  • 使用 URLEncoder或 BASE64为中文名编码,解决中文文件下载乱码问题
package com.atjava.servlet;

import org.apache.commons.io.IOUtils;
import sun.misc.BASE64Encoder;

import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.URLEncoder;

/**
 * @author lv
 * @create 2021-08-31 21:56
 */
public class DownloadServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        // super.doGet(req, resp);
        // 1.获取要下载的文件名
        String downLoadFileName = "2.jpg";

        // 2.读取要下载的文件内容(通过 ServletContent对象读取)
        ServletContext servletContext = getServletContext();
        /**
         * "/"斜杠被服务器解析表示地址为 http://ip:port/工程名/ 映射到代码的 web目录
         */
        InputStream resourceAsStream = servletContext.getResourceAsStream("/file/" + downLoadFileName);

        // 4.在回传前,通过响应头告诉客户端返回的数据类型
        // 获取要返回的文件类型:image/jpeg = 大类型/小类型
        String mimeType = servletContext.getMimeType("/file/" + downLoadFileName);
        resp.setContentType(mimeType);
        // 5.同时要告诉客户端收到的数据是用于下载的(设置响应头)
        // attachment 附件:表示下载使用
        // Content-Disposition 响应头属性:表示客户端收到的内容如何处理
        // filename 文件名:表示要下载的文件名,可以与源文件名不同,自定义下载的文件名
        // resp.setHeader("Content-Disposition", "attachment; filename=22.jpg");
        // URLEncoder.encode("中文.xxx", "UTF-8"):是将内容转换为 %xx%xx的格式;(URLEncoder编码:chrome、IE适用)
        /**
         * 文件名中出现中文,会出现乱码问题,http没有考虑中文情况,默认不支持中文
         * 解决:需要对中文进行编码,才能在网络中传输,
         * 对文件名进行 url编码:URLEncoder.encode("中文.xxx", "UTF-8");
         */
        // 判断当前客户端(浏览器)的类型 User-Agent:Chrome、IE、Firefox
        if (req.getHeader("User-Agent").contains("Firefox")) {
            resp.setHeader("Content-Disposition", "attachment; filename==?UTF-8?B?" + new BASE64Encoder().encode("中文乱码.jpg".getBytes("UTF-8")) + "?=");
        } else {
            resp.setHeader("Content-Disposition", "attachment; filename=" + URLEncoder.encode(downLoadFileName, "UTF-8"));
        }

        /**
         * fireFox适用的编码:BASE64Encoder
         */
//        String fileName = "中文乱码";
//        BASE64Encoder base64Encoder = new BASE64Encoder();
//        String encodeStr = base64Encoder.encode(fileName.getBytes("UTF-8"));
//        System.out.println(encodeStr);

        // 3.将下载的文件内容回传给客户端
        // 获取响应的输出流
        OutputStream outputStream = resp.getOutputStream();
        // copy(): 读取 resourceAsStream中的数据,复制给 outputStream流,输出给客户端
        IOUtils.copy(resourceAsStream, outputStream);

    }
}

书城项目阶段三

页面 jsp动态化

  1. 在 html页面顶行添加 page指令
  2. 修改文件后缀名为:.jsp
  3. 使用 IDEA搜索替换 .html为 .jsp(快捷键 ctrl+r / ctrl+shift+r)

抽取页面中相同的内容

  • footer.jsp
  • head.jsp
  • login_success_menu.jsp
  • manager_menu.jsp

实例:

  • common/head.jsp
<%--
  Created by IntelliJ IDEA.
  User: Weili
  Date: 2021/9/6
  Time: 21:43
  To change this template use File | Settings | File Templates.
--%>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>

<%
    String basePath = request.getScheme()
        + "://"
        + request.getServerName()
        + ":"
        + request.getServerPort()
        + request.getContextPath()
        + "/";
%>
<%--<%=basePath%> http://192.168.0.106/ --%>
<base href="<%=basePath%>" />
<link type="text/css" rel="stylesheet" href="static/css/style.css" >
<script type="text/javascript" src="static/script/jquery-1.7.2.js"></script>
  • login.jsp
<head>
<meta charset="UTF-8">
<title>尚硅谷会员登录页面</title>

   <%-- 使用静态包含替换,包含 base标签、css样式、jQuery文件 --%>
   <%@ include file="/pages/common/head.jsp" %>
   
   <%--<base href="http://localhost:8080/book_project/" />--%>
   <%--<link type="text/css" rel="stylesheet" href="static/css/style.css" >--%>
   <%--<script type="text/javascript" src="static/script/jquery-1.7.2.js"></script>--%>
</head>

登录、注册错误提示,表单回显

  • 使用 request域回显需要的信息
  1. RegistServlet.java
package com.atjava.web;

import com.atjava.pojo.User;
import com.atjava.service.UserService;
import com.atjava.service.impl.UserServiceImpl;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

/**
 * @author lv
 * @create 2021-08-07 22:21
 */
public class RegistServlet extends HttpServlet {

    private UserService us = new UserServiceImpl();
    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        // super.doPost(req, resp);
        // 1.获取请求参数
        String username = req.getParameter("username");
        String password = req.getParameter("password");
        String email = req.getParameter("email");
        String code = req.getParameter("code");
        // 2.检察验证码是否正确 - 当前写死 12345
        if ("12345".equalsIgnoreCase(code)) {
            System.out.println("code is right");

            // 3.检察用户名是否可用
            boolean existsUsername = us.existsUsername(username);
            if (existsUsername) {
                System.out.println("用户名已存在");
                req.setAttribute("msg", "用户名已存在!!!");
                req.setAttribute("username", username);
                // req.setAttribute("password", password);
                req.setAttribute("email", email);
                req.getRequestDispatcher("/pages/user/regist.jsp").forward(req, resp);
            } else {
                System.out.println("用户名可注册");
                us.registUser(new User(null, username, password, email));
                req.getRequestDispatcher("/pages/user/regist_success.jsp").forward(req, resp);
            }
        } else {
            System.out.println("code[ " + code + " ] is error");
            // 跳回注册页面
            // 请求转发,必须以 ‘/’开始,并且默认在 "web"目录下
            // 将回显信息保存到 request域中
            req.setAttribute("msg", "验证码错误!!!");
            req.setAttribute("username", username);
            req.setAttribute("email", email);

            req.getRequestDispatcher("/pages/user/regist.jsp").forward(req, resp);
        }


    }
}
  1. regist.jsp
<div class="tit">
   <h1>注册尚硅谷会员</h1>
   <span class="errorMsg"><%=request.getAttribute("msg")==null ? "" : request.getAttribute("msg")%></span>
</div>

<label>用户名称:</label>
<input class="itxt" type="text" placeholder="请输入用户名" autocomplete="off"
      tabindex="1" name="username" id="username"
      value="<%=request.getAttribute("username")==null ? "" : request.getAttribute("username")%>"
/>
<br />
<label>电子邮件:</label>
<input class="itxt" type="text" placeholder="请输入邮箱地址" autocomplete="off"
      tabindex="1" name="email" id="email"
      value="<%=request.getAttribute("email")==null ? "" : request.getAttribute("email")%>"
/>

BaseServlet 通过功能的抽取

在实际的项目开发中,一个模块,一般只使用一个 Servlet程序

UserServlet.java

  • 合并 login和 regist的 Servlet程序
package com.atjava.web;

import com.atjava.pojo.User;
import com.atjava.service.UserService;
import com.atjava.service.impl.UserServiceImpl;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

/**
 * @author lv
 * @create 2021-09-07 22:24
 */
public class UserServlet extends HttpServlet {
    private UserService us = new UserServiceImpl();

    /**
     * 处理登录功能
     * @param req
     * @param resp
     * @throws ServletException
     * @throws IOException
     */
    protected void login (HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        String username = req.getParameter("username");
        String password = req.getParameter("password");
        User user = us.login(new User(null, username, password, null));
        if (null != user) {
            System.out.println(user);
            req.getRequestDispatcher("/pages/user/login_success.jsp").forward(req, resp);
        } else {
            req.setAttribute("msg", "用户名或密码错误");
            req.setAttribute("username", username);
            req.getRequestDispatcher("/pages/user/login.jsp").forward(req, resp);
        }
    }

    /**
     * 处理注册功能
     * @param req
     * @param resp
     * @throws ServletException
     * @throws IOException
     */
    protected void regist (HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        // 1.获取请求参数
        String username = req.getParameter("username");
        String password = req.getParameter("password");
        String email = req.getParameter("email");
        String code = req.getParameter("code");
        // 2.检察验证码是否正确 - 当前写死 12345
        if ("12345".equalsIgnoreCase(code)) {
            System.out.println("code is right");

            // 3.检察用户名是否可用
            boolean existsUsername = us.existsUsername(username);
            if (existsUsername) {
                System.out.println("用户名已存在");
                req.setAttribute("msg", "用户名已存在!!!");
                req.setAttribute("username", username);
                // req.setAttribute("password", password);
                req.setAttribute("email", email);
                req.getRequestDispatcher("/pages/user/regist.jsp").forward(req, resp);
            } else {
                System.out.println("用户名可注册");
                us.registUser(new User(null, username, password, email));
                req.getRequestDispatcher("/pages/user/regist_success.jsp").forward(req, resp);
            }
        } else {
            System.out.println("code[ " + code + " ] is error");
            // 跳回注册页面
            // 请求转发,必须以 ‘/’开始,并且默认在 "web"目录下
            // 将回显信息保存到 request域中
            req.setAttribute("msg", "验证码错误!!!");
            req.setAttribute("username", username);
            req.setAttribute("email", email);

            req.getRequestDispatcher("/pages/user/regist.jsp").forward(req, resp);
        }
    }
        @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        // super.doPost(req, resp);
        String action = req.getParameter("action");
        if ("login".equals(action)) {
            System.out.println("login");
            login(req, resp);
        } else if ("regist".equals(action)) {
            System.out.println("regist");
            regist(req, resp);
        }
    }
}

优化:

  • UserServlet.java 优化,使用反射避免过多的 if...else...判断
  • 方式二:反射
    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        // super.doPost(req, resp);
        String action = req.getParameter("action");

        // 方式一:if...else... 判断
//        if ("login".equals(action)) {
//            System.out.println("login");
//            login(req, resp);
//        } else if ("regist".equals(action)) {
//            System.out.println("regist");
//            regist(req, resp);
//        }

        // 方式二:反射
        try {
            // 1. 通过 反射获取要调用的 方法的对象 method
//            Method method = UserServlet.class.getDeclaredMethod(action, HttpServletRequest.class, HttpServletResponse.class);
            Method method = this.getClass().getDeclaredMethod(action, HttpServletRequest.class, HttpServletResponse.class);

            // 2. 通过方法对象调用方法本身
            method.invoke(this, req, resp);
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        }
    }

BaseServlet.java

  1. 获取 action参数值
  2. 通过反射获取 action对应的业务方法
  3. 通过反射调用业务方法
  • BaseServlet.java,其它 Servlet 继承此 抽象类
package com.atjava.web;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

/**
 * @author lv
 * @create 2021-09-08 22:44
 */
public abstract class BaseServlet extends HttpServlet {
    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        // super.doPost(req, resp);

        String action = req.getParameter("action");

        // 方式一:if...else... 判断
//        if ("login".equals(action)) {
//            System.out.println("login");
//            login(req, resp);
//        } else if ("regist".equals(action)) {
//            System.out.println("regist");
//            regist(req, resp);
//        }

        // 方式二:反射
        try {
            // 1. 通过 反射获取要调用的 方法的对象 method
//            Method method = UserServlet.class.getDeclaredMethod(action, HttpServletRequest.class, HttpServletResponse.class);
            Method method = this.getClass().getDeclaredMethod(action, HttpServletRequest.class, HttpServletResponse.class);

            // 2. 通过方法对象调用方法本身
            method.invoke(this, req, resp);
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        }
    }
}
  • UserServlet.java 改为继承 BaseServlet抽象类
//public class UserServlet extends HttpServlet {

public class UserServlet extends BaseServlet {
    // ...
}

数据的封装和抽取 BeanUtils的使用

BeanUtils工具类,它可以一次性的把所有请求的参数注入到 JavaBean 中。

BeanUtils 它不是 jdk的类,而是第三方的工具类,需要导包。

  • 导入需要的包
  1. commons-beanutils-1.8.0.jar
  2. commons-logging-1.1.1.jar
  • 使用 BeanUtils 方法实现注入
  1. 获取 username后,BeanUtils通过 username设置对应的 setUsername(username)方法来赋值
  2. username 和 bean中的 setUsername要对应,否则赋值失败
  • WebUtils.java (通用工具类)
  1. 泛型方法 public static <T> T copyParamToBean (Map value, T bean) {}
package com.atjava.utils;

import org.apache.commons.beanutils.BeanUtils;

import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.InvocationTargetException;
import java.util.Map;

/**
 * @author lv
 * @create 2021-09-09 22:09
 */
public class WebUtils {

    /**
     * 1.将 HttpServletRequest 改为 Map,代码的 耦合度更低、适用性更广、扩展性更强、使用更灵活
     
     * 2.使用泛型方法,省略类型转换
     
     * @param value
     * @param bean
     */
//    public static void copyParamToBean (HttpServletRequest req, Object bean) {
//    public static Object copyParamToBean (Map value, Object bean) {
    public static <T> T copyParamToBean (Map value, T bean) {
        try {
            BeanUtils.populate(bean, value);
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        }
        return bean;
    }
}
  • UserServlet.java

    /**
     * 处理注册功能
     * @param req
     * @param resp
     * @throws ServletException
     * @throws IOException
     */
    protected void regist (HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        // 1.获取请求参数
        String username = req.getParameter("username");
        String password = req.getParameter("password");
        String email = req.getParameter("email");
        String code = req.getParameter("code");

        // ctrl + alt + t
        // 方式一:使用 BeanUtils对 JavaBean赋值
//        try {
//            User user = new User();
//            /**
//             * 将所有请求的参数都注入到 user对象中
//             */
//            BeanUtils.populate(user, req.getParameterMap());
//            System.out.println("user:" + user);
//        } catch (IllegalAccessException e) {
//            e.printStackTrace();
//        } catch (InvocationTargetException e) {
//            e.printStackTrace();

        // 方式二:使用 BeanUtils 封装成统一的方法,JavaBean赋值
        Map<String, String[]> parameterMap = req.getParameterMap();
        for (Map.Entry<String, String[]> entry : parameterMap.entrySet()) {
            System.out.println(entry.getKey() + " = " + Arrays.asList(entry.getValue()));
        }
        /**
         * action = [regist]
         * username = [liuwei192]
         * ...
         */

        // User user = new User();
        // 使用泛型方法,省略类型转换
        // User user = (User) WebUtils.copyParamToBean(req.getParameterMap(), new User());
        User user = WebUtils.copyParamToBean(req.getParameterMap(), new User());

//        }

        // 2.检察验证码是否正确 - 当前写死 12345
        if ("12345".equalsIgnoreCase(code)) {
            System.out.println("code is right");

            // 3.检察用户名是否可用
            boolean existsUsername = us.existsUsername(username);
            if (existsUsername) {
                System.out.println("用户名已存在");
                req.setAttribute("msg", "用户名已存在!!!");
                req.setAttribute("username", username);
                // req.setAttribute("password", password);
                req.setAttribute("email", email);
                req.getRequestDispatcher("/pages/user/regist.jsp").forward(req, resp);
            } else {
                System.out.println("用户名可注册");
                us.registUser(new User(null, username, password, email));
                req.getRequestDispatcher("/pages/user/regist_success.jsp").forward(req, resp);
            }
        } else {
            System.out.println("code[ " + code + " ] is error");
            // 跳回注册页面
            // 请求转发,必须以 ‘/’开始,并且默认在 "web"目录下
            // 将回显信息保存到 request域中
            req.setAttribute("msg", "验证码错误!!!");
            req.setAttribute("username", username);
            req.setAttribute("email", email);

            req.getRequestDispatcher("/pages/user/regist.jsp").forward(req, resp);
        }
    }

书城-第四阶段:使用 EL 表达式修改表单回显

在实际开发中都是使用 EL表达式来替代 表达式脚本。

  • 表达式方式:<%=...%>
  • EL表达式方式:${...}
<div class="tit">
   <h1>注册惠特米勒会员</h1>
   <span class="errorMsg">
   
      // 表达式方式:
      <%=request.getAttribute("msg")==null ? "" : request.getAttribute("msg")%>
      
      // EL表达式方式:等价于上边
      ${ requestScope.msg }
   </span>
</div>

<label>用户名称:</label>
<input class="itxt" type="text" placeholder="请输入用户名" autocomplete="off"
      tabindex="1" name="username" id="username"
      
      // 表达式方式:
      value="<%=request.getAttribute("username")==null ? "" : request.getAttribute("username")%>"
      
      // EL 表达式方式:
      value="${requestScope.username}"
/>

第五阶段:

MVC概念:

  • MVC 全称:Model模型、View视图、Controller控制器
  • MVC 最早出现在 JavaEE三层中的 Web层,它可以有效的指导 Web层的代码如何有效分离,单独工作。
  • View 视图:只负责数据和界面的展示,不接受任何与显示数据无关的代码,便于程序员与美工的分工合作 -- JSP/HTML。
  • Controller 控制器:只负责接收请求,调用业务代码处理请求,然后派发页面,是一个“调度者”的角色 -- Servlet;转到某个页面,或者重定向到某个页面。
  • Model 模型:将与业务逻辑相关的数据封装为具体的 JavaBean类,其中不掺杂任何与数据处理相关的代码 -- JavaBean/domain/entity/pojo。

MVC 是一种思想

  • MVC 的理念是将软件代码拆分为 组件,单独开发,组合使用
  • 目的是为了降低耦合度,让代码合理分层,方便后期升级和维护

书城-第五阶段

图书模块:

代码编写的顺序基本如下:

编写图书模块的数据库表

CREATE TABLE t_book (
	`id` INT PRIMARY KEY auto_increment,
	`name` VARCHAR(100),
	`price` DECIMAL(11,2),
	`author` VARCHAR(100),
	`sales` INT,
	`stock` INT,
	`img_path` VARCHAR(200)
);

## 插入初始化测试数据
insert into t_book(`id` , `name` , `author` , `price` , `sales` , `stock` , `img_path`) 
values(null , 'java从入门到放弃' , '国哥' , 80 , 9999 , 9 , 'static/img/default.jpg');

insert into t_book(`id` , `name` , `author` , `price` , `sales` , `stock` , `img_path`) 
values(null , '数据结构与算法' , '严敏君' , 78.5 , 6 , 13 , 'static/img/default.jpg');

编写图书模块的 JavaBean

package com.atjava.pojo;

import java.math.BigDecimal;

/**
 * @author lv
 * @create 2021-09-11 16:22
 */
public class Book {

    private Integer id;
    private String name;
    private String author;
    private BigDecimal price;
    private Integer sales;
    private Integer stock;
    private String imgPath = "static/img/default.jpg";

    public Book () {
    }

    public Book (Integer id, String name, String author, BigDecimal price, Integer sales, Integer stock, String imgPath) {
        this.id = id;
        this.name = name;
        this.author = author;
        this.price = price;
        this.sales = sales;
        this.stock = stock;
        // 要求图片路径不能为空
        if (imgPath != null && !"".equals(imgPath)) {
            this.imgPath = imgPath;
        }
    }

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getAuthor() {
        return author;
    }

    public void setAuthor(String author) {
        this.author = author;
    }

    public BigDecimal getPrice() {
        return price;
    }

    public void setPrice(BigDecimal price) {
        this.price = price;
    }

    public Integer getSales() {
        return sales;
    }

    public void setSales(Integer sales) {
        this.sales = sales;
    }

    public Integer getStock() {
        return stock;
    }

    public void setStock(Integer stock) {
        this.stock = stock;
    }

    public String getImgPath() {
        return imgPath;
    }

    public void setImgPath(String imgPath) {
        // 要求图片路径不能为空
        if (imgPath != null && !"".equals(imgPath)) {
            this.imgPath = imgPath;
        }
    }

    @Override
    public String toString() {
        return "Book{" +
                "id=" + id +
                ", name='" + name + ''' +
                ", author='" + author + ''' +
                ", price=" + price +
                ", sales=" + sales +
                ", stock=" + stock +
                ", imgPath='" + imgPath + ''' +
                '}';
    }
}

编写图书模块的 Dao 和测试 Dao

  • 生成测试类:(在接口中)ctrl + shift + t

  • BookDaoImpl.java

package com.atjava.dao.impl;

import com.atjava.dao.BaseDao;
import com.atjava.dao.BookDao;
import com.atjava.pojo.Book;

import java.util.List;

/**
 * @author lv
 * @create 2021-09-11 16:41
 */
public class BookDaoImpl extends BaseDao implements BookDao {
    @Override
    public int addBook(Book book) {
        String sql = "insert into t_book (name, author, price, sales, stock, img_path) values(?, ?, ?, ?, ?, ?)";

        return updates(sql, book.getName(), book.getAuthor(), book.getPrice(), book.getSales(), book.getStock(), book.getImgPath());
    }

    @Override
    public int deleteBookById(Integer id) {
        String sql = "delete from t_book where id = ?";
        return updates(sql, id);
    }

    @Override
    public int updateBook(Book book) {
        String sql = "update t_book set name = ?, author = ?, price = ?, sales = ?, stock = ?, img_path = ? where id = ?";
        return updates(sql, book.getName(), book.getAuthor(), book.getPrice(), book.getSales(), book.getStock(), book.getImgPath(), book.getId());
    }

    @Override
    public Book queryBookById(Integer id) {
        String sql = "select id, name, author, price, sales, stock, img_path imgPath from t_book where id = ?";

        return queryForOne(Book.class, sql, id);
    }

    @Override
    public List<Book> queryBooks() {
        String sql = "select id, name, author, price, sales, stock, img_path imgPath from t_book";
        return queryForList(Book.class, sql);
    }
}
  • BookDao.java
package com.atjava.dao;

import com.atjava.pojo.Book;

import java.util.List;

/**
 * @author lv
 * @create 2021-09-11 16:35
 */
public interface BookDao {
    // 增
    public int addBook (Book book);
    // 删
    public int deleteBookById (Integer id);
    // 改
    public int updateBook (Book book);
    // 查
    public Book queryBookById (Integer id);

    public List<Book> queryBooks();
}

编写图书模块的 Service 和测试 Service

  • BookService.java
package com.atjava.service;

import com.atjava.pojo.Book;

import java.util.List;

/**
 * @author lv
 * @create 2021-09-12 9:46
 */
public interface BookService {

    public void addBook (Book book);

    public int deleteBookById (Integer id);

    public void updateBook (Book book);

    public Book queryBookById (Integer id);

    public List<Book> queryBooks ();
}
  • BookServiceImpl.java
package com.atjava.service.impl;

import com.atjava.dao.BookDao;
import com.atjava.dao.impl.BookDaoImpl;
import com.atjava.pojo.Book;
import com.atjava.service.BookService;

import java.util.List;

/**
 * @author lv
 * @create 2021-09-12 9:49
 */
public class BookServiceImpl implements BookService {

    private BookDao bookDao = new BookDaoImpl();

    @Override
    public void addBook(Book book) {
        bookDao.addBook(book);
    }

    @Override
    public int deleteBookById(Integer id) {
        int i = bookDao.deleteBookById(id);
        return i;
    }

    @Override
    public void updateBook(Book book) {
        bookDao.updateBook(book);
    }

    @Override
    public Book queryBookById(Integer id) {
        return bookDao.queryBookById(id);
    }

    @Override
    public List<Book> queryBooks() {
        return bookDao.queryBooks();
    }
}

编写图书模块的 Web 层和页面联调测试

  • BookServlet 程序
  1. 查询全部图书
  2. 保存到 request域中
  3. 请求转发到 /pages/manager/book_manager.jsp
  • book_manager.jsp
  1. 从 request域中获取全部数据
  2. 使用 JSTL标签库遍历输出 book 数据
  • BookServlet.java
package com.atjava.web;

import com.atjava.pojo.Book;
import com.atjava.service.BookService;
import com.atjava.service.impl.BookServiceImpl;
import com.atjava.utils.WebUtils;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.List;

/**
 * @author lv
 * @create 2021-09-12 21:30
 */
public class BookServlet extends BaseServlet {

    private BookService bs = new BookServiceImpl();

    protected void add (HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        // super.doPost(req, resp);
        // 此时再设置字符格式已无效
//        req.setCharacterEncoding("UTF-8");
        // 1.获取请求参数,封装为 Book对象
        Book book = WebUtils.copyParamToBean(req.getParameterMap(), new Book());
        // 2.调用 addBook()保存图书
        bs.addBook(book);
        // 3.跳转到图书管理列表页 /manager/bookServlet/book_manager.jsp
        // 请求转发,是一次请求
//        req.getRequestDispatcher("manager/bookServlet?action=list").forward(req, resp);

        // 重定向,不是一次请求;getContextPath()获取工程名
        resp.sendRedirect(req.getContextPath() + "/manager/bookServlet?action=list");
    }

    protected void delete (HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        // super.doPost(req, resp);
        // 1.获取请求的参数 id
        String id = req.getParameter("id");
        // 2.调用 bookServlet.deleteBookById(id),删除图书

        // 字符串转 int,方式一:
//        int i = 0;
//        try {
//            // ctrl + alt + t
//            i = Integer.parseInt(id);
//        } catch (NumberFormatException e) {
//            e.printStackTrace();
//        }
        // 字符串转 int,方式二:
        int i = WebUtils.parseInt(id, -1);

        bs.deleteBookById(i);
        // 3.重定向回图书列表管理页
        resp.sendRedirect(req.getContextPath() + "/manager/bookServlet?action=list");
    }

    protected void update (HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        // super.doPost(req, resp);
        Book book = WebUtils.copyParamToBean(req.getParameterMap(), new Book());
        bs.updateBook(book);
        resp.sendRedirect(req.getContextPath() + "/manager/bookServlet?action=list");
    }

    protected void list (HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        // super.doPost(req, resp);
        // 1.通过 BookServlet查询全部图书
        List<Book> bookList = bs.queryBooks();
        // 2.将查询到的数据保存到 request域中
        req.setAttribute("bookList", bookList);
        // 3.请求转发到 /pages/manager/book_manager.jsp中
        req.getRequestDispatcher("/pages/manager/book_manager.jsp").forward(req, resp);
    }

    protected void getBook (HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        // super.doPost(req, resp);
        String id = req.getParameter("id");
        int i = WebUtils.parseInt(id, -1);
        Book book = bs.queryBookById(i);
        req.setAttribute("book", book);
        req.getRequestDispatcher("/pages/manager/book_edit.jsp").forward(req, resp);
    }
}
  • /pages/manager/book_edit.jsp
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>编辑图书</title>
   <%-- 使用静态包含替换,包含 base标签、css样式、jQuery文件 --%>
   <%@ include file="/pages/common/head.jsp" %>
<%--<link type="text/css" rel="stylesheet" href="../../static/css/style.css" >--%>
<style type="text/css">
   h1 {
      text-align: center;
      margin-top: 200px;
   }
   
   h1 a {
      color:red;
   }
   
   input {
      text-align: center;
   }
</style>
</head>
<body>
        是否是添加操作:${ empty param.id ? "add" : "update" }
      <div id="header">
         <img class="logo_img" alt="" src="../../static/img/logo.gif" >
         <span class="wel_word">编辑图书</span>

         <%@ include file="/pages/common/manager_menu.jsp" %>
         <%--<div>--%>
            <%--<a href="book_manager.jsp">图书管理</a>--%>
            <%--<a href="order_manager.jsp">订单管理</a>--%>
            <%--<a href="../../index.jsp">返回商城</a>--%>
         <%--</div>--%>
      </div>
      
      <div id="main">
         <form action="manager/bookServlet" method="get">
                <%--方案一:--%>
                <%--<input type="hidden" name="action" value="${param.method}">--%>

                <%--方案二:--%>
                <%--<input type="hidden" name="action" value="${ empty requestScope.book ? "add" : "update" }">--%>

                <%--方案三:--%>
                <input type="hidden" name="action" value="${ empty param.id ? "add" : "update" }">
                <c:if test="${!(empty param.id)}">
                    <input name="id" type="hidden" value="${requestScope.book.id}"/>
                </c:if>

                <table>
               <tr>
                  <td>名称</td>
                  <td>价格</td>
                  <td>作者</td>
                  <td>销量</td>
                  <td>库存</td>
                  <td colspan="2">操作</td>
               </tr>
                    <%--<c:forEach var="entry" items="${requestScope.book}">--%>
                        <tr>
                            <td><input name="name" type="text" value="${requestScope.book.name}"/></td>
                            <td><input name="price" type="text" value="${requestScope.book.price}"/></td>
                            <td><input name="author" type="text" value="${requestScope.book.author}"/></td>
                            <td><input name="sales" type="text" value="${requestScope.book.sales}"/></td>
                            <td><input name="stock" type="text" value="${requestScope.book.stock}"/></td>
                            <td><input type="submit" value="提交"/></td>
                        </tr>
                    <%--</c:forEach>--%>

            </table>
         </form>
         
   
      </div>

      <%@ include file="/pages/common/footer.jsp" %>
      <%--<div id="bottom">--%>
         <%--<span>--%>
            <%--尚硅谷书城.Copyright &copy;2015--%>
         <%--</span>--%>
      <%--</div>--%>
</body>
</html>
  • /pages/manager/book_manager.jsp
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>图书管理</title>
   <%-- 使用静态包含替换,包含 base标签、css样式、jQuery文件 --%>
   <%@ include file="/pages/common/head.jsp" %>
<%--<link type="text/css" rel="stylesheet" href="../../static/css/style.css" >--%>
</head>
<body>
   
   <div id="header">
      <img class="logo_img" alt="" src="../../static/img/logo.gif" >
      <span class="wel_word">图书管理系统</span>

      <%@ include file="/pages/common/manager_menu.jsp" %>

   </div>
   
   <div id="main">
      <table>
         <tr>
            <td>名称</td>
            <td>价格</td>
            <td>作者</td>
            <td>销量</td>
            <td>库存</td>
            <td colspan="2">操作</td>
         </tr>
         <c:forEach var="book" items="${requestScope.bookList}">
            <tr>
               <td>${book.name}</td>
               <td>${book.price}</td>
               <td>${book.author}</td>
               <td>${book.sales}</td>
               <td>${book.stock}</td>
               <%--<td><a href="manager/bookServlet?action=getBook&id=${book.id}&method=update">修改</a></td>--%>
               <td><a href="manager/bookServlet?action=getBook&id=${book.id}">修改</a></td>
               <%--<td><a href="/pages/manager/book_edit.jsp">修改</a></td>--%>
               <td><a class="deleteClass" href="manager/bookServlet?action=delete&id=${book.id}">删除</a></td>
            </tr>
         </c:forEach>

         
         <tr>
            <td></td>
            <td></td>
            <td></td>
            <td></td>
            <td></td>
            <td></td>
            <%--<td><a href="pages/manager/book_edit.jsp?method=add">添加图书</a></td>--%>
            <td><a href="pages/manager/book_edit.jsp">添加图书</a></td>
         </tr>
      </table>
   </div>

   <%@ include file="/pages/common/footer.jsp" %>

   <
   <script type="text/javascript">
      $(function () {
         $("a.deleteClass").click(function () {
             /**
             * 在事件的 function函数中,有一个 this对象;
             * 这个 this对象是当前正在响应的 dom元素。
             */
                // $(this).parent().parent().find("td:first").text()
                /**
             * confirm:确认提示框
             * 确认为 true、取消为 false
             * return false; 阻止元素的默认行为,不提交
             * return true; 元素继续执行默认行为
                 */
            return confirm("你确认删除《 " + $(this).parent().parent().find("td:first").text() + " 》图书吗?");
            })
        })
   </script>

   <%--<div id="bottom">--%>
      <%--<span>--%>
         <%--尚硅谷书城.Copyright &copy;2015--%>
      <%--</span>--%>
   <%--</div>--%>
</body>
</html>

表单重复提交:

当用户提交完请求,浏览器会记录下最后一次请求的全部信息;当用户按下 功能键 F5,浏览器会再次发起最后一次的请求。所以当最后一次的请求为 类似 增、删、改 的请求时,不能使用请求转发(请求转发是一次请求),应改为请求重定向(不是一次请求)。

书城-第五阶段:图书分页

  • BookServlet.java
private BookService bs = new BookServiceImpl();

protected void page (HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
    // 1.获取请求的参数 pageNo、pageSize
    int pageNo = WebUtils.parseInt(req.getParameter("pageNo"), 1);
    int pageSize = WebUtils.parseInt(req.getParameter("pageSize"), Page.PAGE_SIZE);
    Page<Book> bookPage = bs.page(pageNo, pageSize);
    req.setAttribute("bookPage", bookPage);
    req.getRequestDispatcher("/pages/manager/book_manager.jsp").forward(req, resp);
}
  • BookServiceImpl.java
@Override
public Page<Book> page (int pageNo, int pageSize) {
    Page<Book> page = new Page<>();
    page.setPageNo(pageNo);
    page.setPageSize(pageSize);
    Integer pageTotalCount = bookDao.queryForPageTotalCount();
    page.setPageTotalCount(pageTotalCount);
    Integer pageTotal = pageTotalCount / pageSize;
    if (pageTotalCount % pageSize > 0) {
        pageTotal++;
    }
    page.setPageTotal(pageTotal);

    int begin = (pageNo - 1) * pageSize;
    List<Book> items = bookDao.queryForPageItems(begin, pageSize);
    page.setItems(items);
    return page;
}

Page算法:

页码展示规则:只能展示 5页页码,当前页在页码中间。

  • book_manager.jsp
  1. 如果页码总页数小于等于5,页码范围在 1-total
  2. 总页码大于 5的情况
    • 当前页码为前面三个 1、2、3时:页码范围为 1 - 5
    • 当前页码为后面三个 8、9、10时:页码范围为 (total-4) - 10
    • 当前页码为 4、5、6、7 时:页码范围为:(pageNo - 2) - (pageNo + 2)
<div id="page_nav">

              <c:if test="${requestScope.bookPage.pageNo > 1}">
                  <a href="manager/bookServlet?action=page&pageNo=1">首页</a>
                  <a href="manager/bookServlet?action=page&pageNo=${requestScope.bookPage.pageNo - 1}">上一页</a>
              </c:if>

              <%--
                  1. 如果页码总页数小于等于5,页码范围在 1-total
                  2. 总页码大于 5的情况
                  2.1 当前页码为前面三个 123时:页码范围为 1 - 5
                  2.2 当前页码为后面三个 8910时:页码范围为 (total-4) - 10
                  2.3 当前页码为 4567 时:页码范围为:(pageNo - 2) - (pageNo + 2)
              --%>
              <%--页码开始:--%>
              <c:choose>
                  <c:when test="${requestScope.bookPage.pageTotal <= 5}">
                      <c:set var="begin" value="1" />
                      <c:set var="end" value="${requestScope.bookPage.pageTotal}" />
                      <%--<c:forEach var="i" begin="1" end="${requestScope.bookPage.pageTotal}">--%>
                          <%--<c:choose>--%>
                              <%--<c:when test="${i != requestScope.bookPage.pageNo}">--%>
                                  <%--<a href="manager/bookServlet?action=page&pageNo=${i}">${i}</a>--%>
                              <%--</c:when>--%>
                              <%--<c:otherwise>--%>
                                  <%--&lt;%&ndash;javascript:null:阻止a标签跳转&ndash;%&gt;--%>
                                  <%--<a href="javascript:null">【${i}】</a>--%>
                              <%--</c:otherwise>--%>
                          <%--</c:choose>--%>
                      <%--</c:forEach>--%>
                  </c:when>
                  <c:when test="${requestScope.bookPage.pageTotal > 5}">
                      <c:choose>
                          <%--<c:when test="${requestScope.bookPage.pageNo == 1 || requestScope.bookPage.pageNo == 2 || requestScope.bookPage.pageNo == 3}">--%>
                          <c:when test="${requestScope.bookPage.pageNo <= 3}">
                              <c:set var="begin" value="1" />
                              <c:set var="end" value="5" />
                              <%--<c:forEach var="i" begin="1" end="5">--%>
                                  <%--<c:choose>--%>
                                      <%--<c:when test="${i != requestScope.bookPage.pageNo}">--%>
                                          <%--<a href="manager/bookServlet?action=page&pageNo=${i}">${i}</a>--%>
                                      <%--</c:when>--%>
                                      <%--<c:otherwise>--%>
                                          <%--&lt;%&ndash;javascript:null:阻止a标签跳转&ndash;%&gt;--%>
                                          <%--<a href="javascript:null">【${i}】</a>--%>
                                      <%--</c:otherwise>--%>
                                  <%--</c:choose>--%>
                              <%--</c:forEach>--%>
                          </c:when>
                          <%--<c:when test="${requestScope.bookPage.pageNo == (requestScope.bookPage.pageTotal - 2) || requestScope.bookPage.pageNo == (requestScope.bookPage.pageTotal - 1) || requestScope.bookPage.pageNo == requestScope.bookPage.pageTotal}">--%>
                          <c:when test="${requestScope.bookPage.pageNo >= (requestScope.bookPage.pageTotal - 2)}">
                              <c:set var="begin" value="${requestScope.bookPage.pageTotal - 4}" />
                              <c:set var="end" value="${requestScope.bookPage.pageTotal}" />
                              <%--<c:forEach var="i" begin="${requestScope.bookPage.pageTotal - 4}" end="${requestScope.bookPage.pageTotal}">--%>
                                  <%--<c:choose>--%>
                                      <%--<c:when test="${i != requestScope.bookPage.pageNo}">--%>
                                          <%--<a href="manager/bookServlet?action=page&pageNo=${i}">${i}</a>--%>
                                      <%--</c:when>--%>
                                      <%--<c:otherwise>--%>
                                          <%--&lt;%&ndash;javascript:null:阻止a标签跳转&ndash;%&gt;--%>
                                          <%--<a href="javascript:null">【${i}】</a>--%>
                                      <%--</c:otherwise>--%>
                                  <%--</c:choose>--%>
                              <%--</c:forEach>--%>
                          </c:when>
                          <c:otherwise>
                              <c:set var="begin" value="${requestScope.bookPage.pageNo - 2}" />
                              <c:set var="end" value="${requestScope.bookPage.pageNo + 2}" />
                              <%--<c:forEach var="i" begin="${requestScope.bookPage.pageNo - 2}" end="${requestScope.bookPage.pageNo + 2}">--%>

                                  <%--<c:choose>--%>
                                      <%--<c:when test="${i != requestScope.bookPage.pageNo}">--%>
                                          <%--<a href="manager/bookServlet?action=page&pageNo=${i}">${i}</a>--%>
                                      <%--</c:when>--%>
                                      <%--<c:otherwise>--%>
                                          <%--&lt;%&ndash;javascript:null:阻止a标签跳转&ndash;%&gt;--%>
                                          <%--<a href="javascript:null">【${i}】</a>--%>
                                      <%--</c:otherwise>--%>
                                  <%--</c:choose>--%>
                              <%--</c:forEach>--%>
                          </c:otherwise>
                      </c:choose>
                  </c:when>
              </c:choose>
          <%--
              使用统一的 forEach循环替代上边类似的功能
              <c:set var="begin" value="..." />
              <c:set var="end" value="..." />
          --%>
              <c:forEach var="i" begin="${begin}" end="${end}">
                  <c:choose>
                      <c:when test="${i != requestScope.bookPage.pageNo}">
                          <a href="manager/bookServlet?action=page&pageNo=${i}">${i}</a>
                      </c:when>
                      <c:otherwise>
                          <%--javascript:null:阻止a标签跳转--%>
                          <a href="javascript:null">【${i}】</a>
                      </c:otherwise>
                  </c:choose>
              </c:forEach>
              <%--页码结束:--%>
              <c:if test="${requestScope.bookPage.pageNo < requestScope.bookPage.pageTotal}">
                  <a href="manager/bookServlet?action=page&pageNo=${requestScope.bookPage.pageNo + 1}">下一页</a>
                  <a href="manager/bookServlet?action=page&pageNo=${requestScope.bookPage.pageTotal}">末页</a>
              </c:if>
    共 ${requestScope.bookPage.pageTotal} 页,
    ${requestScope.bookPage.pageTotalCount} 条记录
    到第<input value="${requestScope.bookPage.pageNo}" name="pn" id="pn_input"/>页
   <input class="subClass" type="button" value="确定">
</div>

Cookie 和 Session

Cookie

Cookie 值只支持 数字和字母,如果想要输入除 数字和字母 以外的值,只能先使用 BASE64 编码转换。

何为 Cookie

  • Cookie 是服务器通知客户端保存键值对的一种技术。
  • 客户端有了 Cookie 后,每次请求都发送给服务器
  • 每个 Cookie 的大小不能超过 4kb

如何创建 Cookie

  1. 创建 Cookie 对象,可以同时创建多个 Cookie
  2. 通知客户端保存 Cookie(必须)
  3. 客户端通过响应头找到有 Set-Cookie 后,就去浏览器中查看是否有此 Cookie,没有就创建,有就修改 Cookie 值
public class CookieServlet extends BaseServlet {

    protected void createCookie (HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        // super.doGet(req, resp);
        // 1.创建 Cookie对象
        Cookie ck = new Cookie("key1", "value1");

        // 设置 服务器和客户端使用同样的字符集 UTF-8,解决响应中文乱码问题
        // resp.setContentType("text/html;charset=UTF-8");

        // 2.通知客户端保存 Cookie
        resp.addCookie(ck); // Set-Cookie: key1=value1

        // 可以一次创建多个 Cookie
        Cookie ck2 = new Cookie("key2", "value2");
        resp.addCookie(ck2); // Set-Cookie: key1=value1

        resp.getWriter().write("Cookie Create Success");
    }
}

服务器如何获取 Cookie

服务器获取客户端的 Cookie 只需要一行代码:req.getCookies():Cookie[]

  • 客户端通过请求头:Cookie:xxx=xxx; xxx=xxx 将浏览器端的 Cookie信息发送给服务器
  • 服务器获取客户端发送的 Cookie 信息:req.getCookies(); 返回 Cookie[]数组
public class CookieServlet extends BaseServlet {

    protected void getCookie (HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        Cookie[] cookies = req.getCookies();
        for (Cookie ck : cookies) {
            System.out.println(ck);
            resp.getWriter().write("Cookie:" + ck.getName() + " - " + ck.getValue() + "<br />");

        }

        /**
         * 查找指定 Cookie 的操作是常见的,经常写成工具类
         */
        Cookie iWantCookie = null;

        // 方式一
//        for (Cookie ck : cookies) {
//            if ("key2".equals(ck.getName())) {
//                iWantCookie = ck;
//                break;
//            }
//        }

        // 方式二:
        iWantCookie = CookieUtils.findCookie("key2", cookies);
        if (null != iWantCookie) {
            resp.getWriter().write("获取了心目中的 Cookie = " + iWantCookie.getName() + ":" + iWantCookie.getValue());
        }
    }
}    
  • CookieUtils.java 工具类
public class CookieUtils {
    public static Cookie findCookie (String key, Cookie[] cookies) {

        if (key == null || cookies == null || cookies.length == 0) {
            return null;
        }
        for (Cookie ck : cookies) {
            if (key.equals(ck.getName())) {
                return ck;
            }
        }
        return null;
    }
}

Cookie 值的修改

方案一:

  1. 先创建一个要修改的同名的 Cookie 对象
  2. 在构造器中,同时赋予新的 Cookie 值
  3. 调用 response.addCookie( cookie )

方案二:

  1. 先查找到需要修改的 Cookie 对象
  2. 调用 setValue() 方法赋予新的 Cookie 值
  3. 调用 response.addCookie( cookie ) 通知客户端保存修改
protected void updateCookie (HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
    // > 方案一:
    // 1. 先创建一个要修改的同名的 Cookie 对象
    // 2. 在构造器中,同时赋予新的 Cookie 值
     Cookie cookie = new Cookie("key1", "newValue1");
    // 3. 调用 response.addCookie( cookie )
     resp.addCookie(cookie);

    // > 方案二:
    // 1. 先查找到需要修改的 Cookie 对象
    // 2. 调用 setValue() 方法赋予新的 Cookie 值
    // 3. 调用 response.addCookie( cookie ) 通知客户端保存修改
    Cookie[] cookies = req.getCookies();
    Cookie ck2 = CookieUtils.findCookie("key2", cookies);
    if (null != ck2) {
        ck2.setValue("newValue2");
        resp.addCookie(ck2);
    }
}

使用 浏览器查看当前网站的 Cookie

Chrome 查看 Cookie

  • 开发者工具 -> Application -> 找到对应的网址

Firefox 查看 Cookie

  • 开发者工具 -> 存储 -> 找到对应的网址

Cookie 的生命控制

Cookie 的生命控制指的是如何管理 Cookie 被删除的时间

setMaxAge():

  • 正数:表示在指定的秒数后过期
  • 负数:(默认,值是 -1)表示浏览器关闭后,Cookie 就会被删除
  • 零:表示马上删除当前 Cookie
  1. lifeCookie() [Cookie的默认存活时间(会话)]
  2. delCookie() [Cookie立即删除]
  3. life3600() [Cookie存活3600秒(1小时)]
public class CookieServlet extends BaseServlet {
    protected void life3600 (HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        Cookie ck = new Cookie("key60", "60");
        ck.setMaxAge(60);
        resp.addCookie(ck);
    }

    protected void delCookie (HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        Cookie[] cks = req.getCookies();
        Cookie ck2 = CookieUtils.findCookie("key2", cks);

        // 立即删除此 Cookie:setMaxAge(0)
        // Set-Cookie: key2=value2; Expires=Thu, 01-Jan-1970 00:00:10 GMT
        ck2.setMaxAge(0);
        resp.addCookie(ck2);

    }
    protected void lifeCookie (HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        Cookie ck = new Cookie("defaultLife", "defaultLife");
        ck.setMaxAge(-1);
        resp.addCookie(ck);
    }
}

Cookie 有效路径 Path 的设置

Cookie 的 path 属性可以有效的过滤哪些 Cookie 可以发动给服务器,哪些不发,path 属性是通过请求的地址来进行有效的过滤。

  • setPath();
  • 属性 Path:/工程路径/abc
  1. 地址:/工程路径,不能看到上边的 Cookie
  2. 地址:/工程路径/abc,能看到上边的 Cookie
  3. /工程路径/abc/c.html,能看到上边的 Cookie
  • 想要看到指定的 Cookie,请求地址必须要 大于等于 Cookie 的 Path 属性
protected void pathCookie (HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
    Cookie ck = new Cookie("path1", "path1");
    // getContextPath() -> 工程路径
    System.out.println("工程路径:" + req.getContextPath());
    ck.setPath(req.getContextPath() + "/abc"); // /工程路径/abc
    // Set-Cookie: path1=path1; Path=/abc
    resp.addCookie(ck);
}

Cookie 练习 -> 免用户名登录

用户第一登录某个网址后,在 Cookie 有效时间内用户再次登录,用户名输入框内自动回显最近一次登录成功的用户名

  • EL 隐含表达式 value="${cookie.username.value}"

  • LoginServlet.java

package com.atjava.servlet;

import javax.servlet.ServletException;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

/**
 * @author lv
 * @create 2021-09-25 10:29
 */
public class LoginServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        // super.doGet(req, resp);

        String username = req.getParameter("username");
        String password = req.getParameter("password");
        /**
         * 连接数据库登录成功后(模拟 sql):
         */
        if ("lv163".equals(username) && "456lv".equals(password)) {
            // 登录成功后:
            Cookie usernameCK = new Cookie("username", username);
            usernameCK.setMaxAge(60 * 3); // 三分钟内有效
            resp.addCookie(usernameCK);
            req.getRequestDispatcher("/login.jsp").forward(req, resp);
            System.out.println("Success");
        } else {
            // 登录失败:
            System.out.println("Fail");
            req.getRequestDispatcher("/login.jsp").forward(req, resp);
        }
    }
}
  • login.jsp
<%--
  Created by IntelliJ IDEA.
  User: Weili
  Date: 2021/9/25
  Time: 10:25
  To change this template use File | Settings | File Templates.
--%>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>Login</title>
    <base href="http://localhost:8080/" />
</head>
<body>
    <form action="login" method="get">
        用户名:<input type="text" name="username" id="username" value="${cookie.username.value}" />
        <br />
        密 码:<input type="text" name="password" id="password" />
        <br />
        <input type="submit" value="登 录" id="sub-btn" />
    </form>
</body>
</html>

Session 会话

何为 Session 会话

  • Session 是一个接口 HttpSession
  • Session 就是一个会话;它是用来维护一个客户端和服务器之间关联的一种技术
  • 每个客户端都有自己的一个 Session 会话
  • 在 Session 会话中,我们经常用来保存用户登录之后的信息

如何创建 Session 和获取(id 号,是否为新)

  • 创建和获取 Session 的 API是一个方法:request.getSession();
  1. 第一次调用是:创建 Session会话
  2. 之后调用都是:获取前面创建好的 Session 会话对象
  • isNew():判断当前 Session 是否为刚创建的(新的),true刚创建、false之前创建好的

  • 每个会话都有一个身份证号,也就是 ID 值,且这个 ID 是唯一的

  1. getId() 得到 Session 的会话 ID 值
  • SessionServlet.java
public class SessionServlet extends BaseServlet {
    
    protected void createOrGetSession (HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        // super.doGet(req, resp);
        // 创建和获取 Session 会话对象
        HttpSession session = req.getSession();
        // 判断当前 Session 会话,是否为新建的
        boolean isNew = session.isNew();
        // 获取 Session 会话的唯一标识 id
        String sId = session.getId();

        resp.getWriter().write("得到的 Session ,它的 id 是:" + sId + "<br />");
        resp.getWriter().write("得到的 Session ,它的 isNew 是:" + isNew + "<br />");
    }
}

Session 域数据的存取

  • setAttribute(key, value);
  • getAttribute(key);
public class SessionServlet extends BaseServlet {

    /**
     * 获取保存在 Session 域中的数据
     * @param req
     * @param resp
     * @throws ServletException
     * @throws IOException
     */
    protected void getSessionAttribute (HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        HttpSession session = req.getSession();
        Object sKey1 = session.getAttribute("key1");
        resp.getWriter().write("key1:" + sKey1);
    }

    /**
     * 将数据保存到 Session 域中
     * @param req
     * @param resp
     * @throws ServletException
     * @throws IOException
     */
    protected void setSessionAttribute (HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        HttpSession session = req.getSession();
        session.setAttribute("key1", "value1");
        resp.getWriter().write("session 域中添加数据");
    }
}    

Session 生命周期控制

  • Session 的超时概念:客户端两次请求的最大间隔时长,如果在间隔时间内再次请求,那间隔的超时时间会重新计时(如果一直刷新,那 Session 就永远不会超时
  1. 时长为正数时,设定 Session 的超时时长
  2. 时长为负数时,表示 Session 永不超时(极少使用
  • public void setMaxInactiveInterval(int ...):设置 Session 的超时时间,超过指定时长,Session 会被销毁,以秒为单位

  • public int getMaxInactiveInterval():获取 Session 的超时时间

  • invalidate():使当前 Session 会话超时、无效

  • Session 的默认超时时长为 1800s,30分钟

  • 在 web.xml 中统一修改 Tomcat 设置的默认时长

<session-config>
    // 20分钟
    <session-timeout>20</session-timeout>
</session-config>
  1. delSession() - [Session马上销毁]
  2. sessionInterval3() - [Session3秒超时销毁]
  3. sessionInterval() - [Session的默认超时及配置]
protected void delSession (HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
    HttpSession session = req.getSession();
    // 销毁当前 Session
    session.invalidate();
    int maxInactiveInterval = session.getMaxInactiveInterval();
    // session.setMaxInactiveInterval();
    resp.getWriter().write("销毁当前 Session:" + maxInactiveInterval);
}

protected void sessionInterval3 (HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
    HttpSession session = req.getSession();
    session.setMaxInactiveInterval(3);
    int maxInactiveInterval = session.getMaxInactiveInterval();

    resp.getWriter().write("Session 时长:" + maxInactiveInterval);
}

protected void sessionInterval (HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
    HttpSession session = req.getSession();
    int maxInactiveInterval = session.getMaxInactiveInterval();

    resp.getWriter().write("Session 默认时长:" + maxInactiveInterval);
}

浏览器和服务器 Session 之间关联的技术内幕

  • Session 技术,底层是基于 Cookie 技术来实现的
  • 服务器创建 Session 后,会返回一个关于 Session 的 Cookie 对象给客户端
  1. Set-Cookie: JSESSIONID=ID 被浏览器保存,
  2. 这个 Cookie 的生命周期是 session 级的,浏览器关闭后,此 Cookie 就会过期
  • 以下就是如何找到以前创建好的 Session 的方法
  1. 浏览器在没有 Cookie 的情况下,发送请求
  2. 服务器收到请求后,使用 request.getSession(); 创建会话对象,并且在创建 Session 会话时,都会创建一个 Cookie 对象,key 永远是 JSESSIONID,value 是新建出来的 Session 的 ID
  3. 服务器通过响应将创建出来的 Session 的 ID 通过 Cookie 的方式返回给客户端
  4. 浏览器接收到响应数据,就会创建一个 Cookie 对象保存 Session 的 ID
  5. 后面有了 Session 的 Cookie 对象后,每次请求都会将 Session 的 ID 以 Cookie 的形式发送给 服务器
  6. 服务器收到请求后,使用 request.getSession() 通过 Cookie 中的 ID 值找到自己之前创建好的 Session 对象,并返回
  • 以上就是如何找到以前创建好的 Session 的方法

项目第六阶段

登录-显示用户名

  • 使用 Session 保存登录人的信息

  • login_success_menu.jsp

<%@ page contentType="text/html;charset=UTF-8" language="java" %>

<div>
    <span>欢迎<span class="um_span"> ${sessionScope.currentUser.username} </span>光临尚硅谷书城</span>
    <a href="pages/order/order.jsp">我的订单</a>
    <a href="userInfo?action=logout">注销</a>&nbsp;&nbsp;
    <a href="index.jsp">返回</a>
</div>
  • UserServlet.java
/**
 * 处理登录功能
 * @param req
 * @param resp
 * @throws ServletException
 * @throws IOException
 */
protected void login (HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
    String username = req.getParameter("username");
    String password = req.getParameter("password");
    User user = us.login(new User(null, username, password, null));
    if (null != user) {
        Cookie ck = new Cookie("username", username);
        ck.setMaxAge(60 * 60 * 24 * 7);
        resp.addCookie(ck);
        // 保存当前登录人的信息到 Session 域中
        HttpSession loginUsername = req.getSession();
        loginUsername.setAttribute("currentUser", user);

        System.out.println(user);
        req.getRequestDispatcher("/pages/user/login_success.jsp").forward(req, resp);
    } else {
        req.setAttribute("msg", "用户名或密码错误");
        req.setAttribute("username", username);
        req.getRequestDispatcher("/pages/user/login.jsp").forward(req, resp);
    }
}

登录-注销用户

  1. 销毁 Session 中的用户登录信息(或者直接销毁当前 Session)

  2. 重定向到首页或登录页

  • UserServlet.java
/**
 * 处理注销功能
 * @param req
 * @param resp
 * @throws ServletException
 * @throws IOException
 */
protected void logout (HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
    // 1. 销毁 Session 中的用户登录信息(或者直接销毁当前 Session)
    req.getSession().invalidate();
    // 2. 重定向到首页或登录页
    resp.sendRedirect(req.getContextPath());
}
  • login_success_menu.jsp
<%@ page contentType="text/html;charset=UTF-8" language="java" %>

<div>
    <span>欢迎<span class="um_span"> ${sessionScope.currentUser.username} </span>光临尚硅谷书城</span>
    <a href="pages/order/order.jsp">我的订单</a>
    <a href="userInfo?action=logout">注销</a>&nbsp;&nbsp;
    <a href="index.jsp">返回</a>
</div>
  • index.jsp
<div>
   <c:choose>
      <%--用户没用登录--%>
      <c:when test="${empty sessionScope.currentUser}">
         <a href="pages/user/login.jsp">登录</a>
         <a href="pages/user/regist.jsp">注册</a> &nbsp;&nbsp;
      </c:when>
      <%--用户登陆后--%>
      <c:otherwise>
         <span>欢迎<span class="um_span"> ${sessionScope.currentUser.username} </span>光临惠特米勒书城</span>
         <a href="pages/order/order.jsp">我的订单</a>
         <a href="userInfo?action=logout">注销</a>&nbsp;&nbsp;
      </c:otherwise>
   </c:choose>

   <a href="pages/cart/cart.jsp">购物车</a>
   <a href="pages/manager/manager.jsp">后台管理</a>
</div>

表单重复提交 -> 验证码

表单重复提交有以下三种常见情况:

  • 第一种情况可以使用请求重定向解决
  • 第 二、三 种情况可以使用 验证码来解决

一、提交完表单,服务器使用请求转发来进行页面跳转

此时,用户按下功能键 F5,浏览器会发起最后一次的请求,造成表单重复提交问题,解决方法,使 重定向来进行跳转

二、用户正常提交请求,但由于网络延迟等原因,迟迟未收到服务器的响应,此时如果用户多点几次提交操作,也会造成表单重复提交

三、用户正常提交服务器,服务器也没有延迟,但是提交完成后,用户回退浏览器,也会造成表单重新提交

验证码:

服务器:

  1. 当用户第一次访问表单页时,就要给表单生成一个验证码字符串
  2. 将服务器生成的验证码放到 Session 域中
  3. 将验证码显示在表单页中

Servlet程序:

  1. 获取 Session 域中的验证码,并将 Session 域中的验证码删除
  2. 获取表单信息,比较表单中的验证码与 Session 中获取的验证码是否相等
  3. 相等,执行后续操作;不等,停止操作,返回表单页

Chrome 的 kaptcha 图片验证码的使用

  1. 导入谷歌验证码的 jar 包:kaptcha-2.3.2.jar

  2. 在 web.xml 中去配置用于生成验证码的 Servlet 程序

  3. 在表单中使用 img 标签去显示验证码图片并使用它

    • 访问此 Servlet程序 /kaptcha.jpg 会自动生成验证码、图片,并将验证码放到 Session 域中
  4. 在服务器中获取谷歌生成的验证码和客户端发送过来的验证码比较使用

  • RegistServlet.java
package com.atjava.servlet;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

import static com.google.code.kaptcha.Constants.KAPTCHA_SESSION_KEY;

/**
 * @author lv
 * @create 2021-09-27 21:22
 */
public class RegistServlet extends HttpServlet {

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        // super.doGet(req, resp);
        // alt + enter:自动引入
        String sessionCode = (String)req.getSession().getAttribute(KAPTCHA_SESSION_KEY);
        // 获取后立即删除当前 Session 中的验证码
        req.getSession().removeAttribute(KAPTCHA_SESSION_KEY);

        String code = req.getParameter("code");

        if (null != code && null != sessionCode && code.equalsIgnoreCase(sessionCode)) {
            String username = req.getParameter("username");
            System.out.println("保存到 sql:" + code + " : " + username);
            resp.sendRedirect(req.getContextPath() + "/ok.jsp");
        } else {
            resp.sendRedirect(req.getContextPath() + "/index.jsp");
        }

        /**
         * 模拟网络延迟:Thread
         */
//        try {
//            Thread.sleep(5000);
//        } catch (InterruptedException e) {
//            e.printStackTrace();
//        }

        // 请求转发:
        // req.getRequestDispatcher("/ok.jsp").forward(req, resp);

        // 请求重定向:
        // resp.sendRedirect(req.getContextPath() + "/ok.jsp");
    }
}
  • index.jsp
<%--
  Created by IntelliJ IDEA.
  User: Weili
  Date: 2021/9/27
  Time: 21:15
  To change this template use File | Settings | File Templates.
--%>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
  <head>
    <title>$Title$</title>
    <base href="http://localhost:8080/" />
  </head>
  <body>
    <form action="regist" method="get">
      <label for="user-name">
        用户名:<input id="user-name" type="text" name="username" />
      </label>
      <br />
      <label for="code">
        验证码:<input type="text" name="code" id="code" style="width: 72px;" />
        <img style="width: 100px; height: 30px;" src="kaptcha.jpg" alt="" />
      </label>
      <br />
      <input type="submit" value="提 交" />
    </form>
  </body>
</html>
  • web.xml
<servlet>
    <servlet-name>KaptchaServlet</servlet-name>
    <servlet-class>com.google.code.kaptcha.servlet.KaptchaServlet</servlet-class>
</servlet>
<servlet-mapping>
    <servlet-name>KaptchaServlet</servlet-name>
    <url-pattern>/kaptcha.jpg</url-pattern>
</servlet-mapping>

验证码的切换

  • clien/index.jsp
<br />
<br />
<label>验证码:</label>
<input class="itxt" type="text" style="width: 150px;" name="code" id="code"/>
<%--<img alt="" src="static/img/code.bmp" style="float: right; margin-right: 40px">--%>
<img id="code_img" alt="code" src="kaptcha.jpg" style="width: 90px; height: 38px; float: right; margin-right: 40px">
<br />
<br />


<script type="text/javascript">
   // 页面加载完成后
   $(function () {
       // 切换验证码:
      $("#code_img").click(function () {
                  /**
          * 在每个事件响应的 function 函数中,都有一个 this 对象,
          * 此 this 对象是当前响应的 DOM 对象。
          *
          * src 是 img 标签的 图片路径属性,可读可写
          *
          * 浏览器的缓存:
          * 浏览器根据缓存名称由 资源名和请求参数组成,
          * 只要任何一点不同就会跳过浏览器的缓存
                   */
         this.src = "${basePath}kaptcha.jpg?id=" + new Date().toString();
         <%--$("#code_img").src = "${basePath}/kaptcha.jpg";--%>
      })
   }
</script>

注意,浏览器缓存:

  • 浏览器的缓存:
    1. 浏览器根据缓存名称由 资源名和请求参数组成,
    2. 只要任何一点不同就会跳过浏览器的缓存
    3. 可以使用添加 时间戳的办法跳过浏览器缓存