电商项目-订单模块

72. 显示确认订单信息页

显示“确认订单信息”页面中,需要显示2种数据:当前用户的所有收货地址列表;用户在前序页面中选择的购物车中的商品。

首先,完成“显示当前用户的所有收货地址的列表”,在此前开发“收货地址”相关功能时,已经可以通过/addresses/这个URL获取收货地址列表!则直接在orderConfirm.html中通过$.ajax()获取数据并显示即可!

接下来,应该“显示用户在前序页面中选择的购物车中的商品的列表”,对应的查询功能的SQL语句大致是:

select * from t_cart left join t_product on t_cart.pid=t_product.id where cid in (?,?,?)

所以,需要在CartMapper.java接口中添加:

List findByCids(Integer[] cids);

CartMapper.xml中配置以上抽象方法的映射:

	<select id="findByCids" resultType="xx.xx.xx.CartVO">
		SELECT 
			cid, uid, 
			pid, t_cart.num, 
			t_cart.price, t_product.price AS realPrice,
			title, image
		FROM 
			t_cart 
		LEFT JOIN
			t_product
		ON
			t_cart.pid=t_product.id
		WHERE 
			cid IN
			<foreach collection="array"
				item="cid" seperator=","
				open="(" close=")">
				#{cid}
			foreach>
		ORDER BY
			t_cart.created_time DESC
	select>

完成持久层后,接下来,需要在ICartService业务层接口中添加抽象方法,以对外提供数据访问功能:

List getByCids(Integer[] cids, Integer uid);

然后,在实现类中,先私有化编写持久层的方法:

private List findByCids(Integer[] cids) {
	return cartMapper.findByCids(cids);
}

并重写接口中的抽象方法:

	public List<CartVO> getByCids(Integer[] cids, Integer uid) {
		if (cids == null) {
			return new ArrayList<>();
		}

		List<CartVO> result = findByCids(cids);

		Iterator<CartVO> it = result.iterator();
		while(it.hasNext()) {
			CartVO cartVO = it.next();
			if (cartVO.getUid() != uid) {
				it.remove();
			}
		}

		return result;
	}

测试完成后,需要在控制器层提供接口:

	@GetMapping("get_by_cids")
	public JsonResult<List<CartVO>> getByCids(
			Integer[] cids, HttpSession session) {
		// 从session中获取uid
		Integer uid = getUidFromSession(session);
		// 调用业务层对象的方法执行任务
		List<CartVO> data = cartService.getByCids(cids, uid);
		// 响应成功
		return new JsonResult<>(SUCCESS, data);
	}

然后,打开浏览器,可以通过http://localhost:8080/carts/get_by_cids?cids=14&cids=15&cids=16进行单元测试。

测试完成后,需要在orderConfirm.html中发出对这个URL的请求,以获取数据,并显示。

73. 创建订单-创建数据表

创建“订单表”:

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

创建“订单商品表”:

CREATE TABLE t_order_item (
	id INT AUTO_INCREMENT COMMENT 'id',
	oid INT COMMENT '归属的订单id',
	pid INT COMMENT '商品id',
	title VARCHAR(100) COMMENT '商品标题',
	image VARCHAR(500) COMMENT '商品图片',
	price BIGINT COMMENT '商品单价',
	num INT COMMENT '购买数量',
	created_user VARCHAR(50) COMMENT '创建人',
	created_time DATETIME COMMENT '创建时间',
	modified_user VARCHAR(50) COMMENT '最后修改人',
	modified_time DATETIME COMMENT '最后修改时间',
	PRIMARY KEY (id)
) DEFAULT CHARSET=UTF8;

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">

		
		
		<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>
	
		
		
		<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类型的对象
	// 遍历以上购物车数据集合对象以计算总价

	// 创建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类型的对象
		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 FROM 	
		t_cart
	WHERE 
		cid IN
		
			#{cid}
		

然后,在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 getByCids(Integer[] cids, Integer uid),得到cid有效,且归属正确的购物车数据

	// 基于以上得到的List得到允许执行删除的cid的数组

	// 执行删除
	deleteByCids(cids);
}

