实战内容 完成用户端历史订单模块、商家端订单管理模块相关业务新功能开发和已有功能优化,具体任务列表如下:
1. 新功能开发 用户端历史订单模块:
商家端订单管理模块:
订单搜索
各个状态的订单数量统计
查询订单详情
接单
拒单
取消订单
派送订单
完成订单
2. 已有功能优化 优化用户下单功能,加入校验逻辑,如果用户的收货地址距离商家门店超出配送范围(配送范围为5公里内),则下单失败。
提示:
1. 基于百度地图开放平台实现(https://lbsyun.baidu.com/)
2. 注册账号--->创建应用获取AK(服务端应用)--->调用接口
相关接口
https://lbsyun.baidu.com/index.php?title=webapi/guide/webservice-geocoding
https://lbsyun.baidu.com/index.php?title=webapi/directionlite-v1
商家门店地址可以配置在配置文件中,例如:
1 2 3 sky: shop: address: 北京市海淀区上地十街10号
实战要求
根据产品原型进行需求分析和接口设计
根据接口设计进行代码实现
分别通过swagger接口文档和前后端联调进行功能测试
分组实战(具体任务分工由组长分配)
用户端历史订单模块 1. 查询历史订单 1.1 需求分析和设计 产品原型:
业务规则
分页查询历史订单
可以根据订单状态查询
展示订单数据时,需要展示的数据包括:下单时间、订单状态、订单金额、订单明细(商品名称、图片)
接口设计:参见接口文档
1.2 代码实现 1.2.1 user/OrderController 1 2 3 4 5 6 7 8 9 10 11 12 13 14 @GetMapping("/historyOrders") @ApiOperation("历史订单查询") public Result<PageResult> page (int page, int pageSize, Integer status) { PageResult pageResult = orderService.pageQuery4User(page, pageSize, status); return Result.success(pageResult); }
1.2.2 OrderService 1 2 3 4 5 6 7 8 PageResult pageQuery4User (int page, int pageSize, Integer status) ;
1.2.3 OrderServiceImpl 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 public PageResult pageQuery4User (int pageNum, int pageSize, Integer status) { PageHelper.startPage(pageNum, pageSize); OrdersPageQueryDTO ordersPageQueryDTO = new OrdersPageQueryDTO (); ordersPageQueryDTO.setUserId(BaseContext.getCurrentId()); ordersPageQueryDTO.setStatus(status); Page<Orders> page = orderMapper.pageQuery(ordersPageQueryDTO); List<OrderVO> list = new ArrayList (); if (page != null && page.getTotal() > 0 ) { for (Orders orders : page) { Long orderId = orders.getId(); List<OrderDetail> orderDetails = orderDetailMapper.getByOrderId(orderId); OrderVO orderVO = new OrderVO (); BeanUtils.copyProperties(orders, orderVO); orderVO.setOrderDetailList(orderDetails); list.add(orderVO); } } return new PageResult (page.getTotal(), list); }
1.2.4 OrderMapper 1 2 3 4 5 Page<Orders> pageQuery (OrdersPageQueryDTO ordersPageQueryDTO) ;
1.2.5 OrderMapper.xml 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 <select id ="pageQuery" resultType ="Orders" > select * from orders <where > <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 ="userId != null" > and user_id = #{userId} </if > <if test ="status != null" > and status = #{status} </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 >
1.2.6 OrderDetailMapper 1 2 3 4 5 6 7 @Select("select * from order_detail where order_id = #{orderId}") List<OrderDetail> getByOrderId (Long orderId) ;
1.3 功能测试 略
2. 查询订单详情 2.1 需求分析和设计 产品原型:
接口设计:参见接口文档
2.2 代码实现 2.2.1 user/OrderController 1 2 3 4 5 6 7 8 9 10 11 12 @GetMapping("/orderDetail/{id}") @ApiOperation("查询订单详情") public Result<OrderVO> details (@PathVariable("id") Long id) { OrderVO orderVO = orderService.details(id); return Result.success(orderVO); }
2.2.2 OrderService 1 2 3 4 5 6 OrderVO details (Long id) ;
2.2.3 OrderServiceImpl 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 public OrderVO details (Long id) { Orders orders = orderMapper.getById(id); List<OrderDetail> orderDetailList = orderDetailMapper.getByOrderId(orders.getId()); OrderVO orderVO = new OrderVO (); BeanUtils.copyProperties(orders, orderVO); orderVO.setOrderDetailList(orderDetailList); return orderVO; }
2.2.4 OrderMapper 1 2 3 4 5 6 @Select("select * from orders where id=#{id}") Orders getById (Long id) ;
2.3 功能测试 略
3. 取消订单 3.1 需求分析和设计 产品原型:
业务规则:
待支付和待接单状态下,用户可直接取消订单
商家已接单状态下,用户取消订单需电话沟通商家
派送中状态下,用户取消订单需电话沟通商家
如果在待接单状态下取消订单,需要给用户退款
取消订单后需要将订单状态修改为“已取消”
接口设计:参见接口文档
3.2 代码实现 3.2.1 user/OrderController 1 2 3 4 5 6 7 8 9 10 11 @PutMapping("/cancel/{id}") @ApiOperation("取消订单") public Result cancel (@PathVariable("id") Long id) throws Exception { orderService.userCancelById(id); return Result.success(); }
3.2.2 OrderService 1 2 3 4 5 void userCancelById (Long id) throws Exception;
3.2.3 OrderServiceImpl 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 public void userCancelById (Long id) throws Exception { Orders ordersDB = orderMapper.getById(id); if (ordersDB == null ) { throw new OrderBusinessException (MessageConstant.ORDER_NOT_FOUND); } if (ordersDB.getStatus() > 2 ) { throw new OrderBusinessException (MessageConstant.ORDER_STATUS_ERROR); } Orders orders = new Orders (); orders.setId(ordersDB.getId()); if (ordersDB.getStatus().equals(Orders.TO_BE_CONFIRMED)) { weChatPayUtil.refund( ordersDB.getNumber(), ordersDB.getNumber(), new BigDecimal (0.01 ), new BigDecimal (0.01 )); orders.setPayStatus(Orders.REFUND); } orders.setStatus(Orders.CANCELLED); orders.setCancelReason("用户取消" ); orders.setCancelTime(LocalDateTime.now()); orderMapper.update(orders); }
3.3 功能测试 略
4. 再来一单 4.1 需求分析和设计 产品原型:
接口设计:参见接口文档
业务规则:
4.2 代码实现 4.2.1 user/OrderController 1 2 3 4 5 6 7 8 9 10 11 12 @PostMapping("/repetition/{id}") @ApiOperation("再来一单") public Result repetition (@PathVariable Long id) { orderService.repetition(id); return Result.success(); }
4.2.2 OrderService 1 2 3 4 5 6 void repetition (Long id) ;
4.2.3 OrderServiceImpl 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 public void repetition (Long id) { Long userId = BaseContext.getCurrentId(); List<OrderDetail> orderDetailList = orderDetailMapper.getByOrderId(id); 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()); shoppingCartMapper.insertBatch(shoppingCartList); }
1 2 3 4 5 6 void insertBatch (List<ShoppingCart> shoppingCartList) ;
1 2 3 4 5 6 7 8 <insert id ="insertBatch" parameterType ="list" > insert into shopping_cart (name, image, user_id, dish_id, setmeal_id, dish_flavor, number, amount, create_time) values <foreach collection ="shoppingCartList" item ="sc" separator ="," > (#{sc.name},#{sc.image},#{sc.userId},#{sc.dishId},#{sc.setmealId},#{sc.dishFlavor},#{sc.number},#{sc.amount},#{sc.createTime}) </foreach > </insert >
4.3 功能测试 略
商家端订单管理模块 1. 订单搜索 1.1 需求分析和设计 产品原型:
业务规则:
输入订单号/手机号进行搜索,支持模糊搜索
根据订单状态进行筛选
下单时间进行时间筛选
搜索内容为空,提示未找到相关订单
搜索结果页,展示包含搜索关键词的内容
分页展示搜索到的订单数据
接口设计:参见接口文档
1.2 代码实现 1.2.1 admin/OrderController 在admin包下创建OrderController
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 @RestController("adminOrderController") @RequestMapping("/admin/order") @Slf4j @Api(tags = "订单管理接口") public class OrderController { @Autowired private OrderService orderService; @GetMapping("/conditionSearch") @ApiOperation("订单搜索") public Result<PageResult> conditionSearch (OrdersPageQueryDTO ordersPageQueryDTO) { PageResult pageResult = orderService.conditionSearch(ordersPageQueryDTO); return Result.success(pageResult); } }
1.2.2 OrderService 1 2 3 4 5 6 PageResult conditionSearch (OrdersPageQueryDTO ordersPageQueryDTO) ;
1.2.3 OrderServiceImpl 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 public PageResult conditionSearch (OrdersPageQueryDTO ordersPageQueryDTO) { PageHelper.startPage(ordersPageQueryDTO.getPage(), ordersPageQueryDTO.getPageSize()); Page<Orders> page = orderMapper.pageQuery(ordersPageQueryDTO); List<OrderVO> orderVOList = getOrderVOList(page); return new PageResult (page.getTotal(), orderVOList); } private List<OrderVO> getOrderVOList (Page<Orders> page) { List<OrderVO> orderVOList = new ArrayList <>(); List<Orders> ordersList = page.getResult(); if (!CollectionUtils.isEmpty(ordersList)) { for (Orders orders : ordersList) { OrderVO orderVO = new OrderVO (); BeanUtils.copyProperties(orders, orderVO); String orderDishes = getOrderDishesStr(orders); orderVO.setOrderDishes(orderDishes); orderVOList.add(orderVO); } } return orderVOList; } private String getOrderDishesStr (Orders orders) { List<OrderDetail> orderDetailList = orderDetailMapper.getByOrderId(orders.getId()); List<String> orderDishList = orderDetailList.stream().map(x -> { String orderDish = x.getName() + "*" + x.getNumber() + ";" ; return orderDish; }).collect(Collectors.toList()); return String.join("" , orderDishList); }
1.3 功能测试 略
2. 各个状态的订单数量统计 2.1 需求分析和设计 产品原型:
接口设计:参见接口文档
2.2 代码实现 2.2.1 admin/OrderController 1 2 3 4 5 6 7 8 9 10 11 @GetMapping("/statistics") @ApiOperation("各个状态的订单数量统计") public Result<OrderStatisticsVO> statistics () { OrderStatisticsVO orderStatisticsVO = orderService.statistics(); return Result.success(orderStatisticsVO); }
2.2.2 OrderService 1 2 3 4 5 OrderStatisticsVO statistics () ;
2.2.3 OrderServiceImpl 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 public OrderStatisticsVO statistics () { Integer toBeConfirmed = orderMapper.countStatus(Orders.TO_BE_CONFIRMED); Integer confirmed = orderMapper.countStatus(Orders.CONFIRMED); Integer deliveryInProgress = orderMapper.countStatus(Orders.DELIVERY_IN_PROGRESS); OrderStatisticsVO orderStatisticsVO = new OrderStatisticsVO (); orderStatisticsVO.setToBeConfirmed(toBeConfirmed); orderStatisticsVO.setConfirmed(confirmed); orderStatisticsVO.setDeliveryInProgress(deliveryInProgress); return orderStatisticsVO; }
2.2.4 OrderMapper 1 2 3 4 5 6 @Select("select count(id) from orders where status = #{status}") Integer countStatus (Integer status) ;
2.3 功能测试 略
3. 查询订单详情 3.1 需求分析和设计 产品原型:
业务规则:
订单详情页面需要展示订单基本信息(状态、订单号、下单时间、收货人、电话、收货地址、金额等)
订单详情页面需要展示订单明细数据(商品名称、数量、单价)
接口设计:参见接口文档
3.2 代码实现 3.2.1 admin/OrderController 1 2 3 4 5 6 7 8 9 10 11 12 @GetMapping("/details/{id}") @ApiOperation("查询订单详情") public Result<OrderVO> details (@PathVariable("id") Long id) { OrderVO orderVO = orderService.details(id); return Result.success(orderVO); }
3.3 功能测试 略
4. 接单 4.1 需求分析和设计 产品原型:
业务规则:
接口设计:参见接口文档
4.2 代码实现 4.2.1 admin/OrderController 1 2 3 4 5 6 7 8 9 10 11 @PutMapping("/confirm") @ApiOperation("接单") public Result confirm (@RequestBody OrdersConfirmDTO ordersConfirmDTO) { orderService.confirm(ordersConfirmDTO); return Result.success(); }
4.2.2 OrderService 1 2 3 4 5 6 void confirm (OrdersConfirmDTO ordersConfirmDTO) ;
4.2.3 OrderServiceImpl 1 2 3 4 5 6 7 8 9 10 11 12 13 public void confirm (OrdersConfirmDTO ordersConfirmDTO) { Orders orders = Orders.builder() .id(ordersConfirmDTO.getId()) .status(Orders.CONFIRMED) .build(); orderMapper.update(orders); }
4.3 功能测试 略
5. 拒单 5.1 需求分析和设计 产品原型:
业务规则:
商家拒单其实就是将订单状态修改为“已取消”
只有订单处于“待接单”状态时可以执行拒单操作
商家拒单时需要指定拒单原因
商家拒单时,如果用户已经完成了支付,需要为用户退款
接口设计:参见接口文档
5.2 代码实现 5.2.1 admin/OrderController 1 2 3 4 5 6 7 8 9 10 11 @PutMapping("/rejection") @ApiOperation("拒单") public Result rejection (@RequestBody OrdersRejectionDTO ordersRejectionDTO) throws Exception { orderService.rejection(ordersRejectionDTO); return Result.success(); }
5.2.2 OrderService 1 2 3 4 5 6 void rejection (OrdersRejectionDTO ordersRejectionDTO) throws Exception;
5.2.3 OrderServiceImpl 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 public void rejection (OrdersRejectionDTO ordersRejectionDTO) throws Exception { Orders ordersDB = orderMapper.getById(ordersRejectionDTO.getId()); 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 = new Orders (); orders.setId(ordersDB.getId()); orders.setStatus(Orders.CANCELLED); orders.setRejectionReason(ordersRejectionDTO.getRejectionReason()); orders.setCancelTime(LocalDateTime.now()); orderMapper.update(orders); }
5.3 功能测试 略
6. 取消订单 6.1 需求分析和设计 产品原型:
业务规则:
取消订单其实就是将订单状态修改为“已取消”
商家取消订单时需要指定取消原因
商家取消订单时,如果用户已经完成了支付,需要为用户退款
接口设计:参见接口文档
6.2 代码实现 6.2.1 admin/OrderController 1 2 3 4 5 6 7 8 9 10 11 @PutMapping("/cancel") @ApiOperation("取消订单") public Result cancel (@RequestBody OrdersCancelDTO ordersCancelDTO) throws Exception { orderService.cancel(ordersCancelDTO); return Result.success(); }
6.2.2 OrderService 1 2 3 4 5 6 void cancel (OrdersCancelDTO ordersCancelDTO) throws Exception;
6.2.3 OrderServiceImpl 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 public void cancel (OrdersCancelDTO ordersCancelDTO) throws Exception { 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 = new Orders (); orders.setId(ordersCancelDTO.getId()); orders.setStatus(Orders.CANCELLED); orders.setCancelReason(ordersCancelDTO.getCancelReason()); orders.setCancelTime(LocalDateTime.now()); orderMapper.update(orders); }
6.3 功能测试 略
7. 派送订单 7.1 需求分析和设计 产品原型:
业务规则:
派送订单其实就是将订单状态修改为“派送中”
只有状态为“待派送”的订单可以执行派送订单操作
接口设计:参见接口文档
7.2 代码实现 7.2.1 admin/OrderController 1 2 3 4 5 6 7 8 9 10 11 @PutMapping("/delivery/{id}") @ApiOperation("派送订单") public Result delivery (@PathVariable("id") Long id) { orderService.delivery(id); return Result.success(); }
7.2.2 OrderService 1 2 3 4 5 6 void delivery (Long id) ;
7.2.3 OrderServiceImpl 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 public void delivery (Long id) { Orders ordersDB = orderMapper.getById(id); if (ordersDB == null || !ordersDB.getStatus().equals(Orders.CONFIRMED)) { throw new OrderBusinessException (MessageConstant.ORDER_STATUS_ERROR); } Orders orders = new Orders (); orders.setId(ordersDB.getId()); orders.setStatus(Orders.DELIVERY_IN_PROGRESS); orderMapper.update(orders); }
7.3 功能测试 略
8. 完成订单 8.1 需求分析和设计 产品原型:
业务规则:
完成订单其实就是将订单状态修改为“已完成”
只有状态为“派送中”的订单可以执行订单完成操作
接口设计:参见接口文档
8.2 代码实现 8.2.1 admin/OrderController 1 2 3 4 5 6 7 8 9 10 11 @PutMapping("/complete/{id}") @ApiOperation("完成订单") public Result complete (@PathVariable("id") Long id) { orderService.complete(id); return Result.success(); }
8.2.2 OrderService 1 2 3 4 5 6 void complete (Long id) ;
8.2.3 OrderServiceImpl 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 public void complete (Long id) { Orders ordersDB = orderMapper.getById(id); if (ordersDB == null || !ordersDB.getStatus().equals(Orders.DELIVERY_IN_PROGRESS)) { throw new OrderBusinessException (MessageConstant.ORDER_STATUS_ERROR); } Orders orders = new Orders (); orders.setId(ordersDB.getId()); orders.setStatus(Orders.COMPLETED); orders.setDeliveryTime(LocalDateTime.now()); orderMapper.update(orders); }
8.3 功能测试 略
校验收货地址是否超出配送范围 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:
相关接口:
https://lbsyun.baidu.com/index.php?title=webapi/guide/webservice-geocoding
https://lbsyun.baidu.com/index.php?title=webapi/directionlite-v1
2. 代码开发 2.1 application.yml 配置外卖商家店铺地址和百度地图的AK:
2.2 OrderServiceImpl 改造OrderServiceImpl,注入上面的配置项:
1 2 3 4 5 @Value("${sky.shop.address}") private String shopAddress;@Value("${sky.baidu.ak}") private String ak;
在OrderServiceImpl中提供校验方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 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 ){ throw new OrderBusinessException ("超出配送范围" ); } }
在OrderServiceImpl的submitOrder方法中调用上面的校验方法: