本篇我们来完成订单模模块功能的开拓,包括前台和后台开拓,里面涉及到的知识点挺多的,须要好好理解。
模块解释
本模块详细包括前台和后台开拓:前台功能包括:创建订单;商品信息;订单列表;订单详情和取消订单这5个部分,而后台功能则包括:订单列表;订单搜索;订单详情和订单发货这4个部分。

学会技能
在本模块中,你将会学到避免业务逻辑中横向越权和纵向越权等安全漏洞;设计实用、安全、扩展性强大的常量、列举类;订单号生成规则、订单严谨性判断;PJO 和 VO 之间的实际操练以及Mybatis的批量插入等知识。
数据表的设计
这里我们会利用到两个数据表,由于订单须要和订单信息绑定在一块:
store_order这个数据表(总订单也便是用于末了付款的订单)
store_order_item这个数据表(子订单)
前台创建订单
首先我们打开controller这个包下面的portal包,打开之前的那个OrderController,里面写入以下代码:
/ 前台创建订单 @author lenovo / @RequestMapping(value = \"大众create.do\公众) //这里便是详细的每个方法的url链接 @ResponseBody //自动序列化json功能 public ServerResponse create(HttpSession session,Integer shippingId){ //验证用户是否登录 User user =(User) session.getAttribute(Const.CURRENT_USER); //未登录须要用户逼迫登录 if(user ==null){ return ServerResponse.createByErrorCodeMessage(ResponseCode.NEED_LOGIN.getCode(),ResponseCode.NEED_LOGIN.getDesc()); } //实现我们前台创建订单的逻辑 return iOrderService.createOrder(user.getId(),shippingId); }
接着打开sevcice包下面的IOrderService.Java文件,里面新增以下代码:
ServerResponse createOrder(Integer userId,Integer shippingId); //前台创建订单
接着打开sevcice包下面的Impl包,我们找到OrderServiceImpl.java文件,里面的新增代码如下:
/ 前台订单功能开拓(代码是从下往上读) / public ServerResponse createOrder(Integer userId,Integer shippingId){ //根据用户id从购物车中获取已经选中的数据(返回结果是Cart类型的list) List<Cart> cartList =cartMapper.selectCheckedCartByUserId(userId); //打算这个订单的总价 //1、判断返回值是否成功 ServerResponse serverResponse =this.getCartOrderItem(userId,cartList); if(!serverResponse.isSuccess()){ return serverResponse; } //2、开始打算总价 List<OrderItem> orderItemList =(List<OrderItem>)serverResponse.getData(); //这里得到的是所有的子订单 BigDecimal payment =this.getOrderTotalPrice(orderItemList); //这里得到的是所有的子订单的总价,也便是订单的总价 //天生订单(将前面的store_order_item表的信息进行汇总,天生store_order表) Order order = this.assembleOrder(userId,shippingId,payment); //判断订单是否为空 if(order==null){ return ServerResponse.createByErrorMessage(\"大众天生订单缺点\公众); } //判断子订单是否为空 if(CollectionUtils.isEmpty(orderItemList)){ return ServerResponse.createByErrorMessage(\"大众购物车为空\"大众); } //在前面我们getCartOrderItem这个函数里面,我们就没有对order_no进行配置,现在须要配置了 for(OrderItem orderItem:orderItemList){ orderItem.setOrderNo(order.getOrderNo()); } //mybatis的批量插入(将所有的子订单进行插入,从而天生订单) orderItemMapper.batchInsert(orderItemList); //订单天生成功,我们须要减少商品的库存 this.produceProductStock(orderItemList); //订单天生成功,接着我们须要清空购物车 this.cleanCart(cartList); //然后须要将数据返回给前端 OrderVo orderVo =assembleOrderVo(order,orderItemList); return ServerResponse.createBySuccess(orderVo); } //组装新的付款订单,并返回给前端(记得新建3个Vo工具便于后面进行付款订单的组装,这个付款的订单包含订单信息,子订单信息,收货地址信息) private OrderVo assembleOrderVo(Order order,List<OrderItem> orderItemList){ //下面的信息是订单数据表里面有的信息 OrderVo orderVo =new OrderVo(); orderVo.setOrderNo(order.getOrderNo()); orderVo.setPayment(order.getPayment()); orderVo.setPaymentType(order.getPaymentType()); orderVo.setPaymentTypeDesc(Const.PaymentTypeEnum.codeOf(order.getPaymentType()).getValue()); orderVo.setPostage(order.getPostage()); orderVo.setStatus(order.getStatus()); orderVo.setStatusDesc(Const.OrderStatusEnum.codeOf(order.getStatus()).getValue()); orderVo.setShippingId(order.getShippingId()); //下面的信息是订单数据表里没有的信息,我们须要从之前的收货地址表中获取(记住此订单不是数据库中的store_order这个表) Shipping shipping =shippingMapper.selectByPrimaryKey(order.getShippingId()); if(shipping !=null){ orderVo.setReceiverName(shipping.getReceiverName()); //这里便是说如果新的订单存在,显示它的姓名及信息 orderVo.setShippingVo(assembleShippingVo(shipping)); } //以下是各种韶光的转换 orderVo.setPaymentTime(DateTimeUtil.dateToStr(order.getPaymentTime())); orderVo.setEndTime(DateTimeUtil.dateToStr(order.getEndTime())); orderVo.setCreateTime(DateTimeUtil.dateToStr(order.getCreateTime())); orderVo.setCloseTime(DateTimeUtil.dateToStr(order.getCloseTime())); orderVo.setImageHost(PropertiesUtil.getProperty(\公众ftp.server.http.prefix\"大众)); //下面的信息是订单数据表里没有的信息,我们须要从之前的子订单表中获取(记住此订单不是数据库中的store_order这个表) List<OrderItemVo> orderItemVoList =Lists.newArrayList(); for(OrderItem orderItem:orderItemList){ OrderItemVo orderItemVo =assembleOrderItemVo(orderItem); orderItemVoList.add(orderItemVo); } orderVo.setOrderItemVoList(orderItemVoList); return orderVo; } // private OrderItemVo assembleOrderItemVo(OrderItem orderItem){ OrderItemVo orderItemVo =new OrderItemVo(); orderItemVo.setOrderNo(orderItem.getOrderNo()); orderItemVo.setProductId(orderItem.getProductId()); orderItemVo.setProductName(orderItem.getProductName()); orderItemVo.setProductImage(orderItem.getProductImage()); orderItemVo .setCurrentUnitPrice(orderItem.getCurrentUnitPrice()); orderItemVo.setTotalPrice(orderItem.getTotalPrice()); orderItemVo.setQuantity(orderItem.getQuantity()); orderItemVo.setCreateTime(DateTimeUtil.dateToStr(orderItem.getCreateTime())); return orderItemVo; } //通过组装成新的收货地址工具,以便后面的天生新的支付订单利用 private ShippingVo assembleShippingVo(Shipping shipping){ ShippingVo shippingVo =new ShippingVo(); shippingVo.setReceiverAddress(shipping.getReceiverAddress()); shippingVo.setReceiverCity(shipping.getReceiverCity()); shippingVo.setReceiverDistrict(shipping.getReceiverDistrict()); shippingVo.setReceiverMobile(shipping.getReceiverMobile()); shippingVo.setReceiverPhone(shipping.getReceiverPhone()); shippingVo.setReceiverName(shipping.getReceiverName()); shippingVo.setReceiverProvince(shipping.getReceiverProvince()); shippingVo.setReceiverZip(shipping.getReceiverZip()); return shippingVo; } //订单天生成功,接着我们须要清空购物车 private void cleanCart( List<Cart> cartList){ for(Cart cart:cartList){ cartMapper.deleteByPrimaryKey(cart.getId()); } } //订单天生成功,我们须要减少商品的库存 private void produceProductStock(List<OrderItem> orderItemList){ for(OrderItem orderItem:orderItemList){ Product product =productMapper.selectByPrimaryKey(orderItem.getProductId()); product.setStock(product.getStock()-orderItem.getQuantity()); productMapper.updateByPrimaryKeySelective(product); } } //组装订单表(将前面的store_order_item表的信息进行汇总,天生store_order表) private Order assembleOrder(Integer userId,Integer shippingId,BigDecimal payment){ //这里面有三个参数:userId用户id, shippingId收货地址id, payment订单总价 //这个是我们组装成的新的订单 Order order =new Order(); long order_no =this.generateOrderNo(); //这里面的字段请依据store_order进行设置 order.setOrderNo(order_no); order.setStatus(Const.OrderStatusEnum.PAID.getCode()); order.setPostage(0); order.setPaymentType(Const.PaymentTypeEnum.ONLINE_PAY.getCode()); order.setUserId(userId); order.setShippingId(shippingId); order.setPayment(payment); //发货韶光:send_time //交易完成韶光:end_time //交易关闭韶光:close_time // 上面的韶光目前先不进行,后面会进行配置 //将新的订单插入到数据库中 int rowCount = orderMapper.insert(order); if(rowCount>0){ return order; } return null; } // 订单号order_no的天生 //订单号的天生是非常主要的,这个设计的好坏关系到往后做分布式,高并发的情形非常有效,这里采取韶光戳取余的办法 private long generateOrderNo(){ long currentTime =System.currentTimeMillis();// return currentTime+currentTime%10; 这种方法是可以的,但是在高并发的时候,可能是同时进行的,因此韶光有可能一样,那样我们对orderNum进行唯一索引的时候就会失落败 return currentTime+new Random().nextInt(100); //加上一个100以内的随机数 } //用于打算订单总价 private BigDecimal getOrderTotalPrice(List<OrderItem> orderItemList){ //将各个子订单进行求和,获取订单总价 BigDecimal payment =new BigDecimal(\"大众0\公众); for(OrderItem orderItem:orderItemList){ payment= BigDecimalUtil.add(payment.doubleValue(),orderItem.getTotalPrice().doubleValue()); //这里默认便是0+一个订单 } return payment; } //用于返回新的子订单明细(实在便是返回一个store_order_item子订单工具) private ServerResponse getCartOrderItem(Integer userId,List<Cart> cartList){ //这个是我们组装成的新的子订单 List<OrderItem> orderItemList =Lists.newArrayList(); if(CollectionUtils.isEmpty(cartList)){ return ServerResponse.createByErrorMessage(\"大众购物车为空\"大众); } //校验购物车的数据,包括产品的状态和数量 for(Cart cartItem:cartList){ //校验产品的状态是否是在售状态 Product product =productMapper.selectByPrimaryKey(cartItem.getProductId()); if(Const.ProductStatusEnum.ON_SALE.getCode() != product.getStatus() ){ return ServerResponse.createByErrorMessage(\"大众产品\公众+product.getName()+\"大众不是在线售卖状态\"大众); } //校验产品的库存 if(cartItem.getQuantity()>product.getStock()){ return ServerResponse.createByErrorMessage(\"大众产品\"大众+product.getName()+\公众库存不敷\公众); } //开始组装成我们的订单工具(除了order_no这个字段须要我们后面设置以外,别的须要我们在这里进行组装也便是order_item表中的信息) OrderItem orderItem =new OrderItem(); //order_no这个字段须要我们后面设置 orderItem.setUserId(userId); orderItem.setProductId(product.getId()); orderItem.setProductName(product.getName()); orderItem.setProductImage(product.getMainImage()); orderItem.setCurrentUnitPrice(product.getPrice()); orderItem.setQuantity(cartItem.getQuantity()); orderItem.setTotalPrice(BigDecimalUtil.mut(product.getPrice().doubleValue(),cartItem.getQuantity())); orderItemList.add(orderItem); } return ServerResponse.createBySuccess(orderItemList); }
上面的代码很多,但是都是为了后面的调用方便而进行了封装。下面分别阐明上面代码的含义(记住上面代码你必须是从下往上读,由于我们是随着开拓的须要而逐渐增加代码的)
1、首先我们是要创建订单,因此必须根据传入的用户id来去购物车(store_cart)这个数据表中查找相应的购物车工具,实在便是很多种商品,你知道的一个用户可能存在多个购物车商品,因此返回的是一个购物车list:
List<Cart> cartList =cartMapper.selectCheckedCartByUserId(userId);
我们须要打开CartMapper.java文件,里面新增代码如下:
List<Cart> selectCheckedCartByUserId(Integer userId);
然后打开CartMapper.xml文件,里面新增代码如下:
<select id=\"大众selectCheckedCartByUserId\"大众 parameterType=\"大众int\"大众 resultMap=\"大众BaseResultMap\公众> select <include refid=\"大众Base_Column_List\公众/> from store_cart where user_id =#{userId} and checked =1 </select>
2、然后就打算刚才那些购物车商品的总价,而这个过程比较繁芜,须要创建一个getCartOrderItem函数,用于返回新的子订单明细(实在便是返回一个store_order_item子订单工具),当然在这个函数里面,你须要校验购物车的数据,包括产品的状态和库存数量。
3、通过第二步,我们得到了所有的子订单,现在去定义一个getOrderTotalPrice函数,去打算所有的子订单的总价,也便是订单的总价,详细的代码参看上面。
4、接着我们须要天生订单号,订单号的天生是非常主要的,这个设计的好坏关系到往后做分布式,高并发的情形非常有效,这里采取韶光戳+随机函数的办法:新建一个用于天生订单号的generateOrderNo函数,我们的核心便是:
currentTime+new Random().nextInt(100);
5、将第3步中的所有子订单进行合并,天生新的支付订单,你知道的支付订单里面包含用户信息,商品信息以及收货地址。首先我们须要判断判断订单是否为空以及子订单是否为空:我们新建一个assembleOrder函数,用于组装订单表,这里面有三个参数:userId用户id, shippingId收货地址id, payment订单总价。
在order.setStatus(Const.OrderStatusEnum.PAID.getCode());和order.setPaymentType(Const.PaymentTypeEnum.ONLINE_PAY.getCode());的时候,我们须要打开Const.java文件,在OrderStatusEnum这个列举类添加codeOf方法:
public static OrderStatusEnum codeOf(int code){ for(OrderStatusEnum orderStatusEnum:values()){ if(orderStatusEnum.getCode() ==code){ return orderStatusEnum; } } throw new RuntimeException(\"大众没有找到对应的列举\公众); }
以及新建一个列举类PaymentTypeEnum:
//支付类型(目前暂支持线上支付,后面会进行扩展) public enum PaymentTypeEnum{ ONLINE_PAY(1,\公众在线支付\"大众) ; private String value; private int code; PaymentTypeEnum(int code,String value){ this.code =code; this.value=value; } public String getValue() { return value; } public void setValue(String value) { this.value = value; } public int getCode() { return code; } public void setCode(int code) { this.code = code; } public static PaymentTypeEnum codeOf(int code){ for(PaymentTypeEnum paymentTypeEnum:values()){ if(paymentTypeEnum.getCode() ==code){ return paymentTypeEnum; } } throw new RuntimeException(\"大众没有找到对应的列举\公众); } }
这里在把稳一下韶光字段,我们这里没有进行配置,我们后面会进行配置。
6、然后便是将新的订单插入到数据库中。mybatis的批量插入(将所有的子订单进行插入,从而天生订单),我们须要打开OrderItemMapper.java文件,里面新增代码如下:
void batchInsert(@Param(value = \"大众orderItemList\"大众) List<OrderItem> orderItemList);
记住在mybatis里面利用多个参数时,须要利用Param表明。
紧接着,打开OrderItemMapper.xml文件,里面新增代码如下:
<insert id=\"大众batchInsert\"大众 parameterType=\公众list\公众> insert into store_order_item (id, order_no,user_id, product_id, product_name, product_image, current_unit_price, quantity, total_price, create_time, update_time) values <foreach collection=\"大众orderItemList\"大众 index=\"大众index\公众 item=\"大众item\"大众 separator=\"大众,\"大众> ( #{item.id},#{item.orderNo},#{item.userId},#{item.productId},#{item.productName},#{item.productImage},#{item.currentUnitPrice},#{item.quantity},#{item.totalPrice},now(),now() ) </foreach> </insert>
这里面采取了foreach 进行循环遍历进行插入。
6、订单天生往后,我们须要清空购物车和减少商品的库存。分别去定义cleanCart和produceProductStock这两个函数,去实现干系的逻辑。
7、接下来我们要做的便是将数据以Json形式返回给前端,为了更好的实现这个功能,我们须要新建三个Vo文件,我们打开Vo这个包,新建OrderVo.java文件,里面的代码如下:
package top.store.vo;import top.store.pojo.Shipping;import top.store.service.ShippingVo;import java.math.BigDecimal;import java.util.Date;import java.util.List;public class OrderVo { private Long orderNo; private BigDecimal payment; private Integer paymentType; private String paymentTypeDesc; private Integer postage; private Integer status; private String statusDesc; //各种韶光,都采取String类型 private String paymentTime; private String sendTime; private String endTime; private String closeTime; private String createTime; private String updateTime; //子订单的明细(其余新建一个OrderItemVo用于组装须要显示的信息) private List<OrderItemVo> orderItemVoList; private String imageHost; private Integer shippingId; private String receiverName; //购物车明细(其余新建一个ShippingVo用于组装须要显示的信息) private ShippingVo shippingVo; public Long getOrderNo() { return orderNo; } public void setOrderNo(Long orderNo) { this.orderNo = orderNo; } public BigDecimal getPayment() { return payment; } public void setPayment(BigDecimal payment) { this.payment = payment; } public Integer getPaymentType() { return paymentType; } public void setPaymentType(Integer paymentType) { this.paymentType = paymentType; } public String getPaymentTypeDesc() { return paymentTypeDesc; } public void setPaymentTypeDesc(String paymentTypeDesc) { this.paymentTypeDesc = paymentTypeDesc; } public Integer getPostage() { return postage; } public void setPostage(Integer postage) { this.postage = postage; } public Integer getStatus() { return status; } public void setStatus(Integer status) { this.status = status; } public String getStatusDesc() { return statusDesc; } public void setStatusDesc(String statusDesc) { this.statusDesc = statusDesc; } public String getPaymentTime() { return paymentTime; } public void setPaymentTime(String paymentTime) { this.paymentTime = paymentTime; } public String getSendTime() { return sendTime; } public void setSendTime(String sendTime) { this.sendTime = sendTime; } public String getEndTime() { return endTime; } public void setEndTime(String endTime) { this.endTime = endTime; } public String getCloseTime() { return closeTime; } public void setCloseTime(String closeTime) { this.closeTime = closeTime; } public String getCreateTime() { return createTime; } public void setCreateTime(String createTime) { this.createTime = createTime; } public String getUpdateTime() { return updateTime; } public void setUpdateTime(String updateTime) { this.updateTime = updateTime; } public List<OrderItemVo> getOrderItemVoList() { return orderItemVoList; } public void setOrderItemVoList(List<OrderItemVo> orderItemVoList) { this.orderItemVoList = orderItemVoList; } public String getImageHost() { return imageHost; } public void setImageHost(String imageHost) { this.imageHost = imageHost; } public Integer getShippingId() { return shippingId; } public void setShippingId(Integer shippingId) { this.shippingId = shippingId; } public String getReceiverName() { return receiverName; } public void setReceiverName(String receiverName) { this.receiverName = receiverName; } public ShippingVo getShippingVo() { return shippingVo; } public void setShippingVo(ShippingVo shippingVo) { this.shippingVo = shippingVo; }}
接着新建OrderItemVo.java文件,里面的代码如下:
package top.store.vo;import java.math.BigDecimal;import java.util.Date;public class OrderItemVo { //从store_order_item表里面选择我们将要在订单中显示的字段 private Long orderNo; private Integer productId; private String productName; private String productImage; private BigDecimal currentUnitPrice; private Integer quantity; private BigDecimal totalPrice; private String createTime; public Long getOrderNo() { return orderNo; } public void setOrderNo(Long orderNo) { this.orderNo = orderNo; } public Integer getProductId() { return productId; } public void setProductId(Integer productId) { this.productId = productId; } public String getProductName() { return productName; } public void setProductName(String productName) { this.productName = productName; } public String getProductImage() { return productImage; } public void setProductImage(String productImage) { this.productImage = productImage; } public BigDecimal getCurrentUnitPrice() { return currentUnitPrice; } public void setCurrentUnitPrice(BigDecimal currentUnitPrice) { this.currentUnitPrice = currentUnitPrice; } public Integer getQuantity() { return quantity; } public void setQuantity(Integer quantity) { this.quantity = quantity; } public BigDecimal getTotalPrice() { return totalPrice; } public void setTotalPrice(BigDecimal totalPrice) { this.totalPrice = totalPrice; } public String getCreateTime() { return createTime; } public void setCreateTime(String createTime) { this.createTime = createTime; }}
末了新建ShippingVo.java文件,里面的代码如下:
package top.store.vo;public class ShippingVo { //从store_shipping表里面选择我们将要在订单中显示的字段 private String receiverName; private String receiverPhone; private String receiverMobile; private String receiverProvince; private String receiverCity; private String receiverDistrict; private String receiverAddress; private String receiverZip; public String getReceiverName() { return receiverName; } public void setReceiverName(String receiverName) { this.receiverName = receiverName; } public String getReceiverPhone() { return receiverPhone; } public void setReceiverPhone(String receiverPhone) { this.receiverPhone = receiverPhone; } public String getReceiverMobile() { return receiverMobile; } public void setReceiverMobile(String receiverMobile) { this.receiverMobile = receiverMobile; } public String getReceiverProvince() { return receiverProvince; } public void setReceiverProvince(String receiverProvince) { this.receiverProvince = receiverProvince; } public String getReceiverCity() { return receiverCity; } public void setReceiverCity(String receiverCity) { this.receiverCity = receiverCity; } public String getReceiverDistrict() { return receiverDistrict; } public void setReceiverDistrict(String receiverDistrict) { this.receiverDistrict = receiverDistrict; } public String getReceiverAddress() { return receiverAddress; } public void setReceiverAddress(String receiverAddress) { this.receiverAddress = receiverAddress; } public String getReceiverZip() { return receiverZip; } public void setReceiverZip(String receiverZip) { this.receiverZip = receiverZip; }}
接着我们须要定义三个函数分别用于组装我们在支付订单里面的信息,为什么这么做?那是由于我们的支付订单里面包含的只是订单表,子订单表(订单明细表)和收货地址表中的部分信息,因此我们须要进行抽离封装成一个我们用在支付订单里面的信息。
这个很好理解,assembleShippingVo函数传入shipping工具进行组装;assembleOrderItemVo函数传入orderItem工具进行组装;assembleOrderVo函数传入order和orderItemList工具进行组装。
这样末了将组装成的新的支付订单工具返回给前端,就完成了我们创建订单的逻辑。