新闻详情

新闻详情

首页 / 资讯中心 / 详情

多商户B2B2C商城实战:Laravel8+Vue3+Redis架构与部署全解析

发布时间:2026/9/16 17:28:32来源:尧图网络
多商户B2B2C商城实战:Laravel8+Vue3+Redis架构与部署全解析
简介一套以 Laravel 8.x 与 Vue3 构建的多商户 B2B2C 商城系统源码适合具备 PHP/Vue 基础、需要搭建新零售或平台型电商业务的开发者与运营者。系统采用前后端分离架构覆盖多商户入驻、秒杀、团购、优惠券、在线聊天、三级分销、积分商城以及微信/支付宝支付等主流电商功能并保留良好的二次开发空间。资源包共 668 个文件包含 372 个 PHP 后端逻辑文件、146 个 Vue 前端页面组件以及 Docker、Nginx、MySQL 等部署配置、数据表 SQL 与项目文档压缩后仅 1.92MB。已有 109 人学习下载既可用于快速构建商城项目原型也能作为研究现代电商业务流程、实践前后端分离开发的参考案例。1. 多商户B2B2C商城为什么选择 Laravel8 Vue3 这套组合把代码拉下来重新部署这套多商户B2B2C商城时最先看到的不是秒杀和分销页面而是一串部署配置artisan、nginx.conf、Dockerfile、xlaravel.pool.conf。这个顺序反过来提醒我这类项目最大的成本不在功能代码而在支付回调、队列消费和 Nginx 转发这些容易被忽略的环节。Laravel 8.x 提供清晰的后端分层Vue3 负责前端交互两者通过 REST API 连接RESTful 接口风格和中间件机制让多商户权限和支付通道可以分开控制适合要做新零售、网店、多商户平台又有二次开发需求的团队。后面这部分我按后端、前端、并发业务、部署扩展的顺序往下走把这套系统里值得复用的实现逐个拆开。2. Laravel 8 后端落地多商户中间件、JWT 与支付回调处理2.1 多商户数据隔离先定字段再谈架构多商户系统首先要回答一个问题商户订单、商品、结算数据是物理隔离还是逻辑隔离。这套包用的是逻辑隔离核心表里普遍带merchant_id配合 Laravel 的全局作用域做默认过滤。对二次开发来说如果每个查询都手动写where(merchant_id, ...)后面漏一处就会串店所以我倾向用一个BelongsToMerchant的 Trait在booted()里注册全局作用域查询模型时自动注入当前商户条件后台跨商户审核时再用withoutGlobalScope()关闭。class MerchantScope implements Scope { public function apply(Builder $builder, Model $model) { $mid auth(api)-user()-merchant_id ?? 0; $builder-where($model-getTable() . .merchant_id, $mid); } }auth(api)-user()依赖 JWT 登录态所以一进路由就得先把用户识别出来。逻辑隔离的优势是部署简单缺点是报表和跨商户聚合查询要格外小心建议所有统计查询显式带上merchant_id不要把所有希望都压在全局作用域上。不同隔离方案对二开的影响差异很大下面这张表是常用的选型依据方案部署成本推荐场景主要坑独立数据库 独立表高强合规、超大型平台运维繁琐跨库查询困难共享库 tenant_id中中型多商户平台漏加过滤条件会串数据共享表 merchant_id低本套包的默认方式报表聚合要小心选择共享表方案后merchant_id的索引覆盖率很关键订单表、商品表、优惠券表都要把它放到联合索引的最左列否则商户后台按时间范围导订单时慢查询会拖垮主库。另外队列消费和artisan命令行里取不到登录态全局作用域里的auth(api)-user()会返回空所以 job 任务不要直接复用 merchant 中间件要给 job 单独传merchant_id。2.2 中间件与 JWT 路由鉴权laravel 中间件实现原理前后分离的 REST API 架构下Laravel 中间件本质是 Pipeline 模式请求按注册顺序进入handle()在把控制权交给控制器之前完成身份校验、签名校验、日志记录等动作。商城这种接口数量多的项目中间件适合干的事情比控制器里干净得多路由里能少写很多重复代码。// app/Http/Kernel.php protected $routeMiddleware [ merchant \App\Http\Middleware\CheckMerchant::class, ]; Route::middleware([auth:api, merchant])-group(function () { Route::get(/merchant/orders, [OrderController::class, index]); Route::post(/merchant/products, [ProductController::class, store]); });auth:api负责认证merchant中间件里检查 JWT 解析出的用户是否绑定商户再查一次该用户在商户角色表里的权限。这两个中间件的顺序不能反认证不过时没必要做商户权限判断。常见的实现是把CheckMerchant放在handle($request, $next)里先取auth(api)-user()再用$user-hasPermission($route-getName())决定是否放行。Laravel 8 的auth驱动如果不是 JWT建议换成 tymon/jwt-auth 并把 guard 驱动改成jwt否则auth(api)-user()拿到 null商户中间件直接 401。部署时如果开了php artisan route:cache新增路由后必须先清缓存不然线上路由还是旧文件。2.3 微信支付、支付宝支付的回调落地支付模块有微信Wechat Pay和支付宝Alipay两个通道痛点不在“发起支付”而在“接收回调”。微信支付 v3 的回调需要先验签再解密报文支付宝是 RSA2 验签加解密两个 SDK 的命名空间和异常类型不同我在代码里统一包了一层PaymentService对外只暴露sign()、notify()两个方法换通道只改配置。public function notify($channel, $payload) { $verified $this-factory-channel($channel)-verify($payload); if (! $verified) { return response()-json([code FAIL, message 验签失败]); } $order Order::where(order_no, $verified[out_trade_no]) -lockForUpdate() -first(); if ($order $order-status Order::STATUS_UNPAID) { $order-status Order::STATUS_PAID; $order-paid_at now(); $order-save(); event(new OrderPaid($order)); } return response()-json([code SUCCESS, message 成功]); }回调处理必须做三件事验签、查单、幂等。lockForUpdate()防止两个回调同时进来时重复入账event(new OrderPaid($order))给库存扣减、积分赠送、分销佣金计算留扩展点。微信回调要求返回SUCCESS/FAIL直接返回 JSON不能返回 Laravel 默认的字符串或 200 空页否则微信会认为回调失败而重复推送。重复推送不可怕可怕的是没做幂等后重复发货。下面这张表是两种通道的参数差异支付通道验签方式解密方式成功返回体微信支付 v3平台证书验签名APIv3 密钥解密{code:SUCCESS}支付宝RSA2 公钥验签AES128-CBC 解密{code:SUCCESS}发起支付时前端只拿到pay_params真正更新订单状态全在后端回调。提示微信支付回调必须返回SUCCESS/FAIL且响应不能带多余空格或 BOM 头否则会被微信判定为回调失败并持续重推。.env里的WECHAT_PAY_KEY、ALIPAY_APP_PRIVATE_KEY要严格禁止入库二开后经常有团队把私钥写在config/payment.php里提交到 Git这就是事故。3. Vue3 前端实现基于 Vite Pinia 的商城交互与状态管理3.1 多入口配置用户商城和商户后台拆分这套前端基于 Vite Vue3 构建src/api、src/views、src/stores三段式目录。用 Vite 而不是 Webpack 对商城类项目有明显收益开发服务器冷启动快HMR 热更新一次毫秒级反应。由于系统同时有用户端和商户后台我建议在vite.config.ts中指定多个build.rollupOptions.input避免把商户后台和用户商城塞进同一个 bundle。// vite.config.ts import { defineConfig } from vite import vue from vitejs/plugin-vue import { fileURLToPath } from node:url export default defineConfig({ plugins: [vue()], resolve: { alias: { : fileURLToPath(new URL(./src, import.meta.url)) } }, build: { rollupOptions: { input: { mall: index.html, admin: admin.html } } }, server: { proxy: { /api: { target: http://localhost:8000, changeOrigin: true } } } })server.proxy解决开发阶段跨域本地联调时/api请求直接转发到 Laravel 后端生产环境由 Nginx 处理前端只发相对路径。多入口配置把用户商城和商户后台拆成两个产物两端依赖独立又可以在同一仓库维护。Vue3 的setup写法带来的直接好处是业务逻辑可以按模块组合比如商品详情页的 SKU 选择和购物车计算可以拆成独立 composable而不是全部挤在data()里。3.2 Pinia 状态管理购物车与登录态Vue3 商城这种多角色、多缓存数据的场景Pinia 的setup store写法比 Vuex 顺手没有模块嵌套actions可以直接await调用省略 Mutation。购物车和 Token 都建议用 Pinia 管理配合localStorage做持久化。// src/stores/cart.ts import { defineStore } from pinia export const useCartStore defineStore(cart, { state: () ({ items: [] as CartItem[], totalCount: 0, totalPrice: 0 }), getters: { checkedItems: (state) state.items.filter((item) item.checked) }, actions: { async addItem(sku: SkuInfo, qty: number) { const authStore useAuthStore() if (!authStore.token) return const res await api.cart.add({ skuId: sku.id, qty }) if (res.code 0) { this.items res.data.items this.calc() } }, calc() { this.totalCount this.items.reduce((sum, item) sum item.qty, 0) this.totalPrice this.items.reduce( (sum, item) sum (item.checked ? item.price * item.qty : 0), 0 ) } } })购物车计算放在actions里自己维护有一个权衡数据量不大时逐项刷新没问题规格组合超过几十个以后建议把totalPrice放到getters用 Vue3 的computed跟踪依赖避免每次增删都全量重算。上面的calc()方法适合订单结算页这种低频场景不需要过度设计。功能点Vuex4Pinia模块组织嵌套 modules扁平 storeMutation需要不需要TypeScript 支持一般第一类支持DevTools支持支持选择 Pinia 后要注意持久化插件的搭配商城项目通常会装一个pinia-plugin-persistedstate但登录态、收货地址、购物车三类数据不要用同一套 key 持久化否则退出登录时旧用户购物车会被新用户读到这类 bug 在测试环境基本发现不了。3.3 商品 SKU 选择与秒杀倒计时商品详情页是 Vue3 商城中最容易写乱的部分。多规格商品需要维护一个 SKU 矩阵常见做法是给每个可选项生成key再用computed判断当前组合是否可售。不要把判断逻辑塞进模板模板只消费canSelect(option)的结果。const selectedSpecs refRecordstring, string({}) const skuList refSkuItem[]([]) const currentSku computed(() { const keys Object.values(selectedSpecs.value).sort().join(-) return skuList.value.find((s) s.specKey keys) }) function canSelect(specName: string, value: string) { if (!currentSku.value) return true const candidate { ...selectedSpecs.value, [specName]: value } const candidateKey Object.values(candidate).sort().join(-) return skuList.value.some((s) s.specKey candidateKey) }computed的粒度要尽量小。currentSku只依赖selectedSpecscanSelect再依赖currentSku这样点击某个规格时只有相关按钮状态被刷新。秒杀倒计时也是同样逻辑页面不实时请求服务器只在进入页面时拿一次serverTime和endTime然后本地用setInterval递减切后台再回来时用visibilitychange事件重算避免长时间挂后台导致倒计时误差。秒杀活动临近前如果本地时间和服务器时间差过大用服务器返回的时间戳做差不要直接使用客户端时间。3.4 在线聊天WebSocket 与消息重连在线聊天模块可以做成 WebSocket 长连接后端可以用 Laravel Broadcasting Redis也可以用 workerman 这类独立服务。这套系统里商户和用户都要收消息把聊天消息和订单状态消息放在同一个 channel 会更省连接。前端组件用onUnmounted释放连接避免切页面后消息继续往销毁组件里塞。const wsUrl wss://mall.example.com/ws?token${token} const socket new WebSocket(wsUrl) socket.onmessage (e) { const msg JSON.parse(e.data) if (msg.type message) { messageList.value.push(msg) } else if (msg.type order_status) { orderStore.refresh() } } onUnmounted(() socket.close())这里有两个实际问题容易被忽略一是断线重连必须带退避策略不能 1 秒重连一次否则后端会被重连请求打满二是消息顺序不能完全依赖 WebSocket服务端要落库客户端维护消息 ID重连后从最后一条 ID 拉取补发。在线聊天只是辅助订单状态推送才是购物体验的关键Vue3 项目里这块逻辑建议做成一个useChannelSocket的 composable把连接、订阅、重连、销毁都收口到同一个文件。4. 秒杀、团购与三级分销并发控制与结算的实现路径4.1 秒杀不超卖Redis 库存与 Lua 脚本秒杀是这套系统里最考验后端功底的部分。数据库UPDATE扣库存虽然可以用条件更新保证不超卖但热点商品的 QPS 一上来就会把数据库连接打满。常见做法是把库存预热到 Redis用一个 Lua 脚本把“检查库存、扣减、记录用户”三个动作放在一次原子操作里完成。-- seckill.lua local stockKey KEYS[1] local userKey KEYS[2] local userId ARGV[1] if redis.call(SISMEMBER, userKey, userId) 1 then return -1 -- 已抢过 end if tonumber(redis.call(GET, stockKey) or 0) 0 then return 0 -- 库存不足 end redis.call(DECR, stockKey) redis.call(SADD, userKey, userId) return 1脚本执行完成后返回三种状态再决定是否发送队列消息创建订单。注意不要把DECR后的库存直接当成可售数量它只是控制入口真正的订单状态还要由异步消费者更新。Lua 脚本在 Redis Cluster 下需要保证 key 落在同一个 slot。提示秒杀场景下 Lua 脚本里用到的 Key 在 Redis Cluster 中必须落到同一个 slot否则线上会报 CROSSSLOT 错误单机环境往往测不出来。4.2 异步订单创建与队列补偿库存扣成功只是第一步订单写入仍要回到 MySQL。同一用户在同一秒杀活动下的订单请求可以放到一个队列用 Redis 做幂等键消费者里先查订单是否存在不存在才创建。Laravel 8 自带的dispatch()如果没有正确配置redis队列驱动很容易把任务丢进数据库队列然后卡死所以生产环境务必改成QUEUE_CONNECTIONredis。SeckillJob::dispatch($userId, $skuId, $activityId) -onQueue(seckill) -onConnection(redis); public function handle() { $exists SeckillOrder::where(user_id, $this-userId) -where(activity_id, $this-activityId) -exists(); if ($exists) { return; } $order DB::transaction(function () { $order Order::create([ order_no generateOrderNo(), user_id $this-userId, sku_id $this-skuId, total_amount $this-amount, ]); StockLog::create([ order_id $order-id, sku_id $this-skuId, change -1, ]); return $order; }); event(new SeckillSucceed($order)); }onQueue(seckill)把秒杀订单和普通订单分流避免普通订单队列被秒杀流量挤爆。DB::transaction里创建订单和库存流水两条 SQL 要么同时成功要么同时回滚订单号由分布式 ID 生成。队列消费还要配失败重试策略$tries和$maxExceptions都要显式设置否则异常任务会无限重试把日志文件打满。方案QPS 上限一致性保障开发成本DB 条件更新较低强最低Redis DECR中弱需补偿中Redis Lua高强较高如果不想引入 Redis也可以把库存放到 MySQL 临时表用“先查后扣”加唯一索引兜底但超卖概率和锁等待都会明显放大。二开时尽量沿用 Lua 方案回调、队列、订单表结构都不用动。4.3 团购与优惠券共享同一套订单流程团购更多是运营门槛而不是技术门槛可以把它看成“带有开团条件的秒杀”。开团时生成group_id成团条件由 Redis 集合大小判断成团后统一发通知并生成物流单。优惠券容易出边界问题重点是校验“券是否属于当前用户、是否在有效期内、是否已经用于某笔订单、使用门槛是否满足”这四项一个都不能少且要用数据库唯一索引把user_id coupon_id order_id锁住避免并发下单重复用券。这里常犯的错是把券状态放在先读到后更新的流程里两个请求同时读到未使用状态一张券被用两次。4.4 三级分销结算佣金计算是异步的三级分销最容易出事的地方在结算。分销关系链不能在订单回调里逐级查询因为分销商和订单不在同一个事务边界。我的习惯是把关系链固化到订单表下单时由后台配置判断该订单归属于哪个一级、二级、三级分销商金额也预先算好支付成功后按比例解冻退单时按原比例原路扣回。$rebates [ [level 1, user_id $inviter1, amount $orderAmount * 0.10], [level 2, user_id $inviter2, amount $orderAmount * 0.05], [level 3, user_id $inviter3, amount $orderAmount * 0.02], ]; Rebate::insert($rebates);佣金结算必须在支付成功后异步触发触发前检查该订单是否已结算过避免微信、支付宝回调重推导致重复发放。三级分销比例做成后台配置项二开时只需在结算服务里读取配置再写明细不需要改表结构。分销商提现时要再走一次财务审核订单退款时还需要生成负数结算单冲抵这套系统里冲抵逻辑往往没有展示在前端容易被忽略。4.5 积分商城与积分流水积分商城库存一般不大可以直接复用 4.1 的 Lua 脚本结构在 ARGV 里多传一个points参数脚本里同时检查积分与库存。积分账户的扣减单独建points_log表不要在users表里直接改积分字段否则用户反馈积分不对时连流水都查不到。5. 前后端分离项目的部署与二次开发nginx、Dockerfile 与路由扩展5.1 Nginx 同时服务静态资源与 API这套商城在服务器上的典型拓扑是Nginx 托管前端编译产物/api请求转发到 Laravel。前端项目里的default.conf可以参照下面的写法server { listen 80; root /var/www/html/mall; index index.html; location /api { proxy_pass http://127.0.0.1:8000; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-Proto $scheme; proxy_read_timeout 60s; } location / { try_files $uri $uri/ /index.html; } }try_files是 Vue3 项目部署时最关键的配置没有它刷新页面就会出现 404。location /api的proxy_pass后面不要加斜杠加了斜杠会把 URL 前缀去掉和后端路由对不上。proxy_read_timeout在普通接口上 60 秒足够但导出和聊天上传接口需要单独放行。5.2 Dockerfile 与部署细节文件列表里的Dockerfile如果只COPY . .而没有 vendor 目录运行时会白屏。正常做法是在镜像构建阶段先composer install再编译路由、配置和视图缓存FROM php:8.1-fpm COPY . /var/www/html WORKDIR /var/www/html RUN composer install --no-dev --prefer-dist --optimize-autoloader \ php artisan route:cache \ php artisan config:cache CMD [php-fpm]xlaravel.pool.conf对应 php-fpm 的池配置容器内存不大时要把pm.max_children调小具体值可以根据单个 php-fpm 进程的平均内存估算。my.cnf里的max_connections和innodb_buffer_pool_size不能沿用默认值秒杀活动前需要提前加大。构建完成进入容器后再把队列消费者挂到进程管理或者单独起一个容器跑php artisan queue:work redis --queueseckill,default --tries3秒杀和普通订单队列分开后即使秒杀任务疯狂重试也不会卡住默认订单队列。5.3 二次开发的扩展方式二开最优先改造的地方不是界面而是路由和事件。商城项目里新增一个“门店自提”模块后端只需在routes/api.php新增一组路由再在订单服务里挂一个订单创建事件监听前端在views/mall下加对应页面即可改动很小。Event::listen(OrderPaid::class, function ($event) { (new StorePickupService())-notifyStore($event-order); });新增代码不要随意改底层命名空间因为源码包的鉴权和路由已经绑定在App\Http\Middleware和App\Models下乱改会导致composer dump-autoload后找不到类。扩展外部服务时可以统一收口到App\Services比如物流查询在LogisticsService里新增方法后用事件订阅OrderShipped即可不影响原有促销和支付链路。本文还有配套的精品资源点击获取
网站建设高端定制企业官网
RELATED

