商品页面

一.热销商品排行

1.数据库创建

1.在store数据库中创建t_product数据表

CREATE TABLE t_product (
  id int(20) NOT NULL COMMENT '商品id',
  category_id int(20) DEFAULT NULL COMMENT '分类id',
  item_type varchar(100) DEFAULT NULL COMMENT '商品系列',
  title varchar(100) DEFAULT NULL COMMENT '商品标题',
  sell_point varchar(150) DEFAULT NULL COMMENT '商品卖点',
  price bigint(20) DEFAULT NULL COMMENT '商品单价',
  num int(10) DEFAULT NULL COMMENT '库存数量',
  image varchar(500) DEFAULT NULL COMMENT '图片路径',
  status int(1) DEFAULT '1' COMMENT '商品状态  1:上架   2:下架   3:删除',
  priority int(10) DEFAULT NULL COMMENT '显示优先级',
  created_time datetime DEFAULT NULL COMMENT '创建时间',
  modified_time datetime DEFAULT NULL COMMENT '最后修改时间',
  created_user varchar(50) DEFAULT NULL COMMENT '创建人',
  modified_user varchar(50) DEFAULT NULL COMMENT '最后修改人',
  PRIMARY KEY (id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
2.实体类创建

在entity包下创建Product

@Data
@AllArgsConstructor
@NoArgsConstructor
public class Product extends BaseEntity implements Serializable {
    private Integer id;
    private Integer categoryId;
    private String itemType;
    private String title;
    private String sellPoint;
    private Long price;
    private Integer num;
    private String image;
    private Integer status;
    private Integer priority;
}
持久层
1.规划SQL语句

1.查询热销商品列表的SQL语句

select * from t_product where status=1 order by priority DESC limit 0,4
2.设计接口和抽象方法

在mapper包下创建ProductMapper

public interface ProductMapper {
    //热销商品排行
    List<Product> findHotList();
}
3.配置映射

在mapper包下创建ProductMapper.xml

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<!--namespace属性:用于指定当前的映射文件和那个接口进行映射,需要指定接口的文件路径-->
<mapper namespace="com.cy.store.mapper.ProductMapper">
<!--    自定义映射规则-->
    <resultMap id="ProductEntityMap" type="com.cy.store.entity.Product">
        <id column="id" property="id"/>
        <result column="category_id" property="categoryId"/>
        <result column="item_type" property="itemType"/>
        <result column="sell_point" property="sellPoint"/>
        <result column="created_user" property="createdUser"/>
        <result column="created_time" property="createdTime"/>
        <result column="modified_user" property="modifiedUser"/>
        <result column="modified_time" property="modifiedTime"/>
    </resultMap>

<!--    查询商品是否存在,并根据优先级取前4个进行排序,-->
    <select id="findHotList" resultMap="ProductEntityMap">
        select * from t_product where status = 1
        order by priority DESC limit 0,4
    </select>

</mapper>
业务层
1.设计接口和抽象方法

在Service包下创建IProductService接口:

/** 处理商品数据的业务层接口 */
public interface IProductService {
    /**
     * 查询热销商品的前四名
     * @return 热销商品前四名的集合
     */
    List<Product> findHotList();
}
2.实现接口

创建接口实现类ProductServiceImpl:

@Service
public class ProductServiceImpl implements IProductService {

    @Autowired
    private ProductMapper productMapper;
    //热销商品排行
    @Override
    public List<Product> findHotList() {
        List<Product> list = productMapper.findHotList();
        for (Product product : list) {
            product.setPriority(null);
            product.setCreatedUser(null);
            product.setCreatedTime(null);
            product.setModifiedUser(null);
            product.setModifiedTime(null);
        }
        return list;
    }
}
3.单元测试

ProductServiceTests:

@SpringBootTest
public class ProductServiceTests {
    @Autowired
    private IProductService productService;

    @Test
    public void findHotList(){
        List<Product> list = productService.findHotList();
        System.out.println(list);
    }

}
控制层
1.设计请求
/products/hot_list
GET
data:无
JsonResult<List<Product>>

在LoginInterceptorConfigurer类中将index.html页面和product/**请求添加到白名单中

patterns.add("/web/index.html");
patterns.add("/products/**");
2.处理请求

在Controller包下创建一个ProductController,去继承BaseController

@RestController
@RequestMapping("products")
public class ProductController extends BaseController {

    @Autowired
    private IProductService productService;

    //热销商品排行
    @RequestMapping("hot_list")
    public JsonResult<List<Product>> HotList(){
        List<Product> data = productService.findHotList();
        return new JsonResult<List<Product>>(OK, data);
        
    }
}
前端页面

在index.html下写一个ajax请求,去接受数据库传出来的热销商品进行页面展示

<script>
			$(document).ready(function () {
				showhotList();
			});
			//热销商品排行
			function showhotList(){
				//先清空表单自带的数据
				$("#hot-list").empty();
				$.ajax({
					url: "/products/hot_list",
					type: "GET",
					dataType: "JSON",
					success: function (json) {
						if (json.state == 200) {
							var list = json.data;
							for (var i = 0; i < list.length; i++) {
								console.log(list[i].title);
								let html = '<div class="col-md-12">'
										+ '<div class="col-md-7 text-row-2"><a href="product.html?id=#{id}">#{title}</a></div>'
										+ '<div class="col-md-2">¥#{price}</div>'
										+ '<div class="col-md-3"><img src="..#{image}collect.png" class="img-responsive" /></div>'
										+ '</div>';

								html = html.replace(/#{id}/g, list[i].id);
								html = html.replace(/#{title}/g, list[i].title);
								html = html.replace(/#{price}/g, list[i].price);
								html = html.replace(/#{image}/g, list[i].image);

								//把tr列表添加到address-list中
								$("#hot-list").append(html);
							}
						}
					}
				});
			}
		</script>

进行单元测试

二.商品详细内容展示

持久层
1.规划SQL语句

根据前端页面传回来的id,将id对应的商品从数据库中查询,在传给前端

select * from t_product where id=?
2.设计抽象方法

在ProductMapper中设计抽象方法

/**
 * 根据id查询商品数据
 * @param id:商品id
 * @return:商品数据
 */
Product findproductById(Integer id);
3.配置映射

ProductMapper.xml

<select id="findproductById" resultMap="ProductEntityMap">
    select * from t_product where id=#{id}
</select>
业务层
1.规划异常

点击商品可能存在商品不存在异常ProductNotFoundException,编写在Service包下的ex中,继承ServiceException,重写五个方法。

2.设计抽象方法

IProductService:

Product findproductById(Integer id);
3.实现抽象方法

ProductServiceImpl:

//商品详细信息
@Override
public Product findproductById(Integer id) {
    Product product = productMapper.findproductById(id);
    if (product==null){
        throw new ProductNotFoundException("商品不存在");
    }
    product.setPriority(null);
    product.setCreatedUser(null);
    product.setCreatedTime(null);
    product.setModifiedUser(null);
    product.setModifiedTime(null);
    return product;
}
控制层
1.处理异常

在BaseController中添加异常:

else if (e instanceof ProductNotFoundException){
    result.setState(7000);
    result.setMessage("商品不存在异常");
}
2.设计请求
/products/{id}/details
GET
@PathVariable("id") Integer id
JsonResult<Product>
3.处理请求
@GetMapping("{id}/details")
public JsonResult<Product> findProductById(@PathVariable("id") Integer id){
    Product data = productService.findproductById(id);
    return new JsonResult<>(OK,data);
}
前端页面

1.在product.html页面中body标签内部的最后添加获取当前商品详情的代码。

<script type="text/javascript">
let id = $.getUrlParam("id");
console.log("id=" + id);
$(document).ready(function() {
    $.ajax({
        url: "/products/" + id + "/details",
        type: "GET",
        dataType: "JSON",
        success: function(json) {
            if (json.state == 200) {
                console.log("title=" + json.data.title);
                //html("hello")
				//<div></div> 将html中的数据传输到div中 <div>hello</div>
                $("#product-title").html(json.data.title);
                $("#product-sell-point").html(json.data.sellPoint);
                $("#product-price").html(json.data.price);

                for (let i = 1; i <= 5; i++) {
                    $("#product-image-" + i + "-big").attr("src", ".." + json.data.image + i + "_big.png");
                    $("#product-image-" + i).attr("src", ".." + json.data.image + i + ".jpg");
                }
            } else if (json.state == 4006) { // 商品数据不存在的异常
                location.href = "index.html";
            } else {
                alert("获取商品信息失败!" + json.message);
            }
        }
    });
});
</script>

购物车

一.加入购物车

1.创建数据库

创建一个t_cart的表

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;
2.创建实体类

在entity包中创建一个Cart

@Data
@AllArgsConstructor
@NoArgsConstructor
public class Cart extends BaseEntity implements Serializable {
    private Integer cid;
    private Integer uid;
    private Integer pid;
    private Long price;
    private Integer num;
}
持久层
1.规划SQL语句

1.当用户在商品页面中点击加入购物车,向购物车表中插入数据

insert into t_cart (aid除外) values (值)

2.当商品已经在购物车中,用户再次点击,则直接更新购物车中商品的数量

cid不是购物车的id,可以理解为一条购物信息,对应一个商品的购物信息

update t_cart  set num=? where cid=?

3.插入或者更新具体执行哪个语句,取决于数据库中是否有这个购物车商品的数据,需要进行查询

select * from t_cart where pid=? and uid=?
2.设计接口和抽象方法

创建一个CartMapper接口,并设计抽象方法

public interface CartMapper {
    /**
     * 插入购物车数据
     * @param cart:购物车数据
     * @return:受影响的行数
     */
    Integer insert(Cart cart);

    /**
     * 更新购物某件商品数量
     * @param cid:购物车数据id
     * @param num:商品数量
     * @param modifiedUser:修改人
     * @param modifiedTime:修改时间
     * @return:受影响行数
     */
    Integer updateNumByCid(@Param("cid") Integer cid,
                           @Param("num")Integer num,
                           @Param("modifiedUser")String modifiedUser,
                           @Param("modifiedTime")Date modifiedTime);
    /**
     * 根据用户的id和购物车数据id来查询购物车中的数据
     * @param pid:商品id
     * @param uid:
     * @return:商品对应的购物车数据
     */
    Cart findByPid(@Param("pid") Integer pid,@Param("uid") Integer uid);
}
3.配置映射

创建CartMapper.xml并在其中配置方法对应的映射

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<!--namespace属性:用于指定当前的映射文件和那个接口进行映射,需要指定接口的文件路径-->
<mapper namespace="com.cy.store.mapper.CartMapper">
    <!--    自定义映射规则-->
    <resultMap id="CartEntityMap" type="com.cy.store.entity.Cart">
        <id column="cid" property="cid"/>
        <result column="created_user" property="createdUser"/>
        <result column="created_time" property="createdTime"/>
        <result column="modified_user" property="modifiedUser"/>
        <result column="modified_time" property="modifiedTime"/>
    </resultMap>

    <insert id="insertCart" 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="findByPid" resultMap="CartEntityMap">
        select * from t_cart where pid=#{pid} and uid=#{uid}
    </select>
</mapper>
业务层
1.规划异常

1.插入时产生的异常 InsertException

2.更新时产生的异常 UpdateException

2.设计接口和抽象方法

ICartService

public interface ICartService {

    /**
     * 将商品添加到购物车中
     * @param uid:用户id
     * @param pid:商品id
     * @param amount:新增数量
     * @param username:修改人
     */
    void addToCart(Integer uid,Integer pid,Integer amount,String username);
}
3.实现抽象方法

创建一个CartServiceImpl去实现接口中的抽象方法

@Service
public class CartServiceImpl implements ICartService {

    //购物车的业务层依赖于购物车的持久层和商品的持久层
    @Autowired
    private CartMapper cartMapper;
    //商品
    @Autowired
    private ProductMapper productMapper;

    @Override
    public void addToCart(Integer uid, Integer pid, Integer amount, String username) {

        Cart result = cartMapper.findByPid(pid, uid);
        //判断购物信息是否已经存在,不存在则插入,存在则修改
        if (result==null){
            //先创建一个cart对象在进行插入
            Cart cart = new Cart();
            cart.setPid(pid);
            cart.setUid(uid);
            cart.setNum(amount);
            //为了获取商品价格,调用productMapper的方法
            Product product = productMapper.findproductById(pid);
            cart.setPrice(product.getPrice());

            cart.setCreatedUser(username);
            cart.setCreatedTime(new Date());
            cart.setModifiedUser(username);
            cart.setModifiedTime(new Date());

            Integer rows = cartMapper.insertCart(cart);
            if (rows!=1){
                throw new InsertException("插入产生未知异常");
            }
        }else {
            //把原有的数量+新增的数量统计,修改到数据库中
            int num = result.getNum() + amount;
            Integer rows = cartMapper.updateNumByCid(result.getCid(), num, username, new Date());
            if (rows!=1){
                throw new UpdateException("更新产生未知异常");
            }
        }
    }
}
4.单元测试

创建一个CartServiceTests

@SpringBootTest
public class CartServiceTests {

    @Autowired
    private ICartService cartService;

    @Test
    public void addToCart(){
        cartService.addToCart(11,1,2,"m");
    }
}
控制层
2.设计请求
/carts/add_to_cart
GET
HTTPSession session,Integer pid,Integer amount
JsonResult<Void>
3.处理请求

创建一个CartController,继承BaseController

@RestController
@RequestMapping("carts")
public class CartController extends BaseController{

    @Autowired
    private ICartService cartService;

    @RequestMapping("add_to_cart")
    public JsonResult<Void> addToCart(HttpSession session,Integer pid,Integer amount){
        Integer uid = getuidFromSession(session);
        String username = getUsernameFromSession(session);
        cartService.addToCart(uid,pid,amount,username);
        return new JsonResult<>(OK);
    }
}
前端页面

在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)
				}
			});
		});

