JavaWeb尚硅谷网上书城项目总结(下)

第七阶段:购物车(Session版本)

7.1 提取购物车模型

  1. 购物车模块分析:
    在这里插入图片描述
  2. 购物车商品项
    public class CartItem {
          
          
        private Integer id;
        private String name;
        private Integer count;
        private BigDecimal price;
        private BigDecimal totalPrice;
    }
    
  3. 购物车对象
    public class Cart {
          
          
    
        private Map<Integer,CartItem> items = new HashMap<Integer,CartItem>();
    
        /**
         * 添加商品项
         */
        public void addItem(CartItem cartItem){
          
          
            CartItem item = items.get(cartItem.getId());
            if(item == null){
          
          
                //之前没添加过此商品
                items.put(cartItem.getId(),cartItem);
            }else{
          
          
                //已经 添加过的情况
                item.setCount(cartItem.getCount()+item.getCount());
                item.setTotalPrice(cartItem.getPrice().multiply(new BigDecimal(item.getCount())));
            }
        }
    
        /**
         * 删除商品项
         */
        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 cartItem = items.get(id);
            if(cartItem != null){
          
          
                cartItem.setCount(count);//修改商品数量
                cartItem.setTotalPrice(cartItem.getPrice().multiply(new BigDecimal(cartItem.getCount())));
            }
        }
    
        //得到购物车总数量
        public Integer getTotalCount() {
          
          
            Integer totalCount = 0;
            for(Map.Entry<Integer,CartItem> entry : items.entrySet()){
          
          
                totalCount += entry.getValue().getCount();
            }
            return totalCount;
        }
    
        //得到购物车总价格
        public BigDecimal getTotalPrice() {
          
          
            BigDecimal totalPrice = new BigDecimal(0);
            for(Map.Entry<Integer,CartItem> entry : items.entrySet()){
          
          
                totalPrice = totalPrice.add(entry.getValue().getTotalPrice());
            }
            return totalPrice;
        }
    }
    

7.2 加入购物车——如何跳回添加商品的页面(重点)

  1. CartServlet 程序中的代码:

        /**
         * 加入购物车
         * @param req
         * @param resp
         * @throws ServletException
         * @throws IOException
         */
        protected void addItem(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
          
          
    
            // 获取请求的参数 商品编号
            Integer id = WebUtils.parseInt(req.getParameter("id"),0);
    
            // 调用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);
            //将最后一个添加的商品名称加到session中
            req.getSession().setAttribute("lastName",cartItem.getName());
    
            //重定向回之前的页面
            String referer = req.getHeader("Referer");
            resp.sendRedirect(referer);
        }
    
  2. index.jsp 页面 js 的代码:

    在这里插入图片描述

    //加入购物车操作
    $(".addCartItem").click(function(){
          
          
    	var bookId = $(this).attr("bookId");
    	$.getJSON("http://localhost:8080/07_book/cartServlet","action=ajaxAddItem&id="+bookId,function(data){
          
          
    		$("#cartTotalCount").text("您的购物车中有"+data.totalCount+"件商品");
    		$("#cartLastName").text(data.lastName);
    	})
    	//之前使用同步的方式处理
    	//在事件相应的function函数中,有一个this对象,这个this对象,是当前正在响应事件的dom对象
    	// location.href = "http://localhost:8080/07_book/cartServlet?action=addItem&id="+bookId;
    });
    
  • 重点:如何跳回添加商品的页面
    HTTP 请求的头信息里面,Referer字段实际上告诉了服务器,用户在访问当前资源之前的位置
    在这里插入图片描述

    //重定向回之前的页面
    String referer = req.getHeader("Referer");
    resp.sendRedirect(referer);
    

7.3 购物车的展示(购物车存在session中)

