基于Java Web的网上图书商城管理系统——(三)

三、详细设计

1.注册

regist.jsp页面------>UserServlet----->UserDao
                
                UserServlet中:
                    1.获取验证码,判断它是否正确,如果正确,向下执行.
                      如果不正确,跳转到regist.jsp页面,显示错误信息
                      
                    2.将所以请求参数封装到User对象中.在User类中创建一个validateRegist,
                    这个方法会对请求参数进行校验,将错误信息封装到一个Map集合,在Servlet中
                    判断集合长度是否>0就可以判断是否有错误信息,如果有,跳转到regist.jsp
                    显示错误信息.
                    
                    3.调用UserService去完成注册操作  调用regist方法,传递User参数
                        
                    4.在regist方法中做了两件事情
                        (1).调用UserDao完成注册操作
                        (2).给注册的用户发送了一封激活邮件
                 
                关于md5加密:
                    在mysql数据库中通过  md5(字段);
                        update users set password=md5(password);
                        
                    在java中可以通过代码实现
                        MessageDigest.getInstance("md5")

2.登录

index.jsp---->page.jsp页面 有登录窗口
            
            会有登录窗口,提交时会访问UserServlet,会带一个请求参数  method=login
            在UserServlet中就可以判断当前操作是登录操作
            就会调用UserServlet中的login方法
        
            UserServlet---UserService----->UserDao
                
                1.userServlet中收集了用户名与密码
                2.UserService中调用UserDao中查找用户操作  findUserByUserNameAndPassword
                3.在UserService中判断了一下得到的User对象是否为null,如果为null,直接抛出一个自定义异常.
                4.如果查找到了用户,但是用户未激活,那么也不能登录成功,抛出了一个自定义异常.
                5.在UserServlet中捕获自定义异常,在page.jsp页面显示错误信息.
                6.在UserServlet中判断用户不为空,就将User存储到session中,并跳转到首面index.jsp,
                  自动跳转到page.jsp页面.
                 
            1.记住用户名
                当用户登录成功后,并且勾选了记住用户名操作,我们将用户的username存储到cookie中,
                持久化存储,并携带到浏览器端.
                在页面上通过el表达式获取username显示出来.
                
                在cookie中是否能存储中文,那么要是用户名是中文,我们可以存储username的utf-8码.
                在页面上,通过js将utf-8码解码.
                
            2.自动登录操作
                当用户登录成功后,并且勾选自动登录操作,我们将username,password都存储到cookie中,
                持久化存储,并携带到浏览器端.
                
                当下一次在访问时,我们可以通过Filter来拦截我们请求,判断cookie中是否有我们存储username,
                passowrd的这个cookie
                
                注意:自动登录时,有以下情况是不需要进行自动登录的.
                    (1).用户已经登录
                    (2).用户访问的路径是  login  regist这样的操作。
                 
            3.注销操作
                我们用户登录成功后,会将用户存储到session中。
                注销操作就是将session销毁就可以以。
                session.invalidate()方法.
                
                点击注销访问UserServlet?method=logout

3.商品添加

添加商品操作其时是一个文件上传操作.添加商品时,需要添加一个商品图片,我们使用文件上传.
            commons-fileupload
            
            浏览器:
                1.method=post
                2.encType="multipart/form-data"
                3.<input type="file" name="f">组件
                
            点击添加图书连接,会访问 addProduct.jsp页面    
            
            AddProductServlet这个servlet中有两个工作:
                1.完成书箱图片的保存(上传操作)
                2.将信息存储到数据库.
              
            创建了一个Map<String,String[]>它用于封装所有请求参数
            通过BeanUtils.populate方法将请求参数直接封装到Product类中。

            可以调用ProductService中的添加图书的方法,完成图书添加操作

            当图片添加成功后,我们会跳转到index.jsp页面.

4.商品查询

(1).查询全部
                index.jsp页面直接跳转到ProductServlet中.
                执行findAll操作,也就是查询出全部信息.
                
                调用ProductService----ProductDao完成查询操作,得到所有商品信息List<Product>
                
                转发到page.jsp页面,在page.jsp页面展示所有商品信息.
            

