电商项目-订单

service层

package com.imooc.mall.service.impl;

import com.github.pagehelper.PageHelper;
import com.github.pagehelper.PageInfo;
import com.imooc.mall.dao.OrderItemMapper;
import com.imooc.mall.dao.OrderMapper;
import com.imooc.mall.dao.ProductMapper;
import com.imooc.mall.dao.ShippingMapper;
import com.imooc.mall.enums.OrderStatusEnum;
import com.imooc.mall.enums.PaymentTypeEnum;
import com.imooc.mall.enums.ProductStatusEnum;
import com.imooc.mall.enums.ResponseEnum;
import com.imooc.mall.pojo.*;
import com.imooc.mall.service.ICartService;
import com.imooc.mall.service.IOrderService;
import com.imooc.mall.vo.OrderItemVo;
import com.imooc.mall.vo.OrderVo;
import com.imooc.mall.vo.ResponseVo;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;

import java.math.BigDecimal;
import java.util.*;
import java.util.stream.Collectors;


@Service
public class OrderServiceImpl implements IOrderService {

	@Autowired
	private ShippingMapper shippingMapper;

	@Autowired
	private ICartService cartService;

	@Autowired
	private ProductMapper productMapper;

	@Autowired
	private OrderMapper orderMapper;

	@Autowired
	private OrderItemMapper orderItemMapper;

	@Override
	@Transactional
	public ResponseVo<OrderVo> create(Integer uid, Integer shippingId) {
		//收货地址校验(总之要查出来的)
		Shipping shipping = shippingMapper.selectByUidAndShippingId(uid, shippingId);
		if (shipping == null) {
			return ResponseVo.error(ResponseEnum.SHIPPING_NOT_EXIST);
		}

		//获取购物车,校验(是否有商品、库存)
		List<Cart> cartList = cartService.listForCart(uid).stream()
				.filter(Cart::getProductSelected)
				.collect(Collectors.toList());
		if (CollectionUtils.isEmpty(cartList)) {
			return ResponseVo.error(ResponseEnum.CART_SELECTED_IS_EMPTY);
		}

		//获取cartList里的productIds
		Set<Integer> productIdSet = cartList.stream()
				.map(Cart::getProductId)
				.collect(Collectors.toSet());
		List<Product> productList = productMapper.selectByProductIdSet(productIdSet);
		Map<Integer, Product> map  = productList.stream()
				.collect(Collectors.toMap(Product::getId, product -> product));

		List<OrderItem> orderItemList = new ArrayList<>();
		Long orderNo = generateOrderNo();
		for (Cart cart : cartList) {
			//根据productId查数据库
			Product product = map.get(cart.getProductId());
			//是否有商品
			if (product == null) {
				return ResponseVo.error(ResponseEnum.PRODUCT_NOT_EXIST,
						"商品不存在. productId = " + cart.getProductId());
			}
			//商品上下架状态
			if (!ProductStatusEnum.ON_SALE.getCode().equals(product.getStatus())) {
				return ResponseVo.error(ResponseEnum.PRODUCT_OFF_SALE_OR_DELETE,
						"商品不是在售状态. " + product.getName());
			}

			//库存是否充足
			if (product.getStock() < cart.getQuantity()) {
				return ResponseVo.error(ResponseEnum.PROODUCT_STOCK_ERROR,
						"库存不正确. " + product.getName());
			}

			OrderItem orderItem = buildOrderItem(uid, orderNo, cart.getQuantity(), product);
			orderItemList.add(orderItem);

			//减库存
			product.setStock(product.getStock() - cart.getQuantity());
			int row = productMapper.updateByPrimaryKeySelective(product);
			if (row <= 0) {
				return ResponseVo.error(ResponseEnum.ERROR);
			}
		}

		//计算总价,只计算选中的商品
		//生成订单,入库:order和order_item,事务
		Order order = buildOrder(uid, orderNo, shippingId, orderItemList);

		int rowForOrder = orderMapper.insertSelective(order);
		if (rowForOrder <= 0) {
			return ResponseVo.error(ResponseEnum.ERROR);
		}

		int rowForOrderItem = orderItemMapper.batchInsert(orderItemList);
		if (rowForOrderItem <= 0) {
			return ResponseVo.error(ResponseEnum.ERROR);
		}

		//更新购物车(选中的商品)
		//Redis有事务(打包命令),不能回滚
		for (Cart cart : cartList) {
			cartService.delete(uid, cart.getProductId());
		}

		//构造orderVo
		OrderVo orderVo = buildOrderVo(order, orderItemList, shipping);
		return ResponseVo.success(orderVo);
	}

	@Override
	public ResponseVo<PageInfo> list(Integer uid, Integer pageNum, Integer pageSize) {
		PageHelper.startPage(pageNum, pageSize);
		List<Order> orderList = orderMapper.selectByUid(uid);

		Set<Long> orderNoSet = orderList.stream()
				.map(Order::getOrderNo)
				.collect(Collectors.toSet());
		List<OrderItem> orderItemList = orderItemMapper.selectByOrderNoSet(orderNoSet);
		Map<Long, List<OrderItem>> orderItemMap = orderItemList.stream()
				.collect(Collectors.groupingBy(OrderItem::getOrderNo));

		Set<Integer> shippingIdSet = orderList.stream()
				.map(Order::getShippingId)
				.collect(Collectors.toSet());
		List<Shipping> shippingList = shippingMapper.selectByIdSet(shippingIdSet);
		Map<Integer, Shipping> shippingMap = shippingList.stream()
				.collect(Collectors.toMap(Shipping::getId, shipping -> shipping));

		List<OrderVo> orderVoList = new ArrayList<>();
		for (Order order : orderList) {
			OrderVo orderVo = buildOrderVo(order,
					orderItemMap.get(order.getOrderNo()),
					shippingMap.get(order.getShippingId()));
			orderVoList.add(orderVo);
		}
		PageInfo pageInfo = new PageInfo<>(orderList);
		pageInfo.setList(orderVoList);

		return ResponseVo.success(pageInfo);
	}

