当前位置: 首页 > news >正文

【苍穹外卖 Day09|订单管理与配送范围校验】

前言

Day09 的实战围绕订单模块展开:用户需要能查看和复用历史订单,商家需要按订单状态处理业务,同时系统还要在下单前判断收货地址是否处于配送范围内。

这几个功能共享同一条主线:订单状态必须按照业务规则流转,任何状态修改前都要先完成必要校验。文章结合当前项目代码,梳理用户端订单、商家端订单管理以及 5 公里配送范围校验的实现。

一、先确定订单的状态与流转规则

订单表中的 status 和 payStatus 分别描述业务状态与支付状态。当前项目定义如下:

publicstaticfinalIntegerPENDING_PAYMENT=1;publicstaticfinalIntegerTO_BE_CONFIRMED=2;publicstaticfinalIntegerCONFIRMED=3;publicstaticfinalIntegerDELIVERY_IN_PROGRESS=4;publicstaticfinalIntegerCOMPLETED=5;publicstaticfinalIntegerCANCELLED=6;publicstaticfinalIntegerUN_PAID=0;publicstaticfinalIntegerPAID=1;publicstaticfinalIntegerREFUND=2;

正常的状态流转为:待付款 → 待接单 → 已接单 → 派送中 → 已完成。用户或商家取消订单、商家拒单时,订单会进入已取消状态。

这里不能只根据前端按钮判断能否操作。服务层必须重新查询订单,并以数据库中的状态作为判断依据。例如商家接单时,当前订单必须处于待接单状态:

publicvoidconfirm(Longid){OrdersordersDB=requireOrder(id);if(!ordersDB.getStatus().equals(Orders.TO_BE_CONFIRMED)){thrownewOrderBusinessException(MessageConstant.ORDER_STATUS_ERROR);}Ordersorders=Orders.builder().id(ordersDB.getId()).status(Orders.CONFIRMED).build();orderMapper.update(orders);}

requireOrder 负责查询订单并处理订单不存在的情况;通过校验后,再构造只包含待更新字段的 Orders 对象交给 Mapper。这样可以避免前端重复提交或请求过期时把订单写入错误状态。

二、用户端历史订单:列表、详情、取消与再来一单

用户端接口统一位于/user/order下,Day09 新增的核心接口包括:

@GetMapping("/historyOrders")publicResult<PageResult>historyOrders(OrdersPageQueryDTOordersPageQueryDTO){returnResult.success(orderService.historyOrders(ordersPageQueryDTO));}@GetMapping("/orderDetail/{id}")publicResult<OrderVO>orderDetail(@PathVariableLongid){returnResult.success(orderService.orderDetail(id));}@PutMapping("/cancel/{id}")publicResultcancel(@PathVariableLongid){orderService.cancel(id);returnResult.success();}@PostMapping("/repetition/{id}")publicResultrepetition(@PathVariableLongid){orderService.repetition(id);returnResult.success();}

1. 历史订单列表为什么还要查询订单明细

订单列表不仅要展示订单号、金额和下单时间,还要展示菜品信息。因此订单分页查询完成后,服务层会遍历当前页订单,根据订单 id 查询订单明细表,并将结果放进 OrderVO。

publicPageResulthistoryOrders(OrdersPageQueryDTOordersPageQueryDTO){ordersPageQueryDTO.setUserId(BaseContext.getCurrentId());PageHelper.startPage(ordersPageQueryDTO.getPage(),ordersPageQueryDTO.getPageSize());Page<OrderVO>page=orderMapper.pageQuery(ordersPageQueryDTO);for(OrderVOorderVO:page.getResult()){List<OrderDetail>details=orderdetailMapper.getByOrderId(orderVO.getId());orderVO.setOrderDetailList(details);tryFillAddress(orderVO);}returnnewPageResult(page.getTotal(),page.getResult());}

BaseContext.getCurrentId() 把当前登录用户 id 写入查询条件,保证用户只能看自己的订单。Mapper 中的用户 id、状态、订单号、手机号和下单时间均是可选条件,最后按下单时间倒序排列:

<selectid="pageQuery"resultType="com.sky.vo.OrderVO">select * from orders<where><iftest="number != null and number != ''">and number like concat('%', #{number}, '%')</if><iftest="userId != null">and user_id = #{userId}</if><iftest="status != null">and status = #{status}</if></where>order by order_time desc</select>

2. 查询详情时补齐数据,并校验订单归属

用户请求订单详情时,不能直接用订单 id 返回数据。项目先验证订单是否属于当前用户,再将订单、订单明细和地址组装为 OrderVO:

privateOrdersrequireUserOrder(Longid){Ordersorders=requireOrder(id);if(!orders.getUserId().equals(BaseContext.getCurrentId())){thrownewOrderBusinessException(MessageConstant.ORDER_NOT_FOUND);}returnorders;}publicOrderVOorderDetail(Longid){returntoOrderVO(requireUserOrder(id));}

这一步既是业务校验,也是接口数据隔离。测试时可用 A 用户的 token 请求 B 用户的订单详情,预期应返回订单不存在或业务异常,而不是暴露订单内容。

3. 取消订单与再来一单

当前项目的取消逻辑会校验订单归属,将状态改为已取消并记录取消时间;当支付状态为已支付时,会进入退款处理入口。当前代码以日志模拟退款申请,文章不把它描述为真实微信退款已经完成。

再来一单不重新创建订单,而是将原订单明细转换为购物车数据,再批量插入购物车表:

publicvoidrepetition(Longid){LonguserId=BaseContext.getCurrentId();List<OrderDetail>details=orderdetailMapper.getByOrderId(id);List<ShoppingCart>carts=details.stream().map(detail->{ShoppingCartcart=newShoppingCart();BeanUtils.copyProperties(detail,cart,"id");cart.setUserId(userId);cart.setCreateTime(LocalDateTime.now());returncart;}).collect(Collectors.toList());shoppingCartMapper.insertBatch(carts);}

复制时排除原订单明细 id,避免把旧记录的主键带入购物车。接口返回成功后,前端跳转购物车即可让用户确认数量、口味和地址,再重新提交订单。

三、商家端订单管理:围绕状态处理订单

商家端接口前缀为 /admin/order,包含订单搜索、状态数量统计、详情查询和订单状态处理。

1. 搜索与状态数量统计

订单搜索复用 OrdersPageQueryDTO,支持订单号、手机号、状态、开始时间和结束时间等条件。分页结果中的每条订单还会补充菜品名称和数量,供管理端列表直接展示。

状态统计则按待接单、待派送和派送中三种状态分别查询:

publicOrderStatisticsVOstatistics(){OrderStatisticsVOresult=newOrderStatisticsVO();result.setToBeConfirmed(orderMapper.countByStatus(Orders.TO_BE_CONFIRMED));result.setConfirmed(orderMapper.countByStatus(Orders.CONFIRMED));result.setDeliveryInProgress(orderMapper.countByStatus(Orders.DELIVERY_IN_PROGRESS));returnresult;}

对应 SQL 很简单,但状态常量必须和前端标签一致:

<selectid="countByStatus"resultType="java.lang.Integer">select count(*) from orders where status = #{status}</select>

2. 拒单、派送和完成订单的校验差异

拒单只允许待接单订单操作;已支付订单需要进入退款入口,并记录拒单原因和取消时间:

publicvoidreject(OrdersRejectionDTOdto){OrdersordersDB=orderMapper.getById(dto.getId());if(ordersDB==null||!ordersDB.getStatus().equals(Orders.TO_BE_CONFIRMED)){thrownewOrderBusinessException(MessageConstant.ORDER_STATUS_ERROR);}if(ordersDB.getPayStatus().equals(Orders.PAID)){log.info("申请退款:{}",ordersDB.getNumber());}orderMapper.update(Orders.builder().id(ordersDB.getId()).status(Orders.CANCELLED).rejectionReason(dto.getRejectionReason()).cancelTime(LocalDateTime.now()).build());}

派送订单必须从已接单状态进入派送中,完成订单必须从派送中进入已完成。把每个动作的前置状态写死在服务层,订单流转才不会被任意接口跳过。

操作允许的原状态更新后的状态额外信息
接单待接单已接单
拒单待接单已取消拒单原因、取消时间;已支付时退款
派送已接单派送中
完成派送中已完成
商家取消订单存在已取消取消时间;已支付时退款

四、下单前增加 5 公里配送范围校验

实战要求规定:收货地址距离门店超过 5 公里时,下单失败。当前项目将门店地址和百度地图 AK 放入环境配置,再在提交订单过程中调用地图 Web 服务。

公共配置文件只保留结构,真实门店地址和 AK 应放到开发环境配置中,提交博客时必须脱敏:

sky:shop:address:${sky.shop.address}baidu:ak:${sky.baidu.ak}

服务层的处理顺序是:

  1. 调用地理编码接口,把门店地址转换成经纬度;
  2. 用同一接口解析用户完整收货地址;
  3. 调用驾车路线规划接口,得到路线距离;
  4. 当距离大于 5000 米时抛出“超出配送范围”异常;
  5. 只有校验通过,才插入订单和订单明细。

核心拦截点位于 submitOrder:

orders.setAddress(buildAddress(addressBook));orders.setUserId(userId);checkOutOfRange(orders.getAddress());orderMapper.insert(orders);

checkOutOfRange 中先调用地理编码接口解析两个地址,再调用驾车路线规划接口。路线接口的起点和终点坐标格式是“纬度,经度”;项目解析首条路线的 distance 字段,并以米为单位与 5000 比较。

JSONObjectresult=jsonObject.getJSONObject("result");JSONArrayroutes=(JSONArray)result.get("routes");Integerdistance=(Integer)((JSONObject)routes.get(0)).get("distance");if(distance>5000){thrownewOrderBusinessException("超出配送范围");}

地址解析或路线规划响应的 status 不为 0 时,当前代码会抛出“店铺地址解析失败”“收货地址解析失败”或“配送路线规划失败”。这样能阻止无效地址继续创建订单,也能让前端给出明确提示。

五、如何测试与排查

可以先通过 Swagger 接口文档逐个验证,再进行前后端联调。建议按以下顺序准备数据:

  1. 创建一笔已支付、待接单订单,验证商家接单、拒单和取消订单;
  2. 将订单接单后,验证派送;再将订单改为派送中,验证完成;
  3. 使用当前用户查询历史订单与详情,再换一个用户 token 验证订单归属校验;
  4. 调用再来一单,检查购物车中是否新增原订单的菜品和套餐明细;
  5. 分别使用门店附近和明显超出范围的收货地址提交订单,观察下单成功与“超出配送范围”异常。

排查配送校验时,优先检查门店地址是否配置、AK 是否有效、地址是否能被解析,以及路线响应中是否存在 result.routes。排查订单状态异常时,先查询数据库中的状态和支付状态,再确认调用的接口是否满足对应动作的前置状态。

总结

Day09 完成的是一条完整的订单处理链路:用户端能管理历史订单,商家端能按状态处理订单,系统在创建订单前还会根据实际驾车路线拦截超出范围的地址。

实现订单模块时,每一次状态变化都要有明确的前置条件、需要记录的字段和可验证的结果。

http://www.jsqmd.com/news/1211015/

相关文章:

  • Arduino制作辉光钟:复古电子与现代创客的完美结合
  • UE5智慧城市数字孪生:从蓝图交互到打包部署的完整实践
  • 8259A PIC中断机制解析与内核开发实践
  • WPS文档加密全攻略:从原理到实战,守护你的数字隐私
  • 2026 年当下,江东正规的石头工厂找哪家,挖出财富的秘密:那些被遗忘的石头价值 - 行业推荐官[官方】--
  • PCB设计制造与工程实践全解析
  • PCIe P2P传输技术解析与应用实践
  • 数字世界的“秘密潜入”:你的浏览器,可能正成为黑客的“隐形跳板”
  • 2026年7月新疆股权转让/新疆一般纳税人代账服务平台哪家靠谱_新疆银穗财税服务集团股份有限公司 - 品牌宣传支持者
  • 2026年7月目前比较好的螺旋钢管生产厂家口碑推荐,涂塑复合钢管/承插口连接钢管/防腐钢管/螺旋钢管,螺旋钢管企业哪家强 - 品牌推荐师
  • Agent Runtime归零时代:会话即事件日志的工程范式革命
  • QML Text 点击交互与富文本
  • Flutter iOS编译错误排查与解决方案
  • C++实现矩形排样优化:基于遗传算法的工程实践
  • 现代技术栈如何自动解决传统开发难题:从内存管理到安全防护
  • 现代C++线程池:从原理到生产级实现,掌握高性能并发编程
  • 江诗丹顿中国官方售后服务中心|地址与官方客服热线权威信息声明(2026年7月更新) - 江诗丹顿服务中心
  • 掌握AI Agent记忆秘诀:从“会调用工具“到“持续工作“的进阶之路(收藏学习)
  • TM4C123 GPIO与DMA外设标识寄存器详解与实战应用
  • UVa 696 How Many Knights
  • Windows内存压缩性能优化指南
  • 汽车电子驻车系统(EPB)核心技术解析
  • 二维运动目标如何实现稳定跟踪?卡尔曼滤波位置与速度估计实战
  • STM32生理监测装置设计与低功耗优化实践
  • AI 漫剧角色标记提效:50集4000~5000句台词自动标记流程
  • AI 每日学习 — Spring Boot 中基于 SSE 的大模型流式响应:从协议到生产落地
  • 微信小程序视频平台开发全攻略
  • C++ I/O流深度解析:从基础操作到性能优化与避坑指南
  • 生产级机器学习服务部署:FastAPI+Docker+K8s工程实践
  • 第三章 工质状态与热力学过程(二)---可压缩性与理想气体模型