ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

likeshop单商户商城部署与二开实战:ThinkPHP6+uniapp多端适配全解析

likeshop单商户商城部署与二开实战:ThinkPHP6+uniapp多端适配全解析 简介likeshop企业单商户商城V3.03版本是一套基于ThinkPHP与uniapp开发的开源B2C自营商城系统目标用户是需要快速构建一体化电商平台的中小企业和独立开发者。系统打通PC商城、H5商城、微信小程序商城与APP商城管理后台统一处理数据内置分销裂变、限时秒杀、拼团活动、砍价活动、优惠券、大转盘抽奖、每日签到、小票打印、积分商城、会员价、微信零钱到账以及多种消息通知等常用营销模块。免费企业版与付费企业版功能基本一致可通过Docker一键启动体验。源码包共2001个文件约98.26MB主要包含505个PHP后端接口文件、386个HTML页面、306个JS脚本、219个Vue组件以及Java、SQL、Shell、Nginx配置等类型覆盖后端逻辑、前端交互、数据库脚本和部署环境配置目录结构清晰适合二次开发和部署迁移目前已有177人学习下载。通过这份资源可获得完整商城前后端源码、Docker部署配置和开发文档既能帮助学习者掌握ThinkPHP与uniapp多端开发技巧也可以在企业合规前提下免费商用快速搭建自有品牌商城。1. 单商户商城为什么选 likeshop免费商用许可是第一道分水岭企业要做品牌独立商城往往第一反应是自己搭一套后端可一旦进入订单、支付、售后、营销分层团队就会被细节拖住几个月。likeshop 企业单商户商城 v3.03 把这类需求做成开源系统后端走 thinkphp前端用 uniapp商品、购物车、订单、会员、营销这类单商户核心模块开箱即有代码里明确写着支持免费商用。对企业决策者来说“免费商用”比源码本身更关键个人学习没问题、放进生产环境也不怕许可证纠纷。另一个要读懂的词是“单商户”一个后台管一个店铺多商户是另一套独立代码选型时不能混。下面不假装你看过源码而是按一个接手工程师的视角从一个还没部署的压缩包开始讲完部署、多端跑通、二开排错最后用一个 thinkphp 的 SQL 监听技巧收尾。2. 基于 thinkphp 部署 likeshop 后端从环境校验到管理后台初始化2.1 先看懂 thinkphp 的多应用目录再决定要不要动配置likeshop v3.03 的后端走 ThinkPHP 6 LTS 这条稳定线。TP6 和很多老项目用的 TP3.2 是完全不同的骨架容器、门面、中间件这套机制在 3.2 里都不存在所以接手时千万不要拿 TP3.2 的“单入口挂一堆控制器”的思维去找代码。TP6 默认是多应用模式入口在 public/index.php应用按目录拆分比如管理端是 admin 应用用户端接口是 api 应用支付回调是 notify 应用。URL 第一段决定进哪个应用这就是为什么你访问域名/admin能进后台访问域名/api/v1/goods能拿商品数据。多应用的好处是权限和参数配置能够按应用隔离后台改烂了不影响用户端接口。拿到压缩包第一步不是装环境而是快速扫一眼目录结构和.env文件确认配置路径都被你认全了。2.2 部署前要确认的 PHP、MySQL 与 Redis 参数多数安装失败的根因不是代码问题而是版本组合太新或太旧。下面这张表是 likeshop 这类 thinkphp 商城项目最常见的要求不保证每个版本完全一致但按这个基线去对能少走弯路。项目最低要求说明PHP7.4推荐 8.0 或 8.1ThinkPHP 6 LTS 路线对 PHP 8 兼容性已经成熟不要再上 PHP 5.6PHP 扩展fileinfo、redis、bcmath、openssl、pdo_mysql缺少 fileinfo 会出现文件上传类功能莫名报错MySQL5.7 以上utf8mb4商品标题、地址字段必须用 utf8mb4 才存得下 emoji 和生僻字Redis3.0 以上token、短信验证码、队列都会用到装好先跑redis-cli ping验证Web 服务器Nginx/Apache伪静态必须指向 public 目录否则 thinkphp 路由全 404MySQL 和 Redis 的账号密码不要用 root 加空密码直接上生产。likeshop 后台的配置修改、数据库连接都在.env里用独立账号能降低配置文件泄露造成的连带危害。PHP 版本如果在 8.2 以上先跑一遍 composer install 看有没有 warning部分第三方支付 SDK 的老代码可能报 Deprecated 但不影响运行出现 fatal 再回退 8.1。2.3 用命令完成 composer 安装、数据库迁移与伪静态likeshop 的安装由根目录的 composer.json 管理常见做法是先装依赖再做数据初始化。下面这组命令适用于具备命令行环境的服务器如果你用的是面板步骤等价只是把 shell 操作换成面板按钮。# 1 安装 composer 依赖生产环境跳过开发包并生成类映射 composer install --no-dev --optimize-autoloader # 2 复制环境变量模板并编辑数据库、redis 配置 cp .env.example .env # vim .env 修改 DB_HOST、DB_NAME、DB_USER、DB_PASSWORD、REDIS_HOST # 3 执行数据库迁移建表并写入初始数据 php think migrate:run # 4 初始化管理员账号具体命令以项目 README 为准 php think admin:init # 5 给运行时目录写权限Nginx 用户必须有权限否则后台提交就 500 chmod -R 775 runtime chown -R www-data:www-data runtime命令参数的逻辑拆开说--no-dev在部署机上有两重意义一是减少依赖数量、降低内存消耗二是避免把 phpunit 这类开发工具暴露在生产目录里--optimize-autoloader会为已安装的类生成映射PHP 加载时少一段文件扫描这对商城这种请求密集的系统有意义。migrate:run 是 TP6 的标准迁移命令如果项目没有用迁移而是提供 install.sql直接把 SQL 导入 MySQL 后再执行后面的初始化步骤。admin:init 的具体名字likeshop 不同小版本差异很大有的叫 create_admin有的直接在安装引导页里完成跑命令前先看 README报 Command not found 就说明这一步应该用网页安装。chmod 与 chown 的目的是让 php-fpm 的 www-data 用户能写 runtime 目录里的缓存、日志和 session 文件权限给到 775 就够了不用 777。伪静态配置如下核心是让所有不存在的路径回到 index.phpThinkPHP 6 才能按路由分发表解析。server { listen 80; server_name shop.example.com; root /var/www/likeshop/public; index index.php index.html; location / { if (!-e $request_filename) { rewrite ^(.*)$ /index.php?s$1 last; } } location ~ \.php$ { include fastcgi_params; fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; fastcgi_pass unix:/run/php/php8.1-fpm.sock; } }root 必须指向 public不是项目根目录否则 thinkphp 的入口文件、静态资源路径全乱。rewrite 把真实不存在的请求交给 index.php 处理s 参数里的$1会被框架作为 PATHINFO 解析。php 的 location 用正则匹配优先级高于前缀 locationfastcgi_pass 的 socket 路径要与你机器上实际安装的 PHP 版本一致8.1 写成 php8.1-fpm.sock8.0 就改成 8.0。部署后先访问域名根能打开说明入口没问题再访问/admin看后台是否出来。后台 404 基本就是这段配置没生效不是 thinkphp 路由写错。2.4 后台初始化与数据表校验改后台路径、核对支付配置后台能打开只是第一步。likeshop 默认后台路径是 /admin上线前把它改掉是最低成本的加固。多应用模式下把 admin 应用目录重命名或者用路由别名映射两者都行。如果不想动代码也可以在 Nginx 里做一层访问限制只允许办公网 IP 访问 /admin。进入后台后先不急着看商品去系统设置里过一遍存储引擎、短信密钥、支付参数。默认的本地存储配置再好看生产环境早晚要切到 OSS 或 COS这个切换的字段在后台就能改不用动代码。数据表校验这块单商户商城必然有用户表、用户令牌表、商品表、订单表、订单明细和支付回调记录这几类结构。likeshop 的表前缀常规是ls_实际以你安装后生成的 SQL 为准。打开数据库把表扫一遍重点确认订单表里是否有pay_status、pay_time、transaction_id支付回调表里是否有原始报文和验签结果字段这些字段在后续对账时缺一不可。如果发现表结构和你业务预期差异很大先看是不是装错了版本v3.03 是单商户版别拿多商户的表结构来套。3. 用 uniapp 前端跑通 H5、微信小程序与 App 三端3.1 先分清 uniapp 工程是 HBuilderX 工程还是 CLI 工程likeshop 的前端是一个 uniapp 工程第一步不是到处找运行按钮而是打开 manifest.json 和 package.json 判断它依赖什么运行环境。如果你在根目录看到了app-plus、mp-weixin这些目录同时没有 node_modules那它是 HBuilderX 工程直接用 HBuilderX 导入后运行内置浏览器如果根目录有 package.json 并且依赖里出现dcloudio/vite-plugin-uni那就是 CLI 工程得先npm install。老版本 likeshop 的 uniapp 前端大多是 vue2 语法直接用 vue3 的思维去改 option API 代码反而会掉进this.$refs和生命周期差异的坑。如果要做 vue2 转 vue3先看 package.json 里的依赖版本和公共组件写法别指望一次性全量升级。运行起来不代表多端都正常。HBuilderX 内置浏览器是 H5 环境小程序编译还要另装微信开发者工具App 端要真机或模拟器。mumu 这类 Android 模拟器在 HBuilderX 里选择“运行到手机或模拟器”就能识别但模拟器里的定位、扫码硬件和真机不一样别在模拟器里验证这些能力。3.2 修改 api 基地址与 token 存储一次封装同时适配三端uniapp 的差异化在于 H5 有 localStorage、小程序有 wx storage、App 有本地文件但表面 API 都是uni.getStorageSync、uni.setStorageSync所以封装层必须统一用 uni 提供的方法。likeshop 前端的请求地址一般集中在 utils/request.js 或 config.js 里只需要改一个 BASE_URL。下面这段是常见的请求封装骨架。// utils/request.js const BASE_URL https://shop.example.com/api; export function request(path, options {}) { return new Promise((resolve, reject) { uni.request({ url: BASE_URL path, method: options.method || GET, header: { token: uni.getStorageSync(token), content-type: application/json }, data: options.data || {}, success: (res) { // likeshop 接口以 code 字段表示业务状态具体取值以实际联调为准 if (res.statusCode 200 res.data.code 1) { resolve(res.data); } else if (res.statusCode 401) { // token 失效统一回到登录页 uni.removeStorageSync(token); uni.navigateTo({ url: /pages/login/login }); reject(res); } else { uni.showToast({ title: res.data.msg, icon: none }); reject(res); } }, fail: (err) reject(err) }); }); }header 里塞 token 是从 storage 同步读出来的三端都能拿到401 分支处理了后端踢出登录的场景避免每个页面各自判断错误码。path 前面要带斜杠BASE_URL 末尾不要带斜杠否则拼接出双斜杠地址部分 CDN 和网关会直接拒绝。code 的判断值likeshop 有的版本用 1 表示成功有的用 200你拿到源码后先看接口真实返回再定不要照抄任何网上教程。3.2.1 合法域名与白名单微信小程序正式环境要求所有请求域名都是 HTTPS 且备案开发者工具里可以勾“不校验合法域名”用于调试真机和上架前必须把shop.example.com换成真实域名并配到小程序后台的 request 合法域名里。H5 嵌入公众号时JS-SDK 的签名要求当前页面 URL 必须与公众号后台配置的 JS 接口安全域名一致最关键的是域名精确匹配不能带路径通配也不能用 IP。3.3 H5 嵌入公众号获取定位签名和 URL 是最大坑热搜里“uniapp 开发 h5 嵌入微信公众号中获取定位”问的人很多。原因是 H5 页面的 uni.getLocation 走浏览器定位在微信内置浏览器里会被提示无法获取地理位置信息。生产环境应该接公众号的 JS-SDK wx.getLocation需要先拿到签名。签名接口在后端做前端只取参数再调 wx.config。// app/api/controller/Wechat.php 中的签名方法 public function jsSdkConfig() { // 关键签名用到的 URL 必须是当前页面完整地址且去掉 # 后的部分 $url request()-header(referer); $url strstr($url, #, true) ?: $url; $ticket (new WechatService())-getJsTicket(); $params [ noncestr uniqid(), jsapi_ticket $ticket, timestamp time(), url $url ]; ksort($params); $signature sha1(http_build_query($params)); return json([ appId 公众号 appId, timestamp $params[timestamp], nonceStr $params[noncestr], signature $signature ]); }微信 JS-SDK 签名算法是固定步骤所有参数先按字典序排序再拼成查询字符串最后 sha1。jsapi_ticket要自己缓存微信公众号接口有频率限制建议把 ticket 存 Redis 并加上 7000 秒过期。url 参数必须与前端调用 wx.config 时location.href去掉 # 后的值一字不差很多人签名失败都是因为保留了 hash 或者多了结尾斜杠。前端拿到参数后在页面里调 wx.config再在 wx.ready 里执行 wx.getLocation定位坐标再交给高德做逆地理编码likeshop 的收货地址、门店距离这类功能就能跑通。3.4 微信小程序与安卓/iOS 打包的 4 个必调参数uniapp 打包的参数大多在 manifest.json 的可视化界面里改成源码模式也可以直接编辑。下面 4 个参数是上架和真机验证时最容易漏的。参数配置位置说明小程序 appidmanifest.json - mp-weixin填真实 appid测试号无法提交审核Android 包名manifest.json - App 模块 - Android反向域名格式例如 com.example.shop应用市场以此识别应用iOS Bundle IDmanifest.json - App 模块 - iOS需要与开发者后台创建的 App ID 完全一致统计模块manifest.json - modules 勾选报“打包时未添加 statistics 模块”就回来勾特别说明“修改刚进入的加载页面”小程序冷启动的加载页不是 App.vue 控制的优先看 pages.json 里配置的第一个页面和页面自带的 onLoad 跳转逻辑以及自定义启动图设置。安卓上架应用市场必须用正式签名HBuilderX 云打包会在生成 keystore 时让你设置口令这个口令一旦丢了后续所有更新包都无法覆盖安装一定要做双人备份。iOS 打包只能通过 mac 的 Xcode 或云端打包.p12 证书与描述文件里要带推送权限。公众号里做自定义分享的注意微信分享要求的通用链接也需要在苹果开发者后台配置这跟 uniapp 端没有关系是微信开放平台和苹果侧的联动。4. 二开与多端联调支付回调、扫码和常见报错的排查路径4.1 在 thinkphp 里给单商户追加一个接口的常规做法likeshop 后端二开的核心套路是控制器只做参数接收和返回业务封装在 service 层数据操作通过模型或 Db。这样后台管理端和用户端接口可以共同调用同一套业务方法。举个例子要给商品详情加一个“查询库存和划线价”的接口常规写法如下。// 文件位置app/api/controller/Goods.php ?php namespace app\api\controller; use think\facade\Db; class Goods extends Base { public function stock() { $id (int) $this-request-param(id); if (!$id) { return json([code 0, msg 参数缺失]); } $goods Db::name(goods) -where(id, $id) -field(id, name, stock, line_price) -find(); return json([code 1, data $goods]); } }Db::name(goods)在配置了prefixls_时自动转成ls_goods你写 SQL 时不能自己再加前缀。field里只取页面实际要用的字段商城商品表字段常多达几十个SELECT 全字段会让接口响应和网络带宽双输。(int)强转参数是为了防止把数组传进来触发 thinkphp 的参数绑定异常。如果要做订单闭环控制器到这里就该收手库存扣减和订单生成必须挪到 service让后台和接口共用同一套事务逻辑。4.2 uniapp 扫码、蓝牙与 NFC 的接入边界likeshop 的核销、优惠券场景常要扫码。uni.scanCode 在小程序和 App 端可用在 H5 端不可用所以网页端核销只能接微信 JS-SDK 的扫一扫这是第一层认知。很多人反馈“scancode 扫码扫出来是一串数字”这不一定是 bug。微信扫同一张码如果是链接uniapp 扫出来反而是一串数字首先要确认码内容本身是文本还是网址。码制上要区分 QR Code 和常见的条形码uni.scanCode 默认能处理多种码制但某些加密二维码会扫码结果异常。蓝牙打印、NFC 读卡这类能力在 uniapp 上必须走原生插件机制HBuilderX 打包时勾选对应 module否则运行到真机会报“未添加模块”。uniappx 和 uniapp 的区别也在这里uniappx 面向更底层的新平台插件体系不通用likeshop 前端的组件和插件不要尝试直接改后缀硬套。接入这些原生能力之前先在 manifest.json 的模块列表里核对权限再决定是找现成插件还是写原生扩展。4.3 伪静态、runtime 权限与 thinkphp 安全基线thinkphp 项目在 Nginx 上最常见的两个状态是“后台 404”和“保存后 500”。前者是伪静态没配后者往往是 runtime 目录权限不足用ls -ld runtime看是不是属于 www-data。生产中必须把 APP_DEBUG 设为 falsedebug 开着时 thinkphp 会把 SQL、文件路径、环境配置都打进错误页等于把部署情报直接交给访问者。对 thinkphp 这类知名框架安全通告里多次提到的是缓存文件投递和参数反序列化防御思路不是停止使用而是把暴露面收窄。至少做三步一是把.env挡在 Web 根之外访问二是后台路径改名或做 IP 白名单三是支付回调必须验签。回调验签必须在 notify 应用里做用支付平台下发的公钥验完签名再更新订单状态不能只看请求来源 IP。Nginx 的最小屏蔽配置如下。location ~* ^/(runtime|backup|\.env) { deny all; } location ~ \.php$ { # 保持原有 include、fastcgi_param 等配置 include fastcgi_params; fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; fastcgi_pass unix:/run/php/php8.1-fpm.sock; }正则 location^/(runtime|backup|\.env)匹配以 runtime、backup 或 .env 开头的 URL直接 deny all 返回 403阻断的是通过 URL 读取配置和缓存的路径。注意这个配置放在 server 块里与伪静态并存。fastcgi_param后的 SCRIPT_FILENAME 要确认$document_root与 root 指令一致不然 PHP 文件路径拼错。4.4 联调时最容易混淆的三类错误场景现象排查点H5 接口正常、微信真机接口失败请求能发出但签名报错公众号后台 JS 安全域名与当前 URL 不一致小程序请求 200 但业务 code 不是成功token 已带上仍提示未登录自定义请求头被网关过滤检查 header key 大小写App 打包后全部接口失败页面空白网络报错Android 9 默认禁 HTTP需开 cleartext 或用 HTTPS联调的正确姿势是先做一个最小复现页面页面里什么都不放只调一个接口把 network 面板里的完整请求头、响应头和响应体抄下来对比。likeshop 后端是否接收到 token可以通过 thinkphp 日志查看把 runtime/log 里的当日日志 tail 出来看请求入口和参数。大多数联调问题都不是 likeshop 本身的问题而是开发工具和真机环境的差异比如开发者工具里勾了不校验域名后一片大好真机上一关就露出原形。5. 给 likeshop 的二次开发加一层 SQL 监听用 thinkphp 的 Db::listen 定位慢查询5.1 监听代码加在哪里网上搜“thinkphp 监听 sql 的代码一般添加在哪里”答案集中在两个位置项目根目录的 app/common.php或者 app/provider.php 的服务提供者注册方法里。common.php 会被框架在初始化时自动加载写在这里最简单provider.php 适合和容器绑定、事件订阅一起做。对 likeshop 这种应用目录比较多的项目放在 app/common.php 一处注册全应用生效。5.2 只打慢 SQL 的监听器// app/common.php 追加到文件末尾 use think\facade\Db; use think\facade\Log; Db::listen(function ($sql, $time) { // $time 单位是秒只记录超过 1 秒的执行语句 if ($time 1.0) { Log::warning(sprintf([SQL慢查询] %.2fs | %s, $time, $sql)); } });Db::listen 是 ThinkPHP 6 的事件钩子任何通过 Db 门面或模型执行的 SQL 都会触发回调$sql是带参数绑定的最终语句$time是执行时间。1.0 秒这个阈值在本地开发可能太宽松建议本地用 0.5 秒抓得更直接生产环境如果磁盘 IO 紧张阈值提到 2 秒并且把日志通道指向独立文件避免和业务日志抢写。5.3 配合请求标识定位并发订单问题慢 SQL 看多了会发现单条查询慢不一定说明问题订单高峰期的死锁和行锁等待才是大头。在监听器里区分读写再结合订单号去 MySQL 侧确认锁等待。Db::listen(function ($sql, $time) { if ($time 1.0 strpos($sql, INSERT) false strpos($sql, UPDATE) false) { return; } $traceId request()-param(trace_id, unknown); Log::warning(sprintf([SQL诊断] %s | %.2fs | %s, $traceId, $time, $sql)); });这样上线前压测时写操作单独成一条日志INSERT 和 UPDATE 又慢又集中在订单表就去查 MySQL 的 innodb 锁等待。likeshop 的商品库存预扣和订单生成如果都在同一个事务里把事务里的 SQL 逐条打出来往往能看到某条 UPDATE 在等之前事务的锁。定位到具体语句后再决定是加索引还是减少事务里的无关查询。这一层监听在正式环境建议作为临时手段问题定位完就关长时间开着会放大日志写入带来的额外开销同时确认 runtime 日志目录没有写满否则日志本身会变成商城首页的瓶颈。本文还有配套的精品资源点击获取
返回列表