	@Override
	public ResponseVo<OrderVo> detail(Integer uid, Long orderNo) {
		Order order = orderMapper.selectByOrderNo(orderNo);
		if (order == null || !order.getUserId().equals(uid)) {
			return ResponseVo.error(ResponseEnum.ORDER_NOT_EXIST);
		}
		Set<Long> orderNoSet = new HashSet<>();
		orderNoSet.add(order.getOrderNo());
		List<OrderItem> orderItemList = orderItemMapper.selectByOrderNoSet(orderNoSet);

		Shipping shipping = shippingMapper.selectByPrimaryKey(order.getShippingId());

		OrderVo orderVo = buildOrderVo(order, orderItemList, shipping);
		return ResponseVo.success(orderVo);
	}

	@Override
	public ResponseVo cancel(Integer uid, Long orderNo) {
		Order order = orderMapper.selectByOrderNo(orderNo);
		if (order == null || !order.getUserId().equals(uid)) {
			return ResponseVo.error(ResponseEnum.ORDER_NOT_EXIST);
		}
		//只有[未付款]订单可以取消,看自己公司业务
		if (!order.getStatus().equals(OrderStatusEnum.NO_PAY.getCode())) {
			return ResponseVo.error(ResponseEnum.ORDER_STATUS_ERROR);
		}

		order.setStatus(OrderStatusEnum.CANCELED.getCode());
		order.setCloseTime(new Date());
		int row = orderMapper.updateByPrimaryKeySelective(order);
		if (row <= 0) {
			return ResponseVo.error(ResponseEnum.ERROR);
		}

		return ResponseVo.success();
	}

	@Override
	public void paid(Long orderNo) {
		Order order = orderMapper.selectByOrderNo(orderNo);
		if (order == null) {
			throw new RuntimeException(ResponseEnum.ORDER_NOT_EXIST.getDesc() + "订单id:" + orderNo);
		}
		//只有[未付款]订单可以变成[已付款],看自己公司业务
		if (!order.getStatus().equals(OrderStatusEnum.NO_PAY.getCode())) {
			throw new RuntimeException(ResponseEnum.ORDER_STATUS_ERROR.getDesc() + "订单id:" + orderNo);
		}

		order.setStatus(OrderStatusEnum.PAID.getCode());
		order.setPaymentTime(new Date());
		int row = orderMapper.updateByPrimaryKeySelective(order);
		if (row <= 0) {
			throw new RuntimeException("将订单更新为已支付状态失败,订单id:" + orderNo);
		}
	}

	private OrderVo buildOrderVo(Order order, List<OrderItem> orderItemList, Shipping shipping) {
		OrderVo orderVo = new OrderVo();
		BeanUtils.copyProperties(order, orderVo);

		List<OrderItemVo> OrderItemVoList = orderItemList.stream().map(e -> {
			OrderItemVo orderItemVo = new OrderItemVo();
			BeanUtils.copyProperties(e, orderItemVo);
			return orderItemVo;
		}).collect(Collectors.toList());
		orderVo.setOrderItemVoList(OrderItemVoList);

		if (shipping != null) {
			orderVo.setShippingId(shipping.getId());
			orderVo.setShippingVo(shipping);
		}

		return orderVo;
	}

	private Order buildOrder(Integer uid,
							 Long orderNo,
							 Integer shippingId,
							 List<OrderItem> orderItemList
							 ) {
		BigDecimal payment = orderItemList.stream()
				.map(OrderItem::getTotalPrice)
				.reduce(BigDecimal.ZERO, BigDecimal::add);

		Order order = new Order();
		order.setOrderNo(orderNo);
		order.setUserId(uid);
		order.setShippingId(shippingId);
		order.setPayment(payment);
		order.setPaymentType(PaymentTypeEnum.PAY_ONLINE.getCode());
		order.setPostage(0);
		order.setStatus(OrderStatusEnum.NO_PAY.getCode());
		return order;
	}

	/**
	 * 企业级:分布式唯一id/主键
	 * @return
	 */
	private Long generateOrderNo() {
		return System.currentTimeMillis() + new Random().nextInt(999);
	}

	private OrderItem buildOrderItem(Integer uid, Long orderNo, Integer quantity, Product product) {
		OrderItem item = new OrderItem();
		item.setUserId(uid);
		item.setOrderNo(orderNo);
		item.setProductId(product.getId());
		item.setProductName(product.getName());
		item.setProductImage(product.getMainImage());
		item.setCurrentUnitPrice(product.getPrice());
		item.setQuantity(quantity);
		item.setTotalPrice(product.getPrice().multiply(BigDecimal.valueOf(quantity)));
		return item;
	}
}

代码解释:
这段代码是一个典型的Java实现,属于一个在线商城系统中处理订单服务(IOrderService)的实现类 (OrderServiceImpl)。它包含了创建订单、查询订单列表、查询订单详情、取消订单以及标记订单为已支付等核心功能。下面是对关键部分的分析与说明:

创建订单 (create 方法)

  1. 校验收货地址:首先通过用户ID和收货地址ID查询收货地址是否存在。
  2. 获取购物车中待结算的商品:仅筛选出被选中的商品,并进行非空检查。
  3. 商品信息校验:包括商品是否存在、是否在售、库存是否足够,并准备批量减少库存的操作。
  4. 构建订单项 (OrderItem):对于每个购物车中的商品,创建相应的订单项并计算总价。
  5. 生成订单 (Order):整合所有订单项信息,生成订单实体,并插入到数据库中。
  6. 更新购物车:删除已结算的商品记录。
  7. 事务处理:确保上述操作在一个事务中执行,保证数据一致性。
  8. 构造响应Vo:将订单信息包装成Vo对象返回前端。

查询订单列表 (list 方法)

  • 使用分页插件 PageHelper 进行分页查询,然后转换查询结果为Vo对象列表。

订单详情 (detail 方法)

  • 根据用户ID和订单号查询订单详情,包括订单项和物流信息。

取消订单 (cancel 方法)

  • 检查订单状态,仅允许未付款的订单被取消,并更新订单状态。

标记订单为已支付 (paid 方法)

  • 更新指定订单号的订单状态为已支付,并设置支付时间。

辅助方法

  • generateOrderNo 生成一个基于当前时间戳和随机数的订单号,用于唯一标识订单。
  • buildOrderVo 将订单数据转换为视图对象供前端展示。
  • buildOrder 构建订单实体,计算总金额等。
  • buildOrderItem 构建订单项,记录订单中的每个商品详情。

技术点与设计模式

  • 事务管理:使用了Spring的@Transactional注解来确保操作的原子性。
  • 数据访问:通过Mapper接口与数据库交互,体现了MyBatis的使用。
  • 流式处理:在处理购物车商品和订单项时,利用了Java 8的Stream API进行集合操作,如过滤、映射和聚合。
  • 领域模型设计:通过Order、OrderItem、Product、Shipping等实体类清晰地定义了业务领域的对象及其关系。
  • 分层架构:实现了业务逻辑层(Service)、数据访问层(Mapper)的分层,符合典型的MVC或三层架构设计。

此代码片段展示了电商系统中订单模块的核心逻辑处理,体现了面向对象设计原则和良好的编程实践。

controller层

package com.imooc.mall.controller;

import com.github.pagehelper.PageInfo;
import com.imooc.mall.consts.MallConst;
import com.imooc.mall.form.OrderCreateForm;
import com.imooc.mall.pojo.User;
import com.imooc.mall.service.IOrderService;
import com.imooc.mall.vo.OrderVo;
import com.imooc.mall.vo.ResponseVo;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;

import javax.servlet.http.HttpSession;
import javax.validation.Valid;

/**

 */
@RestController
public class OrderController {

	@Autowired
	private IOrderService orderService;

	@PostMapping("/orders")
	public ResponseVo<OrderVo> create(@Valid @RequestBody OrderCreateForm form,
									  HttpSession session) {
		User user = (User) session.getAttribute(MallConst.CURRENT_USER);
		return orderService.create(user.getId(), form.getShippingId());
	}

	@GetMapping("/orders")
	public ResponseVo<PageInfo> list(@RequestParam Integer pageNum,
									 @RequestParam Integer pageSize,
									 HttpSession session) {
		User user = (User) session.getAttribute(MallConst.CURRENT_USER);
		return orderService.list(user.getId(), pageNum, pageSize);
	}

	@GetMapping("/orders/{orderNo}")
	public ResponseVo<OrderVo> detail(@PathVariable Long orderNo,
									  HttpSession session) {
		User user = (User) session.getAttribute(MallConst.CURRENT_USER);
		return orderService.detail(user.getId(), orderNo);
	}

	@PutMapping("/orders/{orderNo}")
	public ResponseVo cancel(@PathVariable Long orderNo,
							 HttpSession session) {
		User user = (User) session.getAttribute(MallConst.CURRENT_USER);
		return orderService.cancel(user.getId(), orderNo);
	}
}

代码解释:
这段代码定义了一个名为OrderController的RESTful Web控制器,负责处理与订单相关的HTTP请求。使用了Spring框架的@RestController注解,表明这是一个控制器类,其所有方法的返回值都将直接被序列化为JSON等格式返回给前端。下面是各方法的简要说明:

创建订单 (create 方法)

  • 请求方式: POST
  • 路径: /orders
  • 参数:
    • @Valid @RequestBody OrderCreateForm form: 通过请求体接收表单数据,且进行数据校验。
    • HttpSession session: 从Session中获取当前登录的用户信息。
  • 功能: 根据用户ID和传入的收货地址ID创建订单。使用了@Valid注解对传入的订单创建表单数据进行验证,确保数据的有效性。

查询订单列表 (list 方法)

  • 请求方式: GET
  • 路径: /orders
  • 参数:
    • @RequestParam Integer pageNum, @RequestParam Integer pageSize: 分页查询参数。
    • HttpSession session: 获取当前用户信息。
  • 功能: 根据用户ID和分页参数查询并返回用户的订单列表。

