news 2026/10/22 4:09:50

基于微信小程序与SSM的学生资助管理系统开发全流程

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
基于微信小程序与SSM的学生资助管理系统开发全流程

简介:面向毕业设计场景的学生资助在线管理小程序完整项目包,基于微信小程序、SSM与MySQL设计,包含管理员、班主任、家长三类角色,覆盖公告发布、资助申请、在线评议、名单公示、回执公告、申请复议等业务,适合计算机类专业学生做课题参考或二次开发。全部文件共1036个,zip压缩包约72.16MB:Java与Vue对应后端接口和后台管理页面,WXML/WXSS为小程序前端源码,SQL为数据库脚本,DOC为毕业论文,MP4为演示视频,PNG/JPG等为界面素材,分类清晰。服务端用Java接收小程序端JSON数据,MySQL负责存储,前后端同一业务链衔接,可以直观理解学生从登录、提交资助申请到班主任审核、结果公示的完整流程。已有137人浏览/学习,压缩包内还保留了部分关键配置备份文件,便于对照调整;对需要完成学生资助类管理系统毕业设计或快速搭建微信小程序后台的开发者,具有一定的参考与复用价值。

1. 学生资助在线管理小程序:不只是毕设题目,而是一条能跑通的全流程业务链

学生资助在线管理小程序,说白了就是把高校里“贫困生认定 → 资助项目申请 → 学院初审 → 学校终审 → 名单公示 → 资金发放记录”这一串线下纸质流程,搬到微信小程序 + SSM + MySQL 这套技术栈上。很多同学看到这个题目,第一反应是“又是一个管理系统”,但真拿到源码开始跑的时候,才发现难点不在增删改查,而在微信登录怎么换 openid、多级审核状态怎么流转、材料图片怎么传、以及论文和代码怎么保持一致。这套方案你照着做能落地到什么程度,取决于你踩过多少坑。

这篇笔记我按自己做过的类似资助管理项目来讲:技术栈为什么这么选、数据库和接口怎么落地、审核流程状态机怎么设计,最后再把微信小程序开发里最容易翻车的几个点列清楚。读者如果是拿它做毕业设计,或者帮学院做一个内部小范围能用的资助申请工具,这篇都能直接当实施手册翻。

2. 技术栈拆解:微信小程序 + SSM + MySql 为什么是这条业务线的“安全牌”

2.1 小程序做前端、SSM 做后端、MySQL 存数据:三层职责先划清

资助管理这个业务有明确的“申请-审批”模型,数据之间是强关联的:一个学生可以提交多条申请,每条申请有多个审核节点,每个审核节点又有操作人和时间,公示结束之后还要生成资金发放记录。这种模型天然适合关系型数据库,MySQL 在这个场景里没有替代的必要性,也不存在为了“新”而换 NoSQL 的动机。学生端用微信小程序,是因为目标用户就是学生,微信扫一扫就能进,不需要额外装 App,更不需要在电脑上登录网页填表格。后端用 SSM(Spring + SpringMVC + MyBatis),是这个技术栈里最成熟的组合,网上资料多、遇到问题搜得到、答辩时老师也认可。

我一般建议把这条链路画成一张图放在论文的“系统架构”章节:小程序客户端 → HTTPS 请求 → SpringMVC 的 Controller → Service 业务层 → MyBatis 的 Mapper → MySQL 数据库。所有业务逻辑写在 Service 层,Controller 只做参数接收和结果封装,不要在里面写 SQL,这个分层如果乱了,后面加一个多级审核的功能会非常痛苦。对毕业设计而言,这个架构完全撑得起,面试和答辩时你也好解释“为什么这样分层”。

2.2 一次完整的资助申请请求,到底在前后端之间经历了什么

把链路画清楚,不是让你画架构图交差,是让你调试的时候能定位问题。我用一次“学生提交资助申请”的请求来串一遍:小程序端的学生用户点击申请按钮,前端把表单数据和材料图片的临时路径通过 wx.request 发给后端;后端的 Controller 先校验参数,把图片从临时目录转移到正式存储目录,再组装成一个申请实体交给 Service;Service 层查一下这个学生是不是已经存在、有没有重复申请,通过后调用 Mapper 往申请记录表插入一行;插入成功返回申请 ID,前端拿着这个 ID 跳转到“我的申请详情”页面。