(2).根据id查询
                点击抢购书籍,会访问ProductServlet,
                product?method=findById&id=xxx
                会将这本书籍的id也携带到服务器端.
                
                会调用ProductService的findByid方法,根据id查找书籍,也就是得到一个Product对象.
                
                查找到商品后,跳转到productInfo.jsp页面,展示了商品信息
                
                上传的所有图片大小不确定的,怎样保证显示商品时,它的大小?
                    
                通过一个工具类可以保证当前的商品的图片大小一致
                
                
                PicUtils putils = new PicUtils(this.getServletContext()
                    .getRealPath(product.getImgurl()));
                putils.resize(200, 200);
                
                当添加商品时,会生成商品图片的一个缩略图,以方便我们在页面上显示。
                 
                在Product类中提供了一个getImgurl_s方法 ,这个方法,会根据商品图片的
                路径,获取到缩略图片的路径,我们就可以直接在页面上通过el表达工,获取
                缩略图的路径,显示出这个缩略图.

5.查看商品详情

在page.jsp页面会显示所以商品信息,它提供一个连接,可以点击查看
        当前书籍的详细信息。
        
        <a href='${pageContext.request.contextPath}/product?method=findById&id=${p.id}'>速速抢购</a>
    
        会访问一个servlet(ProductServlet)并且传递了method=findById,id=xxxx.
        
        在servlet中根据传递的method值,判断要执行的是findById方法,也就根据
        id查找书籍信息.会调用ProductService中查找操作的方法,在ProductService中
        调用ProductDao中查找书籍方法,最后得到一个Product对象,也就是商品信息封装
        对象.将查找到的Product对象封装到request域中,请求转发到productInfo.jsp页面,
        在页面上展示我们书籍详细信息.

6.添加到购物车

声明:购物车,没有数据库,直接使用session存储信息.
      
      当productInfo.jsp页面,点击添加商品到购物车时,会将商品的id传递到服务器端
      
      在页面上点击添加商品到购物车会调用一个js函数
      function addProductToCart(id) {
        location.href = "${pageContext.request.contextPath}/cart?method=add&id=" + id;
      }
    
      在服务器端会有CartServlet,它就是用于处理我们购物车操作.
        1.得到商品id  request.getParameter("id");
        2.根据id查找出商品   Product对象.
        3.关于购物车的数据结构
            Map<Product,Integer> 它就是我们的购物车,最终Map集合会存储到session中.
            
        4.第一次添加商品到购物车时,在服务器端,根据就没有购物车,也就是没有map集合.
          得到的是null值,就可以知道是第一次购物,就可以将购物车创建出来,并且,将
          商品添加到Map集合中。    

        5.如果不是第一次购物,查询后得到的map集合就不为null,就说明购物车中可能已经存
          在了商品,就需要考虑一个事情,就需要考虑一个事情,购物车中存在了当间要购买的
          商品。

            Map集合特点:
                key是唯一的,如果使用put方法存储,那么,当key重复时,put方法
                返回的就是原来的value值。
                
                可以根据put方法返回值,来判断商品在购物车中是否存在,
                如果存在了,也就是说,put方法返回值不为null,这时就可以将返回值
                +1,在重新存储到map集合中。

7.查看购物车

当点击查看购物车中商品时,会跳转到一个jsp页面,购物车是存储在session中的,
    那么在jsp页面上就可以直接得到session中的商品信息.
    
    <a href="${pageContext.request.contextPath}/showCart.jsp">
                
          使用jstl的forEach遍历Map集合.
        <c:forEach items=${cart}  var="entry">
            
            ${entry.key} ----对于cart中的key它就是一个Product对象
            ${entry.value}---对于caft中的value它是一个Integer对象,其实就是商品数量
            
        </c:forEach>
        
        通过<c:set> 来完成商品总价计算操作.

8.购物车管理