$.ajax函数中参数data提交请参数的方式:

// 1.适用于参数较多,且都在同一个表单中
data: $("#form表单id属性值").serialize()
// 2.仅适用于上传文件
data: new FormData($("##form表单id属性值")[0])
// 3.参数拼接形式提交
data: "pid=10000005&amount=3"
// 4.使用JSON格式提交参数
data: {
	"pid": 10000005,
	"amount": 3
}
单元测试

二.购物车列表

持久层
1.规划SQL语句

1.需要关联查询t_cart和t_product

#多表查询如果字段不重复则不需要显示声明字段属于哪张表
select 
   cid,uid,pid,
   t_cart.price,t_cart.num,
   t_product.image,t_product.title,
   t_product.price AS realprice  #为了在购物车列表页展示两个价格的差值
from 
   #把t_cart作为主表
   t_cart left join t_product on t_cart.pid=t_product.id
where 
   uid=#{uid} 
order by 
   t_cart.created_time DESC
2.设计接口和抽象方法

VO:Value Object,值对象,当进行Select查询时,查询的结果属于多张表中的内容,此时发现结果集不能直接使用某个POJO实体类来接受,POJO实体类不能包含多表查询出来的信息,解决方式是:重新去构建一个新的对象,这个对象用于存储所查询出来的结果集对应的映射,所以把这个对象称之为值对象。