查询订单详情 (detail 方法)

  • 请求方式: GET
  • 路径: /orders/{orderNo}
  • 参数:
    • @PathVariable Long orderNo: 通过路径变量接收订单号。
    • HttpSession session: 获取当前用户信息。
  • 功能: 根据用户ID和订单号查询并返回订单详情。

取消订单 (cancel 方法)

  • 请求方式: PUT
  • 路径: /orders/{orderNo}
  • 参数:
    • @PathVariable Long orderNo: 通过路径变量接收订单号。
    • HttpSession session: 获取当前用户信息。
  • 功能: 允许用户取消指定的订单,仅当订单处于未支付状态时允许取消。

此控制器通过与IOrderService服务层交互,实现了订单相关的业务逻辑,包括创建、查询列表、查询详情和取消订单,体现了前后端分离的架构风格以及RESTful API的设计原则。用户身份验证通过Session来实现,确保了操作的安全性。

listener:

package com.imooc.mall.listener;

import com.google.gson.Gson;
import com.imooc.mall.pojo.PayInfo;
import com.imooc.mall.service.IOrderService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.rabbit.annotation.RabbitHandler;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

/**
 * 关于PayInfo,正确姿势:pay项目提供client.jar, mall项目引入jar包

 */
@Component
@RabbitListener(queues = "payNotify")
@Slf4j
public class PayMsgListener {

	@Autowired
	private IOrderService orderService;

	@RabbitHandler
	public void process(String msg) {
		log.info("【接收到消息】=> {}", msg);

		PayInfo payInfo = new Gson().fromJson(msg, PayInfo.class);
		if (payInfo.getPlatformStatus().equals("SUCCESS")) {
			//修改订单里的状态
			orderService.paid(payInfo.getOrderNo());
		}
	}
}

这段代码展示了一个使用Spring AMQP的消息监听器(Message Listener),用于监听名为payNotify的RabbitMQ队列。当队列中有消息时,这个监听器会被触发,进而执行消息处理逻辑。以下是该代码段的关键解析:

  • 消息监听器组件:通过@Component注解标记为Spring组件,使其能够被Spring容器管理。
  • 消息队列监听配置:使用@RabbitListener(queues = "payNotify")注解声明该类监听名为payNotify的队列。
  • 日志记录:通过@Slf4j注解引入日志记录功能,方便记录消息处理过程中的日志信息。
  • 消息处理器方法@RabbitHandler注解标记的process方法是实际处理队列中消息的方法。当队列中有消息时,RabbitMQ客户端会将消息内容(字符串形式)传递给此方法。
  • 消息解析与处理
    • 首先,使用Gson库将接收到的JSON格式的消息字符串转换为PayInfo对象。
    • 然后,检查PayInfo对象中的支付平台状态(platformStatus),如果状态为"SUCCESS",则调用orderService.paid(payInfo.getOrderNo())方法,实现订单的支付状态更新逻辑。

这个监听器的作用在于异步接收来自支付系统的支付结果通知,根据通知内容决定是否需要更新订单状态为已支付,这样的设计有助于提升系统的响应速度和解耦支付系统与订单系统的直接依赖。通过消息队列作为中间件,实现了高度解耦和灵活的异步处理机制。

订单模块的代码是在一个Spring Boot应用中使用@Autowired注解来注入依赖的Mapper和Service对象。具体来说:

ShippingMapper、ProductMapper、OrderMapper、OrderItemMapper 是用来与数据库进行交互的 Mapper 接口,通过它们可以执行相应的数据库操作,如查询、插入、更新等。
ICartService 是购物车相关的服务接口,通过该接口可以操作购物车,如添加商品到购物车、从购物车移除商品等。
这些依赖的注入使得在 OrderServiceImpl 类中可以方便地使用这些对象来完成订单相关的业务逻辑。

	@Transactional
	public ResponseVo<OrderVo> create(Integer uid, Integer shippingId) {
		//收货地址校验(总之要查出来的)
		Shipping shipping = shippingMapper.selectByUidAndShippingId(uid, shippingId);
		if (shipping == null) {
			return ResponseVo.error(ResponseEnum.SHIPPING_NOT_EXIST);
		}

		//获取购物车,校验(是否有商品、库存)
		List<Cart> cartList = cartService.listForCart(uid).stream()
				.filter(Cart::getProductSelected)
				.collect(Collectors.toList());
		if (CollectionUtils.isEmpty(cartList)) {
			return ResponseVo.error(ResponseEnum.CART_SELECTED_IS_EMPTY);
		}

		//获取cartList里的productIds
		Set<Integer> productIdSet = cartList.stream()
				.map(Cart::getProductId)
				.collect(Collectors.toSet());
		List<Product> productList = productMapper.selectByProductIdSet(productIdSet);
		Map<Integer, Product> map  = productList.stream()
				.collect(Collectors.toMap(Product::getId, product -> product));

		List<OrderItem> orderItemList = new ArrayList<>();
		Long orderNo = generateOrderNo();
		for (Cart cart : cartList) {
			//根据productId查数据库
			Product product = map.get(cart.getProductId());
			//是否有商品
			if (product == null) {
				return ResponseVo.error(ResponseEnum.PRODUCT_NOT_EXIST,
						"商品不存在. productId = " + cart.getProductId());
			}
			//商品上下架状态
			if (!ProductStatusEnum.ON_SALE.getCode().equals(product.getStatus())) {
				return ResponseVo.error(ResponseEnum.PRODUCT_OFF_SALE_OR_DELETE,
						"商品不是在售状态. " + product.getName());
			}

			//库存是否充足
			if (product.getStock() < cart.getQuantity()) {
				return ResponseVo.error(ResponseEnum.PRODUCT_STOCK_ERROR,
						"库存不正确. " + product.getName());
			}

			OrderItem orderItem = buildOrderItem(uid, orderNo, cart.getQuantity(), product);
			orderItemList.add(orderItem);

			//减库存
			product.setStock(product.getStock() - cart.getQuantity());
			int row = productMapper.updateByPrimaryKeySelective(product);
			if (row <= 0) {
				return ResponseVo.error(ResponseEnum.ERROR);
			}
		}

		//计算总价,只计算选中的商品
		//生成订单,入库:order和order_item,事务
		Order order = buildOrder(uid, orderNo, shippingId, orderItemList);

		int rowForOrder = orderMapper.insertSelective(order);
		if (rowForOrder <= 0) {
			return ResponseVo.error(ResponseEnum.ERROR);
		}

		int rowForOrderItem = orderItemMapper.batchInsert(orderItemList);
		if (rowForOrderItem <= 0) {
			return ResponseVo.error(ResponseEnum.ERROR);
		}

		//更新购物车(选中的商品)
		//Redis有事务(打包命令),不能回滚
		for (Cart cart : cartList) {
			cartService.delete(uid, cart.getProductId());
		}

		//构造orderVo
		OrderVo orderVo = buildOrderVo(order, orderItemList, shipping);
		return ResponseVo.success(orderVo);
	}

