旧物回收系统怎么开发从品类建模到上门回收调度的工程实践旧物回收类平台的技术难点从来不在“做一个表单提交页面”而在于三件事物品怎么被准确地描述、价值怎么被合理地估算、人怎么被高效地调度上门。围绕这三个问题一套可用的旧物回收系统通常由品类中心、估值引擎、回收单状态机、调度派单池和多端应用层组成。本文按开发顺序拆解每一层的设计要点与落地经验代码示例基于 Java Spring Boot MySQL UniApp 这一常见组合思路可平移到其他技术栈。一、领域建模品类树、成色与回收单状态机回收业务的个坑是把“物品”当成一个扁平的商品表。实际上同一件旧家电按品牌、型号、年份、功能完好度、外观磨损度会拆出成百上千种组合。合理的做法是品类树 属性模板 成色枚举三层结构。品类树负责归类属性模板负责差异化字段手机看内存和电池健康度空调看匹数和是否缺氟成色枚举负责统一话语体系避免用户各写各的。-- 品类表支持无限级materialized path 便于整棵子树查询CREATETABLEcategory(idBIGINTPRIMARYKEYAUTO_INCREMENT,parent_idBIGINTNOTNULLDEFAULT0,nameVARCHAR(64)NOTNULL,pathVARCHAR(255)NOTNULLCOMMENT如 /1/12/135/,attr_schema JSONNULLCOMMENT该品类下的动态属性定义,statusTINYINTNOTNULLDEFAULT1,KEYidx_path(path));-- 回收单状态机是整条链路的主线CREATETABLErecycle_order(idBIGINTPRIMARYKEYAUTO_INCREMENT,order_noVARCHAR(32)NOTNULL,user_idBIGINTNOTNULL,category_idBIGINTNOTNULL,snapshotJSONNOTNULLCOMMENT下单时的品类/成色快照防止配置变更导致历史单失真,estimateDECIMAL(10,2)NULLCOMMENT系统估价结果仅作业务字段,statusVARCHAR(24)NOTNULL,address_idBIGINTNOTNULL,worker_idBIGINTNULL,created_atDATETIMENOTNULLDEFAULTCURRENT_TIMESTAMP,UNIQUEKEYuk_order_no(order_no),KEYidx_status_created(status,created_at));状态机建议显式建模不要用零散的 if-else 判断publicenumOrderStatus{DRAFT,// 用户填写中SUBMITTED,// 已提交待系统估价VALUATED,// 已出估价待用户确认CONFIRMED,// 用户确认进入派单池ASSIGNED,// 已指派回收员ON_SITE,// 已上门现场核验SETTLED,// 成交完成CANCELED,// 用户取消REJECTED;// 现场核验不符单方终止publicbooleancanTransferTo(OrderStatusnext){returnswitch(this){caseSUBMITTED-nextVALUATED||nextCANCELED;caseVALUATED-nextCONFIRMED||nextCANCELED;caseCONFIRMED-nextASSIGNED||nextCANCELED;caseASSIGNED-nextON_SITE||nextCONFIRMED;// 回收员拒单可退回caseON_SITE-nextSETTLED||nextREJECTED;default-false;};}}经验点snapshot字段必须存。品类属性和估值规则会随运营调整如果不做快照三个月后回查历史订单会得到完全不同的解释对账时非常痛苦。状态流转要落库审计表记录操作人、时间、前后状态这是后期排查纠纷的依据。二、估值引擎把规则从代码里赶出去估值是回收业务敏感的一层。硬编码在 Service 里的判断逻辑改一次要发一次版运营根本等不起。可行的做法是规则引擎 行情基准表规则描述“怎么算”行情表描述“按什么基准算”。规则用 JSON 配置支持条件命中、系数加权、上下限截断{categoryId:135,version:7,base:{source:market_quote,key:air_conditioner_1_5p},factors:[{field:ageYears,op:between,range:[0,3],weight:1.0},{field:ageYears,op:between,range:[4,8],weight:0.82},{field:condition,op:eq,value:GOOD,weight:1.0},{field:condition,op:eq,value:DAMAGED,weight:0.55},{field:hasInvoice,op:eq,value:true,weight:1.05}],clamp:{minRatio:0.5,maxRatio:1.2}}执行时把用户提交的属性与规则逐条匹配命中项权重相乘再乘行情基准后做截断。这样运营改规则只需新增一条 version 记录历史订单继续用旧版本重算可复现。几个必须做的约束估价结果只作为“参考区间”而非承诺值前端展示时要给出区间与二次核验提示行情基准表要保留时间序列方便回溯任意一天的基准规则命中数过少或过多都要打日志告警这通常意味着属性模板设计出了偏差。三、上门回收调度抢单池、地理围栏与幂等派单回收员的上门成本远高于纯线上业务调度做不好回收员的接单意愿会直接崩塌。调度层建议做两级系统预筛 抢单池。系统预筛负责把明显不合适的单子挡掉——距离超出服务半径、品类不在回收员技能标签内、当日负载已超阈值。剩下的进入抢单池由回收员自主选择。地理筛选不要用ST_Distance直接算全表先粗筛再精算-- 粗筛GeoHash 前缀匹配命中面积约为目标半径的 1~4 倍SELECTo.id,o.order_no,o.category_idFROMrecycle_order oWHEREo.statusCONFIRMEDANDo.geohashLIKE4g%-- 按中心点计算出的前缀ANDo.created_atNOW()-INTERVAL24HOURLIMIT200;拿到候选集后在应用层用 Haversine 精算距离再叠加回收员技能标签与当前负载做排序。派单必须幂等。回收员手速快、网络抖动、客户端重复提交都会造成同一订单被多人抢到。方案是乐观锁 约束双保险UPDATErecycle_orderSETstatusASSIGNED,worker_id#{workerId}, version version 1WHEREid#{orderId} AND status CONFIRMED AND version #{version};-- 影响行数为 0 即代表抢单失败直接返回友好提示如果业务上允许多个回收员参与同一订单例如大件需要两人上门就不要在订单表上抢改为建order_worker关联表并加UNIQUE KEY (order_id, worker_id)。负载均衡建议给回收员维护一个“当日已接单数 / 日承载上限”的滑动统计排序时作为惩罚项。否则热门区域的单子会被少数活跃账号全部吃掉新回收员接不到单很快流失。四、多端架构与配置一致性旧物回收的用户触点很分散小程序适合快速下单APP 适合回收员长时间在线接单H5 常被用作分享和轻量入口。多端并行的代价不是写页面而是同一套业务规则在四端各写一遍。几个收敛点品类树与属性模板由服务端下发客户端只负责渲染 JSON Schema禁止在各端硬编码品类判断。状态机的可用操作由服务端计算接口返回allowedActions避免客户端自己推断“这个状态下该显示哪个按钮”。估价接口做版本化如/api/v1/valuation客户端版本落后时仍能调用兼容逻辑。列表接口统一分页与排序字段小程序和 APP 复用同一套 DTO减少字段漂移。后台管理侧通常需要覆盖多角色平台管理、区域服务商、回收员、企业客户。权限模型建议用 RBAC 数据范围两层角色决定能看哪些菜单数据范围决定能看哪些订单本人 / 本团队 / 本区域 / 全部。这一层如果不提前设计后期加一个角色就要动一次核心查询。五、上线前容易忽略的几件事订单快照与版本估值规则、品类配置、地址数据都要在订单上留档。地址解析用户手填地址的脏数据比例很高接入地图 POI 检索并强制选择能大幅降低派单失败率。图片存储现场核验照片是核心凭证走对象存储 私有读 CDN不要直接存数据库。审计日志状态流转、估值结果、派单结果三类日志必须可追溯。压测目标抢单接口是并发尖峰重点压这一条链路而不是首页。FAQQ旧物回收系统的估值结果和现场核验结果差得多怎么办A这是常态而非异常。设计上把估价定位为“参考区间”在状态机里保留ON_SITE → SETTLED / REJECTED两条出口并允许现场录入实际核验参数重新计算。差异超过阈值的订单单独打标用于回看规则命中是否合理。Q品类树要不要一开始就做得很细A不要。建议先做两层属性模板做成可动态扩展的 JSON Schema。品类过细会导致冷启动阶段每个叶子节点下的样本都很少估值规则没有数据支撑反而算不准。等订单量积累起来再逐步下钻。Q抢单和派单应该选哪个A两者不互斥。常见做法是系统预筛后进入抢单池超时未接单则转为自动指派。纯抢单在低活跃时段会积压订单纯指派在高峰时段又容易造成回收员负载不均。Q多端并行开发怎么保证接口不各自为政A接口契约先于客户端开发用统一的状态枚举、统一的品类下发接口、统一的分页规范。客户端只做渲染和交互任何业务判断都回归服务端。这样新增一个端时工作量基本只剩 UI 层。
