JavaWeb《后端内容:4. 项目实战:书城系统(上篇)》

目录

0.把之前的系统的Myssm部分封装为包 

1.熟悉业务需求

2.准备工作及登录页面

3. 加入购物车的功能

4. 展示购物车详情页面

5. 结账功能

6. 实现功能:我的订单:计算订单数量

7. 实现功能:编辑购物车和合法用户过滤器

0.把之前的系统的Myssm部分封装为包 

新建空的工件jar包

创建相同的文件夹,导入相应文件要从out里面导入字节码文件 

全部设置完后点构建工件,然后点刚刚我们设置的这个工件,然后从src下的out文件相应的位置就能找到这个jar包,用于其他项目的开发了

  

1.熟悉业务需求

1. 需求分析
2. 数据库设计
 1) 实体分析
    - 图书                    Book
    - 用户                    User
    - 订单                    OrderBean
    - 订单详情             OrderItem
    - 购物车项             CartItem
 2) 实体属性分析
    - 图书 : 书名、作者、价格、销量、库存、封面、状态
    - 用户 : 用户名、密码、邮箱
    - 订单 : 订单编号、订单日期、订单金额、订单数量、订单状态、用户
    - 订单详情 : 图书、数量、所属订单
    - 购物车项 : 图书、数量、所属用户

2.准备工作及登录页面

新建module并把前端页面粘贴进去,我们不要像之前一样放到web下面,放到web-inf下面,因为放web下别人可以随便访问。静态资源还放web下

建立数据库和表


CREATE DATABASE bookdb CHAR SET utf8;
USE bookdb ;
CREATE TABLE `t_book` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `bookImg` varchar(200) NOT NULL,
  `bookName` varchar(20) DEFAULT NULL,
  `price` double(10,2) DEFAULT NULL,
  `author` varchar(20) DEFAULT NULL,
  `saleCount` int(11) DEFAULT NULL,
  `bookCount` int(11) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=12 DEFAULT CHARSET=utf8;

/*Data for the table `t_book` */

insert  into `t_book`(`id`,`bookImg`,`bookName`,`price`,`author`,`saleCount`,`bookCount`) values (1,'cyuyanrumenjingdian.jpg','C语言入门经典',99.00,'亚历山大',8,197),(2,'santi.jpg','三体',48.95,'周杰伦',18,892),(3,'ailuntulingzhuan.jpg','艾伦图灵传',50.00,'刘若英',12,143),(4,'bainiangudu.jpg','百年孤独',40.00,'王力宏',3,98),(5,'biancheng.jpg','边城',30.00,'刘德华',2,99),(6,'jieyouzahuodian.jpg','解忧杂货店',27.00,'东野圭吾',5,100),(7,'zhongguozhexueshi.jpg','中国哲学史',45.00,'冯友兰',3,100),(8,'huranqiri.jpg','忽然七日',19.00,'劳伦',50,200),(9,'sudongpozhuan.jpg','苏东坡传',20.00,'林语堂',50,300),(10,'fusang.jpg','扶桑',20.00,'严歌岑',10,89),(11,'geihaizideshi.jpg','给孩子的诗',23.00,'北岛',5,99);


CREATE TABLE `t_user` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `uname` varchar(20) NOT NULL,
  `pwd` varchar(32) NOT NULL,
  `email` varchar(100) DEFAULT NULL,
  `role` int(11) DEFAULT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `uname` (`uname`)
) ENGINE=InnoDB AUTO_INCREMENT=17 DEFAULT CHARSET=utf8;

/*Data for the table `t_user` */

insert  into `t_user`(`id`,`uname`,`pwd`,`email`,`role`) values (1,'lina','ok','lina@sina.com.cn',0),(2,'kate','ok','hello_kate@126.com',1),(3,'鸠摩智','ok','jiujiu@126.com',0),(4,'宝2021','ok','bao2021@sohu.com.cn',0),(5,'宝2022','123','bao2022@sohu.com.cn',0);



/*Table structure for table `t_cart_item` */

