简介:校园失物招领微信小程序源码是一套可直接用于毕业设计或课程设计的完整前后端项目,覆盖管理员端(招领、类别、记录、交流讨论、资讯、用户管理)与用户端(主页、交流论坛、我的记录、个人)等核心模块。资源共2000个文件、20.83MB,其中png/css/html等为界面静态资源,js/java处理前后端逻辑,sql提供数据库初始化脚本,wxml/wxss对应小程序页面与样式,目录结构清晰,便于按需定位与二次开发。目前已有70人学习,特别适合计算机专业学生快速搭建校园失物招领场景的小程序原型。压缩包内含数据库SQL脚本、项目说明文档、LW PPT、演示视频及完整项目文件夹,并给出了Java/php、JDK1.8、mysql5.7及以上、uniapp/原生小程序、HBuilderX/微信开发者工具等技术栈说明,可帮助开发者从环境配置、功能理解到部署演示完整走通毕业设计流程。
1. 失物招领微信小程序毕业设计源码,先看表再看页
校园失物招领这个选题,压缩包里塞的几个交付物,最容易让人忽略的是“阅读顺序”。前端展示页面的确直观,评论区、爱心按钮、图片轮播做得很抢眼,但真正的系统骨架在 MySQL 脚本和 Controller 接口里。毕业答辩时,评审老师通常不会点开每一个页面去体验,而是会问一件更致命的事:你贴出来的数据库表和前后端交互方不一致,怎么办。所以拿到这套源码,正确的阅读路径应该是先跑一遍建表 SQL,把 users、lost_items、found_items、claims 四张表的字段和关系看懂,再顺着 Controller 把接口参数和小程序页面的 bindtap 事件对齐,最后才去看页面的 CSS。小程序源码里最值钱的部分永远是数据流,对失物招领来说,就是一条物品从“丢失/捡到”到“被认领”的流转,这套状态机贯穿整个前后端。按这个顺序挖源码,论文里画 E-R 图和业务流程图的时候才不会跑偏。
2. 失物招领小程序的系统架构与 MySQL 数据表设计
2.1 前后端分离组合:微信小程序只做渲染层,Spring Boot 承担业务层
这个毕业设计采用的是微信小程序 + Spring Boot + MySQL 的前后端分离组合。所谓“前后端分离”,在小程序项目里并不是指单独的 Web 端和移动端,而是指小程序客户端与后端 API 服务的协作模型:小程序端只管页面渲染和用户交互,后端通过 HTTP 接口接收请求、调用业务逻辑、读写 MySQL。
前后端的数据交互围绕 JSON 展开。小程序端通过wx.request发起 GET/POST 请求,后端接口接受参数、查询数据库、返回 JSON,两边约定好字段名和状态码即可。为了实现解耦,我建议在 Spring Boot 侧定义统一的返回结构,避免小程序端每个页面都写一套错误处理逻辑,这也是完整前后端项目里最常见的做法。
| 模块 | 技术选型 | 在本项目中的职责 |
|---|---|---|
| 客户端 | 微信小程序原生 | 页面渲染、表单输入、图片上传、用户操作入口 |
| 服务端 | Spring Boot | 接口路由、业务校验、JWT 鉴权、文件存储 |
| 数据层 | MySQL | 用户、失物、招领、认领四类核心数据的持久化 |
| 中间件 | 无额外引入 | 毕业设计通常不需要 Redis,单机 MySQL 足够 |
这套组合对毕业设计有两个好处。第一,小程序端的 LocalStorage 和wx.getStorageSync已经能承担轻量级会话状态保存,不需要额外搭建认证中心;第二,Spring Boot 的 Starter 生态对 MySQL 和文件上传的支持完善,甚至不需要配置复杂的 XML。整个架构保持两层:小程序直接连 Spring Boot API,数据库只对后端开放端口,不直接暴露给客户端。如果实验室设备允许,我一般会把 MySQL 的端口绑定在127.0.0.1,禁止外部主机访问,这样即使小程序端配置有误,你也不会把数据库数据泄露出去。
2.2 MySQL 建表:users、lost_items、found_items、claims 四张表怎么定字段
失物招领的表设计有两种流派:一种是把失物和招领合并成一张 item 表,用 type 字段区分;另一种是拆成 lost_items 和 found_items 两张表。毕业设计我推荐拆表。原因有两个:一是失物和招领发布的字段语义不同(丢手机拍的是手机照片,捡到校园卡拍的是卡片照片),拆表可以让字段更贴合业务;二是认领流程中状态字段冲突时,合并表需要在同一个表上做复杂的状态过滤,而拆表后每张表的查询条件都很干净。
先看用户表和认领表,以及失物表的建表参考:
CREATE TABLE `users` ( `id` INT NOT NULL AUTO_INCREMENT, `openid` VARCHAR(64) NOT NULL COMMENT '微信 openid,唯一标识', `nickname` VARCHAR(64) DEFAULT '' COMMENT '用户昵称', `avatar` VARCHAR(255) DEFAULT '' COMMENT '头像图 URL', `phone` VARCHAR(20) DEFAULT '' COMMENT '联系电话', `create_time` DATETIME DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (`id`), UNIQUE KEY `uk_openid` (`openid`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='用户表'; CREATE TABLE `lost_items` ( `id` INT NOT NULL AUTO_INCREMENT, `user_id` INT NOT NULL COMMENT '发布者 ID', `title` VARCHAR(100) NOT NULL COMMENT '标题,例如:蓝色雨伞', `description` TEXT COMMENT '详细描述', `image` VARCHAR(255) DEFAULT '' COMMENT '物品图片 URL', `location` VARCHAR(100) DEFAULT '' COMMENT '丢失地点', `lost_time` DATETIME DEFAULT NULL COMMENT '丢失时间', `status` TINYINT DEFAULT 0 COMMENT '0=寻物中, 1=已找到, 2=已撤回', `create_time` DATETIME DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (`id`), KEY `idx_user_id` (`user_id`), KEY `idx_status` (`status`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='失物发布表';found_items与lost_items结构基本一致,差异在字段:多了find_location表示捡到地点,把lost_time换成found_time。这个差异并不大,如果偷懒也可以不拆表,但要说服答辩评委“为什么一张表能承载两种状态”,你很可能会在解释字段冗余时陷入被动。我见过不少源码包,loost 和 found 合并之后,前端列表页必须传type=lost才能拉到正确的数据,这其实是把复杂度转移给了前端。
接着是核心的认领表:
CREATE TABLE `claims` ( `id` INT NOT NULL AUTO_INCREMENT, `item_type` TINYINT NOT NULL COMMENT '1=失物, 2=招领', `item_id` INT NOT NULL COMMENT '对应 lost_items.id 或 found_items.id', `claimer_id` INT NOT NULL COMMENT '认领人 users.id', `reason` VARCHAR(255) DEFAULT '' COMMENT '认领描述,例如:这部手机壳是透明色的', `contact` VARCHAR(100) DEFAULT '' COMMENT '认领人联系方式', `status` TINYINT DEFAULT 0 COMMENT '0=待审核, 1=同意, 2=拒绝', `create_time` DATETIME DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (`id`), KEY `idx_item` (`item_type`, `item_id`), KEY `idx_claimer` (`claimer_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='认领申请记录';item_type + item_id的组合是设计上最需要解释的点。因为失物和招领分了两张表,认领申请需要知道到底是认领哪一张表里的哪一条物品。这个item_type字段很自然地解释了为什么拆表是有必要的:如果合并成一张表,这组字段会退化成单个item_id,但状态流转语义就会混乱。后面实现认领审核时,后端拿item_type = 1去查的是lost_items,item_type = 2则查found_items。
建表时统一使用utf8mb4字符集也很关键。用户昵称里经常出现 emoji,微信接口返回的 nickname 有可能包含符号。如果你用utf8字符集,插入这类数据时 MySQL 会报Incorrect string value,接口直接抛异常,列表页空数据。utf8mb4是utf8的超集,覆盖四字节字符,建库时就要定好,不要等跑起来再改。
2.3 接口清单:登录、发布、列表、详情、认领五个模块的路由设计
后端路由设计直接影响小程序端wx.request的 URL 拼接。完整前后端项目肯定要把接口路径统一,下面是一份常用的接口定义表格,后面写 Controller 代码时也沿用这套路径:
| 方法 | 路径 | 作用 | 请求参数 | 返回数据 |
|---|---|---|---|---|
| POST | /api/login | 微信登录换取 token | { code } | { token, user } |
| GET | /api/lost/list | 失物分页列表 | page,size,keyword,status | 列表 + 总数 |
| GET | /api/found/list | 招领分页列表 | page,size,keyword | 列表 + 总数 |
| GET | /api/lost/detail | 失物详情 | id | 明细 |
| POST | /api/lost/publish | 发布失物 | 表单 + token | 成功状态 |
| POST | /api/found/publish | 发布招领 | 表单 + token | 成功状态 |
| POST | /api/claim | 提交认领申请 | itemType,itemId,reason,contact | 成功状态 |
| PUT | /api/claim | 审核认领 | claimId,status | 成功状态 |
接口路径尽量遵守资源体的单复数命名规范。/api/lost/list和/api/found/list分开是一种常见做法,因为两者的查询条件不相同,失物需要按“是否已找到”过滤,招领则不需要。分页请求全部通过page和size控制,这对应了小程序端的上拉加载机制。小程序端的列表页没必要一次性拉全量数据,内存撑不住,接口也慢。
3. Spring Boot 后端:登录鉴权、发布列表与认领状态流转
3.1 登录接口:用微信 code 换 token 的常见后端写法
小程序端调用wx.login可以拿到一个临时 code,后端拿这个 code 请求微信的jscode2session接口,换取出用户的 openid。因为后端代码不涉及在小程序里直接调用微信 API,所以这里只需要用 Java 的 HTTP 客户端发送请求即可。较常见处理是使用 SpringBoot 自带的RestTemplate组织 GET 请求。
下面是一个简化的登录接口实现:
@PostMapping("/api/login") public Result login(@RequestBody LoginRequest req) { // 1. 用 code 请求微信接口,换取 openid String url = "https://api.weixin.qq.com/sns/jscode2session?appid=" + appid + "&secret=" + secret + "&js_code=" + req.getCode() + "&grant_type=authorization_code"; String res = restTemplate.getForObject(url, String.class); JSONObject obj = JSON.parseObject(res); String openid = obj.getString("openid"); // 2. 根据 openid 查找用户,不存在则创建 Users user = userMapper.selectOne( new LambdaQueryWrapper<Users>().eq(Users::getOpenid, openid)); if (user == null) { user = new Users(); user.setOpenid(openid); user.setNickname("新用户" + openid.substring(openid.length() - 4)); userMapper.insert(user); } // 3. 生成简易 token(生产环境请使用 jjwt 等库) String token = UUID.randomUUID().toString().replace("-", ""); return Result.success(token, user); }这段代码背后的逻辑,是“先查库再插入”的经典写法。openid字段在表里加了唯一索引,所以理论上并发请求时可能会出现插入冲突,毕业设计场景下可接受。如果你要在代码注释里解释这一点,可以写上“依赖数据库唯一索引保证用户不重复”作为题眼。
token 的生成方式在这里只是示例。我习惯用UUID简单处理,因为整套项目里没有引入 Redis 做会话存储,token 直接存在小程序端的 Storage 里、接口请求时传回即可。你在论文里需要明确一句:这个简易方案的局限性是不支持服务端主动失效 token,如果要扩展,可以接 Spring Security + JWT,代码量会多出一截,但论文里能多写一段“安全增强”。
3.2 发布接口:参数校验与图片 URL 的处理
发布失物和发布招领是两个接口,但逻辑非常相似。一个比较容易遗漏的点是:小程序端上传图片后,拿到的是文件访问 URL,后端接收的是一个字符串,而不是二进制图片本身。如果你把文件上传和发布接口写在一起,会让发布接口变得臃肿。比较干净的路线是:上传文件走独立的文件上传接口,发布接口只接收包含 imageUrl 在内的 JSON。
发布接口核心代码如下:
@PostMapping("/api/lost/publish") public Result publishLost(@RequestBody LostItem item, @RequestHeader("Authorization") String token) { // 1. 从 token 解析用户 ID(这里示例为 SimpleAuthUtil) Long userId = authUtil.getUserIdByToken(token); item.setUserId(userId); item.setStatus(0); // 寻物中 // 2. 基础校验:标题不能为空,图片 URL 必须是 http 开头 if (item.getTitle() == null || item.getTitle().isEmpty()) { return Result.error("标题不能为空"); } if (item.getImage() != null && !item.getImage().startsWith("http")) { return Result.error("图片地址非法"); } lostItemMapper.insert(item); return Result.success("发布成功"); }发布接口承担的工作非常少:解析 token、补充用户 ID、检查参数、插入数据库。状态字段status由后端强制写成 0,而不是交给前端传,这样的好处是防止用户手动调接口伪造已完成状态。前端传任何值都不会影响后端初始化逻辑,数据库层面也不会存在“status 为空”的脏数据。
如果你细心一点,会发现@RequestHeader("Authorization")这个参数名和我们之前定义的路由表是对应的。小程序端在每个请求的 header 里带上 token,后端通过拦截器统一校验。这个拦截器可以这样设计:判断Authorization是否为空,是否在缓存表里存在,如果没通过直接返回未登录状态码,小程序端统一跳转登录页。
3.3 列表查询:关键字搜索与分页参数的对应管理
列表页的第二个高频接口是“搜索 + 分页”。失物招领小程序跟电商类项目不太一样,用户的搜索意图通常是“找东西”或“找失主”,关键字命中标题和描述都能提高效率。后端一般这样写:
@GetMapping("/api/lost/list") public Result listLost(@RequestParam(defaultValue = "1") int page, @RequestParam(defaultValue = "10") int size, @RequestParam(required = false) String keyword, @RequestParam(required = false) Integer status) { Page<LostItem> p = new Page<>(page, size); LambdaQueryWrapper<LostItem> wrapper = new LambdaQueryWrapper<>(); if (keyword != null && !keyword.isEmpty()) { wrapper.and(w -> w.like(LostItem::getTitle, keyword) .or().like(LostItem::getDescription, keyword)); } if (status != null) { wrapper.eq(LostItem::getStatus, status); } wrapper.orderByDesc(LostItem::getCreateTime); lostItemMapper.selectPage(p, wrapper); return Result.success(p); }分页参数page从 1 开始,size默认 10。小程序端在onReachBottom触发上拉加载时,把当前页+1传上来,后端再把新的记录追加到列表尾部。keyword参数对 title 和 description 同时生效,注意这里用了wrapper.and(...)把两个like条件包起来,目的是避免 and 与 or 的优先级出问题。如果不加这层括号,SQL 会变成where status=? and title like ? or description like ?,这就会让“描述命中但 status 不对”的记录混进结果,是个很容易踩的坑。
另外,两个selectPage操作的是不同的表,失物、招领分开查,列表返回的Page对象里带有total、pages等字段,前端做“没有更多了”的判断时直接比对records.size()和total即可。我见过一些同学在这里反复查询两次数据库做总数统计,其实 MyBatis-Plus 的selectPage会把总数一并查出来,不要重复造轮子。
4. 微信小程序前端:请求封装、列表渲染与图片上传
4.1 全局配置与请求封装:request.js 的统一处理模式
小程序端和后端交互的第一个技术点,就是封装wx.request。如果每个页面都单独写一段wx.request,代码会显得非常冗余,而且 token 注入和错误提示会散落各页。比较好的做法是抽出utils/request.js,统一管理基础路径和请求头。
// utils/request.js const BASE_URL = 'http://localhost:8080'; function request(url, method = 'GET', data = {}) { return new Promise((resolve, reject) => { const token = wx.getStorageSync('token'); wx.request({ url: BASE_URL + url, method: method, data: data, header: { 'Content-Type': 'application/json', 'Authorization': token ? token : '' }, success: (res) => { if (res.data.code === 200) { resolve(res.data.data); } else if (res.data.code === 401) { wx.navigateTo({ url: '/pages/login/login' }); reject(res.data); } else { wx.showToast({ title: res.data.message, icon: 'none' }); reject(res.data); } }, fail: (err) => { wx.showToast({ title: '网络请求失败', icon: 'none' }); reject(err); } }); }); } module.exports = { request, BASE_URL };注意BASE_URL在开发阶段可以是localhost,但在真机预览时必须改成局域网 IP 或已备案的 HTTPS 域名。这是一个毕业设计交付时最容易埋的雷:模拟器能跑就完事了,结果老师用手机扫码体验,列表加载不出来。原因就是localhost指向的是手机自己,不是你的电脑。
请求封装返回的是一个 Promise,页面里通过async/await使用。登录态失效统一拦截,跳到登录页,不需要每个页面都写判断。这里的错误提示用wx.showToast,既可以展示业务消息,也可以显示网络错误。结构上,任何接口返回的数据都会被resolve,而网络失败和业务失败则走reject,这也是前后端分离项目里常见的“成功只看 code,不迷信 HTTP 状态码”的约定。
4.2 首页信息流列表与下拉刷新的正确打开方式
首页是整个小程序的流量入口,列表页一般会做成 tab 切换“寻物”和“招领”两个信息流。使用wx:for渲染lostItems数组,每个 item 展示标题、缩略图、地点、时间。上拉加载更多和下拉刷新两个事件,分别绑定到页面配置里。
{ "enablePullDownRefresh": true, "onReachBottomDistance": 50 }页面逻辑层的大致骨架:
Page({ data: { lostList: [], page: 1, hasMore: true, loading: false }, onLoad() { this.loadList(true); }, async loadList(reset = false) { if (this.data.loading) return; this.setData({ loading: true }); const page = reset ? 1 : this.data.page; try { const res = await request(`/api/lost/list?page=${page}&size=10`); const records = res.records || []; this.setData({ lostList: reset ? records : this.data.lostList.concat(records), page: page + 1, hasMore: records.length >= 10 }); } finally { this.setData({ loading: false }); } }, onReachBottom() { if (this.data.hasMore) { this.loadList(false); } }, onPullDownRefresh() { this.loadList(true).finally(() => wx.stopPullDownRefresh()); } });reset参数是个很容易忽略的细节。下拉刷新时必须重置页码为 1,并且用新数据覆盖旧数组,否则会出现“越来越多重复内容”的现象。而hasMore的判断依赖records.length >= size,如果后端返回不足 10 条,就说明没有更多数据了,可以停止触发上拉加载。时间字段在前端一般不做格式化,后端直接返回create_time,前端用new Date()转成可读格式,这样数据结构更干净。
前端渲染列表时,还要关注图片加载失败的情况。大部分用户的发布图片可能过大,加载缓慢。如果image字段为空,可以绑定一个默认占位图;如果图片 URL 失效,也要设置binderror事件切换成占位图。微信小程序的image组件在加载失败时会触发binderror,这里千万不要只顾样式设计而忽略异常路径。
4.3 发布页表单与图片上传:wx.uploadFile 的前后端字段对齐
发布页是小程序端比较复杂的一个场景,因为涉及文本字段和二进制图片的混合提交。wx.request只能发送 JSON 文本,wx.uploadFile才能发送文件。图片单独先传到独立接口,拿到 URL 后再随其他字段一起 POST 到发布接口,是更稳妥的前后端分离处理方式。
发布页的关键代码:
async publishLost() { const { title, description, location, lostTime } = this.data; if (!title.trim()) { wx.showToast({ title: '请填写标题', icon: 'none' }); return; } wx.showLoading({ title: '发布中...' }); // 1. 上传图片(如果有) let imageUrl = ''; if (this.data.imagePath) { const uploadRes = await new Promise((resolve, reject) => { wx.uploadFile({ url: BASE_URL + '/api/upload', filePath: this.data.imagePath, name: 'file', header: { 'Authorization': wx.getStorageSync('token') }, success: (res) => { const data = JSON.parse(res.data); if (data.code === 200) { resolve(data.data.url); } else { reject(data); } }, fail: reject }); }); imageUrl = uploadRes; } // 2. 发布失物 const res = await request('/api/lost/publish', 'POST', { title: title.trim(), description: description.trim(), location: location.trim(), lostTime: lostTime, image: imageUrl }); wx.hideLoading(); wx.showToast({ title: '发布成功', icon: 'success' }); wx.navigateBack(); }这段代码里有三个关键点。第一,wx.uploadFile的name字段必须和后端@RequestParam("file")保持一致,否则后端拿不到文件。第二,图片上传失败或返回格式不合法时,不能让发布流程继续,需要在uploadRes阶段 reject 掉。第三,发布成功后页面要navigateBack回到列表页,然后触发onPullDownRefresh拉取最新数据。
表单校验放在前端还是后端,答案是两个都做。前端保证基本格式正确,提升用户体验;后端保证数据合法,防止绕过前端直接调接口。比如title长度限制在 1-100 字、lostTime不能晚于当前时间,这类校验在两段代码里都要有,但不必重复太多文案细节。后端校验是数据最后一道防线,这一点在答辩时可以顺势介绍自己的接口设计一致性。
5. MySQL 部署、接口联调与高频报错排查
5.1 初始化 MySQL 数据库的完整命令顺序
拿到了源码,第一步是准备 MySQL 8.0 环境。完成安装并启动 MySQL 服务后,按照下面的顺序初始化数据库。直接用 MySQL 命令行而不是图形客户端,是为了把每一步的依赖关系看清。
# 1. 使用管理员账号连接 MySQL mysql -u root -p # 2. 建库,指定字符集 CREATE DATABASE lostfound DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci; # 3. 切换数据库 USE lostfound; # 4. 依次执行源码包里的建表脚本 source /path/to/sql/users.sql; source /path/to/sql/lost_items.sql; source /path/to/sql/found_items.sql; source /path/to/sql/claims.sql; # 5. 验证表是否创建成功 SHOW TABLES;执行source命令时建议一个表一个文件,这样哪一条报错能快速定位。如果你的源码包里只有一个汇总的init.sql,直接用mysql -u root -p lostfound < init.sql一次性导入也可以,但如果是答辩演示,分步执行更便于现场解释每一步的作用。初始化完成之后一定要执行SHOW TABLES确认四张表都在,同时顺手看一下SHOW CREATE TABLE lost_items\G,核对字段和注释是否和文档一致。
5.2 Spring Boot 连接 MySQL 的关键配置项
Spring Boot 的数据库连接配置在src/main/resources/application.yml里。源码包里也许已经填好,但你最好逐条理解配置的含义,而不是确认“能跑”就完事。
server: port: 8080 spring: datasource: driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://localhost:3306/lostfound?useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=Asia/Shanghai username: root password: yourpassword servlet: multipart: max-file-size: 10MB max-request-size: 10MB mybatis-plus: configuration: map-underscore-to-camel-case: trueserverTimezone=Asia/Shanghai一定要加,否则 MySQL 8 驱动可能会报时区错误。useSSL=false是因为本地开发不需要加密连接,加了反而在部分环境出现握手告警。map-underscore-to-camel-case开启后,数据库的create_time字段会自动映射到 Java 对象的createTime,这是 MyBatis-Plus 和 Spring Boot 结合时最常用的一个配置。
如果后端启动报错,第一件要检查的事是 MySQL 版本是否与驱动匹配。MySQL 5.7 要用com.mysql.jdbc.Driver,MySQL 8.0 则要用com.mysql.cj.jdbc.Driver。乱用驱动很容易出现ClassNotFoundException,这个错和 MySQL 本身没关系,就是驱动名写错了。
5.3 联调高频报错排查表
前后端分离项目联调阶段最常见的错误,多半是“接口通但数据不对”“数据对但页面不显示”。下面是这个项目里我遇到过的高频问题及排查方向:
| 现象 | 可能原因 | 修复方向 |
|---|---|---|
后端启动报Access denied for user | 数据库账号密码不对 | 检查 application.yml 的 username 和 password |
| 小程序列表页一直无数据 | 后端接口路径拼错或 HTTP 方法不对 | 打开 Network 面板,比对实际请求和 Controller 的 @RequestMapping |
| 登录成功但带不上用户信息 | token 没存到 Storage | 检查 wx.setStorageSync 是否在登录回调的 success 里 |
| 图片上传接口报 413 | 文件大小超过 multipart 限制 | 调大max-file-size,或压缩图片 |
| 列表数据显示 null | 字段名映射不上 | 确认map-underscore-to-camel-case已开启,或检查实体类字段名 |
| 接口返回 401 但页面没有跳登录 | res.data.code判断顺序不对 | 确保先判断 code 再判断 401 |
这其中最隐蔽的问题是“列表数据都是 null”。如果 database 的字段是create_time,而实体类写的是createTime且没有开启驼峰映射,Spring 返回 JSON 时createTime就是 null,但selectPage本身不报错。排查这类问题,后端别只看 HTTP 状态码,要打印日志对比实际 SQL 查询返回的字段名。
另一个高频问题是日期字段的时区错位。如果你插入记录后发现create_time比本地时间早了 8 小时,说明 JDBC URL 里没带serverTimezone=Asia/Shanghai。这个问题从日志里看不出来,但展示出来的时候非常显眼,评审老师一眼就能发现。
6. 把 LW 毕业论文的部署说明写到可直接复现
这是整个源码包的最后一个隐藏加分项:LW 说明文档。论文里的部署章节通常被学生写成“环境配置、运行后端、运行小程序”三句话,但这种粒度根本没法让另一个同学复现。真正合理的写法是把我上面讲的命令按顺序贴进去,并注释清楚每一步的目的。举例来说,论文里的部署小节可以写成这样:
# 步骤 1:创建数据库和表 # 使用 MySQL 命令行,以 root 身份登录,逐条执行 init.sql 内容 mysql -u root -p -e "CREATE DATABASE lostfound DEFAULT CHARACTER SET utf8mb4;" # 步骤 2:启动后端 # 在命令行进入 backend 目录,执行 mvn spring-boot:run # 后端默认占用 8080 端口,启动日志会显示 "Started Application" # 步骤 3:启动小程序 # 使用微信开发者工具导入 miniprogram 目录 # 在 utils/request.js 中把 BASE_URL 改成你的局域网 IP # 编译运行后,模拟器可以看到失物列表这段内容写清楚,等于给评审老师一份可以从头复现的手册。建议额外在文档里放一张表,列清“后端接口路径、请求方式、参数列表”,不要只放一个 Swagger 截图。因为不是每个评审老师都会装 Postman 或打开 Swagger,但一张表格谁都能读懂。
最后提交源码包之前,一定要做一次“手动清理”:删除本地的target目录、node_modules目录、logs文件、IDE 生成的.idea和.vscode隐藏配置,再把application.yml里的数据库密码改成root/123456这种通用值,避免把个人密码泄露在源码里。压缩包解压之后,从 README 的第一行开始按照你写的部署步骤完整跑一遍,确认在干净环境下也能启动成功,这份毕业设计才真正算交付完成。
本文还有配套的精品资源,点击获取