购物车
1. 加入购物车
1.1 创建数据库表
CREATE TABLE t_cart (
cid INT AUTO_INCREMENT COMMENT '购物车数据id',
uid INT NOT NULL COMMENT '用户id',
pid INT NOT NULL COMMENT '商品id',
price BIGINT COMMENT '加入时商品单价',
num INT COMMENT '商品数量',
created_user VARCHAR(20) COMMENT '创建人',
created_time DATETIME COMMENT '创建时间',
modified_user VARCHAR(20) COMMENT '修改人',
modified_time DATETIME COMMENT '修改时间',
PRIMARY KEY (cid)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
1.2 创建实体类
public class Cart extends BaseEntity{
private Integer cid;
private Integer uid;
private Integer pid ;
private Long price;
private Integer num;
//...
1.3 持久层
- 规划需要执行的SQL语句
1.向购物车表中插入数据
2.如果当前商品已经在购物车中存在,则直接更新num的数量即可。insert into t_cart () values ()
3.在插入或更新具体执行哪个语句,取决于数据库中是否有当前的这个购物车商品的数据,得去查询才能确定。update t_cart set num = ? where cid=?
select * from t_cart where cid = ? and uid = ?
- 设计接口和抽象方法
创建一个CartMapper接口持久层的文件。public interface CartMapper { //插入购物车数据 Integer insert(Cart cart); //更新购物车某件商品的数量 Integer updateNumByCid(Integer cid, Integer num, String modifiedUser, Date modifiedTime); //根据用户的id和商品的id来查询购物车的数据 Cart findByUidAndPid(Integer uid, Integer pid); }
- SQL映射
创建一个CartMapper.xml映射文件,添加以上三个抽象方法的SQL语句映射。
测试<?xml version="1.0" encoding="UTF8" ?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> <mapper namespace="com.cy.store.mapper.CartMapper"> <resultMap type="com.cy.store.entity.Cart" id="CartEntityMap"> <id property="cid" column="cid"/> <result property="createdUser" column="created_user"/> <result property="createdTime" column="created_time"/> <result property="modifiedUser" column="modified_user"/> <result property="modifiedTime" column="modified_time"/> </resultMap> <insert id="insert" useGeneratedKeys="true" keyProperty="cid"> insert into t_cart (uid, pid, price, num, created_user, created_time, modified_user, modified_time) values (#{uid}, #{pid}, #{price}, #{num}, #{createdUser},#{createdTime},#{modifiedUser},#{modifiedTime}) </insert> <update id="updateNumByCid"> update t_cart set num=#{num}, modified_user=#{modifiedUser}, modified_time=#{modifiedTime} where cid=#{cid} </update> <select id="findByUidAndPid" resultMap="CartEntityMap"> select * from t_cart where uid=#{uid} and pid=#{pid} </select> </mapper>
@Autowired private CartMapper cartMapper; @Test public void insert(){ Cart cart = new Cart(); cart.setUid(10); cart.setPid(10000011); cart.setNum(2); cart.setPrice(1000L); cartMapper.insert(cart); } @Test public void updateNumByCid(){ cartMapper.updateNumByCid(1,4,"张三", new Date()); } @Test public void findByUidAndPid() { System.out.println(cartMapper.findByUidAndPid(10, 10000011)); }
1.4 业务层
- 异常规划
1.插入数据产生的异常:InsertException
2.更新数据产生的异常:UpdateException - 接口和抽象方法
创建一个ICartService接口文件public interface ICartService { /** * 添加商品到购物车 * @param uid 用户id * @param pid 商品id * @param amount 新增数量 * @param username 用户名(修改者) */ void addToCart(Integer uid, Integer pid, Integer amount, String username); }
- 实现接口
创建一个CartServiceImpl的实现类
测试@Override public void addToCart(Integer uid, Integer pid, Integer amount, String username) { //查询当前要添加的这个物品是否在表中已存在 Cart result = cartMapper.findByUidAndPid(uid, pid); Date date = new Date(); if(result == null) { //表示这个商品从来没有被添加到购物车中,则进行新增操作 Cart cart = new Cart(); cart.setUid(uid); cart.setPid(pid); cart.setNum(amount); //补全价格,来自于商品中的数据 Product product = productMapper.findById(pid); cart.setPrice(product.getPrice()); //补全4项日志 cart.setCreatedUser(username); cart.setCreatedTime(date); cart.setModifiedUser(username); cart.setModifiedTime(date); Integer rows = cartMapper.insert(cart); if(rows != 1) { throw new InsertException("插入数据时产生未知的异常"); } } else { //表示当前的商品在购物车中已经存在,则更新到这条数据的num值 Integer num = result.getNum() + amount; Integer rows = cartMapper.updateNumByCid( result.getCid(), num, username, date ); if(rows != 1) { throw new UpdateException("更新数据时产生未知的异常"); } } }
@Autowired private ICartService cartService; @Test public void addToCart() { cartService.addToCart(10,10000003,5,"管理员"); }
1.5 控制层
- 没有需要处理的异常
- 设计请求处理
/carts/add_to_cart get pid,amount,session JsonResult<Void>
- 完成请求处理方法的编写。创建一个CartController类。
先登录再访问进行测试:http://localhost:8080/carts/add_to_cart?pid=10000021&amount=5@RequestMapping("carts") @RestController public class CartController extends BaseController{ @Autowired private ICartService cartService; @RequestMapping("add_to_cart") public JsonResult<Void> addToCart( Integer pid, Integer amount, HttpSession session) { cartService.addToCart(getuidFromSession(session),pid,amount,getUsernameFromSession(session)); return new JsonResult<>(OK); } }
1.6 前端页面
在ajax函数中data参数的数据设置的方式:
data: #("form表单选择").serialize()
:用于参数比较多,并且表单的参数都需要提交data: new FormData($("form表单选择")[0])
:用于提交文件data: "username=Tom"
:用于参数值固定并且参数值列表有限- 用JSON格式提交:
data: { "username": "TOM", "age": 18, "sex": 0 }
在product.html页面,给[加入购物车]按钮添加点击事件,并发送ajax请求。
$("#btn-add-to-cart").click(function () {
$.ajax({
url: "/carts/add_to_cart",
type: "post",
data: {
"pid": id,
"amount": $("#num").val()
},
dataType: "JSON",
success: function (json) {
if(json.state == 200) {
alert("加入购物车成功");
} else {
alert("加入购物车失败");
}
},
error: function (xhr) {
alert("加入购物车时产生未知的异常" + xhr.message);
}
});
});
2. 显示购物车
登录后将对应用户的购物车数据展示出来。
2.1 持久层
-
规划SQL语句
select cid,uid,pid,t_cart.price,t_cart.num, t_product.title,t_product.image,t_product.price as realPrice from t_cart left join t_product on t_cart.pid = t_product.id where uid = ? order by t_cart.created_time desc;
VO:Value Object,值对象。当进行select查询时,查询的结果属于多张表中的内容,此时发现结果集不能直接使用某个pojo实体类来接收,pojo实体类不能包含多表查询出来的结果。解决方式是:重新构建一个新的对象,这个对象用于存储所查询出来的结果集对应的映射,所以把这个对象称之为 值对象。
在store包下创建一个vo包,在vo包下创建CartVO实体类:
/** 购物车数据的VO类 */ public class CartVO implements Serializable { private Integer cid; private Integer uid; private Integer pid; private Long price; private Integer num; private String title; private String image; private Long realprice; //...
-
设计接口
在CartMapper接口中定义抽象方法List<CartVO> findVOByUid(Integer uid);
-
映射SQL
<select id="findVOByUid" resultType="com.cy.store.vo.CartVO"> select cid, uid, pid, t_cart.price, t_cart.num, t_product.title, t_product.image, t_product.price as realPrice from t_cart left join t_product on t_cart.pid = t_product.id where uid = #{uid} order by t_cart.created_time desc; </select>
测试
@Test public void findVOByUid() { System.out.println(cartMapper.findVOByUid(10)); }
2.2 业务层
- 编写业务层的接口方法
List<CartVO> getVOByUid(Integer uid)
- 在实现类中实现此方法
@Override public List<CartVO> getVOByUid(Integer uid) { return cartMapper.findVOByUid(uid); }
2.3 控制层
- 设计请求
/carts/ session get JsonResult<List<CartVO>>
- 实现请求处理方法的代码
先登录再测试:http://localhost:8080/carts@RequestMapping({"","/"}) public JsonResult<List<CartVO>> getVOByUid(HttpSession session) { List<CartVO> data = cartService.getVOByUid(getuidFromSession(session)); return new JsonResult<>(OK, data); }
2.4 前端页面
1.注释掉cart.js文件
<!-- <script src="../js/cart.js" type="text/javascript" charset="utf-8"></script> -->
2.注意form表单结构。将“结算按钮”改成type="button"
属性值。
3.ready()函数来完成自动的ajax请求的提交和处理。
<script type="text/javascript">
$(document).ready(function () {
showCartList();
});
/** 展示购物车列表数据*/
function showCartList() {
//清空tbody标签的数据
$("#cart-list").empty();
$.ajax({
url: "/carts",
type: "get",
dataType: "JSON",
success: function (json) {
let list = json.data;
for (let i = 0; i < list.length; i++) {
let tr = '<tr>\n' +
'<td>\n' +
'<input name="cids" value="#{cid}" type="checkbox" class="ckitem" />\n' +
'</td>\n' +
'<td><img src="..#{image}collect.png" class="img-responsive" /></td>\n' +
'<td>#{title}#{msg}</td>\n' +
'<td>¥<span id="goodsPrice#{cid}">#{singlePrice}</span></td>\n' +
'<td>\n' +
'<input id="price-#{cid}" type="button" value="-" class="num-btn" οnclick="reduceNum(1)" />\n' +
'<input id="goodsCount#{cid}" type="text" size="2" readonly="readonly" class="num-text" value="#{num}">\n' +
'<input id="price+#{cid}" class="num-btn" type="button" value="+" οnclick="addNum(1)" />\n' +
'</td>\n' +
'<td><span id="goodsCast#{cid}">#{totalPrice}</span></td>\n' +
'<td>\n' +
'<input type="button" οnclick="delCartItem(this)" class="cart-del btn btn-default btn-xs" value="删除" />\n' +
'</td>\n' +
'</tr>'
tr = tr.replace(/#{cid}/g, list[i].cid);
tr = tr.replace(/#{image}/g, list[i].image);
tr = tr.replace(/#{title}/g, list[i].title);
tr = tr.replace(/#{msg}/g, list[i].realPrice);
tr = tr.replace(/#{num}/g, list[i].num);
tr = tr.replace(/#{singlePrice}/g, list[i].price);
tr = tr.replace(/#{totalPrice}/g, list[i].price*list[i].num);
$("#cart-list").append(tr);
}
},
error: function (xhr) {
alert("购物袋列表数据加载产生未知的异常" + xhr.status);
}
});
}
</script>
3. 增加购物车商品数量
3.1 持久层
- 规划需要执行的SQL语句
1.执行更新t_cart表记录的num的值,无需重复开发。
2.根据cid的值来查询当前的购物车这条数据是否存在update t_cart set num = ?, modified_user = ?, modified_time = ? where cid = ?;
select * from t_cart where cid=#{cid}
- 接口和抽象方法
Cart findByCid(Integer cid);
- 配置SQL的映射
测试<select id="findByCid"> select * from t_cart where cid=#{cid}; </select>
@Test public void findByCid() { System.out.println(cartMapper.findByCid(1)); }
3.2 业务层
- 规划异常
1.在更新时会产生更新异常
2.查询的数据是否有访问的权限
3.要查询的数据不存在,抛出CartNotFoundException异常。 - 设计接口和抽象方法
/** * 更新用户的购物车数据的数量 * @param cid * @param uid * @param username * @return 增加成功后新的数量 */ Integer addNum(Integer cid, Integer uid, String username);
- 实现方法
@Override public Integer addNum(Integer cid, Integer uid, String username) { Cart result = cartMapper.findByCid(cid); if(result == null) { throw new CartNotFoundException("数据不存在"); } if(!result.getUid().equals(uid)) { throw new AccessDeniedException("数据非法访问"); } Integer num = result.getNum() + 1; Integer rows = cartMapper.updateNumByCid(cid, num, username, new Date()); if(rows != 1) { throw new UpdateException("更新数据失败!"); } return num; }
3.3 控制层
- 处理异常
else if (e instanceof CartNotFoundException) { result.setState(4007); result.setMessage("购物车数据不存在的异常"); }
- 设计请求
/carts/{cid}/num/add Integer cid, HttpSession post JsonResult<Integer>
- 处理请求
先登录再访问url:http://localhost:8080/carts/1/num/add@RequestMapping("{cid}/num/add") public JsonResult<Integer> addNum(@PathVariable("cid") Integer cid, HttpSession session) { Integer data = cartService.addNum( cid, getuidFromSession(session), getUsernameFromSession(session) ); return new JsonResult<>(OK, data); }
3.4 前端页面
function addNum(cid) {
$.ajax({
url: "/carts/" + cid + "/num/add",
type: "post",
dataType: "JSON",
success: function (json) {
if(json.state == 200) {
$("#goodsCount" + cid).val(json.data);
let price = $("#goodsPrice" + cid).html();
let totalPrice = price * json.data;
$("#goodsCast" + cid).html(totalPrice);
} else {
alert("增加购物车数据失败," + json.message);
}
},
error: function (xhr) {
alert("增加购物车商品数量产生未知的异常" + xhr.message);
}
});
}
4. 显示勾选购物车数据
4.1 持久层
- 规划SQL语句
用户在购物车列表页中通过随机勾选相关的商品,在点击“结算”按钮后,跳转到结算页面,在这个页面中需要展示用户在上个页面所勾选的购物车对应的数据。两个页面需要用户勾选的多个cid传递给下一个页面。select cid,uid,pid,t_cart.price,t_cart.num, t_product.title,t_product.image,t_product.price as realPrice from t_cart left join t_product on t_cart.pid = t_product.id where cid in (?,?,?) order by t_cart.created_time desc;
- 接口和抽象方法
List<CartVO> findVOByCid(Integer[] cids);
- SQL映射
测试<select id="findVOByCid" resultType="com.cy.store.vo.CartVO"> select cid, uid, pid, t_cart.price, t_cart.num, t_product.title, t_product.image, t_product.price as realPrice from t_cart left join t_product on t_cart.pid = t_product.id where cid in ( <foreach collection="array" item="cid" separator=","> #{cid} </foreach> ) order by t_cart.created_time desc; </select>
@Test public void findVOByCid() { Integer[] cids = {1,2,3,4,30,20,40}; System.out.println(cartMapper.findVOByCid(cids)); }
4.2 业务层
1.没有需要进行异常的规划
2.设计业务层接口中抽象方法
List<CartVO> getVOByCid(Integer uid, Integer[] cids);
3.完成抽象方法的设计
@Override
public List<CartVO> getVOByCid(Integer uid, Integer[] cids) {
List<CartVO> list = cartMapper.findVOByCid(cids);
Iterator<CartVO> it = list.iterator();
while(it.hasNext()) {
CartVO cartVO = it.next();
if(!cartVO.getUid().equals(uid)) { //表示这个数据不属于当前对象
//从集合中移除这个数据
list.remove(cartVO);
}
}
return list;
}
4.3 控制层
1.请求设计
/carts/list
Integer cids, HttpSession session
post
JsonResult<CartVO>
2.完成请求处理方法的定义和声明
@RequestMapping("list")
public JsonResult<List<CartVO>> getVOByCid(Integer[] cids, HttpSession session) {
List<CartVO> data = cartService.getVOByCid(getuidFromSession(session),cids);
return new JsonResult<>(OK, data);
}
4.4 前端页面
在cart.html中有个表单,点击“结算”按钮跳转到这个页面:action=“orderConfirm.html”。在orderConfirm.html页面加入如下代码,可以使页面跳转后就加载要结算的数据。
<script type="text/javascript">
$(document).ready(function () {
showCartList();
});
//展示购物车列表
function showCartList() {
//清空tbody标签的数据
$("#cart-list").empty();
$.ajax({
url: "/carts/list",
type: "get",
data: location.search.substr(1),
dataType: "JSON",
success: function (json) {
if(json.state == 200) {
let list= json.data;
let allCount = 0;
let allPrice = 0;
for (let i = 0; i < list.length; i++) {
let tr = '<tr>\n' +
'<td><img src="..#{image}collect.png" class="img-responsive" /></td>\n' +
'<td>#{title}</td>\n' +
'<td>¥<span>#{price}</span></td>\n' +
'<td>#{num}</td>\n' +
'<td><span>#{totalPrice}</span></td>\n' +
'</tr>';
tr = tr.replace(/#{image}/g, list[i].image);
tr = tr.replace(/#{title}/g, list[i].title);
tr = tr.replace(/#{price}/g, list[i].price);
tr = tr.replace(/#{num}/g, list[i].num);
tr = tr.replace(/#{totalPrice}/g, list[i].price*list[i].num);
$("#cart-list").append(tr);
allCount += list[i].num;
allPrice += list[i].price * list[i].num;
}
$("#all-count").html(allCount);
$("#all-price").html(allPrice);
}
},
error: function (xhr) {
alert("购物袋列表数据加载产生未知的异常" + xhr.status);
}
});
}
</script>
注:这里有个bug,当你什么都不勾选就去结算,会报错。因为controller中是需要接收参数的,但是没勾选就提交是没有带参数的,这就有问题了。所以最好在结算的时候判断一下有没有选择商品。
4.5 购物车页面显示收货地址列表
1.收获地址存放在一个select下拉列表中,将查询到的当前登录用户的收货地址动态的加载到这个下拉列表中。从数据库的角度,是一个select查询语句。已经编写了根据用户的uid来查询当前用户的收货地址数据。
2.orderConfirm.html页面中,收货地址数据的展示需要自动进行加载,需要将方法的逻辑放在ready()函数中。
$(document).ready(function () {
showCartList();
showAddressList();
});
3.声明和定义showAddressList()方法,方法中发送ajax请求即可。
function showAddressList() {
//清空tbody标签的数据
$("#address-list").empty();
$.ajax({
url: "/addresses/",
type: "get",
dataType: "JSON",
success: function (json) {
if(json.state == 200) {
let list= json.data;
for (let i = 0; i < list.length; i++) {
let opt = "<option value='#{aid}'>#{name} #{tag} #{provinceName}#{cityName}#{areaName}#{address} #{phone}</option>";
opt = opt.replace(/#{aid}/g, list[i].aid);
opt = opt.replace(/#{name}/g, list[i].name);
opt = opt.replace(/#{tag}/g, list[i].tag);
opt = opt.replace(/#{provinceName}/g, list[i].provinceName);
opt = opt.replace(/#{cityName}/g, list[i].cityName);
opt = opt.replace(/#{areaName}/g, list[i].areaName);
opt = opt.replace(/#{address}/g, list[i].address);
opt = opt.replace(/#{phone}/g, list[i].phone);
$("#address-list").append(opt);
}
}
},
error: function (xhr) {
alert("购物车收货地址加载产生未知的异常" + xhr.status);
}
});
}