相关资讯

更多精彩内容,欢迎继续阅读

较早相关资讯

最新相关资讯

Xinference 集成 Qwen-Image-Edit:图像编辑模型的启动、GGUF 量化与 Lightning 加速实战指南 2026/9/16 18:13:39

Xinference 集成 Qwen-Image-Edit:图像编辑模型的启动、GGUF 量化与 Lightning 加速实战指南

Xinference 集成 Qwen-Image-Edit:图像编辑模型的启动、GGUF 量化与 Lightning 加速实战指南 【免费下载链接】inference Swap GPT for any LLM by changing a single line of code. Xinference lets you run open-source, speech, and multimodal models on cloud,…

阅读更多 →
PyTorch五子棋AI训练系统:从环境建模到MCTS自对弈闭环 2026/9/16 18:13:39

PyTorch五子棋AI训练系统:从环境建模到MCTS自对弈闭环

简介:本资源是一套面向高校计算机专业本科生的毕业设计级AI项目实践包,聚焦PyTorch强化学习在五子棋游戏中的落地实现,帮助学习者系统掌握DQN/Q-learning建模、环境交互、状态表征与策略优化等核心能力。压缩包共47个文件,含10个核…

阅读更多 →
Headlamp 前端 API 参考:PersistentVolumeClaim KubeObject 类的完整解析 2026/9/16 18:13:39

