73. 创建订单-创建实体类
订单实体类:
/**
* 订单数据的实体类
*/
public class Order extends BaseEntity {
private static final long serialVersionUID = -3216224344757796927L;
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;
// ...
}
订单商品实体类:
/**
* 订单商品数据的实体类
*/
public class OrderItem extends BaseEntity {
private static final long serialVersionUID = -8879247924788259070L;
private Integer id;
private Integer oid;
private Integer pid;
private String title;
private String image;
private Long price;
private Integer num;
// ...
}
74. 创建订单-持久层
接口与抽象方法:
/**
* 处理订单和订单商品数据的持久层接口
*/
public interface OrderMapper {
/**
* 插入订单数据
* @param order 订单数据
* @return 受影响的行数
*/
Integer insertOrder(Order order);
/**
* 插入订单商品数据
* @param orderItem 订单商品数据
* @return 受影响的行数
*/
Integer insertOrderItem(OrderItem orderItem);
}
映射:
<mapper namespace="cn.tedu.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>
测试:
@RunWith(SpringRunner.class)
@SpringBootTest
public class OrderMapperTests {
@Autowired
OrderMapper mapper;
@Test
public void insertOrder() {
Order order = new Order();
order.setUid(1);
order.setRecvName("小李同学");
order.setTotalPrice(10086L);
Integer rows = mapper.insertOrder(order);
System.err.println("rows=" + rows);
}
@Test
public void insertOrderItem() {
OrderItem orderItem = new OrderItem();
orderItem.setOid(1);
orderItem.setTitle("某手机");
orderItem.setPrice(3000L);
orderItem.setNum(3);
Integer rows = mapper.insertOrderItem(orderItem);
System.err.println("rows=" + rows);
}
}
75. 创建订单-业务层-基础功能
后续将需要根据收货地址的id,查询收货地址详情,需要在IAddressService
中添加:
Address getByAid(Integer aid);
在收货地址的业务层实现类AddressServiceImpl
中,重写以上方法,调用已经存在的findByAid()
私有方法即可实现。
创建cn.tedu.store.service.IOrderService
业务层接口,在接口中添加抽象方法:
Order create(Integer aid, Integer[] cids, Integer uid, String username);
创建cn.tedu.store.service.impl.OrderServiceImpl
业务层实现类,实现以上接口,添加@Service
注解,在类中添加@Autowired private OrderMapper orderMapper;
持久层对象,另外,还需要添加@Autowired private IAddressService addressService;
收货地址的业务对象和@Autowired private ICartService cartService;
购物车的业务对象。
在实现类中添加2个私有方法,对应持久层的2个方法:
/**
* 插入订单数据
* @param order 订单数据
* @throws InsertException 插入订单数据时出现未知错误
*/
private void insertOrder(Order order) {
Integer rows = orderMapper.insertOrder(order);
if (rows != 1) {
throw new InsertException(
"创建订单失败!插入订单数据时出现未知错误!");
}
}
/**
* 插入订单商品数据
* @param orderItem 订单商品数据
* @throws InsertException 插入订单商品数据时出现未知错误
*/
private void insertOrderItem(OrderItem orderItem) {
Integer rows = orderMapper.insertOrderItem(orderItem);
if (rows != 1) {
throw new InsertException(
"创建订单失败!插入订单商品数据时出现未知错误!");
}
}
然后,重写接口中的抽象方法:
@Transactional
public Order create(Integer aid, Integer[] cids, Integer uid, String username) {
// 创建当前时间对象
// 根据参数cids,通过cartService的getByCids()查询购物车数据,得到List<CartVO>类型的对象
// 遍历以上购物车数据集合对象以计算总价
// 创建Order对象
// 补Order对象属性:uid > 参数uid
// 根据参数aid,通过addressService的getByAid()方法查询收货地址详情
// 补Order对象属性:recv_*
// 补Order对象属性:total_price > 以上遍历时的计算结果
// 补Order对象属性:status > 0
// 补Order对象属性:order_time > 当前时间
// 补Order对象属性:pay_time > null
// 补Order对象属性:日志 > 参数username,当前时间
// 插入订单数据:insertOrder(order)
// 遍历购物车数据集合对象
// -- 创建OrderItem对象
// -- 补OrderItem对象属性:oid > order.getOid();
// -- 补OrderItem对象属性:pid, title, image, price, num > 遍历对象中的pid, title, iamge ,realPrice, num
// -- 补OrderItem对象属性:日志 > 参数username,当前时间
// -- 插入订单商品数据:insertOrderItem(orderItem)
// 未完,待续
}
实现:
@Override
@Transactional
public Order create(Integer aid, Integer[] cids, Integer uid, String username) {
// 创建当前时间对象
Date now = new Date();
// 根据参数cids,通过cartService的getByCids()查询购物车数据,得到List<CartVO>类型的对象
List<CartVO> carts = cartService.getByCids(cids, uid);
// 遍历以上购物车数据集合对象以计算总价
Long totalPrice = 0L;
for (CartVO cart : carts) {
totalPrice += cart.getRealPrice() * cart.getNum();
}
// 创建Order对象
Order order = new Order();
// 补Order对象属性:uid > 参数uid
order.setUid(uid);
// 根据参数aid,通过addressService的getByAid()方法查询收货地址详情
Address address = addressService.getByAid(aid);
// 补Order对象属性:recv_*
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对象属性:total_price > 以上遍历时的计算结果
order.setTotalPrice(totalPrice);
// 补Order对象属性:status > 0
order.setStatus(0);
// 补Order对象属性:order_time > 当前时间
// 补Order对象属性:pay_time > null
order.setOrderTime(now);
// 补Order对象属性:日志 > 参数username,当前时间
order.setCreatedUser(username);
order.setCreatedTime(now);
order.setModifiedUser(username);
order.setModifiedTime(now);
// 插入订单数据:insertOrder(order)
insertOrder(order);
// 遍历购物车数据集合对象
for (CartVO cart : carts) {
// 创建OrderItem对象
OrderItem item = new OrderItem();
// 补OrderItem对象属性:oid > order.getOid();
item.setOid(order.getOid());
// 补OrderItem对象属性:pid, title, image, price, num > 遍历对象中的pid, title, iamge ,realPrice, num
item.setPid(cart.getPid());
item.setTitle(cart.getTitle());
item.setImage(cart.getImage());
item.setPrice(cart.getRealPrice());
item.setNum(cart.getNum());
// 补OrderItem对象属性:日志 > 参数username,当前时间
item.setCreatedUser(username);
item.setCreatedTime(now);
item.setModifiedUser(username);
item.setModifiedTime(now);
// 插入订单商品数据:insertOrderItem(orderItem)
insertOrderItem(item);
}
// 未完,待续
// 返回成功创建的订单对象
return order;
}
测试:
@RunWith(SpringRunner.class)
@SpringBootTest
public class OrderServiceTests {
@Autowired
IOrderService service;
@Test
public void create() {
Integer aid = 25;
Integer[] cids = { 13,14,15,16,17,18,19,20 };
Integer uid = 7;
String username = "购物狂";
Order result = service.create(aid, cids, uid, username);
System.err.println(result);
}
}
关于数据检查
以“用户注册”为例,用户可能需要向服务器提交例如“用户名”等相关数据,但是,每种数据都应该有对应的数据格式要求,所以,在用户提交时,需要对数据的格式进行检查。
通常,在客户端就应该对数据的格式进行检查,例如在HTML中,可以使用Javascript程序对即将提交的数据进行检查,如果数据格式非法,则不会提交表单数据到服务器端。
但是,即使客户端检查了数据,在服务器端收到数据的第一时间,也就是在服务器端的控制器中,仍需要对数据进行同样标准的检查!因为客户端可能是不可靠的,存在被篡改的可能!
尽管服务器端会再次检查,但是,客户端的检查也是非常有必要的!因为客户端的检查可以拦截绝大部分数据格式有误的请求,从而减轻服务器端的压力。
另外,在有些应用中,在业务层收到数据的第一时间,也会检查数据!因为有些数据处理并不是由客户端提交并控制器已经检查过相关数据的!例如某些计划任务等。
所以:如果项目中始终是“客户端 --> 控制器层 --> 业务层”的处理流程,则只需要在客户端和服务器端的控制器层各执行一次相同标准的格式检查即可,如果项目中还包含某些计划任务等不是由客户端发起请求的数据处理流程,则相关的业务层也需要对数据进行检查!
76. 创建订单-业务层-清除购物车对应的数据
当成功的创建了订单后,还应该将购物车中匹配的数据进行删除!所以,在“创建订单”的业务中,还应该调用购物车的业务方法,执行删除,而购物车的业务层功能需要调用购物车的持久层功能来完成这项操作,所以,应该先在购物车数据的持久层CartMapper.java
接口中开发出批量删除功能:
Integer deleteByCids(Integer[] cids);
然后,在CartMapper.xml
中配置映射:
<delete id="deleteByCids">
DELETE FROM
t_cart
WHERE
cid IN
<foreach collection="array"
item="cid" separator=","
open="(" close=")">
#{cid}
</foreach>
</delete>
然后,在CartMapperTests
中对这个功能进行测试:
@Test
public void deleteByCids() {
Integer[] cids = {17,18,19,20,21};
Integer rows = mapper.deleteByCids(cids);
System.err.println("rows=" + rows);
}
接下来,应该在ICartService
业务层接口中添加抽象方法:
void delete(Integer[] cids, Integer uid);
在CartServiceImpl
业务层实现类中添加私有方法,以对应持久层中新添加的批量删除功能:
/**
* 根据若干个购物车数据id删除数据
* @param cids 若干个购物车数据id
* @throws DeleteException 删除数据异常
*/
private void deleteByCids(Integer[] cids) {
Integer rows = cartMapper.deleteByCids(cids);
if (rows < 1) {
throw new DeleteException(
"清除购物车数据失败!删除数据时发生未知错误!");
}
}
然后,重写接口中的抽象方法:
public void delete(Integer[] cids) {
// 判断即将删除的数据是否存在,及数据归属是否正确
// 可以调用自身的:List<CartVO> getByCids(Integer[] cids, Integer uid),得到cid有效,且归属正确的购物车数据
// 基于以上得到的List<CartVO>得到允许执行删除的cid的数组
// 执行删除
deleteByCids(cids);
}
实现为:
@Override
public void delete(Integer[] cids, Integer uid) {
// 判断即将删除的数据是否存在,及数据归属是否正确
// 可以调用自身的:List<CartVO> getByCids(Integer[] cids, Integer uid),得到cid有效,且归属正确的购物车数据
List<CartVO> carts = getByCids(cids, uid);
// 判断以上查询结果的长度是否有效
if (carts.size() == 0) {
throw new CartNotFoundException(
"删除购物车数据失败!尝试访问的数据不存在!");
}
// 基于以上得到的List<CartVO>得到允许执行删除的cid的数组
Integer[] ids = new Integer[carts.size()];
for (int i = 0; i < carts.size(); i++) {
ids[i] = carts.get(i).getCid();
}
// 执行删除
deleteByCids(ids);
}
完成后,执行单元测试:
@Test
public void deleteByCids() {
try {
Integer[] cids = {13,15,23,25,27,29,31};
Integer uid = 1;
service.delete(cids, uid);
System.err.println("OK.");
} catch (ServiceException e) {
System.err.println(e.getClass().getName());
System.err.println(e.getMessage());
}
}
以上功能完成后,就可以在创建订单的OrderServiceImpl
的create()
方法中补充删除的步骤:
// 删除购物车中对应的数据:cartService.deleteBy...
cartService.delete(cids, uid);
以上代码可以补充在插入订单数据、插入订单商品数据之前或之后,务必保证在第1次获取购物车数据之后再执行!
77. 创建订单-业务层-销库存
在创建订单时,应该在商品表t_product
中减少对应商品的库存量,首先,就需要开发“减少库存”的功能:
update t_product set num=? where pid=?
在执行减少库存之前,还应该查询商品数据是否存在、库存量是否充足,这些都可以先通过查询功能来实现,该查询功能已经存在,无需再次开发,所以,在处理商品数据的持久层接口ProductMapper.java
中需要添加1个抽象方法:
Integer updateNum(
@Param("pid") Integer pid,
@Param("num") Integer num);
然后,在ProductMapper.xml
中配置以上方法的映射:
<update id="updateNum">
UPDATE
t_product
SET
num=#{num}
WHERE
id=#{pid}
</update>
接下来处理商品数据的业务层,先创建必要的异常类:
ProductNotFoundException
ProductOutOfStockException
在IProductService
接口中添加抽象方法:
// amount:减少的数量
void reduceNum(Integer pid, Integer amount);
然后,在处理商品数量的业务层实现中,先通过私有方法调用持久层的更新商品数量的功能:
/**
* 更新商品的库存
* @param pid 商品的id
* @param num 新的库存量
* @throws UpdateException 更新商品数量失败
*/
private void updateNum(Integer pid, Integer num) {
Integer rows = productMapper.updateNum(pid, num);
if (rows != 1) {
throw new UpdateException(
"更新商品数量失败!更新数据时出现未知错误!");
}
}
再实现接口中的抽象方法:
public void reduceNum(Integer pid, Integer amount) {
// 通过参数pid查询商品数据
// 判断查询结果是否为null:ProductNotFoundException
// 判断查询结果中的num(当前库存)是否小于参数amount(将要购买或减少的库存量):ProductOutOfStockException
// 执行减少库存
}
代码实现:
@Override
public void reduceNum(Integer pid, Integer amount) {
// 通过参数pid查询商品数据
Product result = findById(pid);
// 判断查询结果是否为null:ProductNotFoundException
if (result == null) {
throw new ProductNotFoundException(
"更新商品库存失败!尝试访问的商品数量不存在!");
}
// 暂不考虑商品下架的问题
// 判断查询结果中的num(当前库存)是否小于参数amount(将要购买或减少的库存量):ProductOutOfStockException
if (result.getNum() < amount) {
throw new ProductOutOfStockException(
"更新商品库存失败!当前商品库存已经不足!");
}
// 执行减少库存
updateNum(pid, result.getNum() - amount);
}
完成后,执行测试:
@Test
public void reduceNum() {
try {
Integer pid = 10000022;
Integer amount = 80;
service.reduceNum(pid, amount);
System.err.println("OK");
} catch (ServiceException e) {
System.err.println(e.getClass().getName());
System.err.println(e.getMessage());
}
}
最后,在OrderServiceImpl
中,添加@Autowired private IProductService productService;
,并在create()
方法中,在循环插入订单商品数据的同时,减少对应的商品的库存:
// 销库存
productService.reduceNum(cart.getPid(), cart.getNum());
--------------------------------
public class xxThread extends Thread {
public void run() {
Thread.sleep(15 * 60 * 1000);
// 检查订单状态是否为0
// -- 修改订单状态
// -- 回库存
}
}