这段代码是创建订单的方法实现。让我逐步解释它:

  1. 开启了事务注解 @Transactional,表示该方法中的数据库操作应当在一个事务中执行,确保数据的一致性。

  2. 根据用户ID和收货地址ID从数据库中查询收货地址信息 Shipping

  3. 如果查询不到对应的收货地址,则返回一个表示收货地址不存在的错误信息。

  4. 调用购物车服务的 listForCart 方法获取用户购物车中的商品列表,并筛选出选中的商品。

  5. 如果购物车中没有选中的商品,则返回一个表示购物车为空的错误信息。

  6. 遍历购物车中选中的商品列表,获取每个商品的信息,包括商品是否存在、是否在售、库存是否充足等。

  7. 对于每个商品,如果存在问题(如商品不存在、不在售、库存不足),则返回相应的错误信息。

  8. 构建订单项 OrderItem,并添加到订单项列表 orderItemList 中。

  9. 根据订单项列表、用户ID、收货地址ID等信息构建订单对象 Order

  10. 将订单信息插入到数据库中,包括订单表和订单项表。

  11. 更新购物车,将购物车中已选中的商品移除。

  12. 构建订单视图对象 OrderVo,包括订单信息、订单项信息、收货地址信息等。

  13. 最后,返回一个表示订单创建成功的响应信息,包含订单视图对象。

这段代码中涉及了订单的创建、库存的扣除、购物车的更新等操作,确保了订单创建的完整性和准确性。

	@Override
	public ResponseVo<PageInfo> list(Integer uid, Integer pageNum, Integer pageSize) {
		PageHelper.startPage(pageNum, pageSize);
		List<Order> orderList = orderMapper.selectByUid(uid);

		Set<Long> orderNoSet = orderList.stream()
				.map(Order::getOrderNo)
				.collect(Collectors.toSet());
		List<OrderItem> orderItemList = orderItemMapper.selectByOrderNoSet(orderNoSet);
		Map<Long, List<OrderItem>> orderItemMap = orderItemList.stream()
				.collect(Collectors.groupingBy(OrderItem::getOrderNo));

		Set<Integer> shippingIdSet = orderList.stream()
				.map(Order::getShippingId)
				.collect(Collectors.toSet());
		List<Shipping> shippingList = shippingMapper.selectByIdSet(shippingIdSet);
		Map<Integer, Shipping> shippingMap = shippingList.stream()
				.collect(Collectors.toMap(Shipping::getId, shipping -> shipping));

		List<OrderVo> orderVoList = new ArrayList<>();
		for (Order order : orderList) {
			OrderVo orderVo = buildOrderVo(order,
					orderItemMap.get(order.getOrderNo()),
					shippingMap.get(order.getShippingId()));
			orderVoList.add(orderVo);
		}
		PageInfo pageInfo = new PageInfo<>(orderList);
		pageInfo.setList(orderVoList);

		return ResponseVo.success(pageInfo);
	}

这段代码是用于列出用户的订单列表的方法实现。让我解释一下它的主要步骤:

  1. 使用 PageHelper.startPage 方法开始分页,指定页码 pageNum 和每页大小 pageSize。

  2. 调用 orderMapper.selectByUid 方法查询数据库,获取该用户的订单列表 orderList。

  3. 从订单列表中提取订单号组成一个 Set 集合 orderNoSet。

  4. 调用 orderItemMapper.selectByOrderNoSet 方法查询数据库,获取所有订单号在 orderNoSet 中的订单项列表 orderItemList。

  5. 将订单项列表按订单号分组,形成一个 Map 结构 orderItemMap,键为订单号,值为对应订单号的订单项列表。

  6. 从订单列表中提取收货地址ID组成一个 Set 集合 shippingIdSet。

  7. 调用 shippingMapper.selectByIdSet 方法查询数据库,获取所有收货地址ID在 shippingIdSet 中的收货地址列表 shippingList。

  8. 将收货地址列表按ID映射成一个 Map 结构 shippingMap,键为收货地址ID,值为对应的收货地址对象。

  9. 遍历订单列表 orderList,构建每个订单的订单视图对象 OrderVo,并添加到订单视图列表 orderVoList 中。

  10. 将订单列表和订单视图列表封装到 PageInfo 对象中,并设置到 PageInfo 对象中。

  11. 最后,返回一个表示成功的响应对象,包含了订单列表的分页信息。