1.关于数据修改问题
        
       (1)关于点击+ -按钮完成商品数量修改操作
            当点击按钮时会调用函数changeCount(商品的id,商品修改数量,商品的库存)
            onclick="changeCount('${entry.key.id }','${entry.value-1}','${entry.key.pnum}')"
        
            在js中它是没数据类型的,那么当传递参数时,在函数中,可能认为它是一个字符串,
            那么就会引起问题。通过parseInt()函数将数值转换成数字.
        
            在函数中处理数据后,会将数据传递到服务器端
            location.href = "${pageContext.request.contextPath}/cart?method=update&id=" + id
                + "&count=" + count;
                
            在CartServlet中通过判断method=update完成操作.
                1).得到要修改商品的id,在得到要修改的商品数量值count.
                2).直接对购物车中的商品进行操作.
                3).为什么直接创建一个Product对象,将id值赋值给它,就可以
                  直接修改商品数量.
                  
                  原因:对Product类中的equals方法进行了重写,只比较商品的id.
                       在重写equalse方法时,也将hashCode方法重写了.
                       
        (2).关于+号操作
            以后面的原理一样.
            
            onclick="changeCount('${entry.key.id}','${entry.value+1}','${entry.key.pnum }')"
        
            区别:-号操作,是将商品的数量进行-1操作
                 +号操作,是将商品的数量进行+1操作
                 
        3.文本框失去焦点时,也调用         
            onblur="changeCount('${entry.key.id}',this.value,'${entry.key.pnum}')
            注意:传递了this.value,它代表的是文本框中的值
            
        4.数字文本框
            通过对文本添加onkeydown事件操作,当键盘按下时,会调用一个函数.numbText(event)
            在函数中通过判断按下键的keyCode值,就是键码值,来判断当前是否按下的是指定的
            按钮。
            
            注意:对于firefox或ie浏览器,它们获取事件对象event有区别。
            code = e.which; 判断firefox浏览器  得到键码值.
            code = window.event.keyCode; 判断是ie浏览器,得到键码值.
            
            if (!(code >= 48 && code <= 57 || code == 8 || code == 46)) 
            这是判断当前按下的不是0-9  delete  backspace
            这时就要阻止事件的默认行为.
            
            e.preventDefault();  firefox阻止默认行为执行
            window.event.returnValue = false; ie浏览器阻止默认行为执行.
            
        5.关于删除操作
            <a href="${pageContext.request.contextPath}/cart?method=remove&id=${entry.key.id}" onclick="delConfirm(event)">删除</a>
            
            这个超连接会访问服务器的一个CartServlet,method=remove代表要执行的是删除操作。
            并且将商品的id传递到服务器端.
            
            得到id后,new Product()这个对象的id值就是传递过去的。
            对于Product类来说,它已经重写了equals方法,它比较的就是id。
            在map集合中就可以直接根据我们创建的Product对象,将商品删除。
            
            最后会判断当前购物车中是否有商品,如果没有,直接将购物车删除。
            
            确认删除操作?
                
                function delConfirm(e){
                    var flag=window.confirm("确认删除商品吗");
                    
                    if(!flag){
                        //不删除商品        
                        //要想不删除商品,要阻止事件的默认行为执行.
                        if(e&&e.preventDefault){
                            // e对象存在,preventDefault方法存在 ---- 火狐浏览器
                            e.preventDefault();
                        }else{
                            // 不支持e对象,或者没有preventDefault方法 ---- IE
                            window.event.returnValue = false;
                        }

                    }                    
                }
                我们阻止超连接的默认事件执行.这样超连接就不会向href指定的路径发送请求。

9.生成订单

