ARTICLE DETAIL

资讯详情

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

基于Node.js+Express+Vue的校园流浪动物救助平台开发实战

基于Node.js+Express+Vue的校园流浪动物救助平台开发实战 上个月把做好的校园流浪动物救助平台收尾时我翻回三周前的代码心里还挺有成就感。这个项目的技术栈很明确——Node.js 做运行时Express 写服务端接口Vue 负责前端页面我把它做成了一套前后端分离的完整系统。功能上覆盖了用户注册登录、流浪动物档案、领养申请与审核、救助上报、后台管理等一条龙流程。最近在整理项目文档想着干脆把整个设计思路和实现过程都写下来既能帮自己把所有踩过的坑都沉淀一遍也能让准备做类似系统的同学少走弯路。这篇文章我尽量说人话不放那种浮夸的架构图直接讲我实际敲过的代码、验证过的方案以及最容易让人卡住的 Node.js 安装和环境配置问题。如果你正在学 Vue 和 Express或者想给学校社团搞一套能用的管理系统这篇应该对你挺有价值。1. 项目整体设计与技术选型思路1.1 为什么选这套技术栈而不是别的先说说技术选型这件事。校园流浪动物救助平台本质上是一个典型的管理信息系统核心诉求是“多角色协同”普通学生要浏览动物信息、提交领养申请管理员要审核信息、维护数据。这种项目用传统 Java 全家桶当然也能做但我的选择逻辑很简单——开发效率优先学习成本可控。Node.js 的优势在于 JavaScript 全栈打通。前端写 Vue后端写 Express语言统一上下文切换几乎没有损耗。尤其是文件上传、图片处理、JSON 解析这些 Web 开发里的高频操作Node.js 的异步模型和丰富生态能让代码量直接砍一半。Express 又是 Node.js 生态里最经典、最稳定的轻量框架本身不强制约束目录结构和设计模式适合快速搭建 RESTful API也非常适合用来理解“中间件”这套机制。Vue 这边选它主要看中的是组件化开发和渐进式引入。项目一开始可能只是一个流浪动物列表页后面慢慢加了领养申请、后台管理、个人中心Vue 的组件体系让页面拆得非常清晰。而且相比 React 的 JSX 语法Vue 模板语法对后端转前端的开发者友好得多学习曲线更平缓。这里不是说别的技术栈不行而是对于“一个学生团队三周之内要出一个能演示、能上线、可维护的项目”这个目标来说Node.js Vue Express 是性价比很高的组合。另外这套组合对部署环境的要求极低。一个 Node.js 进程加一个 MySQL 数据库就能跑起来没有复杂的容器编排和服务器配置。校园场景下能找到一台普通 Windows 或 Linux 机器就已经很好了不需要考虑微服务、消息队列这类偏重的基建。1.2 平台功能模块怎么拆分任何一个实际项目动手写代码前最忌惮的就是“功能边界不清晰”。我第一版就直接把系统拆成了两个端、三条角色路径。前端用户端主要角色是普通同学。他们要完成的操作有查看全部流浪动物档案按猫、狗、是否绝育等条件筛选点进详情页看动物照片、救助故事、健康状况看到喜欢的动物可以提交领养申请留下自己的联系方式和宿舍信息遇到受伤或需要帮助的流浪动物填写发现上报表单附上照片和位置描述在个人中心查看自己提交过的领养记录和上报记录。后台管理端主要角色是管理员。需要做的事情包括新增、编辑、下架动物档案审核用户的领养申请通过或驳回都要填意见浏览所有救助上报记录标记处理状态管理注册用户列表发站内公告。这个拆法让我明确了什么该做、什么不该做。像“志愿者排班”“物资库存”这些听起来很酷但在第一版没法落地的功能我直接砍掉。MVP 阶段核心就是把“信息登记-展示-申请审核”这条闭环跑通。功能不在多闭环完整才是关键。1.3 数据库表结构怎么设计数据模型这块我规划了五张核心表用户表、动物档案表、领养申请表、发现上报表、公告表。用户表的核心字段有 id、用户名、密码用 bcrypt 加密存储、昵称、邮箱、手机号、宿舍信息、角色标识0为普通用户1为管理员、注册时间。密码一定不能明文存储这个我在第一版开发时就踩过坑后来老老实实加上了加密中间件。动物档案表是比较核心的一张表字段包括 id、动物名称、种类猫/狗、性别、年龄段、毛色、是否绝育、是否已领养、健康状况描述、救助故事、照片路径、发现位置、入档时间、状态。这里我用了一个status字段来做逻辑删除避免直接把档案从库里抹掉后续想恢复数据很方便。领养申请表要关联用户和动物两张表字段有 id、用户 id、动物 id、申请理由、联系方式、领养状态待审核/已通过/已驳回、管理员备注、申请时间。上报表则记录上报人、动物描述、照片、位置、处理状态。设计表结构时我养成了两个习惯所有表都加created_at和updated_at时间字段方便后续排错和统计关联字段名统一用单数下划线格式比如animal_id、user_id避免后端写查询时靠猜。2. 后端 Express 服务端搭建实战2.1 初始化项目与依赖安装后端我单独建了一个server目录用npm init -y初始化。依赖包没有一股脑全装而是分了三批。核心依赖是express负责路由和中间件mysql2负责连接 MySQL 数据库sequelize负责 ORM 操作写查询的时候不用自己拼 SQL 字符串既安全又省事cors解决前后端分离开发时的跨域问题bcryptjs做密码加密jsonwebtoken签发登录令牌multer处理图片上传。开发依赖里我装了nodemon改完代码自动重启服务不用手动反复中断再启动。cd server npm init -y npm install express mysql2 sequelize cors bcryptjs jsonwebtoken multer npm install -D nodemon装完之后在package.json里加一个 scripts 配置scripts: { start: node app.js, dev: nodemon app.js }这里有个实用的建议npm install的时候建议把package-lock.json一并提交到版本库里。团队成员安装依赖时能保证所有依赖版本和开发环境完全一致避免因为某个小版本升级导致接口行为发生变化。2.2 Express 项目结构划分Express 最忌讳把所有路由都堆在一个app.js里。我一开始犯过这个毛病后来项目到第二个星期就痛不欲生老老实实拆成模块化目录。server/ ├── app.js # 入口文件 ├── config/ │ └── db.js # 数据库连接配置 ├── models/ # Sequelize 模型定义 ├── middlewares/ │ ├── auth.js # 登录校验中间件 │ └── upload.js # 图片上传中间件 ├── controllers/ # 业务逻辑控制层 └── routes/ # 路由定义层app.js只做三件事加载中间件、挂载路由、启动监听。业务逻辑全部放到controllers里路由文件只负责 URL 和方法映射。举个例子用户相关的路由长这样// routes/user.js const express require(express); const router express.Router(); const userController require(../controllers/userController); router.post(/register, userController.register); router.post(/login, userController.login); router.get(/profile, authenticateToken, userController.getProfile); module.exports router;然后在入口文件中通过/api/user挂载。这样接口 URL 清晰扩展起来也方便。2.3 核心接口实现思路我梳理了后端接口清单前期把接口定义写在一张表里前后端联调时会省很多事。方法接口路径功能说明是否需登录POST/api/user/register用户注册否POST/api/user/login用户登录否GET/api/animal/list动物档案分页列表否GET/api/animal/detail/:id动物档案详情否POST/api/animal/create新增动物档案管理员PUT/api/animal/update/:id更新动物档案管理员DELETE/api/animal/delete/:id下架动物档案管理员POST/api/adoption/apply提交领养申请是GET/api/adoption/my查看我的领养记录是PUT/api/adoption/review审核领养申请管理员POST/api/report/create提交发现上报是GET/api/report/list上报记录列表管理员POST/api/notice/create发布公告管理员GET/api/notice/list公告列表否关键点就是登录校验中间件。我写了一个authenticateToken中间件统一解析前端每次请求时放在请求头里的Authorization: Bearer token然后通过jsonwebtoken.verify校验身份再把用户信息挂到req.user上后续控制器就能直接用了。管理员接口再叠加一个角色判断。领养申请是比较典型的业务接口处理逻辑我写成了这样先检查动物是否存在且未被领养再检查用户是否已经申请过这只动物两个条件都通过才插入领养申请记录同时把动物状态改为“审核中”。这个流程如果不提前想清楚很容易出现“一只猫被五个人同时申请通过”的尴尬局面。2.4 图片上传与静态资源处理流浪动物救助平台对图片的需求量很大——动物档案要有照片上报记录也要有照片。我在后端用了multer做图片上传存储路径设置为uploads/animal/目录文件名通过时间戳加随机数生成避免中文名和重名导致的路径问题。// middlewares/upload.js const multer require(multer); const path require(path); const fs require(fs); const storage multer.diskStorage({ destination: function (req, file, cb) { const dir path.join(__dirname, ../uploads/animal); if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true }); cb(null, dir); }, filename: function (req, file, cb) { const ext path.extname(file.originalname); cb(null, Date.now() - Math.round(Math.random() * 1e9) ext); } }); const upload multer({ storage, limits: { fileSize: 5 * 1024 * 1024 }, fileFilter: function (req, file, cb) { const allow [.jpg, .jpeg, .png, .gif, .webp]; const ext path.extname(file.originalname).toLowerCase(); if (allow.includes(ext)) cb(null, true); else cb(new Error(仅支持图片文件)); } }); module.exports upload;这里我设置了 5MB 的图片大小上限同时限制了文件类型。第一次写的时候没有做限制结果有人传了个视频文件进来页面直接卡死。app.js里把上传目录用express.static暴露出去前端访问/uploads/animal/xxx.jpg就能直接拿到图片。需要注意的是生产环境部署时不要把uploads目录放在代码文件夹中更推荐使用对象存储服务来接管图片资源。但校园项目、社团演示这种量级本地目录存储完全够用后面如果访问量上来了再迁移也不难。3. Vue 前端页面与交互开发3.1 路由规划与页面层级前端我初始化了一个 Vue 项目用的是 Vue Router 来做路由管理。页面结构上我划分了三个层级公共页面、用户页面、管理页面。公共页面包括首页、动物列表、动物详情、公告列表、登录和注册。用户页面需要登录后访问包括个人中心、我的领养记录、发现上报。管理页面则加了路由守卫只有管理员角色才能进入包括后台概览、动物管理、领养审核、上报处理、用户管理。路由配置里我重点做了两件事路由懒加载和路由守卫。懒加载通过() import()实现首屏只加载当前页面需要的组件不一次性把整个后台代码都拉下来。路由守卫则统一处理登录状态和权限校验// router/index.js router.beforeEach((to, from, next) { const token localStorage.getItem(token); if (to.meta.requiresAuth !token) { next({ path: /login, query: { redirect: to.fullPath } }); } else if (to.meta.requiresAdmin localStorage.getItem(role) ! 1) { next({ path: / }); } else { next(); } });这个守卫逻辑不复杂但能避免未登录用户直接通过 URL 访问后台管理页面。校园项目里大家基本是内部使用我给管理端设置了一个比较基础但有效的权限隔离方案。3.2 核心页面实现技巧流浪动物列表是最核心的页面。我把列表分成两个组件一个AnimalCard组件用来展示卡片一个AnimalList页面用来拉数据、处理分页和筛选。卡片上展示照片、动物名、种类、是否绝育等摘要信息点击卡片跳转到详情页。动物详情页比较关键我用了vue-router的动态路由参数来传递动物 id{ path: /animal/:id, name: AnimalDetail, component: () import(../views/AnimalDetail.vue) }页面组件里通过this.$route.params.id拿到当前动物 id再调用接口拉详情。这里有个细节如果用户从列表页点进来时数据已经拿到了是否可以直接通过props传数据避免二次请求我的取舍是列表页只传递 id详情页重新拉完整数据。原因很简单——列表接口返回的字段是摘要详情页还要展示救助故事、更多照片数据本身就不完整重新请求是更稳妥的做法。领养申请这个交互我用了一个弹窗组件。用户点击“申请领养”按钮后弹出表单让用户填写申请理由和联系方式提交后前端立即将按钮置为不可用状态并显示“审核中”。这个细节很重要否则用户连续点击提交后端可能会收到多个重复申请。Vue 的响应式特点在表单处理上表现得很舒服用v-model绑定表单数据不需要手动操作 DOM 同步值。但如果表单字段特别多我用的是reactive包裹一个对象而不是散落的多个ref逻辑上更清楚。3.3 Axios 封装与前后端联调前端和后端的请求我用 Axios 封装了一个统一实例配置了基础 URL 和拦截器。// utils/request.js import axios from axios; const request axios.create({ baseURL: /api, timeout: 10000 }); request.interceptors.request.use(config { const token localStorage.getItem(token); if (token) config.headers.Authorization Bearer token; return config; }); request.interceptors.response.use( res { if (res.data.code ! 0) { // 后端返回错误码时统一提示 console.error(res.data.message); return Promise.reject(new Error(res.data.message)); } return res.data.data; }, err { if (err.response err.response.status 401) { localStorage.removeItem(token); window.location.href /login; } return Promise.reject(err); } ); export default request;这样做的好处非常明显所有接口调用都不需要重复写 token 注入逻辑后端返回的数据结构统一处理401 未认证时自动踢回登录页。前端页面里调用接口就会变得非常干净。开发环境下跨域问题我直接在 Vue 项目的vue.config.js里配置了代理把/api转发到后端的localhost:3000避免一套代码在开发环境用cors放行、生产环境又要改 URL 的麻烦。4. 环境配置高频坑与排查实录4.1 Node.js 安装与环境变量配置这个项目开发中遇到最多的问题反而是最基础的环境配置。Node.js 的下载安装本身很简单但很多同学安装完之后发现node -v能用却不知道npm -v报错或者全局安装的包找不到。这多半是环境变量没配好。Windows 环境下Node.js 安装完成后会自动把 Node 目录加入用户 PATH基本上不用手动配置。但如果用的是免安装的压缩包版本就必须要手动配置环境变量把解压后的目录路径添加到系统变量 PATH 里同时新增一个NODE_PATH环境变量指向node_modules全局模块目录。配置完之后必须重新打开命令行窗口新环境变量才会生效。这一句非常关键很多人配完环境变量发现没生效其实是终端没重启。mac 环境下如果用的是 nvm 管理 Node 版本则不太需要手动配置 PATH。但要注意如果你用brew install node系统可能默认装的是较新的版本需要确认它和你项目依赖的版本是否兼容。4.2 npm 脚本执行权限问题开发过程中另一个高频报错是npm : 无法加载文件 ...npm.ps1因为在此系统上禁止运行脚本这个错误几乎每个 Windows 用户都会遇到。原因很简单PowerShell 的默认执行策略是 Restricted不允许任何 .ps1 脚本运行npm 执行的正是 PowerShell 脚本。解决方案也不复杂打开 PowerShell以管理员身份运行执行下面的命令Set-ExecutionPolicy RemoteSigned选择Y确认即可。这条策略表示本地产的脚本可以运行从网络下载的脚本必须要有可信签名才能运行比完全放开Unrestricted要安全一些。如果公司电脑或者校园实验室电脑有强制策略无法修改也可以用cmd代替 PowerShell 来执行 npm 命令或者用npx.cmd这样的命令显式调用但这属于临时方案。我是建议新手直接改执行策略一次性解决问题。4.3 依赖版本与启动顺序的坑依赖版本不匹配也是个大坑。我记得最初把 Vue 2 的项目模板直接升级成 Vue 3结果大量组件 API 失效卡了一整天。后来我学聪明了单页面应用开发如果是从零开始学建议直接用 Vue 3 Vite 的组合而如果是参考网上很多 Vue 2 的教程那就要认准 Vue 2不要混用。Express 本身的版本兼容性很好但 Sequelize 对 Node 版本有要求。如果 Node 版本过旧安装 Sequelize 时会直接报错。这时候不要慌看错误提示里要求的engines字段把 Node 升级到指定版本以上即可。关于启动顺序后端服务和前端开发服务器要先启动后端再启动前端否则前端接口请求会一直失败而且报错信息还容易让人误以为跨域配置错了。数据库也要提前启动确保 Sequelize 能正常连接。检查顺序我非常推荐先确认 MySQL 是否启动—再确认 Node 服务是否监听—最后看前端代理是否配置正确这个排查顺序能省掉大量时间。5. 实操复盘与扩展建议5.1 我在开发中踩过的几个坑第一个坑是图片路径存储的问题。我一开始把图片相对路径存成uploads/animal/xxx.jpg后端返回给前端时没有处理前缀前端访问图片时拼 URL 拼得很痛苦。后来统一在接口返回时把相对路径自动拼接成完整的http://localhost:3000/uploads/animal/xxx.jpg前端拿到就能直接用。处理这种“数据展示层”的问题前后端约定一种统一的路径格式非常重要。第二个坑是时间格式。MySQL 返回的时间是标准格式前端如果直接渲染会带一个T和时区信息看起来非常丑。我用 dayjs 做了一个全局时间格式化过滤器所有展示时间的地方统一传原始时间戳由前端负责格式化。这个小改动看起来不起眼但整个界面质感提升明显。第三个坑是删除操作的误伤。我第一版做个“删除动物档案”的功能直接执行了物理删除。后来发现管理员误删了一条记录后没有任何恢复途径我赶紧把物理删除改成了逻辑删除。这个教训让我记住了一件事带审核流程的业务系统删除操作要慎用物理删除尽量留有余地。5.2 后续可以往哪些方向扩展这个项目做到能演示、能上线的程度后还有很多扩展空间。首先是移动端适配现在很多同学是手机访问校园网站后续可以考虑做一个响应式适配甚至封装成一个小程序端通过接口复用的方式共享后端服务。其次是消息通知当管理员审核完领养申请后可以通过站内消息或邮件通知用户审核结果这比用户自己反复刷新状态要人性得多。再就是地图定位流浪动物的发现位置可以接入地图 API展示在学校地图或周边区域上方便志愿者精准找到动物。如果把这个项目放在更广的视野看它还可以接入志愿者排班系统管理喂猫、送医、清洁等志愿活动形成一个更完整的流浪动物治理平台。每年的毕业生去向模块、社团活动数据统计也都是可以锦上添花的功能。5.3 给想上手类似项目的你一些建议如果你也想用这套技术栈做一个类似的项目我的建议很直接先画原型图再写接口最后做页面。我第一周基本没写代码全在画原型和维护数据表动手之后才发现前面的规划让开发阶段的效率翻倍。还有一点代码规范从第一天就要定好变量命名、组件命名、目录结构前后端和各成员之间保持一致合作时能省下大量沟通成本。接手这种校园项目最大的成就感往往不是“系统做得多漂亮”而是真的有人通过这个平台领养到了动物、发现了需要救助的小家伙。我在项目上线后收到了好几条来自同学的使用反馈也看到有猫通过平台找到了领养人这种时候会觉得当初熬的夜都值了。技术这东西说到底还是要落到真实的问题上才有意义。
返回列表