ARTICLE DETAIL

资讯详情

深耕编程入门与网站建设的一线实战洞察。

Node.js社区疫情防控管理系统:从毕业设计到实战的完整技术指南

Node.js社区疫情防控管理系统:从毕业设计到实战的完整技术指南 做了这么些年开发每逢毕业季总会被问到一个经典问题毕业论文要写系统选什么题好如果是软件工程、计算机科学这类专业那“Node.js 管理系统”的组合几乎可以说是默认选项了。今天要拆的这个选题就是典型的计算机毕业设计题目——nodejs社区疫情防控管理系统。这个项目单看名字有点唬人但拆开之后本质上就是一套社区级的健康信息管理平台居民信息维护、每日健康状态上报、社区出入登记、公告通知发布、数据统计展示再加上管理员后台。从毕业设计的答辩角度看功能完整度、技术栈热度、工作量都是拉满的。从实际价值看它覆盖了 Node.js 后端开发中最常见的一整套技能点Express 框架、MySQL 数据库设计、JWT 身份认证、异步接口开发、前端页面联调。无论你是想拿它当毕业设计还是想练手积累经验这项目都值得好好盘一盘。这篇文章我会把整套项目的设计思路、数据库结构、核心接口实现、环境配置的坑、前后端联调细节全部梳理一遍。不是那种只列代码不给思路的教程而是把每一步“为什么这么选”“当时踩了什么坑”都讲清楚给到可以直接照着做的程度。1. 项目定位与需求拆解1.1 这个系统到底要解决什么问题在做任何项目之前第一件事永远是搞清楚需求边界。很多同学拿到这种题目就直接开写代码结果做到一半发现功能乱成一团代码越写越难受。所以先花点时间把“社区疫情防控管理系统”到底要管什么理清楚。这个项目的核心场景是社区需要一个后台来统一管理居民的日常健康信息和进出情况。站在用户角度来看主要角色分两类普通居民和社区管理员。普通居民做的事情比较简单——注册登录、每日上报自己的健康状况、进出小区时登记信息、查看社区发布的公告。管理员则负责更重的活审核居民上报的数据、管理居民台账、维护出入记录、发布通知公告、查看各类统计报表。这里有个随处可见的设计误区想着把功能做得多花哨结果开发量暴涨最后连基础功能都没写完。我建议的核心原则就一句话——优先做透高频刚需功能再考虑锦上添花。比如“每日健康上报”和“出入登记”是天天都要用的功能必须做得顺手、稳定而“数据可视化大屏”这种就是展示项能用柱状图、饼图表达出来就够了没必要一开始就堆图表库。1.2 角色权限为什么要区分清楚权限设计是管理系统绕不开的坎。很多同学一开始不重视把所有接口都做成登录就能访问等到答辩老师一问“管理员功能和居民功能有什么不同”当场卡壳。这个项目里角色的划分必须从设计第一天就定清楚普通居民resident个人信息的查看与修改、每日健康上报、出入登记申请、公告查看。社区管理员admin居民账号管理、健康上报审核与统计、出入记录查询、公告发布、系统数据总览。权限控制落到代码层面就是在后端写一个统一的中间件。用户登录成功后返回一个带角色标识的 token每次请求后端先解析 token、拿到当前用户角色再判断该角色有没有权限访问这个接口。这样的设计在答辩时可以非常清楚地讲出“权限如何控制”这一问。需要注意的是这里说的是社区级管理项目只涉及基础数据的管理逻辑和任何行业监管、政策解读都不沾边别把系统功能往复杂的方向联想。2. 技术选型为什么是 Node.js 这一套组合2.1 后端框架选择毕业设计选 Node.js最大的优势就五个字上手门槛低。JavaScript 本身语法灵活、不要求强类型基础前端同学可以直接无缝切到后端开发。加上 Node.js 自带非阻塞 I/O开发小型管理系统时根本不用担心性能瓶颈Express 框架又极其成熟社区资料一抓一大把。Express是这个项目的最优选。你可能会想为什么不用 Koa或者需要性能更强点是不是该考虑 Nest.js从毕业设计的角度出发Express 的中间件生态最丰富网上能找到的参考实现最多出了问题排查也快。Koa 虽然更现代但它大量使用 async/await 和洋葱模型对新手来说思维转换成本不小Nest.js 则是全家桶式框架依赖注入、装饰器这些概念放在毕设里反而容易把自己绕晕。2.2 数据库选择数据库我强推MySQL原因很实际毕设要求通常是“使用数据库”MySQL 关系模型最清晰表结构设计、外键关系、SQL 查询这些是在答辩时能很直观展示的东西。SQL 语法和工具链成熟Navicat、DBeaver 随便连出了问题好排查。Node.js 生态里有非常成熟的mysql2驱动库支持 Promise写起来舒服。当然也有同学选 MongoDB说文档型数据库更适合存一些非结构化的健康上报数据。这话没错但从系统整体看居民信息、出入登记、公告公告这些数据之间关系明确用 MySQL 表达“一个居民有多条上报记录”“一个用户属于一个角色”这种关联更直观。答辩时老师也更熟悉 MySQL 的范式设计逻辑沟通成本低。2.3 前端方案前端部分我建议用经典的HTML CSS JavaScript Bootstrap 或原生模板渲染。不是我不想用 Vue而是要权衡开发效率。如果你本身会 Vue那你可以在项目里引入 Vue 做单页应用这确实能提升项目档次。但如果你仅仅为了这个毕设去现学 Vue 脚手架 组件化开发时间成本太高。最稳的方案是用服务端模板——Express 内置的ejs模板引擎就能搞定后端渲染页面、前端通过 fetch 或 axios 调接口两不耽误还能体现前后端交互的过程。我的具体组合是ejs做页面模板配合 Express 路由渲染后台管理页和用户页面bootstrap5 做 UI 框架表格、按钮、表单组件直接套axios做接口请求库统一处理 token 携带和错误提示图表部分用echarts展示健康统计数据和出入趋势页面数量控制在 10 个以内每个页面功能聚焦不用做出花里胡哨的视觉效果干净整洁、逻辑清晰最加分。3. Node.js 环境搭建与运行时踩坑3.1 Node.js 版本选择与安装环境配置是最开始的一关但恰恰是很多同学第一个栽跟头的地方。开发这个项目Node.js 版本不建议追新也不建议用太老的版本。我目前用下来Node.js 16.x 或 18.x LTS 版本最稳。太新的版本有时候会让一些依赖库出现兼容性告警太老的版本又不支持部分新语法特性比如可选链?.和空值合并??。去官网下载对应系统安装包一路 next 就行。装完之后打开命令行同时输入下面两条命令验证是否成功node -v npm -v如果都能打印出版本号说明 Node.js 本体安装成功。这里有个小细节值得注意安装目录尽量别带空格和中文我自己就踩过C:\Program Files\nodejs这种路径的坑虽然平时没问题但个别原生模块编译时就会因为路径空格出怪毛病。如果你是新手直接装在D:\nodejs这种纯净路径下后续省心很多。3.2 经典报错npm.ps1 无法加载文件这个坑基本是 Windows 用户人手一个很多同学刚装好 Node.js准备执行第一条npm install就弹出这么一句npm : 无法加载文件 C:\Program Files\nodejs\npm.ps1因为在此系统上禁止运行脚本。原因很简单Windows PowerShell 默认的执行策略是Restricted不允许任何脚本文件运行而 npm 本身就是一个.ps1脚本文件。解决办法也有几种我推荐最省事的一种以管理员身份打开 PowerShell执行Set-ExecutionPolicy RemoteSigned然后输入Y确认。这条命令的意思是本地创建的脚本可以运行从网络下载的脚本必须要有可信签名。改完之后重新打开终端npm install就不会再报错了。还有一种办法是避免用 PowerShell改用 cmd 命令提示符但治标不治本后面你还会碰到各种需要 PowerShell 运行脚本的场景。所以还是设置执行策略更根本。3.3 初始化项目与依赖安装环境装好之后开始初始化项目。新建一个文件夹比如community-epidemic-system然后终端进入该目录npm init -y-y的意思是全部默认配置快速生成package.json。接下来安装核心依赖。我把完整的依赖清单列在这里并标注每一样是干什么的{ dependencies: { express: ^4.18.2, mysql2: ^3.2.0, jsonwebtoken: ^9.0.0, bcryptjs: ^2.4.3, ejs: ^3.1.9, axios: ^1.3.4, cors: ^2.8.5, dayjs: ^1.11.7 }, devDependencies: { nodemon: ^2.0.22 } }用一条命令就能全部装完npm install express mysql2 jsonwebtoken bcryptjs ejs axios cors dayjs npm install nodemon --save-dev说说每个包的必要性cors解决跨域请求问题。虽然前后端同域开发时不一定要用但部署分离或本地调试时会遇到跨域提前装上没坏处。bcryptjs用户密码加密。密码绝对不能明文存数据库这是系统安全的基本底线。jsonwebtoken生成 JWT token实现无状态登录认证。dayjs日期处理。项目里到处都要用日期比如上报日期、出入登记时间、公告发布时间用它格式化非常简洁。依赖装完后在package.json的scripts里加上一条常用启动命令scripts: { start: node app.js, dev: nodemon app.js }nodemon的作用是监听文件变化自动重启服务开发时不用每次都手动重新启动效率提升非常明显。4. 数据库设计表结构决定系统的骨架4.1 核心数据表一览数据库设计是整个系统的地基表关系理不清后面写接口就是灾难。我把这个项目需要的数据表先列出来表名作用关键字段users系统用户表存放居民和管理员账号id, username, password, real_name, role, phone, id_cardresidents居民详细信息表id, user_id, address, community_name, statushealth_reports每日健康上报记录id, user_id, report_date, temperature, symptoms, is_risk, statusaccess_records出入登记记录id, user_id, access_type, access_time, destination, remarkannouncements公告通知id, title, content, publisher_id, create_timevisits访客登记表id, visitor_name, phone, id_card, target_user_id, visit_time, leave_time这张表数量对于一个毕设来说刚刚好既不会少到显得单薄也不会多到开发量超出可控范围。每一张表都有独立的业务场景支撑答辩时能讲清楚每张表的用途就足够有说服力这就是“数据模型完整”最直接的表现。4.2 用户与居民的信息分离设计这里我刻意把users表和residents表分开而不是把所有信息挤在一张表里这是有讲究的。users表只关注登录认证相关字段用户名、密码、角色、手机号。这些信息在登录验证时用得最频繁。而residents表存放的是业务属性更重的信息住址、所属社区、健康状态。把它们分开的好处是让“账号体系”和“居民档案”解耦将来如果系统要接入其他功能比如预约核酸、体检等居民信息可以被多个模块单独引用。查询性能更好登录时只需要在users表做一次轻查询不需要把一大串居民信息拉出来。users表里我通过role字段区分角色类型比如0表示管理员1表示普通居民。而residents表通过user_id外键关联到users表形成一对一的对应关系。这个设计在答辩时一定要能讲出来你的用户体系是怎么设计的为什么做分离。4.3 健康上报表为什么单独建健康上报是整个系统的业务核心也是一个明显的多对一关系一个居民每天可以产生多条上报记录或者设定每天只允许上报一次一天一条记录。health_reports表我给出了这些字段report_date上报日期格式是YYYY-MM-DDtemperature体温浮点数symptoms症状描述字符串is_risk是否风险人群布尔值或 0/1status审核状态0待审核、1已通过、2已驳回这里有个关键设计点把status独立出来。因为管理员需要审核居民上报的数据审核这一操作代表了管理员对数据的确认或驳回状态值必须单独记录。如果不做这个字段那居民一上报就自动生效管理员这个角色就失去了存在意义。从索引角度来看我建议给user_id和report_date建联合唯一索引用来保证同一个居民同一天只能提交一条上报记录避免重复数据ALTER TABLE health_reports ADD UNIQUE KEY uk_user_date (user_id, report_date);这个索引写出来答辩时可以说“通过数据库约束保证业务规则的唯一性”是很加分的点。5. 后端接口设计与核心代码实现5.1 API 路由规划后端代码没必要一上来就写先把接口清单规划好。我习惯先画一张“接口地图”知道有多少个端点、每个端点干什么然后按照模块去逐一实现。这个项目我规划的接口如下模块方法路径说明认证POST/api/auth/register居民注册认证POST/api/auth/login登录并获取 token居民GET/api/residents/profile获取当前用户信息居民PUT/api/residents/profile修改居民信息上报POST/api/reports提交每日健康上报上报GET/api/reports/mine查看我的上报记录上报GET/api/reports管理员查看所有上报记录上报PUT/api/reports/:id/status管理员审核上报记录出入POST/api/access提交出入申请出入GET/api/access/mine查看我的出入记录出入GET/api/access管理员查看所有出入记录公告GET/api/announcements获取公告列表公告POST/api/announcements管理员发布公告统计GET/api/statistics/overview管理员查看核心统计数据接口统一使用/api前缀通过 HTTP 方法区分操作语义一看就知道是 RESTful 风格。这样写出来的接口无论对接前端还是给答辩老师演示都显得规范。5.2 JWT 认证实现登录是整个系统安全性的第一道门。密码用bcryptjs加密存储登录成功后签发一个 JWT token下面是一段核心代码const jwt require(jsonwebtoken); const bcrypt require(bcryptjs); // 登录接口 router.post(/login, async (req, res) { const { username, password } req.body; const [rows] await db.query(SELECT * FROM users WHERE username ?, [username]); if (rows.length 0) { return res.status(401).json({ message: 用户不存在 }); } const user rows[0]; const isMatch await bcrypt.compare(password, user.password); if (!isMatch) { return res.status(401).json({ message: 密码错误 }); } const token jwt.sign( { id: user.id, role: user.role }, your_secret_key, { expiresIn: 12h } ); res.json({ token, user: { id: user.id, username: user.username, realName: user.real_name, role: user.role } }); });注意 JWT 的payload里只放了用户 id 和角色不要放密码、身份证号等敏感信息。token 有效期设为 12 小时既保证用户不用频繁重新登录也不会让 token 过期时间过长带来安全隐患。这里的your_secret_key在真实项目中应该放在环境变量里不要硬编码到代码中。5.3 权限控制中间件有了 JWT就可以实现权限控制中间件。这个中间件的主要工作有三个步骤取出请求头里的 token、验证 token 是否有效、把解析后的用户信息挂到req对象上。function authRequired(req, res, next) { const token req.headers[authorization]?.split( )[1]; if (!token) { return res.status(401).json({ message: 未提供登录凭证 }); } try { const decoded jwt.verify(token, your_secret_key); req.user decoded; next(); } catch (err) { return res.status(401).json({ message: 登录凭证无效或已过期 }); } } function adminRequired(req, res, next) { if (req.user.role ! admin) { return res.status(403).json({ message: 无权限访问 }); } next(); }两个中间件的区别在于authRequired保护所有需要登录的接口adminRequired则是在前者的基础上进一步限制只有管理员能访问。使用的时候在路由上叠加router.get(/reports, authRequired, adminRequired, getReports);这样写路由权限逻辑一层层套上去非常清晰答辩讲解时也容易说清楚。如果某天要调整权限策略只需要修改中间件不用动业务代码。5.4 健康上报核心业务逻辑上报接口是这个系统使用频率最高的接口之一。核心逻辑是判断该用户今天是否已经上报过如果上报过直接返回提示否则插入一条新记录。router.post(/reports, authRequired, async (req, res) { const { temperature, symptoms, isRisk } req.body; const userId req.user.id; const today dayjs().format(YYYY-MM-DD); // 查今天是否已上报 const [exists] await db.query( SELECT id FROM health_reports WHERE user_id ? AND report_date ?, [userId, today] ); if (exists.length 0) { return res.status(400).json({ message: 今日已上报无需重复操作 }); } await db.query( INSERT INTO health_reports (user_id, report_date, temperature, symptoms, is_risk, status) VALUES (?, ?, ?, ?, ?, ?), [userId, today, temperature, symptoms, isRisk, 0] ); res.json({ message: 上报成功 }); });这里有个很关键的点不要把日期从前端传过来而是后端自己用dayjs()获取当前日期。为什么因为前端传的日期可以被伪造用户完全可以“补报”之前的数据。后端统一取系统时间更能保证数据的可信度。另外我在实现上报时做了一个小小的状态机设计新增时默认status 0管理员通过审核改为1驳回则改为2。为什么默认不是直接通过因为需要审核流程来体现管理员的角色价值也更能体现系统的完整性。6. 前端模板与页面交互细节6.1 模板页面结构后端接口写完下一步是前端页面。我使用 EJS 模板页面结构大体如下views/ ├── login.ejs ├── register.ejs ├── index.ejs // 首页管理员总览 ├── resident/ │ ├── profile.ejs // 居民档案 │ ├── report.ejs // 健康上报 │ └── access.ejs // 出入登记 ├── admin/ │ ├── reports.ejs // 上报审核列表 │ ├── access.ejs // 出入记录管理 │ ├── residents.ejs // 居民台账 │ └── announcements.ejs // 公告管理每个页面尽量只做一件事。比如resident/report.ejs就是一个简单的表单页加一个历史记录表格页面清爽代码也不会堆成山。很多失败的毕设项目都有一个通病把所有功能塞到一个页面里页面滚个三屏都看不到头自己写的时候头晕答辩演示时也不好看。6.2 axios 请求封装前端页面中所有接口调用我统一封装到一个api.js文件中。为什么封装因为每个请求都需要携带 token且需要统一拦截错误状态码不封装的话每个页面都要写一遍冗余度太高。const api axios.create({ baseURL: /api, timeout: 10000 }); // 请求拦截器自动带 token api.interceptors.request.use(config { const token localStorage.getItem(token); if (token) { config.headers.Authorization Bearer token; } return config; }); // 响应拦截器统一处理 401 api.interceptors.response.use( response response.data, error { if (error.response error.response.status 401) { localStorage.removeItem(token); window.location.href /login; } return Promise.reject(error); } );这段代码值得在答辩时重点讲一下拦截器的设计模式让你在整个前端项目中不需要在每个页面重复判断登录状态。token 一过期自动踢回登录页这是权限系统在前端的闭环。6.3 管理员审核页面的表格交互管理员审核上报记录是后台的核心操作页面。我的实现方式是一个搜索筛选表格字段包括居民姓名、上报日期、审核状态。表格每一行末尾有“通过”和“驳回”两个按钮点击后调用审核接口。这里有个用户体验细节按钮点击后要做防重复处理不能让用户点两下就提交两次审核请求。前端在请求发送期间禁用按钮即可async function review(id, status) { const btn event.target; btn.disabled true; try { await api.put(/reports/${id}/status, { status }); location.reload(); } finally { btn.disabled false; } }一个disabled属性就解决了重复提交的问题背后体现的是对请求幂等性的思考。答辩时随口说一句“这里考虑了用户重复操作的场景”就能让老师觉得你是有工程意识的人而不仅仅是会调接口。6.4 统计展示功能管理员首页我用 ECharts 做了两个核心图表一个是近 7 天每日上报数量柱状图一个是出入记录的进出对比图。前端从后端统计接口拿到基础数据调用 ECharts 的setOption就能渲染。统计接口的 SQL 写法是这段项目的另一个亮点它用到了 MySQL 的日期函数和分组统计SELECT report_date, COUNT(*) AS cnt FROM health_reports WHERE report_date DATE_SUB(CURDATE(), INTERVAL 6 DAY) GROUP BY report_date;这段 SQL 拿到的是过去 7 天每天的上报记录数。配合前端将结果 map 成图表需要的数据格式就能快速画出趋势图。ECharts 的柱状图代码不复杂网上的示例一抓一大把不用在这里浪费时间做创新设计。7. 本地运行、常见报错排查与部署要点7.1 完整的本地启动流程一套完整的本地跑起来步骤我整理成清单照着做就不会乱创建数据库并导入表结构在 Navicat 或命令行中执行建库建表 SQL。修改项目根目录下的.env或config.js填入数据库用户名、密码、数据库名。打开终端进入项目目录安装依赖npm install。启动服务npm run dev这是开发模式用 nodemon。浏览器访问http://localhost:3000看到登录页说明启动成功。用提前插入的管理员账号登录后台测试核心功能。整个流程里最容易出问题的就是第 1 步。有些同学把 SQL 文件导入错了库或者连接时端口写错前端数据死活加载不出来结果排查半天发现是数据库连接参数不对。建议第一步建库之后先写一个简单的测试连接脚本确认能连上数据库再往后走const mysql require(mysql2/promise); const conn await mysql.createConnection({ host: localhost, user: root, password: 123456, database: community_system }); console.log(数据库连接成功);7.2 常见报错速查表把这个项目开发过程中最高频的错误整理成一张表格遇到哪个直接对照处理报错信息产生原因解决办法Error: Cannot find module express依赖未安装成功检查是否在项目根目录执行过npm installECONNREFUSED 127.0.0.1:3306MySQL 服务没启动打开 MySQL 服务或重启数据库ER_ACCESS_DENIED_ERROR数据库密码错误核对数据库配置TokenExpiredErrortoken 过期重新登录获取新 tokenSocket timeout接口请求超时检查后端是否启动、端口是否正确Cannot set headers after they are sent to the client接口中重复发送了响应检查代码是否有多个res.json()在同一路径执行表格里每一项都是我实际遇到过、帮别人排查过的几乎覆盖了新手写这个项目时 90% 的运行错误。遇到报错不要慌先把报错信息完整复制下来搜索比盯着代码发呆有效率得多。7.3 部署时的环境变量与端口配置毕设通常只需要本地演示但如果老师要求部署到服务器有几点要注意Node.js 服务默认监听 3000 端口生产环境建议用 PM2 来守护进程避免服务意外退出后无法自动重启。数据库连接写在配置文件中服务器上需要修改对应的 host、user、password。前端页面通过 Express 静态资源托管部署时不需要单独跑 webpack只要把整个项目目录上传到服务器安装环境后跑npm start即可。npm install pm2 -g pm2 start app.js --name community-system pm2 save pm2 list部署这一步能独立完成算是给项目画上一个完整的句号。很多同学只做到“本地能跑就交差”万一老师让线上演示就抓瞎。提前部署好演示的时候直接在浏览器里打开线上地址比让老师凑到你电脑前看强十倍。8. 一组切实可行的流程建议做完这个项目我最大的体会是毕设系统的难度从来不在于代码本身多复杂而在于你能不能把需求拆清楚、把表结构设计合理、把模块解耦干净。如果你正准备动手做这个项目我的建议是按顺序走这几步第一步先把数据库表建好所有的字段、关系、索引都定了再做后端第二步后端接口按照认证、居民、上报、出入、公告、统计的顺序一个个实现每实现一个接口就用 Postman 测一个第三步页面联调时别一次性把所有页面都做完再调试做一页调一页把问题尽早暴露出来。最后分享一个小技巧答辩之前把用例数据提前准备好比如给管理员账号、给居民账号、给几条不同审核状态的上报记录、几条出入记录。演示的时候严格按照“管理员登录 → 查看统计 → 审核数据 → 居民登录 → 健康上报 → 查看记录”这条主流程走全程不卡壳比临场乱点效果好得多。这个项目的功能框架是现成的只要你愿意花时间把每个模块做扎实答辩时基本就是稳的。
返回列表