javaEE-S-书城项目

阶段一:

前端表单验证:

    <script type="text/javascript" src="../../static/script/jquery-1.7.2.js"></script>
        <script type="text/javascript">
            // 页面加载完成之后
            $(function () {
                // 给注册绑定单击事件
                $("#sub_btn").click(function () {
                    // 验证用户名:必须由字母,数字下划线组成,并且长度为5到12位
                    //1 获取用户名输入框里的内容
                    var usernameText = $("#username").val();
                    //2 创建正则表达式对象
                    var usernamePatt = /^\w{5,12}$/;
                    //3 使用test方法验证
                    if (!usernamePatt.test(usernameText)) {
                        //4 提示用户结果
                        $("span.errorMsg").text("用户名不合法!");

                        return false;
                    }

                    // 验证密码:必须由字母,数字下划线组成,并且长度为5到12位
                    //1 获取用户名输入框里的内容
                    var passwordText = $("#password").val();
                    //2 创建正则表达式对象
                    var passwordPatt = /^\w{5,12}$/;
                    //3 使用test方法验证
                    if (!passwordPatt.test(passwordText)) {
                        //4 提示用户结果
                        $("span.errorMsg").text("密码不合法!");

                        return false;
                    }

                    // 验证确认密码:和密码相同
                    //1 获取确认密码内容
                    var repwdText = $("#repwd").val();
                    //2 和密码相比较
                    if (repwdText != passwordText) {
                        //3 提示用户
                        $("span.errorMsg").text("确认密码和密码不一致!");

                        return false;
                    }

                    // 邮箱验证:xxxxx@xxx.com
                    //1 获取邮箱里的内容
                    var emailText = $("#email").val();
                    //2 创建正则表达式对象
                    var emailPatt = /^[a-z\d]+(\.[a-z\d]+)*@([\da-z](-[\da-z])?)+(\.{1,2}[a-z]+)+$/;
                    //3 使用test方法验证是否合法
                    if (!emailPatt.test(emailText)) {
                        //4 提示用户
                        $("span.errorMsg").text("邮箱格式不合法!");

                        return false;
                    }

                    // 验证码:现在只需要验证用户已输入。因为还没讲到服务器。验证码生成。
                    var codeText = $("#code").val();

                    //去掉验证码前后空格
                    // alert("去空格前:["+codeText+"]")
                    codeText = $.trim(codeText);
                    // alert("去空格后:["+codeText+"]")

                    if (codeText == null || codeText == "") {
                        //4 提示用户
                        $("span.errorMsg").text("验证码不能为空!");

                        return false;
                    }

                    // 去掉错误信息
                    $("span.errorMsg").text("");

                });

            });

        </script>
        

阶段二:

1.JavaEE 项目的三层架构
image.png

分层的目的是为了解耦。解耦就是为了降低代码的耦合度。方便项目后期的维护和升级。

web 层         com.kk.web/servlet/controller 
service 层     com.kk.service Service         接口包       com.kk.service.impl Service  接口实现类 
dao 持久层     com.kk.dao Dao                 接口包         com.kk.dao.impl Dao 接口实现类 
实体 bean 对象             com.kk.pojo/entity/domain/bean          JavaBean 
类 测试包                     com.kk.test/junit 
工具类                         com.kk.utils
2、添加base标签
image.png
3、编写 RegistServlet 程序
protected void doPost(javax.servlet.http.HttpServletRequest req, javax.servlet.http.HttpServletResponse resp) throws javax.servlet.ServletException, IOException {
        // 1、获取请求的参数
        String username = req.getParameter ("username");
        String password = req.getParameter ("password");
        String email = req.getParameter ("email");
        String code = req.getParameter ("code");

        // 2、检查 验证码是否正确 === 写死,要求验证码为:abcde
        if ("abcde".equalsIgnoreCase (code)) {
            // 3、检查 用户名是否可用
            if (userService.existsUsername (username)) {
                System.out.println ("用户名[" + username + "]已存在!");
                // 跳回注册页面
                req.getRequestDispatcher ("/pages/user/regist.html").forward (req, resp);
            } else { // 可用 // 调用 Sservice 保存到数据库
                userService.registUser (new User (null, username, password, email));
                // 跳到注册成功页面 regist_success.html
                req.getRequestDispatcher ("/pages/user/regist_success.html").forward (req, resp);
            }
        } else {
            System.out.println ("验证码[" + code + "]错误");
            req.getRequestDispatcher ("/pages/user/regist.html").forward (req, resp);
        }
    }
4、调试的光标详解
image.png
image.png
5、用户登录功能的实现
base放在title标签下.png

LoginServlet 程序

package com.kk.web;

import com.kk.pojp.User;
import com.kk.service.UserService;
import com.kk.service.impl.UserServiceImpl;

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

public class LoginServlet extends HttpServlet {
    private UserService userService = new UserServiceImpl ();
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {

        // 1、获取请求的参数
        String username = req.getParameter("username");
        String password = req.getParameter("password");

        // 调用 userService.login()登录处理业务
        User login = userService.login (new User (null, username, password,null));
        // 如果等于 null,说明登录 失败!
        if (login==null)
        {
            //跳回登录页面
            req.getRequestDispatcher ("/pages/user/login.html").forward (req,resp);
        }
        else
        {
            // 登录 成功 //跳到成功页面 login_success.html
            req.getRequestDispatcher ("/pages/user/login_success.html").forward (req,resp);
        }
    }

    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {

    }
}

UserServlet(登录注册最终处理):

package com.kk.web;

import com.google.code.kaptcha.servlet.KaptchaServlet;
import com.kk.pojp.User;
import com.kk.service.UserService;
import com.kk.service.impl.UserServiceImpl;

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

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

/*
 * @Description:    登录+注册+注销
 * @Author:         Jk_kang
 * @CreateDate:     2020/7/21 15:05
 * @Param:
 * @Return:
 **/
public class UserServlet extends BaseServlet {

    private UserService userService = new UserServiceImpl ( );

