简介:基于校园失物招领系统的毕业设计全套资料包,面向计算机相关专业(人工智能、通信工程、自动化、电子信息、物联网等)的在校学生、教师或开发者,可直接用于毕业设计、课程设计、项目初期立项演示,也可作为失物招领Web应用开发的学习案例。压缩包共632个文件,大小约27.12MB,涵盖PHP后端源码、tpl模板、前端JS交互与CSS样式、jpg/png图片素材、SQL数据库脚本、Markdown说明文档等,目录按功能模块划分,便于定位代码与资料,二次开发时可直接修改对应模块。项目代码经测试运行成功,获导师认可,答辩评审分达95分,完整性较高;详细文档覆盖需求分析、概要设计、数据库设计等关键环节,方便理解系统从数据表到接口的实现思路。已有91人学习下载,适合需要快速搭建同类型Web系统的毕设学生,也适合有一定基础的开发者在其上进行功能扩展,或新手对照源码逐步进阶。
1. 这套校园失物招领系统毕设,到底给你交付了什么
如果你正在找毕业设计题目,或者已经拿到了一个名为“校园失物招领系统”的完整资料包,心里嘀咕它到底够不够用,那我直接说结论:这类系统是Web方向里少有的“业务闭环完整、技术点适中、演示效果直观”的题目。它覆盖了用户注册登录、失物发布、寻物启事、认领申请、审核流转、留言通知、后台管理等一套完整流程,既能体现你对业务的理解,又能展示数据库设计、接口编写和前端交互的基本功,答辩时也容易讲清楚。
这篇笔记我会按照一线开发者的习惯,把这个题目拆成“立得住的技术方案”和“能落地的代码实现”两部分。你会看到技术栈怎么选、核心表怎么建、认领流程怎么写、文件上传和消息通知怎么接,以及那些让很多人熬夜排查的坑到底在哪。我写的每一段代码都能直接复制到你的项目里改一改就跑,而不是空谈架构。这篇更适合已经学过Java和数据库基础、但还没独立做过完整系统的同学,也适合拿了这个资料包但不知道怎么消化的人。
2. 技术选型与模块拆分:先把架构立稳再动手写代码
2.1 为什么说失物招领系统是最稳的“可选”毕设方向
很多毕设题目看起来高大上,但实际做着做着就发现范围失控。比如做个“智能推荐系统”,如果推荐算法不够深,答辩时容易被追问到原理细节;做个“商城系统”,订单、支付、库存全堆上去,光联调就要一个月。校园失物招领系统最大的优势在于:业务场景限定在学校内,角色清晰,数据量不大,但该有的流程一个不少。
从导师视角看,这个题目能考察的点很集中:数据库设计是否合理(物品分类、状态流转、关联查询)、接口是否有权限控制(用户只能操作自己的数据)、前端是否有基本的交互反馈(发布表单、列表筛选、详情弹窗)。从学生视角看,它的功能边界很容易控制:你可以只做Web端,也可以加上小程序端;你可以用SSM框架,也可以直接用Spring Boot。我最推荐的组合是Spring Boot + MyBatis Plus + Vue,下面我会解释为什么。
这套系统的典型角色只有三种:普通学生用户、管理员(通常是校方或学生会)、以及可能的“超级管理员”。用户发布失物或寻物信息,其他用户浏览后发起认领申请,失主确认后完成认领;管理员负责审核敏感信息、标记已找回、删除违规内容。就这么简单,但每一个环节都有可以展开讲的细节。
2.2 一套常见可复现的技术栈:Spring Boot + MyBatis Plus + Vue
不要一上来就追新,毕设的底线是“稳定可不翻车、原理讲得清”。Spring Boot 2.x + MyBatis Plus + Vue 2 + Element UI 是我这几年看过最多同类项目采用的组合,也是你最容易找到资料、遇到问题能搜到答案的组合。Spring Boot负责把接口层、业务层和数据层串起来,MyBatis Plus帮你省掉大量单表CRUD的XML,Vue加Element UI能把后台管理界面做得像模像样。
后端项目的基本包结构我会按下述方式组织。这样做的好处是:Controller层只做参数接收和结果包装,Service层写业务逻辑,Mapper层只碰数据库,后续答辩被问到分层架构时你能说得非常清楚。
com.campus.lostfound ├── controller // 接口入口,如 LostController、ClaimController ├── service // 业务逻辑接口 ├── service.impl // 业务逻辑实现 ├── mapper // MyBatis Plus的Mapper接口 ├── entity // 数据库实体类 ├── dto // 前端传入的参数对象 ├── vo // 返回给前端的视图对象 ├── config // 全局配置,如文件上传、拦截器 └── common // 统一返回结果、异常处理、常量统一返回结果的类建议一开始就写好,别写到一半再补。前端每个请求都期望拿到一个固定的结构,我一般定义为“code、message、data”三件套,成功时code为200,业务失败时code为500或自定义值。这个习惯能让你后面联调时省下一大半没必要的沟通。
@Data public class Result<T> { private Integer code; private String message; private T data; public static <T> Result<T> ok(T data) { Result<T> r = new Result<>(); r.setCode(200); r.setMessage("success"); r.setData(data); return r; } public static <T> Result<T> fail(String message) { Result<T> r = new Result<>(); r.setCode(500); r.setMessage(message); return r; } }代码说明:泛型T让这个类可以包装任意类型的数据,查询用户时T就是User对象,查询列表时T就是List 。fail方法用于业务校验不通过的情况,比如“这件失物已被认领”。把Result放在common包里,所有Controller统一使用,这样全局异常处理器也方便统一兜底。
数据库这块不用太花哨,MySQL 5.7或8.0都行。表结构我会控制在六到八张:用户表、失物表、寻物表、认领申请表、留言表、通知表,外加分类字典表和操作日志表。具体字段下面一小节讲。
前端部分,Vue 2加Element UI依然是最稳妥的。Vue 3加Element Plus当然也可以,但如果你拿到的资料包或参考代码是Vue 2写的,强行升级会给自己制造很多不必要的坑。页面不用多,学生端五个页面足够:首页列表、发布失物、发布寻物、个人中心、我的认领/我的发布;管理端三到四个页面:失物审核、认领审核、用户管理、数据概览。
2.3 模块边界与数据表设计:搞清“谁在发布、谁来认领”
很多人做这个题目上来就写代码,写到认领申请时发现“失主确认”这个动作不知道该改哪张表的状态,只好临时加字段,最后整个表结构乱成一团。正确做法是先画一张简单的业务流转图:用户发布失物 -> 失物状态为“待认领” -> 其他用户提交认领申请 -> 失主查看申请列表 -> 失主确认某个申请 -> 失物状态变为“已认领”,同时该申请状态变为“通过”,其他申请自动变为“已拒绝”。
围绕这个流转,最核心的表是失物表和认领申请表。失物表我会这样设计:
CREATE TABLE `lost_item` ( `id` bigint(20) NOT NULL AUTO_INCREMENT, `user_id` bigint(20) NOT NULL COMMENT '发布人ID', `title` varchar(100) NOT NULL COMMENT '失物标题', `description` varchar(500) DEFAULT NULL COMMENT '详细描述', `category` varchar(50) DEFAULT NULL COMMENT '分类:证件/电子/书籍/其他', `pick_up_location` varchar(100) DEFAULT NULL COMMENT '拾获地点', `lost_location` varchar(100) DEFAULT NULL COMMENT '丢失地点', `image_url` varchar(200) DEFAULT NULL COMMENT '失物图片', `status` tinyint(4) NOT NULL DEFAULT '0' COMMENT '0待认领 1已认领 2已撤销', `created_time` datetime DEFAULT NULL, `update_time` datetime DEFAULT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='失物表';字段说明:user_id关联用户表但不建外键约束,实际开发里外键只增加维护成本,靠代码保证数据一致性即可。status是整个系统的核心,所有列表查询、按钮显示、权限判断都要依赖它。上传的图片只存相对路径,不要存完整http链接,这样换服务器不用改库。
认领申请表是容易设计错的地方。我见过有同学把“认领人ID”直接加在失物表上,这样第二个人来认领时就得加第二行或覆盖原值,完全没法支持多个申请同时存在。正确做法是单独建表:
CREATE TABLE `claim_apply` ( `id` bigint(20) NOT NULL AUTO_INCREMENT, `lost_item_id` bigint(20) NOT NULL COMMENT '失物ID', `claim_user_id` bigint(20) NOT NULL COMMENT '认领人ID', `claim_reason` varchar(300) DEFAULT NULL COMMENT '认领描述,如特征说明', `contact_info` varchar(100) DEFAULT NULL COMMENT '联系方式', `status` tinyint(4) NOT NULL DEFAULT '0' COMMENT '0待审核 1已通过 2已拒绝', `created_time` datetime DEFAULT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='认领申请表';这里status的流转非常关键:提交申请时状态为0待审核;失主在“我的发布”里看到申请列表,选择通过则置为1,同时把对应的lost_item状态改为1已认领;如果选择了拒绝则置为2。一个失物对应多条认领申请,但只有一条能被置为通过,这个约束逻辑写在Service里,而不是数据库约束,原因是你需要在同一方法里同时更新两张表。
3. 核心代码落一遍:失物发布、认领流程与消息通知
3.1 失物发布接口:从Controller到Service的参数与校验
发布失物是学生端最核心的操作,也是你代码里最应该写得规范的地方。接口路径我定义为POST /api/lost/add,接收表单参数包括title、description、category、pickUpLocation、imageUrl等。这里要注意的是,登录用户ID不能从前端传,而是从当前会话或Token里解析出来,否则任何人都可以冒充别人发布信息。
@RestController @RequestMapping("/api/lost") public class LostController { @Resource private LostService lostService; @PostMapping("/add") public Result<Void> add(@RequestBody @Valid LostAddDTO dto, @RequestAttribute("userId") Long userId) { lostService.addLost(dto, userId); return Result.ok(null); } }参数说明:@RequestBody表示前端以JSON格式传参,@Valid触发DTO上的校验注解,比如title不能为空、description长度不能超过500。@RequestAttribute("userId")来自拦截器里设置的登录用户,后面讲登录认证时会演示怎么在拦截器里写入这个属性。
DTO是专门给前端传参用的对象,不要把数据库实体直接暴露给前端。一个常见错误是前端传过来的字段比实体少,导致MyBatis Plus自动填充的字段被覆盖;另一个错误是实体里有password字段,前端一不小心就被返回到浏览器。下面这个DTO只包含发布时需要的字段。
@Data public class LostAddDTO { @NotBlank(message = "失物标题不能为空") private String title; @NotBlank(message = "失物描述不能为空") @Size(max = 500, message = "描述不能超过500字") private String description; private String category; private String pickUpLocation; private String lostLocation; private String imageUrl; }Service实现类里,不要急着往数据库插数据。先检查图片路径是否存在,再检查同一个人一分钟内是否重复提交,最后再组装实体。我一般会加一个简单的频率控制:用Redis做计数器,没有Redis就直接查数据库最近一条记录的时间。
@Service public class LostServiceImpl implements LostService { @Resource private LostItemMapper lostItemMapper; @Override @Transactional(rollbackFor = Exception.class) public void addLost(LostAddDTO dto, Long userId) { LostItem item = new LostItem(); item.setUserId(userId); item.setTitle(dto.getTitle()); item.setDescription(dto.getDescription()); item.setCategory(dto.getCategory()); item.setPickUpLocation(dto.getPickUpLocation()); item.setLostLocation(dto.getLostLocation()); item.setImageUrl(dto.getImageUrl()); item.setStatus(0); item.setCreatedTime(LocalDateTime.now()); lostItemMapper.insert(item); } }逻辑说明:@Transactional保证这个方法里所有数据库操作要么全部成功要么全部失败,后续如果加入“发布成功后自动通知关注人”的逻辑,这个注解会保护数据一致性。status在插入时写死为0,这是系统安全的起点,不允许前端传入。
3.2 认领流程的状态机:避免“两个人同时认领同一件失物”
认领操作是这个系统里最容易出并发问题的环节。设想这样一个场景:一件失物同时被A和B提交认领申请,失主打开了申请列表,准备通过A的申请;就在他点击“通过”按钮的同一秒,B的申请也被另一个人误操作通过了。如果没有状态校验,这件失物就被认领了两次,数据库里会出现两条status为1的申请记录。
我处理这个问题的方案是“数据库条件更新 + 受影响行数判断”。先根据失物ID把状态从“待认领”更新为“已认领”,如果更新影响的行数为0,说明这件失物已经被别人抢先认领了,直接返回失败。这样不需要悲观锁,也不需要在应用层加锁,一条Update语句就能保证原子性。
@Override @Transactional(rollbackFor = Exception.class) public void approveClaim(Long claimId, Long ownerId) { ClaimApply claim = claimMapper.selectById(claimId); if (claim == null || !claim.getStatus().equals(0)) { throw new RuntimeException("该申请已被处理"); } LostItem lost = lostItemMapper.selectById(claim.getLostItemId()); if (lost == null || !lost.getUserId().equals(ownerId)) { throw new RuntimeException("无权操作该失物"); } // 关键操作:先尝试把失物状态改为已认领 int updated = lostItemMapper.updateStatusIfPending(lost.getId()); if (updated == 0) { throw new RuntimeException("这件失物已被其他人认领"); } // 只有上面的更新成功才允许通过当前申请 claimMapper.updateStatus(claim.getId(), ClaimStatusEnum.PASSED.getValue()); claimMapper.rejectOthers(claim.getLostItemId(), claim.getId()); }Mapper里的动态SQL是今天要背下来的核心。updateStatusIfPending这个自定义方法,MyBatis Plus的LambdaUpdateWrapper可以写:
int updateStatusIfPending(@Param("id") Long id); @Update("UPDATE lost_item SET status = 1 WHERE id = #{id} AND status = 0") int updateStatusIfPending(Long id);代码说明:WHERE status = 0这个条件就是并发安全的关键。即使两个请求同时到达,数据库行锁会让其中一个Update成功、另一个被阻塞后才发现状态已经变成1,于是更新行数为0。rejectOthers方法把同一个失物下的其他申请置为拒绝,这样才能确保前端列表里不会出现两个“已通过”。先更新主表再更新子表,顺序不能反过来,一旦子表更新成功而主表更新失败,事务回滚会处理一切。
前端在提交认领申请时也要做一层简单的拦截:按钮在点击后置灰三秒,防止用户手抖连续点击。虽然后端已经防住了并发,但前端友好性同样影响答辩演示时的观感。
3.3 让系统“主动说话”:消息通知与待办提醒的简单实现
很多毕设项目做到“能发布、能认领”就收了,缺少消息通知会让整个系统显得很被动:用户发布失物后,要等管理员手工告知有人提交了申请;而认领结果也得用户自己反复刷新页面才能看到。加一个轻量级的站内信模块,不需要接入短信或邮件,就能让系统的完整度和演示效果提升一个档次。
通知表结构非常简单:通知人ID、接收人ID、标题、内容、是否已读、创建时间。发布失物的人收到“有人提交了认领申请”的通知,提交申请的人收到“你的认领申请已通过”或“已被拒绝”的通知。这些通知逻辑放在Service层,与认领流程耦合在一起。
@Service public class NoticeServiceImpl implements NoticeService { @Resource private NoticeMapper noticeMapper; @Override public void send(Long receiverId, String title, String content) { Notice notice = new Notice(); notice.setReceiverId(receiverId); notice.setTitle(title); notice.setContent(content); notice.setIsRead(0); notice.setCreatedTime(LocalDateTime.now()); noticeMapper.insert(notice); } }代码说明:send方法就是一个最普通的插入操作,但它在业务代码里被多个地方复用。比如在approveClaim方法里,当失主通过一个申请时,除了更新状态外,再调用noticeService.send(claim.getClaimUserId(), "认领成功", "你申请的失物已通过失主确认")。这样用户登录后首页就能看到未读消息数。
查询未读消息数建议单独写一个接口,前端在进入首页时就请求一次,有未读消息时在导航栏右上角显示一个小红点。这个交互在答辩时非常讨喜,因为它直观地展示了“系统具备主动通知能力”。
public Integer unreadCount(Long userId) { LambdaQueryWrapper<Notice> wrapper = new LambdaQueryWrapper<>(); wrapper.eq(Notice::getReceiverId, userId) .eq(Notice::getIsRead, 0); return Math.toIntExact(noticeMapper.selectCount(wrapper)); }参数说明:LambdaQueryWrapper是MyBatis Plus提供的类型安全查询对象,eq方法表示等于条件,Notice::getReceiverId是Java 8的方法引用写法,避免手写字段字符串,编译期就能发现字段名拼写错误。selectCount返回Long类型,所以用Math.toIntExact转成Integer。
4. 避开毕设高频翻车点:接口、权限和数据一致性的排查清单
4.1 前后端联调时对象返回“循环引用”导致栈溢出
现象:启动项目后调用“查询失物详情”接口,后端日志疯狂打印StackTrace,提示“StackOverflowError”,前端却只看到一个500提示。
原因:失物实体里有User对象或List ,而User里又有一个List ,Jackson序列化时从LostItem序列化到User,再从User序列化回LostItem,无限循环,栈就爆了。
解决:不要直接把实体类返回给前端,改为返回VO对象。VO里只放前端需要的字段,比如失物信息加发布者姓名、头像URL。第二个办法是在关联字段上添加@JsonIgnore注解,但不推荐用来掩盖设计缺陷。我一般两种结合:返回列表用VO,实体里的User字段加@JsonIgnore防止意外循环。
@Data public class LostDetailVO { private Long id; private String title; private String description; private String pickUpLocation; private String imageUrl; private Integer status; private String publisherName; // 冗余字段,来自关联查询 private String publisherAvatar; private LocalDateTime createdTime; }代码说明:查询详情时先查出失物实体,再根据userId查出用户实体,手动组装VO。这样每个字段都是可控的,序列化时不会带上多余的关联对象,也顺便解决了password等敏感信息泄露的问题。
4.2 上传图片后刷新丢失:静态资源映射与虚拟路径
现象:本地开发时上传了一张失物图片,点击确认后立刻显示正常;但第二天重新启动项目再打开页面,图片变成了裂图;或者部署到服务器后,所有图片全部404。
原因:开发时IDEA用了相对路径存文件,比如“./upload”,这个目录在项目启动路径下,IDEA每次重启可能会清空target目录;部署到服务器后又没把上传目录映射到Web应用的静态资源路径,Spring Boot默认只能访问classpath:/static下的文件。
解决:在application.yml里配置一个自定义上传目录,再写一个WebMvcConfigurer把该目录映射为URL路径。我一般会放在D:/lostfound/upload或Linux的/home/xxx/upload,避免路径里有中文。
file: upload-dir: D:/lostfound/upload spring: servlet: multipart: max-file-size: 10MB max-request-size: 20MB@Configuration public class WebConfig implements WebMvcConfigurer { @Value("${file.upload-dir}") private String uploadDir; @Override public void addResourceHandlers(ResourceHandlerRegistry registry) { registry.addResourceHandler("/upload/**") .addResourceHandler(uploadDir + "/"); } }参数说明:addResourceHandler里的"/upload/**"表示访问路径,而映射到本地目录时路径尾部必须加斜杠。访问“http://localhost:8080/upload/xxx.jpg”就会解析到“D:/lostfound/upload/xxx.jpg”。上传Controller里保存文件名时用UUID重命名,不要保留用户原始文件名,防止中文乱码、路径注入和重名覆盖。
4.3 认领审核被绕过:接口层缺少状态校验
现象:用户提交一个认领申请,在等待期间直接调用POST /api/claim/approve接口,传入自己的ID和失物ID,结果居然能把一件不是自己发布的失物标记成已认领。
原因:approveClaim接口里只校验了申请和失物存在,没有校验当前登录用户是不是失物发布人(ownerId)。这是骑车项目里很低级的越权漏洞,但在答辩时被问到的概率极高。
解决:在进入业务方法前,从请求上下文里拿到当前用户ID,再去跟失物表的userId比对。我在前面approveClaim的代码里已经加上了ownerId参数,这里再强调一次:所有涉及“确认、审核、删除、修改”的接口,都必须先确认资源归属权。
Long currentUserId = (Long) request.getAttribute("userId"); LostItem lost = lostItemMapper.selectById(lostId); if (!lost.getUserId().equals(currentUserId)) { throw new RuntimeException("只能操作自己发布的失物"); }逻辑说明:这种校验属于“越权访问”防护,它的核心思想是资源归属校验要放在业务逻辑的最前面,越早返回越好。不要放在最后再判断,否则前面可能已经执行了更新操作,即便事务回滚,也会增加无谓的开销和潜在的日志误导。
4.4 数据库时间字段和前端相差8小时
现象:用户发布失物后,页面上显示的时间比当前时间早了8小时;或者后台管理里看到的时间格式是一长串数字,像“1723456789012”。
原因:前后端两种常见翻车叠加在一起。第一种是数据库连接串没指定时区,MySQL驱动默认使用服务器时区,而服务器是UTC;第二种是后端序列化为时间戳,前端没有统一处理。
解决:在JDBC连接串上加serverTimezone=Asia/Shanghai,同时在后端全局配置统一的时间格式。
spring: datasource: url: jdbc:mysql://localhost:3306/lost_found?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai jackson: date-format: yyyy-MM-dd HH:mm:ss time-zone: GMT+8配置说明:第一行保证数据库读出来的是东八区时间;第二行和第三行保证接口返回给前端的JSON是“yyyy-MM-dd HH:mm:ss”字符串而不是时间戳。前端如果用了Dayjs或Moment,也建议统一格式化工具函数,不要在每个页面手写一遍。
4.5 多个人共用一套代码:改完的模块总被别人覆盖
现象:小组两个人分别改前端和后端,或者一起改后端,Git提交时频繁出现冲突,甚至有人直接覆盖掉别人刚写好的认领接口。
原因:没有约定好开发分支和模块负责人。我见过很多同学直接把同一个Controller里塞了多个人的代码,改之前不更新远程分支,push时强制覆盖。
解决:GitFlow简化版走下来:master始终是可运行的版本,开发时切一个dev分支,每人基于dev再开自己的feature分支。每天开工前先pull --rebase,出门前push自己的分支,不要在本地隔太久才提交。代码合并到dev时用Merge Request,哪怕只有两个人,也能看到改了什么。
# 日常开发流程 git checkout dev git pull --rebase origin dev git checkout -b feature/claim-apply # 写代码... git add . git commit -m "完成认领申请相关接口" git checkout dev git pull --rebase origin dev git merge feature/claim-apply git push origin dev命令说明:pull --rebase会把本地没有提交的改动暂存,先拉取远程更新再应用本地改动,避免生成无意义的merge commit。feature分支命名建议带模块名,这样出问题时能快速定位。如果你拿到的资料包是一个压缩包而不是Git仓库,建义你自己追踪好所有改动文件,不要一次性解压后开始大面积修改,先读取原跑通,再小步修改。
5. 让答辩老师眼前一亮的验收技巧:加分项与演示脚本
系统能跑只是及格线,答辩时的演示方式和准备深度才是拉开差距的地方。很多同学把系统打开,随便点几个页面就结束了,导师问“这个查询为什么这么写”就愣住了。提前准备好演示数据和问答角度。
我在答辩前一定会做两件事。第一件事是准备一份演示脚本,按时间顺序把核心流程走一遍:用一个学生账号发布一件“校园卡”,然后用另一个账号浏览到这条信息,提交认领申请,再切回第一个账号通过申请,当场展示消息通知里出现了认证结果,同时页面上的失物卡片变成了“已认领”。这一步把发布、查询、认领、通知、状态流转全串在一起,比任何口头讲解都有说服力。
第二件事是准备一张表的设计理由总结。答辩的高频问题几乎都在围绕表结构和状态字段:为什么认领申请和失物要分两张表?为什么状态用int不用字符串?为什么没建外键?回答思路是:分表是为了支持一对多关系,int更节省空间且方便扩展状态枚举,不建外键是避免高并发下死锁问题,通过应用层保证数据一致性。这个回答本身就展示了你的思考深度。
演示时不妨主动加点“意外处理”的戏份。比如故意用同一个账号连续点击两次提交认领,然后调出后端控制台,展示第二次请求被拦截的过程和报错信息,并解释这是通过“库存式”状态更新实现的。再比如调出数据库的claim_apply表,展示同一件失物下不同申请各自的状态。这些细节会让导师感受到你不是在演示一个玩具,而是在演示一个有防御机制的系统。
最后再补两个加分细节:一是P0级别的输入校验,比如发布失物时标题必填、描述长度限制,这个在DTO上加了@NotBlank就能覆盖;二是系统日志,用AOP记录每个用户的关键操作行为,比如谁在什么时间认领了哪件失物,答辩时展示“操作痕迹可追溯”这个特性,非常加分。把这些做完,你会发现这个题目不管在功能完整性还是技术深度上,都超过了大多数同类毕设。
我自己带过的几届学生里,凡是按“先定状态机、再写代码、最后补通知”这个顺序做失物招领方向的,基本都是两天就能把核心业务跑通;凡是上来就写Controller然后边写边改表的,多半会在联调阶段熬到深夜。希望这篇笔记能帮你少走这几段夜路。希望帮到你。
本文还有配套的精品资源,点击获取