☰
基于Spring Boot与微信小程序的前后端分离预注册系统设计
2026/10/15 6:14:00 网站建设 项目流程

每年到开学季,总有一批学生要在报到现场排长队:填表、交材料、核对信息,每个环节都堆着人。我做这个学生预注册系统的原因很简单——把报名流程中那些重复、机械的采集和确认工作提前拆掉,让学生在到校之前就通过小程序完成信息提交和资格预审,后台管理人员只需在电脑上批量审核。说直白点,就是用一个前后端分离的 Web + 小程序方案,把"现场排队报名"变成"线上提前注册"。

这套系统我选型时定的组合是 Spring Boot + 微信小程序 + Vue,整体前后端分离。文章适合正在做毕业设计或课程项目的开发者参考,也适合想从零理清"小程序端、管理后台、后端API"三者如何协作的同学。我会把业务设计、数据库结构、接口实现、双端联动、联调部署这些环节的关键点和坑都过一遍,尽量做到拿到题目就能照着自己搭。

1. 预注册不是"报个名"那么简单:先看清业务本质

1.1 预注册的业务流程与真正的核心难点

很多同学看到"学生预注册"这个题目,第一反应是做一张表单、存一条记录,再让管理员能查到。这样想就偏了。预注册的本质是提前完成资格确认和分批引导,它比"填表存库"多了一层审核流转。

一个比较完整的预注册流程是这样的:

  1. 学院或教务人员先创建"注册批次",比如"2025年秋季本科新生预注册",设置批次开放时间、截止时间、可填报人数上限。
  2. 学生在微信小程序里选择对应批次,填写个人基础信息、上传证件材料照片,提交后进入"待审核"状态。
  3. 管理端(Vue后台)按批次查看学生列表,审核每条记录,标记"通过"或"驳回",驳回时通常要填原因。
  4. 小程序端实时获取审核状态,学生看到通过结果后完成后续操作(比如确认到校时间、预选宿舍意向等),状态变为"已确认"。
  5. 管理员按批次导出报名数据,用于后续分班、住宿安排。

这里真正的难点不是增删改查,而是状态机的完整闭环。如果只是存一个字符串字段,审核驳回之后学生改信息再提交,状态怎么回退?批次关闭了还能不能提交?管理员正在审核的时候学生把信息改了怎么办?这些细节才是系统能不能落地的关键。

1.2 为什么选 Spring Boot + 微信小程序 + Vue 这套组合

技术选型不是越新越好,而是要看开发效率、社区资料和团队熟悉度。我最终定下这套组合,有下面几点考虑。

第一,Spring Boot 对于中小型业务系统来说是性价比很高的后端框架。自动配置减少了大量 XML 配置,内嵌 Tomcat 让打 jar 包部署变得非常简单,Spring Data JPA 或 MyBatis 偷懒都行,生态成熟,遇到问题很容易搜到解决方案。对于时间紧的毕设项目,这一条就足够有吸引力。

第二,微信小程序是学生端最合理的载体。现在的学生手机里必然有微信,小程序用完即走,不需要安装 App,也不像 H5 那样要收藏网址。它天然具备微信授权的身份标识能力,一个wx.login就能拿到用户唯一标识,免去了自己设计账号体系的麻烦。

第三,管理端用 Vue 做前后端分离,是考虑到后台操作界面需要频繁迭代。用 Vue 写管理页面非常顺手,列表、表单、筛选、弹窗这些组件生态齐全,开发速度比在服务端里塞一个模板引擎快得多。前后端分离后,后端只需要输出 JSON 接口,小程序端和管理端都通过 HTTP 调用同一套 API,代码复用率高。

注意:选择"前后端分离"并不仅仅是为了写起来爽,它意味着你要提前设计好接口规范、Token 鉴权方案和跨域处理。这些体积会在联调阶段集中爆发,后面我会专门讲。

2. 数据库设计:把注册流程"固化"成一张状态网

