订单中不可变的数据:商品的图片,收获地址
订单中可变的数据:状态
支付之后的异步通知使用RabbitMQ实现。
实体类:
//订单
@Data
public class Order {
private Integer id;
private Long orderNo;
private Integer userId;
private Integer shippingId;
private BigDecimal payment;
private Integer paymentType;
private Integer postage;
private Integer status;
private Date paymentTime;
private Date sendTime;
private Date endTime;
private Date closeTime;
private Date createTime;
private Date updateTime;
}
//订单Vo
@Data
public class OrderVo {
private Long orderNo;
private BigDecimal payment;
private Integer paymentType;
private Integer postage;
private Integer status;
private Date paymentTime;
private Date sendTime;
private Date endTime;
private Date closeTime;
private Date createTime;
private List<OrderItemVo> orderItemVoList;
private Integer shippingId;
private Shipping shippingVo;
}
//订单详情
@Data
public class OrderItem {
private Integer id;
private Integer userId;
private Long orderNo;
private Integer productId;
private String productName;
private String productImage;
private BigDecimal currentUnitPrice;
private Integer quantity;
private BigDecimal totalPrice;
private Date createTime;
private Date updateTime;
}
//订单详情Vo
@Data
public class OrderItemVo {
private Long orderNo;
private Integer productId;
private String productName;
private String productImage;
private BigDecimal currentUnitPrice;
private Integer quantity;
private BigDecimal totalPrice;
private Date createTime;
}
//表单参数
@Data
public class OrderCreateForm {
@NotNull
private Integer ShippingId;
}
枚举类
//订单状态
@Getter
public enum OrderStatus {
CANCEL(0,"已取消"),
NP_PAY(10,"未付款"),
PAID(20,"已付款"),
SHIPPED(40,"已发货"),
TRADE_SUCCESS(50,"交易成功"),
TRADE_CLOSE(60,"交易关闭"),
;
Integer code;
String msg;
OrderStatus(Integer code, String msg) {
this.code = code;
this.msg = msg;
}
}
//支付类型
@Getter
public enum PaymentType {
PAY_ONLINE(1),
;
Integer code;
PaymentType(Integer code) {
this.code = code;
}
}
Controller
@RestController
public class OrderController {
@Autowired
private IOrderService orderService;
/**
* 创建订单
*/
@PostMapping("/order")
public ResponseVo<OrderVo> create(@Valid @RequestBody OrderCreateForm form, HttpSession session) {
User user = (User) session.getAttribute(UserConst.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(UserConst.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(UserConst.CURRENT_USER);
return orderService.detail(user.getId(), orderNo);
}
/**
* 取消订单
*/
@PutMapping("orders/{orderNo}")
public ResponseVo cancel(@PathVariable Long orderNo, HttpSession session) {
User user = (User) session.getAttribute(UserConst.CURRENT_USER);
return orderService.cancel(user.getId(), orderNo);
}
}
实现类:
@Service
public class IOrderService {
@Autowired
private IOrderMapper orderMapper;
@Autowired
private OrderItemMapper orderItemMapper;
@Autowired
private IShippingMapper shippingMapper;
@Autowired
private ICarService carService;
@Autowired
private IProductMapper productMapper;
/**
* 创建订单
*/
@Transactional //事务注解,又数据库实现,不指定默认是RunTimeException时回滚
public ResponseVo<OrderVo> create(Integer uid, Integer shippingId) {
//收获地址校验(总会查出来)
Shipping shipping = shippingMapper.selectByUidAndShippingId(uid, shippingId);
if (shipping == null) {
return ResponseVo.error(ResponseEnum.PARAMS_ERROR);
}
//获取购物车,校验是否有商品,库存
List<Car> carList = carService.listForCar(uid).stream().filter(Car::getProductSelected).collect(Collectors.toList());
if (CollectionUtils.isEmpty(carList)) {
return ResponseVo.error(ResponseEnum.PARAMS_ERROR);
}
//获取carList中的products
Set<Integer> products = carList.stream().map(Car::getProductId).collect(Collectors.toSet());
List<ProductVo> productVoList = productMapper.selectByProductIdSet(products);
/**
* list转Map 这里使用toMap,这里有两个参数
*/
Map<Integer, ProductVo> map = productVoList.stream().collect(Collectors.toMap(ProductVo::getId, product -> product));
ArrayList<OrderItem> orderItemList = new ArrayList<>();
Long orderNo = generateOrderNo();
for (Car car : carList) {
//根据productId查询数据库
ProductVo productVo = map.get(car.getProductId());
//是否有该商品
if (productVo == null) {
return ResponseVo.error(ResponseEnum.PRODUCT_NON, "商品不存在:" + car.getProductId());
}
//商品上架状态
if (!productVo.getStatus().equals(ProductStatusEnum.ON_SALE.getI())) {
return ResponseVo.error(ResponseEnum.PRODUCT_NON, "商品下架:" + car.getProductId());
}
//库存是否充足
if (productVo.getStock() < car.getQuantity()) {
return ResponseVo.error(ResponseEnum.PARAMS_ERROR, "库存不足" + productVo.getName());
}
//构造对象
OrderItem orderItem = buildOrderItem(uid, orderNo, car.getQuantity(), productVo);
orderItemList.add(orderItem);
//减库存
productVo.setStock(productVo.getStock() - car.getQuantity());
int row = productMapper.updateByPrimaryKeySelective(productVo);
if (row <= 0) {
return ResponseVo.error(ResponseEnum.MASHINE_ERROR);
}
}
//扩展,导入优惠卷
//计算价格,被选中总价
//生产订单,入库 order表和order_item表,事务保证插入两表时同时成功或失败
Order order = buildOrder(uid, orderNo, shippingId, orderItemList);
/** 注意:这两条插入要同时写入成功或失败,需添加@Transaction注解 */
int row = orderMapper.insertSelective(order);
if (row <= 0) {
return ResponseVo.error(ResponseEnum.MASHINE_ERROR);
}
int row1 = orderItemMapper.batchInsert(orderItemList); //这里分批用in插入,不要循环插
if (row1 <= 0) {
return ResponseVo.error(ResponseEnum.MASHINE_ERROR);
}
/*****************************to Here**********************************/
//更新购物车(选中的商品)
/**redis是单线程的,实现事务用打包命令,不能回滚*/
for (Car car : carList) {
carService.delete(uid, car.getProductId());
}
//构造orerVo返回
OrderVo orderVo = buildOrderVo(order, orderItemList, shipping);
return ResponseVo.successByCommonData(orderVo);
}
private OrderVo buildOrderVo(Order order, List<OrderItem> orderItemList, Shipping shipping) {
OrderVo orderVo = new OrderVo();
BeanUtils.copyProperties(order, orderVo);
List<OrderItemVo> list = orderItemList.stream().map(e -> {
OrderItemVo orderItemVo = new OrderItemVo();
BeanUtils.copyProperties(e, orderItemVo);
return orderItemVo;
}).collect(Collectors.toList());
if (shipping != null) {
orderVo.setShippingId(shipping.getId());
orderVo.setShippingVo(shipping);
}
orderVo.setOrderItemVoList(list);
return orderVo;
}
private Order buildOrder(Integer uid, Long orderNo, Integer shippingId, List<OrderItem> orderItemList) {
Order order = new Order();
order.setOrderNo(orderNo);
order.setUserId(uid);
order.setShippingId(shippingId);
order.setPayment(orderItemList.stream().map(OrderItem::getTotalPrice).reduce(BigDecimal.ZERO, BigDecimal::add));
order.setPaymentType(PaymentType.PAY_ONLINE.getCode());
order.setPostage(0);
order.setStatus(OrderStatus.NP_PAY.getCode());
return order;
}
/**
* 构建订单号,企业级:分布式唯一ID/主键
*/
private Long generateOrderNo() {
return System.currentTimeMillis() + new Random().nextInt(999);
}
private OrderItem buildOrderItem(Integer uid, Long orderNo, Integer quantity, ProductVo productVo) {
OrderItem item = new OrderItem();
item.setUserId(uid);
item.setOrderNo(orderNo);
item.setProductId(productVo.getId());
item.setProductName(productVo.getName());
item.setProductImage(productVo.getMainImage());
item.setCurrentUnitPrice(productVo.getPrice());
item.setQuantity(quantity);
item.setTotalPrice(productVo.getPrice().multiply(BigDecimal.valueOf(quantity)));
return item;
}
public ResponseVo<PageInfo> list(Integer uid, Integer pageNum, Integer pageSize) {
PageHelper.startPage(pageNum, pageSize);
List<Order> list = orderMapper.selectByUid(uid);
Set<Long> orderNoSet = list.stream().map(Order::getOrderNo).collect(Collectors.toSet());
List<OrderItem> orderItemList = orderItemMapper.selectByOrderNoSet(orderNoSet);
/**
* list转Map中List对象 这里使用groupingBy
*/
Map<Long, List<OrderItem>> orderItemMap = orderItemList.stream().collect(Collectors.groupingBy(OrderItem::getOrderNo));
Set<Integer> shippingIdSet = list.stream().map(Order::getShippingId).collect(Collectors.toSet());
ArrayList<Shipping> shippingList = shippingMapper.selectByShippingIdSet(shippingIdSet);
/**
* list转Map中单个对象 这里使用toMap
*/
Map<Integer, Shipping> shippingMap = shippingList.stream().collect(Collectors.toMap(Shipping::getId, shipping -> shipping));
ArrayList<OrderVo> orderVoList = new ArrayList<>();
for (Order order : list) {
OrderVo orderVo = buildOrderVo(order, orderItemMap.get(order.getOrderNo()), shippingMap.get(order.getShippingId()));
orderVoList.add(orderVo);
}
PageInfo pageInfo = new PageInfo();
pageInfo.setList(orderVoList);
return ResponseVo.successByCommonData(pageInfo);
}
public ResponseVo<OrderVo> detail(Integer uid, Long orderNo) {
Order order = orderMapper.selectByUidAndOrderNo(orderNo);
if (order == null || !order.getUserId().equals(uid)) {
return ResponseVo.error(ResponseEnum.PARAMS_ERROR, "订单不存在");
}
Set<Long> orderNoSet = new HashSet<>();
orderNoSet.add(order.getOrderNo());
List<OrderItem> orderItemList = orderItemMapper.selectByOrderNoSet(orderNoSet);
Shipping shipping = shippingMapper.selectByPrimaryKey(order.getShippingId());
return ResponseVo.successByCommonData(buildOrderVo(order, orderItemList, shipping));
}
public ResponseVo cancel(Integer uid, Long orderNo) {
Order order = orderMapper.selectByUidAndOrderNo(orderNo);
if (order == null || !order.getUserId().equals(uid)) {
return ResponseVo.error(ResponseEnum.PARAMS_ERROR, "订单不存在");
}
//只有未付款的当但才能取消
if (!order.getStatus().equals(OrderStatus.NP_PAY.getCode())) {
return ResponseVo.error(ResponseEnum.PARAMS_ERROR, "订单状态有误");
}
order.setStatus(OrderStatus.CANCEL.getCode());
order.setCloseTime(new Date());
int row = orderMapper.updateByPrimaryKeySelective(order);
if (row <= 0) {
return ResponseVo.error(ResponseEnum.MASHINE_ERROR);
}
return ResponseVo.success();
}
}
用到的循环插入sql -- XML格式:
public interface OrderItemMapper {
/**
* <insert id="batchInsert" parameterType="list">
* insert into mall_order_item (user_id,order_no,
* product_id,product_name,product_image,
* current_unit_price,quantity,total_price)
* values
* <foreach collection="orderItemList" index="index" item="item" separator=",">
* (
* #{item.userId},
* #{item.OrderNo},
* #{item.productId},
* #{item.productName},
* #{item.productImage},
* #{item.currentUnitPrice},
* #{item.quantity},
* #{item.totalPrice}
* )
* </foreach>
* </insert>
*/
int batchInsert(List<OrderItem> record);
/**
* <select id="selectByOrderNoSet" resultType="BaseResultMap">
* select
* <include refid="Base_Column_List" />
* from mall_order_item
* <where>
* <if test="orderNoSet.size() >0">
* order_no in
* <foreach collection="orderNoSet" item="item" index="index" open="(" separator="," close=")">
* #{item}
* </foreach>
* </if>
* </where>
* </select>
*/
List<OrderItem> selectByOrderNoSet(Set<Long> orderNoSet);
}
支付后的异步通知:
wins安装Rabbit需要先安装Erlang语言环境。或者安装CentsOs虚拟机在里面安装docker运行。
Mac/Linux:使用docker安装即可。
docker安装命令:docker run -it --rm --name rabbitmq -p 5672:5672 -p 15672:15672 rabitmq:3.8.2-management
本机登录地址127.0.0.1:15672,默认账号密码都是guest
引入pom:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-amqp</artifactId>
</dependency>
配置RabbitMq:
spring:
rabbitmq:
addresses: 127.0.0.1
port: 5672
username: guest
password: guest
消息发送方接上一个发起支付时异步发送消息
@Autowired
private AmqpTemplate amqpTemplate;
public PayResponse myCreate(String orderId, BigDecimal amount,BestPayTypeEnum bestPayTypeEnum) {
if(bestPayTypeEnum != BestPayTypeEnum.WXPAY_NATIVE && bestPayTypeEnum != BestPayTypeEnum.ALIPAY_PC ) {
throw new RuntimeException("暂不支持的支付类型");
}else{
//写入数据库,注意:支付订单号和支付流水号设置唯一约束
//注意:发起第二次支付前,把上一次支付订单关闭(调用支付平台的api)
PayInfo payInfo = new PayInfo();
payInfo.setOrderNo(Long.valueOf(orderId));
payInfo.setPayPlatform(PayPlatform.getByBestPayTypeEnum(bestPayTypeEnum).getI()); //设置支付类型
payInfo.setPlatformStatus(OrderStatusEnum.NOTPAY.name()); //设置未支付状态
payInfo.setPayAmount(amount);
payInfoMapper.insert(payInfo);
//构建WX支付配置参数
// WxPayConfig wxPayConfig = new WxPayConfig();
// wxPayConfig.setAppId("密钥");
// wxPayConfig.setMchId("商户id");
// wxPayConfig.setMchKey("商户密钥");
// wxPayConfig.setNotifyUrl("接收支付平台异步返回的公网地址/pay/notify");
//构建支付宝支付配置参数
AliPayConfig aliPayConfig = new AliPayConfig();
aliPayConfig.setAppId("appid");
aliPayConfig.setAliPayPublicKey("商户私钥");
aliPayConfig.setAliPayPublicKey("支付宝公钥");
aliPayConfig.setNotifyUrl("接收支付平台异步返回的公网地址/pay/notify");
aliPayConfig.setReturnUrl("支付完成后跳转的地址");
BestPayServiceImpl bestPayService = new BestPayServiceImpl();
// bestPayService.setWxPayConfig(wxPayConfig);
bestPayService.setAliPayConfig(aliPayConfig);
//构建支付请求参数
PayRequest request = new PayRequest();
request.setOrderName(orderId);
request.setOpenid("支付id");
request.setOrderAmount(Double.valueOf(String.valueOf(amount)));
request.setPayTypeEnum(bestPayTypeEnum); //支付类型
PayResponse response = bestPayService.pay(request);
log.info("response={}", response);
return response;
}
}
public String asyncNotify(String notifyData){
WxPayConfig wxPayConfig = new WxPayConfig();
wxPayConfig.setAppId("密钥");
wxPayConfig.setMchId("商户id");
wxPayConfig.setMchKey("商户密钥");
wxPayConfig.setNotifyUrl("接收支付平台异步返回的公网地址/pay/notify");
BestPayServiceImpl bestPayService = new BestPayServiceImpl();
bestPayService.setWxPayConfig(wxPayConfig);
//1.签名校验
PayResponse payResponse = bestPayService.asyncNotify(notifyData);
//2.金额校验(从数据库查订单)
PayInfo payInfo = payInfoMapper.selectByOrderNo(Long.parseLong(payResponse.getOrderId()));
if(payInfo == null){ //比较严重(正常情况不会发生)发出警告
//需要报警操作
throw new RuntimeException("通过OrderN查询结果为null。");
}
if(!payInfo.getPlatformStatus().equals(OrderStatusEnum.SUCCESS.name())){
//compareTo:数值比较,结果为-1,0,1
//Double类型比较大小注意精度问题,1.00和1.0是相等的
if(payInfo.getPayAmount().compareTo(BigDecimal.valueOf(payResponse.getOrderAmount()))!=0){
throw new RuntimeException("异步通知中的金额和数据库中的不一致!orderNo:"+payResponse.getOrderId());
}
//3.修改订单的支付状态
payInfo.setPlatformStatus(OrderStatusEnum.SUCCESS.name());
payInfo.setPlatformNumber(payResponse.getOutTradeNo());
payInfo.setUpdateTime(null);
payInfoMapper.updateByPrimaryKeySelective(payInfo);
}
/**** HERE *****/
//发送异步消息
amqpTemplate.convertAndSend(QUEUE_PAY_NOTIFY,new Gson().toJson(payInfo));
/**** END *****/
//4.告诉微信/支付宝不要在通知(从微信官网获取返回信息模板)
if(payResponse.getPayPlatformEnum() == BestPayPlatformEnum.WX) {
return "<xml>" +
" <return_code><![CDATA[SUCCESS]]></return_code>" +
" <return_msg><![CDATA[OK]]></return_msg>" +
"</xml>";
}else if(payResponse.getPayPlatformEnum() == BestPayPlatformEnum.ALIPAY){
return "success";
}
throw new RuntimeException("异步通知错误的支付平台");
}
}
接收方消费消息:
创建Listener:
/**
* 用于接收支付传来的消息
*/
@Component
@RabbitListener(queues = "payNotity") //绑定的队列名称
@Slf4j
public class PayMessageListener {
@Autowired
private IOrderService orderService;
@RabbitHandler
public void process(String msg){
log.info("接收到消息:{}",msg);
PayInfo payInfo = new Gson().fromJson(msg, PayInfo.class); //PayInfo正确姿势:发送项目提供client.jar包,消费项目引入jar包
if(payInfo.getPlatformStatus().equalsIgnoreCase("SUCCESS")){
//修改订单状态
orderService.updateOrderStatus(payInfo.getOrderNo());
}
}
添加修改支付状态功能:
@Service
public class IOrderService {
@Autowired
private IOrderMapper orderMapper;
@Autowired
private OrderItemMapper orderItemMapper;
@Autowired
private IShippingMapper shippingMapper;
@Autowired
private ICarService carService;
@Autowired
private IProductMapper productMapper;
public void updateOrderStatus(Long orderNo){
Order order = orderMapper.selectByUidAndOrderNo(orderNo);
if (order == null) {
throw new RuntimeException("订单不存在");
}
//只有未付款的才能修改已付款
if (!order.getStatus().equals(OrderStatus.NP_PAY.getCode())) {
throw new RuntimeException("订单状态有误");
}
order.setStatus(OrderStatus.PAID.getCode());
order.setPaymentTime(new Date());
int row = orderMapper.updateByPrimaryKeySelective(order);
if (row <= 0) {
throw new RuntimeException("更新状态有误");
}
}
}