    /**
     * 登录
     *
     * @param req
     * @param resp
     * @throws ServletException
     * @throws IOException
     */
    protected void login(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {

        // 1、获取请求的参数
        String username = req.getParameter ("username");
        String password = req.getParameter ("password");

        // 调用 userService.login()登录处理业务
        User login = userService.login (new User (null, username, password, null));
        // 如果等于 null,说明登录 失败!
        if (login == null) {
            //错误信息,回显表单项信息,保存到request域中
            req.setAttribute ("meg", "用户名或者密码错误");
            req.setAttribute ("username", username);
            //跳回登录页面
            req.getRequestDispatcher ("/pages/user/login.jsp").forward (req, resp);
        } else {
            // 登录 成功
            // 保存用户登录的信息到session域中
            req.getSession ( ).setAttribute ("user", login);//login是查询出对应的User对象

            //跳到成功页面 login_success.jsp
            req.getRequestDispatcher ("/pages/user/login_success.jsp").forward (req, resp);
        }
    }


    /**
     * 注册
     *
     * @param req
     * @param resp
     * @throws javax.servlet.ServletException
     * @throws IOException
     */
    protected void regist(javax.servlet.http.HttpServletRequest req, javax.servlet.http.HttpServletResponse resp) throws javax.servlet.ServletException, IOException {
        //获取 Session中的验证码
        String token = (String) req.getSession ( ).getAttribute (KAPTCHA_SESSION_KEY);
        //删除 Session中的验证码
        req.getSession ( ).removeAttribute (KAPTCHA_SESSION_KEY);
        //获取输入的 code(验证码值)
        String code = req.getParameter ("code");

        // 1、获取请求的参数
        String username = req.getParameter ("username");
        String password = req.getParameter ("password");
        String email = req.getParameter ("email");

        // 2、检查 验证码是否正确
        if (token != null && token.equalsIgnoreCase (code)) {//equalsIgnoreCase不考虑大小写
            // 3、检查 用户名是否可用
            if (userService.existsUsername (username)) {
                // 把 错误提示 信息,保存到Request域中回显
                req.setAttribute ("msg", "用户名已存在!!");
                // 跳回注册页面
                req.getRequestDispatcher ("/pages/user/regist.jsp").forward (req, resp);
            } else {  // 调用 Sservice 保存到数据库
                userService.registUser (new User (null, username, password, email));
                // 跳到注册成功页面 regist_success.jsp
                req.getRequestDispatcher ("/pages/user/regist_success.jsp").forward (req, resp);
            }
        } else {
            req.setAttribute ("msg", "错误的验证码信息!!");
            req.getRequestDispatcher ("/pages/user/regist.jsp").forward (req, resp);
        }
    }

    /**
     * 注销登录
     *
     * @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、重定向到首页(或登录页面)
        System.out.println (req.getContextPath ( ));
        resp.sendRedirect (req.getContextPath ( ));
    }
}

阶段三:

1、页面JSP动态化


image.png

image.png

修改掉所有jsp里面的html为jsp

替换h5头部,防止后面静态包含公共jsp乱码


image.png
2、抽取公用的内容(方便维护:改一处,被引用的地方也改到)

抽取部位:
头部(css,jquery,base)
页脚(友情链接,水印)
重复的内容(菜单,登录信息)

抽取的完整jsp代码:

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

<!--写 base 标签,永远固定相对路径跳转的结果-->
<base href="http://localhost:8080/book/">
<link type="text/css" rel="stylesheet" href="static/css/style.css">
<script type="text/javascript" src="static/script/jquery-1.7.2.js"></script>

引用的jsp代码

<%@include file="../common/head.jsp"%>
3、登录,注册错误提示,及表单回显

以登录回显为示例:
Servlet 程序端需要添加回显信息到 Request 域中

 // 如果等于 null,说明登录 失败!
        if (login==null)
        {
            //错误信息,回显表单项信息,保存到request域中
            req.setAttribute ("meg","用户名或者密码错误");
            req.setAttribute ("username",username);
            //跳回登录页面
            req.getRequestDispatcher ("/pages/user/login.jsp").forward (req,resp);
        }

jsp 页面,需要输出回显信息

<div id="l_content">
                    <span class="login_word">欢迎注册</span>
</div>
4、BaseServlet 的抽取(重头戏)

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

4.1、代码优化一:代码优化:合并 LoginServlet 和 RegistServlet 程序为 UserServlet 程序


image.png

公用的UserServlet:

    private UserService userService = new UserServiceImpl ();
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {

        String action = request.getParameter ("action");
        if ("login".equals (action))
        {
            login (request,response);
        }
        else if ("regist".equals (action)){
            regist (request,response);
        }
    }

jsp上的修改:


image.png

4.2、优化代码二:使用反射优化大量 else if 代码:

          protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {

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

            //优化二:反射
            try {
                // 获取 action 业务鉴别字符串,获取相应的业务 方法反射对象
                Method method = this.getClass ( ).getDeclaredMethod (action, HttpServletRequest.class,
                        HttpServletResponse.class);
                //System.out.println (method);
                // 调用目标业务 方法
                method.invoke (this,request,response);
            } catch (Exception e) {
                e.printStackTrace ( );
            }
    }

科普:


image.png

4.3、抽取 BaseServlet 程序(final)


image.png

BaseServlet 程序代码:

public abstract class BaseServlet extends HttpServlet {
    //<a>标签请求默认为GET,所以。。
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        doPost(req, resp);
    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        String action = req.getParameter ("action");
        try {
            // 获取action业务鉴别字符串,获取相应的业务 方法反射对象
            Method method = this.getClass ( ).getDeclaredMethod (action, HttpServletRequest.class, HttpServletResponse.class);
            //System.out.println (method);
            // 调用目标业务 方法
            method.invoke (this, req, resp);
        } catch (Exception e) {
            e.printStackTrace ( );
        }
    }
}

修改 UserServlet 程序继承 BaseServlet 程序

public class UserServlet extends BaseServlet {
5、数据的封装和抽取 BeanUtils 的使用
image.png

WebUtils 工具类:

public class WebUtilts {
    /**
     * 把 Map 中的值注入到对应的 JavaBean 属性中。
     * @param value
     * @param bean */
    public <T> T copyParamToBean(Map value,T bean)
    {
        try {
            System.out.println("注入之前:" + bean);
            /*** 把所有请求的参数都注入到 user 对象中 */
            BeanUtils.populate (bean,value);
            System.out.println("注入之后:" + bean);
        } catch (IllegalAccessException|InvocationTargetException e) {
            e.printStackTrace ( );
        }
        return bean;
    }
}

6、EL 表达式修改表单回显(登录为例)


image.png

阶段四:

何为MVC:MVC 的作用还是为了降低耦合。让代码合理分层。方便后期升级和维护


image.png

1.1、插入数据库测试数据
1.2、编写图书模块的 JavaBean
1.3、编写图书模块的 Dao 和测试 Dao
1.4、编写图书模块的 Service 和测试 Service
1.5、编写图书模块的 Web 层,和页面联调测试

1.5.1、图书列表功能的实现:
1、图解列表功能流程:

image.png

2、BookServlet 程序中添加 list 方法
image.png

image.png

3、修改【图书管理】请求地址
image.png

4、修改 pages/manager/book_manager.jsp 页面的数据遍历输出
引入JST:<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
页面渲染:
<c:forEach items="${requestScope.books}" var="book">
<tr>
<td>${book.name}</td>
<td>${book.price}</td>
<td>${book.author}</td>
<td>${book.sales}</td>
<td>${book.stock}</td>
<td><a href="book_edit.jsp">修改</a></td>
<td><a href="#">删除</a></td>
</tr>
</c:forEach>

1.5.2前后台概述:


image.png

1.5.3、添加图书功能的实现

1.5.3.1、添加图书流程细节:

image.png

1.5.3.2、问题说明:表单重复提交:
当用户提交完请求,浏览器会记录下最后一次请求的全部信息。当用户按下功能键 F5,就会再一次发起浏览器记录的最后一次 请求。
1.5.3.3、BookServlet 程序中添加 add 方法:
image.png

科普:请求转发和重定向区别:
image.png

1.5.3.4、修改 book_edit.jsp 页面:
image.png

1.5.4、删除图书功能的实现

1.5.4.1、图解删除流程:


image.png

1.5.4.2、BookServlet 程序中的 delete 方法:


image.png

1.5.4.3、给 WebUtils 工具类添加转换 int 类型的工具方法:
image.png

1.5.4.4、修改删除的连接地址:


image.png

1.5.4.5、给删除添加确认提示操作:
image.png

1.5.5、修改图书功能的实现

image.png

1.5.5.2、更新【修改】的请求地址:


image.png

1.5.5.3、BookServlet 程序中添加 getBook 方法:


image.png

1.5.5.4、在 book_edit.jsp 页面中显示修改的数据
image.png

1.5.5.5、在 BookServlet 程序中添加 update 方法:
image.png

1.5.5.6、解决 book_edit.jsp 页面,即要实现添加,又要实现修改操作:


image.png

阶段五(分页):

1、分页模块的分析:


image.png

后端的:
2、分页模型 Page 的抽取(当前页数,总页数,总记录数, 当前页数据,每页记录数):


image.png

3、分页的初步实现:
3.1、BookServlet 程序的代码:


image.png

3.2、BookService程序的代码:
image.png

3.3、BookDao程序的代码:
image.png

前端的:
4、请求地址的修改
4.1、manager_menu.jsp 中【图书管理】请求地址的修改:


image.png

4.2、book_manager.jsp 修改:


image.png

未实现,默认

5、首页、上一页、下一页、末页实现
image.png

6、分页模块中,页码 1,2,【3】,4,5 的显示,要显示 5 个页 码,并且页码可以点击跳转
image.png

image.png

科普:pageEncoding="utf-8"
image.png

科普:${param}:
image.png

7、Page 对象中的修改:
image.png

8、BookService 中 page 方法的修改:(避免步骤7导致崩溃)
image.png

9、分页模块中,页码 1,2,【3】,4,5 的显示,要显示 5 个页 码,并且页码可以点击跳转:
情况分析一:


image.png

情况分析二:


image.png

代码实现:


image.png
<%@ taglib prefix="k" uri="http://java.sun.com/jsp/jstl/core" %>
<%--
  Created by IntelliJ IDEA.
  User: Administrator
  Date: 2020/7/17
  Time: 14:58
  To change this template use File | Settings | File Templates.
--%>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%
    String path = request.getScheme ( ) + "://"
            + request.getServerName ( ) + ":"
            + request.getServerPort ( )
            + request.getContextPath ( )
            + "/";
%>
<%--分页条的开始--%>
<div id="page_nav">
    <%--大于首页,才显示--%>
    <k:if test="${requestScope.page.pageNo > 1}">
        <a href="${requestScope.page.url}&pageNo=1">首页</a>
        <a href="${requestScope.page.url}&pageNo=${requestScope.page.pageNo - 1}">上一页</a>
    </k:if>
    <%--页码输出开始--%>
    <k:choose>
        <%--情况1:如果总页码小于等于5的情况,页码的范围是:1-总页码--%>
        <k:when test="${requestScope.page.pageTotal <= 5}">
            <k:set var="begin" value="1"/>
            <k:set var="end" value="${requestScope.page.pageTotal}"/>
        </k:when>
        <%--情况2:总页码大于5的情况--%>
        <k:when test="${requestScope.page.pageTotal > 5}">
            <k:choose>
                <%--小情况1:当前页码为前面3个:1,2,3的情况,页码范围是:1-5.--%>
                <k:when test="${requestScope.page.pageTotal} <= 3">
                    <k:set var="begin" value="1"/>
                    <k:set var="end" value="5"/>
                </k:when>
                <%--小情况2:当前页码为最后3个,8,9,10,页码范围是:总页码减4 - 总页码--%>
                <k:when test="${requestScope.page.pageNo} > ${requestScope.page.pageTotal -3}">
                    <k:set var="begin" value="${requestScope.page.pageTotal -4}"/>
                    <k:set var="end" value="${requestScope.page.pageTotal}"/>
                </k:when>
                <%--小情况3:4,5,6,7,页码范围是:当前页码减2 - 当前页码加2--%>
                <k:otherwise>
                    <k:set var="begin" value="${requestScope.page.pageTotal -2}"/>
                    <k:set var="end" value="${requestScope.page.pageTotal +2}"/>
                </k:otherwise>
            </k:choose>
        </k:when>
    </k:choose>
    <k:forEach begin="${begin}" end="${end}" var="i">
        <k:if test="${i==requestScope.page.pageNo}">
            <span style="color: #8b1813">【${i}】</span>
        </k:if>
        <k:if test="${i!=requestScope.page.pageNo}">
            <a href="${requestScope.page.url}&pageNo=${i}">${i}</a>
        </k:if>
    </k:forEach>
    <%-- 如果已经 是最后一页,则不显示下一页,末页 --%>
    <k:if test="${requestScope.page.pageNo < requestScope.page.pageTotal}">
        <a href="${requestScope.page.url}&pageNo=${requestScope.page.pageNo +1 }">下一页</a>
        <a href="${requestScope.page.url}&pageNo=${requestScope.page.pageTotal}">末页</a>
    </k:if>
    共${requestScope.page.pageTotal}页,${requestScope.page.pageTotalCount}条记录
    到第<input value="${param.pageNo}" name="pn" id="pn_input"/>页
    <input id="searchPageBtn" type="button" value="GO">

