十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

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

PHP+UniApp商城前后端联调实战:登录态、文件上传与路由打通

PHP+UniApp商城前后端联调实战:登录态、文件上传与路由打通 简介这是一套基于PHP与UniApp技术栈构建的完整商城小程序前后端源码面向Web开发初学者及中小团队开发者解决电商类小程序快速落地与二次开发难题。项目采用ThinkPHP6框架融合事件驱动设计与Layui前端UI库后端高内聚、可扩展前端通过UniApp实现跨平台微信/支付宝/APP统一代码管理支持模块化开发与一键部署。压缩包为ZIP格式大小146.41MB含完整项目结构文件如TP6核心目录、UniApp pages与components模块、Layui静态资源、数据库SQL脚本及安装引导文件便于环境检测、自动初始化与本地调试。目前已有200人学习下载读者可直接获取百万级并发优化的代码重构成果、标准化的前后端分离架构、在线安装部署流程及配套配置说明显著降低商城类小程序从零搭建的技术门槛与运维成本。1. 这不是“拿来即用”的商城模板而是 PHP 后端 UniApp 前端协同落地的最小可行闭环很多人搜“phpuniapp商城小程序前后端源码”第一反应是找一个压缩包解压、改几行数据库配置、npm run build:mp-weixin一跑就上线。现实是90% 的所谓“完整源码”卡在三个致命断点——ThinkPHP6 路由未适配小程序wx.request的header携带规则、UniApp 的uni.uploadFile与 PHP 文件上传逻辑不匹配、小程序登录态code → session_key和 PHP 后端的 JWT 或 session 存储机制完全脱节。这不是代码缺失的问题而是前后端通信契约没对齐。本文聚焦真实生产场景用 ThinkPHP6 构建可验证、可调试、可扩展的 API 层用 UniApp 实现多端一致的用户路径小程序/H5/APP重点拆解「登录态穿透」「文件上传一致性」「路由与权限映射」这三根承重梁。适合已掌握 PHP 基础语法和 UniApp 生命周期、正卡在“前端能发请求但后端收不到参数”或“小程序能登录但订单接口总报 401”的开发者。2. ThinkPHP6 后端从路由定义到登录态校验的硬核配置2.1 多应用模式下必须显式声明小程序 API 路由组ThinkPHP6 默认开启多应用模式但小程序请求路径如/api/v1/goods/list若未在app/route/app.php中明确定义会因路由解析失败直接返回 404。关键不是写Route::get()而是强制指定中间件和跨域策略// app/route/app.php use think\facade\Route; // 小程序专用路由组绑定中间件并禁用 CSRF小程序无 cookie Route::group(api/v1, function () { // 登录相关 Route::post(login, Auth/login); Route::post(logout, Auth/logout); // 商品模块需登录 Route::get(goods/list, Goods/list)-middleware(auth); Route::get(goods/detail/:id, Goods/detail)-middleware(auth); // 订单模块 Route::post(orders/create, Order/create)-middleware(auth); })-middleware(cors); // 自定义跨域中间件非 tp6 内置注意-middleware(auth)不是 TP6 默认的AuthMiddleware必须自定义。小程序不依赖 Cookie所以不能用 TP6 的session验证而要基于AuthorizationHeader 解析 Bearer Token 或小程序code换取的openid。2.2 自定义 auth 中间件用 Redis 存储 openid 与 token 映射关系小程序登录流程是前端调wx.login()获取code→ 传给后端/api/v1/login→ 后端用code向微信服务器换取openid和session_key→ 生成唯一 token 返回 → 前端后续请求带Authorization: Bearer xxx。TP6 的auth中间件需重写// app/middleware/Auth.php ?php declare (strict_types 1); namespace app\middleware; use think\facade\Cache; use think\facade\Request; use think\Response; class Auth { public function handle($request, \Closure $next) { $authHeader $request-header(authorization, ); if (empty($authHeader) || strpos($authHeader, Bearer ) ! 0) { return json([code 401, msg 缺少 Authorization 头])-status(401); } $token trim(substr($authHeader, 7)); // Redis key 格式auth_token:{token} $openid Cache::get(auth_token: . $token); if (!$openid) { return json([code 401, msg 登录已过期请重新登录])-status(401); } // 将 openid 注入 Request供控制器使用 $request-withAttr(openid, $openid); return $next($request); } }逻辑说明Cache::get()使用 Redis推荐避免 MySQL 查询拖慢鉴权。Token 过期时间设为 2 小时Redis TTL比微信session_key的 2 小时略短防止凭据泄露风险。$request-withAttr()是 TP6 3.0 的新写法替代旧版think\Request::instance()-openid $openid。2.3 文件上传必须绕过 TP6 默认的file验证手动处理 multipart/form-dataUniApp 的uni.uploadFile发送的是标准multipart/form-data但 TP6 的Request::file()在某些 Nginx 配置下会丢失Content-Type导致file对象为空。解决方案是放弃Request::file()直接读取原始 POST 数据// app/controller/Upload.php ?php namespace app\controller; use think\facade\Request; use think\facade\Cache; use think\facade\Log; class Upload { public function image() { // 手动解析 multipart/form-data $rawData file_get_contents(php://input); $boundary substr($_SERVER[CONTENT_TYPE], strpos($_SERVER[CONTENT_TYPE], ) 1); // 提取 base64 图片数据UniApp 默认用 base64 上传非二进制流 $postData $_POST; if (isset($postData[base64])) { $base64 $postData[base64]; $imageData base64_decode(str_replace(data:image/png;base64,, , $base64)); // 生成唯一文件名存入 public/uploads/ $fileName upload_ . date(YmdHis) . _ . uniqid() . .png; $filePath public_path() . /uploads/ . $fileName; if (file_put_contents($filePath, $imageData)) { return json([ code 0, msg 上传成功, data [ url /uploads/ . $fileName ] ]); } } return json([code 500, msg 上传失败]); } }参数说明public_path()是 TP6 辅助函数返回public/目录绝对路径/uploads/必须在 Nginx 中配置为静态资源目录location /uploads { alias /www/wwwroot/your-domain/public/uploads/; }否则前端无法访问图片 URL。3. UniApp 前端从小程序登录到 API 请求拦截的全链路实现3.1 小程序端登录用wx.loginuni.request完成 code 换 tokenUniApp 的uni.login在小程序环境实际调用的是wx.login但返回格式与原生不同必须用uni.getProvider判断平台// utils/login.js export const login () { return new Promise((resolve, reject) { uni.getProvider({ service: oauth, success: (res) { if (res.provider.indexOf(weixin) -1) { // 小程序专属wx.login uni.login({ provider: weixin, success: (loginRes) { // 向后端发送 code uni.request({ url: https://your-domain.com/api/v1/login, method: POST, data: { code: loginRes.code }, success: (res) { if (res.data.code 0) { // 保存 token 到 storage uni.setStorageSync(token, res.data.data.token); resolve(res.data.data); } else { reject(res.data.msg); } }, fail: (err) reject(网络错误 err.errMsg) }); } }); } } }); }); };关键点uni.login的provider: weixin必须显式指定否则 H5 环境会报错uni.setStorageSync存储 token 是为了后续请求自动携带避免每次都要调登录。3.2 全局请求拦截自动注入 token 并统一错误处理在main.js中注册全局请求拦截器所有uni.request调用前自动添加Authorization头// main.js import { login } from /utils/login.js; // 请求拦截 uni.addInterceptor(request, { invoke(args) { const token uni.getStorageSync(token); if (token args.url.includes(/api/)) { args.header { ...args.header, Authorization: Bearer token }; } return args; } }); // 响应拦截 uni.addInterceptor(response, { returnValue(res) { if (res.statusCode 401) { // token 过期清除本地存储并跳转登录页 uni.removeStorageSync(token); uni.navigateTo({ url: /pages/login/login }); return Promise.reject(登录过期); } if (res.data?.code ! 0) { uni.showToast({ title: res.data?.msg || 请求失败, icon: none }); return Promise.reject(res.data?.msg); } return res.data; } });提示uni.addInterceptor是 UniApp 3.0 新增 API替代旧版uni.interceptorreturnValue中的res.data?.code ! 0是业务约定需与后端返回结构一致如{ code: 0, msg: ok, data: {} }。3.3 商品列表页用onPullDownRefresh实现下拉刷新与分页加载小程序商品列表必须支持下拉刷新和上拉加载onPullDownRefresh触发时需重置页码并清空数据!-- pages/goods/list.vue -- template view classgoods-list scroll-view scrolltolowerloadMore :scroll-ytrue styleheight: 100vh; view v-foritem in goodsList :keyitem.id classgoods-item image :srcitem.cover modeaspectFill/image text classtitle{{ item.name }}/text text classprice¥{{ item.price }}/text /view view v-ifloading classloading加载中.../view view v-ifnoMore classno-more没有更多了/view /scroll-view /view /template script export default { data() { return { goodsList: [], page: 1, pageSize: 10, loading: false, noMore: false }; }, onPullDownRefresh() { this.page 1; this.goodsList []; this.noMore false; this.loadGoods(); }, methods: { async loadGoods() { if (this.loading || this.noMore) return; this.loading true; try { const res await uni.request({ url: /api/v1/goods/list, data: { page: this.page, size: this.pageSize } }); if (res.data.list.length this.pageSize) { this.noMore true; } this.goodsList [...this.goodsList, ...res.data.list]; this.page; } catch (err) { console.error(err); } finally { this.loading false; uni.stopPullDownRefresh(); // 必须手动停止 } }, loadMore() { this.loadGoods(); } } }; /script参数说明uni.stopPullDownRefresh()是小程序特有 APIH5 环境需加判断res.data.list是后端返回的数组字段需与 TP6 控制器return json([list $list]);结构严格对应。4. 前后端联调用 Postman 模拟小程序请求 Nginx 关键配置4.1 用 Postman 模拟小程序登录全流程绕过前端当小程序前端报错“登录失败”时先用 Postman 排查后端是否正常步骤Postman 操作预期响应1GET https://api.weixin.qq.com/sns/jscode2session?appidxxxsecretxxxjs_codexxxgrant_typeauthorization_code得到openid和session_key2POST https://your-domain.com/api/v1/loginBody raw JSON{code: xxx}返回{code:0,data:{token:xxx}}3GET https://your-domain.com/api/v1/goods/listHeaders 加Authorization: Bearer xxx返回商品列表 JSON提示第 1 步的js_code可从真机调试的小程序控制台复制wx.login的res.code第 2 步若返回code500检查 TP6 日志runtime/log/下的错误堆栈常见是curl_init()未启用或 SSL 证书问题。4.2 Nginx 必须配置的 3 项跨域、上传限制、静态资源TP6 UniApp 小程序部署在 Linux 服务器时Nginx 配置决定成败# /www/server/nginx/conf/vhost/your-domain.conf server { listen 80; server_name your-domain.com; # 1. 跨域头允许小程序域名 add_header Access-Control-Allow-Origin https://servicewechat.com; add_header Access-Control-Allow-Methods GET, POST, OPTIONS, DELETE, PUT; add_header Access-Control-Allow-Headers DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range,Authorization; add_header Access-Control-Expose-Headers Content-Length,Content-Range; # 2. 上传大小限制小程序图片上传常超 2MB client_max_body_size 50M; # 3. 静态资源代理UniApp 构建的 dist 目录 location / { root /www/wwwroot/your-domain/dist/build/mp-weixin; index index.html; try_files $uri $uri/ /index.html; } # 4. API 接口反向代理到 PHP-FPM location /api/ { proxy_pass http://127.0.0.1:9000/; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; } # 5. 上传图片静态目录 location /uploads/ { alias /www/wwwroot/your-domain/public/uploads/; expires 1h; } }注意Access-Control-Allow-Origin必须写https://servicewechat.com小程序官方域名不能写*否则微信客户端拒绝请求proxy_pass后的http://127.0.0.1:9000/对应 PHP-FPM 的监听地址需与www.conf中listen 127.0.0.1:9000一致。4.3 小程序真机调试抓包定位wx.request失败原因在微信开发者工具中开启“调试器 → Network”但真机调试需用weinre或vConsole# 在项目根目录安装 vConsole npm install vconsole --save-dev # main.js 中引入仅开发环境 if (process.env.NODE_ENV development) { const vConsole require(vconsole); new vConsole(); }然后在真机上打开小程序点击右上角菜单 → “调试” → “vConsole”即可看到所有wx.request的请求头、响应体、状态码。常见问题400 Bad Request后端未正确解析Content-Type: application/json检查 TP6 是否开启了json中间件404 Not FoundNginx 未将/api/路径代理到 PHP检查location /api/配置0 Unknown ErrorHTTPS 证书不被信任需用腾讯云/阿里云免费 DV 证书。5. 进阶技巧用 Docker 容器化部署 ThinkPHP6 UniApp 构建产物5.1 构建 PHP-FPM 镜像预装 Redis 扩展与 GD 库TP6 商城需 Redis 缓存和 GD 图片处理Dockerfile 必须显式安装# Dockerfile.php FROM php:8.1-fpm # 安装必要扩展 RUN apt-get update apt-get install -y \ libfreetype6-dev \ libjpeg-dev \ libpng-dev \ docker-php-ext-configure gd --with-freetype --with-jpeg \ docker-php-ext-install -j$(nproc) gd opcache pdo_mysql redis \ pecl install xdebug-3.1.5 \ docker-php-ext-enable xdebug # 复制项目 COPY ./backend /var/www/html WORKDIR /var/www/html RUN composer install --no-dev # 暴露端口 EXPOSE 9000 CMD [php-fpm]参数说明libfreetype6-dev是 GD 库依赖缺失会导致imagecreatefrompng()报错--no-dev减少镜像体积xdebug仅开发环境启用生产环境删掉pecl install行。5.2 构建 Nginx 镜像集成 UniApp 构建产物与 PHP-FPM 代理Nginx 镜像需同时服务静态文件和反向代理# Dockerfile.nginx FROM nginx:alpine # 复制 UniApp 构建产物 COPY ./frontend/dist/build/mp-weixin /usr/share/nginx/html # 复制 Nginx 配置 COPY ./nginx.conf /etc/nginx/nginx.conf # 复制 PHP-FPM 代理配置 COPY ./nginx-site.conf /etc/nginx/conf.d/default.conf # 暴露端口 EXPOSE 805.3 docker-compose.yml一键启动全栈服务# docker-compose.yml version: 3.8 services: php: build: context: . dockerfile: Dockerfile.php volumes: - ./backend:/var/www/html - ./php.ini:/usr/local/etc/php/php.ini networks: - mall-network nginx: build: context: . dockerfile: Dockerfile.nginx ports: - 80:80 depends_on: - php networks: - mall-network redis: image: redis:7-alpine command: redis-server --appendonly yes volumes: - redis-data:/data networks: - mall-network volumes: redis-data: networks: mall-network: driver: bridge验证方法执行docker-compose up -d后访问http://localhost即可看到小程序构建的 H5 版访问http://localhost/api/v1/login返回{code:500,msg:缺少 code 参数}证明 PHP 服务已通。真正的小程序需在微信开发者工具中配置合法域名https://your-domain.com并上传代码。本文还有配套的精品资源点击获取
返回列表