微信小程序宿舍管理系统实战:云函数+云数据库全栈开发
简介这份资源是面向高校宿管人员与学生、以及微信小程序开发初学者的宿舍管理系统完整项目源码基于微信开发者工具构建采用双端登录设计解决传统宿舍管理中签到、卫生评分、留言沟通与晚归申请等事务效率低下的问题。压缩包共2000个文件约8.4MB以769个js与393个ts脚本承载核心业务逻辑258个json负责配置236个md提供说明文档另有wxml、wxss构建页面样式并包含proto、schema等接口定义文件整体结构完整、便于二次开发。目前已有1956人学习下载。项目涵盖GPS定位签到、宿管端卫生评分与留言回复、学生端晚归申请等功能模块并预留MYSQL数据库接口用于密码校验与数据存储读者可据此掌握小程序双角色权限设计、前后端数据交互与数据库对接思路适合作为课程设计或毕业设计的参考方案。1. 宿舍管理系统为什么值得用微信小程序重做一遍很多高校的宿舍管理还停留在 Excel 加微信群通知的阶段宿管阿姨在群里发一条「今晚查寝」学生回「收到」数据落在聊天记录里月底统计时再一条条翻。这套流程在 200 人以内还能凑合一旦超过 500 人漏报、错报、查不到历史记录就成了常态。基于微信小程序的宿舍管理系统本质上是把「查寝签到、报修登记、晚归请假、水电费公示」这几件高频小事从聊天记录里搬到有结构、可查询、能导出报表的数据库里。它适合三类人一是想做一个能写进简历、又能真实跑起来的全栈练手项目的学生二是被纸质登记表折磨、想自己动手改善流程的宿管或辅导员三是需要一套轻量级 CRUD 加权限控制范例的开发者。微信小程序的优势在于免安装、扫码即用、天然带用户身份openid省掉了注册登录这一大块开发量。下面我从数据建模讲到接口实现再到部署上线把这条链路完整走一遍。2. 先定角色和数据表别急着写页面2.1 三种角色与权限边界宿舍管理系统的角色划分直接决定表结构和接口鉴权方式。常见做法是分三种学生、宿管楼长、管理员。学生只能看自己的信息、提交报修和请假宿管能看本楼栋所有学生的签到状态、审批请假管理员管楼栋、房间、账号的增删改查。权限不能只在前端判断否则改个请求参数就能越权。我的做法是在数据库的用户表里存一个role字段后端每个接口先查当前 openid 对应的角色再决定是否放行。下面这张表是我一般会用的角色权限对照角色role 值可访问接口数据范围学生student签到、报修、请假、查水电仅本人宿管manager查本楼签到、审批请假、处理报修本楼栋管理员admin楼栋/房间/账号管理、导出报表全部2.2 核心数据表设计与建表语句表设计是整个系统的地基改表比改代码痛苦得多。我踩过的坑是一开始把「房间号」直接存成字符串放在学生表里后来要按楼栋统计时发现没法聚合。正确做法是把楼栋、房间拆成独立表学生表只存外键。-- 楼栋表 CREATE TABLE building ( id INT PRIMARY KEY AUTO_INCREMENT, name VARCHAR(32) NOT NULL COMMENT 楼栋名称如1号楼, manager_openid VARCHAR(64) COMMENT 该楼宿管的openid ); -- 房间表 CREATE TABLE room ( id INT PRIMARY KEY AUTO_INCREMENT, building_id INT NOT NULL, room_no VARCHAR(16) NOT NULL COMMENT 房间号如302, capacity TINYINT DEFAULT 4 COMMENT 床位容量, INDEX idx_building (building_id) ); -- 用户表 CREATE TABLE user ( id INT PRIMARY KEY AUTO_INCREMENT, openid VARCHAR(64) UNIQUE NOT NULL, name VARCHAR(32), role ENUM(student,manager,admin) DEFAULT student, room_id INT, INDEX idx_room (room_id) ); -- 签到记录表 CREATE TABLE checkin ( id INT PRIMARY KEY AUTO_INCREMENT, user_id INT NOT NULL, check_date DATE NOT NULL, status ENUM(normal,late,absent) DEFAULT normal, created_at DATETIME DEFAULT CURRENT_TIMESTAMP, UNIQUE KEY uk_user_date (user_id, check_date) );逻辑说明checkin表用(user_id, check_date)做唯一索引这样同一天重复签到会被数据库直接拦掉不用在代码里写一堆判断。status用枚举而不是数字查询时一眼能看懂。参数上capacity用 TINYINT 够用一个房间不会超过 127 个床位。注意openid 长度不固定别用 CHAR(28) 这种定长用 VARCHAR(64) 留足余量。2.3 为什么不用云开发的默认权限微信云开发提供了「仅创建者可读写」这类默认权限看起来省事但宿舍管理是典型的多角色交叉访问宿管要读全楼学生的签到学生只能读自己的。默认权限满足不了这种需求最后还是得在云函数里自己写鉴权。所以我一般直接用云数据库加云函数把权限逻辑收在云函数里前端只负责展示。3. 用云函数把签到和报修接口跑通3.1 初始化项目与目录结构先在微信开发者工具里新建一个「云开发」项目开通云环境后会得到一个环境 ID。目录我习惯这样分miniprogram/ pages/ checkin/ 签到页 repair/ 报修页 leave/ 请假页 utils/ request.js 统一请求封装 cloudfunctions/ login/ 获取openid checkin/ 签到逻辑 repair/ 报修逻辑request.js里统一封装wx.cloud.callFunction把错误码和 loading 处理掉页面里就不用每次都写 try-catch。3.2 获取 openid 并写入用户表小程序端拿不到 openid必须通过云函数。下面这个 login 云函数是整套系统的入口// cloudfunctions/login/index.js const cloud require(wx-server-sdk) cloud.init({ env: cloud.DYNAMIC_CURRENT_ENV }) const db cloud.database() exports.main async (event) { // 从调用上下文里拿 openid不信任前端传参 const { OPENID } cloud.getWXContext() const users db.collection(user) const exist await users.where({ openid: OPENID }).get() if (exist.data.length 0) { // 首次进入建一条学生记录room_id 待管理员分配 await users.add({ data: { openid: OPENID, role: student, room_id: null, name: } }) return { openid: OPENID, role: student, isNew: true } } return { openid: OPENID, role: exist.data[0].role, isNew: false } }逻辑说明cloud.getWXContext()返回的 OPENID 由微信服务端注入前端伪造不了这是整个鉴权链的信任根。参数上cloud.DYNAMIC_CURRENT_ENV让云函数自动使用当前环境不用硬编码环境 ID。返回isNew是为了让前端知道要不要跳转到「完善信息」页。3.3 签到接口与防重复提交签到接口要处理三件事判断今天是否已签到、判断是否晚归、写入记录。// cloudfunctions/checkin/index.js const cloud require(wx-server-sdk) cloud.init({ env: cloud.DYNAMIC_CURRENT_ENV }) const db cloud.database() const _ db.command exports.main async (event) { const { OPENID } cloud.getWXContext() const today new Date().toISOString().slice(0, 10) // YYYY-MM-DD // 晚归判定22:30 之后算晚归 const now new Date() const isLate now.getHours() 22 || (now.getHours() 22 now.getMinutes() 30) const user await db.collection(user).where({ openid: OPENID }).get() if (!user.data.length) return { code: 401, msg: 用户不存在 } const userId user.data[0]._id const exist await db.collection(checkin) .where({ user_id: userId, check_date: today }).get() if (exist.data.length) return { code: 409, msg: 今日已签到 } await db.collection(checkin).add({ data: { user_id: userId, check_date: today, status: isLate ? late : normal, created_at: db.serverDate() } }) return { code: 0, msg: 签到成功, status: isLate ? late : normal } }逻辑说明db.serverDate()用服务端时间避免学生改手机时间作弊。晚归阈值 22:30 写死在代码里实际项目建议放到配置表方便不同楼栋设不同时间。返回码用 0 表示成功、409 表示冲突前端根据 code 弹不同提示。参数上check_date存成字符串YYYY-MM-DD而不是 Date 对象因为云数据库对 Date 的时区处理容易出玄学问题存字符串反而稳定。3.4 报修接口与图片上传报修比签到多一个图片上传环节。小程序端先用wx.chooseMedia选图再wx.cloud.uploadFile传到云存储拿到 fileID 后连同文字描述一起提交。// miniprogram/pages/repair/repair.js Page({ data: { desc: , fileID: }, chooseImg() { wx.chooseMedia({ count: 1, mediaType: [image], success: async (res) { const tempPath res.tempFiles[0].tempFilePath const upload await wx.cloud.uploadFile({ cloudPath: repair/${Date.now()}.jpg, filePath: tempPath }) this.setData({ fileID: upload.fileID }) } }) }, async submit() { if (!this.data.desc) return wx.showToast({ title: 请填写描述, icon: none }) const res await wx.cloud.callFunction({ name: repair, data: { desc: this.data.desc, fileID: this.data.fileID } }) if (res.result.code 0) { wx.showToast({ title: 提交成功 }) } } })逻辑说明cloudPath用时间戳命名避免同名覆盖。fileID 是云存储的唯一标识存进数据库后前端用wx.cloud.getTempFileURL换成临时链接展示。参数上count: 1限制一次一张如果要做多图改成数组循环上传即可。4. 避坑与排查那些让我返工三次的问题4.1 云函数超时导致签到失败现象晚高峰签到部分学生提示「签到失败」云函数日志显示 timeout。原因默认云函数超时是 3 秒签到接口里串行查了用户表又查签到表两次数据库往返加上冷启动偶尔超过 3 秒。解决把两次查询改成一次聚合或者在config.json里把超时调到 10 秒。我一般两个都做查询用db.collection(user).aggregate()一次拿到用户和签到状态。4.2 openid 在前端被当成普通参数传递现象有人把 openid 存在本地缓存请求时当参数传给云函数结果换个账号改缓存就能看别人数据。原因前端传的 openid 完全可伪造。解决所有云函数一律用cloud.getWXContext().OPENID永远不读event.openid。这条是红线我在 code review 里见到一次打回一次。4.3 日期时区导致签到记录错位现象学生凌晨 0 点签到记录却算到前一天。原因云函数运行环境是 UTC 时区new Date().toISOString()拿到的是 UTC 日期比北京时间晚 8 小时。解决手动加 8 小时偏移或者用new Date(Date.now() 8 * 3600 * 1000).toISOString().slice(0, 10)。这个坑很隐蔽测试时白天测不出来只有跨零点才暴露。4.4 数据库权限设成「所有人可读」现象上线后发现有人能直接读全表数据。原因云数据库默认权限没改还是「所有用户可读」。解决进云开发控制台把每张表的权限改成「仅管理端可读写」所有读写都走云函数。前端直连数据库虽然方便但权限粒度太粗不适合这种多角色系统。4.5 图片上传没限制大小导致流量超标现象月底收到云存储流量告警。原因学生上传原图一张 5MB几百人一天传几十张。解决wx.chooseMedia里加sizeType: [compressed]并在云函数里校验 fileID 对应的文件大小超过 2MB 直接拒绝。压缩后一般 200KB 以内流量降一个数量级。5. 把系统真正跑起来部署、验证与一个提效技巧5.1 上线前的三步验证功能写完不等于能用我一般按这三步验证第一步用两个不同微信号分别登录确认学生 A 看不到学生 B 的签到记录。这一步验证的是鉴权最容易出问题。第二步把手机时间改到 23:00签到一次确认状态是late而不是normal。这一步验证的是晚归判定逻辑。第三步在云开发控制台手动删掉一条签到记录再让该学生签到确认能成功。这一步验证的是唯一索引和重复提交判断是否一致。5.2 用定时触发器自动生成未签到名单查寝最费时间的不是签到本身而是事后统计谁没签。云开发支持定时触发器可以每天 23:00 自动跑一个云函数把当天没签到的人拉出来推送给宿管。// cloudfunctions/dailyReport/index.js const cloud require(wx-server-sdk) cloud.init({ env: cloud.DYNAMIC_CURRENT_ENV }) const db cloud.database() exports.main async () { const today new Date(Date.now() 8 * 3600 * 1000) .toISOString().slice(0, 10) // 拉全部学生 const students await db.collection(user) .where({ role: student }).get() // 拉今日已签到 const checked await db.collection(checkin) .where({ check_date: today }).get() const checkedIds new Set(checked.data.map(i i.user_id)) const absent students.data.filter(s !checkedIds.has(s._id)) // 按楼栋分组后写入报表集合宿管端读取 await db.collection(daily_report).add({ data: { date: today, absent: absent.map(s s._id), count: absent.length } }) return { date: today, absentCount: absent.length } }逻辑说明Set用来做 O(1) 查找比数组includes快。参数上定时触发器在config.json里配置 cron 表达式0 0 23 * * * *表示每天 23:00 触发。注意云函数的 cron 用的是 UTC 时间要减 8 小时所以实际写0 0 15 * * * *。5.3 一个让我少写一半代码的习惯做完这个系统我最大的体会是别急着写页面。先把角色、表结构、接口返回码定死写成一份简单的接口文档哪怕只有自己看。我前两次返工都是因为表结构没想清楚就动手写到一半发现要加字段前端跟着全改。后来我养成习惯任何 CRUD 项目先花半天画表和定接口后面写代码基本是填空。这个系统从建表到上线真正写业务代码只用了两个晚上大部分时间花在验证权限和时区这些边界上。希望帮到你。本文还有配套的精品资源点击获取