    <%--下拉框--%>
    跳转至:<select onchange="location='<%=path%>${requestScope.page.url}&pageNo='+this.value">
        <k:forEach begin="1" end="${requestScope.page.pageTotal}" var="i">
            <option value="${i}" ${requestScope.page.pageNo == i ?'selected':''}>第 ${i} 页</option>
        </k:forEach>
    </select>

    <script type="text/javascript">
        $(function () {
            $("#searchPageBtn").click(function () {
                var pageNo = $("#pn_input").val();
                // javaScript 语言中提供了一个 location 地址栏对象
                // 它有一个属性叫 href.它可以获取浏览器地址栏中的地址
                // href 属性可读,可写
                location.href = "<%=path%>${requestScope.page.url}&pageNo=" + pageNo;
            });
        });
    </script>
</div>
<%--分页条的结束--%>

10、修改分页后,增加,删除,修改图书信息的回显页面
以修改图书为示例:
1、在修改的请求地址上追加当前页码参数:


image.png

2、在 book_edit.jsp 页面中使用隐藏域记录下 pageNo 参数:


image.png

3、在服务器重定向的时候,获取当前页码追加上进行跳转:
image.png

3、首页 index.jsp 的跳转

image.png

后端代码:


image.png

前端代码:注:记得用base标签


image.png

image.png

4、分页条的抽取

4.1、抽取分页条中请求地址为 url 变量

4.1.1.在 page 对象中添加 url 属性:


image.png

4.1.2 在 Servlet 程序的 page 分页方法中设置 url 的分页请求地址:


image.png

4.1.3、修改分页条中请求地址为 url 变量输出,并抽取一个单独的 jsp 页面:
image.png

5、首页价格搜索

1、分析图:


image.png

2、后端:
2.1、Controller层:


image.png

2.2、Service层:
image.png

2.3、dao层:


image.png

3、前端:
image.png

阶段六:自动登录+验证码(session&&cookie)

1、登陆---显示用户名

1.1、UserServlet 程序中保存用户登录的信息:


image.png

1.2、修改 login_succuess_menu.jsp:


image.png

1.3、还要修改首页 index.jsp 页面的菜单 :
image.png

2、登出---注销用户

1、销毁 Session 中用户登录的信息(或者销毁 Session)
2、重定向到首页(或登录页面)
UserServlet 程序中添加 logout 方法:


image.png

修改【注销】的菜单地址:


image.png

3、表单重复提交之-----验证码

分析:
image.png

原理图:


image.png

4、谷歌 kaptcha 图片验证码的使用

谷歌验证码 kaptcha 使用步骤如下:
1、导入谷歌验证码的 jar 包:kaptcha-2.3.2.jar
2、在 web.xml 中去配置用于生成验证码的 Servlet 程序:


image.png

3、在表单中(注册页面)使用 img 标签去显示验证码图片并使用它:


image.png

4、在服务器(注册控制层接口)获取谷歌生成的验证码和客户端发送过来的验证码比较使用:
image.png

5、切换验证码:


image.png

阶段七:购物车

1、购物车模块分析

image.png

2、购物车模型编写

2.1、购物车模型:

CartItem:购物车中的商品对象

image.png

Cart:购物车对象:
image.png

补充:BigDecimal类运算Api
image.png

参考文章:https://blog.csdn.net/PacosonSWJTU/article/details/80490464?utm_source=blogxgwz1

package com.kk.pojp;
import lombok.Data;
import java.math.BigDecimal;
import java.util.LinkedHashMap;
import java.util.Map;

//购物车
@Data
public class Cart {
    /**
     * key为商品编号
     * value为具体商品信息
     */
    private Map<Integer, CartItem> items = new LinkedHashMap<> ( );

