基于 SpringBoot 的校园二手交易系统:从需求分析到完整实现
每年六月前后毕业生宿舍楼下总是堆满带不走的课本、台灯、小风扇和收纳箱。这些东西对毕业生来说已经成了负担对刚入学的学弟学妹来说却是实打实的刚需。可惜传统的校园交易方式长期停留在QQ群刷屏和线下摆摊信息过载、图片失效、找不到历史记录买卖双方都很难受。基于这个背景我完成了这个基于 Java SpringBoot 的校园线上跳蚤市场系统——一套面向校园场景的闲置物品交易平台覆盖商品发布、分类检索、在线下单、订单管理和后台审核的完整业务闭环。这篇文章把选题思路、系统设计、核心实现和毕设答辩的侧重点都整理了出来给正在做类似课题的同学一个可复用的参考。1. 校园闲置交易的痛点与项目定位1.1 传统校园交易方式的效率瓶颈先说一个很直观的现象大学校园里的闲置物品流通需求一直存在但交易方式一直跟不上。我当年读本科时班上同学处理旧书基本靠两种途径——在年级群里发一条消息附带几张图或者在期末季去跳蚤市场摆半天摊。这两种方式都有明显短板QQ群/微信群交易消息刷屏严重图片很快就沉到下面想搜一个东西只能一条条往上翻。好不容易约好交易过两天再想找卖家信息翻聊天记录翻到怀疑人生。线下跳蚤市场举办频次太低一年最多一两次而且摊位面积有限大多数同学就算想卖东西也抢不到摊位。时间点也不凑巧新生入学的九月和毕业生离校的六月往往错开。校园BBS/论坛板块更新慢、图片上传体验差移动端适配糟糕年轻人基本不会主动去看。这些问题的本质是校园交易缺少一个信息结构化、流程标准化的承接平台。毕设选这个方向首先因为它真实存在于校园场景需求分析写起来不虚其次它涉及用户、商品、订单、消息、权限多个维度业务完整度天然够便于展示一个全栈开发者的基本素养。1.2 系统定位做校园场景的“迷你电商”这个系统的定位不是做一个大而全的电商平台而是聚焦校园内的 C2C 闲置交易。我把它概括成三个关键词校园化注册入口面向在校师生通过学号/工号扩展字段与校内身份绑定商品发布时可选择校内交易地点宿舍区、教学楼、食堂这比普通电商多了一层地理维度的信任。轻量化不需要复杂的购物车、多级类目、物流跟踪核心是“发布 - 浏览 - 联系 - 交易 - 评价”这条主干线功能设计上做减法保证每个模块都能在答辩时讲清楚。有审核管理员后台对商品进行上下架审核用户的举报投诉有处理入口。有了管理端系统就不只是“两个人私下交易”而是一个具备管理和治理能力的完整平台这一点在论文里很加分。1.3 为什么“业务闭环完整”对毕设这么重要选题时我给自己定了一条硬标准系统必须有完整的业务闭环而不是零散的 CRUD 拼凑。什么意思就是每一类用户的操作流都能走通数据状态之间能够正确流转。比如用户发布商品 → 商品进入“待审核” → 管理员审核通过 → 商品“上架” → 买家下单 → 订单“待付款/确认” → 交易完成 → 商品“已售出/下架” → 双方可以评价。买家下单时系统必须保证同一商品不会同时被两个人购买。卖家可以对订单做确认处理买家可以申请退款或确认收货管理员能看到所有流转状态。这条闭环走通之后一方面系统有了“可演示性”答辩时可以从注册开始一步步操作到订单完成整个过程都在控制台和页面里可追踪另一方面闭环意味着数据库表之间外键关系、状态枚举、接口设计都是联动的评委一眼就能看出项目是真正设计过、实现过的而不是堆代码。2. 技术选型为什么是 Java SpringBoot而不是其他方案2.1 各技术路线横向对比做毕设面临的第一道选择题就是技术栈。我当时和几个同学交流过主流路线大致有这么几条技术路线优势劣势适合人群SSMSpring SpringMVC MyBatis经典三层架构教学资料多XML 配置繁琐开发效率低学校强制要求必须用传统结构SpringBoot Thymeleaf Bootstrap上手快前后端一体部署简单页面交互相对保守时间紧张重点是后端逻辑的同学SpringBoot Vue 前后端分离工程化程度高简历加分工作量翻倍跨域/鉴权/token 处理复杂基础较好想把项目做成亮点Python Flask / Node.js Express语法简单代码量少与 Java 生态割裂答辩时说服力稍弱完全没接触过 Java 的同学我自己选的是SpringBoot Thymeleaf Bootstrap后端集成 MyBatis-Plus 做 ORM。理由很实在当时做毕设的时间大约两个半月还要留出一个月写论文前后端分离虽然观感更好但意味着要同时掌握 Vue、Element Plus、Axios 拦截器、JWT 鉴权等一系列额外知识点一旦踩坑时间根本兜不住。而 Thymeleaf 模板引擎天然支持在 HTML 中写 Java 表达式服务端渲染数据页面是后端“渲染”出来的答辩讲解时逻辑链路特别清晰浏览器发起请求 → Controller 接参 → Service 处理业务 → Mapper 查数据库 → 数据回填到模板 → 返回页面。2.2 SpringBoot 对毕设最友好的几个能力SpringBoot 在这类项目中几乎是统治级的选择原因是它对新手极其宽容零配置启动内嵌 Tomcat一个main方法就能跑起 Web 服务不用再经历 SSH笨重的 XML 配置时代那些繁琐步骤。生态组件开箱即用spring-boot-starter-web、mybatis-plus-boot-starter、spring-boot-starter-validation等都是通过 Maven 引入后直接注入使用不需要关心 Bean 装配细节。统一配置中心思想数据库连接、文件上传大小、邮箱 SMTP 等都在application.yml里集中管理出问题好排查。调试体验好spring-boot-devtools支持热重启改完代码自动重新加载省掉了大量手动机器重启时间。2.3 版本选择的一个现实建议如果你是现在才开始做这个项目建议锁定SpringBoot 2.7.x 系列而不是最新的 3.x。原因不复杂3.x 强制要求 JDK 17 及以上很多学校机房电脑装的是 JDK 8而且网上能找到的教程、故障贴绝大多数是基于 2.x 的。2.7.18 是 3.x 之前最稳定的版本支持 JDK 8也能兼容新版 JDK搭配 MyBatis-Plus 3.5.x 和 MySQL 5.7/8.0 的社区资料最丰富遇到问题几乎都能搜到现成答案。注意SpringBoot 3.x 的javax.servlet改成了jakarta.servlet如果你沿用的旧博客代码里面有javax开头的导入要么把代码中这些依赖全部换掉要么直接退回到 2.7.x省得花一下午纠结一个ClassNotFoundException。2.4 配套技术栈清单这个项目我最终采用的完整技术栈如下可以直接当你的基线参考层次技术用途说明后端框架SpringBoot 2.7.18Web 服务、依赖注入、事务管理ORM 框架MyBatis-Plus 3.5.3简化单表 CRUD提供分页插件前端渲染Thymeleaf 3.0服务端模板渲染前端 UIBootstrap 5 jQuery响应式布局、弹窗、表格样式数据库MySQL 5.7 / 8.0数据持久化存储权限方案拦截器 Session登录校验、管理员角色拦截构建工具Maven依赖管理、项目打包开发工具IntelliJ IDEA VSCode后端与前端代码编辑3. 系统功能拆解从用户需求到模块落地3.1 三类角色与功能边界系统的用户角色可以分三类未登录游客、普通用户同时扮演买卖双方、系统管理员。注意我刻意没有把“卖家和买家”拆成两套独立用户体系因为在真实的校园闲置交易里一个人往往是既买又卖的拆成两套反而违背业务直觉。功能边界如下游客/普通用户公共部分浏览首页推荐商品、按分类查看商品列表关键词搜索商品标题、描述查看商品详情与卖家信息普通用户私有部分注册、登录、修改个人信息、更换头像发布闲置商品标题、描述、价格、图片、分类、交易地点编辑商品、下架商品、删除商品下单购买他人商品、取消订单、确认收货、申请退款收藏商品、查看收藏列表站内消息留言、接收交易提醒管理员部分登录后台独立拦截器校验管理员身份商品审核通过 / 驳回驳回时填写原因用户可见商品管理强制下架、删除违规内容分类管理新增分类、停用分类用户管理禁用 / 解禁账号举报处理查看举报内容对违规商品和用户进行处理简易数据面板商品总数、用户总数、订单总数、各分类商品占比3.2 核心业务流程设计业务设计上最花时间的地方是交易状态的边界。我梳理了三条主流程每条都对应一组明确的状态转移发布商品流程用户填写表单 → 后端校验字段合法性 → 图片上传 → 商品初始状态待审核→ 管理员审核通过后状态变上架中如审核驳回则状态为已驳回用户可编辑后重新提交。购买交易流程买家点击“立即购买” → 系统校验商品状态必须为上架中→ 创建订单状态待确认校园交易建议保留买家确认环节→ 买家确认并“付款”这里做的是模拟支付不是真实接入支付宝/微信论文里写清楚即可→ 订单状态变待交付→ 卖家确认已交付状态变已完成如果买家中途申请退款进入退款中卖家同意后订单取消、商品重新上架。举报审核流程用户对违规商品发起举报 → 生成举报记录关联商品、被举报人、举报理由→ 管理员处理举报 → 若属实则商品强制下架、用户扣信用分或禁用若不属实则标记为“无效举报”关闭处理。3.3 为什么要有“审核”和“举报”机制有同学可能会觉得校园闲置交易大家都很熟干嘛要搞审核和举报我在设计时是这样考虑的一个交易平台如果没有最基础的治理能力一旦出现违禁品类、虚假商品或纠纷整个系统就是失控的。审核机制既是对平台的保护也是论文里一个立得住脚的“创新点”——它体现了从业务到技术上的完整考虑。从技术实现上看审核和举报也很容易做商品表加一个status字段举报表加一个handle_status字段都是简单的状态查询和更新。但它们带来的页面和内聚复杂度是实打实的展示出来会立刻让评委觉得“这个系统的功能不是凑出来的”。4. 数据库设计订单状态与交易流程的核心建模4.1 核心数据表结构数据库设计是整个系统的地基复杂度集中在订单关系上。下面列出项目里最核心的几张表字段按实际实现做了精简展示user 用户表字段类型说明idbigint主键自增usernamevarchar登录用户名唯一passwordvarchar加密后的密码nicknamevarchar昵称avatarvarchar头像地址phonevarchar手机号school_idvarchar学号/工号非必填roletinyint0 普通用户1 管理员statustinyint0 正常1 禁用create_timedatetime注册时间product 商品表字段类型说明idbigint主键user_idbigint卖家 IDcategory_idbigint分类 IDtitlevarchar商品标题descriptiontext商品描述pricedecimal价格保留两位小数cover_imagevarchar封面图imagesvarchar其它图片JSON 数组或逗号分隔statustinyint0 待审核1 上架中2 已售出3 已下架4 已驳回view_countint浏览量create_timedatetime发布时间orders 订单表字段类型说明idbigint主键order_novarchar订单编号防止展示明文 IDproduct_idbigint商品 IDseller_idbigint卖家 ID冗余存储避免联表buyer_idbigint买家 IDpricedecimal成交价格拍下时快照statustinyint0 待确认1 待交付2 已完成3 已取消4 退款中pay_typevarchar支付方式模拟支付create_timedatetime下单时间update_timedatetime状态更新时间另外还有category商品分类、favorite收藏、message站内消息、report举报、comment评价几张表相对简单不再展开。4.2 订单状态机的设计与冗余存储的用意订单状态变化是这个系统最值得在答辩时展开讲的点。它本质上是一个状态机待确认 --买家确认付款-- 待交付 --卖家确认交付-- 已完成 | | | |--买家申请退款-- 退款中 --卖家同意-- 已取消 v v 已取消买家下单后反悔 已取消状态值本身只是tinyint关键是流转规则的约束。我在OrderServiceImpl里写了一个私有方法checkStatusChange每次变更前都会判断当前状态是否允许跳到目标状态不是直接UPDATE。比如已完成状态就不能再允许买家申请退款退款中就不能直接跳到待交付。这种“状态守卫”逻辑比单纯依赖前端按钮显隐要严谨得多也是论文中可以写清楚的一个实现细节。还有一点值得强调的是订单表里冗余存储了seller_id和buyer_id而不是从商品表里临时查卖家。刚开始我图省事只存了product_id后续查询订单列表时发现每一条都要去 JOIN 两张表代码复杂不少而且一旦商品被删除订单里的卖家信息就彻底丢了。冗余存储虽然违背了“数据库范式”的直觉但在这个业务场景下它保证了订单的历史快照属性——之后怎么改商品、怎么删商品订单记录仍然完整。4.3 关键词检索与索引设计检索这块我用了 MyBatis-Plus 的LambdaQueryWrapper先按关键字做标题和描述字段的模糊匹配再叠加分类、价格区间筛选最后按发布时间或者价格排序。一个容易忽略的点是当商品数量增长后LIKE %keyword%查询无法走常规索引。毕设阶段数据量小这个问题不明显但我在表设计时还是留了view_count和create_time的普通索引。答辩如果有老师问“数据量大了怎么优化”你就回答一是对热词查询做全文索引二是对列表查询做分页缓存三是将来可引入 Elasticsearch 做检索中间件三步都有现成方案。5. 关键功能实现发布、检索、下单与订单流转5.1 工程结构划分后端工程我采用了标准的包结构分层的目的一是为了代码可读性二是论文的“系统实现”章节可以直接按层描述写作省力。src/main/java/com/example/campusmarket/ ├── controller/ # 控制层用户、商品、订单、后台接口 ├── service/ # 业务层接口 impl 实现 ├── mapper/ # MyBatis-Plus 数据访问层 ├── entity/ # 实体类对应数据库表 ├── vo/ # 视图对象封装页面展示数据 ├── dto/ # 数据传输对象接收前端表单 ├── config/ # 配置类拦截器、文件上传、跨域 ├── interceptor/ # 登录拦截器、管理员拦截器 ├── common/ # 统一返回结果、异常处理、状态枚举 └── utils/ # 文件存储、验证码等工具5.2 application.yml 的关键配置这里直接给出我排过坑之后相对稳定的配置片段重点是文件上传大小和数据库连接server: port: 8080 spring: datasource: url: jdbc:mysql://localhost:3306/campus_market?useUnicodetruecharacterEncodingutf8useSSLfalseserverTimezoneAsia/Shanghai username: root password: 123456 driver-class-name: com.mysql.cj.jdbc.Driver servlet: multipart: max-file-size: 5MB max-request-size: 20MB thymeleaf: cache: false 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几个值得留意的点serverTimezoneAsia/Shanghai必须加否则连接 MySQL 8.0 会报时区错误map-underscore-to-camel-case负责把数据库的create_time自动映射成 Java 的createTime省去手动写 ResultMap 的麻烦逻辑删除配置之后调用 MyBatis-Plus 自带的deleteById不会真正删数据而是更新deleted字段这在答辩时作为“数据安全策略”讲是一个亮点。5.3 图片上传的实现与安全约束商品图片是本项目唯一的文件上传入口。我用的方案是本地磁盘存储配置一个全局上传目录通过ResourceHandler将/upload/**映射到磁盘路径前端直接通过/upload/xxx.jpg访问图片。关键点有两个。第一个是文件类型校验不能只信任前端的acceptimage/*后端必须根据文件的 Content-Type 或文件头魔数判断否则别人可以传一个 JSP 或 PHP 文件到你的上传目录配合路径拼接直接把你服务器打穿。我只允许image/jpeg、image/png、image/gif、image/webp四种类型并重命名文件为 UUID 加后缀杜绝了用户控制文件名导致的目录越权问题。第二个是容量限制。SpringBoot 默认只允许上传 1MB 的文件我上面配置里已经改到 5MB。同时我在 Service 层还做了一次图片压缩超过 800KB 的 JPEG 用 Java 自带的ImageIO重新缩放既保证商品图清晰度又不浪费服务器磁盘。这段逻辑代码量不大但在“系统实现”章节里是一个独立的子功能建议保留。5.4 分页检索MyBatis-Plus 条件构造器示例商品列表页是平台上最常被访问的页面我用 MyBatis-Plus 的分页插件一键搞定。核心代码大约这样public PageProductVO queryProductPage(int pageNum, int pageSize, ProductQueryDTO dto) { PageProduct page new Page(pageNum, pageSize); LambdaQueryWrapperProduct wrapper new LambdaQueryWrapper(); wrapper.eq(Product::getStatus, 1); // 只查上架中 if (StringUtils.hasText(dto.getKeyword())) { wrapper.and(w - w.like(Product::getTitle, dto.getKeyword()) .or().like(Product::getDescription, dto.getKeyword())); } if (dto.getCategoryId() ! null) { wrapper.eq(Product::getCategoryId, dto.getCategoryId()); } if (dto.getMinPrice() ! null) { wrapper.ge(Product::getPrice, dto.getMinPrice()); } if (dto.getMaxPrice() ! null) { wrapper.le(Product::getPrice, dto.getMaxPrice()); } wrapper.orderByDesc(Product::getCreateTime); PageProduct result productMapper.selectPage(page, wrapper); // 将 entity 转成 vo并附带卖家昵称、分类名称 return convertToVO(result); }这段代码我推荐在论文里原样保留。它的优点一是简洁二是完整展示了“条件构造器 分页插件”这两个 MyBatis-Plus 最常用的能力评委看到会认为你是真的掌握了 ORM 的核心用法而不是只会selectList(全部数据)然后自己在内存里subList。5.5 下单防重整个系统最值钱的一段代码下单是业务上最敏感的操作。如果两个买家同时点击“立即购买”系统必须保证只有一个能成功。最开始我写的是先查商品状态再插入订单最后更新商品状态。这存在一个典型的竞态条件两个请求都通过了第一步查询都认为商品上架中然后都插入订单最终导致同一件商品被卖两次。解决方式有两个思路。一个是给商品状态字段加乐观锁在更新时比对旧状态Transactional(rollbackFor Exception.class) public Long createOrder(OrderCreateDTO dto, Long buyerId) { // 1. 查询商品 Product product productMapper.selectById(dto.getProductId()); if (product null || product.getStatus() ! PRODUCT_STATUS_ON_SALE) { throw new BizException(商品不存在或已下架); } // 2. 原子更新商品状态上架中 - 已售出锁定 int updated productMapper.updateStatusByOptimisticLock( product.getId(), PRODUCT_STATUS_ON_SALE, PRODUCT_STATUS_SOLD ); if (updated 0) { throw new BizException(手慢了商品已被其他人买走); } // 3. 创建订单 Order order new Order(); order.setOrderNo(generateOrderNo()); order.setProductId(product.getId()); order.setSellerId(product.getUserId()); order.setBuyerId(buyerId); order.setPrice(product.getPrice()); order.setStatus(ORDER_STATUS_PENDING_CONFIRM); orderMapper.insert(order); return order.getId(); }对应的updateStatusByOptimisticLock在 Mapper 里是这样一条 SQLUPDATE product SET status #{newStatus} WHERE id #{id} AND status #{oldStatus}关键就在于WHERE status #{oldStatus}。MySQL 的UPDATE语句具备原子性两条并发请求同时执行这条 SQL 时只有一条会影响一行记录另一次更新影响行数为 0从而被拦截。这个方法代码量不大但它正确解决了“超卖”问题。建议你在答辩时把这段代码连同执行过程单独讲一遍这是整个项目里技术含量最高的一处。需要注意的是Transactional必须放在 Service 方法上并且第三步“插入订单”和第二步“更新商品”必须在同一个事务内。如果第二步成功、第三步抛异常事务回滚会让商品状态也回滚掉不会出现“商品已卖出但订单没生成”的脏数据。6. 前端页面与交互让非技术用户也能轻松上手6.1 前端方案取舍Thymeleaf Bootstrap 完赛率高前端部分我调研过两条路线最终选了服务端渲染方案。经验是如果你的目标是顺利毕业且把主要精力放在后端业务上Thymeleaf 绝对够用如果你想挑战一下自己、且前端基础不错可以选 Vue 3 Element Plus 做前后端分离。但后者意味着你得同时解决跨域、Token 无状态鉴权、路由守卫、Axios 封装、打包部署等一系列问题每一项都是时间黑洞。所以我的建议很直接毕设求稳别跟自己的时间过不去。Thymeleaf 配合 Bootstrap 的写法很简单页面里直接使用th:each遍历列表th:text渲染字段再通过th:if控制按钮显示条件。比如商品详情的操作按钮button th:if${product.userId session.loginUser.id} th:onclick|editProduct(${product.id})| 编辑商品 /button button th:if${product.userId ! session.loginUser.id product.status 1} th:onclick|buyProduct(${product.id})| 立即购买 /button自己卖的东西不显示购买按钮别人上架的商品才有“立即购买”这一行判断就替代了前端一大串按钮显隐逻辑非常直观。6.2 页面结构与核心交互清单整个前端一共做了这些页面每个页面都对应一个 Controller 路由页面对应路由核心内容首页/最新商品推荐、分类快捷入口、搜索框商品列表/product/list分类筛选、价格区间、分页商品详情/product/{id}轮播图、价格、卖家信息、购买/收藏按钮登录/注册/login/register表单校验、验证码个人中心/user/center我的信息、我的商品、订单记录发布商品/product/publish多图片上传带预览、分类下拉、价格输入后台管理/admin/dashboard数据统计卡片、审核列表、用户列表6.3 商品发布表单里的预览细节商品发布页是最容易让用户放弃的页面所以我花了不少功夫在这上面。图片上传部分我用了input[typefile]加 FileReader 实现本地预览用户选完图片立刻在页面上看到效果确认无误再提交表单。整套逻辑只有 20 行左右的 jQuery 代码但是对于体验改善非常明显——至少省去了“传完图片不知道自己传对没有”的焦虑。另一个细节是价格输入框我限制只能输入数字且最多保留两位小数后端再用DecimalMin(0.01)做二次校验前端防呆、后端防守两边都做了。6.4 后台数据面板的展示效果管理员后台的 Dashboard 我用了最简单的计数卡片加柱状图。计数卡片从汇总表里直接COUNT出来柱状图则是按分类统计商品数量用 ECharts 引入后只需要把JSON格式的数据塞给它渲染。虽然代码不复杂但屏幕上出图的效果立竿见影答辩时可以作为“系统统计分析功能”的直观证据。7. 毕设踩坑实录鉴权、文件上传与事务一致性7.1 拦截器把静态资源拦了页面有 HTML 没样式这是我开发时第一个大坑。登录拦截器验证 Session 后直接把不带登录信息的请求“拦截”并重定向到登录页结果浏览器加载css、js、images这些静态资源的请求也被拦了页面打开后全是裸 HTML惨不忍睹。排查方式在浏览器开发者工具里看 Network发现大量css/bootstrap.min.css返回 302 重定向到登录页立刻意识到是拦截器没有放行静态资源路径。解决方法是写一个专门的StaticResourceInterceptor继承WebMvcConfigurer在注册拦截器时显式排除常见静态目录registry.addInterceptor(loginInterceptor) .addPathPatterns(/**) .excludePathPatterns( /, /login, /register, /product/**, /upload/**, /css/**, /js/**, /images/**, /lib/**, /error );只要用了 SpringBoot 做 Web 项目这几乎是一个必踩的坑。建议大家在配置完拦截器后第一时间先测试静态样式是否正常不要等到页面堆出来之后再统一排查。7.2 文件上传超过默认限制SpringBoot 默认最大单文件 1MB、单次请求 10MB。我上传商品图时连续报MaxUploadSizeExceededException第一次看到这个报错还以为是代码写错了纠结了很久。后来查配置文档才明白是内置限制。解决方式很简单就是开头配置里的那段multipart配置。出于安全考虑我没有把限制调得太大单文件 5MB 对普通手机拍的照片已经足够。7.3 并发下单从“超卖”到“乐观锁”这个坑我在 5.5 小节已经详细展开过。这里想多讲一句排查过程——它是怎么被我“逼出来”的。起初我用 Postman 循环发送 10 个并发购买请求发现订单表里出现了两条相同product_id的订单商品状态也还是“上架中”数据明显错乱。你如果复现类似问题不要第一时间怀疑 MySQL 配置大多数情况是你先查再更新的事务边界没做好。学会用并发工具我用的 JMeter压自己的接口是排查这类问题的必经之路。7.4 数据库连接 URL 不带时区导致启动失败MySQL 8.x 默认使用的驱动com.mysql.cj.jdbc.Driver对时区非常敏感。第一次连接时直接抛The server time zone value йʱ is unrecognized。解决方案就是在 JDBC URL 后面拼上serverTimezoneAsia/Shanghai同时建议把useUnicodetruecharacterEncodingutf8一并加上从根源上杜绝乱码。这个问题在论文的“系统测试”阶段其实不该再出现写在这里是给第一天搭建环境的人一份预防手册。7.5 MyBatis-Plus 字段映射失败驼峰与下划线断舍离数据库字段是create_timeJava 实体属性是createTime如果 MyBatis-Plus 没有开启驼峰映射查询结果中这个字段就会一直是null。我之前用 MyBatis 写 XML 时习惯手动指定resultMap切到 MyBatis-Plus 后一度忽略了全局配置导致列表页的时间列全是空的。解决方式就是map-underscore-to-camel-case: true这一行配置对整个项目里的所有实体类生效一劳永逸。8. 毕业论文写作与答辩准备的侧重点8.1 论文结构的参考目录毕设论文通常是模板化结构但如果你用的就是这套项目可以参考下面的章节安排绪论选题背景、国内外二手交易平台研究现状、研究意义与目标相关技术介绍SpringBoot、MyBatis-Plus、Thymeleaf、Bootstrap、MySQL需求分析功能性需求、非功能性需求、用例图、业务流程时序图系统设计总体架构设计、功能模块划分、数据库设计E-R 图 表结构、接口设计系统实现按模块展示核心代码与页面截图系统测试测试环境、功能测试用例表、性能测试并发下单测试、测试结论总结与展望项目不足、未来可扩展方向重点放在第 3 章到第 6 章。文字量不必刻意堆砌但每个模块必须配图功能结构图、业务流程图、页面截图、核心代码片段。图表是老师评判你“工作量和态度”的直接依据。8.2 答辩高频问题与应对思路根据我和答辩组老师聊天的经验围绕这类选题的高频问题基本就这些高频问题推荐回应为什么选这个课题校园闲置交易有真实痛点和场景贴近校园生活技术栈覆盖全栈容易落地订单状态是怎么管理的用状态机思想每个状态变更前校验合法性防止非法的状态跳转如何防止商品被重复购买用乐观锁 事务原子性地更新商品状态更新影响行数为 0 就直接返回失败如果用户量变大了怎么办先做列表分页和缓存再考虑把检索模块换成 Elasticsearch数据库做读写分离你的项目有什么创新点审核加举报的双重治理机制、订单状态守卫、冗余字段做历史快照这三个点都能讲登录功能的安全性如何保证Session 超时控制、密码加盐哈希存储、静态资源放行策略、文件上传类型校验最后一组问题里密码加密推荐直接用 Spring 自带的BCryptPasswordEncoder不要再写什么 MD5 加盐答辩时提到 BCrypt 这种现代密码哈希算法专业感会不一样。8.3 建议的节奏安排如果你现在还没开工一条比较稳妥的时间线是前两周完成环境搭建、数据库脚本和用户模块中间四周完成商品模块和文件上传再接三周完成订单模块和状态流转最后两周做管理后台、测试和修 bug剩下一个月集中写论文和做 PPT。实测下来这套节奏能覆盖 80% 以上的意外情况包括论文查重修改和答辩演示宕机的容错时间。最后再分享一个小建议答辩演示前一定要准备一个“干净数据”的账号提前注册一个测试买家、一个测试卖家和一个管理员账号商品、订单各准备两条不同状态的记录并预演一遍完整的交易流程。这些细节直接决定演示的流畅度——真到了现场再注册账号、再传图片网络和设备一卡整个答辩节奏就乱了。做毕设到最后拼的其实就是“每一个环节都想到了没”。