spingboot商城购物车订单开发

订单中不可变的数据:商品的图片,收获地址

订单中可变的数据:状态

支付之后的异步通知使用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("更新状态有误");
        }
    }
}

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值