    /**
     * 添加商品项
     *
     * @param cartItem
     */
    public void addItem(CartItem cartItem) {
        // 先查看购物车中是否已经添加过此商品,如果已添加,则数量累加,总金额更新,如果没有添加过,直接放到 集合中即可

        CartItem item = items.get (cartItem.getId ( ));
        if (item == null) {
            // 之前没添加过此商品
            items.put (cartItem.getId ( ), cartItem);
        } else {
            // 已经 添加过的情况
            item.setCount (item.getCount ( ) + 1);// 数量 累加
            item.setTotalPrice (item.getPrice ( ).multiply (new BigDecimal (item.getCount ( ))));// 更新总金额
        }
    }

    /**
     * 删除商品项
     *
     * @param id
     */
    public void deleteItem(Integer id) {
        items.remove (id);
    }

    /**
     * 清空购物车
     */
    public void clear() {
        items.clear ( );
    }

    /**
     * 修改购物车中的商品数量
     *
     * @param id
     * @param count
     */
    public void updateCount(Integer id, Integer count) {
        // 先查看购物车中是否有此商品。如果有,修改商品数量,更新总金额

        CartItem item = items.get (id);
        if (item != null) {
            item.setCount (count);// 修改商品数量
            item.setTotalPrice (item.getPrice ( ).multiply (new BigDecimal (item.getCount ( ))));// 更新总金额
        }
    }

    /**
     * 获取购物车中总数量
     *
     * @return
     */
    public Integer getTotalCount() {
        Integer totalCount = 0;
        for (Map.Entry<Integer, CartItem> entry : items.entrySet ( )) {
            totalCount += entry.getValue ( ).getCount ( );
        }
        return totalCount;
    }

    /**
     * 获取购物车中总价格
     *
     * @return
     */
    public BigDecimal getTotalPrice() {
        BigDecimal titalPrice = new BigDecimal (0);

        for (Map.Entry<Integer, CartItem> entry : items.entrySet ( )) {
            titalPrice = titalPrice.add (entry.getValue ( ).getTotalPrice ().multiply (new BigDecimal (entry.getValue ().getCount ())));
        }
        return titalPrice;
    }

    @Override
    public String toString() {
        return "Cart{" + "totalCount=" + getTotalCount ( ) + ", totalPrice=" + getTotalPrice ( ) + ", items=" + items + '}';
    }
}

2.2、购物车的测试:


    @Test
    public void addItem() {
        Cart cart = new Cart ( );
        cart.addItem (new CartItem (1, "java从入门到精通", 1, new BigDecimal (1000), new BigDecimal (1000)));
        cart.addItem (new CartItem (1, "java从入门到精通", 1, new BigDecimal (1000), new BigDecimal (1000)));
        cart.addItem (new CartItem (2, "数据结构与算法", 1, new BigDecimal (100), new BigDecimal (100)));
        System.out.println (cart);
    }

    @Test
    public void deleteItem() {
        Cart cart = new Cart ( );
        cart.addItem (new CartItem (1, "java从入门到精通", 1, new BigDecimal (1000), new BigDecimal (1000)));
        cart.addItem (new CartItem (1, "java从入门到精通", 1, new BigDecimal (1000), new BigDecimal (1000)));
        cart.addItem (new CartItem (2, "数据结构与算法", 1, new BigDecimal (100), new BigDecimal (100)));
        cart.deleteItem (1);
        System.out.println (cart);
    }

    @Test
    public void clear() {
        Cart cart = new Cart ( );
        cart.addItem (new CartItem (1, "java从入门到精通", 1, new BigDecimal (1000), new BigDecimal (1000)));
        cart.addItem (new CartItem (1, "java从入门到精通", 1, new BigDecimal (1000), new BigDecimal (1000)));
        cart.addItem (new CartItem (2, "数据结构与算法", 1, new BigDecimal (100), new BigDecimal (100)));
        cart.deleteItem (1);
        cart.clear ( );
        System.out.println (cart);
    }

    @Test
    public void updateCount() {
        Cart cart = new Cart ( );
        cart.addItem (new CartItem (1, "java从入门到精通", 1, new BigDecimal (1000), new BigDecimal (1000)));
        cart.addItem (new CartItem (1, "java从入门到精通", 1, new BigDecimal (1000), new BigDecimal (1000)));
        cart.addItem (new CartItem (2, "数据结构与算法", 1, new BigDecimal (100), new BigDecimal (100)));
        cart.deleteItem (1);
        cart.clear ( );
        cart.addItem (new CartItem (1, "java从入门到精通", 1, new BigDecimal (1000), new BigDecimal (1000)));
        cart.updateCount (1, 10);
        System.out.println (cart);
    }

3、加入购物车功能的实现

3.1、CartServlet 程序中的代码:

   private BookService bookService = new BookServiceImpl ();

