点餐与餐具费逻辑分析.md 10 KB

StoreOrderController 点餐与餐具费逻辑分析

基于 StoreOrderControllerCartServiceImplStoreOrderServiceImpl 的代码梳理。


一、点餐逻辑(菜品)

1.1 入口与流程

接口 方法 说明
/cart/add addCartItem 添加菜品到购物车
/cart/update updateCartItem 修改购物车中某菜品数量
/cart/remove removeCartItem 从购物车删除某菜品
/cart/clear clearCart 清空购物车(保留已下单商品和餐具)
/create createOrder 从购物车生成订单(下单)
  • 点餐:先加购物车(add/update),再创建订单 /create,把当前购物车内容落单。
  • 加餐:与点餐同一套流程。用户先增减购物车(加菜或改数量),再点下单;若该桌已有待支付订单,createOrder更新该订单(用当前购物车全量覆盖订单明细),从而实现加餐。没有单独的「加餐接口」,全程用「购物车 + 下单/更新订单」完成。

1.2 购物车项的两个数量

  • quantity:当前购物车中的数量(用户可改)。
  • lockedQuantity:已下单数量(每次调用 createOrder 后由 lockCartItems 更新,只增不减,直到支付后清空购物车)。

二、餐具费逻辑

2.1 餐具在系统中的表示

  • 餐具不是门店菜品表里的菜,而是购物车里的特殊项
    • cuisineId = -1(常量 TABLEWARE_CUISINE_ID
    • 名称固定为「餐具」,单价来自门店配置 store_info.tableware_fee(单位:元/人)。

2.2 两个入口

接口 方法 说明
POST /cart/set-diner-count setDinerCount(tableId, dinerCount) 设置用餐人数,自动添加或更新餐具
PUT /cart/update-tableware updateTablewareQuantity(tableId, quantity) 直接改餐具数量

setDinerCount(设置用餐人数)

  • 入参:tableIddinerCount(必须 > 0)。
  • 逻辑:
    • 若购物车已有餐具项:把该餐具的 数量 = dinerCount,单价按当前门店 tableware_fee 重算,小计 = 单价 × dinerCount。
    • 若没有餐具项:新增一条 cuisineId=-1 的项,数量=dinerCount,单价来自门店,小计同上。
  • 下单后(餐具 lockedQuantity > 0):不允许把人数改得小于已下单数量,否则抛「餐具数量不能少于已下单数量(x)」。

updateTablewareQuantity(更新餐具数量)

  • 入参:tableIdquantity(≥ 0)。
  • 逻辑:
    • quantity = 0:调用 removeItem 删除餐具项;若餐具已有 lockedQuantity > 0 会抛「商品已下单,不允许删除」。
    • quantity > 0:若已有餐具项且 lockedQuantity > 0,则 quantity 不能小于 lockedQuantity,否则抛「餐具数量不能少于已下单数量(x)」;否则新建或更新餐具数量与单价。

2.3 餐具单价来源

  • CartServiceImpl.getTablewareUnitPrice(storeId)
    • store_info 表,用 store_info.tableware_fee(Integer,单位一般为分或元,以当前代码使用方式看是按「元」参与计算)。
    • 门店不存在或未配置则按 0.00 元。

三、什么时候点餐(菜品)可以增减

  • 可加
    • 未下单:直接 addCartItemupdateCartItem 增加数量。
    • 已下单:同一桌有待支付订单时,可再次 addCartItem(会在原数量上叠加,再次下单时 lockCartItems 会更新 lockedQuantity)。
  • 可减
    • 仅当该菜品的「当前数量」大于「已下单数量」时才能减:
    • updateCartItem 把数量改为比 lockedQuantity 小会报错:「商品数量不能少于已下单数量(x),该数量已下单」。
    • 即:已下单的那部分数量不能减,只能减「多出来的」部分。
  • 可删
    • 仅当该菜品 lockedQuantity == 0 或 null 时才能 removeCartItem
    • 若有已下单数量,删除会报错:「商品已下单,已下单数量为 x,不允许删除」。

清空购物车clearCart):

  • 未下单的菜品:删除。
  • 已下单的菜品:保留,且把当前数量恢复为已下单数量(lockedQuantity),相当于只清掉「多选未下单」的部分。
  • 餐具:始终保留,不删。

四、什么时候餐具可以增减(与菜品规则一致)

  • 未下单时:可随意增减。通过 setDinerCountupdateTablewareQuantity 修改人数/餐具数量;updateTablewareQuantity(0) 可删除餐具项。
  • 下单后(餐具已有 lockedQuantity > 0)
    • 可加:可随时通过 setDinerCountupdateTablewareQuantity 增加人数/餐具数量。
    • 不能减:数量不能少于已下单数量。若 setDinerCount(dinerCount)updateTablewareQuantity(quantity) 导致餐具数量小于 lockedQuantity,会报错「餐具数量不能少于已下单数量(x)」。
    • 不能删updateTablewareQuantity(0) 会调 removeItem,若餐具 lockedQuantity > 0 会报错「商品已下单,已下单数量为 x,不允许删除」。

