【项目实战 Day9】springboot + vue 苍穹外卖系统(用户端订单模块 + 商家端订单管理模块 完结)
一、用户端订单模块
1、查询历史订单 - GET接口
(1)需求分析
(2)代码开发
【1】controller层
/*** 历史订单查询* @param page* @param pageSize* @param status 订单状态 1待付款 2待接单 3已接单 4派送中 5已完成 6已取消* @return*/@GetMapping("/historyOrders")@ApiOperation("历史订单查询")public Result<PageResult> page(int page,int pageSize,Integer status){PageResult pageResult = orderService.pageQueryHistory(page,pageSize,status);return Result.success(pageResult);}
【2】service层
- 使用分页插件PageHelper设置分页参数
- 创建OrdersPageQueryDTO,并绑定相应参数(userId、status),传入mapper层进行条件分页查询,获得相应的所有订单信息
- 判断返回的订单信息是否为空,若不为空,遍历每一个订单信息order
- 通过订单id在ordersDetailMapper中获取对应订单明细列表
- 创建OrderVO,并将订单信息order赋值给OrderVO,再加上之前获取的对应订单明细列表
- 接着将每一个封装好的OrderVO加入list列表
- 最后返回PageResult(page.getTotal()订单总数,list)
/*** 订单状态 1待付款 2待接单 3已接单 4派送中 5已完成 6已取消* @param pageNum* @param pageSize* @param status* @return*/public PageResult pageQueryHistory(int pageNum, int pageSize, Integer status) {//1.设置分页参数,使用分页插件开始分页PageHelper.startPage(pageNum,pageSize);//条件分页查询OrdersPageQueryDTO ordersPageQueryDTO = new OrdersPageQueryDTO();ordersPageQueryDTO.setUserId(BaseContext.getCurrentId());ordersPageQueryDTO.setStatus(status);//返回分页结果(相当于返回所有历史订单数据)Page<Orders> page = orderMapper.pageQuery(ordersPageQueryDTO);//创建OrderVO列表用于封装返回数据List<OrderVO> list = new ArrayList<>();if(page != null && page.getTotal() > 0){//遍历分页中每一个订单对象for (Orders orders : page) {//获取当前订单idLong orderId = orders.getId();//用订单id去获取其对应的订单明细List<OrderDetail> dlist = orderDetailMapper.getByOrderId(orderId);//封装到VO//OrderVO继承了Orders中的所有数据(在OrderVO中存在extend Orders),因此可以将orders赋值给ordervoOrderVO orderVO = new OrderVO();BeanUtils.copyProperties(orders,orderVO);orderVO.setOrderDetailList(dlist);//封装好的VO加入结果列表中list.add(orderVO);}}return new PageResult(page.getTotal(),list);}
【3】mapper层
1)OrderMapper
/*** 订单分页查询* @param ordersPageQueryDTO* @return*/Page<Orders> pageQuery(OrdersPageQueryDTO ordersPageQueryDTO);
2)OrderDetailMapper
/*** 根据订单id查询对应订单明细* @param orderId* @return*/@Select("select * from sky_take_out.order_detail where order_id = #{orderId}")List<OrderDetail> getByOrderId(Long orderId);
【4】mybatis层
1)OrderMapper
<select id="pageQuery" resultType="com.sky.entity.Orders">select * from sky_take_out.orders<where><if test="status != null">and status = #{status}</if><if test="userId != null">and user_id = #{userId}</if><if test="number != null and number!=''">and number like concat('%',#{number},'%')</if><if test="phone != null and phone!=''">and phone like concat('%',#{phone},'%')</if><if test="beginTime != null">and order_time >= #{beginTime}</if><if test="endTime != null">and order_time <= #{endTime}</if></where>order by order_time desc</select>
2、查询订单详情 - GET接口
(1)需求分析
(2)代码开发
【1】controller层
/*** 查询订单详情* @param id* @return*/@GetMapping("/orderDetail/{id}")@ApiOperation("查询订单详情")public Result<OrderVO> details(@PathVariable Long id){OrderVO orderVO = orderService.getById(id);return Result.success(orderVO);}
【2】service层
/*** 查询订单详情* @param id* @return*/public OrderVO getById(Long id) {OrderVO orderVO = new OrderVO();Orders order = orderMapper.getById(id);BeanUtils.copyProperties(order,orderVO);List<OrderDetail> list = orderDetailMapper.getByOrderId(id);orderVO.setOrderDetailList(list);return orderVO;}
【3】mapper层
/*** 根据订单id查询订单* @param id* @return*/@Select("select * from sky_take_out.orders where id = #{id}")Orders getById(Long id);
3、取消订单 - PUT接口
(1)需求分析
(2)代码开发
【1】controller层
/*** 取消订单* @param id* @return*/@PutMapping("/cancel/{id}")@ApiOperation("取消订单")public Result cancel(@PathVariable Long id) throws Exception {orderService.cancel(id);return Result.success();}
【2】service层
取消订单的实质是修改订单信息(订单状态、取消原因、取消时间)
- 判断订单是否存在
- 判断订单状态是否可以取消【3已接单、4派送中、5已完成、6已取消】不可以取消
- 如果订单处于待接单状态取消,需要进行退款并更新支付状态
- 最后更新订单状态、取消原因、取消时间(用OrderMapper的update方法)
/*** 取消订单* @param id*/public void cancel(Long id) throws Exception {//1.校验订单是否存在Orders od = orderMapper.getById(id);if(od == null){throw new OrderBusinessException(MessageConstant.ORDER_NOT_FOUND);}//2.订单若为【3已接单、4派送中、5已完成、6已取消】则不能取消if(od.getStatus() > 2){throw new OrderBusinessException(MessageConstant.ORDER_STATUS_ERROR);}//3.若订单处于待接单状态下取消,需要进行退款if(od.getStatus().equals(Orders.TO_BE_CONFIRMED)){ // //调用微信支付退款接口 // weChatPayUtil.refund( // od.getNumber(), //商户订单号 // od.getNumber(), //商户退款单号 // new BigDecimal(0.01),//退款金额,单位 元 // new BigDecimal(0.01));//原订单金额od.setPayStatus(Orders.REFUND);}//4.更新订单状态、取消原因、取消时间od.setStatus(Orders.CANCELLED);od.setCancelReason("用户取消");od.setCancelTime(LocalDateTime.now());orderMapper.update(od);}
4、再来一单 - POST接口
(1)需求分析
(2)代码开发
【1】controller层
/*** 再来一单* @param id* @return*/@PostMapping("/repetition/{id}")@ApiOperation("再来一单")public Result repetition(@PathVariable Long id){orderService.repetition(id);return Result.success();}
【2】service层
再来一单的业务逻辑就是把订单商品全部重新插入购物车中
- 通过订单id获取订单详情列表
- 循环将【订单中的每一个商品】赋值到【单个购物车记录对象】中
- 循环将【单个购物车记录对象】加入购物车列表中
- 把购物车列表通过shoppingCartMapper的insertBatch批量插入进购物车表
/*** 再来一单* @param id*/public void repetition(Long id) {//再来一单逻辑就是原订单商品重新加入购物车Long currentId = BaseContext.getCurrentId();//1.获取该订单的订单详情列表List<OrderDetail> orderDetailList = orderDetailMapper.getByOrderId(id);List<ShoppingCart> shoppingCartList = new ArrayList<>();//2.生成若干购物车商品对象,并加入购物车列表for (OrderDetail orderDetail : orderDetailList) {ShoppingCart shoppingCart = new ShoppingCart();// 将订单详情中的菜品信息重新复制到购物车对象中,"id" 参数表示忽略复制 id 字段(因为购物车需要生成新的id)BeanUtils.copyProperties(orderDetail,shoppingCart,"id");shoppingCart.setUserId(currentId);shoppingCart.setCreateTime(LocalDateTime.now());shoppingCartList.add(shoppingCart);}//第2部分等价写法: // // 将订单详情对象转换为购物车对象 // List<ShoppingCart> shoppingCartList = orderDetailList.stream().map(x -> { // ShoppingCart shoppingCart = new ShoppingCart(); // // // 将原订单详情里面的菜品信息重新复制到购物车对象中 // BeanUtils.copyProperties(x, shoppingCart, "id"); // shoppingCart.setUserId(userId); // shoppingCart.setCreateTime(LocalDateTime.now()); // // return shoppingCart; // }).collect(Collectors.toList());//3.将购物车列表中的内容批量插入购物车表shoppingCartMapper.insertBatch(shoppingCartList);}
【3】mapper层
1)ShoppingCartMapper
/*** 批量插入购物车表* @param shoppingCartList*/void insertBatch(List<ShoppingCart> shoppingCartList);
【4】mybatis层
1)ShoppingCartMapper
<insert id="insertBatch" parameterType="list">insert into sky_take_out.shopping_cart(name, image, user_id, dish_id, setmeal_id, dish_flavor, amount, create_time)VALUES<foreach collection="shoppingCartList" item="sc" separator=",">(#{sc.name},#{sc.image},#{sc.userId},#{sc.dishId},#{sc.setmealId},#{sc.dishFlavor},#{sc.amount},#{sc.createTime})</foreach></insert>
二、商家端订单管理模块
1、订单搜索 - GET接口
(1)需求分析
- 通过需求分析中【返回数据】我们可以知道,要返回的结果为【Orders】+【OrderVO中的orderDishes字符串】
- 而且已知OrderVO中已经继承了Orders的数据,所以只需要把Orders的数据赋值给OrderVO,再加上订单包含菜品的字符串即可
- 因此我们Service层需要封装好这部分返回数据
(2)代码开发
【1】controller层
/*** 订单搜索* @param ordersPageQueryDTO* @return*/@GetMapping("/conditionSearch")@ApiOperation("订单搜索")public Result<PageResult> conditionSearch(OrdersPageQueryDTO ordersPageQueryDTO){PageResult pageResult = orderService.conditionSearch(ordersPageQueryDTO);return Result.success(pageResult);}
【2】service层
这里处理逻辑其实就是分页查询的常规思路,只是将【打包数据】和【把订单菜品列表转换为字符串】两部分提取成两个函数
/*** 订单搜索* @param ordersPageQueryDTO* @return*/public PageResult conditionSearch(OrdersPageQueryDTO ordersPageQueryDTO) {//开始分页,查询分页参数PageHelper.startPage(ordersPageQueryDTO.getPage(),ordersPageQueryDTO.getPageSize());//Page<Orders> 对象包含了分页查询的完整结果Page<Orders> page = orderMapper.pageQuery(ordersPageQueryDTO);//把order + ordervo数据打包成list列表List<OrderVO> orderVOList = getOrderVOList(page);return new PageResult(page.getTotal(),orderVOList);}//把order + ordervo数据打包成list列表public List<OrderVO> getOrderVOList(Page<Orders> page){List<OrderVO> orderVOList = new ArrayList<>();List<Orders> ordersList = page.getResult();//ordersList != null && ordersList.size() > 0if(!CollectionUtils.isEmpty(ordersList)) {for (Orders orders : ordersList) {//把每一个订单取出来赋值给OrderVOOrderVO orderVO = new OrderVO();BeanUtils.copyProperties(orders,orderVO);//创建订单菜品字符串并加入OrderVOString str = getOrderDishesStr(orders);orderVO.setOrderDishes(str);//加入orderVO列表orderVOList.add(orderVO);}}return orderVOList;}//创建订单菜品字符串并加入OrderVOpublic String getOrderDishesStr(Orders orders){List<OrderDetail> orderDetailList = orderDetailMapper.getByOrderId(orders.getId());// 将每一条订单菜品信息拼接为字符串(格式:宫保鸡丁*3;)List<String> orderDishList = orderDetailList.stream().map(x->{String str = x.getName() + "*" + x.getNumber();return str;}).collect(Collectors.toList());return String.join(" ",orderDishList);}
2、各个状态的订单数量统计 - GET接口
(1)需求分析
(2)代码开发
【1】controller层
/*** 各个状态的订单数量统计* @return*/@GetMapping("/statistics")@ApiOperation("各个状态的订单数量统计")public Result<OrderStatisticsVO> statistics(){OrderStatisticsVO orderStatisticsVO = orderService.statistics();return Result.success(orderStatisticsVO);}
【2】service层
/*** 各个状态的订单数量统计* 订单状态 1待付款 2待接单 3已接单 4派送中 5已完成 6已取消* @return*/public OrderStatisticsVO statistics() {OrderStatisticsVO orderStatisticsVO = new OrderStatisticsVO();Integer confirmed = orderMapper.getStatus(Orders.COMPLETED);Integer deliver = orderMapper.getStatus(Orders.DELIVERY_IN_PROGRESS);Integer tobeconfirmed = orderMapper.getStatus(Orders.TO_BE_CONFIRMED);orderStatisticsVO.setConfirmed(confirmed);orderStatisticsVO.setDeliveryInProgress(deliver);orderStatisticsVO.setToBeConfirmed(tobeconfirmed);return orderStatisticsVO;}
【3】mapper层
/*** 根据状态查询订单数量* @param status* @return*/@Select("select count(id) from sky_take_out.orders where status = #{status}")int getStatus(Integer status);
3、查询订单详情 - GET接口
(1)需求分析
(2)代码开发
【1】controller层
/*** 查询订单详情* @param id* @return*/@GetMapping("/details/{id}")@ApiOperation("查询订单详情")public Result<OrderVO> orderDetails(@PathVariable Long id){OrderVO orderVO = orderService.getDetailById(id);return Result.success(orderVO);}
【2】service层
/*** 根据id查询订单详情* @param id* @return*/public OrderVO getDetailById(Long id) {OrderVO orderVO = new OrderVO();Orders orders = orderMapper.getById(id);List<OrderDetail> orderDetailList = orderDetailMapper.getByOrderId(id);BeanUtils.copyProperties(orders,orderVO);orderVO.setOrderDetailList(orderDetailList);String str = getOrderDishesStr(orders);orderVO.setOrderDishes(str);return orderVO;}
4、接单 - PUT接口
(1)需求分析
(2)代码开发
【1】controller层
/*** 接单* @param ordersConfirmDTO* @return*/@PutMapping("/confirm")@ApiOperation("接单")public Result confirm(@RequestBody OrdersConfirmDTO ordersConfirmDTO){orderService.confirm(ordersConfirmDTO);return Result.success();}
【2】service层
接单业务逻辑就是:修改订单状态
/*** 接单* 订单状态 1待付款 2待接单 3已接单 4派送中 5已完成 6已取消* @param ordersConfirmDTO*/public void confirm(OrdersConfirmDTO ordersConfirmDTO) {Orders orders = Orders.builder().id(ordersConfirmDTO.getId()).status(Orders.CONFIRMED).build();orderMapper.update(orders);}
5、拒单 - PUT接口
(1)需求分析
(2)代码开发
【1】controller层
/*** 拒单* @param ordersRejectionDTO* @return*/@PutMapping("/rejection")@ApiOperation("拒单")public Result reject(@RequestBody OrdersRejectionDTO ordersRejectionDTO) throws Exception {orderService.reject(ordersRejectionDTO);return Result.success();}
【2】service层
如果【订单不存在】或【订单状态不为“待接单”】,则需要抛出异常,其他业务逻辑也是修改订单信息
/*** 拒单* @param ordersRejectionDTO*/public void reject(OrdersRejectionDTO ordersRejectionDTO) throws Exception {Orders ordersDB = orderMapper.getById(ordersRejectionDTO.getId());//只有订单存在且状态为2(待接单)才可以拒单if(ordersDB == null || !ordersDB.getStatus().equals(Orders.TO_BE_CONFIRMED)){throw new OrderBusinessException(MessageConstant.ORDER_STATUS_ERROR);}// //支付状态 // Integer payStatus = ordersDB.getPayStatus(); // if (payStatus == Orders.PAID) { // //用户已支付,需要退款 // String refund = weChatPayUtil.refund( // ordersDB.getNumber(), // ordersDB.getNumber(), // new BigDecimal(0.01), // new BigDecimal(0.01)); // log.info("申请退款:{}", refund); // }Orders orders = Orders.builder().id(ordersRejectionDTO.getId()).status(Orders.CANCELLED).cancelTime(LocalDateTime.now()).rejectionReason(ordersRejectionDTO.getRejectionReason()).build();orderMapper.update(orders);}
6、取消订单 - PUT接口
(1)需求分析
(2)代码开发
【1】controller层
/*** 商家取消订单* @param ordersCancelDTO* @return*/@PutMapping("/cancel")@ApiOperation("取消订单")public Result cancel(@RequestBody OrdersCancelDTO ordersCancelDTO) throws Exception {orderService.shopCancel(ordersCancelDTO);return Result.success();}
【2】service层
/*** 商家取消订单* @param ordersCancelDTO*/public void shopCancel(OrdersCancelDTO ordersCancelDTO) { // Orders ordersDB = orderMapper.getById(ordersCancelDTO.getId());// //支付状态 // Integer payStatus = ordersDB.getPayStatus(); // if (payStatus == 1) { // //用户已支付,需要退款 // String refund = weChatPayUtil.refund( // ordersDB.getNumber(), // ordersDB.getNumber(), // new BigDecimal(0.01), // new BigDecimal(0.01)); // log.info("申请退款:{}", refund); // }Orders orders = Orders.builder().id(ordersCancelDTO.getId()).status(Orders.CANCELLED).cancelReason(ordersCancelDTO.getCancelReason()).cancelTime(LocalDateTime.now()).build();orderMapper.update(orders);}
7、派送订单 - PUT接口
(1)需求分析
(2)代码开发
【1】controller层
/*** 派送订单* @param id* @return*/@PutMapping("/delivery/{id}")@ApiOperation("派送订单")public Result delivery(@PathVariable Long id){orderService.delivery(id);return Result.success();}
【2】service层
/*** 派送订单* @param id*/public void delivery(Long id) {Orders orderDB = orderMapper.getById(id);if(orderDB == null || !orderDB.getStatus().equals(Orders.CONFIRMED)){throw new OrderBusinessException(MessageConstant.ORDER_STATUS_ERROR);}Orders orders = Orders.builder().id(id).status(Orders.DELIVERY_IN_PROGRESS).build();orderMapper.update(orders);}
8、完成订单 - PUT接口
(1)需求分析
(2)代码开发
【1】controller层
/*** 完成订单* @param id* @return*/@PutMapping("/complete/{id}")@ApiOperation("完成订单")public Result complete(@PathVariable Long id){orderService.complete(id);return Result.success();}
【2】service层
/*** 完成订单* @param id*/public void complete(Long id) {Orders orderDB = orderMapper.getById(id);if(orderDB == null || !orderDB.getStatus().equals(Orders.DELIVERY_IN_PROGRESS)){throw new OrderBusinessException(MessageConstant.ORDER_STATUS_ERROR);}Orders orders = Orders.builder().id(id).status(Orders.COMPLETED).deliveryTime(LocalDateTime.now()).build();orderMapper.update(orders);}
三、校验收货地址是否超出配送范围
1、环境准备
- 注册账号:https://passport.baidu.com/v2/?reg&tt=1671699340600&overseas=&gid=CF954C2-A3D2-417F-9FE6-B0F249ED7E33&tpl=pp&u=https%3A%2F%2Flbsyun.baidu.com%2Findex.php%3Ftitle%3D%E9%A6%96%E9%A1%B5
- 登录百度地图开放平台:https://lbsyun.baidu.com/
进入控制台,创建应用,获取AK:
2、代码开发
(1)application.yml
(2)OrderServiceImpl
OrderServiceImpl中注入下面的配置项
@Value("${sky.shop.address}")private String shopAddress;@Value("${sky.baidu.ak}")private String ak;
在OrderServiceImpl中提供校验方法
/*** 检查客户的收货地址是否超出配送范围* @param address*/private void checkOutOfRange(String address) {Map map = new HashMap();map.put("address",shopAddress);map.put("output","json");map.put("ak",ak);//获取店铺的经纬度坐标String shopCoordinate = HttpClientUtil.doGet("https://api.map.baidu.com/geocoding/v3", map);JSONObject jsonObject = JSON.parseObject(shopCoordinate);if(!jsonObject.getString("status").equals("0")){throw new OrderBusinessException("店铺地址解析失败");}//数据解析JSONObject location = jsonObject.getJSONObject("result").getJSONObject("location");String lat = location.getString("lat");String lng = location.getString("lng");//店铺经纬度坐标String shopLngLat = lat + "," + lng;map.put("address",address);//获取用户收货地址的经纬度坐标String userCoordinate = HttpClientUtil.doGet("https://api.map.baidu.com/geocoding/v3", map);jsonObject = JSON.parseObject(userCoordinate);if(!jsonObject.getString("status").equals("0")){throw new OrderBusinessException("收货地址解析失败");}//数据解析location = jsonObject.getJSONObject("result").getJSONObject("location");lat = location.getString("lat");lng = location.getString("lng");//用户收货地址经纬度坐标String userLngLat = lat + "," + lng;map.put("origin",shopLngLat);map.put("destination",userLngLat);map.put("steps_info","0");//路线规划String json = HttpClientUtil.doGet("https://api.map.baidu.com/directionlite/v1/driving", map);jsonObject = JSON.parseObject(json);if(!jsonObject.getString("status").equals("0")){throw new OrderBusinessException("配送路线规划失败");}//数据解析JSONObject result = jsonObject.getJSONObject("result");JSONArray jsonArray = (JSONArray) result.get("routes");Integer distance = (Integer) ((JSONObject) jsonArray.get(0)).get("distance");if(distance > 5000){//配送距离超过5000米throw new OrderBusinessException("超出配送范围");}}
在OrderServiceImpl的submitOrder方法中调用上面的校验方法
//检查用户的收货地址是否超出配送范围checkOutOfRange(addressBook.getCityName() + addressBook.getDistrictName() + addressBook.getDetail());