一、用户端订单模块
1、查询历史订单 - GET接口
(1)需求分析
(2)代码开发
【1】controller层
java/** * 历史订单查询 * @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)
java/** * 订单状态 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) { //获取当前订单id Long orderId = orders.getId(); //用订单id去获取其对应的订单明细 List<OrderDetail> dlist = orderDetailMapper.getByOrderId(orderId); //封装到VO //OrderVO继承了Orders中的所有数据(在OrderVO中存在extend Orders),因此可以将orders赋值给ordervo OrderVO orderVO = new OrderVO(); BeanUtils.copyProperties(orders,orderVO); orderVO.setOrderDetailList(dlist); //封装好的VO加入结果列表中 list.add(orderVO); } } return new PageResult(page.getTotal(),list); }
【3】mapper层
1)OrderMapper
java/** * 订单分页查询 * @param ordersPageQueryDTO * @return */ Page<Orders> pageQuery(OrdersPageQueryDTO ordersPageQueryDTO);
2)OrderDetailMapper
java/** * 根据订单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
java<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层
java/** * 查询订单详情 * @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层
java/** * 查询订单详情 * @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层
java/** * 根据订单id查询订单 * @param id * @return */ @Select("select * from sky_take_out.orders where id = #{id}") Orders getById(Long id);
3、取消订单 - PUT接口
(1)需求分析
(2)代码开发
【1】controller层
java/** * 取消订单 * @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方法)
java/** * 取消订单 * @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层
java/** * 再来一单 * @param id * @return */ @PostMapping("/repetition/{id}") @ApiOperation("再来一单") public Result repetition(@PathVariable Long id){ orderService.repetition(id); return Result.success(); }
【2】service层
再来一单的业务逻辑就是把订单商品全部重新插入购物车中
- 通过订单id获取订单详情列表
- 循环将【订单中的每一个商品】赋值到【单个购物车记录对象】中
- 循环将【单个购物车记录对象】加入购物车列表中
- 把购物车列表通过shoppingCartMapper的insertBatch批量插入进购物车表
java/** * 再来一单 * @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
java/** * 批量插入购物车表 * @param shoppingCartList */ void insertBatch(List<ShoppingCart> shoppingCartList);
【4】mybatis层
1)ShoppingCartMapper
XML<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层
java/** * 订单搜索 * @param ordersPageQueryDTO * @return */ @GetMapping("/conditionSearch") @ApiOperation("订单搜索") public Result<PageResult> conditionSearch(OrdersPageQueryDTO ordersPageQueryDTO){ PageResult pageResult = orderService.conditionSearch(ordersPageQueryDTO); return Result.success(pageResult); }
【2】service层
这里处理逻辑其实就是分页查询的常规思路,只是将【打包数据】和【把订单菜品列表转换为字符串】两部分提取成两个函数
java/** * 订单搜索 * @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() > 0 if(!CollectionUtils.isEmpty(ordersList)) { for (Orders orders : ordersList) { //把每一个订单取出来赋值给OrderVO OrderVO orderVO = new OrderVO(); BeanUtils.copyProperties(orders,orderVO); //创建订单菜品字符串并加入OrderVO String str = getOrderDishesStr(orders); orderVO.setOrderDishes(str); //加入orderVO列表 orderVOList.add(orderVO); } } return orderVOList; } //创建订单菜品字符串并加入OrderVO public 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层
java/** * 各个状态的订单数量统计 * @return */ @GetMapping("/statistics") @ApiOperation("各个状态的订单数量统计") public Result<OrderStatisticsVO> statistics(){ OrderStatisticsVO orderStatisticsVO = orderService.statistics(); return Result.success(orderStatisticsVO); }
【2】service层
java/** * 各个状态的订单数量统计 * 订单状态 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层
java/** * 根据状态查询订单数量 * @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层
java/** * 查询订单详情 * @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层
java/** * 根据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层
java/** * 接单 * @param ordersConfirmDTO * @return */ @PutMapping("/confirm") @ApiOperation("接单") public Result confirm(@RequestBody OrdersConfirmDTO ordersConfirmDTO){ orderService.confirm(ordersConfirmDTO); return Result.success(); }
【2】service层
接单业务逻辑就是:修改订单状态
java/** * 接单 * 订单状态 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层
java/** * 拒单 * @param ordersRejectionDTO * @return */ @PutMapping("/rejection") @ApiOperation("拒单") public Result reject(@RequestBody OrdersRejectionDTO ordersRejectionDTO) throws Exception { orderService.reject(ordersRejectionDTO); return Result.success(); }
【2】service层
如果【订单不存在】或【订单状态不为"待接单"】,则需要抛出异常,其他业务逻辑也是修改订单信息
java/** * 拒单 * @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层
java/** * 商家取消订单 * @param ordersCancelDTO * @return */ @PutMapping("/cancel") @ApiOperation("取消订单") public Result cancel(@RequestBody OrdersCancelDTO ordersCancelDTO) throws Exception { orderService.shopCancel(ordersCancelDTO); return Result.success(); }
【2】service层
java/** * 商家取消订单 * @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层
java/** * 派送订单 * @param id * @return */ @PutMapping("/delivery/{id}") @ApiOperation("派送订单") public Result delivery(@PathVariable Long id){ orderService.delivery(id); return Result.success(); }
【2】service层
java/** * 派送订单 * @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层
java/** * 完成订单 * @param id * @return */ @PutMapping("/complete/{id}") @ApiOperation("完成订单") public Result complete(@PathVariable Long id){ orderService.complete(id); return Result.success(); }
【2】service层
java/** * 完成订单 * @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首页
- 登录百度地图开放平台:https://lbsyun.baidu.com/
进入控制台,创建应用,获取AK:
2、代码开发
(1)application.yml
(2)OrderServiceImpl
OrderServiceImpl中注入下面的配置项
java@Value("${sky.shop.address}") private String shopAddress; @Value("${sky.baidu.ak}") private String ak;
在OrderServiceImpl中提供校验方法
java/** * 检查客户的收货地址是否超出配送范围 * @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方法中调用上面的校验方法
java//检查用户的收货地址是否超出配送范围 checkOutOfRange(addressBook.getCityName() + addressBook.getDistrictName() + addressBook.getDetail());