ARTICLE DETAIL

资讯详情

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

Node.js + Vue构建养老院膳食与护工评价管理系统实战

Node.js + Vue构建养老院膳食与护工评价管理系统实战 做养老院管理系统这个项目之前我其实在“到底要不要用 Node.js Vue 来做”这件事上纠结了挺久。毕竟市面上随手能搜到的毕设教程、开源模板里Spring Boot Vue 的组合占了绝大多数Node.js Vue 的完整案例反而少一些。真正让我下定决心用这套技术栈的是养老院膳食和护工评价管理场景里那些“轻业务、重交互、快迭代”的需求特点——一个面向养老院的信息化管理系统核心是把膳食计划、护工考核、老人反馈串成一条完整的数据链Node.js 后端开发的高效和 Vue 组件化开发的体验在这个体量下恰好是匹配的。这篇文章不打算写成那种“系统功能列表 界面截图”式的流水账。我想把从需求拆解、技术选型、数据库设计、前后端实现到环境配置和实际部署过程中那些真正花过时间、踩过坑的东西讲清楚。如果你正在做类似的管理系统项目无论是毕设还是公司内部工具这篇应该能帮你省下不少试错的成本。1. 这个系统到底在解决什么问题养老院日常管理的三块硬骨头在做任何技术设计之前得先弄明白业务老师真正头疼的是什么。我前期去一家民办养老院蹲了半天跟负责后勤的院长助理聊完才意识到膳食、护工评价这类管理核心不是“记录”而是“协调”。1.1 膳食管理的核心痛点是“计划-执行-反馈”闭环断裂养老院的膳食和普通食堂最大的区别在于就餐对象是特殊人群。糖尿病老人要吃低糖餐、高血压老人要低盐、牙口不好的要软食还有一部分卧床老人需要流食。每天几百号人的饮食需求如果只靠食堂阿姨的记忆和纸质菜谱必然会出现荤素搭配失衡、营养餐送错、家属来问“今天老人吃了什么”答不上来的情况。一个合格的膳食管理系统需要把“每周菜谱制定 → 按老人忌口生成送餐名单 → 用餐反馈记录 → 下周菜谱调整”这条链路打通。这个系统里膳食模块不是简单的菜品增删改查而是要充分考虑计划的提前量。比如食堂管理员每周五得排好下周的菜谱系统要能自动校验“糖尿病老人今天吃的主食是否含糖过高”或者提醒“这周鱼虾类蛋白质已经连续两天没安排了”。1.2 护工评价的核心痛点是“评价维度碎片化”护工评价这件事传统做法是季度末发纸质打分表护理部主任凭印象打分家属意见最多挂在院长办公室的意见簿上。这里面的问题很明显——评价不透明、指标不统一、结果没有沉淀。有些养老院试过用 Excel 做评分汇总但收集上来的打分表维度五花八门有人评“态度”有人评“卫生”最后统计的人只能凭感觉加权。所以护工评价模块一定要做到三件事评价指标可配置、评价来源可区分、评价结果可追溯。指标可配置的意思是管理员可以自己定义“生活照料、卫生清洁、服务态度、应急处理”这些维度下面具体包含哪些细项、每个细项占多少权重。来源可区分是指护工组长、护士长、老人家属、老人本人或者通过家属代评都可以发起评价不同角色的评分权重在汇总时不一样。结果可追溯是指任何人看到一名护工的 90 分总分时能一层层点下去看到这 90 分是由哪几项加出来的。1.3 “中心”这个定位把分散的信息收拢到同一张管理视图上标题里“评价中心”这个说法在我看来不是指一个页面而是整套系统的设计基调。它意味着管理员打开系统首页就能看到一周内每个护工的评分变化曲线、食堂各窗口的菜品满意度、待处理的新增评价件数。它不是给护工自己看绩效的而是给管理层做决策用的驾驶舱。理解了这点后面在数据库冗余字段设计和前端首页可视化组件的取舍上都会有一个明确的方向。所有角色在这个系统里的职责边界也要先理清。我最终划分了四种角色系统管理员管账号和基础数据、食堂管理员管菜品和排餐、护士长/组长管护工评分审核、老人家属提交评价与浏览膳食记录。角色权限的差异直接决定了后端接口的鉴权粒度。2. 技术选型复盘为什么是 Node.js Vue 而不是其他搭配说实话这个项目如果让我用 Spring Boot 写也不会有什么技术障碍。但为什么最终选了 Node.js Express Vue 这套组合是基于开发效率、部署成本、学习曲线三个维度的综合判断。2.1 前后端分离的整体架构和 Node.js 的定位整个系统采用经典的前后端分离架构Vue 负责页面渲染和用户交互Node.js 负责提供 RESTful API 和业务逻辑处理MySQL 做数据持久化。这个架构的好处是前后端可以并行开发——我先把接口文档用 Apifox 定义好前端同事或者自己写前端时照着文档 mock 数据两边都不互相卡脖子。Node.js 在这个项目里的角色很明确轻量级 API 网关 业务逻辑执行体。Express 4.x 作为 Web 框架配上 Sequelize 做 ORM在单机部署、日均几百次请求的系统里性能完全够用。很多人担心 Node.js 处理高并发不行但这个系统的真实并发量可能还不如一个小型博客——养老院一个管理后台峰值也就几十个人同时操作Node.js 的非阻塞 I/O 模型反而在文件上传、数据导出这类任务上有天然优势。2.2 和 Spring Boot 的取舍逻辑我知道很多人会劝你“还是用 Spring Boot 吧资料多答辩好过”。这个观点有道理但要注意一个前提资料多对应的是“前人踩过的坑都写在博客里了”而 Node.js 的技术栈更贴近前端生态如果你本身 Vue 是主力再花时间学 Java 那套注解、依赖注入学习成本会翻倍。我选 Node.js 的一个重要理由就是可以完全用 JavaScript 一套语言打通前后端类型定义、数据格式不需要在脑子来回切换语言。另外一个现实因素是部署成本。毕业设计或者小型项目的最终交付通常是一台 2核4G 的云服务器。Node.js 应用打包后可能就 50MB启动只需要node app.js配合 PM2 进程守护内存占用稳定在 300MB 左右。而 Spring Boot 应用一个 fat jar 动辄 100MB 起步JVM 默认配置下随便跑起来就要占用 1G 内存。对于养老院这种舍不得买高配服务器的甲方Node.js 的轻量优势非常实在。2.3 Vue 2 还是 Vue 3、要不要上组件库这个项目用的是 Vue 3 Composition API Element Plus。选 Vue 3 不是因为追新而是因为script setup语法让组件的逻辑组织能力明显改善。比如护工评价页有一个“评价项动态增减”的交互用 Options API 写要维护 data、methods、computed 三块内容来回跳用script setup可以在一个作用域里直接把所有相关逻辑放在一起开发和维护都省心。组件库直接选了 Element Plus因为这种管理系统大量依赖表格、表单、弹窗、日期选择器自己从零造轮子纯属浪费时间。这套组件库在栅格布局、表单校验、表格分页上都有成熟方案能让前端开发周期缩短至少 40%。版本选择上建议锁一个稳定版本比如 element-plus 2.4.x不要一上来就 latest避免刚装完发现和某个插件的依赖冲突。2.4 数据库和 ORM 的选型数据库选了 MySQL 8.0这是最不容易出错的选择。ORM 层我用的是 Sequelize 6。选它的原因是模型定义方式直观、支持 migration 进行表结构版本管理、和 Express 配合成熟。如果你不想写 SQL 语句操作数据库表Sequelize 的 Model 定义和查询方法能在很大程度上提升开发速度。当然代价是复杂查询的 SQL 优化能力会被 ORM 遮蔽所以在这个项目里凡是涉及多表统计的查询我仍然会写原生 SQL 而不是硬用 ORM 拼。3. 数据库设计与核心表结构从“菜品-排餐-评价”这条主线说起数据库设计是这种管理系统项目中最见功力的一环。表建得好不好直接决定后面写接口是顺滑还是痛苦。我的设计原则是核心业务表不要偷懒省字段关联关系尽量清晰统计需要的冗余字段适度增加。3.1 用户角色表和权限控制的前置设计用户表users只保留最基本的字段id、username、passwordmd5 salt 加密存储、real_name、phone、roleadmin/kitchen/nurse/family、elder_id关联被监护老人、status、create_time。这里有个细节要注意护工和家属这两个角色在业务上并不是直接对应用户表那么简单。一个护工要关联到她负责的床位区域一个家属账号要关联到某位老人才能在提交评价时自动带出老人信息和所在区域。所以我在用户表之外单独设置了caregiver_profile表存放护工的工号、负责区域、入职时间、当前状态在family_binding表存放家属和老人的绑定关系。这样用户表只做统一登录鉴权各角色的扩展属性分表存放避免一张表字段过多、逻辑混乱。3.2 膳食模块的核心表链路膳食管理我拆了四张表形成一个清晰的从菜谱到用餐记录的链路dishes菜品表id、name、category早/中/晚/加餐、ingredients、calories、tag低盐/低糖/软食/流食/普通、image_url、status、create_time。标签字段特别重要后续自动排餐校验能否匹配老人忌口全靠这个标签。weekly_menus周菜谱表id、week_start_date、dish_id、day_of_week1-7、meal_typebreakfast/lunch/dinner、quantity、create_time。这张表存储的是“本周某天某餐吃哪道菜”的排期。elder_meal_plans老人用餐计划表id、elder_id、meal_type、dietary_restriction、breakfast_rule/lunch_rule/dinner_rule关联 dish_id 或允许替换规则、status。这张表表达的是“特定老人每餐吃什么、有什么忌口”。meal_records用餐记录表id、elder_id、dish_id、meal_type、record_date、is_completed、feedback_score、feedback_content、create_time。老人用餐后记录实际吃了什么、满意度如何。这套表的逻辑关系稍微绕一点但确实能支撑完整的业务闭环食堂管理员在本周初通过操作界面复制上周菜谱 → 系统自动检查有多少老人当前餐次对应的菜品打上了“低盐”标签却匹配了限制盐分的老人 → 管理员手动调整 → 生成老人们的周排餐计划 → 每天护工端在送餐时勾选“实际用餐状态”并代老人记录满意度 → 月末按菜品汇总满意度。3.3 护工评价模块的表结构及评分权重设计护工评价是另一条主线我设计了三张表支撑灵活的指标配置evaluation_items评价项表id、item_name比如“生活照料-协助洗澡”、category生活照料/卫生清洁/服务态度/应急处理、parent_id支持二级分组、score_rule扣分制或积分制、max_score、weight、status、create_time。evaluation_records评价记录表id、elder_id、caregiver_id、evaluator_id评价人、evaluator_rolenurse/family/manager、period2024-05、total_score、suggestion、status待核/已核、audit_by、create_time。evaluation_record_items评价记录明细表id、record_id、item_id、item_score、remark。这张表存放每条评价记录中每个细项的得分方便追溯。权重设计这个功能我在前端是一个“评分模板配置器”管理员拖动滑块调整各维度权重系统自动校验权重总和必须等于 100%后端在提交模板时也会再次校验。这样避免了“管理员不小心把权重调到 120%”导致的总分异常。总分的计算规则是各维度得分 该维度细项得分之和 ÷ 该维度细项数量总分 三个维度得分分别乘以权重再相加。护工月度绩效分则取当月所有有效评价记录的平均值。3.4 一个容易被忽略的关联设计区域与床位养老院的管理通常以“楼层/区域”为维度比如三楼住的是半自理区四楼是失能区。护工负责的区域、老人居住的区域在做统计时经常要按区域筛选。所以我在老人表elders中增加了building、floor、room_no、bed_no四个字段并在护工评价统计接口中直接用这个字段做分组查询。这个设计虽然简单但在后续按楼层导出月度护理质量报表时能省掉大量关联查询的复杂度。4. 后端接口实现细节Node.js Express 下的业务逻辑拆解后端总的来说是一套标准的 Express 项目结构代码组织上按“路由层 - 控制器层 - 服务层 - 模型层”分层。我不建议把所有逻辑都堆在路由回调函数里那种写法在项目超过 20 个接口后维护起来极其痛苦。4.1 目录结构设计和服务层拆分我最终的目录结构大致是这样的server/ app.js # 入口文件初始化 Express、中间件、路由 config/ db.js # 数据库连接配置 jwt.js # JWT 密钥和过期时间 models/ user.js dish.js weeklyMenu.js elderMealPlan.js mealRecord.js evaluationItem.js evaluationRecord.js evaluationRecordItem.js routes/ auth.js dish.js menu.js mealRecord.js evaluation.js statistics.js controllers/ authController.js dishController.js menuController.js mealRecordController.js evaluationController.js statisticsController.js services/ mealPlanService.js evaluationScoreService.js statisticsService.js middlewares/ auth.js role.js upload.js utils/ response.js crypto.js服务层的意义在于把复杂的业务逻辑从控制器中抽离出来。比如“生成下周菜谱”这个行为控制器只负责接收参数和返回结果真正的逻辑在mealPlanService.js里——它需要先查这周的菜品供给情况、老人忌口表、上个月的菜品满意度然后综合判断后自动生成新菜谱。这样写的好处是如果以后要改成“智能排菜单”只需要改服务层函数接口层完全不用动。4.2 JWT 登录鉴权的实现思路登录鉴权我用的是 JWT登录成功返回一般会签发的信息包括 userId、username、role有效期为 7 天。中间件里做两件事验证 token 是否过期、解码后把用户信息挂在req.user上供后续接口使用。const jwt require(jsonwebtoken); function authMiddleware(req, res, next) { const token req.headers.authorization?.split( )[1]; if (!token) { return res.status(401).json({ code: 401, message: 未登录 }); } try { const decoded jwt.verify(token, process.env.JWT_SECRET); req.user decoded; next(); } catch (err) { return res.status(401).json({ code: 401, message: 登录已过期请重新登录 }); } }角色控制中间件role.js稍微扩展了一下接受一个角色数组参数。比如requireRole([admin, nurse])表示只有管理员和护士长角色能访问该接口。这个中间件在服务层之上确保家属不能直接改菜品库食堂管理员看不到护工评价的审核按钮权限边界清清楚楚。4.3 膳食排期的核心接口自动生成和校验自动生成一周菜谱这个接口是整个膳食模块比较核心的接口之一。设计逻辑其实不复杂但需要细心处理边界条件。// mealPlanService.js 中自动排餐的核心逻辑 async function generateWeeklyMenu(startDate) { const weekStart moment(startDate).startOf(isoWeek); const weekEnd moment(weekStart).add(6, days); // 1. 获取上个月菜品满意度 Top 10 const topDishes await getTopDishesByFeedback(30); // 2. 获取当前所有老人的忌口集合 const elderlyRestrictions await getDietaryRestrictions(); // 3. 初始化周菜单模板默认参考上周 const lastWeekMenu await getLastWeekMenu(weekStart.subtract(7, days)); // 4. 逐日逐餐生成优先复用上周结构用 Top 菜品替换连续重复超过2次的菜品 const weeklyPlan []; for (let day 1; day 7; day) { const dayPlan { day_of_week: day, meals: [] }; for (const mealType of [breakfast, lunch, dinner]) { let dish await pickDishForMeal(topDishes, lastWeekMenu, day, mealType); const conflictCount await checkRestrictionConflict(dish, elderlyRestrictions); if (conflictCount 0) { // 有忌口冲突替换为同标签下满意度次高菜品 dish await findAlternativeDish(dish.tags, elderlyRestrictions); } dayPlan.meals.push({ meal_type: mealType, dish_id: dish.id }); } weeklyPlan.push(dayPlan); } // 5. 批量插入 weekly_menus 表 await bulkCreateWeeklyMenu(weeklyPlan, weekStart); return weeklyPlan; }这里有几个容易出问题的点。第一是“本周”的定义要统一我用的是 ISO 周规则周一到周日避免和“自然周日到周六”混淆导致排期错位。第二是替换菜品时要保证替换后的菜品的营养标签能覆盖原菜品的约束比如原菜品是“低盐”找替代品时就限定tag LIKE %低盐%。第三是替换后的菜品尽量不要和同一周的另外两次重复这也是我之前踩过的坑——系统自动排餐排出了连续三天晚餐都吃土豆烧肉被食堂阿姨电话投诉。4.4 护工评价的算分逻辑和防作弊设计评价算分我在evaluationScoreService.js里实现核心逻辑是取该护工当月所有“已审核”评价记录按评价人身份加权计算。async function computeMonthlyScore(caregiverId, period) { const records await EvaluationRecord.findAll({ where: { caregiver_id: caregiverId, period, status: approved }, include: [{ model: EvaluationRecordItem, as: items }] }); if (records.length 0) return 0; const weights { nurse: 0.5, // 护士长评分权重 50% family: 0.3, // 家属评分权重 30% manager: 0.2 // 管理员评分权重 20% }; let total 0; let weightSum 0; for (const record of records) { const roleWeight weights[record.evaluator_role] || 0.2; // 每条记录的总分已经存入 record.total_score total record.total_score * roleWeight; weightSum roleWeight; } return weightSum 0 ? Math.round((total / weightSum) * 100) / 100 : 0; }防作弊的设计体现在两点一是评价记录从提交到生效必须经过护士长“审核”审核之前不算入月度绩效避免家属心情不好时随手打零分直接拉低护工绩效二是同一个家属对同一位护工在同一个周期内只允许提交一次评价后端在校验时如果发现已有evaluator_id caregiver_id period重复就直接返回“本期已评价过”的提示。5. 前端页面与交互实现Vue 组件化让复杂页面变简单前端这边我用 Vue 3 Vue Router 4 Pinia Element Plus按角色区分路由和菜单。整体页面数量不多但每个页面的交互密度不小尤其是膳食排餐和评价配置这两个页面。5.1 路由设计和权限控制的前端落地路由拆成了静态路由和动态路由两部分。静态路由只有登录页和 404 页其余全部走动态路由——用户登录后后端根据角色返回可访问的路由列表前端拿到后通过router.addRoute动态挂载。// permission.js 路由守卫核心逻辑 router.beforeEach(async (to, from, next) { const userStore useUserStore(); if (userStore.token) { if (to.path /login) { next(/dashboard); } else { if (!userStore.hasLoadedRoutes) { const accessRoutes await userStore.fetchAndGenerateRoutes(); accessRoutes.forEach(route router.addRoute(route)); next({ ...to, replace: true }); } else { next(); } } } else { if (to.path /login) { next(); } else { next(/login); } } });动态路由带来的一个坑是刷新页面后路由会丢失因为 Pinia 里的状态是内存态刷新就清空了。解决方案是在fetchAndGenerateRoutes请求时把路由数据顺便缓存一份到 localStorage刷新时先读缓存再请求后端确认避免“刷新即白屏”的问题。5.2 周菜谱编辑器的组件化拆分膳食排餐页面是整个前端最复杂的页面。它是一个 7 列周一到周日× 3 行早中晚餐的网格每个格子是一个菜品选择弹窗。我把它拆成了三个组件WeekCalendarMenu.vue外层网格容器、MenuCell.vue单个格子、DishSelectorDialog.vue菜品选择弹窗。MenuCell接收两个关键 props——dayOfWeek和mealType内部根据这两个值去调用接口查询当前格子已经安排的菜品。用户点击格子时弹出DishSelectorDialog弹窗内部从/api/dishes拉取候选菜品列表按分类标签筛选选中后调用更新接口同时用 Element Plus 的ElMessage给出成功状态反馈。5.3 Axios 统一封装和接口请求的细节Axios 封装是我觉得前端工程化的基础必做项。统一配置 baseURL 指向/api开发环境通过 Vite 的代理转发到后端 3000 端口生产环境由 Nginx 做反向代理。请求拦截器里自动带上Authorization: Bearer头响应拦截器统一处理错误码401 跳登录页、500 弹出“服务异常请稍后重试”、业务错误码直接ElMessage.error(message)展示后端返回的提示文字。// axios.js 封装的响应拦截器核心 service.interceptors.response.use( response { const res response.data; if (res.code ! 0) { ElMessage.error(res.message || 请求失败); if (res.code 401) { userStore.logout(); router.push(/login); } return Promise.reject(new Error(res.message)); } return res.data; }, error { ElMessage.error(error.message || 网络错误); return Promise.reject(error); } );5.4 用餐记录和评价数据的可视化呈现数据可视化我用的是 ECharts 5 按需引入的方式。首页仪表盘放了三个图折线图展示最近 30 天老人用餐满意度趋势、横向柱状图展示各区域护工月度综合评分排名、饼图展示本周菜品满意度分布。ECharts 在 Vue 3 里的使用方式没什么特殊的关键点是在onUnmounted钩子里调用chart.dispose()否则频繁切路由会内存泄漏。另一个经验是不要直接用echarts.init去初始化同一个 DOM 节点两次Vue 组件销毁重建时容易报 “Initialize failed: invalid dom” 的错误。6. 从零搭建到跑通环境配置和开发调试的实战避坑这个标题下其实有一半的初学者卡住的往往不是业务代码而是环境搭建。尤其是 Windows 环境下Node.js 和 npm 的问题格外多。我在这个项目里遇到并解决的几类问题基本覆盖了网上搜索热度最高的那些坑。6.1 Node.js 安装与环境配置中最高频的报错很多人在 Windows 上装完 Node.js 之后试图在 PowerShell 里运行npm install结果直接飘红npm : 无法加载文件 C:\Program Files\nodejs\npm.ps1因为在此系统上禁止运行脚本。这个问题的本质是 PowerShell 执行策略默认是 Restricted不让运行 .ps1 脚本。解决方案有两种我推荐用第一种第二种的开法不太想提因为它很容易引发性子急的同学顺手把机器的执行策略调到 Unrestricted导致安全问题。说回正经方案。你不需要去动系统级权限只需要把默认终端从 PowerShell 切换成 Git Bash或者在项目目录下直接用 CMD命令提示符运行 npm 命令。如果确实想在 PowerShell 里操作有一个更稳妥的办法打开 PowerShell输入Set-ExecutionPolicy -Scope CurrentUser ExecutionPolicy RemoteSigned只对当前用户放开本地脚本执行权限效果可持续到这台机器重装系统。另外一个高频问题是 Node.js 版本不匹配导致编译失败。Vue 3 Vite 项目要求 Node 版本不低于 16.14我建议直接装 18 LTS 或 20 LTS。用nvm-windows管理 Node 版本也是个好习惯开发时按项目切版本避免不同项目互相污染依赖。6.2 Vue 项目初始化和依赖安装时的崩溃现场前端项目初始化的命令是npm create vuelatest但这个命令在 npm 源默认指向国外镜像时下载速度非常感人经常“转圈半小时然后超时”。我处理的办法是先把 npm 源切到国内镜像npm config set registry https://registry.npmmirror.com。切完源再初始化速度能快一个数量级。依赖安装时另一个常见问题是node_modules里有玄学冲突比如 Element Plus 和某个低版本vite-plugin-vue-setup-extend同时存在时编译会报奇怪的.vue文件解析错误。我的暴力解法是删除node_modules和package-lock.json先只装核心依赖确认能跑起来再逐个安装功能依赖装一个跑一次npm run dev。虽然效率低一点但能精确定位是哪一次安装引入了冲突。6.3 前后端联调阶段最容易卡住的跨域和代理问题开发阶段前后端是分端口跑的Vue 默认跑在 5173Node.js 跑在 3000。如果不做任何处理前端页面里fetch(/api/dishes)会直接请求到 5173 这个端口然后 404。标准解法是在vite.config.js里配置开发代理// vite.config.js export default defineConfig({ server: { port: 5173, proxy: { /api: { target: http://localhost:3000, changeOrigin: true, rewrite: path path.replace(/^\/api/, ) } } } });配置代理的时候要注意changeOrigin: true这个参数。如果不加某些请求带上原始 Host 头后端如果做了 Host 校验就会 403。另外如果你用了history模式路由且不是 SPA 的根路径刷新Nginx 那边要加try_files $uri $uri/ /index.html;否则一刷新就 404 白屏。这个坑我记忆犹新第一次部署时盯着白屏看了半天打开控制台才发现是静态资源路径全偏了。6.4 组件源码发给别人后运行不起来的问题网上经常有人问“Vue 项目源码怎么发给别人才能跑起来”这个问题背后的本质是依赖不完整或环境差异。我的经验是保证项目里保留package.json和package-lock.json把node_modules目录加进.gitignore接收方拿到后先执行npm ci而不是npm install前者会严格按照 lock 文件安装依赖避免版本浮动。另外项目里的绝对路径配置要改成相对路径比如base: ./否则发给别人换目录跑图片和路由全挂。7. 部署到服务器让系统真正跑在养老院的电脑上系统开发完成只是第一步真正让它在养老院的服务器上稳定运行还涉及到部署方式、数据库初始化和进程守护这几件麻烦事。7.1 前后端分离部署的思路和细节我的部署方案分两层前端用 Nginx 托管打包后的静态文件后端用 PM2 守护 Node.js 进程。具体步骤是先跑npm run build生成dist目录把dist里的内容上传到服务器指定目录比如/var/www/meal-system/dist后端整包上传到/opt/meal-system/server安装依赖后执行node app.js验证能不能起来再交给 PM2 托管。Nginx 配置里关键的两块一是location /指向前端静态文件目录二是location /api/做反向代理到http://127.0.0.1:3000。跨域问题在 Nginx 层解决比在后端配 CORS 干净得多。7.2 数据库初始化和历史数据迁移的取舍MySQL 8.0 安装完毕后需要执行数据库初始化脚本。我建议利用 Sequelize 的 migration 机制管理表结构变更而不是手动建表。但项目赶进度的时候直接sequelize.sync({ force: true })一把梭也是合理的——前提是你清楚这会清空所有表数据。我自己的做法是第一版开发时用 sync 自动建表确认表结构没问题之后把建表 SQL 导出来存一份上线时手工执行那批 SQL避免线上误跑 sync 重置数据。这个系统的数据量不大但养老院方面可能会要求把历史纸质台账录入系统。这个问题不要提前做——需要甲方先搞清楚录入范围再评估是用 Excel 导入功能一键导入还是安排实习生手工录入。我在交付时做了一个简易的 Excel 导入接口字段校验仔细一点能省下后期大量补录工作。7.3 进程守护、日志和备份的三个小动作Node.js 应用裸跑是很危险的事情一崩溃就全站 502。我直接用 PM2 托管并设置了开机自启。日志方面把 PM2 的输出重定向到日志文件按日期切割方便排查问题。数据库备份用 cron 每天凌晨两点执行一次mysqldump简单粗暴但足以应对数据丢失的极端情况。我还建议在服务器上配置一个健康检查脚本每分钟自动请求一次系统的健康检查接口如果连续 3 次失败就自动重启 Node 服务。这个脚本用 shell 写不到 30 行但对系统的稳定性提升是实打实的。到这里这套养老院膳食护工评价中心管理系统的主要技术脉络就梳理完了。如果让我再重做一次我会在一开始就把“食堂排餐的自动替换算法”设计得更完善一些而不是在食堂阿姨电话投诉之后才补上“连续三天不能重菜”的约束。开发这类管理系统的最大感受是技术实现本身并没有多难难的永远是提前理解业务现场的真实约束——比如老人忌口不能开玩笑、护工评分得让双方都信服、食堂阿姨不会用复杂交互。这些靠的是跟业务人员多聊几次把他们的真实工作流程变成系统里的默认逻辑。希望这篇分享能帮你少走一些弯路。
返回列表