买的哪些商品?每个商品数量是多少?这些数据来自于用户的购物车,因为点餐的时候用户是先将商品加到购物车当中,然后才能提交订单,所以用户购买的商品种类和数量是由购物车数据来决定的。
订单总金额是多少?订单总金额由两部分费用来构成,一个是菜品的费用,还有一个就是其他费用。餐盒费我们根据商品的数量来算,一个商品对应一个餐盒,每个餐盒一块钱,配送费就固定按照六块钱来算。
哪个用户下的单?收货地址是哪?用户手机号是多少?我们前面完成了导入地址簿功能代码,用户的地址簿中就包括用户的名字、收货地址、手机号这些数据。用户在下单的时候需要选择一个地址簿,这样我们就可以获取到这些数据。
用户下单本质是新增操作,也就是需要将下单后产生的订单数据,把它插入到数据库当中。我们只需要提交一个地址簿id就可以了,至于用户的名字、手机号、详细的收货地址,我们可以在后端查数据库来获得。
支付剩余时间倒计时由前端小程序按照秒递减,我们给它返回一个下单时间就可以,小程序会根据下单时间去计算还剩多少时间,并且进行倒计时的展示。用户下单后下一步需要进行订单支付,而支付的时候就会使用订单id,所以订单id也需要返回回来。
·数据库设计分析
order_id通过这个字段就能体现出来当前这个明细属于哪个订单,dish_id通过这个字段就能体现出来我们具体点的是哪个菜品,setmeal_id通过这个字段就能体现出来我们具体点的是哪个套餐。
@RestController("userOrderController")给当前的这个Controller取一个别名,也就是当前Controller这个Bean在Spring容器里面的名字。因为我们在后面管理端也需要创建一个OrderController,为了防止重名。
用户下单涉及两张表的操作,一张是订单表,也就是用户下单之后需要向订单表来插入一条数,一张是订单明细表用户下单之后也需要向订单明细表来插入一条或多条数据(也就是用户购买一个或多个商品,一个商品对应一个订单明细表)。订单表与订单明细表是一对多的关系,也就是说订单明细表可能会插入多条数据。用户下单之后,该用户购物车里面的数据就不需要了,所以要清空购物车数据。涉及两张表的操作,要保证数据的一致性(出现异常订单表里面插入了数据,订单明细表插入数据失败,导致数据不一致),所以要加@Transactional注解,要么全成功要么全失败,当执行完所有的操作才会提交事务,如果出现异常会进行事务回滚。
1.处理各种业务异常(地址簿、购物车数据为空)
根据前端小程序提交过来的地址簿id去查数据库,如果查出来是空的说明不存在,我们直接抛一个业务异常由全局异常处理器去捕获,如果不为空说明用户已经提交了收货地址。
shoppingCartMapper的list方法需要传入一个ShoppingCart对象,所以我们需要构造这个对象并传进去。如果查上来购物车数据,说明该用户的购物车不为空可以下单,如果查不上来购物车数据,说明该用户的购物车是空的不能下单。
2.向订单表插入1条数据
System.currentTimeMillis()返回当前系统的毫秒值形式。
因为Orders表当中的number是String类型的所以要转成String类型的。
插入订单数据需要返回订单表的主键值,因为我们后面需要插入订单明细,在订单明细实体类里面会使用到我们当前的订单id。
3.向订单明细表插入n条数据
这些数据由购物车当中的商品决定,我们需要把购物车数据遍历出来,封装到orderDetail对象里面去,再去设置orderDetail里面的其他属性,然后批量插入到订单明细表当中。
4.清空当前用户的购物车数据
5.封装Vo返回结果
package com.sky.entity;
import lombok.*;
import java.io.Serializable;
import java.math.BigDecimal;
import java.time.LocalDateTime;
/**
* 订单
*/
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class Orders implements Serializable {
/**
* 订单状态 1待付款 2待接单 3已接单 4派送中 5已完成 6已取消
*/
public static final Integer PENDING_PAYMENT = 1;
public static final Integer TO_BE_CONFIRMED = 2;
public static final Integer CONFIRMED = 3;
public static final Integer DELIVERY_IN_PROGRESS = 4;
public static final Integer COMPLETED = 5;
public static final Integer CANCELLED = 6;
/**
* 支付状态 0未支付 1已支付 2退款
*/
public static final Integer UN_PAID = 0;
public static final Integer PAID = 1;
public static final Integer REFUND = 2;
private static final long serialVersionUID = 1L;
private Long id;
//订单号
private String number;
//订单状态 1待付款 2待接单 3已接单 4派送中 5已完成 6已取消 7退款
private Integer status;
//下单用户id
private Long userId;
//地址id
private Long addressBookId;
//下单时间
private LocalDateTime orderTime;
//结账时间
private LocalDateTime checkoutTime;
//支付方式 1微信,2支付宝
private Integer payMethod;
//支付状态 0未支付 1已支付 2退款
private Integer payStatus;
//实收金额
private BigDecimal amount;
//备注
private String remark;
//用户名
private String userName;
//手机号
private String phone;
//地址
private String address;
//收货人
private String consignee;
//订单取消原因
private String cancelReason;
//订单拒绝原因
private String rejectionReason;
//订单取消时间
private LocalDateTime cancelTime;
//预计送达时间
private LocalDateTime estimatedDeliveryTime;
//配送状态 1立即送出 0选择具体时间
private Integer deliveryStatus;
//送达时间
private LocalDateTime deliveryTime;
//打包费
private int packAmount;
//餐具数量
private int tablewareNumber;
//餐具数量状态 1按餐量提供 0选择具体数量
private Integer tablewareStatus;
}
package com.sky.dto;
import com.fasterxml.jackson.annotation.JsonFormat;
import lombok.Data;
import java.io.Serializable;
import java.math.BigDecimal;
import java.time.LocalDateTime;
@Data
public class OrdersSubmitDTO implements Serializable {
//地址簿id
private Long addressBookId;
//付款方式
private int payMethod;
//备注
private String remark;
//预计送达时间
@JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd HH:mm:ss")
private LocalDateTime estimatedDeliveryTime;
//配送状态 1立即送出 0选择具体时间
private Integer deliveryStatus;
//餐具数量
private Integer tablewareNumber;
//餐具数量状态 1按餐量提供 0选择具体数量
private Integer tablewareStatus;
//打包费
private Integer packAmount;
//总金额
private BigDecimal amount;
}
<insert id="insert" parameterType="Orders" useGeneratedKeys="true" keyProperty="id">
insert into orders(number, status, user_id, address_book_id, order_time, checkout_time, pay_method, pay_status,
amount, remark, phone, address, consignee, estimated_delivery_time, delivery_status,
pack_amount, tableware_number, tableware_status)
VALUES
(#{number},#{status},#{userId},#{addressBookId},#{orderTime},#{checkoutTime},#{payMethod},
#{payStatus},#{amount},#{remark},#{phone},#{address},#{consignee},#{estimatedDeliveryTime},
#{deliveryStatus},#{packAmount},#{tablewareNumber},#{tablewareStatus})
</insert>
<insert id="insertBatch">
insert into order_detail (name, image, order_id, dish_id, setmeal_id, dish_flavor, number, amount)
VALUES
<foreach collection="orderDetailList" item="od" separator=",">
(#{od.name},#{od.image},#{od.orderId},#{od.dishId},#{od.setmealId},#{od.dishFlavor},#{od.number},#{od.amount})
</foreach>
</insert>
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.github.pagehelper.Page;
import com.github.pagehelper.PageHelper;
import com.github.xiaoymin.knife4j.core.util.CollectionUtils;
import com.sky.constant.MessageConstant;
import com.sky.context.BaseContext;
import com.sky.dto.*;
import com.sky.entity.*;
import com.sky.exception.AddressBookBusinessException;
import com.sky.exception.OrderBusinessException;
import com.sky.exception.ShoppingCartBusinessException;
import com.sky.mapper.*;
import com.sky.result.PageResult;
import com.sky.service.OrderService;
import com.sky.utils.WeChatPayUtil;
import com.sky.vo.OrderPaymentVO;
import com.sky.vo.OrderStatisticsVO;
import com.sky.vo.OrderSubmitVO;
import com.sky.vo.OrderVO;
import com.sky.websocket.WebSocketServer;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
@Service
@Slf4j
public class OrderServiceImpl implements OrderService {
@Autowired
private OrderMapper orderMapper;
@Autowired
private OrderDetailMapper orderDetailMapper;
@Autowired
private AddressBookMapper addressBookMapper;
@Autowired
private ShoppingCartMapper shoppingCartMapper;
@Autowired
private UserMapper userMapper;
@Autowired
private WeChatPayUtil weChatPayUtil;
@Autowired
private WebSocketServer webSocketServer;
private Orders orders;
/**
* 用户下单
* @param ordersSubmitDTO
* @return
*/
@Transactional
public OrderSubmitVO submitOrder(OrdersSubmitDTO ordersSubmitDTO) {
//1.处理各种业务异常(地址簿、购物车数据为空)
AddressBook addressBook = addressBookMapper.getById(ordersSubmitDTO.getAddressBookId());
if(addressBook == null){
//抛出业务异常
throw new AddressBookBusinessException(MessageConstant.ADDRESS_BOOK_IS_NULL);
}
//查询当前用户的购物车数据
Long userId = BaseContext.getCurrentId();
ShoppingCart shoppingCart = new ShoppingCart();
shoppingCart.setUserId(userId);
List<ShoppingCart> shoppingCartList = shoppingCartMapper.list(shoppingCart);
if(shoppingCartList == null && shoppingCartList.size() == 0){
//抛出业务异常
throw new ShoppingCartBusinessException(MessageConstant.SHOPPING_CART_IS_NULL);
}
//2.向订单表插入1条数据
Orders orders = new Orders();
BeanUtils.copyProperties(ordersSubmitDTO,orders);
orders.setOrderTime(LocalDateTime.now());
orders.setPayStatus(Orders.UN_PAID);
orders.setStatus(Orders.PENDING_PAYMENT);
orders.setNumber(String.valueOf(System.currentTimeMillis()));
orders.setPhone(addressBook.getPhone());
orders.setConsignee(addressBook.getConsignee());
orders.setUserId(userId);
this.orders = orders;
orderMapper.insert(orders);
List<OrderDetail> orderDetailList = new ArrayList();
//3.向订单明细表插入n条数据
for (ShoppingCart cart : shoppingCartList) {
OrderDetail orderDetail = new OrderDetail();//订单明细
BeanUtils.copyProperties(cart,orderDetail);
orderDetail.setOrderId(orders.getId());//设置当前订单明细相关联的订单id
orderDetailList.add(orderDetail);
}
orderDetailMapper.insertBatch(orderDetailList);
//4.清空当前用户的购物车数据
shoppingCartMapper.deleteByUserId(userId);
//5.封装Vo返回结果
OrderSubmitVO orderSubmitVO = OrderSubmitVO.builder()
.id(orders.getId())
.orderTime(orders.getOrderTime())
.orderNumber(orders.getNumber())
.orderAmount(orders.getAmount())
.build();
return orderSubmitVO;
}
}
·功能测试
补充:
System类概述
·System的功能是通用的,都是直接用类名调用即可,所以System不能被实例化。
System类的常用方法
方法名 说明
public static void exit(int status) 终止当前运行的Java虚拟机,非零表示异常终止
public static long currentTimeMillis() 返回当前系统的时间毫秒值形式
public static void arraycopy(数据源数组,起始索引,目的地数组,起始索引,拷贝个数)
数组拷贝
arraycopy(object src,int srcpos,object dest,int destpos,int length)
参数一:被拷贝的数组
参数二:从哪个索引位置开始拷贝
参数三:复制的目标数组
参数四:粘贴位置
参数五:拷贝元素个数
·计算机认为时间是有起点的,起始时间:1970年1月1日 00:00:00
·时间毫秒值:指的是从1970年1月1日 00:00:00走到此刻的总的毫秒数,应该是很大的。1s=1000ms
1970年1月1日 00:00:00算C语言的生日。