JavaWeb入门实战项目——书城项目


一、用户的登录和注册

用户注册模型分析
注册模型分析
用户登录模型分析
在这里插入图片描述

  1. 创建存储的项目数据库book和对应的用户表t_user
  2. 创建于数据库对应的 user类 即javabase类
  3. 创建数据库连接池
  4. 编写DAO,实现对数据库的增删查改操作
  5. 编写 service,实现业务:登录、注册
  6. 编写 web层在这里插入图片描述
    在这里插入图片描述

二、优化

  1. 将所有页面改为jsp 动态化

     	去除页面缓存:ctr + shift + delete 删除浏览器缓存
       	IDEA查找替换:
        本页面里面按住 ctr + R
        按项目、目录,按住 ctr + shift + R
    
  2. 抽取多个页面中的重复部分,换成jsp的静态包含方便后期维护

  3. base标签中写死了localhost地址,如果外部电脑则访问不了,所以改为动态获取访问地址

<%
            String basePath = request.getScheme()
                    + "://"
                    + request.getServerName()
                    + ":"
                    + request.getServerPort()
                    + request.getContextPath()
                    + "/";
        %>
        <base href="<%=basePath%>">

  1. 优化代码结构
    在这里插入图片描述
|-- 注册登录信息错误,表单回显
            利用 request 的setAttribute() 来设置并传递属性。
            ① 在Servlet程序中设置属性:
                req.setAttribute("msg","用户名或密码错误!");
            ② 在表单中显示属性出来:
                <%=request.getAttribute("msg")==null?"请输入用户名和密码":request.getAttribute("msg")%>
            ③ 在输入框中多加一个value属性回显相应的值。
                <input name="username" value="<%=request.getAttribute("username")==null?"":request.getAttribute("username")%>"/>

        |-- 基于思想:一个模块,使用一个 Servlet 程序。
            将login和regist 两个servlet进行合并
            ① 两个都是post请求,要区分,给提交的表单添加 hidden 的input提交项:<input type="hidden" name="action" value="login"/>

            ② 获取:String action = req.getParameter("action");
                  if("login".equals(action)){...}
        
        |-- 利用反射实现方法的调用,避免繁杂的if、else 判断
           Method method = this.getClass().getDeclaredMethod(action, HttpServletRequest.class, HttpServletResponse.class);
           method.invoke(this,req,resp);

        |-- 每一个servlet模块都会有以下调用的过程,所以进一步抽象出BaseServlet,继承HttpServlet,其他直接继承BaseServlet
            1、获取 action参数值
            2、通过反射获取 action对应的业务方法
            3、通过反射调用业务方法

三、实现图书功能,同登录注册功能实现

在这里插入图片描述

  1. 添加图书
    在这里插入图片描述

  2. 删除图书在这里插入图片描述

  3. 修改图书
    在这里插入图片描述

       1.book_manager.jsp点击修改之后,需要先调用servlet的query方法获取需修改Book对象。 这里面靠id传递id的属性值,实现按id查找Book
     <a href="manager/bookServlet?action=query&id=${book.id}">修改</a>
       2.将查询到的Book记录在request中,消息转发给book_edit.jsp
     Book book = bookService.queryBook(id);
     req.setAttribute("bookinfo",book);
     req.getRequestDispatcher("/pages/manager/book_edit.jsp").forward(req,resp);
    
       3.book_edit.jsp点击提交按钮,调用servlet方法的update方法,用隐性表单传递参数id
       <form action="manager/bookServlet" method="get">
       <input type="hidden" name="id" value="${requestScope.bookinfo.id}">
             
       3.update调用list,刷新页面
    
       4.在用一个页面,book_edit中的同一个隐藏input要传递add和update两个参数,用于区分。
        <input type="hidden" name="action" value=${empty param.id?"add":"update"}>
    

四、分页

