1. 小区废品回收的混乱由来:为什么这个系统成了刚需
干这一行之前,我一直以为废品回收就是个"大爷骑三轮、小区楼下喊两嗓子"的纯体力活。直到某位做物业的朋友跟我算了一笔账,我才意识到这里面的信息断层有多严重。
他管着三个小区,每个月的废品处理量大概在8到12吨,纸箱、旧书、塑料瓶、金属罐、废旧家电什么都有。过去的方式是等一个固定的回收商隔几天来一趟,物业保洁把攒下来的废品堆在垃圾房旁边,既占地方又有消防隐患,赶上价格波动的月份,回收商还会找各种理由压低品级,一个月的保洁绩效就被废品这摊事拖后腿。另一边,小区业主的闲置物品其实更多,搬家剩下的纸箱、双十一攒的快递盒、淘汰的小家电,直接扔了可惜,但也懒得专门找人来收,最后全进了垃圾桶。
两头都有需求,中间缺一个"撮合和管理"的层。我们要做的这个基于微信小程序的小区废品收购管理系统,本质上就是把线下这摊灰色、无序、靠人情的交易搬到线上,改造成有单号、有流程、有定价、有结算的标准化服务。系统服务三类人:业主(卖废品的C端用户)、回收员(上门收货的运力)、物业管理员(小区废品经营的管理者)。
用小程序而不是独立App,原因很直接:用户不需要下载,微信里扫一扫就能用;微信支付的通道现成;小程序能拿到用户的手机号和登录态,不需要单独做注册体系。后端在ThinkPHP和Laravel两个框架上各做了一套,也是为了对比哪套更适合这种中后台需求重、并发没那么夸张、但又要求快速迭代的小型业务系统。
2. 技术选型的真实权衡:ThinkPHP和Laravel各做过一遍之后
很多人在项目开工前会纠结框架选型,我也把顺序反过来了:先按团队的老底子用ThinkPHP把原型跑通了,又用Laravel重写了一遍,最后总结出下面这些差异。如果你还在犹豫,这篇能帮你少走弯路。
2.1 ThinkPHP版本:胜在快,痛点在约束
ThinkPHP在国内的普及率确实高,尤其是中小团队,中文文档完善,上手曲线缓,从控制器到模型的写法非常直白。我们的第一版用的就是ThinkPHP 8,开发效率真的高,一个CURD模块从建表到接口跑通,一个下午就能搞定。
但项目做大以后,有两个问题开始暴露。第一个是目录结构太"随和",路由、控制器、模型之间的约定不够强,代码写久了容易出现同一个功能多个人用三种不同风格实现的情况。第二个是自动加载和依赖注入的容器能力相比Laravel偏弱,很多功能要靠手动引入或辅助函数完成,后续服务层拆分、队列任务、定时任务这些扩展,写起来都有些"手搓感"。
不过如果项目规模就是今天这种小区级别的管理系统,Team里的人都熟悉TP,那ThinkPHP完全够用。单机几千的并发撑得住,SQLite都跑得了,何况通常配的是MySQL。它最大的优点就是:人好招,坑好查,出了问题搜索引擎一搜全是答案。
2.2 Laravel版本:工程化更舒服,但学习曲线陡
第二版用Laravel 11重写,选它的核心原因是考虑系统后期要扩展会员积分体系、回收员绩效核算、财务审计报表,这些都对代码的可维护性提出了更高要求。
Laravel在这几个点上的优势非常明显:
- Eloquent ORM的关联模型写起来非常顺手,订单归属用户、回收员、小区,用with方法一句搞定关联查询,不会出现一堆手写JOIN。
- Migrations(数据库迁移)和Seeders(数据填充)配合,数据库结构可以随代码版本库一起管理。团队里谁改了表结构,看提交记录一清二楚,比用Navicat改完再口头通知其他人强太多。
- 内置了队列、任务调度、缓存、事件监听,这些是后期做消息通知和定时统计的底座,基本不需要额外引第三方库。
- 拿定时任务举例,在app/Console/Commands/里写一个命令,再在Kernel里注册调度,就可以实现"每天凌晨2点统计前一日订单并生成报表"这类操作,而在ThinkPHP里需要额外配一个Crontab才能实现。
代价就是学习门槛:路由、中间件、服务提供者、门面、容器这一堆概念,新手真的会懵。如果团队全是PHP新手,我反而建议先ThinkPHP跑起来。
2.3 双版本让我总结出的选型结论
两个框架放在一起,我的选择建议是这样的:
| 维度 | 适合使用ThinkPHP | 适合使用Laravel |
|---|---|---|
| 团队经验 | 熟悉TP、经验偏Web业务 | 熟悉现代PHP生态、依赖注入 |
| 项目规模 | MVP原型、生命周期短的内部工具 | 要长期迭代、多端接入的系统 |
| 并发压力 | 普通并发,单机部署 | 需要队列削峰、多服务扩展 |
| 第三方生态 | 简单够用 | 包丰富,Composer一把梭 |
| 代码规范 | 相对自由 | 约束强,代码风格更统一 |
最终我们保留了两套版本,业务功能完全一致,线上实际跑的是Laravel版本,因为后续还要接管理后台的权限系统和大屏数据展示。但如果让我给初次做类似小程序创业项目的团队建议,我会说:先ThinkPHP做出来,证明业务模型可行,再考虑要不要换Laravel。系统这东西,能跑起来产生收益永远比架构优雅更重要。
3. 先画清楚这张功能地图,再开始写代码
很多项目做到一半推倒重来,问题不在技术,而在一开始没想清楚"到底要给谁用、用在哪一步、解决什么动作"。废品收购系统的功能其实不复杂,但角色和场景交叉起来容易出现遗漏。
3.1 三条核心用户线的功能拆分
系统分三端:业主微信小程序端、回收员小程序端、物业后台管理端。
业主端的核心场景是"卖废品"这一件事,拆开看有六个动作:查看回收品类与今日价格、在线提交回收订单(选品类、填重量/约师傅上门)、查看订单状态(待接单/已接单/已上门/已完成)、确认称重金额、收款或换成积分、查看历史记录。这里需要注意,业主最在乎的不是有多少个优惠券,而是"我下单之后到底来不来、几点来、称准不准",所以订单状态流转必须清晰可查。
回收员端是作业工具,核心场景是"接到单、收完货、交结果"。包括查看可抢订单列表、接单/拒单、导航到小区门口、上门确认、录入各品类实际重量、拍照存证、生成结算单、查看当日收入。回收员用的手机普遍比较旧,页面要轻,操作按钮要大,尽量少输入多用选择。
后台管理端更像一个传统管理系统,负责:回收品类维护(废纸类、塑料类、金属类、家电类、织物类五大类)、价格模板设置(按地区和日期生效)、回收员审核与管理(身份认证、接单范围)、订单全流程查看与异常干预、财务报表导出、物业分成比例设置。
3.2 最容易被忽略的"价格模板"设计
我见过的同类项目(包括市面上一些已上线的)都容易栽在这个地方:废品价格不是固定的。纸箱价格一周可能变三次,铜价跟着期货走,塑料瓶分PET和PVC价格差出一倍。如果系统里只有一个固定的"废纸5毛一斤",那基本可以宣告项目一个月后就要刷维护工单了。
价格模板应该设计成这种结构:
- 每个回收品类(如"废纸-纸箱"、"废纸-书刊"、"塑料-PET瓶")都有独立的每日指导价。
- 后台可以创建"价格模板",包含一组品类的价格快照,指定生效日期和适用小区。
- 系统在业主端展示价格时,根据当天日期自动匹配模板;回收员在结算时也可以看到价格快照,避免"业主说是5毛,回收员说有4毛"的口舌之争。
- 价格调整要留操作日志,谁改的、几点改的、原价多少新价多少,一清二楚,以后扯皮有据可查。
这个设计建议所有做废品/回收/二手交易的人优先考虑,别等上线了再补。
3.3 多小区隔离与物业分成
不同小区的回收业务相互独立,回收员也有各自的负责片区,小区和小区之间的订单不能互相串。系统设计上用"小区ID"作为业务隔离维度——订单、价格模板、回收员归属、数据统计全部绑定小区ID,后台管理员可以管多个小区,但数据展示默认按小区隔开。
物业分成这个点在业务上非常敏感。有的小区物业是按废品总金额抽成,有的是收场地管理费。系统在结算模型里加上"分成比例"字段(物业自行在后台配置),回收订单完成时自动按比例生成物业收益记录。这样物业也会更愿意主动配合推广小程序,因为他知道每一单都算得清楚。
4. 数据库结构与订单状态机:这套核心设计决定系统长得怎么样
我见过有人拿这套系统当博客做的——用户表、订单表、商品表,完事了。但废品回收不是电商,订单的变数特别多:用户约了上门但人不在家、称重重量和预估不符、品级认定有争议、回收员接单后临时取消……所有这些都是订单状态和数据字段层面的规则问题。
4.1 核心数据表结构与设计理由
数据库主要分这几张核心表:
| 表名 | 核心字段 | 设计说明 |
|---|---|---|
| users | openid, nickname, phone, points, balance, role | 业主和回收员统一放一张表,用role区分,降低前期开发复杂度 |
| categories | name, unit, type_level, parent_id | 废品分类,两级结构,如"塑料类-PET瓶" |
| price_templates | effective_date, expired_date, community_id | 价格模板表,按日期范围生效 |
| price_items | template_id, category_id, price_per_unit | 每个模板下具体品类的单价快照 |
| orders | order_no, user_id, collector_id, community_id, status, total_amount, estimated_weight | 核心订单表,所有业务围绕它转 |
| order_items | order_id, category_id, real_weight, real_price, subtotal | 订单附属明细,记录每种废品的实际重量和金额 |
| settlements | order_id, user_amount, collector_amount, property_amount, is_settled | 结算表,一单一结,资金流向可追溯 |
| points_records | user_id, points, change_type, related_order | 积分流通记录 |
这里面几个容易踩坑的细节:
- order_no(订单号)不要用自增ID暴露给用户,用日期+随机字符串生成,比如"20250615-4832-KFQ",既方便客服凭单号查单,又避免用ID猜系统数据量。
- 价格一定要存快照(price_items里的price_per_unit),不能用"下单时查当前价格"代替。因为订单可能今天下单明天上门,废品价格以回收员上门当日的价格为准,但又要在业主下单时展示预估价格,两个价格可能不一致,必须都保留。
- 所有金额单位建议存储为分,避免浮点误差。PHP浮点数运算0.1+0.2不等于0.3的坑相信大家都有体感,用整型分存储,界面展示时再转为元。
- soft_deletes(软删除)一定要加,订单和用户都不允许物理删除,后期财务审计全靠它。
4.2 订单状态机的流转规则
订单状态是整个系统的脉搏。设计时要考虑每个状态下的用户操作和系统动作:
- PENDING(待接单):业主下单成功,系统推送通知给小区回收员
- ACCEPTED(已接单):回收员接单,此时锁定回收员,业主端可见是谁上门和联系方式
- ONGOING(回收中):回收员点击"开始回收",上门核实废品,逐项录入重量
- COMPLETED(已完成):回收员确认结算单,业主确认重量金额,系统自动发起资金分配
- CANCELED(已取消):订单作废,分两类,业主主动取消和回收员超时未接单自动取消
状态之间不能随意跳转,每个状态变更都要有触发条件,后端在模型层做校验,不能只靠前端按钮控制。比如ACCEPTED状态下,回收员失联导致订单无法推进,后台管理员要能手动改回PENDING让其他回收员接手,这个"后台干预"能力在实际运营中一定用得上。
4.3 计价与结算的具体实现逻辑
以用户卖一箱纸箱为例,跑一遍完整计价逻辑:
- 业主下单时选品类"废纸-纸箱",系统预估重量填5公斤,按当日模板单价0.8元/公斤,生成预估金额4元。
- 回收员上门实际称重为4.6公斤,录入时以实际重量为准,系统按当日单价计算实付3.68元。
- 结算模型按预设比例拆分:业主70%、回收员20%、物业10%(比例后台可配),业主落袋2.576元,保留两位小数,取2.58元;回收员0.736元取0.74元;物业0.368元取0.37元。这里要特别注意取整规则——我采用的是"各角色四舍五入到分"的方式,极小数量的差额(极致情况下可能差1分钱)计入平台运营收入,避免账不平。
- 用户既可以选择微信零钱实时到账(小额定账,满1元才可提现,减少支付手续费),也可以选择兑换为积分到账户里,积分用于抵扣物业费或社区超市消费。
这里有一个业务上很关键的产品点:金额不能直接全额走微信支付,因为微信支付每一笔都有手续费,几分钱几毛钱的交易手续费比例会吞掉利润。小额场景下积分化和余额化才是正解,系统保留一个可配置的"最低体现金额"开关,默认1元。
5. 小程序端实现技巧:登录态、消息推送与称重页面的交互设计
小程序端的体验是这个项目成败的关键,因为它面对的是最挑剔的用户——大爷大妈、上班族、全职妈妈,每个人使用习惯都不一样。
5.1 微信登录态的两种处理方式
说到微信登录,很多人第一反应是直接调用wx.login拿code换openid,再把openid当user_id存起来。但这里有个大坑:用户卸载小程序再重新打开,或者换手机登录同一个微信,openid不变,但session_key过期了,如果只用openid做登录态,会频繁出现掉线。
我们的做法是:
- 首次进入小程序,wx.login获取code,后端调用code2Session接口拿openid和session_key。
- 注册用户时生成一个自有的token(建议JWT),缓存到本地storage中,所有接口请求带上这个token。
- 调用用户敏感接口(如订单操作)时,用token解析用户ID,不去本地存储里读微信信息。
这套方案可以让用户"无感登录",也方便以后接入App端时复用同一套账号体系。不需要每次启动都去验证微信session。
5.2 称重录入页:给回收员用的界面,减法比加法更重要
回收员端最容易做烂的就是称重录入页。回收员上门后,面对一堆杂七杂八的废品,要给每个品类称重、记重量、拍照片,如果界面设计得跟后台表单一样,他填一次就崩溃了。
实测下来几个经验:
- 品类选择用大按钮分组展示,一屏内放完所有一级分类,点进去才是二级细分。不要用下拉框,回收员手上可能戴着干活的手套。
- 重量输入框默认调起带小数点的数字键盘,不要用全键盘。
- 照片上传策略改为"结算单提交前可补拍",现场先录数据,最后统一拍照,减少等待用户拍照的时间。
- 支持"连续添加"模式:录完一个品类后自动弹出下一个品类,不需要点"新增"按钮。
这些细节看起来不起眼,但回收员每天的接单量是系统的命脉,他录入效率高,订单周转才快。同理,业主端的"确认收货"页面,要把每条明细列清楚,总金额放大,按钮就一个"确认无误",不给用户思考的负担。
5.3 小程序消息推送:模板消息被改订阅消息后的应对
早年微信小程序可以无限次给用户发模板消息,后来改成了订阅消息,用户必须一次性点击授权"允许发送",且一次性订阅只能发一次。对订单类系统影响很大:业主下单后,我们希望回收员能收到接单通知;回收员上门后,我们希望业主能收到上门提醒。两个场景都需要推送,但订阅次数有限。
实际解决方案是双轨制:
- 关键节点(下单、成单、结算完成)在用户有订阅授权时走订阅消息。
- 没有授权时不强推,改在订单列表页、小程序首页做醒目的状态角标提醒。
- 结合公众号模板消息作为补充通道——如果用户在物业那里关注了公众号,也可以通过公众号模板消息提醒,这个没有次数限制,是当前比较稳定的方案。
6. 上线前后踩过的那些坑:从类目审核到金额分账
这个项目从开发到上线,我遇到过一堆课本上不会写的问题,挑四个印象最深的分享一下,给大家提前避雷。
第一个坑:小程序类目审核。废品回收属于"居民服务/回收"类目,上线前需要提供相关资质文件。如果你所在的城市对再生资源回收需要备案,一定要提前办理,不然审核直接卡住。别等代码写完才去提交审核,建议框架搭好后就先提一个空壳版本过审。
第二个坑:第三方支付分账。这个系统的资金流非常碎,笔笔都是几块钱,如果用微信商户号自定义分账,API比较复杂,而且分账需要开通产品权限。我们最终用了最简单的模型:用户的钱先进平台商户号,用户提现时平台代付。这样虽然账户上会有"资金池"的概念,但配合可配置的T+1结算,以及完税和平台服务费的模式,目前的体量下完全能支撑。
第三个坑:废品估重与实际重量偏差过大。用户填预估10公斤,师傅上门一称只有3公斤,用户心理落差极大,纠纷率明显高。后来在业主端下单时增加了提示:"实际金额按上门称重为准,可能与预估存在偏差",同时限制单品类预估重量的上下限。更规范的做法是让用户先按袋/箱数估价,称重后差异化报销。
第四个坑:小区保安和楼栋门禁。系统本身解决不了线下门禁问题,但一定要在订单信息里增加"进入小区提示"字段,回收员端可以填写"西门进入需要登记""3栋旁边有垃圾房"等备注,这比任何技术方案都实际。跑通一段时间后,我们甚至为常驻的回收员配发了小区通行牌,线下运营和线上系统是互补关系,别以为小程序上线就完事大吉。
7. 数据运营视角:这套系统真正赚钱的环节在哪里
最后从一个稍微宏观的角度聊几句。这套废品收购管理系统,单看每一笔订单都不赚钱,一单金额几块钱,利润几分钱,但如果把它当成一个数据入口,价值完全不一样。
通过订单数据,你能看到每个小区每天产生的可回收废品种类分布和重量波动,这些数据可以反向指导物业的垃圾分类考核,可以辅助市政的再生资源流向分析。更实际的是,当业主养成"卖废品到小程序"的习惯后,这个用户触达入口未来可以对接家电回收、旧衣回收、社区团购等增值服务——用户刚卖完废旧手机,系统就可以推送环保回收专场活动。
这也是我在架构设计时坚持要把积分体系、余额体系、用户标签体系做得完整的原因——今天它是废品回收系统,明天换个壳就是社区二手置换、社区清洁服务、物业增值服务预约平台。底层的用户、订单、结算、通知四套基础能力是完全复用的。
如果你也准备做类似方向,建议从最小可行版本开始:先只做一个小区、一类废品(比如纸箱)、一个回收员,跑通全部流程再复制。系统设计的复杂度应该跟着业务复杂度走,别在第一天就给自己挖一个大水池。