不知道你有没有想过,一所万人规模的学校里,每天到底会产生多少件遗失物品。从我接触过的几个高校后勤项目来看,光是学生餐厅、图书馆和操场这三个地方,一天捡到的水杯、校园卡、耳机、钥匙就有几十件。这些物品大部分被送到失物招领点之后,就只能在纸板箱里躺着,等着失主碰巧路过翻一眼。信息全靠公告栏一张A4纸,状态全靠管理员手工记账,认领核实靠感觉,逾期处理靠随缘。
这就是“基于web的校园失物招领系统”要做的事——把这条传统链路搬到Web端,把“捡到东西的人找不到失主、丢了东西的人找不到物品”这个信息不对称问题,用一套清晰的业务闭环解决掉。它本质上是典型的信息发布与状态流转系统,但又比普通的CRUD多了一层业务逻辑:物品状态会随认领过程不断变化,而且同时存在两类发布者、两类需求方。
如果你正准备做毕业设计、课程设计,或者只是想拿一个完整的Web项目练手,这篇内容会比较对路。我尽量不给你贴那种“系统功能结构图+核心代码片段”的拼凑产物,而是把从需求分析到实现落地中间那些没人明说、但一定会遇到的决策过程讲清楚,包括为什么这样设计表结构、状态机怎么定、哪些坑是上了线才暴露的。
1. 失物招领的“真实痛点”与系统需求边界
1.1 现实场景里的失物招领有多混乱
先别急着写代码。任何一个正经项目,第一步都是把现实问题拆明白。校园失物招领最大的痛点不是“没人捡到东西”,而是“信息根本无法高效触达”。
我调研过几个校园场景,普遍情况是这样的:拾获者在教学楼或食堂捡到物品后,要么交给值班室,要么拍照发到学生群里问一嘴,然后就没有然后了。值班室的纸质登记本上写着“捡到黑色双肩包一个,内含课本若干”,失主根本不会跑去值班室翻本子,他大概率是在朋友圈和各种群里发“万能墙求转发”。两边都在努力,但因为渠道分散、信息结构不统一,匹配效率极低。
第二个痛点是状态不可追踪。失主去失物招领点问,管理员只能说“我帮你看看登记本”。如果物品已经被认领了,没有记录;如果物品过了一周没人认领,被转移到其他地方,也没有记录。这种情况下,系统要解决的核心就不是“把登记本电子化”,而是构建一条完整的物品生命周期管理链路:从拾获登记、信息发布、失主查询匹配、认领申请、身份核验,到最终状态归档,每一步都要在系统里有迹可循。
1.2 三类角色与核心业务闭环
基于上面的痛点,角色划分就非常自然了:管理员、拾获发布者、失主。有些系统还单独拆了一个“游客”角色出来,但我建议在毕业设计里不做匿名互动,所有发布和认领操作必须基于登录用户身份,毕竟失物招领涉及到物品归属,匿名操作会带来身份核验困难。
核心业务闭环可以这样描述:
拾获者登录系统,登记物品信息(分类、拾获地点、拾获时间、外观描述、图片),提交后进入管理员审核队列。审核通过后,物品状态变为“待认领”,信息出现在公示列表中。失主通过搜索或浏览发现疑似自己的物品后,发起认领申请,填写物品特征、丢失时间地点等信息作为佐证。拾获者或管理员审核申请,如果匹配成功,双方约定线下核验,确认无误后系统将状态更新为“已完成”。如果一段时间内无人认领,管理员可以手动将物品标记为“逾期处理”。
这个闭环里,最容易被忽视也最值得做扎实的是“认领申请与审核”这个环节。很多初版设计只会做“发布—浏览—联系”三个功能,把电话一留就完事了。但现实是,校园里冒领、错领的情况并不少见,如果系统里没有申请和佐证记录,一旦产生纠纷,后续处理会非常被动。
1.3 功能列表怎么定——既要完整又要可控
毕业设计的功能范围是一个经典的“度”的问题。功能太少,论文写起来单薄,评委一眼看穿;功能太猛,课时和精力兜不住,代码烂尾风险急剧上升。
我的建议是:把基础功能做扎实,把1-2个亮点功能做深。基础功能按角色拆如下:
- 学生端:注册登录、发布拾获信息、发布遗失信息(可选)、浏览失物列表、按分类/地点/关键词搜索、提交认领申请、查看自己的发布与申请记录。
- 管理端:用户管理、物品信息审核(通过/驳回/下架)、分类管理、认领申请审核、数据统计面板、公告管理。
这些功能已经能撑起一个完整课题了。至于消息推送、手机短信验证、LBS地理定位之类,属于锦上添花但成本不低的东西,如果时间有限,我不建议在初版里碰。先把主流程跑通,后面有富余时间再迭代。
提示:课题范围定得太大,答辩时会给自己挖坑。评委追问任何一个模块的实现细节,你没有做过,场面会非常尴尬。宁可少做一个模块,也必须保证做过的每个模块都经得起追问。
2. 技术选型:为什么是这套组合而不是别的
2.1 后端框架:Spring Boot 是稳妥的主力选择
我做这套系统的核心后端用的是 Spring Boot 2.x,搭配 MyBatis Plus 做持久层。选这套组合的原因很朴素:可靠、省事、查资料方便。
很多人纠结要不要用 SSM 传统架构,或者干脆用 Servlet + JSP 一步步写。我的态度是,除非学校有明确规定必须用某种技术,否则不要自找麻烦。Spring Boot 的价值在于它帮你把配置的事干完了大半,内置 Tomcat、自动装配、Starter 依赖,你只需要关注业务代码。对于一个失物招领系统,业务复杂度集中在状态流转和查询条件拼接上,Spring Boot 的处理方式足够直接。
MyBatis Plus 对毕业设计尤其友好。它内置了通用的增删改查方法,单表操作不需要写 XML,多条件查询直接用 LambdaQueryWrapper 拼接,代码量比手写 JDBC 或纯 MyBatis 少一个量级,而且它对新手来说几乎没有上手门槛。
2.2 前端与交互:Vue + Element UI 分离式开发
前端我用的是 Vue 2 + Element UI,开发模式上选择了前后端分离。后端只提供 JSON 接口,前端通过 Axios 调用。这个组合现在看起来不算“新”,但胜在极其成熟,Element UI 的表单、表格、上传组件覆盖了本系统 90% 的界面需要。
你也可以选 Vue 3 + Element Plus,差别不大。真正重要的是,前后端分离模式下,你需要额外考虑跨域问题。我在本地开发时配置了后端 CORS 策略,允许指定地址跨域访问,部署时再把前端构建产物放到 Nginx 下,通过反向代理转发/api请求。
如果你对前端不太熟,还有一个备选方案:服务端渲染,用 Thymeleaf 或 JSP 直接在后端写页面。好处是少了一层跨域,坏处是前端交互逻辑和后端代码耦合在一起,改起界面来比较痛苦。我个人的建议是,哪怕前端基础一般,也尽量走前后端分离,因为 Vue 的表单校验、组件化开发,会让开发效率明显高于手写 JSP 标签。
2.3 为什么不上中间件——低并发场景的架构克制
失物招领系统是一个典型的低并发、高事务一致性需求业务。它的单位时间访问量可能只有几十个人,但每一笔认领操作都需要准确记录状态。
因此,我不建议在这个项目里引入 Redis 缓存、消息队列、分布式文件存储这类重型中间件。Redis 当然可以拿来做 Session 共享或热点物品缓存,但在这个体量下,它带来的架构复杂度远大于收益。文件存储我建议直接用服务器本地磁盘,配合 Nginx 做静态资源访问,完全够用。
这背后是一条很重要的原则:架构应该服务于业务特征。用不着为了简历好看,在毕业设计里强行堆 K8s、微服务那一套。如果你将来面试被问到为什么不用 Redis,你完全可以理直气壮地回答:“当前业务规模下,MySQL 的性能完全能够承载,引入缓存反而会带来数据一致性问题。”这话反而会加分,因为它体现了你对技术选型有自己的判断。
3. 数据库设计:失物招领的核心实体与状态机
3.1 核心表结构:一张物品表还是两张?
这是整个数据库设计里最关键的一个决策点。
很多教材和网上的参考设计会把“拾获物品”和“遗失物品”拆成两张表。理由是这两类信息的字段有差异,比如遗失信息通常需要填写“丢失地点”,而拾获信息要填“拾获地点”。但我做完这个系统之后,强烈建议改成一张表加类型字段的设计。
理由有两点。第一,失物招领的业务闭环里,拾获物品信息是“主数据”,遗失信息本质上是“需求方发布的一条公告”,两类信息只有在同一个列表、同一个搜索入口里被综合匹配,才能真正解决信息不对称问题。如果拆成两张表,查询“用户可能丢失或捡到的物品”时要分别查两张表再合并,逻辑会非常别扭。
第二,认领申请这个核心操作,需要同时关联“物品”和“申请人”。拆成两张表时,要么做两张认领申请表,要么做一张申请表但用多态外键(物品类型 + 物品ID),无论哪种都会增加复杂度。
我最终的设计就是这样一张核心表:
| 字段名 | 类型 | 说明 |
|---|---|---|
| id | bigint | 主键 |
| user_id | bigint | 发布者ID |
| type | tinyint | 物品类型:1-拾获,2-遗失 |
| title | varchar | 标题,如“捡到一张校园卡” |
| category | varchar | 物品分类:证件/电子/服饰/其他 |
| place | varchar | 发生地点 |
| happen_time | datetime | 发生时间 |
| description | text | 详细描述 |
| image_url | varchar | 图片路径 |
| status | tinyint | 状态:见下方状态机 |
| create_time | datetime | 发布时间 |
一个表把拾获和遗失统一存储,查询时通过type字段快速过滤,需要展示详情时再根据类型做差异化渲染。这是我在实际开发中反复调整后确认最顺手的设计。
3.2 状态机的设计:从待审核到已完成的流转约束
失物招领系统的状态变化,是整个业务逻辑里最值得花时间设计的部分。我的状态流转规则是这样定义的:
- 0 待审核:拾获者提交信息后,等待管理员审核。
- 1 已发布:审核通过,信息对外可见,处于待认领状态。
- 3 认领中:有用户提交了认领申请,状态从“已发布”变更为“认领中”(表示有候选人了)。
- 5 已完成:经核实,申请者确实为失主,线下完成交接。
- 6 已关闭:审核未通过、被管理员下架,或长时间无人认领被处理。
这里有一个很重要的细节:状态只能沿着合法的方向流转,不能跳变。比如“待审核”只能变到“已发布”或“已关闭”,不能直接跳到“已完成”;“认领中”不能在没有核验通过的情况下直接变回“已发布”。这个约束在代码层面要做校验,不能只靠前端按钮控制。
为什么要单独设一个“认领中”中间态?因为当有人提交申请后,这个物品就进入了协商流程,此时它不应该再出现在“待认领”列表里,防止多个失主同时盯着同一件物品产生混乱。但同时,万一协商失败,它还需要能回到“待认领”状态重新公开,所以中间态给了状态回退的余地。
3.3 图片存储:是存路径还是存 base64?
这是在开发过程中经常有人搞混的点。图片在数据库里的存储方式有三种:直接存二进制(BLOB)、存 base64 编码字符串、存文件路径。
我强烈不建议前两种。BLOB 会显著增大数据库体积,备份和迁移都很痛苦;base64 字符串会让 JSON 报文膨胀约 33%,而且每次列表加载都要传输大字段,接口响应时间会明显变慢。
正确做法是:图片文件上传到服务器的指定磁盘目录,数据库里只存相对路径,比如/upload/2025/06/17/xxx.jpg。前端访问时,由 Web 服务器(开发环境是 Spring Boot 的静态资源映射,生产环境是 Nginx)把该路径映射到物理文件。这样数据库表干净,文件备份也直观。
3.4 关键查询:多条件筛选与模糊匹配
失主找一个物品,最常用的操作是“按分类 + 地点 + 关键词”组合筛选。这个查询逻辑看着简单,但里面有坑:用户可能只填了分类,也可能只填了关键词,还可能什么都没填只想看最新发布。这个时候,SQL 的条件拼接不能硬编码,在 MyBatis Plus 里可以用 LambdaQueryWrapper 优雅处理。
LambdaQueryWrapper<LostItem> wrapper = new LambdaQueryWrapper<>(); // 分类不为空时才拼接分类条件 wrapper.eq(StringUtils.hasText(query.getCategory()), LostItem::getCategory, query.getCategory()); // 状态固定为已发布,只让用户看到审核通过的物品 wrapper.eq(LostItem::getStatus, 1); // 关键词模糊搜索标题或描述 if (StringUtils.hasText(query.getKeyword())) { wrapper.and(w -> w.like(LostItem::getTitle, query.getKeyword()) .or() .like(LostItem::getDescription, query.getKeyword())); } // 按发布时间倒序 wrapper.orderByDesc(LostItem::getCreateTime);至于关键词匹配,在数据量不大的情况下,MySQL 的LIKE '%keyword%'已经够用。如果你想让搜索体验更好,可以给标题和描述加全文索引,但那是进阶优化,不是必需项。不要在这个项目里引入 Elasticsearch,上文已经说过,别给自己找麻烦。
4. 核心模块实现细节:从发布到认领的完整链路
4.1 发布模块:图片上传的落地实现
发布拾获信息是系统最核心的入口。它的实现难点不在文本字段,而在图片上传。
前端我用 Element UI 的 el-upload 组件,提交方式选action直传后端接口,而不是先传给后端再拿 URL 塞进表单。这样做的好处是图片上传和表单提交解耦,用户先传图、再填文字,即使最后放弃提交,也不会产生一条没有图片的脏数据。
后端接收文件时,有几个关键细节:
@PostMapping("/upload") public Result<String> upload(@RequestParam("file") MultipartFile file) { // 1. 校验文件大小,限制在5MB以内 if (file.getSize() > 5 * 1024 * 1024) { return Result.error("图片大小不能超过5MB"); } // 2. 校验文件类型,只允许jpg/png格式 String originalFilename = file.getOriginalFilename(); String ext = originalFilename.substring(originalFilename.lastIndexOf(".")); if (!Arrays.asList(".jpg", ".jpeg", ".png", ".gif").contains(ext)) { return Result.error("仅支持jpg/png/gif格式"); } // 3. 生成不重复的新文件名,避免中文名和重名覆盖问题 String filename = UUID.randomUUID().toString().replace("-", "") + ext; // 4. 按日期分目录存储,方便按时间归档清理 String dateDir = new SimpleDateFormat("yyyyMMdd").format(new Date()); String filePath = uploadDir + File.separator + dateDir + File.separator + filename; file.transferTo(new File(filePath)); // 5. 返回给前端的路径是相对路径 return Result.success("/upload/" + dateDir + "/" + filename); }这里最值得注意的两个细节:文件名必须用 UUID 重写,不能用原始文件名。用户上传的照片可能都叫IMG_20250617.jpg,不重写就会互相覆盖;中文文件名在某些编码环境下会乱码,干脆从一开始就避开。按日期分目录也是一个小技巧,后续如果做“超过30天无人认领自动清理”,扫描目录时会方便得多。
4.2 认领流程:申请提交与状态变更的并发防护
认领模块是这个系统的业务灵魂。我把它设计成两步操作:第一步,失主浏览物品详情后点击“申请认领”,填写一个认领表单,内容包括“丢失物品细节描述”“丢失时间与地点”,前端必须限制至少50字,防止用户一个字就提交上来。第二步,管理员或拾获者进入申请列表,查看失主的佐证描述,与实物核对,决定“确认归还”或“驳回申请”。
这一步最容易踩的坑是:同一件物品被多人同时提交认领申请,如果处理不当,可能会出现两个人同时“认领成功”的并发问题。
防护方案是,提交申请时必须在后端校验物品当前状态:
if (lostItemService.getById(itemId).getStatus() != 1) { return Result.error("该物品当前暂不可认领"); }但这只是第一层。更严谨的做法是加状态条件更新,直接把“状态从已发布改为认领中”合并为一个原子操作:
boolean updated = lostItemService.update( new LambdaUpdateWrapper<LostItem>() .eq(LostItem::getId, itemId) .eq(LostItem::getStatus, 1) // 只有当前是已发布状态才能更新 .set(LostItem::getStatus, 3) ); if (!updated) { return Result.error("该物品已被其他用户申请,请稍后再试"); }这种方式叫做乐观锁思路,通过 WHERE 条件保证状态变更的原子性。即使两个用户同时提交,数据库也只会让其中一个更新成功,另一个更新行数为0,直接返回失败。这个细节建议在论文的“系统实现”章节里专门写一段,因为它是评委比较看重的事务一致性体现。
4.3 管理端:信息审核与统计面板
管理端的功能不复杂,但很能体现系统的完整性。信息审核列表需要支持分页和状态筛选,管理员可以看到所有待审核、已发布、已完成、已关闭的物品,逐个查看详情,审核通过或驳回。
统计面板我用的是 ECharts 和柱状图、饼图。后端提供几个简单的聚合接口,按物品分类统计发布数量、按状态统计物品流转情况、按最近7天统计新增失物趋势。实现方法就是一个GROUP BY加COUNT,不需要额外组件。
@GetMapping("/statistics/category") public Result<List<Map<String, Object>>> statisticsByCategory() { QueryWrapper<LostItem> wrapper = new QueryWrapper<>(); wrapper.select("category", "count(*) as total"); wrapper.groupBy("category"); return Result.success(lostItemService.listMaps(wrapper)); }统计面板能不能用,取决于你有没有真实数据。我建议在答辩前手动构造30-50条模拟物品数据,覆盖不同分类和状态,这样生成的图表才好看,演示的时候也不会出现“空面板”的尴尬。
4.4 权限控制:拦截器与角色路由
权限控制这块,我用了简单的登录拦截器加角色判断,没有引入 Spring Security。理由还是那句话,复杂度要跟着业务走。Spring Security 的过滤器链和 UserDetailsService 机制,对这个项目来说有点重,而且配置出错排查起来比较痛苦。
我的做法是自定义一个 HandlerInterceptor:
public class AuthInterceptor implements HandlerInterceptor { @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { HttpSession session = request.getSession(); User user = (User) session.getAttribute("user"); // 未登录则跳转到登录页 if (user == null) { response.sendRedirect("/api/auth/unauthorized"); return false; } // 需要管理员权限的接口校验角色 if (request.getRequestURI().startsWith("/api/admin") && !"admin".equals(user.getRole())) { response.setStatus(403); return false; } return true; } }前端配合 Vue Router 的路由守卫,控制页面跳转权限。这套方案应对单管理员加若干普通用户的场景绰绰有余,代码量少,思路清晰,也方便在答辩时讲清楚。
5. 实测踩坑记录:文件路径、空格搜索与状态误操作
5.1 图片上传成功但页面显示 404,排查全过程
这个坑基本上每个做文件上传的人都会踩,我说说当时的完整排查链路。
现象:上传接口返回成功,数据库里也存了路径,但页面上的图片就是裂开。打开浏览器开发者工具,发现图片请求地址是http://localhost:8080/upload/20250617/xxx.jpg,网络面板显示 404。
第一步,我先检查后端控制台有没有异常日志。发现没有任何报错,说明请求根本没到达 Spring MVC 的 DispatcherServlet,而是被容器直接返回 404 了。第二步,我去服务器上找上传目录,发现文件其实已经正确写入了/home/ubuntu/upload/20250617/xxx.jpg。第三步,问题定位到静态资源映射——Spring Boot 默认只把classpath:/static/作为静态资源目录,磁盘上的/home/ubuntu/upload并不在映射范围内。
解决办法是在配置类里显式注册资源映射器:
@Configuration public class WebConfig implements WebMvcConfigurer { @Override public void addResourceHandlers(ResourceHandlerRegistry registry) { // 将 /upload/** 映射到本地磁盘上传目录 registry.addResourceHandler("/upload/**") .addResourceLocations("file:" + uploadDir + "/"); } }这里有个细节容易忽略:addResourceLocations里的路径必须以file:开头,并且目录末尾要带/,否则会映射失败。排查这一路走下来,最大的教训是:遇到 404,不要先去怀疑路径拼错了,先确认“资源在哪里”和“映射规则是什么”,两者对上才能通。
5.2 多条件查询时,用户清空输入框导致查不到数据
这个问题听起来低级,但确实发生过。用户第一次搜索时填了关键词“校园卡”,查出了结果,然后清空输入框想重新看全部物品,结果列表空了。
原因在代码里:我在拼接查询条件时,用keyword != null判断是否拼接 LIKE 条件。用户清空输入框后,前端传过来的keyword是空字符串"",而不是null。空字符串不等于 null,所以 LIKE 条件依然被拼上了,查出来的结果自然为空。
修复方案很简单,统一用StringUtils.hasText()判断,它同时处理null、空串、纯空格三种情况。这个细节也提醒了我一件事:前后端交互时,参数判断一定要把边界情况想全,尤其是清空输入框这种高频操作。
5.3 状态误操作:已完成物品还能被撤销
这个坑踩在管理员后台的操作逻辑上。我原来在管理端的“已完成”列表里提供了一个“撤销完成”按钮,目的是方便管理员误操作后回退。结果某天测试时发现,一个已经完成交接的物品,被撤销后重新变为“待认领”,而且原来的认领申请记录还挂在下面,数据变得非常诡异。
后来我决定砍掉这个操作,代码层面对“已完成”状态设置硬约束,禁止任何回退。道理很简单:认领完成的本质是“线下交接结束”,这个状态一旦确认,意味着物品已经物理上归还给了某人,后续如果出现问题,需要走线下流程处理,而不是在系统里改一个状态了事。
这也是毕业设计里一个很好的思辨点:业务上的“不可逆操作”,在系统里要通过状态机的约束来体现。你可以把这条写进论文的“系统不足与改进”,说明自己认识到状态回退的风险,并做了约束处理。
5.4 列表页样式崩坏与前端组件版本冲突
最后一个小坑是关于 Element UI 的。开发初期我图省事,直接在网上找了一个管理后台模板,结果模板自带的 Element UI 版本和我自己写的组件版本不一致,列表页表格样式全部错乱,按钮图标也显示不出来。
排查方式没什么技巧:看控制台的报错信息,发现是打包依赖冲突。最后手动统一了前端 package.json 里的依赖版本,把模板里多余的组件删掉,只保留自己需要的部分。这里劝一句,模板可以用来参考布局,但直接整个搬过来改,往往比从零写还费劲。
6. 从系统实现到毕业答辩:论文怎么写、演示怎么讲
6.1 论文结构:按软件工程标准来,但别堆过程文档
就算系统写得很完整,论文写不好,答辩一样吃亏。我的建议是严格按软件工程的标准来组织:绪论(背景、意义、国内外现状)、需求分析(用例图、用例描述、功能需求与非功能需求)、系统设计(总体架构、功能模块设计、数据库设计)、系统实现(关键功能截图加核心代码说明)、系统测试(测试环境、测试用例、测试结果)。
每一章都有它的价值,不用质疑为什么毕业设计论文都长这样,因为评委习惯了这种结构,他不需要你在格式上创新,在内容里体现思考就够了。
数据库设计那一章,重点画 ER 图和表结构说明,把状态机的定义讲清楚。系统实现那一章,不要贴大段完整代码,挑两三个关键点贴就行,比如图片上传的校验逻辑、认领申请的状态原子更新。这两个点最能体现你对系统核心业务的理解。
6.2 答辩演示脚本:提前把数据准备得漂漂亮亮
答辩演示时翻车,多半不是代码 bug,而是环境问题或数据问题。提前准备一个干净利落的演示脚本,比临时对着系统瞎点强一百倍。
我的演示顺序是这样的:先用管理员账号登录,展示统计面板,说明系统运行了多久、有多少条数据。然后切到普通用户视角,演示一次完整的失物招领流程:发布一条拾获信息(提前准备好测试图片),管理员审核通过,切换到另一个用户账号搜索该物品,提交认领申请,管理员确认认领,状态变为已完成。最后展示一下个人中心里我最近发布的物品和申请记录。
要注意几个细节:演示前先确认测试图片在服务器上能正常访问,确认管理员和普通用户账号都已准备好,确认没有其他人动过测试数据。有些同学演示到一半发现列表数据被人清空了,那种场面在答辩现场真的很难救。
6.3 关于“基于web的校园失物招领系统”的亮点与扩展点
做完之后回头看,这个项目的价值其实不只是“做了一个毕业设计”。它可以成为你简历上一个完整的全栈项目案例,也可以作为后续很多扩展研究的底座。比如可以接入学校统一身份认证,把登录环节做得更贴近实际场景;可以用图像识别辅助物品分类,拍一张照片自动识别是证件、钥匙还是电子产品;可以做物品相似度匹配,失主发一条遗失信息,系统自动推荐相近的拾获物品。
这些扩展点不用真的全部做出来,但在论文的“总结与展望”里写出来,会让评委觉得你有继续深挖的能力。
最后再分享一点个人体会:做完这个项目,我最大的感受是——毕业设计的难点从来不在某个技术点有多难攻克,而在于能不能把现实世界的模糊需求,拆解成一套逻辑自洽、节点明确、可验证的系统方案。失物招领系统的业务规则不算复杂,但它教会我最重要的一件事是:状态机的设计决定了业务逻辑的严谨程度,而严谨程度才是评委判断你是否真正“理解”了系统的分水岭。如果你在做的过程中也遇到了类似的问题,不妨回头审视一下自己的状态流转,很多时候问题就藏在那一条看似不起眼的转变规则里。