分页模型分析
javaBean:Page类

    DAO部分:BookDAO里面新增方法,新增测试
    查询总记录数
        public Integer queryTotalCount() {
            String sql = "SELECT COUNT(*) FROM t_book;";
            Number number = (Number) querySingleValue(sql);
            return number.intValue();
        }
    查询当前页面的图书数据
        public List<Book> queryPageItems(Integer begin,Integer pageSize) {
            String sql = "SELECT id,bookname,author,bookprice,sale,save,img_path FROM t_book LIMIT ?,?;";
            List<Book> books = queryList(Book.class, sql, begin,pageSize);
            return books;
        }

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

        Service部分,新增page()方法
        方法体内调用DAO,得到数据库数据,将总记录数、总页码数、当前页面数据打包成page 对象
        这里稍微有点难理解,因为之前的Book都是直接调用DAO,就实现增删查改的操作。
        
        Servlet部分,新增page部分,获取用户交互页面的信息,调用service实现功能
        获取当前页码、页面显示数量pagesize
        传递显示数据

    4)首页、上一页、下一页、末页实现
        通过超链接,跳转到servlet,并且传递pageNo来实现
        <c:if test="${requestScope.page.pageNo>1}">
			<a href="manager/bookServlet?action=page&pageNo=1">首页</a>
			<a href="manager/bookServlet?action=page&pageNo=${requestScope.page.pageNo-1}">上一页</a>
        </c:if>
        
        <c:if test="${requestScope.page.pageNo<requestScope.page.pageTotal}">
			<a href="manager/bookServlet?action=page&pageNo=${requestScope.page.pageNo+1}">下一页</a>
			<a href="manager/bookServlet?action=page&pageNo=${requestScope.page.pageTotal}">末页</a>
		</c:if>

    5)分页模块中跳转到指定页数功能实现
        通过绑定单击事件,响应跳转servlet调用page方法并且传递pageNo实现
        $("#inputbtn").click(function(){
			var pageNumber = $("#pn_input").val();
			location.href ="${pageScope.basePath}manager/bookServlet?action=page&pageNo="+pageNumber;
		});

    6)分页模块中,页码 1,2,【3】,4,5 的显示,要显示 5 个页码,并且页码可以点击跳转
    
       如果总页码小于等于 5 的情况,页码的范围是:1-总页码
            <c:when test="${requestScope.page.pageTotal<=5}">
				<%--循环输出这些页码--%>
				<c:forEach begin="1" end="${requestScope.page.pageTotal}" var="i">
					<%--如果是当前页面,则显示【当前页码】--%>
					<c:if test="${i == requestScope.page.pageNo}">
						【${i}】
					</c:if>
					<%--如果不是当前页码则直接显示数字,并加上超链接实现可以跳转的功能--%>
					<c:if test="${i != requestScope.page.pageNo}">
						<a href="manager/bookServlet?action=page&pageNo=${i}">${i}</a>
					</c:if>
				</c:forEach>

			</c:when>

       如果总页码大于5,那么就需要分情况讨论
       |-- 开始前3个,显示1-5
       |-- 最后3个页码,显示(Total-4)-Total
       |-- 之外,始终保持当前页码在中间,,显示(当前页码-2)-(当前页码+2)
            <c:when test="${requestScope.page.pageTotal>5}">
				<c:choose>
					<%--页码在前三,显示1-5--%>
					<c:when test="${requestScope.page.pageNo<=3}">
						<c:forEach begin="1" end="5" var="i">
							<%--如果是当前页面,则显示【当前页码】--%>
							<c:if test="${i == requestScope.page.pageNo}">
								【${i}】
							</c:if>
							<%--如果不是当前页码则直接显示数字,并加上超链接实现可以跳转的功能--%>
							<c:if test="${i != requestScope.page.pageNo}">
								<a href="manager/bookServlet?action=page&pageNo=${i}">${i}</a>
							</c:if>
						</c:forEach>
					</c:when>

					<%--页码在后三,显示(Total-4)-Total--%>
					<c:when test="${requestScope.page.pageNo > requestScope.page.pageTotal-3}">
						<c:forEach begin="${requestScope.page.pageTotal-4}" end="${requestScope.page.pageTotal}" var="i">
							<%--如果是当前页面,则显示【当前页码】--%>
							<c:if test="${i == requestScope.page.pageNo}">
								【${i}】
							</c:if>
							<%--如果不是当前页码则直接显示数字,并加上超链接实现可以跳转的功能--%>
							<c:if test="${i != requestScope.page.pageNo}">
								<a href="manager/bookServlet?action=page&pageNo=${i}">${i}</a>
							</c:if>
						</c:forEach>

					</c:when>

					<%--其余其他情况,显示(当前页码-2)-(当前页码+2)--%>
					<c:otherwise>
						<c:forEach begin="${requestScope.page.pageNo-2}" end="${requestScope.page.pageNo+2}" var="i">
							<%--如果是当前页面,则显示【当前页码】--%>
							<c:if test="${i == requestScope.page.pageNo}">
								【${i}】
							</c:if>
							<%--如果不是当前页码则直接显示数字,并加上超链接实现可以跳转的功能--%>
							<c:if test="${i != requestScope.page.pageNo}">
								<a href="manager/bookServlet?action=page&pageNo=${i}">${i}</a>
							</c:if>
						</c:forEach>
					</c:otherwise>
				</c:choose>

			</c:when>


    7) 修改分页后,增加,删除,修改图书信息的回显页面
    要获取页面当前的一些信息,我们需要通过设置页面的属性,比如input,实现将这个值传个服务器
    跳转的时候直接用requestScope.name获取这个属性值

        1、在 book_edit.jsp 页面中使用隐藏域记录下 pageNo 参数,才能实现传递,可以显示当前页面,不然会跑到第一页去
        <input type="hidden" name="pageNo" value="${param.pageNo}">

        2、在修改的请求地址上追加当前页码参数,传递给bookServlet的page方法,设置相应的参数
        <td><a href="manager/bookServlet?action=query&id=${book.id}&pageNo=${requestScope.page.pageNo}">修改</a></td>