这段代码主要是查询用户的订单列表,并将订单列表中的订单信息和订单项信息整合到订单视图对象中,以便于前端展示。

	@Override
	public ResponseVo<OrderVo> detail(Integer uid, Long orderNo) {
		Order order = orderMapper.selectByOrderNo(orderNo);
		if (order == null || !order.getUserId().equals(uid)) {
			return ResponseVo.error(ResponseEnum.ORDER_NOT_EXIST);
		}
		Set<Long> orderNoSet = new HashSet<>();
		orderNoSet.add(order.getOrderNo());
		List<OrderItem> orderItemList = orderItemMapper.selectByOrderNoSet(orderNoSet);

		Shipping shipping = shippingMapper.selectByPrimaryKey(order.getShippingId());

		OrderVo orderVo = buildOrderVo(order, orderItemList, shipping);
		return ResponseVo.success(orderVo);
	}

这段代码是用于获取订单详情的方法实现。让我解释一下它的主要步骤:

  1. 调用 orderMapper.selectByOrderNo 方法根据订单号查询数据库,获取对应的订单对象 order。

  2. 首先检查获取到的订单对象是否为空,或者订单的用户ID是否与传入的用户ID匹配,如果不匹配,则返回一个表示订单不存在的错误信息。

  3. 如果订单存在且用户ID匹配,则构建一个只包含当前订单号的 Set 集合 orderNoSet,并调用 orderItemMapper.selectByOrderNoSet 方法查询数据库,获取该订单号对应的订单项列表 orderItemList。

  4. 调用 shippingMapper.selectByPrimaryKey 方法根据订单中的收货地址ID查询数据库,获取对应的收货地址信息 shipping。

  5. 调用 buildOrderVo 方法构建订单视图对象 OrderVo,将订单信息、订单项信息和收货地址信息整合到一个对象中。

  6. 最后,返回一个表示成功的响应对象,包含了订单详情的订单视图对象。

这段代码主要是根据订单号获取订单详情,并将订单信息、订单项信息和收货地址信息整合到一个对象中,以便于前端展示。

	@Override
	public ResponseVo cancel(Integer uid, Long orderNo) {
		Order order = orderMapper.selectByOrderNo(orderNo);
		if (order == null || !order.getUserId().equals(uid)) {
			return ResponseVo.error(ResponseEnum.ORDER_NOT_EXIST);
		}
		//只有[未付款]订单可以取消,看自己公司业务
		if (!order.getStatus().equals(OrderStatusEnum.NO_PAY.getCode())) {
			return ResponseVo.error(ResponseEnum.ORDER_STATUS_ERROR);
		}

		order.setStatus(OrderStatusEnum.CANCELED.getCode());
		order.setCloseTime(new Date());
		int row = orderMapper.updateByPrimaryKeySelective(order);
		if (row <= 0) {
			return ResponseVo.error(ResponseEnum.ERROR);
		}

		return ResponseVo.success();
	}

这段代码是用于取消订单的方法实现。让我解释一下它的主要步骤:

  1. 调用 orderMapper.selectByOrderNo 方法根据订单号查询数据库,获取对应的订单对象 order。

  2. 首先检查获取到的订单对象是否为空,或者订单的用户ID是否与传入的用户ID匹配,如果不匹配,则返回一个表示订单不存在的错误信息。

  3. 检查订单的状态是否为“未付款”。根据注释提示,这里的取消订单规则是只有未付款的订单才能被取消,如果订单状态不是“未付款”,则返回一个表示订单状态错误的错误信息。

  4. 如果订单存在且满足取消条件,则将订单状态修改为“已取消”,同时记录订单关闭时间为当前时间。

  5. 调用 orderMapper.updateByPrimaryKeySelective 方法更新订单状态,将修改后的订单信息更新到数据库中。

  6. 检查更新是否成功,如果更新失败,则返回一个表示更新错误的错误信息。

  7. 最后,返回一个表示成功的响应对象。

这段代码主要是根据订单号取消订单,并更新订单状态到数据库中。

	@Override
	public void paid(Long orderNo) {
		Order order = orderMapper.selectByOrderNo(orderNo);
		if (order == null) {
			throw new RuntimeException(ResponseEnum.ORDER_NOT_EXIST.getDesc() + "订单id:" + orderNo);
		}
		//只有[未付款]订单可以变成[已付款],看自己公司业务
		if (!order.getStatus().equals(OrderStatusEnum.NO_PAY.getCode())) {
			throw new RuntimeException(ResponseEnum.ORDER_STATUS_ERROR.getDesc() + "订单id:" + orderNo);
		}

		order.setStatus(OrderStatusEnum.PAID.getCode());
		order.setPaymentTime(new Date());
		int row = orderMapper.updateByPrimaryKeySelective(order);
		if (row <= 0) {
			throw new RuntimeException("将订单更新为已支付状态失败,订单id:" + orderNo);
		}
	}

