
1. 三端分离架构的选型逻辑与整体设计1.1 为什么是微信小程序、Node.js、Vue三端组合做校园类系统绕不开一个核心矛盾用户群体分散在移动端和PC端需求差异大但开发资源往往有限。我试过用一套代码适配所有端结果移动端体验拉胯PC端管理功能又施展不开。后来转向三端分离微信小程序负责学生和教师的移动端高频操作Vue构建的Web后台负责管理员和教务人员的PC端复杂业务Node.js作为中间层统一提供API并处理业务逻辑。这个组合不是拍脑袋定的而是基于三个现实考量。第一微信小程序在校园场景的渗透率极高。学生不用装App扫码或搜索就能用课表查询、成绩查看、通知接收这些高频轻量操作小程序体验足够好。第二Vue的生态成熟Element Plus或Ant Design Vue这类组件库能快速搭出管理后台教务排课、数据统计、批量导入导出这些重交互功能PC端大屏操作效率远高于手机。第三Node.js作为BFF层既能复用JavaScript技术栈降低团队学习成本又能灵活聚合后端微服务或直接操作数据库对于校园系统这种中等并发、业务逻辑中等复杂的场景性能完全够用。注意三端分离不等于三套完全独立的代码。核心业务逻辑应当下沉到Node.js层小程序和Vue端只负责展示和交互避免同一套校验规则写三遍。1.2 整体架构分层与数据流向整个系统的架构可以拆成四层客户端层、网关层、业务服务层、数据存储层。客户端层就是微信小程序和Vue Web应用网关层用Nginx做反向代理和负载均衡同时处理静态资源业务服务层是Node.js集群按业务域拆分成用户服务、课程服务、通知服务等模块数据存储层用MySQL做主库Redis做缓存和会话管理文件资源走对象存储。数据流向是这样的小程序端发起请求经过微信服务器转发到我们的NginxNginx根据路径规则转发到对应的Node.js服务实例。Node.js服务先检查Redis中是否有缓存有则直接返回没有则查询MySQL写入缓存后返回。Vue端的管理后台请求走同样的链路但多了JWT鉴权和角色权限校验环节。这种设计的好处是小程序端和Vue端共享同一套API后端只需维护一份业务逻辑前端各自按需调用。1.3 技术选型背后的取舍与踩坑预判选Node.js而不是Java或Go主要考虑团队技术栈统一。如果团队本身是前端出身Node.js的上手成本最低。但要注意Node.js不适合做CPU密集型任务比如大规模报表生成或复杂图像处理这些应当拆成独立服务用其他语言实现。我们当时把成绩单PDF生成放在Node.js里做结果并发一高就阻塞事件循环后来改成用子进程调用外部工具才解决。微信小程序端有个坑不能直接使用npm包需要用微信开发者工具构建npm而且部分Node.js原生模块不支持。我们一开始想在小程序里直接做数据加密发现crypto模块用不了最后把加密逻辑放到Node.js层小程序只传明文走HTTPS。Vue端相对自由但要注意路由懒加载和打包体积管理后台功能多了以后首屏加载超过3秒用户就会明显感知卡顿。2. 核心模块拆解与接口设计要点2.1 用户体系与多端鉴权方案校园系统的用户角色通常分学生、教师、教务管理员、系统管理员四类。微信小程序端用微信授权登录拿到code后传给Node.js层Node.js调用微信接口换取openid再关联系统内的用户ID。Vue端用账号密码登录Node.js验证后签发JWT。这里的关键是两套登录体系最终要映射到同一个用户表用unionid或手机号做关联。JWT的payload里放userId、role、expireTime不要放敏感信息。Token有效期设短一点比如2小时配合refreshToken做续期。小程序端把Token存在Storage里每次请求放在Header的Authorization字段。Vue端可以存在Vuex或Pinia里配合路由守卫做页面级权限控制。实操心得微信小程序的登录态维护有个细节wx.checkSession判断 session_key 是否过期但实际测试发现它并不完全可靠。更稳妥的做法是每次请求如果返回401就静默重新走一遍登录流程用户无感知。2.2 课程与课表模块的数据结构设计课表是校园系统里查询频率最高的功能之一。数据结构设计不好后面查询和渲染都会很痛苦。我们的课程表设计是这样的课程表存课程基本信息课程名、教师、学分、周次范围课程安排表存具体的时间地点课程ID、星期几、第几节、周次、教室。查询某学生某周课表时先查学生选课关系再关联课程安排按星期和节次排序。这里有个坑单双周和调课逻辑。如果只在课程安排表里存一个周次字段查询时要写复杂的SQL判断。我们的做法是在课程安排表里加一个week_type字段0表示每周1表示单周2表示双周查询时用简单的条件过滤。调课则单独建一张调课表记录原时间、新时间、生效周次查询时做合并。小程序端渲染课表用grid布局每个格子对应一个时间槽。Vue端管理后台用表格展示支持拖拽调课。两端的数据格式要统一Node.js层返回的课表数据是一个二维数组行是节次列是星期每个元素是课程对象或null。这样前端拿到就能直接渲染不需要再做转换。2.3 通知与消息推送的实时性处理校园通知分两种一种是系统公告所有或部分用户可见另一种是个人消息比如选课结果、成绩发布。系统公告用轮询或WebSocket推送个人消息用微信模板消息或小程序订阅消息。我们当时选了WebSocket做实时通知Node.js用ws库起一个WebSocket服务小程序端用wx.connectSocket连接。但微信小程序对WebSocket连接数有限制单个用户同时只能有一个连接而且切后台一段时间后会断开。后来改成混合方案重要通知走微信订阅消息普通通知走WebSocketWebSocket断了就降级为轮询。Vue端的管理后台用WebSocket接收实时数据更新比如在线用户数、待办事项提醒。这里要注意WebSocket服务要和HTTP服务共享会话我们用Redis存储sessionWebSocket连接建立时从URL参数或Header里取Token验证后把userId和socketId的映射存到Redis。3. 实操过程与核心环节实现3.1 Node.js服务端项目初始化与目录结构先初始化项目用npm init -y生成package.json然后安装核心依赖express或koa做Web框架mysql2做数据库驱动ioredis做Redis客户端jsonwebtoken做鉴权ws做WebSocketdotenv管理环境变量。我习惯用express生态最全中间件多。目录结构这样组织server/ src/ config/ 数据库、Redis、微信等配置 controllers/ 路由处理函数 services/ 业务逻辑 models/ 数据模型和SQL封装 middlewares/ 鉴权、日志、错误处理 routes/ 路由定义 utils/ 工具函数 app.js 入口文件 .env package.json入口文件里做几件事加载环境变量、初始化数据库连接池、初始化Redis、注册中间件、挂载路由、启动HTTP服务和WebSocket服务。数据库连接池大小根据并发量调校园系统一般设10到20就够。Redis用来存session和热点数据比如课表查询结果缓存5分钟。注意Node.js服务不要用root用户跑用pm2做进程管理配置max_memory_restart防止内存泄漏拖垮服务器。日志用winston或pino按天切割方便排查问题。3.2 微信小程序端核心页面与请求封装小程序端用原生开发或uni-app都行我们选了原生因为对微信API的兼容性最好。核心页面有登录页、首页、课表页、成绩页、通知页、个人中心。每个页面在onLoad时检查登录态未登录跳转到登录页。请求封装是关键。写一个request.js统一处理baseURL、Token注入、错误提示、loading状态。代码大概这样const request (options) { return new Promise((resolve, reject) { const token wx.getStorageSync(token); wx.request({ url: ${baseURL}${options.url}, method: options.method || GET, data: options.data || {}, header: { Authorization: token ? Bearer ${token} : , Content-Type: application/json }, success: (res) { if (res.statusCode 401) { // 重新登录逻辑 wx.removeStorageSync(token); wx.redirectTo({ url: /pages/login/login }); reject(new Error(未登录)); } else if (res.statusCode 200 res.statusCode 300) { resolve(res.data); } else { wx.showToast({ title: res.data.message || 请求失败, icon: none }); reject(new Error(res.data.message)); } }, fail: (err) { wx.showToast({ title: 网络异常, icon: none }); reject(err); } }); }); };课表页渲染用wx:for循环二维数组每个格子根据课程颜色做区分。成绩页用图表展示可以用echarts-for-weixin但要注意包体积建议按需引入。3.3 Vue管理后台的搭建与权限路由Vue端用Vue3 Vite Pinia Vue Router Element Plus。初始化项目后先配路由和权限。路由分两种静态路由登录页、404页和动态路由根据用户角色动态添加。用户登录后Node.js返回角色和权限列表前端根据权限过滤路由表用router.addRoute动态添加。权限控制到按钮级别用自定义指令v-permission传入权限码没有权限则隐藏元素。比如app.directive(permission, { mounted(el, binding) { const permissions store.getters.permissions; if (!permissions.includes(binding.value)) { el.parentNode.removeChild(el); } } });管理后台的核心页面有用户管理、课程管理、课表管理、通知管理、数据统计。数据统计页用echarts注意按需引入图表类型不然打包体积会很大。表格分页用Element Plus的el-pagination后端接口支持page和pageSize参数。实操心得Vue管理后台的接口请求用axios配拦截器统一处理Token和错误。开发环境配proxy解决跨域生产环境用Nginx反向代理。打包时用vite的build配置做代码分割把echarts、element-plus这些大库单独打包利用浏览器缓存。3.4 三端联调与接口契约管理三端联调最容易出问题的环节是接口字段不一致。我们的做法是先用Swagger或Apifox定义好接口契约包括URL、方法、请求参数、响应格式、错误码。Node.js层按契约实现小程序和Vue端按契约调用。联调时用Apifox的Mock功能前端可以先不依赖后端用Mock数据开发。联调顺序建议先调通登录和用户信息接口再调课表和成绩接口最后调通知和统计接口。每调通一个接口用Postman或Apifox保存用例方便回归测试。跨域问题在开发环境用proxy解决生产环境用Nginx配置。微信小程序端有个特殊问题开发工具里可以关闭域名校验但真机测试必须用HTTPS域名而且域名要在微信公众平台配置。我们当时用内网穿透工具做真机调试但要注意合规性正式环境必须用备案域名和HTTPS证书。4. 常见问题与排查技巧实录4.1 接口联调中的典型报错与解决联调阶段最常见的问题我整理成了一张速查表报错现象可能原因排查方法解决方案401 UnauthorizedToken过期或未传检查Header里Authorization字段重新登录或刷新Token403 Forbidden角色权限不足检查JWT里的role和接口要求的权限调整角色权限配置跨域错误开发环境未配proxy看浏览器控制台Network面板配Vite proxy或Nginx小程序请求失败域名未配置或非HTTPS看微信开发者工具控制台配置合法域名和证书数据库连接超时连接池耗尽或网络问题看Node.js日志和MySQL状态调大连接池或检查网络WebSocket频繁断开心跳未配置或超时看小程序端onClose回调加心跳包和重连逻辑其中跨域问题最容易被忽视。开发环境Vite的proxy配置大概这样// vite.config.js export default { server: { proxy: { /api: { target: http://localhost:3000, changeOrigin: true, rewrite: (path) path.replace(/^\/api/, ) } } } };4.2 性能瓶颈的定位与优化校园系统在选课和查成绩的高峰期并发量会突然飙升。我们遇到过MySQL CPU打满的情况排查后发现是课表查询没走索引。课程安排表的student_id和week字段没建联合索引导致全表扫描。加上索引后查询时间从2秒降到50毫秒。Redis缓存也要注意雪崩问题。我们给课表缓存设置了随机过期时间比如300秒加减60秒的随机值避免同一时间大量缓存失效。另外Node.js层用cluster模式启动多进程充分利用多核CPU。pm2配置instances为max自动根据CPU核数启动进程。小程序端的性能优化主要是减少setData的数据量和频率。课表页渲染时不要一次性setData整个二维数组可以分块渲染或者用wx:key优化列表更新。Vue端的优化主要是路由懒加载和组件按需引入首屏只加载必要资源。4.3 数据一致性与事务处理校园系统里选课和退课涉及多个表的更新必须用事务保证一致性。Node.js里用mysql2的connection.beginTransaction()开启事务执行多条SQL后commit出错则rollback。注意事务里的操作要尽量短避免长时间锁表。还有一个坑是并发选课。同一门课名额有限多个学生同时选如果不做并发控制会出现超选。我们的做法是在课程表里加一个selected_count字段选课时用UPDATE course SET selected_count selected_count 1 WHERE id ? AND selected_count capacity根据affectedRows判断是否选课成功。这样利用数据库的行锁保证原子性比在应用层加锁更可靠。注意微信小程序的订阅消息需要用户主动授权而且每次授权只能发一条。重要通知要提前引导用户授权或者用服务号模板消息作为补充。但服务号需要用户关注转化率是个问题实际项目中要权衡。4.4 部署上线与灰度发布策略部署用Docker Compose编排Node.js服务、MySQL、Redis、Nginx。Nginx配置反向代理和静态资源服务Vue打包后的dist目录挂到Nginx里。小程序端通过微信开发者工具上传代码提交审核后发布。灰度发布很重要。我们当时先让一个班级试用观察一周没问题再逐步放开。Node.js服务用pm2的reload做平滑重启避免服务中断。数据库变更用迁移脚本每次上线前先备份变更脚本要可回滚。监控用pm2的monitor和日志配合简单的健康检查接口。如果Node.js服务挂了Nginx会返回502我们配了告警通知到工作群。Redis和MySQL用云服务商的监控面板设置CPU和连接数告警阈值。整个项目从零到上线大概用了两个月其中联调占了一半时间。最大的体会是接口契约一定要提前定好不然后端改一个字段前端三端都要跟着改。另外微信小程序的审核周期不可控重要功能要提前预留审核时间。这个架构后续还可以扩展比如接入校园一卡通数据、图书馆座位预约、宿舍报修等模块Node.js层按业务域拆分成微服务小程序端用分包加载控制主包体积。