ARTICLE DETAIL

资讯详情

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

Node.js + Vue健康体检管理系统全栈开发实战指南

Node.js + Vue健康体检管理系统全栈开发实战指南 健康医疗体检管理系统我前后拆过好几套但基于 nodejs Vue 这套组合的版本还挺值得单独拿出来聊聊的。它不复杂却把“用户端信息维护、体检套餐预约、报告查询、后台数据管理”这条完整业务链全串起来了正好适合拿来当毕设题目或者给刚入门前后端分离开发的人当练手项目。更关键的是从 Node.js 安装到环境变量配置再到 Vue 项目初始化、依赖安装、接口联调、部署上线这一条龙踩坑下来基本就把全栈开发的日常操作摸透了。这套系统解决的核心问题很明确一边是体检用户希望能在线选套餐、预约时间、随时查报告另一边是体检中心希望能高效管理项目、维护用户数据、生成统计报表。传统的电话预约加纸质报告的方式效率太低所以需要用一套在线系统把流程搬到 Web 上。而我这次采用的是 Node.js 提供后端接口服务、Vue 负责前端页面渲染的前后端分离架构数据存 MySQL鉴权走 JWT部署用 Nginx 反向代理整体方案偏实用、好维护拿来跑业务或者做学习都很顺手。1. 项目整体设计与技术选型1.1 为什么用 Node.js Vue而不是 Spring Boot 模板引擎很多人一上来就问体检管理系统这种业务系统不是应该用 Java 系吗为什么选 Node.js我的回答很简单技术选型看场景。这个项目核心是 CRUD 加少量统计逻辑并没有重计算、强事务、复杂并发的要求。Node.js 的事件驱动模型和 npm 生态能让我快速把接口搭起来Express 框架写路由、中间件非常直观配套的 mysql 库、jsonwebtoken、cors 这些包装完就能用开发效率比传统方案高出一截。Vue 这边也一样。它的响应式数据绑定和组件化开发用来做体检预约这种表单密集、状态多的页面特别舒服。用户在预约页选套餐、选时间、填既往病史这些交互逻辑如果用原生 JS 写光是 DOM 操作就够让人头疼的而 Vue 里数据变了页面自动更新开发体验完全是另一个维度。再加上 Element UI 这种现成组件库表格、表单、弹窗、日期选择器全都有几乎不用自己写样式的活儿。对比传统 Spring Boot Thymeleaf 那种后端渲染方案前后端分离最大的优势是分工清楚前端只管页面和交互后端只管数据和业务规则。后期想给系统加一个小程序端或者 App只需要复用同一套接口不用再改后台页面逻辑。这个优势在体检系统这类业务里特别实用因为体检中心往往会先做 Web 端后面再加微信公众号预约入口接口复用能省掉一大半重复工作。1.2 核心功能模块与数据库表设计做这种管理系统第一步永远不是写代码而是先把功能边界和数据结构理清楚。我按业务角色把系统拆成了两个大端用户端和后台管理端。用户端注册登录、浏览体检套餐、在线预约、查看体检报告、个人信息维护。管理端体检项目管理、套餐管理、预约审核与排期、报告录入与发布、用户管理、数据统计。对应到数据库我建了这么几张核心表用户表包含角色字段区分普通用户和管理员、体检项目表存放单个检查项目的名称、价格、参考范围、套餐表一个套餐关联多个检查项目多对多关系、预约表记录用户约了哪个套餐、约在什么时间、当前是什么状态、报告表存放体检结论、指标数据、PDF 附件路径、机构/医生表用于后台排班和报告审核。这里有个实际经验预约状态不要只设计一个“已预约/已完成”这么粗的字段至少要拆出“待审核、已确认、已完成、已取消”四个状态。因为体检中心的业务里用户约完以后可能改期、可能取消前台人员也可能因为资源冲突拒绝预约。状态机越早设计清楚后面写业务逻辑越顺不用返工。我在第一版的时候就只写了两个状态后来被运营需求逼着加字段改动量比一开始就设计好要大得多。设计表结构时还有一个原则我始终守着价格和费用的字段用整数分来存不要用浮点数。体检套餐经常有折扣、叠加优惠浮点计算容易出现 0.1 0.2 不等于 0.3 这种问题整数分配合前端展示时的格式化函数能彻底避开浮点失真。2. 环境搭建与开发准备nodejs、npm、Vue 脚手架2.1 Node.js 安装与环境变量配置的完整过程无论你以前有没有装过 Node.js我都建议按这个思路重新理一遍环境。首先是版本选择别追新去 nodejs.org 下载 LTS 版本就行。LTS 意味着社区已经跑了很久、稳定性和兼容性都有保障。我之前在另一个项目里图新鲜用了奇数版本结果有个原生模块编译不过去折腾半天只能降级从那以后我再也不在正式项目里用非 LTS 了。Windows 下安装没啥悬念一路 Next。但要注意安装路径我建议装到一个没有空格的纯英文路径比如D:\nodejs避开Program Files带空格的坑。装完以后重点验证环境变量在命令行输入node -v和npm -v能打印版本号说明 Node.js 本身装好了。不过环境变量并没有“结束”还有两个地方需要手动配一下。一个是全局安装路径一个是全局缓存路径。我习惯先建好两个目录然后执行下面两条命令把路径指过去这样以后npm i -g安装的全局工具都有了固定存放位置npm config set prefix D:\nodejs\node_global npm config set cache D:\nodejs\node_cache然后去系统环境变量里把D:\nodejs\node_global加入 Path这样全局安装的 Vue CLI、npm 版本管理工具都能直接在命令行里调用。这个配置最多花五分钟但能让后面所有开发过程都顺畅很多因为很多新手遇到的“命令不是内部或外部命令”八成就是全局路径没配置好。2.2 npm 换源与 Vue 项目创建Node.js 装好以后第一件事不是创建项目而是先解决 npm 下载慢的问题。国内直接 npm install 那速度真的没法忍装个依赖能卡十几分钟。我现在的标准操作是把 registry 切换到 npmmirror 镜像源npm config set registry https://registry.npmmirror.com换完源以后创建 Vue 项目我推荐直接用 Vue CLI全局安装完再执行创建命令npm install -g vue/cli vue create health-check-web创建的时候有意思的是Vue CLI 会问你要装 Vue 2 还是 Vue 3要集成哪些插件。我给这个体检系统选的组合是Vue 2 Vue Router Vuex axios Element UI。这套组合比较成熟Element UI 对 Vue 2 的支持最完善网上案例也最多遇到问题随便一搜就有答案。如果你非要用 Vue 3那组件库得换成 Element Plus整体写法差别不大但个别 API 有变动新手容易在版本问题上栽跟头所以我更推荐先把 Vue 2 版本跑通。前端项目结构其实不需要自己手搭脚手架会生成一套规范目录。我通常会再调整一下把src/api目录建出来里面按模块拆文件比如user.js、appointment.js、report.js每个文件专门封装对应的接口请求。这样做的价值在于页面组件里不会到处都是 axios 代码接口变更的时候只需要改一个文件不用满项目去搜。2.3 常见的 npm.ps1 执行策略受限问题这里必须专门拿出来说因为实在太多人卡在这一步了。在 Windows 的 PowerShell 里执行npm命令有时候会报这么一段npm : 无法加载文件 D:\program files\nodejs\npm.ps1因为在此系统上禁止运行脚本很多人一看到英文就慌其实原理不复杂。PowerShell 默认的执行策略是 Restricted也就是不允许运行任何脚本文件。而 npm 命令在 PowerShell 里实际是执行一个npm.ps1脚本所以就被拦下来了。这不是 Node.js 坏了也不是命令打错了纯粹是 PowerShell 的安全策略问题。解决方案是以管理员身份打开 PowerShell执行下面这行命令然后重新打开终端Set-ExecutionPolicy RemoteSignedRemoteSigned的意思是本地创建的脚本可以运行从网上下载的脚本必须要有可信签名才运行。这是兼顾安全性和便利性的折中方案不是放开所有限制。执行的时候如果提示确认敲 Y 回车就行。改完以后再执行npm -v问题就消失了。如果不想动执行策略还有一个更快的绕法直接用 CMD 命令行工具代替 PowerShell。CMD 下执行 npm 没有这个限制适合临时救急。不过我还是建议把执行策略改掉因为后面 Vue CLI、webpack 这类工具偶尔也会调脚本迟早要用到。这算是我踩过好多次以后总结出的结论遇到这个报错先别怀疑安装先查脚本策略。3. 核心功能模块拆解与实操实现3.1 后端接口架构与 JWT 登录鉴权后端我用的 Express 框架目录结构按照功能来分routes放路由定义controllers放业务逻辑db放数据库连接和查询。入口文件app.js里做的事情很清晰加载中间件、挂载路由、监听端口。登录鉴权这个模块是整个系统的地基我用的是 JWTJSON Web Token方案。为什么选 JWT 而不是传统 Session因为前后端分离以后后端接口可能是给 Web 用也可能给未来的小程序用保持无状态能让后端服务更灵活。用户登录成功后后端用密钥生成一个带有效期的 token 返回给前端前端把它存在 localStorage 里之后每次请求都在请求头带上Authorization: Bearer token。后端用一个中间件统一校验 token这一段的实现很核心const jwt require(jsonwebtoken); function authMiddleware(req, res, next) { const header req.headers.authorization || ; const token header.replace(Bearer , ); if (!token) return res.status(401).json({ code: 401, msg: 未登录 }); try { const decoded jwt.verify(token, process.env.JWT_SECRET); req.user decoded; next(); } catch (err) { return res.status(401).json({ code: 401, msg: 登录已过期 }); } }这里有个细节要提醒token 有效期我设置的 24 小时但在实际业务中体检用户很可能隔几天才回来查报告频繁重新登录体验很差。更好的做法是后端在 token 快过期时返回一个特定的状态码前端收到后静默调用刷新接口换新 token。这个机制第一版可以不加但你要知道它存在的意义否则上线后一定会有用户反馈“怎么老要我重新登录”。路由层用中间件保护需要登录的接口也特别方便。只需要在路由定义时加上authMiddlewareapp.use(/api/appointment, authMiddleware, appointmentRoutes);这样预约相关的接口全部自动要求登录而登录和注册接口保持开放。分层清晰后面想给某个接口临时加权限直接改一行路由就行。3.2 体检套餐与预约流程的实现细节预约流程是这个系统业务逻辑最重的部分接口设计上要处理好一个关键问题防止超卖。什么叫超卖就是同一个时间段明明体检中心只能接待十个人结果十个以上的用户都预约成功了。这在实际业务里非常致命用户到了现场才发现没排上体验直接崩。我的处理思路是在预约表中某个时间段对应的机构、日期组合加唯一索引然后在插入预约记录之前先做一次防重校验用事务包住“查询余量”和“插入记录”两个操作。事务的作用是保证这两个操作要么都成功要么都失败不会出现“查到有余量但插入时发现没了”的并发问题。在实际操作中MySQL 默认的 InnoDB 引擎支持事务Express 里配合mysql库的connection.beginTransaction()就能实现。套餐和体检项目是多对多关系前端展示套餐列表时需要把套餐下的检查项目也带出来展示。SQL 写法上用 LEFT JOINSELECT p.id, p.name, p.price, i.id AS item_id, i.name AS item_name FROM package p LEFT JOIN package_item pi ON p.id pi.package_id LEFT JOIN item i ON pi.item_id i.id WHERE p.id ?查出来以后在 Node.js 代码里做一次聚合把相同套餐的项目合并成数组再统一返回给前端。这个“连表查询 代码聚合”的方案虽然简单但要注意一个点如果套餐数量和项目数量都很大一次性把所有套餐的项目全查出来可能会有性能问题。不过对于体检中心这种几十上百个套餐的量级完全够用没必要引入额外的 ORM 或数据库中间件。3.3 报告管理模块与前端页面渲染报告模块是整个体检系统里最有业务价值的部分。用户做完体检前台工作人员或医生在后台录入检查结果上传结论文件然后用户端就能实时看到自己的报告。这里比较关键的是指标数据如何展示。我的做法是报告表存储总体结论和 PDF 附件路径指标明细存在单独的报告指标表中每条记录包含指标名称、测量值、单位、参考范围、是否异常。前端报告页用 Element UI 的表格组件渲染指标明细。这里我加了一个很实用的细节当指标值超出参考范围时这一行高亮标红用户一眼就能看出要关注的数据。实现方式也不复杂表格的row-class-name回调函数里判断状态字段动态返回 CSS 类名。这个功能虽然简单但好评率极高用户都在意自己的检查结果是否偏离正常区间。如果报告需要展示影像资料或者体检视频那可能还会用到播放器组件。之前有人在群里问过 Vue 播放 m3u8 视频流的问题原理是引入hls.js然后通过Hls.isSupported()创建实例挂载到video标签上。体检系统里一般用不上但如果你扩展成远程问诊或者医生解读报告的场景这个方法就能派上用场。3.4 数据统计与 ECharts 图表的接入最后一个核心模块是后台的数据统计。体检中心的管理者最关心的几个数字无非是每天/每月的预约量、体检项目热门度、用户来源渠道、营收趋势。这些统计不需要实时系统那么复杂写几个聚合 SQL 就够用了。比如统计月度预约量SQL 可以这样写SELECT DATE_FORMAT(create_time, %Y-%m) AS month, COUNT(*) AS total FROM appointment GROUP BY DATE_FORMAT(create_time, %Y-%m) ORDER BY month前端我看的是 ECharts在 Vue 组件里通过npm install echarts装好以后按需引入图表实例初识化。这里有一个坑ECharts 的图表容器必须要有明确的宽度和高度如果放在隐藏的元素里初始化会拿到 0 宽 0 高图表不显示。常见的解决方式是调用chart.resize()方法或者在容器可见后再初始化。我一般封装一个initChart方法统一处理图表初始化和 resize 监听。统计模块是锦上添花但它的存在让系统从“能用”变成“好用”。体检中心老板打开后台先不看别的就看这几个数字所以我在整个项目里最优先保证统计接口的速度。聚合查询尽量走索引create_time字段一定加索引否则数据量一旦上来这个页面的响应时间会很难看。4. 常见问题与排查技巧实录4.1 高频报错与解决方案速查我把这个项目开发过程中高频遇到的报错整理成了一张表基本涵盖了从环境搭建到前后端联调的全过程。这里面的经验都是真金白银踩出来的直接照着一项项查能省很多时间。问题现象根本原因解决方法npm.ps1 无法加载文件禁止运行脚本PowerShell 执行策略受限管理员身份执行Set-ExecutionPolicy RemoteSignednode-sass 安装失败或编译报错node-sass 对 Node 版本敏感改用 dart-sasssass 新包名删除 node_modules 重装npm install卡住不动默认源网络不稳定切换registry.npmmirror.com镜像源页面请求接口报 CORS 错误前端端口和后端端口不同跨域被拦后端启用 cors 中间件生产环境用 Nginx 同源代理数据库查询中文乱码数据库表字符集不是 utf8mb4建库建表时指定CHARSETutf8mb4点击按钮没反应但不报错前端接口请求失败被 catch 后静默处理统一封装 axios 拦截器集中错误提示npm run serve启动很慢首次缓存未生成二次启动就快了若持续慢可检查依赖版本冲突前端页面刷新后 404前端路由是 history 模式静态托管需要回退Nginx 配置try_files $uri $uri/ /index.html这张表里我特别想强调“前端页面刷新后 404”这个点。很多人本地开发跑得好好的后端接口也通了结果部署到服务器上一点菜单就 404。原因在于 Vue Router 的 history 模式没有带#号刷新时浏览器会按真实路径去请求服务器资源而服务器上并没有对应路径。Nginx 里加一行try_files配置就能解决属于部署时的必踩坑。4.2 前后端联调的三个关键习惯前后端分离项目里联调阶段出问题的概率远高于开发阶段。我自己的血泪教训总结出三个习惯每个都直接避免过线上事故。第一个习惯前端所有接口请求必须走统一的 axios 封装。这个封装里统一处理 baseURL、请求头 token 注入、响应状态码判断、错误提示。如果不做统一封装每个人的页面里都自己写一小段 axios很快代码里就出现几十种错误处理方式线上出问题根本没法排查。我写代码的风格是宁可前期多花十分钟把封装写好也不要后期花两小时在四十个文件里找 bug。第二个习惯后端接口返回值格式必须规范统一。我用的格式是{ code: 0, msg: success, data: ... }code 为 0 表示成功非 0 表示业务错误。这样前端拦截器里只需要判断一次 code 就能统一处理所有情况。如果有人把接口返回的 data 改成各种形状前端代码就要到处写兼容逻辑迟早出乱子。第三个习惯开发环境的跨域代理必须在 Vue 配置里设置好而不是靠后端的 CORS 插件扛。上面也提到过这一层。用vue.config.js里的 devServer 配置实现代理module.exports { devServer: { proxy: { /api: { target: http://localhost:3000, changeOrigin: true } } } };这样前端代码里所有请求都写相对路径/api/xxx本地开发和后端同源不需要 CORS 放行等部署到生产环境Nginx 再把/api代理到 Node 服务的端口。一套配置走到底省心。4.3 数据库连接与字符集的避坑指南数据库连接这块几乎每个新手都会遇到ER_NOT_SUPPORTED_AUTH_MODE这个报错。原因也明显MySQL 8 默认的认证插件是caching_sha2_password而 Node.js 的mysql库老版本不支持。解决办法有两个一是把 Node 的数据库驱动升级到mysql2这个库对 MySQL 8 支持很好而且 API 基本兼容二是在 MySQL 里把用户的认证插件改成mysql_native_password。我更推荐第一种因为升级驱动是一劳永逸的事不用去动数据库安全配置。中文乱码则是另一个高频问题。一开始我发现体检报告里用户名字和套餐名显示成???第一反应是前端编码问题排查了半天发现是后端拿到的数据就已经是乱码了。最后定位到是数据库连接配置里没有指定字符集。在创建连接池的时候一定要加上这个配置const pool mysql.createPool({ host: localhost, user: root, password: your_password, database: health_check, charset: utf8mb4 });第还有一个经验就是数据库的设计文档最好一开始就维护好。这个体检系统虽然不大但表和表之间的关联也不少套餐、项目、预约、报告之间的关系光靠脑记过一个月再看可能就忘了一半。我习惯在项目根目录放一个docs/schema.sql注释版每个字段都写上说明。别小看这一步后期答辩也好、给同事交接也好这份文档能帮你省下大量解释时间。5. 打包部署与上线运营经验5.1 前端构建与后端服务的部署配置项目开发完成以后进入部署阶段这一阶段的目标是让系统稳定跑在服务器上并且在日常维护中能被轻松管理。前端部署非常直接在项目根目录执行npm run build生成dist静态目录然后交给 Nginx 托管。构建流程里容易忽略的是环境变量我拆成了三个.env文件.env.development、.env.production、.env.test。后端请求地址在.env.production里写成相对路径/api这样构建出来的文件不管部署到什么域名下都能正常工作。后端 Node 服务的部署我强烈建议用 PM2 来守护进程。直接用node app.js启动的问题在于服务一旦崩溃就没人管了而且服务器重启后不会自动恢复。PM2 解决了这两个核心痛点。启动命令很简单pm2 start app.js --name health-check-server pm2 save pm2 startuppm2 save保存当前进程列表pm2 startup会生成开机自启的脚本服务器重启后服务自动拉起来。这个操作帮我避免过两次因为服务器电力维护后服务没自动恢复的尴尬场景体验过的自然懂。5.2 Nginx 反向代理与 HTTPS 配置Nginx 在这个架构里扮演的角色很关键它既要托管前端的静态文件又要像 Nginx 配置那样实现反向代理。健康体检系统的访问流量集中在预约高峰期比如周一上午、节假日前后Nginx 作为入口层分担了一部分压力静态资源由它直接返回只有接口请求才转发给 Node 服务。一个最基础的配置片段可以参考一下server { listen 80; server_name your-domain.com; root /var/www/health-check-web/dist; index index.html; location / { try_files $uri $uri/ /index.html; } location /api/ { proxy_pass http://127.0.0.1:3000; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; } }location /里的try_files就是前面表格里提过的刷新 404 解决方案。location /api/则把接口请求统一转发到本机 Node 服务的 3000 端口。注意proxy_pass后面有没有带路径斜杠这直接影响转发后 URL 是否保留/api前缀。我在刚开始部署的时候就被这个细节坑过转发路径变得乱七八糟。我的经验是proxy_pass http://127.0.0.1:3000;不带路径会保留完整/api/xxx后端路由也按/api开头设计的话这样能保持一致。上线后一定要做的一件事是配 HTTPS 证书。体检系统涉及用户手机号、身份证号、体检指标等大量敏感信息没有 HTTPS 加密传输就等于把这些数据裸奔在网络上。用 Certbot 申请免费证书自动续期就可以了配置起来并不复杂。这一步千万不要省我在真实项目中见过太多团队图省事跳过结果用户数据在链路中被截获出了事故才追悔莫及。5.3 日志、备份与日常运维的一些心得系统上线只是开始不是结束。运维层面我最重视的两件事日志和数据库备份。Node 服务的运行日志我会让 PM2 统一收集pm2 install pm2-logrotate pm2 set pm2-logrotate:max_size 10M日志轮转配置可以避免日志文件无限增长占满磁盘。数据库备份这部分也简单我用系统 crontab 定时任务每天凌晨自动导出 SQL保留最近 7 天的备份文件。体检中心的业务数据非常重要一旦误操作或者数据损坏没有备份就等于业务停摆。0 2 * * * mysqldump -u root -ppassword health_check /backup/health_check_$(date \%F).sql运维经验总结成一句话宁可多做一次没有用处的备份也不要侥幸系统不会出问题。这套系统现在跑在体检中心业务中的时候备份脚本已经帮我恢复过两次误删数据每次都只用了几分钟就恢复到了出事前的状态。这个收益比当初配定时任务花的十分钟高太多了。最后再分享一个小技巧上线之前把前端的控制台所有警告信息都处理一遍。Vue 项目编译时出现的 deprecation warning、ESLint 提示不要觉得“能跑就行”就忽略掉。很多隐患就是藏在这些警告背后的比如一个组件里用了已废弃的 API短期没问题但依赖升级后可能就直接报错。我在这个项目里就清理掉了一个关于$listeners的废弃警告换成新写法后代码反而更简洁也少了一个后续升级的隐患。
返回列表