一、需求分析
移动端用户将菜品或者套餐加入到购物车后,可以点击购物车种的 去结算 按钮,页面跳转到订单确认页面,点击 去支付 按钮则完成下单操作。
二、数据模型
用户下单业务对应的数据表为orders表和order_detail表:
- orders:订单表
CREATE TABLE `orders` ( `id` bigint NOT NULL COMMENT '主键', `number` varchar(50) CHARACTER SET utf8 COLLATE utf8_bin DEFAULT NULL COMMENT '订单号', `status` int NOT NULL DEFAULT '1' COMMENT '订单状态 1待付款,2待派送,3已派送,4已完成,5已取消', `user_id` bigint NOT NULL COMMENT '下单用户', `address_book_id` bigint NOT NULL COMMENT '地址id', `order_time` datetime NOT NULL COMMENT '下单时间', `checkout_time` datetime NOT NULL COMMENT '结账时间', `pay_method` int NOT NULL DEFAULT '1' COMMENT '支付方式 1微信,2支付宝', `amount` decimal(10,2) NOT NULL COMMENT '实收金额', `remark` varchar(100) CHARACTER SET utf8 COLLATE utf8_bin DEFAULT NULL COMMENT '备注', `phone` varchar(255) CHARACTER SET utf8 COLLATE utf8_bin DEFAULT NULL, `address` varchar(255) CHARACTER SET utf8 COLLATE utf8_bin DEFAULT NULL, `user_name` varchar(255) CHARACTER SET utf8 COLLATE utf8_bin DEFAULT NULL, `consignee` varchar(255) CHARACTER SET utf8 COLLATE utf8_bin DEFAULT NULL, PRIMARY KEY (`id`) USING BTREE ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin COMMENT='订单表';
- order_detail:订单明细表
CREATE TABLE `order_detail` ( `id` bigint NOT NULL COMMENT '主键', `name` varchar(50) CHARACTER SET utf8 COLLATE utf8_bin DEFAULT NULL COMMENT '名字', `image` varchar(100) CHARACTER SET utf8 COLLATE utf8_bin DEFAULT NULL COMMENT '图片', `order_id` bigint NOT NULL COMMENT '订单id', `dish_id` bigint DEFAULT NULL COMMENT '菜品id', `setmeal_id` bigint DEFAULT NULL COMMENT '套餐id', `dish_flavor` varchar(50) CHARACTER SET utf8 COLLATE utf8_bin DEFAULT NULL COMMENT '口味', `number` int NOT NULL DEFAULT '1' COMMENT '数量', `amount` decimal(10,2) NOT NULL COMMENT '金额', PRIMARY KEY (`id`) USING BTREE ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin COMMENT='订单明细表';
三、用户下单
1. 代码开发-梳理交互过程
在开发代码之前,需要梳理一下用户下单操作时前端页面和服务端的交互过程:
- 在购物车中点击 去结算 按钮,页面跳转到订单确认页面
- 在订单确认页面,发送ajax请求,请求服务端获取当前登录用户的默认地址
- 在订单确认页面,发送ajax请求,请求服务端获取当前登录用户的购物车数据
- 在订单确认页面点击 去支付 按钮,发送ajax请求,请求服务端完成下单操作
开发用户下单功能 ,其实就是在服务端编写代码去处理前端页面发送的请求即可。
2. 代码开发
在开发业务功能之前,先将需要用到的类和接口基本结构创建好:
实体类 Orders:
package com.tigerhhzz.wuaimai.entity; import lombok.Data; import java.io.Serializable; import java.math.BigDecimal; import java.time.LocalDateTime; /** * 订单 */ @Data public class Orders implements Serializable { private static final long serialVersionUID = 1L; private Long id; //订单号 private String number; //订单状态 1待付款,2待派送,3已派送,4已完成,5已取消 private Integer status; //下单用户id private Long userId; //地址id private Long addressBookId; //下单时间 private LocalDateTime orderTime; //结账时间 private LocalDateTime checkoutTime; //支付方式 1微信,2支付宝 private Integer payMethod; //实收金额 private BigDecimal amount; //备注 private String remark; //用户名 private String userName; //手机号 private String phone; //地址 private String address; //收货人 private String consignee; }
实体类 OrderDetail:
package com.tigerhhzz.wuaimai.entity; import com.baomidou.mybatisplus.annotation.IdType; import com.baomidou.mybatisplus.annotation.TableId; import lombok.Data; import java.io.Serializable; import java.math.BigDecimal; /** * 订单明细 */ @Data public class OrderDetail implements Serializable { private static final long serialVersionUID = 1L; private Long id; //名称 private String name; //订单id private Long orderId; //菜品id private Long dishId; //套餐id private Long setmealId; //口味 private String dishFlavor; //数量 private Integer number; //金额 private BigDecimal amount; //图片 private String image; }
Mapper接口 OrderMapper:
package com.tigerhhzz.wuaimai.mapper; import com.baomidou.mybatisplus.core.mapper.BaseMapper; import com.tigerhhzz.wuaimai.entity.Orders; import org.apache.ibatis.annotations.Mapper; @Mapper public interface OrderMapper extends BaseMapper<Orders> { }
Mapper接口 OrderDetailMapper:
package com.tigerhhzz.wuaimai.mapper; import com.baomidou.mybatisplus.core.mapper.BaseMapper; import com.tigerhhzz.wuaimai.entity.OrderDetail; import org.apache.ibatis.annotations.Mapper; @Mapper public interface OrderDetailMapper extends BaseMapper<OrderDetail> { }
业务接口OrderService:
package com.tigerhhzz.wuaimai.service; import com.baomidou.mybatisplus.extension.service.IService; import com.tigerhhzz.wuaimai.entity.Orders; public interface OrderService extends IService<Orders> { /** * 用户下单 * @param orders */ public void submit(Orders orders); }
业务接口OrderDetailService
package com.tigerhhzz.wuaimai.service; import com.baomidou.mybatisplus.extension.service.IService; import com.tigerhhzz.wuaimai.entity.OrderDetail; public interface OrderDetailService extends IService<OrderDetail> { }
业务层实现类 OrderServiceImpl:
package com.tigerhhzz.wuaimai.service.impl; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.baomidou.mybatisplus.core.toolkit.IdWorker; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import com.tigerhhzz.wuaimai.common.BaseContext; import com.tigerhhzz.wuaimai.common.CustomException; import com.tigerhhzz.wuaimai.entity.*; import com.tigerhhzz.wuaimai.mapper.OrderMapper; import com.tigerhhzz.wuaimai.service.*; import lombok.extern.slf4j.Slf4j; 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.List; import java.util.concurrent.atomic.AtomicInteger; import java.util.stream.Collectors; @Service @Slf4j public class OrderServiceImpl extends ServiceImpl<OrderMapper, Orders> implements OrderService { @Autowired private ShoppingCartService shoppingCartService; @Autowired private UserService userService; @Autowired private AddressBookService addressBookService; @Autowired private OrderDetailService orderDetailService; /** * 用户下单 * @param orders */ @Override @Transactional public void submit(Orders orders) { log.info("订单数据:{}", orders); //获得当前用户id Long userId = BaseContext.getCurrentId(); //查询当前用户的购物车数据 LambdaQueryWrapper<ShoppingCart> wrapper = new LambdaQueryWrapper<>(); wrapper.eq(ShoppingCart::getUserId,userId); List<ShoppingCart> shoppingCarts = shoppingCartService.list(wrapper); if(shoppingCarts == null || shoppingCarts.size() == 0){ throw new CustomException("购物车为空,不能下单"); } //查询用户数据 User user = userService.getById(userId); //查询地址数据 Long addressBookId = orders.getAddressBookId(); AddressBook addressBook = addressBookService.getById(addressBookId); if(addressBook == null){ throw new CustomException("用户地址信息有误,不能下单"); } long orderId = IdWorker.getId();//订单号 AtomicInteger amount = new AtomicInteger(0); List<OrderDetail> orderDetails = shoppingCarts.stream().map((item) -> { OrderDetail orderDetail = new OrderDetail(); orderDetail.setOrderId(orderId); orderDetail.setNumber(item.getNumber()); orderDetail.setDishFlavor(item.getDishFlavor()); orderDetail.setDishId(item.getDishId()); orderDetail.setSetmealId(item.getSetmealId()); orderDetail.setName(item.getName()); orderDetail.setImage(item.getImage()); orderDetail.setAmount(item.getAmount()); amount.addAndGet(item.getAmount().multiply(new BigDecimal(item.getNumber())).intValue()); return orderDetail; }).collect(Collectors.toList()); orders.setId(orderId); orders.setOrderTime(LocalDateTime.now()); orders.setCheckoutTime(LocalDateTime.now()); orders.setStatus(2); orders.setAmount(new BigDecimal(amount.get()));//总金额 orders.setUserId(userId); orders.setNumber(String.valueOf(orderId)); orders.setUserName(user.getName()); orders.setConsignee(addressBook.getConsignee()); orders.setPhone(addressBook.getPhone()); orders.setAddress((addressBook.getProvinceName() == null ? "" : addressBook.getProvinceName()) + (addressBook.getCityName() == null ? "" : addressBook.getCityName()) + (addressBook.getDistrictName() == null ? "" : addressBook.getDistrictName()) + (addressBook.getDetail() == null ? "" : addressBook.getDetail())); //向订单表插入数据,一条数据 this.save(orders); //向订单明细表插入数据,多条数据 orderDetailService.saveBatch(orderDetails); //清空购物车数据 shoppingCartService.remove(wrapper); } }
业务层实现类OrderDetailServiceImpl:
package com.tigerhhzz.wuaimai.service.impl; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import com.tigerhhzz.wuaimai.entity.OrderDetail; import com.tigerhhzz.wuaimai.mapper.OrderDetailMapper; import com.tigerhhzz.wuaimai.service.OrderDetailService; import org.springframework.stereotype.Service; @Service public class OrderDetailServiceImpl extends ServiceImpl<OrderDetailMapper, OrderDetail> implements OrderDetailService { }
控制层OrderController:
package com.tigerhhzz.wuaimai.controller; import com.tigerhhzz.wuaimai.common.R; import com.tigerhhzz.wuaimai.entity.Orders; import com.tigerhhzz.wuaimai.service.OrderService; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; /** * 订单 */ @Slf4j @RestController @RequestMapping("/order") public class OrderController { @Autowired private OrderService orderService; /** * 用户下单 * @param orders * @return */ @PostMapping("/submit") public R<String> submit(@RequestBody Orders orders){ log.info("订单数据:{}",orders); orderService.submit(orders); return R.success("下单成功"); } }
控制层OrderDetailController:
package com.tigerhhzz.wuaimai.controller; import com.tigerhhzz.wuaimai.service.OrderDetailService; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.*; /** * 订单明细 */ @Slf4j @RestController @RequestMapping("/orderDetail") public class OrderDetailController { @Autowired private OrderDetailService orderDetailService; }
3. 移动端查询订单
请求地址:http://localhost:8080/order/userPage?page=页数&pageSize=每页数量
请求类型:GET
请求参数:page,默认1,pageSize,默认5
OrdersService:
/** * 分页查询订单 * @param page * @param pageSize * @return */ Page<OrdersDto> userPage(Integer page, Integer pageSize);
OrdersServiceImpl:
/** * 分页查询订单 * @param page * @param pageSize * @return */ @Override public Page<OrdersDto> userPage(Integer page, Integer pageSize) { // 分页构造器 Page<Orders> ordersPage = new Page<>(page, pageSize); // 条件构造器 LambdaQueryWrapper<Orders> ordersLambdaQueryWrapper = new LambdaQueryWrapper<>(); ordersLambdaQueryWrapper.orderByDesc(Orders::getOrderTime); this.page(ordersPage,ordersLambdaQueryWrapper); Page<OrdersDto> ordersDtoPage = new Page<>(); // 分页的ordersDtoPage,没有records BeanUtils.copyProperties(ordersPage, ordersDtoPage, "records"); // 构造orderDetails List<Orders> ordersList = ordersPage.getRecords(); List<OrdersDto> ordersDtoList = ordersList.stream().map((item) -> { OrdersDto ordersDto = new OrdersDto(); // 订单id String orderNum = item.getNumber(); LambdaQueryWrapper<OrderDetail> orderDetailLambdaQueryWrapper = new LambdaQueryWrapper<>(); orderDetailLambdaQueryWrapper.eq(OrderDetail::getOrderId, orderNum); BeanUtils.copyProperties(item, ordersDto); List<OrderDetail> list = orderDetailService.list(orderDetailLambdaQueryWrapper); ordersDto.setOrderDetails(list); return ordersDto; }).collect(Collectors.toList()); ordersDtoPage.setRecords(ordersDtoList); return ordersDtoPage; }
OrdersController:
/** * 分页查询订单 * @param page * @param pageSize * @return */ @GetMapping("/userPage") public R<Page<OrdersDto>> userPage(Integer page, Integer pageSize){ Page<OrdersDto> dtoPage = ordersService.userPage(page, pageSize); return R.success(dtoPage); }
4. 再来一单
请求地址:http://localhost:8080/order/again
请求类型:POST
请求参数:{id}
/** * 再来一单 * @param orders * @return */ @PostMapping("/again") public R<String> again(@RequestBody Orders orders){ Orders temp = ordersService.getById(orders.getId()); temp.setId(null); temp.setStatus(2); long orderId = IdWorker.getId(); // 订单号 temp.setNumber(String.valueOf(orderId)); temp.setOrderTime(LocalDateTime.now()); temp.setCheckoutTime(LocalDateTime.now()); ordersService.save(temp); return R.success("下单成功"); }
5. 分页多条件查询订单
请求地址:http://localhost:8080/order/page
请求类型:GET
请求参数:page页码、pageSize每页数量、number订单号、beginTime订单开始时间、endTime订单结束时间
PageQueryDto:
package cn.mu00.reggie.dto; import lombok.Data; import java.time.LocalDateTime; /** * 多条件分页查询 */ @Data public class PageQueryDto { int page; int pageSize; String number; String beginTime; String endTime; }
OrdersService:
/** * 分页多条件查询 * @param pageQueryDto * @return */ Page<Orders> queryPage(PageQueryDto pageQueryDto);
OrdersServiceImpl:
/** * 分页多条件查询 * @param pageQueryDto * @return */ @Override public Page<Orders> queryPage(PageQueryDto pageQueryDto) { // 解构pageQueryDto int page = pageQueryDto.getPage(); int pageSize = pageQueryDto.getPageSize(); String number = pageQueryDto.getNumber(); // 订单 分页构造器 Page<Orders> ordersPage = new Page<>(page, pageSize); // 订单 条件构造器 LambdaQueryWrapper<Orders> ordersQueryWrapper = new LambdaQueryWrapper<>(); // 根据订单时间,倒序排列 ordersQueryWrapper.orderByDesc(Orders::getOrderTime); // 条件 订单号模糊查询 ordersQueryWrapper.like(number != null, Orders::getNumber, number); // 判空 if (pageQueryDto.getBeginTime() != null && pageQueryDto.getEndTime() != null){ DateTimeFormatter df = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"); LocalDateTime beginTime = LocalDateTime.parse(pageQueryDto.getBeginTime(), df); LocalDateTime endTime = LocalDateTime.parse(pageQueryDto.getEndTime(), df); // 条件 时间区间 ordersQueryWrapper.between(Orders::getOrderTime, beginTime, endTime); } // 执行查询 this.page(ordersPage, ordersQueryWrapper); return ordersPage; } • 35 • 36
6. 更新订单状态
请求地址:http://localhost:8080/order
请求类型:PUT
请求参数:Orders
OrdersController:
/** * 更新订单状态 * @param orders * @return */ @PutMapping() public R<String> toSend(@RequestBody Orders orders){ log.info("派送订单:{}",orders.toString()); ordersService.updateById(orders); return R.success("派送成功"); }
四、移动端效果展示