实现为:

	@Override
	public void delete(Integer[] cids, Integer uid) {
		// 判断即将删除的数据是否存在,及数据归属是否正确
		// 可以调用自身的:List getByCids(Integer[] cids, Integer uid),得到cid有效,且归属正确的购物车数据
		List<CartVO> carts = getByCids(cids, uid);

		// 判断以上查询结果的长度是否有效
		if (carts.size() == 0) {
			throw new CartNotFoundException(
				"删除购物车数据失败!尝试访问的数据不存在!");
		}

		// 基于以上得到的List得到允许执行删除的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());
		}
	}

以上功能完成后,就可以在创建订单的OrderServiceImplcreate()方法中补充删除的步骤:

// 删除购物车中对应的数据: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
		t_product
	SET
		num=#{num}
	WHERE
		id=#{pid}

接下来处理商品数据的业务层,先创建必要的异常类:

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
		// -- 修改订单状态
		// -- 回库存
	}

}
### 处理超时未支付

当订单生成后,用户必须在指定时间内完成支付,如果在指定时间内未支付(到达约定时间后订单状态status依然为0,0-未支付,1-已支付,2-已取消,3-关闭),则应该关闭订单,关闭时,需要将订单状态进行修改,还需要归还该订单对应的库存。

更改订单状态需要执行的SQL语句大致是:

update t_order set status=3 where oid=?

归还库存时,可以先根据购买的商品的pid查询商品数据(该功能已完成)获取商品的当前数据,再结合订单中预计购买的数据num得到最终归还后的数量,并更新商品表中该商品的数量(该功能已完成)。

所以,在持久层的处理,只需要在OrderMapper.java中添加:

Integer updateStatus(Integer oid, Integer status, String username, Date modifiedTime);

然后,在OrderMapper.xml中配置映射:

	
	
	<update id="updateStatus">
		UPDATE
			t_order
		SET
			status=#{status},
			modified_user=#{username},
			modified_time=#{modifiedTime}
		WHERE
			oid=#{oid}
	update>

再在OrderMapperTests中编写并执行单元测试:

	@Test
	public void updateStatus() {
		Integer oid = 6;
		Integer status = 100;
		String username = "系统";
		Date modifiedTime = new Date();
		Integer rows = mapper.updateStatus(oid, status, username, modifiedTime);
		System.err.println("rows=" + rows);
	}

然后,还需要在持久层添加“根据订单id查询订单状态”的功能:

Order findByOid(Integer oid);

并配置该方法的映射:

	<resultMap id="OrderEntityMap"
		 type="cn.tedu.store.entity.Order">
		<id column="oid" property="oid" />
		<result column="uid" property="uid" />
		<result column="recv_name" property="recvName" />
		<result column="recv_phone" property="recvPhone" />
		<result column="recv_province" property="recvProvince" />
		<result column="recv_city" property="recvCity" />
		<result column="recv_area" property="recvArea" />
		<result column="recv_address" property="recvAddress" />
		<result column="total_price" property="totalPrice" />
		<result column="status" property="status" />
		<result column="order_time" property="orderTime" />
		<result column="pay_time" property="payTime" />
		<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>

	
	
	<select id="findByOid"
		resultMap="OrderEntityMap">
		SELECT
			*
		FROM
			t_order
		WHERE
			oid=#{oid}
	select>

执行单元测试:

@Test
public void findByOid() {
	Integer oid = 6;
	Order result = mapper.findByOid(oid);
	System.err.println(result);
}

与“归还库存”相关的数据操作已完成,在持久层并不需要添加新的数据处理功能。

接下来,还应该在业务层中提供“修改订单状态”、“增加商品库存”这2项功能,这2项功能合并起来,就是“归还库存”功能。

关于“修改订单状态”功能,应该在IOrderService中添加:

void changeStatus(Integer oid, Integer status, String username);

