ARTICLE DETAIL

资讯详情

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

Node.js+PHP+Vue三端协作:健身课程预约系统实战复盘

Node.js+PHP+Vue三端协作:健身课程预约系统实战复盘 这是我从零搭完际健身俱乐部课程预约管理系统之后整理的一篇完整复盘。项目本身不复杂会员在手机上浏览课程、选时间、预约、查记录后台管排课、管名额、管签到。但真正落地时牵扯出的东西不少——Node.js负责前端工程化和定时脚本PHP负责业务接口和数据逻辑Vue负责交互界面。三种技术各管一摊配合得当能省很多事。这篇就把整个系统怎么拆、怎么搭、哪些地方容易翻车都讲清楚适合正在做同类型系统的朋友参考。1. 为什么一个预约系统要凑齐Node.js、PHP、Vue三件套1.1 健身俱乐部的预约业务到底难在哪表面看课程预约就是个选课、提交、记录的流程但健身俱乐部有自己特殊的业务约束不是做个普通报名表单就能应付的。首先是课程种类多。团课动感单车、瑜伽、搏击操、私教课、体验课每种课的预约规则都不一样。团课有名额上限私教课要绑定教练时间体验课要预留销售跟进的空间。一套系统如果只做都能约运营的时候就得人工兜底。其次是时间冲突问题。同一名教练不可能同时上两节课同一名会员也不能在同一时间段预约两节不同的课。这些冲突如果靠人工核对高峰期前台根本忙不过来。第三是预约的履约状态。预约了没来、来了没签到、提前取消、爽约扣次数这些状态互相转换直接关系到会员的课时余额和教练的排班收益。状态管理做不清楚月底对账就是一场灾难。我最初跟俱乐部运营聊需求时他们只提了能约课就行我坚持把课程类型、教练排班、名额限制、状态流转这些规则梳理成文档后面开发才没走弯路。做业务系统一定先摸清业务规则再谈技术选型。1.2 三种技术在系统里各自管什么这个项目的技术栈是标题里明摆着的Node.js PHP Vue很多人第一反应是三个东西混在一起是不是重复了其实分工非常清晰。Vue负责面向用户的部分课程列表页、课程详情页、预约操作、个人预约记录。健身俱乐部的前端交互不算复杂但胜在页面跳转多、状态变化频繁Vue的响应式数据绑定和组件化开发在这种场景下比传统jQuery写起来舒服得多。PHP负责后端业务接口课程数据的增删改查、排课逻辑、预约提交、状态变更、会员余额管理。PHP的优势是部署简单、上手快这类中等复杂度的业务接口用PHP做非常合适不需要引入重型框架也能把代码组织得井井有条。Node.js在这个项目里做了两件事。第一是前端工程化的底座Vue项目用Vite启动开发服务器、打包构建底层都是Node.js在支撑。第二是运维侧的定时脚本我写了一个基于Node.js的预约提醒服务每天早上定时跑一遍查当天有课的会员名单调用短信平台的接口发上课提醒。这个任务用PHP做也不是不行但Node.js的定时任务生态node-cron更轻而且跟前端工程共用一套运行时机器上不用多装一套环境。1.3 三端混合架构的边界划分三端混搭最容易翻车的地方是职责边界模糊。我见过有人用PHP把所有页面模板都写了Vue只是点缀也见过把业务逻辑全塞进前端PHP只做数据透传。我的做法是严格遵守前端只负责呈现和交互后端只负责数据和规则这条线。前端通过HTTP接口与后端通信接口返回JSON。所有业务规则——名额满不满、时间冲不冲突、会员余额够不够——都在PHP端判断前端只做展示和提示。这样做的最大好处是规则变更不需要发版前端改个PHP接口就上线了。Node.js作为工程化工具链的一环只在开发期和运维期出现生产环境的对外服务由Nginx PHP-FPM支撑Node.js不承担线上请求处理。这个划分避免了三端同时对外服务导致的问题排查困难。2. 环境搭建Node.js安装和Vue项目创建的那些坑2.1 Node.js安装与环境配置的正确姿势这个系统开发的第一步是装Node.js。很多人在这一步就卡住了尤其是Windows环境。我建议直接去Node.js官网下载LTS版本长期支持版不要追最新版因为部分Vue生态的依赖对最新Node版本的适配会有滞后。安装时有一个容易被忽略的点安装路径尽量选一个没有空格、没有权限限制的目录。默认装在C:\Program Files\nodejs\其实能用但后续npm安装全局包、修改配置时经常碰到权限弹窗后期很烦。我习惯装到D:\dev\nodejs\这类路径下服务器上则装到/usr/local/nodejs。安装完成后需要确认环境变量。Windows下检查系统环境变量里有没有NODE_HOME指向Node安装目录PATH里有没有%NODE_HOME%。验证是否正确安装打开命令行执行node -v和npm -v能正常输出版本号就说明安装成功了。这里要特别提醒一个新手的坑安装完Node.js之后如果当前命令行窗口是在安装之前打开的环境变量不会自动刷新必须重新开一个窗口才能生效。我遇到不少学员卡在这一步以为安装失败其实是没重开终端。2.2 npm.ps1无法加载文件Windows下的经典报错开发过程中最经典的一个报错搜索量常年居高不下就是下面这段npm : 无法加载文件 D:\Program Files\nodejs\npm.ps1因为在此系统上禁止运行脚本。这个问题的根源在于Windows PowerShell的执行策略Execution Policy默认是Restricted禁止运行本地脚本文件而npm.ps1正好是一个脚本文件。解决办法很简单在PowerShell里执行一次Set-ExecutionPolicy RemoteSigned -Scope CurrentUser这条命令的含义是本地创建的脚本可以运行从网络下载的脚本必须有受信任的签名作用范围只针对当前用户不需要管理员权限。如果不想改执行策略还有一个更简单的路径直接用cmd命令提示符而不用PowerShellcmd不检查脚本执行策略npm照常工作。我当时在项目文档里特意写了这一点因为团队里有人反复遇到这个报错每次都要搜一遍。建议遇到问题先看错误信息是否与脚本执行策略相关别一上来就重装Node。2.3 初始化Vue项目与依赖安装的实战操作创建Vue项目的命令我用的是Vite方式npm create vitelatest fitness-club-front -- --template vue这里有一个小建议npm create vite初始化时选择Vue JavaScript模板就够了不一定非要引入TypeScript项目规模不大时JS的迭代效率更高。如果团队长期维护且成员都有TS基础再上TS不迟。项目创建完成后进入目录安装依赖cd fitness-club-front npm install然后启动开发服务器npm run devVite默认跑在5173端口浏览器打开就能看到Vue项目的初始页面。依赖安装这一步也常有坑。最常见的错误是版本冲突——某个包要求Node版本低于当前版本或者两个包依赖同一个包的不同大版本。我的经验是出现这类问题先别急着手动改package.json先看看有没有lock文件删掉node_modules和package-lock.json重新npm install往往就能解决。还不行的再去针对性调整版本。2.4 开发环境的代理配置前后端分离开发时前端跑在5173端口PHP接口跑在nginx的80端口或自定义端口跨域问题马上就会出现。我的方案是在Vite的配置文件里配代理把/api开头的请求转发到PHP服务。这样前端代码里请求/api/courses浏览器看到的是同源请求绕开了跨域限制。// vite.config.js export default defineConfig({ plugins: [vue()], server: { proxy: { /api: { target: http://localhost:8080, changeOrigin: true } } } })这个配置的意思是所有以/api开头的请求都转发到http://localhost:8080PHP接口地址。changeOrigin设置为true可以保证后端拿到的请求头和浏览器发起的一致。开发期用代理解决跨域生产环境用Nginx做反向代理这个是标准做法后面部署章节再展开。3. Vue前端预约流程的页面拆解与路由设计3.1 前端页面架构与路由规划健身俱乐部预约系统的前端一共需要这么几个核心页面首页俱乐部介绍、热门课程推荐课程列表页按课程分类展示支持搜索和筛选课程详情页课程介绍、教练信息、可预约时间段预约确认页选择上完课的时间段确认预约我的预约页查看已预约的课程、取消预约、历史记录个人中心页会员信息、课时余额对应的Vue Router配置我用动态路由处理课程详情页const routes [ { path: /, component: HomeView }, { path: /courses, component: CourseListView }, { path: /courses/:id, component: CourseDetailView, props: true }, { path: /booking/:scheduleId, component: BookingView, props: true }, { path: /my-bookings, component: MyBookingsView }, { path: /profile, component: ProfileView } ]:id和:scheduleId是路由参数课程详情和预约页面通过this.$route.params拿到对应的ID再调接口加载数据。路由设计上有一个经验预约相关的页面尽量用参数传递ID不要用query带一堆数据。因为预约确认页需要展示的课程信息课程名、时间、教练最好是由详情页通过接口根据ID实时拉取不要依赖上一个页面传过来的状态否则用户刷新页面时数据就丢了。3.2 课程列表页的组件拆分与数据交互课程列表页是用户使用频率最高的页面我把它拆分成了三个组件课程筛选栏、课程卡片列表、分页/加载更多。课程卡片组件接收一个课程对象展示课程封面、名称、教练姓名、剩余名额和预约按钮。数据来源是PHP接口返回的JSON数组Vue的生命周期函数onMounted里调用接口。import { ref, onMounted } from vue import { getCourses } from ../api/course const courses ref([]) const loading ref(true) onMounted(async () { const data await getCourses() courses.value data loading.value false })这里有一个容易踩的坑接口返回的课程列表里如果有该课程今天已满的状态前端不能只靠剩余名额判断能不能预约因为名额会实时变化。我当时的做法是后端在返回列表时直接算出can_book字段前端按钮按这个字段做禁用杜绝了页面显示有名额点进去却说满了的体验问题。3.3 课程详情页的扩展场景m3u8视频与地图课程详情页除了基本信息和排课时间表我还加了两个扩展功能正好对应到大家经常搜索的两个场景。第一个是教练演示视频。俱乐部想放一段教练的动作演示视频运营给的是m3u8格式的流媒体地址。Vue播放m3u8不能直接靠video标签需要引入hls.js库import Hls from hls.js // 播放器初始化 const video document.getElementById(course-video) if (Hls.isSupported()) { const hls new Hls() hls.loadSource(https://media.example.com/course/demo.m3u8) hls.attachMedia(video) hls.on(Hls.Events.MANIFEST_PARSED, () { video.play() }) }这里有个兼容性问题要重点说明iOS Safari本身支持m3u8播放不需要hls.js但Android端和桌面Chrome都需要hls.js来处理。代码里要加Hls.isSupported()判断否则在iPhone上会走错误的播放分支。第二个是门店定位。俱乐部要求详情页能显示门店位置用的是腾讯地图。Vue里接入腾讯地图的常规做法是在index.html里引入地图SDK的script标签然后在组件里初始化地图实例。这两个功能不属于预约核心流程但课程详情页是用户做预约决策的信息汇聚点视频和位置都能提升转化率。做产品时不要把页面只做成信息表单该有的辅助信息要想全。3.4 预约状态的前端控制与用户体验预约操作集中在预约确认页。用户选择课程、选择时间段、确认余额充足后点击确认预约前端调PHP的预约接口拿到结果后做三种处理成功跳转到我的预约页面并提示预约成功失败满额回退到课程详情页提示名额已满失败时间冲突提示用户在某个时间段已经预约了其他课程交互层面我加了一个防止重复提交的按钮状态点击确认预约后按钮立刻变成提交中...并禁用直到接口返回结果。没有这层保护用户手抖点两下会产生两个并发预约请求后端如果不做幂等处理就会出现双重预约。预约成功后的状态反馈我用了Vue的响应式特性在我的预约页面维护一个预约列表取消预约时先更新本地状态再调接口接口失败再回滚。这种乐观更新的策略让界面反应更快但前提是后端接口要稳定可靠否则用户看到的状态和实际不一致反而更糟糕。4. PHP后端课程接口、预约逻辑与跨域处理4.1 后端目录结构与接口设计PHP端我没有用Laravel这种重型框架因为项目规模不需要。我用的是一个轻量的MVC目录结构api/ ├── config/ │ └── database.php // 数据库连接配置 ├── controllers/ │ ├── CourseController.php │ ├── ScheduleController.php │ └── BookingController.php ├── models/ │ ├── Course.php │ ├── Schedule.php │ └── Booking.php ├── helpers/ │ └── response.php // JSON响应封装 └── public/ └── index.php // 前端控制器路由入口接口统一走index.php入口根据请求参数路由到不同的控制器和方法。比如GET /api/courses获取课程列表GET /api/courses/{id}获取课程详情和可预约排课POST /api/booking提交预约DELETE /api/booking/{id}取消预约GET /api/my-bookings?member_idxxx获取我的预约响应格式统一封装成JSON{ code: 0, message: success, data: { ... } }code为0表示成功非0表示业务错误。前端统一拦截非0的响应并弹出message不用每个接口单独处理错误分支。4.2 数据库表设计与课程排课的建模预约系统的核心数据结构围绕四张表会员表members会员ID、姓名、手机号、课时余额。课程表courses课程ID、课程名称、课程类型团课/私教/体验、课程简介、默认时长、封面图。排课表course_schedules排课ID、课程ID、教练ID、上课日期、开始时间、结束时间、总名额、已约名额、状态。预约记录表bookings预约ID、会员ID、排课ID、预约时间、状态已预约/已取消/已签到/爽约。排课表是解决冲突问题的关键。这里有一个建模时要提前想清楚的细节课程表存的是这个课叫什么、什么内容排课表存的是这个课在哪个时间由哪个教练上。一个课程可以有多条排课记录预约针对的是排课而不是课程本身。数据库字符集我统一用了utf8mb4不要用utf8否则存emoji比如会员昵称里的特殊符号会报错。排序规则用utf8mb4_unicode_ci就行。索引这块course_schedules表的(course_id, start_time)要建联合索引bookings表的(member_id, status)要建索引。预约系统的查询基本都是按这两个维度走的索引建对了接口性能完全够用。4.3 预约接口的数据校验与事务处理预约接口是系统的核心代码质量直接决定会不会出现超卖名额满了还能约上。我给出核心逻辑。先校验参数会员是否存在、排课是否存在、当前时间是否早于开课时间。然后开启事务做四步操作用SELECT ... FOR UPDATE锁定排课记录判断booked_count total_countbooked_count加1插入预约记录提交事务。public function createBooking($member_id, $schedule_id) { $pdo-beginTransaction(); try { // 锁定排课记录 $stmt $pdo-prepare( SELECT * FROM course_schedules WHERE id ? FOR UPDATE ); $stmt-execute([$schedule_id]); $schedule $stmt-fetch(); if (!$schedule) { throw new \Exception(排课不存在); } // 校验名额 if ($schedule[booked_count] $schedule[total_count]) { throw new \Exception(课程已满); } // 校验时间冲突 if ($this-hasTimeConflict($member_id, $schedule[start_time], $schedule[end_time])) { throw new \Exception(您在同时段已有其他预约); } // 名额加一 $pdo-prepare( UPDATE course_schedules SET booked_count booked_count 1 WHERE id ? )-execute([$schedule_id]); // 插入预约记录 $pdo-prepare( INSERT INTO bookings (member_id, schedule_id, status, created_at) VALUES (?, ?, booked, NOW()) )-execute([$member_id, $schedule_id]); $pdo-commit(); } catch (\Exception $e) { $pdo-rollBack(); throw $e; } }FOR UPDATE的意思是在事务结束之前其他事务不能修改这条排课记录。这样即使200个人同时抢40个名额数据库会在行级别做串行化不会出现两个事务同时读到booked_count 39然后同时加一的情况。很多人会问为什么不用乐观锁先更新再检查受影响行数那个方案在高并发下会浪费很多重试请求而且处理时间冲突这种复杂业务逻辑时不如行锁直观。预约场景写多读少用行锁是正确的选择。4.4 跨域问题的完整方案从JSONP到CORS热搜词里php跨域jsonp出现的频率很高说明这是PHP开发者做前后端分离时绕不开的问题。跨域的本质是浏览器的同源策略限制http://localhost:5173访问http://localhost:8080的接口端口不同算跨域。历史上有JSONP这种 hack 方案——利用script标签不受同源限制的原理让后端返回一段JS回调代码。但JSONP只支持GET请求而且有安全性问题可能执行恶意代码现在我们开发只用CORS方案。PHP端做CORS最直接的办法是在接口入口统一加响应头header(Access-Control-Allow-Origin: *); // 生产环境替换为前端域名 header(Access-Control-Allow-Methods: GET, POST, PUT, DELETE, OPTIONS); header(Access-Control-Allow-Headers: Content-Type, Authorization);如果前端请求带Content-Type: application/json浏览器会先发一个OPTIONS预检请求Preflight Request后端必须正确响应OPTIONS请求否则实际请求会被拦截。我的入口文件里专门处理了if ($_SERVER[REQUEST_METHOD] OPTIONS) { http_response_code(200); exit(); }生产环境不推荐用*作为Access-Control-Allow-Origin的值应该明确指定前端的正式域名。安全原则是最小权限放开所有来源意味着任何网站都能调用你的接口配合登录态的Cookie时风险很大。4.5 PHP安全加固伪协议与其他陷阱搜索热词里出现过php伪协议和php域名授权系统网站源码这两类词反映了PHP项目安全问题的常见来源。伪协议比如php://input、data://是PHP中一类特殊的流协议正常情况下用于读取请求体和数据流。但如果代码里存在文件包含漏洞攻击者可以利用伪协议读取服务器上的敏感文件或者执行恶意代码。我们的生产环境做了三件事来防范这类问题php.ini中设置allow_url_include Off禁止通过URL包含远程文件文件包含操作只允许传入固定的白名单路径绝不直接使用用户输入当作路径所有接口输入做参数校验数字类型的强制转int字符串的做长度和格式检查域名授权源码类的问题本质上不是技术问题而是业务问题。不建议使用来源不明的授权验证代码这类代码经常藏有后门。商业项目的授权验证应该自己写逻辑很简单就是用域名做一次HMAC签名校验签名算法放在自己手里。4.6 PHP接口联调时遇到的两个小问题联调过程中有两个小问题值得记录都对应了热词里的搜索项。第一个是php body onload事件没有执行。这个问题的原因不是PHP的问题而是前端把script标签放在了HTML的head里此时body元素还没渲染出来window.onload事件自然找不到元素。解决办法是把脚本放在body末尾或者用document.addEventListener(DOMContentLoaded, ...)。第二个是php接口数组对象。前端拿到的JSON数据里PHP关联数组会被编码成JSON对象索引数组会编码成JSON数组。如果出现前端拿到数据格式不对的情况先确认PHP端array()的定义——如果键不是从0开始的连续整数json_encode出来就是对象而不是数组前端遍历时就要用Object.values()而不是直接forEach。这类问题排查时用浏览器开发者工具看接口返回原始JSON最直接。5. 预约冲突与并发控制系统最容易翻车的地方5.1 超级团课开抢的并发场景上线后遇到的第一个实际压力测试是俱乐部每周日晚上的动感单车课。40个名额每逢周日晚8点开放预约实际并发量虽然远比不上电商大促但一两百个请求同时打过来是常事。如果预约接口不做并发控制后果是超卖。两个会员同时提交后端同时读到booked_count 39同时判断还能约结果都成功最后实际到场的可能有41个人。我们前面讲的FOR UPDATE行锁在这个场景下就是唯一的正确解法。但要注意一个细节事务里加锁的顺序必须一致否则可能产生死锁。我们预约流程里只涉及排课表和预约表两张表的写操作顺序统一为先锁排课、再插入预约从不反过来。死锁在MySQL里不致命数据库会自动检测并回滚其中一个事务但对用户来说体验很糟糕明明看到提交了却失败。避免死锁的核心原则就是所有事务都按相同的顺序访问资源。5.2 时间冲突检测的SQL写法会员在同一时间段约了两节课这个场景要靠后端校验。核心逻辑是一段区间重叠判断。课程A在排课表中的时间段是start_time到end_time会员想预约的另一个课程B如果满足以下条件就说明时间重叠了SELECT COUNT(*) FROM course_schedules cs JOIN bookings b ON b.schedule_id cs.id WHERE b.member_id ? AND b.status booked AND cs.start_time ? AND cs.end_time ?SQL语句里的两个参数分别是新课程的开始时间和结束时间。逻辑解释一下cs.start_time 新课程结束时间已有课程的开课时间必须在新课程结束之前cs.end_time 新课程开始时间已有课程的结束时间必须在新课程开始之后同时满足这两个条件说明两个时间段存在交集。这个判断方法比逐条比较时间点简单很多也是处理时间区间重叠问题的通用SQL套路日历应用、会议室预约都能复用。5.3 预约状态机设计与爽约处理预约状态我用四个值管理已预约、已取消、已签到、爽约。状态之间的允许流转做了严格限制已预约可以转为已取消用户主动取消已预约可以转为已签到现场扫码签到已预约可以转为爽约开课后未签到由定时任务自动标记已取消和已签到的状态不可再变状态变更通过PHP接口统一处理不允许前端直接修改状态字段。取消操作的接口要校验时间——开课前30分钟以内不允许取消这是俱乐部对会员的规则约束。爽约规则是爽约一次扣除一次课时连续三次爽约两周内不能预约热门团课。这个规则在PHP端实现每次预约前的校验逻辑里查询近期的爽约次数。我用Node.js写了一个定时任务每天凌晨跑一次把所有已预约但开课时间已过且没有签到记录的记录批量更新为爽约状态。这是Node.js在系统里最实际的运维价值。任务本身不复杂但省掉了运营每天手动核对的工作量。5.4 未确认预约的自动释放运营提了一个需求部分体验课预约需要占位但不锁定会员在15分钟内未确认则自动释放名额。实现思路是在预约记录表加一个expires_at字段创建预约时写入目前时间 15分钟状态设为待确认。此时booked_count是否增加呢我选择增加因为名额确实被占用了如果超时未确认定时任务会把记录状态改成已过期同时把booked_count减一。这里有一个业务细节会员本人看到待确认的预约在过期时间之前可以点击确认确认操作会清除expires_at字段并把状态变为已预约。整个逻辑闭环在PHP端写清楚前端只需要在详情页显示倒计时即可。6. 前后端联调、部署上线与运维心得6.1 联调阶段最常见的三类问题联调是整个项目里耗时最长、最容易消磨耐心的阶段。总结下来有三类问题的出现频率最高。第一类是接口字段名对不上。前端按文档写course_name后端返回的是title前端渲染出来全是空白。解决方法是定接口文档的时候用JSON Schema或者简单的示例JSON钉死每个字段名联调时前端先在浏览器Network面板里看真实返回再对照文档检查取值逻辑。第二类是响应格式不统一。有些接口成功返回的数据直接是数组有些又套了一层对象前端封装请求函数时要写一堆分支判断。我后来在后端的response.php里统一封装所有接口都返回{code, message, data}结构前端请求封装里只处理这一种格式代码清爽很多。第三类是本地能跑通部署到服务器就不行。最常见的原因是数据库连接配置用了localhost而服务器上的数据库在另一个内网IP或者PHP的php.ini配置跟本地不一致比如upload_max_filesize、max_execution_time这些参数影响接口表现。我专门整理了一份部署检查清单每一条都核对过再交付。6.2 Nginx PHP-FPM的部署配置要点生产环境的架构是Nginx作为统一入口静态文件直接返回/api开头或者带.php结尾的请求转给PHP-FPM处理前端打包后的静态资源放在另外一个目录。Nginx的关键配置片段server { listen 80; server_name api.fitness-club.example; root /var/www/fitness-club/api/public; index index.php; location / { try_files $uri $uri/ /index.php?$query_string; } location ~ \.php$ { include fastcgi_params; fastcgi_pass 127.0.0.1:9000; fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; } }前端站点的Nginx配置把location /指向try_files到前端打包目录同时把/api路径反向代理到PHP服务所在的端口。上线之前我把display_errors设置成Off错误日志单独记录到文件。这是安全经验生产环境暴露PHP报错信息等于把服务器内部结构告诉攻击者。调试阶段可以打开上线必须关闭。6.3 Vue项目打包与静态资源路径问题前端部署时最容易遇到的是资源路径问题。npm run build默认生成的资源路径是以/开头的绝对路径如果前端站点部署在子目录下比如https://example.com/fitness/不配置base路径所有资源都会404。解决办法是在vite.config.js里设置export default defineConfig({ base: process.env.NODE_ENV production ? /fitness/ : /, })这个问题我之所以单独拎出来讲是因为本地开发完全看不出异常只有部署到生产才暴露而一旦资源404整个页面样式和脚本全挂排查起来需要一定的经验才能快速定位。6.4 从开发到稳定运行的运维清单项目上线后稳定跑了一个月我把运维中用到的命令和经验整理成了清单。日志监控用的是最朴素的方式PHP的error_log和Nginx的access_log分开配置每天自动切割。预约接口的每天调用量、成功率、失败原因我写了一个简单的统计脚本输出到一张统计表运营每周看一次。定时任务里除了提醒和爽约处理我还加了一个排课自动上架任务运营在后台录入了未来两周的排课但状态是未发布定时任务在开课前24小时自动把状态改成可预约。这个是纯粹为了省运营的事用Node.js写也就是十几行代码。6.5 个人经验小团队技术栈选型的收益与代价做完这个项目我的直观感受是Node.js PHP Vue 这样的组合在小团队和中小型项目中非常务实。Vue提供了一个现代化前端开发体验组件化、响应式、开发调试效率都很高。PHP后端开发速度快部署门槛低任何一台普通云服务器都能跑起来不需要专门的运维人力。Node.js作为工程链和脚本工具把开发和运维环节串了起来。代价当然也有。最大的问题是技术栈确实杂团队里新成员要同时理解三种语言的生态上手成本比纯Node全栈或者纯PHP全栈高一些。另外前后端接口的约定一旦不严谨联调成本会成倍增加。如果项目规模继续扩大这套架构演进的方向也很明确PHP接口保持业务稳定前端可以平滑迁移到更复杂的框架Node.js层可以演化成BFF网关统一做鉴权、聚合、转发后端业务服务用更适合高并发的语言单独提供。但对于一个健身俱乐部的课程预约系统当前这套组合已经足够了。最后分享一个我认为最值得记住的教训预约系统的本质不是怎么约而是怎么保证约的规则不被破坏。名额、时间、状态、余额这些业务规则在任何技术栈下都必须由后端严格把关。前端可以做得花哨但规则必须握在后端手里。想清楚这一点架构选型和代码组织都不会走偏。
返回列表