ThinkPHP 6 + Vue 3 打造茶园茶农文化交流平台完整实践

发布时间:2026/9/8 1:12:59
ThinkPHP 6 + Vue 3 打造茶园茶农文化交流平台完整实践 两年前一个做文旅的朋友找到我说想给当地茶园和茶农做一个线上交流平台茶叶资讯、茶文化文章、茶农之间的经验问答顺带把茶叶和茶具卖起来。当时我脑子里迅速过了一遍技术栈最后定下的方案就是标题里这套组合——ThinkPHP 6 LTS 做后端接口Vue 3 做前端界面合起来实现这个“thinkphpvue茶园茶农文化交流平台”。这种需求其实非常典型有内容展示、有用户体系、有社区互动还掺了一点轻电商逻辑。技术上不算难但麻雀虽小五脏俱全真要从零搭起来要踩的坑一点不比大型项目少。这篇文章我就以这个项目为例子把从需求拆解、数据库设计、PHP 后端接口、Vue 前端页面到打包上线的完整链路讲一遍重点讲清楚每个环节“为什么这么做”以及实际操作中容易翻车的地方。如果你正在构思类似的文化交流类平台、社区系统或者想用一套成熟方案做课设、毕设这篇应该能帮你少走不少弯路。1. 项目到底在做什么需求拆解与整体设计1.1 把笼统标题翻译成可落地的功能清单拿到“茶园茶农文化交流平台”这个标题第一件事不是写代码而是先把用户角色和核心业务场景列出来。我习惯用一句话定义项目让茶农能发内容、让茶友能看内容、让双方能交流互动、让好茶能卖出去。这句话直接决定了功能边界不会做偏。按这个定义我把系统拆成了四个端游客端可以浏览茶园资讯、茶文化文章、茶叶商品列表但要发帖、评论、下单就必须登录。普通用户端茶友注册登录后可以收藏文章和商品、发表评论、发布交流帖、加入购物车、提交订单。茶农/商户端比普通用户多了“内容发布权限”可以发布茶园动态、茶文化文章、上架茶叶商品、处理订单状态。管理后台端管理员负责用户审核、内容审核、商品上下架、数据统计、公告发布。对应到功能模块就是一套经典组合用户认证、内容管理、社区互动、商品交易、后台管理。下面这张表是我做模块拆分时用的直接照着开发就行模块核心功能关键实现点用户模块注册、登录、个人信息、收货地址JWT 认证、手机号/邮箱绑定内容模块茶园资讯、茶文化文章、视频富文本发布、图片上传、HLS 视频播放交流模块发帖、评论、点赞、收藏列表分页、嵌套评论商品模块茶叶展示、搜索筛选、购物车、订单SKU 规格、订单状态机后台模块用户/内容/商品管理、数据看板RBAC 权限控制、统计图表1.2 技术选型为什么是 ThinkPHP 6 LTS Vue而不是别的组合整个项目定下 ThinkPHP 6.0.12 LTS Vue 3不是拍脑袋是我在几个方案里反复比较后的选择。先看后端。PHP 在这个场景里最大的优势是部署简单、上手门槛低ThinkPHP 又是国内使用率最高的 PHP 框架之一手册完善、社区活跃遇到问题搜一下几乎都有答案。选 6.0.12 LTS 而不是 5.x 或者 8.x 的最新版核心原因是稳定。LTS 版本会持续修 bug 和安全补丁不会像大版本升级那样出现破坏性变更对于需要长期维护的交流平台来说“稳”比“新”重要得多。再看前端。Vue 3 目前已经是绝对主流组合式 API 让逻辑复用和代码组织比 Vue 2 时代舒服很多Element Plus 组件库又是一套现成的后台管理界面方案开发效率很高。前后端通过 RESTful API 通信天然支持以后要做 App、小程序时复用同一套后端接口。数据库选了 MySQL 8.0配套 Redis 做验证码缓存和热点数据缓存。文件存储初期直接用本地磁盘以后流量大了再切 OSS 也不迟。我的原则是能用简单方案解决的就不要提前引入分布式组件项目不是越复杂越好而是越合适越好。1.3 工程分层前后端目录设计一个能长期维护的项目目录结构一定要在动手前定好。后端我沿用了 ThinkPHP 6 的默认分层但额外增加了app/middleware和app/common两个目录分别放认证中间件和公共函数/app /controller // 控制器只做参数接收和结果返回 /model // 模型层处理数据查询和业务逻辑 /middleware // 中间件JWT验证、权限控制、跨域 /common // 公共函数和常量 /validate // 参数验证器前端用 Vite 创建 Vue 3 工程后我把src内部按功能做了拆分/src /api // 所有接口请求方法按模块拆文件 /router // 路由表 路由守卫 /store // Pinia 状态管理 /views // 页面组件按业务模块建目录 /components // 通用组件 /utils // 请求封装、工具函数有一个经验值得分享API 请求一定要集中放到src/api目录里不要每个页面组件里直接写 axios。后期接口域名变了、需要统一加 token、统一做错误处理时只改一个文件就行否则几十个页面挨个找能让人崩溃。2. 核心数据模型与后端 ThinkPHP 实现2.1 数据库设计从用户到订单别漏表数据库设计是整个项目的根基表结构一旦定错后面改起来牵一发动全身。这个平台我设计了 12 张核心表覆盖用户、内容、互动、交易四大块user用户表字段包括手机号、密码、昵称、头像、角色、状态。article资讯/文化文章表包含标题、封面、正文、所属分类、发布者ID。article_category内容分类表比如“茶园资讯”“茶文化”“制茶工艺”。topic交流社区帖子表包含标题、内容、发布者、浏览数、点赞数。comment评论表支持二级回复用parent_id关联父评论。goods茶叶商品表包含商品名称、主图、价格、库存、产地、规格。cart购物车表关联用户和商品。order订单表包含订单号、用户ID、总金额、状态、收货信息。order_item订单明细表记录每个订单包含哪些商品、数量、单价。favorite收藏表用户对文章或商品的收藏记录。video茶文化视频表存视频标题、封面、播放地址。admin管理员表后台登录使用。以topic表为例核心字段如下CREATE TABLE topic ( id int(11) unsigned NOT NULL AUTO_INCREMENT, user_id int(11) NOT NULL COMMENT 发布者ID, title varchar(100) NOT NULL COMMENT 标题, content text NOT NULL COMMENT 正文内容, images json DEFAULT NULL COMMENT 图片列表, view_count int(11) NOT NULL DEFAULT 0 COMMENT 浏览数, like_count int(11) NOT NULL DEFAULT 0 COMMENT 点赞数, status tinyint(1) NOT NULL DEFAULT 1 COMMENT 1显示 0隐藏, create_time int(11) NOT NULL COMMENT 创建时间, update_time int(11) NOT NULL COMMENT 更新时间, delete_time int(11) DEFAULT NULL COMMENT 软删除时间, PRIMARY KEY (id), KEY idx_user_id (user_id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT交流帖子表;这里有几个细节值得注意。第一所有表统一用utf8mb4字符集因为茶文化内容里经常会用到生僻字、特殊符号utf8三个字节存不下。第二delete_time字段做软删除管理员下架内容时不是物理删除方便以后排查问题。第三列表查询涉及外键的字段都要建索引比如user_id否则数据量上来后查询会非常慢。2.2 接口设计统一返回格式与接口清单前后端分离的项目接口规范比接口数量更重要。我在项目里定了一个统一返回格式{ code: 200, message: success, data: {} }code为 200 表示成功401 表示未登录403 表示无权限500 表示业务异常。message是给前端提示用的文本直接可以弹出。data是业务数据可以是对象、数组也可以是分页结构{ list, total, page }。后端在 ThinkPHP 中做了一个全局的响应 trait所有控制器调用success()和error()方法返回统一结构避免每个方法自己拼数组代码能整洁不少。接口设计上我按资源划分为以下几组模块接口说明用户POST /api/user/register注册用户POST /api/user/login登录返回 JWT token用户GET /api/user/info获取当前用户信息内容GET /api/article/list文章分页列表内容GET /api/article/detail文章详情内容GET /api/video/play获取视频播放地址交流POST /api/topic/publish发布帖子需登录交流GET /api/topic/list帖子列表交流POST /api/comment/submit发表评论需登录商品GET /api/goods/list商品列表支持关键词和分类筛选交易POST /api/order/create创建订单需登录2.3 JWT 认证与权限控制交流平台有用户体系就必须有登录认证。我选了 JWTJSON Web Token而不是传统的 Session核心原因是平台未来很可能还要出小程序甚至 AppJWT 无状态、跨端友好、天然适合前后端分离架构。ThinkPHP 6 里使用 JWT我通常用firebase/php-jwt这个第三方库。安装后封装一个签发方法use Firebase\JWT\JWT; use Firebase\JWT\Key; // 签发 token public function createToken($userId, $role) { $payload [ iss tea-platform, iat time(), exp time() 7 * 24 * 3600, uid $userId, role $role ]; return JWT::encode($payload, env(JWT_SECRET), HS256); }exp有效期我设置了 7 天茶文化交流平台的使用频率不像电商那么高token 太短会让用户频繁重新登录。如果以后要做支付等敏感操作再单独加一个短期 access token 长期 refresh token 的方案也不迟。权限验证通过中间件实现在app/middleware/AuthCheck.php中解析 tokenpublic function handle($request, \Closure $next) { $token $request-header(Authorization); if (!$token) { return json([code 401, message 请先登录]); } try { $decoded JWT::decode($token, new Key(env(JWT_SECRET), HS256)); $request-uid $decoded-uid; $request-role $decoded-role; } catch (\Exception $e) { return json([code 401, message 登录状态已过期]); } return $next($request); }注册中间件时要注意放行规则登录、注册、文章列表、商品列表这些公开接口不能拦截。ThinkPHP 中间件支持在路由或应用中配置except白名单我第一次做的时候忘了排除结果前端打开首页就报 401排查了半天才发现是登录接口本身没放行。2.4 文件上传与图片处理茶文化平台每天会产生大量图片文章封面、茶园实拍、茶叶商品图、用户头像。文件上传这块处理不好体验会非常差。ThinkPHP 6 的文件上传思路很清晰先拿文件对象再校验合法性最后移动到目标目录。我在common里封装了一个上传方法核心逻辑如下public function uploadImage(UploadedFile $file) { // 校验文件类型和大小 $validate [ size 5 * 1024 * 1024, ext jpg,jpeg,png,gif,webp ]; if (!$file-check($validate)) { throw new \Exception($file-getError()); } // 按日期分目录存储避免单个目录文件过多 $savePath /storage/images/ . date(Ymd) . /; $fileName md5(uniqid()) . . . $file-extension(); $file-move(public_path() . $savePath, $fileName); return $savePath . $fileName; }文件名我选择用md5(uniqid())重新生成而不是保留用户上传的原始文件名可以彻底避免同名文件覆盖问题也防止中文名或特殊字符在 URL 中产生兼容性问题。图片目录按日期分层的做法方便以后写定期清理任务处理起来非常顺手。关于图片压缩我后来补充了一个优化上传原图的同时如果图片宽度超过 1200px用 GD 库生成一张等比缩略图用于列表展示。移动端浏览列表时不用加载高清大图页面速度提升非常明显。2.5 SQL 监听与性能排查做后台接口时接口为什么这么慢是我被问得最多的问题。ThinkPHP 本身支持日志记录 SQL但我更推荐用事件监听的方式把所有 SQL 统一打到一个专门的日志文件里排查问题时一翻一个准。ThinkPHP 6 监听 SQL 的代码位置在app/event.php中注册事件绑定然后在app/listener/QueryListener.php里执行日志记录namespace app\listener; use think\facade\Log; class QueryListener { public function handle($sql) { Log::channel(sql)-info(SQL: . $sql-getSql() . | 参数: . json_encode($sql-getBind())); } }生产环境我会把这条日志关掉或者只记录慢查询因为每个请求都可能执行好几条 SQL全量写入会拖慢性能。实际开发中用这个监听工具我发现了不少性能问题典型的就是 N1 查询。比如文章列表只查出 10 篇文章然后在循环里每篇文章查一次作者和分类10 篇文章就会产生 21 条 SQL。解决方案是使用 ThinkPHP 的关联预加载$list ArticleModel::with([author, category]) -where(status, 1) -paginate(10);把 N1 变成 12 条 SQL性能差距在小数据量时看不出来一旦文章上百条、用户上千个差距就是几倍到几十倍。3. 前端 Vue 工程搭建与核心模块实现3.1 环境准备与工程初始化前端这半边环境配置是第一道坎很多新手在这里就卡住了。我的建议是先把 Node.js 装成 LTS 版本然后换镜像源不然再好的电脑装个别依赖也容易等到怀疑人生。# 检查 node 版本推荐 18 以上 node -v # 使用镜像源加速依赖安装 npm config set registry https://registry.npmmirror.com # 用 Vite 创建 Vue 3 工程 npm create vitelatest tea-web -- --template vue # 进入工程并安装常用依赖 cd tea-web npm install vue-router4 pinia axios element-plus工程初始化后我会第一时间把目录调整成前面规划好的结构再配置 Vite 的路径别名避免组件里出现一长串../../// vite.config.js import { defineConfig } from vite import vue from vitejs/plugin-vue import path from path export default defineConfig({ plugins: [vue()], resolve: { alias: { : path.resolve(__dirname, src) } } })Element Plus 这种大型组件库全量引入会导致打包体积很大我在项目里改成了按需自动导入。热门词里提到的自动导入 elementplus就是这么做的安装unplugin-auto-import和unplugin-vue-components两个插件配好之后你在模板里写了el-button组件会被自动按需加载体积能减少一半以上。3.2 路由与状态管理的设计Vue 3 项目我默认用 Vue Router 4 Pinia。路由表在设计时就区分好权限等级const routes [ { path: /, name: Home, component: () import(/views/Home.vue) }, { path: /articles, name: ArticleList, component: () import(/views/article/List.vue) }, { path: /article/:id, name: ArticleDetail, component: () import(/views/article/Detail.vue) }, { path: /goods, name: GoodsList, component: () import(/views/goods/List.vue) }, { path: /login, name: Login, component: () import(/views/user/Login.vue) }, { path: /user, component: () import(/views/user/Layout.vue), meta: { requiresAuth: true }, children: [ { path: profile, name: UserProfile, component: () import(/views/user/Profile.vue) }, { path: orders, name: UserOrders, component: () import(/views/user/Orders.vue) } ] }, { path: /admin, component: () import(/views/admin/Layout.vue), meta: { requiresAdmin: true } } ]每个页面组件都用懒加载方式引入这样首屏只加载必要的 JS 文件。路由守卫负责登录检查router.beforeEach((to, from, next) { const token localStorage.getItem(token) if (to.meta.requiresAuth !token) { next({ path: /login, query: { redirect: to.fullPath } }) return } next() })这里有个实际体验优化登录成功后用query.redirect回到原来想访问的页面而不是固定跳回首页用户连续操作时不会觉得被打断。Pinia 的 store 主要管理两样东西用户信息和登录状态。刷新页面后Pinia 里的数据会清空所以必须把 token 持久化到localStorage然后在应用启动时重新拉取用户信息。我在App.vue的onMounted里调了一次getUserInfo接口保证刷新后用户仍处于登录状态。3.3 通用请求封装与跨域处理Vue 项目里所有页面都要请求后端接口axios 必须做统一封装。封装的要点有三个baseURL 配置、token 自动注入、错误统一处理。import axios from axios import { ElMessage } from element-plus import router from /router const request axios.create({ baseURL: import.meta.env.VITE_API_BASE_URL || /api, timeout: 15000 }) // 请求拦截器自动携带 token request.interceptors.request.use(config { const token localStorage.getItem(token) if (token) { config.headers.Authorization token } return config }) // 响应拦截器统一处理业务码和 HTTP 错误 request.interceptors.response.use( response { const res response.data if (res.code ! 200) { ElMessage.error(res.message) return Promise.reject(new Error(res.message)) } return res.data }, error { if (error.response error.response.status 401) { localStorage.removeItem(token) router.push(/login) } ElMessage.error(网络异常请稍后重试) return Promise.reject(error) } )这里我踩过一个很经典的坑ThinkPHP 后端接口在测试时直接访问没问题前端请求就报跨域错误。做到了一半才发现是 Content-Type 的问题。前端用 axios POST 请求时默认 Content-Type 是application/jsonThinkPHP 接收 JSON 数据要用json方法获取而post()方法默认接收的是表单格式。我后来统一在 axios 请求封装里设置Content-Type: application/json后端控制器用request-json()读取参数两边对齐后问题彻底解决。开发环境的跨域我推荐在 Vite 配置 proxy 代理而不是在后端开 CORS。原因很简单生产环境前后端同源部署不需要 CORS只有开发环境存在跨域写在 Vite 配置里最省心还能减少不必要的安全风险。3.4 核心页面实现茶园资讯与视频播放整个平台最核心的内容页面是茶园资讯和文化视频页。文章列表页用卡片布局展示封面、标题、发布时间和浏览量Element Plus 的el-cardel-pagination就能快速搞定。文章详情页要特别注意富文本内容的展示。后端保存的文章内容是 HTML 格式前端直接v-html渲染这里有个安全坑如果富文本里有恶意script标签v-html会直接执行常见的做法是引入dompurify库做过滤import DOMPurify from dompurify // 渲染前先净化 HTML const safeHtml DOMPurify.sanitize(article.content)茶文化视频这块热门词里提到的vue播放m3u8正好用得上。很多茶园直播或者茶农拍摄的视频原片是 TS 切片流前端不能直接放video标签需要用 hls.js 做解码。我的实现思路如下import Hls from hls.js function initPlayer(videoElement, url) { if (Hls.isSupported()) { const hls new Hls() hls.loadSource(url) // url 是 .m3u8 地址 hls.attachMedia(videoElement) } else if (videoElement.canPlayType(application/vnd.apple.mpegurl)) { // iOS 系统原生支持 HLS直接赋值即可 videoElement.src url } }这段逻辑最需要注意的是兼容性判断iOS Safari 原生支持 HLSAndroid Chrome 则要依赖 hls.js 做 MSE 解码不能想当然地只用一种方式。3.5 交流社区与商品流程交流社区是平台互动氛围的核心。帖子列表、帖子详情、评论区、点赞收藏这些交互看起来简单但要做得顺手有几个细节值得下功夫。分页加载是第一个细节。交流社区不适合传统的大页码分页移动端用户更习惯下拉加载。为了兼顾 PC 端的浏览习惯我设置了一个阈值PC 端默认显示分页器移动端改成触底加载更多。实现触底加载就是监听滚动事件当滚动位置接近底部时调用下一页接口并追加数据。第二个细节是评论的回复交互。我实现的评论是二级结构顶层评论直接展示回复的评论缩进在下方。发评论的接口需要传topic_id和parent_idparent_id为 0 表示顶层评论。后端查询评论列表时用一次 SQL 把顶层评论取出来再通过parent_id IN (顶层评论ID集合)查子评论最后在内存中按父子关系组装成树形结构避免循环查询数据库。商品流程实际上是一个简化版电商商品列表筛选、商品详情、加入购物车、提交订单。订单状态我定义了四种待付款、待发货、待收货、已完成。商品库存的扣减是关键我在创建订单时用了数据库事务Db::transaction(function () use ($request) { // 1. 锁定商品行查询并校验库存 // 2. 扣减库存 // 3. 创建订单主表 // 4. 创建订单明细表 // 5. 清空购物车 });3.6 后台管理视图管理后台我单独做了一套布局左侧菜单、右侧内容区。这里我用了 Vue Router 的嵌套路由实现菜单项和路由配置一一对应。数据概览页用统计卡片展示关键指标新增用户数、今日发帖数、商品销量、待处理订单数再配一个茶文化文章浏览量 TOP10 的表格。这些统计接口都是后端用聚合查询算出来的前端只需要把接口返回的数据渲染到卡片上。内容审核是后台最重要的功能。管理员在列表里看到用户发布的文章和帖子可以点击通过或下架前端调用接口更新状态即可。这里有一个表格列渲染的小技巧状态列用el-tag标签展示不同状态用不同颜色审核人员扫一眼就能知道哪些内容需要处理。4. 前后端联调、打包部署与常见问题排查4.1 环境变量与开发联调前后端联调阶段最容易出问题的是接口地址不一致。我会在前端根目录建一个.env.development文件VITE_API_BASE_URL/api然后在 Vite 配置里加代理把/api开头的请求转发到本地 ThinkPHP 服务server: { proxy: { /api: { target: http://127.0.0.1:8000, changeOrigin: true } } }这样前端代码里请求/api/user/loginVite 开发服务器会自动转发到http://127.0.0.1:8000/api/user/login浏览器里不会出现跨域报错。生产环境构建时需要确保后端接口地址能通过公网访问。如果前后端部署在同一台服务器的同一个 Nginx 站点下直接使用相对路径/api就好如果分开部署就要把VITE_API_BASE_URL改成后端服务的完整域名并在后端配置 CORS。4.2 打包与常见部署方案前端打包执行npm run build产物在dist目录。部署方案我两种都试过各有适用场景。方案一前端打包产物放到 ThinkPHP 的public目录下由 Nginx 统一托管。这样做最简单不需要考虑跨域后端接口直接按相对路径请求。缺点是每次更新前端代码都要重新覆盖 public 目录里的文件管理起来稍显混乱。方案二前端dist目录独立部署后端单独开启一个站点通过反向代理合并 API 路径。这种方案我最终采用了前后端可以独立发布、独立扩容也更接近正规项目结构。Nginx 配置我列一个最小可用版本核心就是 SPA 应用的路由重写否则刷新页面会 404server { listen 80; server_name tea.example.com; root /var/www/tea-web/dist; index index.html; # SPA 路由重写 location / { try_files $uri $uri/ /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; } }这里必须说明只有前端用了 history 模式才需要try_files重写如果用的是 hash 模式则不需要但 URL 会带上#美观度和 SEO 都不如 history 模式。4.3 高频踩坑记录从登录到打包的实战问题这里我把项目全流程中遇到的高频问题整理成一个表都是真实踩过的坑不是教科书里的理论问题问题现象原因解决方案登录后刷新页面就退出F5 后用户信息丢失Pinia 数据不持久化token 存 localStorage启动时重新请求用户信息购物车总金额不对勾选/取消商品时金额计算错误勾选状态更新时使用回调函数时机错误用 computed 重新计算不依赖缓存变量富文本图片不能显示详情页图片全部裂掉图片路径是后端相对路径前端域名拼不上后端返回完整域名或前端统一拼接图片前缀手机端视频不能播放Android 正常iOS 黑屏HLS 在 iOS 需使用原生能力按平台判断iOS 直接赋值 video.src接口返回 422发布文章一直失败验证器校验不通过但前端没显示具体错误后端统一返回message前端弹出完整提示时间显示相差 8 小时评论时间比实际时间晚 8 小时PHP 和前端时区不一致后端统一存时间戳前端用 dayjs 格式化这里我特别想展开说一下Vue 对象赋值页面不变这个热门词对应的问题。很多时候我们写了obj.name 新值页面却没有任何变化。原因往往是用了ref定义响应式对象但给整个对象重新赋值时没有使用.value比如这样const form ref({ title: }) // 错误做法直接给 form 整体赋值丢失响应式 form { title: 新标题 } // 正确做法修改 ref 的 value form.value.title 新标题 form.value { title: 新标题 }这个问题在 Vue 3 的reactive和ref使用上特别容易犯。我的经验是项目里统一约定用ref定义基本类型用reactive定义嵌套对象并且所有复杂对象赋值都用Object.assign(target, source)一劳永逸避免响应式丢失。还有 Vue DevTools 这个工具做 Vue 项目一定要装。我排查上面这种响应式问题时就是靠 DevTools 的组件树里实时查看form的值是否更新。如果 DevTools 里显示值已经变了但页面没动基本可以确定是响应式丢失如果 DevTools 里值都没变那问题出在数据源思路会完全不一样。4.4 安全与性能优化补充分项目进入后期我花了不少时间做安全和性能优化这些往往是容易被忽视但会被用户直接感知的部分。安全方面PHP 后端我做了三件事第一所有 SQL 查询都通过 ThinkPHP 的 ORM 链式操作避免在复杂查询条件下拼接 SQL 造成注入风险第二富文本内容入库前过滤script等危险标签前端展示再配合 DOMPurify 双重净化第三后台路径使用独立域名或路径前缀并且管理员登录采用二次验证降低账号被爆破的风险。性能方面列表页的分页接口加了简单的缓存策略文章列表、商品列表这种更新频率不高但访问量大的数据用 Redis 缓存 5 分钟数据变更时主动清理缓存。图片懒加载用el-image自带的懒加载能力视频封面用压缩图首屏需要加载的资源和体积都控制在合理范围。前端代码层面我还顺手做了组件懒加载和路由懒加载的双保险npm run build后生成的可视大小报告里主 JS 包控制在 300KB 以内首屏加载时间在生产环境实测差不多 2 秒左右对一台小带宽服务器来说算是能接受了。5. 常见问题与排查技巧实录5.1 登录接口的 401 死循环问题项目第一版上线后前端偶尔会陷入请求接口 - 返回 401 - 跳转登录页 - 登录成功 - 又请求接口 - 又 401的死循环。排查过程让我印象很深。后来在响应拦截器里加了打印日志才发现问题出在后端对于登录接口本身也在做 JWT 校验导致登录请求还没发出去就被拦截了。解决方法是给中间件配置一个放行列表把user/login、user/register、article/list等公开接口统统排除在外。这个坑的本质是中间件作用域没想清楚。ThinkPHP 的中间件如果配置到了全局就会作用在所有路由上必须显式排除公开接口。我后来的经验是全局中间件只放跨域和日志JWT 验证中间件单独挂载到需要登录的路由分组下面职责分离互相不干扰。5.2 移动端 WebView 打开空白页因为后来有客户想把这个平台嵌入到 App 里我用 Android 和 iOS 的 WebView 分别做了测试结果都出现了白屏问题。原因其实不复杂前端打包后生成的 JS 和 CSS 文件路径默认是绝对路径比如/assets/index.js在 WebView 里通过file://协议打开时找不到这个路径下的资源页面自然白屏。解决方法是修改 Vite 的base配置export default defineConfig({ base: ./, // 其余配置 })改成相对路径后打包产物里所有资源引用都变成./assets/index.jsWebView 就能正常加载了。这件事也提醒我上线前一定要用部署环境实测不能只看本地开发环境的表现。5.3 接口慢的定位思路在网上一搜thinkphp 监听sql说明很多人遇到过接口慢的问题但是不知道怎么定位。我提供一个实操性很强的排查顺序第一步用 Chrome DevTools 的 Network 面板看接口耗时是多少、主要耗时在等待阶段还是下载阶段。 第二步打开后端的 SQL 日志看这个接口实际执行了多少条 SQL。 第三步如果 SQL 数量很多检查是否有循环查询加上with()预加载。 第四步如果单条 SQL 已经很快但总耗时很长检查是否在代码里调用了外部接口、发送了邮件短信等耗时操作。 第五步在控制器入口和出口分别记录时间计算出具体时间消耗在哪个函数。这套方法基本能覆盖 90% 的接口性能问题。真到了单条 SQL 也慢的情况再用EXPLAIN看执行计划、补索引把慢查询日志打开定位到具体语句就能找到原因。6. 项目复盘与可扩展方向整个项目从需求梳理到上线大概花了两周业余时间。第一周集中在后端数据库设计、接口开发、权限认证、管理后台接口。第二周集中在前端首页、文章、商品、社区、用户中心和后台管理页面。这里我想说说如果重新做一次哪些地方我会调整。第一个调整是把内容审核做成异步任务流。现在的实现是管理员手动审核体验上还可以。但以后如果用户量大了帖子量多起来人工审核会成为瓶颈。可以引入消息队列用户发帖后先进入待审池触发微信模板消息通知管理员管理员一键通过或驳回全程不用亲自去后台翻找。第二个调整是增加消息通知能力。交流平台的用户互动天然需要通知有人回复了你、有人点赞了你的帖子、订单状态变了都需要通知用户。可以先用 WebSocket 做站内信再对接微信公众号模板消息这块在知名的社交产品里已经是标配但对于中小型平台来说很多都没做反而是机会。第三个调整是把视频部分从点播升级成直播。茶园采摘季做一场直播是很好的卖点直播技术栈可以从简单的 HLS 直播流开始用开源方案搭一台简易流媒体服务器前端继续复用现有的 hls.js 方案底层的复用性很高。回到开头那句话这类文化社区电商的项目技术核心其实是一个中规中矩的 Web 业务系统但它覆盖的知识面相当完整——认证、内容、互动、交易、部署、优化一环都不少。把 ThinkPHP 6 和 Vue 3 这套组合吃透你不仅能轻松搞定这个茶园平台以后遇到别的行业交流平台、社区系统同样能快速上手。我实际做下来最深的体会是项目能不能成技术选型只是其中一环更关键的是把业务角色和用户流程想清楚再动手写代码。数据表怎么设计、接口怎么划分、前端组件怎么组织全部是由业务逻辑牵引的。把业务理清楚了代码怎么写都顺业务没理清再牛的框架也拯救不了混乱的结构。最后再分享一个小技巧上线前的联调阶段别急着把所有功能做完再统一测。每完成一个模块就前后端一起联调验证一个模块出问题立刻修修复成本最低。我这次就是按用户模块、内容模块、社区模块、商品模块的顺序逐块推进整体进度反而比我预想得快很多。希望这篇内容能帮到正要上手类似项目的人。如果你有别的场景想落地也欢迎在评论区聊聊你的思路大家一起把这类中小型平台的坑踩平。