首页index.jsp跳转
在这里插入图片描述

|-- index.jsp首页显示数据库列表信息,增设一个 ClientBookServlet程序,获取数据再页面转发过去。
   (1)web目录下的index.jsp只需要负责把请求转发给 ClientBookServlet,实现page方法就ok
        <jsp:forward page="/client/clientBookServlet"></jsp:forward>
   (2)ClientBookServlet再把数据打包转发给client下的index.jsp,给人一种直接访问的是首页
        req.getRequestDispatcher(("/pages/client/index.jsp")).forward(req,resp);
   (3)首页显示的分页条与后台管理的也一致,抽取出来。成,pagenav_common.jsp

        此时跳转的servlet服务不一样,所以抽取出 url,作为 page 的一分部属性
        每一个servlet的程序中的page方法,另外设置url地址,
        page.setUrl("client/clientBookServlet?action=page");

        pagenav_common.jsp中的地址改为
        <a href="${requestScope.page.url}&pageNo=1">首页</a>

五、首页价格搜索(价格索引)

在这里插入图片描述

   1.servlet
       在clientBookServlet中对传入的价格区间的数据进行分页显示——>pageByPrice().
       里面调用 service的——>pageByPrice(pageNo,pageSize,min,max)来实现

   2.service
       在page的基础之上,增加pageByPrice(pageNo,pageSize,min,max)
            需要计算价格区间内的数据总数——>DAO增加 queryPriceCount(min,max)
            需要返回价格区间内的数据——>DAO增加 queryPriceItems(min,max)
       
   3.DAO
       在 queryTotalCount 的基础之上增加 queryPriceCount方法,计算在价格区间的个数
       在 queryPageItems 的基础之上增加 queryPriceItems() 方法,返回区间之间的BOOK数据

   4.其他补充:
        |-- 筛选价格的回显
            <input id="min" type="text" name="min" value="${param.min}">
            地址传递时,有些参数不符合要求,考虑可以通过attribute传递
        |-- 筛选结果分页显示中不带min,max参数,导致跳转的时候没有价格筛选
            思路:用StringBuffer追加上去
            if(req.getParameter("min")!=null){
                stringBuffer.append("&min=").append(req.getParameter("min"));
            }