<%@ 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" %>
</head>
<body>
	
	<div id="header">
		<img class="logo_img" alt="" src="static/img/logo.gif" >
		<span class="wel_word">购物车</span>
		<%@ include file="/pages/common/login_success_menu.jsp"%>
	</div>
	
	<div id="main">
	
		<table>
			<tr>
				<td>商品名称</td>
				<td>数量</td>
				<td>单价</td>
				<td>金额</td>
				<td>操作</td>
			</tr>
			<c:if test="${empty sessionScope.cart.items}">
				<%--如果购物车为空的情况--%>
				<tr>
					<td colspan="5"><a href="index.jsp">亲,您的购物车为空</a></td>
				</tr>
			</c:if>
			<c:if test="${not empty sessionScope.cart.items}">
				<%--如果购物车非空的情况--%>
				<c:forEach items="${sessionScope.cart.items}" var="entry">
					<tr>
						<td>${entry.value.name}</td>
						<td><input class="updateCount" type="text" bookId="${entry.value.id}"
								   value="${entry.value.count}" style="width:50px"/></td>
						<td>${entry.value.price}</td>
						<td>${entry.value.totalPrice}</td>
						<td><a class="deleteItem" href="cartServlet?action=deleteItem&id=${entry.value.id}">删除</a></td>
					</tr>
				</c:forEach>
			</c:if>
		</table>

		<%--如果购物车非空才输出页面的内容--%>
		<c:if test="${not empty sessionScope.cart.items}">
			<div class="cart_info">
				<span class="cart_span">购物车中共有<span class="b_count">${sessionScope.cart.totalCount}</span>件商品</span>
				<span class="cart_span">总金额<span class="b_price">${sessionScope.cart.totalPrice}</span></span>
				<span class="clearCart"><a href="cartServlet?action=clear">清空购物车</a></span>
				<span class="cart_span"><a href="pages/cart/checkout.jsp">去结账</a></span>
			</div>
		</c:if>
	</div>

	<%--静态包含页脚内容--%>
	<%@ include file="/pages/common/footer.jsp" %>
</body>
</html>

7.4 修改购物车中的商品数量

  1. 修改 pages/cart/cart.jsp 购物车页面
    在这里插入图片描述

  2. 修改商品数量 js 代码:
    在这里插入图片描述

  3. CartServlet 程序

    /**
     * 修改商品数量
     * @param req
     * @param resp
     * @throws ServletException
     * @throws IOException
     */
    protected void updateCount(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
          
          
        //获取请求的参数 商品编号、商品数量
        int id = WebUtils.parseInt(req.getParameter("id"), 0);
        int count = WebUtils.parseInt(req.getParameter("count"), 0);
        //获取cart购物车对象
        Cart cart = (Cart)req.getSession().getAttribute("cart");
        if(cart != null){
          
          
            //修改商品数量
            cart.updateCount(id,count);
            //重定向回原来购物车展示页面
            resp.sendRedirect(req.getHeader("Referer"));
        }
    }
    

第八阶段——订单模块

在这里插入图片描述

第九阶段——事务处理(重点、难点)

9.1 使用 Filter 过滤器实现权限检查

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 httpServletRequest = (HttpServletRequest) servletRequest;
        Object user = httpServletRequest.getSession().getAttribute("user");
        if(user == null){
    
    
            //如果没有登录跳转到登录页面
            httpServletRequest.getRequestDispatcher("/pages/user/login.jsp").forward(servletRequest,servletResponse);
            return ;
        }else{
    
    
            //继续执行接下来的操作
            filterChain.doFilter(servletRequest,servletResponse);
        }
    }

    @Override
    public void destroy() {
    
    

    }
}

9.2 ThreadLocal的使用(难点)

  • ThreadLocal 的作用,它可以解决多线程的数据安全问题。

  • ThreadLocal 它可以给当前线程关联一个数据(可以是普通变量,可以是对象,也可以是数组,集合)

  • ThreadLocal 的特点:

      1、ThreadLocal 可以为当前线程关联一个数据。(它可以像Map一样存取数据,key 为当前线程)
      2、每一个 ThreadLocal 对象,只能为当前线程关联一个数据,如果要为当前线程关联多个数据,就需要使用多个ThreadLocal 对象实例。
      3、每个 ThreadLocal 对象实例定义的时候,一般都是 static 类型
      4、ThreadLocal 中保存数据,在线程销毁后。会由 JVM 虚拟自动释放
    

