显示登录的用户信息
用户登录成功后跳转到登录成功页面,需要显示用户名信息。若用户继续跳转到订单页面,也需要显示用户名信息
此时就不能使用Request域保存数据,因为它只在一次请求中有效,所以使用作用范围比它稍大的session域,这也正是session会话的作用,即保存用户登录后的信息
UserServlet.java
login_success_menu.jsp
用户点击返回,会跳转到首页,此时首页要显示的也是用户登录成功后的信息
client/index.jsp
用户未登录
用户登录成功
返回首页
注销登录
用户点击注销后需要做两件事,销毁session中的用户登录信息(或是销毁session),重定向到首页或登录页面
login_success_menu.jsp
client/index.jsp
UserServlet.java
表单重复提交的三种常见情况
1,提交完表单,服务器使用请求转发进行页面跳转,若此时用户按下f5刷新页面,则又会发起浏览器记录的最后一次请求,造成表单重复提交。解决方法是将请求转发改为重定向来进行跳转
新建临时动态web工程tmp
regist.jsp
RegistServlet.java
web.xml
第一行输出是点击提交后的结果,之后每次按下f5都会打印一条。因为是请求转发,所以第一次点击提交至转发到ok.jsp的整个过程只是一次请求,每次f5浏览器都会发起这次请求,就出现了表单重复提交
将请求转发改为重定向,重定向是一次新的请求,浏览器记录下最后的这次请求,之后f5发起的是重定向的这个请求,就不会出现表单重复提交
2,用户正常提交给服务器,但由于网络延迟等原因导致未收到服务器响应,此时用户认为提交失败,就会多点几次提交,也会造成表单重复提交
用户提交后没有反应,多次点击提交造成表单重复提交
3,用户正常提交给服务器,没有网络延迟,但提交完成后用户点击回退,重新提交,也会造成表单重复提交
用户提交完成后多次点击回退并重新提交,也会造成表单重复提交
验证码底层原理
上述表单重复提交的后两种情况可以使用验证码解决
谷歌验证码的使用
谷歌验证码kaptcha使用步骤:
1,导入谷歌验证码的jar包(Add as Library...)
2,在web.xml中配置用于生成验证码的Servlet程序,该Servlet定义在导入的jar包中。访问/kaptcha.jpg会生成验证码图片并保存到session域中
3,在表单中使用img标签显示验证码图片,regist.jsp
4,服务器获取谷歌生成的验证码,比较客户端发送过来的验证码,RegistServlet.java
用户访问regist.jsp,img标签访问/kaptcha.jsp得到验证码图片,用户输入后点击登录。首先获取生成验证码的Servlet程序在session域中保存的验证码,然后删除域中的验证码,比较用户填写的验证码和生成的验证码是否一致,一致则显示登录成功。若此时用户点击回退并重新登录,这是session域中的验证码已被删除,就不会显示登录成功并提示用户请勿重复提交表单,解决了表单重复提交的问题
将谷歌验证码功能加入书城
在book工程中导入jar包
web.xml
regist.jsp
UserServlet.java
验证码的切换
regist.jsp
但是还有一个问题需要注意,当使用火狐或IE浏览器刷新验证码时,第一次点击时图片会改变,之后点击图片不变。这是缓存的原因,第一次点击后服务器返回验证码图片,浏览器将图片以请求地址的资源名&参数为名缓存到内存或硬盘中,以上面为例,缓存图片为kaptcha.jsp,之后每次点击的请求地址都不变,浏览器认为请求的资源一样,就直接返回缓存的数据
解决方法就是给每次请求都加上不一样的参数
购物车模块的分析
购物车模型的创建
购物车商品项类
package com.atguigu.pojo;
import java.math.BigDecimal;
public class CartItem {
private Integer id;
private String name;
private Integer count;
private BigDecimal price;
private BigDecimal totalPrice;
public CartItem() {
}
public CartItem(Integer id, String name, Integer count, BigDecimal price, BigDecimal totalPrice) {
this.id = id;
this.name = name;
this.count = count;
this.price = price;
this.totalPrice = totalPrice;
}
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getCount() {
return count;
}
public void setCount(Integer count) {
this.count = count;
}
public BigDecimal getPrice() {
return price;
}
public void setPrice(BigDecimal price) {
this.price = price;
}
public BigDecimal getTotalPrice() {
return totalPrice;
}
public void setTotalPrice(BigDecimal totalPrice) {
this.totalPrice = totalPrice;
}
public String toString() {
return "CartItem{" +
"id=" + id +
", name='" + name + '\'' +
", count=" + count +
", price=" + price +
", totalPrice=" + totalPrice +
'}';
}
}
购物车类
package com.atguigu.pojo;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.List;
public class Cart {
private Integer totalCount;
private BigDecimal totalPrice;
private List<CartItem> items = new ArrayList<CartItem>();
public Cart() {
}
public Cart(Integer totalCount, BigDecimal totalPrice, List<CartItem> items) {
this.totalCount = totalCount;
this.totalPrice = totalPrice;
this.items = items;
}
public Integer getTotalCount() {
return totalCount;
}
public void setTotalCount(Integer totalCount) {
this.totalCount = totalCount;
}
public BigDecimal getTotalPrice() {
return totalPrice;
}
public void setTotalPrice(BigDecimal totalPrice) {
this.totalPrice = totalPrice;
}
public List<CartItem> getItems() {
return items;
}
public void setItems(List<CartItem> items) {
this.items = items;
}
public String toString() {
return "Cart{" +
"totalCount=" + totalCount +
", totalPrice=" + totalPrice +
", items=" + items +
'}';
}
}
购物车功能方法的实现和测试
修改存储商品项的集合为Map,便于后续方法操作,items的get()和set()返回值类型也要修改。删除totalCount和totalPrice两个属性,改成在需要获取这两个属性时通过遍历Map集合获取
修改后的Cart.java
package com.atguigu.pojo;
import java.math.BigDecimal;
import java.util.LinkedHashMap;
import java.util.Map;
public class Cart {
// key代表商品编号,value为商品信息
private Map<Integer, CartItem> items = new LinkedHashMap<Integer, CartItem>();
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;
}
public Map<Integer, CartItem> getItems() {
return items;
}
public void setItems(Map<Integer, CartItem> items) {
this.items = items;
}
public String toString() {
return "Cart{" +
"totalCount=" + getTotalCount() +
", totalPrice=" + getTotalPrice() +
", items=" + items +
'}';
}
/**
* 添加商品项
* @param cartItem
*/
public void addItem(CartItem cartItem) {
// 先判断购物车中是否已经添加过该商品,若已有则累加数量、更新总金额,若没有则添加进集合
CartItem item = items.get(cartItem.getId());
if (item == null) {
items.put(cartItem.getId(), cartItem);
} else {
item.setCount(item.getCount() + 1);
// multiply()是BigDecimal类型的乘法
item.setTotalPrice(item.getPrice().multiply(new BigDecimal(item.getCount())));
}
}
/**
* 删除商品项
* @param id
*/
public void deleteItem(Integer id) {
items.remove(id);
}
/**
* 清空购物车
*/
public void clear() {
items.clear();
}
/**
* 修改商品数量
* @param id
* @param count
*/
public void updateCount(Integer id, Integer count) {
// 先判断购物车中是否已有此商品,有就更新商品数量、总金额
CartItem cartItem = items.get(id);
if (cartItem != null) {
cartItem.setCount(count);
cartItem.setTotalPrice(cartItem.getPrice().multiply(new BigDecimal(cartItem.getCount())));
}
}
}
Ctrl+shift+t生成测试方法
public void addItem() {
Cart cart = new Cart();
cart.addItem(new CartItem(1, "java从入门到精通", 1, new BigDecimal(1000), new BigDecimal(1000)));
cart.addItem(new CartItem(1, "java从入门到精通", 1, new BigDecimal(1000), new BigDecimal(1000)));
cart.addItem(new CartItem(2, "数据结构与算法", 1, new BigDecimal(100), new BigDecimal(100)));
System.out.println(cart);
}
public void deleteItem() {
Cart cart = new Cart();
cart.addItem(new CartItem(1, "java从入门到精通", 1, new BigDecimal(1000), new BigDecimal(1000)));
cart.addItem(new CartItem(1, "java从入门到精通", 1, new BigDecimal(1000), new BigDecimal(1000)));
cart.addItem(new CartItem(2, "数据结构与算法", 1, new BigDecimal(100), new BigDecimal(100)));
cart.deleteItem(1);
System.out.println(cart);
}
public void clear() {
Cart cart = new Cart();
cart.addItem(new CartItem(1, "java从入门到精通", 1, new BigDecimal(1000), new BigDecimal(1000)));
cart.addItem(new CartItem(1, "java从入门到精通", 1, new BigDecimal(1000), new BigDecimal(1000)));
cart.addItem(new CartItem(2, "数据结构与算法", 1, new BigDecimal(100), new BigDecimal(100)));
cart.clear();
System.out.println(cart);
}
public void updateCount() {
Cart cart = new Cart();
cart.addItem(new CartItem(1, "java从入门到精通", 1, new BigDecimal(1000), new BigDecimal(1000)));
cart.addItem(new CartItem(1, "java从入门到精通", 1, new BigDecimal(1000), new BigDecimal(1000)));
cart.addItem(new CartItem(2, "数据结构与算法", 1, new BigDecimal(100), new BigDecimal(100)));
cart.updateCount(1, 10);
System.out.println(cart);
}
添加商品到购物车功能的实现
client/index.jsp
package com.atguigu.web;
import com.atguigu.pojo.Book;
import com.atguigu.pojo.Cart;
import com.atguigu.pojo.CartItem;
import com.atguigu.service.BookService;
import com.atguigu.service.impl.BookServiceImpl;
import com.atguigu.utils.WebUtils;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
public class CartServlet extends BaseServlet {
private BookService bookService = new BookServiceImpl();
protected void addItem(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// 获取请求中的参数商品编号
int id = WebUtils.parseInt(req.getParameter("id"), 0);
// 查询得到对应编号的图书信息
Book book = bookService.queryBookById(id);
// 将图书信息转换为CartItem对象
CartItem cartItem = new CartItem(book.getId(), book.getName(), 1, book.getPrice(), book.getPrice());
// 在session域中获取购物车对象,若没有就创建,保证只有一个购物车对象
Cart cart = (Cart) req.getSession().getAttribute("cart");
if (cart == null) {
cart = new Cart();
req.getSession().setAttribute("cart", cart);
}
// 将商品项进入购物车
cart.addItem(cartItem);
// 浏览器发起请求时会通过请求头Referer将地址栏中的地址发送给服务器
// 重定向回发起请求的页面,即在哪个页面执行的加入购物车操作就跳回到哪个页面
resp.sendRedirect(req.getHeader(("Referer")));
}
}
web.xml
购物车的展示
cart.jsp
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<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>${ entry.value.count }</td>
<td>${ entry.value.price }</td>
<td>${ entry.value.totalPrice }</td>
<td><a href="#">删除</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="cart_span"><a href="#">清空购物车</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>
删除购物车中的商品项
cart.jsp
CartServlet.java
清空购物车的实现
cart.jsp
cartServlet.java
修改购物车商品数量
cart.jsp
cartServlet.java
首页购物车数据展示
client/index.jsp
CartServlet.java的addItem()
订单模块的分析
创建订单模型的数据库表
需要外键约束,即数据行中某列的值必须是指定表的指定列中存在的
CREATE TABLE t_order(
`order_id` VARCHAR(50) PRIMARY KEY,
`create_time` DATETIME,
`price` DECIMAL(11,2),
`status` INT,
`user_id` INT,
FOREIGN KEY(`user_id`) REFERENCES t_user(`id`)
);
CREATE TABLE t_order_item(
`id` INT PRIMARY KEY AUTO_INCREMENT,
`name` VARBINARY(100),
`count` INT,
`price` DECIMAL(11,2),
`total_price` DECIMAL(11,2),
`order_id` VARCHAR(50),
FOREIGN KEY(`order_id`) REFERENCES t_order(`order_id`)
);
编写订单模块的两个数据模型Order和OrderItem
package com.atguigu.pojo;
import java.math.BigDecimal;
import java.util.Date;
public class Order {
private String orderId;
private Date createTime;
private BigDecimal price;
private Integer status = 0;
private Integer userId;
public Order() {
}
public Order(String orderId, Date createTime, BigDecimal price, Integer status, Integer userId) {
this.orderId = orderId;
this.createTime = createTime;
this.price = price;
this.status = status;
this.userId = userId;
}
public String getOrderId() {
return orderId;
}
public void setOrderId(String orderId) {
this.orderId = orderId;
}
public Date getCreateTime() {
return createTime;
}
public void setCreateTime(Date createTime) {
this.createTime = createTime;
}
public BigDecimal getPrice() {
return price;
}
public void setPrice(BigDecimal price) {
this.price = price;
}
public Integer getStatus() {
return status;
}
public void setStatus(Integer status) {
this.status = status;
}
public Integer getUserId() {
return userId;
}
public void setUserId(Integer userId) {
this.userId = userId;
}
public String toString() {
return "Order{" +
"orderId='" + orderId + '\'' +
", createTime=" + createTime +
", price=" + price +
", status=" + status +
", userId=" + userId +
'}';
}
}
package com.atguigu.pojo;
import java.math.BigDecimal;
public class OrderItem {
private Integer id;
private String name;
private Integer count;
private BigDecimal price;
private BigDecimal totalPrice;
private String orderId;
public OrderItem() {
}
public OrderItem(Integer id, String name, Integer count, BigDecimal price, BigDecimal totalPrice, String orderId) {
this.id = id;
this.name = name;
this.count = count;
this.price = price;
this.totalPrice = totalPrice;
this.orderId = orderId;
}
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getCount() {
return count;
}
public void setCount(Integer count) {
this.count = count;
}
public BigDecimal getPrice() {
return price;
}
public void setPrice(BigDecimal price) {
this.price = price;
}
public BigDecimal getTotalPrice() {
return totalPrice;
}
public void setTotalPrice(BigDecimal totalPrice) {
this.totalPrice = totalPrice;
}
public String getOrderId() {
return orderId;
}
public void setOrderId(String orderId) {
this.orderId = orderId;
}
public String toString() {
return "OrderItem{" +
"id=" + id +
", name='" + name + '\'' +
", count=" + count +
", price=" + price +
", totalPrice=" + totalPrice +
", orderId='" + orderId + '\'' +
'}';
}
}
编写订单模块的Dao和测试
package com.atguigu.dao;
import com.atguigu.pojo.Order;
public interface OrderDao {
public int saveOrder(Order order);
}
package com.atguigu.dao;
import com.atguigu.pojo.OrderItem;
public interface OrderItemDao {
public int saveOrderItem(OrderItem orderItem);
}
package com.atguigu.dao.impl;
import com.atguigu.dao.OrderDao;
import com.atguigu.pojo.Order;
public class OrderDaoImpl extends BaseDao implements OrderDao {
public int saveOrder(Order order) {
String sql = "insert into t_order(`order_id`,`create_time`,`price`,`status`,`user_id`) values(?,?,?,?,?)";
return update(sql,order.getOrderId(),order.getCreateTime(),order.getPrice(),order.getStatus(),order.getUserId());
}
}
package com.atguigu.dao.impl;
import com.atguigu.dao.OrderItemDao;
import com.atguigu.pojo.OrderItem;
public class OrderItemDaoImpl extends BaseDao implements OrderItemDao {
public int saveOrderItem(OrderItem orderItem) {
String sql = "insert into t_order_item(`name`,`count`,`price`,`total_price`,`order_id`) values(?,?,?,?,?)";
update(sql,orderItem.getName(),orderItem.getCount(),orderItem.getPrice(),orderItem.getTotalPrice(),orderItem.getOrderId());
return 0;
}
}
package com.atguigu.test;
import com.atguigu.dao.OrderDao;
import com.atguigu.dao.impl.OrderDaoImpl;
import com.atguigu.pojo.Order;
import org.junit.Test;
import java.math.BigDecimal;
import java.util.Date;
public class OrderDaoTest {
public void saveOrder() {
OrderDao orderDao = new OrderDaoImpl();
orderDao.saveOrder(new Order("1234567890", new Date(), new BigDecimal(100), 0, 1));
}
}
package com.atguigu.test;
import com.atguigu.dao.OrderItemDao;
import com.atguigu.dao.impl.OrderItemDaoImpl;
import com.atguigu.pojo.OrderItem;
import org.junit.Test;
import java.math.BigDecimal;
public class OrderItemDaoTest {
public void saveOrderItem() {
OrderItemDao orderItemDao = new OrderItemDaoImpl();
orderItemDao.saveOrderItem(new OrderItem(null, "Java从入门到精通", 1, new BigDecimal(100), new BigDecimal(100), "1234567890"));
orderItemDao.saveOrderItem(new OrderItem(null, "JavaScript从入门到精通", 2, new BigDecimal(100), new BigDecimal(200), "1234567890"));
orderItemDao.saveOrderItem(new OrderItem(null, "c++从入门到精通", 1, new BigDecimal(100), new BigDecimal(100), "1234567890"));
}
}
编写订单模块的Service和测试
package com.atguigu.service;
import com.atguigu.pojo.Cart;
public interface OrderService {
public String createOrder(Cart cart, Integer userId);
}
package com.atguigu.service.impl;
import com.atguigu.dao.OrderDao;
import com.atguigu.dao.OrderItemDao;
import com.atguigu.dao.impl.OrderDaoImpl;
import com.atguigu.dao.impl.OrderItemDaoImpl;
import com.atguigu.pojo.Cart;
import com.atguigu.pojo.CartItem;
import com.atguigu.pojo.Order;
import com.atguigu.pojo.OrderItem;
import com.atguigu.service.OrderService;
import java.util.Date;
import java.util.Map;
public class OrderServiceImpl implements OrderService {
private OrderDao orderDao = new OrderDaoImpl();
private OrderItemDao orderItemDao = new OrderItemDaoImpl();
public String createOrder(Cart cart, Integer userId) {
// 订单号要保证唯一,当有两个用户同时创建订单时,加上他们的userId来保证订单号唯一
String orderId = System.currentTimeMillis() + "" + userId;
Order order = new Order(orderId, new Date(), cart.getTotalPrice(), 0, userId);
orderDao.saveOrder(order);
// 遍历购物车中的每个商品项,转换为订单项后保存到数据库
for (Map.Entry<Integer, CartItem> entry : cart.getItems().entrySet()) {
CartItem cartItem = entry.getValue();
OrderItem orderItem = new OrderItem(null, cartItem.getName(), cartItem.getCount(), cartItem.getPrice(), cartItem.getTotalPrice(), orderId);
orderItemDao.saveOrderItem(orderItem);
}
// 点击结账后要清空购物车
cart.clear();
return orderId;
}
}
package com.atguigu.test;
import com.atguigu.pojo.Cart;
import com.atguigu.pojo.CartItem;
import com.atguigu.service.impl.OrderServiceImpl;
import org.junit.Test;
import java.math.BigDecimal;
import static org.junit.Assert.*;
public class OrderServiceTest {
public void createOrder() {
Cart cart = new Cart();
OrderServiceImpl orderService = new OrderServiceImpl();
cart.addItem(new CartItem(1, "java从入门到精通", 1, new BigDecimal(1000), new BigDecimal(1000)));
cart.addItem(new CartItem(1, "java从入门到精通", 1, new BigDecimal(1000), new BigDecimal(1000)));
cart.addItem(new CartItem(2, "数据结构与算法", 1, new BigDecimal(100), new BigDecimal(100)));
System.out.println(orderService.createOrder(cart, 1));
}
}
结账功能实现,生成订单
cart.jsp
OrderServlet.java
web.xml
checkout.jsp
解决生成订单的bug
OrderServlet.java
checkout.jsp
订单结账后首页显示的库存、销量要相应的改变