简介:面向高校毕业设计与课程设计应用场景,这份个人日程安排系统是一个基于微信小程序前端、Java后端与MySQL数据库的完整开发项目,适合计算机相关专业学生参考项目结构、拆解功能模块或快速搭建可运行演示环境。压缩包内共1175个文件,整体大小约40.44MB,以Java源码、页面逻辑、结构样式、界面图片、数据库脚本与演示视频等类型为主,同时包含前后端项目文件和部署脚本,便于按模块查阅。已有158人学习或浏览。系统包含管理员端与用户端两类角色,功能涵盖用户管理、重要日管理、工作日程管理、会面管理、用餐管理与日程管理等模块,并附演示视频和说明文档,配合安装运行脚本与数据库脚本,可帮助读者理解前后端数据交互、后端接口和表结构设计,也为二次开发、课程设计报告撰写和答辩演示提供完整参考。
1. 个人日程安排系统开发项目:一个能当毕设交付也能当模板复用的微信小程序
收到一份名为【微信小程序毕业设计】个人日程安排系统开发项目的 rar 压缩包,我第一反应是拿它当普通毕设源码验收,拆开才发现链路比想象完整:小程序前端、后端接口、数据库脚本、演示视频和说明文档都在里面,能直接跑通"登录、建日程、设置提醒、到点收订阅消息"这条完整业务。
个人日程安排系统开发项目这类选题,在毕设里属于"功能清楚、工作量可控、演示效果好"的典型——它不像商城外卖那样堆页面,而是靠日历视图、分类管理、提醒机制几个核心模块把业务讲透。适合两类人:拿现成工程改改就能交差的毕业生,和想快速搭一个日程类小程序模板、后续接自己业务的开发者。下面按我验收这类系统走过的路径写:先拆架构,再跑起来,再讲坑。
2. 先拆后端与数据层:个人日程系统为什么值得用"自建服务 + MySQL"这套组合
2.1 为什么毕设选"小程序 + 自建后端 + MySQL",而不是纯云开发
个人日程安排系统看起来功能不多——登录、建日程、改状态、定提醒——于是有人提议全部塞进小程序本地存储,一个 wx.setStorageSync 搞定,连后端都省了。作为毕业设计,这条路我建议慎走。指导老师验收毕设看的通常不只是"能不能用",还有"系统设计"这一章怎么写:数据表长什么样、接口怎么划分、权限怎么控制。纯本地存储意味着数据库设计、API 设计这些章节全都要靠编,答辩时一个追问就露馅。
更常见、也更稳妥的做法是:小程序端只负责页面和交互,后端单独起一个服务,数据库用真实的 MySQL。技术栈不挑,Node.js、Java Spring Boot、PHP 都行,只要能把 CRUD 和鉴权讲清楚。Windows 系统开发日常使用 MySQL 数据库就是一个很典型的组合:本地装一个 MySQL 8.0,再配 Navicat 或 DBeaver,建库、导数据、看执行计划都直观,后面写毕设文档时截几张表结构图也方便。后端接口独立部署后,小程序端只是它的一个客户端,这个"前后端分离"的架构在论文里也最好写。
如果改用微信云开发,数据库变成 NoSQL 文档型,确实能省掉服务器部署,但毕设要是想讲关系型表结构、字段约束、索引设计,云开发反而不容易展开。这是我建议保留 MySQL 的第二个原因,也是我判断一个毕设源码"含金量"的起点。
2.2 建表先于写代码:用户表、日程表、分类表的字段设计
初始化数据库的第一步不是写接口,是建表。个人日程系统最少要有两张表:用户表和日程表;如果做了分类,再加一张分类表。下面是常见做法里最精简的一套建表语句:
-- 用户表:一个微信用户对应一条记录 CREATE TABLE `user` ( `id` INT UNSIGNED AUTO_INCREMENT PRIMARY KEY, `openid` VARCHAR(64) NOT NULL UNIQUE COMMENT '微信 openid,登录后写入', `nickname` VARCHAR(64) DEFAULT '' COMMENT '昵称,可后补', `phone` VARCHAR(20) DEFAULT '' COMMENT '手机号,授权登录后写入', `create_time` DATETIME DEFAULT CURRENT_TIMESTAMP, KEY `idx_create_time` (`create_time`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='用户表'; -- 日程表:核心业务表 CREATE TABLE `schedule` ( `id` INT UNSIGNED AUTO_INCREMENT PRIMARY KEY, `user_id` INT UNSIGNED NOT NULL COMMENT '对应用户表 id', `title` VARCHAR(128) NOT NULL COMMENT '日程标题', `content` TEXT COMMENT '日程详情,可为空', `category_id` INT UNSIGNED DEFAULT 0 COMMENT '分类 id,0 表示未分类', `start_time` DATETIME NOT NULL COMMENT '开始时间', `end_time` DATETIME DEFAULT NULL COMMENT '结束时间,可为空', `remind_time` DATETIME DEFAULT NULL COMMENT '提醒时间,到点触发', `status` TINYINT NOT NULL DEFAULT 0 COMMENT '0 未完成,1 已完成', `create_time` DATETIME DEFAULT CURRENT_TIMESTAMP, KEY `idx_user_time` (`user_id`, `start_time`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='日程表';表字段是整套系统最容易返工的地方,说几个细节。第一,openid 用 VARCHAR(64) 而不要用 VARCHAR(20) 这种拍脑袋的长度——微信返回的 openid 实际在 28 位左右,但留出余量能让以后接公众号、开放平台时不用改表。第二,时间字段一律用 DATETIME,不要用字符串,否则做"本月日程""即将到期"这类区间查询时,字符串比较会出各种边界问题。第三,status 用 TINYINT 而不是 BIT 或 BOOLEAN,MySQL 的 BOOLEAN 本质就是 TINYINT(1),直接用 TINYINT 并注释 0/1 含义,后面想扩"已取消"状态也不用动字段类型。第四,utf8mb4 必须写——日程标题里用户会输入 emoji,utf8 存不进去,插入时直接报错。
索引方面,idx_user_time 是这套系统最重要的一个索引。日程列表、日历视图、提醒扫描全部都要按 user_id 过滤、按 start_time 排序,这个联合索引能让查询走索引而不是全表扫。提醒扫描则是另一条路径:定时任务要查"remind_time 在最近几分钟内且未发送"的记录,所以提醒功能做扎实的话,还应该给 remind_time 单独建一个索引。多数毕设数据量小,索引影响不明显,但把索引设计写进文档,是答辩时很加分的细节。
2.3 日历视图的数据查询:按月取数和时间边界处理
日程系统的门面是日历页。常见误用是把整张表查出来,在前端用 JavaScript 按天分组——几百条数据时感觉不出问题,数据到几千条,小程序端渲染就开始卡,setData 传大数组的耗时也上来了。常见做法是后端支持按"月份区间"查询,前端翻月时重新请求:
-- 查某个用户某个月的全部日程,两个边界时间由后端按请求月份算好 SELECT id, title, start_time, end_time, category_id, status FROM schedule WHERE user_id = ? AND start_time >= '2025-06-01 00:00:00' AND start_time < '2025-07-01 00:00:00' ORDER BY start_time ASC;为什么用 >= 和 < 而不是 BETWEEN?BETWEEN 是闭区间,写成 BETWEEN '2025-06-01' AND '2025-06-30' 会丢掉 6 月 30 日 23:59 之后那部分数据,而大部分日程恰恰容易落在月末的晚上。用下月 1 号作为上限,语义是"大于等于月初、小于下月月初",一个边界都不会漏。月份参数怎么传?前端传 '2025-06' 这种短格式就行,后端自己拼两个边界时间,不要信任前端传进来的完整时间字符串,防止有人绕过页面直接调接口传任意范围。
日历页通常还要显示"某天有几条日程"的角标。这个不用每天单独发请求,一条聚合查询就能拿到整月分布:
SELECT DATE_FORMAT(start_time, '%Y-%m-%d') AS day, COUNT(*) AS total FROM schedule WHERE user_id = ? AND start_time >= ? AND start_time < ? GROUP BY day;返回的 day 是 '2025-06-03' 这种短字符串,前端日历组件直接用当天日期做 key,往对应格子上挂数量角标。注意 GROUP BY 用的是 SELECT 里的别名 day,这是 MySQL 的扩展语法,别写 GROUP BY DATE_FORMAT(...) 再取别名,可读性差一截。这套按月取数的设计,既控制接口返回量,又让日历页的每次请求都可缓存,后面做性能优化时有的讲。
3. 从 rar 到可运行:微信开发者工具导入、接口地址修改与数据库初始化
3.1 解压 rar 先分三类:前端工程、后端工程、数据库脚本
拿到一个 .rar 后缀的毕设交付包,先别急着双击导入微信开发者工具,第一件事是解压后分清里面有几样东西。按标题,这个包里至少包含源码、演示视频、说明文档三部分,源码又通常拆成前端和后端两块。判断依据很直接:有小程序前端工程特征的目录里一定有 app.json、app.js 和 pages 目录;后端工程如果是 Node.js,会有 package.json,如果是 Java,会有 pom.xml;数据库脚本的特征是 .sql 后缀,文件名常见是 init.sql 或 schedule.sql。演示视频一般是 mp4,说明文档一般是 PDF 或 Word。
我收到这类工程时,最常见的交付结构是三个文件夹加一个文档:miniprogram(或叫 client、frontend)、server(或叫 backend)、database(或叫 sql)。如果你的压缩包不是这个结构,按"能导入开发者工具的是前端、能起服务接口的是后端"这个原则去分也不会走偏。分好之后,先看说明文档里有没有写运行步骤,有就按它来;没有或者写得含糊,才需要按下面这套顺序自己捋。
3.2 微信开发者工具导入:appid、接口地址和不校验合法域名
把小程序前端目录导入微信开发者工具之后,通常会遇到两类报错:一类是 appid 不对,另一类是接口请求直接 fail。appid 的问题好解——你自己的小程序如果还没注册,导入时选择"测试号"即可;如果工程里已经写死了别人的 appid,在 project.config.json 里改掉,或者在工具右上角"详情-基本信息"里替换。有一个动作特别容易踩:直接用别人的 appid 预览,结果登录、订阅消息全失败,因为订阅消息模板、手机号能力都和 appid 绑定。
接口地址是第二处必改项。绝大多数毕设源码里的后端接口写的是 http://127.0.0.1:8080 或 http://localhost:8080,这是后端在本机跑时的配置。拿过来要改的地方通常集中在 app.js 的 globalData 里:
// app.js 中常见的全局配置,所有页面的请求都从这里取 baseUrl App({ globalData: { // 本地调试:后端跑在本机,开发者工具里用 127.0.0.1 就能通 baseUrl: 'http://127.0.0.1:8080' // 真机预览:改成电脑的局域网 IP,例如 http://192.168.1.100:8080 } })为什么真机预览要用局域网 IP 而不是 127.0.0.1?因为手机上的 127.0.0.1 指向手机自己,不是你的电脑。真机和电脑连同一个 Wi-Fi,再把电脑的 IPv4 地址填进来,请求才能打到你本机的后端。这里还有个前提:微信开发者工具默认会拦截 http 请求,所以本地调试阶段,要在"详情-本地设置"里勾选"不校验合法域名、web-view(业务域名)、TLS 版本以及 HTTPS 证书",否则接口请求全部失败。这个开关只影响开发工具和真机调试,一旦上传体验版、正式版就不起作用,线上环境必须用配置过白名单的 https 域名,区别后面避坑章再讲。
3.3 数据库初始化:本机 MySQL 与 Docker 两种启动方式的取舍
后端要跑起来,数据库得先就位。Windows 系统开发日常使用 MySQL 数据库,常见做法是直接下载 MySQL Installer 装一个 8.0,一路 Next 设好 root 密码,再用 Navicat 或命令行把交付包里的 .sql 导入。命令行导入一句话:
mysql -uroot -p123456 < schedule.sql如果 .sql 脚本里没有 CREATE DATABASE,要手动先建库再导入,否则会报"数据库不存在"。这条命令的 -u 是用户名,-p 后面紧跟密码且不要有空格,很多新手把 -p 123456 写成带空格,MySQL 会把 123456 当成库名解析,反复报错半天。
另一条路是用 Docker 起 MySQL。如果电脑上已经有 Docker Desktop,这种方式更省心,尤其要演示给导师看、又不想污染本机环境的时候:
# 启动一个名为 schedule-mysql 的 MySQL 8.0 容器 # -d 后台运行,--name 指定容器名,-e 传环境变量,-p 映射端口 docker run -d \ --name schedule-mysql \ -e MYSQL_ROOT_PASSWORD=123456 \ -e MYSQL_DATABASE=schedule \ -p 3306:3306 \ mysql:8.0 # 导入数据库脚本:把宿主机上的 schedule.sql 导入容器里的 MySQL docker exec -i schedule-mysql mysql -uroot -p123456 schedule < schedule.sql几个参数解释一下。-d 是后台运行,不占用当前终端;-e MYSQL_ROOT_PASSWORD 和 -e MYSQL_DATABASE 是官方镜像约定的初始化参数,第一次启动自动建好库;-p 3306:3306 把容器内 3306 端口映射到宿主机,这样本机的 Navicat、后端服务都能用 127.0.0.1:3306 连上。导入那一步的 -i 是 interactive,让标准输入能进容器,< schedule.sql 把宿主机文件重定向进去。
直接安装在本机上和利用 Docker 启动,推荐哪种?我做毕设项目时一般是这样判断的:需要截数据库设计图、要跟导师当面演示,就选本机安装 MySQL,直观、好截图,可视化工具一抓一大把;如果电脑上同时跑着多个项目、MySQL 版本经常打架,或者打算答辩后反复重装环境,就选 Docker,容器删掉再起一个就是全新环境,没有残留。两种方式对源码没有区别,后端连接串都是 jdbc:mysql://127.0.0.1:3306/schedule 或 mysql://root:123456@127.0.0.1:3306/schedule。唯一要留心的是时区:
注意:Docker 里的 MySQL 默认时区是 UTC,日程系统对时间敏感,连接串要带 serverTimezone=Asia/Shanghai,或者在容器启动参数加 -e TZ=Asia/Shanghai,否则存进去的提醒时间会比本地差 8 小时。这种问题最容易让人以为是代码 bug,我见过不止一个人在这里花掉半天。
4. 核心功能落地:手机号登录、日程增删改查和订阅消息提醒的实现要点
4.1 微信小程序登录获取手机号:bindgetphonenumber 拿到 code 之后
日程系统要做个性化数据,第一步是登录。微信小程序登录获取手机号,正确姿势不是在小程序里直接拿手机号,而是通过<button open-type="getPhoneNumber">触发授权,在回调里拿到一个动态 code,再把这个 code 发给后端,由后端调微信接口换手机号和 openid。最小实现长这样:
<!-- index.wxml 登录按钮 --> <button open-type="getPhoneNumber" bindgetphonenumber="onGetPhone"> 微信一键登录 </button>// index.js 里的回调 Page({ onGetPhone(e) { // e.detail.errMsg 是授权结果,先判断用户有没有点允许 if (e.detail.errMsg !== 'getPhoneNumber:ok') { wx.showToast({ title: '需要授权才能继续', icon: 'none' }) return } // 把 e.detail.code 发给后端,后端拿它换手机号 wx.request({ url: getApp().globalData.baseUrl + '/api/login', method: 'POST', data: { code: e.detail.code }, success: (res) => { // 后端返回 token 和用户信息,token 存本地,后续请求带上 if (res.data.token) { wx.setStorageSync('token', res.data.token) wx.setStorageSync('userInfo', res.data.userInfo) } } }) } })关键认知是:e.detail.code 不是手机号,也不是 openid,它是一个短期有效的授权凭证。后端拿到这个 code 后,要拿它 + 自己的 appid + appsecret 去调用微信对应的接口,才能换回手机号。这个 code 有效期只有五分钟,而且只能用一次,所以前端拿到后要立刻发请求,不要在中间夹别的操作。很多新手把 code 打印在控制台里研究半天再发请求,然后报"code 已被使用",就是这个原因。
登录成功后,后端要做两件事:用 openid 查用户表,查不到就新建用户;签发一个 token 返回给小程序。token 的生成方式不挑,JWT 或简单随机串都行,关键是后续所有 /api/schedule 接口都要校验这个 token,从里面解析出 user_id,而不是让前端在参数里传 user_id——后者等于数据权限裸奔,任何人改个参数就能看别人的日程。这个点写进论文的"接口设计"一节,是很扎实的内容。
4.2 日程的增删改查:前端校验只是体验,后端校验才是底线
日程 CRUD 是整个系统的躯干。以新增日程为例,前端要做的是收集表单、做初步校验、调接口;后端要做的是校验参数、写库、返回结果。常见的翻车点,是前端把校验做得特别全,后端却只写了 insert 语句——前端校验只是用户体验,后端校验才是数据安全底线。一个典型的新增日程前端处理:
submitSchedule() { const { title, startTime, endTime, remindTime } = this.data.form // 第一道校验:标题非空 if (!title || !title.trim()) { wx.showToast({ title: '标题不能为空', icon: 'none' }) return } // 第二道校验:结束时间必须晚于开始时间 if (endTime && new Date(endTime) <= new Date(startTime)) { wx.showToast({ title: '结束时间必须晚于开始时间', icon: 'none' }) return } // 校验通过后调接口,token 放在请求头里 wx.request({ url: getApp().globalData.baseUrl + '/api/schedule', method: 'POST', header: { Authorization: wx.getStorageSync('token') }, data: this.data.form, success: (res) => { if (res.data.code === 0) { wx.navigateBack() } else { wx.showToast({ title: res.data.msg, icon: 'none' }) } } }) }new Date(endTime) <= new Date(startTime) 这种比较在 JavaScript 里可以直接用,两个日期字符串会转成时间戳比大小;但要注意 iOS 对 '2025-06-01 10:00:00' 这种带横杠的字符串解析不稳定,比较稳妥的做法是让日期选择器用 picker 组件,mode="date" 配 mode="time" 分开选,再拼成完整 datetime 传后端。这样可以避开大量手输时间的脏数据,也避免同一套代码在两个系统上表现不一致。
后端拿到请求后,至少要再校验一遍:title 长度、时间字段能否解析、end_time 是否晚于 start_time。不要因为前端验过了就信任它,小程序可以被抓包、可以被直接调接口,前端代码挡不住任何恶意请求。后端校验通过,把 user_id 从 token 里解析出来写进表里,这条日程才算合法落库。修改和删除的套路一致,但两个接口都必须校验"这条日程的 user_id 是不是当前登录用户",WHERE 条件里除了 id 还要带 user_id,防止越权操作——只写 WHERE id = ? 不写 AND user_id = ?,用另一个账号一测就能删掉别人的数据。
4.3 订阅消息做日程提醒:一次性订阅的边界与后端定时任务
日程系统的灵魂是提醒。微信小程序在用户离开小程序之后,唯一的触达通道是订阅消息。它和 App 推送不一样,核心规则是:每一条订阅消息,都要用户先主动授权一次,你才能下发一条。看申请订阅的最小实现:
// 设置提醒前,先向用户申请订阅授权 wx.requestSubscribeMessage({ tmplIds: ['模板消息ID,在微信公众平台申请'], success(res) { // res['模板消息ID'] 的取值:'accept' 同意,'reject' 拒绝 if (res['模板消息ID'] === 'accept') { // 同意了一次,后端那边只能下发一条 this.setData({ subscribed: true }) wx.showToast({ title: '到点会提醒你', icon: 'success' }) } else { wx.showToast({ title: '未授权则收不到提醒', icon: 'none' }) } } })tmplIds 里的模板 ID 要去微信公众平台的小程序后台申请,每个模板对应一种消息格式,日程提醒一般选"待办事项提醒"类模板,注意模板字段里要包含日程标题、时间这些内容,后端下发时按模板字段名拼数据,字段名对不上就下发失败。这个 ID 跟着 appid 走,换 appid 或使用测试号就会失效,这是毕设里订阅消息"发不出去"最常见的原因。
第二个坑是:用户授权之后,小程序一旦切到后台,前端的 setTimeout 并不会继续跑,所以"到点提醒"绝对不能靠小程序本地定时器。正确做法是把提醒交给后端:后端起一个定时任务(Node 的 node-schedule、Java 的 Spring Task 都行),每隔一分钟扫一次 schedule 表,找出 remind_time 在最近一分钟内、且未发送过提醒的记录,逐条调微信订阅消息接口下发,成功后打一个已发送标记。如果没有这个标记字段,定时任务每扫一次就会把同一批记录重复发送,用户会在一分钟内收到好几条一模一样的提醒。最简单的实现就是给 schedule 表加一列 remind_sent TINYINT,默认 0,发送成功后置 1——这个小细节,比任何花哨功能都能让导师觉得你想到了他没想到的问题。
5. 微信小程序个人日程系统排查指南:导航栏高度、白屏、包体超限和提醒收不到
5.1 自定义导航栏高度对不齐:用胶囊按钮位置反推
很多日程系统为了视觉效果,会把默认导航栏换成自定义的,标题、返回按钮、右侧胶囊都自己画。结果一发真机预览就露馅:iPhone 上和安卓上导航栏高度不一样,刘海屏和普通屏也不一样。现象很统一:自定义标题要么顶进状态栏里,要么和胶囊按钮错位。原因就是有人把导航栏高度写死成了 44 或 64 这种固定值,某个机型上是对的,换台手机就不对。
解决思路不是去适配每个机型,而是拿到当前机型胶囊按钮的真实位置,用它反推导航栏高度:
// 在自定义导航栏组件里,动态计算状态栏高度和导航栏高度 const sysInfo = wx.getSystemInfoSync() const statusBarHeight = sysInfo.statusBarHeight // 状态栏高度 const menuRect = wx.getMenuButtonBoundingClientRect() // 胶囊按钮位置信息 // 导航栏高度 = 胶囊按钮上边距的两倍 + 胶囊按钮高度,这是官方推荐的算法 const navBarHeight = (menuRect.top - statusBarHeight) * 2 + menuRect.height this.setData({ statusBarHeight, navBarHeight, menuRect })getMenuButtonBoundingClientRect 返回胶囊按钮的 top、height 等坐标,top 减去状态栏高度,就是胶囊距状态栏底部的距离。官方建议导航栏总高度取这个距离的两倍再加上胶囊高度,这样标题能稳稳居中于胶囊按钮的垂直中线。把算出来的两个值写进页面根节点的 padding 或高度样式,不管换什么机型,布局都不会再跳。这套算法是所有自定义导航栏组件的通用解法。日程系统的日历页、详情页如果都用了自定义导航栏,把它抽成一个公共组件,别每个页面各算一遍——否则改一处要同步四处,迟早漏改。
5.2 真机白屏、接口请求失败:先查域名校验
现象一:开发工具里一切正常,点"真机预览",小程序能打开但列表页一直转圈、数据出不来。现象二更严重:页面直接白屏,控制台一片 request:fail。这类问题九成出在域名校验上:开发工具默认允许勾选"不校验合法域名",所以本地 http://127.0.0.1:8080 能通;真机调试或体验版里这个开关失效,微信强制要求所有请求域名都必须在公众平台配置过,并且是 https。
原因就一句话:真机环境和开发工具环境的网络策略不一样。解决分两步。本地联调阶段,手机和电脑连同一个 Wi-Fi,把 baseUrl 改成电脑的局域网 IP,并且保持开发者工具里勾选"不校验合法域名",真机调试一般就能通。要发体验版给导师看,就必须把后端部署到一台有公网地址的服务器上,配好 https 域名,再到公众平台的"开发设置-服务器域名"里把 request 合法域名加进去,然后重新上传代码。这里有个容易忽略的点:改了服务器域名配置后,要等几分钟生效,小程序端代码里也要同步改成 https 的正式域名,别让前端还指着 127.0.0.1。
排查这类问题,我一般会在 wx.request 的 fail 回调里把错误打出来,比只看一个"fail"有用得多:
wx.request({ url: getApp().globalData.baseUrl + '/api/schedule', method: 'GET', header: { Authorization: wx.getStorageSync('token') }, success(res) { console.log('接口成功', res.data) }, fail(err) { // errMsg 会给出具体原因,例如 url not in domain list console.error('接口失败', err.errMsg) } })如果 errMsg 里出现 url not in domain list 字样,就是域名校验没过;如果只是 request:fail 且没有 domain 字样,优先查网络连通性,比如手机和电脑是不是同一个 Wi-Fi、后端服务有没有真的起来。还有一个低概率但遇见过的情况:域名配了、https 也正常,请求还是失败。去公众平台确认域名有没有漏掉协议头,或者把 www 和无 www 当成同一个域名——微信的域名校验是精确匹配。遇到这种"配置看着都对就是不通"的场面,先拿手机浏览器直接访问一次接口地址,能打开再回小程序排查,能过滤掉一半的怀疑对象。
5.3 上传报错 2612kb 超 2MB:图片压缩与分包
做毕设的小程序,功能还没多少,上传代码时突然报一句 source size 2612kb exceed max limit 2mb,属于经典开局。现象很明确:开发工具里编译正常、预览正常,一"上传"就失败,因为上传时检查的是主包体积,主包不能超过 2MB。原因基本就一个:图片。毕设里最常见的做法是把整套 UI 截图、装饰图、背景图直接丢进项目目录,一张几 MB 的截图放进去,包体立刻超标。
解决按优先级做三件事。第一,把所有图片压一遍,用 TinyPNG 或本地工具把 png/jpg 压到几十 KB 级别,背景图能用纯色或 CSS 渐变替代就替代。第二,把不再引用的图片和页面文件夹删干净,很多人从网上下载源码后,里面一堆 demo 页面和冗余文件根本没删,这些小文件加起来非常可观。在开发者工具的资源管理器里按文件大小倒序排一遍,重点盯几十 KB 以上的资源,基本能定位到元凶。第三,把低频页面拆成分包,微信允许主包之外挂多个分包,每个分包上限按平台规则走,日程系统里像"数据统计""关于页"这种用得少的页面,很适合挪进分包:
{ "pages": [ "pages/index/index", "pages/calendar/calendar", "pages/detail/detail" ], "subpackages": [ { "root": "packageStat", "pages": ["pages/statistics/statistics"] } ] }配置里注意两点:分包里的页面路径不能同时出现在主包 pages 里,否则编译报错;分包内部的页面跳转要用 /packageStat/pages/statistics/statistics 这种完整路径。拆完之后,首次启动只加载主包,启动速度反而会变快——这个点完全可以写进答辩的"性能优化"部分。
5.4 提醒到点没收到:订阅消息不是推送通道
这是日程系统里用户反馈最多、也最容易被误判的一条。现象:用户明明在设置日程时点了"允许",到了提醒时间什么也没收到。排查下来通常不是代码 bug,而是对订阅消息机制的预期错了。
微信订阅消息是"用户授权一次,开发者下发一条",而且有几个硬限制:第一,用户拒绝授权后,你不能在代码里反复弹授权框,需要用户碰到某个按钮再次触发;第二,模板消息有频次控制,滥用会被限制;第三,用户没有点过授权的那条日程,下发时接口直接报错。所以"到点没收到"最常见的三个原因:授权次数用完了、后端定时任务没跑、查询条件写错没扫到那条记录。
解决思路是在产品层面就把"一次性"跟用户说清楚。设置提醒时,弹窗文案写"开启后本条日程到点提醒一次",而不是笼统写"允许提醒";后端记录每个用户剩余的可订阅次数,在用户设置新日程、次数不足时引导再次授权;提醒发送失败时,把微信返回的错误码记到日志里,便于定位是模板 ID 失效、用户未授权还是频率超限。真要说经验教训,就是别把订阅消息当成短信通道来设计产品,它本质是"用户主动要的一次性通知",所有把"每天推送"做成订阅消息的方案,最后都会在用户授权耗尽后集体失灵。
6. 答辩前验收:用性能测试和演示脚本把个人日程系统讲出亮点
日程系统的功能跑通只是第一步,毕设答辩看的还有稳定性和考虑周全。我每次交付前都会按下面这张清单过一遍,每一项都有明确做法和及格线:
| 检查项 | 做法 | 及格线 |
|---|---|---|
| 启动耗时 | 开发者工具"体验评分"跑一遍 | 主包控制在 2MB 内,3 秒出首页 |
| 列表滚动 | 真机调试,快速滑动日程列表 | 无明显卡顿、不掉帧 |
| 接口响应 | 看后端日志 /api/schedule 耗时 | 本地环境 200ms 以内 |
| setData 频率 | 检查列表页每次 setData 的数据量 | 单次不超过几百 KB |
| 未登录守卫 | 退出登录后逐个页面点一遍 | 全部跳回登录引导,不白屏 |
真机性能测试不要只在开发者工具里做——工具的渲染性能和真机差很多,尤其在低端安卓机上,日程列表一次 setData 传几百条数据,滚动时就会明显掉帧。我一般会在列表页做分页,每页 20 条,触底加载下一页,既减轻渲染压力,也能在答辩时讲"分页加载"这个优化点。
演示脚本建议固定成五步,照着走不乱:第一步,用手机号登录拿 token;第二步,新建一条日程,设一个 5 分钟后的提醒;第三步,切到日历视图,确认新日程出现在对应日期、角标数字正确;第四步,回列表把这条日程标为完成,再删除;第五步,打开统计页看本月完成率有没有更新。每一步背后都对应一张表或一个接口的改动,导师追问时你有一个明确的落点可以讲。
最后说一个我自己的习惯:交付前永远要在"未登录状态"下把小程序完整点一遍,确认每个页面都能正确弹回登录引导,而不是转圈或白屏。日程系统的数据全部挂在用户下,没登录就想看列表是最常见的崩溃入口。我最早做这类日程系统时就在这上面翻过车,演示一打开就白屏,后来把 token 为空时的页面守卫写进每个页面的 onLoad,再没出过事。希望帮到你。
本文还有配套的精品资源,点击获取