2.1 核心表结构拆解与字段设计思路

数据库设计这个环节,我建议先画清楚业务对象,再落表。预注册系统的核心对象主要有五类:批次、学生用户、预注册申请记录、审核日志、通知记录。下面是我在这套系统里实际使用的核心表设计。

先看注册批次表register_batch:

字段类型说明
idbigint主键
batch_namevarchar(100)批次名称,如"2025年秋季预注册"
start_timedatetime开放时间
end_timedatetime截止时间
register_limitint人数上限,默认0表示不限
statustinyint批次状态:1草稿,2已发布,3已结束
create_timedatetime创建时间

再看用户主表student_user:

字段类型说明
idbigint主键
openidvarchar(64)微信 openid,唯一索引
real_namevarchar(50)姓名
id_numbervarchar(20)身份证号
phonevarchar(20)手机号
avatar_urlvarchar(255)头像地址
roletinyint1学生,2管理员
create_timedatetime创建时间

然后是核心的预注册申请表register_apply:

字段类型说明
idbigint主键
batch_idbigint关联批次ID
student_idbigint关联学生用户ID
student_typetinyint新生/转校生/往届
major_idbigint意向专业ID
statustinyint1草稿,2已提交待审核,3审核通过,4审核驳回,5已确认
ext_infojson/text扩展信息,存各批次自定义字段
reject_reasonvarchar(255)驳回原因
submit_timedatetime提交时间
audit_timedatetime审核时间

最后是一张审核日志表audit_log和一张通知记录表notify_log,分别用来记录每次状态变动和给用户发送的消息。

这里有几个容易踩的坑,我提醒一下:

  • 不要把ext_info设计成几十个独立的可空字段。不同批次的表单字段可能完全不一样,灵活的做法是存 JSON 字符串,查询时如果需要按具体字段筛选,再考虑拆列或建索引。
  • 身份证号、手机号这类敏感字段建议单独处理,如果需要明文展示,至少做好权限控制,返回接口时不带多余信息。
  • openid必须加唯一索引,因为同一个微信号同一个小程序只会对应一个 openid,它天然是用户表的业务主键。

2.2 注册状态流转:为什么用整数字段而不是复杂状态表

状态流是预注册系统里最容易写乱的地方。我用了status整数段,整个系统大概有五种状态:

  1. 草稿:学生填了一部分还没提交,可以随时修改。
  2. 已提交待审核:学生点了提交,管理员可见,学生自己不可再改。
  3. 审核通过:管理员审核通过,系统自动写入通过时间。
  4. 审核驳回:管理员填了驳回原因,学生可以修改后重新提交(状态回到2)。
  5. 已确认:学生看到审核通过后,在小程序上点击"确认并补充信息",完成最终注册。

为什么不设计一张状态流转表?因为对于这种规模的系统,多一张状态表并不会带来业务上的便利,反而让每次状态更新都变成一次多表写入。用一个字段存当前状态,配合audit_log记录历史记录,已经足够支撑回溯需求。如果未来要做复杂的审批流、多人协作审核,再考虑引入状态机框架也不迟。

我在实际编码时还在 Service 层加了一个checkStatusChange(current, target)的校验方法,专门判断状态迁移是否合法。比如"审核通过"状态下不允许学生直接修改信息重新提交,"草稿"状态下管理员不需要看到待办列表。这样做的目的是把状态规则收敛到一个地方,避免 Controller 里到处都是if (status == 3)的散装判断。

3. 后端实现细节:登录鉴权与表单数据的"安全性错觉"

3.1 小程序登录态的设计:openid 换 token,而不是存明文

小程序端和后端交互时,绕不开微信登录。很多人第一次写这个会对wx.login有误解——code不是后端能直接使用的身份信息,它必须通过后端调用微信接口换取openid和session_key,然后后端再生成自己的会话凭证。