测试代码:

public class ThreadLocalTest {
    
    

//    public static Map<String,Object> data = new Hashtable<>();
    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("线程["+name+"]生成的随机数是:" + i);

//            data.put(name,i);
            threadLocal.set(i);
            try {
    
    
                Thread.sleep(3000);
            } catch (InterruptedException e) {
    
    
                e.printStackTrace();
            }
            new OrderService().createOrder();


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

    public static void main(String[] args){
    
    
        for(int i = 0;i<3;i++){
    
    
            new Thread(new Task()).start();
        }
    }
}
class OrderService {
    
    
    public void createOrder(){
    
    
        String name = Thread.currentThread().getName();
        System.out.println("OrderService 当前线程[" + name + "]中保存的数据是:" + ThreadLocalTest.threadLocal.get());
    }
}

执行结果:
在这里插入图片描述

9.3 使用Filter和ThreadLocal组合管理事务(难点、重点)

在这里插入图片描述

分析订单结账功能,既要生成订单,还要生成订单项,同时还要保证商品的库存等等。所以必须使用事务保证这些操作要么都成功,要么都失败。
在这里插入图片描述

要求:
1.保证所有操作都在一个事务内。就必须保证,所有操作都是用同一个Connection连接对象。
2.如何确保所有操作使用同一个Connection连接对象?
可以使用ThreadLocal对象,来确保所有操作都是用同一个Connection对象。(前提条件:所有的操作必须在同一个线程中完成)

  1. 使用ThreadLocal来确保所有dao操作都在同一个Connection连接对象中完成

    JdbcUtils 工具类的修改:

    public class JDBCUtils {
          
          
    
        private static DataSource dataSource = null;
        private static ThreadLocal<Connection> conns = new ThreadLocal<Connection>();
    
        static{
          
          
            try {
          
          
                Properties properties = new Properties();
                InputStream is = JDBCUtils.class.getClassLoader().getResourceAsStream("jdbc.properties");
                properties.load(is);
                dataSource = DruidDataSourceFactory.createDataSource(properties);
            } catch (Exception e) {
          
          
                e.printStackTrace();
            }
        }
    
        /**
         * 获取数据库连接池中的连接
         * @return
         */
        public static Connection getConnection(){
          
          
            Connection conn = conns.get();
            if(conn == null){
          
          
                try {
          
          
                    conn = dataSource.getConnection();
                    conns.set(conn);//保存到ThreadLocal对象中,供后面的jdbc操作使用
                    conn.setAutoCommit(false);//设置为手动管理事务
                } catch (Exception e) {
          
          
                    e.printStackTrace();
                }
            }
            return conn;
        }
    
        /**
         * 提交事务,并关闭释放连接
         */
        public static void commitAndClose(){
          
          
            Connection conn = conns.get();
            if(conn != null){
          
          
                try {
          
          
                    conn.commit();//提交事务
                } catch (Exception e) {
          
          
                    e.printStackTrace();
                }finally{
          
          
                    try {
          
          
                        conn.close();//关闭连接,资源释放
                    } catch (SQLException e) {
          
          
                        e.printStackTrace();
                    }
                }
            }
            //移除conns中的连接,防止下次发现不为空直接使用导致出错
            conns.remove();
        }
    
        /**
         * 回滚事务,并关闭释放连接
         */
        public static void rollbackAndClose(){
          
          
            Connection conn = conns.get();
            if(conn != null){
          
          
                try {
          
          
                    conn.rollback();//回滚事务
                } catch (SQLException e) {
          
          
                    e.printStackTrace();
                }finally{
          
          
                    try {
          
          
                        conn.close();//关闭连接,资源释放
                    } catch (SQLException e) {
          
          
                        e.printStackTrace();
                    }
                }
            }
            //移除conns中的连接,防止下次发现不为空直接使用导致出错
            conns.remove();
        }
    }
    
  2. 修改 BaseDao:(把异常捕获改成异常抛出)

    public abstract class BaseDao {
          
          
    
        private QueryRunner queryRunner = new QueryRunner();
    
        /**
         * update() 方法用来执行 insert/update/delete语句
         * @param sql
         * @param args
         * @return 如果返回-1,说明执行失败,返回其他表示受影响的行数
         */
        public int update(String sql,Object...args){
          
          
    
            Connection conn = null;
            try {
          
          
                conn = JDBCUtils.getConnection();
                return queryRunner.update(conn,sql,args);
            } catch (SQLException e) {
          
          
                e.printStackTrace();
                throw new RuntimeException(e);
            }
        }
    
        /**
         * 查询返回一个javaBean的sql语句
         * @param type  返回的对象类型
         * @param sql   执行的sql语句
         * @param args  sql对应的参数值
         * @param <T>   返回的类型的泛型
         * @return
         */
        public <T> T queryForOne(Class<T> type,String sql,Object...args){
          
          
            Connection conn = null;
            try{
          
          
                conn = JDBCUtils.getConnection();
                return queryRunner.query(conn,sql,new BeanHandler<T>(type),args);
            }catch(Exception e){
          
          
                e.printStackTrace();
                throw new RuntimeException(e);
            }
        }
    
        /**
         * 查询返回多个javaBean的sql语句
         * @param type  返回的对象类型
         * @param sql   执行的sql语句
         * @param args  sql对应的参数值
         * @param <T>   返回的类型的泛型
         * @return
         */
        public <T> List<T> queryForList(Class<T> type, String sql, Object...args){
          
          
            Connection conn = null;
            try{
          
          
                conn = JDBCUtils.getConnection();
                return queryRunner.query(conn,sql,new BeanListHandler<T>(type),args);
            }catch(Exception e){
          
          
                e.printStackTrace();
                throw new RuntimeException(e);
            }
        }
    
        /**
         * 执行返回一行一列的sql语句
         * @param sql   执行的sql语句
         * @param args  sql对应的参数值
         * @return
         */
        public Object queryForSingleValue(String sql,Object...args){
          
          
            Connection conn = null;
            try{
          
          
                conn = JDBCUtils.getConnection();
                return queryRunner.query(conn,sql,new ScalarHandler(),args);
            }catch(Exception e){
          
          
                e.printStackTrace();
                throw new RuntimeException(e);
            }
        }
    }
    