    /**
     * 加入购物车
     * @param req
     * @param resp
     * @throws ServletException
     * @throws IOException
     */
    protected void addItem(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        // 获取请求的参数 商品编号
        Integer id = WebUtilts.getParameter ("id", Integer.class,req);
        // 调用 bookService.queryBookById(id):Book 得到图书的信息
        Book book = bookService.queryBookById (id);
        // 把图书信息,转换成为 CartItem 商品项
        CartItem cartItem = new CartItem (book.getId ( ), book.getName ( ), 1, book.getPrice ( ), book.getPrice ( ));
        // 调用 Cart.addItem(CartItem);添加商品项
        Cart cart = (Cart) req.getSession ( ).getAttribute ("cart");
        if (cart==null)
        {
            cart = new Cart ();
            req.getSession ().setAttribute ("cart",cart);
        }
        cart.addItem (cartItem);
        System.out.println (cart );
        System.out.println ("请求头Referer的值:  "+req.getHeader ("Referer") );

        // 重定向回原来商品所在的地址页面
        resp.sendRedirect(req.getHeader("Referer"));
    }

3.2、index.jsp 页面 js 的代码:


image.png

3.3、图解说明,如何跳回添加商品的页面:


image.png

4、购物车的展示(cart.jsp页面渲染)

image.png

5、删除购物车商品项

5.1、CartServlet 程序:

    /**
     * 删除购物车
     *
     * @param req
     * @param resp
     * @throws ServletException
     * @throws IOException
     */
    protected void deleteItem(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        // 获取商品编号
        Integer id = WebUtilts.getParameter ("id", Integer.class, req);

        // 获取购物车对象
        Cart cart = (Cart) req.getSession ( ).getAttribute ("cart");
        if (cart != null) {
            // 删除 了购物车商品项
            cart.deleteItem (id);
            System.out.println ("请求头Referer的值:  " + req.getHeader ("Referer"));
            // 重定向回原来购物车展示页面
            resp.sendRedirect (req.getHeader ("Referer"));
        }
    }

5.2、购物车/pages/cart/cart.jsp 页面的代码:

 <td><a class="deleteItem" href="cartServlet?action=deleteItem&id=${entry.value.id}">删除</a></td>

5.3、删除的确认提示操作:

$(function () {
        // 给 【删除】绑定单击事件
        $("a.deleteItem").click(function () {
            return confirm('你确定删除【'+$(this).parent().parent().find("td:first").text()+'】吗');
        });
    });

6、清空购物车

6.1、CartServlet 程序:

    /**
     * 清空购物车
     * @param req
     * @param resp
     * @throws ServletException
     * @throws IOException
     */
    protected void clear(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        // 获取购物车对象
        Cart cart = (Cart) req.getSession ( ).getAttribute ("cart");

        if (cart!=null)
        {
            // 清空购物车
            cart.clear ();
            //重定向回来
            resp.sendRedirect (req.getHeader ("Referer"));
        }
    }

6.2、修改 pages/cart/cart.jsp 购物车页面:

<span id="clearCart" class="cart_span"><a href="cartServlet?action=clear">清空购物车</a></span>

6.3、清空的确认提示操作:

 // 给 【清空】绑定单击事件
        $("#clearCart").click(function () {
            return confirm("你确定清空购物车吗");
        });

7、修改购物车商品数量

7.1、CartServlet 程序:

/**
     * 修改商品订单
     * @param req
     * @param resp
     * @throws ServletException
     * @throws IOException
     */
    protected void updateCount(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        // 获取请求的参数 商品编号 、商品数量
        int id = WebUtilts.parseInt (req.getParameter ("id"), 0);
        int count = WebUtilts.parseInt (req.getParameter ("count"), 1);

        // 获取购物车
        Cart cart = (Cart) req.getSession ( ).getAttribute ("cart");

        if (cart!=null)
        {
            // 修改商品数量
            cart.updateCount (id,count);
            // 重定向回去
            resp.sendRedirect (req.getHeader ("Referer"));
        }
    }

7.2、修改 pages/cart/cart.jsp 购物车页面:

                    <td>
                        <input type="text" class="updateCount"
                               bookId="${entry.value.id}"
                               value="${entry.value.count}"
                               style="width: 80px">
                    </td>

7.3、修改商品数量 js 代码:

// 给输入框绑定 onchange 内容发生改变事件
        // 注:通过渲染出来的多个控件,动态绑定用class选择器()
        // change-改变,发生改变触发
        $(".updateCount").change(function () {
            // 获取商品名称
            var name = $(this).parent().parent().find("td:first").text();
            var id = $(this).attr("bookId");

            // 获取商品数量
            //var count = $(this).val(); //写法一
            var count = this.value;
            if (confirm("你确定把【" + name + "】商品修改数量为:" + count + "吗?")) {
                //发起请求。给服务器保存修改
                location.href = 'cartServlet?action=updateCount&id=' + id + "&count=" + count;
            }
            else {
                // defaultValue 属性是表单项 Dom 对象的属性。它表示默认的 value 属性值。
                this.value = this.defaultValue;
            }
        });

8、首页,购物车数据回显

8.1、在添加商品到购物车的时候,保存最后一个添加的商品名称:


cartServlet/add

8.2、在 pages/client/index.jsp 页面中输出购物车信息:


image.png

阶段八:订单

1、订单模块的分析:

image.png

2:订单模块的实现:

2.1、创建订单模块的数据库表:

use book; 
create table t_order(
            `order_id` varchar(50) primary key, 
            `create_time` datetime,
            `price` decimal(11,2), 
            `status` int,
            `user_id` int,
            foreign key(`user_id`) references t_user(`id`)
 );
create table t_order_item( 
            `id` int primary key auto_increment,
            `name` varchar(100), `count` int, 
            `price` decimal(11,2),
            `total_price` decimal(11,2),
            `order_id` varchar(50), 
            foreign key(`order_id`) references t_order(`order_id`) 
);

2.2、创建订单模块的数据模型:

/**
 * 订单
 */
public class Order {
    private String orderId;
    private Date createTime;
    private BigDecimal price;
    // 0 未发货,1 已发货,2 表示已签收 
    private Integer status = 0;
    private Integer userId;
}


/**
 * 订单项
 */
@Data
public class OrderItem {
    private Integer id;
    private String name;
    private Integer count;
    private BigDecimal price;
    private BigDecimal totalPrice;//全部的价格
    private String orderId;
}

2.3、编写订单模块的 Dao 程序(接口就不整理进来,但是有):
2.3.1、OrderDao:

public class OrderDaoImpl extends BaseDao implements OrderDao {
    
    @Override
    public int addOrder(Order order) {
        String sql = "insert into t_order(`order_id`,`create_time`,`price`,`status`,`user_id`) values(?,?,?,?,?)";
        
        return update (sql,order.getOrderId (),order.getCreateTime (),order.getPrice (),order.getStatus (),order.getUserId ());
    }
}

2.3.2、OrderItemDap:

public class OrderItemDaoImpl extends BaseDao implements OrderItemDao {
    @Override
    public int addOrderItem(OrderItem orderItem) {
        String sql = "insert into t_order_item(`name`,`count`,`price`,`total_price`,`order_id`) values(?,?,?,?,?)";
        return update (sql,orderItem.getName (),orderItem.getCount (),orderItem.getPrice (),orderItem.getTotalPrice (),
                orderItem.getOrderId ());
    }
}

2.4、编写订单模块的 Service (接口就不整理进来,但是有):
2.4.1、OrderService

public class OrderServiceImpl implements OrderService {