1.创建一个新的包vo,在vo下创建一个类CartVO

//购物车数据的VO类,不去映射某张表,而是一种结果集的映射
@Data
@AllArgsConstructor
@NoArgsConstructor
public class CartVO implements Serializable {
    private Integer cid;
    private Integer uid;
    private Integer pid;
    private Long price;
    private Integer num;
    private String title;
    private Long realPrice;
    private String image;
}

2.在CartMapper接口中定义方法

/**
 * 查询购物车列表中的数据,返回前端页面
 * @param uid:用户id
 * @return:购物车内的数据
 */
List<CartVO> findVOByUid(Integer uid);
3.配置映射

在CartMapper.xml中配置映射

<!--    购物车列表-->
    <select id="findproductById" resultMap="com.cy.store.vo.CartVO">
        select 
               cid,uid,pid,
               t_cart.price,t_cart.num,
               t_product.image,t_product.title,
               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>
4.单元测试

在CartMapperTests中进行测试

@Test
public void findVOByUid(){
    System.out.println(cartMapper.findVOByUid(11));
}
业务层
1.设计抽象方法

在ICartService中创建抽象方法:

List<CartVO> getVOByUid(Integer uid);
3.实现抽象方法

CartServiceImpl:

@Override
public List<CartVO> getVOByUid(Integer uid) {
    return cartMapper.findVOByUid(uid);
}
控制层
2.设计请求
/carts/
GET
HttpSession session
JsonResult<List<CartVO>>
3.实现请求
//购物车列表
@RequestMapping({"","/"})
public JsonResult<List<CartVO>> getVOByUid(HttpSession session){
    List<CartVO> data = cartService.getVOByUid(getuidFromSession(session));
    return new JsonResult<>(OK,data);
}
4.前端页面
2.给form标签添加action="orderConfirm.html"属性、tbody标签添加id="cart-list"属性、结算按钮的类型改为type="button"值。如果以上属性值已经添加过无需重复添加。