五、流程小结(含下单、锁定)

  1. 用户选桌、设置用餐人数setDinerCount → 购物车出现或更新餐具项(数量 = 人数,单价 = 门店餐具费)。
  2. 用户加菜addCartItem / updateCartItem → 购物车有菜品与餐具。
  3. 用户下单createOrder → 订单写入订单表+订单明细(含菜品和餐具),然后 lockCartItems:所有购物车项(含餐具)的 lockedQuantity 设为当前数量。
  4. 之后:
    • 菜品:只能加不能减到低于 lockedQuantity,不能删除已下单部分。
    • 餐具:与菜品一致,只能加不能减到低于 lockedQuantity,不能删除已下单的餐具。
  5. 支付完成后:购物车被清空(或重置),lockedQuantity 不再存在,下一轮重新点餐、重新设人数和餐具。

六、说明与建议

  1. 餐具与菜品规则一致
    updateTablewareQuantitysetDinerCount 已与菜品一致:下单后(lockedQuantity > 0)只能增不能减,数量不能小于已下单数量;updateTablewareQuantity(0) 删除餐具时,若已下单则走 removeItem 报错不允许删除。
  2. createOrder 时餐具费
    创建订单时,订单头的 tablewareFeetotalAmountpayAmount完全采用前端传参,后端不校验;订单明细中的餐具行仍来自购物车(单价、数量、小计)。若前端与购物车不一致,会出现订单头与明细不一致,需前端保证与购物车一致或后端在需要时做一致性校验。

七、潜在问题(已知)

  1. 创建订单金额 vs 支付时重算

    • 创建订单时:totalAmounttablewareFeediscountAmountpayAmount 完全采用前端传参,后端不校验。
    • 支付时:payOrder 会按订单的 totalAmounttablewareFee 和优惠券重新计算 discountAmountpayAmount 并写回订单,即支付时以后端计算为准。
    • 若前端在创建订单时传的优惠/实付与后端规则不一致,支付后订单上的优惠与实付会被覆盖。需与产品确认:是否接受「创建时仅展示用、支付以重算为准」的语义。
  2. 更新订单优惠券时后端重算
    updateOrderCoupon 会根据订单的 totalAmounttablewareFee 和新的优惠券重新计算 discountAmountpayAmount 并更新订单,与支付逻辑一致。若希望「改券不改金额、由前端传」需单独约定并改逻辑。

  3. 门店餐具费单位
    store_info.tableware_fee 为 Integer,当前代码按「数值直接作为元」参与计算(如 1 表示 1 元)。若实际配置为「分」,需在 getTablewareUnitPrice 中做单位换算(如 ÷100),并与配置约定一致。


八、潜藏风险(排查结论)

  1. 【高】用户将菜品/餐具数量减至「已下单数量」后,再次点「下单」会报错,订单未同步(按产品策略不修复)

    • 现象createOrder 要求「有新增商品或商品数量增加」才放行;若用户仅把某品数量减少后点下单,接口会报错,订单未同步。
    • 产品策略下单后只能增不能减,已下单数量不允许通过再次下单减少,故不开放「减量更新订单」。
    • 建议:前端在用户减少数量后提示「已下单部分不可减少,如需改单请联系店员」或类似文案,避免用户误以为可减量后再次下单同步。
  2. 【中】清空购物车时餐具数量未恢复为已下单数量(已修复)

    • 现象clearCart 对菜品会把「当前数量」恢复为 lockedQuantity,此前对餐具只做「保留」、不恢复数量。
    • 修复:清空购物车时,若餐具存在且 lockedQuantity > 0,将其数量与小计恢复为 lockedQuantity,与菜品逻辑一致。
  3. 【低】Redis 过期后从 DB 加载购物车的时序

    • 现象:购物车先写 Redis 再异步写 DB;若 Redis 过期(如 24h)且异步写未完成或失败,loadCartFromDatabase 可能读到旧数据。
    • 后果:极端情况下购物车内容或 lockedQuantity 与当前订单不一致。
    • 建议:保证 lockCartItems 后同步写 DB 一次(或关键操作后同步落库),或缩短 Redis TTL + 监控异步写失败。
  4. 【低】订单与购物车展示不一致的体验

    • 因问题 1,当用户「只减不加」时无法通过再次下单同步,订单与购物车可能不一致,前端若以购物车为主展示会误导用户,支付金额以订单为准。
    • 建议:前端在「下单」失败时提示「当前无新增或加量,无法更新订单;若已减少数量,请先加一件商品再下单,或联系店员」;或后端按问题 1 放宽更新条件后,以订单为准展示待支付内容。

文档基于当前代码逻辑整理,若后续接口或锁定策略有改动,需同步更新本文档。