☰
智慧校园Android客户端加管理系统:Java全栈毕设方案与答辩避坑指南
2026/10/22 9:53:08 网站建设 项目流程

简介:这是一套面向高校计算机相关专业学生与开发者的智慧校园Android客户端及后台管理系统完整源码,适合作为毕业设计、课程设计或项目立项演示,也可供Java与Android初学者进阶学习。项目围绕校园资讯浏览、活动比赛公告、社团与班级团队管理展开,支持资讯发布、点赞、评论、分享,以及任务安排、接任务、任务提醒与进度管理、机构申请等协作功能,贴近真实校园场景。压缩包共763个文件,约26.8MB,以218个java源文件、210个xml布局与配置、260个png界面素材为主,另含jar依赖、so库、gradle构建脚本及keystore签名文件,工程结构完整。已有107人学习下载。代码均经测试运行成功,答辩评审平均分达96分,下载后可参照README快速了解模块划分,并在此基础上修改扩展功能。

1. 智慧校园 Android 客户端加管理系统:一套能跑通、能答辩、能写进简历的 Java 全栈方案

很多同学做毕设时卡在同一个地方:想做一个「看起来完整」的系统,结果要么只写了后端接口没有界面,要么只做了 Android 端没有管理后台,答辩时被问「数据从哪来、谁在管」就答不上来。智慧校园这个题目之所以年年有人选,是因为它天然包含两类角色——学生/教师用移动端,管理员用 Web 后台,一套系统能把 Android 开发、Java 后端、数据库设计、权限管理全串起来。但真正能拿高分的,不是功能堆得多,而是端到端跑得通、数据流清晰、有可演示的完整闭环。这篇笔记就按「一个学生查课表、请假、看通知,管理员在后台发通知、审请假、管用户」这条主线,把技术选型、建表、接口、Android 端对接、以及答辩前最容易翻车的地方讲清楚。适合正在做 Java 方向毕设、需要一套可复现全栈方案的人。

2. 技术选型与整体架构:为什么这套组合最适合毕设落地

2.1 后端选 Spring Boot 而不是原生 Servlet 的理由

毕设最怕的是「代码写了一堆,但结构说不清」。原生 Servlet + JSP 能跑,但一旦要加登录拦截、统一返回格式、分页查询,代码就会散得到处都是。Spring Boot 的价值在于它把这些横切关注点收敛成注解和配置,答辩时你能指着 Controller 说「这是接口层」、指着 Service 说「这是业务层」、指着 Mapper 说「这是数据层」,层次分明。

我一般推荐的组合是:Spring Boot 2.7.x + MyBatis-Plus + MySQL 8.0 + JWT。选 MyBatis-Plus 而不是 JPA,是因为毕设里大量是「按条件查列表 + 分页」,MyBatis-Plus 的QueryWrapper写起来直观,SQL 可控,遇到复杂查询还能直接写 XML,不会像 JPA 那样在关联查询上绕圈子。JWT 用来做无状态登录,Android 端和 Web 后台共用一套鉴权逻辑,省得维护两套 Session。

// 统一返回结构,前后端约定好字段,Android 端解析才不会乱 public class Result<T> { private int code; // 200 成功,401 未登录,500 业务异常 private String msg; // 给前端提示用的文案 private T data; // 泛型承载具体数据 public static <T> Result<T> ok(T data) { Result<T> r = new Result<>(); r.code = 200; r.msg = "success"; r.data = data; return r; } public static <T> Result<T> fail(int code, String msg) { Result<T> r = new Result<>(); r.code = code; r.msg = msg; return r; } }

这段代码的关键在于统一返回格式。Android 端拿到响应后先判断code,再决定是解析data还是弹提示,不用每个接口单独处理。参数上code建议固定几个值:200 成功、401 令牌失效、403 无权限、500 业务错误。很多同学后期改接口时随手返回字符串,导致 Android 端解析崩溃,就是一开始没定好这个结构。

2.2 Android 端用原生还是跨平台

毕设场景下我建议用Android 原生(Java + Retrofit + OkHttp)。原因很实际:答辩老师大概率懂 Java,你讲 Retrofit 的注解式接口、OkHttp 的拦截器加 Token,他能听懂;如果你用 Flutter 或 React Native,老师一问「这个跨平台框架怎么和 Java 后端对接」,你反而要多解释一层。而且原生方案在简历上写「Android 原生开发」比写「跨平台」更稳。

网络层用 Retrofit + OkHttp + Gson 三件套。Retrofit 负责把 HTTP 接口声明成 Java 接口,OkHttp 负责底层连接和拦截器,Gson 负责 JSON 解析。拦截器里统一往请求头塞 Token,这样每个接口方法不用重复写鉴权代码。