CREATE TABLE `t_cart_item` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `book` int(11) DEFAULT NULL,
  `buyCount` int(11) DEFAULT NULL,
  `userBean` int(11) DEFAULT NULL,
  PRIMARY KEY (`id`),
  KEY `FK_cart_book` (`book`),
  KEY `FK_cart_user` (`userBean`),
  CONSTRAINT `FK_cart_book` FOREIGN KEY (`book`) REFERENCES `t_book` (`id`),
  CONSTRAINT `FK_cart_user` FOREIGN KEY (`userBean`) REFERENCES `t_user` (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=21 DEFAULT CHARSET=utf8;

/*Data for the table `t_cart_item` */

insert  into `t_cart_item`(`id`,`book`,`buyCount`,`userBean`) values (9,1,1,2),(10,5,1,1),(11,1,2,1),(12,2,13,1),(13,3,2,1),(14,4,1,1),(15,6,1,1),(16,7,1,1),(17,8,1,1),(18,9,1,1),(19,10,1,1),(20,11,4,1);

/*Table structure for table `t_order` */

CREATE TABLE `t_order` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `orderNo` varchar(128) NOT NULL,
  `orderDate` datetime DEFAULT NULL,
  `orderUser` int(11) DEFAULT NULL,
  `orderMoney` double(10,2) DEFAULT NULL,
  `orderStatus` int(11) DEFAULT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `orderNo` (`orderNo`),
  KEY `FK_order_user` (`orderUser`),
  CONSTRAINT `FK_order_user` FOREIGN KEY (`orderUser`) REFERENCES `t_user` (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=12 DEFAULT CHARSET=utf8;

/*Data for the table `t_order` */

insert  into `t_order`(`id`,`orderNo`,`orderDate`,`orderUser`,`orderMoney`,`orderStatus`) values (4,'5eaab6146dc54e0482fdb8b6120c229b_20211025112519','2021-10-25 11:25:20',1,506.90,0),(5,'f5a22aac925d42eabc6b49c45a3eb74f_20211025113004','2021-10-25 11:30:04',1,48.95,0),(6,'8a245df4359e4224b531cf121c4acab3_20211025113019','2021-10-25 11:30:20',1,0.00,0),(7,'b521cd49ab2943f0bbc0630c95978f1c_20211025113039','2021-10-25 11:30:40',1,48.95,0),(8,'d4f366a82cd4491c9899b181753804b4_20211025113151','2021-10-25 11:31:52',1,48.95,0),(9,'8f5869a839f4483e947bd2c3163f3c23_20211025113159','2021-10-25 11:31:59',1,48.95,0),(10,'c5fcd95dbe7f49669f96b4ad6444ae6b_20211025120531','2021-10-25 12:05:32',1,147.95,0),(11,'6240ec3e5ac04e3583e1beb75a9e94ec_20211025120542','2021-10-25 12:05:42',1,147.95,0);

/*Table structure for table `t_order_item` */

CREATE TABLE `t_order_item` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `book` int(11) DEFAULT NULL,
  `buyCount` int(11) DEFAULT NULL,
  `orderBean` int(11) DEFAULT NULL,
  PRIMARY KEY (`id`),
  KEY `FK_detail_book` (`book`),
  KEY `FK_detail_order` (`orderBean`),
  CONSTRAINT `FK_detail_book` FOREIGN KEY (`book`) REFERENCES `t_book` (`id`),
  CONSTRAINT `FK_detail_order` FOREIGN KEY (`orderBean`) REFERENCES `t_order` (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=19 DEFAULT CHARSET=utf8;

/*Data for the table `t_order_item` */

insert  into `t_order_item`(`id`,`book`,`buyCount`,`orderBean`) values (6,1,1,4),(7,2,2,4),(8,10,1,4),(9,3,5,4),(10,4,1,4),(11,2,1,5),(12,2,1,7),(13,2,1,8),(14,2,1,9),(15,1,1,10),(16,2,1,10),(17,1,1,11),(18,2,1,11);

数据库一共五个表,第一个是 t_user(用户表)主键id(多次作为别的表的外键) 

 

第二个是 t_book(图书表)

这里老师说最好加个状态的列 

第三个是 t_cart_item(购物车项表)t_user的主键 id 为它 userBean 的外键, book的主键 id 为它 book 的外键 

 

第四个是 t_oreder(订单表)t_user的主键 id 为它 orderUser 的外键

第五个是 t_oreder_item(订单项表)(一个订单包括多个订单项,类似一个购物车包含多个购物车项)t_user的主键 id 为它 orderBean的外键,t_book的主键id为它book的外键

 

然后开始写pojo类,同时记得把druid数据库连接池的配置文件和applicationcontext.xml书写Bean的配置文件复制到src下。这里page还是需要保留的,方便我们能保证一些访问不绕开Thymeleaf或者说Servlet

<?xml version="1.0" encoding="utf-8" ?>

<!DOCTYPE beans [
        <!ELEMENT beans (bean*)>
        <!ELEMENT bean (property*)>
        <!ELEMENT property (#PCDATA)>

        <!ATTLIST bean id ID #REQUIRED>
        <!ATTLIST bean class CDATA #IMPLIED>
        <!ATTLIST property name CDATA #IMPLIED>
        <!ATTLIST property ref IDREF #IMPLIED>
        ]>

<beans>

    <bean id="page" class="com.fanxy.myssm.myspringmvc.PageController"/>
</beans>

 这里就省略构造函数和get set方法,实际是需要写的,还把一些业务上的1对多,多对一写了,并且参照我们上个项目,有对应关系的写对方类的类型属性,同时都写上带id的构造函数,以备我们改造的新的BaseDAO能直接通过反射生成对方带id的成员变量。也别忘了写空参构造函数(JavaBean基本结构,方便调用反射生成实例)

public class User {
    private Integer id;
    private String uname;
    private String pwd;
    private String email;
    private Integer role;
}

//我们应该给购物车项写一个Cart购物车类,存放一个人的购物车的购物车项的集合
public class CartItem {
    private Integer id;
    private Book book;
    private Integer buyCount;
    private User userBean;
}

public class Book {
    private Integer id;
    private String bookImg;
    private String bookName;
    private Double price;
    private String author;
    private Integer saleCount;
    private Integer bookCount;
    private Integer bookStatus = 0; // 0:代表有效 default值 -1:代表无效
}

public class OrderItem {
    private Integer id;
    private Book book; // M : 1
    private Integer buyCount;
    private OrderBean orderBean; // M : 1
}

public class Order {
    private Integer id;
    private String orderNo;
    private LocalDateTime orderDate;
    private User orderUser;
    private Double orderMoney;
    private Integer orderStatus;
    private List<OrderItem> orderItemList; // 1 : M
}

也别忘了把Bean的配置文件参数和Thymeleaf的上下文参数配置复制到web.xml文件,这次我们把前端页面都放在了WEB-INF下,我们干脆把index.html也放进去,然后Thymeleaf配置的前缀我们就改成现在的目录前缀 /WEB-INF/pages/ ,不再是以前的一个 / 

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
         version="4.0">

    <!-- 在上下文参数中配置视图前缀和视图后缀 -->
    <context-param>
        <param-name>view-prefix</param-name>
        <param-value>/WEB-INF/pages/</param-value>
    </context-param>
    <context-param>
        <param-name>view-suffix</param-name>
        <param-value>.html</param-value>
    </context-param>

    <context-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>applicationContext.xml</param-value>
    </context-param>
    
</web-app>

根据我们现在的登录页面的位置,我们的tomcat根据PageController的启动和渲染,我们是要经过Thymeleaf,故前缀的一部分其实Thymeleaf的配置帮我们写了,我们直接写user/login即可 

接着我们还是延续我们之前系统的思路,先去写登录页面的前端,引入Thymeleaf格式,把src能改的先改了,然后表单的action我们应该从静态页面改成/user.do,将来写UserController,其次我们表单的method写post,当然老规矩,既然是表单我们的到时候写发送逻辑的时候要遵守我们的DispatcherServlet,需要写一个隐藏域的input,name="operate",value="login",意味着将来要写用户的login方法。同时表单的登录部分的账号和密码部分的name要写的和数据库对应(uname,pwd)

很明显,下一步登录需要UserController,写它之前先写DAO层和Service层相应的结构,让我们优先完成登录模块,记得把Bean的配置写到xml里面。

<beans>
    <bean id="page" class="com.fanxy.myssm.myspringmvc.PageController"/>

    <!--  DAO   -->
    <bean id="userDAO" class="com.fanxy.dao.impl.UserDAOImpl"/>

    <!--  Service   -->
    <bean id="userService" class="com.fanxy.service.impl.UserServiceImpl">
        <property name="userDAO" ref="userDAO"/>
    </bean>

    <!--  Controller   -->
    <bean id="user" class="com.fanxy.controller.UserController">
        <property name="userService" ref="userService"/>
    </bean>

</beans>

这里先让Controller输出一下查到的用户,看看是否这部分有bug。

public interface UserDAO {
    public User getUser(String uname, String pwd);
}

public class UserDAOImpl extends BaseDao implements UserDAO {
    @Override
    public User getUser(String uname, String pwd) {
        String sql = "SELECT * FROM t_user WHERE uname = ? AND pwd = ?;";
        try {
            List<User> users = executeQuery(User.class, sql, uname, pwd);
            if (users.size() > 0) {
                return users.get(0);
            }
        } catch (Exception e) {
            e.printStackTrace();
            throw new RuntimeException("UserDAO.getUser出错了");
        }
        return null;
    }
}
--------------------------------------------------------------------------------------
public interface UserService {
    public User login(String uname, String pwd);
}

public class UserServiceImpl implements UserService {
    private UserDAO userDAO = null;
    
    @Override
    public User login(String uname, String pwd) {
        return userDAO.getUser(uname, pwd);    
    }
}
--------------------------------------------------------------------------------------
public class UserController {
    private UserService userService = null;

    public String login(String uname, String pwd, HttpSession session){
        User user = userService.login(uname, pwd);
        System.out.println(user);
        return "index";
    }
}

经过调试发现前面无bug,下面我们要思考我们的index页面,需要显示图书列表,这些图书的信息来自数据库。所以显然我们需要写BookService层和DAO层,让我们的登录方法返回的页面是具有图书信息的,放入session作用域。

这里我们按我们之前做过的水果系统我们是关键字查询,还带分页,逻辑在页面处判断,需要加入参数,通过Limit分页,查询靠LIKE关键字, 这里如果我们想按价格最大值最小值是可以通过输入这两个参数,然后通过BETEEN AND这种方法查询,然后分页逻辑也一样,这里就先不做了,直接全部查询bookStatus为0的即可用的。当然别忘了注册Bean。

public interface BookDAO {
    public List<Book> getBookList();
}
--------------------------------------------------------------------------------------
public class BookDAOImpl extends BaseDao implements BookDAO {
    @Override
    public List<Book> getBookList() {
        String sql = "SELECT * FROM t_book WHERE bookStatus = 0;";
        try {
            return executeQuery(Book.class, sql);
        } catch (Exception e) {
            e.printStackTrace();
            throw new RuntimeException("BookDAOImpl.getBookList出错了");
        }
    }
}
--------------------------------------------------------------------------------------
public interface BookService {
    public List<Book> getBookList();
}

public class BookServiceImpl implements BookService {
    private BookDAO bookDAO = null;

    @Override
    public List<Book> getBookList() {
        return bookDAO.getBookList();
    }
}

这里我们不想让登录方法还同时把图书列表的功能给聚合,我们后台还有管理员,还有图书的管理操作,显然根据高内聚低耦合的思路,我们应该把图书管理的部分专门写Controller去负责。这里我们的redirect没有写operate=index是因为我们之前写这个模块的时候,operate为空默认赋值index了。

public class UserController {
    private UserService userService = null;

    public String login(String uname, String pwd, HttpSession session){

        User user = userService.login(uname, pwd);
        if(user != null){
            session.setAttribute("currUser", user);
            return "redirect:book.do";
        }
        return "user/login";
    }
}

所以现在我们就可以开始写BookController了,这里我们先测试一下能否查到bookList,设置断点调试,别忘了注册,经过测试发现能获得BookList。

public class BookController {
    private BookService bookService = null;

    public String index(HttpSession session){
        List<Book> bookList = bookService.getBookList();
        session.setAttribute("bookList", bookList);        
        return "index";
    }
}

现在保证能获取数据了,就去前端页面去写渲染的部分了。首先导入Thymeleaf,然后给css的href写入相应格式。然后我们删除多余的一个个静态的图书列表的部分,然后我们改造成Thymeleaf的格式遍历图书列表,我们不想每个属性都写book.xxx,因此加上th:object="${book}",然后我们遍历的图片地址也写好相应的地址,各种属性也标注好。

修改为如下,发现已经完成这个功能。可以在网页显示书籍信息。然后提前先写了要调用一个js方法,到时候完成加入购物车的操作。

                <div class="list-content">
                    <div class="list-item" th:each="book : ${session.bookList}" th:object="${book}">
                        <img th:src="@{|/static/uploads/*{bookImg}|}" alt="">
                        <p th:text="|书名:*{bookName}|">书名:活着</p>
                        <p th:text="|作者:*{author}|">作者:余华</p>
                        <p th:text="|价格:¥*{price}|">价格:¥66.6</p>
                        <p th:text="|销量:*{saleCount}|">销量:230</p>
                        <p th:text="|库存:*{bookCount}|">库存:1000</p>
                        <button th:onclick="|addCart(*{id})|">加入购物车</button>
                    </div>
                </div>

3. 加入购物车的功能

接下来我们首页的上面的如果登录状态进入首页,会显示能进入自己的购物车和主页,而未登录用户显示的是登录注册,其次还有购物车页面。

            <div class="topbar-right" th:if="${session.currUser==null}">
                <a href="user/login.html" class="login">登录</a>
                <a href="user/regist.html" class="register">注册</a>
                <a href="cart/cart.html" class="cart iconfont icon-gouwuche">
                    购物车
                    <div class="cart-num">3</div>
                </a>
                <a href="manager/book_manager.html" class="admin">后台管理</a>
            </div>
            <div class="topbar-right" th:unless="${session.currUser!=null}">
                <!--  登录后风格   -->
                <span>欢迎你<b th:text="${session.currUser.uname}">张总</b></span>
                <a href="#" class="register">注销</a>
                <a href="pages/cart/cart.jsp" class="cart iconfont icon-gouwuche ">
                    购物车
                    <div class="cart-num">3</div>
                </a>
                <a href="./pages/manager/book_manager.html" class="admin">后台管理</a>
            </div>

然后我们要去对应的位置去实现上面的js方法,同时js是为了我们的CartItemController服务的,我们显然根据js方法应该写对应的addCart方法,并且需要bookId参数。

function addCart(bookId) {
    window.location.href = "cart.do?operate=addCart&bookId=" + bookId;
}

将制定的图书添加到当前用户的购物车中

1.如果当前用户的购物车已经有这个图书,需要给这个CartItem项的数量+1
2.如果当前用户的购物车没有这个图书,需要给他的购物车新增一个这个图书的CartItem项,然后数量设置为1

根据上面的逻辑去写DAO层和Service层。这里Service层老师说建议写一个两者结合的方法。然后每次直接调用这个方法即可,判断的业务操作写在方法内部,不需要在外部关心要调用哪个。

public interface CartItemDAO {
    //新增购物车项
    public void addCartItem(CartItem cartItem);
    //修改特定购物车项
    public void updateCartItem(CartItem cartItem);
}
--------------------------------------------------------------------------------------
public class CartItemDAOImpl extends BaseDao implements CartItemDAO {
    @Override
    public void addCartItem(CartItem cartItem) {
        String sql = "INSERT INTO t_cart_item VALUES(0, ?, ?, ?);";
        try {
            executeUpdate(sql, cartItem.getBook().getId(), cartItem.getBuyCount(),
                    cartItem.getUserBean().getId());
        } catch (Exception e) {
            e.printStackTrace();
            throw new RuntimeException("CartItemImpl.addCartItem出错了");
        }
    }

    @Override
    public void updateCartItem(CartItem cartItem) {
        String sql = "UPDATE t_cart_item SET buyCount = ? WHERE id = ?;";
        try {
            executeUpdate(sql, cartItem.getBuyCount(), cartItem.getId());
        } catch (SQLException e) {
            e.printStackTrace();
            throw new RuntimeException("CartItemImpl.updateCartItem出错了");
        }
    }
}

public interface CartItemService {
    public void addCartItem(CartItem cartItem);
    public void updateCartItem(CartItem cartItem);
    public void addOrUpdateCartItem(CartItem cartItem);
}

写到这里发现,我们其实需要一个类,购物车类,来存储一个用户的所有的购物车项目,然后我们的Service的方法addOrUpdateCartItem是 判断当前用户的购物车中是否有这本书 有——>update 无——>add。所以购物车类应该作为一个用户的私有属性出现。

购物车类数据库表中没有,但是比如总金额,商品数这些单独写在购物车项是不可能的,我们写在这个类,刚好能帮助我们完成业务。同时购物车的总金额和总数明显是个只读属性,我们让它通过调用get方法自动计算,而不需要进行set,所以不用写。而相应总金额和总数我们显然应该遍历这个Map。

public class Cart {
    private Map<Integer, CartItem> cartItemMap; //购物车中购物项的集合, key就是book的id
    private Double totalMoney;  //购物车中购物车项的总金额
    private Integer totalCount; //购物车中购物车项的总数

    public Cart() {}

    public Map<Integer, CartItem> getCartItemMap() {
        return cartItemMap;
    }

    public void setCartItemMap(Map<Integer, CartItem> cartItemMap) {
        this.cartItemMap = cartItemMap;
    }

    public Double getTotalMoney() {
        totalMoney = 0.0;
        if (cartItemMap != null && cartItemMap.size() > 0){
            Set<Map.Entry<Integer, CartItem>> entries = cartItemMap.entrySet();
            for (Map.Entry<Integer, CartItem> cartItemEntry : entries) {
                CartItem cartItem = cartItemEntry.getValue();
                totalMoney += cartItem.getBook().getPrice() * cartItem.getBuyCount();
            }
        }
        return totalMoney;
    }

    public Integer getTotalCount() {
        totalCount = 0;
        if (cartItemMap != null && cartItemMap.size() > 0){
            return cartItemMap.size();
        }
        return totalCount;
    }
}

此时我们就可以把Service方法写完了,同时我们的Controller类就不需要管内部是怎么实现的了,我们只需要调用Service层的addOrUpdateCartItem方法即可 

public class CartItemServiceImpl implements CartItemService {
    private CartItemDAO cartItemDAO = null;
    @Override
    public void addCartItem(CartItem cartItem) {
        cartItemDAO.addCartItem(cartItem);
    }

    @Override
    public void updateCartItem(CartItem cartItem) {
        cartItemDAO.updateCartItem(cartItem);
    }

    //1.如果当前用户的购物车已经有这个图书,需要给这个OrderItem项的数量+1
    //2.如果当前用户的购物车没有这个图书,需要给他的购物车新增一个这个图书的OrderItem项
    //  然后数量设置为1
    @Override
    public void addOrUpdateCartItem(CartItem cartItem, Cart cart) {
        if(cart != null) {
            Map<Integer, CartItem> cartItemMap = cart.getCartItemMap();
            if (cartItemMap == null){
                cartItemMap = new HashMap<>();
            }

            if (cartItemMap.containsKey(cartItem.getBook().getId())){
                CartItem cartItemTemp = cartItemMap.get(cartItem.getBook().getId());
                cartItemTemp.setBuyCount(cartItemTemp.getBuyCount() + 1);
                updateCartItem(cartItemTemp);
            }else {
                addCartItem(cartItem);
            }
        }else {
            addCartItem(cartItem);
        }
    }
}

复杂的是,我们调用CartController的时候需要传入一个购物车类,和购物车项目。那么我们就需要给我们的登录添加功能了,我们希望登录的时候就能查到这个用户购物车项的所有内容,而且右上角还可以根据购物车内购物车项总数显示红色的数字圆点。而不是写在BookController中,因为我们的商城用户可能没有登录就访问了。

因此我们先给给购物车项的Service层写一个方法,能通过传入的User获取他购物车的信息,当然它肯定依赖于DAO层的相应方法,也写上,从下往上实现。

public interface CartItemService {
    public void addCartItem(CartItem cartItem);
    public void updateCartItem(CartItem cartItem);
    public void addOrUpdateCartItem(CartItem cartItem, Cart cart);
    public Cart getCart(User user);
}
--------------------------------------------------------------------------------------
public interface CartItemDAO {
    //新增购物车项
    public void addCartItem(CartItem cartItem);
    //修改特定购物车项
    public void updateCartItem(CartItem cartItem);
    //获取特定用户的所有购物车项
    public List<CartItem> getCartItemList(User user);
}
--------------------------------------------------------------------------------------
public class CartItemDAOImpl extends BaseDao implements CartItemDAO {
    @Override
    public void addCartItem(CartItem cartItem) {---}

    @Override
    public void updateCartItem(CartItem cartItem) {---}

    @Override
    public List<CartItem> getCartItemList(User user) {
        String sql = "SELECT * FROM t_cart_item WHERE userBean = ?";
        try {
            return executeQuery(CartItem.class, sql, user.getId());
        } catch (Exception e) {
            e.printStackTrace();
            throw new RuntimeException("CartItemImpl.getCartItems出错了");
        }
    }
}
--------------------------------------------------------------------------------------
public class CartItemServiceImpl implements CartItemService {
    private CartItemDAO cartItemDAO = null;
    @Override
    public void addCartItem(CartItem cartItem) {---}

    @Override
    public void updateCartItem(CartItem cartItem) {---}

    @Override
    public void addOrUpdateCartItem(CartItem cartItem, Cart cart) {---}

    @Override
    public Cart getCart(User user) {
        List<CartItem> cartItemList = cartItemDAO.getCartItemList(user);
        Map<Integer, CartItem> cartItemMap = new HashMap<>();
        for (CartItem cartItem : cartItemList) {
            cartItemMap.put(cartItem.getBook().getId(), cartItem);
        }
        Cart cart = new Cart();
        cart.setCartItemMap(cartItemMap);
        return cart;
    }
}

此时我们的UserController的登录就可以书写登录后通过CartItemService加载属于它的购物车的方法了,这里购物车和用户是绑定的,我们不妨给User写入一个私有的Cart类属性,然后我们就把查到并生成的购物车赋值给用户的这个属性,再放入session作用域。

public class UserController {
    private UserService userService = null;
    private CartItemService cartItemService = null;
    
    public String login(String uname, String pwd, HttpSession session){
        User user = userService.login(uname, pwd);
        if(user != null){
            Cart cart = cartItemService.getCart(user);
            user.setCart(cart);
            session.setAttribute("currUser", user);
            return "redirect:book.do";
        }
        return "user/login";
    }
}

现在我们就回到index页面,通过实现右上角的小红点数目,即可验证我们有无bug。

现在我们想实现加入购物车的操作的CartController的时候,可以从session获取用户和用户的购物车,但是想获取当前这本书封装的CartItem还需要思考一下。我们除了主键id,不妨设置一个除了它这个参数的构造方法,直接想办法新建一个cartItem出来。

这里数目写1的意义在于如果购物车没有关于这本书的订单,会使用这个1加入CartItem的数据表。因为我们addOrUpdateCartItem方法只是查询Map中有无同id图书的订单,有的话就让Map里面的这个订单的buyCount + 1,然后再通过update方法把更新的cartItem的buyCount更新到数据库。

如果没有同id图书的订单,我们直接使用这个构造的订单,通过add方法把这个cartItem加入到数据库中,而加入的新的订单的购买数量就是这个1。 

public class CartController {
    private CartItemService cartItemService;

    public String addCart(Integer bookId, HttpSession session) {
        //将制定的图书添加到当前用户的购物车中
        User user = (User) session.getAttribute("currUser");
        CartItem cartItem = new CartItem(new Book(bookId), 1, user);
        cartItemService.addOrUpdateCartItem(cartItem, user.getCart());
        return "redirect:cart.do";
    }
}

这里注册一下Bean文件,然后开始调试,看看能否完成加入购物车和给session里面的user加入购物车的流程。

此时运行无法显示首页,但是调试发现能给session中的user获取到cart,所以总体功能还是实现了。

 而BookList也能查到

最后在浏览器查看html文件内容发现数据都查到了,但是就是没显示,最终确定是js方法这里不能使用单标签 

<script language="JavaScript" th:src="@{/static/script/index.js}" />
改为
<script language="JavaScript" th:src="@{/static/script/index.js}" ></script>

此时调试发现功能完成,可以点击加入购物车数据库 t_cart_item表如果有这个订单项就增加数目,没有的话就增加这个项目。

4. 展示购物车详情页面

既然CartController选择让用户重新request页面,进入index页面,把数据库更新的数据加载到session。

public class CartController {
    private CartItemService cartItemService;

    public String addCart(Integer bookId, HttpSession session) {
        //将制定的图书添加到当前用户的购物车中
        User user = (User) session.getAttribute("currUser");
        CartItem cartItem = new CartItem(new Book(bookId), 1, user);
        cartItemService.addOrUpdateCartItem(cartItem, user.getCart());
        return "redirect:cart.do";
    }
}

这里没写operate根据我们DispatcherServlet的逻辑应该自动赋值为index,那么我们就写购物车的index方法,让购物车详情页面能根据数据加载渲染。这里渲染到cart文件夹下的cart.html。

public class CartController {
    private CartItemService cartItemService;

    public String index(HttpSession session){
        User user = (User) session.getAttribute("currUser");
        Cart cart = cartItemService.getCart(user);
        user.setCart(cart);
        session.setAttribute("currUser", user);
        return "cart/cart";
    }
    public String addCart(Integer bookId, HttpSession session) {---}
}

我们接着就开始写cart的前端页面的渲染。导入xmlns,然后把css路径改为@{}下的。

然后遍历的一个个购物车项按逻辑语句写,把静态的页面中一个个tbody的数据删除,并改为Thymleaf表达式的格式。

          <tbody>
            <tr>
              <td>
                <img src="../../static/uploads/huozhe.jpg" alt="" />
              </td>
              <td>活着</td>
              <td>
                <span class="count">-</span>
                <input class="count-num" type="text" value="1" />
                <span class="count">+</span>
              </td>
              <td>36.8</td>
              <td>36.8</td>
              <td><a href="">删除</a></td>
            </tr>
            </tbody>

改为 

            <tbody>
            <tr th:each="cartItem : ${session.currUser.getCart().getCartItemMap().values()}">
                <td>
                    <img th:src="@{|/static/uploads/${cartItem.getBook().bookImg}|}" alt=""/>
                </td>
                <td th:text="${cartItem.getBook().bookName}">活着</td>
                <td>
                    <span class="count">-</span>
                    <input class="count-num" type="text" th:value="${cartItem.buyCount}"/>
                    <span class="count">+</span>
                </td>
                <td th:text="${cartItem.getBook().price}">36.8</td>
                <td th:text="${cartItem.getBook().price * cartItem.buyCount}">36.8</td>
                <td><a href="">删除</a></td>
            </tr>
            </tbody>

同时页面还会显示商品总数和总金额(之前写了总金额的计算在Cart类),但是我们商品总数之前的函数返回的是有多少种图书,或者说多少个cartItem,这里要的是多少个商品的数目,具体来说就是每个cartItem具体购买的数量的求和。这里我们就给Cart加入一个私有属性totalBookCount,保存商品数,然后写个get方法获取。

public class Cart {
    ---
    private Integer totalBookCount; //购物车中的商品数目
    ---
    ---
    public Integer getTotalBookCount() {
        totalBookCount = 0;
        if (cartItemMap != null && cartItemMap.size() > 0){
            for (CartItem cartItem : cartItemMap.values()) {
                totalBookCount += cartItem.getBuyCount();
            }
        }
        return totalBookCount;
    }
}

所以页面这里最后的输出可以改写成Thymeleaf的格式

        <div class="footer">
            <div class="footer-left">
                <a href="#" class="clear-cart">清空购物车</a>
                <a href="#">继续购物</a>
            </div>
            <div class="footer-right">
                <div>共<span>3</span>件商品</div>
                <div class="total-price">总金额<span>99.9</span>元</div>
                <a class="pay" href="checkout.html">去结账</a>
            </div>
        </div>
        <div class="footer">
            <div class="footer-left">
                <a href="#" class="clear-cart">清空购物车</a>
                <a href="#">继续购物</a>
            </div>
            <div class="footer-right">
                <div>共<span th:text="${session.currUser.cart.getTotalBookCount()}">3</span>件商品</div>
                <div class="total-price">总金额<span th:text="${session.currUser.cart.getTotalMoney()}">99.9</span>元</div>
                <a class="pay" href="checkout.html">去结账</a>
            </div>
        </div>

这里试着运行,发现报错,表达式出错"session.currUser.cart.getTotalMoney()",调试发现在调用这个方法的时候,执行getTotalMoney的以下语句出错,剖析源码,问题出在我们生成购物车中的cartItem项的时候是在CartItemService层,通过getCart方法生成的购物车。

而购物车通过CartItemDAO,写的参数是用户,通过用户的id查对应的cartItem,然后返回一个List。而这个List中的一个个cartItem的book的类并非数据库中的Integer,我们实际是通过反射构建的,但是我们只有一个id字段(表里确实只有id)。这里我们最好能放入购物车中之前,先把书的信息查一下赋值,然后再放入购物车的cartItemMap。

totalMoney += cartItem.getBook().getPrice() * cartItem.getBuyCount();

所以我们给CartItemService接口添加一个方法,getCartItemList让我们对最开始只有id的cartItemList中的book都查询并赋值应该有的属性。显然查询的是book的信息,这里是Service层,我们应该调用BookService层的getBook方法,通过book的id查询全部信息,这个方法还没写,我们就从BookService一直写到BookDAO把这个方法完善。

public interface BookService {
    public List<Book> getBookList();
    public Book getBook(Integer id);
}
--------------------------------------------------------------------------------------
public class BookServiceImpl implements BookService {
    private BookDAO bookDAO = null;

    @Override
    public List<Book> getBookList() {
        return bookDAO.getBookList();
    }

    @Override
    public Book getBook(Integer id) {
        return bookDAO.getBook(id);
    }
}
--------------------------------------------------------------------------------------
public interface BookDAO {
    public List<Book> getBookList();
    public Book getBook(Integer id);
}
--------------------------------------------------------------------------------------
public class BookDAOImpl extends BaseDao implements BookDAO {
    @Override
    public List<Book> getBookList() {
        String sql = "SELECT * FROM t_book WHERE bookStatus = 0;";
        try {
            return executeQuery(Book.class, sql);
        } catch (Exception e) {
            e.printStackTrace();
            throw new RuntimeException("BookDAOImpl.getBookList出错了");
        }
    }

    @Override
    public Book getBook(Integer id) {
        String sql = "SELECT * FROM t_book WHERE id = ?;";
        try {
            List<Book> books = executeQuery(Book.class, sql, id);
            if (books != null && books.size() > 0) {
                return books.get(0);
            }
        } catch (Exception e) {
            e.printStackTrace();
            throw new RuntimeException("BookDAOImpl.getBook出错了");
        }
        return null;
    }
}

此时完善Book的查询,现在就可以在CartItemService层直接调用BookService,给我们的cartItemList的book一一赋值了。然后getCart方法就不要通过CartItemDAO的getCartItemList了,直接调用内部的我们按照上述逻辑写好的getCartItemList方法即可。记得把这个BookService注册在Service内部。

public class CartItemServiceImpl implements CartItemService {
    private CartItemDAO cartItemDAO = null;
    private BookService bookService = null;

    @Override
    public void addCartItem(CartItem cartItem) {---}

    @Override
    public void updateCartItem(CartItem cartItem) {---}

    @Override
    public void addOrUpdateCartItem(CartItem cartItem, Cart cart) {---}

    @Override
    public List<CartItem> getCartItemList(User user) {
        List<CartItem> cartItemList = cartItemDAO.getCartItemList(user);
        for (CartItem cartItem : cartItemList) {
            Book book = bookService.getBook(cartItem.getBook().getId());
            cartItem.setBook(book);
        }
        return cartItemList;
    }

    @Override
    public Cart getCart(User user) {
        List<CartItem> cartItemList = getCartItemList(user);
        Map<Integer, CartItem> cartItemMap = new HashMap<>();
        for (CartItem cartItem : cartItemList) {
            cartItemMap.put(cartItem.getBook().getId(), cartItem);
        }
        Cart cart = new Cart();
        cart.setCartItemMap(cartItemMap);
        return cart;
    }
}

测试发现,果然完美查到商品总数和总金额,功能完成。 

5. 结账功能

1 订单表添加一条记录
2 订单详情表添加对应多条记录
3 购物车项表中需要删除对应的多条记录

由上面的分析我们应该先去给订单表和订单项表,还有购物车项表写DAO层删除购物车项方法,添加订单。

public interface OderDAO {
    //添加订单
    public void addOrder(Order order);
}

public class OrderDAOImpl extends BaseDao implements OrderDAO {
    @Override
    public void addOrder(Order order) {
        String sql = "INSERT INTO t_order VALUES(0, ?, ?, ?, ?, ?);";
        try {
            executeUpdate(sql, order.getOrderNo(), order.getOrderDate(), order.getOrderUser().getId(),
                    order.getOrderMoney(), order.getOrderStatus());
        } catch (SQLException e) {
            e.printStackTrace();
            throw new RuntimeException("OrderDAOImpl.addOrder出错了");
        }
    }
}

--------------------------------------------------------------------------------------
public interface OrderItemDAO {
    //添加订单项
    public void addOrderItem(OrderItem orderItem);
}

public class OrderItemDAOImpl extends BaseDao implements OrderItemDAO {
    @Override
    public void addOrderItem(OrderItem orderItem) {
        String sql = "INSERT INTO t_order_Item VALUES(0, ?, ?, ?);";
        try {
            executeUpdate(sql, orderItem.getBook().getId(), orderItem.getBuyCount(),
                    orderItem.getOrderBean().getId());
        } catch (SQLException e) {
            e.printStackTrace();
            throw new RuntimeException("OrderItem.addOrderItem出错了");
        }
    }
}
--------------------------------------------------------------------------------------
public interface CartItemDAO {
    //新增购物车项
    public void addCartItem(CartItem cartItem);
    //修改特定购物车项
    public void updateCartItem(CartItem cartItem);
    //获取特定用户的所有购物车项
    public List<CartItem> getCartItemList(User user);
    //删除特定的购物车项
    public delCartItem(CartItem cartItem);
}

public class CartItemDAOImpl extends BaseDao implements CartItemDAO {
    @Override
    public void addCartItem(CartItem cartItem) {---}

    @Override
    public void updateCartItem(CartItem cartItem) {---}

    @Override
    public List<CartItem> getCartItemList(User user) {---}

    @Override
    public void delCartItem(CartItem cartItem) {
        String sql = "DELETE FROM t_cart_item WHERE id = ?;";
        try {
            executeUpdate(sql, cartItem.getId());
        } catch (SQLException e) {
            e.printStackTrace();
            throw new RuntimeException("CartItemImpl.delCartItem出错了");
        }
    }
}

现在回到Service层去Service实现具体的业务。不写一个User参数我们可以从内部获取。

public interface OrderService {
    public void addOrder(Order order);
}

我们的订单项的oderbean列依赖于订单的自增主键,这里为了能生成订单项的时候获取到这个值,然后生成OrderItem项的时候能直接利用到这个字段,这里老师是给BaseDAO封装了方法,我用的JDBC和老师不一样,只能给DAO额外添加一个DAO的查询方法,让Service层调,或者是直接给addOrder方法内部写上对应的逻辑。

public class OrderDAOImpl extends BaseDao implements OrderDAO {
    @Override
    public void addOrder(Order order) {
        String sql = "INSERT INTO t_order VALUES(0, ?, ?, ?, ?, ?);";
        try {
            executeUpdate(sql, order.getOrderNo(), order.getOrderDate(), order.getOrderUser().getId(),
                    order.getOrderMoney(), order.getOrderStatus());
            sql = "SELECT id FROM t_order WHERE orderNO = ?;";
            List<Order> orderList = executeQuery(Order.class, sql, order.getOrderNo());
            Order orderCurr = orderList.get(0);
            order.setId(orderCurr.getId());
        } catch (Exception e) {
            e.printStackTrace();
            throw new RuntimeException("OrderDAOImpl.addOrder出错了");
        }
    }
}

现在实现类大概就可以根据它来写了,这里老师部分是通过先写逻辑,排错,发现赋值为空,再改正。这里我们提前就知道肯定这个order没有orderItemList。同时我们也违背了互相调用对方的DAO层的开发流程。后续肯定会进行修改,这里先这么写。

public class OrderServiceImpl extends BaseDao implements OrderService {
    private OrderDAO orderDAO = null;
    private OrderItemDAO orderItemDAO = null;
    private CartItemDAO cartItemDAO = null;
    @Override
    public void addOrder(Order order) {
        //1 订单表添加一条记录
        orderDAO.addOrder(order); // 这里DAO层会给这个order的id赋值
        //2 订单详情表添加对应多条记录
        List<OrderItem> orderItemList = order.getOrderItemList();
        for (OrderItem orderItem : orderItemList) {
            orderItemDAO.addOrderItem(orderItem);
        }
        //3 购物车项表中需要删除对应的多条记录
        User currUser = order.getOrderUser();
        Map<Integer, CartItem> cartItemMap = currUser.getCart().getCartItemMap();
        for(CartItem cartItem : cartItemMap.values()) {
            cartItemDAO.delCartItem(cartItem);
        }
    }
}

然后就写OrderController,这里肯定像之前说的一样,存在问题,这里先这么写,给Order存在的字段赋值。

public class OrderController {
    private OrderService orderService;

    //结账
    public String checkOut(HttpSession session) {
        Order order = new Order();
        LocalDateTime now = LocalDateTime.now();
        order.setOrderNo(UUID.randomUUID() + "_" + now);
        order.setOrderDate(now);

        User currUser = (User) session.getAttribute("currUser");
        order.setOrderUser(currUser);
        order.setOrderMoney(currUser.getCart().getTotalMoney());
        order.setOrderStatus(0);

        return "index";
    }
}

然后修改一下前端这里的Thymeleaf

<a class="pay" th:href="@{/order.do(operate='checkOut')}">去结账</a>

调试判断当前是否有bug,这里前面已经知道了,肯定有问题。 

果然发现orderItemList是空的。 

我们应该根据用户的购物车中的购物车项去转换成一个一个的订单项 

public class OrderServiceImpl extends BaseDao implements OrderService {
    private OrderDAO orderDAO = null;
    private OrderItemDAO orderItemDAO = null;
    private CartItemDAO cartItemDAO = null;
    @Override
    public void addOrder(Order order) {
        //1 订单表添加一条记录
        orderDAO.addOrder(order); // 这里DAO层会给这个order的id赋值
        //2 订单详情表添加对应多条记录
        //orderBean中的orderItemList是空的,此处我们应该根据用户的购物车中的购物车项去转换成一个一个的订单项
        User currUser = order.getOrderUser();
        Map<Integer, CartItem> cartItemMap = currUser.getCart().getCartItemMap();
        for(CartItem cartItem : cartItemMap.values()) {
            OrderItem orderItem = new OrderItem();
            orderItem.setBook(cartItem.getBook());
            orderItem.setBuyCount(cartItem.getBuyCount());
            orderItem.setOrderBean(order);
            orderItemDAO.addOrderItem(orderItem);
            //3 购物车项表中需要删除对应的多条记录
            cartItemDAO.delCartItem(cartItem);
        }
    }
}

因为数据库数据太杂,为了方便我们debug,我们这里直接清空t_cart_item表和t_order_item和t_order表。然后直接加入购物车然后执行点击结账,发现果然功能实现,购物车项的表数据消失,而增加了订单表和订单项表的数据。 

6. 实现功能:我的订单:计算订单数量

我们返回首页发现购物车小红点还在,要改善这个功能,同时还要实现订单页面,显示订单号和已经成功生成的页面。然后可以点击我的订单,去查看我们现有的订单。

首先我们完善右上角点击购物车返回购物车页面的功能。给超链接写Thymeleaf。

<a th:href="@{/cart.do}" class="cart iconfont icon-gouwuche ">
                    购物车
                    <div class="cart-num" th:text="${session.currUser.cart.getTotalCount()}">3</div>
                </a>

同时右上角点击我的订单进入订单列表,这个功能也做一下。

<div class="order"><a th:href="@{/order.do(operate='getOrderList')}">我的订单</a></div>

那OrderController除了结账功能,我们还应该写一个查看订单页面的功能,我们就叫作getOrderList(HttpSession session),可以从Session中获取CurrUser,然后通过User的id查询t_order表,显然我们应该给OderDAO补充方法。

public interface OrderDAO {
    //添加订单
    public void addOrder(Order order);
    //获取指定用户的订单列表
    public List<Order> getOrderList(User user);
}
--------------------------------------------------------------------------------------
public class OrderDAOImpl extends BaseDao implements OrderDAO {
    @Override
    public void addOrder(Order order) {---}

    @Override
    public List<Order> getOrderList(User user) {
        String sql = "SELECT * FROM t_order WHERE orderUser = ?;";
        try {
            return executeQuery(Order.class, sql, user.getId());
        } catch (Exception e) {
            e.printStackTrace();
            throw new RuntimeException("OrderDAOImpl.getOrder出错了");
        }
    }
}
--------------------------------------------------------------------------------------
public interface OrderService {
    public void addOrder(Order order);
    public List<Order> getOrderList(User user);
}
--------------------------------------------------------------------------------------
public class OrderServiceImpl extends BaseDao implements OrderService {
    private OrderDAO orderDAO = null;
    private OrderItemDAO orderItemDAO = null;
    private CartItemDAO cartItemDAO = null;
    @Override
    public void addOrder(Order order) {---}

    @Override
    public List<Order> getOrderList(User user) {
        return orderDAO.getOrderList(user);
    }
}

这里我们的OrderController能通过Service层获取到这个用户相关的订单,我们既可以把它放入Session作用域,也可以直接给User设定这么一个属性,这样Session里面只保存一个User就很方便。

public class OrderController {
    private OrderService orderService;

    //结账
    public String checkOut(HttpSession session) {---}

    public String getOrderList(HttpSession session){
        User user = (User) session.getAttribute("currUser");

        List<Order> orderList = orderService.getOrderList(user);
        user.setOrderList(orderList);
        session.setAttribute("currUser", user);
        return "order/order";
    }
}

然后我们给order.html的注入Thymeleaf渲染,如css路径,图片路径,这里相同的代码其实Thymeleaf有对应的功能能抽取,这里先不说就复制粘贴。把显示具体的订单列表信息部分写好。

        <table>
            <thead>
            <tr>
                <th>订单号</th>
                <th>订单日期</th>
                <th>订单金额</th>
                <th>订单数量</th>
                <th>订单状态</th>
                <th>订单详情</th>
            </tr>
            </thead>
            <tbody>
            <tr>
                <td>12354456895</td>
                <td>
                    2015.04.23
                </td>
                <td>90.00</td>
                <td>88</td>
                <td><a href="" class="send">等待发货</a></td>
                <td><a href="">查看详情</a></td>
            </tr>
            </tbody>
        </table>
            <tbody>
            <tr th:each="order : ${session.currUser.getOrderList()}" th:object="${order}">
                <td th:text="*{orderNo}">12354456895</td>
                <td th:text="*{orderDate}">
                    2015.04.23
                </td>
                <td th:text="*{orderMoney}">90.00</td>
                <td th:text="*{totalBookCount}">88</td>
                <td><a href="" class="send">等待发货</a></td>
                <td><a href="">查看详情</a></td>
            </tr>
            </tbody>

这里麻烦的是订单数量这里我们之前给购物车写了TotalBookCount,最简单的做法肯定是给t_order表单独增加这个列,然后pojo添加这个属性。我们之前从购物车生成订单信息直接把购物车这个值赋值写入数据库即可。

老师这里展示了如果项目不允许改动数据库的一种写法:多表连接。把订单项表和订单表按订单表id和订单项表orderBean连接,然后按orderBean分组查求和buyCount。

其实这里我分析了一下,我们最开始进入OrderController调用封装的Service的getOrderList方法然后赋值到User的操作,已经查到了对应每个OrderBean的id,我们直接在OrderItem单表查询按查到的List中的OrderBean的id分组求和即可。

我的做法:还是给Order类增加totalBookCount属性但是是Integer。同时我们使用的是德鲁伊新版JDBC,反射查聚合势必反射不到相同类型,之前水果系统单独写了个POJO,FruitData存分类查总数,这里干脆把OrderItem增加这个totalBookCount属性设置为BigDecimal(之前查出来是Long,这里不知道为什么是这个属性),然后查询之后在OderService的getOrderList方法中,转换成Integer赋值给Order的这个属性。同时我们之前的BaseDAO的isNotMyType方法还没加入BigDecimal,还得改一下jar包。

public interface OrderDAO {
    //添加订单
    public void addOrder(Order order);
    //获取指定用户的订单列表
    public List<Order> getOrderList(User user);
    //获取同属一个订单的订单项的购买数量的和
    public BigDecimal getOrderTotalBookCount(Order order);
}
--------------------------------------------------------------------------------------
public class OrderDAOImpl extends BaseDao implements OrderDAO {
    @Override
    public void addOrder(Order order) {---}

    @Override
    public List<Order> getOrderList(User user) {---}

    @Override
    public BigDecimal getOrderTotalBookCount(Order order) {
        String sql = "SELECT SUM(buyCount) AS totalBookCount FROM t_order_item WHERE orderBean = ?;";
        List<OrderItem> orderItemList = null;
        try {
            orderItemList = executeQuery(OrderItem.class, sql, order.getId());
        } catch (Exception e) {
            e.printStackTrace();
            throw new RuntimeException("OrderDAOImpl.getOrderTotalBookCount出错了");
        }
        return orderItemList.get(0).getTotalBookCount();
    }
}
--------------------------------------------------------------------------------------
public class OrderServiceImpl extends BaseDao implements OrderService {
    private OrderDAO orderDAO = null;
    private OrderItemDAO orderItemDAO = null;
    private CartItemDAO cartItemDAO = null;
    @Override
    public void addOrder(Order order) {---}

    @Override
    public List<Order> getOrderList(User user) {
        List<Order> orderList = orderDAO.getOrderList(user);
        for (Order order : orderList) {
            BigDecimal orderTotalBookCount = orderDAO.getOrderTotalBookCount(order);
            order.setTotalBookCount(Integer.parseInt(orderTotalBookCount.toString()));
        }
        return orderList;
    }
}

成功完成这个功能。 

7. 实现功能:编辑购物车和合法用户过滤器

首先实现购物车能够点击加号减号编辑购物车物品的数量的功能。

<span class="count">-</span>
<input class="count-num" type="text" th:value="${cartItem.buyCount}"/>
<span class="count">+</span>

首先加入js方法

<script language="JavaScript" th:src="@{/static/script/cart.js}"></script>
function editCart(cartItemId, buyCount){
    window.location.href='cart.do?operate=editCart&cartItemId='+cartItemId+'&buyCount='+buyCount;
}
<span class="count" th:onclick="|editCart(${cartItem.id}, ${cartItem.buyCount - 1})|">-</span>
<input class="count-num" type="text" th:value="${cartItem.buyCount}"/>
<span class="count" th:onclick="|editCart(${cartItem.id}, ${cartItem.buyCount + 1})|">+</span>

然后可以在CartController里面写这个方法editCart,我们靠cartItemService之前写过的updateCartItem方法,同时肯定要传入这俩参数,但是没有相应的构造方法,我们补一个直接调Service的方法即可。

public class CartController {
    private CartItemService cartItemService;

    public String index(HttpSession session){---}

    public String addCart(Integer bookId, HttpSession session) {---}

    public String editCart(Integer cartItemId, Integer buyCount) {
        cartItemService.updateCartItem(new CartItem(cartItemId, buyCount));
        return "redirect:cart.do";
    }
}

这里测试发现没问题,但是发现带有小数点的价值计算会显示太多的位数,同时购物车数量能小于0甚至等于0。 精度问题我们可以给CartItem多增一个属性,然后就不直接用Book的价格乘CartItem的buyCount,我们内部把get方法写成Bigdicimal实现即可。

<td th:text="${cartItem.getBook().price * cartItem.buyCount}">36.8</td>
public Double getXj() {
    BigDecimal BigDecimalPrice = new BigDecimal(getBook().getPrice() + "");
    BigDecimal BigDecimalBuyCount = new BigDecimal(getBuyCount().toString() + "");
    return BigDecimalPrice.multiply(BigDecimalBuyCount).doubleValue();
}
<td th:text="${cartItem.xj}">36.8</td>

当然这里总金额也是相同的问题,我们之前返回的是Double相乘,是Cart的属性,按同样方法更改即可。

    public Double getTotalMoney() {
        totalMoney = 0.0;
        if (cartItemMap != null && cartItemMap.size() > 0){
            Set<Map.Entry<Integer, CartItem>> entries = cartItemMap.entrySet();
            for (Map.Entry<Integer, CartItem> cartItemEntry : entries) {
                CartItem cartItem = cartItemEntry.getValue();
                BigDecimal bigDecimalPrice = new BigDecimal(cartItem.getBook().getPrice() + "");
                BigDecimal bigDecimalCount = new BigDecimal(cartItem.getBuyCount() + "");
                totalMoney += bigDecimalCount.multiply(bigDecimalPrice).doubleValue();
            }
        }
        return totalMoney;
    }

然后我们希望用户通过硬编码url访问我们网页的时候,如果Session没有用户,就给他跳转到登录页面,而一些首页等访问,让它能正常访问。这就需要我们添加一个过滤器,但是这里我们的过滤器之前放在myssm,现在封装成了jar包,我们过滤器之前放在myssm的filter下,现在在com.fanxy.book下再创建因为book包是b开头的,会先覆盖之前的过滤器。

我们这里就只能把包名换一个字典序小的,为z_book.filters下书写这个过滤器。

过滤器判断是否是合法用户:
   - 解决方法:新建SessionFilter , 用来判断session中是否保存了currUser
   - 如果没有currUser,表明当前不是一个登录合法的用户,应该跳转到登录页面让其登录

   - 现在添加了过滤器之后,出现了如下错误:
   localhost 将您重定向的次数过多。
   尝试清除 Cookie.
   ERR_TOO_MANY_REDIRECTS

@WebFilter(urlPatterns = {"*.do", "*.html"},
        initParams = {
                @WebInitParam(name = "bai", value = "/webmodule15/page.do?operate=page&page=user/login,/webmodule15/user.do?null")
        })
public class SessionFilter implements Filter {

    List<String> baiList = null;

    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        String bai = filterConfig.getInitParameter("bai");
        String[] baiArr = bai.split(",");
        baiList = Arrays.asList(baiArr);
    }

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

        //http://localhost:8080/webmodule15/page.do?operate=page&page=user/login
        //System.out.println("request.getRequestURI() = " + request.getRequestURI());
        //System.out.println("request.getQueryString() = " + request.getQueryString());

        String uri = request.getRequestURI() ;
        String queryString = request.getQueryString() ;
        String str = uri + "?" + queryString ;
        if(baiList.contains(str)){
            filterChain.doFilter(request, response);
        }else{
            HttpSession session = request.getSession();
            Object currUserObj = session.getAttribute("currUser");
            if(currUserObj == null){
                response.sendRedirect("page.do?operate=page&page=user/login");
            }else {
                filterChain.doFilter(request, response);
            }
        }
    }

    @Override
    public void destroy() {

    }
}

如果不这么做,就得在很多的Controller方法判断Session里面的currUser是否为空,为空就返回登录页面的代码,很冗余

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值