这段代码是用于标记订单已支付的方法实现。让我解释一下它的主要步骤:

  1. 调用 orderMapper.selectByOrderNo 方法根据订单号查询数据库,获取对应的订单对象 order。

  2. 首先检查获取到的订单对象是否为空,如果订单对象为空,则抛出一个运行时异常,表示订单不存在。

  3. 检查订单的状态是否为“未付款”。根据注释提示,这里的逻辑是只有未付款的订单才能被标记为已支付,如果订单状态不是“未付款”,则抛出一个运行时异常,表示订单状态错误。

  4. 如果订单存在且满足标记支付条件,则将订单状态修改为“已支付”,同时记录订单支付时间为当前时间。

  5. 调用 orderMapper.updateByPrimaryKeySelective 方法更新订单状态,将修改后的订单信息更新到数据库中。

  6. 检查更新是否成功,如果更新失败,则抛出一个运行时异常,表示将订单更新为已支付状态失败。

这段代码主要是用于将订单状态标记为已支付,并更新订单状态到数据库中。

	private OrderVo buildOrderVo(Order order, List<OrderItem> orderItemList, Shipping shipping) {
		OrderVo orderVo = new OrderVo();
		BeanUtils.copyProperties(order, orderVo);

		List<OrderItemVo> OrderItemVoList = orderItemList.stream().map(e -> {
			OrderItemVo orderItemVo = new OrderItemVo();
			BeanUtils.copyProperties(e, orderItemVo);
			return orderItemVo;
		}).collect(Collectors.toList());
		orderVo.setOrderItemVoList(OrderItemVoList);

		if (shipping != null) {
			orderVo.setShippingId(shipping.getId());
			orderVo.setShippingVo(shipping);
		}

		return orderVo;
	}

这段代码是用于构建订单视图对象 OrderVo 的方法。让我解释一下它的主要步骤:

  1. 首先创建一个空的订单视图对象 OrderVo

  2. 使用 BeanUtils.copyProperties 方法将订单对象 order 的属性复制到订单视图对象 orderVo 中。这样可以快速地复制相同属性的值,避免手动设置。

  3. 遍历订单项列表 orderItemList,将每个订单项 OrderItem 转换为订单项视图对象 OrderItemVo。这里使用了 Java 8 的 Stream API 和 lambda 表达式,通过映射操作将订单项转换为订单项视图对象,并收集到列表中。

  4. 将转换后的订单项视图对象列表设置到订单视图对象的属性 orderItemVoList 中。

  5. 如果收货地址对象 shipping 不为空,则将其收货地址ID设置到订单视图对象的属性 shippingId 中,并将收货地址对象设置到订单视图对象的属性 shippingVo 中。

  6. 最后,返回构建好的订单视图对象 orderVo

这段代码主要是用于将订单信息、订单项信息和收货地址信息整合到一个订单视图对象中,以便于前端展示。

	private Order buildOrder(Integer uid,
							 Long orderNo,
							 Integer shippingId,
							 List<OrderItem> orderItemList
							 ) {
		BigDecimal payment = orderItemList.stream()
				.map(OrderItem::getTotalPrice)
				.reduce(BigDecimal.ZERO, BigDecimal::add);

		Order order = new Order();
		order.setOrderNo(orderNo);
		order.setUserId(uid);
		order.setShippingId(shippingId);
		order.setPayment(payment);
		order.setPaymentType(PaymentTypeEnum.PAY_ONLINE.getCode());
		order.setPostage(0);
		order.setStatus(OrderStatusEnum.NO_PAY.getCode());
		return order;
	}

这段代码是用于构建订单对象 Order 的方法。让我解释一下它的主要步骤:

  1. 首先,计算订单的总金额 payment。使用 Stream API 遍历订单项列表 orderItemList,提取每个订单项的总价 totalPrice,并将所有订单项的总价相加得到订单的总金额。如果订单项列表为空,则订单的总金额默认为 0。

  2. 创建一个新的订单对象 order

  3. 设置订单号 orderNo、用户ID uid、收货地址ID shippingId、支付方式 PAY_ONLINE、邮费 0、订单状态为待支付 NO_PAY

  4. 将计算得到的总金额 payment 设置到订单对象的支付金额属性 payment 中。

  5. 最后,返回构建好的订单对象 order

这段代码主要是用于根据用户ID、订单号、收货地址ID和订单项列表构建订单对象,其中订单的总金额通过计算订单项列表中所有订单项的总价得到。

	/**
	 * 企业级:分布式唯一id/主键
	 * @return
	 */
	private Long generateOrderNo() {
		return System.currentTimeMillis() + new Random().nextInt(999);
	}

	private OrderItem buildOrderItem(Integer uid, Long orderNo, Integer quantity, Product product) {
		OrderItem item = new OrderItem();
		item.setUserId(uid);
		item.setOrderNo(orderNo);
		item.setProductId(product.getId());
		item.setProductName(product.getName());
		item.setProductImage(product.getMainImage());
		item.setCurrentUnitPrice(product.getPrice());
		item.setQuantity(quantity);
		item.setTotalPrice(product.getPrice().multiply(BigDecimal.valueOf(quantity)));
		return item;
	}

