基于Spring Boot和微信小程序的学生公寓管理系统设计与实现
发布时间:2026/9/10 21:13:39 锦皓数字建站

最近帮好几个学弟调过这套学生公寓管理系统的毕设发现这类基于微信小程序的管理系统看着简单真正从零写起来坑还是挺多的。今天就把我整理过的完整思路、设计要点、核心代码和避坑记录一次说清楚照着这份思路走至少能省你一周的折腾时间。这个项目本质上是“Java后端 微信小程序前端”的全栈毕设背景是高校学生公寓/宿舍管理。要解决的问题很明确学生查公告、报修、缴水电费、查寝签到、查看宿舍评分这些日常操作以前要靠宿管阿姨跑腿或人工登记现在统一搬到小程序里后台再用Spring Boot管理用户、宿舍、报修单、账单这些数据。适合计算机相关专业、需要做毕业设计或者想练手全栈项目的同学参考。1. 先说结论这套系统到底在解决什么问题1.1 公寓管理的真实痛点你去任何一所高校的宿管办公室坐半天就能感受到传统的公寓管理有多琐碎。报修靠手写登记本学生说“我宿舍灯坏了”得等宿管去办公室翻记录、打电话联系维修师傅查寝靠宿管挨个敲门有的学生不在宿舍还得打电话确认水电费每个月人工抄表、算账、贴通知单学生再抽时间去线下缴费。整个过程效率低、易出错、不透明而且学生和管理员双方体验都不好。这个项目要解决的就是把这些线下流程数字化。学生端小程序负责日常操作管理员端后台负责信息维护和流程处理两端通过接口实时同步数据。这样一来报修进度可追踪查寝结果可汇总账单明细可查询所有记录都留存在数据库里出了问题也有据可查。说白了就是用一套系统把公寓管理的“信息孤岛”串起来。1.2 为什么偏偏选“Java 微信小程序”这个组合很多同学纠结技术栈选什么。我给你们拆解一下这个组合的逻辑。Java后端一般用Spring Boot框架是当前企业级应用最主流的方案之一生态成熟、资料丰富、稳定性好用来做管理系统后端可以说非常稳妥。而且对毕设来说Spring Boot能让你用最少的配置把项目跑起来省去大量繁琐的XML配置。微信小程序作为前端好处更明显。第一学生不用安装App微信里扫一扫或者搜索一下就能用使用门槛极低第二微信提供了现成的登录认证体系wx.login openid不用自己造账号系统的轮子第三小程序支持消息订阅、模板通知报修进度更新、查寝提醒这些场景都能用上。对学校这类场景来说小程序比App更轻、比H5体验更好所以这个组合在毕设里特别受欢迎。1.3 适合谁参考参考到什么程度这套内容适合三类人。第一类是马上就要交毕设、需要快速搭建一个能跑通、能答辩的完整系统的同学你可以直接照着我后面的表结构和核心代码搭第二类是Java基础一般、想通过一个实战项目把Spring Boot和微信小程序串起来的人建议跟着数据库设计、接口设计的思路走一遍比刷面试题管用得多第三类是已经有点基础、想在这个项目上做功能扩展拿高分的文末我会说几个能加分的扩展方向。需要提醒的是我做这套系统的定位是“毕设够用、演示流畅、答辩能讲清楚”不是生产级的高并发架构所以不要纠结Redis集群、分布式事务这些那些在本阶段属于过度设计。2. 系统整体架构与功能拆分2.1 前端小程序端的功能清单小程序端是学生最直接接触的入口功能设计要围绕“高频、自助、轻量”三个原则来展开。我按实际开发优先级把功能分成三批。第一批是基础必备功能微信登录获取openid自动注册、首页公告栏展示宿管发布的公寓通知、个人中心查看并修改个人资料、绑定宿舍信息。第二批是核心业务功能在线报修填写报修类型、描述、上传照片提交后生成维修单、报修进度查询查看维修单当前状态待接单、维修中、已完成、宿舍账单查看本宿舍的水电费账单明细与缴费状态、查寝签到在规定时间段内进行宿舍定位签到。第三批是加分扩展功能宿舍评分查看文明宿舍评比结果、访客登记外来人员拜访登记、意见反馈学生对公寓管理的匿名建议。2.2 后端管理端的功能清单管理端我建议做成Web管理后台技术栈用Spring Boot Thymeleaf或者前后端分离都行看你的时间安排。管理员的核心操作围绕数据维护和流程审批展开。人员管理模块负责维护学生信息、批量导入宿舍成员名单宿舍管理模块负责宿舍楼、房间、床位的增删改查以及学生入住与退宿的分配操作。流程处理模块是管理端的重头戏。报修管理要能看到所有维修单按状态筛选派单给维修人员并填写处理结果查寝管理要能查看某一栋楼、某一层、某个宿舍的实时签到情况对未签到学生发送提醒账单管理要能生成本月水电账单、查看缴纳状态、处理线下缴费确认。此外还有公告管理发布和维护公寓通知、数据统计按宿舍、按月维度统计报修量、缴费率等人气指标这部分能大大提升答辩时的展示效果。2.3 角色权限模型设计这个系统涉及三类角色普通学生、宿舍管理员/宿管、系统超级管理员。在权限设计上我建议用最简单但清晰的“角色-菜单-权限”模型不要一上来就搞Spring Security那套复杂的RBAC毕设阶段会把自己绕晕。具体做法是学生角色只能访问小程序端通过后端接口时用token校验身份拦截器里判断角色编码比如只有ROLE_STUDENT才能调用报修接口管理员角色分两级公寓管理员能操作自己负责的宿舍楼数据超级管理员拥有全部权限。实际开发中我在Spring Boot里写一个简单的AuthInterceptor拦截所有/api/**请求从token里解析出用户角色再用RequireRole注解声明接口需要的权限即可够用且好答辩。2.4 技术选型的底层逻辑后端我推荐Spring Boot 2.7.x MyBatis-Plus MySQL 8.0的组合。选Spring Boot是因为它内置Tomcat、简化依赖管理一个注解就能启动Web服务MyBatis-Plus相比原生MyBatis最大的优势是提供了BaseMapper单表CRUD不用写一行SQL这对赶毕设的人来说太重要了MySQL作为关系型数据库存储用户、宿舍、报修单这种结构化数据完全够用。前端如果没学过原生小程序开发我建议直接用uni-app框架一套代码能编译到微信小程序、H5和App。它的语法是Vue风格的如果你Vue基础还可以上手很快。当然如果你想专心搞后端直接用微信开发者工具写原生小程序也没问题我们后面章节讲的是原生写法用uni-app在逻辑上完全能对应上。3. 数据库设计与核心表结构3.1 用户表与角色表设计数据库是整个系统的地基表结构设计得好不好直接决定后面写代码省不省心。我见过很多毕设把用户表设计成“一表通吃”什么角色都往一个表里塞字段又杂又乱后面接口写着写着就崩了。我的建议是拆两表一张sys_user统一存用户的基本信息用role字段区分角色再配合一张student_info表存学生的专属扩展信息比如学号、班级、入住宿舍编号等。这样做的好处是通用字段和业务字段解耦维护方便。下面是我实际用过的核心字段版本你直接抄就行CREATE TABLE sys_user ( id BIGINT PRIMARY KEY AUTO_INCREMENT COMMENT 主键ID, openid VARCHAR(64) NOT NULL UNIQUE COMMENT 微信openid, username VARCHAR(50) COMMENT 用户名, password VARCHAR(255) COMMENT 密码管理员用BCrypt加密, nickname VARCHAR(50) COMMENT 昵称, avatar_url VARCHAR(255) COMMENT 头像, role VARCHAR(20) NOT NULL DEFAULT STUDENT COMMENT 角色STUDENT/ADMIN/SUPER_ADMIN, phone VARCHAR(20) COMMENT 手机号, status TINYINT DEFAULT 1 COMMENT 状态1启用 0禁用, create_time DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT 创建时间 ); CREATE TABLE student_info ( id BIGINT PRIMARY KEY AUTO_INCREMENT, user_id BIGINT NOT NULL COMMENT 关联sys_user.id, student_no VARCHAR(30) COMMENT 学号, real_name VARCHAR(50) COMMENT 真实姓名, college VARCHAR(100) COMMENT 学院, grade VARCHAR(20) COMMENT 年级, dorm_id BIGINT COMMENT 关联宿舍表id, bed_no VARCHAR(20) COMMENT 床位编号, UNIQUE KEY uk_user (user_id) );要注意的一个小坑是openid字段必须加唯一索引。微信登录时同一个用户每次登录拿到的code都不同但通过code换回来的openid是一样的。用唯一索引能防止重复插入用户数据。3.2 宿舍与床位分配表宿舍楼、房间、床位这三个概念要分清。宿舍楼是一栋楼房间是一个具体位置比如3号楼502床位是房间内的具体床号比如A床、B床、C床。我见过有人只建一张dorm_room表床位用字符串“A床,B床,C床”拼接存储后面对接入住分配时非常痛苦解析字符串写一堆逻辑纯属自找麻烦。正确做法是建三张表dorm_building楼栋信息、dorm_room房间信息带楼栋外键、dorm_bed床位信息带房间外键。每个床位有一个status字段标记是否被占用入住时把student_info里的bed_id指向具体床位即可。这样的设计在后面做“一键分配宿舍”“查看空床位”功能时一条SQL就能搞定。3.3 报修单状态机设计报修单是整个系统里流程最复杂的表核心是状态流转设计。我建议把状态定义清楚待接单0→ 已接单1→ 维修中2→ 已完成3→ 已取消4→ 已评价5。为什么要用数字而不是直接存中文因为数字做条件查询和统计更方便展示层再映射成中文文本即可。CREATE TABLE repair_order ( id BIGINT PRIMARY KEY AUTO_INCREMENT, order_no VARCHAR(32) COMMENT 报修单编号, student_id BIGINT NOT NULL COMMENT 报修学生id, dorm_id BIGINT COMMENT 宿舍id, repair_type VARCHAR(30) COMMENT 报修类型水电/门窗/电器/其他, description TEXT COMMENT 问题描述, images VARCHAR(1000) COMMENT 照片URL多张用逗号分隔, status TINYINT DEFAULT 0 COMMENT 状态0待接单 1已接单 2维修中 3已完成 4已取消 5已评价, assignee_id BIGINT COMMENT 维修人员id, handle_note VARCHAR(500) COMMENT 处理结果备注, score TINYINT COMMENT 学生评分1-5, create_time DATETIME DEFAULT CURRENT_TIMESTAMP, update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP );这里有个小技巧update_time字段用ON UPDATE CURRENT_TIMESTAMP每次更新行记录时自动刷新时间这样查询“最近的维修进度”时不需要手动维护时间字段。3.4 水电缴费与账单表水电费这部分关键不是表多复杂而是账单和支付记录的联动。我的设计是bill表和payment_record表。bill表每月按宿舍生成一条账单记录包含上月读数、本月读数、用量、单价、总金额、缴费截止日期、状态未缴/已缴/逾期。payment_record表记录每一笔支付操作的流水包含支付单号、用户、关联账单、支付金额、支付方式微信支付/线下、支付状态。CREATE TABLE bill ( id BIGINT PRIMARY KEY AUTO_INCREMENT, dorm_id BIGINT NOT NULL COMMENT 宿舍id, bill_month VARCHAR(7) COMMENT 账单月份格式2024-06, water_usage DECIMAL(10,2) COMMENT 用水量(吨), electricity_usage DECIMAL(10,2) COMMENT 用电量(度), total_amount DECIMAL(10,2) COMMENT 总金额(元), status TINYINT DEFAULT 0 COMMENT 0未缴 1已缴 2逾期, deadline DATETIME COMMENT 缴费截止时间, pay_time DATETIME COMMENT 实际支付时间 );生成账单时总金额 用水量 × 水单价 用电量 × 电单价。在后台管理端管理员录入本月读数系统自动计算用量和金额这一步用MyBatis-Plus的updateById就能轻松实现。3.5 ER图怎么画才能过审很多同学问ER图怎么画这里讲个核心原则ER图不是画得越复杂越好而是要能让人一眼看出实体关系并且和你写的表结构完全对得上。你画ER图时就把上面说的实体画出来用户、学生信息、宿舍楼、房间、床位、报修单、账单、支付记录、公告。连接关系写上用户与学生对“1对1”宿舍楼与房间为“1对N”房间与床位为“1对N”学生与报修单为“1对N”宿舍与账单为“1对N”。画完自己检查一遍每个实体在数据库里有没有对应表每个关系在表设计里能不能体现出来如果答案是肯定的这张ER图就站得住脚。工具用visio、draw.io或者ProcessOn都行有一个整洁清晰的图答辩时能加分不少。4. 核心接口实现与关键代码4.1 小程序登录与token鉴权登录是整个系统的入口微信小程序的登录流程比较特殊初学者经常搞错。正确流程是小程序端调用wx.login()拿到临时code然后把code传给后端后端拿着code请求微信接口https://api.weixin.qq.com/sns/jscode2session换取openid和session_key。拿到openid后去sys_user表查有没有这个用户没有就自动注册一个新账号然后生成一个token返回给小程序。这里要注意的是jscode2session接口需要在小程序后台配置appid和secret两个值都要放在后端配置里千万别写死在小程序代码中否则任何人反编译你的小程序都能偷走密钥。后端我用JWT生成token核心代码如下PostMapping(/login) public Result login(RequestBody LoginRequest req) { String url https://api.weixin.qq.com/sns/jscode2session?appid appId secret appSecret js_code req.getCode() grant_typeauthorization_code; String result restTemplate.getForObject(url, String.class); JSONObject json JSONObject.parseObject(result); String openid json.getString(openid); if (StringUtils.isBlank(openid)) { return Result.error(微信登录失败); } SysUser user userService.findByOpenid(openid); if (user null) { user new SysUser(); user.setOpenid(openid); user.setRole(STUDENT); userService.save(user); } String token JwtUtil.generateToken(user.getId(), user.getRole()); return Result.ok().put(token, token).put(userInfo, user); }前端拿到token后每次请求都要放在请求头里用Authorization: Bearer token传递。后端写一个拦截器统一检查token解析出用户ID和角色后放到ThreadLocal里方便后续接口取用。4.2 报修流程的完整链路报修流程是最容易出彩的功能因为它的状态流转最能体现后端设计能力。学生提交报修请求时除了保存表单数据还要自动生成一个唯一报修单号。我建议用时间戳加随机数的组合20240612103025 4位随机数避免并发下单号重复。报修单从创建到完成的完整链路是这样的学生提交报修单状态0→ 管理员在后台看到待接单列表点击“接单”并指派维修人员状态1→ 维修人员确认开始维修状态2→ 维修完成管理员填写处理备注状态3→ 学生查看完成状态可以进行评分和评价状态5。如果中途学生想取消且状态还没变成“维修中”可以走取消分支状态4。状态流转这块我在Service层写了一个状态校验方法public boolean changeStatus(Long orderId, Integer oldStatus, Integer newStatus, Long operatorId) { RepairOrder order repairOrderMapper.selectById(orderId); if (order null || !order.getStatus().equals(oldStatus)) { throw new BusinessException(状态已变更请刷新后重试); } order.setStatus(newStatus); // 根据不同状态补充对应字段 if (newStatus 1) { order.setAssigneeId(operatorId); } if (newStatus 3) { order.setHandleNote(note); } return repairOrderMapper.updateById(order) 0; }为什么要把“当前状态”作为参数传进来因为这样可以避免并发情况下两个人同时对同一单操作把状态改乱。比如学生和维修工同时操作可能把状态从0直接跳到3。加一个oldStatus校验如果当前状态已经不是预期值就直接报错让前端提示“请刷新后重试”。4.3 查寝签到与定位校验查寝点名功能在系统里属于亮点功能但很多人第一次做时会陷入一个误区以为要用高精度GPS去精确定位每个学生的手机。实际上宿舍查寝不需要那么精确只需要判断学生当前是否在宿舍楼范围内即可。我的做法是小程序端用wx.getLocation()获取当前经纬度把经纬度一起提交到后端后端在数据库里配置每一栋宿舍楼的中心经纬度和允许半径比如200米用Haversine公式计算学生当前位置和宿舍楼中心点的球面距离如果小于半径就判定为在宿舍楼内。private static final double EARTH_RADIUS 6371000; public static double distance(double lat1, double lon1, double lat2, double lon2) { double dLat Math.toRadians(lat2 - lat1); double dLon Math.toRadians(lon2 - lon1); double a Math.sin(dLat / 2) * Math.sin(dLat / 2) Math.cos(Math.toRadians(lat1)) * Math.cos(Math.toRadians(lat2)) * Math.sin(dLon / 2) * Math.sin(dLon / 2); double c 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a)); return EARTH_RADIUS * c; }这里有个重要提醒wx.getLocation()需要在小程序后台申请“地理位置”接口权限而且用户拒绝授权时接口会直接失败。所以小程序端要做授权失败后的引导处理提示用户去设置页打开定位权限否则这一功能在演示时很容易翻车。4.4 数据统计接口数据统计是答辩时最容易出彩的功能也是很多同学最后才做甚至不做的模块很可惜。其实实现起来很简单就是在bill、repair_order、check_in_record三张表上做聚合查询。用MyBatis-Plus的QueryWrapper或者写原生SQL都行核心是几个统计维度按月度统计报修单数量与完成率、按宿舍楼统计缴费率、按时间统计查寝签到率。public MapString, Object dashboard() { MapString, Object result new HashMap(); // 本月报修总量 Long totalRepair repairOrderMapper.selectCount( new QueryWrapperRepairOrder().apply(DATE_FORMAT(create_time, %Y-%m) DATE_FORMAT(NOW(), %Y-%m)) ); // 本月已完成报修量 Long doneRepair repairOrderMapper.selectCount( new QueryWrapperRepairOrder().eq(status, 3) .apply(DATE_FORMAT(create_time, %Y-%m) DATE_FORMAT(NOW(), %Y-%m)) ); result.put(repairTotal, totalRepair); result.put(repairDone, doneRepair); result.put(repairRate, totalRepair 0 ? 0 : doneRepair * 100.0 / totalRepair); return result; }统计接口不用写得多花哨关键是展示的形式要清楚。前端用柱状图显示近6个月报修量对比用饼图显示各类报修占比答辩时一放出来评委一眼就能看到你系统的实用价值。5. 小程序端的关键页面实现5.1 首页与公告栏小程序首页是整个系统的门面信息架构要清爽。我的首页布局是顶部是用户头像和昵称登录后显示中间是四个核心功能图标报修、账单、查寝、宿舍评分下面是公告列表。公告列表用onPullDownRefresh支持下拉刷新每次进入首页拉取最新的前五条公告。公告数据在后端接口/api/notice/list返回小程序用wx.request发起请求。这里有个高频问题真机调试时请求一直失败但开发者工具里正常。原因多半是没在小程序后台配置request合法域名。开发阶段可以在开发者工具右上角“详情-本地设置”里勾选“不校验合法域名”但上线前必须换成https的正式域名这是微信平台的规定。5.2 报修表单与图片上传报修表单是比较复杂的一个页面因为涉及图片上传。微信小程序上传图片有两个坑要提前知道第一wx.chooseMedia接口调起相册或相机后返回的是临时文件路径不能直接拿来当永久链接使用必须用wx.uploadFile上传到自己的服务器第二上传图片数量限制后端接口一般接收MultipartFile数组注意配置上传大小上限Spring Boot默认限制1MB太小了不够用我在配置里改成了10MB。服务端接收图片并保存的代码如下PostMapping(/upload) public Result upload(RequestParam(file) MultipartFile file) { if (file.isEmpty()) { return Result.error(请选择文件); } String originalFilename file.getOriginalFilename(); String suffix originalFilename.substring(originalFilename.lastIndexOf(.)); String filename UUID.randomUUID().toString().replace(-, ) suffix; String monthPath new SimpleDateFormat(yyyyMM).format(new Date()); String savePath uploadDir / monthPath / filename; File dest new File(savePath); if (!dest.getParentFile().exists()) { dest.getParentFile().mkdirs(); } file.transferTo(dest); String url /uploads/ monthPath / filename; return Result.ok().put(url, url); }这里有个经验之谈图片保存路径一定要按月份分目录否则时间一长uploads目录下文件多到没法管理运行一段时间后查问题都找不到文件在哪。5.3 我的宿舍与缴费“我的宿舍”页面要聚合展示当前用户的宿舍楼、房间、床位信息还有本月账单状态。这里需要后端提供一个聚合接口一次性返回用户信息和宿舍账单信息避免小程序端发多个请求拼数据。缴费页面我建议用微信支付v3但如实说微信支付接入流程非常繁琐光一个商户号申请就要企业资质个人主体的毕设很难跑通。所以我的建议是毕设阶段在缴费页面做一个“模拟支付”按钮点击后走一个模拟支付接口后端直接把账单状态改成“已缴”并生成一条支付流水。然后在代码里预留微信支付的对接入口答辩时话术是“实际商用可以替换为微信支付v3”。如果你想挑战完整对接网上有沙箱环境但时间成本确实不低自己权衡。6. 开发中的坑与排查思路6.1 微信登录session获取不到这个问题的典型表现是前端调用wx.login拿到了code但后端去请求微信接口时返回报错或者拿到的openid为空。排查思路从三步走第一步检查小程序appid是否与后端配置一致很多人会在微信公众平台注册了一个小程序A但后端代码里写的secret是小程序B的二者对不上就会报invalid appid第二步检查secret是否正确可以在微信公众平台的“开发-开发设置”里重置第三步检查服务器时间是否准确微信接口对时间戳有偏差容忍度服务器时间偏移超过几分钟都会导致签名验证失败。还有一个容易忽略的点如果你用的是测试号appid和secret和正式号是两套别混用。6.2 图片上传403或失败上传图片报403十有八九是权限问题。本地开发时检查uploadDir目录是否有写权限部署到服务器时检查Nginx或Tomcat对上传目录的映射是否配置正确。另外Spring Boot对上传文件大小默认限制是1MB如果图片稍微大一点就会报MaxUploadSizeExceededException需要手动在application.yml里调大限制spring: servlet: multipart: max-file-size: 10MB max-request-size: 20MB6.3 支付功能被封禁或不可用先说明一下现实情况很多人的小程序账号因为一些违规原因支付功能会被微信平台停用导致调用微信支付接口时直接报错提示“支付功能暂时无法使用”。遇到这种情况不要慌毕设答辩不会真的让你收一分钱所以最稳妥的做法是上面说的“模拟支付”方案前端页面保留缴费按钮后端接口标记为模拟支付省去微信支付那套繁琐的流程。等答辩演示时导师关心的往往是缴费记录是否生成、账单状态是否正确流转而不是真的看你调了微信支付接口。6.4 小程序请求后台失败常见原因排查表我整理了一个排查表你自己对照逐步查比盲改代码高效得多现象可能原因解决方法开发者工具请求失败真机却正常开发者工具勾选了“校验合法域名”本地设置里临时关闭校验真机请求失败开发者工具正常未配置request合法域名小程序后台添加https域名白名单请求返回404后端接口路径或方法不匹配检查Controller层RequestMapping路径请求返回500后端代码运行时异常查看后端日志定位具体异常栈请求返回401token缺失或已过期检查前端请求头是否带token后端检查JWT过期时间数据库中文乱码数据库连接URL未设置编码在jdbc url后加?useUnicodetruecharacterEncodingutf8日期字段少8小时服务器时区不是东八区数据库连接url加serverTimezoneAsia/Shanghai并检查MySQL全局时区这里特意说下日期乱码和时区问题。很多同学数据库表和Java实体都用DATETIME和LocalDateTime但因为MySQL连接串没指定时区或者服务器是UTC时间展示出来的时间始终比北京时间早8小时。排查时看数据库原始数据是否正常如果正常问题就在应用层的时区配置上。聊到这儿我再分享一个我实际开发中体会很深的小技巧这套系统开发时先用一个固定的测试账号去测所有流程把报修从提交到完成、账单从生成到缴费这两条主链路先跑通再去开发边角功能。原因很简单主链路涉及的表和接口最多把这些打通了项目整体框架就立住了后面加功能都是在框架上做加法心里完全不慌。而且答辩时导师问你“这个流程怎么走”你可以完整地把数据从学生端一直追到数据库这个过程本身就很有说服力。
锦
锦皓数字建站
深耕本土企业品牌数字化升级,专注原创端正雅致商务官网,从视觉设计到稳定运维全程保驾护航。