    private OrderDao orderDao = new OrderDaoImpl ();
    private OrderItemDao orderitemDao = new OrderItemDaoImpl ();

    @Override
    public String createOrder(Cart cart, Integer useId) {
        // 订单号===唯一性
        String orderId = System.currentTimeMillis ()+""+useId;
        // 创建一个订单对象
        Order order = new Order (orderId, new Date ( ), cart.getTotalPrice ( ), 0, useId);
        // 保存订单
        orderDao.addOrder (order);

        // 遍历购物车中每一个商品项转换成为订单项保存到数据库
        for (Map.Entry<Integer, CartItem> entry: cart.getItems ().entrySet ())
        {
            // 获取每一个购物车中的商品项
            CartItem item = entry.getValue ( );
            // 转换为每一个订单项
            OrderItem orderItem = new OrderItem (null, item.getName ( ), item.getCount ( ), item.getPrice ( ),
                    item.getTotalPrice ( ), orderId);
            // 保存订单项到数据库
            orderitemDao.addOrderItem (orderItem);

        }
        // 清空购物车
        cart.clear ();
        return orderId;
    }
}

2.5、编写订单模块的 web 层和页面联调:
修改 OrderService 程序:

public class OrderServiceImpl implements OrderService {

    private OrderDao orderDao = new OrderDaoImpl ();
    private OrderItemDao orderitemDao = new OrderItemDaoImpl ();
    private BookDao bookDao = new BookDaoImpl ();

    @Override
    public String createOrder(Cart cart, Integer useId) {
        // 订单号===唯一性
        String orderId = System.currentTimeMillis ()+""+useId;
        // 创建一个订单对象
        Order order = new Order (orderId, new Date ( ), cart.getTotalPrice ( ), 0, useId);
        // 保存订单
        orderDao.addOrder (order);

        // 遍历购物车中每一个商品项转换成为订单项保存到数据库
        for (Map.Entry<Integer, CartItem> entry: cart.getItems ().entrySet ())
        {
            // 获取每一个购物车中的商品项
            CartItem item = entry.getValue ( );
            // 转换为每一个订单项
            OrderItem orderItem = new OrderItem (null, item.getName ( ), item.getCount ( ), item.getPrice ( ),
                    item.getTotalPrice ( ), orderId);
            // 保存订单项到数据库
            orderitemDao.addOrderItem (orderItem);

            // 更新库存和销量(注:book的出售不大于存货,可以setSales处理)
            Book book = bookDao.queryBookById (item.getId ( ));
            book.setSales (book.getSales () + item.getCount ());
            book.setStock (book.getStock () - item.getCount ());
            bookDao.updateBook (book);
        }
        // 清空购物车
        cart.clear ();
        return orderId;
    }
}

OrderServlet 程序:

public class OrderServlet extends BaseServlet{

    private OrderService orderService = new OrderServiceImpl ();

    /**
     * 生成订单
     * @param req
     * @param resp
     * @throws ServletException
     * @throws IOException
     */
    protected void createOrder(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {

        // 先获取 Cart 购物车对象
        Cart cart = (Cart) req.getSession ( ).getAttribute ("cart");
        // 获取 Userid
        User loginUser = (User) req.getSession ( ).getAttribute ("user");

        if (loginUser==null)
        {
            req.getRequestDispatcher ("pages/user/login.jsp").forward (req,resp);
            return;
        }

        Integer userId = loginUser.getId ( );
        // 调用 orderService.createOrder(Cart,Userid);生成订单
        String orderId = orderService.createOrder (cart, userId);

        // 保存订单号
        req.getSession ().setAttribute ("orderId",orderId);
        resp.sendRedirect (req.getContextPath ()+"/pages/cart/checkout.jsp");

    }
}

前端:
1、修改 pages/cart/cart.jsp 页面,结账的请求地址:


image.png

2、修改 pages/cart/checkout.jsp 页面,输出订单号:


image.png

阶段九:过滤器拦截改造

1、使用 Filter 过滤器拦截/pages/manager/所有内容,实 现权限检查:

Filter 代码:

public class ManagerFilter implements Filter {
    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
    }

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        HttpServletRequest request = (HttpServletRequest) servletRequest;
        HttpServletResponse response = (HttpServletResponse) servletResponse;

        Object user = request.getSession ( ).getAttribute ("user");
        if (user == null) {
            request.getRequestDispatcher ("/pages/user/login.jsp").forward (servletRequest, servletResponse);
        } else {
            filterChain.doFilter (servletRequest, servletResponse);
        }
    }

    @Override
    public void destroy() {
    }
}

