尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

ThinkPHP与Laravel双框架实战:小区团购平台从架构到踩坑全复盘

ThinkPHP与Laravel双框架实战:小区团购平台从架构到踩坑全复盘 项目上线第二周我在办公室盯着线上监控面板发懵用户在 Laravel 写的下单接口里支付成功了订单状态也更新了但运营在用 ThinkPHP 维系的旧后台里刷新半天订单列表还是停留在“待支付”。两边明明连的是同一个 MySQL数据却像隔着两个世界。这个项目就是标题里写的“基于 ThinkPHP-Laravel 的小区团购平台”。我们当时的做法听起来有点反常规——一个项目里同时用两套 PHP 框架ThinkPHP 负责运营后台和基础管理能力Laravel 负责 C 端用户交易链路。很多同行一听到“双框架”第一反应是维护地狱但如果你面临的是“老系统必须留着、新业务必须快速起来”的现实这其实是一条非常务实的路。这篇文章把我们从需求拆解、技术选型、数据库设计到踩坑修复的完整过程写下来希望能给准备用 PHP 做社区团购、或者正纠结于老框架迁移的团队一些参考。1. 小区团购的需求复盘真正要解决的三件事先别急着谈技术这个项目的第一个坎是需求本身。小区团购和普通电商平台在表面上看差不多——都有商品、订单、支付但实际跑起来之后你会发现它的核心逻辑完全围绕“小区”这个最小的地理单位展开很多常规电商里的设计在这里是要被推翻重来的。1.1 小区团购不是“缩小版”电商普通电商是“人找货”用户打开 APP 逛全站商品下单后快递送到家。小区团购是“货找人”平台把一个小区或者周边几个小区的人组织起来针对少数几个单品集中开团第二天统一配送到小区自提点用户下楼自提。这里有几个关键差异强地理约束用户必须绑定某个小区和具体楼栋平台才能判断他属于哪个团长负责的片区。成团机制用户发起拼团后必须在规定时间内凑齐最少人数否则自动退款这不是普通电商的普通下单流程。自提交付没有快递员挨家挨户送订单完成后生成核销码团长扫码核销整个交易才算闭环。所以需求调研阶段我们最终把平台要解决的核心问题收敛成三件事让业主用最少的操作完成拼团和自提让团长能高效管理自己片区的订单和佣金让平台运营能安全稳定地推动活动和结算。所有的系统设计包括框架选型都是围绕这三件事展开的。1.2 三方角色的核心诉求我建议任何团队在做这类平台时都先把自己的用户角色画出来。我们这个系统里最终定了三类角色业主C 端消费者关注点很直接——附近有什么团购活动、价格是否便宜、成团率高不高、自提点是否方便、佣金和售后怎么处理。业主端最常见的操作路径是打开小程序 → 看到首页推荐活动 → 点开详情 → 支付参团 → 第二天收到到货通知 → 去自提点扫码提货。团长小区合伙人这角色被很多项目做成了“兼职客服”但我们把他定义成运营节点的核心。团长负责自己小区的开团选品建议、货品接收、用户核销、售后登记平台按成交额给他结算佣金。团长端的核心诉求是“少点几下就能完成核销”“佣金账目清楚”“遇到售后能记录流转”。平台运营后台管理员管理小区、楼栋、用户、商品、团购活动、团长审核、佣金提现审批等。运营后台是 ThinkPHP 的主战场。这三类角色的诉求一拆开系统的边界也就清楚了C 端要的是流畅稳定的交易体验所以我们选 Laravel 来承载后台要的是快速开发大量 CRUD 管理页面ThinkPHP 的快速开发模式更契合现有团队的维护习惯。1.3 自提模式带来的独特交互自提模式给系统增加了一个普通电商没有的流程——核销。用户支付成团后系统会生成一个动态核销码团长在后台或团长端小程序里扫描或输入核销码订单状态才会从“待核销”变成“已完成”同时触发佣金结算。这个流程看起来简单但它直接影响数据库设计和接口设计。后面我会讲到订单状态机这里先提醒一句核销不是简单的“更新订单状态”它还要处理“部分核销”“错拿别人货”“用户没来取货”这些真实场景。我们最终把核销动作设计成一个独立表而不是订单表上的一个字段就是为了能记录每次核销的操作人和时间出问题的时候可以追溯。2. 技术选型的取舍逻辑为什么是 ThinkPHPLaravel 双框架而不是二选一我知道很多人看到“双框架”第一反应是这不自找麻烦吗统一技术栈不香吗说实话单一框架一定更省事但我们当时面对的现实情况是团队里老员工对 ThinkPHP 的熟练度远高于 Laravel公司之前已经有一套基于 ThinkPHP 的订单和商品管理系统在线上跑着不可能直接下掉。而新来的核心开发和我对 Laravel 的生态更熟C 端交易链路又需要更高的开发效率和更好的异步处理能力。2.1 为什么不让 ThinkPHP 一套走到底ThinkPHP 在 PHP 圈子里以“上手快、文档中文友好、ORM 简单直接”著称特别适合做运营后台这类以 CRUD 为主的管理系统。但到了 C 端交易链路它的短板会逐渐暴露中间件和管道设计相对朴素做登录态校验、接口签名、权限拦截时代码组织没有 Laravel 那么清晰。队列和异步任务虽然能用但生态和文档远不如 Laravel 丰富。社区团购业务里典型的场景——支付回调后要通知团长、成团后要批量生成核销码、佣金入账后要更新钱包余额——这些异步任务在 Laravel 里用 Redis 队列 worker 常驻进程非常成熟ThinkPHP 则需要自己折腾常驻脚本和任务分发。Laravel 的 Eloquent ORM、迁移机制、Seeder 工厂在多人协作开发时效率优势很明显尤其是数据库结构变更的维护比手工维护 SQL 文件舒服得多。2.2 为什么不让 Laravel 直接把老系统全部重写重写听起来最美好但风险也最大。老后台里有大量已经稳定运行了好几年的逻辑——商品规格管理、供应商结算、财务报表这些逻辑直接重写一遍测试成本和新引入的 bug 风险都不可控。而且当时团队能熟练写 Laravel 的人只有两个全量重写会把人拖垮。所以最终的决策是用 ThinkPHP 继续承载运营后台和旧业务模块用 Laravel 承载 C 端交易核心链路小程序/H5 API两套代码放在同一个代码仓库的不同目录下共用同一个 MySQL 实例通过 Nginx 按路径前缀分流。下面这张表是我在选型阶段给团队做的核心能力对比基本决定了两个框架各自的分工边界能力维度ThinkPHP 值Laravel 值我们的选择团队熟练度高全员可维护中核心 2 人熟练C 端核心逻辑由 Laravel 主导ORM 开发效率快速适合 CRUD优雅关联模型强大Laravel 处理交易TP 处理管理页中间件/管道简单够用生态完善Laravel 做接口鉴权、签名、限流队列与异步需要自己实现Redis 队列 worker 成熟全平台异步任务统一由 Laravel worker 消费数据迁移弱迁移/Seeder 完善ThinkPHP 后台也按 Laravel 迁移规范建表定时任务crontab 手动Scheduler 可视化定时任务按模块分开避免互相覆盖2.3 双框架的边界线必须划清楚双框架项目最容易翻车的地方不是技术而是“边界混乱”。我们当时立了一条硬性规定同一个业务接口不允许同时出现在两个框架里。用户端能调用的接口一律以 Laravel 的 /api 为准后台管理接口一律以 ThinkPHP 的 /admin 为准。两边确实会有一些业务是重叠的比如“查看订单列表”用户端看的是自己的订单后台看的是全部小区的订单但这两者我们明确是两套独立接口不互相调用避免出现一条链路两个技术栈同时维护的情况。另外还有一条约定跨框架的数据访问只准通过数据库层完成不准通过 HTTP 互相调用。意思就是 ThinkPHP 后台要读取 Laravel 写入的订单数据直接写 SQL 查订单表而不是去请求 Laravel 的接口。这样做的好处是避免两个应用之间的接口风暴和状态粘连。3. 系统架构与工程组织双框架如何做到互相不拖累架构设计上我们没有做微服务那种复杂拆分仍然是经典的“单体多应用”部署方式。这套架构的核心思路是两个应用进程各自独立共享基础设施数据库、Redis、文件存储通过约定来避免冲突。3.1 代码仓库的组织方式项目开始前我们专门花了一个下午敲定代码仓库结构。最终采用的是一个仓库、两个应用目录的方式project-root/ ├── app-laravel/ # Laravel C端应用 │ ├── app/ │ ├── routes/api.php │ └── ... ├── app-thinkphp/ # ThinkPHP 后台应用 │ ├── app/ │ └── ... ├── docs/ # 统一接口文档和数据库变更说明 ├── deploy/ │ ├── nginx.conf │ └── supervisor.conf └── docker-compose.yml有人会问为什么不干脆拆两个 git 仓库我们当时的考虑是团队规模不大拆仓库会增加跨仓库 Issue 和版本对齐的成本。放在一个仓库里每次提交能看到改动是否影响到了另一方Code Review 的时候可以全局判断。缺点是仓库体积会变大但对这个规模的项目完全可接受。3.2 统一入口与 Nginx 分流双框架共用同一个域名用户在微信小程序里只需要配置一个 baseURL不需要关心背后是两套应用。Nginx 配置大概是这样的server { listen 443 ssl; server_name tuan.example.com; # 用户端 API - Laravel location /api/ { alias /var/www/project/app-laravel/public/; try_files $uri $uri/ /index.php?$query_string; fastcgi_pass unix:/var/run/php/php8.1-fpm.sock; include fastcgi_params; fastcgi_param SCRIPT_FILENAME /var/www/project/app-laravel/public/index.php; } # 运营后台 - ThinkPHP location /admin/ { alias /var/www/project/app-thinkphp/public/; try_files $uri $uri/ /index.php?$query_string; fastcgi_pass unix:/var/run/php/php8.1-fpm.sock; include fastcgi_params; fastcgi_param SCRIPT_FILENAME /var/www/project/app-thinkphp/public/index.php; } location /static/ { alias /var/www/project/shared-static/; } }这段配置里最关键的就是两个 location 块/api 开头走 Laravel 的 public/index.php/admin 开头走 ThinkPHP 的 public/index.php。两个应用各自有独立的入口文件、独立的 php-fpm 进程池配置互不干扰。这里有个小坑Nginx 的 alias 和 root 的路径计算方式不一样直接用 root 容易把 URL 路径拼到物理路径里去导致 404我们当时在测试环境折腾了半小时才反应过来。3.3 “共享层”的约定和基础设施双框架之间真正需要共享的无非是四样东西数据库、缓存、文件存储、队列。我们针对每一项都定了统一约定数据库统一使用 MySQL 8.0字符集 utf8mb4。表命名按业务前缀区分所有 C 端交易相关表统一以tuan_开头后台系统表统一以sys_开头。这样即使以后真的拆库直接按前缀拆就行。Redis统一使用 Redis 6但强制要求 key 必须带模块前缀。C 端缓存用lv_开头ThinkPHP 缓存用tp_process_开头绝不允许用无前缀的裸 key。文件存储商品图片、用户头像、核销码图片全部走本地磁盘 Nginx 静态路由文件路径存相对路径绝对 URL 由前端根据环境变量拼接。队列全平台只保留一套队列使用 Laravel 的 Redis 队列驱动ThinkPHP 侧通过 Redis 的 RPUSH 命令向同一个队列投递任务数据由 Laravel 的 worker 进程统一消费。这里要解释一下为什么队列要统一到 Laravel 侧去消费。因为队列任务里包含大量业务逻辑如果同一份任务既能在 ThinkPHP 里消费又能在 Laravel 里消费逻辑势必会写两遍而且两边的序列化格式不一致。统一到 Laravel worker 之后ThinkPHP 只负责“投递任务描述”真正执行业务逻辑的只存在 Laravel 一处省了很多事。4. 数据库设计从“楼下门店”到“小区团长”的模型落地数据库是整个平台的底座这部分设计的合理程度直接决定后续两个月开发顺不顺畅。我们的总体设计原则是以小区为核心维度建立数据模型用户、商品、活动、订单、佣金全部围绕小区维度做关系。4.1 核心表结构设计我把最关键的表列出来每一张都值得细看。首先是小区和楼栋相关表表名核心字段说明tuan_villageid, name, address, lng, lat, status, created_at小区表状态字段控制是否可下单tuan_buildingid, village_id, building_no, unit_no, room_no楼栋表业主绑定信息的地基tuan_userid, mobile, password, nickname, role, bind_village_id, bind_room_id, status用户表role 区分业主/团长/运营tuan_leader_applyid, user_id, village_id, real_name, id_card, status团长申请表后台审核用tuan_productid, category_id, name, price, cost_price, stock, image, status商品表tuan_groupon_activityid, product_id, village_id, leader_id, group_price, min_people, max_people, start_time, end_time, status团购活动表注意活动限定了 village_idtuan_groupon_memberid, activity_id, user_id, order_id, status拼团成员表记录参团关系tuan_orderid, order_no, user_id, activity_id, product_id, quantity, amount, status, pay_type, pay_time, pickup_code, pickup_status, created_at订单表交易链路的核心tuan_verify_recordid, order_id, operator_id, verify_code, created_at核销记录表tuan_commission_logid, user_id, order_id, type, amount, status, created_at佣金流水表tuan_withdrawid, user_id, amount, status, audit_remark, created_at提现申请表我重点说两张表。一是tuan_groupon_activity它把“小区”和“商品”绑定在一起同一个商品在不同小区可能是不同的活动、不同的拼团价和成团人数。这个设计在最初讨论时有过分歧有人觉得应该用“商品 通用活动”模式但实际运营中不同小区对不同品类的消费能力差别很大A 小区能成的 10 人团在 B 小区可能连 3 人都凑不齐。所以活动必须挂在小区的维度下。二是tuan_order这张核心订单表。普通电商订单表上通常有收货地址、物流单号这些字段但小区拼团场景下这些都不需要取而代之的是activity_id关联到哪个拼团活动和pickup_code自提核销码。这再次说明业务模型差异会直接体现在表结构上。4.2 拼团订单状态机订单状态不能靠拍脑袋设计得先在文档里把状态流转图画出来。当然我这里不用图形直接用状态表来讲状态值含义可触发动作pending_payment待支付用户支付、取消订单paid已支付待成团等待成团此时金额在平台中间账户success已成团待核销生成核销码通知团长备货failed流团已退款成团失败原路退款pickup已核销团长核销完成进入佣金结算refunded已退款售后或超时未提货退款completed已完成用户确认或超时自动完成这里最容易出错的是paid到success的转换。我们的逻辑是在tuan_groupon_member表上做 COUNT当已支付人数达到min_people时开启一个事务把订单状态批量更新为success同时生成pickup_code。这个转换必须加锁或使用原子操作否则并发场景下可能把成团人数判断错。4.3 唯一约束与索引设计数据库设计阶段最容易犯的错就是“索引太少约束不够”。我们上线前专门做了一次压测发现几个慢查询根因都是没有在查询字段上建索引。这里列几个最重要的约束tuan_order.order_no必须建唯一索引并且业务代码在生成单号时要用雪花算法或“日期随机串”绝不允许用自增 ID 直接当单号暴露给用户。tuan_groupon_member上建(activity_id, user_id)联合唯一索引防止同一个用户对同一个拼团活动重复参团。tuan_verify_record.order_id建唯一索引保证同一个订单只能被核销一次这是幂等性的数据库兜底。tuan_commission_log上建(order_id, user_id, type)联合唯一索引防止佣金重复入账。这四条约束是我们在实际发生问题之后补上去的补索引前就已经出现过失真数据。希望后来者能在一开始就设计好。5. 核心业务实现登录鉴权、开团参团、佣金分账这一章是开发量最大的部分我挑几个最核心、最容易写错的点详细讲。5.1 登录鉴权双框架共用一个身份体系前面说过PHP 默认的 session 机制在两个框架之间是不互通的Laravel 默认把 session 写在storage/framework/sessions文件里ThinkPHP 默认把 session 写在 runtime 目录里两边根本不认识对方。我们最终放弃了 session全面改用JWT 无状态令牌。具体落地方式是Laravel 负责 C 端用户登录接口校验手机号 密码或验证码后签发出一个 JWT有效期 2 小时refresh_token 有效期 7 天。一个自定义的 JWT 校验中间件在 Laravel 侧统一解析 token拿到user_id后从tuan_user表里读用户信息塞进请求上下文。ThinkPHP 后台同样需要登录鉴权但我们没有让它去解析 C 端 JWT而是独立维护了一套后台管理员账号体系。管理员账号存在sys_admin表里登录后签发后台专用 token。这样 C 端和后台的会话体系彻底解耦各自的 token 失效、封禁互不影响。有人会问后台为什么不用 Laravel因为后台的权限管理RBAC在 ThinkPHP 里已经有现成逻辑了迁移成本不划算而且后台面对的是公司内部人员没有高并发压力ThinkPHP 完全足够。5.2 开团和参团的接口设计开团和参团看着像同一个动作其实逻辑略有区别开团用户选择某个活动创建一条新的拼团记录成为团长活动发起人。对应操作是在tuan_groupon_member表里插入一条is_leader1的记录并创建一条待支付订单。参团用户加入已经存在的拼团插入一条is_leader0的记录同样创建待支付订单。两个动作的关键点都在于“创建订单”必须和“插入拼团成员”在同一个事务里完成否则会出现有人付了钱但拼团成员表里没有记录的问题。我们用 Laravel 的DB::transaction包裹整个流程订单状态先置为pending_payment支付成功后才置为paid。还有一个细节开团接口需要先判断活动状态和当前拼团成员数。我们用 Redis 的incr命令做一个原子计数器每次有人参团就incr一次当计数达到min_people时再去数据库里确认最终的成团状态避免并发下多个请求同时触发成团逻辑。这里提到的是 Redis 原子操作实际代码里用Lua 脚本保证判断和自增是一个原子操作。5.3 支付回调的幂等处理支付回调是交易系统最容易出事故的地方。微信支付或者模拟支付平台会多次通知同一笔订单如果回调处理方法不是幂等的就会造成订单被重复更新、佣金被重复结算、积分被重复发放。我们的实现思路是在业务代码入口处先按order_no查订单状态如果已经是paid或success直接返回成功应答不再往下走。在订单表上增加transaction_id字段并建立唯一索引支付回调里先尝试把transaction_id更新到订单记录上如果更新影响行数为 0说明这条回调已经被处理过直接跳过。支付成功后的业务动作通知团长、推送模板消息、记录佣金流水全部放入 Redis 队列异步执行避免支付回调接口被“慢业务”拖住导致微信端超时重试风暴。幂等处理做得好不好直接决定线上稳定度。这一块我建议任何团队都不要图省事直接跳过。5.4 佣金分账的流水设计佣金是平台和团长之间最重要的信任凭证账目一旦出问题团长会立刻流失。我们的规则是订单被核销后系统自动按商品设置的佣金比例生成一条tuan_commission_log状态为pending待入账。过了售后期48 小时或用户确认收货后定时任务把这批记录置为settled已入账同时累加团长钱包余额。这里有个容易犯错的地方佣金金额的计算必须用订单实付金额不能用商品原价或拼团页展示价。因为支付环节可能存在优惠券、积分抵扣实付金额和展示价不一定一致。我们统一在订单支付成功时就把actual_amount落库佣金按实际金额计算避免后续争议。另外佣金记录表和钱包余额表不能分开看每次更新钱包余额必须在同一个数据库事务里更新佣金记录的status否则会出现“钱包有钱但流水对不上”的脏数据。6. 双框架部署与接口联调Nginx 路由、HTTPS 与版本化部署和联调阶段是双框架项目最让人头疼的部分很多问题都是“本地好好的一上服务器就不行”。这章讲我们实际踩过的部署和联调问题。6.1 统一域名下的 Nginx 路由细节除了前面说的 /api 和 /admin 分流还有几个细节需要注意静态资源要单独处理商品图片、核销码图片如果直接放在某个应用目录里Nginx 配不好会导致另一个应用引用不到。我们最后把所有上传文件统一放到项目根目录的storage/uploads下Nginx 里加一个/uploads/location 指向该目录两个应用都能通过同一个 URL 访问。HTTPS 证书要做全小程序要求所有网络请求都必须是 HTTPS。我们申请了域名的通配符证书Nginx 里 443 端口统一监听/api 和 /admin 都走同一个证书证书自动续期脚本只配一次。PHP-FPM 进程池区分我给 Laravel 和 ThinkPHP 分别配置了独立的 PHP-FPM pool比如pool_laravel.conf和pool_thinkphp.conf各自有独立的监听 socket 和 worker 数量。这样 C 端流量高峰时即使打爆了 Laravel 的进程池后台管理页面还依然可以打开两边互不拖累。6.2 API 版本化和错误码统一双框架下最容易出现的混乱是前端对接时不知道某个接口到底是 Laravel 的还是 ThinkPHP 的。我们在路由设计上直接用版本号划分/api/v1/...开头的是 Laravel C 端接口/admin/v1/...开头的是 ThinkPHP 后台接口同时全局统一了错误码规范比如错误码含义0成功10001参数错误10002未登录或 token 失效10003无权限操作20001订单状态不允许当前操作20002拼团人数已满20003活动已结束或未开始30001佣金金额不足以提现这个规范要写进团队的接口文档里任何一方新增接口都必须遵守否则前端就乱了。6.3 跨域与参数签名的处理开发阶段前端跑在localhost:8080后端跑在https://tuan.example.com必然遇到跨域问题。Laravel 侧我们用的是fruitcake/laravel-cors包配置好 allowed_origins 和 allowed_headers 就行。但要注意生产环境必须把跨域白名单收紧只允许正式域名绝不能直接允许所有来源。否则别人网页就能随意调用你的接口配合登录态拿到用户数据那就是严重安全漏洞。小程序端和 H5 端还有一个区别小程序里没有 Cookie 机制所以我们从头到尾都用 Header 传Authorization: Bearer {token}服务端统一从 Authorization 头里解析 token跟 Cookie 完全解耦。这也让跨域配置更简单不需要处理withCredentials的场景。7. 踩坑记录四个换框架过程中最容易翻车的点这章是我最想写的部分。说实话上面的设计文档和代码方案看着都挺合理的但真正上线后你还是会遇到一堆“想都想不到”的问题。我把印象最深的四个坑列出来每个都是真实发生的附带排查思路。7.1 订单查不到从库同步延迟背了锅回到文章开头那个场景用户在 Laravel 下单后ThinkPHP 后台刷新订单列表数据一直不更新。当时第一反应是缓存但清了 Redis 缓存没用然后怀疑是事务没提交查了订单表发现数据明明已经写进去了后台查不到实在诡异。后来排查数据库连接信息才发现后台的数据库连接配置里指向的是一个10.0.0.6的只读从库而从库的 binlog 同步延迟在高峰期超过了 30 秒。也就是说用户在 Laravel 主库上写入订单后从库还没同步过来后台自然查不到。这个问题给了我们两个教训第一订单这类强一致性数据必须走主库查询不能为了分摊读压力把它们扔到从库上第二双框架共用数据库时连接配置必须统一管理不能一个框架连主库一个框架连从库否则数据漂移问题会折磨死你。最终我们让 ThinkPHP 后台的订单相关查询全部强制走主库其他非核心统计报表才允许走从库。7.2 用户登录态串号Redis key 污染有段时间用户反馈“明明是自己的账号却看到别人的订单”。一开始以为是 JWT 私钥泄露排查了半天最后发现问题出在 Redis key 冲突上。Laravel 这边缓存用户信息用的是user:info:{id}ThinkPHP 老代码里也有一个地方用了完全一样的 key 写用户扩展信息两边把不同格式的数据写进了同一个 key。Laravel 读出来的是 ThinkPHP 序列化的数组解析失败后走了降级逻辑从数据库重查结果又把错误数据塞回去了。整个查询链路一层套一层最终导致用户信息张冠李戴。修复方案很简单全平台 Redis key 统一规范Laravel 的 key 必须带lv_前缀ThinkPHP 的 key 必须带tp_前缀。同时用 Redis 的SCAN命令把历史脏数据清理掉。这个坑告诉我们多应用共享基础设施时命名规范不是文档里的“建议”而是必须通过 Code Review 强制执行的“法律”。7.3 队列任务两边不认账序列化格式不一致这个坑是我们在做“成团后批量通知用户”时踩的。ThinkPHP 后台有一个功能是批量给用户发送模板消息技术方案是让 ThinkPHP 通过队列把任务投递给 Laravel worker 消费。但实测发现任务推不进去worker 不断报错“Undefined array key job”。查了半天发现原因很搞笑ThinkPHP 往 Redis list 里 push 的是一个数组PHP 的serialize()序列化格式而 Laravel 的 Redis 队列对任务格式有严格约定它默认读取的是 JSON 序列化的 payload包含job和data两个键。ThinkPHP 直接 push 的量不符合 Laravel 对任务数据的解析约定worker 一读就报错。我们的解决方案是为这种跨框架投递场景封装了一个统一的发布函数由它来把任务描述构造成标准的 JSON 格式再RPUSH到 Laravel 队列消费的 Redis key 上。任务消费逻辑在 Laravel 侧通过Queue::push处理。从此之后ThinkPHP 侧不再感知 Laravel 队列的细节只调用这个统一封装函数。7.4 定时任务重复执行两边 cron 打架平台上线一段时间后后台同事反映佣金结算脚本偶尔会执行两次。排查后确认是 ThinkPHP 的定时任务和 Laravel 的 scheduler 都配置了同一个结算命令。两个应用分别在各自的环境变量里配置了 cron 规则到了整点都会触发导致佣金入账被重复执行。这个问题的解决方案是约定全平台定时任务只能由 Laravel 的 scheduler 统一管理ThinkPHP 侧关闭所有业务定时任务。如果需要定时执行的数据在 ThinkPHP 的库里Laravel 的定时任务会用命令行方式直接操作目标表而不是通过 ThinkPHP 的代码去执行。如果确实需要用到 ThinkPHP 里封装好的方法就封装成命令行脚本由 Laravel scheduler 调用。这样既保证了只有一处定时触发源又不会丢失 ThinkPHP 的既有逻辑。四个坑汇总下来结合一张表方便大家对照排查问题现象根因解决方案后台查不到新订单后台连了从库主从同步延迟订单查询强制走主库用户看到别人订单Redis key 命名冲突key 增加业务前缀清理历史脏数据队列任务消费失败两边队列数据格式不一致统一封装投递函数标准 JSON 格式佣金重复结算两边 cron 同时触发同一命令定时任务统一由 Laravel scheduler 调度结尾关于“双框架”这件事的个人体会项目从立项到稳定运行差不多用了三个月期间无数次被质疑“为什么不统一框架”最后的结果其实也谈不上哪边更好而是合适的人在合适的位置干合适的活。ThinkPHP 侧的老同事继续维护他们的后台效率很高Laravel 侧我们迭代 C 端交易链路节奏也很快。两者之间通过数据库、Redis 和队列解耦边界划清楚之后并没有出现网上一说的“两套框架 两倍维护成本”。我个人体会最深的是这个项目真正的核心竞争力不在框架本身而在于一开始就把三件事想清楚了业务角色的边界、数据模型的边界、基础设施的边界。框架只是一个工具ThinkPHP 和 Laravel 哪个更好这种问题在真实项目里没有标准答案只有适合当下的取舍。如果你也在犹豫老系统和新框架共存的问题我的建议是先划边界再定规范最后才是写代码。边界一旦模糊后面每个迭代都会有人跨过约定那时候你和同事之间的沟通成本会比任何框架的性能差异都更致命。最后再分享一个实用的小经验双框架项目的接口文档一定要从第一天就开始维护而不是等出了联调问题再补。我们用简单的 Markdown 文件记录每个接口的入参、出参、错误码和所属框架谁新增接口谁改文档。这个小动作在项目后期省掉了非常多“这个接口是你那边的还是我那边的”的无效沟通。希望这篇复盘能帮你少走几条弯路。
返回列表