这个过程中每一个环节都可能出问题,最常见的几个:一是小程序端 wx.request 的 URL 和 Controller 的 @RequestMapping 路径对不上,404;二是 POST 请求的 Content-Type 没设置成 application/json,后端拿不到参数;三是日期格式不一致,前端存的字符串是 2024-05-01,后端的 LocalDateTime 解析失败。这几个坑后面「避坑」章节会逐一讲,现在你只要记住:联调的时候先看控制台报错,再看 Network 面板返回的状态码,不要一上来就怀疑数据库。

2.3 SSM 项目的目录结构和依赖:照着搭不会迷路

拿到一个现成的 SSM 毕业设计源码包,第一步不是打开数据库脚本右键执行,而是先看它的目录结构。一个标准的、适合资助管理小程序的 SSM 工程,包名一般是 com.xxx.StudentAid 或个人标识类的主包名,里面按层分包:controller、service、mapper(也可以叫 dao)、pojo(或者 entity)、common(放统一返回结果和工具类)。resources 目录下放 Spring 和 MyBatis 的配置文件,比如 applicationContext.xml、spring-mvc.xml、jdbc.properties、mybatis-config.xml。

pom.xml 里重点看几个依赖坐标是否存在:spring-webmvc、mybatis、mybatis-spring、mysql-connector-java、fastjson 或 jackson(JSON 序列化用)。微信小程序端和传统网页端最大的差别在于登录方式——前端拿不到 cookie,所以你需要在后端放一个拦截器或 HandlerInterceptor 来处理 token 校验。这个小项目的做法通常是:用户微信登录成功后生成一个 token 返回给前端,前端每次请求在 header 里带 Authorization,后端拦截器放行 login 接口、拦截其余接口,在拦截器里解析 token 拿到用户身份。

<dependencies> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-webmvc</artifactId> <version>5.2.22.RELEASE</version> </dependency> <dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis</artifactId> <version>3.5.6</version> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>8.0.28</version> </dependency> </dependencies>

这段依赖只列了三个核心坐标,实际工程还要加 mybatis-spring、druid 连接池、fastjson。为什么强调连接池:如果直接用 DriverManager 获取连接,并发上来后数据库连接数会爆,小程序端的请求虽然量不大,但审核人员和学生同一时间段访问的情况很容易出现。选 druid 的原因是你可以在监控页看到当前连接数和慢 SQL,答辩时讲“我用 Druid 做连接监控”比空口说“我优化了性能”有说服力得多。

3. 落地步骤:从建库建表到小程序端发请求全打通

3.1 建库建表:资助管理系统的核心表和字段

拿到 SQL 脚本后,建议不要把写好的数据库文件直接导入就跑,先对照需求文档把表结构看一遍。学生资助管理系统最核心的五个数据对象:学生、资助项目、申请记录、审核记录、资金发放记录。我在做这个项目时表结构通常是这样的:

  • student 表:id 主键、student_no 学号、name 姓名、gender、college 学院、major 专业、className、phone、wx_openid 微信唯一标识、family_income 家庭年收入、poverty_level 贫困等级、create_time、status 状态。wx_openid 这个字段要建唯一索引,因为每个微信用户只能对应一个学生档案,如果重复插入会导致同一用户在资助中心看到两份身份信息。
  • funding_program 资助项目表:id、program_name 项目名称、amount 资助金额、quota 名额、apply_start_time、apply_end_time、description 申报说明、status 是否开放申请。
  • application 申请记录表:id、student_id、program_id、apply_time、status 当前状态、material_info 材料说明、audit_opinion 审核意见。这里 status 用整数表示,0 待审核、1 学院初审通过、2 学院初审驳回、3 学校终审通过、4 学校终审驳回、5 已公示。字段名叫 apply_status 也可以,但要跟后端的常量类保持一致。
  • audit_record 审核记录表:对应一次申请经历几次审核,每审核一次写一条记录。字段包括 id、application_id、auditor_id、auditor_role、audit_time、audit_result、audit_comment。
  • fund_dispatch 资金发放记录表:id、application_id、student_id、amount、dispatch_time、operator_id、pay_account 学生银行卡号、pay_status 发放状态。