web.xml代码:

    <filter>
        <filter-name>ManagesFilter</filter-name>
        <filter-class>com.kk.filter.ManagerFilter</filter-class>
    </filter>
    <filter-mapping>
        <filter-name>ManagesFilter</filter-name>
        <url-pattern>/pages/manager/*</url-pattern>
        <url-pattern>/manager/bookServlet</url-pattern>
    </filter-mapping>

2、ThreadLocal 的使用

2.1概要:

作用:
ThreadLocal 的作用,它可以解决多线程的数据安全问题。
ThreadLocal 它可以给当前线程关联一个数据(可以是普通变量,可以是对象,也可以是数组,集合)
特点:
1、ThreadLocal 可以为当前线程关联一个数据。(它可以像 Map 一样存取数据,key 为当前线程)
2、每一个 ThreadLocal 对象,只能为当前线程关联一个数据,如果要为当前线程关联多个数据,就需要使用多个 ThreadLocal 对象实例。
3、每个 ThreadLocal 对象实例定义的时候,一般都是 static 类型
4、ThreadLocal 中保存数据,在线程销毁后。会由 JVM 虚拟自动释放。

2.2、测试类:案例

package com.kk.test;
import java.util.Random;
public class OrderServiceTest {
    public void createOrder() {
        String name = Thread.currentThread ( ).getName ( );
        System.out.println ("Service-----当前线程名为【" + name + "】,保存为的数据为:" + ThreadLocalTest.threadLocal.get ( ));
        new OrderDaoTest ( ).saveOrder ( );
    }
}

class OrderDaoTest {
    public void saveOrder() {
        String name = Thread.currentThread ( ).getName ( );
        System.out.println ("Dao------当前线程名为:【" + name + "】,中保存的数据:" + ThreadLocalTest.threadLocal.get ( ));
    }
}

class ThreadLocalTest {

    public static ThreadLocal<Object> threadLocal = new ThreadLocal<> ( );
    private static Random random = new Random ( );

    public static class Task implements Runnable {
        @Override
        public void run() {
            // 在 Run 方法中,随机生成一个变量(线程要关联的数据),然后以当前线程名为 key 保存到 map 中
            Integer i = random.nextInt (1000);
            // 获取当前线程名
            String name = Thread.currentThread ( ).getName ( );
            System.out.println ("web--------当前线程名为:【" + name + "】,生成的随机数:" + i);
            threadLocal.set (i);

            try {
                Thread.sleep (3000);
            } catch (InterruptedException e) {
                e.printStackTrace ( );
            }
            new OrderServiceTest ( ).createOrder ( );

            // 在 Run 方法结束之前,以当前线程名获取出数据并打印。查看是否可以取出操作
            Object o = threadLocal.get ( );
            System.out.println ("close-------在线程【" + name + "】快结束时取出关联的数据是:" + o);

        }

        public static void main(String[] args) {
            for (int i = 0; i < 3; i++) {
                new Thread (new Task ( )).start ( );
            }
        }
    }
}

/*****************************运行测试某次输出(线程每次数次都不同)********************************
web--------当前线程名为:【Thread-1】,生成的随机数:25
web--------当前线程名为:【Thread-0】,生成的随机数:416
web--------当前线程名为:【Thread-2】,生成的随机数:360
Service-----当前线程名为【Thread-0】,保存为的数据为:416
Service-----当前线程名为【Thread-1】,保存为的数据为:25
Service-----当前线程名为【Thread-2】,保存为的数据为:360
Dao------当前线程名为:【Thread-1】,中保存的数据:25
close-------在线程【Thread-1】快结束时取出关联的数据是:25
Dao------当前线程名为:【Thread-2】,中保存的数据:360
Dao------当前线程名为:【Thread-0】,中保存的数据:416
close-------在线程【Thread-0】快结束时取出关联的数据是:416
close-------在线程【Thread-2】快结束时取出关联的数据是:360

3、使用 Filter 和 ThreadLocal 组合管理事务

3.1、使用 ThreadLocal 来确保所有 dao 操作都在同一个 Connection 连接对象中完 成
原理分析图:


image.png

javaEE-X-各层封装(反射)&&工具类](https://www.jianshu.com/p/184fa8875dd8)
JdbcUtils 工具类的修改:
BaseDao工具类的修改:

3.2、使用 Filter 过滤器统一给所有的 Service 方法都加上 try-catch。来进行实现的管理
原理分析图:


image.png

Filter 类代码:

public class TransactionFilter implements Filter {
    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        try {
            filterChain.doFilter (servletRequest, servletResponse);
            JdbcUtils.commitAndClose ();//提交事务
        } catch (IOException |ServletException e) {
            JdbcUtils.rollbackAndClose ();//回滚事务
            e.printStackTrace ( );
        }
    }

在 web.xml 中的配置:

<!--设置事务过滤器-->
    <filter>
        <filter-name>TransactionFilter</filter-name>
        <filter-class>com.kk.filter.TransactionFilter</filter-class>
    </filter>
    <filter-mapping>
        <filter-name>TransactionFilter</filter-name>
        <!-- /* 表示当前工程下所有请求 -->
        <url-pattern>/*</url-pattern>
    </filter-mapping>

一定要记得把 BaseServlet 中的异常往外抛给 Filter 过滤器:
在原有基础加了句throw new RuntimeException(e);

public abstract class BaseServlet extends HttpServlet {

    //<a>标签请求默认为GET,所以。。
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        doPost(req, resp);
    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        req.setCharacterEncoding ("UTF-8");//处理post乱码
        resp.setCharacterEncoding ("utf-8");//在页面显示测试防止乱码
        resp.setContentType ("text/html;charset=UTF-8");

        String action = req.getParameter ("action");
        try {
            // 获取action业务鉴别字符串,获取相应的业务 方法反射对象
            Method method = this.getClass ( ).getDeclaredMethod (action, HttpServletRequest.class, HttpServletResponse.class);
            method.setAccessible (true);
            //System.out.println (method);
            // 调用目标业务 方法
            method.invoke (this, req, resp);
        } catch (Exception e) {
            e.printStackTrace ( );
            throw new RuntimeException (e);
        }
    }
}

3.3、将所有异常都统一交给 Tomcat,让 Tomcat 展示友好的错误信息页面
web.xml 中配置错误页面:

    <!--报错500显示页面-->
    <error-page>
        <error-code>500</error-code>
        <location>/pages/error/error500.jpg</location>
    </error-page>
    
    <!--报错404显示页面-->
    <error-page>
        <error-code>404</error-code>
        <location>/pages/error/error404.jpg</location>
    </error-page>

阶段十:Ajax改造

1、Ajax 验证用户名是否可用。

需求分析:
使用 Ajax 验证用户名是否可用。我们需要在页面端,给用户名输入框添加一个失去焦点事件。当用户名输入框失去 焦点的时候,触发事件。获取输入的用户名。然后发送 Ajax 请求到服务器諯去验证。 然后服务器通过 json 数据,返回是否存在,result 为 0 表示 不存在,result 为 1 表示存在。当然我们还要做一个用 户名不为空的简单验证。才能让请求发送到服务器端。

1.1、修改 pages/user/regist.jsp 页面。给用户名输入框添加失去焦点事件

//用户名是否存在的验证
    $("#username").blur(function () {
        // 获取用户名
        var usernameValue = this.value;
        // 判断用户名不能为空
        if (usernameValue == "") {
            $("#errorSpan").html("用户名不能为空");
            return;
        }
        // 发送 ajax 请求验证
        $.getJSON("userServlet?action=existsUsername&username=" + usernameValue, function (data) {
            // result 等于 0,说明用户名不存在
            if (data == 0) {
                $("#errorSpan").html("用户名可用");
            } else if (data == 1) {
                $("#errorSpan").html("用户名已经存在");
            }
        });
    });

1.2、修改 UserServlet 类,添加检查用户名是否存在的方法:

    /**
     * ajax校验用户名
     *
     * @param req
     * @param resp
     * @throws ServletException
     * @throws IOException
     */
    protected void existsUsername(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        String username = req.getParameter ("username");
        if (userService.existsUsername (username)) {
            //说明用户名存在,返回 1
            resp.getWriter ().write ("1");
        } else {
            //用户名不存在,返回 0
            resp.getWriter ().write ("0");
        }
    }

理想写法:


image.png

2、Ajax 修改购物车模块---添加商品---修改数量

需求:
以 Ajax 请求的方式修改购物车的模块。我们修改的功能有,添加到购物车,修改数量,以及删除商品,和清空购物 车。我们以添加购物车和修改商品数量为例

2.1、添加商品:

添加商品到购物车。首先我们要把商品的编号,以 Ajax 的方式传到服务器。然后器添加成功后把购物车的数量,最 后一本书的名字返回,给用户显示。

2.1.1、修改 pages/client/index.jsp 页面,添加购物车的 a 标签代码:

 <div class="book_add">
         <!--<button bookId="${book.id}" class="addToCart" onclick="addBookCart(this)">加入购物车</button>-->
             <a idv="${book.id}" class="addToCart" onclick="return onajax(this)">加入购物车</a>
 </div>

