简介:面向计算机专业毕业设计的学生资助在线管理小程序,基于微信小程序、SSM框架与MySQL数据库开发,围绕管理员、班主任、家长三种角色,实现学生信息管理、资助申请、审核评议、名单公示、回执公告、申请复议等完整业务流程,体现小程序前端与Java后端通过JSON交互的典型开发模式。资源包共一千零三十六个文件,约七十二点一六兆字节,涵盖后台Vue页面、Java源码、JS脚本、小程序WXML与WXSS文件、PNG图片以及SQL数据库脚本等;其中Vue页面用于管理端界面,Java源码承担业务逻辑,WXML/WXSS与JS构成小程序端,SQL脚本用于初始化数据。另附毕业论文、MP4视频演示和运行脚本,目录按模块划分,便于直接部署和二次开发。已有一百三十七人学习浏览,适合需要完整毕业设计案例、快速掌握SSM整合开发或进行功能扩展的开发者。
1. 学生资助在线管理小程序:一套能跑通答辩的完整毕设方案
想用微信小程序作为毕业设计的前端展示,又需要一个看起来有技术深度的后端框架,SSM加MySql几乎是计算机专业学生最常选的一条路。「学生资助在线管理小程序」这个题目出的很典型:业务逻辑清晰,不复杂到失控,但该有的模块——登录、角色权限、数据增删改查、报表统计——全都有,正好覆盖了SSM框架和MySQL数据库的核心知识点。这篇笔记我就按自己做过的方案讲一遍:从SSM后端怎么搭、小程序端怎么对接、数据库表怎么设计,到部署时容易翻车的地方,全部落成可以直接复现的命令和代码。适合正在做选题、或者已经搭了一半但被Session、跨域、请求封装卡住的人。
2. 搭好SSM后端骨架:三个关键配置文件与Maven依赖
做SSM项目,最怕一开始就扎进代码里写Controller,结果连Spring容器都没起来。我一般会先把后端工程的结构定死:Maven管理依赖,web.xml启动Spring和SpringMVC,然后再处理MyBatis和数据库的连接。这一步走完了,后面写接口就是往里面填肉。
2.1 Maven依赖与标准目录结构:先把版本冲突挡在门外
SSM的坑很多时候不是代码问题,是依赖版本互相打架。比如Spring 5和MyBatis 3.5搭配没问题,但如果你从网上随便拷一份pom.xml,很可能把Spring 4和MyBatis 3.0凑到一起,启动时直接报BeanCreationException。下面这份是我一直用的组合,Spring 5.2系配MyBatis 3.5系,兼容性比较稳定。
<properties> <maven.compiler.source>1.8</maven.compiler.source> <maven.compiler.target>1.8</maven.compiler.target> <spring.version>5.2.15.RELEASE</spring.version> <mybatis.version>3.5.6</mybatis.version> <mybatis-spring.version>2.0.6</mybatis-spring.version> </properties>依赖需要重点关注的是:spring-webmvc、spring-jdbc、mybatis、mybatis-spring、mysql-connector-java、fastjson或jackson-databind用于JSON序列化。这里有个容易踩的点:spring-webmvc已经包含了spring-web和spring-core,不需要再单独引入spring-web,否则版本冲突的可能就多一层。
目录结构方面,我用标准的Maven Web结构:src/main/java放Java源码,按com.example.xx分包(controller、service、mapper、pojo);src/main/resources放配置文件;src/main/webapp下面放静态资源和WEB-INF。如果你是用的IDE(如IDEA或Eclipse)新建的Web项目,务必要确认web.xml存在,这是启动Spring容器和SpringMVC前端控制器的入口。
提示:Maven依赖不要追求最新版本。SSM这个生态已经很成熟了,新版本反而可能在Tomcat版本上出问题。比如Spring 6要求JDK 17,而你毕设用的Tomcat 8.5还是JDK 8,这种基础环境不匹配会让你在配置上浪费大量时间。
2.2 web.xml与spring配置文件:把DispatcherServlet和ContextLoaderListener接好
web.xml是SSM的启动总开关。这里要做两件事:配置ContextLoaderListener加载Spring根容器(管理Service和Mapper),配置DispatcherServlet加载SpringMVC子容器(管理Controller)。子容器能访问父容器的Bean,反过来不行,这个关系在后续写代码时会影响你的注入方式。
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee" version="3.1"> <!-- 指定Spring配置文件位置 --> <context-param> <param-name>contextConfigLocation</param-name> <param-value>classpath:spring/spring-mybatis.xml</param-value> </context-param> <!-- 启动Spring根容器 --> <listener> <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class> </listener> <!-- SpringMVC前端控制器 --> <servlet> <servlet-name>springMVC</servlet-name> <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> <init-param> <param-name>contextConfigLocation</param-name> <param-value>classpath:spring/spring-mvc.xml</param-value> </init-param> <load-on-startup>1</load-on-startup> </servlet> <servlet-mapping> <servlet-name>springMVC</servlet-name> <url-pattern>/</url-pattern> </servlet-mapping> </web-app>URL Pattern这里有个细节:用“/”而不是“/”。如果写成“/”,JSP页面和静态资源都不经过SpringMVC的静态资源映射解析,直接导致404或静态文件被Controller拦截。这个错误很隐蔽,因为它只在访问HTML或图片时才暴露出来,接口调用反而是正常的。
2.3 spring-mybatis.xml与spring-mvc.xml:连接池、Mapper扫描与注解驱动
这一层是SSM的核心,也是几乎所有故障的源头。我在实训中看到过最多的问题就是:数据库密码写在代码里、连接池没有配置空闲回收、Mapper接口和XML文件扫描路径不一致。
先看spring-mybatis.xml,它负责数据源和MyBatis的整合:
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource" destroy-method="close"> <property name="driverClass" value="com.mysql.cj.jdbc.Driver" /> <property name="jdbcUrl" value="jdbc:mysql://127.0.0.1:3306/student_aid?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai" /> <property name="user" value="root" /> <property name="password" value="你的密码" /> <property name="initialPoolSize" value="5" /> <property name="maxPoolSize" value="20" /> <property name="maxIdleTime" value="300" /> </bean> <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean"> <property name="dataSource" ref="dataSource" /> <property name="typeAliasesPackage" value="com.example.pojo" /> <property name="mapperLocations" value="classpath:mapper/*.xml" /> </bean> <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer"> <property name="basePackage" value="com.example.dao" /> </bean>数据源我选的是c3p0,参数里initialPoolSize是最小连接数,maxPoolSize是最大连接数。这两个值在毕设场景下不必大——5和20就够了,没必要做压力测试。jdbcUrl里面的serverTimezone一定要配,MySql 8.X默认UTC时区,你不写会导致Date类型字段差8小时,这在后期排查时特别玄学。
spring-mvc.xml的配置目标是让Controller能工作、静态资源不被拦截、JSON能正确转换。关键配置如下:
<context:component-scan base-package="com.example.controller" /> <mvc:annotation-driven /> <mvc:default-servlet-handler /> <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver"> <property name="prefix" value="/WEB-INF/views/" /> <property name="suffix" value=".jsp" /> </bean>Json转换要单独说明一下。现在流行的做法是用@RestController配合jackson包,自动把返回对象序列化成JSON。但如果你在pom里漏了jackson-databind这个依赖,@ResponseBody返回的对象就会变成一串类似于com.example.pojo.Student@4f241d的字符串,小程序那边解析直接报错。我建议在pom.xml里显式添加:
<dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-databind</artifactId> <version>2.9.8</version> </dependency>3. 微信小程序端对接SSM:从请求封装到数据渲染
后端一旦跑通,工作重心就要转移到小程序端。这里的技术点跟传统网页开发不同——小程序没有Cookie机制,传统的Session管理在默认情况下是失效的。如果选题答辩时遇到“Session怎么保持登录态”这个问题答不上来,就会很被动。所以我的方案是用token方式做用户状态管理,同时在小程序端封装统一的请求工具。
3.1 请求封装:为什么不用原生wx.request直接调接口
你肯定会先试一次wx.request,然后发现每次调用都要写url、header、method,而且每个页面都要重复这段代码。更重要的是token管理没有统一出口。我习惯先封装一个request.js,把所有网络请求都走这一层:
const BASE_URL = 'http://127.0.0.1:8080/student_aid'; function request(path, method = 'GET', data = {}) { return new Promise((resolve, reject) => { wx.request({ url: BASE_URL + path, method: method, data: data, header: { 'Content-Type': 'application/json', 'token': wx.getStorageSync('token') || '' }, success: (res) => { if (res.statusCode === 200) { resolve(res.data); } else if (res.statusCode === 401) { wx.redirectTo({ url: '/pages/login/login' }); reject(res); } else { reject(res); } }, fail: (err) => { reject(err); } }); }); } module.exports = { request, BASE_URL };这个封装的逻辑很直白:所有请求自动带token,后端接口若返回401就说明登录过期,自动跳登录页。BASE_URL里的IP地址是开发环境地址,真机调试时要把127.0.0.1换成电脑在局域网中的IP,否则手机会连不上。如果你在开发工具里勾了“不校验合法域名”,那也要确保这个地址是开发工具能访问的。
提示:微信开发者工具默认会校验接口域名必须是HTTPS。本地联调时,点击右上角“详情”→本地设置→勾选“不校验合法域名,以及web-view(业务域名)、TLS版本以及HTTPS证书”,能帮你少走一大截弯路。
3.2 登录态管理:token方案比Session更适配小程序
我将这个方案的登录流程设计为:用户通过wx.login获得code发送给后端,后端用code换openid,然后生成token返回给小程序端。小程序端收到token后存入Storage,后续请求在header里带token。这样做的好处是:后端不需要维护Session,小程序重启后用户仍然保持登录,不会被微信的启动机制打断。
后端生成token最简单的方式是用UUID。虽然网上说JWT更规范,但毕设场景下UUID的随机串配合数据库一个用户表字段就够了,代码量少、答辩容易解释。在Controller里,登录接口写成这样:
@RequestMapping("/login") @ResponseBody public Result login(@RequestBody User user) { User dbUser = userService.findByUsernameAndPassword(user); if (dbUser != null) { String token = UUID.randomUUID().toString().replaceAll("-", ""); dbUser.setToken(token); userService.updateToken(dbUser.getId(), token); return Result.success(dbUser); } return Result.error("用户名或密码错误"); }这里注意,我返回的是整个user对象,里面包含了id、username、role和token。小程序端拿到之后直接存storage,下次启动读出来就是登录态。密码字段我是刻意不查出来,这里在SQL里就要去掉select password。
3.3 动态数据渲染:以资助申请列表为例
小程序页面里的list渲染,是微信小程序的基本功,同时也是考察你对MVVM模式理不理解的点。在wxml里用wx:for循环,在js里用data装载数据。我来写一个资助申请列表的核心页面逻辑:
<view class="card" wx:for="{{aidList}}" wx:key="id"> <view class="title">{{item.studentName}}的申请</view> <view class="info">资助类型:{{item.aidType}}</view> <view class="status {{item.status == '待审核' ? 'pending' : 'done'}}">审核状态:{{item.status}}</view> </view>const { request } = require('../../utils/request.js'); Page({ data: { aidList: [] }, onLoad: function () { this.fetchAidList(); }, fetchAidList: function () { request('/aid/list', 'GET').then(res => { if (res.code === 0) { this.setData({ aidList: res.data.rows }); } }).catch(() => { wx.showToast({ title: '加载失败', icon: 'none' }); }); } });setData是我重点讲解Visual层的一个点:数据更新一定要通过setData才能驱动视图变化,直接改变this.data.aidList在界面上不会有任何反应。wx:key必须绑定唯一ID,否则列表变更时小程序无法准确复用节点,会有性能问题。这里的status字段我直接存的是中文,虽然不规范但demo阶段可读性好。
4. 数据库设计与后端接口实现:把学生资助业务落到表结构
标题里写了学生资助在线管理,那业务模块就至少应该包括:学生用户、资助项目、资助申请、审核记录、资助发放。表设计是答辩时最容易出彩的点,评审老师几乎都会问“你觉得你数据库设计好在哪”。你需要能从范式、索引、表关系三个维度回答。
4.1 核心表结构:六张表解决一个完整业务链路
我不建议一开始就做十几张表,毕设不可能维护得动。下面这套6表设计是能够覆盖业务并且不冗余的。
| 表名 | 关键字段 | 作用 |
|---|---|---|
| tb_user | id, username, password, role, token | 用户登录表,role区分学生/管理员 |
| tb_student | id, user_id, name, student_no, class_name, phone | 学生基本信息 |
| tb_aid_type | id, type_name, description | 资助类型设置,如国家助学金、校内补助 |
| tb_aid_application | id, student_id, aid_type_id, status, apply_time | 资助申请主表,status标记审核进度 |
| tb_aid_review | id, application_id, reviewer, comment, review_time | 审核记录表 |
| tb_aid_grant | id, application_id, amount, grant_time | 发放记录表 |
为什么把学生信息和用户信息拆成两张表?因为一个用户可能是学生,将来也可能有老师角色或者管理员角色,用户表只关心账号和权限,学生表只关心个体属性。这属于第二范式的思路,答辩时能讲出这个理由,比单纯说“为了存更多信息”有说服力。
tb_aid_review表存在的意义是保留审核轨迹。开发时很多人只做一张申请表的status字段更新,觉得能完成功能就够了。但评审老师如果问“审核是谁操作的、什么时候操作的”你就答不上来。多一张review表设计上就完整了。下面是核心的建表SQL:
CREATE TABLE tb_aid_application ( id INT PRIMARY KEY AUTO_INCREMENT, student_id INT NOT NULL, aid_type_id INT NOT NULL, status TINYINT DEFAULT 0 COMMENT '0-待审核 1-已通过 2-被驳回', apply_time DATETIME DEFAULT CURRENT_TIMESTAMP, CONSTRAINT fk_app_student FOREIGN KEY (student_id) REFERENCES tb_student(id), CONSTRAINT fk_app_type FOREIGN KEY (aid_type_id) REFERENCES tb_aid_type(id) );两个外键约束做了显式声明,这是告诉评审老师你有表关系设计的意识。但我要提醒一个坑:MyBatis在联表查询时要小心外键字段的映射,因为student_id和aid_type_id在子表里同时存在,如果直接用resultMap自动映射,容易把主表字段灌进子对象里。
4.2 MyBatis的ResultMap:解决一对多查询映射
学生资助列表页需要一个聚合数据:每条申请记录,同时显示学生姓名、资助类型名、审核状态。这个查询跨了三张表,在mapper XML里这样写:
<resultMap id="AidApplicationVO" type="com.example.pojo.AidApplicationVO"> <id property="id" column="id" /> <result property="status" column="status" /> <result property="applyTime" column="apply_time" /> <association property="student" javaType="com.example.pojo.Student"> <id property="id" column="student_id" /> <result property="name" column="name" /> <result property="studentNo" column="student_no" /> </association> <association property="aidType" javaType="com.example.pojo.AidType"> <id property="id" column="aid_type_id" /> <result property="typeName" column="type_name" /> </association> </resultMap> <select id="selectApplicationVOList" resultMap="AidApplicationVO"> SELECT a.id, a.status, a.apply_time, s.id as student_id, s.name, s.student_no, t.id as aid_type_id, t.type_name FROM tb_aid_application a LEFT JOIN tb_student s ON a.student_id = s.id LEFT JOIN tb_aid_type t ON a.aid_type_id = t.id </select>这里的核心要点是column别名。SQL里s.id的列名和a.id的列名都叫id,在ResultMap的association节点里,要保证column写的是别名student_id和aid_type_id,否则数据会错乱。这种“别名对应”是MyBatis里最容易翻车的地方之一,很多人查询结果student对象有值但字段全是null,就是因为没做别名区分。
4.3 Service层事务控制:审核操作的两个数据变更必须原子完成
学生资助审核这个动作,实际上在代码里会同时发生两件事:更新tb_aid_application的状态,写入tb_aid_review的审核记录。如果第一条update成功、第二条insert失败,会出现“状态显示已审核但没有任何审核记录”的脏数据。这就是为什么Service层必须加事务。
@Service public class AidApplicationServiceImpl implements AidApplicationService { @Autowired private AidApplicationMapper aidApplicationMapper; @Autowired private AidReviewMapper aidReviewMapper; @Transactional(rollbackFor = Exception.class) public void review(Integer applicationId, String reviewer, String comment, Integer status) { AidApplication application = new AidApplication(); application.setId(applicationId); application.setStatus(status); aidApplicationMapper.updateStatus(application); AidReview review = new AidReview(); review.setApplicationId(applicationId); review.setReviewer(reviewer); review.setComment(comment); aidReviewMapper.insert(review); } }@Transactional注解的方法里,只要抛出RuntimeException或者Exception,两个数据库操作都会回滚。rollbackFor=Exception.class的作用是把非运行时异常也纳入回滚范围。这里有个SSM项目常见的坑:事务只对Spring管理的Bean生效,如果用new关键字自己new了一个ServiceImpl对象然后调方法,事务是不生效的。另外,事务方法要在Controller里通过接口注入,而不是直接new成实例。
5. 微信小程序与SSM项目联调避坑:五个最常见的翻车现场
这一章我专门把平时带学生调试时最常碰到的五个问题列出来,每一条都是真实踩过坑的,不是网上抄来的经验。这些问题都有一个共同特点:解决后会觉得非常简单,但定位过程却让人抓狂。
5.1 小程序请求404:URLPattern的“/”与“/*”惹的祸
现象:接口地址在浏览器里直接访问是好的,但小程序请求永远返回404,而且后端控制台没有任何日志输出。
原因:DispatcherServlet的url-pattern配成了“/*”,直接拦掉了所有请求,包括静态资源。但实际上你的接口路径是有的,后来一步一步排查发现,被拦截后SpringMVC找不到对应HandlerMapping,所以返回404。
解决:把url-pattern改回“/”。顶层Servlet容器在处理请求时,会把匹配交给DefaultServlet兜底,SpringMVC只处理@Controller映射的路径,其余交由容器处理。
5.2 数据库连接失败:MySql 8的驱动类与时区问题
现象:Tomcat启动时报错,No suitable driver found for jdbc:mysql://127.0.0.1:3306/student_aid,或者报时区异常。
原因:两个常见版本坑叠加在一起。其一是pom里引入的是mysql-connector-java 5.1.X,而本机装的是MySql 8.X,驱动类从com.mysql.jdbc.Driver改成了com.mysql.cj.jdbc.Driver;其二是MySql 8默认时区是UTC,必须显式在jdbcUrl写上serverTimezone。
解决:pom.xml统一用8.0.33版本的mysql-connector-java,jdbcUrl里带上serverTimezone=Asia/Shanghai。顺带补一句,数据库连接池我建议用c3p0,后续想要优化再换Druid,不至于在配置上过早陷入细节。
5.3 json数据有斜杠被转义:接口返回正常却在小程序端解析失败
现象:后端返回的日期或字符串字段出现了反斜杠,例如“2025-04-01”,在小程序端JSON.parse报错。
原因:后台手动拼接了JSON字符串,而不是使用Jackson或Fastjson序列化对象。最典型的情况是在Controller里写了return Result.success("{\"code\":0}")这种代码,字符串被二次转义。
解决:后端所有接口返回值统一使用Java对象,配合@ResponseBody交给Jackson做序列化。不要在业务代码里手写JSON字符串。只要是对象序列化,小程序的wx.request就一定能拿到真正的JSON格式。
5.4 真机调试无法访问本地接口:127.0.0.1在手机上指向手机自身
现象:开发工具里接口全通,一用手机预览就全部超时。
原因:手机上的127.0.0.1是手机自己,不是你的电脑。小程序真机调试必须要走局域网IP,且后端Tomcat需要监听0.0.0.0。
解决:BASE_URL改成电脑在局域网里的IP地址,例如http://192.168.1.101:8080/student_aid。同时检查电脑防火墙是否放行8080端口;Windows下在防火墙高级设置里新增入站规则允许TCP 8080端口的连接。
5.5 MyBatis二级缓存没配好:查询时莫名拿到旧数据
现象:修改数据库后查列表,第一次是对的,第二次之后一直是旧数据,期间没重启Tomcat,百思不得其解。
原因:你在mapper XML里加了 标签,但实体类没有实现Serializable接口,导致二级缓存序列化失败后并没有真正缓存,或者缓存的是过期的全量查询结果。
解决:SSM毕业设计建议关掉MyBatis二级缓存,只保留一级缓存(Session级别,默认开启)。绝不建议为了性能开二级缓存,因为它的失效机制不直观,而且联表查询容易返旧数据。我自己的方案是:在mybatis-config.xml里把cacheEnabled设成false。
6. 从开发到答辩演示:把项目做成越用越顺的完整闭环
最后这一章,我聊聊项目工程化的收尾工作。这个阶段往往被学生忽略:代码写完了就丢在IDE里,没有文档、没有演示脚本、没有验证方法。结果答辩时一紧张,操作乱了,演示翻车,前面写了多少代码都白搭。
6.1 数据准备与演示数据库:让评审老师边看边理解
我强烈建议你准备一份“演示专用”的数据库初始化脚本,里面放好6个以上学生账号、10条以上资助申请记录(分布在待审核/通过/驳回三种状态)、管理员账号一个。管理员登录的账号密码建议直接写在演示PPT里,方便现场快速登录。每次演示之前,运行一遍reset.sql把数据库恢复到初始状态,避免上一次演示留下的脏数据干扰结果。初始化脚本里建议用固定主键,方便讲解时指定某条记录做联表查询演示。
6.2 答辩时值得多讲的三个技术点:从“能用”到“会讲”
答辩评审不会每行代码都看,他们只会挑几个点来问你。我总结下来,下面三个技术点是这套项目里提问率比较高的:
第一是ResultMap如何完成多表联查。这个讲清楚association节点和column别名的对应关系,证明你真的掌握了MyBatis,而不是只会用MyBatis Generator。第二是Spring事务的生效机制。用review方法举例子,讲明白为什么必须在Service层加事务、rollbackFor的含义。第三是token登录态管理与Session的区别。能讲清无状态认证在小程序端的必要性,是一个加分项。
6.3 本地部署的最后一个窍门:用Maven打war包而不是复制目录
很多同学喜欢直接把WebContent目录拷到Tomcat里,这种方式很容易漏掉Maven的依赖,而且一旦换设备就全部作废。我建议在命令行里执行mvn clean package,生成war包后放到Tomcat的webapps目录下,Tomcat会自动解压部署。这样Tomcat到项目之间的依赖关系是干净的,不容易出现缺jar的情况。
如果你遇到了“打包后resource下的xml文件没进classes目录”这个问题,记得在pom.xml里加一句build配置,把applicationContext.xml和mapper目录排除出过滤范围。
<resources> <resource> <directory>src/main/java</directory> <includes> <include>**/*.xml</include> </includes> </resource> <resource> <directory>src/main/resources</directory> </resource> </resources>我在带学生做项目的时候,经常看到最后阶段因为这种打包问题翻车。如果没有这一步,Maven会默认忽略src/main/java下的XML文件,导致MyBatis找不到Mapper映射文件,接口全部报Invalid bound statement。
这套方案我前前后后已经帮学生在多个毕业设计场景中落地过,步骤走通之后会发现它就是一层窗户纸:SSM负责把Web开发的标准动作讲清楚,小程序负责解决移动端入口和学生日常使用习惯的问题,MySql把业务数据稳稳接住。整个链路并不复杂,关键是每一条配置都要知其所以然,答辩时才能顶得住追问。最后给你一个诚恳的建议:养成每天启动一次服务、完整跑一遍核心流程的习惯,这比答辩前临时抱佛脚有用得多。希望帮到你。
本文还有配套的精品资源,点击获取