CREATE TABLE `application` ( `id` INT NOT NULL AUTO_INCREMENT, `student_id` INT NOT NULL, `program_id` INT NOT NULL, `apply_time` DATETIME DEFAULT CURRENT_TIMESTAMP, `apply_status` TINYINT DEFAULT 0, `material_info` VARCHAR(1000), `audit_opinion` VARCHAR(500), PRIMARY KEY (`id`), KEY `idx_student_program` (`student_id`, `program_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

一个容易漏的细节:idx_student_program 这个联合索引。它的作用是防止同一个人对同一个资助项目重复申请。后端 Service 里查一次加一条唯一约束,双保险。如果没用联合索引,你只能靠业务代码里先 select 再 insert,但一旦两个人同时提交,还是可能插进去两条重复记录,这在答辩演示时会很尴尬。

字段设计上还有一个取舍:金额字段用 DECIMAL(10,2) 不要用 FLOAT。学生资助金额是精确值,不是统计量,用浮点类型在累加时可能出现 0.01 的误差,虽然不影响演示效果,但导师如果看到数据库设计文档里金额是 float,可能会质疑基本功。补贴银行卡号用 VARCHAR 存,不要用什么 bigint,因为有些卡号前面带 0。

3.2 最小接口清单:后端先把这六个接口写好,前端就能动

一个学生资助小程序要能“演示整个流程”,后端至少要有六个接口:微信登录接口、获取资助项目列表、提交资助申请、获取我的申请列表、审核接口(学院初审 + 学校终审可以走同一个,用角色区分)、公示名单查询。很多新手一开始就想着做权限管理、做公告管理,结果核心流程还没跑通,开发周期就拉长了。毕业设计评分看的是“业务闭环”,不是功能数量。

先讲微信登录接口。小程序端 wx.login() 拿到一个临时 code,传到后端; 后端用 appid 和 secret 调用微信的 jscode2session 接口换取 openid; 查 student 表里有没有这个 openid; 有就返回学生信息和 token,没有就提示“请先联系学院辅导员建档”。这里有一个要命的设计点:小程序的 appid 和 secret 不能硬编码在小程序前端代码里,因为小程序包可以被反编译,secret 泄露后别人能冒充你的小程序调用微信接口。正确做法是放在后端配置文件 jdbc.properties 旁边的 wx.properties 里。

@RestController @RequestMapping("/api/user") public class UserController { @Autowired private StudentService studentService; @PostMapping("/login") public Result login(@RequestBody Map<String, String> params) { String code = params.get("code"); if (StringUtils.isEmpty(code)) { return Result.error("code不能为空"); } // 1. 用code换openid String openid = wxService.code2Session(code); // 2. 查学生档案 Student student = studentService.findByOpenid(openid); if (student == null) { return Result.error(1001, "未建档,请先联系辅导员"); } // 3. 生成token并返回 String token = tokenService.createToken(student.getId()); return Result.success(token, student); } }

注意这里的 Result.error(1001, ...) 里的 1001 是业务错误码。小程序端判断逻辑不能只看 HTTP 状态码,因为登录接口 HTTP 永远返回 200,业务失败靠 body 里的 code 字段区分。我见过很多新手在微信生态里踩这个坑:后端返回 JSON 里 code=200 表示成功,前端却去判断 HTTP状态码是不是 200,结果接口通了但数据永远渲染不出来。联调时先约定好:HTTP 状态码只管网络层,业务成功与否看 JSON 里的 code。

其他几个接口的模式基本相同:Controller 接收参数 → Service 做业务校验 → Mapper 操作数据库 → 返回统一 Result。其中模板代码比较多,但建议不要偷懒用代码生成器直接拖一堆表生成 CRUD,因为资助申请这个业务几乎每个接口都带状态判断,生成的模板代码你还是要改,不如手写核心接口来得实在。

3.3 小程序端目录结构与 request 封装

微信小程序端拿到源码包,第一件事不是去看页面长什么样,而是打开 app.js、utils/request.js、pages 目录看结构。正常的分层是:pages 下面按业务模块分目录,每个模块下有 index.js、index.wxml、index.wxss、index.json 四个文件; utils 里放 request.js 和 auth.js; components 放公共组件。如果一个源码包把几十个页面全塞在 pages 下一层,那是没有工程规范,建议自己重构一下,答辩时这也是加分项。

request.js 的封装质量,决定了你前后端联调时会不会反复改代码。核心要做三层处理:基础路径拼接、token 自动注入、统一错误提示。下面是一个常用封装:

const BASE_URL = 'http://localhost:8080/api'; function request(url, method = 'GET', data = {}) { return new Promise((resolve, reject) => { wx.request({ url: BASE_URL + url, method: method, data: data, header: { 'Content-Type': 'application/json', 'Authorization': wx.getStorageSync('token') || '' }, success: (res) => { if (res.data.code === 200) { resolve(res.data.data); } else { wx.showToast({ title: res.data.msg, icon: 'none' }); reject(res.data); } }, fail: (err) => { wx.showToast({ title: '网络异常', icon: 'none' }); reject(err); } }); }); }

这段封装的逻辑说明:BASE_URL 在开发阶段用 localhost,真机预览时要改成局域网 IP 或已备案的 HTTPS 域名; token 从 storage 里取,每次请求自动带上,后端拦截器靠它识别身份; 成功与否的判断条件是 res.data.code 而不是 res.statusCode。封装完之后,页面里调用就变成 request('/user/login', 'POST', { code }),不用每个页面都重复写 header,也不会出现“这个页面没带 token”的低级错误。

注意一个细节:BASE_URL 建议单独放到 app.js 的 globalData 里,不要散落在每个页面的 js 文件里。否则后端起在 8080 端口,你在三个页面里写死了三个不同的 IP,联调时改到怀疑人生。经验做法是在 config.js 里统一管理,并且写注释说明“开发环境用局域网IP + 关闭域名校验,生产环境替换为备案域名”。

3.4 参数命名、时间格式、状态码:前后端联调里三个最容易玄学的地方

前后端联调翻车,九成不是逻辑问题,而是三个细节:参数名对不上、时间格式对不上、状态码约定不一致。先讲参数名。小程序端传给后端的字段叫 applyStatus,后端实体类字段叫 applyStatus,但数据库列名是 apply_status,这三者如果碰巧不一致,要么用 @JsonProperty 注解映射,要么干脆统一用驼峰命名传到后端再转换。我在实际项目中踩过一次:前端传 studentId,后端实体类叫 stuId,查了半天才发现是 Controller 里的 @RequestBody 反序列化时字段名不匹配,直接静默赋了 null。

时间格式是第二个坑。MySQL 的 datetime 类型在 JDBC 里默认映射成 java.sql.Timestamp,如果你的实体类用 java.util.Date,MyBatis 映射一般没事,但前端拿到后端返回的 JSON 时,如果后端没有配置统一时间格式化,返回的可能是一长串数字(1970 毫秒值格式),小程序端 new Date(毫秒值) 才能解析,否则显示红叉叉。建议在 spring-mvc.xml 里配一个全局的 Jackson 时间格式化,把 LocalDateTime 统一输出成 yyyy-MM-dd HH:mm:ss,这个问题直接消失。

状态码约定是第三个。建议从第一天就定义一个 Result 类,包含 code、msg、data 三个字段,code=200 表示业务成功,其他值表示业务失败,比如 1001 未建档、1002 不在申请期内、1003 重复申报。不要用 HTTP 200/500 去区分业务成功失败,也不要每个接口返回结构不一样——有的接口直接返回数组,有的接口返回对象,前端泛解析会非常痛苦。

4. 核心业务逻辑:贫困生认定、多级审核和公示状态的实现

4.1 贫困生认定链路:身份建档到材料提交

资助管理业务的起点不是“申请资助”,而是“贫困生认定”。学生第一次打开小程序时必须先完成建档:填写学号、姓名、学院、专业、家庭年收入、家庭成员数,上传贫困证明或低保证明图片。后端收到建档请求后,把材料图片存到服务器指定目录,把学生信息插入 student 表,status 设为 0(待认定); 辅导员在管理端看到待认定列表,核对材料后点击“通过”,学生才具备申请资助项目的资格。

这个链路里后端要做一个关键校验:同一个学号不能重复建档。因为学生在微信里授权后,你的系统拿到的是 openid,但学生本人可能换手机号、换微信,唯一稳定的是学号的身份证关联,所以 student_no 字段建议加 unique 约束。后端 Service 里要先查 student_no 是否存在,如果已经存在就直接提示“该学号已完成建档,请联系辅导员修改信息”,而不是又插入一条重复记录。

建档材料图片的上传,可以用小程序的 wx.uploadFile 接口,这个跟普通 request 不同,是 multipart/form-data 格式,后端要用独立的 upload 接口接收 MultipartFile,而不是 @RequestBody。上传成功后前端拿到图片 URL,再随建档表单一起提交。字符串拼接的 URL 要小心:微信开发者工具里看到的是 localhost 路径,真机上看不到,因为手机访问不到你自己电脑的 localhost。所以要封装一个 host 方法,把图片域名和接口域名统一管理,这是我在真实项目里被折磨过两个小时的问题。

4.2 多级审核与状态机:学院初审到学校终审的状态流转

资助申请提交后,要经历“学院初审 → 学校终审”两级审核。这个流程在后端实现时,我强烈建议画一个状态机图再写代码:0 待审核 → 1 学院初审通过 → 3 学校终审通过 → 5 已公示,或者 0 待审核 → 2 学院驳回 → 关闭,以及学院初审通过但学校驳回的路径 1 → 4。状态可以放在 application 表里一个整数字段,同时把每次审核的结论写进 audit_record 表,方便追溯。

审核接口的设计核心是“谁在操作、能不能操作这个状态”。后端从 header 的 token 里解析出当前用户是谁,查出该用户的角色(学院辅导员还是学校资助中心管理员),然后判断当前申请状态是否允许该角色审核。比如学院初审的记录,只有辅导员能审,管理员去审要返回“当前节点不允许此角色操作”。

public Result audit(Integer applicationId, Integer auditResult, String comment, Integer operatorId) { Application app = applicationMapper.findById(applicationId); if (app == null) { return Result.error("申请记录不存在"); } // 状态机校验:只有待审核允许初审,学院初审通过后允许终审 if (auditResult == 1 && app.getApplyStatus() != 0) { return Result.error("当前状态不可进行学院初审"); } if (auditResult == 3 && app.getApplyStatus() != 1) { return Result.error("当前状态不可进行学校终审"); } app.setApplyStatus(auditResult); applicationMapper.updateStatus(app); auditRecordMapper.insert(new AuditRecord(applicationId, operatorId, auditResult, comment)); return Result.success(); }

这段逻辑要特别注意:审核操作一定要先查后改,并且在 update 的 SQL 里带上条件 UPDATE application SET apply_status = ? WHERE id = ? AND apply_status = 前一个状态,防止两个操作员同时处理一个申请时后提交的把人家的结论覆盖。这是并发方向的问题,毕业设计一般不会考到分布式,但带上条件更新这个习惯,简历上可以写“防重复审核处理”。

驳回的场景也要想清楚:学院初审驳回后,申请状态变成 2,学生端能看到审核意见,但不能直接再提交同一资助项目。如果允许学生修改材料重新提交,最干净的做法是新增一条申请记录,而不是把驳回的记录修改状态。这样 audit_record 里能完整看到第一次被驳回的原因和第二次提交的轨迹,对后续审计和数据分析都有用。

4.3 公示与发放:怎么让前端把“待审批/已通过/已公示”区分开

学校终审通过后,资助名单需要公示,通常公示期是 3 到 5 个工作日。公示这个业务在数据模型上的表现是:申请状态变为 4 且到了某个公示截止时间后,状态变为 5(已公示结束)。很多源码包为了简单,把“公示中”和“已通过”混成一个状态,导致前端列表页上无法区分当前是公示中还是已经公示结束。这不是一个单纯的字段问题,而是业务时序的表达问题。

我的常见做法是:在 application 表加一个 public_time 字段,表示公示开始时间,公示截止时间用 funding_program 表里的公示天数推算。前端判断逻辑是:status=4 且当前时间小于截止时间,显示“公示中”;status=4 且当前时间大于截止时间,显示“已结束”。这个判断逻辑可以放在后端查询时算好返回给前端一个 derived status,也可以在 Service 层查询后统一转换。

资金发放记录是流程的最后一段。公示期结束无异议后,资助中心在管理端点击“确认发放”,后端生成一条 fund_dispatch 记录,状态为“待发放”或“已发放”。在这里要记住一点:发放记录和申请记录是一对一关系,数据库设计时可以用 application_id 做唯一索引。如果一个申请被拆成两笔发放,那 model 就变了,实际业务里没有这种需求,不建议做复杂。

在管理端页面上,最常用的是“按状态筛选”的表格:全部 / 待初审 / 初审驳回 / 待终审 / 终审驳回 / 公示中 / 已发放 。每个状态对应一种颜色标签。前端用 enum 对象统一映射状态值和显示文字,禁止把魔法数字散落在 wxml 里的 wx:if 判断中。这样改了后端一个状态值,前端不会出现“页面显示已通过,但接口返回的是数字 3”这种对不上的情况。

5. 学生资助小程序避坑实录:5 个高频翻车点和排查办法

5.1 微信登录一直报“code 无效”,问题可能不在后端

现象:小程序端调用 wx.login 拿到 code 后发给后端,后端请求微信接口换 openid 时提示 errcode 40029 code 无效。前端显示白屏或弹窗报错。

原因:最常见的原因有两个。一是后端拿着旧的 code 重复使用,微信的 code 是一次性的,用过了就失效;二是前后端调用时中间隔了太久,code 的有效期只有五分钟。另外一个隐蔽原因:后端请求微信接口时用的 appid 和小程序开发者工具里填的 appid 不一致。

解决:在前端每次登录时重新调 wx.login 获取新 code,不要在 storage 里缓存 code。后端收到 code 后立刻处理,不要做任何耗时的数据库操作再调微信接口。排查时先在后端日志里把 code 打出来,手工拿这个 code 去微信测试接口试一下,如果能换到 openid,就是代码时序问题;如果也报错,就是 appid/secret 配置问题。

5.2 wx.request 请求 404:路径、域名校验和 HTTPS 三座大山

现象:小程序开发者工具点按钮请求,Network 面板里看到 404。初学者第一反应是后端接口不存在,实际可能是三种情况:Controller 的 @RequestMapping 路径写的是 /api/user/login,小程序端 BASE_URL 已经带了 /api,又拼了一次 /api/user/login,变成双重 /api;或者 BaseURL 是 http://localhost:8080,但真机调试时访问的是自己电脑的 localhost,手机当然找不到;再或者线上小程序没配合法域名,请求直接被拦了。

解决:开发阶段在微信开发者工具右上角点“详情 - 本地设置 - 不校验合法域名、web-view(业务域名)、TLS 版本以及 HTTPS 证书”,这样可以用 http + IP 联调。真机预览时把 BASE_URL 改成电脑的局域网 IP,比如 http://192.168.x.x:8080,手机和电脑必须在同一网段。排查 404 时,先在浏览器里直接访问这个 URL + 参数,能通说明后端没问题,问题只在小程序端。这一步能帮你省下至少半小时。

5.3 中文乱码和金额显示不对:字符集与数据类型

现象:学生姓名在数据库里是正常的,前端展示时全变成“??”。或者资助金额 2000 元,数据库存的是 2000.00,前端显示 20.00。

原因:数据库表或 JDBC 连接串没指定 utf8mb4,MySQL 默认可能是 latin1,中文插入后变乱码。金额问题多半是数据库字段用了 FLOAT,配合框架自动转换时精度丢失,或者后端返回给前端时把 2000.00 转成了 2000 字符串,前端当数字除以了 100。

解决:数据库连接串加 characterEncoding=utf8mb4 和 useUnicode=true,建表语句统一 CHARSET=utf8mb4。金额字段统一 DECIMAL(10,2),后端实体类用 BigDecimal,返回给前端时保持字符串格式,前端展示时直接渲染,不要做数学运算。如果前端需要计算总额,用 parseFloat 转一次再算,避免“2000.00”拼接成“20000.00”这种看起来像 bug 的字符串拼接问题。

5.4 审核状态被覆盖:两个人同时点审核,后提交的覆盖先提交的

现象:辅导员和管理员同时打开同一个申请列表,都审核同一个学生。A 先审核通过,B 后审核驳回,最终状态变成 B 的结果,A 的操作记录也丢了。

原因:这是典型的并发覆盖问题。后端代码是“先查出申请状态,判断合法,再 update 新状态”,两步之间没有做一个条件限制,后执行的 update 无条件覆盖了前一个。

解决:update 语句带上原状态条件,UPDATE application SET apply_status = #{newStatus} WHERE id = #{id} AND apply_status = #{oldStatus},如果返回影响行数为 0,说明状态已经被别人改过,直接提示“该申请已被其他审核人处理,请刷新列表”。这是最简单有效的行级乐观锁方案,不需要引入版本号字段。

5.5 材料图片上传失败:临时目录、大小限制和真机访问

现象:小程序端选完图片点提交,开发者工具里上传成功,真机上却传不上去,或者上传成功但后端保存的图片在后面前端展示时 404。

原因:开发工具里默认上传到 localhost,真机上传必须指向局域网可访问的 IP。后端没配置上传目录时,Spring Boot 的临时文件目录可能是系统 tmp,重启后图片就没了。图片大小超过后端 tomcat 的 maxPostSize 配置时,直接抛异常。展示时用的是相对路径,前端拼 CDN 前缀拼错了。

解决:后端单独配置 upload 目录,比如 /data/aid_uploads,不要放在项目根目录的 target 下,因为 Maven clean 会删掉 target 里的所有文件。Tomcat 的 maxPostSize 和 multipart 配置都调成 20MB 以上,前端在 wx.chooseMedia 里限制 sizeType compressed,压缩完再上传。上传成功后后端返回的 URL 必须是可以直接访问的完整路径,不要返回一个相对路径让前端去猜。你猜错的后果,就是公示期图片全部裂开,学生和辅导员双双向你反馈问题,这个场景在演示时特别尴尬。

6. 答辩演示前值得补的三块内容:日志留痕、数据看板、演示脚本

到这一步,核心流程已经通了,但“能跑”和“能过答辩”之间还有一段距离。我建议在交论文前补三个小功能,投入小、演示效果好,而且技术上都能讲出几句话。

第一个是登录日志和审核日志。用一张 login_log 表记录微信登录时间、openid、IP,用 audit_record 表记录每次审核的操作人和意见。答辩时老师如果问“系统怎么保证可追溯”,你直接打开数据库展示这两张表的结构和数据即可,比口头讲权限安全更有说服力。

第二个是管理端首页的数据看板。不用接任何图表库,用三个数字卡片展示当前总申请数、待审核数、公示中的项目数,外加一个按学院统计的申请人数表格。可以用一个聚合 SQL 实现,答辩时演示“从数据概览能看到这学期哪个学院申请人数最多”,会显得你不只写了 CRUD。

第三个是公示到期自动关闭的定时任务。Spring 的 @Scheduled 注解每天凌晨跑一次,把公示截止日期小于当前时间的申请状态从“公示中”改成“已公示结束”。这个功能属于加分项,代码量不大,但能证明你考虑到了业务闭环。

演示视频的拍法也有讲究。不要对着系统把每个按钮点一遍,而是按业务故事来拍:某学院贫困生张三打开小程序 → 完成贫困生认定 → 提交励志奖学金申请 → 辅导员初审通过 → 资助中心终审通过 → 名单公示 → 资金发放。这七个镜头能串成一个完整业务闭环,老师看完知道你确实理解了业务流程,而不仅是会做页面。拍摄时手机用另一台设备,不要用电脑录屏软件直接录,画面上出现一堆控制台日志反而减分。

我当年做类似的项目时,最失落的不是技术难点,而是演示录完才发现没人能看出这个系统是“学生资助”而不是“随便一个管理系统”。学生端的核心是“申请”-“查进度”,管理端的核心是“审核”-“公示”-“发放”,把这些关键词做成明显的功能入口,比在论文里写一百句系统介绍都有用。要是重来一遍,我会把首页的进度状态做成可视化的步骤条,学生一眼能看到自己的申请卡在哪一级。

希望这篇笔记能帮你在学生资助这条业务线上少走弯路,把精力花在流程完整性和演示说服力上,而不是纠结 404 和乱码。加油。

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

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/22 4:09:49

微信小程序+SSM架构下的学生资助审批管理系统设计

简介&#xff1a;一套面向毕业设计的学生资助在线管理小程序完整方案&#xff0c;基于微信小程序SSMMySQL架构&#xff0c;实现管理员、班主任、家长三类角色的协同流程&#xff0c;覆盖资助申请、评议、名单公示、申请复议等环节&#xff0c;适合计算机专业学生作为课程设计与…

作者头像 李华
网站建设 2026/10/22 4:09:29

Kylin V10服务器Xorg进程内存泄漏排查与治理实践

凌晨1点40分&#xff0c;监控系统把电话打到了我这里&#xff0c;一台Kylin V10服务器内存占用超过92%&#xff0c;业务系统已经明显卡顿。我登录进去第一件事就是看进程列表&#xff0c;结果发现罪魁祸首不是业务进程&#xff0c;而是Xorg进程&#xff0c;RSS已经涨到7.3GB&am…

作者头像 李华
网站建设 2026/10/22 4:09:01

昇腾AI服务器整机设计:从芯片到系统的工程逻辑与实操要点

1. 从一颗芯片到一台整机&#xff1a;昇腾AI服务器新品背后的工程逻辑第一次看到“基于昇腾AI&#xff0c;百信人工智能服务器再推新品”这个标题&#xff0c;我脑子里蹦出来的不是发布会PPT上的跑分数字&#xff0c;而是一个很实际的问题&#xff1a;从昇腾AI芯片到一台能稳定…

作者头像 李华
网站建设 2026/10/22 4:08:20

多模态大模型MLLM架构设计与训练实战指南

1. 从单模态到多模态&#xff1a;为什么我们需要MLLM这两年做模型的人应该都有一个共同感受&#xff1a;单模态模型的天花板越来越明显了。纯文本模型再卷&#xff0c;也就是在语言理解、代码生成、逻辑推理这些维度上做文章&#xff0c;它感知不到图像里的空间关系&#xff0c…

作者头像 李华
网站建设 2026/10/22 4:07:47

智驾与AI Agent融合深度解析:三层架构、落地场景与实操踩坑

1. 一场深访带来的认知刷新前阵子跟几位分别在主机厂、智驾方案商和AI应用团队做事的朋友做了一轮长聊&#xff0c;主题就一个&#xff1a;汽车智驾和AI Agent到底怎么融合&#xff0c;融合到什么程度了&#xff0c;哪些是真落地&#xff0c;哪些还在PPT里。聊完最大的感受是&a…

作者头像 李华
网站建设 2026/10/22 4:07:10

自动驾驶基座模型深度解析:从专用模块到通用AI能力体系

前两天在行业群里看到一场技术峰会发布会的核心内容&#xff0c;一家做量产辅助驾驶做了好几年的公司&#xff0c;把“基座模型”摆到了台面上。新闻稿不长&#xff0c;却把整套思路从自动驾驶一直延伸到AI能力体系。说实话&#xff0c;这类发布见过不少&#xff0c;但这回的差…

作者头像 李华