2.1.2、添加 Ajax 请求的 js 代码:

<script src="static/script/jquery-1.7.2.js"></script>
<script>
    // 添加购物车 Ajax 请求
    function onajax(da) {
        // 通过添加 idv 属性保存商品 id 信息
        var idv = $(da).attr("idv");
        $.getJSON("cartServlet?action=ajaxAddItem&id=" + idv, function (data) {
            if (data.result == 0) {
                $("#cart_totalCount").html("您购物车有<span style='color: salmon;'>" + data.totalCount + "</span>件商品");
                $("#last_product").html("您刚刚将<span style='color:indianred;'>" + data.lastProduct + "</span>添加到购物车");
            }
        });
        return false;
    };
</script>

2.1.3、修改添加购物车后。搜索下方的购物车显示:

注:为什么购物车空还要绑定id?,很简单,为了第一次进来是没有数据的,ajax只能在没有数据的框上面进行渲染,而不是在有数据的框内。
<div style="text-align: center">
            <k:choose>
                <%--购物车为空的输出--%>
                <k:when test="${empty cart.items}">
                    <span id="cart_totalCount">您的购物车为空</span>
                    <div id="last_product">&nbsp;</div>
                </k:when>
                <%--购物车不为空的输出--%>
                <k:otherwise>
                    <span id="cart_totalCount">您的购物车中有<span style="color: salmon">${ cart.totalCount }</span>件商品</span>
                    <div id="last_product">您刚刚将<span style="color: indianred">${ sessionScope.lastProduct }</span>加入到了购物车中
                    </div>
                </k:otherwise>
            </k:choose>
</div>

2.1.4、 CartServlet 中添加 Ajax 版的添加购物车代码:


    /**
     * Ajax 版--添加到购物车
     * @param req
     * @param resp
     * @throws ServletException
     * @throws IOException
     */
    protected void ajaxAddItem(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        // 获取购物车
        Cart cart = (Cart) req.getSession ( ).getAttribute ("cart");
        if (cart == null) {
            // 生成一个新的购物车,放到 Session 对象中
            cart = new Cart ();
            req.getSession ().setAttribute ("cart",cart);
        }
        // 获取图书 的 id
        Integer id = WebUtilts.getParameter ("id", Integer.class, req);
        // 查找图书
        Book book = bookService.queryBookById (id);
        // 图书转换商品
        CartItem cartItem = new CartItem (book.getId ( ), book.getName ( ), 1, book.getPrice ( ), book.getPrice ( ));
        // 添加到购物车中
        cart.addItem (cartItem);
        // 添加最后一个商品名到 Session 对象中
        req.getSession ().setAttribute ("lastProduct",cartItem.getName ());
        // 打印测试
        System.out.println(cart);
        // 创建一个 map 用于返回结果
        Map<String, Object> result = new HashMap<> ( );
        result.put ("result",0);
        result.put ("totalCount",cart.getTotalCount ());
        result.put ("lastProduct",cartItem.getName ());
        Gson gson = new Gson ( );
        resp.getWriter ().write (gson.toJson (result));
    }

2.2、修改数量

分析:
修改购物车数量,我们要把修改的商品编号和数量发送到服务器。然后服务器把修改后商品的总价 item_totalMoney, 购物车的总数量 cart_totalCount,以及购物车的总金额 cart_totalMoney 返回用于前端的修改。

2.2.1、修改原来购物车更新数量的方法(Cart),返回修改后商品的总金额:

    /**
     * 修改购物车中的商品数量(更新前)
     *
     * @param id
     * @param count
     */
    public void updateCount(Integer id, Integer count) {
        // 先查看购物车中是否有此商品。如果有,修改商品数量,更新总金额
        CartItem item = items.get (id);
        if (item != null) {
            item.setCount (count);// 修改商品数量
            item.setTotalPrice (item.getPrice ( ).multiply (new BigDecimal (item.getCount ( ))));// 更新总金额
        }
    }
--------------------------------------------------------------------------------------------------------------------------------------------
    /**
     * ajax版:修改购物车中的商品数量(更新后)
     * @param id
     * @param count
     * @return
     */
    public double updateItem(Integer id,Integer count)
    {
        CartItem item = items.get (id);
        if (item !=null)
        {
            item.setCount (count);// 修改商品数量
            item.setTotalPrice (item.getPrice ( ).multiply (new BigDecimal (item.getCount ( ))));// 更新总金额
            return item.getTotalPrice ().intValue ();//返回最终价格, intValue () 转 int
        }
        return 0;
    }

2.2.2、升级Servlet代码:

    /**
     * 修改商品订单(旧:页面跳转)
     * @param req
     * @param resp
     * @throws ServletException
     * @throws IOException
     */
    protected void updateCount(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        // 获取请求的参数 商品编号 、商品数量
        int id = WebUtilts.parseInt (req.getParameter ("id"), 0);
        int count = WebUtilts.parseInt (req.getParameter ("count"), 1);
        // 获取购物车
        Cart cart = (Cart) req.getSession ( ).getAttribute ("cart");
        if (cart!=null)
        {
            // 修改商品数量
            cart.updateCount (id,count);

            // 重定向回去
            resp.sendRedirect (req.getHeader ("Referer"));

        }
    }
--------------------------------------------------------------------------------------------------------------------------------------------
    /**
     * 修改商品订单(新:ajax版)
     * @param req
     * @param resp
     * @throws ServletException
     * @throws IOException
     */
    protected void updateItemCount(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        // 获取请求的参数 商品编号 、商品数量
        int id = WebUtilts.parseInt (req.getParameter ("id"), 0);
        int count = WebUtilts.parseInt (req.getParameter ("count"), 1);
        // 获取购物车
        Cart cart = (Cart) req.getSession ( ).getAttribute ("cart");
        if (cart != null) {
            // 修改商品数量
            double item_totalmonery = cart.updateItem (id, count);

            // 创建一个 Map 返回要显示的内容
            Map<String, Object> result = new HashMap<> ( );
            result.put ("item_totalmonery",item_totalmonery);
            result.put ("cart_titalmonery",cart.getTotalPrice ());
            result.put ("cart_titalcount",cart.getTotalCount ());

            //用ajax后不需要页面跳转
            Gson gson = new Gson ( );
            resp.getWriter ( ).write (gson.toJson (result));
        }
    }

2.2.3、修改 pages/cart/cart.jsp 页面中的内容:

改动点1:

改动点2:

项目需求补充:

1、主页购物车点击后,应该开启一个新页面:

1.1、需要改动页面:


image.png

1.2、变动的代码:


最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 219,490评论 6 508
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,581评论 3 395
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 165,830评论 0 356
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,957评论 1 295
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,974评论 6 393
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,754评论 1 307
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,464评论 3 420
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,357评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,847评论 1 317
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,995评论 3 338
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,137评论 1 351
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,819评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,482评论 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 32,023评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,149评论 1 272
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,409评论 3 373
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 45,086评论 2 355