Headlamp 前端 API 参考:PersistentVolumeClaim KubeObject 类的完整解析

Headlamp 前端 API 参考:PersistentVolumeClaim KubeObject 类的完整解析 【免费下载链接】headlamp A Kubernetes web UI that is fully-featured, user-friendly and extensible 项目地址: https://gitcode.com/GitHub_Trending/he/headlamp 本文基于 Head…

阅读更多 →
使用 AWS CLI 的 codebuild batch-get-reports 批量获取 CodeBuild 测试与覆盖率报告详情 2026/9/16 18:13:39

使用 AWS CLI 的 codebuild batch-get-reports 批量获取 CodeBuild 测试与覆盖率报告详情

使用 AWS CLI 的 codebuild batch-get-reports 批量获取 CodeBuild 测试与覆盖率报告详情 【免费下载链接】aws-cli Universal Command Line Interface for Amazon Web Services 项目地址: https://gitcode.com/GitHub_Trending/aw/aws-cli 导读 本文围绕 AWS CLI 中 a…

阅读更多 →
子域名收集与爆破原理实战:从DNS解析到工具链应用 2026/9/16 18:13:39

子域名收集与爆破原理实战:从DNS解析到工具链应用

做安全评估或者资产梳理的时候,我听到最多的一个问法就是:“这个目标到底有多少个子域名?”主域名往往只是门面,真正承载业务的、风险最高的是那些散落在各个环境里的子域名——测试站点、管理后台、旧版接口、第三方系统&#xf…

阅读更多 →
Hertz v0.6.5 版本解析:RequestContext 的 VisitAll 遍历方法与 HTTP/1.1 协议层四项关键修复 2026/9/16 18:10:38

Hertz v0.6.5 版本解析:RequestContext 的 VisitAll 遍历方法与 HTTP/1.1 协议层四项关键修复

Hertz v0.6.5 版本解析:RequestContext 的 VisitAll 遍历方法与 HTTP/1.1 协议层四项关键修复 【免费下载链接】hertz Go HTTP framework with high-performance and strong-extensibility for building micro-services. 项目地址: https://gitcode.com/GitHub_Tr…

阅读更多 →

今日资讯

本周资讯

本月资讯

看完文章仍有疑问?

联系尧图顾问,获取一对一建站咨询

立即免费咨询 📞 400-888-8888
📞