// OkHttp 拦截器:统一给请求加 Token,并处理 401 public class TokenInterceptor implements Interceptor { private final SharedPreferences sp; public TokenInterceptor(SharedPreferences sp) { this.sp = sp; } @Override public Response intercept(Chain chain) throws IOException { String token = sp.getString("token", ""); Request original = chain.request(); Request request = original.newBuilder() .header("Authorization", "Bearer " + token) .header("Content-Type", "application/json") .build(); Response response = chain.proceed(request); // 令牌过期统一处理,避免每个页面单独判断 if (response.code() == 401) { sp.edit().remove("token").apply(); } return response; } }

参数说明:Authorization头用Bearer前缀是 JWT 的通用约定,后端解析时去掉前缀再验签。SharedPreferences存 Token 只适合毕设演示,生产环境应该用 EncryptedSharedPreferences,这一点答辩时如果被问到可以主动说明,反而是加分项。

2.3 数据库表设计:六张核心表撑起整个系统

智慧校园系统功能再多,核心数据关系就那几条。下面六张表能覆盖用户、角色、通知、请假、课表、班级,足够演示完整闭环。

表名关键字段说明
userid, username, password, real_name, role_id, class_id所有用户,role_id 区分学生/教师/管理员
roleid, role_name, permissions角色与权限,管理员可配
noticeid, title, content, publisher_id, create_time通知公告,管理员发布
leave_recordid, student_id, reason, start_time, end_time, status请假记录,status 为待审/通过/驳回
courseid, course_name, teacher_id, class_id, week_day, section课表,按星期和节次排
class_infoid, class_name, grade班级信息

建表时有两个细节容易忽略:一是leave_record的status用 tinyint 而不是字符串,查询和索引都更快;二是所有表加create_time和update_time,答辩时老师问「这条记录什么时候改的」你能答上来。外键在毕设里可以不建物理外键,用逻辑关联即可,避免插入顺序问题导致演示时卡住。

3. 后端接口与权限:从登录到请假审批的完整链路

3.1 JWT 登录与角色鉴权的最小实现

登录接口是整个系统的入口,做不好后面全乱。流程是:客户端提交用户名密码 → 后端校验 → 生成 JWT 返回 → 客户端存 Token → 后续请求带 Token → 后端拦截器验签并解析角色。

// 登录接口:校验密码后签发 JWT @PostMapping("/login") public Result<Map<String, Object>> login(@RequestBody LoginDTO dto) { User user = userService.getByUsername(dto.getUsername()); if (user == null || !BCrypt.checkpw(dto.getPassword(), user.getPassword())) { return Result.fail(500, "用户名或密码错误"); } // 载荷里放 userId 和 roleId,后续鉴权直接取 String token = JwtUtil.createToken(user.getId(), user.getRoleId()); Map<String, Object> data = new HashMap<>(); data.put("token", token); data.put("roleId", user.getRoleId()); data.put("realName", user.getRealName()); return Result.ok(data); }

逻辑说明:密码用 BCrypt 加密存储,不要用 MD5,答辩时老师问「密码怎么存的」你能说出加盐哈希。JWT 载荷里放userId和roleId,不放敏感信息,因为 JWT 只是 Base64 编码不是加密。JwtUtil.createToken里设置过期时间,建议 7 天,毕设演示够用。

