1. 为什么我选择做校园网综合服务系统,而不是又做一个"选课助手"
每年的毕业设计季,我都能看到大量同质化的校园系统题目——基本都是"校园二手交易平台""课程表小程序""失物招领系统"这一类。不是说这些题目不好,而是它们普遍存在一个问题:只覆盖了校园生活的某一个切片,做完之后很难真正用起来,答辩时也缺乏纵深可讲。这次我做的"基于微信小程序的校园网综合服务系统",本质上是一个把多个高频校园场景统一收口的微校园平台,核心目标不是炫技术,而是让"登录一次,办N件事"这件事在校园网环境下真正跑通。
先说说这个系统到底能做什么。学生端进入小程序后,可以完成校园资讯浏览、课表查询、成绩查询、校园卡充值流水查询、报修工单提交与进度跟踪、校内活动报名、失物招领信息发布、意见反馈等核心功能。管理员端则通过SpringBoot后端提供的管理接口,在小程序内嵌的管理页面或Web管理端完成资讯发布、工单派发、活动审核、用户管理、数据统计等操作。换句话说,这是一个典型的"学生—辅导员—后勤/管理员"三方角色联动平台,把原来分散在多个网站、多个公众号菜单里的服务集中到一个微信小程序入口。
这套东西适合谁来参考?我认为主要有三类人:第一类是正在做毕业设计、需要一套能完整跑通"小程序端+后端接口+数据库设计+部署上线"全链路的计算机或软件工程专业学生;第二类是想在校内创业或参与实验室项目、需要快速搭建一个校园服务原型的开发者;第三类是已经工作、但对SpringBoot+微信小程序这套组合还不够熟的Java工程师,可以通过这个项目把前后端联调、微信登录态维护、权限控制这些高频技能点补齐。
选型上,后端采用Java + SpringBoot是深思熟虑的结果。SpringBoot在校园项目里几乎是"标准答案"级别——自动配置极大降低了搭建成本,内嵌Tomcat让部署变得简单,丰富的Starter生态可以快速集成MyBatis-Plus、Redis、OSS等组件。更重要的是,Java技术栈在校园环境里的资料密度最高,遇到问题能搜到的解决方案最多,对需要独立完成项目的学生来说,这比追求冷门框架要稳妥得多。前端选择微信小程序而不是独立App,理由也很直接:微信是学生群体不可能卸载的超级入口,小程序无需安装、扫码即用,而且微信生态提供了完整的登录、支付、消息订阅能力,非常适合校园场景的轻量服务。
我自己在动手前踩过的一个认知误区是:以为"校园网综合服务系统"就等于"把几个功能塞到一个后台里"。真正深入需求后才发现,这类系统的核心难点根本不在于单个功能怎么写,而在于三个交叉问题——多角色权限如何统一管理、高频数据(如课表成绩)如何与校园现有系统对接或模拟、以及服务流程(如报修工单)如何做到状态可追踪。把这几个问题想清楚,项目的技术含金量和答辩深度都会完全不同。这篇文章我会按照从需求分析、数据库设计、后端接口开发、小程序联调,到性能优化和部署上线的完整链路来复盘,所有关键环节都会给出理由和踩坑记录,而不是单纯贴代码。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 需求边界与角色权限设计:先想清楚"谁在用、用什么、限制什么"
2.1 三类核心角色的用例拆解
系统设计的第一步不是建表,而是把参与者和他们的操作权限梳理清楚。校园网综合服务系统的用户天然分成三层:学生、教师/辅导员、系统管理员。层级不复杂,但每一层的操作边界必须严格划分,否则后患无穷。
学生端的功能集合可以概括为"查看与提交"。查看课表、成绩、资讯、活动、失物招领;提交报修单、活动报名、意见反馈。这里要注意,学生不应该拥有任何删除或审核权限,比如误报了失物招领信息,只能通过联系管理员来下架,而不是自己直接删除——这是为了防止数据滥用。
教师/辅导员端的职责主要是"审批与核实"。活动报名需要辅导员审核(防止学生随意旷课参加活动)、学生请假或特殊申请需要辅导员通过、报修单的初步核实也可以由辅导员确认后再转给后勤。这个角色的权限落在"查看名下学生数据 + 处理待审批事项"的范围内。
管理员是整个系统的超级入口,管理资讯发布、报修工单分派、用户冻结/解冻、数据看板统计、系统参数配置。从业务体量来看,管理员角色通常数量很少(几人),但是功能点最多。
2.2 为什么选择RBAC模型来实现权限控制
说到权限控制,很多同学的第一个念头是"在用户表加一个role字段,然后if判断"。这在角色只有两种、接口只有十几个的小项目里确实够用,但是一旦功能模块变多,这种硬编码方式会迅速失控——每个接口都要写一堆if/else,后续加角色、加权限点都要全局搜索修改。
我在这个项目里采用的是经典的RBAC(基于角色的访问控制)模型,引入五张核心表:用户表、角色表、菜单权限表、用户角色关联表、角色菜单关联表。直观理解就是"用户—角色—权限"三层映射:不直接把权限绑在用户身上,而是先把权限绑到角色上,再把角色分配给人。这种设计的好处是:新增一个"楼栋管理员"角色时,只需要在角色表里插入一行,然后配置该角色能访问的菜单和接口,不需要改动用户表结构,也不需要改业务代码。
不过这里我必须说一个实际取舍:完整RBAC对这个项目来说略有冗余,所以我在落地时做了一层简化——权限粒度精确到"接口+菜单"级别,但不对数据行级做细粒度控制。比如辅导员只能看到本学院学生的报修单,这一点不是靠RBAC实现的,而是在SQL查询时用college_id条件过滤。换句话说,RBAC负责"你能访问哪些功能",而数据范围靠业务代码控制。这个组合在校园系统场景里非常实用,既保证了权限管理的统一性和扩展性,又避免了过度设计。
2.3 微信小程序登录态与后端会话的衔接
校园网综合服务系统因为是微信小程序端,绕不开微信登录体系。这里要讲清楚登录的完整链路,很多第一次做小程序的同学都在这个地方懵过。
微信小程序的登录流程是这样的:小程序端调用wx.login()拿到一个临时凭证code,把这个code传给后端;后端拿到code后,调用微信的code2Session接口,用appid + appsecret + code换回openid和session_key。其中openid是用户在当前小程序下的唯一标识,session_key用于解密用户手机号等敏感信息。
这个流程里最关键的一个设计决策是:后端不要直接把openid返回给小程序端,更不能拿openid当会话凭证。原因很简单——小程序端代码是半公开的,网络请求可以被抓包,如果openid直接暴露在响应里,别人拿到你的openid就能伪造你的身份。正确做法是后端在首次登录后,生成一个自定义的登录凭证(我使用的是JWT令牌),并建立token -> openid的映射关系,后续所有请求都通过请求头里的token来识别用户身份。
我在实现时用一个工具方法统一管理当前登录用户:
java复制public class UserContext {
private static final ThreadLocal<Integer> CURRENT_USER = new ThreadLocal<>();
public static void set(Long userId) {
CURRENT_USER.set(userId);
}
public static Long get() {
return CURRENT_USER.get();
}
public static void clear() {
CURRENT_USER.remove();
}
}
配合一个拦截器,在进入Controller之前解析JWT并把用户ID放入ThreadLocal:
java复制public class LoginInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
String token = request.getHeader("Authorization");
if (StringUtils.isBlank(token)) {
throw new BusinessException(401, "未登录或登录已过期");
}
// 解析JWT,拿到userId,存入ThreadLocal
Long userId = JwtUtil.parseToken(token);
UserContext.set(userId);
return true;
}
}
这里有一个非常容易踩的坑:ThreadLocal在当前线程处理完请求后必须清理,否则在Tomcat线程池复用的场景下,下一次请求可能会读到上一个用户的ID。所以我实现了afterCompletion方法调用UserContext.clear()。这个细节在自测阶段可能永远不暴露,但一旦并发量上来就是严重的安全漏洞。
2.4 服务模块划分:微服务还是单体?
又一个很多同学纠结的问题:校园网综合服务系统听起来功能很多,要不要拆微服务?我的结论非常明确——不拆。这个项目从功能数量和团队规模来看,单体应用是最合理的选择。微服务带来的服务发现、配置中心、分布式事务、链路追踪等复杂性,对这个体量来说完全是负资产。
但我做了一件比"拆不拆微服务"更重要的事情:在单体内部做了清晰的模块划分,采用典型的分层架构——Controller层只负责参数接收和响应封装,Service层承载业务逻辑,Mapper层通过MyBatis-Plus操作数据库。这样即使将来某个模块(比如报修工单)单独拆出去,代码迁移的成本也能控制在可接受范围。模块划分上按照业务域拆包:
text复制com.campus.platform
├── controller # 接口层
│ ├── AuthController
│ ├── InfoController
│ ├── CourseController
│ ├── RepairController
│ ├── ActivityController
│ └── AdminController
├── service # 业务层
├── mapper # 数据访问层
├── entity # 实体类
├── common # 通用类:返回结果、异常、常量
├── config # 配置类:拦截器、跨域、JWT等
└── utils # 工具类
这样设计的收益是:职责边界清晰,多人协作或后续维护时不需要在整个项目里大海捞针式找代码;同时测试时可以针对Service层写单元测试,不需要启动整个Web容器,测试效率提升明显。
3. 数据库与核心表结构设计:校园数据模型的取舍与反范式设计
3.1 核心数据表全景
数据库是这类系统最容易"建完就后悔"的部分。我见过太多人在刚拿到题目时就把二十多张表一股脑建出来,结果做到一半发现冗余字段、找不到关联键、类型定义不合理。正确的做法是先画业务流程图,再根据流程抽象出实体关系。
这个系统最终的核心表我控制在18张以内,主要分四组:
- 用户权限组:
user(用户基础信息)、role(角色)、user_role(用户角色关联)、menu(菜单权限)、role_menu(角色菜单关联) - 信息内容组:
banner(轮播图)、notice(校园资讯公告)、activity(校园活动)、activity_signup(活动报名记录)、lost_found(失物招领) - 教学服务组:
course(课程)、course_student(学生选课关联)、score(成绩) - 服务流程组:
repair_order(报修单)、repair_log(报修流转日志)、feedback(意见反馈)
下面我会重点讲其中最有代表性的几张表,因为它们的取舍直接影响了系统可用性。
3.2 用户表:为什么不用微信openid做主键
用户表是整个系统的地基,这里我做过一个重要的设计决策。很多教程为了方便直接把openid作为用户表主键