六、表单重复提交与验证码(Session)

在这里插入图片描述

  1. 表单重复提交情况:

     用户按下功能键 F5————请求重定向解决
     用户多点了几次提交操作————验证码
     用户回退浏览器。重新提交————验证码
    
  2. 验证码:谷歌 kaptcha 图片验证码

kaptcha 是一个验证码生成工具。可配置的。kaptcha工作的原理是调用 com.google.code.kaptcha.servlet.KaptchaServlet,生成一个图片。同时将生成的验证码字符串放到 HttpSession中。

    1、导入谷歌验证码的 jar 包
        kaptcha-2.3.2.jar
    2、在 web.xml 中去配置用于生成验证码的 Servlet 程序
    <servlet-class>com.google.code.kaptcha.servlet.KaptchaServlet</servlet-class>
    3、在表单中使用 img 标签去显示验证码图片并使用它
    <img alt="" src="/kaptchaServlet.jpg">
    4、在服务器获取谷歌生成的验证码和客户端发送过来的验证码比较使用。 
  1. 验证码解决表单重复

     KaptchaServlet
    1、当用户第一次访问表单的时候,kaptcha给表单生成一个随机的验证码字符串
    并把验证码保存到 Session域中的 KAPTCHA_SESSION_KEY 
    2、要把验证码生成为验证码图片显示在表中
    
    Registservlet
    1、获取Session中验证码,并删除 Session中的验证码
    String token = (String) req.getSession().getAttribute(KAPTCHA_SESSION_KEY);
    req.getSession().removeAttribute(KAPTCHA_SESSION_KEY);
    
    2、获取表草中的表单项信息,比较一致则下一步,不一致则阻止
     if (codeTrue!=null && codeTrue.equalsIgnoreCase(code)) {}
    
  2. 实现单击图片更新,绑定单击事件

     $(".imgcode").click(function(){
            //后端增加上时间避免浏览器的缓存机制
            this.src="${pageContext.request.contextPath}/kaptchaServlet.jpg?d=" + new Date();
    });
    

七、完善购物车的功能

在这里插入图片描述

|-- 抽象出javaBean
1.抽象出购物车Cart的模型
    商品项 items
    总数量 totalCount
    总金额 totalPrice

2.抽象出商品项CartItems的模型
    商品 id
    商品名称 name
    商品单价 price
    商品数量 count
    商品总价 totalPrice

3.使用Session来实现数据的存储,所以此时需要Cart提供以下方法
    增加商品 addItem(CartItem item)
    删除商品 deleteItem(Integer id)
    清空商品 clear()
    修改商品数量 updateCount(id,count)

4.生成测试文件 CartTest

|-- 与表单交互,实现按钮加入购物车的功能
1. 按钮绑定单击响应,跳转到servlet
    $(function(){
		$("button.addToCart").click(function(){
			var id = $(this).attr("value");
			location.href = "http://localhost:8080/04_book_war_exploded/cartServlet?action=addItem&id=" + id;
		});
	});

2. CartServlet 里面调用addItem方法。
  通过id获取Book
  int id = WebUtils.parseInt(req.getParameter("id"), 0);
  Book book = bookservice.queryBook(id);

  但是需要实现将 Book 转化成 cartItem
  CartItem item = new CartItem(book.getId(),book.getBookname(),book.getBookprice(),1,book.getBookprice());
    //如果从session中发现还没有创建cart,就新建cart
  Cart cart = (Cart) req.getSession().getAttribute("cart");

  里面的Cart 也不能每次都要新建,通过session获取是否已经创建了Cart
  if(cart==null){
        cart = new Cart();
        req.getSession().setAttribute("cart",cart);
   }
  cart.addItem(item);

  跳转回原页面
  resp.sendRedirect(req.getHeader("Referer"));

