简介:在毕业设计开发中,微信小程序凭借免安装、即用即走的特点,成为政务服务、预约管理等轻量级应用的首选载体。一个完整的预约系统通常由小程序前端、Spring Boot后端、MySQL数据库及配套文档组成,涉及WXML页面渲染、RESTful接口设计、Token鉴权、数据库表关联等核心环节。理解前后端分离的数据流——从wx.login获取code,到后端换取openid并签发token,再到预约业务的状态流转与防重复校验——是掌握这类项目的关键。同时,合法域名配置、本地图片路径、MySQL时区编码等工程细节也直接影响系统能否稳定运行。本文按实际项目拆解路径,梳理微信小程序毕业设计的架构、数据库、登录权限、核心业务与答辩要点,帮助开发者快速跑通源码并深入理解每段逻辑,从容应对技术提问与功能演示。 做毕业设计,最怕的不是功能做不出来,而是打开一个源码包,发现几百个文件堆在一起,根本不知道从哪儿看起。标题里写着“完整前后端+mysql+说明文档+LW”,听起来东西挺全,但“完整”这两个字恰恰是最唬人的——代码能跑起来和能讲清楚,是两码事。
这篇博文就按我平时拿到一个毕设源码后的拆解顺序来写,把“最多跑一次”微信小程序这个项目从架构、数据库、登录鉴权,到核心业务代码、踩坑点、答辩准备,一条线全部梳理清楚。看完你不仅能把这个项目跑起来,还能知道每一块代码为什么这么写,答辩的时候老师怎么问都接得住。
1. 项目定位与功能拆解:这个毕业设计到底在做什么
1.1 别再被标题唬住:整个项目由哪几部分组成
先把这个zip里的东西拆开看。一个标准的微信小程序毕设,实际上是由四个独立但又互相依赖的部分组成的:
- 小程序前端:用户手机微信里打开的那个界面,基于微信小程序原生框架开发,负责页面展示、用户交互、调用wx.request发请求。标题里说的“前后端”指的就是这一层和后端服务层。
- 后端服务:跑在服务器上的Java程序,一般是Spring Boot项目,负责处理业务逻辑、操作数据库、给小程序提供JSON格式的接口。前端只是“皮”,真正的数据都在这层管着。
- MySQL数据库:存用户、办事事项、预约记录、管理员账号这些结构化数据的地方。MySQL在毕设里属于“够用且主流”的选择,导师不会在这上面挑刺。
- 说明文档/LW文档:毕业设计说明书或者配套论文,用来凑“文字工作量”的。这块很多同学不重视,实际上答辩时老师翻得最多的就是它。
拿到源码包的第一件事,不是急着跑代码,而是先看目录结构,分清哪个是前端工程、哪个是后端工程、哪个是数据库脚本。一般数据库脚本会以.sql文件存在,前端是一个独立的文件夹,后端是Maven或Gradle工程。如果你打开压缩包发现所有文件混在一起没分类,那就要自己手动按目录重新整理一遍,不然编辑器都打不开。
1.2 用户端功能清单:查、约、办、评一条线
“最多跑一次”这个概念,落到产品功能上,核心就是四件事:查事项、约时间、办业务、看进度。我把一个常规毕设项目的用户端功能整理成了一张表,你对照着源码里的pages目录看,基本能一一对应上:
| 功能模块 | 页面路径(常见命名) | 核心作用 |
|---|---|---|
| 微信登录 | pages/login / index | 获取用户微信身份,绑定openid |
| 办事指南 | pages/guide / list | 按分类展示所有办事事项,附材料清单、办理流程、咨询电话 |
| 事项搜索 | pages/search | 根据关键词模糊匹配事项名称 |
| 在线预约 | pages/appointment / book | 选日期、选时段、填个人信息,提交预约 |
| 预约记录 | pages/my/appointments | 查看自己约过的事项、当前状态 |
| 办件进度 | pages/progress / detail | 查看当前事项审批到哪一步,如“待审核/审核通过/已办结” |
| 个人中心 | pages/my/index | 展示头像昵称、我的预约、意见反馈入口 |
| 意见反馈 | pages/feedback | 提交文字反馈,方便“事后追溯” |
为什么说这四个环节是一条线?因为用户实际操作路径是闭环的:先搜索或浏览找到自己要办的“事项”,再查看这个事项需要准备什么材料,然后预约一个时间去线下窗口,办完之后随时打开小程序看进度,最后办结完还可以给个评价。这个闭环是项目设计的亮点,写论文的时候“业务流程设计”那一章,直接按这条主线画时序图就行。
1.3 管理端功能:毕设加分的关键角色
很多同学做一个微信小程序毕设,只做了用户端,结果答辩时老师问“你的系统谁来管理数据和安排预约”,一下子就答不上来。所以这个标题里既然说了“完整前后端”,那必然包含一个管理后台。管理端常见的实现方式有两种:
- 方式一:后端再用一个Web管理页面,比如用Vue或Thymeleaf写一个后台管理系统,跑在浏览器里。这种方式工作量更大,但显得项目更完整。
- 方式二:小程序里区分用户角色,管理员登录后小程序会多出几个管理页面。这种方式实现起来更简单,也是很多毕设源码的选择。
管理端的功能一般围绕以下几条:
- 事项管理:发布、修改、下架办事事项。比如“办理居住证”需要哪些材料、承诺几天办结,这些说明文字都是管理员维护的。
- 预约审核:用户在线上提交预约后,管理员可以在后台看到预约列表,决定“通过”还是“驳回”,驳回时还能填理由。这一步是整个项目业务逻辑里最有技术含量的地方。
- 办件状态推进:把预约记录的状态从“已预约”改为“办理中”,最后改为“已办结”。
- 公告与通知:发布停办通知、节假日安排等。
- 数据统计:统计每天、每月新增预约量、办结量,用表格或图表展示。
一句话总结这个项目的本质:它是一个带角色权限的预约管理系统,只不过业务场景换了。理解了这一点,后面看代码就会非常轻松——因为预约管理的通用逻辑(增删改查+状态流转+统计)在别的项目里也是一样的套路。
2. 技术选型与前后端架构设计
2.1 前端为什么选原生小程序,而不是uniapp
现在很多教程一上来就推荐用uniapp做跨端开发,一套代码能同时编译成微信小程序、H5、App。但对于毕设而言,我强烈建议优先用微信小程序原生框架,原因是:原生框架结构简单、运行稳定、调试工具链成熟,而且网上相关的问答和示例最多。你在做的时候遇到一个原生API的问题,搜索五分钟就能找到答案;换成uniapp,很可能踩到的是框架本身的坑,搜索半天也未必对症。
这个项目如果按原生小程序开发,核心技术栈是这样的:
- WXML:写页面结构,相当于网页的HTML。
- WXSS:写样式,相当于网页的CSS,支持rpx自适应单位。
- JavaScript / TypeScript:写页面逻辑和请求封装。
- 微信开发者工具:本地开发调试、上传预览、查看控制台报错。
原生小程序启动时会加载app.json文件,它声明了所有页面路径、窗口样式、TabBar配置。你在源码里看到pages数组里的每一项,对应一个页面的文件“四件套”:.wxml、.wxss、.js、.json。这个结构本身没有太多高端技巧,但它是后续所有页面开发的地基。
2.2 后端为什么是Spring Boot + MyBatis Plus
Java方向的毕设,后端十有八九是Spring Boot。原因很现实:Spring Boot把大量的配置简化了,内嵌Tomcat,一个java -jar就能启动,而且围绕它的生态非常完整。这个项目的后端你可以按经典的三层架构来看:
- Controller层:接收前端请求,返回JSON,不写业务逻辑。
- Service层:业务逻辑的所在地。比如预约之前校验这个时间段是否已满,就是在这层实现的。
- Mapper层(DAO层):和MySQL打交道。如果用的是MyBatis,就是写XML里的SQL;如果用的是MyBatis Plus,那连SQL都能省掉大半。
MyBatis Plus在毕设项目里几乎是“作弊器”一样的存在。它内置了通用Mapper,单表增删改查不需要自己手写SQL。比如你要查一个用户的所有预约记录,MyBatis Plus里写一句appointmentMapper.selectList(new LambdaQueryWrapper<Appointment>().eq(Appointment::getUserId, userId))就搞定了,根本不用自己拼SQL字符串。
2.3 前后端分离的数据流设计
微信小程序的前后端分离,和网页端的“前后端分离”不完全一样。网页端有跨域问题需要配CORS,小程序端没有CORS的概念,但有一个更硬性的限制:所有请求的域名必须是小程序后台配置的合法域名,而且必须是HTTPS。
整个项目的一次典型请求链路,以“提交预约”为例:
- 小程序端通过
wx.request发起POST请求,URL指向https://你的域名/api/appointment/add。 - 请求头里带着token,用来告诉后端“我是谁”。
- Spring Boot后端的拦截器先拦截这个请求,校验token是否有效。
- 校验通过后,请求进入Controller,Controller接收JSON参数,调用Service。
- Service里先查时段是否已约满,再查出当前用户的openid,把预约数据封装好,调用Mapper写入MySQL。
- 数据落库后,返回一个统一的结果对象,比如
{ "code": 200, "msg": "预约成功", "data": null }。 - 小程序端拿到响应后,
wx.showToast弹窗提示“预约成功”,并跳转到“我的预约”页面。
这套链路写清楚之后,代码里很多看似零散的文件就能对号入座了。前端看utils/request.js,后端看config包下面有没有拦截器配置,再看controller包里的类名,整个项目的骨架就浮出水面了。顺带说一句,如果你拿到的源码里前端请求全部指向http://localhost:8080,这也是正常的——本地开发阶段常用这种方式,但要跑通必须得在开发者工具里勾选“不校验合法域名”。后面踩坑部分我会详细讲。
3. 数据库设计:七张核心表的字段与关联
3.1 用户表与微信登录态的存储
数据库是整个项目里“最值钱”的部分,因为功能再花哨,数据存不下来全是白搭。我先给你一套符合这个项目业务逻辑的建表方案,你可以对照着源码里的.sql脚本看,如果源码的脚本字段不同,思路是相通的。
第一张表是用户表,我习惯起名user:
CREATE TABLE `user` ( `user_id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '用户ID', `openid` varchar(64) NOT NULL COMMENT '微信openid,唯一标识', `nickname` varchar(50) DEFAULT '' COMMENT '昵称', `avatar` varchar(255) DEFAULT '' COMMENT '头像URL', `phone` varchar(20) DEFAULT '' COMMENT '手机号', `create_time` datetime DEFAULT NULL COMMENT '注册时间', PRIMARY KEY (`user_id`), UNIQUE KEY `uk_openid` (`openid`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='微信用户表';有一个细节必须注意:openid是用户在小程序里的唯一身份标识,同一个微信用户在不同小程序下的openid不同,但在同一小程序下永远不变。所以注册登录就是拿openid查表,查不到就新插入一条,查到了就更新一下昵称头像。这个“先查后插”的逻辑,就是整个登录功能的全部秘密。
还有一点,用户表里不建议直接用微信的昵称当数据库的唯一字段,因为微信允许用户改昵称,而openid是永远不变的。你写论文的时候,在“数据库设计”这一章把openid设计成唯一键,老师看了会觉得你懂业务。
3.2 办事事项表与预约记录表:核心业务落点
第二张核心表是办事事项表,可以叫affair或者thing,字段大致是:
CREATE TABLE `affair` ( `affair_id` bigint(20) NOT NULL AUTO_INCREMENT, `category_id` bigint(20) DEFAULT NULL COMMENT '所属分类ID', `title` varchar(100) NOT NULL COMMENT '事项名称,如:办理居住证', `cover` varchar(255) DEFAULT '' COMMENT '封面图', `material` text COMMENT '所需材料清单,支持换行文本', `process_desc` text COMMENT '办理流程说明', `address` varchar(255) DEFAULT '' COMMENT '线下办理地点', `office_time` varchar(100) DEFAULT '' COMMENT '窗口工作时间', `consult_phone` varchar(20) DEFAULT '' COMMENT '咨询电话', `status` tinyint(4) DEFAULT '1' COMMENT '1上架 0下架', `create_time` datetime DEFAULT NULL, PRIMARY KEY (`affair_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='办事事项表';这张表的信息完全由管理员通过后台维护,用户端只是展示。所以它是最标准的CRUD表,增删改查,没什么复杂的。
第三张核心表是预约记录表,整个系统里最有“含金量”的业务表:
CREATE TABLE `appointment` ( `appointment_id` bigint(20) NOT NULL AUTO_INCREMENT, `user_id` bigint(20) NOT NULL COMMENT '预约用户ID', `affair_id` bigint(20) NOT NULL COMMENT '预约事项ID', `appointment_date` date NOT NULL COMMENT '预约日期', `time_slot` varchar(20) NOT NULL COMMENT '时间段,如 09:00-10:00', `status` tinyint(4) NOT NULL DEFAULT '1' COMMENT '1待审核 2已通过 3已驳回 4已办结', `remark` varchar(200) DEFAULT NULL COMMENT '用户备注', `audit_remark` varchar(200) DEFAULT NULL COMMENT '审核意见', `create_time` datetime DEFAULT NULL, PRIMARY KEY (`appointment_id`), KEY `idx_user_id` (`user_id`), KEY `idx_date_slot` (`appointment_date`, `time_slot`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='预约记录表';这张表里最容易被忽略的是联合索引idx_date_slot。为什么要建这个索引?因为业务里经常要做这样一件事:查某一天某个时间段已经被约了多少人。如果没有索引,数据量大了之后每次查询都是全表扫描,放在论文里写“通过联合索引优化高频查询”就是一个小小的加分项。
3.3 管理端需要的辅助表
除了上面三张核心表,还需要几张辅助表:
- 分类表(category):存“户籍”“社保”“税务”这类分类,用于前台按分类筛选。
- 管理员表(admin):存管理员的用户名、密码、角色。密码绝对不能明文存储,至少要用MD5或BCrypt加密后入库。很多毕设源码明文存密码,你可以改成加密版本,答辩时能加分。
- 公告表(notice):存管理员发布的公告标题和内容,在小程序首页滚动展示。
- 反馈表(feedback):存用户提交的意见反馈内容和联系方式。
这四张表的字段都行逻辑比较简单,基本就是主键加内容字段加时间字段,读懂核心三张表之后,几乎不用花时间。要注意表与表之间的关系:预约表通过user_id关联用户表,通过affair_id关联事项表;事项表通过category_id关联分类表。这就是教材上说的“外键关联”,虽然物理上可以不建外键约束,但逻辑上必须通过字段把数据串起来。
4. 微信登录与权限控制的完整实现
4.1 wx.login获取code的完整流程
微信小程序登录是每个微信小程序项目都绕不开的模块。简单来说,微信生态不允许小程序直接拿到用户的手机号、密码等敏感信息,它采用的是“code换openid”机制。整个过程分为两步:
第一步,小程序端调用wx.login,微信会返回一个临时的code(这个code五分钟内有效),前端把这个code发给自己的后端。
wx.login({ success: (res) => { if (res.code) { wx.request({ url: 'https://你的域名/api/user/login', data: { code: res.code }, method: 'POST', success: (response) => { const token = response.data.data.token; wx.setStorageSync('token', token); } }); } } });第二步,后端拿到code之后,调用微信的jscode2session接口,用appid + secret + code换回openid和session_key。这一步必须由后端来做,绝对不能在服务器端代码里暴露微信小程序的AppSecret,否则任何人都可以拿它冒充你的小程序。
4.2 后端处理code并颁发token
后端的核心处理逻辑大致是这样的:
@PostMapping("/user/login") public Result login(@RequestBody Map<String, String> params) { String code = params.get("code"); // 调用微信接口,用code换openid WxLoginResp wxResp = wxService.code2Session(code); if (wxResp == null || wxResp.getOpenid() == null) { return Result.error("登录失败"); } String openid = wxResp.getOpenid(); // 根据openid查用户,没有则自动注册 User user = userMapper.selectOne( new LambdaQueryWrapper<User>().eq(User::getOpenid, openid)); if (user == null) { user = new User(); user.setOpenid(openid); user.setCreateTime(new Date()); userMapper.insert(user); } // 生成token返回给前端 String token = JwtUtil.generateToken(user.getUserId()); return Result.ok(token); }这里有几个设计点你可以拿去用在答辩里:
- 为什么不用session,而用token?因为小程序是无状态的,它和后端之间没有浏览器Cookie自动携带的机制。token相当于一张“临时身份证”,前端每次请求都手动带上,后端看到这张证就知道是谁。
- token里放什么?常见做法是放用户ID和过期时间,然后通过JWT签名保证不可篡改。
需要特别强调的是,code2Session调用微信接口时需要注意返回包的错误码。微信官方接口可能会因为code失效、频率限制等原因返回错误,代码里一定要判断errcode,否则用户量一上来,莫名其妙登录不了的时候你根本找不着北。
4.3 Token鉴权:拦截器怎么拦截请求
Token发出去之后,后端得有一个统一的校验机制,不然每个接口都重复写“从请求头里取token再解析”的代码,会把人写吐。Spring Boot里通常用**拦截器(HandlerInterceptor)**来实现统一鉴权:
public class LoginInterceptor implements HandlerInterceptor { @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { String token = request.getHeader("Authorization"); if (token == null || !JwtUtil.verify(token)) { response.setStatus(401); return false; } Long userId = JwtUtil.getUserId(token); request.setAttribute("userId", userId); return true; } }然后在WebMvcConfig里注册这个拦截器,指定拦截哪些路径、放行哪些路径:
@Override public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(new LoginInterceptor()) .addPathPatterns("/api/**") .excludePathPatterns("/api/user/login"); }管理员权限怎么控制?一般有两种思路:
- 思路一:用户的token里加一个
role字段,管理员登录生成的token带role=admin,后端在需要的接口上判断这个字段。 - 思路二:用户表之外单独建admin表,管理员登录走单独的接口,生成的token前缀不同,后端根据前缀识别身份。
思路一实现起来更顺滑,因为拦截器里解析完token顺便就能拿到角色。我建议毕设项目里面用思路一即可,因为管理员的数目非常少,给不给独立表其实都无所谓,但token里带role字段是必须的,否则你就是要写两套拦截器。
5. 核心业务模块的代码走读
5.1 办事指南列表与搜索
办事指南模块,说白了就是“文章列表 + 文章详情”。但列表页有一个细节值得注意:分页。
微信小程序列表页最忌讳一次性把几百条数据全拉下来,不仅慢,而且用户上滑体验很卡。常见做法是后端接口接收page和size两个参数,用MyBatis Plus的Page对象分页查询:
@GetMapping("/affair/list") public Result list(@RequestParam(defaultValue = "1") Integer page, @RequestParam(defaultValue = "10") Integer size, @RequestParam(required = false) String keyword) { LambdaQueryWrapper<Affair> wrapper = new LambdaQueryWrapper<>(); wrapper.eq(Affair::getStatus, 1); // 只显示上架状态 if (StringUtils.hasText(keyword)) { wrapper.like(Affair::getTitle, keyword); // 关键词模糊搜索 } wrapper.orderByDesc(Affair::getCreateTime); Page<Affair> result = affairMapper.selectPage(new Page<>(page, size), wrapper); return Result.ok(result); }前端在onReachBottom生命周期里把page+1,再调一次接口,把新数据追加到数组末尾,就实现了“上拉加载更多”。这里有个非常常见的Bug:列表页在安卓上上拉到底会连续触发好几次onReachBottom,如果没有用loading标志位拦截,数据就会重复。所以前端代码里一般会有一个isLoading变量,请求发出去之前先判断,请求结束之后再开放。
搜索功能本质上就是like查询,没有太多花活。但是要留意:关键词为空时不能拼like %%把全表查出来,这是初学者最容易写出来的低效SQL。
5.2 预约取号:时间冲突校验是关键
预约模块是整个后台逻辑里最核心的。这里有个最重要的规则:同一个用户在同一天、同一个时段,不能重复预约同一个事项。
这个校验放在哪里?必须放在后端Service层,不能只靠前端按钮置灰。因为前端限制只是体验上的,防君子不防小人。后端的校验逻辑大概是这样:
public Result addAppointment(AppointmentVO vo, Long userId) { // 1. 校验用户是否已存在同一天同一时段预约 Long count = appointmentMapper.selectCount( new LambdaQueryWrapper<Appointment>() .eq(Appointment::getUserId, userId) .eq(Appointment::getAppointmentDate, vo.getDate()) .eq(Appointment::getTimeSlot, vo.getTimeSlot()) .in(Appointment::getStatus, Arrays.asList(1, 2)) // 待审核和已通过都算占用 ); if (count > 0) { return Result.error("你已预约该时段,请勿重复提交"); } // 2. 校验当前时间段剩余名额 Long used = appointmentMapper.selectCount( new LambdaQueryWrapper<Appointment>() .eq(Appointment::getAppointmentDate, vo.getDate()) .eq(Appointment::getTimeSlot, vo.getTimeSlot()) .eq(Appointment::getStatus, 2) ); if (used >= 10) { // 假设每个时段最多约10人 return Result.error("该时段预约已满"); } // 3. 插入预约记录 Appointment appointment = new Appointment(); BeanUtils.copyProperties(vo, appointment); appointment.setUserId(userId); appointment.setStatus(1); appointment.setCreateTime(new Date()); appointmentMapper.insert(appointment); return Result.ok("预约成功"); }这段代码里有几个判断条件值得细琢磨:
- 状态为什么是1和2都算占用?因为一个预约从提交到被管理员审核通过,中间有一段时间。如果只算状态2,用户提交后还没审核时再提交一次,就能重复预约。所以“待审核”和“已通过”都必须算入占用数。
- 时段容量用
count查出来再比,会不会有并发问题?在这里会的。但毕设项目一般不用考虑并发,如果有同学想装个逼写“用数据库唯一索引防止并发重复预约”,非常加分,但要注意实现成本。
前端在选择时间段时,一般用单选组件(微信小程序的radio-group)展示当天可约的时段。后端可以提供一个接口返回“哪些时段余量充足、哪些已满”,前端根据返回值把已满的时段置灰。这里顺带提一个热搜词“微信小程序单选框”:radio-group里的每个radio有一个value属性,你提交时拿e.detail.value取到的就是选中的时段字符串。如果你的时段是从后端动态加载的,记得给radio的value绑上数据库里的值,不要用数组下标,否则后端没法映射。
5.3 进度查询与状态流转
进度查询模块比预约简单得多,就是一个列表查询,但有一个业务细节:用户只能看到自己的记录。这里一定不能忘掉userId条件:
@GetMapping("/appointment/my") public Result myAppointments(@RequestAttribute("userId") Long userId, @RequestParam(defaultValue = "1") Integer page, @RequestParam(defaultValue = "10") Integer size) { LambdaQueryWrapper<Appointment> wrapper = new LambdaQueryWrapper<>(); wrapper.eq(Appointment::getUserId, userId) .orderByDesc(Appointment::getCreateTime); Page<Appointment> result = appointmentMapper.selectPage(new Page<>(page, size), wrapper); return Result.ok(result); }注意这个@RequestAttribute("userId"),它就是拦截器里往request里塞的那个userId。如果拦截器和Controller这里对不上号,请求就会报空指针。这是联调时最容易犯的低级错误,我之前有一次排查了半天,最后发现是拦截器里存的key叫userId,Controller里取的key叫id。
状态流转是管理端的核心功能。管理员审核通过时,前端传一个status=2过来,后端做一次更新。更规范一点的做法是后端定义状态枚举,只允许“待审核转已通过”或“待审核转已驳回”,不允许“已办结转回待审核”。毕设项目如果时间紧,可以不做这么严格,但一旦做了,论文里的“系统设计合理性”就能多写半页。
5.4 数据统计:用一条SQL把图表撑起来
管理端的数据统计模块,是很多人觉得“难”的地方。其实微信小程序毕设里的统计不需要做复杂的BI分析,无非是柱状图、折线图、饼图。而且这些图表在小程序里一般用ec-canvas(ECharts的微信小程序版本)来画,后端只需要提供统计数据接口。
统计的核心就是SQL的聚合函数。比如按月统计预约量:
SELECT DATE_FORMAT(create_time, '%Y-%m') AS month, COUNT(*) AS total FROM appointment GROUP BY DATE_FORMAT(create_time, '%Y-%m') ORDER BY month DESC如果后端用了MyBatis Plus,聚合查询一般还是得手写一个SQL,写在Mapper的XML文件里:
<select id="statisticsByMonth" resultType="map"> SELECT DATE_FORMAT(create_time, '%Y-%m') AS month, COUNT(*) AS total FROM appointment GROUP BY DATE_FORMAT(create_time, '%Y-%m') ORDER BY month DESC </select>前端拿到返回的List,就能转成ECharts需要的数据格式。这里有一个现实问题:刚做好的系统里几乎没有数据,图表画出来是空的。所以你至少得往数据库里手动插十几条造数据,不然演示的时候图表光秃秃的,画面很难看。我一般会在测试环境写一个简单的循环,按日期随机生成几十条预约记录,让图表看起来“有说服力”。
6. 毕业设计最容易翻车的几个坑
6.1 小程序合法域名与“不校验合法域名”的选择
这是微信小程序开发里最经典的一个坑。本地开发调试时,你在开发者工具里发请求到http://localhost:8080,如果不勾选“不校验合法域名、web-view(业务域名)、TLS版本以及HTTPS证书”,请求会被直接拦截,小程序显示“url not in domain list”。
这个勾选在开发阶段没问题,但你一旦用手机真机预览,它只对开发者工具生效,真机上照样请求失败。所以如果要把项目部署到公网演示,必须满足两个条件:域名备案、HTTPS证书。很多时候毕设没有现成的备案域名,有一个绕开办法:不用真机预览,答辩的时候用开发者工具的模拟器演示——这虽然是权宜之计,但胜在稳定。如果你想让老师在手机上扫二维码看,那就必须老老实实配一个HTTPS域名,微信小程序后台的“开发管理-服务器域名”里加上request合法域名,然后重新上传代码。
6.2 本地图片路径与线上图片路径
很多毕设源码里,事项的封面图和用户头像都是存在服务器本地的相对路径,比如/upload/xxx.jpg。模拟器里显示是正常的,因为模拟器能直接访问你电脑本地的地址。但真机预览时,localhost指向的是用户自己的手机,自然访问不到电脑上的图片,显示出来就全是裂图。
解决方式有三种:
- 把图片传到公网图床,数据库里存完整URL。这是最省事的。
- 让后端提供静态资源映射,图片放在服务器的
/upload目录,通过https://域名/upload/xxx.jpg访问。这个需要nginx或Spring Boot静态资源配置支持。 - 用微信云开发的存储,把图片传到云存储,返回一个
cloud://的临时链接。
毕设里最稳定的做法是第一种,直接把图片URL换成线上图床的链接。但注意:数据库中存储的字段长度要够,有些源码里封面字段长度只有50,一个图床的URL都不止这个长度,结果插入时报错。
6.3 MySQL 8.0的时区和编码问题
后端连接MySQL时,如果用的是MySQL 8.0,JDBC连接串里必须带serverTimezone=Asia/Shanghai,否则会报一个关于CST时区的错误:
spring: datasource: url: jdbc:mysql://localhost:3306/max_once?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai另外,MySQL 8.0的默认认证插件是caching_sha2_password,而一些旧版本的JDBC驱动不认识这个插件,会报“Unable to load authentication plugin”。解决办法就是换用新版驱动,Spring Boot 2.5+的mysql-connector-java版本基本都兼容。数据库和表的字符集统一用utf8mb4,它能存emoji表情,如果表是utf8,用户昵称带个emoji就会插入失败,整条事务都会回滚。
6.4 端口占用和跨域配置
后端默认跑在8080端口,你本机如果装了别的服务占用8080,启动就失败。两个排查方法:
- 在
application.yml里改端口,比如改成9090。 - 用
netstat -ano | findstr 8080(Windows)查到占用端口进程的PID,去任务管理器结束它。
另外,如果你的毕设后端还要同时供一个Web管理后台访问(比如Vue项目),后端必须配置跨域。Spring Boot的跨域配置在WebMvcConfig里重写addCorsMappings方法即可。但注意:微信小程序的请求不受浏览器的同源策略限制,跨域配置只影响Web端。所以小程序端联调报跨域错误,基本不可能是跨域问题,而是域名或HTTPS的问题。
6.5 微信开发者工具白屏与分包问题
开发过程中会遇到一种情况:开发者工具预览时是白屏,控制台没有任何报错。最常见的原因有两个:
- 页面路径没有在
app.json的pages数组里注册。微信小程序要求所有页面必须注册,漏了一个,跳转时页面就是空白的。 - 基础库版本过低,项目里用了新版本的API,老库不支持。去详情里把调试基础库版本调高试试。
大量热搜词里也出现了“微信小程序分包异步化”,这里简单解释。如果你的项目页面很多,微信要求主包大小不能超过2M,总包不能超过20M。解决办法是把一些低频页面放到subpackages分包里,而“分包异步化”是指主包里的页面可以提前调用分包里的组件或方法。这个毕设项目如果规模不大,一般不需要分包。但如果你在源码里看到了subpackages配置,说明作者已经做了分包处理,这本身也是写论文时一个可以谈的技术亮点。
7. 从源码到答辩:说明文档与演示准备
7.1 说明文档应该包含哪些章节
“说明文档+LW”是毕业设计资源包里容易被忽略的一环,恰恰是答辩打分的重要依据。一般一份合格的毕业设计说明文档,至少包含以下九个章节:
- 绪论:项目背景、研究意义、国内外现状。这里面写“最多跑一次”的业务背景和信息化趋势就行,不要长篇大论,两三页即可。
- 相关技术介绍:微信小程序框架、Spring Boot、MySQL、MyBatis Plus。
- 需求分析:功能性需求(用户端、管理端)、非功能性需求(性能、安全、易用性)。
- 系统设计:总体架构图、功能模块图、数据库E-R图、表结构说明。
- 系统实现:核心界面截图、核心代码说明。这里要注意,每个功能模块配一段核心代码,并解释逻辑。
- 系统测试:测试环境、测试用例表格、测试结论。留几个“测试通过”的结论就好。
- 总结与展望:归纳完成的工作,再谈谈系统不足和未来可以优化的方向。
- 参考文献:至少十篇以上,格式要规范。
- 致谢。
文档里最忌讳的是“全篇贴代码”,老师看到满屏代码会很烦。代码应该只挑核心的贴,而且贴完必须有解释。按我的经验,一个功能模块配一个关键方法的代码就够了,剩下的用界面截图补充。
7.2 答辩演示的最优路径
答辩现场时间有限,演示的效果取决于你有没有设计一条“故事线”。我建议的演示顺序是这样:
- 打开小程序首页,快速介绍四个Tab之间的关系。
- 演示搜索:搜一个关键词,展示事项列表。
- 点进事项详情:让评委看到材料清单和流程说明,说明“办事指南功能解决了用户不知道带什么材料去办的问题”。
- 演示预约流程:选日期、选时段、提交预约,此时强调“后端做了防重复预约校验”,让用户最多跑一次。
- 切到管理端:审核刚才的预约,把状态改为“已通过”,再回到用户端刷新,展示状态变化。
- 展示统计页面:让评委看到预约量的趋势图。
- 最后打开数据库:展示核心表里面的数据,和刚才操作产生的记录对应上。
这条路径的逻辑是“一个完整的业务闭环”——从用户查事项,到管理员审核,到数据落库,全流程都串起来了。老师看到的是一个能跑通的系统,而不是一堆零散页面的堆积。
7.3 答辩现场最可能被问到的问题
提前把这几个问题想清楚,答辩现场你就不慌:
- 为什么选微信小程序而不是App?回答角度:微信小程序免安装、即用即走,适合低频刚需的政务办事场景。
- openid和unionid的区别?回答角度:openid是同一个用户在同一个小程序下的唯一标识,unionid是同一个用户在同一开放平台账号下跨应用统一的标识。如果只做一个小程序,用openid就够了。
- token过期了怎么办?回答角度:前端拦截401响应,自动重新调用
wx.login换取新的token。虽然很多毕设项目没做自动续期,但你“知道”这个方案就行。 - 如何防止恶意刷预约?回答角度:除了后端的重复预约校验外,还可以加一个时间段内的预约次数限制。如果没做,就说“当前项目里做了同一时段防重复校验,更严格的限制可以作为后续优化方向”。
- 数据库为什么用MySQL,不用别的?回答角度:MySQL开源免费、生态成熟、和Java配合度好,毕设规模下性能和稳定都足够。
7.4 从源码到自己的项目:二次开发的切入点
最后聊一个很现实的问题。很多同学拿到的源码其实不是你写的,但是答辩的时候老师可能会问“这个系统你做了哪些工作”。所以拿到源码之后,强烈建议做至少三处“看得见”的改动,然后把改动写进论文和演示文稿里:
- 界面改动:改小程序主题色、首页Banner文案、底部Tab图标。这个最简单,也最容易在演示时直观展示。
- 功能增加:给预约模块增加一个“取消预约”功能,用户在规定时间前可以取消,取消后释放时段名额。这个功能在原系统里经常没有,加上它,业务闭环就更完整了。
- 数据优化:把管理端统计模块从简单的表格展示,改成一个真正的柱状图或折线图。用ECharts真画出来,视觉效果提升非常大。
我的个人看法是:毕设项目的评价标准从来不是“代码量有多大”,而是“你对你写的系统有多了解”。哪怕你只是改了三个小地方,但只要你能把每一个改动背后的原因、设计思路、遇到的问题都讲清楚,这个项目就是你自己的。
这一套流程走下来,从拿到zip的那一刻到答辩结束,每个环节心里都有数了。这个项目本质不复杂,但细节很多,按文章里梳理的顺序一步步来,跑通、看懂、改出新意,你就能稳稳过关。
本文还有配套的精品资源,点击获取