3.在cart.html页面body标签内的script标签中编写展示购物车列表的代码。

```javascript
<script type="text/javascript">
   /*
   $(function() {
      //返回链接
      $(".link-account").click(function() {
         location.href = "orderConfirm.html";
      })
   })
   */
   $(document).ready(function () {
      showCartList();
   });

   function showCartList(){
      //清空原有的样板数据
      $("#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>'
                     + '<td>'
                     +  '<input name="cids" value="#{cid}" type="checkbox" class="ckitem" />'
                     + '</td>'
                     + '<td><img src="..#{image}collect.png" class="img-responsive" /></td>'
                     + '<td>#{title}#{msg}</td>'
                     + '<td>¥<span id="price-#{cid}">#{realPrice}</span></td>'
                     + '<td>'
                     +  '<input type="button" value="-" class="num-btn" οnclick="reduceNum(1)" />'
                     +  '<input id="num-#{cid}" type="text" size="2" readonly="readonly" class="num-text" value="#{num}">'
                     +  '<input class="num-btn" type="button" value="+" οnclick="addNum(#{cid})" />'
                     + '</td>'
                     + '<td>¥<span id="total-price-#{cid}">#{totalPrice}</span></td>'
                     + '<td>'
                     +  '<input type="button" οnclick="delCartItem(this)" class="cart-del btn btn-default btn-xs" value="删除" />'
                     + '</td>'
                     + '</tr>';
               tr = tr.replace(/#{cid}/g, list[i].cid);
               tr = tr.replace(/#{title}/g, list[i].title);
               tr = tr.replace(/#{image}/g, list[i].image);
               tr = tr.replace(/#{realPrice}/g, list[i].realPrice);
               tr = tr.replace(/#{num}/g, list[i].num);
               tr = tr.replace(/#{totalPrice}/g, list[i].realPrice * list[i].num);

               if (list[i].realPrice < list[i].price) {
                  tr = tr.replace(/#{msg}/g, "比加入时降价" + (list[i].price - list[i].realPrice) + "元");
               } else {
                  tr = tr.replace(/#{msg}/g, "");
               }
               $("#cart-list").append(tr);
            }
         },
         error:function (xhr) {
            alert("购物车加载产生未知异常"+xhr.message)
         }
      });
   }
</script>
单元测试

登录,进行购物车列表展示页面测试

三.增加购物车商品

持久层
1.规划SQL语句

1.首先进行查询需要操作的购物车数据信息

select * from t_cart where cid=?

2.然后计算出新的商品数量值,如果满足更新条件则执行更新操作。此SQL语句无需重复开发。

update t_cart set num=?,modified_user=?,modified_time=? where cid=?
2.设计抽象方法

CartMapper:

Cart findByCid(Integer cid);
3.配置映射

CartMapper.xml

<select id="findByCid" resultType="com.cy.store.entity.Cart">
    select * from t_cart where cid=#{cid}
</select>
4.单元测试

CartMapperTests:

@Test
public void findByCid(){
    Cart cart = cartMapper.findByCid(3);
    System.out.println(cart);
}
业务层
1.规划异常

1.在更新时发生更新异常

2.查询的数据是否有访问的权限

3.查询的数据不存在,CartNotFoundException,在Service中的ex下创建

2.设计抽象方法
/**
 * 购物车增加商品数量
 * @param cid:购物车id
 *  @param uid:
 * @param username:修改人
 * @return:增加成功后新的数量
 */
Integer addNumByCid(Integer cid, Integer uid, String username);
3.实现抽象方法
@Override
public Integer addNumByCid(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("非法访问");
    }
    int num = result.getNum() + 1;
    Integer rows = cartMapper.updateNumByCid(cid, num, username, new Date());
    if (rows!=1){
        throw new UpdateException("更新数据失败");
    }
    return num+1;
}
4.单元测试

CartServiceTests:

@Test
public void addNum(){
    Integer num = cartService.addNumByCid(3, 11, "t");
    System.out.println(num);
}
控制层
1.处理异常

在BaseController中添加新的异常

else if (e instanceof CartNotFoundException){
    result.setState(7001);
    result.setMessage("购物车数据不存在异常");
}
2.设计请求
/carts/{cid}/num/add
POST
Integer cid,HttpSession session
JsonResult<Integer>
3.实现请求

在CartController中实现请求方法

//购物车中的增加商品功能
@RequestMapping("{cid}/num/add")
public JsonResult<Integer> addNum(@PathVariable("cid") Integer cid,
								  HttpSession session){
    Integer uid = getuidFromSession(session);
    String username = getUsernameFromSession(session);
    Integer data = cartService.addNumByCid(cid,uid,username);
    return new JsonResult<>(OK,data);
}
前端页面
function addNum(cid) {
				$.ajax({
					url: "/carts/" + cid + "/num/add",
					type: "POST",
					dataType: "JSON",
					success: function(json) {
						if (json.state == 200) {
							// showCartList();
							$("#num-" + cid).val(json.data);
							let price = $("#price-" + cid).html();
							let totalPrice = price * json.data;
							$("#total-price-" + cid).html(totalPrice);
						} else {
							alert("增加商品数量失败!" + json.message);
						}
					},
					error: function(xhr) {
						alert("您的登录信息已经过期,请重新登录!HTTP响应码:" + xhr.status);
						location.href = "login.html";
					}
				});
			}

四.显示购物车勾选数据

持久层
1.规划SQL语句

在“确认订单页”显示的商品信息,应来自前序页面(购物车列表)中勾选的数据,所以显示的信息其实是购物车中的数据。到底需要显示哪些取决于用户的勾选操作,当用户勾选了若干条购物车数据后,这些数据的id应传递到当前“确认订单页”中,该页面根据这些id获取需要显示的数据列表。

所以在持久层需要完成“根据若干个不确定的id值,查询购物车数据表,显示购物车中的数据信息”。则需要执行的SQL语句大致是。

select 
   cid,uid,pid,
   t_cart.price,t_cart.num,
   t_product.image,t_product.title,
   t_product.price AS realprice  #为了在购物车列表页展示两个价格的差值
from 
   #把t_cart作为主表
   t_cart left join t_product on t_cart.pid=t_product.id
where 
#表示用户前端勾选的值
   cid in (?,?,?)
order by 
   t_cart.created_time DESC
2.设计抽象方法

CartMapper

/**
 * 根据用户勾选购物车中的商品对被勾选的商品进行查询
 * @param cids:商品的id集合
 * @return:被勾选的商品列表
 */
List<CartVO> findVOByCid(Integer[] cids);
3.配置映射
<!--    勾选购物车购物信息-->
<select id="findVOByCid" resultType="com.cy.store.vo.CartVO">
    select
        cid,uid,pid,
        t_cart.price,t_cart.num,
        t_product.image,t_product.title,
        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>

foreach循环:

  • collection标识循环的是list集合还是数组,如果是list集合就用collection=“list”
  • item用来接收每次循环获取的值
  • separator标识循环出来的值中间用什么隔开,且最后循环出来的值后面不加
4.单元测试

CartMapperTests

@Test
public void findVOBycid(){
    Integer[] cids={1,2,3,4};
    System.out.println(cartMapper.findVOByCid(cids));
}
业务层
1.设计抽象方法

CartService

List<CartVO> getVOByCid(Integer uid,Integer[] cid);
2.实现抽象方法

CartServiceImpl:

@Override
public List<CartVO> getVOByCid(Integer uid, Integer[] cid) {
    List<CartVO> list = cartMapper.findVOByCid(cid);
    for (CartVO cart : list) {
        //将购物车数据中不属于这个uid的数据移除
        if (!cart.getUid().equals(uid)){
            list.remove(cart);
        }
    }
    return list;
}
3.单元测试
@Test
public void getByCid(){
    Integer[] cid={1,2,3,4,5};
    List<CartVO> list = cartService.getVOByCid(11, cid);
    System.out.println(list);
}
控制层
1.设计请求
/carts/list
GET
Integer cids,HttpSession session
JsonResult<List<CartVO>>
2.实现请求

CartController:

@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.前端页面

在orderConfirm.html页面编写js代码

<script type="text/javascript">
   $(document).ready(function() {
      showCartList();
   });

   function showCartList() {
      $("#cart-list").empty();
      $.ajax({
         url: "/carts/list",
         type: "GET",
         data: location.search.substr(1),
         dataType: "JSON",
         success: function(json) {
            if (json.state == 200) {
               var list = json.data;
               console.log(location.search.substr(1));//调试用

               //声明两个变量用于更新"确认订单"页的总件数和总价
               var allCount = 0;
               var allPrice = 0;

               for (var i = 0; i < list.length; i++) {
                  var 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}",list[i].image);
                  tr = tr.replace("#{title}",list[i].title);
                  tr = tr.replace("#{price}",list[i].realPrice);
                  tr = tr.replace("#{num}",list[i].num);
                  tr = tr.replace("#{totalPrice}",list[i].realPrice*list[i].num);
                  $("#cart-list").append(tr);

                  //更新"确认订单"页的总件数和总价
                  allCount += list[i].num;
                  allPrice += list[i].realPrice*list[i].num;
               }
               $("#all-count").html(allCount);
               $("#all-price").html(allPrice);
            }
         },
         error: function (xhr) {
            alert("在确认订单页加载勾选的购物车数据时发生未知的异常"+xhr.status);
         }
      });
   }
</script>
5.显示选择收货地址

1.收货地址存放在前端的一个select下拉列表中,我们需要将查询到的当前登录用户的收货地址动态的加载到这个下拉列表中.从数据库的角度看,是一个select查询语句,在"收货地址列表展示"模块,后端功能已经编写过了,直接修改前端页面即可.

1.前端页面

1.在orderConfirm.html页面中的ready函数中添加showAddressList方法的调用,使确认订单页加载时能够自动从后端获取该用户地址填充到select控件中并将第一个地址显示出来

$(document).ready(function() {
    showCartList();
    showAddressList();
});

2.在orderConfirm.html页面中编写showAddressList方法

function showAddressList() {
    $("#address-list").empty();
    $.ajax({
        url: "/addresses",
        type: "GET",
        dataType: "JSON",
        success: function(json) {
            let list = json.data;
            console.log("count=" + list.length);
            for (let i = 0; i < list.length; i++) {
                console.log(list[i].name);
                let opt = '<option value="#{aid}">#{name} | #{tag} | #{province}#{city}#{area}#{address} | #{phone}</option>';

                opt = opt.replace(/#{aid}/g, list[i].aid);
                opt = opt.replace(/#{tag}/g, list[i].tag);
                opt = opt.replace("#{name}", list[i].name);
                opt = opt.replace("#{province}", list[i].provinceName);
                opt = opt.replace("#{city}", list[i].cityName);
                opt = opt.replace("#{area}", list[i].areaName);
                opt = opt.replace("#{address}", list[i].address);
                opt = opt.replace("#{phone}", list[i].phone);

                $("#address-list").append(opt);
            }
        }
    });
}

五.订单

1.创建数据库

在store数据库中创建t_order和t_order_item数据表。

CREATE TABLE t_order (
	oid INT AUTO_INCREMENT COMMENT '订单id',
	uid INT NOT NULL COMMENT '用户id',
	recv_name VARCHAR(20) NOT NULL COMMENT '收货人姓名',
	recv_phone VARCHAR(20) COMMENT '收货人电话',
	recv_province VARCHAR(15) COMMENT '收货人所在省',
	recv_city VARCHAR(15) COMMENT '收货人所在市',
	recv_area VARCHAR(15) COMMENT '收货人所在区',
	recv_address VARCHAR(50) COMMENT '收货详细地址',
	total_price BIGINT COMMENT '总价',
	status INT COMMENT '状态:0-未支付,1-已支付,2-已取消,3-已关闭,4-已完成',
	order_time DATETIME COMMENT '下单时间',
	pay_time DATETIME COMMENT '支付时间',
	created_user VARCHAR(20) COMMENT '创建人',
	created_time DATETIME COMMENT '创建时间',
	modified_user VARCHAR(20) COMMENT '修改人',
	modified_time DATETIME COMMENT '修改时间',
	PRIMARY KEY (oid)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

CREATE TABLE t_order_item (
	id INT AUTO_INCREMENT COMMENT '订单中的商品记录的id',
	oid INT NOT NULL COMMENT '所归属的订单的id',
	pid INT NOT NULL COMMENT '商品的id',
	title VARCHAR(100) NOT NULL COMMENT '商品标题',
	image VARCHAR(500) COMMENT '商品图片',
	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 (id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
2.创建实体类

1.在com.cy.store.entity包下创建Order实体类。

//订单数据实体类
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Order extends BaseEntity implements Serializable {
    private Integer oid;
    private Integer uid;
    private String recvName;
    private String recvPhone;
    private String recvProvince;
    private String recvCity;
    private String recvArea;
    private String recvAddress;
    private Long totalPrice;
    private Integer status;
    private Date orderTime;
    private Date payTime;
}

2.在com.cy.store.entity包下创建OrderItem实体类。

//订单中的商品数据
@NoArgsConstructor
@AllArgsConstructor
@Data
public class OrderItem extends BaseEntity implements Serializable {
    private Integer id;
    private Integer oid;
    private Integer pid;
    private String title;
    private String image;
    private Long price;
    private Integer num;

}
持久层
1.规划SQL语句

1.将数据插入订单表中

insert into t_order (oid除外) values (值)

2.将商品数据插入到订单项的表中

insert into t_order_item (id除外) values (值)
2.设计接口和抽象方法

创建一个OrderMapper接口,添加SQL语句对应抽象方法

//订单的持久层接口
public interface OrderMapper {

    /**
     * 将数据插入订单
     * @param order:订单数据
     * @return:受影响行数
     */
    Integer insertOrder(Order order);

    /**
     * 插入订单项的数据
     * @param orderItem:订单项数据
     * @return:受影响行数
     */
    Integer insertOrderItem(OrderItem orderItem);
}
3.配置映射

创建一个OrderMapper.xml映射文件,编写映射语句

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<!--namespace属性:用于指定当前的映射文件和那个接口进行映射,需要指定接口的文件路径-->
<mapper namespace="com.cy.store.mapper.OrderMapper">


    <!-- 插入订单数据:Integer insertOrder(Order order) -->
    <insert id="insertOrder" useGeneratedKeys="true" keyProperty="oid">
        insert into t_order (
            uid, recv_name, recv_phone, recv_province, recv_city, recv_area, recv_address,
            total_price,status, order_time, pay_time, created_user, created_time, modified_user,
            modified_time
        ) values (
                     #{uid}, #{recvName}, #{recvPhone}, #{recvProvince}, #{recvCity}, #{recvArea},
                     #{recvAddress}, #{totalPrice}, #{status}, #{orderTime}, #{payTime}, #{createdUser},
                     #{createdTime}, #{modifiedUser}, #{modifiedTime}
                 )
    </insert>

    <!-- 插入订单商品数据:Integer insertOrderItem(OrderItem orderItem) -->
    <insert id="insertOrderItem" useGeneratedKeys="true" keyProperty="id">
        INSERT INTO t_order_item (
            oid, pid, title, image, price, num, created_user,
            created_time, modified_user, modified_time
        ) VALUES (
                     #{oid}, #{pid}, #{title}, #{image}, #{price}, #{num}, #{createdUser},
                     #{createdTime}, #{modifiedUser}, #{modifiedTime}
                 )
    </insert>
    
</mapper>
3.单元测试

创建一个OrderMapperTests,进行测试

@SpringBootTest
public class OrderMapperTests {

    @Autowired
    private OrderMapper orderMapper;

    @Test
    public void insertOrder(){
        Order order = new Order();
        order.setUid(11);
        order.setRecvName("t");
        order.setRecvPhone("1814385745");
        orderMapper.insertOrder(order);
    }

    @Test
    public void  insertOrderItem(){
        OrderItem orderItem = new OrderItem();
        orderItem.setOid(1);
        orderItem.setPid(2);
        orderItem.setTitle("华为笔记本");
        orderMapper.insertOrderItem(orderItem);
    }
}
业务层
1.设计接口和抽象方法

1.由于处理过程中还需要涉及收货地址数据的处理,所以需要先在IAddressService接口中添加getByAid()方法。

/**
 * 根据收货地址数据的id,查询收货地址详情
 * @param aid 收货地址id
 * @param uid 归属的用户id
 * @return 匹配的收货地址详情
 */
Address getByAid(Integer aid, Integer uid);

2.并在AddressServiceImpl中实现方法

//通过aid查询address
@Override
public Address getByAid(Integer aid,Integer uid) {
    Address address = addressMapper.findByAid(aid);
    if (address==null){
        throw new AddressNotFoundException("收货地址数据不存在");
    }
    if (!address.getUid().equals(uid)){
        throw new AccessDeniedException("非法访问");
    }
    //把不需要的数据设为null
    address.setProvinceCode(null);
    address.setCityCode(null);
    address.setAreaCode(null);
    address.setCreatedUser(null);
    address.setCreatedTime(null);
    address.setModifiedUser(null);
    address.setModifiedTime(null);
    return address;
}

3.在service包下创建一个IOderService接口,并且创建抽象方法createOrder()

//订单业务层接口
public interface IOrderService {
       /**
     * 创建订单
     * @param aid:地址id
     * @param uid:用户id
     * @param cids:购物车的id集合
     * @param username:修改人
     * @return:返回订单
     */
    Order createOrder(Integer aid, Integer uid, Integer[] cids, String username);

}
2.实现抽象方法

1.创建一个实现类OrderServiceImpl

@Service
public class OrderServiceImpl implements IOrderService {

    @Autowired
    private OrderMapper orderMapper;

    //从address的业务层中获取地址
    @Autowired
    private IAddressService addressService;

    //获取购物车商品的数据
    @Autowired
    private ICartService cartService;


    //创建订单
    @Override
    public Order createOrder(Integer aid, Integer uid, Integer[] cids, String username) {

        //获取商品总价格
        List<CartVO> list = cartService.getVOByCid(uid, cids);
        if (list==null){
            throw new CartNotFoundException("购物车数据不存在");
        }
        Long totalPrice=0L;

        for (CartVO cartVO : list) {
            if (!cartVO.getUid().equals(uid)){
                list.remove(cartVO);
            }
            //计算总价格
            totalPrice+=cartVO.getRealPrice();
        }

        //去获取收货地址
        Address address = addressService.getByAid(aid, uid);

        //创建订单
        Order order = new Order();
        order.setUid(uid);
        //填入地址
        order.setRecvName(address.getName());
        order.setRecvPhone(address.getPhone());
        order.setRecvProvince(address.getProvinceName());
        order.setRecvCity(address.getCityName());
        order.setRecvArea(address.getAreaName());
        order.setRecvAddress(address.getAddress());
        //总价,未支付状态
        order.setTotalPrice(totalPrice);
        order.setStatus(0);
        //创建订单时间
        order.setOrderTime(new Date());
        //日志
        order.setCreatedUser(username);
        order.setCreatedTime(new Date());
        order.setModifiedUser(username);
        order.setModifiedTime(new Date());

        Integer rows = orderMapper.insertOrder(order);
        if (rows!=1){
            throw new InsertException("插入时产生未知异常");
        }


        for (CartVO cartVO : list) {
            //创建订单详细的商品项
            OrderItem orderItem = new OrderItem();
            //把oid添加进去
            orderItem.setOid(order.getOid());
            //补全OrderItem信息
            orderItem.setPid(cartVO.getPid());
            orderItem.setTitle(cartVO.getTitle());
            orderItem.setImage(cartVO.getImage());
            orderItem.setPrice(cartVO.getPrice());
            orderItem.setNum(cartVO.getNum());
            //日志
            orderItem.setCreatedUser(username);
            orderItem.setCreatedTime(new Date());
            orderItem.setModifiedUser(username);
            orderItem.setModifiedTime(new Date());

            //插入数据库
            rows = orderMapper.insertOrderItem(orderItem);
            if(rows!=1){
                throw new InsertException("插入时产生未知异常");
            }
        }

        return order;
    }
}
4.单元测试

1.创建一个OrderServiceTests进行测试

@SpringBootTest
public class OrderServiceTests {

    @Autowired
    private IOrderService orderService;

    @Test
    public void createOrder(){
        Integer[] cids={1,2,3};
        Order order = orderService.createOrder(9, 11, cids, "t");
        System.out.println(order);
    }
}
控制层
1.设计请求
/orders/create
POST
Integer aid,Integer[] cids,HttpSession session
JsonResult<Order>
2.实现请求

创建OrderController,编写方法

@RestController
@RequestMapping("orders")
public class OrderController extends BaseController{

    @Autowired
    private IOrderService orderService;

    @RequestMapping("create")
    public JsonResult<Order> createOrder(Integer aid,HttpSession session,Integer[] cids){
        Integer uid = getuidFromSession(session);
        String username = getUsernameFromSession(session);
        Order data = orderService.createOrder(aid, uid, cids, username);
        return new JsonResult<>(OK,data);
    }
}
前端页面

在"确认订单页"添加发送请求的处理方法使点击"在线支付"按钮可以创建订单并跳转到"支付信息页"(支付页显示详细商品信息这个功能这里不做了)

$("#btn-create-order").click(function() {
    var aid = $("#address-list").val();//12
    var cids = location.search.substr(1);//cids=4&cids=6&cids=8
    $.ajax({
        url: "/orders/create",
        data: "aid=" + aid + "&" + cids,//aid=12&cids=4&cids=6&cids=8
        type: "GET",
        dataType: "JSON",
        success: function(json) {
            if (json.state == 200) {
                location.href = "payment.html";
            } else {
                alert("创建订单失败!" + json.message);
            }
        },
        error: function(xhr) {
            alert("创建订单数据时产生未知的异常" + xhr.status);
        }
    });
});

单元测试