3.靠 session 来传递消息,sessionScope来获取
    <c:if test="${not empty sessionScope.cart.items}">
        <c:forEach items="${sessionScope.cart.items}" var="entry">
            <tr>
                <td>${entry.value.name}</td>
                <td>${entry.value.count}</td>
                <td>${entry.value.price}</td>
                <td>${entry.value.totalPrice}</td>
                <td><a href="#">删除</a></td>
            </tr>
        </c:forEach>
    </c:if>

|-- 购物车功能实现
    1.删除商品,cartServt中增加一个 deleteItem的方法调用Cart.deleteItem
    <td><a class="deleteBook" href="cartServlet?action=deleteItem&id=${entry.value.id}">删除</a></td>

补充:一般这种删除操作都需要增加一个删除确认的操作
    $(function () {
		$(".deleteBook").click(function(){
			return confirm("你确定要删除【" + $(this).parent().parent().find("td:first").text() +"】吗?")
		});
	});
     
    2.清空购物车,cartServt中增加一个 clear的方法调用Cart.clear()

    3.修改商品数量
        增加输入框,并为其绑定 change 事件,传递当前输入框的count与id值
        cartServt中增加一个 updateItem的方法调用Cart.updateItem

八、订单模块

在这里插入图片描述

九、用Filter 实现权限检测

对于后台管理,必须登录之后才可以访问。
  1. 重写doFilter

    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        HttpServletRequest httpServletRequest = (HttpServletRequest) servletRequest;
        Object user = httpServletRequest.getSession().getAttribute("loguser");
        if(user==null){
            //用户未登录,跳转到登录界面
            servletRequest.getRequestDispatcher("/pages/user/login.jsp").forward(servletRequest,servletResponse);
        }else{
            //用户已登录,放行
            filterChain.doFilter(servletRequest,servletResponse);
        }
    }
    
  2. 配置xml文件

        1.拦截跳转到manager页面的请求
        2.拦截通过servlet跳转到页面的请求
        <url-pattern>/pages/manager/星</url-pattern>
        <url-pattern>/manager/bookServlet</url-pattern>
    
  3. Filter加上ThreadLocal,结合jdbc的事务,实现数据的操作一致性(线程安全)

    |-- 给jdbcUtil 操作类
     创建数据库连接时:
         1. 将连接存储到ThreadLocal中,使后续调用方法时直接通过ThreadLocal 得到一致的Connect
         private static ThreadLocal<Connection> conn = new ThreadLocal<>();
         2. 取消自动提交
    
         public static Connection getConnect(){
             Connection con = conn.get();
             if(con==null){
                 try {
                     con = dataSource.getConnection();
                     conn.set(con);
                     con.setAutoCommit(false); // 设置为手动管理事务
                 } catch (SQLException e) {
                     e.printStackTrace();
                 }
         }
         return con;
     }
    
     新增提交事务、回滚事务的操作,代码一致
     public static void commitAndClose(){
         Connection connection = conn.get();
         if(connection!=null){
             try {
                 //提交事务
                 connection.commit();
             } catch (SQLException e) {
                 e.printStackTrace();
             }finally {
                 try {
                     //关闭资源
                     connection.close();
                 } catch (SQLException e) {
                     e.printStackTrace();
                 }
             }
         }
         conn.remove();
     }
    
    |-- 所有的异常,必须上抛,由 Filter 来捕获,实现事务的回滚
     1.修改BaseDAO中的抛异常,去除finally中的关闭操作
         catch (SQLException e) {
             e.printStackTrace();
             throw new RuntimeException(e);
         }
    
     2.修改BaseServlet中的抛异常
    
    |-- 使用 Filter 过滤器统一给所有的 Service 方法都加上 try-catch,实现提交回滚事务,并配置
     @Override
     public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
         try {
             filterChain.doFilter(servletRequest,servletResponse);
             jdbcUtils.commitAndClose();
         } catch (IOException e) {
             e.printStackTrace();
             jdbcUtils.rollAndClose();
         } 
     }
    

总结

提示:代码详情请参考:
JavaWeb入门实战项目:书城项目,思路分析。: https://blog.csdn.net/qq_42647903/article/details/111222070

  • 1
    点赞
  • 26
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值