9.4 使用 Filter过滤器统一给所有的Service方法都加上try-catch

分析:
在这里插入图片描述
Filter类代码:

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

    }
    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
    
    
        try {
    
    
            filterChain.doFilter(servletRequest,servletResponse);
            JDBCUtils.commitAndClose();//提交事务
        } catch (Exception e) {
    
    
            JDBCUtils.rollbackAndClose();//回滚事务
            e.printStackTrace();
        }
    }
    @Override
    public void destroy() {
    
    
    }
}

web.xml文件:

    <filter>
        <filter-name>TransactionFilter</filter-name>
        <filter-class>com.zb.filter.TransactionFilter</filter-class>
    </filter>
    <filter-mapping>
        <filter-name>TransactionFilter</filter-name>
        <!-- /* 表示当前工程下所有请求-->
        <url-pattern>/*</url-pattern>
    </filter-mapping>

一定要记得把 BaseServlet 中的异常往外抛给 Filter 过滤器:

public abstract class BaseServlet extends HttpServlet {
    
    

    @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);
            //调用目标业务
            method.invoke(this,req,resp);
        } catch (Exception e) {
    
    
            e.printStackTrace();
            throw new RuntimeException(e);//把异常抛给Filter过滤器
        }
    }

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
    
    
        doPost(req,resp);
    }
}

9.5 将所有异常都统一交给Tomcat,让Tomcat展示友好的错误信息页面

在 web.xml 中我们可以通过错误页面配置来进行管理

    <!--error-page标签配置,服务器出错之后,自动跳转的页面-->
    <error-page>
        <!--error-code是错误类型-->
        <error-code>500</error-code>
        <!--location标签表示。要跳转去的页面路径-->
        <location>/pages/error/error500.jsp</location>
    </error-page>

    <!--error-page标签配置,服务器出错之后,自动跳转的页面-->
    <error-page>
        <!--error-code是错误类型-->
        <error-code>404</error-code>
        <!--location标签表示。要跳转去的页面路径-->
        <location>/pages/error/error404.jsp</location>
    </error-page>

注意Filter类中要将异常抛给Tomcat:

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
    
    

        try {
    
    
            filterChain.doFilter(servletRequest,servletResponse);
            JDBCUtils.commitAndClose();//提交事务
        } catch (Exception e) {
    
    
            JDBCUtils.rollbackAndClose();//回滚事务
            e.printStackTrace();
            throw new RuntimeException(e);//把异常抛给Tomcat管理展示友好的错误页面
        }
    }

第十阶段——使用AJAX

10.1 使用 AJAX 验证用户名是否可用

在这里插入图片描述

regist.jsp 页面中的代码:

//当输入框失去焦点时触发
$("#username").blur(function(){
    
    
	//获取用户名
	var username = this.value;
	$.getJSON("http://localhost:8080/07_book/userServlet","action=ajaxExistUsername&username="+username,function(data){
    
    
		if(data.existUsername){
    
    
			$("span.errorMsg").text("用户名已存在!");
		}else{
    
    
			$("span.errorMsg").text("用户名可用!");
		}
	});
});

UserServlet 程序中 ajaxExistsUsername 方法:

protected void ajaxExistUsername(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
    
    
    String username = req.getParameter("username");
    boolean existUsername = userService.existsUsername(username);
    Map<String,Object> resultMap = new HashMap<>();
    resultMap.put("existUsername",existUsername);

    Gson gson = new Gson();
    String json = gson.toJson(resultMap);
    resp.getWriter().write(json);
}

10.2 使用AJAX修改把商品添加到购物车

在这里插入图片描述
pages/client/index.jsp 页面代码:

//加入购物车操作
$(".addCartItem").click(function(){
    
    
	var bookId = $(this).attr("bookId");
	$.getJSON("http://localhost:8080/07_book/cartServlet","action=ajaxAddItem&id="+bookId,function(data){
    
    
		$("#cartTotalCount").text("您的购物车中有"+data.totalCount+"件商品");
		$("#cartLastName").text(data.lastName);
	})
	//之前使用同步的方式处理
	//在事件相应的function函数中,有一个this对象,这个this对象,是当前正在响应事件的dom对象
	// location.href = "http://localhost:8080/07_book/cartServlet?action=addItem&id="+bookId;
});

CartServlet 程序:

/**
 * 使用ajax加入购物车
 * @param req
 * @param resp
 * @throws ServletException
 * @throws IOException
 */
protected void ajaxAddItem(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
    
    

    // 获取请求的参数 商品编号
    Integer id = WebUtils.parseInt(req.getParameter("id"),0);

    // 调用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);
    //将最后一个添加的商品名称加到session中
    req.getSession().setAttribute("lastName",cartItem.getName());

    //返回购物车总的商品数量和最后一个添加的商品名称
    Map<String,Object> resultMap = new HashMap<String,Object>();
    resultMap.put("totalCount",cart.getTotalCount());
    resultMap.put("lastName",cartItem.getName());

    Gson json = new Gson();
    String resultMapJsonString = json.toJson(resultMap);
    resp.getWriter().write(resultMapJsonString);
}

总结

jQuery和JavaScript事件中的return false

问题的引入:想给a标签添加点击事件,但又不想实现跳转。

解决:
在事件的函数中return false表示阻止与事件关联的默认动作并停止事件的传播。

JQuery的代码:
在这里插入图片描述

JavaScript代码:

错误代码:
在这里插入图片描述
正确代码:
在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/weixin_44630656/article/details/114679952