在我的实现里,小程序打开后第一步是wx.login拿到 code,然后调用后端接口/api/auth/login,后端通过HttpClient或 Hutool 工具类请求微信官方接口jscode2session,拿到openid和session_key。这个openid查用户表,如果不存在则自动注册一个基础用户并返回token;如果存在则直接生成token返回。

关键点是:**session_key绝对不要返回给前端**。它是用来解密手机号等敏感信息的密钥,一旦泄露,整个会话都不安全。后端只需要把openid跟自己的用户表绑定,然后签发一个自己的加密 Token 就行。

这里我用的是两层 Token 方案:

  1. 学生端登录后拿到student_token,有效期设为 2 小时,后续请求在请求头X-Token中携带。
  2. 管理端走的是独立的账号密码登录,签发admin_token,有效期 12 小时。

后端用一个拦截器统一处理:

public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) { String token = request.getHeader("X-Token"); if (token == null || !jwtUtil.verifyToken(token)) { response.setStatus(401); return false; } String role = jwtUtil.getRole(token); if (!"admin".equals(role)) { response.setStatus(403); return false; } request.setAttribute("userId", jwtUtil.getUserId(token)); return true; }

这样管理端接口加了/api/admin/**前缀统一做角色校验,学生端接口走/api/student/**做基本登录校验,权限边界非常清晰。

3.2 表单校验和文件上传:不能信任"前端已经校验过了"

预注册系统里免不了要上传证件照、头像、体检表等图片。小程序端通常用wx.chooseImage选图后通过wx.uploadFile直接传到后端接口。这里有一个很常见的坑:图片体积和类型不能只靠前端控制。

一个wx.uploadFile默认上传的文件,如果后端使用 Spring 的MultipartFile接收,默认单文件最大是 1MB。学生手机拍的照片动辄三五兆,直接报错。我的做法是在配置类里调大限制:

spring: servlet: multipart: max-file-size: 20MB max-request-size: 50MB

但调大不等于不限制,我还在后端做了两次校验:第一次在进入 Controller 之前,用拦截器判断文件后缀名是否在白名单内(jpg、png、pdf),第二次在业务代码里用第三方工具类读取文件头判断真实格式。为什么做两次?因为修改后缀名绕过校验是入门级攻击手段,直接读文件头最保险。

表单本身的校验同样不能省。小程序端填了姓名和身份证号,后端还要再做一次正则校验,身份证号格式、手机号格式都要查。我在 Service 层封装了一个ValidateUtils,专门处理这些规则。有人觉得重复校验啰嗦,但线上的真实请求可不都来自你的小程序,用 Postman 直接发请求绕过前端太简单了。

4. 双端联动:小程序端与 Vue 管理端怎么配合

4.1 管理后台的功能布局与接口调用封装

Vue 管理端我使用的是 Vue 2 + Element UI 的组合,页面主要分三类:批次管理、申请审核、数据统计。

批次管理页负责增删改查注册批次,核心操作是"发布批次"和"结束批次"。考虑到批次时间必须由后端校验,前端表单提交的数据只做展示性校验,真正的时间逻辑判断放在 Service 层。比如发布一个批次时,后端必须检查当前时间是否在start_time和end_time之间,防止有人通过接口直接造一个过期批次。

申请审核页是整个后台的流量中心。列表按批次过滤,默认展示"待审核"状态的学生记录,每条记录右侧有"通过"和"驳回"两个按钮。驳回时弹出dialog要求填写原因,原因不为空才能提交。这里有一个细节:审核通过和驳回都是覆盖式操作,操作前必须弹二次确认框,防止误触。

接口请求的封装上,我统一在src/utils/request.js里基于 axios 封装了一个实例:

service.interceptors.response.use( response => response.data, error => { if (error.response.status === 401) { localStorage.removeItem('admin_token'); router.push('/login'); } Message.error(error.response.data.message || '请求失败'); return Promise.reject(error); } );

这样所有接口只需要关心业务数据,鉴权失败和错误提示统一处理,代码量减少很多。管理端用到的接口前缀是/api/admin/batch、/api/admin/apply这类,跟学生端天然隔离。

4.2 小程序端的页面流转与状态同步

小程序端按照"登录 - 批次列表 - 填写信息 - 提交结果 - 审核状态"五步设计页面。

登录页不需要用户输入账号密码,进入时静默调用wx.login,登录完成后把 token 存到wx.setStorageSync。其实这一步可以在 App 启动时刻完成,不必占用一个独立页面。品牌形象好的做法是做一个启动封面页,等待登录成功后自动跳转。

填写信息页是小程序端最复杂的页面,因为要适配不同的批次字段。我的做法是:

  1. 进入页面时请求/api/student/batch/{batchId}/form-config,后端返回该批次需要展示的字段列表及类型(文本、下拉、日期、文件上传)。
  2. 前端根据字段配置动态渲染表单组件。
  3. 提交时前端把表单数据统一组装成ext_infoJSON 对象传给后端。

这样带来的最大好处是:新增一个批次不用重新发布小程序,后端配置好字段就能用。

提交结果页展示两种状态:审核中(展示"预计 X 个工作日内审核")和审核完成(展示通过或驳回原因)。如果被驳回,页面上要有"重新修改"按钮,点击后回到填写信息页,此时所有字段自动回填上次已保存的数据。

审核状态同步我做了两件事:提交成功后的页面上放一个onPullDownRefresh下拉刷新,随时拉最新状态;小程序 App 的onShow事件里也触发一次状态查询接口,保证从旧页面切回来时数据不过期。不要依赖 WebSocket 推送,毕竟小程序后台消息推送限制很多,学生端的轻量轮询已经足够。

5. 一路踩过来的坑:联调、跨域、域名与真机调试

5.1 本地联调时的跨域处理方案

前后端分离项目在本地开发时,小程序开发者工具里可以勾选"不校验合法域名",但管理端 Vue 开发服务器和后端 Spring Boot 之间依然存在跨域问题。

我的做法是开发环境用 Vue CLI 的devServer.proxy转发,生产环境用 Nginx 配置反向代理,同时后端也配置了全局 CORS 过滤器,双保险。

Vue 配置如下:

// vue.config.js module.exports = { devServer: { proxy: { '/api': { target: 'http://localhost:8080', changeOrigin: true } } } };

后端 CORS 配置:

@Configuration public class CorsConfig { @Bean public CorsFilter corsFilter() { CorsConfiguration config = new CorsConfiguration(); config.addAllowedOriginPattern("*"); config.addAllowedMethod("*"); config.addAllowedHeader("*"); config.setAllowCredentials(true); UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(); source.registerCorsConfiguration("/**", config); return new CorsFilter(source); } }

这里有一个细节值得注意:allowedOrigin("*")和allowCredentials(true)不能同时开,浏览器会拒绝带凭证的跨域请求。Spring Boot 新版本里addAllowedOrigin("*")配allowCredentials(true)大概率报错,要改用addAllowedOriginPattern("*")搭配处理,我在旧版本项目上踩过一次,排查了半个小时才发现是这里的问题。

5.2 小程序上线前的域名与 HTTPS 配置

小程序线上环境对请求域名有严格限制:必须是 HTTPS,且域名必须在小程序后台配置为 request 合法域名。这意味着后端上线不能直接暴露 IP 加端口,需要一套完整的域名和证书。

我第一次部署用的方案是:

  1. 云服务器上装 Nginx,监听 443 端口。
  2. 配置 SSL 证书(现在有免费的 DV 证书可用)。
  3. Nginx 把/api/前缀的请求反向代理到内网的 Spring Boot 8080 端口。

Nginx 配置核心部分:

server { listen 443 ssl; server_name your-domain.com; ssl_certificate /etc/nginx/cert/fullchain.pem; ssl_certificate_key /etc/nginx/cert/privkey.pem; location /api/ { proxy_pass http://127.0.0.1:8080; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; } }

部署完成后,小程序开发者工具里还需要手动把域名添加到"request 合法域名"列表,上传体验版之前先做一次真机预览。真机调试和开发者工具里的最大差异是网络环境:工具里勾选"不校验合法域名"能跳过 HTTPS 校验,真机上不行,所以很多问题真机才暴露。建议提前准备一个二级域名专门用于测试,避免上线前手忙脚乱。

上传小程序代码时还要注意,小程序包体不能超过 2MB。如果图片资源很多,要么走云端文件地址,要么精简静态资源。我用的是把证件照等上传文件直接存服务器本地目录,数据库只存 URL,这样小程序包体只包含页面代码,绝不会超限。

5.3 几个容易忽略的经典问题

最后整理几个实际项目中非常容易出现的问题:

  • 时间问题:小程序端展示时间要格式化时区,后端存 UTC 或本地时间必须达成一致。我统一用LocalDateTime存数据库,返回给前端的 JSON 统一格式化为字符串。
  • 并发重复提交:学生双击提交按钮,可能产生两条申请记录。我在register_apply表对student_id + batch_id加唯一索引,从数据库层面兜底,同时在提交接口进入时先查一次是否已有正在审核的记录。
  • 数据库连接池默认配置:Spring Boot 2.x 默认 HikariCP 连接池很稳,但默认池大小可能不够用。如果是多人和小程序并发测试,建议在配置里明确设置minimum-idle和maximum-pool-size,避免高峰期连接等待超时。
  • 日志记录:审核操作必须记录操作人,不只是靠audit_log表,还要在应用日志里把完整的请求参数和结果打出来。遇到争议时可以快速定位到底是谁在什么时间改了什么内容。

6. 可扩展的方向与实际操作体会

这个预注册系统做完后,我又补了几个比较实用的扩展点,如果你时间充裕,可以试着加上去。

一是导出 Excel 功能。管理员审核完所有学生记录后,需要按批次导出名单。直接用 Apache POI 生成 XLSX 文件,后端接口/api/admin/apply/export返回文件流,Vue 端用blob下载。这个功能在实际办公场景里几乎是刚需,加上之后系统完整度提升很大。

二是订阅消息提醒。微信小程序可以发送一次性订阅消息,审核通过后触发通知,学生微信上收到"您的预注册审核已通过"的提醒。这里要注意的是,订阅消息需要用户提前在小程序里点击授权按钮,且一次性订阅只能发送一次对应模板消息,设计流程时要把授权时机放在"提交申请"之后。

三是数据统计看板。管理端首页放几个统计卡片:总报名人数、各专业报名分布、待审核数量、通过率。SQL 用group by统计即可,前端用 ECharts 画几个基础图表,整个系统的演示效果会明显上一个档次。

我在实际带项目时经常遇到的一个问题是:开发者把大量精力花在花哨的页面上,却忽略了业务逻辑的严谨性。比如审核驳回后学生能不能重新提交,这个看起来不显眼的功能,实际用起来却发现状态被卡死,只能改数据库手动处理。我的建议是,动手写代码之前先把状态流转图画出来,哪怕只画在纸面上,都要比边写边想清晰得多。

这套系统的实现难度属于中等偏上,但因为它完整覆盖了小程序端、管理端、后端、数据库、部署五大环节,做完之后对整个前后端分离开发流程的理解会非常扎实。如果你正好是第一次接触微信小程序,建议先单独跑通一个登录接口,确认能拿到 openid 再开始写业务代码,否则很容易上来就被登录流程卡住,后面越做越没信心。

我个人最后一点体会是:做这类系统,先做"丑但完整"的闭环,再做"好看但缺功能"的壳子。预注册的核心价值在于流程的完整性——从学生注册、管理员审核、到状态同步缺一不可。把这条主线跑通之后,再去打磨按钮样式、页面动画,整个项目的节奏就会从容很多。

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

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

立即咨询