OrderServiceImpl中先添加私有方法:

	/**
	 * 修改订单状态
	 * @param oid 订单id
	 * @param status 状态值,0-未支付,1-已支付,2-已取消,3-关闭
	 * @param username 修改执行人
	 * @param modifiedTime 修改时间
	 * @throws UpdateException 更新数据异常
	 */
	private void updateStatus(Integer oid, Integer status, 
		String username, Date modifiedTime) throws UpdateException {
		Integer rows = orderMapper.updateStatus(oid, status, username, modifiedTime);
		if (rows != 1) {
			throw new UpdateException(
				"修改订单状态失败!更新数据时出现未知错误!");
		}
	}
	
	/**
	 * 根据订单id查询订单详情
	 * @param oid 订单id
	 * @return 匹配的订单详情,如果没有匹配的数据,则返回null
	 */
	private Order findByOid(Integer oid) {
		return orderMapper.findByOid(oid);
	}

然后,重写接口中的抽象方法:

	public void changeStatus(Integer oid, Integer status, String username) {
		// 根据参数oid查询订单状态
		// 判断查询结果是否不存在:OrderNotFoundException

		// 执行修改订单状态
	}

实现:

	@Override
	public void changeStatus(Integer oid, Integer status, String username) {
		// 根据参数oid查询订单状态
		Order result = findByOid(oid);
		// 判断查询结果是否不存在:OrderNotFoundException
		if (result == null) {
			throw new OrderNotFoundException(
				"修改订单状态失败!尝试访问的数据不存在!");
		}

		// 执行修改订单状态
		updateStatus(oid, status, username, new Date());
	}

完成后,测试:

	@Test
	public void changeStatus() {
		try {
			Integer oid = 6;
			Integer status = 500;
			String username = "系统管理员";
			service.changeStatus(oid, status, username);
			System.err.println("OK.");
		} catch (ServiceException e) {
			System.err.println(e.getClass().getName());
			System.err.println(e.getMessage());
		}
	}

另外,还需要添加“增加商品库存”功能,该功能所需要持久层操作已经完全完成,需要在IProductService中添加该功能:

void addNum(Integer pid, Integer amount);

具体实现方式可参考此前已经完成的reduceNum()功能,实现代码为:

	@Override
	public void addNum(Integer pid, Integer amount) {
		// 通过参数pid查询商品数据
		Product result = findById(pid);
		// 判断查询结果是否为null:ProductNotFoundException
		if (result == null) {
			throw new ProductNotFoundException(
				"更新商品库存失败!尝试访问的商品数量不存在!");
		}
		
		// 暂不考虑商品下架的问题
		
		// 执行增加库存
		updateNum(pid, result.getNum() + amount);
	}

测试:

	@Test
	public void addNum() {
		try {
			Integer pid = 10000022;
			Integer amount = 80;
			service.addNum(pid, amount);
			System.err.println("OK");
		} catch (ServiceException e) {
			System.err.println(e.getClass().getName());
			System.err.println(e.getMessage());
		}
	}

最后,在订单的业务层接口和实现类中添加“关闭订单”的操作:

	@Override
	@Transactional
	public void close(Integer oid, List<OrderItem> orderItems, String username) {
		// 检查订单状态是否是“未支付”
		Order result = findByOid(oid);
		// 检查查询结果是否不存在
		if (result == null) {
			throw new OrderNotFoundException(
				"关闭订单失败!尝试访问的数据不存在!");
		}
		
		// 检查订单当前状态
		if (result.getStatus() != Status.UNPAID) {
			return;
		}
		
		// 将订单状态修改为已关闭
		changeStatus(oid, Status.CLOSED, username);
		
		// 归还订单中所有商品的库存
		for (OrderItem orderItem : orderItems) {
			productService.addNum(orderItem.getPid(), orderItem.getNum());
		}
		System.err.println("OrderService:订单已关闭!");
	}

并且在`create()`创建订单的最后,添加计划任务:

	// 开启倒计时任务(Timer/Thread),如果用户在规定时间内未支付,则关闭订单,并归还库存
	new Thread() {
		public void run() {
			System.err.println("OrderService:计划15分钟后检查订单状态,准备关闭订单");
			try {
				Thread.sleep(15 * 60 * 1000);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			System.err.println("OrderService:准备关闭单……");
			close(order.getOid(), orderItems, username);
		}
	}.start();

你可能感兴趣的:(学习日志)