ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

SpringBoot+Vue+MySQL失物招领系统毕业设计全攻略

SpringBoot+Vue+MySQL失物招领系统毕业设计全攻略 说实话每年到了毕业季都有不少同学拿着“校园失物招领系统”这种经典选题来找我改代码。这题不难但能把它做得完整、能跑通、能答辩的人真不多。SpringBoot Vue MySQL 这个组合放在失物招领场景里刚好覆盖了前后端分离、数据库设计、状态流转、权限控制这些核心考点拿来当毕业设计非常合适。我帮你把从框架搭建到论文整理的整套思路捋一遍包括建表SQL、关键接口、前端交互、部署踩坑照着做基本能少走一半弯路。1. 为什么选这个题目做毕业设计1.1 失物招领系统到底要解决什么问题学校里的失物招领线下基本靠食堂门口贴条、宿舍楼群转发效率低不说信息还容易丢。线上化之后核心就三件事发布丢失物品、发布捡到物品、完成认领对接。听起来简单但落到系统里你要处理的问题其实不少——用户身份怎么区分物品状态怎么流转捡到的人怎么证明东西是自己的管理员要不要审核这些刚好对应了用户模块、物品模块、认领模块、审核模块。做毕业设计时能把这些逻辑讲清楚答辩的时候就有话说。另外这个选题的代码量不大不小一个人完全能扛下来。后端大概二十来个接口前端六七个页面数据库五六张表既能体现工作量又不会复杂到失控。用到的技术也都是主流招聘要求里的常见项写进简历不丢人。1.2 技术栈选型的实际考量后端选SpringBoot最直接的好处是省配置。Spring Boot 2.7 或 3.x 版本都行但考虑到很多学校机房电脑JDK版本不高建议用 Spring Boot 2.7 JDK 1.8 的组合稳定兼容性好。配合 MyBatis-Plus 操作数据库省去大量写 ResultSet 转换的功夫。鉴权用 JWT无状态前后端分离的好搭档。前端选 Vue最好用 Vue 3 Element Plus组件现成表格、表单、弹窗都不用自己造轮子。数据存储就是 MySQL 5.7 或者 8.0选哪个后面细说反正都是毕业生最熟悉的数据库。有人会纠结用不用 Redis这个场景完全没必要。失物招领没有高并发也没有复杂的缓存一致性需求硬塞一个 Redis 进去反而增加部署负担。毕业设计讲究“够用且合理”不要在无关紧要的地方炫技这点我在后面论文写法里还会强调。2. 整体功能设计与数据库建模2.1 角色划分与功能清单系统分三种角色普通学生、捡到物品登记人其实也属于学生、管理员。学生可以注册登录、发布丢失物品、发布拾到物品、浏览所有物品列表、提交认领申请、在我的认领记录里看进度。管理员可以审核所有发布的物品信息、审核认领申请、删除违规或已过期的物品、发布公告。注意学生角色实际上没细分一个人既能“丢东西”也能“捡东西”所以没必要建两个角色。功能权限上学生只能操作属于自己的数据管理员拥有全部数据的管理权。这里用权限控制明显比写死逻辑要好。核心功能清单我列一下用户注册、登录、密码加密存储物品发布丢失/拾获、图片上传、修改、删除物品列表分页查询支持按物品名称、类型、状态、时间筛选物品详情页查看发布者信息和联系信息用户提交认领申请填写认领理由及证明信息发布者审核认领申请认领成功后物品状态变更为“已归还”管理员后台对物品和认领记录进行审核、下架、数据统计2.2 数据库表结构怎么设计我设计的是五张核心表用户表、物品表、认领申请表、通知/留言表可选、管理员日志表。这里最关键是物品表和认领申请表的状态设计。用户表 user字段idusernamepasswordreal_namephoneemailrolecreate_time。password 一定要存 BCrypt 加密后的结果别用明文。role 用 int1 表示普通用户2 表示管理员。物品表 item字段iduser_idtypetitledescriptioncategoryimage_urlstatuslocationcontact_phonefound_timecreate_time。type 区分 lost丢失和 found拾获。status 我建议用 int0 表示待审核新发布1 表示展示中审核通过2 表示认领中有人提交申请但未最终确认3 表示已归还4 表示已下架。image_url 别存成本地路径加完整域名就存相对路径或直接存OSS地址下面部署时细说。认领申请表 claim字段iditem_idclaim_user_idreasonproof_imagesstatuscreate_time。status 用 int0 待审核1 同意认领2 拒绝认领3 已确认完成。这张表是连接用户和物品的中间表也是整个系统状态流转的关键。表与表的关系item 表通过 user_id 关联 user 表claim 表通过 item_id 关联 item 表通过 claim_user_id 关联 user 表。索引上给 item 表的 status、type、create_time 建联合索引因为列表页查询基本都带着这几个条件。以下是我实际测试过的建表SQL省去重复缩进你在Navicat或命令行里直接执行即可CREATE DATABASE lost_found DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci; USE lost_found; CREATE TABLE user ( id int(11) NOT NULL AUTO_INCREMENT, username varchar(50) NOT NULL COMMENT 登录名, password varchar(100) NOT NULL COMMENT BCrypt加密密码, real_name varchar(50) NOT NULL COMMENT 真实姓名, phone varchar(20) DEFAULT NULL, email varchar(100) DEFAULT NULL, role int(11) NOT NULL DEFAULT 1 COMMENT 1普通 2管理员, create_time datetime NOT NULL, PRIMARY KEY (id), UNIQUE KEY uk_username (username) ) ENGINEInnoDB AUTO_INCREMENT1 DEFAULT CHARSETutf8mb4 COMMENT用户表; CREATE TABLE item ( id int(11) NOT NULL AUTO_INCREMENT, user_id int(11) NOT NULL, type varchar(10) NOT NULL COMMENT lost/found, title varchar(100) NOT NULL, description text, category varchar(50) DEFAULT NULL COMMENT 证件/数码/书籍/其他, image_url varchar(255) DEFAULT NULL, status int(11) NOT NULL DEFAULT 0 COMMENT 0待审核 1展示中 2认领中 3已归还 4已下架, location varchar(255) DEFAULT NULL COMMENT 丢失/拾获地点, contact_phone varchar(20) DEFAULT NULL, found_time datetime DEFAULT NULL COMMENT 丢失或拾获时间, create_time datetime NOT NULL, PRIMARY KEY (id), KEY idx_status_type_time (status,type,create_time) ) ENGINEInnoDB AUTO_INCREMENT1 DEFAULT CHARSETutf8mb4 COMMENT失物招领物品表; CREATE TABLE claim ( id int(11) NOT NULL AUTO_INCREMENT, item_id int(11) NOT NULL, claim_user_id int(11) NOT NULL, reason varchar(500) NOT NULL COMMENT 认领理由, proof_images varchar(500) DEFAULT NULL COMMENT 证明材料JSON或逗号分隔路径, status int(11) NOT NULL DEFAULT 0 COMMENT 0待审核 1同意 2拒绝 3已确认归还, create_time datetime NOT NULL, PRIMARY KEY (id), UNIQUE KEY uk_item_claim (item_id,claim_user_id) ) ENGINEInnoDB AUTO_INCREMENT1 DEFAULT CHARSETutf8mb4 COMMENT认领申请表;这里有个细节认领申请我建了唯一索引防止同一用户对同一物品重复提交申请。如果业务上你允许用户多次认领就把这个索引去掉。但从用户体验来说重复申请意义不大留着更合理。2.3 关键设计物品状态流转状态流转是整个系统的灵魂。很多同学把状态写在逻辑里写成一团乱麻。我建议这样定义流转路径用户发布物品后status 0待管理员审核。管理员审核通过status 1展示在前端列表。有人提交认领申请后status 2表示认领中。此时该物品仍在前端展示但不能再被其他人申请认领后端要校验。发布者同意某个认领申请status 自动变为 3已归还。对应申请记录的状态也变为 1同意。如果发布者拒绝status 重新回到 1申请记录状态变为 2其他用户可以继续申请。管理员可以随时把物品下架status 4或直接删除。这个状态机在答辩时非常重要。你可以画一张状态图但用文字描述清楚也一样有说服力。实现时所有状态变更都要在Service层做校验比如 status1 的物品才能提交认领申请status2 时拒绝其他申请这些必须在后端写死不能依赖前端按钮隐藏。3. 后端核心模块实现3.1 SpringBoot工程初始化与统一结果封装我推荐用 Spring Initializr 创建工程。依赖选择Spring Web、MyBatis-Plus手动加上、MySQL Driver、Lombok、Validation。Spring Boot 版本选 2.7.18完全够用。项目结构上按包功能分——controller、service、mapper、entity、common、config、utils。第一个要写的东西是统一返回结果类。前后端分离时最烦的就是每个接口返回格式不一。我用了泛型封装Data public class ResultT { private Integer code; private String msg; private T data; public static T ResultT success(T data) { ResultT r new Result(); r.code 200; r.msg success; r.data data; return r; } public static T ResultT error(String msg) { ResultT r new Result(); r.code 500; r.msg msg; r.data null; return r; } }这样一来前端 Axios 拦截器就可以统一判断 code 是否为 200而不是每个接口单独判。同时配合全局异常处理器把参数校验异常、业务异常、未知异常分别转成不同 code前端处理会轻松得多。全局异常处理这块我用一个RestControllerAdvice类拦截MethodArgumentNotValidException、BusinessException自定义、Exception。核心做法是返回Result.error(e.getMessage())但注意堆栈信息别丢日志里正常打印。这一步在答辩时也是加分项体现出工程化意识。3.2 认证授权JWT登录与鉴权登录流程不复杂用户提交用户名密码Service层通过 BCrypt 校验成功后生成 JWT 返回给前端。JWT 里我放了 userId 和 role过期时间设为24小时。前端拿到 token 后存到 localStorage每次请求在请求头带上Authorization: Bearer xxx。后端写一个拦截器只拦截需要登录的接口管理员接口再校验 role。这里我用的是jjwt0.9.1注意 JDK 版本兼容性。如果 JDK8 没问题JDK11 可能需要额外引入javax.xml.bind依赖这是老坑后面在部署章节提。核心工具类大致如下public class JwtUtil { private static final String SECRET your-secret-key-please-change; private static final long EXPIRATION 24 * 60 * 60 * 1000; public static String createToken(Long userId, Integer role) { return Jwts.builder() .claim(userId, userId) .claim(role, role) .setExpiration(new Date(System.currentTimeMillis() EXPIRATION)) .signWith(SignatureAlgorithm.HS256, SECRET) .compact(); } public static Claims parseToken(String token) { return Jwts.parser().setSigningKey(SECRET).parseClaimsJws(token).getBody(); } }拦截器里解析 token把 userId 和 role 存到 ThreadLocal 或 Request 属性中后续 Controller 里直接取。注意一个安全细节改密码的时候要把旧 token 失效。简单做法是登录接口返回 token 时在 Redis 里记录版本号——但前面说了不用 Redis。那就退而求其次把密码修改时间写进 JWT 的 claims每次校验时对比用户当前密码修改时间如果 token 里的时间早于修改时间则拒绝访问。这一招在毕业设计里属于加分项但如果你觉得麻烦也可以不做全程用单 token 无状态即可。认证拦截器要放行登录、注册、验证码、静态资源这几个接口其他全部拦截。拦截器配置路径时特别注意/api/user/login、/api/user/register放行/api/item/list、/api/item/**中的查询部分要不要放行取决于产品设定。个人建议物品列表和详情可以公开访问毕竟失物招领是公益性质但发布、认领、审核等写操作必须登录。3.3 发布、认领、管理员审核的接口逻辑发布物品接口前端提交表单后端做参数校验。重点有两个图片上传的地址和物品状态初始值。图片上传我用的是本地存储——/uploads/目录映射静态资源文件名用 UUID 重命名防止重名。上传接口返回相对路径比如/uploads/xxx.jpg前端拼出完整 URL 显示。生产环境你可能会换成 OSS但本地这套代码跑通最简单。发布接口逻辑PostMapping(/api/item/publish) public ResultItem publish(RequestBody Valid ItemDTO dto) { Item item new Item(); BeanUtils.copyProperties(dto, item); item.setUserId(CurrentUser.get().getId()); item.setStatus(0); // 待审核 item.setCreateTime(new Date()); itemMapper.insert(item); return Result.success(item); }认领申请接口必须先查物品状态是否为1。如果是2直接返回“已有认领申请”如果是3或4返回“物品不可认领”。然后判断当前用户是否是该物品的发布者如果是就不能认领自己的物品。这个判断很简单但很容易漏漏了就会被答辩老师问住。管理员审核接口本质就是改 status。审核通过就是item.status 1下架就是item.status 4。这里记得加权限注解或拦截器里的 role 判断。另外管理员后台的统计数据可以写一个简单的 SQL 查询按状态分组统计物品数量、按 type 统计、按天统计发布数量。不要做太复杂的图表后端返回几个数前端用 ECharts 画两个饼图折线图就够用了。4. 前端Vue实现与联调4.1 Vue3工程搭建与路由设计前端我用 Vite 创建 Vue3 工程然后安装 Element Plus、Axios、Vue Router、Pinia。项目结构大致是src/ views/ 页面组件 components/ 公共组件 api/ axios 封装和接口函数 router/ 路由配置 store/ pinia 状态管理 utils/ 工具函数页面包括登录注册页、首页物品列表、发布页、我的发布页、认领管理页、管理员后台页。路由配置里设置beforeEach导航守卫判断 token 是否过期、角色是否有权限进入后台。但记住前端路由守卫只是体验优化真正的权限校验永远在后端。首页列表页用 Element Plus 的el-table和el-pagination展示物品分页数据。每张卡片显示标题、图片、状态、地点、时间。筛选条件用el-select加el-date-picker。这些组件使用不难难的是把筛选参数传给后端时要注意空值处理。后端用 MyBatis-Plus 的LambdaQueryWrapper时需要判断参数是否为 null 再拼条件否则会出现“选了类型查不出东西”的诡异问题。4.2 Axios封装与登录态处理Axios 封装是前端工程化的基本动作。我一般建一个request.js配置baseURL和超时时间然后在请求拦截器里加 token在响应拦截器里统一处理异常。import axios from axios import router from /router const request axios.create({ baseURL: /api, timeout: 10000 }) request.interceptors.request.use(config { const token localStorage.getItem(token) if (token) { config.headers.Authorization Bearer token } return config }) request.interceptors.response.use( response { const res response.data if (res.code ! 200) { if (res.code 401) { localStorage.removeItem(token) router.push(/login) } return Promise.reject(new Error(res.msg)) } return res }, error { return Promise.reject(error) } )有一个容易踩的坑如果前后端做了跨域代理baseURL 要写/api然后在 Vite 的vite.config.js里配置 proxy代理到后端地址。这样部署时还能规避 CORS。如果你选择让后端开启CrossOrigin那 baseURL 就必须写全地址。我偏向用代理因为后期前后端同端口部署更方便。4.3 页面实现亮点图片上传与搜索图片上传组件Element Plus 的el-upload非常好用。需要注意几点action要指向后端真正的上传接口headers里带 tokenon-success里把返回的相对路径存到表单字段中回显时用http://localhost:8080 路径拼接。如果你用了代理回显路径直接写getFullUrl()方法根据当前环境拼前缀。搜索功能我建议前端用防抖。等用户停300毫秒后再请求接口避免每次输入都发请求。这个实现起来就三行代码但能极大降低后端压力也能体现工程细节。发布页面的表单校验也要做好。丢失物品的标题、描述、时间、地点是必填拾获物品同样。位置信息可以做成一个简单的输入框加下拉建议不要做地图坐标那会牵扯到第三方地图SDK超出毕业设计范围。如果你想把项目包装得更完整可以在“找回率”统计上下功夫比如展示找回成功多少件提高了多少效率这些数据假造并不难但能在答辩PPT里形成小亮点。5. 后端与数据库部署实操5.1 MySQL版本与连接配置我建议本地开发用 MySQL 5.7生产部署用 MySQL 8.0 也行。区别主要在于驱动名和时区。如果 MySQL 8.0JDBC URL 要加useSSLfalseserverTimezoneAsia/ShanghaiallowPublicKeyRetrievaltrue否则会报 SSL 连接错误。5.7 则可以不用allowPublicKeyRetrieval。Spring Boot 配置文件application.ymlserver: port: 8080 spring: datasource: url: jdbc:mysql://localhost:3306/lost_found?useUnicodetruecharacterEncodingutf8useSSLfalseserverTimezoneAsia/Shanghai username: root password: 123456 driver-class-name: com.mysql.cj.jdbc.Driver mybatis-plus: configuration: map-underscore-to-camel-case: true log-impl: org.apache.ibatis.logging.stdout.StdOutImpl global-config: db-config: logic-delete-field: deleted logic-delete-value: 1 logic-not-delete-value: 0这里我特别想说一下逻辑删除。物品和用户表我加了deleted字段上面建表SQL里没加你实际做可以加上。这样删除操作变成更新操作数据不会物理消失既可以反悔也能保留统计数据。MyBatis-Plus 自带逻辑删除配置加上字段注解后所有查询都会自动过滤已删除数据。这个点很实用体现数据库设计素养。5.2 前后端打包与部署流程毕设最终是要运行给老师看的部署环节不能掉链子。我的做法是本地用 ID 打包然后跑在 Windows 服务器或阿里云轻量服务器上用 Nginx 部署前端静态文件后端 jar 直接跑。后端打包前先确认application.yml中的数据库密码、端口是正式环境的。然后执行mvn clean package -DskipTests生成的 jar 在 target 目录下上传到服务器执行nohup java -jar lost-found-0.0.1-SNAPSHOT.jar log.txt 21 后端跑起来后把前端npm run build生成的dist目录上传到服务器配置 Nginxserver { listen 80; server_name your_domain; root /usr/share/nginx/html; index index.html; location / { try_files $uri $uri/ /index.html; } location /api/ { proxy_pass http://127.0.0.1:8080; proxy_set_header Host $host; } }这一步就实现了同源访问前端请求/api被代理到后端前端页面路径由 Nginx 托管不会出现跨域问题。如果你是在 Windows 上演示不用 Nginx 也行直接 IDEA 启动后端再用 Live Server 或 Vite preview 打开前端然后把 API 地址改为 localhost 后端地址记住关闭浏览器的跨域校验或者在后端加CrossOrigin。但正式演示建议还是用 Nginx 方案稳。图片目录也要映射一下。后端本地图片上传后的uploads文件夹不要放在项目里面否则打包或者上传服务器容易丢。我会在application.yml里配置本地路径然后在WebMvcConfigurer中添加静态资源映射Configuration public class WebConfig implements WebMvcConfigurer { Override public void addResourceHandlers(ResourceHandlerRegistry registry) { registry.addResourceHandler(/uploads/**) .addResourceLocations(file: PROPERTIES.getImgDir()); } }这样图片文件存放在服务器固定目录即使 jar 包更新了图片还在。部署后前端访问/uploads/xxx.jpg会由后端解析到正确目录。6. 常见问题与避坑经验6.1 跨域、404、图片无法显示这三个问题我基本在每个毕设里都会遇到。跨域问题后端没开跨域或前端代理没配好表现为浏览器 console 报 CORS。自查方法先看请求是否发出再看响应头。我用 Vite 代理就完全不需要后端CrossOrigin简单省事。404问题分几种前端页面刷新后404说明 Nginx 没有按try_files处理 history 路由接口404说明后端RequestMapping路径或前端请求路径不一致图片404多半是映射路径不对或者图片存到了打包目录里。关于图片从始至终我都不推荐存到项目的resources/static下那样每次重新打包图片就没了。6.2 时间格式、长文本、乱码后端返回时间格式默认是 ISO 字符串前端显示出来可能是2024-06-01T12:30:00.00000:00很难看。在后端配置spring: jackson: date-format: yyyy-MM-dd HH:mm:ss time-zone: GMT8这样接口返回的时间就正常了。另一个是乱码主要因为 MySQL 表字符集不是 utf8mb4或者 JDBC URL 没加characterEncodingutf8。解决方法是建库时统一制定 utf8mb4连接串里加上编码参数。搬运 SQL 文件时如果源文件是 GBK 编码用 Navicat 导入可能会乱码可以用 Notepad 转换编码后再导。长文本问题description字段如果超过 255 字符注意用text类型。前端展示时用 CSS 截断或el-tooltip不要在接口里截断字符串。6.3 其他值得记住的坑jjwt0.9.1 在 JDK11 以上会报ClassNotFoundException: javax.xml.bind.DatatypeConverter解决方法是在 pom 中引入jakarta.xml.bind:jakarta.xml.bind-api或换成io.jsonwebtoken:jjwt-api0.11.x。MyBatis-Plus 分页需要单独配置PaginationInnerInterceptor不加的话selectPage不生效而且不会报错只是返回全量数据。这个坑非常隐蔽。前端提交表单后重新拉列表要确保页码回到第一页否则会出现新增数据不在当前页的错觉。上传图片的el-upload默认会在on-success之后手动fileList残留记得清空fileList不然二次编辑时会出现幽灵图片。如果把这些问题提前验证过答辩演示时基本不会翻车。7. 论文撰写与答辩准备7.1 论文结构怎么安排毕设论文的核心骨架我建议分成六章绪论、需求分析、系统设计、系统实现、系统测试、总结。重点放在需求分析和系统设计实现部分不需要贴大段代码只需要贴核心类图、关键流程图、接口效果。很多同学喜欢把代码全贴进去看起来像说明书老师最反感。开头需求分析你要写清楚传统线下失物招领存在的痛点信息孤岛、时效性差、认领过程不透明。引出系统目标提供一个公开、可信、可追溯的校内信息发布和认领平台。接着写用户角色、功能用例。这里可以用用例图但不需要 Mermaid用 Word 里的图形即可。系统设计章节需要包含总体架构图、功能模块图、数据库ER图和表结构说明。表结构说明列出每张表的用途和关键字段不要直接贴全部 SQL。系统实现章节按功能模块写配合页面截图和关键逻辑描述。系统测试章节要写测试环境、测试用例、功能测试结果。注意写清楚测试数据比如测试了多少条用例通过率多少要有确切的数字不能写“测试全部通过”就完了。7.2 答辩时容易被追问的问题答辩老师最喜欢问这几个问题提前准备好答案为什么选这个技术栈答SpringBoot 生态成熟、配置简洁Vue 响应式适合做信息管理页面MySQL 免费稳定三者的资料丰富适合在周期内独立完成。认领过程中如何保证真实性这个问题要诚实回答系统提供了实名认证和证明材料上传但线下最终交接由发布者自行确认。可以在设计中增加发布者与认领人的站内聊天或联系方式展示但最终核实还是靠线下。如果物品被领错了怎么办答系统保留认领记录和申明理由管理员可以查看操作日志必要时追溯。也可以在物品状态流转中增加“争议”状态。数据量大了怎么办答按状态和时间建了索引分页查询后续可引入 Redis 缓存热点数据、OSS 存储图片这个扩展思路提前想好。项目有哪些不足不要只说“没有不足”可以回答目前缺少消息推送认领成功通知依赖用户主动查看缺少班级/学院字段精确匹配弱一些。这些缺陷正是以后改进的方向。7.3 项目包装与简历写法如果这个项目要写进简历项目描述不要写成“开发了一个失物招领系统就完了”。要突出结果和量化数据。可以写基于 SpringBoot Vue3 MySQL 构建前后端分离的失物招领平台实现物品发布、审核、认领闭环管理。设计 5 张核心数据表、20 个 RESTful 接口覆盖多角色权限控制。通过 JWT 实现无状态登录鉴权对敏感操作进行角色校验密码采用 BCrypt 加密存储。使用 Nginx 完成前端静态资源与后端 API 的同源部署容器化部署可弹性扩展。这些描述比“熟悉 SpringBoot”这种空话有说服力得多。不过前提是你真的把功能跑通了别简历写得花里胡哨答辩现场一运行全是 bug那就尴尬了。从我带过不少毕设的经验来看这类系统最大的问题不是代码写不出来而是做到一半陷入细节比如纠结图标好不好看、颜色配不配。毕业设计的核心是逻辑完整、技术运用合理、能演示、能讲清。你先把失物招领的闭环跑通再考虑锦上添花。最后再分享一个小技巧在物品列表页加一个“重新发布”的入口把已归还的物品一键复制生成新发布单这个细节既能体现你对用户体验的思考答辩时也能作为亮点提出而且实现成本就是十行代码。写到这里剩下的就是你自己动手做了。
返回列表