这两个方法是用于生成订单号和构建订单项对象的方法。让我简要说明一下它们的作用:

  1. generateOrderNo 方法用于生成一个唯一的订单号。它首先获取当前系统时间的毫秒数,然后加上一个随机数,以确保生成的订单号在一定程度上是唯一的。在企业级应用中,可能会使用更加复杂的方式来生成唯一的订单号,比如基于分布式系统的 ID 生成器或者分布式锁等。

  2. buildOrderItem 方法用于根据用户ID、订单号、数量和商品信息构建一个订单项对象。它首先创建一个新的订单项对象,并设置订单项的属性,包括用户ID、订单号、商品ID、商品名称、商品图片、当前单价、数量和总价。订单项的总价通过计算当前单价乘以数量得到。

这两个方法都是用于订单模块的辅助方法,在订单创建时会被调用,用于生成订单号和构建订单项对象。

在提供的代码片段中,虽然没有直接展示AOP(面向切面编程)的代码实现,但我们可以看到几个方面体现了IOC(控制反转)的概念,这是通过Spring框架实现的。以下是对这两个概念在代码中的体现的分析:

IOC(控制反转)

  1. 依赖注入(Dependency Injection):

    • 使用@Autowired注解自动装配bean,这减少了代码间的紧耦合。例如,在OrderControllerOrderServiceImpl类中,通过@Autowired注解自动注入了IOrderService服务实例。这样,Spring容器负责管理这些bean的生命周期和依赖关系,而不是在类内部手动创建依赖对象。
  2. Spring管理Bean的生命周期:

    • 类上的@Service@Component注解标记了它们作为Spring管理的bean,使得Spring容器知道需要创建这些类的实例并管理它们。例如,OrderServiceImpl被标记为一个服务bean,由Spring容器初始化和管理。
  3. 事务管理:

    • 虽然直接的事务管理代码未展示,但提到的@Transactional注解(在OrderServiceImpl类的注释中提及)是IOC的一个重要应用,它体现了通过声明式事务管理,将事务控制的逻辑从业务代码中抽离出来,由Spring框架在运行时自动管理事务边界。

AOP(面向切面编程)

虽然代码片段中没有直接展示AOP的实现细节,但基于对Spring框架的了解,以及@Transactional注解的提及,我们可以推断AOP在如下方面应用:

  • 事务管理:

    • @Transactional注解是一个典型的AOP应用场景,它代表了一个切面,用于处理事务逻辑。当标记了这个注解的方法被调用时,Spring会在方法执行前后自动应用事务管理的逻辑,包括开始事务、提交或回滚事务等,而无需在业务代码中显式编写这些逻辑。
  • 日志记录、安全检查等横切关注点:

    • 虽然未直接展示,但AOP常用于处理如日志记录、权限验证等跨多个模块的功能,这些被称为横切关注点。通过定义切面,可以将这类逻辑集中管理,减少代码重复,并保持业务逻辑的纯净。

综上,代码片段通过使用Spring框架的特性间接体现了IOC和AOP的设计理念,尤其是在依赖管理和事务处理方面。AOP的具体实现细节可能在其他配置类或切面定义中,而IOC则通过注解和依赖注入的方式广泛应用于代码中。

集合的应用

在提供的代码片段中,集合主要被用来处理和操作一系列的对象,特别是在处理订单、商品和购物车条目等场景中。这里有几个集合使用的例子:

  1. 过滤和转换购物车商品:
    create方法中,通过流(Stream API)从购物车中筛选出被选中的商品(filter(Cart::getProductSelected)), 然后收集这些被选中的购物车项到一个新的列表中(collect(Collectors.toList()))。这演示了如何使用集合的流式API进行条件过滤和转换操作。

  2. 创建集合以进行集合操作:
    为了进行商品ID的集合操作,如去重和后续的查询,代码创建了一个Set<Integer>来保存所有被选中购物车商品的ID(map(Cart::getProductId)), 然后通过collect(Collectors.toSet())转换为无重复元素的集合。集合Set的特点是不允许重复元素,适合用于去重场景。

  3. 使用Map来关联对象:
    在处理商品信息时,代码使用了Map<Integer, Product>来关联商品ID和商品对象。通过收集器Collectors.toMap(Product::getId, product -> product),将商品列表转换为以商品ID为键、商品对象为值的映射关系。这种方式便于后续快速根据商品ID查找对应的商品信息。

  4. 批量操作和聚合计算:
    在计算总价和处理订单项时,使用集合的遍历和聚合功能。例如,通过遍历orderItemList来累加每个订单项的总价(reduce(BigDecimal.ZERO, BigDecimal::add)), 实现了对订单总价的计算。此外,集合还用于批量插入数据库操作,如orderItemMapper.batchInsert(orderItemList),展示了集合作为数据批量操作的载体。

  5. 分组和关联数据:
    在构建订单列表页面时,使用了集合的分组操作来关联订单项和订单(groupingBy(OrderItem::getOrderNo)), 以及按Shipping的ID进行分组(groupingBy(Shipping::getId)), 这样可以高效地将相关联的数据组织在一起,便于构建最终的展示对象。

通过这些例子可以看出,集合在代码中扮演了数据组织、过滤、转换、聚合和操作的核心角色,是实现复杂业务逻辑不可或缺的一部分。集合框架的灵活性和强大的功能帮助简化了许多数据处理任务,提高了代码的可读性和效率。

  • 13
    点赞
  • 27
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值