1.showCart.jsp页面,点击结算会生成订单
2.会跳转到order.jsp页面,在页面展示我们订单中的信息.
      需要输入一上订单的收货地址.
    生成订单的代码实现:
        (1).在order.jsp页面表单会向 ${pageContext.request.contextPath}/order提交.
          表单中有一个隐藏域 <input type="hidden" name="method" value="add">
        (2).在 OrderServlet中有一个add方法,它是订单添加操作.
            
            订单的添加注意事项:
                当订单生成后,需要对以下的表进行操作.
                1).订单表中要插入数据
                2).商品表中的商品数量要进行修改(修改商品的库存)
                3).订单与用户之间也存在关系,添加订单时,也需要得到当前用户的id.
                
            以上操作需要进行事务控制。
                1).获取Connection时,要使用同一个,需要在DataSourceUtils中对获取
                 Connection对象操作进行修改,将其放入到ThreadLocale中.
                2).Dbutils
                    QueryRunner 直接使用带参数的 参数类型是DataSource类型。
                    new QueryRunner(DataSource ds);这个操作,在调用update,query方法,
                    一般不会带Connection参数,这样,它就是一条sql一个事务。
                    
                    而现在我们需要事务管理,所以我们在使用QueryRunner时,就会
                    不带参数  new QueryRunner(),而使用带Connection参数的update,query方法.
              
            ---------------------------------
            要注意订单要包含商品信息,这时就需要从session中获取购物车,将购物车中的信息封装
            Order对象中.

            现在我们需要事务控制,所以我们在service层进行了事务的开启
                
            在添加订单项时,使用了批处理,因为订单与商品之间存在多对多关系,那么
            我们的中间表orderItem,它就有可能有多条数据,所以我们使用了QueryRuner
            的batch方法完成添加订单项操作
            
            注意:当我们操作完成后,一定要将Connection对象从ThreadLocale中remove掉.      
     

10.查看订单

查看订单,会根据用户的role去显示出不同的订单。
    如果role=admin  它查询出所有的订单.
    如果role=user   它只查询出当前用户的订单.
    
    代码实现:
        查看订单的入口:
            1).在首页,提供了查看订单连接
            2).当用户添加完成订单成功后,会显示查看订单操作.            
            <a href="${pageContext.request.contextPath}/order?method=search">
            
        (1).当点击连接后会访问OrderServlet,并提交一个参数 method=search;
        (2).在OrderServlet会首先得到当前用户   request.getSession().getAttribute("user");    
          如果用户没有登录,会让它登录,如果用户登录了,会查看订单信息.
          
        (3).当调用dao中查询订单操作时,会根据当前用户的role,进行不同的sql语句操作。
          查询出订单后,订单中不包含商品信息,所以要根据订单的信息,在orderItem表与
          products表中查询出商品信息。

       (4).查询出所有订单后,会得到一个List<Order>,将集合存储到request域中,最后请求转发到
          showOrder.jsp页面,在页面上显示出所有查询出的订单.

11.订单管理

1.支付操作
        使用了在线支付操作   epay第三方支付平台.
        
        在显示订单页面上showOrder.jsp页面,显示订单信息中,包含了当前支付状态。
        会显示  "已支付"  "未支付",如果是未支付,会有一个连接访问pay.jsp页面,
        并将当前订单的id,以及当前订单的金额传递到pay.jsp页面。
        
        (1).在pay.jsp页面上可以选择银行,表单提交时,将订单编号,金额,以及银行,提交到OnlinepayServlet中。
        (2).在OnlinePayServlet中完成请求参数封装.
        (3).第三方支付,会根据你提交的请求参数   p8_Url 向这个路径发送信息,
        (4).可以指定p8_url为CallbackServlet,那么我们在servlet中就可以得到支付结果信息
        (5).通过判断信息是否正确,以及r9_BType=1  r9_BType=2 可以知道,是否支付成功
        (6).当判断支付成功后,我们要修改订单的状态。
            1).在orders表中有一个字段  paystate=0 代表未支付,我们支付成功后,要修改订单的状态。
                paystat=1  这个代表订单已对付.
                
            2).修改订单状态要根据订单编号修改,在返回的支付结果信息中r6_Order,它就代表了
              我们的订单编号。                
    ---------------------------------------------

  2.订单取消
        在显示订单的页面上,会提供一个删除订单的连接。
        
       (1).录取消订单时,这个超连接会携带当前订单的编号传递到服务器端.
            <a href="${pageContext.request.contextPath}/order?method=del&id=${order.id}">取消订单</a>
        (2).这个连接会访问OrderServlet,并且 method=del  id=订单编号
            
        (3).OrderServlet中会根据传递method判断执行取消订单操作,会根据id知道要删除哪一个订单.
            
        (4).删除订单注意事项
            1).删除订单要将orders表中数据删除---根据id删除.
            2).需要删除orderItem表中数据
            3).需要修改商品的数量  也就是说需要对products表进行update操作.
            
            代码:
                1).根据订单id在orderitem表中查询出相关的商品信息.
                2).修改商品信息
                3).删除订单项信息
                4).删除订单.
            以上操作,也需要进行事务控制。                
          