鉴权拦截器里根据roleId判断访问路径:学生只能访问/student/**,管理员才能访问/admin/**。这样即使 Android 端被反编译,也拿不到管理员接口。

3.2 请假审批的状态流转与接口设计

请假是这套系统里最能体现「业务闭环」的功能,因为它有状态流转:学生提交 → 待审核 → 教师/管理员审批 → 通过或驳回。接口设计要围绕状态机来。

// 审批请假:只有待审核状态才能被审批 @PostMapping("/leave/approve") public Result<Void> approve(@RequestBody ApproveDTO dto) { LeaveRecord record = leaveService.getById(dto.getRecordId()); if (record == null) { return Result.fail(500, "记录不存在"); } // 状态校验:已审批的不能重复审批 if (record.getStatus() != 0) { return Result.fail(500, "该申请已处理,请勿重复操作"); } record.setStatus(dto.getPass() ? 1 : 2); record.setApproveTime(new Date()); leaveService.updateById(record); return Result.ok(null); }

参数说明:status用 0 待审、1 通过、2 驳回,前端根据这个值显示不同颜色标签。approveTime记录审批时间,方便后续统计。这里的关键是状态校验,很多同学演示时重复点审批按钮导致数据错乱,加上这层判断就稳了。

3.3 通知公告的分页查询与 Android 端对接

通知列表是 Android 端首页的核心内容,接口要支持分页,否则数据一多就卡。

// 分页查询通知,按时间倒序 @GetMapping("/notice/page") public Result<Page<Notice>> pageNotice( @RequestParam(defaultValue = "1") Integer pageNum, @RequestParam(defaultValue = "10") Integer pageSize) { Page<Notice> page = new Page<>(pageNum, pageSize); QueryWrapper<Notice> wrapper = new QueryWrapper<>(); wrapper.orderByDesc("create_time"); return Result.ok(noticeService.page(page, wrapper)); }

Android 端用 Retrofit 声明对应接口,返回类型用Result<Page<Notice>>,Gson 会自动解析。下拉刷新时pageNum加一,上拉加载时追加数据。这里有个坑:Page对象的字段名要和后端一致,如果后端用了 MyBatis-Plus 的Page,返回 JSON 里是records、total、current,Android 端实体类字段名必须对上,否则解析出来是 null。

4. Android 端关键页面:登录、课表、请假三个页面的实现要点

4.1 登录页:Token 存储与自动登录

登录页不只是输入框加按钮,关键是登录成功后 Token 怎么存、下次打开怎么自动跳转。

// 登录成功后保存 Token 和角色,下次启动直接判断 private void onLoginSuccess(LoginResponse data) { SharedPreferences sp = getSharedPreferences("user", MODE_PRIVATE); sp.edit() .putString("token", data.getToken()) .putInt("roleId", data.getRoleId()) .putString("realName", data.getRealName()) .apply(); // 根据角色跳不同首页 if (data.getRoleId() == 1) { startActivity(new Intent(this, StudentMainActivity.class)); } else { startActivity(new Intent(this, TeacherMainActivity.class)); } finish(); }

逻辑说明:apply()是异步写入,比commit()快,适合这种不要求立即读的场景。启动页里读token,如果存在且未过期就直接跳主页,否则跳登录页。这里要注意 Token 过期时间要和后端一致,否则会出现「本地以为没过期、后端返回 401」的情况。

4.2 课表页:用 RecyclerView 加 GridLayoutManager 实现周课表

课表是智慧校园里视觉上最像「成品」的页面。用RecyclerView配合GridLayoutManager,列数设为 7(周一到周日),每个格子对应一节课。

// 课表适配器:根据位置计算星期和节次 @Override public void onBindViewHolder(ViewHolder holder, int position) { int row = position / 7; // 第几节 int col = position % 7; // 星期几 Course course = courseList.get(row * 7 + col); if (course == null) { holder.tvCourse.setText(""); holder.itemView.setBackgroundColor(Color.TRANSPARENT); } else { holder.tvCourse.setText(course.getCourseName() + "\n" + course.getTeacherName()); holder.itemView.setBackgroundColor(Color.parseColor("#4A90D9")); } }

参数说明:row和col的换算关系是position / 7和position % 7,这是网格布局的通用算法。空白格子要设透明背景,否则会显示默认色块,看起来像 bug。课程数据从后端按class_id查询,返回后填充到对应位置。

4.3 请假页:日期选择与状态回显

请假页要处理日期范围选择和提交后的状态显示。日期用DatePickerDialog,提交后把记录加到列表顶部并显示「待审核」。

// 提交请假申请 private void submitLeave() { String reason = etReason.getText().toString().trim(); if (reason.isEmpty()) { Toast.makeText(this, "请填写请假理由", Toast.LENGTH_SHORT).show(); return; } LeaveRequest req = new LeaveRequest(); req.setReason(reason); req.setStartTime(startTime); req.setEndTime(endTime); api.submitLeave(req).enqueue(new Callback<Result<Void>>() { @Override public void onResponse(Call<Result<Void>> call, Response<Result<Void>> response) { if (response.body() != null && response.body().getCode() == 200) { Toast.makeText(LeaveActivity.this, "提交成功", Toast.LENGTH_SHORT).show(); loadLeaveList(); // 重新加载列表,显示最新状态 } } @Override public void onFailure(Call<Result<Void>> call, Throwable t) { Toast.makeText(LeaveActivity.this, "网络异常", Toast.LENGTH_SHORT).show(); } }); }

逻辑说明:提交成功后重新拉取列表而不是本地插入,是为了保证状态和后端一致。enqueue是异步请求,不能在主线程调execute,否则会抛 NetworkOnMainThreadException。日期格式统一用yyyy-MM-dd HH:mm,后端用@JsonFormat注解解析,避免时区问题。

5. 避坑与排查:答辩前最容易翻车的五个地方

5.1 中文乱码:数据库、后端、Android 三处编码不一致

现象:通知内容里的中文在 Android 端显示成问号或方块。原因:MySQL 建库时没指定utf8mb4,或者连接串没加characterEncoding=utf8。解决:建库语句写DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci,JDBC 连接串加?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai。Android 端 Retrofit 的 GsonConverter 默认按 UTF-8 解析,一般不用改。

5.2 真机连不上后端:localhost 和局域网 IP 搞混

现象:模拟器能登录,真机一直转圈然后超时。原因:Android 模拟器里localhost指向模拟器自身,真机要用电脑的局域网 IP。解决:后端启动时监听0.0.0.0,Android 端 BaseUrl 写电脑的局域网 IP(如http://192.168.1.100:8080/),并确保手机和电脑在同一网络。另外 Android 9 以上默认禁止明文 HTTP,需要在AndroidManifest.xml里加android:usesCleartextTraffic="true"。

5.3 Token 过期后接口全部 401:拦截器没做统一跳转

现象:演示到一半,所有接口都返回 401,页面空白。原因:Token 过期后没有统一处理,每个页面各自报错。解决:在 OkHttp 拦截器里判断 401,清除本地 Token 并跳转登录页。注意跳转要用Intent.FLAG_ACTIVITY_NEW_TASK,因为拦截器里没有 Activity 上下文。

5.4 分页查询总数不对:count 语句被 order by 干扰

现象:通知列表总数显示为当前页条数,翻页后总数变化。原因:MyBatis-Plus 分页插件在 count 时如果 SQL 里有order by,某些版本会优化掉导致计数错误。解决:升级 MyBatis-Plus 到 3.5.x,或者在QueryWrapper里把orderByDesc放到分页参数之后。更稳妥的做法是自定义 count 查询,不依赖自动生成。

5.5 答辩演示时数据库连不上:没做连接池和超时配置

现象:演示中途数据库连接断开,页面报 500。原因:默认连接池配置不适合长时间演示,连接被回收后没重连。解决:用 HikariCP(Spring Boot 默认),配置maximum-pool-size=10、connection-timeout=30000、idle-timeout=600000。演示前重启一次后端,确保连接池是热的。

6. 进阶技巧:让这套系统从「能跑」变成「能讲」

6.1 用 AOP 记录操作日志,答辩时多一个亮点

在管理员审批请假、发布通知时,用 Spring AOP 切面记录操作人、操作时间、操作类型,存到operation_log表。这个功能代码量不大,但答辩时能讲「系统有审计能力」,比单纯增删改查高一个层次。

@Aspect @Component public class LogAspect { @Autowired private OperationLogService logService; // 切所有带 @LogAnnotation 的方法 @Around("@annotation(logAnnotation)") public Object around(ProceedingJoinPoint pjp, LogAnnotation logAnnotation) throws Throwable { long start = System.currentTimeMillis(); Object result = pjp.proceed(); OperationLog log = new OperationLog(); log.setMethod(pjp.getSignature().getName()); log.setParams(Arrays.toString(pjp.getArgs())); log.setCost(System.currentTimeMillis() - start); log.setCreateTime(new Date()); logService.save(log); return result; } }

参数说明:@Around环绕通知能在方法执行前后都插入逻辑,pjp.proceed()是执行原方法。cost记录耗时,答辩时可以说「系统对慢操作有监控」。注意日志表要定期清理,否则演示数据太多影响查询速度。

6.2 接口文档用 Knife4j 自动生成,省去手写文档

毕设要求交文档说明,手写 Word 又慢又容易和代码不一致。集成 Knife4j(Swagger 增强版),启动后访问/doc.html就能看到所有接口、参数、返回示例,直接截图放进论文。

<!-- pom.xml 加依赖 --> <dependency> <groupId>com.github.xiaoymin</groupId> <artifactId>knife4j-openapi2-spring-boot-starter</artifactId> <version>4.3.0</version> </dependency>

配置类里加@EnableSwagger2WebMvc和DocketBean,Controller 方法上加@ApiOperation注解。这样接口一改,文档自动更新,答辩时老师问「这个接口参数是什么」你直接打开文档页面,比翻代码快得多。

6.3 演示前的检查清单

最后分享一个我每次演示前都会走的流程:先启动 MySQL 确认库表存在,再启动后端看控制台有没有报错,然后用 Postman 跑一遍登录、查通知、提交请假、审批四个接口,确认返回正常,最后开模拟器走一遍完整流程。真机演示的话提前连好同一网络,把 BaseUrl 改成局域网 IP。这套流程走下来,基本不会在答辩现场翻车。

做毕设最深的体会是:功能不在多,在于每个功能都能说清楚数据从哪来、经过谁处理、最后到哪去。把这套智慧校园系统的登录、通知、请假三条线讲透,比堆十个半成品功能强得多。希望帮到你。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询