12.权限控制

1.做一个注解
            @Retention(RetentionPolicy.RUNTIME)  //说明当前注解在runtime阶段有效果
            @Target(ElementType.METHOD) //当前注解是在方法上使用的
            @Inherited  //当前注解具有继承性
            public @interface PrivilegeInfo {

                String value(); //权限名称
            }
        

 2.对所有的service层的类进行提取接口操作.
            ProductServiceImpl 类---------->ProductService 接口
            OrderServieImple类 ------------>OrderService接口.
            
            在接口的方法上添加注解,注解中的value值,就是当前方法要执行,所需要的权限名称。
            

3.在servlet中得到的service对象,我们不直接new出来,可以针对每一个Service提供一个
         工厂,在工厂中生产对应的service对象,并且,返回的是代理对象.

            OrderServiceFactory
            ProductServiceFactory
            
            它们用于生产不同的service对象。
            
            在工厂中创建出对应的service对象,在提供的getInstance()方法中,
            返回其代理对象.
            
            这样我们在servlet中,就通过工厂获取service对象。得到的其实是代理对象。
            

 4.在动态代理的  InvocationHandler的invoke方法中进行权限控制.
           (1).得到当前方法上的注解
                1).判断当前方法上是否有指定的注解
                2).如果没有,代表这个操作不需要权限控制.
                       如果有,就会存在我们的注解.
                3).得到方法上的注解,通过注解对象得到当前方法要执行时所需要的权限名称。
                4).得到注解后,还需要得到当前用户,好么我们在所有添加注解的方法上
                  添加了一个参数  User.
                  可以在invoke方法中通过args参数获取User对象.
                5).可以判断当前用户是否存在,知道是否有权限操作。
                    1).如果用户不存在,throw new PrivilegeException();权限不足.
                    2).如果用户存在
                        1).根据user的role,在数据库中查询出用户所具有的所有的权限名称,
                          与注解上提供的权限名称对比。如果包含,那么具有权限,
                          如果不包含,没有权限
                        2).不包含     throw new PrivilegeException();权限不足.
                           包含  method.invoke();

13.销售榜单导出

获得商品销售情况,需要查询orderitem表  ------- 统计已支付订单项内容

1.榜单中存在哪些信息?(已支付订单中商品)

商品信息 products表

销售数量 orderitem表

订单支付情况 orders表

select * from products,orderitem,orders where products.id = orderitem.product_id and orderitem.order_id = orders.id ;

进行商品分组查询 group by

select products.* , sum(orderitem.buynum) totalSaleNum from products,orderitem,orders where products.id = orderitem.product_id and orderitem.order_id = orders.id and orders.paystate = 1 group by products.id order by totalSaleNum desc;

2.榜单文件是什么格式?

导出Excel 使用 POI类库

csv 格式文件 , 逗号分隔文件

    1) 信息当中有,在两端加 双引号

    2) 信息当中有" 在之前加双引号 转义

文件下载

设置Content-Type、Content-Disposition 头信息

文件流输出 (输出文件内容)

Excel 默认读取字符集gbk

猜你喜欢

转载自blog.csdn.net/xu_benjamin/article/details/84639803